Skip to content

Commit 9f76114

Browse files
authored
Merge pull request #3 from adielBm/hotfix-double-parsing
fix-lat-lon-parsing
2 parents 876ada3 + 4aafed1 commit 9f76114

5 files changed

+164
-109
lines changed

Flow.Launcher.Plugin.Weather.generated.sln

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@ Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 17
44
VisualStudioVersion = 17.5.002.0
55
MinimumVisualStudioVersion = 10.0.40219.1
6-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Weather", "Flow.Launcher.Plugin.Weather.csproj", "{08477645-7726-437B-844E-18AC8A659738}"
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Weather", "Flow.Launcher.Plugin.Weather.csproj", "{25007132-C8F6-4F48-B535-850D7A380688}"
77
EndProject
88
Global
99
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1010
Debug|Any CPU = Debug|Any CPU
1111
Release|Any CPU = Release|Any CPU
1212
EndGlobalSection
1313
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14-
{08477645-7726-437B-844E-18AC8A659738}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15-
{08477645-7726-437B-844E-18AC8A659738}.Debug|Any CPU.Build.0 = Debug|Any CPU
16-
{08477645-7726-437B-844E-18AC8A659738}.Release|Any CPU.ActiveCfg = Release|Any CPU
17-
{08477645-7726-437B-844E-18AC8A659738}.Release|Any CPU.Build.0 = Release|Any CPU
14+
{25007132-C8F6-4F48-B535-850D7A380688}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{25007132-C8F6-4F48-B535-850D7A380688}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{25007132-C8F6-4F48-B535-850D7A380688}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{25007132-C8F6-4F48-B535-850D7A380688}.Release|Any CPU.Build.0 = Release|Any CPU
1818
EndGlobalSection
1919
GlobalSection(SolutionProperties) = preSolution
2020
HideSolutionNode = FALSE

Main.cs

Lines changed: 49 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,28 @@
77
using System.Windows.Controls;
88
using System.Net.Http;
99
using System.Text.Json;
10-
1110
using Flow.Launcher.Plugin;
1211
using Flow.Launcher.Plugin.SharedCommands;
1312

