合并对设置系统的更新 (#11)

* Add Windows system chrome patchers (Harmony)

Introduce support for toggling the system chrome on Windows using Harmony patchers. Adds Lib.Harmony.Thin to package props and project, new patcher infrastructure (ChromePatchState, PatcherEntrance) and two Harmony patches that disable FluentAvalonia's Windows chrome when configured. Program.cs now loads the chrome setting and installs patchers conditionally on Windows/x86-x64. Settings viewmodel and view updated: expose IsWindowsOs, require restart on appearance changes, migrate SettingsWindow to FAAppWindow and adapt titlebar/layout (include Windows caption placeholder and footer menu items). Also add a .gitkeep and a build log file.

* Refactor settings window UI and theming

Improve theming and layout for the Settings window and related services.

- MaterialSurfaceService: add special material parameters for SettingsWindowBackground (lower alpha, no blur) and avoid hot-switching real backdrops for non-settings windows.
- GlassEffectService: add AdaptiveSettingsWindowTintBrush + ResolveSettingsWindowTintAlpha to provide optional content tinting tied to system material mode.
- SettingsWindowService: refactor theme application into ApplyThemeVariantAndResources, ensure settings window material is applied at show/activate times, and tidy theme/resource application flow.
- SettingsWindow.axaml / .axaml.cs: restructure title bar (separate Grid.Row=0 border) and FANavigationView host, add pane-footer toggle button for :minimal layout, use dynamic corner radius resource, and update toggle/visibility/icon logic and responsive layout code.
- SettingsPages: remove some IconText usages and adjust margins; use DesignCornerRadiusLg for update card corner radius.
- Add NuGet.Config to set local globalPackagesFolder and ignore .nuget/packages in .gitignore.

These changes aim to improve visuals, avoid backdrop overdraw, and make the settings window behavior consistent across themes and layouts.

* Add localization and localize settings pages

Add many new localization keys (en-US and zh-CN) for notifications, developer tools, about page, status bar, and video wallpaper. Update Notification, Dev, About and StatusBar view models to use LocalizationService, expose localized ObservableProperties, and refresh localized text at construction. Localize selection options and test notification texts, and fix notification severity handling. Wire up XAML to the new localized properties (About/Dev/StatusBar pages) and update the settings page title for notifications. Also adjust copyright line generation and replace hardcoded placeholders with bound Watermark properties.

* Redesign settings window with fluent shell & search

Rebuild the settings window as a Fluent shell: adds a custom 48-DIP titlebar with Back, pane toggle, icon/title, search box, restart/more menu, and caption-button spacer; moves compact pane toggle into the titlebar and preserves FANavigationView as the primary navigation surface. Introduces a SettingsSearchService (with UI AutoComplete integration, search indexing, navigation-by-result, and search result highlighting) plus focused tests for search filtering and theme material normalization. Adds navigation history/back stack, updates SettingsViewModels for new bindings and localization keys, and updates General/Apearance pages to expose new strings and options. Implements an "auto" system material mode: default in AppSettingsSnapshot, new MaterialAuto constants and normalization/resolution logic in ThemeAppearanceValues, WindowMaterialService and MaterialSurfaceService adjustments to prefer Mica on Win11 and Acrylic on Win10 using TransparencyLevelHint. GlassEffectService and AppearanceThemeService updated to use effective material mode and to track live theme state changes. Adds localization entries (en-US, zh-CN), spec/tasks docs, and other UI/style tweaks to support the redesign.

* fix.修折叠与展开按钮

* Add OOBE startup presentation and settings merge

Introduce a new OOBE step for "Startup & Presentation" that exposes startup and UI preferences in OobeWindow (toggles for taskbar, slide/fade transitions, fused popup, and autostart). Add HostAppSettingsOobeMerger to read/write Host settings.json (PascalCase fields) and MergeStartupPresentation behavior, plus LauncherWindowsStartupService to sync the current Launcher into the Windows Run key on Windows. Wire UI handlers, persist choices on Next, and load defaults when entering the step. Include unit tests for the merger, adjust SettingsWindow navigation pane/toggle handling, and update docs/LAUNCHER.md to describe the new OOBE step and implementation files.

* Move whiteboard persistence to file storage

Switch whiteboard note storage from legacy DB rows to per-note JSON files and add migration support. Update WhiteboardNoteSnapshot schema (version bump, viewport, canvas, expires, PathSvgData) and change IWhiteboardNotePersistenceService.SaveNote to return bool to surface write failures (e.g. read-only files). Implement file-based WhiteboardNotePersistenceService with legacy DB migration/cleanup, retention handling, and logging. Add comprehensive unit tests for persistence, stroke path builder, SVG import and viewport helper. Also add ThirdParty/DotNetCampus.InkCanvas project and reference it in the main csproj, and bump PostHog package to 2.6.0.

* Introduce render gate and chart caching

Replace UI DispatcherTimer polling with a StudySnapshotRenderGate across multiple widgets to queue and apply only the latest analytics snapshot; components updated include StudyDeductionReasonsWidget, StudyEnvironmentWidget, StudyInterruptDensityWidget, StudyNoiseCurveWidget. Add StudySnapshotRenderGate implementation to coordinate rendering and monitoring leases and update subscription/lease lifecycle handling (subscribe/unsubscribe, Acquire/Dispose leases, Clear/Dispose gate). Rewrite chart controls (StudyNoiseCurveChartControl and StudyNoiseDistributionScatterChartControl) to use stable logical-time origins, split series into static vs dynamic tails, add geometry/sample caching, stable jitter/coordinate mapping helpers, and expose internal helpers & counts for testing. Add unit tests (StudyComponentRenderingTests) covering the render gate and chart behaviors (layer counts, logical X mapping, stable jitter, cache rebuild). These changes improve rendering correctness and performance by avoiding redundant renders and enabling deterministic chart layout.

* Use MaterialColorSnapshot in appearance flow

Introduce unified material/color spec and tests, and refactor appearance plumbing to use MaterialColorSnapshot as the single source of truth. Add .trae material-color-service spec/checklist/tasks and integration/unit tests for plugin mapping and appearance VM behavior. AppearanceChangedEvent extended with new appearance change flags and HasChanged logic. ComponentEditorMaterialThemeAdapter rewritten to accept MaterialColorSnapshot and derive palette from snapshot data. Simplify AppearanceSettingsPageViewModel and related view code: remove legacy preview/custom-seed UI logic, preserve material/color fields when updating theme or corner radius, and update save calls to use with-expressions. Update ComponentEditorWindow to use adapter-provided OnPrimary brush and minor docs updates.

* Add material color services, plugin DTOs, and tests

Introduce IPC wire-format appearance DTOs (PluginIsolation.Contracts) and clarify they are distinct from the runtime PluginSdk snapshot. Update PluginSdk comments to document the runtime-facing snapshot shape. Change ComponentColorSchemeHelper to use the HostMaterialColorProvider and add an overload that accepts a MaterialColorSnapshot. Add new services and pipelines (MaterialColorService, MaterialSurfaceService, WindowMaterialService, WallpaperColorPipeline) and refactor AppearanceThemeService to depend on MaterialColorService while removing legacy internal implementations. Add multiple unit tests (ComponentColorSchemeHelper, PluginAppearanceBoundary, SettingsCatalogService, WallpaperSettingsPageViewModel) and update localization resources with new material_color and wallpaper keys.

* Add CODE_WIKI and update localization

Add a comprehensive CODE_WIKI.md documenting project architecture, modules, startup flow, plugin system, testing and developer workflows. Update localization resources (en-US.json, zh-CN.json) with new/translated keys for wallpaper controls (custom color UI), material & color settings (semantic roles, surfaces, refresh/polling state), appearance (corner radius), status bar font size options, privacy policy text, component library labels, clock settings, and new language entry (Korean). Also modify settings-related ViewModels and Settings page views to surface these new features and texts (MaterialColorSettingsPageViewModel.cs, SettingsViewModels.cs, WallpaperSettingsPageViewModel.cs, MainWindow.SettingsHardCut.Stubs.cs, ComponentsSettingsPage.axaml, WallpaperSettingsPage.axaml).

* Add Data settings page and storage scanner

Introduce a new "Data" settings page to visualize and manage local app storage. Adds DataStorageService (scanning, disk info, clean operations), DataSettingsPageViewModel, XAML view and code-behind, and HexToColor/HexToBrush converters; registers converters in App.axaml. Also update localization strings for the new page and add icon mapping so the settings entry uses the Database icon. Enables per-category and global cleaning workflows and formatted size display.

* Add IPC backoff/retries and safer disposal

Introduce exponential backoff, jitter and retry logic across IPC components to improve robustness and avoid tight retry loops; make disposal idempotent and add connection guards. Key changes:
- LauncherCoordinatorIpcServer / LauncherIpcServer: add backoff constants, ComputeBackoff(), consecutive error tracking and delayed retries with jitter.
- LanMountainDesktopIpcClient / LauncherIpcClient: add connect retry loops, timeouts, delayed retries, improved error logging, and use ArrayPool for buffered async writes; ensure proper cleanup on failures.
- PublicIpcHostService: add disposed flag, guard OnPeerConnected and Dispose, and clear connected peers on dispose.
- Add many auto-generated commit analysis docs under docs/auto_commit_md and new scripts for analyzing/generating commit docs.
These changes aim to make IPC connection handling more resilient and resource-safe.

* Add preview controls and settings UI tweaks

Introduce GridPreviewControl and CornerRadiusPreviewControl for visual previews and wire them into the Components settings (add ScreenAspectRatio, CornerRadiusPreviewValue, and screen aspect init). Refactor ComponentsSettingsPage UI to show live previews. Improve DataSettingsPage layout and storage bar logic (use item percentages directly, include remaining segment, adjust visuals and visibility triggers). Simplify LauncherSettingsPage header/appearance layout. Add SECURITY_AUDIT_REPORT.md, analysis summary, mockup HTML, and a local .claude settings file.

* Add install checkpoint/resume and DDSS workflows

Introduce install checkpoint support and resume logic for updates, plus related locking and validation. Adds InstallCheckpoint model, AppJsonContext serialization, and UpdatePaths helpers for deployment lock, apply-in-progress lock and install-checkpoint path. UpdateEngineService gains checkpoint load/save/delete, incoming-state validation, resume logic for PLONDS and legacy updates, apply lock handling, and safer cleanup; ApplyPendingPlondsUpdateAsync and ApplyPendingUpdate flow updated accordingly. Add DeploymentLock contract and extend UpdateState with pause/resume/cancel helpers. Tests updated to cover stale/valid checkpoint resume and legacy/PLONDS flows. CI: enhance ddss-publish to detect release channel, validate S3 assets, prepare and atomically publish channel pointer; add ddss-rollback workflow to publish rollbacks; adjust plonds-build concurrency and release events.

* changed.更了好多

* fix.消息盒子媒体播放器组件服务修复

* change.重做天气,为回到系统提供自定义功能。

* feat.airapp与融合桌面

* feat.动画优化与更新界面

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

* feat.完善了时钟轻应用,为启动器提供了多语言支持

* feat.发布与打包优化

* changed.天气选项卡更新
This commit is contained in:
lincube
2026-05-19 07:55:21 +08:00
committed by GitHub
parent 458494d131
commit 7a70476ce8
904 changed files with 78052 additions and 18366 deletions

View File

@@ -4,12 +4,14 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:LanMountainDesktop.Launcher.Views"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:res="clr-namespace:LanMountainDesktop.Launcher.Resources"
mc:Ignorable="d"
d:DesignWidth="520"
d:DesignHeight="480"
x:Class="LanMountainDesktop.Launcher.Views.DataLocationPromptWindow"
x:DataType="views:DataLocationPromptWindow"
Title="Choose Data Location"
x:CompileBindings="False"
Title="{x:Static res:Strings.DataLocation_Title}"
Width="520"
Height="480"
CanResize="False"
@@ -24,11 +26,11 @@
</Grid.RenderTransform>
<Grid Margin="36" RowDefinitions="Auto,*,Auto">
<StackPanel Grid.Row="0" Spacing="8" Margin="0,0,0,20">
<TextBlock Text="Choose Data Location"
<TextBlock Text="{x:Static res:Strings.DataLocation_ChooseLocation}"
FontSize="22"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="Choose where launcher and desktop data should be stored. You can change this later in settings."
<TextBlock Text="{x:Static res:Strings.DataLocation_ChooseLocationDesc}"
FontSize="13"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
@@ -45,7 +47,7 @@
<fi:SymbolIcon Symbol="Important"
FontSize="16"
Foreground="{DynamicResource SystemFillColorCriticalBrush}" />
<TextBlock Text="App folder is not writable"
<TextBlock Text="{x:Static res:Strings.DataLocation_NotWritable}"
FontWeight="SemiBold"
FontSize="13"
Foreground="{DynamicResource SystemFillColorCriticalBrush}" />
@@ -71,11 +73,11 @@
GroupName="DataLocation"
IsChecked="True" />
<StackPanel Grid.Column="1" Spacing="4">
<TextBlock Text="Store in the system user profile (Recommended)"
<TextBlock Text="{x:Static res:Strings.DataLocation_SystemProfile}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="Data stays tied to the current Windows user and remains intact across app reinstalls and updates."
<TextBlock Text="{x:Static res:Strings.DataLocation_SystemProfileDesc}"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
@@ -102,11 +104,11 @@
GroupName="DataLocation"
IsEnabled="False" />
<StackPanel Grid.Column="1" Spacing="4">
<TextBlock Text="Store next to the app"
<TextBlock Text="{x:Static res:Strings.DataLocation_Portable}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="Useful for portable installs. The whole app folder can be moved to another machine together with its data."
<TextBlock Text="{x:Static res:Strings.DataLocation_PortableDesc}"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
@@ -146,7 +148,7 @@
Theme="{DynamicResource ButtonTheme}"
IsVisible="False" />
<Button x:Name="ConfirmButton"
Content="Confirm"
Content="{x:Static res:Strings.DataLocation_ButtonConfirm}"
Theme="{DynamicResource AccentButtonTheme}" />
</StackPanel>
</Grid>

View File

@@ -7,6 +7,7 @@ using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Styling;
using LanMountainDesktop.Launcher.Models;
using LanMountainDesktop.Launcher.Resources;
using LanMountainDesktop.Launcher.Services;
namespace LanMountainDesktop.Launcher.Views;
@@ -106,7 +107,7 @@ internal partial class DataLocationPromptWindow : Window
if (migrationInfoText is not null && hasExistingData)
{
migrationInfoText.Text = "Existing system data was detected. Choosing portable mode will migrate the current data automatically.";
migrationInfoText.Text = Strings.DataLocation_MigrateWarning;
}
}

