diff --git a/Wino.Core.UWP/Services/ThemeService.cs b/Wino.Core.UWP/Services/ThemeService.cs
index 54f1bbd8..50d5d681 100644
--- a/Wino.Core.UWP/Services/ThemeService.cs
+++ b/Wino.Core.UWP/Services/ThemeService.cs
@@ -7,7 +7,6 @@ using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
-using Newtonsoft.Json;
using Windows.Storage;
using Windows.UI.ViewManagement;
@@ -21,6 +20,8 @@ using Wino.Core.Messages.Shell;
using Wino.Core.UWP.Extensions;
using Wino.Core.UWP.Models.Personalization;
using Wino.Core.UWP.Services;
+using System.Text.Json;
+using Wino.Core.WinUI.Services;
#if NET8_0
using Microsoft.UI.Xaml.Controls;
@@ -38,432 +39,419 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Markup;
#endif
-namespace Wino.Services
+
+namespace Wino.Services;
+
+///
+/// Class providing functionality around switching and restoring theme settings
+///
+public class ThemeService(IConfigurationService configurationService,
+ IUnderlyingThemeService underlyingThemeService,
+ IApplicationResourceManager applicationResourceManager,
+ IAppShellService appShellService) : IThemeService
{
+ public const string CustomThemeFolderName = "CustomThemes";
+
+ private const string MicaThemeId = "a160b1b0-2ab8-4e97-a803-f4050f036e25";
+ private const string AcrylicThemeId = "fc08e58c-36fd-46e2-a562-26cf277f1467";
+ private const string CloudsThemeId = "3b621cc2-e270-4a76-8477-737917cccda0";
+ private const string ForestThemeId = "8bc89b37-a7c5-4049-86e2-de1ae8858dbd";
+ private const string NightyThemeId = "5b65e04e-fd7e-4c2d-8221-068d3e02d23a";
+ private const string SnowflakeThemeId = "e143ddde-2e28-4846-9d98-dad63d6505f1";
+ private const string GardenThemeId = "698e4466-f88c-4799-9c61-f0ea1308ed49";
+
+ private Frame _mainApplicationFrame;
+
+ public event EventHandler ElementThemeChanged;
+ public event EventHandler AccentColorChanged;
+ public event EventHandler AccentColorChangedBySystem;
+
+ private const string AccentColorKey = nameof(AccentColorKey);
+ private const string CurrentApplicationThemeKey = nameof(CurrentApplicationThemeKey);
+
+ // Custom theme
+ public const string CustomThemeAccentColorKey = nameof(CustomThemeAccentColorKey);
+
+ // Keep reference so it does not get optimized/garbage collected
+ private readonly UISettings _uiSettings = new();
+
+ private readonly IConfigurationService _configurationService = configurationService;
+ private readonly IUnderlyingThemeService _underlyingThemeService = underlyingThemeService;
+ private readonly IApplicationResourceManager _applicationResourceManager = applicationResourceManager;
+ private readonly IAppShellService _appShellService = appShellService;
+
+ private List _preDefinedThemes{ get; } =
+ [
+ new SystemAppTheme("Mica", Guid.Parse(MicaThemeId)),
+ new SystemAppTheme("Acrylic", Guid.Parse(AcrylicThemeId)),
+ new PreDefinedAppTheme("Nighty", Guid.Parse(NightyThemeId), "#e1b12c", ApplicationElementTheme.Dark),
+ new PreDefinedAppTheme("Forest", Guid.Parse(ForestThemeId), "#16a085", ApplicationElementTheme.Dark),
+ new PreDefinedAppTheme("Clouds", Guid.Parse(CloudsThemeId), "#0984e3", ApplicationElementTheme.Light),
+ new PreDefinedAppTheme("Snowflake", Guid.Parse(SnowflakeThemeId), "#4a69bd", ApplicationElementTheme.Light),
+ new PreDefinedAppTheme("Garden", Guid.Parse(GardenThemeId), "#05c46b", ApplicationElementTheme.Light),
+ ];
+
///
- /// Class providing functionality around switching and restoring theme settings
+ /// Gets or sets (with LocalSettings persistence) the RequestedTheme of the root element.
///
- public class ThemeService : IThemeService
+ public ApplicationElementTheme RootTheme
{
- public const string CustomThemeFolderName = "CustomThemes";
-
- private static string _micaThemeId = "a160b1b0-2ab8-4e97-a803-f4050f036e25";
- private static string _acrylicThemeId = "fc08e58c-36fd-46e2-a562-26cf277f1467";
- private static string _cloudsThemeId = "3b621cc2-e270-4a76-8477-737917cccda0";
- private static string _forestThemeId = "8bc89b37-a7c5-4049-86e2-de1ae8858dbd";
- private static string _nightyThemeId = "5b65e04e-fd7e-4c2d-8221-068d3e02d23a";
- private static string _snowflakeThemeId = "e143ddde-2e28-4846-9d98-dad63d6505f1";
- private static string _gardenThemeId = "698e4466-f88c-4799-9c61-f0ea1308ed49";
-
- private Frame mainApplicationFrame = null;
-
- public event EventHandler ElementThemeChanged;
- public event EventHandler AccentColorChanged;
- public event EventHandler AccentColorChangedBySystem;
-
- private const string AccentColorKey = nameof(AccentColorKey);
- private const string CurrentApplicationThemeKey = nameof(CurrentApplicationThemeKey);
-
- // Custom theme
- public const string CustomThemeAccentColorKey = nameof(CustomThemeAccentColorKey);
-
- // Keep reference so it does not get optimized/garbage collected
- private readonly UISettings uiSettings = new UISettings();
-
- private readonly IConfigurationService _configurationService;
- private readonly IUnderlyingThemeService _underlyingThemeService;
- private readonly IApplicationResourceManager _applicationResourceManager;
-
- private List preDefinedThemes { get; set; } = new List()
+ get => (_mainApplicationFrame?.RequestedTheme.ToWinoElementTheme()) ?? ApplicationElementTheme.Default;
+ set
{
- new SystemAppTheme("Mica", Guid.Parse(_micaThemeId)),
- new SystemAppTheme("Acrylic", Guid.Parse(_acrylicThemeId)),
- new PreDefinedAppTheme("Nighty", Guid.Parse(_nightyThemeId), "#e1b12c", ApplicationElementTheme.Dark),
- new PreDefinedAppTheme("Forest", Guid.Parse(_forestThemeId), "#16a085", ApplicationElementTheme.Dark),
- new PreDefinedAppTheme("Clouds", Guid.Parse(_cloudsThemeId), "#0984e3", ApplicationElementTheme.Light),
- new PreDefinedAppTheme("Snowflake", Guid.Parse(_snowflakeThemeId), "#4a69bd", ApplicationElementTheme.Light),
- new PreDefinedAppTheme("Garden", Guid.Parse(_gardenThemeId), "#05c46b", ApplicationElementTheme.Light),
- };
-
- public ThemeService(IConfigurationService configurationService,
- IUnderlyingThemeService underlyingThemeService,
- IApplicationResourceManager applicationResourceManager)
- {
- _configurationService = configurationService;
- _underlyingThemeService = underlyingThemeService;
- _applicationResourceManager = applicationResourceManager;
- }
-
- ///
- /// Gets or sets (with LocalSettings persistence) the RequestedTheme of the root element.
- ///
- public ApplicationElementTheme RootTheme
- {
- get
- {
- if (mainApplicationFrame == null) return ApplicationElementTheme.Default;
-
- return mainApplicationFrame.RequestedTheme.ToWinoElementTheme();
- }
- set
- {
- if (mainApplicationFrame == null)
- return;
-
- mainApplicationFrame.RequestedTheme = value.ToWindowsElementTheme();
-
- _configurationService.Set(UnderlyingThemeService.SelectedAppThemeKey, value);
-
- UpdateSystemCaptionButtonColors();
-
- // PopupRoot usually needs to react to changes.
- NotifyThemeUpdate();
- }
- }
-
-
- private Guid currentApplicationThemeId;
-
- public Guid CurrentApplicationThemeId
- {
- get { return currentApplicationThemeId; }
- set
- {
- currentApplicationThemeId = value;
-
- _configurationService.Set(CurrentApplicationThemeKey, value);
-
- _ = mainApplicationFrame.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, async () =>
- {
- await ApplyCustomThemeAsync(false);
- });
- }
- }
-
-
- private string accentColor;
-
- public string AccentColor
- {
- get { return accentColor; }
- set
- {
- accentColor = value;
-
- UpdateAccentColor(value);
-
- _configurationService.Set(AccentColorKey, value);
- AccentColorChanged?.Invoke(this, value);
- }
- }
-
- public async Task InitializeAsync()
- {
- // Already initialized. There is no need.
- if (mainApplicationFrame != null)
+ if (_mainApplicationFrame == null)
return;
- // Save reference as this might be null when the user is in another app
+ _mainApplicationFrame.RequestedTheme = value.ToWindowsElementTheme();
+ _configurationService.Set(UnderlyingThemeService.SelectedAppThemeKey, value);
-#if NET8_0
- // WinUI
-#else
- mainApplicationFrame = Window.Current.Content as Frame;
-#endif
-
-
- if (mainApplicationFrame == null) return;
-
- RootTheme = _configurationService.Get(UnderlyingThemeService.SelectedAppThemeKey, ApplicationElementTheme.Default);
- AccentColor = _configurationService.Get(AccentColorKey, string.Empty);
-
- // Set the current theme id. Default to Mica.
- var applicationThemeGuid = _configurationService.Get(CurrentApplicationThemeKey, _micaThemeId);
-
- currentApplicationThemeId = Guid.Parse(applicationThemeGuid);
-
- await ApplyCustomThemeAsync(true);
-
- // Registering to color changes, thus we notice when user changes theme system wide
- uiSettings.ColorValuesChanged += UISettingsColorChanged;
- }
-
- private void NotifyThemeUpdate()
- {
- if (mainApplicationFrame == null || mainApplicationFrame.Dispatcher == null) return;
-
- _ = mainApplicationFrame.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
- {
- ElementThemeChanged?.Invoke(this, RootTheme);
- WeakReferenceMessenger.Default.Send(new ApplicationThemeChanged(_underlyingThemeService.IsUnderlyingThemeDark()));
- });
- }
-
- private void UISettingsColorChanged(UISettings sender, object args)
- {
- // Make sure we have a reference to our window so we dispatch a UI change
- if (mainApplicationFrame != null)
- {
- // Dispatch on UI thread so that we have a current appbar to access and change
-
- _ = mainApplicationFrame.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
- {
- UpdateSystemCaptionButtonColors();
-
- var accentColor = sender.GetColorValue(UIColorType.Accent);
- //AccentColorChangedBySystem?.Invoke(this, accentColor.ToHex());
- });
- }
+ UpdateSystemCaptionButtonColors();
+ // PopupRoot usually needs to react to changes.
NotifyThemeUpdate();
}
+ }
- public void UpdateSystemCaptionButtonColors()
+ private Guid currentApplicationThemeId;
+
+ public Guid CurrentApplicationThemeId
+ {
+ get => currentApplicationThemeId;
+ set
{
- if (mainApplicationFrame == null) return;
+ currentApplicationThemeId = value;
- _ = mainApplicationFrame.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
+ _configurationService.Set(CurrentApplicationThemeKey, value);
+
+ _ = _mainApplicationFrame.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.High, async () => await ApplyCustomThemeAsync(false));
+ }
+ }
+
+ private string accentColor;
+
+ public string AccentColor
+ {
+ get => accentColor;
+ set
+ {
+ accentColor = value;
+
+ UpdateAccentColor(value);
+
+ _configurationService.Set(AccentColorKey, value);
+ AccentColorChanged?.Invoke(this, value);
+ }
+ }
+
+ public async Task InitializeAsync()
+ {
+ // Already initialized. There is no need.
+ if (_mainApplicationFrame != null)
+ return;
+
+ // Save reference as this might be null when the user is in another app
+#if NET8_0
+ _mainApplicationFrame = _appShellService.AppWindow.Content as Frame;
+#else
+ mainApplicationFrame = Window.Current.Content as Frame;
+#endif
+
+ if (_mainApplicationFrame == null) return;
+
+ RootTheme = _configurationService.Get(UnderlyingThemeService.SelectedAppThemeKey, ApplicationElementTheme.Default);
+ AccentColor = _configurationService.Get(AccentColorKey, string.Empty);
+
+ // Set the current theme id. Default to Mica.
+ var applicationThemeGuid = _configurationService.Get(CurrentApplicationThemeKey, MicaThemeId);
+
+ currentApplicationThemeId = Guid.Parse(applicationThemeGuid);
+
+ await ApplyCustomThemeAsync(true);
+
+ // Registering to color changes, thus we notice when user changes theme system wide
+ _uiSettings.ColorValuesChanged += UISettingsColorChanged;
+ }
+
+ private void NotifyThemeUpdate()
+ {
+ if (_mainApplicationFrame == null) return;
+
+ _ = _mainApplicationFrame.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.High, () =>
+ {
+ ElementThemeChanged?.Invoke(this, RootTheme);
+ WeakReferenceMessenger.Default.Send(new ApplicationThemeChanged(_underlyingThemeService.IsUnderlyingThemeDark()));
+ });
+ }
+
+ private void UISettingsColorChanged(UISettings sender, object args)
+ {
+ // Make sure we have a reference to our window so we dispatch a UI change
+ if (_mainApplicationFrame != null)
+ {
+ // Dispatch on UI thread so that we have a current appbar to access and change
+ _ = _mainApplicationFrame.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.High, () =>
{
- ApplicationViewTitleBar titleBar = ApplicationView.GetForCurrentView().TitleBar;
+ UpdateSystemCaptionButtonColors();
- if (titleBar == null) return;
-
- if (_underlyingThemeService.IsUnderlyingThemeDark())
- {
- titleBar.ButtonForegroundColor = Colors.White;
- }
- else
- {
- titleBar.ButtonForegroundColor = Colors.Black;
- }
+ var accentColor = sender.GetColorValue(UIColorType.Accent);
+ //AccentColorChangedBySystem?.Invoke(this, accentColor.ToHex());
});
}
- public void UpdateAccentColor(string hex)
+ NotifyThemeUpdate();
+ }
+
+ public void UpdateSystemCaptionButtonColors()
+ {
+ if (_mainApplicationFrame == null) return;
+
+ _ = _mainApplicationFrame.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () =>
{
- // Change accent color if specified.
- if (!string.IsNullOrEmpty(hex))
+ ApplicationViewTitleBar titleBar = null;
+ try
{
+ // TODO: Should be removed after migration to native titlebar.
+ titleBar = ApplicationView.GetForCurrentView().TitleBar;
+ }
+ catch { }
+
+ if (titleBar == null) return;
+
+ if (_underlyingThemeService.IsUnderlyingThemeDark())
+ {
+ titleBar.ButtonForegroundColor = Colors.White;
+ }
+ else
+ {
+ titleBar.ButtonForegroundColor = Colors.Black;
+ }
+ });
+ }
+
+ public void UpdateAccentColor(string hex)
+ {
+ // Change accent color if specified.
+ if (!string.IsNullOrEmpty(hex))
+ {
#if NET8_0
- var brush = new SolidColorBrush(CommunityToolkit.WinUI.Helpers.ColorHelper.ToColor(hex));
+ var brush = new SolidColorBrush(hex.ToColor());
#else
var brush = new SolidColorBrush(Microsoft.Toolkit.Uwp.Helpers.ColorHelper.ToColor(hex));
#endif
- if (_applicationResourceManager.ContainsResourceKey("SystemAccentColor"))
- _applicationResourceManager.ReplaceResource("SystemAccentColor", brush);
+ if (_applicationResourceManager.ContainsResourceKey("SystemAccentColor"))
+ _applicationResourceManager.ReplaceResource("SystemAccentColor", brush);
- if (_applicationResourceManager.ContainsResourceKey("NavigationViewSelectionIndicatorForeground"))
- _applicationResourceManager.ReplaceResource("NavigationViewSelectionIndicatorForeground", brush);
+ if (_applicationResourceManager.ContainsResourceKey("NavigationViewSelectionIndicatorForeground"))
+ _applicationResourceManager.ReplaceResource("NavigationViewSelectionIndicatorForeground", brush);
- RefreshThemeResource();
- }
+ RefreshThemeResource();
}
+ }
- private void RefreshThemeResource()
+ private void RefreshThemeResource()
+ {
+ if (_mainApplicationFrame == null) return;
+
+ if (_mainApplicationFrame.RequestedTheme == ElementTheme.Dark)
{
- if (mainApplicationFrame == null) return;
-
- if (mainApplicationFrame.RequestedTheme == ElementTheme.Dark)
- {
- mainApplicationFrame.RequestedTheme = ElementTheme.Light;
- mainApplicationFrame.RequestedTheme = ElementTheme.Dark;
- }
- else if (mainApplicationFrame.RequestedTheme == ElementTheme.Light)
- {
- mainApplicationFrame.RequestedTheme = ElementTheme.Dark;
- mainApplicationFrame.RequestedTheme = ElementTheme.Light;
- }
- else
- {
- var isUnderlyingDark = _underlyingThemeService.IsUnderlyingThemeDark();
-
- mainApplicationFrame.RequestedTheme = isUnderlyingDark ? ElementTheme.Light : ElementTheme.Dark;
- mainApplicationFrame.RequestedTheme = ElementTheme.Default;
- }
+ _mainApplicationFrame.RequestedTheme = ElementTheme.Light;
+ _mainApplicationFrame.RequestedTheme = ElementTheme.Dark;
}
-
- public async Task ApplyCustomThemeAsync(bool isInitializing)
+ else if (_mainApplicationFrame.RequestedTheme == ElementTheme.Light)
{
- AppThemeBase applyingTheme = null;
-
- var controlThemeList = new List(preDefinedThemes);
-
- // Don't search for custom themes if applying theme is already in pre-defined templates.
- // This is important for startup performance because we won't be loading the custom themes on launch.
-
- bool isApplyingPreDefinedTheme = preDefinedThemes.Exists(a => a.Id == currentApplicationThemeId);
-
- if (isApplyingPreDefinedTheme)
- {
- applyingTheme = preDefinedThemes.Find(a => a.Id == currentApplicationThemeId);
- }
- else
- {
- // User applied custom theme. Load custom themes and find it there.
- // Fallback to Mica if nothing found.
-
- var customThemes = await GetCurrentCustomThemesAsync();
-
- controlThemeList.AddRange(customThemes.Select(a => new CustomAppTheme(a)));
-
- applyingTheme = controlThemeList.Find(a => a.Id == currentApplicationThemeId) ?? preDefinedThemes.First(a => a.Id == Guid.Parse(_micaThemeId));
- }
-
- try
- {
- var existingThemeDictionary = _applicationResourceManager.GetLastResource();
-
- if (existingThemeDictionary != null && existingThemeDictionary.TryGetValue("ThemeName", out object themeNameString))
- {
- var themeName = themeNameString.ToString();
-
- // Applying different theme.
- if (themeName != applyingTheme.ThemeName)
- {
- var resourceDictionaryContent = await applyingTheme.GetThemeResourceDictionaryContentAsync();
-
- var resourceDictionary = XamlReader.Load(resourceDictionaryContent) as ResourceDictionary;
-
- // Custom themes require special attention for background image because
- // they share the same base theme resource dictionary.
-
- if (applyingTheme is CustomAppTheme)
- {
- resourceDictionary["ThemeBackgroundImage"] = $"ms-appdata:///local/{CustomThemeFolderName}/{applyingTheme.Id}.jpg";
- }
-
- _applicationResourceManager.RemoveResource(existingThemeDictionary);
- _applicationResourceManager.AddResource(resourceDictionary);
-
- bool isSystemTheme = applyingTheme is SystemAppTheme || applyingTheme is CustomAppTheme;
-
- if (isSystemTheme)
- {
- // For system themes, set the RootElement theme from saved values.
- // Potential bug: When we set it to system default, theme is not applied when system and
- // app element theme is different :)
-
- var savedElement = _configurationService.Get(UnderlyingThemeService.SelectedAppThemeKey, ApplicationElementTheme.Default);
- RootTheme = savedElement;
-
- // Quickly switch theme to apply theme resource changes.
- RefreshThemeResource();
- }
- else
- RootTheme = applyingTheme.ForceElementTheme;
-
- // Theme has accent color. Override.
- if (!isInitializing)
- {
- AccentColor = applyingTheme.AccentColor;
- }
- }
- else
- UpdateSystemCaptionButtonColors();
- }
- }
- catch (Exception ex)
- {
- Debug.WriteLine($"Apply theme failed -> {ex.Message}");
- }
+ _mainApplicationFrame.RequestedTheme = ElementTheme.Dark;
+ _mainApplicationFrame.RequestedTheme = ElementTheme.Light;
}
-
- public async Task> GetAvailableThemesAsync()
+ else
{
- var availableThemes = new List(preDefinedThemes);
+ var isUnderlyingDark = _underlyingThemeService.IsUnderlyingThemeDark();
+ _mainApplicationFrame.RequestedTheme = isUnderlyingDark ? ElementTheme.Light : ElementTheme.Dark;
+ _mainApplicationFrame.RequestedTheme = ElementTheme.Default;
+ }
+ }
+
+ public async Task ApplyCustomThemeAsync(bool isInitializing)
+ {
+ AppThemeBase applyingTheme = null;
+
+ List controlThemeList = [.. _preDefinedThemes];
+
+ // Don't search for custom themes if applying theme is already in pre-defined templates.
+ // This is important for startup performance because we won't be loading the custom themes on launch.
+
+ bool isApplyingPreDefinedTheme = _preDefinedThemes.Exists(a => a.Id == currentApplicationThemeId);
+
+ if (isApplyingPreDefinedTheme)
+ {
+ applyingTheme = _preDefinedThemes.Find(a => a.Id == currentApplicationThemeId);
+ }
+ else
+ {
+ // User applied custom theme. Load custom themes and find it there.
+ // Fallback to Mica if nothing found.
var customThemes = await GetCurrentCustomThemesAsync();
- availableThemes.AddRange(customThemes.Select(a => new CustomAppTheme(a)));
+ controlThemeList.AddRange(customThemes.Select(a => new CustomAppTheme(a)));
- return availableThemes;
+ applyingTheme = controlThemeList.Find(a => a.Id == currentApplicationThemeId) ?? _preDefinedThemes.First(a => a.Id == Guid.Parse(MicaThemeId));
}
- public async Task CreateNewCustomThemeAsync(string themeName, string accentColor, byte[] wallpaperData)
+ try
{
- if (wallpaperData == null || wallpaperData.Length == 0)
- throw new CustomThemeCreationFailedException(Translator.Exception_CustomThemeMissingWallpaper);
+ var existingThemeDictionary = _applicationResourceManager.GetLastResource();
- if (string.IsNullOrEmpty(themeName))
- throw new CustomThemeCreationFailedException(Translator.Exception_CustomThemeMissingName);
+ if (existingThemeDictionary == null || !existingThemeDictionary.TryGetValue("ThemeName", out object themeNameString)) return;
- var themes = await GetCurrentCustomThemesAsync();
+ var themeName = themeNameString.ToString();
- if (themes.Exists(a => a.Name == themeName))
- throw new CustomThemeCreationFailedException(Translator.Exception_CustomThemeExists);
-
- var newTheme = new CustomThemeMetadata()
+ // Applying different theme.
+ if (themeName != applyingTheme.ThemeName)
{
- Id = Guid.NewGuid(),
- Name = themeName,
- AccentColorHex = accentColor
- };
+ var resourceDictionaryContent = await applyingTheme.GetThemeResourceDictionaryContentAsync();
- // Save wallpaper.
- // Filename would be the same as metadata id, in jpg format.
+ var resourceDictionary = XamlReader.Load(resourceDictionaryContent) as ResourceDictionary;
- var themeFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(CustomThemeFolderName, CreationCollisionOption.OpenIfExists);
+ // Custom themes require special attention for background image because
+ // they share the same base theme resource dictionary.
- var wallpaperFile = await themeFolder.CreateFileAsync($"{newTheme.Id}.jpg", CreationCollisionOption.ReplaceExisting);
- await FileIO.WriteBytesAsync(wallpaperFile, wallpaperData);
+ if (applyingTheme is CustomAppTheme)
+ {
+ resourceDictionary["ThemeBackgroundImage"] = $"ms-appdata:///local/{CustomThemeFolderName}/{applyingTheme.Id}.jpg";
+ }
- // Generate thumbnail for settings page.
+ _applicationResourceManager.RemoveResource(existingThemeDictionary);
+ _applicationResourceManager.AddResource(resourceDictionary);
- var thumbnail = await wallpaperFile.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode.PicturesView);
- var thumbnailFile = await themeFolder.CreateFileAsync($"{newTheme.Id}_preview.jpg", CreationCollisionOption.ReplaceExisting);
+ bool isSystemTheme = applyingTheme is SystemAppTheme || applyingTheme is CustomAppTheme;
- using (var readerStream = thumbnail.AsStreamForRead())
- {
- byte[] bytes = new byte[readerStream.Length];
+ if (isSystemTheme)
+ {
+ // For system themes, set the RootElement theme from saved values.
+ // Potential bug: When we set it to system default, theme is not applied when system and
+ // app element theme is different :)
+ RootTheme = _configurationService.Get(UnderlyingThemeService.SelectedAppThemeKey, ApplicationElementTheme.Default);
- await readerStream.ReadAsync(bytes, 0, bytes.Length);
+ // Quickly switch theme to apply theme resource changes.
+ RefreshThemeResource();
+ }
+ else
+ {
+ RootTheme = applyingTheme.ForceElementTheme;
+ }
- var buffer = bytes.AsBuffer();
-
- await FileIO.WriteBufferAsync(thumbnailFile, buffer);
+ // Theme has accent color. Override.
+ if (!isInitializing)
+ {
+ AccentColor = applyingTheme.AccentColor;
+ }
}
-
- // Save metadata.
- var metadataFile = await themeFolder.CreateFileAsync($"{newTheme.Id}.json", CreationCollisionOption.ReplaceExisting);
-
- var serialized = JsonConvert.SerializeObject(newTheme);
- await FileIO.WriteTextAsync(metadataFile, serialized);
-
- return newTheme;
- }
-
- public async Task> GetCurrentCustomThemesAsync()
- {
- var results = new List();
-
- var themeFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(CustomThemeFolderName, CreationCollisionOption.OpenIfExists);
-
- var allFiles = await themeFolder.GetFilesAsync();
-
- var themeMetadatas = allFiles.Where(a => a.FileType == ".json");
-
- foreach (var theme in themeMetadatas)
+ else
{
- var metadata = await GetCustomMetadataAsync(theme).ConfigureAwait(false);
-
- if (metadata == null) continue;
-
- results.Add(metadata);
+ UpdateSystemCaptionButtonColors();
}
-
- return results;
}
-
- private async Task GetCustomMetadataAsync(IStorageFile file)
+ catch (Exception ex)
{
- var fileContent = await FileIO.ReadTextAsync(file);
-
- return JsonConvert.DeserializeObject(fileContent);
+ Debug.WriteLine($"Apply theme failed -> {ex.Message}");
}
-
- public string GetSystemAccentColorHex()
- => uiSettings.GetColorValue(UIColorType.Accent).ToHex();
}
+
+ public async Task> GetAvailableThemesAsync()
+ {
+ List availableThemes = [.. _preDefinedThemes];
+
+ var customThemes = await GetCurrentCustomThemesAsync();
+
+ availableThemes.AddRange(customThemes.Select(a => new CustomAppTheme(a)));
+
+ return availableThemes;
+ }
+
+ public async Task CreateNewCustomThemeAsync(string themeName, string accentColor, byte[] wallpaperData)
+ {
+ if (wallpaperData == null || wallpaperData.Length == 0)
+ throw new CustomThemeCreationFailedException(Translator.Exception_CustomThemeMissingWallpaper);
+
+ if (string.IsNullOrEmpty(themeName))
+ throw new CustomThemeCreationFailedException(Translator.Exception_CustomThemeMissingName);
+
+ var themes = await GetCurrentCustomThemesAsync();
+
+ if (themes.Exists(a => a.Name == themeName))
+ throw new CustomThemeCreationFailedException(Translator.Exception_CustomThemeExists);
+
+ var newTheme = new CustomThemeMetadata()
+ {
+ Id = Guid.NewGuid(),
+ Name = themeName,
+ AccentColorHex = accentColor
+ };
+
+ // Save wallpaper.
+ // Filename would be the same as metadata id, in jpg format.
+
+ var themeFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(CustomThemeFolderName, CreationCollisionOption.OpenIfExists);
+
+ var wallpaperFile = await themeFolder.CreateFileAsync($"{newTheme.Id}.jpg", CreationCollisionOption.ReplaceExisting);
+ await FileIO.WriteBytesAsync(wallpaperFile, wallpaperData);
+
+ // Generate thumbnail for settings page.
+
+ var thumbnail = await wallpaperFile.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode.PicturesView);
+ var thumbnailFile = await themeFolder.CreateFileAsync($"{newTheme.Id}_preview.jpg", CreationCollisionOption.ReplaceExisting);
+
+ await using (var readerStream = thumbnail.AsStreamForRead())
+ {
+ byte[] bytes = new byte[readerStream.Length];
+
+ await readerStream.ReadAsync(bytes);
+
+ var buffer = bytes.AsBuffer();
+
+ await FileIO.WriteBufferAsync(thumbnailFile, buffer);
+ }
+
+ // Save metadata.
+ var metadataFile = await themeFolder.CreateFileAsync($"{newTheme.Id}.json", CreationCollisionOption.ReplaceExisting);
+
+ var serialized = JsonSerializer.Serialize(newTheme);
+ await FileIO.WriteTextAsync(metadataFile, serialized);
+
+ return newTheme;
+ }
+
+ public async Task> GetCurrentCustomThemesAsync()
+ {
+ var results = new List();
+
+ var themeFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(CustomThemeFolderName, CreationCollisionOption.OpenIfExists);
+
+ var allFiles = await themeFolder.GetFilesAsync();
+
+ var themeMetadatas = allFiles.Where(a => a.FileType == ".json");
+
+ foreach (var theme in themeMetadatas)
+ {
+ var metadata = await GetCustomMetadataAsync(theme).ConfigureAwait(false);
+
+ if (metadata == null) continue;
+
+ results.Add(metadata);
+ }
+
+ return results;
+ }
+
+ private static async Task GetCustomMetadataAsync(IStorageFile file)
+ {
+ var fileContent = await FileIO.ReadTextAsync(file);
+
+ return JsonSerializer.Deserialize(fileContent);
+ }
+
+ public string GetSystemAccentColorHex()
+ => _uiSettings.GetColorValue(UIColorType.Accent).ToHex();
}