1413
namespace Flow.Launcher.Plugin.Weather
1514
{
15+
/// <summary>
16+
/// Represents the main class of the Weather plugin.
17+
/// </summary>
1618
public class Main : IAsyncPlugin, ISettingProvider, IContextMenu
1719
{
18-
private string ClassName => GetType().Name;
19-
private PluginInitContext _context;
20-
private Settings _settings;
21-
private bool UseFahrenheit => _settings.useFahrenheit;
22-
23-
// OLD client
24-
// private readonly OpenMeteoClient _client;
25-
26-
// NEW client
27-
private readonly OpenMeteoApiClient _weather_client;
28-
29-
private readonly CityLookupService cityService;
30-
31-
public Main()
20+
private PluginInitContext context;
21+
private Settings settings;
22+
private bool UseFahrenheit => settings.useFahrenheit;
23+
private OpenMeteoApiClient weatherClient;
24+
private CityLookupService cityService;
25+
public Task InitAsync(PluginInitContext context)
3226
{
33-
// OLD client
34-
// _client = new OpenMeteoClient();
35-
// NEW client
36-
_weather_client = new OpenMeteoApiClient();
37-
cityService = new CityLookupService();
38-
}
39-
40-
// Initialise query url
41-
public async Task InitAsync(PluginInitContext context)
42-
{
43-
_context = context;
44-
_settings = _context.API.LoadSettingJsonStorage<Settings>();
45-
46-
27+
this.context = context;
28+
settings = context.API.LoadSettingJsonStorage<Settings>();
29+
weatherClient = new OpenMeteoApiClient(context);
30+
cityService = new CityLookupService(context);
31+
return Task.CompletedTask;
4732
}
4833

4934
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
@@ -52,16 +37,15 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
5237

5338
if (string.IsNullOrWhiteSpace(searchTerm))
5439
{
55-
if (!string.IsNullOrWhiteSpace(_settings.defaultLocation))
40+
if (!string.IsNullOrWhiteSpace(settings.defaultLocation))
5641
{
57-
searchTerm = _settings.defaultLocation;
42+
searchTerm = settings.defaultLocation;
5843
}
5944
else
6045
{
6146
return new List<Result>
6247
{
63-
new Result
64-
{
48+
new() {
6549
Title = "Please enter a city name",
6650
IcoPath = "Images\\weather-icon.png"
6751
}
@@ -71,21 +55,13 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
7155
try
7256
{
7357
token.ThrowIfCancellationRequested();
74-
75-
// Get city data
76-
// OLD
77-
// GeocodingOptions geocodingData = new GeocodingOptions(searchTerm);
78-
// GeocodingApiResponse geocodingResult = await _client.GetLocationDataAsync(geocodingData);
79-
80-
// NEW
8158
var cityDetails = await cityService.GetCityDetailsAsync(searchTerm);
8259

8360
if (cityDetails == null || cityDetails?.Latitude == null || cityDetails?.Longitude == null)
8461
{
8562
return new List<Result>
8663
{
87-
new Result
88-
{
64+
new() {
8965
Title = "City not found",
9066
SubTitle = "Please check the city name and try again",
9167
IcoPath = "Images\\weather-icon.png",
@@ -94,19 +70,17 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
9470
};
9571
}
9672

97-
9873
token.ThrowIfCancellationRequested();
9974

100-
WeatherForecast weatherData = await _weather_client.GetForecastAsync(cityDetails.Latitude, cityDetails.Longitude);
75+
WeatherForecast weatherData = await weatherClient.GetForecastAsync(cityDetails.Latitude, cityDetails.Longitude);
10176

10277
token.ThrowIfCancellationRequested();
10378

10479
if (weatherData == null)
10580
{
10681
return new List<Result>
10782
{
108-
new Result
109-
{
83+
new() {
11084
Title = $"Weather data not found for {cityDetails.Name}",
11185
SubTitle = $"Please try again later",
11286
IcoPath = "Images\\weather-icon.png",
@@ -128,9 +102,9 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
128102

129103
// Set glyph (if enabled)
130104
GlyphInfo glyph = null;
131-
if (_settings.useGlyphs)
105+
if (settings.useGlyphs)
132106
{
133-
string fontPath = Path.Combine(_context.CurrentPluginMetadata.PluginDirectory, "Resources", "easy.ttf");
107+
string fontPath = Path.Combine(context.CurrentPluginMetadata.PluginDirectory, "Resources", "easy.ttf");
134108
PrivateFontCollection privateFonts = new PrivateFontCollection();
135109
privateFonts.AddFontFile(fontPath);
136110
glyph = new GlyphInfo(
@@ -150,11 +124,9 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
150124
// Result
151125
return new List<Result>
152126
{
153-
new Result
154-
{
127+
new() {
155128
Title = $"{temp} {(UseFahrenheit ? "°F" : "°C")}",
156129
SubTitle = subTitle,
157-
// SubTitle = $"{_client.WeathercodeToString((int)(weatherData?.Current?.Weathercode))} ({weatherData?.Current?.Weathercode}) ({dayOrNight}) | {cityData.Name}, {cityData.Country}",
158130
IcoPath = $"Images\\{GetWeatherIcon((int)(weatherData?.Current?.WeatherCode), isNight)}.png",
159131
Glyph = glyph,
160132
}
@@ -177,27 +149,22 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
177149
}
178150
};
179151
}
180-
return new List<Result>();
181152
}
182153

183154
public void Dispose()
184155
{
185156
// _httpClient.Dispose();
186157
}
187-
public Control CreateSettingPanel() => new SettingsControl(_settings);
158+
public Control CreateSettingPanel() => new SettingsControl(settings);
188159

189160
public List<Result> LoadContextMenus(Result selectedResult)
190161
{
191-
var results = new List<Result>
192-
{
193-
194-
};
195-
162+
var results = new List<Result> { };
196163
return results;
197164
}
198165

199166
// celsius to fahrenheit
200-
private double CelsiusToFahrenheit(double celsius)
167+
private static double CelsiusToFahrenheit(double celsius)
201168
{
202169
return celsius * 9 / 5 + 32;
203170
}
@@ -254,7 +221,7 @@ public string GetWeatherIcon(int wmoCode, bool isNight = false)
254221
}
255222

256223

257-
private string GetDayIcon(int wmoCode)
224+
private static string GetDayIcon(int wmoCode)
258225
{
259226
return wmoCode switch
260227
{
@@ -311,24 +278,38 @@ public static string GetWeatherIconUnicode(int wmoCode, bool isNight = false)
311278

312279
}
313280

314-
315281
public class CityDetails
316282
{
317283
public string DisplayName { get; set; }
318284
public string Name { get; set; }
319-
public double Latitude { get; set; }
320-
public double Longitude { get; set; }
285+
public string Latitude { get; set; }
286+
public string Longitude { get; set; }
321287
}
322288

289+
290+
/// <summary>
291+
/// Service for looking up city details using the OpenStreetMap Nominatim API.
292+
/// </summary>
323293
public class CityLookupService
324294
{
325-
private static readonly HttpClient client = new HttpClient();
326-
327-
public CityLookupService()
295+
private static readonly HttpClient client = new();
296+
private readonly PluginInitContext context;
297+
298+
/// <summary>
299+
/// Initializes a new instance of the <see cref="CityLookupService"/> class.
300+
/// </summary>
301+
/// <param name="context">The plugin initialization context.</param>
302+
public CityLookupService(PluginInitContext context)
328303
{
304+
this.context = context;
329305
client.DefaultRequestHeaders.Add("User-Agent", "Flow.Launcher.Plugin.Weather");
330306
}
331307

308+
/// <summary>
309+
/// Gets the details of a city asynchronously.
310+
/// </summary>
311+
/// <param name="cityName">The name of the city to look up.</param>
312+
/// <returns>A task that represents the asynchronous operation. The task result contains the city details.</returns>
332313
public async Task<CityDetails> GetCityDetailsAsync(string cityName)
333314
{
334315
string url = $"https://nominatim.openstreetmap.org/search?q={Uri.EscapeDataString(cityName)}&format=json&limit=1&accept-language=en";
@@ -352,8 +333,8 @@ public async Task<CityDetails> GetCityDetailsAsync(string cityName)
352333
{
353334
DisplayName = cityInfo.GetProperty("display_name").GetString(),
354335
Name = cityInfo.GetProperty("name").GetString(),
355-
Latitude = double.Parse(cityInfo.GetProperty("lat").GetString()),
356-
Longitude = double.Parse(cityInfo.GetProperty("lon").GetString())
336+
Latitude = cityInfo.GetProperty("lat").GetString(),
337+
Longitude = cityInfo.GetProperty("lon").GetString()
357338
};
358339
}
359340

OpenMeteoApiClient.cs

Lines changed: 52 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,60 +8,77 @@ namespace Flow.Launcher.Plugin.Weather
88
{
99
public class OpenMeteoApiClient
1010
{
11-
private static readonly HttpClient client = new HttpClient();
11+
private static readonly HttpClient client = new();
12+
private const string BaseUrl = "https://api.open-meteo.com/v1/forecast";
13+
private readonly PluginInitContext context;
1214

13-
public OpenMeteoApiClient()
15+
public OpenMeteoApiClient(PluginInitContext context)
1416
{
17+
this.context = context;
1518
client.DefaultRequestHeaders.Add("User-Agent", "Flow.Launcher.Plugin.Weather");
1619
}
1720

18-
public async Task<WeatherForecast> GetForecastAsync(double latitude, double longitude)
21+
public async Task<WeatherForecast> GetForecastAsync(string latitude, string longitude)
1922
{
2023
string url = $"{BaseUrl}?latitude={latitude}&longitude={longitude}&current=temperature_2m,apparent_temperature,is_day,weather_code&daily=apparent_temperature_max,apparent_temperature_min&forecast_days=1";
21-
var response = await client.GetAsync(url);
2224

23-
response.EnsureSuccessStatusCode(); // Throws if status code is not 2xx
25+
try
26+
{
27+
var response = await client.GetAsync(url);
2428

25-
var responseString = await response.Content.ReadAsStringAsync();
29+
var responseString = await response.Content.ReadAsStringAsync();
2630

27-
var json = JsonDocument.Parse(responseString);
28-
var root = json.RootElement;
31+
var json = JsonDocument.Parse(responseString);
32+
var root = json.RootElement;
2933

30-
// Check if there's any data
31-
if (root.TryGetProperty("current", out JsonElement currentWeather) &&
32-
root.TryGetProperty("daily", out JsonElement dailyWeather))
33-
{
34-
return new WeatherForecast
34+
// Check if there's any data
35+
if (root.TryGetProperty("current", out JsonElement currentWeather) &&
36+
root.TryGetProperty("daily", out JsonElement dailyWeather))
3537
{
36-
Latitude = latitude,
37-
Longitude = longitude,
38-
Current = new CurrentWeather
39-
{
40-
Time = currentWeather.GetProperty("time").GetDateTime(),
41-
Temperature2m = Math.Round(currentWeather.GetProperty("temperature_2m").GetDouble()),
42-
ApparentTemperature = Math.Round(currentWeather.GetProperty("apparent_temperature").GetDouble()),
43-
IsDay = currentWeather.GetProperty("is_day").GetInt32(),
44-
WeatherCode = currentWeather.GetProperty("weather_code").GetInt32()
45-
},
46-
Daily = new DailyWeather
38+
return new WeatherForecast
4739
{
48-
Time = dailyWeather.GetProperty("time").EnumerateArray().Select(d => d.GetDateTime()).ToArray(),
49-
ApparentTemperatureMax = dailyWeather.GetProperty("apparent_temperature_max").EnumerateArray().Select(d => Math.Round(d.GetDouble())).ToArray(),
50-
ApparentTemperatureMin = dailyWeather.GetProperty("apparent_temperature_min").EnumerateArray().Select(d => Math.Round(d.GetDouble())).ToArray()
51-
}
52-
};
53-
}
40+
Latitude = latitude,
41+
Longitude = longitude,
42+
Current = new CurrentWeather
43+
{
44+
Time = currentWeather.GetProperty("time").GetDateTime(),
45+
Temperature2m = Math.Round(currentWeather.GetProperty("temperature_2m").GetDouble()),
46+
ApparentTemperature = Math.Round(currentWeather.GetProperty("apparent_temperature").GetDouble()),
47+
IsDay = currentWeather.GetProperty("is_day").GetInt32(),
48+
WeatherCode = currentWeather.GetProperty("weather_code").GetInt32()
49+
},
50+
Daily = new DailyWeather
51+
{
52+
Time = dailyWeather.GetProperty("time").EnumerateArray().Select(d => d.GetDateTime()).ToArray(),
53+
ApparentTemperatureMax = dailyWeather.GetProperty("apparent_temperature_max").EnumerateArray().Select(d => Math.Round(d.GetDouble())).ToArray(),
54+
ApparentTemperatureMin = dailyWeather.GetProperty("apparent_temperature_min").EnumerateArray().Select(d => Math.Round(d.GetDouble())).ToArray()
55+
}
56+
};
57+
}
5458

55-
return null; // No weather data found
59+
context.API.LogWarn(nameof(OpenMeteoApiClient), "No weather data found in response.");
60+
return null; // No weather data found
61+
}
62+
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.BadRequest)
63+
{
64+
context.API.LogWarn(nameof(OpenMeteoApiClient), $"Bad Request: {ex.Message}");
65+
return null; // Handle specific error case
66+
}
67+
catch (Exception ex)
68+
{
69+
context.API.LogException(nameof(OpenMeteoApiClient), "An error occurred while fetching the weather forecast.", ex);
70+
return null; // Handle general exceptions
71+
}
5672
}
5773

58-
private const string BaseUrl = "https://api.open-meteo.com/v1/forecast";
74+
5975
}
6076

77+
6178
public class WeatherForecast
6279
{
63-
public double Latitude { get; set; }
64-
public double Longitude { get; set; }
80+
public string Latitude { get; set; }
81+
public string Longitude { get; set; }
6582
public CurrentWeather Current { get; set; }
6683
public DailyWeather Daily { get; set; }
6784
}
@@ -81,4 +98,4 @@ public class DailyWeather
8198
public double[] ApparentTemperatureMax { get; set; }
8299
public double[] ApparentTemperatureMin { get; set; }
83100
}
84-
}
101+
}

plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"Name": "Weather",
55
"Description": "Get weather information for a location",
66
"Author": "adielBm",
7-
"Version": "1.1.0",
7+
"Version": "1.2.0",
88
"Language": "csharp",
99
"Website": "https://github.com/adielBm/Flow.Launcher.Plugin.Weather",
1010
"IcoPath": "Images\\weather-icon.png",

0 commit comments

Comments
 (0)