View File

@@ -3,10 +3,12 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:LanMountainDesktop.Launcher.ViewModels"
xmlns:res="clr-namespace:LanMountainDesktop.Launcher.Resources"
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="600"
x:Class="LanMountainDesktop.Launcher.Views.DevDebugWindow"
x:DataType="vm:DevDebugWindowViewModel"
Title="开发调试窗口 - Launcher"
x:CompileBindings="False"
Title="{x:Static res:Strings.DevDebug_Title}"
Width="500"
Height="600"
WindowStartupLocation="CenterScreen"
@@ -43,7 +45,7 @@
Padding="15">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock Text="🚀 启动画面 (SplashWindow)"
<TextBlock Text="{x:Static res:Strings.DevDebug_Splash}"
FontWeight="SemiBold"
FontSize="14" />
<TextBlock Text="显示启动进度和状态"
@@ -52,11 +54,11 @@
Margin="0,3,0,0" />
</StackPanel>
<StackPanel Grid.Column="1" Spacing="8">
<ToggleSwitch Content="启用功能"
<ToggleSwitch Content="{x:Static res:Strings.DevDebug_EnableFeature}"
IsChecked="{Binding IsSplashEnabled}"
OnContent="功能"
OffContent="查看" />
<Button Content="打开"
<Button Content="{x:Static res:Strings.DevDebug_Open}"
Command="{Binding OpenSplashCommand}"
HorizontalAlignment="Right" />
</StackPanel>
@@ -69,7 +71,7 @@
Padding="15">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock Text="❌ 错误页面 (ErrorWindow)"
<TextBlock Text="{x:Static res:Strings.DevDebug_Error}"
FontWeight="SemiBold"
FontSize="14" />
<TextBlock Text="显示错误信息和重试选项"
@@ -78,11 +80,11 @@
Margin="0,3,0,0" />
</StackPanel>
<StackPanel Grid.Column="1" Spacing="8">
<ToggleSwitch Content="启用功能"
<ToggleSwitch Content="{x:Static res:Strings.DevDebug_EnableFeature}"
IsChecked="{Binding IsErrorEnabled}"
OnContent="功能"
OffContent="查看" />
<Button Content="打开"
<Button Content="{x:Static res:Strings.DevDebug_Open}"
Command="{Binding OpenErrorCommand}"
HorizontalAlignment="Right" />
</StackPanel>
@@ -95,7 +97,7 @@
Padding="15">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock Text="⬆️ 更新页面 (UpdateWindow)"
<TextBlock Text="{x:Static res:Strings.DevDebug_Update}"
FontWeight="SemiBold"
FontSize="14" />
<TextBlock Text="显示更新进度和状态"
@@ -104,11 +106,11 @@
Margin="0,3,0,0" />
</StackPanel>
<StackPanel Grid.Column="1" Spacing="8">
<ToggleSwitch Content="启用功能"
<ToggleSwitch Content="{x:Static res:Strings.DevDebug_EnableFeature}"
IsChecked="{Binding IsUpdateEnabled}"
OnContent="功能"
OffContent="查看" />
<Button Content="打开"
<Button Content="{x:Static res:Strings.DevDebug_Open}"
Command="{Binding OpenUpdateCommand}"
HorizontalAlignment="Right" />
</StackPanel>
@@ -121,7 +123,7 @@
Padding="15">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock Text="👋 OOBE页面 (OobeWindow)"
<TextBlock Text="{x:Static res:Strings.DevDebug_Oobe}"
FontWeight="SemiBold"
FontSize="14" />
<TextBlock Text="首次运行引导页面"
@@ -130,11 +132,11 @@
Margin="0,3,0,0" />
</StackPanel>
<StackPanel Grid.Column="1" Spacing="8">
<ToggleSwitch Content="启用功能"
<ToggleSwitch Content="{x:Static res:Strings.DevDebug_EnableFeature}"
IsChecked="{Binding IsOobeEnabled}"
OnContent="功能"
OffContent="查看" />
<Button Content="打开"
<Button Content="{x:Static res:Strings.DevDebug_Open}"
Command="{Binding OpenOobeCommand}"
HorizontalAlignment="Right" />
</StackPanel>
@@ -147,7 +149,7 @@
Padding="15">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock Text="📁 数据位置选择 (DataLocationPromptWindow)"
<TextBlock Text="{x:Static res:Strings.DevDebug_DataLocation}"
FontWeight="SemiBold"
FontSize="14" />
<TextBlock Text="选择数据保存位置"
@@ -156,11 +158,11 @@
Margin="0,3,0,0" />
</StackPanel>
<StackPanel Grid.Column="1" Spacing="8">
<ToggleSwitch Content="启用功能"
<ToggleSwitch Content="{x:Static res:Strings.DevDebug_EnableFeature}"
IsChecked="{Binding IsDataLocationEnabled}"
OnContent="功能"
OffContent="查看" />
<Button Content="打开"
<Button Content="{x:Static res:Strings.DevDebug_Open}"
Command="{Binding OpenDataLocationCommand}"
HorizontalAlignment="Right" />
</StackPanel>
@@ -176,10 +178,10 @@
HorizontalAlignment="Center"
Spacing="10"
Margin="0,15">
<Button Content="全部设为查看模式"
<Button Content="{x:Static res:Strings.DevDebug_SetAllViewMode}"
Command="{Binding SetAllViewOnlyCommand}"
Background="{DynamicResource SystemControlBackgroundAltMediumBrush}" />
<Button Content="全部设为功能模式"
<Button Content="{x:Static res:Strings.DevDebug_SetAllFunctionMode}"
Command="{Binding SetAllFunctionalCommand}"
Background="{DynamicResource SystemControlHighlightAccentBrush}"
Foreground="White" />
@@ -197,7 +199,7 @@
Opacity="0.8"
TextTrimming="CharacterEllipsis" />
<Button Grid.Column="1"
Content="关闭"
Content="{x:Static res:Strings.DevDebug_Close}"
Command="{Binding CloseCommand}"
Padding="15,5" />
</Grid>

View File

