diff --git a/LanMountainDesktop/ComponentSystem/DesktopComponentRuntimeContext.cs b/LanMountainDesktop/ComponentSystem/DesktopComponentRuntimeContext.cs new file mode 100644 index 0000000..e0df3b8 --- /dev/null +++ b/LanMountainDesktop/ComponentSystem/DesktopComponentRuntimeContext.cs @@ -0,0 +1,8 @@ +using LanMountainDesktop.Services; + +namespace LanMountainDesktop.ComponentSystem; + +public sealed record DesktopComponentRuntimeContext( + string ComponentId, + string? PlacementId, + IComponentInstanceSettingsStore ComponentSettingsStore); diff --git a/LanMountainDesktop/ComponentSystem/IComponentPlacementContextAware.cs b/LanMountainDesktop/ComponentSystem/IComponentPlacementContextAware.cs new file mode 100644 index 0000000..da50401 --- /dev/null +++ b/LanMountainDesktop/ComponentSystem/IComponentPlacementContextAware.cs @@ -0,0 +1,6 @@ +namespace LanMountainDesktop.ComponentSystem; + +public interface IComponentPlacementContextAware +{ + void SetComponentPlacementContext(string componentId, string? placementId); +} diff --git a/LanMountainDesktop/ComponentSystem/IComponentRuntimeContextAware.cs b/LanMountainDesktop/ComponentSystem/IComponentRuntimeContextAware.cs new file mode 100644 index 0000000..41a86a1 --- /dev/null +++ b/LanMountainDesktop/ComponentSystem/IComponentRuntimeContextAware.cs @@ -0,0 +1,6 @@ +namespace LanMountainDesktop.ComponentSystem; + +public interface IComponentRuntimeContextAware +{ + void SetComponentRuntimeContext(DesktopComponentRuntimeContext context); +} diff --git a/LanMountainDesktop/ComponentSystem/IComponentSettingsStoreAware.cs b/LanMountainDesktop/ComponentSystem/IComponentSettingsStoreAware.cs new file mode 100644 index 0000000..e98500e --- /dev/null +++ b/LanMountainDesktop/ComponentSystem/IComponentSettingsStoreAware.cs @@ -0,0 +1,8 @@ +using LanMountainDesktop.Services; + +namespace LanMountainDesktop.ComponentSystem; + +public interface IComponentSettingsStoreAware +{ + void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore); +} diff --git a/LanMountainDesktop/Services/ComponentSettingsService.cs b/LanMountainDesktop/Services/ComponentSettingsService.cs index 199730c..9ba3a3c 100644 --- a/LanMountainDesktop/Services/ComponentSettingsService.cs +++ b/LanMountainDesktop/Services/ComponentSettingsService.cs @@ -2,12 +2,13 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Text.Json; using LanMountainDesktop.Models; namespace LanMountainDesktop.Services; -public sealed class ComponentSettingsService +public sealed class ComponentSettingsService : IComponentInstanceSettingsStore { private static readonly JsonSerializerOptions SerializerOptions = new() { @@ -18,12 +19,14 @@ public sealed class ComponentSettingsService private static readonly TimeSpan CacheProbeInterval = TimeSpan.FromMilliseconds(400); private static string? _cachedPath; - private static ComponentSettingsSnapshot? _cachedSnapshot; + private static ComponentSettingsDocumentSnapshot? _cachedSnapshot; private static DateTime _cachedWriteTimeUtc = DateTime.MinValue; private static DateTime _lastProbeUtc = DateTime.MinValue; private readonly string _settingsPath; private readonly string _legacyAppSettingsPath; + private string _scopedComponentId = string.Empty; + private string _scopedPlacementId = string.Empty; public ComponentSettingsService() { @@ -35,51 +38,17 @@ public sealed class ComponentSettingsService public ComponentSettingsSnapshot Load() { + if (HasScopedComponentContext()) + { + return LoadForComponent(_scopedComponentId, _scopedPlacementId); + } + try { lock (CacheGate) { - var nowUtc = DateTime.UtcNow; - if (TryGetCachedWithoutProbe(nowUtc, out var cached)) - { - return cached; - } - - var hasFile = File.Exists(_settingsPath); - var writeTimeUtc = hasFile - ? File.GetLastWriteTimeUtc(_settingsPath) - : DateTime.MinValue; - - _lastProbeUtc = nowUtc; - if (TryGetCachedAfterProbe(writeTimeUtc, out cached)) - { - return cached; - } - - ComponentSettingsSnapshot loadedSnapshot; - var loadedFromLegacy = false; - if (hasFile) - { - loadedSnapshot = LoadSnapshotFromDisk(); - } - else if (TryLoadLegacySnapshot(out var migratedSnapshot)) - { - loadedSnapshot = migratedSnapshot; - loadedFromLegacy = true; - } - else - { - loadedSnapshot = new ComponentSettingsSnapshot(); - } - - var normalizedSnapshot = NormalizeSnapshot(loadedSnapshot); - if (loadedFromLegacy) - { - writeTimeUtc = PersistSnapshotToDisk(normalizedSnapshot); - } - - UpdateCache(normalizedSnapshot, writeTimeUtc, nowUtc); - return normalizedSnapshot.Clone(); + var document = LoadDocumentLocked(); + return document.DefaultSettings.Clone(); } } catch @@ -90,15 +59,21 @@ public sealed class ComponentSettingsService public void Save(ComponentSettingsSnapshot snapshot) { + if (HasScopedComponentContext()) + { + SaveForComponent(_scopedComponentId, _scopedPlacementId, snapshot); + return; + } + var snapshotToPersist = NormalizeSnapshot(snapshot); try { - var writeTimeUtc = PersistSnapshotToDisk(snapshotToPersist); - lock (CacheGate) { - UpdateCache(snapshotToPersist, writeTimeUtc, DateTime.UtcNow); + var document = LoadDocumentLocked(); + document.DefaultSettings = snapshotToPersist; + PersistDocumentLocked(document); } } catch @@ -107,7 +82,201 @@ public sealed class ComponentSettingsService } } - private bool TryGetCachedWithoutProbe(DateTime nowUtc, out ComponentSettingsSnapshot snapshot) + public ComponentSettingsSnapshot LoadForComponent(string componentId, string? placementId) + { + try + { + lock (CacheGate) + { + var document = LoadDocumentLocked(); + var instanceKey = BuildInstanceKey(componentId, placementId); + if (!string.IsNullOrWhiteSpace(instanceKey) && + document.InstanceSettings.TryGetValue(instanceKey, out var snapshot)) + { + return snapshot.Clone(); + } + + return document.DefaultSettings.Clone(); + } + } + catch + { + return new ComponentSettingsSnapshot(); + } + } + + public void SaveForComponent(string componentId, string? placementId, ComponentSettingsSnapshot snapshot) + { + var normalizedSnapshot = NormalizeSnapshot(snapshot); + var instanceKey = BuildInstanceKey(componentId, placementId); + if (string.IsNullOrWhiteSpace(instanceKey)) + { + Save(normalizedSnapshot); + return; + } + + try + { + lock (CacheGate) + { + var document = LoadDocumentLocked(); + document.InstanceSettings[instanceKey] = normalizedSnapshot; + PersistDocumentLocked(document); + } + } + catch + { + // Swallow persistence errors to keep UI interactions uninterrupted. + } + } + + public void DeleteForComponent(string componentId, string? placementId) + { + var instanceKey = BuildInstanceKey(componentId, placementId); + if (string.IsNullOrWhiteSpace(instanceKey)) + { + return; + } + + try + { + lock (CacheGate) + { + var document = LoadDocumentLocked(); + var changed = document.InstanceSettings.Remove(instanceKey); + changed |= document.PluginSettings.Remove(instanceKey); + if (changed) + { + PersistDocumentLocked(document); + } + } + } + catch + { + // Swallow persistence errors to keep UI interactions uninterrupted. + } + } + + public T LoadPluginSettings(string componentId, string? placementId) where T : new() + { + try + { + lock (CacheGate) + { + var document = LoadDocumentLocked(); + var instanceKey = BuildInstanceKey(componentId, placementId); + if (string.IsNullOrWhiteSpace(instanceKey) || + !document.PluginSettings.TryGetValue(instanceKey, out var settingsElement)) + { + return new T(); + } + + return JsonSerializer.Deserialize(settingsElement.GetRawText(), SerializerOptions) ?? new T(); + } + } + catch + { + return new T(); + } + } + + public void SavePluginSettings(string componentId, string? placementId, T settings) + { + var instanceKey = BuildInstanceKey(componentId, placementId); + if (string.IsNullOrWhiteSpace(instanceKey)) + { + return; + } + + try + { + lock (CacheGate) + { + var document = LoadDocumentLocked(); + document.PluginSettings[instanceKey] = JsonSerializer.SerializeToElement(settings, SerializerOptions).Clone(); + PersistDocumentLocked(document); + } + } + catch + { + // Swallow persistence errors to keep UI interactions uninterrupted. + } + } + + public void DeletePluginSettings(string componentId, string? placementId) + { + var instanceKey = BuildInstanceKey(componentId, placementId); + if (string.IsNullOrWhiteSpace(instanceKey)) + { + return; + } + + try + { + lock (CacheGate) + { + var document = LoadDocumentLocked(); + if (document.PluginSettings.Remove(instanceKey)) + { + PersistDocumentLocked(document); + } + } + } + catch + { + // Swallow persistence errors to keep UI interactions uninterrupted. + } + } + + public void SetScopedComponentContext(string componentId, string? placementId) + { + _scopedComponentId = componentId?.Trim() ?? string.Empty; + _scopedPlacementId = placementId?.Trim() ?? string.Empty; + } + + public void ClearScopedComponentContext() + { + _scopedComponentId = string.Empty; + _scopedPlacementId = string.Empty; + } + + public static void ApplyScopedContextToTarget(object? target, string componentId, string? placementId) + { + if (target is null) + { + return; + } + + var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; + foreach (var field in target.GetType().GetFields(flags)) + { + if (field.FieldType != typeof(ComponentSettingsService)) + { + continue; + } + + if (field.GetValue(target) is ComponentSettingsService settingsService) + { + settingsService.SetScopedComponentContext(componentId, placementId); + } + } + + foreach (var property in target.GetType().GetProperties(flags)) + { + if (property.PropertyType != typeof(ComponentSettingsService) || + !property.CanRead) + { + continue; + } + + if (property.GetValue(target) is ComponentSettingsService settingsService) + { + settingsService.SetScopedComponentContext(componentId, placementId); + } + } + } + + private bool TryGetCachedWithoutProbe(DateTime nowUtc, out ComponentSettingsDocumentSnapshot snapshot) { if (string.Equals(_cachedPath, _settingsPath, StringComparison.Ordinal) && _cachedSnapshot is not null && @@ -121,7 +290,7 @@ public sealed class ComponentSettingsService return false; } - private bool TryGetCachedAfterProbe(DateTime writeTimeUtc, out ComponentSettingsSnapshot snapshot) + private bool TryGetCachedAfterProbe(DateTime writeTimeUtc, out ComponentSettingsDocumentSnapshot snapshot) { if (string.Equals(_cachedPath, _settingsPath, StringComparison.Ordinal) && _cachedSnapshot is not null && @@ -135,17 +304,78 @@ public sealed class ComponentSettingsService return false; } - private ComponentSettingsSnapshot LoadSnapshotFromDisk() + private ComponentSettingsDocumentSnapshot LoadDocumentLocked() + { + var nowUtc = DateTime.UtcNow; + if (TryGetCachedWithoutProbe(nowUtc, out var cached)) + { + return cached; + } + + var hasFile = File.Exists(_settingsPath); + var writeTimeUtc = hasFile + ? File.GetLastWriteTimeUtc(_settingsPath) + : DateTime.MinValue; + + _lastProbeUtc = nowUtc; + if (TryGetCachedAfterProbe(writeTimeUtc, out cached)) + { + return cached; + } + + ComponentSettingsDocumentSnapshot loadedSnapshot; + var loadedFromLegacy = false; + if (hasFile) + { + loadedSnapshot = LoadSnapshotFromDisk(); + } + else if (TryLoadLegacySnapshot(out var migratedSnapshot)) + { + loadedSnapshot = new ComponentSettingsDocumentSnapshot + { + DefaultSettings = NormalizeSnapshot(migratedSnapshot) + }; + loadedFromLegacy = true; + } + else + { + loadedSnapshot = new ComponentSettingsDocumentSnapshot(); + } + + var normalizedSnapshot = NormalizeDocument(loadedSnapshot); + if (loadedFromLegacy) + { + writeTimeUtc = PersistSnapshotToDisk(normalizedSnapshot); + } + + UpdateCache(normalizedSnapshot, writeTimeUtc, nowUtc); + return normalizedSnapshot.Clone(); + } + + private ComponentSettingsDocumentSnapshot LoadSnapshotFromDisk() { try { var json = File.ReadAllText(_settingsPath); - var snapshot = JsonSerializer.Deserialize(json, SerializerOptions); - return NormalizeSnapshot(snapshot); + using var document = JsonDocument.Parse(json); + if (document.RootElement.ValueKind == JsonValueKind.Object && + (document.RootElement.TryGetProperty("defaultSettings", out _) || + document.RootElement.TryGetProperty("instanceSettings", out _) || + document.RootElement.TryGetProperty("pluginSettings", out _))) + { + var snapshot = JsonSerializer.Deserialize(json, SerializerOptions); + return NormalizeDocument(snapshot); + } + + var legacySnapshot = JsonSerializer.Deserialize(json, SerializerOptions); + return new ComponentSettingsDocumentSnapshot + { + DefaultSettings = NormalizeSnapshot(legacySnapshot) + }; } catch { - return new ComponentSettingsSnapshot(); + return new ComponentSettingsDocumentSnapshot(); } } @@ -204,7 +434,13 @@ public sealed class ComponentSettingsService } } - private DateTime PersistSnapshotToDisk(ComponentSettingsSnapshot snapshot) + private void PersistDocumentLocked(ComponentSettingsDocumentSnapshot snapshot) + { + var writeTimeUtc = PersistSnapshotToDisk(snapshot); + UpdateCache(snapshot, writeTimeUtc, DateTime.UtcNow); + } + + private DateTime PersistSnapshotToDisk(ComponentSettingsDocumentSnapshot snapshot) { var directory = Path.GetDirectoryName(_settingsPath); if (!string.IsNullOrWhiteSpace(directory)) @@ -257,6 +493,40 @@ public sealed class ComponentSettingsService return normalized; } + private static ComponentSettingsDocumentSnapshot NormalizeDocument(ComponentSettingsDocumentSnapshot? snapshot) + { + var normalized = snapshot?.Clone() ?? new ComponentSettingsDocumentSnapshot(); + normalized.DefaultSettings = NormalizeSnapshot(normalized.DefaultSettings); + + var instanceSettings = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var pair in normalized.InstanceSettings) + { + var key = NormalizeInstanceKey(pair.Key); + if (string.IsNullOrWhiteSpace(key)) + { + continue; + } + + instanceSettings[key] = NormalizeSnapshot(pair.Value); + } + + var pluginSettings = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var pair in normalized.PluginSettings) + { + var key = NormalizeInstanceKey(pair.Key); + if (string.IsNullOrWhiteSpace(key)) + { + continue; + } + + pluginSettings[key] = pair.Value.Clone(); + } + + normalized.InstanceSettings = instanceSettings; + normalized.PluginSettings = pluginSettings; + return normalized; + } + private static List NormalizeImportedSchedules( IReadOnlyList? schedules) { @@ -360,7 +630,30 @@ public sealed class ComponentSettingsService return RefreshIntervalCatalog.Normalize(minutes, 20); } - private void UpdateCache(ComponentSettingsSnapshot snapshot, DateTime writeTimeUtc, DateTime probeTimeUtc) + private static string BuildInstanceKey(string componentId, string? placementId) + { + var normalizedComponentId = componentId?.Trim() ?? string.Empty; + var normalizedPlacementId = placementId?.Trim() ?? string.Empty; + if (string.IsNullOrWhiteSpace(normalizedComponentId) || string.IsNullOrWhiteSpace(normalizedPlacementId)) + { + return string.Empty; + } + + return $"{normalizedComponentId}::{normalizedPlacementId}"; + } + + private static string NormalizeInstanceKey(string? key) + { + return key?.Trim() ?? string.Empty; + } + + private bool HasScopedComponentContext() + { + return !string.IsNullOrWhiteSpace(_scopedComponentId) && + !string.IsNullOrWhiteSpace(_scopedPlacementId); + } + + private void UpdateCache(ComponentSettingsDocumentSnapshot snapshot, DateTime writeTimeUtc, DateTime probeTimeUtc) { _cachedPath = _settingsPath; _cachedSnapshot = snapshot.Clone(); @@ -368,6 +661,39 @@ public sealed class ComponentSettingsService _lastProbeUtc = probeTimeUtc; } + private sealed class ComponentSettingsDocumentSnapshot + { + public ComponentSettingsSnapshot DefaultSettings { get; set; } = new(); + + public Dictionary InstanceSettings { get; set; } = + new(StringComparer.OrdinalIgnoreCase); + + public Dictionary PluginSettings { get; set; } = + new(StringComparer.OrdinalIgnoreCase); + + public ComponentSettingsDocumentSnapshot Clone() + { + var clone = new ComponentSettingsDocumentSnapshot + { + DefaultSettings = DefaultSettings?.Clone() ?? new ComponentSettingsSnapshot(), + InstanceSettings = new Dictionary(StringComparer.OrdinalIgnoreCase), + PluginSettings = new Dictionary(StringComparer.OrdinalIgnoreCase) + }; + + foreach (var pair in InstanceSettings) + { + clone.InstanceSettings[pair.Key] = pair.Value?.Clone() ?? new ComponentSettingsSnapshot(); + } + + foreach (var pair in PluginSettings) + { + clone.PluginSettings[pair.Key] = pair.Value.Clone(); + } + + return clone; + } + } + private sealed class LegacyComponentSettingsSnapshot { public string DailyArtworkMirrorSource { get; set; } = DailyArtworkMirrorSources.Overseas; diff --git a/LanMountainDesktop/Services/IComponentInstanceSettingsStore.cs b/LanMountainDesktop/Services/IComponentInstanceSettingsStore.cs new file mode 100644 index 0000000..a7ce205 --- /dev/null +++ b/LanMountainDesktop/Services/IComponentInstanceSettingsStore.cs @@ -0,0 +1,22 @@ +using LanMountainDesktop.Models; + +namespace LanMountainDesktop.Services; + +public interface IComponentInstanceSettingsStore +{ + ComponentSettingsSnapshot Load(); + + void Save(ComponentSettingsSnapshot snapshot); + + ComponentSettingsSnapshot LoadForComponent(string componentId, string? placementId); + + void SaveForComponent(string componentId, string? placementId, ComponentSettingsSnapshot snapshot); + + void DeleteForComponent(string componentId, string? placementId); + + T LoadPluginSettings(string componentId, string? placementId) where T : new(); + + void SavePluginSettings(string componentId, string? placementId, T settings); + + void DeletePluginSettings(string componentId, string? placementId); +} diff --git a/LanMountainDesktop/Views/Components/AnalogClockWidget.axaml.cs b/LanMountainDesktop/Views/Components/AnalogClockWidget.axaml.cs index 069a79d..3ed9940 100644 --- a/LanMountainDesktop/Views/Components/AnalogClockWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/AnalogClockWidget.axaml.cs @@ -7,11 +7,12 @@ using Avalonia.Controls.Shapes; using Avalonia.Media; using Avalonia.Styling; using Avalonia.Threading; +using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.Services; namespace LanMountainDesktop.Views.Components; -public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget +public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware { private static readonly IReadOnlyDictionary ZhCityNames = new Dictionary(StringComparer.OrdinalIgnoreCase) @@ -54,9 +55,11 @@ public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, I private const double DialSize = 258; private const double Center = DialSize / 2; + private string _componentId = BuiltInComponentIds.DesktopClock; + private string _placementId = string.Empty; private readonly AppSettingsService _appSettingsService = new(); - private readonly ComponentSettingsService _componentSettingsService = new(); + private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService(); private readonly LocalizationService _localizationService = new(); private TimeZoneService? _timeZoneService; private double _currentCellSize = 48; @@ -112,6 +115,21 @@ public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, I UpdateClock(); } + public void SetComponentPlacementContext(string componentId, string? placementId) + { + _componentId = string.IsNullOrWhiteSpace(componentId) + ? BuiltInComponentIds.DesktopClock + : componentId.Trim(); + _placementId = placementId?.Trim() ?? string.Empty; + RefreshFromSettings(); + } + + public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore) + { + _componentSettingsStore = settingsStore ?? new ComponentSettingsService(); + RefreshFromSettings(); + } + private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e) { InitializeDialIfNeeded(); @@ -359,7 +377,7 @@ public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, I private void LoadClockSettings() { var appSnapshot = _appSettingsService.Load(); - var componentSnapshot = _componentSettingsService.Load(); + var componentSnapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); _languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode); var configuredTimeZoneId = string.IsNullOrWhiteSpace(componentSnapshot.DesktopClockTimeZoneId) diff --git a/LanMountainDesktop/Views/Components/AnalogClockWidgetSettingsWindow.axaml.cs b/LanMountainDesktop/Views/Components/AnalogClockWidgetSettingsWindow.axaml.cs index 44b3f95..d9e73ac 100644 --- a/LanMountainDesktop/Views/Components/AnalogClockWidgetSettingsWindow.axaml.cs +++ b/LanMountainDesktop/Views/Components/AnalogClockWidgetSettingsWindow.axaml.cs @@ -4,11 +4,12 @@ using System.Linq; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; +using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.Services; namespace LanMountainDesktop.Views.Components; -public partial class AnalogClockWidgetSettingsWindow : UserControl +public partial class AnalogClockWidgetSettingsWindow : UserControl, IComponentPlacementContextAware, IComponentSettingsStoreAware { private static readonly IReadOnlyDictionary ZhTimeZoneNames = new Dictionary(StringComparer.OrdinalIgnoreCase) @@ -28,11 +29,13 @@ public partial class AnalogClockWidgetSettingsWindow : UserControl }; private readonly AppSettingsService _appSettingsService = new(); - private readonly ComponentSettingsService _componentSettingsService = new(); + private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService(); private readonly LocalizationService _localizationService = new(); private readonly TimeZoneService _timeZoneService = new(); private bool _suppressEvents; private string _languageCode = "zh-CN"; + private string _componentId = BuiltInComponentIds.DesktopClock; + private string _placementId = string.Empty; private IReadOnlyList _allTimeZones = Array.Empty(); private string _selectedTimeZoneId = string.Empty; private string _secondHandMode = ClockSecondHandMode.Tick; @@ -47,10 +50,29 @@ public partial class AnalogClockWidgetSettingsWindow : UserControl PopulateTimeZoneComboBox(); } + public void SetComponentPlacementContext(string componentId, string? placementId) + { + _componentId = string.IsNullOrWhiteSpace(componentId) + ? BuiltInComponentIds.DesktopClock + : componentId.Trim(); + _placementId = placementId?.Trim() ?? string.Empty; + LoadState(); + ApplyLocalization(); + PopulateTimeZoneComboBox(); + } + + public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore) + { + _componentSettingsStore = settingsStore ?? new ComponentSettingsService(); + LoadState(); + ApplyLocalization(); + PopulateTimeZoneComboBox(); + } + private void LoadState() { var appSnapshot = _appSettingsService.Load(); - var componentSnapshot = _componentSettingsService.Load(); + var componentSnapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); _languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode); _selectedTimeZoneId = string.IsNullOrWhiteSpace(componentSnapshot.DesktopClockTimeZoneId) ? "China Standard Time" @@ -149,10 +171,10 @@ public partial class AnalogClockWidgetSettingsWindow : UserControl _selectedTimeZoneId = normalizedId; _secondHandMode = GetSelectedSecondHandMode(); - var snapshot = _componentSettingsService.Load(); + var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); snapshot.DesktopClockTimeZoneId = normalizedId; snapshot.DesktopClockSecondHandMode = _secondHandMode; - _componentSettingsService.Save(snapshot); + _componentSettingsStore.SaveForComponent(_componentId, _placementId, snapshot); SettingsChanged?.Invoke(this, EventArgs.Empty); } diff --git a/LanMountainDesktop/Views/Components/ClassScheduleSettingsWindow.axaml.cs b/LanMountainDesktop/Views/Components/ClassScheduleSettingsWindow.axaml.cs index d72839a..1b04221 100644 --- a/LanMountainDesktop/Views/Components/ClassScheduleSettingsWindow.axaml.cs +++ b/LanMountainDesktop/Views/Components/ClassScheduleSettingsWindow.axaml.cs @@ -10,19 +10,22 @@ using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform.Storage; +using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.Models; using LanMountainDesktop.Services; namespace LanMountainDesktop.Views.Components; -public partial class ClassScheduleSettingsWindow : UserControl +public partial class ClassScheduleSettingsWindow : UserControl, IComponentPlacementContextAware, IComponentSettingsStoreAware { private readonly AppSettingsService _appSettingsService = new(); - private readonly ComponentSettingsService _componentSettingsService = new(); + private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService(); private readonly LocalizationService _localizationService = new(); private readonly List _importedSchedules = []; private string _activeScheduleId = string.Empty; private string _languageCode = "zh-CN"; + private string _componentId = BuiltInComponentIds.DesktopClassSchedule; + private string _placementId = string.Empty; public event EventHandler? SettingsChanged; @@ -34,10 +37,29 @@ public partial class ClassScheduleSettingsWindow : UserControl RenderImportedSchedules(); } + public void SetComponentPlacementContext(string componentId, string? placementId) + { + _componentId = string.IsNullOrWhiteSpace(componentId) + ? BuiltInComponentIds.DesktopClassSchedule + : componentId.Trim(); + _placementId = placementId?.Trim() ?? string.Empty; + LoadState(); + ApplyLocalization(); + RenderImportedSchedules(); + } + + public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore) + { + _componentSettingsStore = settingsStore ?? new ComponentSettingsService(); + LoadState(); + ApplyLocalization(); + RenderImportedSchedules(); + } + private void LoadState() { var appSnapshot = _appSettingsService.Load(); - var componentSnapshot = _componentSettingsService.Load(); + var componentSnapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); _languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode); _importedSchedules.Clear(); @@ -299,7 +321,7 @@ public partial class ClassScheduleSettingsWindow : UserControl private void SaveState() { - var snapshot = _componentSettingsService.Load(); + var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); snapshot.ImportedClassSchedules = _importedSchedules .Select(item => new ImportedClassScheduleSnapshot { @@ -309,7 +331,7 @@ public partial class ClassScheduleSettingsWindow : UserControl }) .ToList(); snapshot.ActiveImportedClassScheduleId = _activeScheduleId ?? string.Empty; - _componentSettingsService.Save(snapshot); + _componentSettingsStore.SaveForComponent(_componentId, _placementId, snapshot); SettingsChanged?.Invoke(this, EventArgs.Empty); } diff --git a/LanMountainDesktop/Views/Components/ClassScheduleWidget.axaml.cs b/LanMountainDesktop/Views/Components/ClassScheduleWidget.axaml.cs index f319a45..fef59bb 100644 --- a/LanMountainDesktop/Views/Components/ClassScheduleWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/ClassScheduleWidget.axaml.cs @@ -7,12 +7,13 @@ using Avalonia.Controls; using Avalonia.Media; using Avalonia.Styling; using Avalonia.Threading; +using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.Models; using LanMountainDesktop.Services; namespace LanMountainDesktop.Views.Components; -public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget +public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware { private sealed record CourseItemViewModel( string Name, @@ -26,7 +27,7 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget, }; private readonly AppSettingsService _appSettingsService = new(); - private readonly ComponentSettingsService _componentSettingsService = new(); + private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService(); private readonly LocalizationService _localizationService = new(); private readonly IClassIslandScheduleDataService _scheduleService = new ClassIslandScheduleDataService(); @@ -35,6 +36,8 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget, private IReadOnlyList _courseItems = Array.Empty(); private bool _isNightVisual = true; private string _languageCode = "zh-CN"; + private string _componentId = BuiltInComponentIds.DesktopClassSchedule; + private string _placementId = string.Empty; public ClassScheduleWidget() { @@ -113,10 +116,25 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget, RefreshSchedule(); } + public void SetComponentPlacementContext(string componentId, string? placementId) + { + _componentId = string.IsNullOrWhiteSpace(componentId) + ? BuiltInComponentIds.DesktopClassSchedule + : componentId.Trim(); + _placementId = placementId?.Trim() ?? string.Empty; + RefreshSchedule(); + } + + public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore) + { + _componentSettingsStore = settingsStore ?? new ComponentSettingsService(); + RefreshSchedule(); + } + private void RefreshSchedule() { var appSettings = _appSettingsService.Load(); - var componentSettings = _componentSettingsService.Load(); + var componentSettings = _componentSettingsStore.LoadForComponent(_componentId, _placementId); _languageCode = _localizationService.NormalizeLanguageCode(appSettings.LanguageCode); var now = _timeZoneService?.GetCurrentTime() ?? DateTime.Now; UpdateHeader(now); diff --git a/LanMountainDesktop/Views/Components/DailyArtworkSettingsWindow.axaml.cs b/LanMountainDesktop/Views/Components/DailyArtworkSettingsWindow.axaml.cs index ce9f14a..a07c9c7 100644 --- a/LanMountainDesktop/Views/Components/DailyArtworkSettingsWindow.axaml.cs +++ b/LanMountainDesktop/Views/Components/DailyArtworkSettingsWindow.axaml.cs @@ -2,18 +2,21 @@ using System; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; +using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.Models; using LanMountainDesktop.Services; namespace LanMountainDesktop.Views.Components; -public partial class DailyArtworkSettingsWindow : UserControl +public partial class DailyArtworkSettingsWindow : UserControl, IComponentPlacementContextAware, IComponentSettingsStoreAware { private readonly AppSettingsService _appSettingsService = new(); - private readonly ComponentSettingsService _componentSettingsService = new(); + private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService(); private readonly LocalizationService _localizationService = new(); private string _languageCode = "zh-CN"; private bool _suppressEvents; + private string _componentId = BuiltInComponentIds.DesktopDailyArtwork; + private string _placementId = string.Empty; public event EventHandler? SettingsChanged; @@ -24,10 +27,27 @@ public partial class DailyArtworkSettingsWindow : UserControl ApplyLocalization(); } + public void SetComponentPlacementContext(string componentId, string? placementId) + { + _componentId = string.IsNullOrWhiteSpace(componentId) + ? BuiltInComponentIds.DesktopDailyArtwork + : componentId.Trim(); + _placementId = placementId?.Trim() ?? string.Empty; + LoadState(); + ApplyLocalization(); + } + + public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore) + { + _componentSettingsStore = settingsStore ?? new ComponentSettingsService(); + LoadState(); + ApplyLocalization(); + } + private void LoadState() { var appSnapshot = _appSettingsService.Load(); - var componentSnapshot = _componentSettingsService.Load(); + var componentSnapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); _languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode); var source = DailyArtworkMirrorSources.Normalize(componentSnapshot.DailyArtworkMirrorSource); @@ -59,9 +79,9 @@ public partial class DailyArtworkSettingsWindow : UserControl } var source = GetSelectedSource(); - var snapshot = _componentSettingsService.Load(); + var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); snapshot.DailyArtworkMirrorSource = source; - _componentSettingsService.Save(snapshot); + _componentSettingsStore.SaveForComponent(_componentId, _placementId, snapshot); UpdateSourceStatus(source); SettingsChanged?.Invoke(this, EventArgs.Empty); diff --git a/LanMountainDesktop/Views/Components/DailyArtworkWidget.axaml.cs b/LanMountainDesktop/Views/Components/DailyArtworkWidget.axaml.cs index 1e603f7..99eeebf 100644 --- a/LanMountainDesktop/Views/Components/DailyArtworkWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/DailyArtworkWidget.axaml.cs @@ -13,12 +13,13 @@ using Avalonia.Input; using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.Threading; +using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.Models; using LanMountainDesktop.Services; namespace LanMountainDesktop.Views.Components; -public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget, IRecommendationInfoAwareComponentWidget +public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget, IRecommendationInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware { private static readonly IReadOnlyDictionary ZhWeekdays = new Dictionary @@ -58,6 +59,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget, }; private readonly AppSettingsService _settingsService = new(); + private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService(); private readonly LocalizationService _localizationService = new(); private IRecommendationInfoService _recommendationService = DefaultRecommendationService; @@ -67,6 +69,8 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget, private double _currentCellSize = BaseCellSize; private bool _isAttached; private bool _isRefreshing; + private string _componentId = BuiltInComponentIds.DesktopDailyArtwork; + private string _placementId = string.Empty; private string? _currentArtworkSourceUrl; private string? _currentArtworkImageUrl; @@ -136,6 +140,21 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget, } } + public void SetComponentPlacementContext(string componentId, string? placementId) + { + _componentId = string.IsNullOrWhiteSpace(componentId) + ? BuiltInComponentIds.DesktopDailyArtwork + : componentId.Trim(); + _placementId = placementId?.Trim() ?? string.Empty; + RefreshFromSettings(); + } + + public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore) + { + _componentSettingsStore = settingsStore ?? new ComponentSettingsService(); + RefreshFromSettings(); + } + private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e) { _isAttached = true; @@ -203,6 +222,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget, { var query = new DailyArtworkQuery( Locale: _languageCode, + MirrorSource: ResolveMirrorSource(), ForceRefresh: forceRefresh); var result = await _recommendationService.GetDailyArtworkAsync(query, cts.Token); if (!_isAttached || cts.IsCancellationRequested) @@ -640,6 +660,19 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget, } } + private string ResolveMirrorSource() + { + try + { + var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); + return DailyArtworkMirrorSources.Normalize(snapshot.DailyArtworkMirrorSource); + } + catch + { + return DailyArtworkMirrorSources.Overseas; + } + } + private void UpdateDateLabels() { var now = DateTime.Now; diff --git a/LanMountainDesktop/Views/Components/DesktopComponentRuntimeRegistry.cs b/LanMountainDesktop/Views/Components/DesktopComponentRuntimeRegistry.cs index 48fbbd0..9b16d08 100644 --- a/LanMountainDesktop/Views/Components/DesktopComponentRuntimeRegistry.cs +++ b/LanMountainDesktop/Views/Components/DesktopComponentRuntimeRegistry.cs @@ -42,9 +42,33 @@ public sealed class DesktopComponentRuntimeDescriptor TimeZoneService timeZoneService, IWeatherInfoService weatherInfoService, IRecommendationInfoService recommendationInfoService, - ICalculatorDataService calculatorDataService) + ICalculatorDataService calculatorDataService, + IComponentInstanceSettingsStore componentSettingsStore, + string? placementId = null) { var control = _controlFactory(); + var runtimeContext = new DesktopComponentRuntimeContext( + Definition.Id, + placementId, + componentSettingsStore); + + if (control is IComponentRuntimeContextAware runtimeContextAwareComponent) + { + runtimeContextAwareComponent.SetComponentRuntimeContext(runtimeContext); + } + + if (control is IComponentPlacementContextAware placementAwareComponent) + { + placementAwareComponent.SetComponentPlacementContext(Definition.Id, placementId); + } + + if (control is IComponentSettingsStoreAware settingsStoreAwareComponent) + { + settingsStoreAwareComponent.SetComponentSettingsStore(componentSettingsStore); + } + + ComponentSettingsService.ApplyScopedContextToTarget(control, Definition.Id, placementId); + if (control is IDesktopComponentWidget sizedComponent) { sizedComponent.ApplyCellSize(cellSize); diff --git a/LanMountainDesktop/Views/Components/ExtendedWeatherWidget.axaml.cs b/LanMountainDesktop/Views/Components/ExtendedWeatherWidget.axaml.cs index bfb6cbc..22e0fb2 100644 --- a/LanMountainDesktop/Views/Components/ExtendedWeatherWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/ExtendedWeatherWidget.axaml.cs @@ -9,13 +9,14 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Threading; +using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.Models; using LanMountainDesktop.Services; using LanMountainDesktop.Theme; namespace LanMountainDesktop.Views.Components; -public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget +public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware { private static readonly IWeatherInfoService DefaultWeatherInfoService = new XiaomiWeatherService(); private static readonly IReadOnlyList SupportedAutoRefreshIntervalsMinutes = RefreshIntervalCatalog.SupportedIntervalsMinutes; @@ -25,7 +26,7 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge private readonly ScaleTransform _backgroundMotionScaleTransform = new(1, 1); private readonly TranslateTransform _backgroundMotionTranslateTransform = new(); private readonly AppSettingsService _settingsService = new(); - private readonly ComponentSettingsService _componentSettingsService = new(); + private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService(); private readonly LocalizationService _localizationService = new(); private IWeatherInfoService _weatherInfoService = DefaultWeatherInfoService; @@ -39,6 +40,8 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge private bool _autoRefreshEnabled = true; private string _languageCode = "zh-CN"; private HyperOS3WeatherVisualKind _activeVisualKind = HyperOS3WeatherVisualKind.ClearDay; + private string _componentId = BuiltInComponentIds.DesktopExtendedWeather; + private string _placementId = string.Empty; private readonly TextBlock[] _hourlyTempBlocks; private readonly TextBlock[] _hourlyTimeBlocks; private readonly Image[] _hourlyIconBlocks; @@ -173,6 +176,21 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge } } + public void SetComponentPlacementContext(string componentId, string? placementId) + { + _componentId = string.IsNullOrWhiteSpace(componentId) + ? BuiltInComponentIds.DesktopExtendedWeather + : componentId.Trim(); + _placementId = placementId?.Trim() ?? string.Empty; + RefreshFromSettings(); + } + + public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore) + { + _componentSettingsStore = settingsStore ?? new ComponentSettingsService(); + RefreshFromSettings(); + } + public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode) { _ = isEditMode; @@ -935,7 +953,7 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge try { - var snapshot = _componentSettingsService.Load(); + var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); enabled = snapshot.WeatherAutoRefreshEnabled; intervalMinutes = NormalizeAutoRefreshIntervalMinutes(snapshot.WeatherAutoRefreshIntervalMinutes); } diff --git a/LanMountainDesktop/Views/Components/HourlyWeatherWidget.axaml.cs b/LanMountainDesktop/Views/Components/HourlyWeatherWidget.axaml.cs index 0b1ecb3..c2bb96f 100644 --- a/LanMountainDesktop/Views/Components/HourlyWeatherWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/HourlyWeatherWidget.axaml.cs @@ -11,13 +11,14 @@ using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Threading; +using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.Models; using LanMountainDesktop.Services; using LanMountainDesktop.Theme; namespace LanMountainDesktop.Views.Components; -public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget +public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware { private enum WeatherVisualKind { @@ -95,7 +96,7 @@ public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget, }; private readonly AppSettingsService _settingsService = new(); - private readonly ComponentSettingsService _componentSettingsService = new(); + private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService(); private readonly LocalizationService _localizationService = new(); private readonly Dictionary _backgroundBrushCache = new(); private readonly Dictionary _particleBrushCache = new(); @@ -118,6 +119,8 @@ public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget, private bool _isOnActivePage = true; private bool _isRefreshing; private bool _autoRefreshEnabled = true; + private string _componentId = BuiltInComponentIds.DesktopHourlyWeather; + private string _placementId = string.Empty; private readonly TextBlock[] _hourlyTimeBlocks; private readonly Image[] _hourlyIconBlocks; private readonly TextBlock[] _hourlyTempBlocks; @@ -224,6 +227,21 @@ public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget, } } + public void SetComponentPlacementContext(string componentId, string? placementId) + { + _componentId = string.IsNullOrWhiteSpace(componentId) + ? BuiltInComponentIds.DesktopHourlyWeather + : componentId.Trim(); + _placementId = placementId?.Trim() ?? string.Empty; + RefreshFromSettings(); + } + + public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore) + { + _componentSettingsStore = settingsStore ?? new ComponentSettingsService(); + RefreshFromSettings(); + } + public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode) { _ = isEditMode; @@ -1424,7 +1442,7 @@ public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget, try { - var snapshot = _componentSettingsService.Load(); + var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); enabled = snapshot.WeatherAutoRefreshEnabled; intervalMinutes = NormalizeAutoRefreshIntervalMinutes(snapshot.WeatherAutoRefreshIntervalMinutes); } diff --git a/LanMountainDesktop/Views/Components/MultiDayWeatherWidget.axaml.cs b/LanMountainDesktop/Views/Components/MultiDayWeatherWidget.axaml.cs index 171417e..53ee724 100644 --- a/LanMountainDesktop/Views/Components/MultiDayWeatherWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/MultiDayWeatherWidget.axaml.cs @@ -9,13 +9,14 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Threading; +using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.Models; using LanMountainDesktop.Services; using LanMountainDesktop.Theme; namespace LanMountainDesktop.Views.Components; -public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget +public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware { private enum WeatherVisualKind { @@ -93,7 +94,7 @@ public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidge }; private readonly AppSettingsService _settingsService = new(); - private readonly ComponentSettingsService _componentSettingsService = new(); + private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService(); private readonly LocalizationService _localizationService = new(); private readonly Dictionary _backgroundBrushCache = new(); private readonly Dictionary _particleBrushCache = new(); @@ -116,6 +117,8 @@ public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidge private bool _isOnActivePage = true; private bool _isRefreshing; private bool _autoRefreshEnabled = true; + private string _componentId = BuiltInComponentIds.DesktopMultiDayWeather; + private string _placementId = string.Empty; private readonly TextBlock[] _hourlyTimeBlocks; private readonly Image[] _hourlyIconBlocks; private readonly TextBlock[] _hourlyTempBlocks; @@ -222,6 +225,21 @@ public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidge } } + public void SetComponentPlacementContext(string componentId, string? placementId) + { + _componentId = string.IsNullOrWhiteSpace(componentId) + ? BuiltInComponentIds.DesktopMultiDayWeather + : componentId.Trim(); + _placementId = placementId?.Trim() ?? string.Empty; + RefreshFromSettings(); + } + + public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore) + { + _componentSettingsStore = settingsStore ?? new ComponentSettingsService(); + RefreshFromSettings(); + } + public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode) { _ = isEditMode; @@ -1274,7 +1292,7 @@ public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidge try { - var snapshot = _componentSettingsService.Load(); + var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); enabled = snapshot.WeatherAutoRefreshEnabled; intervalMinutes = NormalizeAutoRefreshIntervalMinutes(snapshot.WeatherAutoRefreshIntervalMinutes); } diff --git a/LanMountainDesktop/Views/Components/WeatherClockWidget.axaml.cs b/LanMountainDesktop/Views/Components/WeatherClockWidget.axaml.cs index 14fef34..bed0d1a 100644 --- a/LanMountainDesktop/Views/Components/WeatherClockWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/WeatherClockWidget.axaml.cs @@ -10,12 +10,13 @@ using Avalonia.Controls.Shapes; using Avalonia.Media; using Avalonia.Styling; using Avalonia.Threading; +using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.Models; using LanMountainDesktop.Services; namespace LanMountainDesktop.Views.Components; -public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget +public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware { private sealed record WeatherClockConfig( string LanguageCode, @@ -41,7 +42,7 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget, }; private readonly AppSettingsService _settingsService = new(); - private readonly ComponentSettingsService _componentSettingsService = new(); + private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService(); private readonly LocalizationService _localizationService = new(); private readonly Line _hourHandLine = CreateHandLine("#232938", 4.0); private readonly Line _minuteHandLine = CreateHandLine("#2F3749", 2.8); @@ -59,6 +60,8 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget, private bool? _isNightModeApplied; private string _languageCode = "zh-CN"; private HyperOS3WeatherVisualKind _activeVisualKind = HyperOS3WeatherVisualKind.CloudyDay; + private string _componentId = BuiltInComponentIds.DesktopWeatherClock; + private string _placementId = string.Empty; public WeatherClockWidget() { @@ -116,6 +119,21 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget, } } + public void SetComponentPlacementContext(string componentId, string? placementId) + { + _componentId = string.IsNullOrWhiteSpace(componentId) + ? BuiltInComponentIds.DesktopWeatherClock + : componentId.Trim(); + _placementId = placementId?.Trim() ?? string.Empty; + RefreshFromSettings(); + } + + public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore) + { + _componentSettingsStore = settingsStore ?? new ComponentSettingsService(); + RefreshFromSettings(); + } + public void ApplyCellSize(double cellSize) { _currentCellSize = Math.Max(1, cellSize); @@ -659,7 +677,7 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget, try { - var snapshot = _componentSettingsService.Load(); + var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); enabled = snapshot.WeatherAutoRefreshEnabled; intervalMinutes = NormalizeAutoRefreshIntervalMinutes(snapshot.WeatherAutoRefreshIntervalMinutes); } diff --git a/LanMountainDesktop/Views/Components/WeatherWidget.axaml.cs b/LanMountainDesktop/Views/Components/WeatherWidget.axaml.cs index 3aeb597..754a3a9 100644 --- a/LanMountainDesktop/Views/Components/WeatherWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/WeatherWidget.axaml.cs @@ -11,13 +11,14 @@ using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Threading; +using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.Models; using LanMountainDesktop.Services; using LanMountainDesktop.Theme; namespace LanMountainDesktop.Views.Components; -public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget +public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware { private enum WeatherVisualKind { @@ -89,7 +90,7 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk }; private readonly AppSettingsService _settingsService = new(); - private readonly ComponentSettingsService _componentSettingsService = new(); + private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService(); private readonly LocalizationService _localizationService = new(); private readonly Dictionary _backgroundBrushCache = new(); private readonly Dictionary _particleBrushCache = new(); @@ -112,6 +113,8 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk private bool _isOnActivePage = true; private bool _isRefreshing; private bool _autoRefreshEnabled = true; + private string _componentId = BuiltInComponentIds.DesktopWeather; + private string _placementId = string.Empty; public WeatherWidget() { @@ -167,6 +170,21 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk } } + public void SetComponentPlacementContext(string componentId, string? placementId) + { + _componentId = string.IsNullOrWhiteSpace(componentId) + ? BuiltInComponentIds.DesktopWeather + : componentId.Trim(); + _placementId = placementId?.Trim() ?? string.Empty; + RefreshFromSettings(); + } + + public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore) + { + _componentSettingsStore = settingsStore ?? new ComponentSettingsService(); + RefreshFromSettings(); + } + public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode) { _ = isEditMode; @@ -1063,7 +1081,7 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk try { - var snapshot = _componentSettingsService.Load(); + var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); enabled = snapshot.WeatherAutoRefreshEnabled; intervalMinutes = NormalizeAutoRefreshIntervalMinutes(snapshot.WeatherAutoRefreshIntervalMinutes); } diff --git a/LanMountainDesktop/Views/Components/WeatherWidgetSettingsWindow.axaml.cs b/LanMountainDesktop/Views/Components/WeatherWidgetSettingsWindow.axaml.cs index e6fbfe4..83c71cf 100644 --- a/LanMountainDesktop/Views/Components/WeatherWidgetSettingsWindow.axaml.cs +++ b/LanMountainDesktop/Views/Components/WeatherWidgetSettingsWindow.axaml.cs @@ -4,20 +4,23 @@ using System.Linq; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; +using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.Models; using LanMountainDesktop.Services; namespace LanMountainDesktop.Views.Components; -public partial class WeatherWidgetSettingsWindow : UserControl +public partial class WeatherWidgetSettingsWindow : UserControl, IComponentPlacementContextAware, IComponentSettingsStoreAware { private static readonly IReadOnlyList SupportedIntervals = RefreshIntervalCatalog.SupportedIntervalsMinutes; private readonly AppSettingsService _appSettingsService = new(); - private readonly ComponentSettingsService _componentSettingsService = new(); + private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService(); private readonly LocalizationService _localizationService = new(); private bool _suppressEvents; private string _languageCode = "zh-CN"; + private string _componentId = BuiltInComponentIds.DesktopWeather; + private string _placementId = string.Empty; public event EventHandler? SettingsChanged; @@ -29,10 +32,27 @@ public partial class WeatherWidgetSettingsWindow : UserControl ApplyLocalization(); } + public void SetComponentPlacementContext(string componentId, string? placementId) + { + _componentId = string.IsNullOrWhiteSpace(componentId) + ? BuiltInComponentIds.DesktopWeather + : componentId.Trim(); + _placementId = placementId?.Trim() ?? string.Empty; + LoadState(); + ApplyLocalization(); + } + + public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore) + { + _componentSettingsStore = settingsStore ?? new ComponentSettingsService(); + LoadState(); + ApplyLocalization(); + } + private void LoadState() { var appSnapshot = _appSettingsService.Load(); - var componentSnapshot = _componentSettingsService.Load(); + var componentSnapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); _languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode); var enabled = componentSnapshot.WeatherAutoRefreshEnabled; @@ -83,10 +103,10 @@ public partial class WeatherWidgetSettingsWindow : UserControl private void SaveState() { - var snapshot = _componentSettingsService.Load(); + var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); snapshot.WeatherAutoRefreshEnabled = AutoRefreshCheckBox.IsChecked == true; snapshot.WeatherAutoRefreshIntervalMinutes = GetSelectedInterval(); - _componentSettingsService.Save(snapshot); + _componentSettingsStore.SaveForComponent(_componentId, _placementId, snapshot); SettingsChanged?.Invoke(this, EventArgs.Empty); } diff --git a/LanMountainDesktop/Views/Components/WorldClockWidget.axaml.cs b/LanMountainDesktop/Views/Components/WorldClockWidget.axaml.cs index e36d56a..a3e5971 100644 --- a/LanMountainDesktop/Views/Components/WorldClockWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/WorldClockWidget.axaml.cs @@ -8,11 +8,12 @@ using Avalonia.Layout; using Avalonia.Media; using Avalonia.Styling; using Avalonia.Threading; +using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.Services; namespace LanMountainDesktop.Views.Components; -public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget +public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware { private const int BaseWidthCells = 4; private const int BaseHeightCells = 2; @@ -92,7 +93,7 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT }; private readonly AppSettingsService _appSettingsService = new(); - private readonly ComponentSettingsService _componentSettingsService = new(); + private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService(); private readonly LocalizationService _localizationService = new(); private readonly ClockEntryVisual[] _entryVisuals = new ClockEntryVisual[WorldClockTimeZoneCatalog.ClockCount]; private readonly TimeZoneInfo[] _entryTimeZones = new TimeZoneInfo[WorldClockTimeZoneCatalog.ClockCount]; @@ -103,6 +104,8 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT private DateTime _nextLanguageProbeUtc = DateTime.MinValue; private string _secondHandMode = ClockSecondHandMode.Tick; private bool _isNightVisual = true; + private string _componentId = BuiltInComponentIds.DesktopWorldClock; + private string _placementId = string.Empty; public WorldClockWidget() { @@ -147,6 +150,21 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT UpdateClockVisuals(); } + public void SetComponentPlacementContext(string componentId, string? placementId) + { + _componentId = string.IsNullOrWhiteSpace(componentId) + ? BuiltInComponentIds.DesktopWorldClock + : componentId.Trim(); + _placementId = placementId?.Trim() ?? string.Empty; + RefreshFromSettings(); + } + + public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore) + { + _componentSettingsStore = settingsStore ?? new ComponentSettingsService(); + RefreshFromSettings(); + } + public void ApplyCellSize(double cellSize) { _currentCellSize = Math.Max(1, cellSize); @@ -525,7 +543,7 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT private void LoadFromSettings() { var appSnapshot = _appSettingsService.Load(); - var componentSnapshot = _componentSettingsService.Load(); + var componentSnapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); _languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode); var ids = WorldClockTimeZoneCatalog.NormalizeTimeZoneIds(componentSnapshot.WorldClockTimeZoneIds); diff --git a/LanMountainDesktop/Views/Components/WorldClockWidgetSettingsWindow.axaml.cs b/LanMountainDesktop/Views/Components/WorldClockWidgetSettingsWindow.axaml.cs index 67e1a4e..17e360e 100644 --- a/LanMountainDesktop/Views/Components/WorldClockWidgetSettingsWindow.axaml.cs +++ b/LanMountainDesktop/Views/Components/WorldClockWidgetSettingsWindow.axaml.cs @@ -4,11 +4,12 @@ using System.Linq; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; +using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.Services; namespace LanMountainDesktop.Views.Components; -public partial class WorldClockWidgetSettingsWindow : UserControl +public partial class WorldClockWidgetSettingsWindow : UserControl, IComponentPlacementContextAware, IComponentSettingsStoreAware { private static readonly IReadOnlyDictionary ZhTimeZoneNames = new Dictionary(StringComparer.OrdinalIgnoreCase) @@ -28,12 +29,14 @@ public partial class WorldClockWidgetSettingsWindow : UserControl }; private readonly AppSettingsService _appSettingsService = new(); - private readonly ComponentSettingsService _componentSettingsService = new(); + private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService(); private readonly LocalizationService _localizationService = new(); private readonly TimeZoneService _timeZoneService = new(); private readonly ComboBox[] _timeZoneComboBoxes; private bool _suppressEvents; private string _languageCode = "zh-CN"; + private string _componentId = BuiltInComponentIds.DesktopWorldClock; + private string _placementId = string.Empty; private IReadOnlyList _allTimeZones = Array.Empty(); private IReadOnlyList _selectedTimeZoneIds = Array.Empty(); private string _secondHandMode = ClockSecondHandMode.Tick; @@ -57,10 +60,29 @@ public partial class WorldClockWidgetSettingsWindow : UserControl PopulateTimeZoneComboBoxes(); } + public void SetComponentPlacementContext(string componentId, string? placementId) + { + _componentId = string.IsNullOrWhiteSpace(componentId) + ? BuiltInComponentIds.DesktopWorldClock + : componentId.Trim(); + _placementId = placementId?.Trim() ?? string.Empty; + LoadState(); + ApplyLocalization(); + PopulateTimeZoneComboBoxes(); + } + + public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore) + { + _componentSettingsStore = settingsStore ?? new ComponentSettingsService(); + LoadState(); + ApplyLocalization(); + PopulateTimeZoneComboBoxes(); + } + private void LoadState() { var appSnapshot = _appSettingsService.Load(); - var componentSnapshot = _componentSettingsService.Load(); + var componentSnapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); _languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode); _allTimeZones = _timeZoneService @@ -167,10 +189,10 @@ public partial class WorldClockWidgetSettingsWindow : UserControl var normalizedIds = WorldClockTimeZoneCatalog.NormalizeTimeZoneIds(selectedIds, _allTimeZones); _secondHandMode = GetSelectedSecondHandMode(); - var snapshot = _componentSettingsService.Load(); + var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId); snapshot.WorldClockTimeZoneIds = normalizedIds.ToList(); snapshot.WorldClockSecondHandMode = _secondHandMode; - _componentSettingsService.Save(snapshot); + _componentSettingsStore.SaveForComponent(_componentId, _placementId, snapshot); _selectedTimeZoneIds = normalizedIds; SettingsChanged?.Invoke(this, EventArgs.Empty); diff --git a/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs b/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs index 0c85f51..d54b4bc 100644 --- a/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs +++ b/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs @@ -14,6 +14,7 @@ using FluentIcons.Avalonia; using FluentIcons.Common; using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.Models; +using LanMountainDesktop.Services; using LanMountainDesktop.Theme; using LanMountainDesktop.Views.Components; @@ -689,6 +690,7 @@ public partial class MainWindow } _desktopComponentPlacements.Remove(placement); + _componentSettingsService.DeleteForComponent(placement.ComponentId, placement.PlacementId); ClearDesktopComponentSelection(); @@ -724,80 +726,80 @@ public partial class MainWindow if (placement.ComponentId == BuiltInComponentIds.Date) { - OpenDateComponentSettings(); + OpenDateComponentSettings(placement); return; } if (placement.ComponentId == BuiltInComponentIds.DesktopClock) { - OpenDesktopClockComponentSettings(); + OpenDesktopClockComponentSettings(placement); return; } if (placement.ComponentId == BuiltInComponentIds.DesktopClassSchedule) { - OpenClassScheduleComponentSettings(); + OpenClassScheduleComponentSettings(placement); return; } if (placement.ComponentId == BuiltInComponentIds.DesktopWorldClock) { - OpenWorldClockComponentSettings(); + OpenWorldClockComponentSettings(placement); return; } if (IsWeatherComponentId(placement.ComponentId)) { - OpenWeatherComponentSettings(); + OpenWeatherComponentSettings(placement); return; } if (placement.ComponentId == BuiltInComponentIds.DesktopDailyArtwork) { - OpenDailyArtworkComponentSettings(); + OpenDailyArtworkComponentSettings(placement); return; } if (placement.ComponentId == BuiltInComponentIds.DesktopCnrDailyNews) { - OpenCnrDailyNewsComponentSettings(); + OpenCnrDailyNewsComponentSettings(placement); return; } if (placement.ComponentId == BuiltInComponentIds.DesktopIfengNews) { - OpenIfengNewsComponentSettings(); + OpenIfengNewsComponentSettings(placement); return; } if (placement.ComponentId == BuiltInComponentIds.DesktopDailyWord || placement.ComponentId == BuiltInComponentIds.DesktopDailyWord2x2) { - OpenDailyWordComponentSettings(); + OpenDailyWordComponentSettings(placement); return; } if (placement.ComponentId == BuiltInComponentIds.DesktopBilibiliHotSearch) { - OpenBilibiliHotSearchComponentSettings(); + OpenBilibiliHotSearchComponentSettings(placement); return; } if (placement.ComponentId == BuiltInComponentIds.DesktopBaiduHotSearch) { - OpenBaiduHotSearchComponentSettings(); + OpenBaiduHotSearchComponentSettings(placement); return; } if (placement.ComponentId == BuiltInComponentIds.DesktopStcn24Forum) { - OpenStcn24ForumComponentSettings(); + OpenStcn24ForumComponentSettings(placement); return; } if (placement.ComponentId == BuiltInComponentIds.DesktopStudyEnvironment) { - OpenStudyEnvironmentComponentSettings(); + OpenStudyEnvironmentComponentSettings(placement); return; } } @@ -811,212 +813,129 @@ public partial class MainWindow string.Equals(componentId, BuiltInComponentIds.DesktopExtendedWeather, StringComparison.OrdinalIgnoreCase); } - private void OpenDateComponentSettings() + private void ShowComponentSettings(Control settingsContent, DesktopComponentPlacementSnapshot placement) { if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null) { return; } - var settingsContent = new DateWidgetSettingsWindow(); + var runtimeContext = new DesktopComponentRuntimeContext( + placement.ComponentId, + placement.PlacementId, + _componentSettingsService); + + if (settingsContent is IComponentRuntimeContextAware runtimeContextAwareComponent) + { + runtimeContextAwareComponent.SetComponentRuntimeContext(runtimeContext); + } + + if (settingsContent is IComponentPlacementContextAware placementAwareComponent) + { + placementAwareComponent.SetComponentPlacementContext(placement.ComponentId, placement.PlacementId); + } + + if (settingsContent is IComponentSettingsStoreAware settingsStoreAwareComponent) + { + settingsStoreAwareComponent.SetComponentSettingsStore(_componentSettingsService); + } + + ComponentSettingsService.ApplyScopedContextToTarget(settingsContent, placement.ComponentId, placement.PlacementId); + ComponentSettingsContentHost.Content = settingsContent; - ComponentSettingsWindow.IsVisible = true; ComponentSettingsWindow.Opacity = 0; - ComponentSettingsWindow.Opacity = 1; } - private void OpenClassScheduleComponentSettings() + private void OpenDateComponentSettings(DesktopComponentPlacementSnapshot placement) { - if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null) - { - return; - } + var settingsContent = new DateWidgetSettingsWindow(); + ShowComponentSettings(settingsContent, placement); + } + private void OpenClassScheduleComponentSettings(DesktopComponentPlacementSnapshot placement) + { var settingsContent = new ClassScheduleSettingsWindow(); settingsContent.SettingsChanged += OnClassScheduleSettingsChanged; - ComponentSettingsContentHost.Content = settingsContent; - - ComponentSettingsWindow.IsVisible = true; - ComponentSettingsWindow.Opacity = 0; - ComponentSettingsWindow.Opacity = 1; + ShowComponentSettings(settingsContent, placement); } - private void OpenDesktopClockComponentSettings() + private void OpenDesktopClockComponentSettings(DesktopComponentPlacementSnapshot placement) { - if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null) - { - return; - } - var settingsContent = new AnalogClockWidgetSettingsWindow(); settingsContent.SettingsChanged += OnDesktopClockSettingsChanged; - ComponentSettingsContentHost.Content = settingsContent; - - ComponentSettingsWindow.IsVisible = true; - ComponentSettingsWindow.Opacity = 0; - ComponentSettingsWindow.Opacity = 1; + ShowComponentSettings(settingsContent, placement); } - private void OpenWorldClockComponentSettings() + private void OpenWorldClockComponentSettings(DesktopComponentPlacementSnapshot placement) { - if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null) - { - return; - } - var settingsContent = new WorldClockWidgetSettingsWindow(); settingsContent.SettingsChanged += OnWorldClockSettingsChanged; - ComponentSettingsContentHost.Content = settingsContent; - - ComponentSettingsWindow.IsVisible = true; - ComponentSettingsWindow.Opacity = 0; - ComponentSettingsWindow.Opacity = 1; + ShowComponentSettings(settingsContent, placement); } - private void OpenWeatherComponentSettings() + private void OpenWeatherComponentSettings(DesktopComponentPlacementSnapshot placement) { - if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null) - { - return; - } - var settingsContent = new WeatherWidgetSettingsWindow(); settingsContent.SettingsChanged += OnWeatherSettingsChanged; - ComponentSettingsContentHost.Content = settingsContent; - - ComponentSettingsWindow.IsVisible = true; - ComponentSettingsWindow.Opacity = 0; - ComponentSettingsWindow.Opacity = 1; + ShowComponentSettings(settingsContent, placement); } - private void OpenStudyEnvironmentComponentSettings() + private void OpenStudyEnvironmentComponentSettings(DesktopComponentPlacementSnapshot placement) { - if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null) - { - return; - } - var settingsContent = new StudyEnvironmentWidgetSettingsWindow(); settingsContent.SettingsChanged += OnStudyEnvironmentSettingsChanged; - ComponentSettingsContentHost.Content = settingsContent; - - ComponentSettingsWindow.IsVisible = true; - ComponentSettingsWindow.Opacity = 0; - ComponentSettingsWindow.Opacity = 1; + ShowComponentSettings(settingsContent, placement); } - private void OpenDailyArtworkComponentSettings() + private void OpenDailyArtworkComponentSettings(DesktopComponentPlacementSnapshot placement) { - if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null) - { - return; - } - var settingsContent = new DailyArtworkSettingsWindow(); settingsContent.SettingsChanged += OnDailyArtworkSettingsChanged; - ComponentSettingsContentHost.Content = settingsContent; - - ComponentSettingsWindow.IsVisible = true; - ComponentSettingsWindow.Opacity = 0; - ComponentSettingsWindow.Opacity = 1; + ShowComponentSettings(settingsContent, placement); } - private void OpenCnrDailyNewsComponentSettings() + private void OpenCnrDailyNewsComponentSettings(DesktopComponentPlacementSnapshot placement) { - if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null) - { - return; - } - var settingsContent = new CnrDailyNewsSettingsWindow(); settingsContent.SettingsChanged += OnCnrDailyNewsSettingsChanged; - ComponentSettingsContentHost.Content = settingsContent; - - ComponentSettingsWindow.IsVisible = true; - ComponentSettingsWindow.Opacity = 0; - ComponentSettingsWindow.Opacity = 1; + ShowComponentSettings(settingsContent, placement); } - private void OpenIfengNewsComponentSettings() + private void OpenIfengNewsComponentSettings(DesktopComponentPlacementSnapshot placement) { - if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null) - { - return; - } - var settingsContent = new IfengNewsSettingsWindow(); settingsContent.SettingsChanged += OnIfengNewsSettingsChanged; - ComponentSettingsContentHost.Content = settingsContent; - - ComponentSettingsWindow.IsVisible = true; - ComponentSettingsWindow.Opacity = 0; - ComponentSettingsWindow.Opacity = 1; + ShowComponentSettings(settingsContent, placement); } - private void OpenDailyWordComponentSettings() + private void OpenDailyWordComponentSettings(DesktopComponentPlacementSnapshot placement) { - if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null) - { - return; - } - var settingsContent = new DailyWordSettingsWindow(); settingsContent.SettingsChanged += OnDailyWordSettingsChanged; - ComponentSettingsContentHost.Content = settingsContent; - - ComponentSettingsWindow.IsVisible = true; - ComponentSettingsWindow.Opacity = 0; - ComponentSettingsWindow.Opacity = 1; + ShowComponentSettings(settingsContent, placement); } - private void OpenBilibiliHotSearchComponentSettings() + private void OpenBilibiliHotSearchComponentSettings(DesktopComponentPlacementSnapshot placement) { - if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null) - { - return; - } - var settingsContent = new BilibiliHotSearchSettingsWindow(); settingsContent.SettingsChanged += OnBilibiliHotSearchSettingsChanged; - ComponentSettingsContentHost.Content = settingsContent; - - ComponentSettingsWindow.IsVisible = true; - ComponentSettingsWindow.Opacity = 0; - ComponentSettingsWindow.Opacity = 1; + ShowComponentSettings(settingsContent, placement); } - private void OpenBaiduHotSearchComponentSettings() + private void OpenBaiduHotSearchComponentSettings(DesktopComponentPlacementSnapshot placement) { - if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null) - { - return; - } - var settingsContent = new BaiduHotSearchSettingsWindow(); settingsContent.SettingsChanged += OnBaiduHotSearchSettingsChanged; - ComponentSettingsContentHost.Content = settingsContent; - - ComponentSettingsWindow.IsVisible = true; - ComponentSettingsWindow.Opacity = 0; - ComponentSettingsWindow.Opacity = 1; + ShowComponentSettings(settingsContent, placement); } - private void OpenStcn24ForumComponentSettings() + private void OpenStcn24ForumComponentSettings(DesktopComponentPlacementSnapshot placement) { - if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null) - { - return; - } - var settingsContent = new Stcn24ForumSettingsWindow(); settingsContent.SettingsChanged += OnStcn24ForumSettingsChanged; - ComponentSettingsContentHost.Content = settingsContent; - - ComponentSettingsWindow.IsVisible = true; - ComponentSettingsWindow.Opacity = 0; - ComponentSettingsWindow.Opacity = 1; + ShowComponentSettings(settingsContent, placement); } private void OnClassScheduleSettingsChanged(object? sender, EventArgs e) @@ -1408,6 +1327,7 @@ public partial class MainWindow foreach (var placement in placementsToRemove) { _desktopComponentPlacements.Remove(placement); + _componentSettingsService.DeleteForComponent(placement.ComponentId, placement.PlacementId); } _desktopPageCount = Math.Clamp(_desktopPageCount - 1, MinDesktopPageCount, MaxDesktopPageCount); @@ -1601,7 +1521,7 @@ public partial class MainWindow placement.PlacementId = Guid.NewGuid().ToString("N"); } - var component = CreateDesktopComponentControl(placement.ComponentId); + var component = CreateDesktopComponentControl(placement.ComponentId, placement.PlacementId); if (component is null) { return null; @@ -2035,7 +1955,7 @@ public partial class MainWindow return onLeft || onRight || onTop || onBottom; } - private Control? CreateDesktopComponentControl(string componentId) + private Control? CreateDesktopComponentControl(string componentId, string? placementId = null) { if (!_componentRuntimeRegistry.TryGetDescriptor(componentId, out var runtimeDescriptor)) { @@ -2047,7 +1967,9 @@ public partial class MainWindow _timeZoneService, _weatherDataService, _recommendationInfoService, - _calculatorDataService); + _calculatorDataService, + _componentSettingsService, + placementId); component.Classes.Add(DesktopComponentClass); return component; } @@ -3195,7 +3117,8 @@ public partial class MainWindow _timeZoneService, _weatherDataService, _recommendationInfoService, - _calculatorDataService); + _calculatorDataService, + _componentSettingsService); // Component library previews must stay non-interactive so drag gesture is reliable. previewControl.IsHitTestVisible = false; previewControl.Focusable = false; diff --git a/README.md b/README.md index c40f51f..11d9836 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,9 @@ ## 当前状态 - 项目包含桌面端与推荐后端两个子项目,并在同一 solution 中维护。 -- 配置默认写入本地:`%LOCALAPPDATA%\LanMountainDesktop\settings.json`。 -- 启动台与桌面布局现已拆分到独立文件:`%LOCALAPPDATA%\LanMountainDesktop\launcher-settings.json`、`%LOCALAPPDATA%\LanMountainDesktop\desktop-layout-settings.json`。 +- 通用应用配置默认写入本地:`%LOCALAPPDATA%\LanMountainDesktop\settings.json`。 +- 启动台与桌面布局已拆分到独立文件:`%LOCALAPPDATA%\LanMountainDesktop\launcher-settings.json`、`%LOCALAPPDATA%\LanMountainDesktop\desktop-layout-settings.json`。 +- 组件配置统一写入:`%LOCALAPPDATA%\LanMountainDesktop\component-settings.json`;同类组件按实例 `componentId::placementId` 隔离存储,同时预留插件专属配置区。 - 当前体验以 Windows 为主要目标平台。 ## 运行说明