Skip to content

Commit

Permalink
Merge pull request #3 from adielBm/hotfix-double-parsing
Browse files Browse the repository at this point in the history
fix-lat-lon-parsing
  • Loading branch information
adielBm authored Sep 4, 2024
2 parents 876ada3 + 4aafed1 commit 9f76114
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 109 deletions.
10 changes: 5 additions & 5 deletions Flow.Launcher.Plugin.Weather.generated.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Weather", "Flow.Launcher.Plugin.Weather.csproj", "{08477645-7726-437B-844E-18AC8A659738}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Weather", "Flow.Launcher.Plugin.Weather.csproj", "{25007132-C8F6-4F48-B535-850D7A380688}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{08477645-7726-437B-844E-18AC8A659738}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{08477645-7726-437B-844E-18AC8A659738}.Debug|Any CPU.Build.0 = Debug|Any CPU
{08477645-7726-437B-844E-18AC8A659738}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08477645-7726-437B-844E-18AC8A659738}.Release|Any CPU.Build.0 = Release|Any CPU
{25007132-C8F6-4F48-B535-850D7A380688}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{25007132-C8F6-4F48-B535-850D7A380688}.Debug|Any CPU.Build.0 = Debug|Any CPU
{25007132-C8F6-4F48-B535-850D7A380688}.Release|Any CPU.ActiveCfg = Release|Any CPU
{25007132-C8F6-4F48-B535-850D7A380688}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
117 changes: 49 additions & 68 deletions Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,28 @@
using System.Windows.Controls;
using System.Net.Http;
using System.Text.Json;

using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;

namespace Flow.Launcher.Plugin.Weather
{
/// <summary>
/// Represents the main class of the Weather plugin.
/// </summary>
public class Main : IAsyncPlugin, ISettingProvider, IContextMenu
{
private string ClassName => GetType().Name;
private PluginInitContext _context;
private Settings _settings;
private bool UseFahrenheit => _settings.useFahrenheit;

// OLD client
// private readonly OpenMeteoClient _client;

// NEW client
private readonly OpenMeteoApiClient _weather_client;

private readonly CityLookupService cityService;

public Main()
private PluginInitContext context;
private Settings settings;
private bool UseFahrenheit => settings.useFahrenheit;
private OpenMeteoApiClient weatherClient;
private CityLookupService cityService;
public Task InitAsync(PluginInitContext context)

Check warning on line 25 in Main.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'Main.InitAsync(PluginInitContext)'
{
// OLD client
// _client = new OpenMeteoClient();
// NEW client
_weather_client = new OpenMeteoApiClient();
cityService = new CityLookupService();
}

// Initialise query url
public async Task InitAsync(PluginInitContext context)
{
_context = context;
_settings = _context.API.LoadSettingJsonStorage<Settings>();


this.context = context;
settings = context.API.LoadSettingJsonStorage<Settings>();
weatherClient = new OpenMeteoApiClient(context);
cityService = new CityLookupService(context);
return Task.CompletedTask;
}

public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)

Check warning on line 34 in Main.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'Main.QueryAsync(Query, CancellationToken)'
Expand All @@ -52,16 +37,15 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)