@@ -1,5 +1,6 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using LanMountainDesktop.Launcher.Resources;
using LanMountainDesktop.Launcher.Services;
using LanMountainDesktop.Launcher.ViewModels;
using LanMountainDesktop.Launcher.Views;
@@ -62,12 +63,12 @@ public partial class DevDebugWindow : Window
{
// 查看模式:显示模拟错误
errorWindow.SetDebugMode(true);
errorWindow.SetErrorMessage("[调试模式] 这是一个模拟的错误消息,用于查看错误页面的样式和布局。");
errorWindow.SetErrorMessage(Strings.Preview_ErrorMessage);
}
else
{
// 功能模式:显示真实错误
errorWindow.SetErrorMessage("找不到阑山桌面应用程序。\n\n请检查应用安装是否完整。");
errorWindow.SetErrorMessage(Strings.Error_HostNotFoundMessage);
}
errorWindow.Show();
@@ -160,7 +161,7 @@ public partial class DevDebugWindow : Window
/// </summary>
private async Task SimulateSplashProgress(SplashWindow splashWindow)
{
var stages = new[] { "初始化", "检查更新", "加载组件", "启动应用" };
var stages = new[] { Strings.Preview_SplashInitializing, Strings.Preview_SplashCheckingUpdates, Strings.Preview_SplashCheckingPlugins, Strings.Preview_SplashLaunchingHost };
var reporter = (ISplashStageReporter)splashWindow;
for (int i = 0; i < stages.Length; i++)

View File

@@ -3,12 +3,14 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:LanMountainDesktop.Launcher.Views"
xmlns:res="clr-namespace:LanMountainDesktop.Launcher.Resources"
mc:Ignorable="d"
d:DesignWidth="420"
d:DesignHeight="320"
x:Class="LanMountainDesktop.Launcher.Views.ErrorDebugWindow"
x:DataType="views:ErrorDebugWindow"
Title="调试模式"
x:CompileBindings="False"
Title="{x:Static res:Strings.DebugDebug_Title}"
Width="420"
Height="320"
CanResize="False"
@@ -23,7 +25,7 @@
<Grid Margin="24" RowDefinitions="Auto,*,Auto">
<!-- 标题 -->
<TextBlock Grid.Row="0"
Text="调试设置"
Text="{x:Static res:Strings.DebugDebug_SettingsTitle}"
FontSize="20"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
@@ -37,18 +39,18 @@
Padding="16,12">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock Text="开发模式"
<TextBlock Text="{x:Static res:Strings.DebugDebug_DevMode}"
FontSize="14"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="启用后自动扫描开发目录"
<TextBlock Text="{x:Static res:Strings.DebugDebug_DevModeDesc}"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,2,0,0" />
</StackPanel>
<ToggleSwitch x:Name="DevModeToggle"
Grid.Column="1"
OnContent=""
OffContent="" />
OnContent="{x:Static res:Strings.DebugDebug_On}"
OffContent="{x:Static res:Strings.DebugDebug_Off}" />
</Grid>
</Border>
@@ -58,19 +60,19 @@
Padding="16,12">
<Grid RowDefinitions="Auto,Auto" ColumnDefinitions="*,Auto">
<TextBlock Grid.Row="0" Grid.Column="0"
Text="应用路径"
Text="{x:Static res:Strings.DebugDebug_AppPath}"
FontSize="14"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock x:Name="PathTextBlock"
Grid.Row="1" Grid.Column="0"
Text="未选择"
Text="{x:Static res:Strings.DebugDebug_NotSelected}"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextTrimming="CharacterEllipsis"
Margin="0,4,12,0" />
<Button x:Name="BrowseButton"
Grid.Row="0" Grid.RowSpan="2" Grid.Column="1"
Content="浏览..."
Content="{x:Static res:Strings.DebugDebug_Browse}"
VerticalAlignment="Center" />
</Grid>
</Border>
@@ -80,7 +82,7 @@
CornerRadius="{DynamicResource ControlCornerRadius}"
Padding="12,10"
IsVisible="True">
<TextBlock Text="此功能仅供开发人员使用"
<TextBlock Text="{x:Static res:Strings.DebugDebug_Warning}"
FontSize="12"
Foreground="{DynamicResource SystemFillColorCautionBrush}"
TextWrapping="Wrap" />
@@ -94,11 +96,11 @@
Spacing="12"
Margin="0,16,0,0">
<Button x:Name="CancelButton"
Content="取消"
Content="{x:Static res:Strings.DebugDebug_ButtonCancel}"
Width="80"
Height="32" />
<Button x:Name="OkButton"
Content="确定"
Content="{x:Static res:Strings.DebugDebug_ButtonOk}"
Width="80"
Height="32"
Theme="{DynamicResource AccentButtonTheme}" />

View File

@@ -2,6 +2,7 @@ using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Platform.Storage;
using LanMountainDesktop.Launcher.Resources;
namespace LanMountainDesktop.Launcher.Views;
@@ -87,7 +88,7 @@ public partial class ErrorDebugWindow : Window
var options = new FilePickerOpenOptions
{
Title = "Select LanMountainDesktop host executable",
Title = Strings.DebugDebug_SelectExeDialog,
AllowMultiple = false,
FileTypeFilter =
[
@@ -114,7 +115,7 @@ public partial class ErrorDebugWindow : Window
{
if (this.FindControl<TextBlock>("PathTextBlock") is { } pathTextBlock)
{
pathTextBlock.Text = string.IsNullOrEmpty(path) ? "Not selected" : path;
pathTextBlock.Text = string.IsNullOrEmpty(path) ? Strings.DebugDebug_NotSelected : path;
}
}
}

View File

@@ -3,16 +3,22 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:LanMountainDesktop.Launcher.Views"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:res="clr-namespace:LanMountainDesktop.Launcher.Resources"
mc:Ignorable="d"
x:Class="LanMountainDesktop.Launcher.Views.ErrorWindow"
x:DataType="views:ErrorWindow"
Title="LanMountain Desktop"
Width="560"
Height="320"
x:CompileBindings="False"
Title="{x:Static res:Strings.Error_Title}"
Width="760"
Height="460"
MinWidth="640"
MinHeight="420"
CanResize="False"
WindowStartupLocation="CenterScreen"
Background="#111318"
TransparencyLevelHint="None"
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
TransparencyLevelHint="Mica, AcrylicBlur, None"
Icon="/Assets/logo.ico">
<Design.DataContext>
<views:ErrorWindow />
@@ -20,79 +26,128 @@
<Grid RowDefinitions="*,Auto">
<Grid Grid.Row="0"
Margin="24"
Margin="28,24,28,20"
RowDefinitions="Auto,Auto,*"
ColumnDefinitions="Auto,*">
<Border x:Name="ErrorIconBorder"
Grid.Column="0"
Width="52"
Height="52"
Margin="0,4,18,0"
Background="#2B161A"
CornerRadius="26"
Width="56"
Height="56"
Margin="0,0,18,0"
Background="{DynamicResource SystemFillColorCriticalBackgroundBrush}"
CornerRadius="28"
VerticalAlignment="Top">
<TextBlock Text="!"
FontSize="24"
FontWeight="Bold"
Foreground="#FFB4AB"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<fi:SymbolIcon Symbol="ErrorCircle"
IconVariant="Regular"
FontSize="28"
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<StackPanel Grid.Column="1"
Spacing="10">
Spacing="8">
<TextBlock x:Name="TitleText"
Text="Launcher could not confirm startup"
FontSize="20"
Text="{x:Static res:Strings.Error_TitleCannotConfirm}"
FontSize="22"
FontWeight="SemiBold"
Foreground="#F6F7FB"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
TextWrapping="Wrap" />
<TextBlock x:Name="ErrorMessageText"
Text="LanMountain Desktop did not reach the expected startup state."
Text="{x:Static res:Strings.Error_MessageNotReached}"
FontSize="14"
Foreground="#D2D7E1"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
LineHeight="22" />
<TextBlock x:Name="SuggestionText"
Text="You can inspect logs, retry when the old process is gone, or reactivate the current instance."
FontSize="13"
Foreground="#9BA5B7"
TextWrapping="Wrap"
LineHeight="20" />
</StackPanel>
<ui:FAInfoBar x:Name="SuggestionInfoBar"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0,20,0,14"
IsOpen="True"
IsClosable="False"
Severity="Warning"
Title="{x:Static res:Strings.Error_SuggestionTitle}"
Message="{x:Static res:Strings.Error_SuggestionMessage}">
<ui:FAInfoBar.IconSource>
<ui:FAFontIconSource Glyph="&#xF0288;"
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FAInfoBar.IconSource>
</ui:FAInfoBar>
<Expander Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Header="{x:Static res:Strings.Error_DiagnosticHeader}"
IsExpanded="True">
<TextBox x:Name="ErrorDetailsTextBox"
Margin="0,10,0,0"
MinHeight="150"
MaxHeight="190"
AcceptsReturn="True"
TextWrapping="Wrap"
IsReadOnly="True"
BorderThickness="0"
FontSize="12"
Text="Stage: launch&#x0a;Code: unknown"
VerticalContentAlignment="Top" />
</Expander>
</Grid>
<Border Grid.Row="1"
Padding="24,16"
Background="#171A21">
<Grid ColumnDefinitions="*,Auto,Auto,Auto"
ColumnSpacing="8">
<Button x:Name="OpenLogButton"
Grid.Column="0"
Content="Open Logs"
MinWidth="108"
Height="34"
HorizontalAlignment="Left" />
Padding="18,14"
Background="{DynamicResource LayerOnMicaBaseAltFillColorDefaultBrush}">
<Grid ColumnDefinitions="Auto,*,Auto"
ColumnSpacing="12">
<StackPanel Grid.Column="0"
Orientation="Horizontal"
Spacing="8">
<Button x:Name="OpenLogButton"
MinWidth="112"
Height="34">
<StackPanel Orientation="Horizontal" Spacing="6">
<fi:SymbolIcon Symbol="FolderOpen" IconVariant="Regular" FontSize="16"/>
<TextBlock Text="{x:Static res:Strings.Error_ButtonOpenLogs}"/>
</StackPanel>
</Button>
<Button x:Name="SecondaryActionButton"
Grid.Column="1"
Content="Wait"
MinWidth="108"
Height="34"
IsVisible="False" />
<Button x:Name="CopyDetailsButton"
MinWidth="100"
Height="34">
<StackPanel Orientation="Horizontal" Spacing="6">
<fi:SymbolIcon Symbol="Copy" IconVariant="Regular" FontSize="16"/>
<TextBlock Text="{x:Static res:Strings.Error_ButtonCopy}"/>
</StackPanel>
</Button>
</StackPanel>
<Button x:Name="ExitButton"
Grid.Column="2"
Content="Exit"
MinWidth="90"
Height="34" />
<StackPanel Grid.Column="2"
Orientation="Horizontal"
Spacing="8">
<Button x:Name="SecondaryActionButton"
Content="{x:Static res:Strings.Error_ButtonWait}"
MinWidth="96"
Height="34"
IsVisible="False" />
<Button x:Name="PrimaryActionButton"
Grid.Column="3"
Content="Retry"
MinWidth="108"
Height="34" />
<Button x:Name="ExitButton"
MinWidth="92"
Height="34">
<StackPanel Orientation="Horizontal" Spacing="6">
<fi:SymbolIcon Symbol="Dismiss" IconVariant="Regular" FontSize="16"/>
<TextBlock Text="{x:Static res:Strings.Error_ButtonExit}"/>
</StackPanel>
</Button>
<Button x:Name="PrimaryActionButton"
Classes="accent"
Content="{x:Static res:Strings.Error_ButtonRetry}"
MinWidth="112"
Height="34" />
</StackPanel>
</Grid>
</Border>
</Grid>

View File

@@ -1,8 +1,11 @@
using System.Diagnostics;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using FluentAvalonia.UI.Controls;
using LanMountainDesktop.Launcher.Resources;
using LanMountainDesktop.Launcher.Services;
namespace LanMountainDesktop.Launcher.Views;
@@ -33,9 +36,21 @@ public partial class ErrorWindow : Window
public void SetErrorMessage(string message)
{
var normalizedMessage = string.IsNullOrWhiteSpace(message)
? Strings.Error_MessageNotReached
: message.Trim();
var firstLine = normalizedMessage
.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries)
.FirstOrDefault() ?? normalizedMessage;
if (this.FindControl<TextBlock>("ErrorMessageText") is { } errorText)
{
errorText.Text = message;
errorText.Text = firstLine;
}
if (this.FindControl<TextBox>("ErrorDetailsTextBox") is { } detailsTextBox)
{
detailsTextBox.Text = normalizedMessage;
}
}
@@ -44,16 +59,16 @@ public partial class ErrorWindow : Window
_isDebugMode = isDebugMode;
if (isDebugMode && this.FindControl<TextBlock>("TitleText") is { } titleText)
{
titleText.Text = "[Debug] Launcher error";
titleText.Text = Strings.Error_DebugTitle;
}
}
public void ConfigureForHostNotFound()
{
ApplyActionLayout(
title: "Launcher could not find the desktop executable",
suggestion: "Pick another executable in debug mode, inspect logs, or retry after fixing the deployment path.",
primaryLabel: "Retry",
title: Strings.Error_HostNotFoundTitle,
suggestion: Strings.Error_HostNotFoundMessage,
primaryLabel: Strings.Error_ButtonRetry,
primaryAction: ErrorWindowResult.Retry,
secondaryLabel: null,
secondaryAction: null);
@@ -62,25 +77,27 @@ public partial class ErrorWindow : Window
public void ConfigureForGenericFailure(bool allowRetry)
{
ApplyActionLayout(
title: "Launcher could not confirm startup",
title: Strings.Error_TitleCannotConfirm,
suggestion: allowRetry
? "Inspect logs, then retry once the previous startup attempt has fully finished."
: "Inspect logs or exit. Launcher will avoid creating another desktop process while the old one is still running.",
primaryLabel: allowRetry ? "Retry" : "Activate",
? Strings.Error_GenericRetryMessage
: Strings.Error_GenericNoRetryMessage,
primaryLabel: allowRetry ? Strings.Error_ButtonRetry : Strings.Error_ButtonActivate,
primaryAction: allowRetry ? ErrorWindowResult.Retry : ErrorWindowResult.ActivateExisting,
secondaryLabel: allowRetry ? null : "Wait",
secondaryLabel: allowRetry ? null : Strings.Error_ButtonWait,
secondaryAction: allowRetry ? null : ErrorWindowResult.ContinueWaiting);
}
public void ConfigureForRunningHostFailure(int? hostPid)
{
var pidHint = hostPid is > 0 ? $" Current host PID: {hostPid}." : string.Empty;
var suggestion = hostPid is > 0
? string.Format(Strings.Error_PendingMessageWithPid, hostPid)
: Strings.Error_PendingMessage;
ApplyActionLayout(
title: "Startup is still pending",
suggestion: $"The desktop process is still running, so Launcher will not start a second instance.{pidHint}",
primaryLabel: "Activate",
title: Strings.Error_PendingTitle,
suggestion: suggestion,
primaryLabel: Strings.Error_ButtonActivate,
primaryAction: ErrorWindowResult.ActivateExisting,
secondaryLabel: "Wait",
secondaryLabel: Strings.Error_ButtonWait,
secondaryAction: ErrorWindowResult.ContinueWaiting);
}
@@ -120,6 +137,11 @@ public partial class ErrorWindow : Window
{
openLogButton.Click += OnOpenLogClick;
}
if (this.FindControl<Button>("CopyDetailsButton") is { } copyDetailsButton)
{
copyDetailsButton.Click += OnCopyDetailsClick;
}
}
private void ApplyActionLayout(
@@ -138,9 +160,9 @@ public partial class ErrorWindow : Window
titleText.Text = title;
}
if (this.FindControl<TextBlock>("SuggestionText") is { } suggestionText)
if (this.FindControl<FAInfoBar>("SuggestionInfoBar") is { } suggestionInfoBar)
{
suggestionText.Text = suggestion;
suggestionInfoBar.Message = suggestion;
}
if (this.FindControl<Button>("PrimaryActionButton") is { } primaryButton)
@@ -243,6 +265,28 @@ public partial class ErrorWindow : Window
await Task.CompletedTask;
}
private async void OnCopyDetailsClick(object? sender, RoutedEventArgs e)
{
try
{
var details = this.FindControl<TextBox>("ErrorDetailsTextBox")?.Text;
if (string.IsNullOrWhiteSpace(details))
{
details = this.FindControl<TextBlock>("ErrorMessageText")?.Text;
}
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
if (clipboard is not null && !string.IsNullOrWhiteSpace(details))
{
await clipboard.SetTextAsync(details);
}
}
catch (Exception ex)
{
Debug.WriteLine($"[ErrorWindow] Failed to copy diagnostics: {ex}");
}
}
private void ScanDevPaths()
{
var executable = OperatingSystem.IsWindows() ? "LanMountainDesktop.exe" : "LanMountainDesktop";

View File

@@ -1,13 +1,15 @@
<Window xmlns="https://github.com/avaloniaui"
<Window 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"
xmlns:views="clr-namespace:LanMountainDesktop.Launcher.Views"
xmlns:res="clr-namespace:LanMountainDesktop.Launcher.Resources"
mc:Ignorable="d"
d:DesignWidth="600"
d:DesignHeight="500"
x:Class="LanMountainDesktop.Launcher.Views.LoadingDetailsWindow"
Title="LanMountain Desktop - Loading Details"
x:CompileBindings="False"
Title="{x:Static res:Strings.Loading_Title}"
Width="600"
Height="500"
WindowStartupLocation="CenterScreen"
@@ -23,12 +25,12 @@
Padding="20,16">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="Starting LanMountain Desktop"
<TextBlock Text="{x:Static res:Strings.Loading_StartingDesktop}"
FontSize="18"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
<TextBlock x:Name="SubtitleText"
Text="Initializing..."
Text="{x:Static res:Strings.Loading_StatusInitializing}"
FontSize="13"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
</StackPanel>
@@ -86,14 +88,14 @@
<TextBlock x:Name="CurrentItemName"
Grid.Row="0" Grid.Column="1"
Text="Initializing..."
Text="{x:Static res:Strings.Loading_StatusInitializing}"
FontSize="15"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
<TextBlock x:Name="CurrentItemDescription"
Grid.Row="1" Grid.Column="1"
Text="Preparing components"
Text="{x:Static res:Strings.Loading_StatusPreparing}"
FontSize="13"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,4,0,0"/>
@@ -115,7 +117,7 @@
<Grid RowDefinitions="Auto,*">
<Grid Grid.Row="0" Margin="12,8" ColumnDefinitions="*,Auto,Auto">
<TextBlock Grid.Column="0"
Text="Loading Items"
Text="{x:Static res:Strings.Loading_LoadingItems}"
FontSize="12"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"/>
@@ -126,7 +128,7 @@
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,0,4,0"/>
<TextBlock Grid.Column="2"
Text="Done"
Text="{x:Static res:Strings.Loading_Done}"
FontSize="12"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"/>
</Grid>
@@ -199,7 +201,7 @@
VerticalAlignment="Center"/>
<TextBlock x:Name="ErrorText"
Grid.Column="1"
Text="An error occurred while loading."
Text="{x:Static res:Strings.Loading_ErrorOccurred}"
FontSize="13"
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
TextWrapping="Wrap"/>
@@ -218,12 +220,12 @@
VerticalAlignment="Center"/>
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8">
<Button x:Name="DetailsButton"
Content="Details"
Content="{x:Static res:Strings.Loading_ButtonDetails}"
Width="90"
Height="32"
FontSize="13"/>
<Button x:Name="CancelButton"
Content="Cancel"
Content="{x:Static res:Strings.Loading_ButtonCancel}"
Width="90"
Height="32"
FontSize="13"/>

View File

@@ -3,6 +3,7 @@ using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.Launcher.Resources;
using LanMountainDesktop.Launcher.Services;
using LanMountainDesktop.Shared.Contracts.Launcher;
using System.Collections.ObjectModel;
@@ -57,7 +58,7 @@ public partial class LoadingDetailsWindow : Window
}
/// <summary>
/// 鏇存柊鍔犺浇鐘舵€? /// </summary>
/// 更新加载状<EFBFBD>? /// </summary>
public void UpdateLoadingState(LoadingStateMessage state)
{
Dispatcher.UIThread.Post(() =>
@@ -120,7 +121,7 @@ public partial class LoadingDetailsWindow : Window
}
/// <summary>
/// 鏇存柊褰撳墠娲诲姩椤? /// </summary>
/// 更新当前活动<EFBFBD>? /// </summary>
private void UpdateCurrentItem(LoadingStateMessage state)
{
var currentItem = state.ActiveItems.FirstOrDefault();
@@ -181,7 +182,7 @@ public partial class LoadingDetailsWindow : Window
}
}
// 鎸夌姸鎬佹帓搴忥細杩涜<EFBFBD>涓?-> 绛夊緟涓?-> 宸插畬鎴?-> 澶辫触
// 按状态排序进行<EFBFBD>?-> 等待<E7AD89>?-> 已完<E5B7B2>?-> 失败
var sortedItems = _items.OrderBy(i => GetStatePriority(i.State)).ToList();
_items.Clear();
foreach (var item in sortedItems)
@@ -234,20 +235,20 @@ public partial class LoadingDetailsWindow : Window
/// </summary>
private static string GetStageDescription(StartupStage stage) => stage switch
{
StartupStage.Initializing => "正在初始化系统...",
StartupStage.LoadingSettings => "正在加载设置...",
StartupStage.LoadingPlugins => "正在加载插件...",
StartupStage.InitializingUI => "正在初始化界面...",
StartupStage.ShellInitialized => "桌面外壳已初始化",
StartupStage.DesktopVisible => "桌面已经可见",
StartupStage.ActivationRedirected => "已激活现有实例",
StartupStage.ActivationFailed => "现有实例激活失败",
StartupStage.Ready => "加载完成",
_ => "正在加载..."
StartupStage.Initializing => "正在初始化系统...",
StartupStage.LoadingSettings => "正在加载设置...",
StartupStage.LoadingPlugins => "正在加载插件...",
StartupStage.InitializingUI => "正在初始化界面...",
StartupStage.ShellInitialized => "桌面程序已初始化",
StartupStage.DesktopVisible => "桌面已经可见",
StartupStage.ActivationRedirected => "已激活现有实例",
StartupStage.ActivationFailed => "激活现有实例失败",
StartupStage.Ready => Strings.Loading_StageReady,
_ => "正在加载..."
};
/// <summary>
/// 鑾峰彇椤规弿杩? /// </summary>
/// 获取项描<EFBFBD>? /// </summary>
private static string GetItemDescription(LoadingItem item)
{
if (!string.IsNullOrEmpty(item.Description))
@@ -255,17 +256,17 @@ public partial class LoadingDetailsWindow : Window
return item.Type switch
{
LoadingItemType.Plugin => "姝e湪鍔犺浇鎻掍欢...",
LoadingItemType.Component => "姝e湪鍔犺浇缁勪欢...",
LoadingItemType.Resource => "姝e湪鍔犺浇璧勬簮...",
LoadingItemType.Data => "姝e湪鍔犺浇鏁版嵁...",
LoadingItemType.Network => "姝e湪涓嬭浇...",
_ => "姝e湪澶勭悊..."
LoadingItemType.Plugin => Strings.Loading_ItemPlugin,
LoadingItemType.Component => Strings.Loading_ItemComponent,
LoadingItemType.Resource => Strings.Loading_ItemResource,
LoadingItemType.Data => Strings.Loading_ItemData,
LoadingItemType.Network => Strings.Loading_ItemDownload,
_ => Strings.Loading_ItemProcess
};
}
/// <summary>
/// 鑾峰彇椤瑰浘鏍? /// </summary>
/// 获取项图<EFBFBD>? /// </summary>
private static string GetItemIcon(LoadingItemType type) => type switch
{
LoadingItemType.Plugin => "\uE768",
@@ -294,7 +295,7 @@ public partial class LoadingDetailsWindow : Window
}
/// <summary>
/// 鍔犺浇椤硅<EFBFBD>鍥炬ā鍨?/// </summary>
/// 加载项视图模<EFBFBD>?/// </summary>
public class LoadingItemViewModel : INotifyPropertyChanged
{
public string Id { get; }
@@ -306,7 +307,7 @@ public class LoadingItemViewModel : INotifyPropertyChanged
public string StatusIcon => GetStatusIcon(State);
public IBrush StatusColor => GetStatusColor(State);
public string ProgressText => State == LoadingState.Completed ? "瀹屾垚" : $"{ProgressPercent}%";
public string ProgressText => State == LoadingState.Completed ? Strings.Loading_ItemComplete : $"{ProgressPercent}%";
public string TypeLabel => GetTypeLabel(Type);
public IBrush TypeBackground => GetTypeBackground(Type);
public IBrush TypeForeground => GetTypeForeground(Type);
@@ -359,14 +360,14 @@ public class LoadingItemViewModel : INotifyPropertyChanged
private static string GetTypeLabel(LoadingItemType type) => type switch
{
LoadingItemType.Plugin => "鎻掍欢",
LoadingItemType.Component => "缁勪欢",
LoadingItemType.Resource => "璧勬簮",
LoadingItemType.Data => "鏁版嵁",
LoadingItemType.Network => "缃戠粶",
LoadingItemType.Settings => "璁剧疆",
LoadingItemType.System => "绯荤粺",
_ => "鍏朵粬"
LoadingItemType.Plugin => Strings.Loading_TypePlugin,
LoadingItemType.Component => Strings.Loading_TypeComponent,
LoadingItemType.Resource => Strings.Loading_TypeResource,
LoadingItemType.Data => Strings.Loading_TypeData,
LoadingItemType.Network => Strings.Loading_TypeNetwork,
LoadingItemType.Settings => Strings.Loading_TypeSettings,
LoadingItemType.System => Strings.Loading_TypeSystem,
_ => Strings.Loading_TypeOther
};
private static IBrush GetTypeBackground(LoadingItemType type) => type switch

View File

@@ -3,12 +3,14 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:LanMountainDesktop.Launcher.Views"
xmlns:res="clr-namespace:LanMountainDesktop.Launcher.Resources"
mc:Ignorable="d"
d:DesignWidth="520"
d:DesignHeight="360"
x:Class="LanMountainDesktop.Launcher.Views.MigrationPromptWindow"
x:DataType="views:MigrationPromptWindow"
Title="阑山桌面 - 版本迁移"
x:CompileBindings="False"
Title="{x:Static res:Strings.Migration_Title}"
Width="520"
Height="360"
CanResize="False"
@@ -51,7 +53,7 @@
<!-- 说明文字 -->
<TextBlock x:Name="DescriptionText"
Text="检测到您的系统中安装了旧版本的阑山桌面0.8.4)。新版本采用了全新的架构,建议卸载旧版本以获得更好的体验。"
Text="{x:Static res:Strings.Migration_DetectedDesc}"
FontSize="14"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
@@ -65,7 +67,7 @@
<Grid RowDefinitions="Auto,Auto,Auto" ColumnDefinitions="Auto,*">
<!-- 版本号 -->
<TextBlock Grid.Row="0" Grid.Column="0"
Text="版本:"
Text="{x:Static res:Strings.Migration_Version}"
FontSize="12"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"/>
<TextBlock x:Name="VersionText"
@@ -77,7 +79,7 @@
<!-- 安装路径 -->
<TextBlock Grid.Row="1" Grid.Column="0"
Text="位置:"
Text="{x:Static res:Strings.Migration_Location}"
FontSize="12"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
Margin="0,4,0,0"/>
@@ -91,13 +93,13 @@
<!-- 安装类型 -->
<TextBlock Grid.Row="2" Grid.Column="0"
Text="类型:"
Text="{x:Static res:Strings.Migration_Type}"
FontSize="12"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
Margin="0,4,0,0"/>
<TextBlock x:Name="TypeText"
Grid.Row="2" Grid.Column="1"
Text="安装版"
Text="{x:Static res:Strings.Migration_Installed}"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="8,4,0,0"/>
@@ -105,7 +107,7 @@
</Border>
<!-- 提示信息 -->
<TextBlock Text="卸载旧版本不会影响新版本的使用,您的个人数据将保留。"
<TextBlock Text="{x:Static res:Strings.Migration_UninstallNote}"
FontSize="12"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
TextWrapping="Wrap"
@@ -121,7 +123,7 @@
<!-- 左侧:查看位置按钮 -->
<Button x:Name="ShowLocationButton"
Grid.Column="0"
Content="查看位置"
Content="{x:Static res:Strings.Migration_ButtonViewLocation}"
Width="100"
Height="32"
FontSize="13"
@@ -137,7 +139,7 @@
Height="32"
FontSize="13"/>
<Button x:Name="UninstallButton"
Content="卸载旧版本"
Content="{x:Static res:Strings.Migration_ButtonUninstall}"
Width="100"
Height="32"
FontSize="13"

View File

@@ -1,6 +1,7 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using LanMountainDesktop.Launcher.Resources;
using LanMountainDesktop.Launcher.Services;
namespace LanMountainDesktop.Launcher.Views;
@@ -46,15 +47,15 @@ public partial class MigrationPromptWindow : Window
{
typeText.Text = info.InstallType switch
{
LegacyInstallType.Registry => "安装版",
LegacyInstallType.Portable => "便携版",
_ => "未知"
LegacyInstallType.Registry => Strings.Migration_Installed,
LegacyInstallType.Portable => Strings.Migration_Portable,
_ => Strings.Migration_Unknown
};
}
if (descriptionText != null)
{
descriptionText.Text = $"检测到您的系统中安装了旧版本的阑山桌面({info.Version})。新版本采用了全新的架构,建议卸载旧版本以获得更好的体验。";
descriptionText.Text = string.Format(Strings.Migration_DetectedDescFormat, info.Version);
}
}

