mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-23 18:04:26 +08:00
Launcher fix (#6)
* fix.hy3试图修复中 * Resolve dev paths and fix splash UI thread Compute a solutionRoot and expand development search paths (LanMountainDesktop and dev-test) in DeploymentLocator, add logging when scanning/finding hosts, and return distinct full paths. Ensure backward-compatible path checks. Fix cross-thread UI calls: invoke splashWindow.DismissAsync on the UI thread in LauncherFlowCoordinator, and make SplashWindow.DismissAsync ensure it runs on the UI thread before closing (simplified Close call). These changes improve development host discovery and prevent UI-thread access issues during shutdown. * Add configurable data location (portable/system) Introduce support for choosing and resolving the application's data root (system user dir vs. portable app folder). Adds DataLocationConfig model, DataLocationResolver (load/save/resolve/migrate), a UI prompt (DataLocationPromptWindow) and an OOBE step (DataLocationOobeStep) to let users pick and optionally migrate existing data. Wire the chosen data root into the launcher flow and host launch plan (forwarded via --data-root and LMD_DATA_ROOT), and add AppDataPathProvider to let runtime services read the effective data root (initialized in Program.Main). Update various services (logging, settings, DB, plugin/market, startup registry, etc.) to use the new provider/resolver and register the config type in the JSON context. This enables portable installs, safe migration, and runtime overrides via CLI or environment variable. * Add dev/debug startup flow and launch profiles Handle design-time initialization and add a developer debug startup path: App now skips normal startup when in design mode and shows a DevDebugWindow when running in debug (unless a preview or apply-update command). CommandContext.IsDebugMode is extended to include DOTNET_ENVIRONMENT=Development via a new IsDevelopmentEnvironment helper. Program.Main and BuildAvaloniaApp are made public to aid tooling. Added multiple launchSettings profiles for debug and preview commands that set DOTNET_ENVIRONMENT=Development to simplify IDE debugging and UI previewing. * Simplify splash to fade; add themed about banners Simplify splash startup visuals by removing the multi-mode/slide behavior and always using a fade animation. Update App to create SplashWindow without a StartupVisualMode parameter and remove related fields, layout configuration, slide animation, and easing helpers from SplashWindow. Clean up unused using. Replace the single about_banner asset with theme-aware variants (about_banner_dark.png and about_banner_light.png), delete the old about_banner.png, and update AboutSettingsPage to use a DynamicResource ImageBrush (AboutBannerBrush) that selects the appropriate banner per theme. * Use AppJsonContext for startup state serialization Switch serialization to the source-generated System.Text.Json context: add JsonSerializable(typeof(StartupAttemptRecord)) to AppJsonContext and replace the previous JsonSerializerOptions-based Serialize/Deserialize calls with AppJsonContext.Default.StartupAttemptRecord. Also remove the now-unused SerializerOptions field. Additionally, update .gitignore to exclude /test-aot-publish. * Add OOBE redesign, theme & data location support Introduce a redesigned OOBE flow and data-location/theme support across the launcher. Adds a new ThemeService for applying light/dark and accent colors; integrates FluentIcons.Avalonia package for icons. Overhauls OobeWindow (UX animations, typing effect, multi-step theme and data-location pages, Monet options, and final welcome step) and its code-behind to handle step navigation, accent selection, and data-location resolution. Adds DataLocation UI and handlers (DataLocationPromptWindow changes, DataLocation resolver usage) and wires a DevDebug UI for toggling/opening the data-location page. UpdateEngineService now resolves the launcher root via DataLocationResolver. Misc: update various view models, localization entries and remove TrimmerRoots.xml. * Refactor data location paths and add background service Refactor DataLocationResolver to centralize data path resolution (ResolveLauncherDataPath, ResolveDesktopDataPath, ResolveConfigPath, ResolveLauncherLogsPath, ResolveLauncherStatePath) and replace usages of the previous ".launcher" layout with a "Launcher" folder. Update API: LoadConfig/SaveConfig reorganized and ApplyLocationChoice now accepts an optional custom path and migration flag; migration logic updated accordingly. Update dependent services and views (Logger, DeploymentLocator, UpdateEngineService, OobeStateService, StartupAttemptRegistry, LauncherDebugSettingsStore, OobeWindow) to use the new resolver APIs and paths. Add LauncherBackgroundService to load/validate/cache a custom splash background image and wire it into SplashWindow (AXAML/Axaml.cs) with UI placeholders and overlay. Misc: minor cleanup of Oobe/Splash XAML and related code adjustments and logging improvements.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
@@ -150,6 +150,37 @@ public partial class App : Application
|
||||
|
||||
_settingsFacade.Settings.Changed += OnSettingsChanged;
|
||||
_appearanceThemeService.Changed += OnAppearanceThemeChanged;
|
||||
|
||||
// 监听系统主题变化
|
||||
PropertyChanged += OnAppPropertyChanged;
|
||||
}
|
||||
|
||||
private void OnAppPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property == ActualThemeVariantProperty)
|
||||
{
|
||||
// 系统主题变化时,检查是否需要更新
|
||||
var themeMode = _settingsFacade.Settings.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App).ThemeMode;
|
||||
if (string.Equals(themeMode, ThemeAppearanceValues.ThemeModeFollowSystem, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var newThemeVariant = (ThemeVariant?)e.NewValue;
|
||||
var isDark = newThemeVariant == ThemeVariant.Dark;
|
||||
|
||||
// 同步到设置
|
||||
var currentThemeState = _settingsFacade.Theme.Get();
|
||||
if (currentThemeState.IsNightMode != isDark)
|
||||
{
|
||||
_settingsFacade.Theme.Save(currentThemeState with { IsNightMode = isDark });
|
||||
}
|
||||
|
||||
// 应用主题
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
ApplyThemeFromSettings();
|
||||
RefreshTrayIconContent();
|
||||
}, DispatcherPriority.Background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
@@ -762,9 +793,30 @@ public partial class App : Application
|
||||
private void ApplyThemeFromSettings()
|
||||
{
|
||||
var snapshot = _appearanceThemeService.GetCurrent();
|
||||
RequestedThemeVariant = snapshot.IsNightMode
|
||||
? ThemeVariant.Dark
|
||||
: ThemeVariant.Light;
|
||||
var themeMode = _settingsFacade.Settings.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App).ThemeMode;
|
||||
|
||||
// 处理跟随系统主题模式
|
||||
if (string.Equals(themeMode, ThemeAppearanceValues.ThemeModeFollowSystem, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// 使用 Avalonia 的系统主题检测
|
||||
var systemTheme = ActualThemeVariant;
|
||||
RequestedThemeVariant = systemTheme;
|
||||
|
||||
// 同步 IsNightMode 到设置
|
||||
var isSystemDark = systemTheme == ThemeVariant.Dark;
|
||||
var currentThemeState = _settingsFacade.Theme.Get();
|
||||
if (currentThemeState.IsNightMode != isSystemDark)
|
||||
{
|
||||
_settingsFacade.Theme.Save(currentThemeState with { IsNightMode = isSystemDark });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RequestedThemeVariant = snapshot.IsNightMode
|
||||
? ThemeVariant.Dark
|
||||
: ThemeVariant.Light;
|
||||
}
|
||||
|
||||
ApplyAdaptiveThemeResources();
|
||||
}
|
||||
|
||||
@@ -1054,6 +1106,7 @@ public partial class App : Application
|
||||
var themeChanged =
|
||||
refreshAll ||
|
||||
changedKeys.Contains(nameof(AppSettingsSnapshot.IsNightMode), StringComparer.OrdinalIgnoreCase) ||
|
||||
changedKeys.Contains(nameof(AppSettingsSnapshot.ThemeMode), StringComparer.OrdinalIgnoreCase) ||
|
||||
changedKeys.Contains(nameof(AppSettingsSnapshot.UseSystemChrome), StringComparer.OrdinalIgnoreCase) ||
|
||||
changedKeys.Contains(nameof(AppSettingsSnapshot.CornerRadiusStyle), StringComparer.OrdinalIgnoreCase) ||
|
||||
(string.Equals(liveAppearance.ThemeColorMode, ThemeAppearanceValues.ColorModeSeedMonet, StringComparison.OrdinalIgnoreCase) &&
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 999 KiB |
BIN
LanMountainDesktop/Assets/about_banner_dark.png
Normal file
BIN
LanMountainDesktop/Assets/about_banner_dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 682 KiB |
BIN
LanMountainDesktop/Assets/about_banner_light.png
Normal file
BIN
LanMountainDesktop/Assets/about_banner_light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
@@ -349,6 +349,11 @@
|
||||
"settings.appearance.title": "Appearance",
|
||||
"settings.appearance.description": "Adjust theme source, system material, and window chrome.",
|
||||
"settings.appearance.theme_header": "Theme",
|
||||
"settings.appearance.theme_mode_label": "Theme mode",
|
||||
"settings.appearance.theme_mode_desc": "Choose light, dark, or follow system theme.",
|
||||
"settings.appearance.theme_mode.light": "Light",
|
||||
"settings.appearance.theme_mode.dark": "Dark",
|
||||
"settings.appearance.theme_mode.follow_system": "Follow system",
|
||||
"settings.color.enable_night_mode_toggle": "Enable night mode",
|
||||
"settings.color.use_system_chrome_toggle": "Use system window chrome",
|
||||
"settings.color.theme_color_label": "Theme accent color",
|
||||
|
||||
@@ -292,6 +292,11 @@
|
||||
"settings.appearance.title": "外観",
|
||||
"settings.appearance.description": "テーマソース、システムマテリアル、ウィンドウクロームを調整します。",
|
||||
"settings.appearance.theme_header": "テーマ",
|
||||
"settings.appearance.theme_mode_label": "テーマモード",
|
||||
"settings.appearance.theme_mode_desc": "ライト、ダーク、またはシステムに従うを選択してください。",
|
||||
"settings.appearance.theme_mode.light": "ライト",
|
||||
"settings.appearance.theme_mode.dark": "ダーク",
|
||||
"settings.appearance.theme_mode.follow_system": "システムに従う",
|
||||
"settings.color.enable_night_mode_toggle": "夜モードを有効にする",
|
||||
"settings.color.use_system_chrome_toggle": "システムのウィンドウクロームを使用",
|
||||
"settings.color.theme_color_label": "テーマのアクセントカラー",
|
||||
|
||||
@@ -338,6 +338,11 @@
|
||||
"settings.appearance.title": "외관",
|
||||
"settings.appearance.description": "테마 소스, 시스템 소재 및 창 외관을 조정합니다.",
|
||||
"settings.appearance.theme_header": "테마",
|
||||
"settings.appearance.theme_mode_label": "테마 모드",
|
||||
"settings.appearance.theme_mode_desc": "라이트, 다크 또는 시스템 설정 따르기를 선택하세요.",
|
||||
"settings.appearance.theme_mode.light": "라이트",
|
||||
"settings.appearance.theme_mode.dark": "다크",
|
||||
"settings.appearance.theme_mode.follow_system": "시스템 설정 따르기",
|
||||
"settings.color.enable_night_mode_toggle": "야간 모드 활성화",
|
||||
"settings.color.use_system_chrome_toggle": "시스템 창 제목 표시줄 사용",
|
||||
"settings.color.theme_color_label": "테마 강조 색상",
|
||||
|
||||
@@ -344,6 +344,11 @@
|
||||
"settings.appearance.title": "外观",
|
||||
"settings.appearance.description": "调整主题来源、系统材质与窗口外观。",
|
||||
"settings.appearance.theme_header": "主题",
|
||||
"settings.appearance.theme_mode_label": "主题模式",
|
||||
"settings.appearance.theme_mode_desc": "选择日间、夜间或跟随系统主题。",
|
||||
"settings.appearance.theme_mode.light": "日间",
|
||||
"settings.appearance.theme_mode.dark": "夜间",
|
||||
"settings.appearance.theme_mode.follow_system": "跟随系统",
|
||||
"settings.color.enable_night_mode_toggle": "启用夜间模式",
|
||||
"settings.color.use_system_chrome_toggle": "使用系统窗口标题栏",
|
||||
"settings.color.theme_color_label": "主题强调色",
|
||||
|
||||
@@ -27,6 +27,8 @@ public sealed class AppSettingsSnapshot
|
||||
|
||||
public string? SelectedWallpaperSeed { get; set; }
|
||||
|
||||
public string ThemeMode { get; set; } = "light";
|
||||
|
||||
public string? WallpaperPath { get; set; }
|
||||
|
||||
public string WallpaperType { get; set; } = "Image";
|
||||
|
||||
@@ -22,6 +22,7 @@ public sealed class Program
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
AppLogger.Initialize();
|
||||
AppDataPathProvider.Initialize(args);
|
||||
DevPluginOptions.Parse(args);
|
||||
RegisterGlobalExceptionLogging();
|
||||
var restartParentProcessId = LauncherRuntimeMetadata.GetRestartParentProcessId(args);
|
||||
|
||||
66
LanMountainDesktop/Services/AppDataPathProvider.cs
Normal file
66
LanMountainDesktop/Services/AppDataPathProvider.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
namespace LanMountainDesktop.Services;
|
||||
|
||||
public static class AppDataPathProvider
|
||||
{
|
||||
private static string? _overriddenDataRoot;
|
||||
|
||||
public static void Initialize(string[] args)
|
||||
{
|
||||
var dataRoot = ResolveDataRootFromArgs(args);
|
||||
if (!string.IsNullOrWhiteSpace(dataRoot))
|
||||
{
|
||||
_overriddenDataRoot = Path.GetFullPath(dataRoot);
|
||||
AppLogger.Info("AppDataPath", $"Data root overridden by launcher: '{_overriddenDataRoot}'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var envDataRoot = Environment.GetEnvironmentVariable("LMD_DATA_ROOT");
|
||||
if (!string.IsNullOrWhiteSpace(envDataRoot))
|
||||
{
|
||||
_overriddenDataRoot = Path.GetFullPath(envDataRoot);
|
||||
AppLogger.Info("AppDataPath", $"Data root overridden by environment variable: '{_overriddenDataRoot}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetDataRoot()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_overriddenDataRoot))
|
||||
{
|
||||
return _overriddenDataRoot;
|
||||
}
|
||||
|
||||
return Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"LanMountainDesktop");
|
||||
}
|
||||
|
||||
public static string GetSettingsDirectory()
|
||||
{
|
||||
return GetDataRoot();
|
||||
}
|
||||
|
||||
public static string GetPluginMarketDirectory()
|
||||
{
|
||||
return Path.Combine(GetDataRoot(), "PluginMarket");
|
||||
}
|
||||
|
||||
public static string GetWallpapersDirectory()
|
||||
{
|
||||
return Path.Combine(GetDataRoot(), "Wallpapers");
|
||||
}
|
||||
|
||||
private static string? ResolveDataRootFromArgs(string[] args)
|
||||
{
|
||||
const string prefix = "--data-root=";
|
||||
foreach (var arg in args)
|
||||
{
|
||||
if (arg.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return arg[prefix.Length..];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,7 @@ public sealed class AppDatabaseService
|
||||
|
||||
public AppDatabaseService()
|
||||
{
|
||||
var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
var dataDirectory = Path.Combine(appData, "LanMountainDesktop");
|
||||
var dataDirectory = AppDataPathProvider.GetDataRoot();
|
||||
_databasePath = Path.Combine(dataDirectory, "app.db");
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,7 @@ public sealed class AppSettingsService
|
||||
|
||||
public AppSettingsService()
|
||||
{
|
||||
var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
var settingsDirectory = Path.Combine(appData, "LanMountainDesktop");
|
||||
var settingsDirectory = AppDataPathProvider.GetSettingsDirectory();
|
||||
_settingsPath = Path.Combine(settingsDirectory, "settings.json");
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,7 @@ public sealed class LauncherSettingsService
|
||||
|
||||
public LauncherSettingsService()
|
||||
{
|
||||
var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
var settingsDirectory = Path.Combine(appData, "LanMountainDesktop");
|
||||
var settingsDirectory = AppDataPathProvider.GetSettingsDirectory();
|
||||
_settingsPath = Path.Combine(settingsDirectory, "launcher-settings.json");
|
||||
_legacyAppSettingsPath = Path.Combine(settingsDirectory, "settings.json");
|
||||
}
|
||||
|
||||
@@ -76,9 +76,7 @@ internal sealed class SqliteComponentDomainStorage :
|
||||
public SqliteComponentDomainStorage(string? settingsRoot = null)
|
||||
{
|
||||
_settingsRoot = string.IsNullOrWhiteSpace(settingsRoot)
|
||||
? Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"LanMountainDesktop")
|
||||
? AppDataPathProvider.GetDataRoot()
|
||||
: settingsRoot.Trim();
|
||||
_dbPath = Path.Combine(_settingsRoot, "component-state.db");
|
||||
_layoutJsonPath = Path.Combine(_settingsRoot, "desktop-layout-settings.json");
|
||||
|
||||
@@ -33,7 +33,8 @@ public sealed record ThemeAppearanceSettingsState(
|
||||
string CornerRadiusStyle = GlobalAppearanceSettings.DefaultCornerRadiusStyle,
|
||||
string ThemeColorMode = ThemeAppearanceValues.ColorModeDefaultNeutral,
|
||||
string SystemMaterialMode = ThemeAppearanceValues.MaterialNone,
|
||||
string? SelectedWallpaperSeed = null);
|
||||
string? SelectedWallpaperSeed = null,
|
||||
string ThemeMode = ThemeAppearanceValues.ThemeModeLight);
|
||||
public sealed record StatusBarSettingsState(
|
||||
IReadOnlyList<string> TopStatusComponentIds,
|
||||
IReadOnlyList<string> PinnedTaskbarActions,
|
||||
|
||||
@@ -167,10 +167,7 @@ internal sealed class WallpaperMediaService : IWallpaperMediaService
|
||||
|
||||
public WallpaperMediaService()
|
||||
{
|
||||
var appDataRoot = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"LanMountainDesktop");
|
||||
_wallpapersDirectory = Path.Combine(appDataRoot, "Wallpapers");
|
||||
_wallpapersDirectory = AppDataPathProvider.GetWallpapersDirectory();
|
||||
}
|
||||
|
||||
public WallpaperMediaType DetectMediaType(string? path)
|
||||
@@ -269,7 +266,21 @@ internal sealed class ThemeAppearanceService : IThemeAppearanceService
|
||||
cornerRadiusStyle,
|
||||
ThemeAppearanceValues.NormalizeThemeColorMode(snapshot.ThemeColorMode, snapshot.ThemeColor),
|
||||
ThemeAppearanceValues.NormalizeSystemMaterialMode(snapshot.SystemMaterialMode),
|
||||
snapshot.SelectedWallpaperSeed);
|
||||
snapshot.SelectedWallpaperSeed,
|
||||
NormalizeThemeMode(snapshot.ThemeMode));
|
||||
}
|
||||
|
||||
private static string NormalizeThemeMode(string? value)
|
||||
{
|
||||
if (string.Equals(value, ThemeAppearanceValues.ThemeModeDark, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ThemeAppearanceValues.ThemeModeDark;
|
||||
}
|
||||
if (string.Equals(value, ThemeAppearanceValues.ThemeModeFollowSystem, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ThemeAppearanceValues.ThemeModeFollowSystem;
|
||||
}
|
||||
return ThemeAppearanceValues.ThemeModeLight;
|
||||
}
|
||||
|
||||
public void Save(ThemeAppearanceSettingsState state)
|
||||
@@ -326,6 +337,13 @@ internal sealed class ThemeAppearanceService : IThemeAppearanceService
|
||||
changedKeys.Add(nameof(AppSettingsSnapshot.SelectedWallpaperSeed));
|
||||
}
|
||||
|
||||
var normalizedThemeMode = NormalizeThemeMode(state.ThemeMode);
|
||||
if (!string.Equals(snapshot.ThemeMode, normalizedThemeMode, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
snapshot.ThemeMode = normalizedThemeMode;
|
||||
changedKeys.Add(nameof(AppSettingsSnapshot.ThemeMode));
|
||||
}
|
||||
|
||||
if (changedKeys.Count == 0)
|
||||
{
|
||||
return;
|
||||
@@ -1026,10 +1044,7 @@ internal sealed class PluginCatalogSettingsService : IPluginCatalogSettingsServi
|
||||
{
|
||||
_pluginRuntimeService = pluginRuntimeService;
|
||||
|
||||
var dataRoot = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"LanMountainDesktop",
|
||||
"PluginMarket");
|
||||
var dataRoot = AppDataPathProvider.GetPluginMarketDirectory();
|
||||
var cacheService = new AirAppMarketCacheService(dataRoot);
|
||||
_indexService = new AirAppMarketIndexService(cacheService);
|
||||
if (_pluginRuntimeService is not null)
|
||||
@@ -1049,10 +1064,7 @@ internal sealed class PluginCatalogSettingsService : IPluginCatalogSettingsServi
|
||||
return;
|
||||
}
|
||||
|
||||
var dataRoot = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"LanMountainDesktop",
|
||||
"PluginMarket");
|
||||
var dataRoot = AppDataPathProvider.GetPluginMarketDirectory();
|
||||
_installService = new AirAppMarketInstallService(_pluginRuntimeService, dataRoot);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,9 +26,7 @@ internal sealed class SettingsService : ISettingsService
|
||||
|
||||
public SettingsService()
|
||||
{
|
||||
var root = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"LanMountainDesktop");
|
||||
var root = AppDataPathProvider.GetDataRoot();
|
||||
_pluginSettingsPath = Path.Combine(root, "plugin-settings.json");
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@ public static class ThemeAppearanceValues
|
||||
public const string ColorSchemeFollowSystem = "follow_system";
|
||||
public const string ColorSchemeNative = "native";
|
||||
|
||||
public const string ThemeModeLight = "light";
|
||||
public const string ThemeModeDark = "dark";
|
||||
public const string ThemeModeFollowSystem = "follow_system";
|
||||
|
||||
public const string MaterialNone = "none";
|
||||
public const string MaterialMica = "mica";
|
||||
public const string MaterialAcrylic = "acrylic";
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<linker>
|
||||
<!-- Avalonia and UI framework assemblies that should not be trimmed -->
|
||||
<assembly fullname="Avalonia" preserve="all" />
|
||||
<assembly fullname="Avalonia.Controls" preserve="all" />
|
||||
<assembly fullname="Avalonia.Core" preserve="all" />
|
||||
<assembly fullname="Avalonia.Dialogs" preserve="all" />
|
||||
<assembly fullname="Avalonia.Desktop" preserve="all" />
|
||||
<assembly fullname="Avalonia.Themes.Fluent" preserve="all" />
|
||||
<assembly fullname="Avalonia.Fonts.Inter" preserve="all" />
|
||||
|
||||
<!-- FluentUI packages -->
|
||||
<assembly fullname="FluentAvaloniaUI" preserve="all" />
|
||||
<assembly fullname="FluentIcons.Avalonia" preserve="all" />
|
||||
<assembly fullname="FluentIcons.Avalonia.Fluent" preserve="all" />
|
||||
|
||||
<!-- Media and rendering -->
|
||||
<assembly fullname="LibVLCSharp" preserve="all" />
|
||||
<assembly fullname="LibVLCSharp.Avalonia" preserve="all" />
|
||||
<assembly fullname="WebView.Avalonia" preserve="all" />
|
||||
<assembly fullname="WebView.Avalonia.Desktop" preserve="all" />
|
||||
|
||||
<!-- MVVM and utilities -->
|
||||
<assembly fullname="CommunityToolkit.Mvvm" preserve="all" />
|
||||
<assembly fullname="YamlDotNet" preserve="all" />
|
||||
<assembly fullname="DotNetCampus.AvaloniaInkCanvas" preserve="all" />
|
||||
<assembly fullname="PortAudioSharp2" preserve="all" />
|
||||
|
||||
<!-- System assemblies with reflection usage -->
|
||||
<assembly fullname="System.Drawing.Common" preserve="all" />
|
||||
<assembly fullname="System.Runtime.WindowsRuntime" preserve="all" />
|
||||
<assembly fullname="System.ComponentModel.TypeConverter" preserve="all" />
|
||||
<assembly fullname="System.Reflection" preserve="all" />
|
||||
<assembly fullname="System.Reflection.Emit" preserve="all" />
|
||||
<assembly fullname="System.Reflection.Emit.Lightweight" preserve="all" />
|
||||
</linker>
|
||||
@@ -1,10 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Styling;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using LanMountainDesktop.ComponentSystem;
|
||||
@@ -576,10 +579,36 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
|
||||
_languageCode = _localizationService.NormalizeLanguageCode(_settingsFacade.Region.Get().LanguageCode);
|
||||
RefreshLocalizedText();
|
||||
ThemeColorModes = CreateThemeColorModes();
|
||||
ThemeModeOptions = CreateThemeModeOptions();
|
||||
|
||||
_isInitializing = true;
|
||||
Load();
|
||||
_isInitializing = false;
|
||||
|
||||
}
|
||||
|
||||
partial void OnSelectedThemeModeChanged(SelectionOption value)
|
||||
{
|
||||
if (_isInitializing || value is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据选择的主题模式更新夜间模式状态
|
||||
var newIsNightMode = value.Value switch
|
||||
{
|
||||
ThemeAppearanceValues.ThemeModeDark => true,
|
||||
ThemeAppearanceValues.ThemeModeLight => false,
|
||||
ThemeAppearanceValues.ThemeModeFollowSystem => Application.Current?.ActualThemeVariant == ThemeVariant.Dark,
|
||||
_ => IsNightMode
|
||||
};
|
||||
|
||||
if (IsNightMode != newIsNightMode)
|
||||
{
|
||||
IsNightMode = newIsNightMode;
|
||||
}
|
||||
|
||||
PersistCurrentState(restartRequired: false);
|
||||
}
|
||||
|
||||
public event Action<string>? RestartRequested;
|
||||
@@ -595,6 +624,27 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
|
||||
[ObservableProperty]
|
||||
private string _themeColor = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private IReadOnlyList<SelectionOption> _themeModeOptions = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private SelectionOption _selectedThemeMode = new(ThemeAppearanceValues.ThemeModeLight, "Light");
|
||||
|
||||
[ObservableProperty]
|
||||
private string _themeModeLabel = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _themeModeDescription = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _themeModeLightText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _themeModeDarkText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _themeModeFollowSystemText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private Color _customSeedPickerValue = DefaultSeedColor;
|
||||
|
||||
@@ -797,16 +847,6 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
|
||||
UpdatePreview(theme);
|
||||
}
|
||||
|
||||
partial void OnIsNightModeChanged(bool value)
|
||||
{
|
||||
if (_isInitializing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PersistCurrentState(restartRequired: false);
|
||||
}
|
||||
|
||||
partial void OnUseSystemChromeChanged(bool value)
|
||||
{
|
||||
if (_isInitializing)
|
||||
@@ -887,7 +927,11 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
|
||||
PageTitle = L("settings.appearance.title", "Appearance");
|
||||
PageDescription = L("settings.appearance.description", "Adjust theme source, material background, and window chrome.");
|
||||
ThemeHeader = L("settings.appearance.theme_header", "Theme");
|
||||
NightModeLabel = L("settings.color.enable_night_mode_toggle", "Enable night mode");
|
||||
ThemeModeLabel = L("settings.appearance.theme_mode_label", "Theme mode");
|
||||
ThemeModeDescription = L("settings.appearance.theme_mode_desc", "Choose light, dark, or follow system preference.");
|
||||
ThemeModeLightText = L("settings.appearance.theme_mode.light", "Light");
|
||||
ThemeModeDarkText = L("settings.appearance.theme_mode.dark", "Dark");
|
||||
ThemeModeFollowSystemText = L("settings.appearance.theme_mode.follow_system", "Follow system");
|
||||
UseSystemChromeLabel = L("settings.color.use_system_chrome_toggle", "Use system window chrome");
|
||||
ThemeColorLabel = L("settings.color.theme_color_label", "Theme Accent Color");
|
||||
ThemeColorModeLabel = L("settings.appearance.theme_color_mode_label", "Theme color source");
|
||||
@@ -957,6 +1001,26 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
|
||||
SelectedSystemMaterialMode = SystemMaterialModes.FirstOrDefault(option =>
|
||||
string.Equals(option.Value, savedSystemMaterialMode, StringComparison.OrdinalIgnoreCase))
|
||||
?? SystemMaterialModes[0];
|
||||
|
||||
// 应用主题模式设置
|
||||
var savedThemeMode = NormalizeThemeMode(theme.ThemeMode);
|
||||
SelectedThemeMode = ThemeModeOptions.FirstOrDefault(option =>
|
||||
string.Equals(option.Value, savedThemeMode, StringComparison.OrdinalIgnoreCase))
|
||||
?? ThemeModeOptions.FirstOrDefault(o => o.Value == ThemeAppearanceValues.ThemeModeLight)
|
||||
?? new SelectionOption(ThemeAppearanceValues.ThemeModeLight, ThemeModeLightText);
|
||||
}
|
||||
|
||||
private static string NormalizeThemeMode(string? value)
|
||||
{
|
||||
if (string.Equals(value, ThemeAppearanceValues.ThemeModeDark, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ThemeAppearanceValues.ThemeModeDark;
|
||||
}
|
||||
if (string.Equals(value, ThemeAppearanceValues.ThemeModeFollowSystem, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ThemeAppearanceValues.ThemeModeFollowSystem;
|
||||
}
|
||||
return ThemeAppearanceValues.ThemeModeLight;
|
||||
}
|
||||
|
||||
private void PersistCurrentState(bool restartRequired)
|
||||
@@ -984,6 +1048,16 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
private IReadOnlyList<SelectionOption> CreateThemeModeOptions()
|
||||
{
|
||||
return
|
||||
[
|
||||
new SelectionOption(ThemeAppearanceValues.ThemeModeLight, ThemeModeLightText),
|
||||
new SelectionOption(ThemeAppearanceValues.ThemeModeDark, ThemeModeDarkText),
|
||||
new SelectionOption(ThemeAppearanceValues.ThemeModeFollowSystem, ThemeModeFollowSystemText)
|
||||
];
|
||||
}
|
||||
|
||||
private ThemeAppearanceSettingsState BuildPendingState(bool usePickerSeed)
|
||||
{
|
||||
var themeColorMode = ThemeAppearanceValues.NormalizeThemeColorMode(SelectedThemeColorMode?.Value, ThemeColor);
|
||||
@@ -998,7 +1072,8 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
|
||||
GlobalAppearanceSettings.NormalizeCornerRadiusStyle(CornerRadiusStyle),
|
||||
themeColorMode,
|
||||
ThemeAppearanceValues.NormalizeSystemMaterialMode(SelectedSystemMaterialMode?.Value),
|
||||
_selectedWallpaperSeed);
|
||||
_selectedWallpaperSeed,
|
||||
SelectedThemeMode?.Value ?? ThemeAppearanceValues.ThemeModeLight);
|
||||
}
|
||||
|
||||
private void UpdatePreview(ThemeAppearanceSettingsState pendingState)
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
|
||||
x:Class="LanMountainDesktop.Views.SettingsPages.AboutSettingsPage"
|
||||
x:DataType="vm:AboutSettingsPageViewModel">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<ImageBrush x:Key="AboutBannerBrush" Source="/Assets/about_banner_light.png" Stretch="Uniform" AlignmentX="Center" AlignmentY="Center" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<ImageBrush x:Key="AboutBannerBrush" Source="/Assets/about_banner_dark.png" Stretch="Uniform" AlignmentX="Center" AlignmentY="Center" />
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="StackPanel.about-page-container">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
@@ -38,10 +51,7 @@
|
||||
Classes="about-hero-card"
|
||||
Height="240"
|
||||
PointerPressed="OnAboutHeroCardPointerPressed">
|
||||
<Image Source="/Assets/about_banner.png"
|
||||
Stretch="Uniform"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
<Panel Background="{DynamicResource AboutBannerBrush}" />
|
||||
</Border>
|
||||
|
||||
<TextBlock Classes="settings-subsection-title"
|
||||
|
||||
@@ -13,12 +13,21 @@
|
||||
Text="{Binding ThemeHeader}"
|
||||
Margin="0,0,0,4" />
|
||||
|
||||
<ui:SettingsExpander Header="{Binding NightModeLabel}">
|
||||
<ui:SettingsExpander Header="{Binding ThemeModeLabel}"
|
||||
Description="{Binding ThemeModeDescription}">
|
||||
<ui:SettingsExpander.IconSource>
|
||||
<fi:SymbolIconSource Symbol="WeatherMoon" />
|
||||
</ui:SettingsExpander.IconSource>
|
||||
<ui:SettingsExpander.Footer>
|
||||
<ToggleSwitch IsChecked="{Binding IsNightMode}" />
|
||||
<ComboBox Width="200"
|
||||
ItemsSource="{Binding ThemeModeOptions}"
|
||||
SelectedItem="{Binding SelectedThemeMode}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:SelectionOption">
|
||||
<TextBlock Text="{Binding Label}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</ui:SettingsExpander.Footer>
|
||||
</ui:SettingsExpander>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user