if (string.IsNullOrWhiteSpace(searchTerm))
{
if (!string.IsNullOrWhiteSpace(_settings.defaultLocation))
if (!string.IsNullOrWhiteSpace(settings.defaultLocation))
{
searchTerm = _settings.defaultLocation;
searchTerm = settings.defaultLocation;
}
else
{
return new List<Result>
{
new Result
{
new() {
Title = "Please enter a city name",
IcoPath = "Images\\weather-icon.png"
}
Expand All @@ -71,21 +55,13 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
try
{
token.ThrowIfCancellationRequested();

// Get city data
// OLD
// GeocodingOptions geocodingData = new GeocodingOptions(searchTerm);
// GeocodingApiResponse geocodingResult = await _client.GetLocationDataAsync(geocodingData);

// NEW
var cityDetails = await cityService.GetCityDetailsAsync(searchTerm);

if (cityDetails == null || cityDetails?.Latitude == null || cityDetails?.Longitude == null)
{
return new List<Result>
{
new Result
{
new() {
Title = "City not found",
SubTitle = "Please check the city name and try again",
IcoPath = "Images\\weather-icon.png",
Expand All @@ -94,19 +70,17 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
};
}


token.ThrowIfCancellationRequested();

WeatherForecast weatherData = await _weather_client.GetForecastAsync(cityDetails.Latitude, cityDetails.Longitude);
WeatherForecast weatherData = await weatherClient.GetForecastAsync(cityDetails.Latitude, cityDetails.Longitude);

token.ThrowIfCancellationRequested();

if (weatherData == null)
{
return new List<Result>
{
new Result
{
new() {
Title = $"Weather data not found for {cityDetails.Name}",
SubTitle = $"Please try again later",
IcoPath = "Images\\weather-icon.png",
Expand All @@ -128,9 +102,9 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)

// Set glyph (if enabled)
GlyphInfo glyph = null;
if (_settings.useGlyphs)
if (settings.useGlyphs)
{
string fontPath = Path.Combine(_context.CurrentPluginMetadata.PluginDirectory, "Resources", "easy.ttf");
string fontPath = Path.Combine(context.CurrentPluginMetadata.PluginDirectory, "Resources", "easy.ttf");
PrivateFontCollection privateFonts = new PrivateFontCollection();
privateFonts.AddFontFile(fontPath);
glyph = new GlyphInfo(
Expand All @@ -150,11 +124,9 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
// Result
return new List<Result>
{
new Result
{
new() {
Title = $"{temp} {(UseFahrenheit ? "°F" : "°C")}",
SubTitle = subTitle,
// SubTitle = $"{_client.WeathercodeToString((int)(weatherData?.Current?.Weathercode))} ({weatherData?.Current?.Weathercode}) ({dayOrNight}) | {cityData.Name}, {cityData.Country}",
IcoPath = $"Images\\{GetWeatherIcon((int)(weatherData?.Current?.WeatherCode), isNight)}.png",
Glyph = glyph,
}
Expand All @@ -177,27 +149,22 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
}
};
}
return new List<Result>();
}

public void Dispose()

Check warning on line 154 in Main.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'Main.Dispose()'
{
// _httpClient.Dispose();
}
public Control CreateSettingPanel() => new SettingsControl(_settings);
public Control CreateSettingPanel() => new SettingsControl(settings);

Check warning on line 158 in Main.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'Main.CreateSettingPanel()'

public List<Result> LoadContextMenus(Result selectedResult)

Check warning on line 160 in Main.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'Main.LoadContextMenus(Result)'
{
var results = new List<Result>
{

};

var results = new List<Result> { };
return results;
}

// celsius to fahrenheit
private double CelsiusToFahrenheit(double celsius)
private static double CelsiusToFahrenheit(double celsius)
{
return celsius * 9 / 5 + 32;
}
Expand Down Expand Up @@ -254,7 +221,7 @@ public string GetWeatherIcon(int wmoCode, bool isNight = false)
}


private string GetDayIcon(int wmoCode)
private static string GetDayIcon(int wmoCode)
{
return wmoCode switch
{
Expand Down Expand Up @@ -311,24 +278,38 @@ public static string GetWeatherIconUnicode(int wmoCode, bool isNight = false)

}


public class CityDetails

Check warning on line 281 in Main.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'CityDetails'
{
public string DisplayName { get; set; }

Check warning on line 283 in Main.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'CityDetails.DisplayName'
public string Name { get; set; }

Check warning on line 284 in Main.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'CityDetails.Name'
public double Latitude { get; set; }
public double Longitude { get; set; }
public string Latitude { get; set; }
public string Longitude { get; set; }
}


/// <summary>
/// Service for looking up city details using the OpenStreetMap Nominatim API.
/// </summary>
public class CityLookupService
{
private static readonly HttpClient client = new HttpClient();

public CityLookupService()
private static readonly HttpClient client = new();
private readonly PluginInitContext context;

/// <summary>
/// Initializes a new instance of the <see cref="CityLookupService"/> class.
/// </summary>
/// <param name="context">The plugin initialization context.</param>
public CityLookupService(PluginInitContext context)
{
this.context = context;
client.DefaultRequestHeaders.Add("User-Agent", "Flow.Launcher.Plugin.Weather");
}

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

Expand Down
87 changes: 52 additions & 35 deletions OpenMeteoApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,60 +8,77 @@ namespace Flow.Launcher.Plugin.Weather
{
public class OpenMeteoApiClient
{
private static readonly HttpClient client = new HttpClient();
private static readonly HttpClient client = new();
private const string BaseUrl = "https://api.open-meteo.com/v1/forecast";
private readonly PluginInitContext context;

public OpenMeteoApiClient()
public OpenMeteoApiClient(PluginInitContext context)
{
this.context = context;
client.DefaultRequestHeaders.Add("User-Agent", "Flow.Launcher.Plugin.Weather");
}

public async Task<WeatherForecast> GetForecastAsync(double latitude, double longitude)
public async Task<WeatherForecast> GetForecastAsync(string latitude, string longitude)
{
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";
var response = await client.GetAsync(url);

response.EnsureSuccessStatusCode(); // Throws if status code is not 2xx
try
{
var response = await client.GetAsync(url);

var responseString = await response.Content.ReadAsStringAsync();
var responseString = await response.Content.ReadAsStringAsync();

var json = JsonDocument.Parse(responseString);
var root = json.RootElement;
var json = JsonDocument.Parse(responseString);
var root = json.RootElement;

// Check if there's any data
if (root.TryGetProperty("current", out JsonElement currentWeather) &&
root.TryGetProperty("daily", out JsonElement dailyWeather))
{
return new WeatherForecast
// Check if there's any data
if (root.TryGetProperty("current", out JsonElement currentWeather) &&
root.TryGetProperty("daily", out JsonElement dailyWeather))
{
Latitude = latitude,
Longitude = longitude,
Current = new CurrentWeather
{
Time = currentWeather.GetProperty("time").GetDateTime(),
Temperature2m = Math.Round(currentWeather.GetProperty("temperature_2m").GetDouble()),
ApparentTemperature = Math.Round(currentWeather.GetProperty("apparent_temperature").GetDouble()),
IsDay = currentWeather.GetProperty("is_day").GetInt32(),
WeatherCode = currentWeather.GetProperty("weather_code").GetInt32()
},
Daily = new DailyWeather
return new WeatherForecast
{
Time = dailyWeather.GetProperty("time").EnumerateArray().Select(d => d.GetDateTime()).ToArray(),
ApparentTemperatureMax = dailyWeather.GetProperty("apparent_temperature_max").EnumerateArray().Select(d => Math.Round(d.GetDouble())).ToArray(),
ApparentTemperatureMin = dailyWeather.GetProperty("apparent_temperature_min").EnumerateArray().Select(d => Math.Round(d.GetDouble())).ToArray()
}
};
}
Latitude = latitude,
Longitude = longitude,
Current = new CurrentWeather
{
Time = currentWeather.GetProperty("time").GetDateTime(),
Temperature2m = Math.Round(currentWeather.GetProperty("temperature_2m").GetDouble()),
ApparentTemperature = Math.Round(currentWeather.GetProperty("apparent_temperature").GetDouble()),
IsDay = currentWeather.GetProperty("is_day").GetInt32(),
WeatherCode = currentWeather.GetProperty("weather_code").GetInt32()
},
Daily = new DailyWeather
{
Time = dailyWeather.GetProperty("time").EnumerateArray().Select(d => d.GetDateTime()).ToArray(),
ApparentTemperatureMax = dailyWeather.GetProperty("apparent_temperature_max").EnumerateArray().Select(d => Math.Round(d.GetDouble())).ToArray(),
ApparentTemperatureMin = dailyWeather.GetProperty("apparent_temperature_min").EnumerateArray().Select(d => Math.Round(d.GetDouble())).ToArray()
}
};
}

return null; // No weather data found
context.API.LogWarn(nameof(OpenMeteoApiClient), "No weather data found in response.");
return null; // No weather data found
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.BadRequest)
{
context.API.LogWarn(nameof(OpenMeteoApiClient), $"Bad Request: {ex.Message}");
return null; // Handle specific error case
}
catch (Exception ex)
{
context.API.LogException(nameof(OpenMeteoApiClient), "An error occurred while fetching the weather forecast.", ex);
return null; // Handle general exceptions
}
}

private const string BaseUrl = "https://api.open-meteo.com/v1/forecast";

}


public class WeatherForecast
{
public double Latitude { get; set; }
public double Longitude { get; set; }
public string Latitude { get; set; }
public string Longitude { get; set; }
public CurrentWeather Current { get; set; }
public DailyWeather Daily { get; set; }
}
Expand All @@ -81,4 +98,4 @@ public class DailyWeather
public double[] ApparentTemperatureMax { get; set; }
public double[] ApparentTemperatureMin { get; set; }
}
}
}
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"Name": "Weather",
"Description": "Get weather information for a location",
"Author": "adielBm",
"Version": "1.1.0",
"Version": "1.2.0",
"Language": "csharp",
"Website": "https://github.com/adielBm/Flow.Launcher.Plugin.Weather",
"IcoPath": "Images\\weather-icon.png",
Expand Down
Loading

0 comments on commit 9f76114

Please sign in to comment.