From 7d5f532b85093a14f8f0575567af254a087ed187 Mon Sep 17 00:00:00 2001 From: Thomas May Date: Tue, 3 Dec 2024 20:05:18 +0000 Subject: [PATCH 1/2] Add Fluent/Performance themes --- UniSky/App.xaml | 38 +- UniSky/App.xaml.cs | 13 +- UniSky/Package.appxmanifest | 2 +- UniSky/Pages/HomePage.xaml | 30 +- UniSky/Services/ISettingsService.cs | 21 + UniSky/Services/IThemeService.cs | 8 + UniSky/Services/SettingsService.cs | 206 +++ UniSky/Services/ThemeService.cs | 76 + UniSky/Templates/ButtonStyles.xaml | 265 --- UniSky/Templates/Icons.xaml | 9 + UniSky/Templates/NavigationViewStyles.xaml | 16 - UniSky/Templates/custom-twitter/Icons.xaml | 9 + .../Themes/Backport/NavigationView_rs1.xaml | 1582 +++++++++++++++++ UniSky/Themes/Fluent.xaml | 310 ++++ UniSky/Themes/Performance.xaml | 302 ++++ UniSky/Themes/ThemeResources.cs | 30 + UniSky/UniSky.csproj | 29 +- 17 files changed, 2607 insertions(+), 339 deletions(-) create mode 100644 UniSky/Services/ISettingsService.cs create mode 100644 UniSky/Services/IThemeService.cs create mode 100644 UniSky/Services/SettingsService.cs create mode 100644 UniSky/Services/ThemeService.cs create mode 100644 UniSky/Templates/Icons.xaml create mode 100644 UniSky/Templates/custom-twitter/Icons.xaml create mode 100644 UniSky/Themes/Backport/NavigationView_rs1.xaml create mode 100644 UniSky/Themes/Fluent.xaml create mode 100644 UniSky/Themes/Performance.xaml create mode 100644 UniSky/Themes/ThemeResources.cs diff --git a/UniSky/App.xaml b/UniSky/App.xaml index ad310bd..71b111f 100644 --- a/UniSky/App.xaml +++ b/UniSky/App.xaml @@ -5,14 +5,18 @@ xmlns:local="using:UniSky" xmlns:controls="using:Microsoft.UI.Xaml.Controls" xmlns:media="using:Microsoft.UI.Xaml.Media" - xmlns:sheets="using:UniSky.Controls.Sheet"> + xmlns:sheets="using:UniSky.Controls.Sheet" + xmlns:themes="using:UniSky.Themes"> + + + @@ -25,39 +29,7 @@ - - - - #10FFFFFF - #FF404040 - - - - - #FFDEDEDE - #10000000 - - - - - - M13.873 3.77C21.21 9.243 29.103 20.342 32 26.3v15.732c0-.335-.13.043-.41.858-1.512 4.414-7.418 21.642-20.923 7.87-7.111-7.252-3.819-14.503 9.125-16.692-7.405 1.252-15.73-.817-18.014-8.93C1.12 22.804 0 8.431 0 6.488 0-3.237 8.579-.18 13.873 3.77ZM50.127 3.77C42.79 9.243 34.897 20.342 32 26.3v15.732c0-.335.13.043.41.858 1.512 4.414 7.418 21.642 20.923 7.87 7.111-7.252 3.819-14.503-9.125-16.692 7.405 1.252 15.73-.817 18.014-8.93C62.88 22.804 64 8.431 64 6.488 64-3.237 55.422-.18 50.127 3.77Z - 64 - 57 - - - 1 - 0,0,0,0 - 14 14 diff --git a/UniSky/App.xaml.cs b/UniSky/App.xaml.cs index 75639d3..4924530 100644 --- a/UniSky/App.xaml.cs +++ b/UniSky/App.xaml.cs @@ -38,11 +38,11 @@ sealed partial class App : Application /// public App() { + this.ConfigureServices(); + this.InitializeComponent(); this.Suspending += OnSuspending; - this.ConfigureServices(); - // ResourceContext.SetGlobalQualifierValue("Custom", "Twitter", ResourceQualifierPersistence.LocalMachine); } @@ -52,16 +52,17 @@ private void ConfigureServices() collection.AddLogging(c => c.AddDebug() .SetMinimumLevel(LogLevel.Trace)); - collection.AddTransient(); - collection.AddTransient(); - + collection.AddSingleton(); + collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); - Ioc.Default.ConfigureServices(collection.BuildServiceProvider()); + collection.AddTransient(); + collection.AddTransient(); + Ioc.Default.ConfigureServices(collection.BuildServiceProvider()); Configurator.Formatters.Register("en", (locale) => new ShortTimespanFormatter("en")); } diff --git a/UniSky/Package.appxmanifest b/UniSky/Package.appxmanifest index 598932a..06f711b 100644 --- a/UniSky/Package.appxmanifest +++ b/UniSky/Package.appxmanifest @@ -10,7 +10,7 @@ + Version="1.0.129.0" /> diff --git a/UniSky/Pages/HomePage.xaml b/UniSky/Pages/HomePage.xaml index 0c0ece4..e8abf70 100644 --- a/UniSky/Pages/HomePage.xaml +++ b/UniSky/Pages/HomePage.xaml @@ -188,7 +188,7 @@ Visibility="Collapsed" Grid.Row="1" Height="40" - Background="{ThemeResource SystemControlChromeMediumLowAcrylicWindowMediumBrush}"> + Background="{ThemeResource HomePageFooterBackgroundBrush}"> @@ -199,10 +199,10 @@ - - - - - + - - - + diff --git a/UniSky/Services/ISettingsService.cs b/UniSky/Services/ISettingsService.cs new file mode 100644 index 0000000..bb3821b --- /dev/null +++ b/UniSky/Services/ISettingsService.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace UniSky.Services; + +#nullable enable +public interface ISettingsService +{ + void Clear(); + bool KeyExists(string key); + bool KeyExists(string compositeKey, string key); + T? Read(string key, T? @default = default); + T? Read(string compositeKey, string key, T? @default = default); + void Save(string compositeKey, IDictionary values); + void Save(string key, T value); + bool TryDelete(string key); + bool TryDelete(string compositeKey, string key); + bool TryRead(string key, out T? value); + bool TryRead(string compositeKey, string key, out T? value); +} + +#nullable disable diff --git a/UniSky/Services/IThemeService.cs b/UniSky/Services/IThemeService.cs new file mode 100644 index 0000000..408f51c --- /dev/null +++ b/UniSky/Services/IThemeService.cs @@ -0,0 +1,8 @@ +namespace UniSky.Services; + +public interface IThemeService +{ + AppTheme GetThemeForDisplay(); + AppTheme GetTheme(); + void SetThemeOnRelaunch(AppTheme theme); +} \ No newline at end of file diff --git a/UniSky/Services/SettingsService.cs b/UniSky/Services/SettingsService.cs new file mode 100644 index 0000000..dc15920 --- /dev/null +++ b/UniSky/Services/SettingsService.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using Windows.Storage; + +namespace UniSky.Services; + +#nullable enable + +[JsonSerializable(typeof(sbyte))] +[JsonSerializable(typeof(short))] +[JsonSerializable(typeof(int))] +[JsonSerializable(typeof(long))] +[JsonSerializable(typeof(byte))] +[JsonSerializable(typeof(ushort))] +[JsonSerializable(typeof(uint))] +[JsonSerializable(typeof(ulong))] +[JsonSerializable(typeof(bool))] +[JsonSerializable(typeof(string))] +[JsonSourceGenerationOptions(WriteIndented = false, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] +public partial class SettingsJsonContext : JsonSerializerContext { } + +internal class SettingsService : ISettingsService +{ + private static readonly JsonSerializerOptions Options + = new JsonSerializerOptions { TypeInfoResolver = SettingsJsonContext.Default }; + + /// + /// Gets the settings container. + /// + public readonly ApplicationDataContainer Settings = ApplicationData.Current.LocalSettings; + + /// + /// Determines whether a setting already exists. + /// + /// Key of the setting (that contains object). + /// True if a value exists. + public bool KeyExists(string key) + { + return Settings.Values.ContainsKey(key); + } + + /// + /// Retrieves a single item by its key. + /// + /// Type of object retrieved. + /// Key of the object. + /// Default value of the object. + /// The TValue object. + public T? Read(string key, T? @default = default) + { + if (Settings.Values.TryGetValue(key, out var valueObj) && valueObj is string valueString) + { + return JsonSerializer.Deserialize(valueString, Options); + } + + return @default; + } + + /// + public bool TryRead(string key, out T? value) + { + if (Settings.Values.TryGetValue(key, out var valueObj) && valueObj is string valueString) + { + value = JsonSerializer.Deserialize(valueString, Options); + return true; + } + + value = default; + return false; + } + + /// + public void Save(string key, T value) + { + Settings.Values[key] = JsonSerializer.Serialize(value, Options); + } + + /// + public bool TryDelete(string key) + { + return Settings.Values.Remove(key); + } + + /// + public void Clear() + { + Settings.Values.Clear(); + } + + /// + /// Determines whether a setting already exists in composite. + /// + /// Key of the composite (that contains settings). + /// Key of the setting (that contains object). + /// True if a value exists. + public bool KeyExists(string compositeKey, string key) + { + if (TryRead(compositeKey, out ApplicationDataCompositeValue? composite) && composite != null) + { + return composite.ContainsKey(key); + } + + return false; + } + + /// + /// Attempts to retrieve a single item by its key in composite. + /// + /// Type of object retrieved. + /// Key of the composite (that contains settings). + /// Key of the object. + /// The value of the object retrieved. + /// The T object. + public bool TryRead(string compositeKey, string key, out T? value) + { + if (TryRead(compositeKey, out ApplicationDataCompositeValue? composite) && composite != null) + { + string compositeValue = (string)composite[key]; + if (compositeValue != null) + { + value = JsonSerializer.Deserialize(compositeValue, Options); + return true; + } + } + + value = default; + return false; + } + + /// + /// Retrieves a single item by its key in composite. + /// + /// Type of object retrieved. + /// Key of the composite (that contains settings). + /// Key of the object. + /// Default value of the object. + /// The T object. + public T? Read(string compositeKey, string key, T? @default = default) + { + if (TryRead(compositeKey, out ApplicationDataCompositeValue? composite) && composite != null) + { + if (composite.TryGetValue(key, out object valueObj) && valueObj is string value) + { + return JsonSerializer.Deserialize(value, Options); + } + } + + return @default; + } + + /// + /// Saves a group of items by its key in a composite. + /// This method should be considered for objects that do not exceed 8k bytes during the lifetime of the application + /// and for groups of settings which need to be treated in an atomic way. + /// + /// Type of object saved. + /// Key of the composite (that contains settings). + /// Objects to save. + public void Save(string compositeKey, IDictionary values) + { + if (TryRead(compositeKey, out ApplicationDataCompositeValue? composite) && composite != null) + { + foreach (KeyValuePair setting in values) + { + if (composite.ContainsKey(setting.Key)) + { + composite[setting.Key] = JsonSerializer.Serialize(setting.Value, Options); + } + else + { + composite.Add(setting.Key, JsonSerializer.Serialize(setting.Value, Options)); + } + } + } + else + { + composite = new ApplicationDataCompositeValue(); + foreach (KeyValuePair setting in values) + { + composite.Add(setting.Key, JsonSerializer.Serialize(setting.Value, Options)); + } + + Settings.Values[compositeKey] = composite; + } + } + + /// + /// Deletes a single item by its key in composite. + /// + /// Key of the composite (that contains settings). + /// Key of the object. + /// A boolean indicator of success. + public bool TryDelete(string compositeKey, string key) + { + if (TryRead(compositeKey, out ApplicationDataCompositeValue? composite) && composite != null) + { + return composite.Remove(key); + } + + return false; + } +} + +#nullable disable \ No newline at end of file diff --git a/UniSky/Services/ThemeService.cs b/UniSky/Services/ThemeService.cs new file mode 100644 index 0000000..866f10a --- /dev/null +++ b/UniSky/Services/ThemeService.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Toolkit.Uwp.Helpers; + +namespace UniSky.Services; + +public enum AppTheme +{ + Performance, + Fluent, + SunValley, + OLED +} + +internal class ThemeService : IThemeService +{ + private const string Themes_AppTheme = "AppTheme"; + private const string Themes_AppThemeSetOnLaunch = "AppThemeSet"; + + private readonly ISettingsService settings; + + public ThemeService(ISettingsService settings) + { + this.settings = settings; + } + + public AppTheme GetTheme() + { + if (settings.TryRead(Themes_AppThemeSetOnLaunch, out var value)) + { + settings.Save(Themes_AppTheme, value); + settings.TryDelete(Themes_AppThemeSetOnLaunch); + } + + if (settings.TryRead(Themes_AppTheme, out var theme)) + return (AppTheme)theme; + + var defaultTheme = GetDefaultAppTheme(); + settings.Save(Themes_AppTheme, (int)defaultTheme); + + return defaultTheme; + } + + public AppTheme GetThemeForDisplay() + { + if (settings.TryRead(Themes_AppThemeSetOnLaunch, out var value)) + { + return (AppTheme)value; + } + + if (settings.TryRead(Themes_AppTheme, out var theme)) + return (AppTheme)theme; + + return GetDefaultAppTheme(); + } + + public void SetThemeOnRelaunch(AppTheme theme) + { + settings.Save(Themes_AppThemeSetOnLaunch, (int)theme); + } + + public AppTheme GetDefaultAppTheme() + { + //var osBuild = SystemInformation.OperatingSystemVersion.Build; + //if (osBuild >= 22000) + // return AppTheme.SunValley; + + if (SystemInformation.DeviceFamily == "Windows.Mobile") + return AppTheme.Performance; + + return AppTheme.Fluent; + } +} diff --git a/UniSky/Templates/ButtonStyles.xaml b/UniSky/Templates/ButtonStyles.xaml index c1d7bbe..73956c3 100644 --- a/UniSky/Templates/ButtonStyles.xaml +++ b/UniSky/Templates/ButtonStyles.xaml @@ -64,270 +64,6 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + --> + + \ No newline at end of file diff --git a/UniSky/Themes/Fluent.xaml b/UniSky/Themes/Fluent.xaml new file mode 100644 index 0000000..f985067 --- /dev/null +++ b/UniSky/Themes/Fluent.xaml @@ -0,0 +1,310 @@ + + + + + #10FFFFFF + #FF404040 + + + + #FFDEDEDE + #10000000 + + + + + 0,0,0,0 + 0,0,0,0 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UniSky/Themes/Performance.xaml b/UniSky/Themes/Performance.xaml new file mode 100644 index 0000000..864edac --- /dev/null +++ b/UniSky/Themes/Performance.xaml @@ -0,0 +1,302 @@ + + + + + + + + + + #10FFFFFF + #FF404040 + + + #FFDEDEDE + #10000000 + + + + 0,0,0,0 + 0,0,0,0 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UniSky/Themes/ThemeResources.cs b/UniSky/Themes/ThemeResources.cs new file mode 100644 index 0000000..82155e7 --- /dev/null +++ b/UniSky/Themes/ThemeResources.cs @@ -0,0 +1,30 @@ +using System; +using CommunityToolkit.Mvvm.DependencyInjection; +using UniSky.Services; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls.Primitives; + +namespace UniSky.Themes +{ + internal class ThemeResources : ResourceDictionary + { + public ThemeResources() + { + //var theme = Ioc.Default.GetRequiredService() + // .GetTheme(); + + var theme = AppTheme.Performance; + + Uri uri = theme switch + { + AppTheme.OLED => new Uri("ms-appx:///Themes/OLED.xaml"), + AppTheme.Fluent => new Uri("ms-appx:///Themes/Fluent.xaml"), + AppTheme.Performance => new Uri("ms-appx:///Themes/Performance.xaml"), + AppTheme.SunValley => new Uri("ms-appx:///Themes/SunValley.xaml"), + _ => throw new InvalidOperationException("Unknown theme"), + }; + + Application.LoadComponent(this, uri, ComponentResourceLocation.Application); + } + } +} diff --git a/UniSky/UniSky.csproj b/UniSky/UniSky.csproj index 0d78275..dfb4a11 100644 --- a/UniSky/UniSky.csproj +++ b/UniSky/UniSky.csproj @@ -208,13 +208,18 @@ + + FeedTemplates.xaml + + + @@ -411,6 +416,14 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -427,6 +440,18 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + @@ -486,9 +511,7 @@ UniSky.Models - - - + 14.0 From 68a4e7e4331e88f91059a1cbc7ea80d21bd39af6 Mon Sep 17 00:00:00 2001 From: Thomas May Date: Tue, 3 Dec 2024 20:56:34 +0000 Subject: [PATCH 2/2] Enable default theme detection --- UniSky/Themes/ThemeResources.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/UniSky/Themes/ThemeResources.cs b/UniSky/Themes/ThemeResources.cs index 82155e7..7c6271c 100644 --- a/UniSky/Themes/ThemeResources.cs +++ b/UniSky/Themes/ThemeResources.cs @@ -10,10 +10,8 @@ internal class ThemeResources : ResourceDictionary { public ThemeResources() { - //var theme = Ioc.Default.GetRequiredService() - // .GetTheme(); - - var theme = AppTheme.Performance; + var theme = Ioc.Default.GetRequiredService() + .GetTheme(); Uri uri = theme switch {