mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 02:36:19 +02:00
CmdPal: Extract persistence services from SettingsModel and AppStateModel (#46312)
## Summary of the Pull Request Extracts persistence (load/save) logic from `SettingsModel` and `AppStateModel` into dedicated service classes, following the single-responsibility principle. Consumers now interact with `ISettingsService` and `IAppStateService` instead of receiving raw model objects through DI. **New services introduced:** - `IPersistenceService` / `PersistenceService` — generic `Load<T>` / `Save<T>` with AOT-compatible `JsonTypeInfo<T>`, ensures target directory exists before writing - `ISettingsService` / `SettingsService` — loads settings on construction, runs migrations, exposes `Settings` property and `SettingsChanged` event - `IAppStateService` / `AppStateService` — loads state on construction, exposes `State` property and `StateChanged` event **Key changes:** - `SettingsModel` and `AppStateModel` are now pure data models — all file I/O, migration, and directory management removed - Raw `SettingsModel` and `AppStateModel` removed from DI container; consumers receive the appropriate service - `IApplicationInfoService.ConfigDirectory` injected into services for config path resolution (no more hardcoded `Utilities.BaseSettingsPath`) - ~30 consumer files updated across `Microsoft.CmdPal.UI.ViewModels` and `Microsoft.CmdPal.UI` projects - All `#pragma warning disable SA1300` suppressions removed — convenience accessors replaced with direct `_settingsService.Settings` / `_appStateService.State` access - Namespace prefixes (`Services.ISettingsService`) replaced with proper `using` directives ## PR Checklist - [ ] **Communication:** I've discussed this with core contributors already. - [x] **Tests:** Added/updated and all pass - [ ] **Localization:** N/A — no end-user-facing strings changed - [ ] **Dev docs:** N/A — internal refactor, no public API changes - [ ] **New binaries:** N/A — no new binaries introduced ## Detailed Description of the Pull Request / Additional comments ### Architecture Services are registered as singletons in `App.xaml.cs`: ```csharp services.AddSingleton<IPersistenceService, PersistenceService>(); services.AddSingleton<ISettingsService, SettingsService>(); services.AddSingleton<IAppStateService, AppStateService>(); ``` `PersistenceService.Save<T>` writes the serialized model directly to disk, creating the target directory if it doesn't exist. It also does not attempt to merge existing and new settings/state. `SettingsService` runs hotkey migrations on load and raises `SettingsChanged` after saves. `AppStateService` always raises `StateChanged` after saves. ### Files changed (41 files, +1169/−660) | Area | Files | What changed | |------|-------|-------------| | New services | `Services/IPersistenceService.cs`, `PersistenceService.cs`, `ISettingsService.cs`, `SettingsService.cs`, `IAppStateService.cs`, `AppStateService.cs` | New service interfaces and implementations | | Models | `SettingsModel.cs`, `AppStateModel.cs` | Stripped to pure data bags | | DI | `App.xaml.cs` | Service registration, removed raw model DI | | ViewModels | 12 files | Constructor injection of services | | UI | 10 files | Service injection replacing model access | | Settings | `DockSettings.cs` | `Colors.Transparent` replaced with struct literal to avoid WinUI3 COM dependency | | Tests | `PersistenceServiceTests.cs`, `SettingsServiceTests.cs`, `AppStateServiceTests.cs` | 38 unit tests covering all three services | | Config | `.gitignore` | Added `.squad/`, `.github/agents/` exclusions | ## Validation Steps Performed - Built `Microsoft.CmdPal.UI` with MSBuild (x64/Debug) — exit code 0, clean build - Ran 38 unit tests via `vstest.console.exe` — all passing - Verified no remaining `#pragma warning disable SA1300` blocks - Verified no remaining `Services.` namespace prefixes --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -6,6 +6,7 @@ using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Settings;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
@@ -15,12 +16,12 @@ namespace Microsoft.CmdPal.UI;
|
||||
|
||||
internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFactory
|
||||
{
|
||||
private readonly SettingsModel _settingsModel;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly TopLevelCommandManager _topLevelCommandManager;
|
||||
|
||||
public CommandPaletteContextMenuFactory(SettingsModel settingsModel, TopLevelCommandManager topLevelCommandManager)
|
||||
public CommandPaletteContextMenuFactory(ISettingsService settingsService, TopLevelCommandManager topLevelCommandManager)
|
||||
{
|
||||
_settingsModel = settingsModel;
|
||||
_settingsService = settingsService;
|
||||
_topLevelCommandManager = topLevelCommandManager;
|
||||
}
|
||||
|
||||
@@ -65,7 +66,7 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
var providerId = providerContext.ProviderId;
|
||||
if (_topLevelCommandManager.LookupProvider(providerId) is CommandProviderWrapper provider)
|
||||
{
|
||||
var providerSettings = _settingsModel.GetProviderSettings(provider);
|
||||
var providerSettings = _settingsService.Settings.GetProviderSettings(provider);
|
||||
|
||||
var alreadyPinnedToTopLevel = providerSettings.PinnedCommandIds.Contains(itemId);
|
||||
|
||||
@@ -82,7 +83,7 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
providerId: providerId,
|
||||
pin: !alreadyPinnedToTopLevel,
|
||||
PinLocation.TopLevel,
|
||||
_settingsModel,
|
||||
_settingsService,
|
||||
_topLevelCommandManager);
|
||||
|
||||
var contextItem = new PinToContextItem(pinToTopLevelCommand, commandItem);
|
||||
@@ -132,7 +133,7 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
var providerId = providerContext.ProviderId;
|
||||
if (_topLevelCommandManager.LookupProvider(providerId) is CommandProviderWrapper provider)
|
||||
{
|
||||
var providerSettings = _settingsModel.GetProviderSettings(provider);
|
||||
var providerSettings = _settingsService.Settings.GetProviderSettings(provider);
|
||||
|
||||
var isPinnedSubCommand = providerSettings.PinnedCommandIds.Contains(itemId);
|
||||
if (isPinnedSubCommand)
|
||||
@@ -142,7 +143,7 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
providerId: providerId,
|
||||
pin: !isPinnedSubCommand,
|
||||
PinLocation.TopLevel,
|
||||
_settingsModel,
|
||||
_settingsService,
|
||||
_topLevelCommandManager);
|
||||
|
||||
var contextItem = new PinToContextItem(pinToTopLevelCommand, commandItem);
|
||||
@@ -168,22 +169,22 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
List<IContextItem> moreCommands,
|
||||
CommandItemViewModel commandItem)
|
||||
{
|
||||
if (!_settingsModel.EnableDock)
|
||||
if (!_settingsService.Settings.EnableDock)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var inStartBands = _settingsModel.DockSettings.StartBands.Any(band => MatchesBand(band, itemId, providerId));
|
||||
var inCenterBands = _settingsModel.DockSettings.CenterBands.Any(band => MatchesBand(band, itemId, providerId));
|
||||
var inEndBands = _settingsModel.DockSettings.EndBands.Any(band => MatchesBand(band, itemId, providerId));
|
||||
var inStartBands = _settingsService.Settings.DockSettings.StartBands.Any(band => MatchesBand(band, itemId, providerId));
|
||||
var inCenterBands = _settingsService.Settings.DockSettings.CenterBands.Any(band => MatchesBand(band, itemId, providerId));
|
||||
var inEndBands = _settingsService.Settings.DockSettings.EndBands.Any(band => MatchesBand(band, itemId, providerId));
|
||||
var alreadyPinned = inStartBands || inCenterBands || inEndBands; /** &&
|
||||
_settingsModel.DockSettings.PinnedCommands.Contains(this.Id)**/
|
||||
_settingsService.Settings.DockSettings.PinnedCommands.Contains(this.Id)**/
|
||||
var pinToTopLevelCommand = new PinToCommand(
|
||||
commandId: itemId,
|
||||
providerId: providerId,
|
||||
pin: !alreadyPinned,
|
||||
PinLocation.Dock,
|
||||
_settingsModel,
|
||||
_settingsService,
|
||||
_topLevelCommandManager);
|
||||
|
||||
var contextItem = new PinToContextItem(pinToTopLevelCommand, commandItem);
|
||||
@@ -231,7 +232,7 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
{
|
||||
private readonly string _commandId;
|
||||
private readonly string _providerId;
|
||||
private readonly SettingsModel _settings;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly TopLevelCommandManager _topLevelCommandManager;
|
||||
private readonly bool _pin;
|
||||
private readonly PinLocation _pinLocation;
|
||||
@@ -251,13 +252,13 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
string providerId,
|
||||
bool pin,
|
||||
PinLocation pinLocation,
|
||||
SettingsModel settings,
|
||||
ISettingsService settingsService,
|
||||
TopLevelCommandManager topLevelCommandManager)
|
||||
{
|
||||
_commandId = commandId;
|
||||
_providerId = providerId;
|
||||
_pinLocation = pinLocation;
|
||||
_settings = settings;
|
||||
_settingsService = settingsService;
|
||||
_topLevelCommandManager = topLevelCommandManager;
|
||||
_pin = pin;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user