269 lines
11 KiB
C#
269 lines
11 KiB
C#
using Microsoft.UI.Text;
|
|
using Microsoft.UI.Xaml;
|
|
using Microsoft.UI.Xaml.Automation;
|
|
using Microsoft.UI.Xaml.Controls;
|
|
using Microsoft.UI.Xaml.Controls.Primitives;
|
|
using Microsoft.UI.Xaml.Media;
|
|
using YMhut.Box.Core.Settings;
|
|
using YMhut.Box.WinUI.Services;
|
|
|
|
namespace YMhut.Box.WinUI.Controls;
|
|
|
|
public sealed class WeatherCapsuleControl : UserControl
|
|
{
|
|
private readonly ITitleWeatherService _weatherService;
|
|
private readonly ISettingsService _settingsService = AppServices.GetRequiredService<ISettingsService>();
|
|
private readonly Button _button;
|
|
private readonly AnimatedWeatherIconControl _weatherIcon;
|
|
private readonly ProgressRing _loadingRing;
|
|
private readonly TextBlock _locationText;
|
|
private readonly TextBlock _tempText;
|
|
private readonly TextBlock _conditionText;
|
|
private readonly Flyout _flyout;
|
|
|
|
private TitleWeatherSnapshot _snapshot = TitleWeatherSnapshot.Loading;
|
|
private TitleWeatherSnapshot? _lastAvailableSnapshot;
|
|
private int _loadVersion;
|
|
|
|
public WeatherCapsuleControl(ITitleWeatherService weatherService)
|
|
{
|
|
_weatherService = weatherService;
|
|
HorizontalAlignment = HorizontalAlignment.Right;
|
|
VerticalAlignment = VerticalAlignment.Center;
|
|
|
|
_weatherIcon = new AnimatedWeatherIconControl(20)
|
|
{
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
VerticalAlignment = VerticalAlignment.Center
|
|
};
|
|
_loadingRing = new ProgressRing
|
|
{
|
|
Width = 18,
|
|
Height = 18,
|
|
IsActive = true,
|
|
Visibility = Visibility.Collapsed
|
|
};
|
|
_locationText = ModernUi.Text(_snapshot.Location, 12, FontWeights.SemiBold, ModernUi.TextSecondary, maxLines: 1);
|
|
_conditionText = ModernUi.Text(_snapshot.Condition, 10.5, foreground: ModernUi.TextSecondary, maxLines: 1);
|
|
_tempText = ModernUi.Text(_snapshot.TemperatureText, 15, FontWeights.SemiBold, ModernUi.TextPrimary, maxLines: 1);
|
|
ConfigureCompactLine(_locationText, 14);
|
|
ConfigureCompactLine(_conditionText, 13);
|
|
ConfigureCompactLine(_tempText, 18);
|
|
|
|
var visual = new Grid
|
|
{
|
|
ColumnSpacing = 9,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
ColumnDefinitions =
|
|
{
|
|
new ColumnDefinition { Width = GridLength.Auto },
|
|
new ColumnDefinition(),
|
|
new ColumnDefinition { Width = GridLength.Auto }
|
|
}
|
|
};
|
|
var iconHost = new Grid { Width = 22, Height = 22, Children = { _weatherIcon, _loadingRing } };
|
|
visual.Children.Add(iconHost);
|
|
var text = new Grid
|
|
{
|
|
MinWidth = 76,
|
|
MaxWidth = 130,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
RowDefinitions =
|
|
{
|
|
new RowDefinition { Height = GridLength.Auto },
|
|
new RowDefinition { Height = GridLength.Auto }
|
|
}
|
|
};
|
|
text.Children.Add(_locationText);
|
|
Grid.SetRow(_conditionText, 1);
|
|
text.Children.Add(_conditionText);
|
|
Grid.SetColumn(text, 1);
|
|
visual.Children.Add(text);
|
|
Grid.SetColumn(_tempText, 2);
|
|
visual.Children.Add(_tempText);
|
|
|
|
_flyout = new Flyout { Placement = FlyoutPlacementMode.BottomEdgeAlignedRight };
|
|
_flyout.Opening += (_, _) => _flyout.Content = BuildFlyoutContent();
|
|
|
|
_button = new Button
|
|
{
|
|
Height = 38,
|
|
MinWidth = 168,
|
|
MaxWidth = 242,
|
|
Margin = new Thickness(0, 4, 0, 4),
|
|
Padding = new Thickness(12, 4, 12, 4),
|
|
CornerRadius = new CornerRadius(19),
|
|
Background = ModernUi.Surface,
|
|
BorderBrush = ModernUi.Stroke,
|
|
BorderThickness = new Thickness(1),
|
|
Content = visual,
|
|
Flyout = _flyout
|
|
};
|
|
AutomationProperties.SetName(_button, AppLocalizer.T("天气", "Weather"));
|
|
ToolTipService.SetToolTip(_button, AppLocalizer.T("查看天气详情", "Show weather details"));
|
|
Content = _button;
|
|
ApplySnapshot(_snapshot);
|
|
}
|
|
|
|
public async Task LoadAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
await RefreshAsync(cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task RefreshAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var loadVersion = Interlocked.Increment(ref _loadVersion);
|
|
DispatcherQueue.TryEnqueue(() => ApplySnapshot(TitleWeatherSnapshot.Loading, loadingOverlay: true));
|
|
var snapshot = await _weatherService.GetCurrentAsync(cancellationToken).ConfigureAwait(false);
|
|
if (loadVersion != _loadVersion)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DispatcherQueue.TryEnqueue(() => ApplySnapshot(snapshot));
|
|
}
|
|
|
|
public void RefreshLanguage()
|
|
{
|
|
ApplySnapshot(_snapshot);
|
|
}
|
|
|
|
private void ApplySnapshot(TitleWeatherSnapshot snapshot, bool loadingOverlay = false)
|
|
{
|
|
var displaySnapshot = loadingOverlay && _lastAvailableSnapshot is not null
|
|
? _lastAvailableSnapshot
|
|
: snapshot;
|
|
_snapshot = displaySnapshot;
|
|
if (displaySnapshot.IsAvailable)
|
|
{
|
|
_lastAvailableSnapshot = displaySnapshot;
|
|
}
|
|
|
|
var loading = loadingOverlay || ReferenceEquals(snapshot, TitleWeatherSnapshot.Loading);
|
|
_loadingRing.IsActive = loading;
|
|
_loadingRing.Visibility = loading ? Visibility.Visible : Visibility.Collapsed;
|
|
_loadingRing.Opacity = loading ? 0.78 : 0;
|
|
_weatherIcon.Visibility = Visibility.Visible;
|
|
_weatherIcon.Opacity = loading ? 0.62 : 1;
|
|
_weatherIcon.Update(displaySnapshot, _settingsService.Current.AnimationsEnabled);
|
|
_locationText.Text = displaySnapshot.Location;
|
|
_conditionText.Text = displaySnapshot.Condition;
|
|
_tempText.Text = displaySnapshot.TemperatureText;
|
|
_button.Background = displaySnapshot.IsAvailable ? ModernUi.Surface : ModernUi.SurfaceAlt;
|
|
_button.BorderBrush = displaySnapshot.IsAvailable ? ModernUi.Stroke : ModernUi.StrokeStrong;
|
|
ToolTipService.SetToolTip(_button, loading ? AppLocalizer.T("天气正在刷新", "Weather is refreshing") : BuildTooltip(displaySnapshot));
|
|
AutomationProperties.SetName(_button, loading ? AppLocalizer.T("天气正在刷新", "Weather is refreshing") : BuildTooltip(displaySnapshot));
|
|
}
|
|
|
|
private string BuildTooltip(TitleWeatherSnapshot snapshot)
|
|
{
|
|
return snapshot.IsAvailable
|
|
? $"{snapshot.Location} {snapshot.Condition} {snapshot.TemperatureText}"
|
|
: AppLocalizer.T("天气暂不可用,点击重试", "Weather unavailable. Click to retry.");
|
|
}
|
|
|
|
private UIElement BuildFlyoutContent()
|
|
{
|
|
var snapshot = _snapshot;
|
|
var refresh = ModernUi.PillButton(AppLocalizer.T("刷新", "Refresh"), "\uE72C", async () => await RefreshAsync(), primary: true);
|
|
refresh.HorizontalAlignment = HorizontalAlignment.Right;
|
|
Grid.SetColumn(refresh, 2);
|
|
|
|
return new StackPanel
|
|
{
|
|
Width = 280,
|
|
Padding = new Thickness(4),
|
|
Spacing = 12,
|
|
Children =
|
|
{
|
|
new Grid
|
|
{
|
|
ColumnSpacing = 12,
|
|
ColumnDefinitions =
|
|
{
|
|
new ColumnDefinition { Width = GridLength.Auto },
|
|
new ColumnDefinition(),
|
|
new ColumnDefinition { Width = GridLength.Auto }
|
|
},
|
|
Children =
|
|
{
|
|
BuildFlyoutIcon(snapshot),
|
|
BuildTitleBlock(snapshot),
|
|
refresh
|
|
}
|
|
},
|
|
BuildDetailLine(AppLocalizer.T("体感", "Feels like"), snapshot.FeelsLikeText, "\uE706"),
|
|
BuildDetailLine(AppLocalizer.T("湿度", "Humidity"), snapshot.HumidityText, "\uE81F"),
|
|
BuildDetailLine(AppLocalizer.T("风速", "Wind"), snapshot.WindText, "\uE9CA"),
|
|
BuildDetailLine(AppLocalizer.T("今日温度", "Today"), snapshot.RangeText, "\uE787"),
|
|
BuildDetailLine(AppLocalizer.T("更新时间", "Updated"), snapshot.UpdatedText, "\uE823"),
|
|
snapshot.ErrorMessage is null
|
|
? new Border { Height = 0 }
|
|
: ModernUi.Card(
|
|
ModernUi.Text(snapshot.ErrorMessage, 12, foreground: ModernUi.TextSecondary, maxLines: 3),
|
|
new Thickness(10),
|
|
radius: 8,
|
|
background: ModernUi.SurfaceAlt)
|
|
}
|
|
};
|
|
}
|
|
|
|
private static StackPanel BuildTitleBlock(TitleWeatherSnapshot snapshot)
|
|
{
|
|
var panel = new StackPanel
|
|
{
|
|
Spacing = 1,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
Children =
|
|
{
|
|
ModernUi.Text(snapshot.Location, 17, FontWeights.SemiBold, maxLines: 1),
|
|
ModernUi.Text($"{snapshot.Condition} · {snapshot.TemperatureText} · {snapshot.QueryLevel}", 13, foreground: ModernUi.TextSecondary, maxLines: 1)
|
|
}
|
|
};
|
|
Grid.SetColumn(panel, 1);
|
|
return panel;
|
|
}
|
|
|
|
private UIElement BuildFlyoutIcon(TitleWeatherSnapshot snapshot)
|
|
{
|
|
var icon = new AnimatedWeatherIconControl(44);
|
|
icon.Update(snapshot, _settingsService.Current.AnimationsEnabled);
|
|
return new Border
|
|
{
|
|
Width = 52,
|
|
Height = 52,
|
|
CornerRadius = new CornerRadius(8),
|
|
Background = ModernUi.AccentSoft,
|
|
BorderBrush = ModernUi.Stroke,
|
|
BorderThickness = new Thickness(1),
|
|
Child = icon
|
|
};
|
|
}
|
|
|
|
private static void ConfigureCompactLine(TextBlock text, double lineHeight)
|
|
{
|
|
text.TextWrapping = TextWrapping.NoWrap;
|
|
text.TextTrimming = TextTrimming.CharacterEllipsis;
|
|
text.LineHeight = lineHeight;
|
|
text.VerticalAlignment = VerticalAlignment.Center;
|
|
}
|
|
|
|
private static UIElement BuildDetailLine(string label, string value, string glyph)
|
|
{
|
|
var grid = new Grid { ColumnSpacing = 10 };
|
|
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
|
grid.ColumnDefinitions.Add(new ColumnDefinition());
|
|
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
|
grid.Children.Add(ModernUi.IconTile(glyph, 30, ModernUi.SurfaceAlt, ModernUi.TextSecondary, 13));
|
|
|
|
var title = ModernUi.Text(label, 13, FontWeights.SemiBold, ModernUi.TextSecondary, maxLines: 1);
|
|
Grid.SetColumn(title, 1);
|
|
grid.Children.Add(title);
|
|
|
|
var text = ModernUi.Text(value, 13, FontWeights.SemiBold, ModernUi.TextPrimary, maxLines: 1);
|
|
Grid.SetColumn(text, 2);
|
|
grid.Children.Add(text);
|
|
return grid;
|
|
}
|
|
}
|