diff --git a/src/TeamsISO.App.WinUI/Services/ThemeManager.cs b/src/TeamsISO.App.WinUI/Services/ThemeManager.cs index 1553263..5e4880d 100644 --- a/src/TeamsISO.App.WinUI/Services/ThemeManager.cs +++ b/src/TeamsISO.App.WinUI/Services/ThemeManager.cs @@ -26,11 +26,26 @@ public sealed class ThemeManager { _uiSettings = new UISettings(); _uiSettings.ColorValuesChanged += OnSystemColorsChanged; + + // Hydrate the preference from disk so the operator's choice + // survives across launches. Defaults to "System" if the prefs + // file is missing or unreadable (Load() catches its own errors). + try + { + var prefs = UIPreferences.Load(); + if (prefs.Theme == "System" || prefs.Theme == "Dark" || prefs.Theme == "Light") + { + _preference = prefs.Theme; + } + } + catch + { + // Defensive — ThemeManager.Current is a static singleton; a + // throw here would prevent the app from getting any theme. + } } private readonly UISettings _uiSettings; - // Default: System (follow OS app-mode). Override at runtime via Set(); - // persistence to UIPreferences.Theme lands in the view-model commit. private string _preference = "System"; public string Preference => _preference; @@ -63,7 +78,7 @@ public sealed class ThemeManager return ResolveTheme(); } - /// Set the preference and broadcast the resolved theme. + /// Set the preference, persist to disk, broadcast the resolved theme. public void Set(string preference) { if (preference != "System" && preference != "Dark" && preference != "Light") @@ -72,6 +87,8 @@ public sealed class ThemeManager } _preference = preference; + try { UIPreferences.SetTheme(preference); } + catch { /* persistence is best-effort */ } Themed?.Invoke(this, ResolveTheme()); } diff --git a/src/TeamsISO.App.WinUI/Services/UIPreferences.cs b/src/TeamsISO.App.WinUI/Services/UIPreferences.cs new file mode 100644 index 0000000..da5a717 --- /dev/null +++ b/src/TeamsISO.App.WinUI/Services/UIPreferences.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; +using System.Text.Json; + +namespace TeamsISO.App.WinUI.Services; + +/// +/// Persistent UI-side toggles shared between hosts via JSON at +/// %LOCALAPPDATA%\TeamsISO\ui-prefs.json. The WPF host's copy at +/// src/TeamsISO.App/Services/UIPreferences.cs reads/writes the same +/// file with the same record shape — new fields added in one host's +/// copy degrade gracefully in the other (JSON deserializer ignores +/// unknown fields, missing fields fall back to defaults). +/// +/// The WinUI copy adds the Theme field which the WPF host doesn't +/// know about yet; when the WPF host's UIPreferences gets the same +/// field, both hosts will share the operator's theme choice across +/// host swaps. +/// +public static class UIPreferences +{ + private static readonly object _gate = new(); + + private static string PrefsPath => + Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "TeamsISO", "ui-prefs.json"); + + public enum SortMode { JoinOrder, Alphabetical, OnlineFirst, LoudestFirst } + + /// The on-disk shape. New fields added here become opt-in for older files via default values. + public sealed record Prefs( + bool HideLocalSelf = true, + bool AutoDisableOnDeparture = false, + SortMode ParticipantSort = SortMode.JoinOrder, + bool MinimizeToTray = false, + bool ControlSurfaceLanReachable = false, + bool LaunchTeamsOnStartup = false, + bool AutoHideTeamsWindows = false, + bool AutoRecordOnCall = false, + bool EmbedTeamsWindow = false, + // Theme preference: "System" (follow OS app-mode), "Dark", or + // "Light". WinUI host owns the value for now; WPF host gets the + // same field in its UIPreferences when its theme system lands. + string Theme = "System"); + + public static Prefs Load() + { + try + { + if (!File.Exists(PrefsPath)) return new Prefs(); + var json = File.ReadAllText(PrefsPath); + return JsonSerializer.Deserialize(json) ?? new Prefs(); + } + catch + { + return new Prefs(); + } + } + + public static void Save(Prefs prefs) + { + try + { + lock (_gate) + { + var dir = Path.GetDirectoryName(PrefsPath); + if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir); + var json = JsonSerializer.Serialize(prefs, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(PrefsPath, json); + } + } + catch + { + // Disk full / permission denied — in-memory state still holds for this session. + } + } + + /// Update just the Theme field without touching other prefs. + public static void SetTheme(string theme) + { + var current = Load(); + Save(current with { Theme = theme }); + } +}