From b13ceac49f7e7020975dd6664dafde28b60a65df Mon Sep 17 00:00:00 2001 From: Russell Camo <32549126+russkyc@users.noreply.github.com> Date: Tue, 29 Aug 2023 00:52:29 +0800 Subject: [PATCH] refactor: migrate from ini app config to json, add theming and config services Signed-off-by: Russell Camo <32549126+russkyc@users.noreply.github.com> --- .../ViewModels/AppViewModel.cs | 36 ++++++++++++ .../ViewModels/SettingsViewModel.cs | 16 +++++- .../Configuration/ConfigurationService.cs | 7 ++- .../GroomWise.Infrastructure.csproj | 2 + .../Interfaces/IThemeManagerService.cs | 12 ++++ GroomWise.WPF/App.xaml.cs | 34 +++++------ .../PageContextToSelectionConverter.cs | 25 ++++++++ GroomWise.WPF/GroomWise.WPF.csproj | 6 +- GroomWise.WPF/Services/ThemeManagerService.cs | 24 ++++++++ GroomWise.WPF/Views/MainView.xaml | 12 +++- .../Views/Templates/NavItemTemplate.xaml | 2 +- .../Views/Templates/NavItemTemplate.xaml.cs | 3 + .../Views/Templates/SettingsTemplate.xaml | 57 +++++++++++-------- GroomWise.WPF/appconfig.ini | 18 ------ GroomWise.WPF/appconfig.json | 5 ++ 15 files changed, 190 insertions(+), 69 deletions(-) create mode 100644 GroomWise.Infrastructure/Theming/Interfaces/IThemeManagerService.cs create mode 100644 GroomWise.WPF/Converters/PageContextToSelectionConverter.cs create mode 100644 GroomWise.WPF/Services/ThemeManagerService.cs delete mode 100644 GroomWise.WPF/appconfig.ini create mode 100644 GroomWise.WPF/appconfig.json diff --git a/GroomWise.Application/ViewModels/AppViewModel.cs b/GroomWise.Application/ViewModels/AppViewModel.cs index c4063d2..eac8713 100644 --- a/GroomWise.Application/ViewModels/AppViewModel.cs +++ b/GroomWise.Application/ViewModels/AppViewModel.cs @@ -7,8 +7,10 @@ using GroomWise.Domain.Enums; using GroomWise.Infrastructure.Authentication.Enums; using GroomWise.Infrastructure.Authentication.Interfaces; +using GroomWise.Infrastructure.Configuration.Interfaces; using GroomWise.Infrastructure.IoC.Interfaces; using GroomWise.Infrastructure.Navigation.Interfaces; +using GroomWise.Infrastructure.Theming.Interfaces; using Injectio.Attributes; using MvvmGen; using MvvmGen.ViewModels; @@ -19,11 +21,16 @@ namespace GroomWise.Application.ViewModels; [Inject(typeof(IDialogFactory))] [Inject(typeof(INavigationService))] [Inject(typeof(IAppServicesContainer))] +[Inject(typeof(IConfigurationService))] +[Inject(typeof(IThemeManagerService))] [Inject(typeof(DashboardViewModel))] [ViewModel] [RegisterSingleton] public partial class AppViewModel { + [Property] + private IConfigurationService _configuration; + [Property] private ViewModelBase _pageContext; @@ -33,6 +40,7 @@ public partial class AppViewModel partial void OnInitialize() { PageContext = DashboardViewModel; + Configuration = ConfigurationService; AuthenticatedUserRoles = AuthenticationService.GetSession()?.Roles!; } @@ -74,4 +82,32 @@ private async Task NavigateToPage(object param) } }); } + + [Command] + public async Task SetDarkTheme(object param) + { + await Task.Run(() => + { + if (param is bool useDarkTheme) + { + Configuration.DarkMode = true; + OnPropertyChanged(nameof(Configuration.DarkMode)); + ThemeManagerService.SetDarkTheme(useDarkTheme); + } + }); + } + + [Command] + public async Task SetColorTheme(object param) + { + await Task.Run(() => + { + if (param is string themeId) + { + Configuration.ColorTheme = themeId; + OnPropertyChanged(nameof(Configuration.ColorTheme)); + ThemeManagerService.SetColorTheme(themeId); + } + }); + } } diff --git a/GroomWise.Application/ViewModels/SettingsViewModel.cs b/GroomWise.Application/ViewModels/SettingsViewModel.cs index 34fce39..23eb4a8 100644 --- a/GroomWise.Application/ViewModels/SettingsViewModel.cs +++ b/GroomWise.Application/ViewModels/SettingsViewModel.cs @@ -3,11 +3,25 @@ // Unauthorized copying or redistribution of all files, in source and binary forms via any medium // without written, signed consent from the author is strictly prohibited. +using GroomWise.Infrastructure.Configuration.Interfaces; +using GroomWise.Infrastructure.Theming.Interfaces; using Injectio.Attributes; using MvvmGen; namespace GroomWise.Application.ViewModels; [ViewModel] +[Inject(typeof(IThemeManagerService))] +[Inject(typeof(IConfigurationService))] [RegisterSingleton] -public partial class SettingsViewModel { } +public partial class SettingsViewModel +{ + [Property] + private IConfigurationService _configuration; + + partial void OnInitialize() + { + Configuration = ConfigurationService; + } + +} diff --git a/GroomWise.Infrastructure/Configuration/ConfigurationService.cs b/GroomWise.Infrastructure/Configuration/ConfigurationService.cs index cf9559b..8197f4b 100644 --- a/GroomWise.Infrastructure/Configuration/ConfigurationService.cs +++ b/GroomWise.Infrastructure/Configuration/ConfigurationService.cs @@ -3,15 +3,18 @@ // Unauthorized copying or redistribution of all files, in source and binary forms via any medium // without written, signed consent from the author is strictly prohibited. +using System.ComponentModel; using GroomWise.Infrastructure.Configuration.Interfaces; +using Injectio.Attributes; using Russkyc.Configuration; namespace GroomWise.Infrastructure.Configuration; +[RegisterSingleton] public class ConfigurationService : ConfigProvider, IConfigurationService { - public ConfigurationService(string path) - : base(path) { } + public ConfigurationService() + : base("appconfig.json") { } public bool DarkMode { diff --git a/GroomWise.Infrastructure/GroomWise.Infrastructure.csproj b/GroomWise.Infrastructure/GroomWise.Infrastructure.csproj index 34f4b2a..cf160f8 100644 --- a/GroomWise.Infrastructure/GroomWise.Infrastructure.csproj +++ b/GroomWise.Infrastructure/GroomWise.Infrastructure.csproj @@ -34,6 +34,8 @@ + + diff --git a/GroomWise.Infrastructure/Theming/Interfaces/IThemeManagerService.cs b/GroomWise.Infrastructure/Theming/Interfaces/IThemeManagerService.cs new file mode 100644 index 0000000..de05c3e --- /dev/null +++ b/GroomWise.Infrastructure/Theming/Interfaces/IThemeManagerService.cs @@ -0,0 +1,12 @@ +// Copyright (C) 2023 Russell Camo (Russkyc).- All Rights Reserved +// +// Unauthorized copying or redistribution of all files, in source and binary forms via any medium +// without written, signed consent from the author is strictly prohibited. + +namespace GroomWise.Infrastructure.Theming.Interfaces; + +public interface IThemeManagerService +{ + void SetDarkTheme(bool useDarkTheme); + void SetColorTheme(string themeId); +} \ No newline at end of file diff --git a/GroomWise.WPF/App.xaml.cs b/GroomWise.WPF/App.xaml.cs index 20099b4..0474621 100644 --- a/GroomWise.WPF/App.xaml.cs +++ b/GroomWise.WPF/App.xaml.cs @@ -5,8 +5,10 @@ using System.Threading; using GroomWise.Application.Enums; +using GroomWise.Infrastructure.Configuration.Interfaces; using GroomWise.Infrastructure.IoC.Interfaces; using GroomWise.Infrastructure.Navigation.Interfaces; +using GroomWise.Infrastructure.Theming.Interfaces; using GroomWise.Views; using GroomWise.Views.Dialogs; using Microsoft.Extensions.DependencyInjection; @@ -25,12 +27,12 @@ public App() { InitializeComponent(); - var services = new ServiceCollection() + var container = new ServiceCollection() .AddGroomWiseInfrastructure() .AddGroomWiseApplication() - .AddGroomWise(); + .AddGroomWise() + .BuildServiceProvider(); - var container = services.BuildServiceProvider(); var scope = container.GetService(); scope?.AddContainer(container); @@ -38,26 +40,24 @@ public App() var login = scope?.GetService(); var navigation = scope?.GetService(); - Current.MainWindow = login; - - Current.Dispatcher.BeginInvoke(() => + Dispatcher.BeginInvoke(() => { navigation?.Initialize(SynchronizationContext.Current!, login!); navigation?.Add(AppViews.Login, login!); navigation?.Add(AppViews.Main, main!); }); - MainWindow?.Show(); - } - /*protected override void OnStartup(StartupEventArgs e) - { - base.OnStartup(e); + // Load Theme Defaults + var config = scope?.GetService(); + var themeManager = scope?.GetService(); - // Set the maximum amount of memory that the application can use to 2 GB for x86 and 4 GB for x64. - IntPtr maxWorkingSet = new IntPtr( - (IntPtr.Size == 4) ? (int)(1.5 * 1024 * 1024 * 1024) : 4L * 1024L * 1024L * 1024L - ); + if (config is not null && themeManager is not null) + { + themeManager.SetDarkTheme(config.DarkMode); + themeManager.SetColorTheme(config.ColorTheme); + } - Process.GetCurrentProcess().MaxWorkingSet = maxWorkingSet; - }*/ + MainWindow = login; + MainWindow?.Show(); + } } diff --git a/GroomWise.WPF/Converters/PageContextToSelectionConverter.cs b/GroomWise.WPF/Converters/PageContextToSelectionConverter.cs new file mode 100644 index 0000000..6c17d33 --- /dev/null +++ b/GroomWise.WPF/Converters/PageContextToSelectionConverter.cs @@ -0,0 +1,25 @@ +// Copyright (C) 2023 Russell Camo (Russkyc).- All Rights Reserved +// +// Unauthorized copying or redistribution of all files, in source and binary forms via any medium +// without written, signed consent from the author is strictly prohibited. + +using System; +using System.Globalization; +using System.Windows.Data; + +namespace GroomWise.Converters; + +public class PageContextToSelectionConverter : IValueConverter +{ + public static PageContextToSelectionConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value.GetType() == (Type)parameter; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} diff --git a/GroomWise.WPF/GroomWise.WPF.csproj b/GroomWise.WPF/GroomWise.WPF.csproj index 1786259..971ac30 100644 --- a/GroomWise.WPF/GroomWise.WPF.csproj +++ b/GroomWise.WPF/GroomWise.WPF.csproj @@ -63,10 +63,8 @@ Always - - - - Always + + Always diff --git a/GroomWise.WPF/Services/ThemeManagerService.cs b/GroomWise.WPF/Services/ThemeManagerService.cs new file mode 100644 index 0000000..f631da7 --- /dev/null +++ b/GroomWise.WPF/Services/ThemeManagerService.cs @@ -0,0 +1,24 @@ +// Copyright (C) 2023 Russell Camo (Russkyc).- All Rights Reserved +// +// Unauthorized copying or redistribution of all files, in source and binary forms via any medium +// without written, signed consent from the author is strictly prohibited. + +using GroomWise.Infrastructure.Theming.Interfaces; +using Injectio.Attributes; +using org.russkyc.moderncontrols.Helpers; + +namespace GroomWise.Services; + +[RegisterSingleton] +public class ThemeManagerService : IThemeManagerService +{ + public void SetDarkTheme(bool useDarkTheme) + { + ThemeManager.Instance.SetBaseTheme(useDarkTheme ? "Dark" : "Light"); + } + + public void SetColorTheme(string themeId) + { + ThemeManager.Instance.SetColorTheme(themeId); + } +} diff --git a/GroomWise.WPF/Views/MainView.xaml b/GroomWise.WPF/Views/MainView.xaml index ca5e5b7..99b4635 100644 --- a/GroomWise.WPF/Views/MainView.xaml +++ b/GroomWise.WPF/Views/MainView.xaml @@ -2,6 +2,7 @@ x:Class="GroomWise.Views.MainView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:converters="clr-namespace:GroomWise.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:icons="clr-namespace:Material.Icons.WPF;assembly=Material.Icons.WPF" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" @@ -100,13 +101,13 @@ Margin="5" Padding="6" CheckedForeground="{DynamicResource inverted-lighten-3}" - Command="{Binding SwitchBaseThemeCommand}" + Command="{Binding SetDarkThemeCommand}" CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Self}}" Content="{icons:MaterialIconExt Kind=Lightbulb}" CornerRadius="5" DefaultForeground="{DynamicResource fg-600}" HoverForeground="{DynamicResource fg-500}" - IsChecked="{Binding ThemeManagerService.DarkMode, UpdateSourceTrigger=PropertyChanged}" /> + IsChecked="{Binding Configuration.DarkMode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> @@ -127,31 +128,37 @@ Margin="8,8,8,0" Icon="{icons:MaterialIconExt Home}" PageContext="{x:Type viewModels:DashboardViewModel}" + Selected="{Binding PageContext, Converter={x:Static converters:PageContextToSelectionConverter.Instance}, ConverterParameter={x:Type viewModels:DashboardViewModel}, Mode=OneWay}" Tooltip="Home" /> diff --git a/GroomWise.WPF/Views/Templates/NavItemTemplate.xaml b/GroomWise.WPF/Views/Templates/NavItemTemplate.xaml index fdd74d5..23869bb 100644 --- a/GroomWise.WPF/Views/Templates/NavItemTemplate.xaml +++ b/GroomWise.WPF/Views/Templates/NavItemTemplate.xaml @@ -28,7 +28,7 @@ GroupName="NavItemGroup" HoverBackground="{DynamicResource bg-200}" HoverForeground="{DynamicResource bg-600}" - IsChecked="{Binding Selected, UpdateSourceTrigger=PropertyChanged}" + IsChecked="{Binding Selected, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource FindAncestor, AncestorType=templates:NavItemTemplate}}" PressedForeground="{DynamicResource inverted-default}" ToolTip="{Binding Tooltip, RelativeSource={RelativeSource FindAncestor, AncestorType=templates:NavItemTemplate}}" /> diff --git a/GroomWise.WPF/Views/Templates/NavItemTemplate.xaml.cs b/GroomWise.WPF/Views/Templates/NavItemTemplate.xaml.cs index 6add32e..c14176d 100644 --- a/GroomWise.WPF/Views/Templates/NavItemTemplate.xaml.cs +++ b/GroomWise.WPF/Views/Templates/NavItemTemplate.xaml.cs @@ -22,6 +22,9 @@ public NavItemTemplate() [DependencyProperty(typeof(string))] public static readonly DependencyProperty TooltipProperty; + [DependencyProperty(typeof(bool))] + public static readonly DependencyProperty SelectedProperty; + [DependencyProperty(typeof(Type))] public static readonly DependencyProperty PageContextProperty; } diff --git a/GroomWise.WPF/Views/Templates/SettingsTemplate.xaml b/GroomWise.WPF/Views/Templates/SettingsTemplate.xaml index 83a89dd..483fcdf 100644 --- a/GroomWise.WPF/Views/Templates/SettingsTemplate.xaml +++ b/GroomWise.WPF/Views/Templates/SettingsTemplate.xaml @@ -6,11 +6,20 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:russkyc="clr-namespace:org.russkyc.moderncontrols;assembly=Russkyc.ModernControls.WPF" + xmlns:system="clr-namespace:System;assembly=System.Runtime" xmlns:viewModels="clr-namespace:GroomWise.Application.ViewModels;assembly=GroomWise.Application" + xmlns:views="clr-namespace:GroomWise.Views" d:DataContext="{d:DesignInstance viewModels:AppViewModel, IsDesignTimeCreatable=True}" d:DesignWidth="700" + DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType=views:MainView}}" mc:Ignorable="d"> + + + False + True + + @@ -121,11 +130,11 @@ HorizontalContentAlignment="Center" VerticalContentAlignment="Center" CheckedBackground="{DynamicResource bg-300}" - Command="{Binding DataContext.SwitchBaseThemeCommand, RelativeSource={RelativeSource FindAncestor, AncestorLevel=2, AncestorType={x:Type UserControl}}}" + Command="{Binding SetDarkThemeCommand}" CommandParameter="{StaticResource True}" CornerRadius="34" DefaultBackground="{DynamicResource bg-000}" - IsChecked="{Binding ThemeManagerService.DarkMode, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" + IsChecked="{Binding Configuration.DarkMode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" PressedBackground="{DynamicResource bg-200}">