From 17873f0f4378ae470360b7f9681f230c89bd2727 Mon Sep 17 00:00:00 2001 From: lincube Date: Sat, 30 May 2026 17:15:16 +0800 Subject: [PATCH] =?UTF-8?q?fix.=E4=BF=AE=E5=A4=8D=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...MaterialColorSettingsPageViewModelTests.cs | 276 ++++++++++++++++++ .../MaterialColorSettingsPageViewModel.cs | 31 +- 2 files changed, 301 insertions(+), 6 deletions(-) create mode 100644 LanMountainDesktop.Tests/MaterialColorSettingsPageViewModelTests.cs diff --git a/LanMountainDesktop.Tests/MaterialColorSettingsPageViewModelTests.cs b/LanMountainDesktop.Tests/MaterialColorSettingsPageViewModelTests.cs new file mode 100644 index 0000000..48a3fa8 --- /dev/null +++ b/LanMountainDesktop.Tests/MaterialColorSettingsPageViewModelTests.cs @@ -0,0 +1,276 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; +using LanMountainDesktop.Appearance; +using LanMountainDesktop.Models; +using LanMountainDesktop.PluginSdk; +using LanMountainDesktop.Services; +using LanMountainDesktop.Services.Settings; +using LanMountainDesktop.Settings.Core; +using LanMountainDesktop.Shared.Contracts; +using LanMountainDesktop.ViewModels; +using Xunit; + +namespace LanMountainDesktop.Tests; + +public sealed class MaterialColorSettingsPageViewModelTests +{ + [Fact] + public void Load_SelectsSavedNoneMaterialMode() + { + var facade = new FakeSettingsFacade(CreateThemeState(ThemeAppearanceValues.MaterialNone)); + var materialService = new FakeMaterialColorService(CreateSnapshot(ThemeAppearanceValues.MaterialNone)); + + var viewModel = new MaterialColorSettingsPageViewModel(facade, materialService); + + Assert.Equal(ThemeAppearanceValues.MaterialNone, viewModel.SelectedSystemMaterialMode.Value); + Assert.Contains(viewModel.SystemMaterialModes, option => option.Value == ThemeAppearanceValues.MaterialAuto); + Assert.Contains(viewModel.SystemMaterialModes, option => option.Value == ThemeAppearanceValues.MaterialNone); + Assert.Contains(viewModel.SystemMaterialModes, option => option.Value == ThemeAppearanceValues.MaterialMica); + Assert.Contains(viewModel.SystemMaterialModes, option => option.Value == ThemeAppearanceValues.MaterialAcrylic); + Assert.Equal(0, facade.ThemeSaveCount); + } + + [Fact] + public void MaterialSnapshotRefresh_KeepsExplicitNoneSelection() + { + var facade = new FakeSettingsFacade(CreateThemeState(ThemeAppearanceValues.MaterialNone)); + var materialService = new FakeMaterialColorService(CreateSnapshot(ThemeAppearanceValues.MaterialNone)); + var viewModel = new MaterialColorSettingsPageViewModel(facade, materialService); + + materialService.RaiseChanged(CreateSnapshot(ThemeAppearanceValues.MaterialAuto)); + + Assert.Equal(ThemeAppearanceValues.MaterialNone, viewModel.SelectedSystemMaterialMode.Value); + Assert.Equal(0, facade.ThemeSaveCount); + } + + [Theory] + [InlineData(ThemeAppearanceValues.MaterialNone)] + [InlineData(ThemeAppearanceValues.MaterialAuto)] + [InlineData(ThemeAppearanceValues.MaterialMica)] + [InlineData(ThemeAppearanceValues.MaterialAcrylic)] + public void UserSelection_SavesRequestedMaterialMode(string targetMode) + { + var initialMode = targetMode == ThemeAppearanceValues.MaterialNone + ? ThemeAppearanceValues.MaterialAuto + : ThemeAppearanceValues.MaterialNone; + var facade = new FakeSettingsFacade(CreateThemeState(initialMode)); + var materialService = new FakeMaterialColorService(CreateSnapshot(initialMode)); + var viewModel = new MaterialColorSettingsPageViewModel(facade, materialService); + + viewModel.SelectedSystemMaterialMode = viewModel.SystemMaterialModes.Single(option => + option.Value == targetMode); + + Assert.Equal(targetMode, facade.ThemeState.SystemMaterialMode); + Assert.Equal(1, facade.ThemeSaveCount); + } + + private static ThemeAppearanceSettingsState CreateThemeState(string materialMode) + { + return new ThemeAppearanceSettingsState( + IsNightMode: false, + ThemeColor: "#FF445566", + UseSystemChrome: false, + CornerRadiusStyle: GlobalAppearanceSettings.CornerRadiusStyleRounded, + ThemeColorMode: ThemeAppearanceValues.ColorModeDefaultNeutral, + SystemMaterialMode: materialMode, + SelectedWallpaperSeed: null, + ThemeMode: ThemeAppearanceValues.ThemeModeLight, + ThemeWallpaperColorSource: ThemeAppearanceValues.WallpaperColorSourceAuto, + UseNativeWallpaperChangeEvents: true); + } + + private static MaterialColorSnapshot CreateSnapshot(string materialMode) + { + var seed = Color.Parse("#FF3B82F6"); + var palette = new LanMountainDesktop.Models.MaterialColorPalette( + seed, + Color.Parse("#FF64748B"), + seed, + Colors.White, + Color.Parse("#FF60A5FA"), + Color.Parse("#FF93C5FD"), + Color.Parse("#FFBFDBFE"), + Color.Parse("#FF2563EB"), + Color.Parse("#FF1D4ED8"), + Color.Parse("#FF1E40AF"), + Color.Parse("#FFF8FAFC"), + Color.Parse("#FFFFFFFF"), + Color.Parse("#FFF1F5F9"), + Color.Parse("#FF0F172A"), + Color.Parse("#FF334155"), + Color.Parse("#FF64748B"), + seed, + Color.Parse("#FF0F172A"), + Colors.White, + seed, + Color.Parse("#22000000"), + Color.Parse("#33000000"), + Color.Parse("#443B82F6"), + seed, + Color.Parse("#4464748B"), + Color.Parse("#663B82F6")); + var surface = new MaterialSurfaceSnapshot( + MaterialSurfaceRole.SettingsWindowBackground, + Color.Parse("#FFF8FAFC"), + Color.Parse("#22000000"), + 0, + 1); + var surfaces = new Dictionary + { + [MaterialSurfaceRole.SettingsWindowBackground] = surface, + [MaterialSurfaceRole.DockBackground] = surface with { Role = MaterialSurfaceRole.DockBackground }, + [MaterialSurfaceRole.DesktopComponentHost] = surface with { Role = MaterialSurfaceRole.DesktopComponentHost }, + [MaterialSurfaceRole.OverlayPanel] = surface with { Role = MaterialSurfaceRole.OverlayPanel } + }; + + return new MaterialColorSnapshot( + IsNightMode: false, + ThemeColorMode: ThemeAppearanceValues.ColorModeDefaultNeutral, + ThemeWallpaperColorSource: ThemeAppearanceValues.WallpaperColorSourceAuto, + ColorSourceKind: MaterialColorSourceKind.Neutral, + ResolvedSeedSource: "neutral", + CornerRadiusTokens: new AppearanceCornerRadiusTokens( + new CornerRadius(2), + new CornerRadius(4), + new CornerRadius(6), + new CornerRadius(8), + new CornerRadius(10), + new CornerRadius(12), + new CornerRadius(14), + new CornerRadius(8)), + UserThemeColor: seed.ToString(), + SelectedWallpaperSeed: null, + EffectiveSeedColor: seed, + AccentColor: seed, + MonetPalette: new MonetPalette([seed], seed, seed, seed, seed, seed, seed), + Palette: palette, + WallpaperSeedCandidates: [seed], + SystemMaterialMode: materialMode, + AvailableSystemMaterialModes: + [ + ThemeAppearanceValues.MaterialAuto, + ThemeAppearanceValues.MaterialNone, + ThemeAppearanceValues.MaterialMica, + ThemeAppearanceValues.MaterialAcrylic + ], + CanChangeSystemMaterial: true, + UseSystemChrome: false, + ResolvedWallpaperPath: null, + UseNativeWallpaperChangeEvents: true, + NativeWallpaperChangeEventsActive: false, + WallpaperPollingActive: false, + Surfaces: surfaces); + } + + private sealed class FakeSettingsFacade(ThemeAppearanceSettingsState themeState) : ISettingsFacadeService + { + private readonly FakeThemeAppearanceService _theme = new(themeState); + private readonly FakeRegionSettingsService _region = new(); + private readonly FakeWallpaperSettingsService _wallpaper = new(); + + public ThemeAppearanceSettingsState ThemeState => _theme.State; + public int ThemeSaveCount => _theme.SaveCount; + + public ISettingsService Settings => throw new NotSupportedException(); + public ISettingsCatalog Catalog => throw new NotSupportedException(); + public IGridSettingsService Grid => throw new NotSupportedException(); + public IWallpaperSettingsService Wallpaper => _wallpaper; + public IWallpaperMediaService WallpaperMedia => throw new NotSupportedException(); + public IThemeAppearanceService Theme => _theme; + public IStatusBarSettingsService StatusBar => throw new NotSupportedException(); + public ITextCapsuleSettingsService TextCapsule => throw new NotSupportedException(); + public IWeatherSettingsService Weather => throw new NotSupportedException(); + public IRegionSettingsService Region => _region; + public IPrivacySettingsService Privacy => throw new NotSupportedException(); + public IUpdateSettingsService Update => throw new NotSupportedException(); + public ILauncherCatalogService LauncherCatalog => throw new NotSupportedException(); + public ILauncherPolicyService LauncherPolicy => throw new NotSupportedException(); + public IPluginManagementSettingsService PluginManagement => throw new NotSupportedException(); + public IPluginCatalogSettingsService PluginCatalog => throw new NotSupportedException(); + public IApplicationInfoService ApplicationInfo => throw new NotSupportedException(); + } + + private sealed class FakeThemeAppearanceService(ThemeAppearanceSettingsState state) : IThemeAppearanceService + { + public ThemeAppearanceSettingsState State { get; private set; } = state; + public int SaveCount { get; private set; } + + public ThemeAppearanceSettingsState Get() => State; + + public void Save(ThemeAppearanceSettingsState state) + { + SaveCount++; + State = state; + } + + public MonetPalette BuildPalette(bool nightMode, string? wallpaperPath, string? preferredSeedColor = null) + { + var seed = Color.Parse(preferredSeedColor ?? "#FF3B82F6"); + return new MonetPalette([seed], seed, seed, seed, seed, seed, seed); + } + } + + private sealed class FakeRegionSettingsService : IRegionSettingsService + { + public RegionSettingsState Get() => new("en-US", null); + + public void Save(RegionSettingsState state) + { + _ = state; + } + + public TimeZoneService GetTimeZoneService() => new(); + } + + private sealed class FakeWallpaperSettingsService : IWallpaperSettingsService + { + public WallpaperSettingsState Get() => new(null, "SolidColor", "#FFFFFFFF", "Fill", 300); + + public void Save(WallpaperSettingsState state) + { + _ = state; + } + } + + private sealed class FakeMaterialColorService(MaterialColorSnapshot snapshot) : IMaterialColorService + { + private MaterialColorSnapshot _snapshot = snapshot; + + public event EventHandler? MaterialColorChanged; + + public MaterialColorSnapshot GetMaterialColorSnapshot() => _snapshot; + + public MaterialColorSnapshot BuildMaterialColorPreview(ThemeAppearanceSettingsState pendingState) + { + _ = pendingState; + return _snapshot; + } + + public void ApplyThemeResources(IResourceDictionary resources) + { + _ = resources; + } + + public MaterialSurfaceSnapshot GetSurface(MaterialSurfaceRole role) + { + return _snapshot.Surfaces[role]; + } + + public void ApplyWindowMaterial(Window window, MaterialSurfaceRole role) + { + _ = window; + _ = role; + } + + public void RefreshWallpaperColors() + { + } + + public void RaiseChanged(MaterialColorSnapshot snapshot) + { + _snapshot = snapshot; + MaterialColorChanged?.Invoke(this, snapshot); + } + } +} diff --git a/LanMountainDesktop/ViewModels/MaterialColorSettingsPageViewModel.cs b/LanMountainDesktop/ViewModels/MaterialColorSettingsPageViewModel.cs index f1f720d..2d5dc7e 100644 --- a/LanMountainDesktop/ViewModels/MaterialColorSettingsPageViewModel.cs +++ b/LanMountainDesktop/ViewModels/MaterialColorSettingsPageViewModel.cs @@ -54,6 +54,7 @@ public sealed partial class MaterialColorSettingsPageViewModel : ViewModelBase private readonly LocalizationService _localizationService = new(); private readonly string _languageCode; private bool _isInitializing; + private bool _isSynchronizingSystemMaterialMode; private string? _selectedWallpaperSeed; public MaterialColorSettingsPageViewModel( @@ -307,7 +308,7 @@ public sealed partial class MaterialColorSettingsPageViewModel : ViewModelBase partial void OnSelectedSystemMaterialModeChanged(SelectionOption value) { - if (_isInitializing || value is null) + if (_isInitializing || _isSynchronizingSystemMaterialMode || value is null) { return; } @@ -452,15 +453,33 @@ public sealed partial class MaterialColorSettingsPageViewModel : ViewModelBase private void RefreshMaterialModes(MaterialColorSnapshot snapshot) { - SystemMaterialModes = snapshot.AvailableSystemMaterialModes + var selectedValue = ThemeAppearanceValues.NormalizeSystemMaterialMode( + SelectedSystemMaterialMode?.Value ?? snapshot.SystemMaterialMode); + var snapshotValue = ThemeAppearanceValues.NormalizeSystemMaterialMode(snapshot.SystemMaterialMode); + + SystemMaterialModes = ThemeAppearanceValues.NormalizeAvailableMaterialModes(snapshot.AvailableSystemMaterialModes) .Select(value => new SelectionOption(value, ResolveMaterialLabel(value))) .ToArray(); - if (!SystemMaterialModes.Any(option => - string.Equals(option.Value, SelectedSystemMaterialMode?.Value, StringComparison.OrdinalIgnoreCase))) + var nextSelection = SystemMaterialModes.FirstOrDefault(option => + string.Equals(option.Value, selectedValue, StringComparison.OrdinalIgnoreCase)) + ?? SystemMaterialModes.FirstOrDefault(option => + string.Equals(option.Value, snapshotValue, StringComparison.OrdinalIgnoreCase)) + ?? SystemMaterialModes.FirstOrDefault() + ?? new SelectionOption(ThemeAppearanceValues.MaterialNone, MaterialNoneText); + + if (!string.Equals(SelectedSystemMaterialMode?.Value, nextSelection.Value, StringComparison.OrdinalIgnoreCase) || + !ReferenceEquals(SelectedSystemMaterialMode, nextSelection)) { - SelectedSystemMaterialMode = SystemMaterialModes.FirstOrDefault() - ?? new SelectionOption(ThemeAppearanceValues.MaterialNone, MaterialNoneText); + _isSynchronizingSystemMaterialMode = true; + try + { + SelectedSystemMaterialMode = nextSelection; + } + finally + { + _isSynchronizingSystemMaterialMode = false; + } } }