From 07e4cfbbf9f838e23eb9d0052f2bc2be700ede93 Mon Sep 17 00:00:00 2001 From: Cryolitia Date: Wed, 27 Sep 2023 03:37:51 +0800 Subject: [PATCH] refactor: config Signed-off-by: Cryolitia --- src/MaaWpfGui/Configuration/ConfigFactory.cs | 169 ++++++++++++++++++ src/MaaWpfGui/Configuration/GUIConfig.cs | 41 +++++ .../PropertyChangedEventDetailArgs.cs | 19 ++ src/MaaWpfGui/Configuration/RootConfig.cs | 35 ++++ src/MaaWpfGui/Configuration/SpecificConfig.cs | 11 ++ src/MaaWpfGui/Constants/ConfigurationKeys.cs | 2 - src/MaaWpfGui/FodyWeavers.xml | 4 +- src/MaaWpfGui/Helper/ToastNotification.cs | 3 +- src/MaaWpfGui/MaaWpfGui.csproj | 6 + .../ViewModels/UI/SettingsViewModel.cs | 47 +---- 10 files changed, 293 insertions(+), 44 deletions(-) create mode 100644 src/MaaWpfGui/Configuration/ConfigFactory.cs create mode 100644 src/MaaWpfGui/Configuration/GUIConfig.cs create mode 100644 src/MaaWpfGui/Configuration/PropertyChangedEventDetailArgs.cs create mode 100644 src/MaaWpfGui/Configuration/RootConfig.cs create mode 100644 src/MaaWpfGui/Configuration/SpecificConfig.cs diff --git a/src/MaaWpfGui/Configuration/ConfigFactory.cs b/src/MaaWpfGui/Configuration/ConfigFactory.cs new file mode 100644 index 0000000000..c94c060a8d --- /dev/null +++ b/src/MaaWpfGui/Configuration/ConfigFactory.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using MaaWpfGui.Helper; +using ObservableCollections; +using Serilog; + +[assembly: PropertyChanged.FilterType("MaaWpfGui.Configuration.")] + +namespace MaaWpfGui.Configuration +{ + public class ConfigFactory + { + // TODO: delete test in main branch + private static readonly string _configurationFile = Path.Combine(Environment.CurrentDirectory, "config/gui.test.json"); + + // TODO: write backup method + private static readonly string _configurationBakFile = Path.Combine(Environment.CurrentDirectory, "config/gui.test.json.bak"); + + private static readonly ILogger _logger = Log.ForContext(); + + private static readonly object _lock = new object(); + + public delegate void ConfigurationUpdateEventHandler(string key, object oldValue, object newValue); + + public static event ConfigurationUpdateEventHandler ConfigurationUpdateEvent; + + private static readonly JsonSerializerOptions _options = new JsonSerializerOptions {WriteIndented = true, Converters = {new JsonStringEnumConverter()}}; + + private static readonly Lazy _rootConfig = new Lazy(() => + { + lock (_lock) + { + if (Directory.Exists("config") is false) + { + Directory.CreateDirectory("config"); + } + + RootConfig parsed = null; + if (File.Exists(_configurationFile)) + { + try + { + parsed = JsonSerializer.Deserialize(File.ReadAllText(_configurationFile), _options); + } + catch (Exception e) + { + _logger.Information("Failed to parse configuration file: " + e); + } + } + + if (parsed is null) + { + _logger.Information("Failed to load configuration file, creating a new one"); + parsed = new RootConfig(); + } + + if (parsed.CurrentConfig is null) + { + parsed.CurrentConfig = new SpecificConfig(); + } + + parsed.PropertyChanged += OnPropertyChangedFactory("Configurations."); + parsed.Configurations.CollectionChanged += (in NotifyCollectionChangedEventArgs> args) => + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + case NotifyCollectionChangedAction.Replace: + if (args.IsSingleItem) + { + args.NewItem.Value.GuiConfig.PropertyChanged += OnPropertyChangedFactory("Configurations." + args.NewItem.Key, JsonSerializer.Serialize(args.NewItem.Value, _options), null); + } + else + { + foreach (var value in args.NewItems) + { + value.Value.GuiConfig.PropertyChanged += OnPropertyChangedFactory("Configurations." + value.Key, JsonSerializer.Serialize(value.Value, _options), null); + } + } + + break; + } + + OnPropertyChangedFactory("Configurations"); + }; + + foreach (var keyValue in parsed.Configurations) + { + keyValue.Value.GuiConfig.PropertyChanged += OnPropertyChangedFactory("Configurations." + keyValue.Key + ".GUIConfig."); + } + + return parsed; + } + }); + + private static PropertyChangedEventHandler OnPropertyChangedFactory(string key, object oldValue, object newValue) + { + return (o, args) => + { + var after = newValue; + if (after == null && args is PropertyChangedEventDetailArgs) + { + after = (args as PropertyChangedEventDetailArgs).NewValue; + } + + OnPropertyChanged(key + args.PropertyName, oldValue, after); + }; + } + + private static PropertyChangedEventHandler OnPropertyChangedFactory(string key) + { + return (o, args) => + { + object after = null; + if (args is PropertyChangedEventDetailArgs) + { + after = (args as PropertyChangedEventDetailArgs).NewValue; + } + + OnPropertyChanged(key + args.PropertyName, null, after); + }; + } + + public static RootConfig RootConfig => _rootConfig.Value; + + public static readonly SpecificConfig CurrentConfig = RootConfig.CurrentConfig; + + public static async void OnPropertyChanged(string key, object oldValue, object newValue) + { + var result = await Save(); + if (result) + { + ConfigurationUpdateEvent?.Invoke(key, oldValue, newValue); + _logger.Debug("Configuration {Key} has been set to {Value}", key, newValue); + } + else + { + _logger.Warning("Failed to save configuration {Key} to {Value}", key, newValue); + } + } + + private static async Task Save(string file = null) + { + return await Task.Run(() => + { + lock (_lock) + { + try + { + File.WriteAllText(file ?? _configurationFile, JsonSerializer.Serialize(RootConfig, _options)); + } + catch (Exception e) + { + _logger.Error(e, "Failed to save configuration file."); + return false; + } + + return true; + } + }); + } + } +} diff --git a/src/MaaWpfGui/Configuration/GUIConfig.cs b/src/MaaWpfGui/Configuration/GUIConfig.cs new file mode 100644 index 0000000000..9f9fb02ab6 --- /dev/null +++ b/src/MaaWpfGui/Configuration/GUIConfig.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace MaaWpfGui.Configuration +{ + public class GUIConfig : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + public DarkModeType DarkMode { get; set; } = DarkModeType.SyncWithOS; + + public bool UseNotify { get; set; } = true; + + public void OnPropertyChanged(string propertyName, object before, object after) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventDetailArgs(propertyName, before, after)); + } + } + + /// + /// 表示深色模式的类型。 + /// + public enum DarkModeType + { + /// + /// 明亮的主题。 + /// + Light, + + /// + /// 暗黑的主题。 + /// + Dark, + + /// + /// 与操作系统的深色模式同步。 + /// + SyncWithOS + } +} diff --git a/src/MaaWpfGui/Configuration/PropertyChangedEventDetailArgs.cs b/src/MaaWpfGui/Configuration/PropertyChangedEventDetailArgs.cs new file mode 100644 index 0000000000..340167adfe --- /dev/null +++ b/src/MaaWpfGui/Configuration/PropertyChangedEventDetailArgs.cs @@ -0,0 +1,19 @@ +using System.ComponentModel; + +namespace MaaWpfGui.Configuration +{ + // https://github.com/dotnet/runtime/issues/27252 + public class PropertyChangedEventDetailArgs : PropertyChangedEventArgs + { + public PropertyChangedEventDetailArgs(string propertyName, object oldValue, object newValue) + : base(propertyName) + { + OldValue = oldValue; + NewValue = newValue; + } + + public object OldValue { get; private set; } + + public object NewValue { get; private set; } + } +} diff --git a/src/MaaWpfGui/Configuration/RootConfig.cs b/src/MaaWpfGui/Configuration/RootConfig.cs new file mode 100644 index 0000000000..48124f4110 --- /dev/null +++ b/src/MaaWpfGui/Configuration/RootConfig.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Text.Json.Serialization; +using ObservableCollections; + +namespace MaaWpfGui.Configuration +{ + public class RootConfig : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + public ObservableDictionary Configurations { get; set; } = new ObservableDictionary(); + + public string Current { get; set; } = "Default"; + + [JsonIgnore] + public SpecificConfig CurrentConfig + { + get + { + SpecificConfig result = null; + Configurations.TryGetValue(Current, out result); + return result; + } + + set => Configurations[Current] = value; + } + + public void OnPropertyChanged(string propertyName, object before, object after) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventDetailArgs(propertyName, before, after)); + } + } +} diff --git a/src/MaaWpfGui/Configuration/SpecificConfig.cs b/src/MaaWpfGui/Configuration/SpecificConfig.cs new file mode 100644 index 0000000000..b7dc7aece8 --- /dev/null +++ b/src/MaaWpfGui/Configuration/SpecificConfig.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace MaaWpfGui.Configuration +{ + public class SpecificConfig + { + public GUIConfig GuiConfig { get; set; } = new GUIConfig(); + } +} diff --git a/src/MaaWpfGui/Constants/ConfigurationKeys.cs b/src/MaaWpfGui/Constants/ConfigurationKeys.cs index c9bcebab6c..129eff3ae9 100644 --- a/src/MaaWpfGui/Constants/ConfigurationKeys.cs +++ b/src/MaaWpfGui/Constants/ConfigurationKeys.cs @@ -28,7 +28,6 @@ namespace MaaWpfGui.Constants public const string Localization = "GUI.Localization"; public const string MinimizeToTray = "GUI.MinimizeToTray"; public const string HideCloseButton = "GUI.HideCloseButton"; - public const string UseNotify = "GUI.UseNotify"; public const string UseLogItemDateFormat = "GUI.UseLogItemDateFormat"; public const string LogItemDateFormat = "GUI.LogItemDateFormatString"; public const string WindowPlacement = "GUI.Placement"; @@ -39,7 +38,6 @@ namespace MaaWpfGui.Constants public const string CustomStageCode = "GUI.CustomStageCode"; public const string InverseClearMode = "GUI.InverseClearMode"; public const string WindowTitlePrefix = "GUI.WindowTitlePrefix"; - public const string DarkMode = "GUI.DarkMode"; public const string AddressHistory = "Connect.AddressHistory"; public const string AutoDetect = "Connect.AutoDetect"; diff --git a/src/MaaWpfGui/FodyWeavers.xml b/src/MaaWpfGui/FodyWeavers.xml index 5029e70602..97601a61d5 100644 --- a/src/MaaWpfGui/FodyWeavers.xml +++ b/src/MaaWpfGui/FodyWeavers.xml @@ -1,3 +1,3 @@  - - \ No newline at end of file + + diff --git a/src/MaaWpfGui/Helper/ToastNotification.cs b/src/MaaWpfGui/Helper/ToastNotification.cs index 7db6b9667c..f0f617a9ae 100644 --- a/src/MaaWpfGui/Helper/ToastNotification.cs +++ b/src/MaaWpfGui/Helper/ToastNotification.cs @@ -22,6 +22,7 @@ using System.Windows; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; +using MaaWpfGui.Configuration; using MaaWpfGui.Constants; using Microsoft.Toolkit.Uwp.Notifications; using Microsoft.Win32; @@ -394,7 +395,7 @@ namespace MaaWpfGui.Helper NotificationSounds sound = NotificationSounds.Notification, NotificationContent notificationContent = null) { - if (!Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.UseNotify, bool.TrueString))) + if (!ConfigFactory.CurrentConfig.GuiConfig.UseNotify) { return; } diff --git a/src/MaaWpfGui/MaaWpfGui.csproj b/src/MaaWpfGui/MaaWpfGui.csproj index 5e63b070fd..06ac8b5c11 100644 --- a/src/MaaWpfGui/MaaWpfGui.csproj +++ b/src/MaaWpfGui/MaaWpfGui.csproj @@ -108,6 +108,12 @@ 6.1.0.5 + + 1.1.3 + + + 4.1.0 + 2.2.0 diff --git a/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs b/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs index bf6e0a936f..d66d3e16d3 100644 --- a/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/SettingsViewModel.cs @@ -29,6 +29,7 @@ using System.Threading.Tasks; using System.Windows; using HandyControl.Controls; using HandyControl.Data; +using MaaWpfGui.Configuration; using MaaWpfGui.Constants; using MaaWpfGui.Extensions; using MaaWpfGui.Helper; @@ -3130,18 +3131,16 @@ namespace MaaWpfGui.ViewModels.UI } } - private bool _useNotify = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.UseNotify, bool.TrueString)); - /// /// Gets or sets a value indicating whether to use notification. /// public bool UseNotify { - get => _useNotify; + get => ConfigFactory.CurrentConfig.GuiConfig.UseNotify; set { - SetAndNotify(ref _useNotify, value); - ConfigurationHelper.SetValue(ConfigurationKeys.UseNotify, value.ToString()); + ConfigFactory.CurrentConfig.GuiConfig.UseNotify = value; + NotifyOfPropertyChange(); if (value) { Execute.OnUIThread(() => @@ -3271,39 +3270,12 @@ namespace MaaWpfGui.ViewModels.UI } } - /// - /// 表示深色模式的类型。 - /// - public enum DarkModeType - { - /// - /// 明亮的主题。 - /// - Light, - - /// - /// 暗黑的主题。 - /// - Dark, - - /// - /// 与操作系统的深色模式同步。 - /// - SyncWithOS, - } - - private DarkModeType _darkModeType = - Enum.TryParse(ConfigurationHelper.GetValue(ConfigurationKeys.DarkMode, DarkModeType.Light.ToString()), - out DarkModeType temp) - ? temp - : DarkModeType.Light; - /// /// Gets or sets the dark mode. /// public string DarkMode { - get => _darkModeType.ToString(); + get => ConfigFactory.CurrentConfig.GuiConfig.DarkMode.ToString(); set { if (!Enum.TryParse(value, out DarkModeType tempEnumValue)) @@ -3311,8 +3283,8 @@ namespace MaaWpfGui.ViewModels.UI return; } - SetAndNotify(ref _darkModeType, tempEnumValue); - ConfigurationHelper.SetValue(ConfigurationKeys.DarkMode, value); + ConfigFactory.CurrentConfig.GuiConfig.DarkMode = tempEnumValue; + NotifyOfPropertyChange(); SwitchDarkMode(); /* @@ -3331,10 +3303,7 @@ namespace MaaWpfGui.ViewModels.UI public void SwitchDarkMode() { - DarkModeType darkModeType = - Enum.TryParse(ConfigurationHelper.GetValue(ConfigurationKeys.DarkMode, DarkModeType.Light.ToString()), - out DarkModeType temp) - ? temp : DarkModeType.Light; + DarkModeType darkModeType = ConfigFactory.CurrentConfig.GuiConfig.DarkMode; switch (darkModeType) { case DarkModeType.Light: