0.4.3fixed

This commit is contained in:
lincube
2026-03-05 21:21:03 +08:00
parent b8643a2959
commit e917a1e4af
14 changed files with 862 additions and 109 deletions

View File

@@ -251,8 +251,7 @@ public sealed class ComponentRegistry
MinWidthCells: 4,
MinHeightCells: 2,
AllowStatusBarPlacement: false,
AllowDesktopPlacement: true,
ResizeMode: DesktopComponentResizeMode.Free),
AllowDesktopPlacement: true),
new DesktopComponentDefinition(
BuiltInComponentIds.DesktopWhiteboard,
"Blackboard Portrait",

View File

@@ -337,6 +337,17 @@
"dailysentence.widget.fallback_sentence": "Daily sentence is temporarily unavailable.",
"dailysentence.widget.fallback_translation": "Tap refresh and try again.",
"dailysentence.widget.source_default": "Youdao Dictionary",
"daily_sentence.settings.title": "Daily Sentence Settings",
"daily_sentence.settings.desc": "Configure auto-rotation and refresh interval.",
"daily_sentence.settings.auto_rotate_label": "Auto-rotation",
"daily_sentence.settings.auto_rotate_enabled": "Enable auto-rotation",
"daily_sentence.settings.frequency_label": "Rotation interval",
"daily_sentence.settings.frequency_5m": "5 minutes",
"daily_sentence.settings.frequency_10m": "10 minutes",
"daily_sentence.settings.frequency_40m": "40 minutes",
"daily_sentence.settings.frequency_1h": "1 hour",
"daily_sentence.settings.frequency_12h": "12 hours",
"daily_sentence.settings.frequency_24h": "24 hours",
"cnrnews.widget.loading": "Loading...",
"cnrnews.widget.loading_title": "Fetching CNR headlines",
"cnrnews.widget.loading_subtitle": "Please wait",
@@ -344,6 +355,17 @@
"cnrnews.widget.fallback_title": "CNR news is temporarily unavailable",
"cnrnews.widget.fallback_subtitle": "Tap refresh and try again",
"cnrnews.widget.hot_label": "Hot",
"cnrnews.settings.title": "CNR Settings",
"cnrnews.settings.desc": "Configure auto-rotation and refresh interval.",
"cnrnews.settings.auto_rotate_label": "Auto-rotation",
"cnrnews.settings.auto_rotate_enabled": "Enable auto-rotation",
"cnrnews.settings.frequency_label": "Rotation interval",
"cnrnews.settings.frequency_5m": "5 minutes",
"cnrnews.settings.frequency_10m": "10 minutes",
"cnrnews.settings.frequency_40m": "40 minutes",
"cnrnews.settings.frequency_1h": "1 hour",
"cnrnews.settings.frequency_12h": "12 hours",
"cnrnews.settings.frequency_24h": "24 hours",
"artwork.settings.title": "Daily Artwork Settings",
"artwork.settings.desc": "Switch the data source used by Daily Artwork.",
"artwork.settings.source_label": "Mirror Source",

View File

@@ -337,6 +337,17 @@
"dailysentence.widget.fallback_sentence": "今日英语句子暂不可用",
"dailysentence.widget.fallback_translation": "请点击右上角刷新重试",
"dailysentence.widget.source_default": "有道词典",
"daily_sentence.settings.title": "英语句子设置",
"daily_sentence.settings.desc": "配置自动轮换与刷新频率。",
"daily_sentence.settings.auto_rotate_label": "自动轮换",
"daily_sentence.settings.auto_rotate_enabled": "启用自动轮换",
"daily_sentence.settings.frequency_label": "轮换频率",
"daily_sentence.settings.frequency_5m": "5 分钟",
"daily_sentence.settings.frequency_10m": "10 分钟",
"daily_sentence.settings.frequency_40m": "40 分钟",
"daily_sentence.settings.frequency_1h": "1 小时",
"daily_sentence.settings.frequency_12h": "12 小时",
"daily_sentence.settings.frequency_24h": "24 小时",
"cnrnews.widget.loading": "加载中...",
"cnrnews.widget.loading_title": "正在获取新闻热点",
"cnrnews.widget.loading_subtitle": "请稍候",
@@ -344,6 +355,17 @@
"cnrnews.widget.fallback_title": "央广网新闻暂不可用",
"cnrnews.widget.fallback_subtitle": "点击右上角稍后重试",
"cnrnews.widget.hot_label": "热点",
"cnrnews.settings.title": "央广网设置",
"cnrnews.settings.desc": "配置新闻自动轮换与刷新频率。",
"cnrnews.settings.auto_rotate_label": "自动轮换",
"cnrnews.settings.auto_rotate_enabled": "启用自动轮换",
"cnrnews.settings.frequency_label": "轮换频率",
"cnrnews.settings.frequency_5m": "5 分钟",
"cnrnews.settings.frequency_10m": "10 分钟",
"cnrnews.settings.frequency_40m": "40 分钟",
"cnrnews.settings.frequency_1h": "1 小时",
"cnrnews.settings.frequency_12h": "12 小时",
"cnrnews.settings.frequency_24h": "24 小时",
"artwork.settings.title": "每日图片设置",
"artwork.settings.desc": "切换每日图片的数据源。",
"artwork.settings.source_label": "镜像源",

View File

@@ -98,6 +98,14 @@ public sealed class AppSettingsSnapshot
];
public string WorldClockSecondHandMode { get; set; } = "Tick";
public bool DailySentenceAutoRotateEnabled { get; set; } = true;
public int DailySentenceAutoRotateIntervalMinutes { get; set; } = 60;
public bool CnrDailyNewsAutoRotateEnabled { get; set; } = true;
public int CnrDailyNewsAutoRotateIntervalMinutes { get; set; } = 60;
public AppSettingsSnapshot Clone()
{
var clone = (AppSettingsSnapshot)MemberwiseClone();

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -53,6 +53,7 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
private DailyPoetryCacheEntry? _dailyPoetryCache;
private DailyNewsCacheEntry? _dailyNewsCache;
private DailyWordCacheEntry? _dailyWordCache;
private int _dailyNewsRotationCursor;
static RecommendationDataService()
{
@@ -206,10 +207,10 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
"No CNR news items were returned.");
}
var snapshot = new DailyNewsSnapshot(
var snapshot = new DailyNewsSnapshot(
Provider: "CNR",
Source: "央广网·头条",
Items: items.Take(targetCount).ToArray(),
Items: SelectDailyNewsItems(items, targetCount, normalizedQuery.ForceRefresh),
FetchedAt: DateTimeOffset.UtcNow);
SetDailyNewsCache(snapshot);
@@ -521,6 +522,33 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
}
}
private IReadOnlyList<DailyNewsItemSnapshot> SelectDailyNewsItems(
IReadOnlyList<DailyNewsItemSnapshot> items,
int targetCount,
bool forceRefresh)
{
if (items.Count == 0 || targetCount <= 0)
{
return [];
}
var safeCount = Math.Min(targetCount, items.Count);
if (!forceRefresh || items.Count <= safeCount)
{
return items.Take(safeCount).ToArray();
}
var cursor = Math.Abs(Interlocked.Increment(ref _dailyNewsRotationCursor) - 1);
var startIndex = cursor % items.Count;
var selection = new List<DailyNewsItemSnapshot>(safeCount);
for (var i = 0; i < safeCount; i++)
{
selection.Add(items[(startIndex + i) % items.Count]);
}
return selection;
}
private bool TryGetDailyWordFromCache(out DailyWordSnapshot snapshot)
{
lock (_cacheGate)
@@ -723,7 +751,7 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
return null;
}
return string.Join("", lines
return string.Join("; ", lines
.Where(line => !string.IsNullOrWhiteSpace(line))
.Distinct(StringComparer.OrdinalIgnoreCase)
.Take(3));
@@ -1646,3 +1674,4 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
: $"{text[..maxLength]}...";
}
}

