mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-25 11:14:26 +08:00
settings_re4
This commit is contained in:
@@ -6,29 +6,20 @@ using LanMountainDesktop.ComponentSystem;
|
||||
using LanMountainDesktop.Models;
|
||||
using LanMountainDesktop.Views;
|
||||
using LanMountainDesktop.Views.Components;
|
||||
using LanMountainDesktop.Services.Settings;
|
||||
|
||||
namespace LanMountainDesktop.Services;
|
||||
|
||||
public sealed record ComponentLibraryCreateContext(
|
||||
double CellSize,
|
||||
TimeZoneService TimeZoneService,
|
||||
IWeatherInfoService WeatherInfoService,
|
||||
IRecommendationInfoService RecommendationInfoService,
|
||||
ICalculatorDataService CalculatorDataService,
|
||||
string? PlacementId = null);
|
||||
|
||||
public interface IComponentLibraryService
|
||||
public interface IEmbeddedComponentLibraryService
|
||||
{
|
||||
IReadOnlyList<DesktopComponentDefinition> GetDefinitions();
|
||||
void Open(MainWindow window);
|
||||
|
||||
bool TryCreateControl(
|
||||
string componentId,
|
||||
ComponentLibraryCreateContext context,
|
||||
out Control? control,
|
||||
out Exception? exception);
|
||||
void Close(MainWindow window);
|
||||
|
||||
void Toggle(MainWindow window);
|
||||
}
|
||||
|
||||
public interface IComponentLibraryWindowService
|
||||
public interface IDetachedComponentLibraryWindowService
|
||||
{
|
||||
void Open(MainWindow window);
|
||||
|
||||
@@ -53,6 +44,31 @@ internal sealed class ComponentLibraryService : IComponentLibraryService
|
||||
return _registry.GetAll().ToArray();
|
||||
}
|
||||
|
||||
public IReadOnlyList<ComponentLibraryCategoryEntry> GetDesktopCategories()
|
||||
{
|
||||
return _runtimeRegistry
|
||||
.GetDesktopComponents()
|
||||
.GroupBy(
|
||||
descriptor => string.IsNullOrWhiteSpace(descriptor.Definition.Category)
|
||||
? "Other"
|
||||
: descriptor.Definition.Category.Trim(),
|
||||
StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(group => group.Key, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(group => new ComponentLibraryCategoryEntry(
|
||||
group.Key,
|
||||
group
|
||||
.OrderBy(descriptor => descriptor.Definition.DisplayName, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(descriptor => new ComponentLibraryComponentEntry(
|
||||
descriptor.Definition.Id,
|
||||
descriptor.Definition.DisplayName,
|
||||
descriptor.DisplayNameLocalizationKey,
|
||||
group.Key,
|
||||
descriptor.Definition.MinWidthCells,
|
||||
descriptor.Definition.MinHeightCells))
|
||||
.ToArray()))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public bool TryCreateControl(
|
||||
string componentId,
|
||||
ComponentLibraryCreateContext context,
|
||||
@@ -75,6 +91,7 @@ internal sealed class ComponentLibraryService : IComponentLibraryService
|
||||
context.WeatherInfoService,
|
||||
context.RecommendationInfoService,
|
||||
context.CalculatorDataService,
|
||||
context.SettingsFacade,
|
||||
context.PlacementId);
|
||||
return true;
|
||||
}
|
||||
@@ -86,7 +103,7 @@ internal sealed class ComponentLibraryService : IComponentLibraryService
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ComponentLibraryWindowService : IComponentLibraryWindowService
|
||||
internal sealed class EmbeddedComponentLibraryService : IEmbeddedComponentLibraryService
|
||||
{
|
||||
public void Open(MainWindow window)
|
||||
{
|
||||
@@ -112,3 +129,30 @@ internal sealed class ComponentLibraryWindowService : IComponentLibraryWindowSer
|
||||
window.OpenComponentLibraryWindowFromService();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DetachedComponentLibraryWindowService : IDetachedComponentLibraryWindowService
|
||||
{
|
||||
public void Open(MainWindow window)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(window);
|
||||
window.OpenDetachedComponentLibraryWindowFromService();
|
||||
}
|
||||
|
||||
public void Close(MainWindow window)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(window);
|
||||
window.CloseDetachedComponentLibraryWindowFromService();
|
||||
}
|
||||
|
||||
public void Toggle(MainWindow window)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(window);
|
||||
if (window.IsDetachedComponentLibraryWindowOpenFromService)
|
||||
{
|
||||
window.CloseDetachedComponentLibraryWindowFromService();
|
||||
return;
|
||||
}
|
||||
|
||||
window.OpenDetachedComponentLibraryWindowFromService();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using LanMountainDesktop.Models;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services.Settings;
|
||||
|
||||
namespace LanMountainDesktop.Services;
|
||||
@@ -19,6 +20,11 @@ public sealed class ComponentSettingsService : IComponentInstanceSettingsStore
|
||||
_settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
|
||||
}
|
||||
|
||||
public ComponentSettingsService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
||||
}
|
||||
|
||||
internal ComponentSettingsService(string settingsDirectory)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(settingsDirectory))
|
||||
|
||||
@@ -33,7 +33,8 @@ public static class DesktopComponentRegistryFactory
|
||||
|
||||
public static DesktopComponentRuntimeRegistry CreateRuntimeRegistry(
|
||||
ComponentRegistry componentRegistry,
|
||||
PluginRuntimeService? pluginRuntimeService)
|
||||
PluginRuntimeService? pluginRuntimeService,
|
||||
ISettingsFacadeService settingsFacade)
|
||||
{
|
||||
var registrations = DesktopComponentRuntimeRegistry.GetDefaultRegistrations().ToList();
|
||||
var registeredIds = new HashSet<string>(
|
||||
@@ -65,6 +66,7 @@ public static class DesktopComponentRegistryFactory
|
||||
}
|
||||
}
|
||||
|
||||
_ = settingsFacade;
|
||||
return new DesktopComponentRuntimeRegistry(componentRegistry, registrations);
|
||||
}
|
||||
|
||||
@@ -116,7 +118,7 @@ public static class DesktopComponentRegistryFactory
|
||||
try
|
||||
{
|
||||
var settingsService = contribution.Plugin.Services.GetService(typeof(ISettingsService)) as ISettingsService
|
||||
?? HostSettingsFacadeProvider.GetOrCreate().Settings;
|
||||
?? context.SettingsService;
|
||||
var pluginSettings = new PluginScopedSettingsService(
|
||||
contribution.Plugin.Manifest.Id,
|
||||
settingsService);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using LanMountainDesktop.Services.Settings;
|
||||
|
||||
namespace LanMountainDesktop.Services;
|
||||
|
||||
internal static class HostComponentSettingsStoreProvider
|
||||
{
|
||||
private static readonly IComponentInstanceSettingsStore Instance =
|
||||
new ComponentSettingsService(HostSettingsFacadeProvider.GetOrCreate().Settings);
|
||||
|
||||
public static IComponentInstanceSettingsStore GetOrCreate()
|
||||
{
|
||||
return Instance;
|
||||
}
|
||||
}
|
||||
41
LanMountainDesktop/Services/IComponentLibraryService.cs
Normal file
41
LanMountainDesktop/Services/IComponentLibraryService.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Avalonia.Controls;
|
||||
using LanMountainDesktop.ComponentSystem;
|
||||
using LanMountainDesktop.Services.Settings;
|
||||
|
||||
namespace LanMountainDesktop.Services;
|
||||
|
||||
public sealed record ComponentLibraryComponentEntry(
|
||||
string ComponentId,
|
||||
string DisplayName,
|
||||
string? DisplayNameLocalizationKey,
|
||||
string CategoryId,
|
||||
int MinWidthCells,
|
||||
int MinHeightCells);
|
||||
|
||||
public sealed record ComponentLibraryCategoryEntry(
|
||||
string Id,
|
||||
IReadOnlyList<ComponentLibraryComponentEntry> Components);
|
||||
|
||||
public sealed record ComponentLibraryCreateContext(
|
||||
double CellSize,
|
||||
TimeZoneService TimeZoneService,
|
||||
IWeatherInfoService WeatherInfoService,
|
||||
IRecommendationInfoService RecommendationInfoService,
|
||||
ICalculatorDataService CalculatorDataService,
|
||||
ISettingsFacadeService SettingsFacade,
|
||||
string? PlacementId = null);
|
||||
|
||||
public interface IComponentLibraryService
|
||||
{
|
||||
IReadOnlyList<DesktopComponentDefinition> GetDefinitions();
|
||||
|
||||
IReadOnlyList<ComponentLibraryCategoryEntry> GetDesktopCategories();
|
||||
|
||||
bool TryCreateControl(
|
||||
string componentId,
|
||||
ComponentLibraryCreateContext context,
|
||||
out Control? control,
|
||||
out Exception? exception);
|
||||
}
|
||||
@@ -19,10 +19,7 @@ internal sealed class SettingsCatalogService : ISettingsCatalog
|
||||
new SettingsSectionDefinition("appearance", SettingsCategories.Appearance, SettingsScope.App, "settings.appearance.title", iconKey: "DesignIdeas", sortOrder: 10),
|
||||
new SettingsSectionDefinition("components", SettingsCategories.Components, SettingsScope.ComponentInstance, "settings.components.title", iconKey: "GridDots", sortOrder: 20),
|
||||
new SettingsSectionDefinition("plugins", SettingsCategories.Plugins, SettingsScope.Plugin, "settings.plugins.title", iconKey: "PuzzlePiece", sortOrder: 30),
|
||||
new SettingsSectionDefinition("plugin-market", SettingsCategories.PluginMarket, SettingsScope.Plugin, "settings.plugin_market.title", iconKey: "Shop", sortOrder: 40),
|
||||
new SettingsSectionDefinition("update", SettingsCategories.Update, SettingsScope.App, "settings.update.title", iconKey: "ArrowSync", sortOrder: 50),
|
||||
new SettingsSectionDefinition("about", SettingsCategories.About, SettingsScope.App, "settings.about.title", iconKey: "Info", sortOrder: 60),
|
||||
new SettingsSectionDefinition("advanced", SettingsCategories.Advanced, SettingsScope.App, "settings.advanced.title", iconKey: "DeveloperBoard", sortOrder: 70)
|
||||
new SettingsSectionDefinition("about", SettingsCategories.About, SettingsScope.App, "settings.about.title", iconKey: "Info", sortOrder: 40)
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ public enum WallpaperMediaType
|
||||
|
||||
public sealed record GridSettingsState(int ShortSideCells, string SpacingPreset, int EdgeInsetPercent);
|
||||
public sealed record WallpaperSettingsState(string? WallpaperPath, string Placement);
|
||||
public sealed record ThemeAppearanceSettingsState(bool IsNightMode, string? ThemeColor);
|
||||
public sealed record ThemeAppearanceSettingsState(bool IsNightMode, string? ThemeColor, bool UseSystemChrome);
|
||||
public sealed record StatusBarSettingsState(
|
||||
IReadOnlyList<string> TopStatusComponentIds,
|
||||
IReadOnlyList<string> PinnedTaskbarActions,
|
||||
|
||||
@@ -7,18 +7,24 @@ using System.Threading.Tasks;
|
||||
using Avalonia.Media.Imaging;
|
||||
using LanMountainDesktop.Models;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.Services.PluginMarket;
|
||||
|
||||
namespace LanMountainDesktop.Services.Settings;
|
||||
|
||||
internal sealed class GridSettingsService : IGridSettingsService
|
||||
{
|
||||
private readonly AppSettingsService _appSettingsService = new();
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly DesktopGridLayoutService _gridLayoutService = new();
|
||||
|
||||
public GridSettingsService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
||||
}
|
||||
|
||||
public GridSettingsState Get()
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
var snapshot = _settingsService.Load();
|
||||
return new GridSettingsState(
|
||||
snapshot.GridShortSideCells,
|
||||
snapshot.GridSpacingPreset,
|
||||
@@ -27,11 +33,19 @@ internal sealed class GridSettingsService : IGridSettingsService
|
||||
|
||||
public void Save(GridSettingsState state)
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
var snapshot = _settingsService.Load();
|
||||
snapshot.GridShortSideCells = state.ShortSideCells;
|
||||
snapshot.GridSpacingPreset = state.SpacingPreset;
|
||||
snapshot.DesktopEdgeInsetPercent = state.EdgeInsetPercent;
|
||||
_appSettingsService.Save(snapshot);
|
||||
_settingsService.SaveSnapshot(
|
||||
SettingsScope.App,
|
||||
snapshot,
|
||||
changedKeys:
|
||||
[
|
||||
nameof(AppSettingsSnapshot.GridShortSideCells),
|
||||
nameof(AppSettingsSnapshot.GridSpacingPreset),
|
||||
nameof(AppSettingsSnapshot.DesktopEdgeInsetPercent)
|
||||
]);
|
||||
}
|
||||
|
||||
public string NormalizeSpacingPreset(string? value)
|
||||
@@ -67,22 +81,34 @@ internal sealed class GridSettingsService : IGridSettingsService
|
||||
|
||||
internal sealed class WallpaperSettingsService : IWallpaperSettingsService
|
||||
{
|
||||
private readonly AppSettingsService _appSettingsService = new();
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
public WallpaperSettingsService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
||||
}
|
||||
|
||||
public WallpaperSettingsState Get()
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
var snapshot = _settingsService.Load();
|
||||
return new WallpaperSettingsState(snapshot.WallpaperPath, snapshot.WallpaperPlacement);
|
||||
}
|
||||
|
||||
public void Save(WallpaperSettingsState state)
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
var snapshot = _settingsService.Load();
|
||||
snapshot.WallpaperPath = state.WallpaperPath;
|
||||
snapshot.WallpaperPlacement = string.IsNullOrWhiteSpace(state.Placement)
|
||||
? "Fill"
|
||||
: state.Placement.Trim();
|
||||
_appSettingsService.Save(snapshot);
|
||||
_settingsService.SaveSnapshot(
|
||||
SettingsScope.App,
|
||||
snapshot,
|
||||
changedKeys:
|
||||
[
|
||||
nameof(AppSettingsSnapshot.WallpaperPath),
|
||||
nameof(AppSettingsSnapshot.WallpaperPlacement)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,24 +208,39 @@ internal sealed class WallpaperMediaService : IWallpaperMediaService
|
||||
|
||||
internal sealed class ThemeAppearanceService : IThemeAppearanceService
|
||||
{
|
||||
private readonly AppSettingsService _appSettingsService = new();
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly MonetColorService _monetColorService = new();
|
||||
private readonly WallpaperMediaService _wallpaperMediaService = new();
|
||||
|
||||
public ThemeAppearanceService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
||||
}
|
||||
|
||||
public ThemeAppearanceSettingsState Get()
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
var snapshot = _settingsService.Load();
|
||||
return new ThemeAppearanceSettingsState(
|
||||
snapshot.IsNightMode ?? false,
|
||||
snapshot.ThemeColor);
|
||||
snapshot.ThemeColor,
|
||||
snapshot.UseSystemChrome);
|
||||
}
|
||||
|
||||
public void Save(ThemeAppearanceSettingsState state)
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
var snapshot = _settingsService.Load();
|
||||
snapshot.IsNightMode = state.IsNightMode;
|
||||
snapshot.ThemeColor = state.ThemeColor;
|
||||
_appSettingsService.Save(snapshot);
|
||||
snapshot.UseSystemChrome = state.UseSystemChrome;
|
||||
_settingsService.SaveSnapshot(
|
||||
SettingsScope.App,
|
||||
snapshot,
|
||||
changedKeys:
|
||||
[
|
||||
nameof(AppSettingsSnapshot.IsNightMode),
|
||||
nameof(AppSettingsSnapshot.ThemeColor),
|
||||
nameof(AppSettingsSnapshot.UseSystemChrome)
|
||||
]);
|
||||
}
|
||||
|
||||
public MonetPalette BuildPalette(bool nightMode, string? wallpaperPath)
|
||||
@@ -236,11 +277,16 @@ internal sealed class ThemeAppearanceService : IThemeAppearanceService
|
||||
|
||||
internal sealed class StatusBarSettingsService : IStatusBarSettingsService
|
||||
{
|
||||
private readonly AppSettingsService _appSettingsService = new();
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
public StatusBarSettingsService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
||||
}
|
||||
|
||||
public StatusBarSettingsState Get()
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
var snapshot = _settingsService.Load();
|
||||
return new StatusBarSettingsState(
|
||||
snapshot.TopStatusComponentIds?.ToArray() ?? [],
|
||||
snapshot.PinnedTaskbarActions?.ToArray() ?? [],
|
||||
@@ -253,7 +299,7 @@ internal sealed class StatusBarSettingsService : IStatusBarSettingsService
|
||||
|
||||
public void Save(StatusBarSettingsState state)
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
var snapshot = _settingsService.Load();
|
||||
snapshot.TopStatusComponentIds = state.TopStatusComponentIds?.ToList() ?? [];
|
||||
snapshot.PinnedTaskbarActions = state.PinnedTaskbarActions?.ToList() ?? [];
|
||||
snapshot.EnableDynamicTaskbarActions = state.EnableDynamicTaskbarActions;
|
||||
@@ -261,7 +307,19 @@ internal sealed class StatusBarSettingsService : IStatusBarSettingsService
|
||||
snapshot.ClockDisplayFormat = state.ClockDisplayFormat;
|
||||
snapshot.StatusBarSpacingMode = state.SpacingMode;
|
||||
snapshot.StatusBarCustomSpacingPercent = state.CustomSpacingPercent;
|
||||
_appSettingsService.Save(snapshot);
|
||||
_settingsService.SaveSnapshot(
|
||||
SettingsScope.App,
|
||||
snapshot,
|
||||
changedKeys:
|
||||
[
|
||||
nameof(AppSettingsSnapshot.TopStatusComponentIds),
|
||||
nameof(AppSettingsSnapshot.PinnedTaskbarActions),
|
||||
nameof(AppSettingsSnapshot.EnableDynamicTaskbarActions),
|
||||
nameof(AppSettingsSnapshot.TaskbarLayoutMode),
|
||||
nameof(AppSettingsSnapshot.ClockDisplayFormat),
|
||||
nameof(AppSettingsSnapshot.StatusBarSpacingMode),
|
||||
nameof(AppSettingsSnapshot.StatusBarCustomSpacingPercent)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,12 +353,17 @@ internal sealed class WeatherProviderAdapter : IWeatherProvider, IWeatherInfoSer
|
||||
|
||||
internal sealed class WeatherSettingsService : IWeatherSettingsService, IDisposable
|
||||
{
|
||||
private readonly AppSettingsService _appSettingsService = new();
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly WeatherProviderAdapter _weatherProvider = new();
|
||||
|
||||
public WeatherSettingsService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
||||
}
|
||||
|
||||
public WeatherSettingsState Get()
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
var snapshot = _settingsService.Load();
|
||||
return new WeatherSettingsState(
|
||||
snapshot.WeatherLocationMode,
|
||||
snapshot.WeatherLocationKey,
|
||||
@@ -316,7 +379,7 @@ internal sealed class WeatherSettingsService : IWeatherSettingsService, IDisposa
|
||||
|
||||
public void Save(WeatherSettingsState state)
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
var snapshot = _settingsService.Load();
|
||||
snapshot.WeatherLocationMode = state.LocationMode;
|
||||
snapshot.WeatherLocationKey = state.LocationKey;
|
||||
snapshot.WeatherLocationName = state.LocationName;
|
||||
@@ -327,7 +390,22 @@ internal sealed class WeatherSettingsService : IWeatherSettingsService, IDisposa
|
||||
snapshot.WeatherIconPackId = state.IconPackId;
|
||||
snapshot.WeatherNoTlsRequests = state.NoTlsRequests;
|
||||
snapshot.WeatherLocationQuery = state.LocationQuery;
|
||||
_appSettingsService.Save(snapshot);
|
||||
_settingsService.SaveSnapshot(
|
||||
SettingsScope.App,
|
||||
snapshot,
|
||||
changedKeys:
|
||||
[
|
||||
nameof(AppSettingsSnapshot.WeatherLocationMode),
|
||||
nameof(AppSettingsSnapshot.WeatherLocationKey),
|
||||
nameof(AppSettingsSnapshot.WeatherLocationName),
|
||||
nameof(AppSettingsSnapshot.WeatherLatitude),
|
||||
nameof(AppSettingsSnapshot.WeatherLongitude),
|
||||
nameof(AppSettingsSnapshot.WeatherAutoRefreshLocation),
|
||||
nameof(AppSettingsSnapshot.WeatherExcludedAlerts),
|
||||
nameof(AppSettingsSnapshot.WeatherIconPackId),
|
||||
nameof(AppSettingsSnapshot.WeatherNoTlsRequests),
|
||||
nameof(AppSettingsSnapshot.WeatherLocationQuery)
|
||||
]);
|
||||
}
|
||||
|
||||
public IWeatherInfoService GetWeatherInfoService()
|
||||
@@ -343,41 +421,74 @@ internal sealed class WeatherSettingsService : IWeatherSettingsService, IDisposa
|
||||
|
||||
internal sealed class RegionSettingsService : IRegionSettingsService
|
||||
{
|
||||
private readonly AppSettingsService _appSettingsService = new();
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly TimeZoneService _timeZoneService = new();
|
||||
|
||||
public RegionSettingsService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
||||
ApplyTimeZone(_settingsService.Load().TimeZoneId);
|
||||
}
|
||||
|
||||
public RegionSettingsState Get()
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
var snapshot = _settingsService.Load();
|
||||
return new RegionSettingsState(snapshot.LanguageCode, snapshot.TimeZoneId);
|
||||
}
|
||||
|
||||
public void Save(RegionSettingsState state)
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
var snapshot = _settingsService.Load();
|
||||
snapshot.LanguageCode = string.IsNullOrWhiteSpace(state.LanguageCode)
|
||||
? "zh-CN"
|
||||
: state.LanguageCode.Trim();
|
||||
snapshot.TimeZoneId = string.IsNullOrWhiteSpace(state.TimeZoneId)
|
||||
? null
|
||||
: state.TimeZoneId.Trim();
|
||||
_appSettingsService.Save(snapshot);
|
||||
_settingsService.SaveSnapshot(
|
||||
SettingsScope.App,
|
||||
snapshot,
|
||||
changedKeys:
|
||||
[
|
||||
nameof(AppSettingsSnapshot.LanguageCode),
|
||||
nameof(AppSettingsSnapshot.TimeZoneId)
|
||||
]);
|
||||
ApplyTimeZone(snapshot.TimeZoneId);
|
||||
}
|
||||
|
||||
public TimeZoneService GetTimeZoneService()
|
||||
{
|
||||
return _timeZoneService;
|
||||
}
|
||||
|
||||
private void ApplyTimeZone(string? timeZoneId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(timeZoneId))
|
||||
{
|
||||
_timeZoneService.CurrentTimeZone = TimeZoneInfo.Local;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_timeZoneService.SetTimeZoneById(timeZoneId))
|
||||
{
|
||||
_timeZoneService.CurrentTimeZone = TimeZoneInfo.Local;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class UpdateSettingsService : IUpdateSettingsService, IDisposable
|
||||
{
|
||||
private readonly AppSettingsService _appSettingsService = new();
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly GitHubReleaseUpdateService _releaseUpdateService = new("wwiinnddyy", "LanMountainDesktop");
|
||||
|
||||
public UpdateSettingsService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
||||
}
|
||||
|
||||
public UpdateSettingsState Get()
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
var snapshot = _settingsService.Load();
|
||||
return new UpdateSettingsState(
|
||||
snapshot.AutoCheckUpdates,
|
||||
snapshot.IncludePrereleaseUpdates,
|
||||
@@ -386,11 +497,19 @@ internal sealed class UpdateSettingsService : IUpdateSettingsService, IDisposabl
|
||||
|
||||
public void Save(UpdateSettingsState state)
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
var snapshot = _settingsService.Load();
|
||||
snapshot.AutoCheckUpdates = state.AutoCheckUpdates;
|
||||
snapshot.IncludePrereleaseUpdates = state.IncludePrereleaseUpdates;
|
||||
snapshot.UpdateChannel = state.UpdateChannel;
|
||||
_appSettingsService.Save(snapshot);
|
||||
_settingsService.SaveSnapshot(
|
||||
SettingsScope.App,
|
||||
snapshot,
|
||||
changedKeys:
|
||||
[
|
||||
nameof(AppSettingsSnapshot.AutoCheckUpdates),
|
||||
nameof(AppSettingsSnapshot.IncludePrereleaseUpdates),
|
||||
nameof(AppSettingsSnapshot.UpdateChannel)
|
||||
]);
|
||||
}
|
||||
|
||||
public Task<UpdateCheckResult> CheckForUpdatesAsync(
|
||||
@@ -443,11 +562,12 @@ internal sealed class LauncherPolicyService : ILauncherPolicyService
|
||||
|
||||
internal sealed class PluginManagementSettingsService : IPluginManagementSettingsService
|
||||
{
|
||||
private readonly AppSettingsService _appSettingsService = new();
|
||||
private readonly ISettingsService _settingsService;
|
||||
private PluginRuntimeService? _pluginRuntimeService;
|
||||
|
||||
public PluginManagementSettingsService(PluginRuntimeService? pluginRuntimeService)
|
||||
public PluginManagementSettingsService(ISettingsService settingsService, PluginRuntimeService? pluginRuntimeService)
|
||||
{
|
||||
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
||||
_pluginRuntimeService = pluginRuntimeService;
|
||||
}
|
||||
|
||||
@@ -458,15 +578,18 @@ internal sealed class PluginManagementSettingsService : IPluginManagementSetting
|
||||
|
||||
public PluginManagementSettingsState Get()
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
var snapshot = _settingsService.Load();
|
||||
return new PluginManagementSettingsState(snapshot.DisabledPluginIds?.ToArray() ?? []);
|
||||
}
|
||||
|
||||
public void Save(PluginManagementSettingsState state)
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
var snapshot = _settingsService.Load();
|
||||
snapshot.DisabledPluginIds = state.DisabledPluginIds?.ToList() ?? [];
|
||||
_appSettingsService.Save(snapshot);
|
||||
_settingsService.SaveSnapshot(
|
||||
SettingsScope.App,
|
||||
snapshot,
|
||||
changedKeys: [nameof(AppSettingsSnapshot.DisabledPluginIds)]);
|
||||
}
|
||||
|
||||
public IReadOnlyList<InstalledPluginInfo> GetInstalledPlugins()
|
||||
@@ -653,19 +776,19 @@ internal sealed class SettingsFacadeService : ISettingsFacadeService, IDisposabl
|
||||
{
|
||||
Settings = new SettingsService();
|
||||
Catalog = new SettingsCatalogService();
|
||||
Grid = new GridSettingsService();
|
||||
Wallpaper = new WallpaperSettingsService();
|
||||
Grid = new GridSettingsService(Settings);
|
||||
Wallpaper = new WallpaperSettingsService(Settings);
|
||||
WallpaperMedia = new WallpaperMediaService();
|
||||
Theme = new ThemeAppearanceService();
|
||||
StatusBar = new StatusBarSettingsService();
|
||||
_weatherSettingsService = new WeatherSettingsService();
|
||||
Theme = new ThemeAppearanceService(Settings);
|
||||
StatusBar = new StatusBarSettingsService(Settings);
|
||||
_weatherSettingsService = new WeatherSettingsService(Settings);
|
||||
Weather = _weatherSettingsService;
|
||||
Region = new RegionSettingsService();
|
||||
_updateSettingsService = new UpdateSettingsService();
|
||||
Region = new RegionSettingsService(Settings);
|
||||
_updateSettingsService = new UpdateSettingsService(Settings);
|
||||
Update = _updateSettingsService;
|
||||
LauncherCatalog = new LauncherCatalogService();
|
||||
LauncherPolicy = new LauncherPolicyService();
|
||||
_pluginManagementSettingsService = new PluginManagementSettingsService(pluginRuntimeService);
|
||||
_pluginManagementSettingsService = new PluginManagementSettingsService(Settings, pluginRuntimeService);
|
||||
PluginManagement = _pluginManagementSettingsService;
|
||||
_pluginMarketSettingsService = new PluginMarketSettingsService(pluginRuntimeService);
|
||||
PluginMarket = _pluginMarketSettingsService;
|
||||
|
||||
334
LanMountainDesktop/Services/Settings/SettingsPageRegistry.cs
Normal file
334
LanMountainDesktop/Services/Settings/SettingsPageRegistry.cs
Normal file
@@ -0,0 +1,334 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Avalonia.Controls;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Plugins;
|
||||
using LanMountainDesktop.ViewModels;
|
||||
using LanMountainDesktop.Views.SettingsPages;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace LanMountainDesktop.Services.Settings;
|
||||
|
||||
public sealed class SettingsPageDescriptor
|
||||
{
|
||||
private readonly Func<ISettingsPageHostContext, Control> _factory;
|
||||
|
||||
public SettingsPageDescriptor(
|
||||
string pageId,
|
||||
string title,
|
||||
string? description,
|
||||
string iconKey,
|
||||
string? selectedIconKey,
|
||||
SettingsPageCategory category,
|
||||
int sortOrder,
|
||||
string? pluginId,
|
||||
bool isBuiltIn,
|
||||
bool hideDefault,
|
||||
bool hidePageTitle,
|
||||
bool useFullWidth,
|
||||
string? groupId,
|
||||
Func<ISettingsPageHostContext, Control> factory)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(pageId);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(title);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(iconKey);
|
||||
ArgumentNullException.ThrowIfNull(factory);
|
||||
|
||||
PageId = pageId.Trim();
|
||||
Title = title.Trim();
|
||||
Description = string.IsNullOrWhiteSpace(description) ? null : description.Trim();
|
||||
IconKey = iconKey.Trim();
|
||||
SelectedIconKey = string.IsNullOrWhiteSpace(selectedIconKey) ? IconKey : selectedIconKey.Trim();
|
||||
Category = category;
|
||||
SortOrder = sortOrder;
|
||||
PluginId = string.IsNullOrWhiteSpace(pluginId) ? null : pluginId.Trim();
|
||||
IsBuiltIn = isBuiltIn;
|
||||
HideDefault = hideDefault;
|
||||
HidePageTitle = hidePageTitle;
|
||||
UseFullWidth = useFullWidth;
|
||||
GroupId = string.IsNullOrWhiteSpace(groupId) ? null : groupId.Trim();
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
public string PageId { get; }
|
||||
|
||||
public string Title { get; }
|
||||
|
||||
public string? Description { get; }
|
||||
|
||||
public string IconKey { get; }
|
||||
|
||||
public string SelectedIconKey { get; }
|
||||
|
||||
public SettingsPageCategory Category { get; }
|
||||
|
||||
public int SortOrder { get; }
|
||||
|
||||
public string? PluginId { get; }
|
||||
|
||||
public bool IsBuiltIn { get; }
|
||||
|
||||
public bool HideDefault { get; }
|
||||
|
||||
public bool HidePageTitle { get; }
|
||||
|
||||
public bool UseFullWidth { get; }
|
||||
|
||||
public string? GroupId { get; }
|
||||
|
||||
public Control CreatePage(ISettingsPageHostContext hostContext) => _factory(hostContext);
|
||||
}
|
||||
|
||||
public interface ISettingsPageRegistry
|
||||
{
|
||||
void Rebuild();
|
||||
|
||||
IReadOnlyList<SettingsPageDescriptor> GetPages();
|
||||
|
||||
bool TryGetPage(string pageId, out SettingsPageDescriptor? descriptor);
|
||||
}
|
||||
|
||||
internal sealed class SettingsPageRegistry : ISettingsPageRegistry, IDisposable
|
||||
{
|
||||
private readonly ISettingsFacadeService _settingsFacade;
|
||||
private readonly IHostApplicationLifecycle _hostApplicationLifecycle;
|
||||
private readonly LocalizationService _localizationService;
|
||||
private readonly Func<PluginRuntimeService?> _pluginRuntimeAccessor;
|
||||
private readonly object _gate = new();
|
||||
private readonly List<SettingsPageDescriptor> _pages = [];
|
||||
private ServiceProvider? _hostServices;
|
||||
|
||||
public SettingsPageRegistry(
|
||||
ISettingsFacadeService settingsFacade,
|
||||
IHostApplicationLifecycle hostApplicationLifecycle,
|
||||
LocalizationService localizationService,
|
||||
Func<PluginRuntimeService?> pluginRuntimeAccessor)
|
||||
{
|
||||
_settingsFacade = settingsFacade ?? throw new ArgumentNullException(nameof(settingsFacade));
|
||||
_hostApplicationLifecycle = hostApplicationLifecycle ?? throw new ArgumentNullException(nameof(hostApplicationLifecycle));
|
||||
_localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
|
||||
_pluginRuntimeAccessor = pluginRuntimeAccessor ?? throw new ArgumentNullException(nameof(pluginRuntimeAccessor));
|
||||
}
|
||||
|
||||
public void Rebuild()
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
_pages.Clear();
|
||||
RebuildHostServices();
|
||||
|
||||
RegisterAssemblyPages(
|
||||
typeof(App).Assembly,
|
||||
_hostServices!,
|
||||
pluginId: null,
|
||||
isBuiltIn: true);
|
||||
|
||||
var pluginRuntime = _pluginRuntimeAccessor();
|
||||
if (pluginRuntime is null)
|
||||
{
|
||||
SortPages();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var loadedPlugin in pluginRuntime.LoadedPlugins)
|
||||
{
|
||||
RegisterPluginPages(loadedPlugin);
|
||||
RegisterLegacyPluginSections(loadedPlugin);
|
||||
}
|
||||
|
||||
SortPages();
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<SettingsPageDescriptor> GetPages()
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
return _pages.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetPage(string pageId, out SettingsPageDescriptor? descriptor)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(pageId);
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
descriptor = _pages.FirstOrDefault(item =>
|
||||
string.Equals(item.PageId, pageId, StringComparison.OrdinalIgnoreCase));
|
||||
return descriptor is not null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_hostServices?.Dispose();
|
||||
}
|
||||
|
||||
private void RebuildHostServices()
|
||||
{
|
||||
_hostServices?.Dispose();
|
||||
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton(_settingsFacade);
|
||||
services.AddSingleton(_settingsFacade.Settings);
|
||||
services.AddSingleton(_settingsFacade.Catalog);
|
||||
services.AddSingleton(_hostApplicationLifecycle);
|
||||
services.AddSingleton(_localizationService);
|
||||
|
||||
var pluginRuntime = _pluginRuntimeAccessor();
|
||||
if (pluginRuntime is not null)
|
||||
{
|
||||
services.AddSingleton(pluginRuntime);
|
||||
}
|
||||
|
||||
_hostServices = services.BuildServiceProvider(new ServiceProviderOptions
|
||||
{
|
||||
ValidateScopes = false,
|
||||
ValidateOnBuild = false
|
||||
});
|
||||
}
|
||||
|
||||
private void RegisterAssemblyPages(
|
||||
Assembly assembly,
|
||||
IServiceProvider services,
|
||||
string? pluginId,
|
||||
bool isBuiltIn)
|
||||
{
|
||||
foreach (var pageType in assembly.GetTypes()
|
||||
.Where(type => !type.IsAbstract && typeof(SettingsPageBase).IsAssignableFrom(type)))
|
||||
{
|
||||
var pageInfo = pageType.GetCustomAttribute<SettingsPageInfoAttribute>();
|
||||
if (pageInfo is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var category = isBuiltIn ? pageInfo.Category : SettingsPageCategory.Plugins;
|
||||
var sortOrder = isBuiltIn ? pageInfo.SortOrder : 100 + pageInfo.SortOrder;
|
||||
var title = ResolveLocalizedText(pageInfo.TitleLocalizationKey, pageInfo.Name);
|
||||
var description = ResolveLocalizedText(pageInfo.DescriptionLocalizationKey, null);
|
||||
_pages.Add(new SettingsPageDescriptor(
|
||||
pageInfo.Id,
|
||||
title,
|
||||
description,
|
||||
pageInfo.IconKey,
|
||||
pageInfo.SelectedIconKey,
|
||||
category,
|
||||
sortOrder,
|
||||
pluginId,
|
||||
isBuiltIn,
|
||||
pageInfo.HideDefault,
|
||||
pageInfo.HidePageTitle,
|
||||
pageInfo.UseFullWidth,
|
||||
pageInfo.GroupId,
|
||||
hostContext => CreatePage(services, pageType, hostContext)));
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterPluginPages(LoadedPlugin loadedPlugin)
|
||||
{
|
||||
RegisterAssemblyPages(
|
||||
loadedPlugin.Assembly,
|
||||
loadedPlugin.Services,
|
||||
loadedPlugin.Manifest.Id,
|
||||
isBuiltIn: false);
|
||||
}
|
||||
|
||||
private void RegisterLegacyPluginSections(LoadedPlugin loadedPlugin)
|
||||
{
|
||||
var localizer = PluginLocalizer.Create(loadedPlugin.RuntimeContext);
|
||||
|
||||
foreach (var section in loadedPlugin.SettingsSections)
|
||||
{
|
||||
var pageId = $"plugin:{loadedPlugin.Manifest.Id}:{section.Id}";
|
||||
var title = localizer.GetString(section.TitleLocalizationKey, section.TitleLocalizationKey);
|
||||
var description = string.IsNullOrWhiteSpace(section.DescriptionLocalizationKey)
|
||||
? null
|
||||
: localizer.GetString(section.DescriptionLocalizationKey, section.DescriptionLocalizationKey);
|
||||
|
||||
_pages.Add(new SettingsPageDescriptor(
|
||||
pageId,
|
||||
title,
|
||||
description,
|
||||
section.IconKey,
|
||||
section.IconKey,
|
||||
SettingsPageCategory.Plugins,
|
||||
200 + section.SortOrder,
|
||||
loadedPlugin.Manifest.Id,
|
||||
isBuiltIn: false,
|
||||
hideDefault: false,
|
||||
hidePageTitle: false,
|
||||
useFullWidth: false,
|
||||
groupId: null,
|
||||
hostContext =>
|
||||
{
|
||||
var page = new GeneratedPluginSettingsPage(
|
||||
new PluginGeneratedSettingsPageViewModel(
|
||||
_settingsFacade.Settings,
|
||||
loadedPlugin.Manifest.Id,
|
||||
section,
|
||||
localizer));
|
||||
page.InitializeHostContext(hostContext);
|
||||
return page;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private void SortPages()
|
||||
{
|
||||
_pages.Sort(static (left, right) =>
|
||||
{
|
||||
var categoryCompare = left.Category.CompareTo(right.Category);
|
||||
if (categoryCompare != 0)
|
||||
{
|
||||
return categoryCompare;
|
||||
}
|
||||
|
||||
var sortOrderCompare = left.SortOrder.CompareTo(right.SortOrder);
|
||||
if (sortOrderCompare != 0)
|
||||
{
|
||||
return sortOrderCompare;
|
||||
}
|
||||
|
||||
var pluginCompare = string.Compare(left.PluginId, right.PluginId, StringComparison.OrdinalIgnoreCase);
|
||||
if (pluginCompare != 0)
|
||||
{
|
||||
return pluginCompare;
|
||||
}
|
||||
|
||||
return string.Compare(left.PageId, right.PageId, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
}
|
||||
|
||||
private string ResolveLocalizedText(string? localizationKey, string? fallback)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(localizationKey))
|
||||
{
|
||||
return fallback ?? string.Empty;
|
||||
}
|
||||
|
||||
var languageCode = _settingsFacade.Region.Get().LanguageCode;
|
||||
var normalizedLanguageCode = _localizationService.NormalizeLanguageCode(languageCode);
|
||||
return _localizationService.GetString(
|
||||
normalizedLanguageCode,
|
||||
localizationKey,
|
||||
string.IsNullOrWhiteSpace(fallback) ? localizationKey : fallback);
|
||||
}
|
||||
|
||||
private static Control CreatePage(
|
||||
IServiceProvider services,
|
||||
Type pageType,
|
||||
ISettingsPageHostContext hostContext)
|
||||
{
|
||||
var page = (Control)ActivatorUtilities.CreateInstance(services, pageType);
|
||||
if (page is SettingsPageBase settingsPage)
|
||||
{
|
||||
settingsPage.InitializeHostContext(hostContext);
|
||||
}
|
||||
|
||||
return page;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using LanMountainDesktop.Models;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
|
||||
namespace LanMountainDesktop.Services;
|
||||
|
||||
public static class SettingsServiceAppSnapshotExtensions
|
||||
{
|
||||
public static AppSettingsSnapshot Load(this ISettingsService settingsService)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(settingsService);
|
||||
return settingsService.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App);
|
||||
}
|
||||
|
||||
public static void Save(this ISettingsService settingsService, AppSettingsSnapshot snapshot)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(settingsService);
|
||||
settingsService.SaveSnapshot(SettingsScope.App, snapshot ?? new AppSettingsSnapshot());
|
||||
}
|
||||
}
|
||||
306
LanMountainDesktop/Services/Settings/SettingsWindowService.cs
Normal file
306
LanMountainDesktop/Services/Settings/SettingsWindowService.cs
Normal file
@@ -0,0 +1,306 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Threading;
|
||||
using LanMountainDesktop.Models;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.ViewModels;
|
||||
using LanMountainDesktop.Views;
|
||||
|
||||
namespace LanMountainDesktop.Services.Settings;
|
||||
|
||||
public enum SettingsWindowAnchorTarget
|
||||
{
|
||||
DesktopDockTrailingEdge = 0
|
||||
}
|
||||
|
||||
public enum SettingsWindowFallbackMode
|
||||
{
|
||||
None = 0,
|
||||
ScreenBottomRight = 1
|
||||
}
|
||||
|
||||
public readonly record struct SettingsWindowOpenRequest(
|
||||
string Source,
|
||||
Window? Owner = null,
|
||||
string? PageId = null,
|
||||
SettingsWindowAnchorTarget AnchorTarget = SettingsWindowAnchorTarget.DesktopDockTrailingEdge,
|
||||
SettingsWindowFallbackMode FallbackMode = SettingsWindowFallbackMode.ScreenBottomRight);
|
||||
|
||||
public interface ISettingsWindowAnchorProvider
|
||||
{
|
||||
bool TryGetSettingsWindowAnchorBounds(out PixelRect anchorBounds);
|
||||
}
|
||||
|
||||
public interface ISettingsWindowService
|
||||
{
|
||||
bool IsOpen { get; }
|
||||
|
||||
event EventHandler? StateChanged;
|
||||
|
||||
void Open(SettingsWindowOpenRequest request);
|
||||
|
||||
void Close();
|
||||
|
||||
void Toggle(SettingsWindowOpenRequest request);
|
||||
}
|
||||
|
||||
internal sealed class SettingsWindowService : ISettingsWindowService
|
||||
{
|
||||
private readonly ISettingsPageRegistry _pageRegistry;
|
||||
private readonly IHostApplicationLifecycle _hostApplicationLifecycle;
|
||||
private readonly ISettingsFacadeService _settingsFacade;
|
||||
private readonly LocalizationService _localizationService;
|
||||
private SettingsWindowViewModel _viewModel = null!;
|
||||
private SettingsWindow? _window;
|
||||
|
||||
public SettingsWindowService(
|
||||
ISettingsPageRegistry pageRegistry,
|
||||
IHostApplicationLifecycle hostApplicationLifecycle,
|
||||
ISettingsFacadeService settingsFacade)
|
||||
{
|
||||
_pageRegistry = pageRegistry;
|
||||
_hostApplicationLifecycle = hostApplicationLifecycle;
|
||||
_settingsFacade = settingsFacade;
|
||||
_localizationService = new();
|
||||
_settingsFacade.Settings.Changed += OnSettingsChanged;
|
||||
}
|
||||
|
||||
private string L(string key)
|
||||
{
|
||||
var regionState = _settingsFacade.Region.Get();
|
||||
var languageCode = regionState.LanguageCode ?? "zh-CN";
|
||||
return _localizationService.GetString(languageCode, key, key);
|
||||
}
|
||||
|
||||
public bool IsOpen => _window is { IsVisible: true };
|
||||
public event EventHandler? StateChanged;
|
||||
|
||||
public void Open(SettingsWindowOpenRequest request)
|
||||
{
|
||||
_pageRegistry.Rebuild();
|
||||
_window ??= CreateWindow();
|
||||
var themeState = _settingsFacade.Theme.Get();
|
||||
_window.ApplyChromeMode(themeState.UseSystemChrome);
|
||||
ApplyTheme(_window, themeState.IsNightMode);
|
||||
_window.ReloadPages(request.PageId);
|
||||
PositionWindow(_window, request);
|
||||
|
||||
if (!_window.IsVisible)
|
||||
{
|
||||
if (request.Owner is not null && request.Owner.IsVisible)
|
||||
{
|
||||
_window.Show(request.Owner);
|
||||
}
|
||||
else
|
||||
{
|
||||
_window.Show();
|
||||
}
|
||||
|
||||
NotifyStateChanged();
|
||||
PositionWindowLater(_window, request);
|
||||
return;
|
||||
}
|
||||
|
||||
_window.Activate();
|
||||
PositionWindowLater(_window, request);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_window?.Close();
|
||||
}
|
||||
|
||||
public void Toggle(SettingsWindowOpenRequest request)
|
||||
{
|
||||
if (IsOpen)
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
Open(request);
|
||||
}
|
||||
|
||||
private SettingsWindow CreateWindow()
|
||||
{
|
||||
var regionState = _settingsFacade.Region.Get();
|
||||
var languageCode = regionState.LanguageCode ?? "zh-CN";
|
||||
|
||||
_viewModel = new SettingsWindowViewModel(_localizationService, languageCode).Initialize();
|
||||
|
||||
var themeState = _settingsFacade.Theme.Get();
|
||||
var useSystemChrome = themeState.UseSystemChrome;
|
||||
|
||||
var window = new SettingsWindow(
|
||||
_viewModel,
|
||||
_pageRegistry,
|
||||
_hostApplicationLifecycle,
|
||||
useSystemChrome);
|
||||
ApplyTheme(window, themeState.IsNightMode);
|
||||
window.ShowInTaskbar = false;
|
||||
window.Closed += (_, _) =>
|
||||
{
|
||||
_window = null;
|
||||
NotifyStateChanged();
|
||||
};
|
||||
return window;
|
||||
}
|
||||
|
||||
private void PositionWindowLater(SettingsWindow window, SettingsWindowOpenRequest request)
|
||||
{
|
||||
Dispatcher.UIThread.Post(
|
||||
() =>
|
||||
{
|
||||
if (!window.IsVisible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PositionWindow(window, request);
|
||||
},
|
||||
DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
private static void PositionWindow(SettingsWindow window, SettingsWindowOpenRequest request)
|
||||
{
|
||||
if (request.AnchorTarget == SettingsWindowAnchorTarget.DesktopDockTrailingEdge &&
|
||||
request.Owner is ISettingsWindowAnchorProvider anchorProvider &&
|
||||
anchorProvider.TryGetSettingsWindowAnchorBounds(out var anchorBounds))
|
||||
{
|
||||
PositionWindowAboveAnchor(window, anchorBounds, request);
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.FallbackMode == SettingsWindowFallbackMode.ScreenBottomRight)
|
||||
{
|
||||
PositionWindowNearScreenBottomRight(window, request);
|
||||
}
|
||||
}
|
||||
|
||||
private static void PositionWindowAboveAnchor(Window window, PixelRect anchorBounds, SettingsWindowOpenRequest request)
|
||||
{
|
||||
var workingArea = GetWorkingArea(window, request);
|
||||
|
||||
if (anchorBounds.Width <= 0 || anchorBounds.Height <= 0 ||
|
||||
anchorBounds.Right < workingArea.X || anchorBounds.Y > workingArea.Bottom)
|
||||
{
|
||||
PositionWindowNearScreenBottomRight(window, request);
|
||||
return;
|
||||
}
|
||||
|
||||
var scale = window.RenderScaling > 0 ? window.RenderScaling : 1d;
|
||||
var width = ResolveWindowWidth(window, scale);
|
||||
var height = ResolveWindowHeight(window, scale);
|
||||
var inset = (int)Math.Round(24 * scale);
|
||||
var gap = (int)Math.Round(16 * scale);
|
||||
|
||||
var x = anchorBounds.Right - width - inset;
|
||||
var y = anchorBounds.Y - height - gap;
|
||||
x = Math.Clamp(x, workingArea.X + inset, Math.Max(workingArea.X + inset, workingArea.Right - width - inset));
|
||||
y = Math.Clamp(y, workingArea.Y + inset, Math.Max(workingArea.Y + inset, workingArea.Bottom - height - inset));
|
||||
window.Position = new PixelPoint(x, y);
|
||||
}
|
||||
|
||||
private static void PositionWindowNearScreenBottomRight(Window window, SettingsWindowOpenRequest request)
|
||||
{
|
||||
var workingArea = GetWorkingArea(window, request);
|
||||
var scale = window.RenderScaling > 0 ? window.RenderScaling : 1d;
|
||||
var width = ResolveWindowWidth(window, scale);
|
||||
var height = ResolveWindowHeight(window, scale);
|
||||
var inset = (int)Math.Round(24 * scale);
|
||||
|
||||
var x = Math.Max(workingArea.X + inset, workingArea.Right - width - inset);
|
||||
var y = Math.Max(workingArea.Y + inset, workingArea.Bottom - height - inset);
|
||||
window.Position = new PixelPoint(x, y);
|
||||
}
|
||||
|
||||
private static PixelRect GetWorkingArea(Window window, SettingsWindowOpenRequest request)
|
||||
{
|
||||
if (request.Owner is not null && request.Owner.Screens?.ScreenFromWindow(request.Owner) is { } ownerScreen)
|
||||
{
|
||||
return ownerScreen.WorkingArea;
|
||||
}
|
||||
|
||||
if (window.Screens?.ScreenFromWindow(window) is { } windowScreen)
|
||||
{
|
||||
return windowScreen.WorkingArea;
|
||||
}
|
||||
|
||||
return window.Screens?.Primary?.WorkingArea
|
||||
?? new PixelRect(
|
||||
0,
|
||||
0,
|
||||
Math.Max(1280, ResolveWindowWidth(window, 1d) + 96),
|
||||
Math.Max(720, ResolveWindowHeight(window, 1d) + 96));
|
||||
}
|
||||
|
||||
private static int ResolveWindowWidth(Window window, double scale)
|
||||
{
|
||||
var widthDip = window.Bounds.Width > 1 ? window.Bounds.Width : Math.Max(window.Width, window.MinWidth);
|
||||
return Math.Max(320, (int)Math.Round(widthDip * scale));
|
||||
}
|
||||
|
||||
private static int ResolveWindowHeight(Window window, double scale)
|
||||
{
|
||||
var heightDip = window.Bounds.Height > 1 ? window.Bounds.Height : Math.Max(window.Height, window.MinHeight);
|
||||
return Math.Max(240, (int)Math.Round(heightDip * scale));
|
||||
}
|
||||
|
||||
private void NotifyStateChanged()
|
||||
{
|
||||
StateChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void OnSettingsChanged(object? sender, SettingsChangedEvent e)
|
||||
{
|
||||
_ = sender;
|
||||
|
||||
if (e.Scope != SettingsScope.App)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (_window is null || _viewModel is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var changedKeys = e.ChangedKeys?.ToArray();
|
||||
var refreshAll = changedKeys is null || changedKeys.Length == 0;
|
||||
var languageChanged = refreshAll || changedKeys.Contains(nameof(AppSettingsSnapshot.LanguageCode), StringComparer.OrdinalIgnoreCase);
|
||||
var themeChanged =
|
||||
refreshAll ||
|
||||
changedKeys.Contains(nameof(AppSettingsSnapshot.IsNightMode), StringComparer.OrdinalIgnoreCase) ||
|
||||
changedKeys.Contains(nameof(AppSettingsSnapshot.UseSystemChrome), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (languageChanged)
|
||||
{
|
||||
var regionState = _settingsFacade.Region.Get();
|
||||
_viewModel.RefreshLanguage(regionState.LanguageCode);
|
||||
_pageRegistry.Rebuild();
|
||||
_window.ReloadPages(_viewModel.CurrentPageId);
|
||||
_window.RefreshShellText();
|
||||
}
|
||||
|
||||
if (themeChanged)
|
||||
{
|
||||
var themeState = _settingsFacade.Theme.Get();
|
||||
_window.ApplyChromeMode(themeState.UseSystemChrome);
|
||||
ApplyTheme(_window, themeState.IsNightMode);
|
||||
}
|
||||
}, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
private static void ApplyTheme(SettingsWindow window, bool isNightMode)
|
||||
{
|
||||
window.RequestedThemeVariant = isNightMode
|
||||
? ThemeVariant.Dark
|
||||
: ThemeVariant.Light;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user