模块化解耦
This commit is contained in:
lincube
2026-03-08 04:22:19 +08:00
parent d72cd42483
commit 854deae801
23 changed files with 866 additions and 259 deletions

View File

@@ -0,0 +1,8 @@
using LanMountainDesktop.Services;
namespace LanMountainDesktop.ComponentSystem;
public sealed record DesktopComponentRuntimeContext(
string ComponentId,
string? PlacementId,
IComponentInstanceSettingsStore ComponentSettingsStore);

View File

@@ -0,0 +1,6 @@
namespace LanMountainDesktop.ComponentSystem;
public interface IComponentPlacementContextAware
{
void SetComponentPlacementContext(string componentId, string? placementId);
}

View File

@@ -0,0 +1,6 @@
namespace LanMountainDesktop.ComponentSystem;
public interface IComponentRuntimeContextAware
{
void SetComponentRuntimeContext(DesktopComponentRuntimeContext context);
}

View File

@@ -0,0 +1,8 @@
using LanMountainDesktop.Services;
namespace LanMountainDesktop.ComponentSystem;
public interface IComponentSettingsStoreAware
{
void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore);
}

View File

@@ -2,12 +2,13 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Text.Json; using System.Text.Json;
using LanMountainDesktop.Models; using LanMountainDesktop.Models;
namespace LanMountainDesktop.Services; namespace LanMountainDesktop.Services;
public sealed class ComponentSettingsService public sealed class ComponentSettingsService : IComponentInstanceSettingsStore
{ {
private static readonly JsonSerializerOptions SerializerOptions = new() private static readonly JsonSerializerOptions SerializerOptions = new()
{ {
@@ -18,12 +19,14 @@ public sealed class ComponentSettingsService
private static readonly TimeSpan CacheProbeInterval = TimeSpan.FromMilliseconds(400); private static readonly TimeSpan CacheProbeInterval = TimeSpan.FromMilliseconds(400);
private static string? _cachedPath; private static string? _cachedPath;
private static ComponentSettingsSnapshot? _cachedSnapshot; private static ComponentSettingsDocumentSnapshot? _cachedSnapshot;
private static DateTime _cachedWriteTimeUtc = DateTime.MinValue; private static DateTime _cachedWriteTimeUtc = DateTime.MinValue;
private static DateTime _lastProbeUtc = DateTime.MinValue; private static DateTime _lastProbeUtc = DateTime.MinValue;
private readonly string _settingsPath; private readonly string _settingsPath;
private readonly string _legacyAppSettingsPath; private readonly string _legacyAppSettingsPath;
private string _scopedComponentId = string.Empty;
private string _scopedPlacementId = string.Empty;
public ComponentSettingsService() public ComponentSettingsService()
{ {
@@ -35,51 +38,17 @@ public sealed class ComponentSettingsService
public ComponentSettingsSnapshot Load() public ComponentSettingsSnapshot Load()
{ {
if (HasScopedComponentContext())
{
return LoadForComponent(_scopedComponentId, _scopedPlacementId);
}
try try
{ {
lock (CacheGate) lock (CacheGate)
{ {
var nowUtc = DateTime.UtcNow; var document = LoadDocumentLocked();
if (TryGetCachedWithoutProbe(nowUtc, out var cached)) return document.DefaultSettings.Clone();
{
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();
} }
} }
catch catch
@@ -90,15 +59,21 @@ public sealed class ComponentSettingsService
public void Save(ComponentSettingsSnapshot snapshot) public void Save(ComponentSettingsSnapshot snapshot)
{ {
if (HasScopedComponentContext())
{
SaveForComponent(_scopedComponentId, _scopedPlacementId, snapshot);
return;
}
var snapshotToPersist = NormalizeSnapshot(snapshot); var snapshotToPersist = NormalizeSnapshot(snapshot);
try try
{ {
var writeTimeUtc = PersistSnapshotToDisk(snapshotToPersist);
lock (CacheGate) lock (CacheGate)
{ {
UpdateCache(snapshotToPersist, writeTimeUtc, DateTime.UtcNow); var document = LoadDocumentLocked();
document.DefaultSettings = snapshotToPersist;
PersistDocumentLocked(document);
} }
} }
catch 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<T>(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<T>(settingsElement.GetRawText(), SerializerOptions) ?? new T();
}
}
catch
{
return new T();
}
}
public void SavePluginSettings<T>(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) && if (string.Equals(_cachedPath, _settingsPath, StringComparison.Ordinal) &&
_cachedSnapshot is not null && _cachedSnapshot is not null &&
@@ -121,7 +290,7 @@ public sealed class ComponentSettingsService
return false; 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) && if (string.Equals(_cachedPath, _settingsPath, StringComparison.Ordinal) &&
_cachedSnapshot is not null && _cachedSnapshot is not null &&
@@ -135,17 +304,78 @@ public sealed class ComponentSettingsService
return false; 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 try
{ {
var json = File.ReadAllText(_settingsPath); var json = File.ReadAllText(_settingsPath);
var snapshot = JsonSerializer.Deserialize<ComponentSettingsSnapshot>(json, SerializerOptions); using var document = JsonDocument.Parse(json);
return NormalizeSnapshot(snapshot); 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<ComponentSettingsDocumentSnapshot>(json, SerializerOptions);
return NormalizeDocument(snapshot);
}
var legacySnapshot = JsonSerializer.Deserialize<ComponentSettingsSnapshot>(json, SerializerOptions);
return new ComponentSettingsDocumentSnapshot
{
DefaultSettings = NormalizeSnapshot(legacySnapshot)
};
} }
catch 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); var directory = Path.GetDirectoryName(_settingsPath);
if (!string.IsNullOrWhiteSpace(directory)) if (!string.IsNullOrWhiteSpace(directory))
@@ -257,6 +493,40 @@ public sealed class ComponentSettingsService
return normalized; return normalized;
} }
private static ComponentSettingsDocumentSnapshot NormalizeDocument(ComponentSettingsDocumentSnapshot? snapshot)
{
var normalized = snapshot?.Clone() ?? new ComponentSettingsDocumentSnapshot();
normalized.DefaultSettings = NormalizeSnapshot(normalized.DefaultSettings);
var instanceSettings = new Dictionary<string, ComponentSettingsSnapshot>(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<string, JsonElement>(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<ImportedClassScheduleSnapshot> NormalizeImportedSchedules( private static List<ImportedClassScheduleSnapshot> NormalizeImportedSchedules(
IReadOnlyList<ImportedClassScheduleSnapshot>? schedules) IReadOnlyList<ImportedClassScheduleSnapshot>? schedules)
{ {
@@ -360,7 +630,30 @@ public sealed class ComponentSettingsService
return RefreshIntervalCatalog.Normalize(minutes, 20); 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; _cachedPath = _settingsPath;
_cachedSnapshot = snapshot.Clone(); _cachedSnapshot = snapshot.Clone();
@@ -368,6 +661,39 @@ public sealed class ComponentSettingsService
_lastProbeUtc = probeTimeUtc; _lastProbeUtc = probeTimeUtc;
} }
private sealed class ComponentSettingsDocumentSnapshot
{
public ComponentSettingsSnapshot DefaultSettings { get; set; } = new();
public Dictionary<string, ComponentSettingsSnapshot> InstanceSettings { get; set; } =
new(StringComparer.OrdinalIgnoreCase);
public Dictionary<string, JsonElement> PluginSettings { get; set; } =
new(StringComparer.OrdinalIgnoreCase);
public ComponentSettingsDocumentSnapshot Clone()
{
var clone = new ComponentSettingsDocumentSnapshot
{
DefaultSettings = DefaultSettings?.Clone() ?? new ComponentSettingsSnapshot(),
InstanceSettings = new Dictionary<string, ComponentSettingsSnapshot>(StringComparer.OrdinalIgnoreCase),
PluginSettings = new Dictionary<string, JsonElement>(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 private sealed class LegacyComponentSettingsSnapshot
{ {
public string DailyArtworkMirrorSource { get; set; } = DailyArtworkMirrorSources.Overseas; public string DailyArtworkMirrorSource { get; set; } = DailyArtworkMirrorSources.Overseas;

View File

@@ -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<T>(string componentId, string? placementId) where T : new();
void SavePluginSettings<T>(string componentId, string? placementId, T settings);
void DeletePluginSettings(string componentId, string? placementId);
}

View File

@@ -7,11 +7,12 @@ using Avalonia.Controls.Shapes;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Threading; using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Services; using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components; namespace LanMountainDesktop.Views.Components;
public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware
{ {
private static readonly IReadOnlyDictionary<string, string> ZhCityNames = private static readonly IReadOnlyDictionary<string, string> ZhCityNames =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
@@ -54,9 +55,11 @@ public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, I
private const double DialSize = 258; private const double DialSize = 258;
private const double Center = DialSize / 2; private const double Center = DialSize / 2;
private string _componentId = BuiltInComponentIds.DesktopClock;
private string _placementId = string.Empty;
private readonly AppSettingsService _appSettingsService = new(); private readonly AppSettingsService _appSettingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new(); private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly LocalizationService _localizationService = new(); private readonly LocalizationService _localizationService = new();
private TimeZoneService? _timeZoneService; private TimeZoneService? _timeZoneService;
private double _currentCellSize = 48; private double _currentCellSize = 48;
@@ -112,6 +115,21 @@ public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, I
UpdateClock(); 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) private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{ {
InitializeDialIfNeeded(); InitializeDialIfNeeded();
@@ -359,7 +377,7 @@ public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, I
private void LoadClockSettings() private void LoadClockSettings()
{ {
var appSnapshot = _appSettingsService.Load(); var appSnapshot = _appSettingsService.Load();
var componentSnapshot = _componentSettingsService.Load(); var componentSnapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
_languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode); _languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode);
var configuredTimeZoneId = string.IsNullOrWhiteSpace(componentSnapshot.DesktopClockTimeZoneId) var configuredTimeZoneId = string.IsNullOrWhiteSpace(componentSnapshot.DesktopClockTimeZoneId)

View File

@@ -4,11 +4,12 @@ using System.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Services; using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components; namespace LanMountainDesktop.Views.Components;
public partial class AnalogClockWidgetSettingsWindow : UserControl public partial class AnalogClockWidgetSettingsWindow : UserControl, IComponentPlacementContextAware, IComponentSettingsStoreAware
{ {
private static readonly IReadOnlyDictionary<string, string> ZhTimeZoneNames = private static readonly IReadOnlyDictionary<string, string> ZhTimeZoneNames =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
@@ -28,11 +29,13 @@ public partial class AnalogClockWidgetSettingsWindow : UserControl
}; };
private readonly AppSettingsService _appSettingsService = new(); private readonly AppSettingsService _appSettingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new(); private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly LocalizationService _localizationService = new(); private readonly LocalizationService _localizationService = new();
private readonly TimeZoneService _timeZoneService = new(); private readonly TimeZoneService _timeZoneService = new();
private bool _suppressEvents; private bool _suppressEvents;
private string _languageCode = "zh-CN"; private string _languageCode = "zh-CN";
private string _componentId = BuiltInComponentIds.DesktopClock;
private string _placementId = string.Empty;
private IReadOnlyList<TimeZoneInfo> _allTimeZones = Array.Empty<TimeZoneInfo>(); private IReadOnlyList<TimeZoneInfo> _allTimeZones = Array.Empty<TimeZoneInfo>();
private string _selectedTimeZoneId = string.Empty; private string _selectedTimeZoneId = string.Empty;
private string _secondHandMode = ClockSecondHandMode.Tick; private string _secondHandMode = ClockSecondHandMode.Tick;
@@ -47,10 +50,29 @@ public partial class AnalogClockWidgetSettingsWindow : UserControl
PopulateTimeZoneComboBox(); 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() private void LoadState()
{ {
var appSnapshot = _appSettingsService.Load(); var appSnapshot = _appSettingsService.Load();
var componentSnapshot = _componentSettingsService.Load(); var componentSnapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
_languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode); _languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode);
_selectedTimeZoneId = string.IsNullOrWhiteSpace(componentSnapshot.DesktopClockTimeZoneId) _selectedTimeZoneId = string.IsNullOrWhiteSpace(componentSnapshot.DesktopClockTimeZoneId)
? "China Standard Time" ? "China Standard Time"
@@ -149,10 +171,10 @@ public partial class AnalogClockWidgetSettingsWindow : UserControl
_selectedTimeZoneId = normalizedId; _selectedTimeZoneId = normalizedId;
_secondHandMode = GetSelectedSecondHandMode(); _secondHandMode = GetSelectedSecondHandMode();
var snapshot = _componentSettingsService.Load(); var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
snapshot.DesktopClockTimeZoneId = normalizedId; snapshot.DesktopClockTimeZoneId = normalizedId;
snapshot.DesktopClockSecondHandMode = _secondHandMode; snapshot.DesktopClockSecondHandMode = _secondHandMode;
_componentSettingsService.Save(snapshot); _componentSettingsStore.SaveForComponent(_componentId, _placementId, snapshot);
SettingsChanged?.Invoke(this, EventArgs.Empty); SettingsChanged?.Invoke(this, EventArgs.Empty);
} }

View File

@@ -10,19 +10,22 @@ using Avalonia.Interactivity;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models; using LanMountainDesktop.Models;
using LanMountainDesktop.Services; using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components; namespace LanMountainDesktop.Views.Components;
public partial class ClassScheduleSettingsWindow : UserControl public partial class ClassScheduleSettingsWindow : UserControl, IComponentPlacementContextAware, IComponentSettingsStoreAware
{ {
private readonly AppSettingsService _appSettingsService = new(); private readonly AppSettingsService _appSettingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new(); private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly LocalizationService _localizationService = new(); private readonly LocalizationService _localizationService = new();
private readonly List<ImportedClassScheduleSnapshot> _importedSchedules = []; private readonly List<ImportedClassScheduleSnapshot> _importedSchedules = [];
private string _activeScheduleId = string.Empty; private string _activeScheduleId = string.Empty;
private string _languageCode = "zh-CN"; private string _languageCode = "zh-CN";
private string _componentId = BuiltInComponentIds.DesktopClassSchedule;
private string _placementId = string.Empty;
public event EventHandler? SettingsChanged; public event EventHandler? SettingsChanged;
@@ -34,10 +37,29 @@ public partial class ClassScheduleSettingsWindow : UserControl
RenderImportedSchedules(); 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() private void LoadState()
{ {
var appSnapshot = _appSettingsService.Load(); var appSnapshot = _appSettingsService.Load();
var componentSnapshot = _componentSettingsService.Load(); var componentSnapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
_languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode); _languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode);
_importedSchedules.Clear(); _importedSchedules.Clear();
@@ -299,7 +321,7 @@ public partial class ClassScheduleSettingsWindow : UserControl
private void SaveState() private void SaveState()
{ {
var snapshot = _componentSettingsService.Load(); var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
snapshot.ImportedClassSchedules = _importedSchedules snapshot.ImportedClassSchedules = _importedSchedules
.Select(item => new ImportedClassScheduleSnapshot .Select(item => new ImportedClassScheduleSnapshot
{ {
@@ -309,7 +331,7 @@ public partial class ClassScheduleSettingsWindow : UserControl
}) })
.ToList(); .ToList();
snapshot.ActiveImportedClassScheduleId = _activeScheduleId ?? string.Empty; snapshot.ActiveImportedClassScheduleId = _activeScheduleId ?? string.Empty;
_componentSettingsService.Save(snapshot); _componentSettingsStore.SaveForComponent(_componentId, _placementId, snapshot);
SettingsChanged?.Invoke(this, EventArgs.Empty); SettingsChanged?.Invoke(this, EventArgs.Empty);
} }

View File

@@ -7,12 +7,13 @@ using Avalonia.Controls;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Threading; using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models; using LanMountainDesktop.Models;
using LanMountainDesktop.Services; using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components; namespace LanMountainDesktop.Views.Components;
public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware
{ {
private sealed record CourseItemViewModel( private sealed record CourseItemViewModel(
string Name, string Name,
@@ -26,7 +27,7 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
}; };
private readonly AppSettingsService _appSettingsService = new(); private readonly AppSettingsService _appSettingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new(); private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly LocalizationService _localizationService = new(); private readonly LocalizationService _localizationService = new();
private readonly IClassIslandScheduleDataService _scheduleService = new ClassIslandScheduleDataService(); private readonly IClassIslandScheduleDataService _scheduleService = new ClassIslandScheduleDataService();
@@ -35,6 +36,8 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
private IReadOnlyList<CourseItemViewModel> _courseItems = Array.Empty<CourseItemViewModel>(); private IReadOnlyList<CourseItemViewModel> _courseItems = Array.Empty<CourseItemViewModel>();
private bool _isNightVisual = true; private bool _isNightVisual = true;
private string _languageCode = "zh-CN"; private string _languageCode = "zh-CN";
private string _componentId = BuiltInComponentIds.DesktopClassSchedule;
private string _placementId = string.Empty;
public ClassScheduleWidget() public ClassScheduleWidget()
{ {
@@ -113,10 +116,25 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
RefreshSchedule(); 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() private void RefreshSchedule()
{ {
var appSettings = _appSettingsService.Load(); var appSettings = _appSettingsService.Load();
var componentSettings = _componentSettingsService.Load(); var componentSettings = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
_languageCode = _localizationService.NormalizeLanguageCode(appSettings.LanguageCode); _languageCode = _localizationService.NormalizeLanguageCode(appSettings.LanguageCode);
var now = _timeZoneService?.GetCurrentTime() ?? DateTime.Now; var now = _timeZoneService?.GetCurrentTime() ?? DateTime.Now;
UpdateHeader(now); UpdateHeader(now);

View File

@@ -2,18 +2,21 @@ using System;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models; using LanMountainDesktop.Models;
using LanMountainDesktop.Services; using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components; namespace LanMountainDesktop.Views.Components;
public partial class DailyArtworkSettingsWindow : UserControl public partial class DailyArtworkSettingsWindow : UserControl, IComponentPlacementContextAware, IComponentSettingsStoreAware
{ {
private readonly AppSettingsService _appSettingsService = new(); private readonly AppSettingsService _appSettingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new(); private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly LocalizationService _localizationService = new(); private readonly LocalizationService _localizationService = new();
private string _languageCode = "zh-CN"; private string _languageCode = "zh-CN";
private bool _suppressEvents; private bool _suppressEvents;
private string _componentId = BuiltInComponentIds.DesktopDailyArtwork;
private string _placementId = string.Empty;
public event EventHandler? SettingsChanged; public event EventHandler? SettingsChanged;
@@ -24,10 +27,27 @@ public partial class DailyArtworkSettingsWindow : UserControl
ApplyLocalization(); 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() private void LoadState()
{ {
var appSnapshot = _appSettingsService.Load(); var appSnapshot = _appSettingsService.Load();
var componentSnapshot = _componentSettingsService.Load(); var componentSnapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
_languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode); _languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode);
var source = DailyArtworkMirrorSources.Normalize(componentSnapshot.DailyArtworkMirrorSource); var source = DailyArtworkMirrorSources.Normalize(componentSnapshot.DailyArtworkMirrorSource);
@@ -59,9 +79,9 @@ public partial class DailyArtworkSettingsWindow : UserControl
} }
var source = GetSelectedSource(); var source = GetSelectedSource();
var snapshot = _componentSettingsService.Load(); var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
snapshot.DailyArtworkMirrorSource = source; snapshot.DailyArtworkMirrorSource = source;
_componentSettingsService.Save(snapshot); _componentSettingsStore.SaveForComponent(_componentId, _placementId, snapshot);
UpdateSourceStatus(source); UpdateSourceStatus(source);
SettingsChanged?.Invoke(this, EventArgs.Empty); SettingsChanged?.Invoke(this, EventArgs.Empty);

View File

@@ -13,12 +13,13 @@ using Avalonia.Input;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Threading; using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models; using LanMountainDesktop.Models;
using LanMountainDesktop.Services; using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components; namespace LanMountainDesktop.Views.Components;
public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget, IRecommendationInfoAwareComponentWidget public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget, IRecommendationInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware
{ {
private static readonly IReadOnlyDictionary<DayOfWeek, string> ZhWeekdays = private static readonly IReadOnlyDictionary<DayOfWeek, string> ZhWeekdays =
new Dictionary<DayOfWeek, string> new Dictionary<DayOfWeek, string>
@@ -58,6 +59,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
}; };
private readonly AppSettingsService _settingsService = new(); private readonly AppSettingsService _settingsService = new();
private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly LocalizationService _localizationService = new(); private readonly LocalizationService _localizationService = new();
private IRecommendationInfoService _recommendationService = DefaultRecommendationService; private IRecommendationInfoService _recommendationService = DefaultRecommendationService;
@@ -67,6 +69,8 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
private double _currentCellSize = BaseCellSize; private double _currentCellSize = BaseCellSize;
private bool _isAttached; private bool _isAttached;
private bool _isRefreshing; private bool _isRefreshing;
private string _componentId = BuiltInComponentIds.DesktopDailyArtwork;
private string _placementId = string.Empty;
private string? _currentArtworkSourceUrl; private string? _currentArtworkSourceUrl;
private string? _currentArtworkImageUrl; 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) private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{ {
_isAttached = true; _isAttached = true;
@@ -203,6 +222,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
{ {
var query = new DailyArtworkQuery( var query = new DailyArtworkQuery(
Locale: _languageCode, Locale: _languageCode,
MirrorSource: ResolveMirrorSource(),
ForceRefresh: forceRefresh); ForceRefresh: forceRefresh);
var result = await _recommendationService.GetDailyArtworkAsync(query, cts.Token); var result = await _recommendationService.GetDailyArtworkAsync(query, cts.Token);
if (!_isAttached || cts.IsCancellationRequested) 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() private void UpdateDateLabels()
{ {
var now = DateTime.Now; var now = DateTime.Now;

View File

@@ -42,9 +42,33 @@ public sealed class DesktopComponentRuntimeDescriptor
TimeZoneService timeZoneService, TimeZoneService timeZoneService,
IWeatherInfoService weatherInfoService, IWeatherInfoService weatherInfoService,
IRecommendationInfoService recommendationInfoService, IRecommendationInfoService recommendationInfoService,
ICalculatorDataService calculatorDataService) ICalculatorDataService calculatorDataService,
IComponentInstanceSettingsStore componentSettingsStore,
string? placementId = null)
{ {
var control = _controlFactory(); 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) if (control is IDesktopComponentWidget sizedComponent)
{ {
sizedComponent.ApplyCellSize(cellSize); sizedComponent.ApplyCellSize(cellSize);

View File

@@ -9,13 +9,14 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Threading; using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models; using LanMountainDesktop.Models;
using LanMountainDesktop.Services; using LanMountainDesktop.Services;
using LanMountainDesktop.Theme; using LanMountainDesktop.Theme;
namespace LanMountainDesktop.Views.Components; 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 IWeatherInfoService DefaultWeatherInfoService = new XiaomiWeatherService();
private static readonly IReadOnlyList<int> SupportedAutoRefreshIntervalsMinutes = RefreshIntervalCatalog.SupportedIntervalsMinutes; private static readonly IReadOnlyList<int> SupportedAutoRefreshIntervalsMinutes = RefreshIntervalCatalog.SupportedIntervalsMinutes;
@@ -25,7 +26,7 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
private readonly ScaleTransform _backgroundMotionScaleTransform = new(1, 1); private readonly ScaleTransform _backgroundMotionScaleTransform = new(1, 1);
private readonly TranslateTransform _backgroundMotionTranslateTransform = new(); private readonly TranslateTransform _backgroundMotionTranslateTransform = new();
private readonly AppSettingsService _settingsService = new(); private readonly AppSettingsService _settingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new(); private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly LocalizationService _localizationService = new(); private readonly LocalizationService _localizationService = new();
private IWeatherInfoService _weatherInfoService = DefaultWeatherInfoService; private IWeatherInfoService _weatherInfoService = DefaultWeatherInfoService;
@@ -39,6 +40,8 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
private bool _autoRefreshEnabled = true; private bool _autoRefreshEnabled = true;
private string _languageCode = "zh-CN"; private string _languageCode = "zh-CN";
private HyperOS3WeatherVisualKind _activeVisualKind = HyperOS3WeatherVisualKind.ClearDay; private HyperOS3WeatherVisualKind _activeVisualKind = HyperOS3WeatherVisualKind.ClearDay;
private string _componentId = BuiltInComponentIds.DesktopExtendedWeather;
private string _placementId = string.Empty;
private readonly TextBlock[] _hourlyTempBlocks; private readonly TextBlock[] _hourlyTempBlocks;
private readonly TextBlock[] _hourlyTimeBlocks; private readonly TextBlock[] _hourlyTimeBlocks;
private readonly Image[] _hourlyIconBlocks; 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) public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode)
{ {
_ = isEditMode; _ = isEditMode;
@@ -935,7 +953,7 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
try try
{ {
var snapshot = _componentSettingsService.Load(); var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
enabled = snapshot.WeatherAutoRefreshEnabled; enabled = snapshot.WeatherAutoRefreshEnabled;
intervalMinutes = NormalizeAutoRefreshIntervalMinutes(snapshot.WeatherAutoRefreshIntervalMinutes); intervalMinutes = NormalizeAutoRefreshIntervalMinutes(snapshot.WeatherAutoRefreshIntervalMinutes);
} }

View File

@@ -11,13 +11,14 @@ using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Threading; using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models; using LanMountainDesktop.Models;
using LanMountainDesktop.Services; using LanMountainDesktop.Services;
using LanMountainDesktop.Theme; using LanMountainDesktop.Theme;
namespace LanMountainDesktop.Views.Components; 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 private enum WeatherVisualKind
{ {
@@ -95,7 +96,7 @@ public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget,
}; };
private readonly AppSettingsService _settingsService = new(); private readonly AppSettingsService _settingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new(); private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly LocalizationService _localizationService = new(); private readonly LocalizationService _localizationService = new();
private readonly Dictionary<WeatherVisualKind, IBrush> _backgroundBrushCache = new(); private readonly Dictionary<WeatherVisualKind, IBrush> _backgroundBrushCache = new();
private readonly Dictionary<HyperOS3WeatherVisualKind, IBrush> _particleBrushCache = new(); private readonly Dictionary<HyperOS3WeatherVisualKind, IBrush> _particleBrushCache = new();
@@ -118,6 +119,8 @@ public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget,
private bool _isOnActivePage = true; private bool _isOnActivePage = true;
private bool _isRefreshing; private bool _isRefreshing;
private bool _autoRefreshEnabled = true; private bool _autoRefreshEnabled = true;
private string _componentId = BuiltInComponentIds.DesktopHourlyWeather;
private string _placementId = string.Empty;
private readonly TextBlock[] _hourlyTimeBlocks; private readonly TextBlock[] _hourlyTimeBlocks;
private readonly Image[] _hourlyIconBlocks; private readonly Image[] _hourlyIconBlocks;
private readonly TextBlock[] _hourlyTempBlocks; 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) public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode)
{ {
_ = isEditMode; _ = isEditMode;
@@ -1424,7 +1442,7 @@ public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget,
try try
{ {
var snapshot = _componentSettingsService.Load(); var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
enabled = snapshot.WeatherAutoRefreshEnabled; enabled = snapshot.WeatherAutoRefreshEnabled;
intervalMinutes = NormalizeAutoRefreshIntervalMinutes(snapshot.WeatherAutoRefreshIntervalMinutes); intervalMinutes = NormalizeAutoRefreshIntervalMinutes(snapshot.WeatherAutoRefreshIntervalMinutes);
} }

View File

@@ -9,13 +9,14 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Threading; using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models; using LanMountainDesktop.Models;
using LanMountainDesktop.Services; using LanMountainDesktop.Services;
using LanMountainDesktop.Theme; using LanMountainDesktop.Theme;
namespace LanMountainDesktop.Views.Components; 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 private enum WeatherVisualKind
{ {
@@ -93,7 +94,7 @@ public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidge
}; };
private readonly AppSettingsService _settingsService = new(); private readonly AppSettingsService _settingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new(); private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly LocalizationService _localizationService = new(); private readonly LocalizationService _localizationService = new();
private readonly Dictionary<WeatherVisualKind, IBrush> _backgroundBrushCache = new(); private readonly Dictionary<WeatherVisualKind, IBrush> _backgroundBrushCache = new();
private readonly Dictionary<HyperOS3WeatherVisualKind, IBrush> _particleBrushCache = new(); private readonly Dictionary<HyperOS3WeatherVisualKind, IBrush> _particleBrushCache = new();
@@ -116,6 +117,8 @@ public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidge
private bool _isOnActivePage = true; private bool _isOnActivePage = true;
private bool _isRefreshing; private bool _isRefreshing;
private bool _autoRefreshEnabled = true; private bool _autoRefreshEnabled = true;
private string _componentId = BuiltInComponentIds.DesktopMultiDayWeather;
private string _placementId = string.Empty;
private readonly TextBlock[] _hourlyTimeBlocks; private readonly TextBlock[] _hourlyTimeBlocks;
private readonly Image[] _hourlyIconBlocks; private readonly Image[] _hourlyIconBlocks;
private readonly TextBlock[] _hourlyTempBlocks; 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) public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode)
{ {
_ = isEditMode; _ = isEditMode;
@@ -1274,7 +1292,7 @@ public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidge
try try
{ {
var snapshot = _componentSettingsService.Load(); var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
enabled = snapshot.WeatherAutoRefreshEnabled; enabled = snapshot.WeatherAutoRefreshEnabled;
intervalMinutes = NormalizeAutoRefreshIntervalMinutes(snapshot.WeatherAutoRefreshIntervalMinutes); intervalMinutes = NormalizeAutoRefreshIntervalMinutes(snapshot.WeatherAutoRefreshIntervalMinutes);
} }

View File

@@ -10,12 +10,13 @@ using Avalonia.Controls.Shapes;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Threading; using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models; using LanMountainDesktop.Models;
using LanMountainDesktop.Services; using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components; 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( private sealed record WeatherClockConfig(
string LanguageCode, string LanguageCode,
@@ -41,7 +42,7 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget,
}; };
private readonly AppSettingsService _settingsService = new(); private readonly AppSettingsService _settingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new(); private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly LocalizationService _localizationService = new(); private readonly LocalizationService _localizationService = new();
private readonly Line _hourHandLine = CreateHandLine("#232938", 4.0); private readonly Line _hourHandLine = CreateHandLine("#232938", 4.0);
private readonly Line _minuteHandLine = CreateHandLine("#2F3749", 2.8); private readonly Line _minuteHandLine = CreateHandLine("#2F3749", 2.8);
@@ -59,6 +60,8 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget,
private bool? _isNightModeApplied; private bool? _isNightModeApplied;
private string _languageCode = "zh-CN"; private string _languageCode = "zh-CN";
private HyperOS3WeatherVisualKind _activeVisualKind = HyperOS3WeatherVisualKind.CloudyDay; private HyperOS3WeatherVisualKind _activeVisualKind = HyperOS3WeatherVisualKind.CloudyDay;
private string _componentId = BuiltInComponentIds.DesktopWeatherClock;
private string _placementId = string.Empty;
public WeatherClockWidget() 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) public void ApplyCellSize(double cellSize)
{ {
_currentCellSize = Math.Max(1, cellSize); _currentCellSize = Math.Max(1, cellSize);
@@ -659,7 +677,7 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget,
try try
{ {
var snapshot = _componentSettingsService.Load(); var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
enabled = snapshot.WeatherAutoRefreshEnabled; enabled = snapshot.WeatherAutoRefreshEnabled;
intervalMinutes = NormalizeAutoRefreshIntervalMinutes(snapshot.WeatherAutoRefreshIntervalMinutes); intervalMinutes = NormalizeAutoRefreshIntervalMinutes(snapshot.WeatherAutoRefreshIntervalMinutes);
} }

View File

@@ -11,13 +11,14 @@ using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Threading; using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models; using LanMountainDesktop.Models;
using LanMountainDesktop.Services; using LanMountainDesktop.Services;
using LanMountainDesktop.Theme; using LanMountainDesktop.Theme;
namespace LanMountainDesktop.Views.Components; 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 private enum WeatherVisualKind
{ {
@@ -89,7 +90,7 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk
}; };
private readonly AppSettingsService _settingsService = new(); private readonly AppSettingsService _settingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new(); private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly LocalizationService _localizationService = new(); private readonly LocalizationService _localizationService = new();
private readonly Dictionary<WeatherVisualKind, IBrush> _backgroundBrushCache = new(); private readonly Dictionary<WeatherVisualKind, IBrush> _backgroundBrushCache = new();
private readonly Dictionary<HyperOS3WeatherVisualKind, IBrush> _particleBrushCache = new(); private readonly Dictionary<HyperOS3WeatherVisualKind, IBrush> _particleBrushCache = new();
@@ -112,6 +113,8 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk
private bool _isOnActivePage = true; private bool _isOnActivePage = true;
private bool _isRefreshing; private bool _isRefreshing;
private bool _autoRefreshEnabled = true; private bool _autoRefreshEnabled = true;
private string _componentId = BuiltInComponentIds.DesktopWeather;
private string _placementId = string.Empty;
public WeatherWidget() 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) public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode)
{ {
_ = isEditMode; _ = isEditMode;
@@ -1063,7 +1081,7 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk
try try
{ {
var snapshot = _componentSettingsService.Load(); var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
enabled = snapshot.WeatherAutoRefreshEnabled; enabled = snapshot.WeatherAutoRefreshEnabled;
intervalMinutes = NormalizeAutoRefreshIntervalMinutes(snapshot.WeatherAutoRefreshIntervalMinutes); intervalMinutes = NormalizeAutoRefreshIntervalMinutes(snapshot.WeatherAutoRefreshIntervalMinutes);
} }

View File

@@ -4,20 +4,23 @@ using System.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models; using LanMountainDesktop.Models;
using LanMountainDesktop.Services; using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components; namespace LanMountainDesktop.Views.Components;
public partial class WeatherWidgetSettingsWindow : UserControl public partial class WeatherWidgetSettingsWindow : UserControl, IComponentPlacementContextAware, IComponentSettingsStoreAware
{ {
private static readonly IReadOnlyList<int> SupportedIntervals = RefreshIntervalCatalog.SupportedIntervalsMinutes; private static readonly IReadOnlyList<int> SupportedIntervals = RefreshIntervalCatalog.SupportedIntervalsMinutes;
private readonly AppSettingsService _appSettingsService = new(); private readonly AppSettingsService _appSettingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new(); private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly LocalizationService _localizationService = new(); private readonly LocalizationService _localizationService = new();
private bool _suppressEvents; private bool _suppressEvents;
private string _languageCode = "zh-CN"; private string _languageCode = "zh-CN";
private string _componentId = BuiltInComponentIds.DesktopWeather;
private string _placementId = string.Empty;
public event EventHandler? SettingsChanged; public event EventHandler? SettingsChanged;
@@ -29,10 +32,27 @@ public partial class WeatherWidgetSettingsWindow : UserControl
ApplyLocalization(); 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() private void LoadState()
{ {
var appSnapshot = _appSettingsService.Load(); var appSnapshot = _appSettingsService.Load();
var componentSnapshot = _componentSettingsService.Load(); var componentSnapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
_languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode); _languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode);
var enabled = componentSnapshot.WeatherAutoRefreshEnabled; var enabled = componentSnapshot.WeatherAutoRefreshEnabled;
@@ -83,10 +103,10 @@ public partial class WeatherWidgetSettingsWindow : UserControl
private void SaveState() private void SaveState()
{ {
var snapshot = _componentSettingsService.Load(); var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
snapshot.WeatherAutoRefreshEnabled = AutoRefreshCheckBox.IsChecked == true; snapshot.WeatherAutoRefreshEnabled = AutoRefreshCheckBox.IsChecked == true;
snapshot.WeatherAutoRefreshIntervalMinutes = GetSelectedInterval(); snapshot.WeatherAutoRefreshIntervalMinutes = GetSelectedInterval();
_componentSettingsService.Save(snapshot); _componentSettingsStore.SaveForComponent(_componentId, _placementId, snapshot);
SettingsChanged?.Invoke(this, EventArgs.Empty); SettingsChanged?.Invoke(this, EventArgs.Empty);
} }

View File

@@ -8,11 +8,12 @@ using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Threading; using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Services; using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components; 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 BaseWidthCells = 4;
private const int BaseHeightCells = 2; private const int BaseHeightCells = 2;
@@ -92,7 +93,7 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
}; };
private readonly AppSettingsService _appSettingsService = new(); private readonly AppSettingsService _appSettingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new(); private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly LocalizationService _localizationService = new(); private readonly LocalizationService _localizationService = new();
private readonly ClockEntryVisual[] _entryVisuals = new ClockEntryVisual[WorldClockTimeZoneCatalog.ClockCount]; private readonly ClockEntryVisual[] _entryVisuals = new ClockEntryVisual[WorldClockTimeZoneCatalog.ClockCount];
private readonly TimeZoneInfo[] _entryTimeZones = new TimeZoneInfo[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 DateTime _nextLanguageProbeUtc = DateTime.MinValue;
private string _secondHandMode = ClockSecondHandMode.Tick; private string _secondHandMode = ClockSecondHandMode.Tick;
private bool _isNightVisual = true; private bool _isNightVisual = true;
private string _componentId = BuiltInComponentIds.DesktopWorldClock;
private string _placementId = string.Empty;
public WorldClockWidget() public WorldClockWidget()
{ {
@@ -147,6 +150,21 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
UpdateClockVisuals(); 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) public void ApplyCellSize(double cellSize)
{ {
_currentCellSize = Math.Max(1, cellSize); _currentCellSize = Math.Max(1, cellSize);
@@ -525,7 +543,7 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
private void LoadFromSettings() private void LoadFromSettings()
{ {
var appSnapshot = _appSettingsService.Load(); var appSnapshot = _appSettingsService.Load();
var componentSnapshot = _componentSettingsService.Load(); var componentSnapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
_languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode); _languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode);
var ids = WorldClockTimeZoneCatalog.NormalizeTimeZoneIds(componentSnapshot.WorldClockTimeZoneIds); var ids = WorldClockTimeZoneCatalog.NormalizeTimeZoneIds(componentSnapshot.WorldClockTimeZoneIds);

View File

@@ -4,11 +4,12 @@ using System.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Services; using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components; namespace LanMountainDesktop.Views.Components;
public partial class WorldClockWidgetSettingsWindow : UserControl public partial class WorldClockWidgetSettingsWindow : UserControl, IComponentPlacementContextAware, IComponentSettingsStoreAware
{ {
private static readonly IReadOnlyDictionary<string, string> ZhTimeZoneNames = private static readonly IReadOnlyDictionary<string, string> ZhTimeZoneNames =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
@@ -28,12 +29,14 @@ public partial class WorldClockWidgetSettingsWindow : UserControl
}; };
private readonly AppSettingsService _appSettingsService = new(); private readonly AppSettingsService _appSettingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new(); private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly LocalizationService _localizationService = new(); private readonly LocalizationService _localizationService = new();
private readonly TimeZoneService _timeZoneService = new(); private readonly TimeZoneService _timeZoneService = new();
private readonly ComboBox[] _timeZoneComboBoxes; private readonly ComboBox[] _timeZoneComboBoxes;
private bool _suppressEvents; private bool _suppressEvents;
private string _languageCode = "zh-CN"; private string _languageCode = "zh-CN";
private string _componentId = BuiltInComponentIds.DesktopWorldClock;
private string _placementId = string.Empty;
private IReadOnlyList<TimeZoneInfo> _allTimeZones = Array.Empty<TimeZoneInfo>(); private IReadOnlyList<TimeZoneInfo> _allTimeZones = Array.Empty<TimeZoneInfo>();
private IReadOnlyList<string> _selectedTimeZoneIds = Array.Empty<string>(); private IReadOnlyList<string> _selectedTimeZoneIds = Array.Empty<string>();
private string _secondHandMode = ClockSecondHandMode.Tick; private string _secondHandMode = ClockSecondHandMode.Tick;
@@ -57,10 +60,29 @@ public partial class WorldClockWidgetSettingsWindow : UserControl
PopulateTimeZoneComboBoxes(); 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() private void LoadState()
{ {
var appSnapshot = _appSettingsService.Load(); var appSnapshot = _appSettingsService.Load();
var componentSnapshot = _componentSettingsService.Load(); var componentSnapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
_languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode); _languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode);
_allTimeZones = _timeZoneService _allTimeZones = _timeZoneService
@@ -167,10 +189,10 @@ public partial class WorldClockWidgetSettingsWindow : UserControl
var normalizedIds = WorldClockTimeZoneCatalog.NormalizeTimeZoneIds(selectedIds, _allTimeZones); var normalizedIds = WorldClockTimeZoneCatalog.NormalizeTimeZoneIds(selectedIds, _allTimeZones);
_secondHandMode = GetSelectedSecondHandMode(); _secondHandMode = GetSelectedSecondHandMode();
var snapshot = _componentSettingsService.Load(); var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
snapshot.WorldClockTimeZoneIds = normalizedIds.ToList(); snapshot.WorldClockTimeZoneIds = normalizedIds.ToList();
snapshot.WorldClockSecondHandMode = _secondHandMode; snapshot.WorldClockSecondHandMode = _secondHandMode;
_componentSettingsService.Save(snapshot); _componentSettingsStore.SaveForComponent(_componentId, _placementId, snapshot);
_selectedTimeZoneIds = normalizedIds; _selectedTimeZoneIds = normalizedIds;
SettingsChanged?.Invoke(this, EventArgs.Empty); SettingsChanged?.Invoke(this, EventArgs.Empty);

View File

@@ -14,6 +14,7 @@ using FluentIcons.Avalonia;
using FluentIcons.Common; using FluentIcons.Common;
using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models; using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Theme; using LanMountainDesktop.Theme;
using LanMountainDesktop.Views.Components; using LanMountainDesktop.Views.Components;
@@ -689,6 +690,7 @@ public partial class MainWindow
} }
_desktopComponentPlacements.Remove(placement); _desktopComponentPlacements.Remove(placement);
_componentSettingsService.DeleteForComponent(placement.ComponentId, placement.PlacementId);
ClearDesktopComponentSelection(); ClearDesktopComponentSelection();
@@ -724,80 +726,80 @@ public partial class MainWindow
if (placement.ComponentId == BuiltInComponentIds.Date) if (placement.ComponentId == BuiltInComponentIds.Date)
{ {
OpenDateComponentSettings(); OpenDateComponentSettings(placement);
return; return;
} }
if (placement.ComponentId == BuiltInComponentIds.DesktopClock) if (placement.ComponentId == BuiltInComponentIds.DesktopClock)
{ {
OpenDesktopClockComponentSettings(); OpenDesktopClockComponentSettings(placement);
return; return;
} }
if (placement.ComponentId == BuiltInComponentIds.DesktopClassSchedule) if (placement.ComponentId == BuiltInComponentIds.DesktopClassSchedule)
{ {
OpenClassScheduleComponentSettings(); OpenClassScheduleComponentSettings(placement);
return; return;
} }
if (placement.ComponentId == BuiltInComponentIds.DesktopWorldClock) if (placement.ComponentId == BuiltInComponentIds.DesktopWorldClock)
{ {
OpenWorldClockComponentSettings(); OpenWorldClockComponentSettings(placement);
return; return;
} }
if (IsWeatherComponentId(placement.ComponentId)) if (IsWeatherComponentId(placement.ComponentId))
{ {
OpenWeatherComponentSettings(); OpenWeatherComponentSettings(placement);
return; return;
} }
if (placement.ComponentId == BuiltInComponentIds.DesktopDailyArtwork) if (placement.ComponentId == BuiltInComponentIds.DesktopDailyArtwork)
{ {
OpenDailyArtworkComponentSettings(); OpenDailyArtworkComponentSettings(placement);
return; return;
} }
if (placement.ComponentId == BuiltInComponentIds.DesktopCnrDailyNews) if (placement.ComponentId == BuiltInComponentIds.DesktopCnrDailyNews)
{ {
OpenCnrDailyNewsComponentSettings(); OpenCnrDailyNewsComponentSettings(placement);
return; return;
} }
if (placement.ComponentId == BuiltInComponentIds.DesktopIfengNews) if (placement.ComponentId == BuiltInComponentIds.DesktopIfengNews)
{ {
OpenIfengNewsComponentSettings(); OpenIfengNewsComponentSettings(placement);
return; return;
} }
if (placement.ComponentId == BuiltInComponentIds.DesktopDailyWord || if (placement.ComponentId == BuiltInComponentIds.DesktopDailyWord ||
placement.ComponentId == BuiltInComponentIds.DesktopDailyWord2x2) placement.ComponentId == BuiltInComponentIds.DesktopDailyWord2x2)
{ {
OpenDailyWordComponentSettings(); OpenDailyWordComponentSettings(placement);
return; return;
} }
if (placement.ComponentId == BuiltInComponentIds.DesktopBilibiliHotSearch) if (placement.ComponentId == BuiltInComponentIds.DesktopBilibiliHotSearch)
{ {
OpenBilibiliHotSearchComponentSettings(); OpenBilibiliHotSearchComponentSettings(placement);
return; return;
} }
if (placement.ComponentId == BuiltInComponentIds.DesktopBaiduHotSearch) if (placement.ComponentId == BuiltInComponentIds.DesktopBaiduHotSearch)
{ {
OpenBaiduHotSearchComponentSettings(); OpenBaiduHotSearchComponentSettings(placement);
return; return;
} }
if (placement.ComponentId == BuiltInComponentIds.DesktopStcn24Forum) if (placement.ComponentId == BuiltInComponentIds.DesktopStcn24Forum)
{ {
OpenStcn24ForumComponentSettings(); OpenStcn24ForumComponentSettings(placement);
return; return;
} }
if (placement.ComponentId == BuiltInComponentIds.DesktopStudyEnvironment) if (placement.ComponentId == BuiltInComponentIds.DesktopStudyEnvironment)
{ {
OpenStudyEnvironmentComponentSettings(); OpenStudyEnvironmentComponentSettings(placement);
return; return;
} }
} }
@@ -811,212 +813,129 @@ public partial class MainWindow
string.Equals(componentId, BuiltInComponentIds.DesktopExtendedWeather, StringComparison.OrdinalIgnoreCase); string.Equals(componentId, BuiltInComponentIds.DesktopExtendedWeather, StringComparison.OrdinalIgnoreCase);
} }
private void OpenDateComponentSettings() private void ShowComponentSettings(Control settingsContent, DesktopComponentPlacementSnapshot placement)
{ {
if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null) if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null)
{ {
return; 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; ComponentSettingsContentHost.Content = settingsContent;
ComponentSettingsWindow.IsVisible = true; ComponentSettingsWindow.IsVisible = true;
ComponentSettingsWindow.Opacity = 0; ComponentSettingsWindow.Opacity = 0;
ComponentSettingsWindow.Opacity = 1; ComponentSettingsWindow.Opacity = 1;
} }
private void OpenClassScheduleComponentSettings() private void OpenDateComponentSettings(DesktopComponentPlacementSnapshot placement)
{ {
if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null) var settingsContent = new DateWidgetSettingsWindow();
{ ShowComponentSettings(settingsContent, placement);
return; }
}
private void OpenClassScheduleComponentSettings(DesktopComponentPlacementSnapshot placement)
{
var settingsContent = new ClassScheduleSettingsWindow(); var settingsContent = new ClassScheduleSettingsWindow();
settingsContent.SettingsChanged += OnClassScheduleSettingsChanged; settingsContent.SettingsChanged += OnClassScheduleSettingsChanged;
ComponentSettingsContentHost.Content = settingsContent; ShowComponentSettings(settingsContent, placement);
ComponentSettingsWindow.IsVisible = true;
ComponentSettingsWindow.Opacity = 0;
ComponentSettingsWindow.Opacity = 1;
} }
private void OpenDesktopClockComponentSettings() private void OpenDesktopClockComponentSettings(DesktopComponentPlacementSnapshot placement)
{ {
if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null)
{
return;
}
var settingsContent = new AnalogClockWidgetSettingsWindow(); var settingsContent = new AnalogClockWidgetSettingsWindow();
settingsContent.SettingsChanged += OnDesktopClockSettingsChanged; settingsContent.SettingsChanged += OnDesktopClockSettingsChanged;
ComponentSettingsContentHost.Content = settingsContent; ShowComponentSettings(settingsContent, placement);
ComponentSettingsWindow.IsVisible = true;
ComponentSettingsWindow.Opacity = 0;
ComponentSettingsWindow.Opacity = 1;
} }
private void OpenWorldClockComponentSettings() private void OpenWorldClockComponentSettings(DesktopComponentPlacementSnapshot placement)
{ {
if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null)
{
return;
}
var settingsContent = new WorldClockWidgetSettingsWindow(); var settingsContent = new WorldClockWidgetSettingsWindow();
settingsContent.SettingsChanged += OnWorldClockSettingsChanged; settingsContent.SettingsChanged += OnWorldClockSettingsChanged;
ComponentSettingsContentHost.Content = settingsContent; ShowComponentSettings(settingsContent, placement);
ComponentSettingsWindow.IsVisible = true;
ComponentSettingsWindow.Opacity = 0;
ComponentSettingsWindow.Opacity = 1;
} }
private void OpenWeatherComponentSettings() private void OpenWeatherComponentSettings(DesktopComponentPlacementSnapshot placement)
{ {
if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null)
{
return;
}
var settingsContent = new WeatherWidgetSettingsWindow(); var settingsContent = new WeatherWidgetSettingsWindow();
settingsContent.SettingsChanged += OnWeatherSettingsChanged; settingsContent.SettingsChanged += OnWeatherSettingsChanged;
ComponentSettingsContentHost.Content = settingsContent; ShowComponentSettings(settingsContent, placement);
ComponentSettingsWindow.IsVisible = true;
ComponentSettingsWindow.Opacity = 0;
ComponentSettingsWindow.Opacity = 1;
} }
private void OpenStudyEnvironmentComponentSettings() private void OpenStudyEnvironmentComponentSettings(DesktopComponentPlacementSnapshot placement)
{ {
if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null)
{
return;
}
var settingsContent = new StudyEnvironmentWidgetSettingsWindow(); var settingsContent = new StudyEnvironmentWidgetSettingsWindow();
settingsContent.SettingsChanged += OnStudyEnvironmentSettingsChanged; settingsContent.SettingsChanged += OnStudyEnvironmentSettingsChanged;
ComponentSettingsContentHost.Content = settingsContent; ShowComponentSettings(settingsContent, placement);
ComponentSettingsWindow.IsVisible = true;
ComponentSettingsWindow.Opacity = 0;
ComponentSettingsWindow.Opacity = 1;
} }
private void OpenDailyArtworkComponentSettings() private void OpenDailyArtworkComponentSettings(DesktopComponentPlacementSnapshot placement)
{ {
if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null)
{
return;
}
var settingsContent = new DailyArtworkSettingsWindow(); var settingsContent = new DailyArtworkSettingsWindow();
settingsContent.SettingsChanged += OnDailyArtworkSettingsChanged; settingsContent.SettingsChanged += OnDailyArtworkSettingsChanged;
ComponentSettingsContentHost.Content = settingsContent; ShowComponentSettings(settingsContent, placement);
ComponentSettingsWindow.IsVisible = true;
ComponentSettingsWindow.Opacity = 0;
ComponentSettingsWindow.Opacity = 1;
} }
private void OpenCnrDailyNewsComponentSettings() private void OpenCnrDailyNewsComponentSettings(DesktopComponentPlacementSnapshot placement)
{ {
if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null)
{
return;
}
var settingsContent = new CnrDailyNewsSettingsWindow(); var settingsContent = new CnrDailyNewsSettingsWindow();
settingsContent.SettingsChanged += OnCnrDailyNewsSettingsChanged; settingsContent.SettingsChanged += OnCnrDailyNewsSettingsChanged;
ComponentSettingsContentHost.Content = settingsContent; ShowComponentSettings(settingsContent, placement);
ComponentSettingsWindow.IsVisible = true;
ComponentSettingsWindow.Opacity = 0;
ComponentSettingsWindow.Opacity = 1;
} }
private void OpenIfengNewsComponentSettings() private void OpenIfengNewsComponentSettings(DesktopComponentPlacementSnapshot placement)
{ {
if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null)
{
return;
}
var settingsContent = new IfengNewsSettingsWindow(); var settingsContent = new IfengNewsSettingsWindow();
settingsContent.SettingsChanged += OnIfengNewsSettingsChanged; settingsContent.SettingsChanged += OnIfengNewsSettingsChanged;
ComponentSettingsContentHost.Content = settingsContent; ShowComponentSettings(settingsContent, placement);
ComponentSettingsWindow.IsVisible = true;
ComponentSettingsWindow.Opacity = 0;
ComponentSettingsWindow.Opacity = 1;
} }
private void OpenDailyWordComponentSettings() private void OpenDailyWordComponentSettings(DesktopComponentPlacementSnapshot placement)
{ {
if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null)
{
return;
}
var settingsContent = new DailyWordSettingsWindow(); var settingsContent = new DailyWordSettingsWindow();
settingsContent.SettingsChanged += OnDailyWordSettingsChanged; settingsContent.SettingsChanged += OnDailyWordSettingsChanged;
ComponentSettingsContentHost.Content = settingsContent; ShowComponentSettings(settingsContent, placement);
ComponentSettingsWindow.IsVisible = true;
ComponentSettingsWindow.Opacity = 0;
ComponentSettingsWindow.Opacity = 1;
} }
private void OpenBilibiliHotSearchComponentSettings() private void OpenBilibiliHotSearchComponentSettings(DesktopComponentPlacementSnapshot placement)
{ {
if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null)
{
return;
}
var settingsContent = new BilibiliHotSearchSettingsWindow(); var settingsContent = new BilibiliHotSearchSettingsWindow();
settingsContent.SettingsChanged += OnBilibiliHotSearchSettingsChanged; settingsContent.SettingsChanged += OnBilibiliHotSearchSettingsChanged;
ComponentSettingsContentHost.Content = settingsContent; ShowComponentSettings(settingsContent, placement);
ComponentSettingsWindow.IsVisible = true;
ComponentSettingsWindow.Opacity = 0;
ComponentSettingsWindow.Opacity = 1;
} }
private void OpenBaiduHotSearchComponentSettings() private void OpenBaiduHotSearchComponentSettings(DesktopComponentPlacementSnapshot placement)
{ {
if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null)
{
return;
}
var settingsContent = new BaiduHotSearchSettingsWindow(); var settingsContent = new BaiduHotSearchSettingsWindow();
settingsContent.SettingsChanged += OnBaiduHotSearchSettingsChanged; settingsContent.SettingsChanged += OnBaiduHotSearchSettingsChanged;
ComponentSettingsContentHost.Content = settingsContent; ShowComponentSettings(settingsContent, placement);
ComponentSettingsWindow.IsVisible = true;
ComponentSettingsWindow.Opacity = 0;
ComponentSettingsWindow.Opacity = 1;
} }
private void OpenStcn24ForumComponentSettings() private void OpenStcn24ForumComponentSettings(DesktopComponentPlacementSnapshot placement)
{ {
if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null)
{
return;
}
var settingsContent = new Stcn24ForumSettingsWindow(); var settingsContent = new Stcn24ForumSettingsWindow();
settingsContent.SettingsChanged += OnStcn24ForumSettingsChanged; settingsContent.SettingsChanged += OnStcn24ForumSettingsChanged;
ComponentSettingsContentHost.Content = settingsContent; ShowComponentSettings(settingsContent, placement);
ComponentSettingsWindow.IsVisible = true;
ComponentSettingsWindow.Opacity = 0;
ComponentSettingsWindow.Opacity = 1;
} }
private void OnClassScheduleSettingsChanged(object? sender, EventArgs e) private void OnClassScheduleSettingsChanged(object? sender, EventArgs e)
@@ -1408,6 +1327,7 @@ public partial class MainWindow
foreach (var placement in placementsToRemove) foreach (var placement in placementsToRemove)
{ {
_desktopComponentPlacements.Remove(placement); _desktopComponentPlacements.Remove(placement);
_componentSettingsService.DeleteForComponent(placement.ComponentId, placement.PlacementId);
} }
_desktopPageCount = Math.Clamp(_desktopPageCount - 1, MinDesktopPageCount, MaxDesktopPageCount); _desktopPageCount = Math.Clamp(_desktopPageCount - 1, MinDesktopPageCount, MaxDesktopPageCount);
@@ -1601,7 +1521,7 @@ public partial class MainWindow
placement.PlacementId = Guid.NewGuid().ToString("N"); placement.PlacementId = Guid.NewGuid().ToString("N");
} }
var component = CreateDesktopComponentControl(placement.ComponentId); var component = CreateDesktopComponentControl(placement.ComponentId, placement.PlacementId);
if (component is null) if (component is null)
{ {
return null; return null;
@@ -2035,7 +1955,7 @@ public partial class MainWindow
return onLeft || onRight || onTop || onBottom; 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)) if (!_componentRuntimeRegistry.TryGetDescriptor(componentId, out var runtimeDescriptor))
{ {
@@ -2047,7 +1967,9 @@ public partial class MainWindow
_timeZoneService, _timeZoneService,
_weatherDataService, _weatherDataService,
_recommendationInfoService, _recommendationInfoService,
_calculatorDataService); _calculatorDataService,
_componentSettingsService,
placementId);
component.Classes.Add(DesktopComponentClass); component.Classes.Add(DesktopComponentClass);
return component; return component;
} }
@@ -3195,7 +3117,8 @@ public partial class MainWindow
_timeZoneService, _timeZoneService,
_weatherDataService, _weatherDataService,
_recommendationInfoService, _recommendationInfoService,
_calculatorDataService); _calculatorDataService,
_componentSettingsService);
// Component library previews must stay non-interactive so drag gesture is reliable. // Component library previews must stay non-interactive so drag gesture is reliable.
previewControl.IsHitTestVisible = false; previewControl.IsHitTestVisible = false;
previewControl.Focusable = false; previewControl.Focusable = false;

View File

@@ -38,8 +38,9 @@
## 当前状态 ## 当前状态
- 项目包含桌面端与推荐后端两个子项目,并在同一 solution 中维护。 - 项目包含桌面端与推荐后端两个子项目,并在同一 solution 中维护。
- 配置默认写入本地:`%LOCALAPPDATA%\LanMountainDesktop\settings.json` - 通用应用配置默认写入本地:`%LOCALAPPDATA%\LanMountainDesktop\settings.json`
- 启动台与桌面布局已拆分到独立文件:`%LOCALAPPDATA%\LanMountainDesktop\launcher-settings.json``%LOCALAPPDATA%\LanMountainDesktop\desktop-layout-settings.json` - 启动台与桌面布局已拆分到独立文件:`%LOCALAPPDATA%\LanMountainDesktop\launcher-settings.json``%LOCALAPPDATA%\LanMountainDesktop\desktop-layout-settings.json`
- 组件配置统一写入:`%LOCALAPPDATA%\LanMountainDesktop\component-settings.json`;同类组件按实例 `componentId::placementId` 隔离存储,同时预留插件专属配置区。
- 当前体验以 Windows 为主要目标平台。 - 当前体验以 Windows 为主要目标平台。
## 运行说明 ## 运行说明