View File

@@ -0,0 +1,87 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignWidth="420"
d:DesignHeight="300"
x:Class="LanMountainDesktop.Views.Components.CnrDailyNewsSettingsWindow">
<Border Background="{DynamicResource AdaptiveBackgroundBrush}"
Padding="16">
<Grid RowDefinitions="Auto,Auto,*"
RowSpacing="10">
<TextBlock x:Name="TitleTextBlock"
Text="CNR news settings"
FontSize="18"
FontWeight="SemiBold"
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
<TextBlock x:Name="DescriptionTextBlock"
Grid.Row="1"
Text="Configure auto-rotation and refresh interval."
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
<ScrollViewer Grid.Row="2"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="10"
Margin="0,0,6,0">
<Border Background="{DynamicResource AdaptiveSurfaceRaisedBrush}"
BorderBrush="{DynamicResource AdaptiveButtonBorderBrush}"
BorderThickness="1"
CornerRadius="12"
Padding="12">
<StackPanel Spacing="6">
<TextBlock x:Name="AutoRotateLabelTextBlock"
Text="Auto-rotation"
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
<CheckBox x:Name="AutoRotateCheckBox"
Content="Enable auto-rotation"
Checked="OnAutoRotateChanged"
Unchecked="OnAutoRotateChanged" />
</StackPanel>
</Border>
<Border x:Name="FrequencyCardBorder"
Background="{DynamicResource AdaptiveSurfaceRaisedBrush}"
BorderBrush="{DynamicResource AdaptiveButtonBorderBrush}"
BorderThickness="1"
CornerRadius="12"
Padding="12"
IsVisible="False">
<StackPanel Spacing="6">
<TextBlock x:Name="FrequencyLabelTextBlock"
Text="Rotation interval"
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
<ComboBox x:Name="FrequencyComboBox"
HorizontalAlignment="Stretch"
MinWidth="0"
SelectionChanged="OnFrequencySelectionChanged">
<ComboBoxItem x:Name="Frequency5mItem"
Tag="5"
Content="5 min" />
<ComboBoxItem x:Name="Frequency10mItem"
Tag="10"
Content="10 min" />
<ComboBoxItem x:Name="Frequency40mItem"
Tag="40"
Content="40 min" />
<ComboBoxItem x:Name="Frequency1hItem"
Tag="60"
Content="1 hour" />
<ComboBoxItem x:Name="Frequency12hItem"
Tag="720"
Content="12 hours" />
<ComboBoxItem x:Name="Frequency24hItem"
Tag="1440"
Content="24 hours" />
</ComboBox>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</Grid>
</Border>
</UserControl>

View File