View File

@@ -0,0 +1,126 @@
<Window 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"
xmlns:views="clr-namespace:LanMountainDesktop.Launcher.Views"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:res="clr-namespace:LanMountainDesktop.Launcher.Resources"
mc:Ignorable="d"
x:Class="LanMountainDesktop.Launcher.Views.MultiInstancePromptWindow"
x:DataType="views:MultiInstancePromptWindow"
x:CompileBindings="False"
Title="{x:Static res:Strings.MultiInstance_Title}"
Width="620"
Height="360"
MinWidth="560"
MinHeight="330"
CanResize="False"
WindowStartupLocation="CenterScreen"
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
TransparencyLevelHint="Mica, AcrylicBlur, None"
Icon="/Assets/logo.ico">
<Design.DataContext>
<views:MultiInstancePromptWindow />
</Design.DataContext>
<Grid RowDefinitions="*,Auto">
<Grid Grid.Row="0"
Margin="28,26,28,20"
RowDefinitions="Auto,Auto,*"
ColumnDefinitions="Auto,*">
<Border Grid.Column="0"
Width="52"
Height="52"
Margin="0,0,18,0"
Background="{DynamicResource SystemFillColorAttentionBackgroundBrush}"
CornerRadius="26"
VerticalAlignment="Top">
<fi:SymbolIcon Symbol="Desktop"
IconVariant="Regular"
FontSize="26"
Foreground="{DynamicResource SystemFillColorAttentionBrush}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<StackPanel Grid.Column="1"
Spacing="8">
<TextBlock Text="{x:Static res:Strings.MultiInstance_AlreadyRunning}"
FontSize="22"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
TextWrapping="Wrap" />
<TextBlock x:Name="MessageText"
Text="{x:Static res:Strings.MultiInstance_AlreadyRunningMessage}"
FontSize="14"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
LineHeight="22" />
</StackPanel>
<ui:FAInfoBar Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0,22,0,14"
IsOpen="True"
IsClosable="False"
Severity="Informational"
Title="{x:Static res:Strings.MultiInstance_RepeatedLaunchTitle}"
Message="{x:Static res:Strings.MultiInstance_RepeatedLaunchMessage}">
<ui:FAInfoBar.IconSource>
<ui:FAFontIconSource Glyph="&#xF0288;"
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FAInfoBar.IconSource>
</ui:FAInfoBar>
<TextBlock x:Name="DetailsText"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="{x:Static res:Strings.MultiInstance_NoSecondProcess}" />
</Grid>
<Border Grid.Row="1"
Padding="18,14"
Background="{DynamicResource LayerOnMicaBaseAltFillColorDefaultBrush}">
<Grid ColumnDefinitions="*,Auto">
<Button x:Name="CopyDetailsButton"
Grid.Column="0"
MinWidth="104"
Height="34"
HorizontalAlignment="Left">
<StackPanel Orientation="Horizontal" Spacing="6">
<fi:SymbolIcon Symbol="Copy" IconVariant="Regular" FontSize="16"/>
<TextBlock Text="{x:Static res:Strings.MultiInstance_ButtonCopy}"/>
</StackPanel>
</Button>
<StackPanel Grid.Column="1"
Orientation="Horizontal"
Spacing="8">
<Button x:Name="CloseButton"
MinWidth="92"
Height="34">
<StackPanel Orientation="Horizontal" Spacing="6">
<fi:SymbolIcon Symbol="Dismiss" IconVariant="Regular" FontSize="16"/>
<TextBlock Text="{x:Static res:Strings.MultiInstance_ButtonClose}"/>
</StackPanel>
</Button>
<Button x:Name="OpenDesktopButton"
Classes="accent"
MinWidth="136"
Height="34">
<StackPanel Orientation="Horizontal" Spacing="6">
<fi:SymbolIcon Symbol="ArrowRight" IconVariant="Regular" FontSize="16"/>
<TextBlock Text="{x:Static res:Strings.MultiInstance_ButtonOpenDesktop}"/>
</StackPanel>
</Button>
</StackPanel>
</Grid>
</Border>
</Grid>
</Window>

