From 14ea3f9fc10ba10b7628739ddffaa699795f990d Mon Sep 17 00:00:00 2001 From: Andreas Date: Thu, 21 Mar 2024 08:07:53 +0100 Subject: [PATCH] Added `readme.txt` Fixed #31 --- src/MauiSettings.sln | 11 +- src/MauiSettings/MauiSettings.csproj | 1 + src/MauiSettings/readme.txt | 207 +++++++++++++++++++++++++++ 3 files changed, 209 insertions(+), 10 deletions(-) create mode 100644 src/MauiSettings/readme.txt diff --git a/src/MauiSettings.sln b/src/MauiSettings.sln index 4fddb2e..c6a5a9b 100644 --- a/src/MauiSettings.sln +++ b/src/MauiSettings.sln @@ -10,9 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Projektmappenelemente", "Pr nuget.config = nuget.config EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedNetCoreLibrary", "..\..\SharedMauiCoreLibrary\src\SharedNetCoreLibrary\SharedNetCoreLibrary.csproj", "{CA7C2653-5783-4A4C-960A-97CA4038748F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MauiSettings.Example", "MauiSettings.Example\MauiSettings.Example.csproj", "{343E7298-7A56-4737-A1A2-312835BCB98C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiSettings.Example", "MauiSettings.Example\MauiSettings.Example.csproj", "{343E7298-7A56-4737-A1A2-312835BCB98C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -24,10 +22,6 @@ Global {9A79D717-2C07-48A4-A78D-BD252E9BCA25}.Debug|Any CPU.Build.0 = Debug|Any CPU {9A79D717-2C07-48A4-A78D-BD252E9BCA25}.Release|Any CPU.ActiveCfg = Release|Any CPU {9A79D717-2C07-48A4-A78D-BD252E9BCA25}.Release|Any CPU.Build.0 = Release|Any CPU - {CA7C2653-5783-4A4C-960A-97CA4038748F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CA7C2653-5783-4A4C-960A-97CA4038748F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CA7C2653-5783-4A4C-960A-97CA4038748F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CA7C2653-5783-4A4C-960A-97CA4038748F}.Release|Any CPU.Build.0 = Release|Any CPU {343E7298-7A56-4737-A1A2-312835BCB98C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {343E7298-7A56-4737-A1A2-312835BCB98C}.Debug|Any CPU.Build.0 = Debug|Any CPU {343E7298-7A56-4737-A1A2-312835BCB98C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU @@ -38,9 +32,6 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {CA7C2653-5783-4A4C-960A-97CA4038748F} = {26232A22-4BBC-4509-99A5-987CE67F1882} - EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2733F837-2A02-4993-BB5C-771792807C3C} EndGlobalSection diff --git a/src/MauiSettings/MauiSettings.csproj b/src/MauiSettings/MauiSettings.csproj index e6c59b1..90a5910 100644 --- a/src/MauiSettings/MauiSettings.csproj +++ b/src/MauiSettings/MauiSettings.csproj @@ -37,6 +37,7 @@ + diff --git a/src/MauiSettings/readme.txt b/src/MauiSettings/readme.txt new file mode 100644 index 0000000..150a333 --- /dev/null +++ b/src/MauiSettings/readme.txt @@ -0,0 +1,207 @@ +# MauiSettings +A nuget to improve settings storage (locally and eventually in the cloud) on .NET MAUI projects. + +The plugin idea is based on the Advexp.Settings.Local nuget by Alexey Ivakin
+Repo: https://bitbucket.org/advexp/component-advexp.settings/src/master/
+License: Apache-2.0 (https://licenses.nuget.org/Apache-2.0)
+ +This project was created from scratch, however uses the basic idea to keep all Settings in the +static object. All taken and changed files have been marked so. + +# Nuget +Get the latest version from nuget.org
+[![NuGet](https://img.shields.io/nuget/v/SettingsMaui.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/SettingsMaui/) +[![NuGet](https://img.shields.io/nuget/dt/SettingsMaui.svg)](https://www.nuget.org/packages/SettingsMaui) + +# Usage +## Settings Object +In the .NET MAUI project, create a new `Class` (for instance `SettingsApp.cs`) holding your setting properties. + +```cs +public partial class SettingsApp : MauiSettings +{ + + #region Settings + + #region Version + [MauiSetting(Name = nameof(App_SettingsVersion))] + public static Version App_SettingsVersion { get; set; } = new("1.0.0"); + + #endregion + + #region CloudSync + [MauiSetting(Name = nameof(Cloud_ShowInitialPrompt), DefaultValue = true)] + public static bool Cloud_ShowInitialPrompt { get; set; } + + [MauiSetting(Name = nameof(Cloud_ShowInitialPrompt), DefaultValue = SettingsStaticDefault.Cloud_EnableSync)] + public static bool Cloud_EnableSync { get; set; } + #endregion + + #region Theme + [MauiSetting(Name = nameof(Theme_UseDeviceDefaultSettings), DefaultValue = SettingsStaticDefault.General_UseDeviceSettings)] + public static bool Theme_UseDeviceDefaultSettings { get; set; } + + [MauiSetting(Name = nameof(Theme_UseDarkTheme), DefaultValue = SettingsStaticDefault.General_UseDarkTheme)] + public static bool Theme_UseDarkTheme { get; set; } + + [MauiSetting(Name = nameof(Theme_PrimaryThemeColor), DefaultValue = SettingsStaticDefault.Theme_PrimaryThemeColor)] + public static string Theme_PrimaryThemeColor { get; set; } + + #endregion + + #region Localization + + [MauiSetting(Name = nameof(Localization_CultureCode), DefaultValue = SettingsStaticDefault.Localization_Default)] + public static string Localization_CultureCode { get; set; } + + #endregion + + #region Secure + + // Encrypt: The value is encrypt before saving it on the device, and decrypt when loaded + // Note: Only `Secure` properties can be encrypted + [MauiSetting(Name = nameof(Localization_CultureCode), DefaultValue ="", Secure = true, Encrypt = true)] + public static string User_Username { get; set; } + + // SkipForExport: Value is not added to the Dictionary when exporting the settings + [MauiSetting(Name = nameof(Localization_CultureCode), DefaultValue ="", Secure = true, Encrypt = true, SkipForExport = true)] + public static string User_Password { get; set; } + + #endregion + + #endregion +} +``` + +## Load +To load the settings from the storage, call `SettingsApp.LoadSettings()` (mostly in the `App` constructor of your App.xmls file. The project uses the `Maui.Storage.Preferences` in order to store the settings on the corresponding device. + +## Save +Whenever you do make changes to a settings property of your class, call `SettingsApp.SaveSettings()`. This will write the settings to the storage. + +# Encryption +Starting from version `1.0.6`, you can encrypt secure properties with a `AES`encryption. An example how it works is shown below. +Sample: https://github.com/AndreasReitberger/MauiSettings/tree/main/src/MauiSettings.Example + +## App.xaml.cs +An example of how to load the settings from the `App.xaml`. + +```cs +public partial class App : Application +{ + // Example key, it is recommended to generate the key on the device and save it as `Secure` property instead of + // adding it in clear text to the source code. + public static string Hash = "mYGUbR61NUNjIvdEv/veySPxQEWcCRUZ3SZ7TT72IuI="; + public App() + { + InitializeComponent(); + + // Example of how to generate a new key + //string t = EncryptionManager.GenerateBase64Key(); + + // Only Async methods do support encryption! + _ = Task.Run(async () => await SettingsApp.LoadSettingsAsync(Hash)); + MainPage = new AppShell(); + } + + protected override void OnSleep() + { + base.OnSleep(); + if (SettingsApp.SettingsChanged) + { + try + { + SettingsApp.SaveSettings(Hash); + } + catch (Exception) + { + + } + } + } +} +``` + +## MainPageViewModel +An example of a`ViewModel` loading and saving encrypted settings. +```cs +public partial class MainPageViewModel : ObservableObject +{ + #region Settings + [ObservableProperty] + bool isLoading = false; + + [ObservableProperty] + string hashKey = App.Hash; + + [ObservableProperty] + string licenseInfo = string.Empty; + partial void OnLicenseInfoChanged(string value) + { + if (!IsLoading) + { + SettingsApp.LicenseInfo = value; + SettingsApp.SettingsChanged = true; + } + } + + [ObservableProperty] + ObservableCollection settings = []; + #endregion + + #region Ctor + public MainPageViewModel() + { + LoadSettings(); + } + #endregion + + #region Methods + void LoadSettings() + { + IsLoading = true; + + LicenseInfo = SettingsApp.LicenseInfo; + + IsLoading = false; + } + #endregion + + #region Commands + [RelayCommand] + async Task SaveSettings() => await SettingsApp.SaveSettingsAsync(key: App.Hash); + + [RelayCommand] + async Task LoadSettingsFromDevice() + { + try + { + await SettingsApp.LoadSettingsAsync(key: App.Hash); + LoadSettings(); + } + catch(Exception) + { + // Throus if the key missmatches + } + } + + [RelayCommand] + async Task ExchangeHashKey() + { + string newKey = EncryptionManager.GenerateBase64Key(); + await SettingsApp.ExhangeKeyAsync(oldKey: App.Hash, newKey: newKey); + App.Hash = HashKey = newKey; + LoadSettings(); + } + + [RelayCommand] + async Task ToDictionary() + { + // All "SkipForExport" should be missing here. + Dictionary> dict = await SettingsApp.ToDictionaryAsync(); + Settings = [.. dict.Select(kp => new SettingsItem() { Key = kp.Key, Value = kp.Value.Item1.ToString() })]; + } + #endregion +} +``` +For more information, please see the example project.