@@ -0,0 +1,137 @@
using System;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components;
public partial class CnrDailyNewsSettingsWindow : UserControl
{
private static readonly int[] SupportedIntervals = [5, 10, 40, 60, 720, 1440];
private readonly AppSettingsService _appSettingsService = new();
private readonly LocalizationService _localizationService = new();
private bool _suppressEvents;
private string _languageCode = "zh-CN";
public event EventHandler? SettingsChanged;
public CnrDailyNewsSettingsWindow()
{
InitializeComponent();
LoadState();
ApplyLocalization();
}
private void LoadState()
{
var snapshot = _appSettingsService.Load();
_languageCode = _localizationService.NormalizeLanguageCode(snapshot.LanguageCode);
var enabled = snapshot.CnrDailyNewsAutoRotateEnabled;
var interval = NormalizeInterval(snapshot.CnrDailyNewsAutoRotateIntervalMinutes);
_suppressEvents = true;
AutoRotateCheckBox.IsChecked = enabled;
SelectInterval(interval);
FrequencyCardBorder.IsVisible = enabled;
_suppressEvents = false;
}
private void ApplyLocalization()
{
TitleTextBlock.Text = L("cnrnews.settings.title", "CNR news settings");
DescriptionTextBlock.Text = L("cnrnews.settings.desc", "Configure auto-rotation and refresh interval.");
AutoRotateLabelTextBlock.Text = L("cnrnews.settings.auto_rotate_label", "Auto-rotation");
AutoRotateCheckBox.Content = L("cnrnews.settings.auto_rotate_enabled", "Enable auto-rotation");
FrequencyLabelTextBlock.Text = L("cnrnews.settings.frequency_label", "Rotation interval");
Frequency5mItem.Content = L("cnrnews.settings.frequency_5m", "5 min");
Frequency10mItem.Content = L("cnrnews.settings.frequency_10m", "10 min");
Frequency40mItem.Content = L("cnrnews.settings.frequency_40m", "40 min");
Frequency1hItem.Content = L("cnrnews.settings.frequency_1h", "1 hour");
Frequency12hItem.Content = L("cnrnews.settings.frequency_12h", "12 hours");
Frequency24hItem.Content = L("cnrnews.settings.frequency_24h", "24 hours");
}
private void OnAutoRotateChanged(object? sender, RoutedEventArgs e)
{
_ = sender;
_ = e;
if (_suppressEvents)
{
return;
}
var enabled = AutoRotateCheckBox.IsChecked == true;
FrequencyCardBorder.IsVisible = enabled;
SaveState();
}
private void OnFrequencySelectionChanged(object? sender, SelectionChangedEventArgs e)
{
_ = sender;
_ = e;
if (_suppressEvents)
{
return;
}
SaveState();
}
private void SaveState()
{
var snapshot = _appSettingsService.Load();
snapshot.CnrDailyNewsAutoRotateEnabled = AutoRotateCheckBox.IsChecked == true;
snapshot.CnrDailyNewsAutoRotateIntervalMinutes = GetSelectedInterval();
_appSettingsService.Save(snapshot);
SettingsChanged?.Invoke(this, EventArgs.Empty);
}
private int GetSelectedInterval()
{
if (FrequencyComboBox.SelectedItem is ComboBoxItem item &&
item.Tag is string tagText &&
int.TryParse(tagText, out var minutes))
{
return NormalizeInterval(minutes);
}
return 60;
}
private void SelectInterval(int intervalMinutes)
{
var selected = FrequencyComboBox.Items
.OfType<ComboBoxItem>()
.FirstOrDefault(item =>
item.Tag is string tagText &&
int.TryParse(tagText, out var minutes) &&
minutes == intervalMinutes);
FrequencyComboBox.SelectedItem = selected ?? FrequencyComboBox.Items.OfType<ComboBoxItem>().FirstOrDefault();
}
private static int NormalizeInterval(int minutes)
{
if (minutes <= 0)
{
return 60;
}
if (SupportedIntervals.Contains(minutes))
{
return minutes;
}
return SupportedIntervals
.OrderBy(value => Math.Abs(value - minutes))
.FirstOrDefault(60);
}
private string L(string key, string fallback)
{
return _localizationService.GetString(_languageCode, key, fallback);
}
}

View File

@@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:fi="using:FluentIcons.Avalonia"
mc:Ignorable="d"
d:DesignWidth="640"
d:DesignHeight="320"
@@ -56,12 +57,12 @@
Spacing="4"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock x:Name="RefreshGlyphTextBlock"
Text="&#8635;"
Foreground="#52575F"
FontSize="19"
FontWeight="SemiBold"
VerticalAlignment="Center" />
<fi:SymbolIcon x:Name="RefreshGlyphIcon"
Symbol="ArrowClockwise"
IconVariant="Regular"
Foreground="#52575F"
FontSize="19"
VerticalAlignment="Center" />
<TextBlock x:Name="RefreshLabelTextBlock"
Text="&#25442;&#19968;&#25442;"
Foreground="#202327"

View File