View File

@@ -0,0 +1,77 @@
using Avalonia.Controls;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using LanMountainDesktop.Launcher.Resources;
namespace LanMountainDesktop.Launcher.Views;
public partial class MultiInstancePromptWindow : Window
{
private readonly TaskCompletionSource<MultiInstancePromptResult> _completionSource =
new(TaskCreationOptions.RunContinuationsAsynchronously);
private string _details = Strings.MultiInstance_AlreadyRunning;
public MultiInstancePromptWindow()
{
AvaloniaXamlLoader.Load(this);
Loaded += OnLoaded;
Closed += (_, _) => _completionSource.TrySetResult(MultiInstancePromptResult.Close);
}
public Task<MultiInstancePromptResult> WaitForChoiceAsync() => _completionSource.Task;
public void SetDetails(int processId, string shellState)
{
_details = string.Format(Strings.MultiInstance_DetailsFormat, processId, shellState);
if (this.FindControl<TextBlock>("DetailsText") is { } detailsText)
{
detailsText.Text = _details;
}
}
private void OnLoaded(object? sender, RoutedEventArgs e)
{
if (this.FindControl<Button>("CloseButton") is { } closeButton)
{
closeButton.Click += (_, _) => Complete(MultiInstancePromptResult.Close);
}
if (this.FindControl<Button>("OpenDesktopButton") is { } openDesktopButton)
{
openDesktopButton.Click += (_, _) => Complete(MultiInstancePromptResult.OpenDesktop);
}
if (this.FindControl<Button>("CopyDetailsButton") is { } copyDetailsButton)
{
copyDetailsButton.Click += OnCopyDetailsClick;
}
}
private void Complete(MultiInstancePromptResult result)
{
_completionSource.TrySetResult(result);
Close();
}
private async void OnCopyDetailsClick(object? sender, RoutedEventArgs e)
{
try
{
if (TopLevel.GetTopLevel(this)?.Clipboard is IClipboard clipboard)
{
await clipboard.SetTextAsync(_details);
}
}
catch
{
}
}
}
public enum MultiInstancePromptResult
{
Close,
OpenDesktop
}

