mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-21 16:14:28 +08:00
735 lines
26 KiB
C#
735 lines
26 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Avalonia.Media;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using LanMountainDesktop.Models;
|
|
using LanMountainDesktop.Services;
|
|
using LanMountainDesktop.Services.Settings;
|
|
using LanMountainDesktop.Views.Components;
|
|
|
|
namespace LanMountainDesktop.ViewModels;
|
|
|
|
public sealed partial class WeatherSettingsPageViewModel : ViewModelBase
|
|
{
|
|
private readonly ISettingsFacadeService _settingsFacade;
|
|
private readonly LocalizationService _localizationService;
|
|
private readonly ILocationService _locationService;
|
|
private readonly WeatherLocationRefreshService _weatherLocationRefreshService;
|
|
private string _languageCode;
|
|
private bool _isInitializing;
|
|
|
|
public WeatherSettingsPageViewModel(
|
|
ISettingsFacadeService settingsFacade,
|
|
LocalizationService localizationService,
|
|
ILocationService locationService,
|
|
WeatherLocationRefreshService weatherLocationRefreshService,
|
|
bool enableStartupPreviewRefresh = true)
|
|
{
|
|
_settingsFacade = settingsFacade ?? throw new ArgumentNullException(nameof(settingsFacade));
|
|
_localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
|
|
_locationService = locationService ?? throw new ArgumentNullException(nameof(locationService));
|
|
_weatherLocationRefreshService = weatherLocationRefreshService ?? throw new ArgumentNullException(nameof(weatherLocationRefreshService));
|
|
|
|
var regionState = _settingsFacade.Region.Get();
|
|
_languageCode = _localizationService.NormalizeLanguageCode(regionState.LanguageCode);
|
|
|
|
RefreshLocalizedText();
|
|
LocationModes = CreateLocationModes();
|
|
|
|
var weatherState = _settingsFacade.Weather.Get();
|
|
_isInitializing = true;
|
|
ApplySavedState(weatherState, save: false);
|
|
_isInitializing = false;
|
|
|
|
IsLocationSupported = _locationService.IsSupported;
|
|
UpdateModeVisibility();
|
|
UpdateCurrentLocationSummary();
|
|
LocationActionStatus = IsLocationSupported
|
|
? LocationReadyText
|
|
: LocationUnsupportedText;
|
|
|
|
if (enableStartupPreviewRefresh)
|
|
{
|
|
_ = RefreshPreviewAsync();
|
|
}
|
|
}
|
|
|
|
public IReadOnlyList<SelectionOption> LocationModes { get; }
|
|
|
|
public ObservableCollection<WeatherLocation> SearchResults { get; } = [];
|
|
|
|
[ObservableProperty]
|
|
private string _pageTitle = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _pageDescription = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _previewHeader = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _previewDescription = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _locationSourceHeader = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _locationSourceDescription = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _citySearchHeader = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _citySearchDescription = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _coordinatesHeader = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _coordinatesDescription = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _locationServicesHeader = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _locationServicesDescription = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _alertFilterHeader = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _alertFilterDescription = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _requestHeader = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _requestDescription = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _searchPlaceholder = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _searchButtonText = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _applyCityButtonText = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _refreshButtonText = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _applyCoordinatesButtonText = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _useCurrentLocationButtonText = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _autoRefreshLabel = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _latitudeLabel = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _longitudeLabel = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _locationKeyPlaceholder = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _locationNamePlaceholder = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _noTlsToggleText = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _locationUnsupportedText = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _locationReadyText = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _locationRefreshingText = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _footerHint = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private SelectionOption _selectedLocationMode = new("CitySearch", "City Search");
|
|
|
|
[ObservableProperty]
|
|
private bool _isCitySearchMode = true;
|
|
|
|
[ObservableProperty]
|
|
private bool _isCoordinatesMode;
|
|
|
|
[ObservableProperty]
|
|
private bool _isLocationSupported;
|
|
|
|
[ObservableProperty]
|
|
private string _searchKeyword = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private WeatherLocation? _selectedSearchResult;
|
|
|
|
[ObservableProperty]
|
|
private string _searchStatus = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private bool _isSearching;
|
|
|
|
[ObservableProperty]
|
|
private double _latitude;
|
|
|
|
[ObservableProperty]
|
|
private double _longitude;
|
|
|
|
[ObservableProperty]
|
|
private string _locationKey = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _locationName = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private bool _autoRefreshLocation;
|
|
|
|
[ObservableProperty]
|
|
private string _excludedAlerts = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private bool _noTlsRequests;
|
|
|
|
[ObservableProperty]
|
|
private string _currentLocationSummary = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _locationActionStatus = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private bool _isRefreshingLocation;
|
|
|
|
[ObservableProperty]
|
|
private bool _isRefreshingPreview;
|
|
|
|
[ObservableProperty]
|
|
private IImage? _previewIcon;
|
|
|
|
[ObservableProperty]
|
|
private string _previewLocation = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _previewTemperature = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _previewCondition = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _previewUpdated = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _previewStatus = string.Empty;
|
|
|
|
partial void OnSelectedLocationModeChanged(SelectionOption value)
|
|
{
|
|
UpdateModeVisibility();
|
|
UpdateCurrentLocationSummary();
|
|
if (_isInitializing || value is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_settingsFacade.Weather.Save(CreateEditableState(value.Value));
|
|
_ = RefreshPreviewAsync();
|
|
}
|
|
|
|
partial void OnAutoRefreshLocationChanged(bool value)
|
|
{
|
|
_ = value;
|
|
if (_isInitializing)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_settingsFacade.Weather.Save(CreateEditableState());
|
|
}
|
|
|
|
partial void OnExcludedAlertsChanged(string value)
|
|
{
|
|
_ = value;
|
|
if (_isInitializing)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_settingsFacade.Weather.Save(CreateEditableState());
|
|
}
|
|
|
|
partial void OnNoTlsRequestsChanged(bool value)
|
|
{
|
|
_ = value;
|
|
if (_isInitializing)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_settingsFacade.Weather.Save(CreateEditableState());
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task SearchAsync()
|
|
{
|
|
SearchStatus = string.Empty;
|
|
SearchResults.Clear();
|
|
SelectedSearchResult = null;
|
|
|
|
if (string.IsNullOrWhiteSpace(SearchKeyword))
|
|
{
|
|
SearchStatus = L("settings.weather.search_required", "Please enter a city keyword first.");
|
|
return;
|
|
}
|
|
|
|
IsSearching = true;
|
|
try
|
|
{
|
|
var result = await _settingsFacade.Weather.SearchLocationsAsync(
|
|
SearchKeyword.Trim(),
|
|
NormalizeWeatherLocale(_languageCode));
|
|
if (!result.Success)
|
|
{
|
|
SearchStatus = string.Format(
|
|
ResolveCulture(),
|
|
L("settings.weather.search_failed_format", "Search failed: {0}"),
|
|
result.ErrorMessage ?? result.ErrorCode ?? L("settings.weather.preview_unknown", "Unknown"));
|
|
return;
|
|
}
|
|
|
|
foreach (var item in result.Data ?? [])
|
|
{
|
|
SearchResults.Add(item);
|
|
}
|
|
|
|
SearchStatus = SearchResults.Count == 0
|
|
? L("settings.weather.search_no_results", "No locations were found.")
|
|
: string.Format(
|
|
ResolveCulture(),
|
|
L("settings.weather.search_result_count_format", "Found {0} locations."),
|
|
SearchResults.Count);
|
|
|
|
SelectedSearchResult = SearchResults.FirstOrDefault();
|
|
}
|
|
finally
|
|
{
|
|
IsSearching = false;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task ApplyCitySelectionAsync()
|
|
{
|
|
if (SelectedSearchResult is null)
|
|
{
|
|
SearchStatus = L("settings.weather.search_select_required", "Please select one location from search results.");
|
|
return;
|
|
}
|
|
|
|
var selected = SelectedSearchResult;
|
|
var nextState = new WeatherSettingsState(
|
|
"CitySearch",
|
|
selected.LocationKey,
|
|
selected.Name,
|
|
selected.Latitude,
|
|
selected.Longitude,
|
|
AutoRefreshLocation,
|
|
ExcludedAlerts ?? string.Empty,
|
|
"HyperOS3",
|
|
NoTlsRequests,
|
|
SearchKeyword?.Trim() ?? string.Empty);
|
|
|
|
ApplySavedState(nextState);
|
|
SearchStatus = string.Format(
|
|
ResolveCulture(),
|
|
L("settings.weather.search_applied_format", "Location applied: {0}"),
|
|
selected.Name);
|
|
await RefreshPreviewAsync();
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task ApplyCoordinatesAsync()
|
|
{
|
|
var nextState = CreateEditableState("Coordinates");
|
|
_settingsFacade.Weather.Save(nextState);
|
|
ApplySavedState(nextState, save: false);
|
|
SearchStatus = string.Format(
|
|
ResolveCulture(),
|
|
L("settings.weather.coordinates_saved_format", "Coordinates saved: {0:F4}, {1:F4}"),
|
|
nextState.Latitude,
|
|
nextState.Longitude);
|
|
await RefreshPreviewAsync();
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task UseCurrentLocationAsync()
|
|
{
|
|
if (!IsLocationSupported)
|
|
{
|
|
LocationActionStatus = LocationUnsupportedText;
|
|
return;
|
|
}
|
|
|
|
IsRefreshingLocation = true;
|
|
LocationActionStatus = LocationRefreshingText;
|
|
try
|
|
{
|
|
var result = await _weatherLocationRefreshService.RefreshCurrentLocationAsync();
|
|
if (!result.Success || result.AppliedState is null)
|
|
{
|
|
LocationActionStatus = string.Format(
|
|
ResolveCulture(),
|
|
L("settings.weather.location_refresh_failed_format", "Failed to get current location: {0}"),
|
|
result.ErrorMessage ?? result.LocationResult?.FailureReason.ToString() ?? L("settings.weather.preview_unknown", "Unknown"));
|
|
return;
|
|
}
|
|
|
|
ApplySavedState(result.AppliedState, save: false);
|
|
LocationActionStatus = string.Format(
|
|
ResolveCulture(),
|
|
L("settings.weather.location_refresh_success_format", "Current location applied: {0}"),
|
|
result.AppliedState.LocationName);
|
|
await RefreshPreviewAsync();
|
|
}
|
|
finally
|
|
{
|
|
IsRefreshingLocation = false;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task RefreshPreviewAsync()
|
|
{
|
|
IsRefreshingPreview = true;
|
|
try
|
|
{
|
|
var state = ResolvePreviewState();
|
|
if (string.IsNullOrWhiteSpace(state.LocationKey))
|
|
{
|
|
PreviewStatus = L("settings.weather.preview_missing_location", "Please apply one weather location before testing.");
|
|
PreviewIcon = null;
|
|
PreviewLocation = CurrentLocationSummary;
|
|
PreviewTemperature = "--";
|
|
PreviewCondition = string.Empty;
|
|
PreviewUpdated = string.Empty;
|
|
return;
|
|
}
|
|
|
|
var result = await _settingsFacade.Weather.GetWeatherInfoService().GetWeatherAsync(
|
|
new WeatherQuery(
|
|
state.LocationKey,
|
|
state.Latitude,
|
|
state.Longitude,
|
|
ForecastDays: 3,
|
|
Locale: NormalizeWeatherLocale(_languageCode),
|
|
ForceRefresh: true));
|
|
if (!result.Success || result.Data is null)
|
|
{
|
|
PreviewStatus = string.Format(
|
|
ResolveCulture(),
|
|
L("settings.weather.preview_failed_format", "Test fetch failed: {0}"),
|
|
result.ErrorMessage ?? result.ErrorCode ?? L("settings.weather.preview_unknown", "Unknown"));
|
|
PreviewIcon = null;
|
|
return;
|
|
}
|
|
|
|
var snapshot = result.Data;
|
|
var isNight = snapshot.Current.IsDaylight.HasValue
|
|
? !snapshot.Current.IsDaylight.Value
|
|
: _settingsFacade.Theme.Get().IsNightMode;
|
|
var preview = XiaomiWeatherVisualResolver.Resolve(
|
|
snapshot.Current.WeatherText,
|
|
snapshot.Current.WeatherCode,
|
|
isNight,
|
|
_languageCode);
|
|
PreviewIcon = HyperOS3WeatherAssetLoader.LoadImage(preview.PrimaryIconAsset);
|
|
PreviewLocation = string.IsNullOrWhiteSpace(snapshot.LocationName)
|
|
? state.LocationName
|
|
: snapshot.LocationName!;
|
|
PreviewTemperature = snapshot.Current.TemperatureC.HasValue
|
|
? string.Format(CultureInfo.InvariantCulture, "{0:0.#}°C", snapshot.Current.TemperatureC.Value)
|
|
: "--";
|
|
PreviewCondition = preview.DisplayText;
|
|
|
|
var updatedAt = (snapshot.ObservationTime ?? snapshot.FetchedAt).ToLocalTime();
|
|
PreviewUpdated = string.Format(
|
|
ResolveCulture(),
|
|
L("settings.weather.preview_updated_format", "Updated {0}"),
|
|
updatedAt.ToString("g", ResolveCulture()));
|
|
PreviewStatus = string.Format(
|
|
ResolveCulture(),
|
|
L("settings.weather.preview_success_format", "Test success: {0} · {1} · {2}"),
|
|
PreviewLocation,
|
|
PreviewTemperature,
|
|
PreviewCondition);
|
|
}
|
|
finally
|
|
{
|
|
IsRefreshingPreview = false;
|
|
}
|
|
}
|
|
|
|
internal void ApplyDesignTimePreview()
|
|
{
|
|
_isInitializing = true;
|
|
|
|
var previewLocation = new WeatherLocation(
|
|
"Shenzhen Nanshan",
|
|
"101280601",
|
|
22.5431,
|
|
114.0579,
|
|
"Guangdong, China");
|
|
var alternateLocation = new WeatherLocation(
|
|
"Shanghai Pudong",
|
|
"101020600",
|
|
31.2304,
|
|
121.4737,
|
|
"Shanghai, China");
|
|
|
|
SelectedLocationMode = LocationModes.FirstOrDefault(option =>
|
|
string.Equals(option.Value, "CitySearch", StringComparison.OrdinalIgnoreCase))
|
|
?? LocationModes[0];
|
|
SearchKeyword = "shenzhen";
|
|
SelectedSearchResult = previewLocation;
|
|
|
|
SearchResults.Clear();
|
|
SearchResults.Add(previewLocation);
|
|
SearchResults.Add(alternateLocation);
|
|
|
|
Latitude = previewLocation.Latitude;
|
|
Longitude = previewLocation.Longitude;
|
|
LocationKey = previewLocation.LocationKey;
|
|
LocationName = previewLocation.Name;
|
|
AutoRefreshLocation = true;
|
|
ExcludedAlerts = "Heat\nThunderstorm";
|
|
NoTlsRequests = false;
|
|
IsLocationSupported = true;
|
|
IsRefreshingLocation = false;
|
|
IsRefreshingPreview = false;
|
|
|
|
_isInitializing = false;
|
|
|
|
UpdateModeVisibility();
|
|
UpdateCurrentLocationSummary();
|
|
|
|
var preview = XiaomiWeatherVisualResolver.Resolve(
|
|
"Partly cloudy",
|
|
4,
|
|
isNight: false,
|
|
_languageCode);
|
|
|
|
SearchStatus = "2 sample locations are shown for design preview.";
|
|
LocationActionStatus = "Using mocked Windows location support in design mode.";
|
|
PreviewIcon = HyperOS3WeatherAssetLoader.LoadImage(preview.PrimaryIconAsset);
|
|
PreviewLocation = previewLocation.Name;
|
|
PreviewTemperature = "24 deg C";
|
|
PreviewCondition = preview.DisplayText;
|
|
PreviewUpdated = "Updated 09:42";
|
|
PreviewStatus = "Preview data is mocked for Avalonia design mode.";
|
|
}
|
|
|
|
private void RefreshLocalizedText()
|
|
{
|
|
PageTitle = L("settings.weather.title", "Weather");
|
|
PageDescription = L("settings.weather.description", "Configure weather location, automatic positioning, and Xiaomi weather preview.");
|
|
PreviewHeader = L("settings.weather.preview_panel_header", "Weather Preview");
|
|
PreviewDescription = L("settings.weather.preview_panel_desc", "Refresh and verify current weather service status.");
|
|
LocationSourceHeader = L("settings.weather.location_source_header", "Location Source");
|
|
LocationSourceDescription = L("settings.weather.location_source_desc", "Choose how weather widgets resolve location.");
|
|
CitySearchHeader = L("settings.weather.city_search_header", "City Search");
|
|
CitySearchDescription = L("settings.weather.city_search_desc", "Search cities and apply one weather location.");
|
|
CoordinatesHeader = L("settings.weather.coordinates_header", "Coordinates");
|
|
CoordinatesDescription = L("settings.weather.coordinates_desc", "Set latitude/longitude and optional key/name.");
|
|
LocationServicesHeader = L("settings.weather.location_services_header", "Location Service");
|
|
LocationServicesDescription = L("settings.weather.location_services_desc", "Use the current Windows location and decide whether it refreshes automatically at startup.");
|
|
AlertFilterHeader = L("settings.weather.alert_filter_header", "Excluded Alerts");
|
|
AlertFilterDescription = L("settings.weather.alert_filter_desc", "Alerts containing these words will not be shown. One rule per line.");
|
|
RequestHeader = L("settings.weather.no_tls_header", "No TLS Weather Request");
|
|
RequestDescription = L("settings.weather.no_tls_desc", "Not recommended. Enable only for incompatible network environments.");
|
|
SearchPlaceholder = L("settings.weather.search_placeholder", "e.g. Beijing");
|
|
SearchButtonText = L("settings.weather.search_button", "Search");
|
|
ApplyCityButtonText = L("settings.weather.apply_city_button", "Apply City");
|
|
RefreshButtonText = L("settings.weather.refresh_button", "Refresh");
|
|
ApplyCoordinatesButtonText = L("settings.weather.apply_coordinates_button", "Apply Coordinates");
|
|
UseCurrentLocationButtonText = L("settings.weather.use_current_location", "Use Current Location");
|
|
AutoRefreshLabel = L("settings.weather.auto_refresh", "Auto refresh location on startup");
|
|
LatitudeLabel = L("settings.weather.latitude_label", "Latitude");
|
|
LongitudeLabel = L("settings.weather.longitude_label", "Longitude");
|
|
LocationKeyPlaceholder = L("settings.weather.location_key_placeholder", "Location key (optional)");
|
|
LocationNamePlaceholder = L("settings.weather.location_name_placeholder", "Display name (optional)");
|
|
NoTlsToggleText = L("settings.weather.no_tls_toggle", "Allow non-TLS request fallback");
|
|
LocationUnsupportedText = L("settings.weather.location_unsupported", "Current platform does not support retrieving the current location.");
|
|
LocationReadyText = L("settings.weather.location_ready", "You can use the current Windows location.");
|
|
LocationRefreshingText = L("settings.weather.location_refreshing", "Requesting current location…");
|
|
FooterHint = L("settings.weather.footer_hint", "Desktop weather widgets will reuse the location and alert exclusion settings configured here.");
|
|
}
|
|
|
|
private IReadOnlyList<SelectionOption> CreateLocationModes()
|
|
{
|
|
return
|
|
[
|
|
new SelectionOption("CitySearch", L("settings.weather.mode_city_search", "City Search")),
|
|
new SelectionOption("Coordinates", L("settings.weather.mode_coordinates", "Coordinates"))
|
|
];
|
|
}
|
|
|
|
private void UpdateModeVisibility()
|
|
{
|
|
var mode = SelectedLocationMode?.Value ?? "CitySearch";
|
|
IsCitySearchMode = string.Equals(mode, "CitySearch", StringComparison.OrdinalIgnoreCase);
|
|
IsCoordinatesMode = string.Equals(mode, "Coordinates", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private void UpdateCurrentLocationSummary()
|
|
{
|
|
var state = CreateEditableState();
|
|
var modeLabel = SelectedLocationMode?.Label ?? state.LocationMode;
|
|
if (string.Equals(state.LocationMode, "CitySearch", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
CurrentLocationSummary = string.IsNullOrWhiteSpace(state.LocationKey)
|
|
? L("settings.weather.status_city_empty", "No city location is configured.")
|
|
: string.Format(
|
|
ResolveCulture(),
|
|
L("settings.weather.status_city_format", "Mode: {0} | {1} | Key: {2}"),
|
|
modeLabel,
|
|
string.IsNullOrWhiteSpace(state.LocationName) ? L("settings.weather.location_not_selected", "No location selected") : state.LocationName,
|
|
state.LocationKey);
|
|
return;
|
|
}
|
|
|
|
CurrentLocationSummary = string.Format(
|
|
ResolveCulture(),
|
|
L("settings.weather.status_coordinates_format", "Mode: {0} | Lat {1:F4}, Lon {2:F4} | Key: {3}"),
|
|
modeLabel,
|
|
state.Latitude,
|
|
state.Longitude,
|
|
state.LocationKey);
|
|
}
|
|
|
|
private WeatherSettingsState CreateEditableState(string? locationMode = null)
|
|
{
|
|
var mode = locationMode ?? SelectedLocationMode?.Value ?? "CitySearch";
|
|
var locationKey = LocationKey?.Trim() ?? string.Empty;
|
|
var locationName = LocationName?.Trim() ?? string.Empty;
|
|
|
|
if (string.Equals(mode, "Coordinates", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
if (string.IsNullOrWhiteSpace(locationKey))
|
|
{
|
|
locationKey = BuildCoordinateKey(Latitude, Longitude);
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(locationName))
|
|
{
|
|
locationName = BuildCoordinateDisplayName(Latitude, Longitude);
|
|
}
|
|
}
|
|
|
|
return new WeatherSettingsState(
|
|
mode,
|
|
locationKey,
|
|
locationName,
|
|
Latitude,
|
|
Longitude,
|
|
AutoRefreshLocation,
|
|
ExcludedAlerts ?? string.Empty,
|
|
"HyperOS3",
|
|
NoTlsRequests,
|
|
SearchKeyword?.Trim() ?? string.Empty);
|
|
}
|
|
|
|
private WeatherSettingsState ResolvePreviewState()
|
|
{
|
|
if (IsCitySearchMode && SelectedSearchResult is not null)
|
|
{
|
|
return new WeatherSettingsState(
|
|
"CitySearch",
|
|
SelectedSearchResult.LocationKey,
|
|
SelectedSearchResult.Name,
|
|
SelectedSearchResult.Latitude,
|
|
SelectedSearchResult.Longitude,
|
|
AutoRefreshLocation,
|
|
ExcludedAlerts ?? string.Empty,
|
|
"HyperOS3",
|
|
NoTlsRequests,
|
|
SearchKeyword?.Trim() ?? string.Empty);
|
|
}
|
|
|
|
return CreateEditableState();
|
|
}
|
|
|
|
private void ApplySavedState(WeatherSettingsState state, bool save = true)
|
|
{
|
|
if (save)
|
|
{
|
|
_settingsFacade.Weather.Save(state);
|
|
}
|
|
|
|
_isInitializing = true;
|
|
SelectedLocationMode = LocationModes.FirstOrDefault(option =>
|
|
string.Equals(option.Value, state.LocationMode, StringComparison.OrdinalIgnoreCase))
|
|
?? LocationModes[0];
|
|
Latitude = state.Latitude;
|
|
Longitude = state.Longitude;
|
|
LocationKey = state.LocationKey;
|
|
LocationName = state.LocationName;
|
|
AutoRefreshLocation = state.AutoRefreshLocation;
|
|
ExcludedAlerts = state.ExcludedAlerts;
|
|
NoTlsRequests = state.NoTlsRequests;
|
|
SearchKeyword = state.LocationQuery;
|
|
_isInitializing = false;
|
|
|
|
UpdateModeVisibility();
|
|
UpdateCurrentLocationSummary();
|
|
}
|
|
|
|
private string BuildCoordinateDisplayName(double latitude, double longitude)
|
|
{
|
|
return string.Format(
|
|
CultureInfo.InvariantCulture,
|
|
L("settings.weather.coordinates_default_name_format", "Coordinate {0:F4}, {1:F4}"),
|
|
latitude,
|
|
longitude);
|
|
}
|
|
|
|
private static string BuildCoordinateKey(double latitude, double longitude)
|
|
{
|
|
return FormattableString.Invariant($"coord:{latitude:F4},{longitude:F4}");
|
|
}
|
|
|
|
private string L(string key, string fallback)
|
|
{
|
|
return _localizationService.GetString(_languageCode, key, fallback);
|
|
}
|
|
|
|
private CultureInfo ResolveCulture()
|
|
{
|
|
try
|
|
{
|
|
return CultureInfo.GetCultureInfo(_languageCode);
|
|
}
|
|
catch (CultureNotFoundException)
|
|
{
|
|
return CultureInfo.InvariantCulture;
|
|
}
|
|
}
|
|
|
|
private static string NormalizeWeatherLocale(string? languageCode)
|
|
{
|
|
return string.Equals(languageCode, "en-US", StringComparison.OrdinalIgnoreCase)
|
|
? "en_us"
|
|
: "zh_cn";
|
|
}
|
|
}
|