Files
Sharepoint-Toolbox/SharepointToolbox/Services/ThemeManager.cs
T
Dev f4cc81bb71 chore: release v2.4
- Add theme system (Dark/Light palettes, ModernTheme, ThemeManager)
- Add InputDialog, Spinner common view
- Add DuplicatesCsvExportService
- Refresh views, dialogs, and view models across tabs
- Update localization strings (en/fr)
- Tweak services (transfer, permissions, search, user access, ownership elevation, bulk operations)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 11:23:11 +02:00

136 lines
4.0 KiB
C#

using System.Windows;
using Microsoft.Win32;
using Microsoft.Extensions.Logging;
namespace SharepointToolbox.Services;
public enum ThemeMode { System, Light, Dark }
/// <summary>
/// Swaps the merged palette dictionary at runtime so all DynamicResource brush lookups retint live.
/// "System" mode reads HKCU AppsUseLightTheme (0 = dark, 1 = light) and subscribes to system theme changes.
/// </summary>
public class ThemeManager
{
private const string LightPaletteSource = "pack://application:,,,/Themes/LightPalette.xaml";
private const string DarkPaletteSource = "pack://application:,,,/Themes/DarkPalette.xaml";
private const string PersonalizeKey = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
private readonly ILogger<ThemeManager> _logger;
private ThemeMode _mode = ThemeMode.System;
private bool _systemSubscribed;
public event EventHandler? ThemeChanged;
public ThemeMode Mode => _mode;
public bool IsDarkActive { get; private set; }
public ThemeManager(ILogger<ThemeManager> logger)
{
_logger = logger;
}
public void ApplyMode(ThemeMode mode)
{
_mode = mode;
bool dark = ResolveDark(mode);
ApplyPalette(dark);
EnsureSystemSubscription(mode);
}
public void ApplyFromString(string? value)
{
var mode = (value ?? "System") switch
{
"Light" => ThemeMode.Light,
"Dark" => ThemeMode.Dark,
_ => ThemeMode.System,
};
ApplyMode(mode);
}
private bool ResolveDark(ThemeMode mode) => mode switch
{
ThemeMode.Light => false,
ThemeMode.Dark => true,
_ => ReadSystemPrefersDark(),
};
private bool ReadSystemPrefersDark()
{
try
{
using var key = Registry.CurrentUser.OpenSubKey(PersonalizeKey);
if (key?.GetValue("AppsUseLightTheme") is int v)
return v == 0;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to read system theme preference, defaulting to light");
}
return false;
}
private void ApplyPalette(bool dark)
{
var app = Application.Current;
if (app is null) return;
var newPalette = new ResourceDictionary
{
Source = new Uri(dark ? DarkPaletteSource : LightPaletteSource, UriKind.Absolute)
};
var dicts = app.Resources.MergedDictionaries;
int replaced = -1;
for (int i = 0; i < dicts.Count; i++)
{
var src = dicts[i].Source?.OriginalString ?? string.Empty;
if (src.EndsWith("LightPalette.xaml", StringComparison.OrdinalIgnoreCase) ||
src.EndsWith("DarkPalette.xaml", StringComparison.OrdinalIgnoreCase))
{
replaced = i;
break;
}
}
if (replaced >= 0)
dicts[replaced] = newPalette;
else
dicts.Insert(0, newPalette);
IsDarkActive = dark;
ThemeChanged?.Invoke(this, EventArgs.Empty);
}
private void EnsureSystemSubscription(ThemeMode mode)
{
if (mode == ThemeMode.System && !_systemSubscribed)
{
SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged;
_systemSubscribed = true;
}
else if (mode != ThemeMode.System && _systemSubscribed)
{
SystemEvents.UserPreferenceChanged -= OnUserPreferenceChanged;
_systemSubscribed = false;
}
}
private void OnUserPreferenceChanged(object? sender, UserPreferenceChangedEventArgs e)
{
if (e.Category != UserPreferenceCategory.General) return;
if (_mode != ThemeMode.System) return;
var app = Application.Current;
if (app is null) return;
app.Dispatcher.BeginInvoke(new Action(() =>
{
bool dark = ReadSystemPrefersDark();
if (dark != IsDarkActive)
ApplyPalette(dark);
}));
}
}