View File

@@ -5,12 +5,14 @@
xmlns:views="clr-namespace:LanMountainDesktop.Launcher.Views"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:res="clr-namespace:LanMountainDesktop.Launcher.Resources"
mc:Ignorable="d"
d:DesignWidth="850"
d:DesignHeight="650"
x:Class="LanMountainDesktop.Launcher.Views.OobeWindow"
x:DataType="views:OobeWindow"
Title="欢迎使用阑山桌面"
x:CompileBindings="False"
Title="{x:Static res:Strings.Oobe_Title}"
Width="850"
Height="650"
CanResize="False"
@@ -149,7 +151,7 @@
<Button.RenderTransform>
<ScaleTransform ScaleX="0.1" ScaleY="0.1" />
</Button.RenderTransform>
<TextBlock Text="开始使用"
<TextBlock Text="{x:Static res:Strings.Oobe_ButtonGetStarted}"
FontSize="16"
FontWeight="SemiBold" />
</Button>
@@ -173,11 +175,11 @@
<!-- 步骤 2: 主题选择页面 -->
<Grid x:Name="ThemeStep" Margin="48" RowDefinitions="Auto,*,Auto" IsVisible="False">
<StackPanel Grid.Row="0" Spacing="8" Margin="0,0,0,24">
<TextBlock Text="个性化你的桌面"
<TextBlock Text="{x:Static res:Strings.Oobe_AppearanceTitle}"
FontSize="24"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="选择你喜欢的主题样式,可随时在设置中更改"
<TextBlock Text="{x:Static res:Strings.Oobe_AppearanceDesc}"
FontSize="13"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
@@ -189,7 +191,7 @@
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="16">
<StackPanel Spacing="12">
<TextBlock Text="外观模式"
<TextBlock Text="{x:Static res:Strings.Oobe_AppearanceMode}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
@@ -215,7 +217,7 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<TextBlock Text="浅色模式"
<TextBlock Text="{x:Static res:Strings.Oobe_LightMode}"
FontSize="13"
HorizontalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
@@ -247,7 +249,7 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<TextBlock Text="深色模式"
<TextBlock Text="{x:Static res:Strings.Oobe_DarkMode}"
FontSize="13"
HorizontalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
@@ -265,7 +267,7 @@
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="16">
<StackPanel Spacing="12">
<TextBlock Text="主题色"
<TextBlock Text="{x:Static res:Strings.Oobe_ThemeColor}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
@@ -335,11 +337,11 @@
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="16">
<StackPanel Spacing="12">
<TextBlock Text="莫奈取色来源"
<TextBlock Text="{x:Static res:Strings.Oobe_MonetSource}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="从壁纸自动提取主题色,让界面与桌面完美融合"
<TextBlock Text="{x:Static res:Strings.Oobe_MonetDesc}"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
@@ -359,11 +361,11 @@
VerticalAlignment="Center"
Margin="0,0,12,0" />
<StackPanel Grid.Column="1" Spacing="4">
<TextBlock Text="从桌面壁纸取色"
<TextBlock Text="{x:Static res:Strings.Oobe_MonetFromWallpaper}"
FontSize="13"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="自动分析当前壁纸颜色生成主题"
<TextBlock Text="{x:Static res:Strings.Oobe_MonetFromWallpaperDesc}"
FontSize="11"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
@@ -384,11 +386,11 @@
VerticalAlignment="Center"
Margin="0,0,12,0" />
<StackPanel Grid.Column="1" Spacing="4">
<TextBlock Text="自定义图片取色"
<TextBlock Text="{x:Static res:Strings.Oobe_MonetFromCustomImage}"
FontSize="13"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="选择一张图片作为取色来源"
<TextBlock Text="{x:Static res:Strings.Oobe_MonetFromCustomImageDesc}"
FontSize="11"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
@@ -409,11 +411,11 @@
VerticalAlignment="Center"
Margin="0,0,12,0" />
<StackPanel Grid.Column="1" Spacing="4">
<TextBlock Text="不使用莫奈取色"
<TextBlock Text="{x:Static res:Strings.Oobe_MonetDisabled}"
FontSize="13"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="使用固定的预设主题色"
<TextBlock Text="{x:Static res:Strings.Oobe_MonetDisabledDesc}"
FontSize="11"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
@@ -431,10 +433,10 @@
Spacing="12"
Margin="0,24,0,0">
<Button x:Name="ThemeBackButton"
Content="返回"
Content="{x:Static res:Strings.Oobe_ButtonBack}"
Theme="{DynamicResource ButtonTheme}" />
<Button x:Name="ThemeNextButton"
Content="下一步"
Content="{x:Static res:Strings.Oobe_ButtonNext}"
Theme="{DynamicResource AccentButtonTheme}" />
</StackPanel>
</Grid>
@@ -442,11 +444,11 @@
<!-- 步骤 3: 数据位置选择页面 -->
<Grid x:Name="DataLocationStep" Margin="48" RowDefinitions="Auto,*,Auto" IsVisible="False">
<StackPanel Grid.Row="0" Spacing="8" Margin="0,0,0,32">
<TextBlock Text="选择数据保存位置"
<TextBlock Text="{x:Static res:Strings.Oobe_DataLocationTitle}"
FontSize="28"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="决定将应用数据保存在哪里,可随时在设置中更改"
<TextBlock Text="{x:Static res:Strings.Oobe_DataLocationDesc}"
FontSize="14"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
@@ -462,12 +464,12 @@
<fi:SymbolIcon Symbol="ShieldError"
FontSize="20"
Foreground="{DynamicResource SystemFillColorCriticalBrush}" />
<TextBlock Text="无法保存到应用目录"
<TextBlock Text="{x:Static res:Strings.Oobe_NotWritable}"
FontWeight="SemiBold"
FontSize="14"
Foreground="{DynamicResource SystemFillColorCriticalBrush}" />
</StackPanel>
<TextBlock Text="当前安装目录需要管理员权限才能写入,数据将自动保存到系统用户目录。"
<TextBlock Text="{x:Static res:Strings.Oobe_NotWritableDesc}"
FontSize="13"
TextWrapping="Wrap"
Foreground="{DynamicResource SystemFillColorCriticalBrush}" />
@@ -494,12 +496,12 @@
<fi:SymbolIcon Symbol="Folder"
FontSize="24"
Foreground="{DynamicResource AccentFillColorDefaultBrush}" />
<TextBlock Text="保存在系统用户目录(推荐)"
<TextBlock Text="{x:Static res:Strings.Oobe_SystemProfile}"
FontSize="16"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
</StackPanel>
<TextBlock Text="数据与当前 Windows 用户绑定,重装应用或更新后数据不会丢失。适合大多数用户。"
<TextBlock Text="{x:Static res:Strings.Oobe_SystemProfileDesc}"
FontSize="13"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
@@ -537,12 +539,12 @@
<fi:SymbolIcon Symbol="Save"
FontSize="24"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<TextBlock Text="保存在应用安装目录(便携模式)"
<TextBlock Text="{x:Static res:Strings.Oobe_Portable}"
FontSize="16"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
</StackPanel>
<TextBlock Text="便于携带,可随应用文件夹整体移动到其他电脑。适合在多台电脑间使用或需要便携运行的场景。"
<TextBlock Text="{x:Static res:Strings.Oobe_PortableDesc}"
FontSize="13"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
@@ -584,26 +586,161 @@
Spacing="12"
Margin="0,32,0,0">
<Button x:Name="DataLocationBackButton"
Content="返回"
Content="{x:Static res:Strings.Oobe_ButtonBack}"
Theme="{DynamicResource ButtonTheme}"
Width="100"
Height="36" />
<Button x:Name="DataLocationNextButton"
Content="下一步"
Content="{x:Static res:Strings.Oobe_ButtonNext}"
Theme="{DynamicResource AccentButtonTheme}"
Width="100"
Height="36" />
</StackPanel>
</Grid>
<!-- 步骤 4: 信息与隐私页面 -->
<Grid x:Name="PrivacyStep" Margin="48" RowDefinitions="Auto,*,Auto" IsVisible="False">
<StackPanel Grid.Row="0" Spacing="8" Margin="0,0,0,24">
<TextBlock Text="信息与隐私"
<!-- 步骤 4: 启动与展示(紧接数据保存位置) -->
<Grid x:Name="StartupPresentationStep" Margin="48" RowDefinitions="Auto,*,Auto" IsVisible="False">
<StackPanel Grid.Row="0" Spacing="8" Margin="0,0,0,16">
<TextBlock Text="{x:Static res:Strings.Oobe_StartupTitle}"
FontSize="24"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="选择是否参与遥测计划,查看隐私政策"
<TextBlock Text="{x:Static res:Strings.Oobe_StartupDesc}"
FontSize="13"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="14">
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="16">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="{x:Static res:Strings.Oobe_ShowInTaskbar}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="{x:Static res:Strings.Oobe_ShowInTaskbarDesc}"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
<ToggleSwitch x:Name="OobeShowInTaskbarToggle"
Grid.Column="1"
VerticalAlignment="Center" />
</Grid>
</Border>
<Border x:Name="OobeSlideTransitionSection"
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="16">
<StackPanel Spacing="12">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="{x:Static res:Strings.Oobe_SlideTransition}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="{x:Static res:Strings.Oobe_SlideTransitionDesc}"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
<ToggleSwitch x:Name="OobeSlideTransitionToggle"
Grid.Column="1"
VerticalAlignment="Center" />
</Grid>
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="{x:Static res:Strings.Oobe_FadeTransition}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="{x:Static res:Strings.Oobe_FadeTransitionDesc}"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
<ToggleSwitch x:Name="OobeFadeTransitionToggle"
Grid.Column="1"
VerticalAlignment="Center" />
</Grid>
</StackPanel>
</Border>
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="16">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Spacing="4">
<StackPanel Orientation="Horizontal" Spacing="8">
<fi:SymbolIcon Symbol="SlideLayout"
FontSize="20"
Foreground="{DynamicResource AccentFillColorDefaultBrush}" />
<TextBlock Text="{x:Static res:Strings.Oobe_FusedDesktop}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
</StackPanel>
<TextBlock Text="{x:Static res:Strings.Oobe_FusedDesktopDesc}"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
<ToggleSwitch x:Name="OobeFusedPopupToggle"
Grid.Column="1"
VerticalAlignment="Center" />
</Grid>
</Border>
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="16">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="{x:Static res:Strings.Oobe_AutoStart}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock x:Name="OobeAutoStartDescriptionText"
Text="{x:Static res:Strings.Oobe_AutoStartDesc}"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
<ToggleSwitch x:Name="OobeAutoStartToggle"
Grid.Column="1"
VerticalAlignment="Center" />
</Grid>
</Border>
</StackPanel>
</ScrollViewer>
<StackPanel Grid.Row="2"
Orientation="Horizontal"
HorizontalAlignment="Right"
Spacing="12"
Margin="0,24,0,0">
<Button x:Name="StartupPresentationBackButton"
Content="{x:Static res:Strings.Oobe_ButtonBack}"
Theme="{DynamicResource ButtonTheme}" />
<Button x:Name="StartupPresentationNextButton"
Content="{x:Static res:Strings.Oobe_ButtonNext}"
Theme="{DynamicResource AccentButtonTheme}" />
</StackPanel>
</Grid>
<!-- 步骤 5: 信息与隐私页面 -->
<Grid x:Name="PrivacyStep" Margin="48" RowDefinitions="Auto,*,Auto" IsVisible="False">
<StackPanel Grid.Row="0" Spacing="8" Margin="0,0,0,24">
<TextBlock Text="{x:Static res:Strings.Oobe_PrivacyTitle}"
FontSize="24"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="{x:Static res:Strings.Oobe_PrivacyDesc}"
FontSize="13"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
@@ -615,11 +752,11 @@
Padding="16">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="发送匿名崩溃报告"
<TextBlock Text="{x:Static res:Strings.Oobe_CrashReports}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="帮助改进应用稳定性,不包含个人身份信息"
<TextBlock Text="{x:Static res:Strings.Oobe_CrashReportsDesc}"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
@@ -638,11 +775,11 @@
Padding="16">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="发送匿名使用统计"
<TextBlock Text="{x:Static res:Strings.Oobe_UsageStats}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="帮助了解功能使用情况,优化产品体验"
<TextBlock Text="{x:Static res:Strings.Oobe_UsageStatsDesc}"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
@@ -660,11 +797,11 @@
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="16">
<StackPanel Spacing="8">
<TextBlock Text="隐私追踪 ID"
<TextBlock Text="{x:Static res:Strings.Oobe_PrivacyTrackingId}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="此 ID 用于匿名标识您的设备,不包含任何个人信息"
<TextBlock Text="{x:Static res:Strings.Oobe_PrivacyTrackingIdDesc}"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
@@ -688,12 +825,12 @@
<CheckBox x:Name="PrivacyAgreementCheckBox"
VerticalAlignment="Center" />
<StackPanel Orientation="Horizontal" Spacing="4" VerticalAlignment="Center">
<TextBlock Text="同意"
<TextBlock Text="{x:Static res:Strings.Oobe_Agree}"
FontSize="13"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<Button x:Name="ViewPrivacyPolicyButton"
Content="《阑山桌面遥测隐私数据收集协议》"
Content="{x:Static res:Strings.Oobe_PrivacyPolicyLink}"
Background="Transparent"
BorderThickness="0"
Padding="0"
@@ -709,7 +846,7 @@
</StackPanel>
<!-- 提示文本 -->
<TextBlock Text="您必须阅读并同意隐私协议后,才能开启遥测功能。遥测数据仅用于改进应用稳定性和优化产品体验,不包含任何个人身份信息。"
<TextBlock Text="{x:Static res:Strings.Oobe_PrivacyAgreementNote}"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
@@ -723,15 +860,15 @@
Spacing="12"
Margin="0,24,0,0">
<Button x:Name="PrivacyBackButton"
Content="返回"
Content="{x:Static res:Strings.Oobe_ButtonBack}"
Theme="{DynamicResource ButtonTheme}" />
<Button x:Name="PrivacyNextButton"
Content="下一步"
Content="{x:Static res:Strings.Oobe_ButtonNext}"
Theme="{DynamicResource AccentButtonTheme}" />
</StackPanel>
</Grid>
<!-- 步骤 5: 欢迎完成页面 -->
<!-- 步骤 6: 欢迎完成页面 -->
<Grid x:Name="WelcomeStep" Margin="48" RowDefinitions="*,Auto" IsVisible="False">
<StackPanel Grid.Row="0"
VerticalAlignment="Center"
@@ -752,12 +889,12 @@
</Border>
<StackPanel Spacing="12" HorizontalAlignment="Center">
<TextBlock Text="欢迎使用阑山桌面"
<TextBlock Text="{x:Static res:Strings.Oobe_CompleteTitle}"
FontSize="32"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
HorizontalAlignment="Center" />
<TextBlock Text="你的桌面,不止一面"
<TextBlock Text="{x:Static res:Strings.Oobe_CompleteSubtitle}"
FontSize="16"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
HorizontalAlignment="Center" />

