This commit is contained in:
lincube
2026-03-24 17:47:54 +08:00
parent af2e7b4f2f
commit a0bb83c743
23 changed files with 2041 additions and 104 deletions

View File

@@ -58,14 +58,16 @@
BorderThickness="1"
Foreground="#bb5649"
Focusable="False"
ToolTip.Tip="刷新新闻"
ToolTip.Tip="刷新今日新闻"
Click="OnRefreshButtonClick">
<StackPanel Orientation="Horizontal" Spacing="4">
<fi:SymbolIcon Symbol="ArrowSync"
<fi:SymbolIcon x:Name="RefreshIcon"
Symbol="ArrowSync"
IconVariant="Regular"
FontSize="14"
Foreground="#bb5649" />
<TextBlock Text="刷新"
<TextBlock x:Name="RefreshButtonText"
Text="刷新"
FontSize="13"
VerticalAlignment="Center" />
</StackPanel>

View File

@@ -625,13 +625,84 @@ public partial class JuyaNewsWidget : UserControl, IDesktopComponentWidget
return;
}
_cachedNews.Clear();
_loadedDates.Clear();
_dailyViews.Clear();
NewsStackPanel.Children.Clear();
_earliestLoadedDate = DateTime.Today;
_isLoading = true;
RefreshButtonText.Text = "刷新中...";
RefreshIcon.IsEnabled = false;
await LoadInitialNewsAsync();
try
{
var allNews = await FetchJuyaNewsAsync();
if (!_isAttached)
{
return;
}
var today = DateTime.Today;
var todayNews = allNews.FirstOrDefault(n => n.Date.Date == today);
if (todayNews != null)
{
_cachedNews[today] = todayNews;
await Dispatcher.UIThread.InvokeAsync(() =>
{
if (!_isAttached) return;
var existingIndex = _loadedDates.IndexOf(today);
if (existingIndex >= 0 && _dailyViews.Count > existingIndex)
{
var oldView = _dailyViews[existingIndex];
var insertIndex = NewsStackPanel.Children.IndexOf(oldView);
if (insertIndex >= 0)
{
NewsStackPanel.Children.RemoveAt(insertIndex);
_dailyViews.RemoveAt(existingIndex);
var newView = new DailyNewsView(todayNews, _isNightVisual);
newView.CoverImageClicked += (s, e) => TryOpenUrl(todayNews.IssueUrl);
NewsStackPanel.Children.Insert(insertIndex, newView);
_dailyViews.Insert(existingIndex, newView);
}
}
else
{
var newView = new DailyNewsView(todayNews, _isNightVisual);
newView.CoverImageClicked += (s, e) => TryOpenUrl(todayNews.IssueUrl);
NewsStackPanel.Children.Insert(0, newView);
_dailyViews.Insert(0, newView);
_loadedDates.Insert(0, today);
}
RefreshButtonText.Text = "刷新";
RefreshIcon.IsEnabled = true;
UpdateAdaptiveLayout();
});
}
else
{
await Dispatcher.UIThread.InvokeAsync(() =>
{
RefreshButtonText.Text = "刷新";
RefreshIcon.IsEnabled = true;
});
}
}
catch
{
await Dispatcher.UIThread.InvokeAsync(() =>
{
RefreshButtonText.Text = "刷新";
RefreshIcon.IsEnabled = true;
});
}
finally
{
_isLoading = false;
}
}
private void TryOpenUrl(string? url)

View File

