feat.数字时钟,白板功能修复

This commit is contained in:
lincube
2026-05-18 08:30:40 +08:00
parent 9404a0b347
commit 93758fc083
28 changed files with 1729 additions and 81 deletions

View File

@@ -1,9 +1,59 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sty="using:FluentAvalonia.Styling"
xmlns:fi="using:FluentIcons.Avalonia"
x:Class="LanMountainDesktop.AirAppHost.AirApp"
RequestedThemeVariant="Default">
<Application.Styles>
<FluentTheme />
<sty:FluentAvaloniaTheme />
<Style Selector="Window">
<Setter Property="FontFamily" Value="{DynamicResource AppFontFamily}" />
</Style>
<Style Selector="UserControl">
<Setter Property="FontFamily" Value="{DynamicResource AppFontFamily}" />
</Style>
<Style Selector="TextBlock">
<Setter Property="FontFeatures" Value="tnum" />
<Setter Property="FontWeight" Value="Normal" />
</Style>
<Style Selector="SelectableTextBlock">
<Setter Property="FontFeatures" Value="tnum" />
<Setter Property="FontWeight" Value="Normal" />
</Style>
<Style Selector="ScrollViewer">
<Setter Property="ScrollViewer.IsScrollInertiaEnabled" Value="False" />
</Style>
<Style Selector="fi|SymbolIcon">
<Setter Property="Foreground" Value="{DynamicResource AdaptiveTextPrimaryBrush}" />
<Setter Property="FontSize" Value="16" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
<Style Selector="fi|FluentIcon">
<Setter Property="Foreground" Value="{DynamicResource AdaptiveTextPrimaryBrush}" />
<Setter Property="FontSize" Value="16" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
<Style Selector="fi|SymbolIcon.icon-s, fi|FluentIcon.icon-s">
<Setter Property="FontSize" Value="12" />
</Style>
<Style Selector="fi|SymbolIcon.icon-m, fi|FluentIcon.icon-m">
<Setter Property="FontSize" Value="16" />
</Style>
<Style Selector="fi|SymbolIcon.icon-l, fi|FluentIcon.icon-l">
<Setter Property="FontSize" Value="20" />
</Style>
</Application.Styles>
<Application.Resources>

View File

@@ -6,7 +6,8 @@ public sealed record AirAppLaunchOptions(
string? SourceComponentId,
string? SourcePlacementId,
string? LauncherPipeName,
string? InstanceKey)
string? InstanceKey,
string? DataRoot)
{
public const string WorldClockAppId = "world-clock";
public const string WhiteboardAppId = "whiteboard";
@@ -28,6 +29,19 @@ public sealed record AirAppLaunchOptions(
continue;
}
var equalsIndex = key.IndexOf('=');
if (equalsIndex > 0)
{
var inlineValue = key[(equalsIndex + 1)..];
key = key[..equalsIndex].Trim();
if (!string.IsNullOrWhiteSpace(key))
{
values[key] = inlineValue;
}
continue;
}
if (index + 1 < args.Count && !args[index + 1].StartsWith("--", StringComparison.Ordinal))
{
values[key] = args[index + 1];
@@ -45,7 +59,8 @@ public sealed record AirAppLaunchOptions(
GetOptionalValue(values, "source-component-id"),
GetOptionalValue(values, "source-placement-id"),
GetOptionalValue(values, "launcher-pipe"),
GetOptionalValue(values, "instance-key"));
GetOptionalValue(values, "instance-key"),
GetOptionalValue(values, "data-root"));
}
private static string GetValue(IReadOnlyDictionary<string, string> values, string key, string fallback)

View File