View File

@@ -7,6 +7,7 @@ using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.Launcher.Models;
using LanMountainDesktop.Launcher.Resources;
using LanMountainDesktop.Launcher.Services;
namespace LanMountainDesktop.Launcher.Views;
@@ -31,6 +32,9 @@ public partial class OobeWindow : Window
private string _selectedAccentColor = "#0078D4";
private MonetSource _selectedMonetSource = MonetSource.Wallpaper;
private readonly bool _startupSlideUiAvailable;
private bool _suppressOobeStartupTransitionHandlers;
public OobeWindow()
{
AvaloniaXamlLoader.Load(this);
@@ -39,6 +43,7 @@ public partial class OobeWindow : Window
var appRoot = AppDomain.CurrentDomain.BaseDirectory;
_resolver = new DataLocationResolver(appRoot);
_startupSlideUiAvailable = OperatingSystem.IsWindows();
}
public void SetDebugMode(bool isDebugMode)
@@ -181,7 +186,27 @@ public partial class OobeWindow : Window
};
}
// 步骤 4: 隐私设置页面
if (this.FindControl<Button>("StartupPresentationBackButton") is { } startupPresentationBack)
{
startupPresentationBack.Click += OnStartupPresentationBackClick;
}
if (this.FindControl<Button>("StartupPresentationNextButton") is { } startupPresentationNext)
{
startupPresentationNext.Click += OnStartupPresentationNextClick;
}
if (this.FindControl<ToggleSwitch>("OobeSlideTransitionToggle") is { } oobeSlideTransition)
{
oobeSlideTransition.IsCheckedChanged += OnOobeStartupSlideTransitionChanged;
}
if (this.FindControl<ToggleSwitch>("OobeFadeTransitionToggle") is { } oobeFadeTransition)
{
oobeFadeTransition.IsCheckedChanged += OnOobeStartupFadeTransitionChanged;
}
// 步骤 5: 隐私设置页面
if (this.FindControl<Button>("PrivacyBackButton") is { } privacyBackButton)
{
privacyBackButton.Click += OnPrivacyBackClick;
@@ -203,7 +228,7 @@ public partial class OobeWindow : Window
privacyCheckBox.IsCheckedChanged += OnPrivacyAgreementChanged;
}
// 步骤 5: 欢迎完成页面
// 步骤 6: 欢迎完成页面
if (this.FindControl<Button>("EnterButton") is { } enterButton)
{
enterButton.Click += OnEnterClick;
@@ -248,7 +273,7 @@ public partial class OobeWindow : Window
if (typingTextBlock == null || cursorBorder == null) return;
// 打字机效果:阑山桌面 LanMountain Desktop在同一行
var fullText = "阑山桌面 LanMountain Desktop";
var fullText = Strings.Oobe_TypingAppName;
for (int i = 0; i <= fullText.Length; i++)
{
typingTextBlock.Text = fullText.Substring(0, i);
@@ -351,7 +376,7 @@ public partial class OobeWindow : Window
}
// 打字机效果:下一代
var nextGenText = "下一代";
var nextGenText = Strings.Oobe_TypingNextGen;
for (int i = 0; i <= nextGenText.Length; i++)
{
nextGenTextBlock.Text = nextGenText.Substring(0, i);
@@ -368,7 +393,7 @@ public partial class OobeWindow : Window
}
// 打字机效果:互动信息看板
var dashboardText = "互动信息看板";
var dashboardText = Strings.Oobe_TypingDashboard;
for (int i = 0; i <= dashboardText.Length; i++)
{
dashboardTextBlock.Text = dashboardText.Substring(0, i);
@@ -460,11 +485,189 @@ public partial class OobeWindow : Window
await NavigateToStep(4);
}
// 启动与展示OOBE 步骤 4
private async void OnStartupPresentationBackClick(object? sender, RoutedEventArgs e)
{
if (_isTransitioning) return;
await NavigateToStep(3);
}
private async void OnStartupPresentationNextClick(object? sender, RoutedEventArgs e)
{
if (_isTransitioning) return;
SaveOobeStartupPresentation();
await NavigateToStep(5);
}
private void SaveOobeStartupPresentation()
{
try
{
var choices = CollectOobeStartupChoices();
var path = HostAppSettingsOobeMerger.GetSettingsFilePath(_resolver.ResolveDataRoot());
HostAppSettingsOobeMerger.MergeStartupPresentation(path, choices);
if (OperatingSystem.IsWindows())
{
_ = new LauncherWindowsStartupService().SetEnabled(choices.AutoStartWithWindows);
}
Logger.Info($"[OobeWindow] 启动与展示已写入 '{path}'.");
}
catch (Exception ex)
{
Logger.Warn($"[OobeWindow] 启动与展示保存失败: {ex.Message}");
}
}
private void RefreshOobeStartupPresentationFromDisk()
{
var path = HostAppSettingsOobeMerger.GetSettingsFilePath(_resolver.ResolveDataRoot());
var defaults = HostAppSettingsOobeMerger.LoadStartupDefaults(path);
if (this.FindControl<Border>("OobeSlideTransitionSection") is { } slideSection)
{
slideSection.IsVisible = _startupSlideUiAvailable;
}
if (this.FindControl<TextBlock>("OobeAutoStartDescriptionText") is { } autoStartDesc)
{
autoStartDesc.Text = OperatingSystem.IsWindows()
? Strings.Oobe_AutoStartDesc
: Strings.Oobe_AutoStartDescNonWindows;
}
_suppressOobeStartupTransitionHandlers = true;
try
{
if (this.FindControl<ToggleSwitch>("OobeShowInTaskbarToggle") is { } taskbar)
{
taskbar.IsChecked = defaults.ShowInTaskbar;
}
if (_startupSlideUiAvailable)
{
if (this.FindControl<ToggleSwitch>("OobeSlideTransitionToggle") is { } slide)
{
slide.IsChecked = defaults.EnableSlideTransition;
}
if (this.FindControl<ToggleSwitch>("OobeFadeTransitionToggle") is { } fade)
{
fade.IsChecked = defaults.EnableFadeTransition;
fade.IsEnabled = !defaults.EnableSlideTransition;
}
}
if (this.FindControl<ToggleSwitch>("OobeFusedPopupToggle") is { } fused)
{
fused.IsChecked = defaults.FusedPopupExperience;
}
if (this.FindControl<ToggleSwitch>("OobeAutoStartToggle") is { } autoStart)
{
autoStart.IsChecked = defaults.AutoStartWithWindows;
autoStart.IsEnabled = OperatingSystem.IsWindows();
}
}
finally
{
_suppressOobeStartupTransitionHandlers = false;
}
}
private HostAppSettingsStartupChoices CollectOobeStartupChoices()
{
var showTaskbar = this.FindControl<ToggleSwitch>("OobeShowInTaskbarToggle")?.IsChecked == true;
var fused = this.FindControl<ToggleSwitch>("OobeFusedPopupToggle")?.IsChecked == true;
var autoStart = OperatingSystem.IsWindows() &&
this.FindControl<ToggleSwitch>("OobeAutoStartToggle")?.IsChecked == true;
bool fade;
bool slide;
if (_startupSlideUiAvailable)
{
slide = this.FindControl<ToggleSwitch>("OobeSlideTransitionToggle")?.IsChecked == true;
fade = this.FindControl<ToggleSwitch>("OobeFadeTransitionToggle")?.IsChecked == true;
}
else
{
slide = false;
fade = true;
}
return new HostAppSettingsStartupChoices(
ShowInTaskbar: showTaskbar,
EnableFadeTransition: fade,
EnableSlideTransition: slide,
FusedPopupExperience: fused,
AutoStartWithWindows: autoStart);
}
private void OnOobeStartupSlideTransitionChanged(object? sender, RoutedEventArgs e)
{
if (!_startupSlideUiAvailable || _suppressOobeStartupTransitionHandlers)
{
return;
}
if (sender is not ToggleSwitch slide || slide.IsChecked != true)
{
if (this.FindControl<ToggleSwitch>("OobeFadeTransitionToggle") is { } fade)
{
fade.IsEnabled = true;
}
return;
}
_suppressOobeStartupTransitionHandlers = true;
try
{
if (this.FindControl<ToggleSwitch>("OobeFadeTransitionToggle") is { } fade)
{
fade.IsChecked = false;
fade.IsEnabled = false;
}
}
finally
{
_suppressOobeStartupTransitionHandlers = false;
}
}
private void OnOobeStartupFadeTransitionChanged(object? sender, RoutedEventArgs e)
{
if (!_startupSlideUiAvailable || _suppressOobeStartupTransitionHandlers)
{
return;
}
if (sender is not ToggleSwitch fade || fade.IsChecked != true)
{
return;
}
_suppressOobeStartupTransitionHandlers = true;
try
{
if (this.FindControl<ToggleSwitch>("OobeSlideTransitionToggle") is { } slide)
{
slide.IsChecked = false;
}
fade.IsEnabled = true;
}
finally
{
_suppressOobeStartupTransitionHandlers = false;
}
}
// 隐私设置页面按钮
private async void OnPrivacyBackClick(object? sender, RoutedEventArgs e)
{
if (_isTransitioning) return;
await NavigateToStep(3);
await NavigateToStep(4);
}
private async void OnPrivacyNextClick(object? sender, RoutedEventArgs e)
@@ -474,7 +677,7 @@ public partial class OobeWindow : Window
// 保存隐私设置
SavePrivacySettings();
await NavigateToStep(5);
await NavigateToStep(6);
}
private void OnViewPrivacyPolicyClick(object? sender, RoutedEventArgs e)
@@ -568,7 +771,7 @@ public partial class OobeWindow : Window
}
if (this.FindControl<TextBlock>("MigrationInfoText") is { } migrationText)
{
migrationText.Text = "检测到现有数据,选择便携模式时将自动迁移。";
migrationText.Text = Strings.Oobe_MigrationDetected;
}
}
}
@@ -712,8 +915,9 @@ public partial class OobeWindow : Window
1 => this.FindControl<Grid>("TypingStep"),
2 => this.FindControl<Grid>("ThemeStep"),
3 => this.FindControl<Grid>("DataLocationStep"),
4 => this.FindControl<Grid>("PrivacyStep"),
5 => this.FindControl<Grid>("WelcomeStep"),
4 => this.FindControl<Grid>("StartupPresentationStep"),
5 => this.FindControl<Grid>("PrivacyStep"),
6 => this.FindControl<Grid>("WelcomeStep"),
_ => null
};
@@ -723,8 +927,9 @@ public partial class OobeWindow : Window
1 => this.FindControl<Grid>("TypingStep"),
2 => this.FindControl<Grid>("ThemeStep"),
3 => this.FindControl<Grid>("DataLocationStep"),
4 => this.FindControl<Grid>("PrivacyStep"),
5 => this.FindControl<Grid>("WelcomeStep"),
4 => this.FindControl<Grid>("StartupPresentationStep"),
5 => this.FindControl<Grid>("PrivacyStep"),
6 => this.FindControl<Grid>("WelcomeStep"),
_ => null
};
@@ -737,6 +942,11 @@ public partial class OobeWindow : Window
await AnimateOpacityAsync(currentStepControl, 1, 0, AnimationDurationMs);
currentStepControl.IsVisible = false;
if (step == 4)
{
RefreshOobeStartupPresentationFromDisk();
}
nextStepControl.IsVisible = true;
nextStepControl.Opacity = 0;
await AnimateOpacityAsync(nextStepControl, 0, 1, AnimationDurationMs);