@@ -1,4 +1,4 @@
<UserControl xmlns="https://github.com/avaloniaui"
<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"
@@ -9,86 +9,107 @@
d:DesignHeight="480"
x:Class="LanMountainDesktop.Views.Components.WhiteboardWidget">
<Border x:Name="RootBorder"
Background="#F1F4F9"
CornerRadius="20"
ClipToBounds="True"
Padding="8">
<Grid RowDefinitions="*,Auto"
RowSpacing="8">
<Border x:Name="CanvasBorder"
Grid.Row="0"
Background="#FFFFFF"
BorderBrush="#24000000"
BorderThickness="1"
CornerRadius="14"
ClipToBounds="True">
<inking:InkCanvas x:Name="InkCanvas" />
</Border>
<Grid>
<Border x:Name="RootBorder"
Background="#F1F4F9"
CornerRadius="20"
ClipToBounds="True"
Padding="8">
<Grid RowDefinitions="*,Auto"
RowSpacing="8">
<Border x:Name="CanvasBorder"
Grid.Row="0"
Background="#FFFFFF"
BorderBrush="#24000000"
BorderThickness="1"
CornerRadius="14"
ClipToBounds="True">
<inking:InkCanvas x:Name="InkCanvas" />
</Border>
<Border x:Name="ToolbarBorder"
Grid.Row="1"
HorizontalAlignment="Center"
Background="#E6FFFFFF"
BorderBrush="#16000000"
<Border x:Name="ToolbarBorder"
Grid.Row="1"
HorizontalAlignment="Center"
Background="#E6FFFFFF"
BorderBrush="#16000000"
BorderThickness="1"
CornerRadius="14"
Padding="8,6">
<StackPanel x:Name="ToolbarButtonsPanel"
Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="8">
<Button x:Name="PenButton"
Width="30"
Height="30"
Padding="0"
CornerRadius="15"
ToolTip.Tip="Pen"
Click="OnPenButtonClick">
<fi:SymbolIcon x:Name="PenIcon"
Symbol="Pen"
IconVariant="Regular"
FontSize="14" />
</Button>
<Button x:Name="EraserButton"
Width="30"
Height="30"
Padding="0"
CornerRadius="15"
ToolTip.Tip="Eraser"
Click="OnEraserButtonClick">
<fi:SymbolIcon x:Name="EraserIcon"
Symbol="EraserTool"
IconVariant="Regular"
FontSize="14" />
</Button>
<Button x:Name="ClearButton"
Width="30"
Height="30"
Padding="0"
CornerRadius="15"
ToolTip.Tip="Clear"
Click="OnClearButtonClick">
<fi:SymbolIcon x:Name="ClearIcon"
Symbol="Delete"
IconVariant="Regular"
FontSize="14" />
</Button>
<Button x:Name="ExportButton"
Width="30"
Height="30"
Padding="0"
CornerRadius="15"
ToolTip.Tip="Export SVG"
Click="OnExportButtonClick">
<fi:SymbolIcon x:Name="ExportIcon"
Symbol="ArrowExport"
IconVariant="Regular"
FontSize="14" />
</Button>
</StackPanel>
</Border>
</Grid>
</Border>
<Popup x:Name="ColorPickerPopup"
Placement="Top"
PlacementTarget="{Binding #PenButton}"
IsLightDismissEnabled="True"
WindowManagerAddShadowHint="False">
<Border Background="{DynamicResource SystemControlBackgroundChromeMediumBrush}"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1"
CornerRadius="14"
Padding="8,6">
<StackPanel x:Name="ToolbarButtonsPanel"
Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="8">
<Button x:Name="PenButton"
Width="30"
Height="30"
Padding="0"
CornerRadius="15"
ToolTip.Tip="Pen"
Click="OnPenButtonClick">
<fi:SymbolIcon x:Name="PenIcon"
Symbol="Pen"
IconVariant="Regular"
FontSize="14" />
</Button>
<Button x:Name="EraserButton"
Width="30"
Height="30"
Padding="0"
CornerRadius="15"
ToolTip.Tip="Eraser"
Click="OnEraserButtonClick">
<fi:SymbolIcon x:Name="EraserIcon"
Symbol="EraserTool"
IconVariant="Regular"
FontSize="14" />
</Button>
<Button x:Name="ClearButton"
Width="30"
Height="30"
Padding="0"
CornerRadius="15"
ToolTip.Tip="Clear"
Click="OnClearButtonClick">
<fi:SymbolIcon x:Name="ClearIcon"
Symbol="Delete"
IconVariant="Regular"
FontSize="14" />
</Button>
<Button x:Name="ExportButton"
Width="30"
Height="30"
Padding="0"
CornerRadius="15"
ToolTip.Tip="Export SVG"
Click="OnExportButtonClick">
<fi:SymbolIcon x:Name="ExportIcon"
Symbol="ArrowExport"
IconVariant="Regular"
FontSize="14" />
</Button>
</StackPanel>
CornerRadius="8"
Padding="12">
<ColorView x:Name="InkColorPicker"
IsAlphaEnabled="False"
IsColorSpectrumVisible="True"
IsColorPaletteVisible="True"
IsHexInputVisible="True"
ColorChanged="OnColorPickerColorChanged" />
</Border>
</Grid>
</Border>
</Popup>
</Grid>
</UserControl>

View File

@@ -6,6 +6,7 @@ using System.Reflection;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Platform.Storage;
@@ -38,7 +39,7 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
private double _currentCellSize = 48;
private WhiteboardToolMode _toolMode = WhiteboardToolMode.Pen;
private bool? _isNightModeApplied;
private SKColor _currentInkColor = SKColors.Black;
private SKColor _selectedInkColor = SKColors.Black;
private string _componentId = BuiltInComponentIds.DesktopWhiteboard;
private string _placementId = string.Empty;
private int _noteRetentionDays = WhiteboardNoteRetentionPolicy.DefaultDays;
@@ -66,9 +67,22 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
ApplyCellSize(_currentCellSize);
RefreshFromSettings();
ApplyThemeVisual(force: true);
InitializeColorPicker();
SetToolMode(WhiteboardToolMode.Pen);
}
private void InitializeColorPicker()
{
if (InkColorPicker is not null)
{
InkColorPicker.Color = new Color(
_selectedInkColor.Alpha,
_selectedInkColor.Red,
_selectedInkColor.Green,
_selectedInkColor.Blue);
}
}
public int NoteRetentionDays => _noteRetentionDays;
private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
@@ -149,7 +163,6 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
}
_isNightModeApplied = isNightMode;
_currentInkColor = isNightMode ? SKColors.White : SKColors.Black;
RootBorder.Background = new SolidColorBrush(isNightMode ? Color.Parse("#FF181B22") : Color.Parse("#FFF1F4F9"));
CanvasBorder.Background = new SolidColorBrush(isNightMode ? Color.Parse("#FF000000") : Color.Parse("#FFFFFFFF"));
@@ -157,8 +170,6 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
ToolbarBorder.Background = new SolidColorBrush(isNightMode ? Color.Parse("#1AFFFFFF") : Color.Parse("#E6FFFFFF"));
ToolbarBorder.BorderBrush = new SolidColorBrush(isNightMode ? Color.Parse("#26FFFFFF") : Color.Parse("#16000000"));
InkCanvas.AvaloniaSkiaInkCanvas.Settings.InkColor = _currentInkColor;
RecolorAllStrokes(_currentInkColor);
RefreshToolButtonVisuals();
}
@@ -204,6 +215,30 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
}
}
public void ForceSaveNote()
{
if (_disposed || !HasValidPersistenceContext())
{
return;
}
if (!_noteDirty)
{
return;
}
_noteDirty = false;
_noteSaveTimer.Stop();
var noteSnapshot = BuildNoteSnapshot();
try
{
_notePersistenceService.SaveNote(_componentId, _placementId, noteSnapshot, _noteRetentionDays);
}
catch
{
}
}
public void Dispose()
{
if (_disposed)
@@ -300,12 +335,22 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
if (mode == WhiteboardToolMode.Pen)
{
InkCanvas.AvaloniaSkiaInkCanvas.Settings.InkColor = _currentInkColor;
InkCanvas.AvaloniaSkiaInkCanvas.Settings.InkColor = _selectedInkColor;
}
RefreshToolButtonVisuals();
}
private void SetInkColor(SKColor color)
{
_selectedInkColor = color;
if (_toolMode == WhiteboardToolMode.Pen)
{
InkCanvas.AvaloniaSkiaInkCanvas.Settings.InkColor = _selectedInkColor;
}
RefreshToolButtonVisuals();
}
private void RefreshToolButtonVisuals()
{
var isNightMode = _isNightModeApplied ?? ResolveIsNightMode();
@@ -350,7 +395,27 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
private void OnPenButtonClick(object? sender, RoutedEventArgs e)
{
SetToolMode(WhiteboardToolMode.Pen);
if (_toolMode == WhiteboardToolMode.Pen && ColorPickerPopup is not null)
{
if (ColorPickerPopup.IsOpen)
{
ColorPickerPopup.Close();
}
else
{
ColorPickerPopup.Open();
}
}
else
{
SetToolMode(WhiteboardToolMode.Pen);
}
}
private void OnColorPickerColorChanged(object? sender, ColorChangedEventArgs e)
{
var color = e.NewColor;
SetInkColor(new SKColor(color.R, color.G, color.B, color.A));
}
private void OnEraserButtonClick(object? sender, RoutedEventArgs e)
@@ -509,14 +574,13 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
_noteDirty = false;
_noteSaveTimer.Stop();
var noteSnapshot = BuildNoteSnapshot();
var componentId = _componentId;
var placementId = _placementId;
var retentionDays = _noteRetentionDays;
_ = Task.Run(() => _notePersistenceService.SaveNote(
componentId,
placementId,
noteSnapshot,
retentionDays));
try
{
_notePersistenceService.SaveNote(_componentId, _placementId, noteSnapshot, _noteRetentionDays);
}
catch
{
}
}
private async void SchedulePersistedNoteLoad()
@@ -553,7 +617,6 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
{
ClearAllStrokes();
ApplyNoteSnapshot(noteSnapshot);
RecolorAllStrokes(_currentInkColor);
}
finally
{

View File

@@ -3276,4 +3276,19 @@ public partial class MainWindow
_isComponentLibraryComponentGestureActive = false;
ApplyComponentLibraryComponentOffset();
}
internal void SaveAllWhiteboardNotes()
{
foreach (var pageGrid in _desktopPageComponentGrids.Values)
{
foreach (var host in pageGrid.Children.OfType<Border>())
{
var contentHost = TryGetContentHost(host);
if (contentHost?.Child is WhiteboardWidget whiteboard)
{
whiteboard.ForceSaveNote();
}
}
}
}
}

View File

@@ -500,6 +500,7 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
var wasVisible = IsVisible;
var windowState = WindowState.ToString();
SaveAllWhiteboardNotes();
PersistSettings();
_componentEditorWindowService.Close();
if (_detachedComponentLibraryWindow is not null)