@@ -14,6 +14,7 @@ public sealed partial class AirAppWindow : Window
{
private readonly AirAppLaunchOptions _options;
private readonly AirAppWindowDescriptor _descriptor;
private WhiteboardWidget? _whiteboardWidget;
private string _instanceKey = string.Empty;
public AirAppWindow()
@@ -117,6 +118,7 @@ public sealed partial class AirAppWindow : Window
? 4
: 2;
var widget = new WhiteboardWidget(baseWidthCells);
_whiteboardWidget = widget;
widget.SetComponentPlacementContext(componentId, _options.SourcePlacementId);
widget.SetSurfaceMode(
WhiteboardWidgetSurfaceMode.AirApp,
@@ -127,6 +129,9 @@ public sealed partial class AirAppWindow : Window
});
ContentHost.Content = widget;
AppLogger.Info(
"AirAppWindow",
$"Whiteboard content created. ComponentId='{componentId}'; PlacementId='{_options.SourcePlacementId ?? string.Empty}'.");
}
protected override void OnOpened(EventArgs e)
@@ -144,6 +149,7 @@ public sealed partial class AirAppWindow : Window
protected override void OnClosed(EventArgs e)
{
SaveAndDisposeWhiteboard();
_ = UnregisterWithLauncherAsync();
base.OnClosed(e);
}
@@ -158,9 +164,45 @@ public sealed partial class AirAppWindow : Window
private void OnCloseClick(object? sender, RoutedEventArgs e)
{
SaveWhiteboard();
Close();
}
private void SaveAndDisposeWhiteboard()
{
var widget = _whiteboardWidget;
if (widget is null)
{
return;
}
SaveWhiteboard();
if (ContentHost.Content == widget)
{
ContentHost.Content = null;
}
widget.Dispose();
_whiteboardWidget = null;
}
private void SaveWhiteboard()
{
if (_whiteboardWidget is null)
{
return;
}
try
{
_whiteboardWidget.ForceSaveNote();
}
catch (Exception ex)
{
AppLogger.Warn("AirAppWindow", "Failed to force-save whiteboard before closing Air APP.", ex);
}
}
private async Task RegisterWithLauncherAsync()
{
if (string.IsNullOrWhiteSpace(_options.LauncherPipeName))

View File

@@ -25,7 +25,11 @@ public sealed record AirAppWindowDescriptor(
return Standard(
"World Clock - Air APP",
"World Clock",
"Air APP");
"Air APP",
width: 360,
height: 220,
minWidth: 320,
minHeight: 220);
}
if (string.Equals(options.AppId, AirAppLaunchOptions.WhiteboardAppId, StringComparison.OrdinalIgnoreCase))

View File

@@ -25,5 +25,6 @@
<PackageReference Include="Avalonia.Fonts.Inter" />
<PackageReference Include="Avalonia.Themes.Fluent" />
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="FluentIcons.Avalonia" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,5 @@
using Avalonia;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.AirAppHost;
@@ -7,8 +8,22 @@ internal static class Program
[STAThread]
public static void Main(string[] args)
{
BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
AppLogger.Initialize();
AppDataPathProvider.Initialize(args);
RegisterGlobalExceptionLogging();
AppLogger.Info("AirAppHost", $"Starting. Args='{string.Join(" ", args)}'.");
try
{
BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
AppLogger.Info("AirAppHost", "Exited normally.");
}
catch (Exception ex)
{
AppLogger.Critical("AirAppHost", "Unhandled startup exception.", ex);
throw;
}
}
private static AppBuilder BuildAvaloniaApp()
@@ -18,4 +33,21 @@ internal static class Program
.WithInterFont()
.LogToTrace();
}
private static void RegisterGlobalExceptionLogging()
{
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
{
AppLogger.Critical(
"AirAppHost",
"Unhandled AppDomain exception.",
e.ExceptionObject as Exception);
};
TaskScheduler.UnobservedTaskException += (_, e) =>
{
AppLogger.Error("AirAppHost", "Unobserved task exception.", e.Exception);
e.SetObserved();
};
}
}

View File

@@ -2,26 +2,26 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="LanMountainDesktop.AirAppHost.WorldClockAirAppView">
<Grid RowDefinitions="*,Auto"
Margin="24,8,24,24">
Margin="18,0,18,16">
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="10">
Spacing="8">
<TextBlock x:Name="TimeTextBlock"
Text="00:00:00"
FontSize="58"
FontSize="42"
FontWeight="SemiBold"
LetterSpacing="0"
Foreground="{DynamicResource AirAppTitleTextBrush}"
HorizontalAlignment="Center" />
<TextBlock x:Name="DateTextBlock"
Text="0000-00-00"
FontSize="17"
FontSize="14"
FontWeight="Medium"
Foreground="{DynamicResource AirAppSecondaryTextBrush}"
HorizontalAlignment="Center" />
<TextBlock x:Name="TimeZoneTextBlock"
Text="Local Time"
FontSize="13"
FontSize="12"
Foreground="{DynamicResource AirAppSecondaryTextBrush}"
HorizontalAlignment="Center" />
</StackPanel>