View File

@@ -4,10 +4,12 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:md="clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia"
xmlns:views="clr-namespace:LanMountainDesktop.Launcher.Views"
xmlns:res="clr-namespace:LanMountainDesktop.Launcher.Resources"
mc:Ignorable="d"
x:Class="LanMountainDesktop.Launcher.Views.PrivacyPolicyWindow"
x:DataType="views:PrivacyPolicyViewModel"
Title="阑山桌面遥测隐私数据收集协议"
x:CompileBindings="False"
Title="{x:Static res:Strings.Privacy_Title}"
Width="800"
Height="600"
MinWidth="600"
@@ -25,11 +27,11 @@
BorderThickness="0,0,0,1"
Padding="24,16">
<StackPanel Spacing="4">
<TextBlock Text="阑山桌面遥测隐私数据收集协议"
<TextBlock Text="{x:Static res:Strings.Privacy_Header}"
FontSize="20"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="请仔细阅读以下协议内容,了解我们如何收集、使用和保护您的数据"
<TextBlock Text="{x:Static res:Strings.Privacy_Description}"
FontSize="13"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
@@ -54,7 +56,7 @@
HorizontalAlignment="Right"
Spacing="12">
<Button x:Name="CloseButton"
Content="关闭"
Content="{x:Static res:Strings.Privacy_ButtonClose}"
Theme="{DynamicResource AccentButtonTheme}"
Width="100" />
</StackPanel>

View File

@@ -3,10 +3,12 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:LanMountainDesktop.Launcher.Views"
xmlns:res="clr-namespace:LanMountainDesktop.Launcher.Resources"
mc:Ignorable="d"
x:Class="LanMountainDesktop.Launcher.Views.SplashWindow"
x:DataType="views:SplashWindow"
Title="LanMountain Desktop"
x:CompileBindings="False"
Title="{x:Static res:Strings.Splash_Title}"
Width="480"
Height="320"
CanResize="False"
@@ -66,7 +68,7 @@
FontSize="11"
Foreground="#B9C0CC"
HorizontalAlignment="Right"
Text="Initializing..." />
Text="{x:Static res:Strings.Splash_StatusInitializing}" />
</Grid>
<ProgressBar x:Name="ProgressIndicator"

View File

@@ -6,6 +6,7 @@ using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.Launcher.Resources;
using LanMountainDesktop.Launcher.Services;
namespace LanMountainDesktop.Launcher.Views;
@@ -184,7 +185,7 @@ public partial class SplashWindow : Window, ISplashStageReporter
return;
}
UpdateStatus("[Debug Mode] Splash Preview");
UpdateStatus(Strings.Splash_DebugPreview);
}
private void OnVersionTextClick(object? sender, PointerPressedEventArgs e)

View File

@@ -3,12 +3,14 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:LanMountainDesktop.Launcher.Views"
xmlns:res="clr-namespace:LanMountainDesktop.Launcher.Resources"
mc:Ignorable="d"
d:DesignWidth="480"
d:DesignHeight="320"
x:Class="LanMountainDesktop.Launcher.Views.UpdateWindow"
x:DataType="views:UpdateWindow"
Title="阑山桌面 - 更新"
x:CompileBindings="False"
Title="{x:Static res:Strings.Update_Title}"
Width="480"
Height="320"
CanResize="False"
@@ -26,7 +28,7 @@
<Grid VerticalAlignment="Top" Margin="24,24,24,0">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center" Spacing="8">
<TextBlock x:Name="TitleText"
Text="阑山桌面"
Text="{x:Static res:Strings.Update_AppName}"
FontSize="24"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
@@ -34,7 +36,7 @@
CornerRadius="4"
Padding="6,2"
VerticalAlignment="Center">
<TextBlock Text="Update"
<TextBlock Text="{x:Static res:Strings.Update_StatusUpdate}"
FontSize="11"
FontWeight="SemiBold"
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}" />
@@ -80,7 +82,7 @@
Opacity="0.8"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Text="正在更新,请稍候..." />
Text="{x:Static res:Strings.Update_StatusUpdating}" />
<!-- 右下角:百分比 -->
<TextBlock x:Name="PercentText"

View File

@@ -1,6 +1,7 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using LanMountainDesktop.Launcher.Resources;
namespace LanMountainDesktop.Launcher.Views;
@@ -87,12 +88,12 @@ public partial class UpdateWindow : Window
if (success)
{
statusText.Text = "更新完成";
statusText.Text = Strings.Update_Complete;
}
else
{
titleText.Text = "更新失败";
statusText.Text = errorMessage ?? "更新过程中发生错误";
titleText.Text = Strings.Update_Failed;
statusText.Text = errorMessage ?? Strings.Update_FailedMessage;
}
});
}
@@ -115,8 +116,8 @@ public partial class UpdateWindow : Window
if (isDebugMode)
{
titleText.Text = "[调试模式] 更新页面";
statusText.Text = "预览更新进度界面";
titleText.Text = Strings.Update_DebugTitle;
statusText.Text = Strings.Update_DebugMessage;
}
});
}