From dff219f951dbfa2f8e91867fc5a5aefef609db04 Mon Sep 17 00:00:00 2001
From: gaviny82 <38808970+gaviny82@users.noreply.github.com>
Date: Sun, 8 Sep 2024 14:42:21 +0800
Subject: [PATCH] Fix AOT compatibility issues in Infra (#273)
* Enable AOT compatibility analyzers
* AOT compatible design for SettingItem converter
* Add source generation json serializer context for SettingsService
* Mark SettingsCollection as AOT incompatible
* Config serializer context in SettingsService
* Mark dynamically accessed members in Infra.UI
* Add DynamicallyAccessedMembers to WinRTSettingsStorage
* Add DynamicallyAccessedMembers to SettingsContainer
* Mark SettingsCollection as trimming incompatible
* Clean up
* Refactor Infra.Settings using generics for AOT
* Fix SettingItem generator
* Fix JsonStringConverter
---
.../Services/Settings/SettingsService.cs | 17 +-
.../SettingsItemGenerator.cs | 18 +-
.../Converters/DataTypeConverters.cs | 50 +-----
.../Converters/JsonStringConverter.cs | 42 ++---
.../FluentLauncher.Infra.Settings.csproj | 1 +
.../Interfaces.cs | 7 +-
.../PublishProfiles/FolderProfile.pubxml | 19 --
.../SettingsAttributes.cs | 5 -
.../SettingsCollection.cs | 78 ++++----
.../SettingsContainer.cs | 170 ++++++++++++------
.../Pages/IPageProvider.cs | 18 +-
.../Pages/PageProviderBuilder.cs | 10 +-
.../Windows/ActivationServiceBuilder.cs | 11 +-
.../Windows/IActivationService.cs | 17 +-
.../FluentLauncher.Infra.WinUI.csproj | 1 +
.../Settings/WinRTSettingsStorage.cs | 19 +-
16 files changed, 276 insertions(+), 207 deletions(-)
delete mode 100644 infra/FluentLauncher.Infra.Settings/Properties/PublishProfiles/FolderProfile.pubxml
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