diff --git a/Natsurainko.FluentLauncher/Services/Settings/SettingsService.cs b/Natsurainko.FluentLauncher/Services/Settings/SettingsService.cs index a5990dd2..f952b234 100644 --- a/Natsurainko.FluentLauncher/Services/Settings/SettingsService.cs +++ b/Natsurainko.FluentLauncher/Services/Settings/SettingsService.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text.Json; using System.Text.Json.Nodes; +using System.Text.Json.Serialization; using Windows.Storage; namespace Natsurainko.FluentLauncher.Services.Settings; @@ -133,9 +134,11 @@ public partial class SettingsService : SettingsContainer [SettingItem(Default = 0u)] public partial uint SettingsVersion { get; set; } - public SettingsService(ISettingsStorage storage) : base(storage) { + // Configure JsonSerializerContext for NativeAOT-compatible JsonStringConverter + JsonStringConverterConfig.SerializerContext = SetingsJsonSerializerContext.Default; + var appsettings = ApplicationData.Current.LocalSettings; // Migrate settings data structures from old versions @@ -271,3 +274,15 @@ private static void MigrateFrom_2_3_0_0() appsettings.Values["ActiveInstanceId"] = clientId; } } + +[JsonSerializable(typeof(int))] +[JsonSerializable(typeof(uint))] +[JsonSerializable(typeof(bool))] +[JsonSerializable(typeof(float))] +[JsonSerializable(typeof(double))] +[JsonSerializable(typeof(string))] +[JsonSerializable(typeof(Windows.UI.Color))] +[JsonSerializable(typeof(WinUIEx.WindowState))] +internal partial class SetingsJsonSerializerContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/infra/FluentLauncher.Infra.Settings.SourceGenerators/SettingsItemGenerator.cs b/infra/FluentLauncher.Infra.Settings.SourceGenerators/SettingsItemGenerator.cs index 639c23e8..1dec9606 100644 --- a/infra/FluentLauncher.Infra.Settings.SourceGenerators/SettingsItemGenerator.cs +++ b/infra/FluentLauncher.Infra.Settings.SourceGenerators/SettingsItemGenerator.cs @@ -124,14 +124,26 @@ private static void GenerateSettingItem(SettingItemInfo settingItemInfo, StringB string defaultValue = ""; string converter = ""; + string sourceTypeName = ""; // Parse default value and converter foreach(var item in attribute.NamedArguments) { if (item.Key == "Default") + { defaultValue = $", {item.Value.ToCSharpString()}"; + } else if (item.Key == "Converter") - converter = $", global::FluentLauncher.Infra.Settings.Converters.DataTypeConverters.GetConverter(typeof(global::{item.Value.Value}))"; + { + if (item.Value.Value is not ITypeSymbol converterType) + continue; + INamedTypeSymbol? interfaceType = converterType.Interfaces.FirstOrDefault(syn => syn.Name.Contains("IDataTypeConverter")); + if (interfaceType is null) continue; + ITypeSymbol sourceType = interfaceType.TypeArguments[0]; + + sourceTypeName = $", {sourceType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}"; + converter = $", global::{item.Value.Value}.Instance"; + } } // If default value is not provided, the property is nullable @@ -142,8 +154,8 @@ private static void GenerateSettingItem(SettingItemInfo settingItemInfo, StringB memberBuilder.Append($$""" public partial {{propTypeName}}{{nullable}} {{propIdentifierName}} { - get => GetValue<{{propTypeName}}{{nullable}}>(nameof({{propIdentifierName}}){{defaultValue}}{{converter}}); - set => SetValue<{{propTypeName}}>(nameof({{propIdentifierName}}), value, {{propIdentifierName}}Changed{{converter}}); + get => GetValue<{{propTypeName}}{{sourceTypeName}}>(nameof({{propIdentifierName}}){{defaultValue}}{{converter}}); + set => SetValue<{{propTypeName}}{{sourceTypeName}}>(nameof({{propIdentifierName}}), value, {{propIdentifierName}}Changed{{converter}}); } public event global::FluentLauncher.Infra.Settings.SettingChangedEventHandler? {{propIdentifierName}}Changed; diff --git a/infra/FluentLauncher.Infra.Settings/Converters/DataTypeConverters.cs b/infra/FluentLauncher.Infra.Settings/Converters/DataTypeConverters.cs index bf177b61..10dfe18a 100644 --- a/infra/FluentLauncher.Infra.Settings/Converters/DataTypeConverters.cs +++ b/infra/FluentLauncher.Infra.Settings/Converters/DataTypeConverters.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -10,49 +11,12 @@ namespace FluentLauncher.Infra.Settings.Converters; /// ConvertFrom is used when converting an object from the displayed type to the stored type.
/// Convert is used when converting an object from the stored type to the displayed type. /// -public interface IDataTypeConverter +/// Type stored in the storage +/// Type used in the application +public interface IDataTypeConverter { - /// - /// Type stored in the storage - /// - Type SourceType { get; } - /// - /// Type used in the application - /// - Type TargetType { get; } + TTarget Convert(TSource source); + TSource ConvertFrom(TTarget target); - object? Convert(object? source); - object? ConvertFrom(object? target); -} - -public static class DataTypeConverters -{ - public static Dictionary Converters { get; } = new(); - - /// - /// Returns an instance of the converter for the specified type. - /// - /// Type of the converter class - /// - /// Singletons of the converters are stored in the Converters dictionary. - /// - public static IDataTypeConverter GetConverter(Type type) - { - // Checks if type is IDataTypeConverter - if (typeof(IDataTypeConverter).IsAssignableFrom(type)) - { - // Checks if the converter is already registered - if (!Converters.ContainsKey(type)) - { - // Creates an instance of the converter - var converter = (IDataTypeConverter)Activator.CreateInstance(type)!; - Converters.Add(type, converter); - } - return Converters[type]; - } - else - { - throw new ArgumentException("The specified type is not a data type converter."); - } - } + static abstract IDataTypeConverter Instance { get; } } diff --git a/infra/FluentLauncher.Infra.Settings/Converters/JsonStringConverter.cs b/infra/FluentLauncher.Infra.Settings/Converters/JsonStringConverter.cs index 98fe99c5..0a20077d 100644 --- a/infra/FluentLauncher.Infra.Settings/Converters/JsonStringConverter.cs +++ b/infra/FluentLauncher.Infra.Settings/Converters/JsonStringConverter.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; namespace FluentLauncher.Infra.Settings.Converters; @@ -11,29 +13,30 @@ namespace FluentLauncher.Infra.Settings.Converters; /// Converts a string to a type T using JSON serialization /// /// Type used in the application -public class JsonStringConverter : IDataTypeConverter +public class JsonStringConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T> : IDataTypeConverter { - /// - public Type SourceType => typeof(string); + private readonly JsonSerializerContext _serializerContext; - /// - public Type TargetType => typeof(T); + public JsonStringConverter() + { + if (JsonStringConverterConfig.SerializerContext is null) + throw new InvalidOperationException("JsonSerializerContext is not available."); + _serializerContext = JsonStringConverterConfig.SerializerContext; + } + public static IDataTypeConverter Instance { get; } = new JsonStringConverter(); /// /// Returns the type used in the application /// /// /// - public T? Convert(string json) => (T?)((IDataTypeConverter)this).Convert(json); - - /// - object? IDataTypeConverter.Convert(object? source) + public T? Convert(string json) { - if (source is not string json) - return null; + if (json is null || JsonStringConverterConfig.SerializerContext is null) + return default; - return JsonSerializer.Deserialize(json, TargetType); + return (T?)JsonSerializer.Deserialize(json, typeof(T), _serializerContext); } /// @@ -41,14 +44,13 @@ public class JsonStringConverter : IDataTypeConverter /// /// /// - public string? Convert(T target) => (string?)((IDataTypeConverter)this).ConvertFrom(target); - - /// - object? IDataTypeConverter.ConvertFrom(object? target) + public string ConvertFrom(T? target) { - if (target is not T) - return null; - - return JsonSerializer.Serialize(target, TargetType); + return JsonSerializer.Serialize(target, typeof(T), _serializerContext); } } + +public static class JsonStringConverterConfig +{ + public static JsonSerializerContext? SerializerContext { get; set; } +} diff --git a/infra/FluentLauncher.Infra.Settings/FluentLauncher.Infra.Settings.csproj b/infra/FluentLauncher.Infra.Settings/FluentLauncher.Infra.Settings.csproj index 0672ddb0..d86a0c34 100644 --- a/infra/FluentLauncher.Infra.Settings/FluentLauncher.Infra.Settings.csproj +++ b/infra/FluentLauncher.Infra.Settings/FluentLauncher.Infra.Settings.csproj @@ -3,6 +3,7 @@ net8.0 enable + true diff --git a/infra/FluentLauncher.Infra.Settings/Interfaces.cs b/infra/FluentLauncher.Infra.Settings/Interfaces.cs index a3c3e1b6..eac3438b 100644 --- a/infra/FluentLauncher.Infra.Settings/Interfaces.cs +++ b/infra/FluentLauncher.Infra.Settings/Interfaces.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -53,7 +54,7 @@ public interface ISettingsStorage /// Throws if the key is not found /// If a type converter is specified, the storage provider will be responsible for converting the type stored ///
into T when the item is accessed, and converting T into the type used in storage.
- T GetValue(string path) where T : notnull; + T GetValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string path) where T : notnull; /// /// Obtains the value of a setting item from the storage @@ -61,7 +62,7 @@ public interface ISettingsStorage /// A unique path that locates the item in the storage /// Type of the value expected
This may be used when dealing with special types such as arrays. /// - object GetValue(string path, Type type); + //object GetValue(string path, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type); /// /// Sets a setting item to a new value in the storage @@ -78,5 +79,5 @@ public interface ISettingsStorage /// The new value /// Type of the value /// - void SetValue(string path, object value, Type type); + //void SetValue(string path, object value, Type type); } \ No newline at end of file diff --git a/infra/FluentLauncher.Infra.Settings/Properties/PublishProfiles/FolderProfile.pubxml b/infra/FluentLauncher.Infra.Settings/Properties/PublishProfiles/FolderProfile.pubxml deleted file mode 100644 index 7bb18bdd..00000000 --- a/infra/FluentLauncher.Infra.Settings/Properties/PublishProfiles/FolderProfile.pubxml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Release - x64 - bin\Release\net7.0\publish\ - FileSystem - <_TargetId>Folder - net7.0 - win-x86 - true - false - true - true - - \ No newline at end of file diff --git a/infra/FluentLauncher.Infra.Settings/SettingsAttributes.cs b/infra/FluentLauncher.Infra.Settings/SettingsAttributes.cs index 2d76c613..26ff2105 100644 --- a/infra/FluentLauncher.Infra.Settings/SettingsAttributes.cs +++ b/infra/FluentLauncher.Infra.Settings/SettingsAttributes.cs @@ -12,11 +12,6 @@ namespace FluentLauncher.Infra.Settings [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class SettingItemAttribute : Attribute { - /// - /// Key of the setting item - /// - [Obsolete] - public string? Key { get; init; } /// /// An optional converter to convert the value stored in the storage to the type of the property.
If not specified, type casting will be used. ///
diff --git a/infra/FluentLauncher.Infra.Settings/SettingsCollection.cs b/infra/FluentLauncher.Infra.Settings/SettingsCollection.cs index a2d93d1d..df7ea4f4 100644 --- a/infra/FluentLauncher.Infra.Settings/SettingsCollection.cs +++ b/infra/FluentLauncher.Infra.Settings/SettingsCollection.cs @@ -3,57 +3,79 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using FluentLauncher.Infra.Settings.Converters; namespace FluentLauncher.Infra.Settings; -// Cannot store null values in a collection public class SettingsCollection : ObservableCollection where T : notnull { private readonly ISettingsStorage _settingsStorage; - private readonly IDataTypeConverter? _typeConverter; private readonly string _storagePath; - - public SettingsCollection(ISettingsStorage settingsStorage, string storagePath, IDataTypeConverter? typeConverter = null) + public SettingsCollection(ISettingsStorage settingsStorage, string storagePath) { _settingsStorage = settingsStorage; _storagePath = storagePath; - - if (typeConverter is not null) + + // Init the storage with an empty array if it doesn't exist + if (!_settingsStorage.Contains(storagePath)) { - if (typeConverter.TargetType == typeof(T)) - _typeConverter = typeConverter; - else - throw new ArgumentException($"Typer converter given cannot convert from {typeof(T)}", nameof(typeConverter)); + _settingsStorage.SetValue(storagePath, Array.Empty()); } + + // Load initial values from storage + T[] storedArray = _settingsStorage.GetValue(storagePath); + + foreach (T value in storedArray) + Add(value); + + // Register CollectionChanged event after initialization + CollectionChanged += SettingsCollection_CollectionChanged; + } + + private void SettingsCollection_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + // Save the entire list as an array in the storage + T[] array = this.ToArray(); + _settingsStorage.SetValue(_storagePath, array); + } +} + +public class SettingsCollection : ObservableCollection where T : notnull +{ + private readonly ISettingsStorage _settingsStorage; + private readonly IDataTypeConverter _typeConverter; + private readonly string _storagePath; + + public SettingsCollection(ISettingsStorage settingsStorage, string storagePath, IDataTypeConverter typeConverter) + { + _settingsStorage = settingsStorage; + _storagePath = storagePath; + _typeConverter = typeConverter; // Init the storage with an empty array if it doesn't exist if (!_settingsStorage.Contains(storagePath)) { - Type elementType = _typeConverter is null ? typeof(T) : _typeConverter.SourceType; - _settingsStorage.SetValue(storagePath, Array.CreateInstance(elementType, 0)); + _settingsStorage.SetValue(storagePath, Array.Empty()); } - + // Load initial values from storage // Get the array from the storage and apply type conversion if (typeConverter is not null) { - Array storedArray = (Array)_settingsStorage.GetValue(storagePath, typeConverter.SourceType.MakeArrayType()); - - // Throws if the type converter cannot convert to T - if (typeConverter.TargetType != typeof(T)) - throw new ArgumentException($"Typer converter given cannot convert to {typeof(T)}", nameof(typeConverter)); + TSource[] storedArray = _settingsStorage.GetValue(storagePath); // Convert each element of the array and add it to the collection IEnumerator enumerator = storedArray.GetEnumerator(); while (enumerator.MoveNext()) { - T convertedValue = (T)typeConverter.ConvertFrom(enumerator.Current)!; // Type conversion is guaranteed to succeed + T convertedValue = typeConverter.Convert((TSource)enumerator.Current); Add(convertedValue); } } @@ -71,20 +93,8 @@ public SettingsCollection(ISettingsStorage settingsStorage, string storagePath, private void SettingsCollection_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { - // Save the entire list as an array in the storage - if(_typeConverter is not null) // Convert each element using the type converter - { - if (typeof(T) != _typeConverter.TargetType) - throw new InvalidCastException($"Type converter given does not support convertion from {typeof(T)}"); - - Array convertedArray = Array.CreateInstance(_typeConverter.SourceType, this.Count); - this.Select(item => _typeConverter.ConvertFrom(item)).ToArray().CopyTo(convertedArray, 0); - _settingsStorage.SetValue(_storagePath, convertedArray); - } - else // No type conversion needed - { - T[] array = this.ToArray(); - _settingsStorage.SetValue(_storagePath, array); - } + TSource[] convertedArray = new TSource[Count]; + this.Select(item => _typeConverter.ConvertFrom(item)).ToArray().CopyTo(convertedArray, 0); + _settingsStorage.SetValue(_storagePath, convertedArray); } } diff --git a/infra/FluentLauncher.Infra.Settings/SettingsContainer.cs b/infra/FluentLauncher.Infra.Settings/SettingsContainer.cs index 0545f553..6fe09502 100644 --- a/infra/FluentLauncher.Infra.Settings/SettingsContainer.cs +++ b/infra/FluentLauncher.Infra.Settings/SettingsContainer.cs @@ -77,33 +77,30 @@ private string GetPathFromKey(string key) #region GetValue - protected T? GetValue(string key, IDataTypeConverter? converter = null) + protected T? GetValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string key) + where T : notnull { var path = GetPathFromKey(key); if (!Storage.Contains(path)) - { return default(T?); // null - } Type type = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); + return Storage.GetValue(path); + } + + protected T? GetValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TSource>(string key, IDataTypeConverter converter) + where T: notnull + where TSource: notnull + { + var path = GetPathFromKey(key); + + if (!Storage.Contains(path)) + return default(T?); // null // If a type converter is given, use it to convert the value; otherwise, return the value as the type stored - if (converter is not null) - { - if (converter.TargetType == type) - { - object value = Storage.GetValue(path, converter.SourceType); - if (value.GetType() == converter.SourceType) - return (T?)converter.Convert(value); - } - - throw new ArgumentException($"Typer converter given cannot convert to {type}", nameof(converter)); - } - else - { - return (T)Storage.GetValue(path, type); - } + TSource value = Storage.GetValue(path); + return (T?)converter.Convert(value); } /// @@ -113,29 +110,52 @@ private string GetPathFromKey(string key) /// /// Cannot be null; the setting item is removed if set to null. /// - protected T GetValue(string key, T defaultValue, IDataTypeConverter? converter = null) where T : notnull + protected T GetValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TSource>(string key, T defaultValue, IDataTypeConverter converter) + where T : notnull + where TSource : notnull { if (defaultValue is null) throw new ArgumentNullException(nameof(defaultValue)); - if (converter is not null && converter.TargetType != typeof(T)) - throw new ArgumentException($"Type converter given cannot convert to {typeof(T)}", nameof(converter)); - var path = GetPathFromKey(key); - object defaultValueConverted = converter?.ConvertFrom(defaultValue) ?? defaultValue; + TSource defaultValueConverted = converter.ConvertFrom(defaultValue); if (!Storage.Contains(path)) // key is not found { - Storage.SetValue(path, defaultValueConverted, defaultValueConverted.GetType()); + Storage.SetValue(path, defaultValueConverted); return defaultValue; } // Try to get the value from the storage - T? value = GetValue(key, converter); + T? value = GetValue(key, converter); if (value is null) { - Storage.SetValue(path, defaultValueConverted, defaultValueConverted.GetType()); + Storage.SetValue(path, defaultValueConverted); + return defaultValue; + } + + return value; + } + + protected T GetValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string key, T defaultValue) where T : notnull + { + if (defaultValue is null) + throw new ArgumentNullException(nameof(defaultValue)); + + var path = GetPathFromKey(key); + + if (!Storage.Contains(path)) // key is not found + { + Storage.SetValue(path, defaultValue); + return defaultValue; + } + + // Try to get the value from the storage + T? value = GetValue(key); + if (value is null) + { + Storage.SetValue(path, defaultValue); return defaultValue; } @@ -146,44 +166,44 @@ protected T GetValue(string key, T defaultValue, IDataTypeConverter? converte #region SetValue - private void _setValue(string key, + private void SetValueInternal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TSource>(string key, T value, SettingChangedEventHandler? _event, - IDataTypeConverter? converter = null) where T : notnull + IDataTypeConverter converter) + where T : notnull + where TSource: notnull { var path = GetPathFromKey(key); - object? currentValue; - if (converter is not null) - { - if (typeof(T) != converter.TargetType) - throw new ArgumentException($"Type converter given cannot convert from {typeof(T)}", nameof(converter)); - currentValue = Storage.Contains(path) ? Storage.GetValue(path, converter.SourceType) : null; - } - else - { - currentValue = Storage.Contains(path) ? Storage.GetValue(path) : null; - } + T? currentValue = Storage.Contains(path) ? converter.Convert(Storage.GetValue(path)) : default; if (value.Equals(currentValue)) return; // Only set the value when the new value is different - if (converter is not null) // Convert value from T using the converter - { - if (typeof(T) != converter.TargetType) - throw new ArgumentException($"Type converter given cannot convert from {typeof(T)}", nameof(converter)); + object? convertedValue = converter.ConvertFrom(value); + if(convertedValue is null) + Storage.DeleteItem(path); + else + Storage.SetValue(path, convertedValue); - object? convertedValue = converter.ConvertFrom(value); + // Notify a setting item has been changed + _event?.Invoke(this, new SettingChangedEventArgs(path, value)); + SettingsChanged?.Invoke(this, new SettingChangedEventArgs(path, value)); + } - if(convertedValue is null) - Storage.DeleteItem(path); - else - Storage.SetValue(path, convertedValue); - } - else // No converter is given; store the value as type T. - { - Storage.SetValue(path, value); - } + private void SetValueInternal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string key, + T value, + SettingChangedEventHandler? _event) + where T : notnull + { + var path = GetPathFromKey(key); + T? currentValue = Storage.Contains(path) ? Storage.GetValue(path) : default; + + if (value.Equals(currentValue)) + return; + + // No converter is given; store the value as type T. + Storage.SetValue(path, value); // Notify a setting item has been changed _event?.Invoke(this, new SettingChangedEventArgs(path, value)); @@ -197,17 +217,34 @@ private void _setValue(string key, /// /// /// - protected void SetValue(string key, + protected void SetValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TSource>(string key, T? value, SettingChangedEventHandler? _event, - IDataTypeConverter? converter = null) where T : struct + IDataTypeConverter converter) + where T : struct + where TSource : notnull { if (value is null) { Storage.DeleteItem(key); return; } - _setValue(key, (T)value, _event, converter); + + SetValueInternal(key, (T)value, _event, converter); + } + + protected void SetValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string key, + T? value, + SettingChangedEventHandler? _event) + where T : struct + { + if (value is null) + { + Storage.DeleteItem(key); + return; + } + + SetValueInternal(key, (T)value, _event); } /// @@ -217,17 +254,32 @@ protected void SetValue(string key, /// /// /// - protected void SetValue(string key, + protected void SetValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TSource>(string key, T? value, SettingChangedEventHandler? _event, - IDataTypeConverter? converter = null) where T : class + IDataTypeConverter converter) + where T : class + where TSource : notnull + { + if (value is null) + { + Storage.DeleteItem(key); + return; + } + SetValueInternal(key, value, _event, converter); + } + + protected void SetValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string key, + T? value, + SettingChangedEventHandler? _event) + where T : class { if (value is null) { Storage.DeleteItem(key); return; } - _setValue(key, value, _event, converter); + SetValueInternal(key, value, _event); } #endregion SetValue diff --git a/infra/FluentLauncher.Infra.UI/Pages/IPageProvider.cs b/infra/FluentLauncher.Infra.UI/Pages/IPageProvider.cs index d5f83272..f07591e8 100644 --- a/infra/FluentLauncher.Infra.UI/Pages/IPageProvider.cs +++ b/infra/FluentLauncher.Infra.UI/Pages/IPageProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace FluentLauncher.Infra.UI.Pages; @@ -12,4 +13,19 @@ public interface IPageProvider object? GetViewModel(string key); } -public record PageDescriptor(Type PageType, Type? ViewModelType = default); +public record PageDescriptor +{ + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + public Type PageType { get; init; } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + public Type? ViewModelType { get; init; } + + public PageDescriptor( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type pageType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type? vmType) + { + PageType = pageType; + ViewModelType = vmType; + } +} diff --git a/infra/FluentLauncher.Infra.UI/Pages/PageProviderBuilder.cs b/infra/FluentLauncher.Infra.UI/Pages/PageProviderBuilder.cs index 3872b97e..8c84d158 100644 --- a/infra/FluentLauncher.Infra.UI/Pages/PageProviderBuilder.cs +++ b/infra/FluentLauncher.Infra.UI/Pages/PageProviderBuilder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace FluentLauncher.Infra.UI.Pages; @@ -11,13 +12,16 @@ public abstract class PageProviderBuilder where TPageP public PageProviderBuilder() { } - public PageProviderBuilder WithPage(string key) + public PageProviderBuilder WithPage<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TPage, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TViewModel>(string key) => WithPage(key, typeof(TPage), typeof(TViewModel)); - public PageProviderBuilder WithPage(string key) + public PageProviderBuilder WithPage<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TPage>(string key) => WithPage(key, typeof(TPage)); - public PageProviderBuilder WithPage(string key, Type pageType, Type? viewModelType = null) + public PageProviderBuilder WithPage( + string key, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type pageType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type? viewModelType = null) { _registeredPages.Add(key, new PageDescriptor(pageType, viewModelType)); return this; diff --git a/infra/FluentLauncher.Infra.UI/Windows/ActivationServiceBuilder.cs b/infra/FluentLauncher.Infra.UI/Windows/ActivationServiceBuilder.cs index 5f9bb859..edc91d03 100644 --- a/infra/FluentLauncher.Infra.UI/Windows/ActivationServiceBuilder.cs +++ b/infra/FluentLauncher.Infra.UI/Windows/ActivationServiceBuilder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; namespace FluentLauncher.Infra.UI.Windows; @@ -25,7 +26,7 @@ public ActivationServiceBuilder() { } /// Key of the window /// Type of the window /// Whether multiple instances of the window is allowed - public ActivationServiceBuilder WithWindow(string key, Type windowType, bool multiInstance) + public ActivationServiceBuilder WithWindow(string key, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type windowType, bool multiInstance) { if (!windowType.IsSubclassOf(typeof(TWindowBase))) throw new ArgumentException($"Type {windowType} is not a subclass of {typeof(TWindowBase)}"); @@ -34,16 +35,16 @@ public ActivationServiceBuilder WithWindow(string key, Ty return this; } - public ActivationServiceBuilder AddSingleInstanceWindow(string key, Type windowType) + public ActivationServiceBuilder AddSingleInstanceWindow(string key, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type windowType) => WithWindow(key, windowType, false); - public ActivationServiceBuilder AddSingleInstanceWindow(string key) + public ActivationServiceBuilder AddSingleInstanceWindow<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TWindow>(string key) => AddSingleInstanceWindow(key, typeof(TWindow)); - public ActivationServiceBuilder AddMultiInstanceWindow(string key, Type windowType) + public ActivationServiceBuilder AddMultiInstanceWindow(string key, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type windowType) => WithWindow(key, windowType, true); - public ActivationServiceBuilder AddMultiInstanceWindow(string key) + public ActivationServiceBuilder AddMultiInstanceWindow<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TWindow>(string key) => AddMultiInstanceWindow(key, typeof(TWindow)); public abstract IActivationService Build(IServiceProvider serviceProvider); diff --git a/infra/FluentLauncher.Infra.UI/Windows/IActivationService.cs b/infra/FluentLauncher.Infra.UI/Windows/IActivationService.cs index 997bf592..8b302087 100644 --- a/infra/FluentLauncher.Infra.UI/Windows/IActivationService.cs +++ b/infra/FluentLauncher.Infra.UI/Windows/IActivationService.cs @@ -1,9 +1,24 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace FluentLauncher.Infra.UI.Windows; -public record WindowDescriptor(Type WindowType, bool AllowMultiInstances = false); +public record WindowDescriptor +{ + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + public Type WindowType { get; init; } + + public bool AllowMultiInstances { get; init; } + + public WindowDescriptor( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type windowType, + bool allowMultiInstances = false) + { + WindowType = windowType; + AllowMultiInstances = allowMultiInstances; + } +} /// /// A service for activating app windows. diff --git a/infra/FluentLauncher.Infra.WinUI/FluentLauncher.Infra.WinUI.csproj b/infra/FluentLauncher.Infra.WinUI/FluentLauncher.Infra.WinUI.csproj index 806adfc3..1d16db1f 100644 --- a/infra/FluentLauncher.Infra.WinUI/FluentLauncher.Infra.WinUI.csproj +++ b/infra/FluentLauncher.Infra.WinUI/FluentLauncher.Infra.WinUI.csproj @@ -7,6 +7,7 @@ win10-x86;win10-x64;win10-arm64 true enable + true diff --git a/infra/FluentLauncher.Infra.WinUI/Settings/WinRTSettingsStorage.cs b/infra/FluentLauncher.Infra.WinUI/Settings/WinRTSettingsStorage.cs index 20f38c94..2756a872 100644 --- a/infra/FluentLauncher.Infra.WinUI/Settings/WinRTSettingsStorage.cs +++ b/infra/FluentLauncher.Infra.WinUI/Settings/WinRTSettingsStorage.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using FluentLauncher.Infra.Settings; +using System.Diagnostics.CodeAnalysis; namespace FluentLauncher.Infra.WinUI.Settings; @@ -97,17 +98,15 @@ public void DeleteItem(string path) // so there is no need to use generic types. /// - public T GetValue(string path) where T : notnull - => (T)GetValue(path, typeof(T)); - - /// - public object GetValue(string path, Type type) + public T GetValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string path) + where T : notnull { (ApplicationDataContainer? container, string key) = GetContainerAndKey(path); + Type type = typeof(T); if (type.IsArray) { - Type elementType = type.GetElementType()!; + Type elementType = typeof(T).GetElementType()!; if (!IsTypeSupported(elementType)) throw new InvalidOperationException($"Type {elementType} is not supported by {nameof(WinRTSettingsStorage)}"); @@ -116,7 +115,7 @@ public object GetValue(string path, Type type) if (container is not null && container.Values.ContainsKey(key)) { // If the key exists, return the stored array, which is never empty. - object value = container.Values[key]; + T value = (T)container.Values[key]; // Check that the stored value is an array of the correct type. if (value.GetType().GetElementType() != elementType) @@ -127,7 +126,7 @@ public object GetValue(string path, Type type) else // If the array is empty, it is stored as null in the ApplicationDataContainer. { // Return an empty array of the correct type. - return Array.CreateInstance(elementType, 0); + return (T)Activator.CreateInstance(typeof(T), 0)!; } } @@ -145,10 +144,10 @@ public object GetValue(string path, Type type) { // Retrieve enums as their integral types var integralValue = container.Values[key]; - return Enum.ToObject(type, integralValue); + return (T)Enum.ToObject(type, integralValue); } - return container.Values[key]; + return (T)container.Values[key]; } #endregion