@@ -36,6 +36,7 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
private const double BaseCellSize = 48d;
private const int BaseWidthCells = 4;
private const int BaseHeightCells = 2;
private static readonly int[] SupportedAutoRotateIntervalsMinutes = [5, 10, 40, 60, 720, 1440];
private readonly DispatcherTimer _refreshTimer = new()
{
@@ -85,6 +86,7 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
private double _currentCellSize = BaseCellSize;
private bool _isAttached;
private bool _isRefreshing;
private bool _autoRotateEnabled = true;
public CnrDailyNewsWidget()
{
@@ -92,7 +94,6 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
BrandPrimaryTextBlock.FontFamily = MiSansFontFamily;
BrandSecondaryTextBlock.FontFamily = MiSansFontFamily;
RefreshGlyphTextBlock.FontFamily = MiSansFontFamily;
RefreshLabelTextBlock.FontFamily = MiSansFontFamily;
News1TitleTextBlock.FontFamily = MiSansFontFamily;
News2TitleTextBlock.FontFamily = MiSansFontFamily;
@@ -106,6 +107,7 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
ApplyCellSize(_currentCellSize);
UpdateLanguageCode();
ApplyAutoRotateSettings();
ApplyLoadingState();
UpdateRefreshButtonState();
}
@@ -128,6 +130,7 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
public void RefreshFromSettings()
{
_recommendationService.ClearCache();
ApplyAutoRotateSettings();
if (_isAttached)
{
_ = RefreshNewsAsync(forceRefresh: true);
@@ -137,8 +140,8 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
_isAttached = true;
ApplyAutoRotateSettings();
UpdateRefreshButtonState();
_refreshTimer.Start();
_ = RefreshNewsAsync(forceRefresh: false);
}
@@ -155,26 +158,6 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
private void OnSizeChanged(object? sender, SizeChangedEventArgs e)
{
ApplyCellSize(_currentCellSize);
var desiredCount = ResolveDesiredNewsItemCount();
var previousRenderedCount = _renderedNewsCount;
if (_activeNewsItems.Count > 0 && desiredCount != previousRenderedCount)
{
RenderExtraNewsRows(_activeNewsItems.Take(desiredCount).Skip(2).ToArray());
UpdateNewsInteractionState();
}
var shouldFetchMoreItems = desiredCount > _activeNewsItems.Count;
var shouldReloadExpandedImages =
desiredCount > previousRenderedCount &&
desiredCount <= _activeNewsItems.Count;
if (_isAttached &&
!_isRefreshing &&
_activeNewsItems.Count > 0 &&
(shouldFetchMoreItems || shouldReloadExpandedImages))
{
_ = RefreshNewsAsync(forceRefresh: false);
}
}
private async void OnRefreshButtonClick(object? sender, RoutedEventArgs e)
@@ -190,7 +173,7 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
private async void OnRefreshTimerTick(object? sender, EventArgs e)
{
await RefreshNewsAsync(forceRefresh: false);
await RefreshNewsAsync(forceRefresh: true);
}
private void OnNewsItem1PointerPressed(object? sender, PointerPressedEventArgs e)
@@ -290,10 +273,9 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
private async Task ApplySnapshotAsync(DailyNewsSnapshot snapshot, CancellationToken cancellationToken)
{
var desiredCount = ResolveDesiredNewsItemCount();
var items = snapshot.Items is null
? []
: snapshot.Items.Take(desiredCount).ToArray();
: snapshot.Items.Take(2).ToArray();
_activeNewsItems = items;
var item1 = items.Length > 0 ? items[0] : null;
@@ -308,52 +290,27 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
_newsUrls.Add(NormalizeHttpUrl(item.Url));
}
RenderExtraNewsRows(items.Skip(2).ToArray());
RenderExtraNewsRows([]);
UpdateNewsInteractionState();
StatusTextBlock.IsVisible = false;
UpdateAdaptiveLayout();
var loadTasks = items
.Select(item => TryDownloadBitmapAsync(item.ImageUrl, cancellationToken))
.ToArray();
var loadTasks = new[]
{
TryDownloadBitmapAsync(item1?.ImageUrl, cancellationToken),
TryDownloadBitmapAsync(item2?.ImageUrl, cancellationToken)
};
var bitmaps = await Task.WhenAll(loadTasks);
if (cancellationToken.IsCancellationRequested || !_isAttached)
{
foreach (var bitmap in bitmaps)
{
bitmap?.Dispose();
}
bitmaps[0]?.Dispose();
bitmaps[1]?.Dispose();
return;
}
var consumed = new bool[bitmaps.Length];
Bitmap? TakeBitmapAt(int index)
{
if (index < 0 || index >= bitmaps.Length)
{
return null;
}
consumed[index] = true;
return bitmaps[index];
}
SetNewsBitmap(0, TakeBitmapAt(0));
SetNewsBitmap(1, TakeBitmapAt(1));
for (var rowIndex = 0; rowIndex < _extraNewsRows.Count; rowIndex++)
{
SetExtraNewsBitmap(rowIndex, TakeBitmapAt(rowIndex + 2));
}
for (var i = 0; i < bitmaps.Length; i++)
{
if (!consumed[i])
{
bitmaps[i]?.Dispose();
}
}
SetNewsBitmap(0, bitmaps[0]);
SetNewsBitmap(1, bitmaps[1]);
}
private void ApplyLoadingState()
@@ -389,24 +346,7 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
private int ResolveDesiredNewsItemCount()
{
var span = ResolveCurrentCellSpan();
var baseEquivalentHeight = span.HeightCells * (double)BaseWidthCells / Math.Max(BaseWidthCells, span.WidthCells);
var effectiveHeightCells = (int)Math.Round(baseEquivalentHeight, MidpointRounding.AwayFromZero);
return Math.Clamp(Math.Max(BaseHeightCells, effectiveHeightCells), 2, 12);
}
private (int WidthCells, int HeightCells) ResolveCurrentCellSpan()
{
var pitch = Math.Max(1, _currentCellSize);
var totalWidth = Bounds.Width > 1 ? Bounds.Width : _currentCellSize * BaseWidthCells;
var totalHeight = Bounds.Height > 1 ? Bounds.Height : _currentCellSize * BaseHeightCells;
var normalizedWidth = totalWidth + Math.Clamp(pitch * 0.20, 6, 16);
var normalizedHeight = totalHeight + Math.Clamp(pitch * 0.18, 4, 12);
var widthCells = Math.Max(BaseWidthCells, (int)Math.Round(normalizedWidth / pitch, MidpointRounding.AwayFromZero));
var heightCells = Math.Max(BaseHeightCells, (int)Math.Round(normalizedHeight / pitch, MidpointRounding.AwayFromZero));
return (widthCells, heightCells);
return 2;
}
private void UpdateHotHeadlineText(string? title)
@@ -558,7 +498,7 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
RefreshButton.Height = refreshHeight;
RefreshButton.Width = refreshWidth;
RefreshButton.CornerRadius = new CornerRadius(refreshHeight / 2d);
RefreshGlyphTextBlock.FontSize = Math.Clamp(19 * scale, 11, 24);
RefreshGlyphIcon.FontSize = Math.Clamp(19 * scale, 11, 24);
RefreshLabelTextBlock.FontSize = Math.Clamp(22 * scale, 11, 29);
var imageWidth = Math.Clamp(totalWidth * 0.20, 60, 170);
@@ -621,7 +561,7 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
{
RefreshButton.IsEnabled = !_isRefreshing;
RefreshButton.Opacity = _isAttached ? 1.0 : 0.85;
RefreshGlyphTextBlock.Opacity = _isRefreshing ? 0.56 : 1.0;
RefreshGlyphIcon.Opacity = _isRefreshing ? 0.56 : 1.0;
RefreshLabelTextBlock.Opacity = _isRefreshing ? 0.56 : 1.0;
}
@@ -774,6 +714,60 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
}
}
private void ApplyAutoRotateSettings()
{
var enabled = true;
var intervalMinutes = 60;
try
{
var snapshot = _settingsService.Load();
enabled = snapshot.CnrDailyNewsAutoRotateEnabled;
intervalMinutes = NormalizeAutoRotateIntervalMinutes(snapshot.CnrDailyNewsAutoRotateIntervalMinutes);
}
catch
{
// Keep fallback defaults.
}
_autoRotateEnabled = enabled;
_refreshTimer.Interval = TimeSpan.FromMinutes(intervalMinutes);
if (!_isAttached)
{
return;
}
if (_autoRotateEnabled)
{
if (!_refreshTimer.IsEnabled)
{
_refreshTimer.Start();
}
}
else if (_refreshTimer.IsEnabled)
{
_refreshTimer.Stop();
}
}
private static int NormalizeAutoRotateIntervalMinutes(int minutes)
{
if (minutes <= 0)
{
return 60;
}
if (SupportedAutoRotateIntervalsMinutes.Contains(minutes))
{
return minutes;
}
return SupportedAutoRotateIntervalsMinutes
.OrderBy(value => Math.Abs(value - minutes))
.FirstOrDefault(60);
}
private void CancelRefreshRequest()
{
var cts = Interlocked.Exchange(ref _refreshCts, null);

View File

@@ -0,0 +1,87 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignWidth="420"
d:DesignHeight="300"
x:Class="LanMountainDesktop.Views.Components.DailySentenceSettingsWindow">
<Border Background="{DynamicResource AdaptiveBackgroundBrush}"
Padding="16">
<Grid RowDefinitions="Auto,Auto,*"
RowSpacing="10">
<TextBlock x:Name="TitleTextBlock"
Text="Daily sentence settings"
FontSize="18"
FontWeight="SemiBold"
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
<TextBlock x:Name="DescriptionTextBlock"
Grid.Row="1"
Text="Configure auto-rotation and refresh interval."
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
<ScrollViewer Grid.Row="2"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="10"
Margin="0,0,6,0">
<Border Background="{DynamicResource AdaptiveSurfaceRaisedBrush}"
BorderBrush="{DynamicResource AdaptiveButtonBorderBrush}"
BorderThickness="1"
CornerRadius="12"
Padding="12">
<StackPanel Spacing="6">
<TextBlock x:Name="AutoRotateLabelTextBlock"
Text="Auto-rotation"
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
<CheckBox x:Name="AutoRotateCheckBox"
Content="Enable auto-rotation"
Checked="OnAutoRotateChanged"
Unchecked="OnAutoRotateChanged" />
</StackPanel>
</Border>
<Border x:Name="FrequencyCardBorder"
Background="{DynamicResource AdaptiveSurfaceRaisedBrush}"
BorderBrush="{DynamicResource AdaptiveButtonBorderBrush}"
BorderThickness="1"
CornerRadius="12"
Padding="12"
IsVisible="False">
<StackPanel Spacing="6">
<TextBlock x:Name="FrequencyLabelTextBlock"
Text="Rotation interval"
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
<ComboBox x:Name="FrequencyComboBox"
HorizontalAlignment="Stretch"
MinWidth="0"
SelectionChanged="OnFrequencySelectionChanged">
<ComboBoxItem x:Name="Frequency5mItem"
Tag="5"
Content="5 min" />
<ComboBoxItem x:Name="Frequency10mItem"
Tag="10"
Content="10 min" />
<ComboBoxItem x:Name="Frequency40mItem"
Tag="40"
Content="40 min" />
<ComboBoxItem x:Name="Frequency1hItem"
Tag="60"
Content="1 hour" />
<ComboBoxItem x:Name="Frequency12hItem"
Tag="720"
Content="12 hours" />
<ComboBoxItem x:Name="Frequency24hItem"
Tag="1440"
Content="24 hours" />
</ComboBox>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</Grid>
</Border>
</UserControl>

View File

@@ -0,0 +1,137 @@
using System;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components;
public partial class DailySentenceSettingsWindow : UserControl
{
private static readonly int[] SupportedIntervals = [5, 10, 40, 60, 720, 1440];
private readonly AppSettingsService _appSettingsService = new();
private readonly LocalizationService _localizationService = new();
private bool _suppressEvents;
private string _languageCode = "zh-CN";
public event EventHandler? SettingsChanged;
public DailySentenceSettingsWindow()
{
InitializeComponent();
LoadState();
ApplyLocalization();
}
private void LoadState()
{
var snapshot = _appSettingsService.Load();
_languageCode = _localizationService.NormalizeLanguageCode(snapshot.LanguageCode);
var enabled = snapshot.DailySentenceAutoRotateEnabled;
var interval = NormalizeInterval(snapshot.DailySentenceAutoRotateIntervalMinutes);
_suppressEvents = true;
AutoRotateCheckBox.IsChecked = enabled;
SelectInterval(interval);
FrequencyCardBorder.IsVisible = enabled;
_suppressEvents = false;
}
private void ApplyLocalization()
{
TitleTextBlock.Text = L("daily_sentence.settings.title", "Daily sentence settings");
DescriptionTextBlock.Text = L("daily_sentence.settings.desc", "Configure auto-rotation and refresh interval.");
AutoRotateLabelTextBlock.Text = L("daily_sentence.settings.auto_rotate_label", "Auto-rotation");
AutoRotateCheckBox.Content = L("daily_sentence.settings.auto_rotate_enabled", "Enable auto-rotation");
FrequencyLabelTextBlock.Text = L("daily_sentence.settings.frequency_label", "Rotation interval");
Frequency5mItem.Content = L("daily_sentence.settings.frequency_5m", "5 min");
Frequency10mItem.Content = L("daily_sentence.settings.frequency_10m", "10 min");
Frequency40mItem.Content = L("daily_sentence.settings.frequency_40m", "40 min");
Frequency1hItem.Content = L("daily_sentence.settings.frequency_1h", "1 hour");
Frequency12hItem.Content = L("daily_sentence.settings.frequency_12h", "12 hours");
Frequency24hItem.Content = L("daily_sentence.settings.frequency_24h", "24 hours");
}
private void OnAutoRotateChanged(object? sender, RoutedEventArgs e)
{
_ = sender;
_ = e;
if (_suppressEvents)
{
return;
}
var enabled = AutoRotateCheckBox.IsChecked == true;
FrequencyCardBorder.IsVisible = enabled;
SaveState();
}
private void OnFrequencySelectionChanged(object? sender, SelectionChangedEventArgs e)
{
_ = sender;
_ = e;
if (_suppressEvents)
{
return;
}
SaveState();
}
private void SaveState()
{
var snapshot = _appSettingsService.Load();
snapshot.DailySentenceAutoRotateEnabled = AutoRotateCheckBox.IsChecked == true;
snapshot.DailySentenceAutoRotateIntervalMinutes = GetSelectedInterval();
_appSettingsService.Save(snapshot);
SettingsChanged?.Invoke(this, EventArgs.Empty);
}
private int GetSelectedInterval()
{
if (FrequencyComboBox.SelectedItem is ComboBoxItem item &&
item.Tag is string tagText &&
int.TryParse(tagText, out var minutes))
{
return NormalizeInterval(minutes);
}
return 60;
}
private void SelectInterval(int intervalMinutes)
{
var selected = FrequencyComboBox.Items
.OfType<ComboBoxItem>()
.FirstOrDefault(item =>
item.Tag is string tagText &&
int.TryParse(tagText, out var minutes) &&
minutes == intervalMinutes);
FrequencyComboBox.SelectedItem = selected ?? FrequencyComboBox.Items.OfType<ComboBoxItem>().FirstOrDefault();
}
private static int NormalizeInterval(int minutes)
{
if (minutes <= 0)
{
return 60;
}
if (SupportedIntervals.Contains(minutes))
{
return minutes;
}
return SupportedIntervals
.OrderBy(value => Math.Abs(value - minutes))
.FirstOrDefault(60);
}
private string L(string key, string fallback)
{
return _localizationService.GetString(_languageCode, key, fallback);
}
}

View File

@@ -35,7 +35,8 @@
Margin="16,14,16,14"
RowDefinitions="Auto,*,Auto"
RowSpacing="8">
<Grid Grid.Row="0"
<Grid x:Name="HeaderGrid"
Grid.Row="0"
ColumnDefinitions="Auto,*,Auto"
ColumnSpacing="8">
<TextBlock x:Name="DayTextBlock"
@@ -80,7 +81,8 @@
</Button>
</Grid>
<StackPanel Grid.Row="1"
<StackPanel x:Name="SentenceStack"
Grid.Row="1"
VerticalAlignment="Center"
Spacing="8">
<TextBlock x:Name="SentenceTextBlock"

View File

@@ -2,6 +2,7 @@ using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
@@ -37,6 +38,7 @@ public partial class DailySentenceWidget : UserControl, IDesktopComponentWidget,
private const double BaseCellSize = 48d;
private const int BaseWidthCells = 4;
private const int BaseHeightCells = 2;
private static readonly int[] SupportedAutoRotateIntervalsMinutes = [5, 10, 40, 60, 720, 1440];
private readonly DispatcherTimer _refreshTimer = new()
{
@@ -54,6 +56,7 @@ public partial class DailySentenceWidget : UserControl, IDesktopComponentWidget,
private double _currentCellSize = BaseCellSize;
private bool _isAttached;
private bool _isRefreshing;
private bool _autoRotateEnabled = true;
public DailySentenceWidget()
{
@@ -75,6 +78,7 @@ public partial class DailySentenceWidget : UserControl, IDesktopComponentWidget,
ApplyCellSize(_currentCellSize);
UpdateLanguageCode();
ApplyAutoRotateSettings();
UpdateDateText();
ApplyLoadingState();
UpdateRefreshButtonState();
@@ -98,6 +102,7 @@ public partial class DailySentenceWidget : UserControl, IDesktopComponentWidget,
public void RefreshFromSettings()
{
_recommendationService.ClearCache();
ApplyAutoRotateSettings();
if (_isAttached)
{
_ = RefreshSentenceAsync(forceRefresh: true);
@@ -107,8 +112,8 @@ public partial class DailySentenceWidget : UserControl, IDesktopComponentWidget,
private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
_isAttached = true;
ApplyAutoRotateSettings();
UpdateRefreshButtonState();
_refreshTimer.Start();
_ = RefreshSentenceAsync(forceRefresh: false);
}
@@ -139,7 +144,7 @@ public partial class DailySentenceWidget : UserControl, IDesktopComponentWidget,
private async void OnRefreshTimerTick(object? sender, EventArgs e)
{
await RefreshSentenceAsync(forceRefresh: false);
await RefreshSentenceAsync(forceRefresh: true);
}
private void OnSourceTextBlockPointerPressed(object? sender, Avalonia.Input.PointerPressedEventArgs e)
@@ -400,6 +405,7 @@ public partial class DailySentenceWidget : UserControl, IDesktopComponentWidget,
Math.Clamp(16 * scale, 8, 28),
Math.Clamp(14 * scale, 7, 24));
ContentGrid.RowSpacing = Math.Clamp(8 * scale, 4, 12);
HeaderGrid.ColumnSpacing = Math.Clamp(8 * scale, 4, 14);
var refreshSize = Math.Clamp(42 * scale, 24, 54);
RefreshButton.Width = refreshSize;
@@ -409,14 +415,36 @@ public partial class DailySentenceWidget : UserControl, IDesktopComponentWidget,
var innerWidth = Math.Max(100, totalWidth - ContentGrid.Margin.Left - ContentGrid.Margin.Right);
var innerHeight = Math.Max(56, totalHeight - ContentGrid.Margin.Top - ContentGrid.Margin.Bottom);
var rowSpacingTotal = ContentGrid.RowSpacing * 2d;
var availableRowsHeight = Math.Max(1, innerHeight - rowSpacingTotal);
var topRowHeight = Math.Max(20, innerHeight * 0.22);
var bottomRowHeight = Math.Max(14, innerHeight * 0.14);
var middleHeight = Math.Max(24, innerHeight - topRowHeight - bottomRowHeight - ContentGrid.RowSpacing * 2);
var topRowHeight = Math.Clamp(innerHeight * 0.24, 18, 98);
var bottomRowHeight = Math.Clamp(innerHeight * 0.15, 11, 54);
var minTopRowHeight = Math.Max(Math.Clamp(14 * scale, 10, 28), refreshSize * 0.60);
var minBottomRowHeight = Math.Clamp(10 * scale, 7, 22);
var minMiddleRowHeight = Math.Clamp(22 * scale, 14, 46);
if (topRowHeight + bottomRowHeight + minMiddleRowHeight > availableRowsHeight)
{
var scaling = availableRowsHeight / Math.Max(1, topRowHeight + bottomRowHeight + minMiddleRowHeight);
topRowHeight = Math.Max(minTopRowHeight, topRowHeight * scaling);
bottomRowHeight = Math.Max(minBottomRowHeight, bottomRowHeight * scaling);
}
var middleHeight = Math.Max(
minMiddleRowHeight,
availableRowsHeight - topRowHeight - bottomRowHeight);
if (ContentGrid.RowDefinitions.Count >= 3)
{
ContentGrid.RowDefinitions[0].Height = new GridLength(topRowHeight, GridUnitType.Pixel);
ContentGrid.RowDefinitions[1].Height = new GridLength(middleHeight, GridUnitType.Pixel);
ContentGrid.RowDefinitions[2].Height = new GridLength(bottomRowHeight, GridUnitType.Pixel);
}
var topTextWidth = Math.Max(76, innerWidth - refreshSize - ContentGrid.RowSpacing);
var dayWidth = Math.Max(20, topTextWidth * 0.16);
var monthYearWidth = Math.Max(48, topTextWidth - dayWidth - 6 * scale);
var dayWidth = Math.Clamp(topTextWidth * 0.18, 20, Math.Max(24, topTextWidth * 0.32));
var monthYearWidth = Math.Max(48, topTextWidth - dayWidth - Math.Clamp(6 * scale, 2, 12));
DayTextBlock.MaxWidth = dayWidth;
MonthYearTextBlock.MaxWidth = monthYearWidth;
@@ -448,17 +476,50 @@ public partial class DailySentenceWidget : UserControl, IDesktopComponentWidget,
MonthYearTextBlock.FontWeight = monthLayout.Weight;
MonthYearTextBlock.LineHeight = monthLayout.LineHeight;
var sentenceLineLimit = innerHeight < _currentCellSize * 1.78 ? 2 : 3;
var sentenceHeight = Math.Max(16, middleHeight * 0.66);
var translationHeight = Math.Max(14, middleHeight - sentenceHeight - Math.Clamp(8 * scale, 3, 12));
var sentenceTextDemand = Math.Clamp(NormalizeCompactText(SentenceTextBlock.Text).Length, 12, 360);
var translationTextDemand = Math.Clamp(NormalizeCompactText(TranslationTextBlock.Text).Length, 8, 260);
var sentenceMinLines = innerHeight >= _currentCellSize * 1.78 ? 2 : 1;
var sentenceMaxLines = innerHeight >= _currentCellSize * 2.7
? 4
: innerHeight >= _currentCellSize * 1.92
? 3
: 2;
var translationMaxLines = innerHeight >= _currentCellSize * 2.35 ? 3 : 2;
var sentenceMinFont = Math.Clamp(23 * scale, 10, 42);
var translationMinFont = Math.Clamp(16 * scale, 8.5, 30);
var sentenceMinHeight = sentenceMinFont * 1.06 * sentenceMinLines;
var translationMinHeight = translationMinFont * 1.06;
var bodyGap = Math.Clamp(8 * scale, 3, 12);
SentenceStack.Spacing = bodyGap;
var minBodyHeight = sentenceMinHeight + translationMinHeight + bodyGap;
double sentenceHeight;
double translationHeight;
if (middleHeight <= minBodyHeight + 0.6)
{
var compression = middleHeight / Math.Max(1, minBodyHeight);
sentenceHeight = Math.Max(10, sentenceMinHeight * compression);
translationHeight = Math.Max(8, translationMinHeight * compression);
}
else
{
var extraHeight = middleHeight - minBodyHeight;
var sentenceWeight = sentenceTextDemand + 16d;
var translationWeight = translationTextDemand + 8d;
var totalWeight = Math.Max(1d, sentenceWeight + translationWeight);
sentenceHeight = sentenceMinHeight + extraHeight * (sentenceWeight / totalWeight);
translationHeight = translationMinHeight + extraHeight * (translationWeight / totalWeight);
}
var sentenceLayout = FitAdaptiveTextLayout(
SentenceTextBlock.Text,
innerWidth,
sentenceHeight,
minLines: 1,
maxLines: sentenceLineLimit,
minFontSize: Math.Clamp(23 * scale, 10, 42),
minLines: sentenceMinLines,
maxLines: sentenceMaxLines,
minFontSize: sentenceMinFont,
maxFontSize: Math.Clamp(58 * scale, 18, 80),
weightCandidates: HeadlineWeightCandidates,
lineHeightFactor: 1.06);
@@ -473,8 +534,8 @@ public partial class DailySentenceWidget : UserControl, IDesktopComponentWidget,
innerWidth,
translationHeight,
minLines: 1,
maxLines: 2,
minFontSize: Math.Clamp(16 * scale, 8.5, 30),
maxLines: translationMaxLines,
minFontSize: translationMinFont,
maxFontSize: Math.Clamp(40 * scale, 12, 54),
weightCandidates: BodyWeightCandidates,
lineHeightFactor: 1.06);
@@ -483,6 +544,8 @@ public partial class DailySentenceWidget : UserControl, IDesktopComponentWidget,
TranslationTextBlock.FontSize = translationLayout.FontSize;
TranslationTextBlock.FontWeight = translationLayout.Weight;
TranslationTextBlock.LineHeight = translationLayout.LineHeight;
SentenceTextBlock.MinHeight = Math.Max(0, sentenceLayout.LineHeight * Math.Min(sentenceLayout.MaxLines, Math.Max(1, sentenceMinLines)));
TranslationTextBlock.MinHeight = Math.Max(0, translationLayout.LineHeight);
var sourceLayout = FitAdaptiveTextLayout(
SourceTextBlock.Text,
@@ -532,6 +595,60 @@ public partial class DailySentenceWidget : UserControl, IDesktopComponentWidget,
}
}
private void ApplyAutoRotateSettings()
{
var enabled = true;
var intervalMinutes = 60;
try
{
var snapshot = _settingsService.Load();
enabled = snapshot.DailySentenceAutoRotateEnabled;
intervalMinutes = NormalizeAutoRotateIntervalMinutes(snapshot.DailySentenceAutoRotateIntervalMinutes);
}
catch
{
// Keep fallback defaults.
}
_autoRotateEnabled = enabled;
_refreshTimer.Interval = TimeSpan.FromMinutes(intervalMinutes);
if (!_isAttached)
{
return;
}
if (_autoRotateEnabled)
{
if (!_refreshTimer.IsEnabled)
{
_refreshTimer.Start();
}
}
else if (_refreshTimer.IsEnabled)
{
_refreshTimer.Stop();
}
}
private static int NormalizeAutoRotateIntervalMinutes(int minutes)
{
if (minutes <= 0)
{
return 60;
}
if (SupportedAutoRotateIntervalsMinutes.Contains(minutes))
{
return minutes;
}
return SupportedAutoRotateIntervalsMinutes
.OrderBy(value => Math.Abs(value - minutes))
.FirstOrDefault(60);
}
private void UpdateDateText()
{
var now = DateTime.Now;

View File

@@ -725,9 +725,22 @@ public partial class MainWindow
return;
}
if (placement.ComponentId == BuiltInComponentIds.DesktopDailySentence)
{
OpenDailySentenceComponentSettings();
return;
}
if (placement.ComponentId == BuiltInComponentIds.DesktopCnrDailyNews)
{
OpenCnrDailyNewsComponentSettings();
return;
}
if (placement.ComponentId == BuiltInComponentIds.DesktopStudyEnvironment)
{
OpenStudyEnvironmentComponentSettings();
return;
}
}
@@ -827,6 +840,38 @@ public partial class MainWindow
ComponentSettingsWindow.Opacity = 1;
}
private void OpenDailySentenceComponentSettings()
{
if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null)
{
return;
}
var settingsContent = new DailySentenceSettingsWindow();
settingsContent.SettingsChanged += OnDailySentenceSettingsChanged;
ComponentSettingsContentHost.Content = settingsContent;
ComponentSettingsWindow.IsVisible = true;
ComponentSettingsWindow.Opacity = 0;
ComponentSettingsWindow.Opacity = 1;
}
private void OpenCnrDailyNewsComponentSettings()
{
if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null)
{
return;
}
var settingsContent = new CnrDailyNewsSettingsWindow();
settingsContent.SettingsChanged += OnCnrDailyNewsSettingsChanged;
ComponentSettingsContentHost.Content = settingsContent;
ComponentSettingsWindow.IsVisible = true;
ComponentSettingsWindow.Opacity = 0;
ComponentSettingsWindow.Opacity = 1;
}
private void OnClassScheduleSettingsChanged(object? sender, EventArgs e)
{
if (_selectedDesktopComponentHost is null)
@@ -931,6 +976,54 @@ public partial class MainWindow
PersistSettings();
}
private void OnDailySentenceSettingsChanged(object? sender, EventArgs e)
{
_ = sender;
_ = e;
foreach (var pageGrid in _desktopPageComponentGrids.Values)
{
foreach (var host in pageGrid.Children.OfType<Border>())
{
if (!host.Classes.Contains(DesktopComponentHostClass))
{
continue;
}
if (TryGetContentHost(host)?.Child is DailySentenceWidget widget)
{
widget.RefreshFromSettings();
}
}
}
PersistSettings();
}
private void OnCnrDailyNewsSettingsChanged(object? sender, EventArgs e)
{
_ = sender;
_ = e;
foreach (var pageGrid in _desktopPageComponentGrids.Values)
{
foreach (var host in pageGrid.Children.OfType<Border>())
{
if (!host.Classes.Contains(DesktopComponentHostClass))
{
continue;
}
if (TryGetContentHost(host)?.Child is CnrDailyNewsWidget widget)
{
widget.RefreshFromSettings();
}
}
}
PersistSettings();
}
private void CloseComponentSettingsWindow()
{
if (ComponentSettingsWindow is null)
@@ -963,6 +1056,16 @@ public partial class MainWindow
worldClockSettingsWindow.SettingsChanged -= OnWorldClockSettingsChanged;
}
if (ComponentSettingsContentHost?.Content is DailySentenceSettingsWindow dailySentenceSettingsWindow)
{
dailySentenceSettingsWindow.SettingsChanged -= OnDailySentenceSettingsChanged;
}
if (ComponentSettingsContentHost?.Content is CnrDailyNewsSettingsWindow cnrDailyNewsSettingsWindow)
{
cnrDailyNewsSettingsWindow.SettingsChanged -= OnCnrDailyNewsSettingsChanged;
}
ComponentSettingsWindow.Opacity = 0;
DispatcherTimer.RunOnce(() =>
@@ -1374,6 +1477,14 @@ public partial class MainWindow
new ComponentScaleRule(WidthUnit: 2, HeightUnit: 1, MinScale: 2));
}
if (string.Equals(componentId, BuiltInComponentIds.DesktopCnrDailyNews, StringComparison.OrdinalIgnoreCase))
{
// Keep CNR widget at a 2:1 ratio: 4x2, 6x3, 8x4...
return SnapSpanToScaleRules(
span,
new ComponentScaleRule(WidthUnit: 2, HeightUnit: 1, MinScale: 2));
}
if (string.Equals(componentId, BuiltInComponentIds.DesktopStudyNoiseCurve, StringComparison.OrdinalIgnoreCase))
{
// Keep noise curve widget in a 2:1 ratio with minimum 4x2.