diff --git a/Wino.Core.UWP/Services/ThemeService.cs b/Wino.Core.UWP/Services/ThemeService.cs
index 50d5d681..54f1bbd8 100644
--- a/Wino.Core.UWP/Services/ThemeService.cs
+++ b/Wino.Core.UWP/Services/ThemeService.cs
@@ -7,6 +7,7 @@ using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
+using Newtonsoft.Json;
using Windows.Storage;
using Windows.UI.ViewManagement;
@@ -20,8 +21,6 @@ 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;
@@ -39,419 +38,432 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Markup;
#endif
-
-namespace Wino.Services;
-
-///
-/// Class providing functionality around switching and restoring theme settings
-///
-public class ThemeService(IConfigurationService configurationService,
- IUnderlyingThemeService underlyingThemeService,
- IApplicationResourceManager applicationResourceManager,
- IAppShellService appShellService) : IThemeService
+namespace Wino.Services
{
- 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),
- ];
-
///
- /// Gets or sets (with LocalSettings persistence) the RequestedTheme of the root element.
+ /// Class providing functionality around switching and restoring theme settings
///
- public ApplicationElementTheme RootTheme
+ public class ThemeService : IThemeService
{
- get => (_mainApplicationFrame?.RequestedTheme.ToWinoElementTheme()) ?? ApplicationElementTheme.Default;
- set
+ 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()
{
- if (_mainApplicationFrame == null)
- return;
+ 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),
+ };
- _mainApplicationFrame.RequestedTheme = value.ToWindowsElementTheme();
-
- _configurationService.Set(UnderlyingThemeService.SelectedAppThemeKey, value);
-
- UpdateSystemCaptionButtonColors();
-
- // PopupRoot usually needs to react to changes.
- NotifyThemeUpdate();
+ public ThemeService(IConfigurationService configurationService,
+ IUnderlyingThemeService underlyingThemeService,
+ IApplicationResourceManager applicationResourceManager)
+ {
+ _configurationService = configurationService;
+ _underlyingThemeService = underlyingThemeService;
+ _applicationResourceManager = applicationResourceManager;
}
- }
- private Guid currentApplicationThemeId;
-
- public Guid CurrentApplicationThemeId
- {
- get => currentApplicationThemeId;
- set
+ ///
+ /// Gets or sets (with LocalSettings persistence) the RequestedTheme of the root element.
+ ///
+ public ApplicationElementTheme RootTheme
{
- currentApplicationThemeId = value;
-
- _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, () =>
+ 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();
- var accentColor = sender.GetColorValue(UIColorType.Accent);
- //AccentColorChangedBySystem?.Invoke(this, accentColor.ToHex());
+ // 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)
+ return;
+
+ // Save reference as this might be null when the user is in another app
+
+
+#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()));
});
}
- NotifyThemeUpdate();
- }
-
- public void UpdateSystemCaptionButtonColors()
- {
- if (_mainApplicationFrame == null) return;
-
- _ = _mainApplicationFrame.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () =>
+ private void UISettingsColorChanged(UISettings sender, object args)
{
- ApplicationViewTitleBar titleBar = null;
- try
+ // Make sure we have a reference to our window so we dispatch a UI change
+ if (mainApplicationFrame != null)
{
- // TODO: Should be removed after migration to native titlebar.
- titleBar = ApplicationView.GetForCurrentView().TitleBar;
- }
- catch { }
+ // Dispatch on UI thread so that we have a current appbar to access and change
- if (titleBar == null) return;
+ _ = mainApplicationFrame.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
+ {
+ UpdateSystemCaptionButtonColors();
- if (_underlyingThemeService.IsUnderlyingThemeDark())
- {
- titleBar.ButtonForegroundColor = Colors.White;
+ var accentColor = sender.GetColorValue(UIColorType.Accent);
+ //AccentColorChangedBySystem?.Invoke(this, accentColor.ToHex());
+ });
}
- else
- {
- titleBar.ButtonForegroundColor = Colors.Black;
- }
- });
- }
- public void UpdateAccentColor(string hex)
- {
- // Change accent color if specified.
- if (!string.IsNullOrEmpty(hex))
+ NotifyThemeUpdate();
+ }
+
+ public void UpdateSystemCaptionButtonColors()
{
+ if (mainApplicationFrame == null) return;
+
+ _ = mainApplicationFrame.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
+ {
+ ApplicationViewTitleBar titleBar = ApplicationView.GetForCurrentView().TitleBar;
+
+ 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(hex.ToColor());
+ var brush = new SolidColorBrush(CommunityToolkit.WinUI.Helpers.ColorHelper.ToColor(hex));
#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();
- }
- }
-
- private void RefreshThemeResource()
- {
- 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;
- }
- }
-
- 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();
-
- controlThemeList.AddRange(customThemes.Select(a => new CustomAppTheme(a)));
-
- applyingTheme = controlThemeList.Find(a => a.Id == currentApplicationThemeId) ?? _preDefinedThemes.First(a => a.Id == Guid.Parse(MicaThemeId));
+ RefreshThemeResource();
+ }
}
- try
+ private void RefreshThemeResource()
{
- var existingThemeDictionary = _applicationResourceManager.GetLastResource();
+ if (mainApplicationFrame == null) return;
- if (existingThemeDictionary == null || !existingThemeDictionary.TryGetValue("ThemeName", out object themeNameString)) return;
-
- var themeName = themeNameString.ToString();
-
- // Applying different theme.
- if (themeName != applyingTheme.ThemeName)
+ if (mainApplicationFrame.RequestedTheme == ElementTheme.Dark)
{
- 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 :)
- RootTheme = _configurationService.Get(UnderlyingThemeService.SelectedAppThemeKey, ApplicationElementTheme.Default);
-
- // Quickly switch theme to apply theme resource changes.
- RefreshThemeResource();
- }
- else
- {
- RootTheme = applyingTheme.ForceElementTheme;
- }
-
- // Theme has accent color. Override.
- if (!isInitializing)
- {
- AccentColor = applyingTheme.AccentColor;
- }
+ mainApplicationFrame.RequestedTheme = ElementTheme.Light;
+ mainApplicationFrame.RequestedTheme = ElementTheme.Dark;
+ }
+ else if (mainApplicationFrame.RequestedTheme == ElementTheme.Light)
+ {
+ mainApplicationFrame.RequestedTheme = ElementTheme.Dark;
+ mainApplicationFrame.RequestedTheme = ElementTheme.Light;
}
else
{
- UpdateSystemCaptionButtonColors();
+ var isUnderlyingDark = _underlyingThemeService.IsUnderlyingThemeDark();
+
+ mainApplicationFrame.RequestedTheme = isUnderlyingDark ? ElementTheme.Light : ElementTheme.Dark;
+ mainApplicationFrame.RequestedTheme = ElementTheme.Default;
}
}
- catch (Exception ex)
+
+ public async Task ApplyCustomThemeAsync(bool isInitializing)
{
- Debug.WriteLine($"Apply theme failed -> {ex.Message}");
- }
- }
+ AppThemeBase applyingTheme = null;
- public async Task> GetAvailableThemesAsync()
- {
- List availableThemes = [.. _preDefinedThemes];
+ var controlThemeList = new List(preDefinedThemes);
- var customThemes = await GetCurrentCustomThemesAsync();
+ // 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.
- availableThemes.AddRange(customThemes.Select(a => new CustomAppTheme(a)));
+ bool isApplyingPreDefinedTheme = preDefinedThemes.Exists(a => a.Id == currentApplicationThemeId);
- return availableThemes;
- }
+ 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.
- public async Task CreateNewCustomThemeAsync(string themeName, string accentColor, byte[] wallpaperData)
- {
- if (wallpaperData == null || wallpaperData.Length == 0)
- throw new CustomThemeCreationFailedException(Translator.Exception_CustomThemeMissingWallpaper);
+ var customThemes = await GetCurrentCustomThemesAsync();
- if (string.IsNullOrEmpty(themeName))
- throw new CustomThemeCreationFailedException(Translator.Exception_CustomThemeMissingName);
+ controlThemeList.AddRange(customThemes.Select(a => new CustomAppTheme(a)));
- var themes = await GetCurrentCustomThemesAsync();
+ applyingTheme = controlThemeList.Find(a => a.Id == currentApplicationThemeId) ?? preDefinedThemes.First(a => a.Id == Guid.Parse(_micaThemeId));
+ }
- if (themes.Exists(a => a.Name == themeName))
- throw new CustomThemeCreationFailedException(Translator.Exception_CustomThemeExists);
+ try
+ {
+ var existingThemeDictionary = _applicationResourceManager.GetLastResource();
- var newTheme = new CustomThemeMetadata()
- {
- Id = Guid.NewGuid(),
- Name = themeName,
- AccentColorHex = accentColor
- };
+ if (existingThemeDictionary != null && existingThemeDictionary.TryGetValue("ThemeName", out object themeNameString))
+ {
+ var themeName = themeNameString.ToString();
- // Save wallpaper.
- // Filename would be the same as metadata id, in jpg format.
+ // Applying different theme.
+ if (themeName != applyingTheme.ThemeName)
+ {
+ var resourceDictionaryContent = await applyingTheme.GetThemeResourceDictionaryContentAsync();
- var themeFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(CustomThemeFolderName, CreationCollisionOption.OpenIfExists);
+ var resourceDictionary = XamlReader.Load(resourceDictionaryContent) as ResourceDictionary;
- var wallpaperFile = await themeFolder.CreateFileAsync($"{newTheme.Id}.jpg", CreationCollisionOption.ReplaceExisting);
- await FileIO.WriteBytesAsync(wallpaperFile, wallpaperData);
+ // Custom themes require special attention for background image because
+ // they share the same base theme resource dictionary.
- // Generate thumbnail for settings page.
+ if (applyingTheme is CustomAppTheme)
+ {
+ resourceDictionary["ThemeBackgroundImage"] = $"ms-appdata:///local/{CustomThemeFolderName}/{applyingTheme.Id}.jpg";
+ }
- var thumbnail = await wallpaperFile.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode.PicturesView);
- var thumbnailFile = await themeFolder.CreateFileAsync($"{newTheme.Id}_preview.jpg", CreationCollisionOption.ReplaceExisting);
+ _applicationResourceManager.RemoveResource(existingThemeDictionary);
+ _applicationResourceManager.AddResource(resourceDictionary);
- await using (var readerStream = thumbnail.AsStreamForRead())
- {
- byte[] bytes = new byte[readerStream.Length];
+ bool isSystemTheme = applyingTheme is SystemAppTheme || applyingTheme is CustomAppTheme;
- await readerStream.ReadAsync(bytes);
+ 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 buffer = bytes.AsBuffer();
+ var savedElement = _configurationService.Get(UnderlyingThemeService.SelectedAppThemeKey, ApplicationElementTheme.Default);
+ RootTheme = savedElement;
- await FileIO.WriteBufferAsync(thumbnailFile, buffer);
+ // 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}");
+ }
}
- // 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)
+ public async Task> GetAvailableThemesAsync()
{
- var metadata = await GetCustomMetadataAsync(theme).ConfigureAwait(false);
+ var availableThemes = new List(preDefinedThemes);
- if (metadata == null) continue;
+ var customThemes = await GetCurrentCustomThemesAsync();
- results.Add(metadata);
+ availableThemes.AddRange(customThemes.Select(a => new CustomAppTheme(a)));
+
+ return availableThemes;
}
- return results;
+ 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);
+
+ using (var readerStream = thumbnail.AsStreamForRead())
+ {
+ byte[] bytes = new byte[readerStream.Length];
+
+ await readerStream.ReadAsync(bytes, 0, bytes.Length);
+
+ var buffer = bytes.AsBuffer();
+
+ await FileIO.WriteBufferAsync(thumbnailFile, buffer);
+ }
+
+ // 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)
+ {
+ var metadata = await GetCustomMetadataAsync(theme).ConfigureAwait(false);
+
+ if (metadata == null) continue;
+
+ results.Add(metadata);
+ }
+
+ return results;
+ }
+
+ private async Task GetCustomMetadataAsync(IStorageFile file)
+ {
+ var fileContent = await FileIO.ReadTextAsync(file);
+
+ return JsonConvert.DeserializeObject(fileContent);
+ }
+
+ public string GetSystemAccentColorHex()
+ => uiSettings.GetColorValue(UIColorType.Accent).ToHex();
}
-
- 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();
}