diff --git a/BrickController2/BrickController2/App.xaml b/BrickController2/BrickController2/App.xaml
index 132be36f..fd0716b3 100644
--- a/BrickController2/BrickController2/App.xaml
+++ b/BrickController2/BrickController2/App.xaml
@@ -27,7 +27,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BrickController2/BrickController2/UI/Controls/SettingsControl.xaml.cs b/BrickController2/BrickController2/UI/Controls/SettingsControl.xaml.cs
new file mode 100644
index 00000000..b753bebd
--- /dev/null
+++ b/BrickController2/BrickController2/UI/Controls/SettingsControl.xaml.cs
@@ -0,0 +1,14 @@
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.Controls.Xaml;
+
+namespace BrickController2.UI.Controls
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class SettingsControl : ContentView
+ {
+ public SettingsControl()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/BrickController2/BrickController2/UI/DI/UiModule.cs b/BrickController2/BrickController2/UI/DI/UiModule.cs
index d11113ed..b7bcce6d 100644
--- a/BrickController2/BrickController2/UI/DI/UiModule.cs
+++ b/BrickController2/BrickController2/UI/DI/UiModule.cs
@@ -32,7 +32,7 @@ protected override void Load(ContainerBuilder builder)
builder.RegisterType().As().As().SingleInstance();
// Register viewmodels
- foreach (var vmType in GetSubClassesOf())
+ foreach (var vmType in GetSubClassesOf().Where(t => !t.IsAbstract))
{
builder.RegisterType(vmType).Keyed(vmType);
}
diff --git a/BrickController2/BrickController2/UI/Pages/DeviceListPage.xaml b/BrickController2/BrickController2/UI/Pages/DeviceListPage.xaml
index d8bef2af..95d1c33d 100644
--- a/BrickController2/BrickController2/UI/Pages/DeviceListPage.xaml
+++ b/BrickController2/BrickController2/UI/Pages/DeviceListPage.xaml
@@ -39,6 +39,14 @@
+
+
+
+
+
diff --git a/BrickController2/BrickController2/UI/Pages/DevicePage.xaml b/BrickController2/BrickController2/UI/Pages/DevicePage.xaml
index ea6ef53c..bb2163b1 100644
--- a/BrickController2/BrickController2/UI/Pages/DevicePage.xaml
+++ b/BrickController2/BrickController2/UI/Pages/DevicePage.xaml
@@ -24,6 +24,9 @@
+
+
+
diff --git a/BrickController2/BrickController2/UI/Pages/DeviceSettingsPage.xaml b/BrickController2/BrickController2/UI/Pages/DeviceSettingsPage.xaml
new file mode 100644
index 00000000..ac23f690
--- /dev/null
+++ b/BrickController2/BrickController2/UI/Pages/DeviceSettingsPage.xaml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BrickController2/BrickController2/UI/Pages/DeviceSettingsPage.xaml.cs b/BrickController2/BrickController2/UI/Pages/DeviceSettingsPage.xaml.cs
new file mode 100644
index 00000000..2798a31c
--- /dev/null
+++ b/BrickController2/BrickController2/UI/Pages/DeviceSettingsPage.xaml.cs
@@ -0,0 +1,18 @@
+using BrickController2.UI.Services.Background;
+using BrickController2.UI.Services.Dialog;
+using BrickController2.UI.ViewModels;
+using Microsoft.Maui.Controls.Xaml;
+
+namespace BrickController2.UI.Pages
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class DeviceSettingsPage
+ {
+ public DeviceSettingsPage(PageViewModelBase vm, IBackgroundService backgroundService, IDialogServerHost dialogServerHost)
+ : base(backgroundService, dialogServerHost)
+ {
+ InitializeComponent();
+ AfterInitialize(vm);
+ }
+ }
+}
\ No newline at end of file
diff --git a/BrickController2/BrickController2/UI/Templates/DataTemplatesSelector.cs b/BrickController2/BrickController2/UI/Templates/DataTemplatesSelector.cs
new file mode 100644
index 00000000..014b6f19
--- /dev/null
+++ b/BrickController2/BrickController2/UI/Templates/DataTemplatesSelector.cs
@@ -0,0 +1,23 @@
+using BrickController2.UI.ViewModels.Settings;
+using Microsoft.Maui.Controls;
+
+namespace BrickController2.UI.Templates;
+
+public class DataTemplatesSelector : DataTemplateSelector
+{
+ public DataTemplate BoolDataTemplate { get; set; } = default!;
+
+ public DataTemplate EnumDataTemplate { get; set; } = default!;
+
+ protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
+ {
+ var itemType = item.GetType();
+
+ if (itemType == typeof(BoolSettingViewModel))
+ return BoolDataTemplate;
+ if (itemType == typeof(EnumSettingViewModel))
+ return EnumDataTemplate;
+
+ return default!;
+ }
+}
diff --git a/BrickController2/BrickController2/UI/Themes/DarkTheme.xaml b/BrickController2/BrickController2/UI/Themes/DarkTheme.xaml
index f61ae490..e8ef2e9f 100644
--- a/BrickController2/BrickController2/UI/Themes/DarkTheme.xaml
+++ b/BrickController2/BrickController2/UI/Themes/DarkTheme.xaml
@@ -25,10 +25,12 @@
#202020
Gray
Red
+ Green
#202020
#404040
Green
LightGreen
+ #606060
#606060
#606060
LightGray
diff --git a/BrickController2/BrickController2/UI/Themes/LightTheme.xaml b/BrickController2/BrickController2/UI/Themes/LightTheme.xaml
index af383f66..67456b3e 100644
--- a/BrickController2/BrickController2/UI/Themes/LightTheme.xaml
+++ b/BrickController2/BrickController2/UI/Themes/LightTheme.xaml
@@ -25,10 +25,12 @@
White
White
LightPink
+ LightGreen
#f0f0f0
Gray
LightGreen
DarkGreen
+ Gray
#c0c0c0
#c0c0c0
#303030
diff --git a/BrickController2/BrickController2/UI/ViewModels/DeviceListPageViewModel.cs b/BrickController2/BrickController2/UI/ViewModels/DeviceListPageViewModel.cs
index 4e8f6fd7..22ddf2cb 100644
--- a/BrickController2/BrickController2/UI/ViewModels/DeviceListPageViewModel.cs
+++ b/BrickController2/BrickController2/UI/ViewModels/DeviceListPageViewModel.cs
@@ -31,6 +31,7 @@ public DeviceListPageViewModel(
ScanCommand = new SafeCommand(async () => await ScanAsync(), () => !DeviceManager.IsScanning);
DeviceTappedCommand = new SafeCommand(async device => await NavigationService.NavigateToAsync(new NavigationParameters(("device", device))));
DeleteDeviceCommand = new SafeCommand(async device => await DeleteDeviceAsync(device));
+ DeviceSettingsCommand = new SafeCommand(OpenDeviceSettingsAsync);
}
public IDeviceManager DeviceManager { get; }
@@ -38,6 +39,7 @@ public DeviceListPageViewModel(
public ICommand ScanCommand { get; }
public ICommand DeviceTappedCommand { get; }
public ICommand DeleteDeviceCommand { get; }
+ public ICommand DeviceSettingsCommand { get; }
public override void OnAppearing()
{
@@ -74,6 +76,17 @@ await _dialogService.ShowProgressDialogAsync(
}
}
+ private async Task OpenDeviceSettingsAsync(Device device)
+ {
+ try
+ {
+ await NavigationService.NavigateToAsync(new (("device", device)));
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ }
+
private async Task ScanAsync()
{
if (!DeviceManager.IsBluetoothOn)
diff --git a/BrickController2/BrickController2/UI/ViewModels/DevicePageViewModel.cs b/BrickController2/BrickController2/UI/ViewModels/DevicePageViewModel.cs
index 02356c40..50d55a7b 100644
--- a/BrickController2/BrickController2/UI/ViewModels/DevicePageViewModel.cs
+++ b/BrickController2/BrickController2/UI/ViewModels/DevicePageViewModel.cs
@@ -38,6 +38,8 @@ public DevicePageViewModel(
_dialogService = dialogService;
Device = parameters.Get("device");
+ BuWizzOutputLevel = Device.DefaultOutputLevel;
+ BuWizz2OutputLevel = Device.DefaultOutputLevel;
DeviceOutputs = Enumerable
.Range(0, Device.NumberOfChannels)
.Select(channel => new DeviceOutputViewModel(Device, channel))
@@ -46,18 +48,25 @@ public DevicePageViewModel(
RenameCommand = new SafeCommand(async () => await RenameDeviceAsync());
BuWizzOutputLevelChangedCommand = new SafeCommand(outputLevel => SetBuWizzOutputLevel(outputLevel));
BuWizz2OutputLevelChangedCommand = new SafeCommand(outputLevel => SetBuWizzOutputLevel(outputLevel));
+ OpenDeviceSettingsPageCommand = new SafeCommand(async () => await navigationService.NavigateToAsync(new (("device", Device))),
+ () => CanOpenSettings);
}
public Device Device { get; }
public bool IsBuWizzDevice => Device.DeviceType == DeviceType.BuWizz;
public bool IsBuWizz2Device => Device.DeviceType == DeviceType.BuWizz2;
+ public bool CanOpenSettings => Device.HasSettings &&
+ Device.DeviceState == DeviceState.Connected &&
+ !_deviceManager.IsScanning;
+
public ICommand RenameCommand { get; }
public ICommand BuWizzOutputLevelChangedCommand { get; }
public ICommand BuWizz2OutputLevelChangedCommand { get; }
+ public ICommand OpenDeviceSettingsPageCommand { get; }
- public int BuWizzOutputLevel { get; set; } = 1;
- public int BuWizz2OutputLevel { get; set; } = 1;
+ public int BuWizzOutputLevel { get; set; }
+ public int BuWizz2OutputLevel { get; set; }
public IEnumerable DeviceOutputs { get; }
@@ -204,6 +213,8 @@ await _dialogService.ShowMessageBoxAsync(
{
SetBuWizzOutputLevel(BuWizz2OutputLevel);
}
+ // update command enablement
+ UpdateCommandsAvailability();
}
}
}
@@ -216,6 +227,13 @@ await _dialogService.ShowMessageBoxAsync(
private void OnDeviceDisconnected(Device device)
{
+ // update command enablement
+ UpdateCommandsAvailability();
+ }
+
+ private void UpdateCommandsAvailability()
+ {
+ OpenDeviceSettingsPageCommand.RaiseCanExecuteChanged();
}
private void SetBuWizzOutputLevel(int level)
diff --git a/BrickController2/BrickController2/UI/ViewModels/DeviceSettingsPageViewModel.cs b/BrickController2/BrickController2/UI/ViewModels/DeviceSettingsPageViewModel.cs
new file mode 100644
index 00000000..1f541055
--- /dev/null
+++ b/BrickController2/BrickController2/UI/ViewModels/DeviceSettingsPageViewModel.cs
@@ -0,0 +1,69 @@
+using BrickController2.DeviceManagement;
+using BrickController2.UI.Commands;
+using BrickController2.UI.Services.Dialog;
+using BrickController2.UI.Services.Navigation;
+using BrickController2.UI.Services.Translation;
+using BrickController2.UI.ViewModels.Settings;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace BrickController2.UI.ViewModels;
+
+public class DeviceSettingsPageViewModel : SettingsPageViewModelBase
+{
+ public DeviceSettingsPageViewModel(
+ INavigationService navigationService,
+ ITranslationService translationService,
+ IDialogService dialogService,
+ NavigationParameters parameters)
+ : this(navigationService, translationService, dialogService, parameters.Get("device"))
+ {
+ }
+
+ private DeviceSettingsPageViewModel(
+ INavigationService navigationService,
+ ITranslationService translationService,
+ IDialogService dialogService,
+ Device device) : base(navigationService, translationService, dialogService, device.CurrentSettings)
+ {
+ Device = device;
+ SaveSettingsCommand = new SafeCommand(ApplyChanges, () => AllSettings.Any(x => x.HasChanged));
+ }
+
+ public ICommand SaveSettingsCommand { get; }
+
+ public Device Device { get; }
+
+ protected override void OnSettingChanged()
+ {
+ base.OnSettingChanged();
+ SaveSettingsCommand.RaiseCanExecuteChanged();
+ }
+
+ private async Task ApplyChanges()
+ {
+ try
+ {
+ await DialogService.ShowProgressDialogAsync(
+ false,
+ async (progressDialog, token) =>
+ {
+ var changedSettings = AllSettings
+ .Where(s => s.HasChanged)
+ .Select(s => s.Setting)
+ .ToArray();
+
+ await Device.UpdateDeviceSettingsAsync(changedSettings);
+ },
+ Translate("Saving"),
+ token: DisappearingToken);
+
+ await NavigationService.NavigateBackAsync();
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ }
+}
diff --git a/BrickController2/BrickController2/UI/ViewModels/Settings/BoolSettingViewModel.cs b/BrickController2/BrickController2/UI/ViewModels/Settings/BoolSettingViewModel.cs
new file mode 100644
index 00000000..b2f17821
--- /dev/null
+++ b/BrickController2/BrickController2/UI/ViewModels/Settings/BoolSettingViewModel.cs
@@ -0,0 +1,20 @@
+using BrickController2.Settings;
+using BrickController2.UI.Services.Translation;
+
+namespace BrickController2.UI.ViewModels.Settings;
+
+public class BoolSettingViewModel : SettingViewModelBase
+{
+ public BoolSettingViewModel(NamedSetting setting,
+ SettingsPageViewModelBase parent,
+ ITranslationService translationService)
+ : base(setting, parent, translationService)
+ {
+ }
+
+ public override bool Value
+ {
+ get => (bool)SettingValue;
+ set => SettingValue = value!;
+ }
+}
diff --git a/BrickController2/BrickController2/UI/ViewModels/Settings/EnumSettingViewModel.cs b/BrickController2/BrickController2/UI/ViewModels/Settings/EnumSettingViewModel.cs
new file mode 100644
index 00000000..877cf912
--- /dev/null
+++ b/BrickController2/BrickController2/UI/ViewModels/Settings/EnumSettingViewModel.cs
@@ -0,0 +1,47 @@
+using BrickController2.Settings;
+using BrickController2.UI.Commands;
+using BrickController2.UI.Services.Translation;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace BrickController2.UI.ViewModels.Settings;
+
+public class EnumSettingViewModel : SettingViewModelBase
+{
+ public EnumSettingViewModel(NamedSetting setting,
+ SettingsPageViewModelBase parent,
+ ITranslationService translationService)
+ : base(setting, parent, translationService)
+ {
+ SelectItemCommand = new SafeCommand(SelectItemAsync);
+ }
+
+ public IEnumerable Items => Enum.GetNames(Setting.Type);
+ public ICommand SelectItemCommand { get; }
+
+ public override string Value
+ {
+ get => Enum.GetName(Setting.Type, SettingValue)!;
+ set
+ {
+ var enumValue = Enum.Parse(Setting.Type, value);
+ SettingValue = enumValue;
+ }
+ }
+
+ public override void ResetToDefault()
+ {
+ Value = Enum.GetName(Setting.Type, Setting.DefaultValue)!;
+ }
+
+ private async Task SelectItemAsync()
+ {
+ var result = await ShowSelectionDialogAsync(Items);
+ if (result.IsOk)
+ {
+ Value = result.SelectedItem;
+ }
+ }
+}
diff --git a/BrickController2/BrickController2/UI/ViewModels/Settings/SettingGroupViewModel.cs b/BrickController2/BrickController2/UI/ViewModels/Settings/SettingGroupViewModel.cs
new file mode 100644
index 00000000..d2470e97
--- /dev/null
+++ b/BrickController2/BrickController2/UI/ViewModels/Settings/SettingGroupViewModel.cs
@@ -0,0 +1,73 @@
+using BrickController2.UI.Services.Translation;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+
+namespace BrickController2.UI.ViewModels.Settings;
+
+public class SettingGroupViewModel : ObservableCollection
+{
+ private const string DefaultGroupName = "_defaultGroup";
+
+ private readonly string _groupName;
+ private readonly ITranslationService _translationService;
+ private bool _changed;
+ private bool _nonDefaultValue;
+
+ public SettingGroupViewModel(string groupName,
+ ICollection settings,
+ ITranslationService translationService)
+ : base(settings)
+ {
+ _groupName = !string.IsNullOrEmpty(groupName) ? groupName : DefaultGroupName;
+ _translationService = translationService;
+ _nonDefaultValue = settings.Any(x => x.HasNonDefaultValue);
+ // subscribe to changes - collection is expected to be immutable
+ foreach (var setting in settings)
+ {
+ setting.PropertyChanged += Setting_PropertyChanged;
+ }
+ }
+
+ public bool HasChanged
+ {
+ get => _changed;
+ protected set
+ {
+ if (_changed != value)
+ {
+ _changed = value;
+ OnPropertyChanged(new PropertyChangedEventArgs(nameof(HasChanged)));
+ }
+ }
+ }
+
+ public bool HasNonDefaultValue
+ {
+ get => _nonDefaultValue;
+ protected set
+ {
+ if (_nonDefaultValue != value)
+ {
+ _nonDefaultValue = value;
+ OnPropertyChanged(new PropertyChangedEventArgs(nameof(HasNonDefaultValue)));
+ }
+ }
+ }
+
+ public string GroupName => _translationService.Translate(_groupName);
+
+ private void Setting_PropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ // notify group due to possible change
+ if (e.PropertyName == nameof(SettingViewModelBase.HasChanged))
+ {
+ HasChanged = this.Any(x => x.HasChanged);
+ }
+ else if (e.PropertyName == nameof(SettingViewModelBase.HasNonDefaultValue))
+ {
+ HasNonDefaultValue = this.Any(x => x.HasNonDefaultValue);
+ }
+ }
+}
diff --git a/BrickController2/BrickController2/UI/ViewModels/Settings/SettingViewModelBase.cs b/BrickController2/BrickController2/UI/ViewModels/Settings/SettingViewModelBase.cs
new file mode 100644
index 00000000..1f668ca1
--- /dev/null
+++ b/BrickController2/BrickController2/UI/ViewModels/Settings/SettingViewModelBase.cs
@@ -0,0 +1,99 @@
+using BrickController2.Helpers;
+using BrickController2.Settings;
+using BrickController2.UI.Services.Dialog;
+using BrickController2.UI.Services.Translation;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace BrickController2.UI.ViewModels.Settings;
+
+public abstract class SettingViewModelBase : NotifyPropertyChangedSource
+{
+ protected readonly object OriginalValue;
+
+ private bool _changed;
+ private bool _hasDefaultValue;
+
+ protected SettingViewModelBase(NamedSetting setting)
+ {
+ OriginalValue = setting.Value;
+ // make editable copy
+ Setting = setting with { };
+ // initialize flag(s)
+ _hasDefaultValue = !setting.Value.Equals(Setting.DefaultValue);
+ }
+
+ public bool HasChanged
+ {
+ get => _changed;
+ protected set
+ {
+ if (_changed != value)
+ {
+ _changed = value;
+ RaisePropertyChanged();
+ }
+ }
+ }
+
+ public bool HasNonDefaultValue
+ {
+ get => _hasDefaultValue;
+ protected set
+ {
+ if (_hasDefaultValue != value)
+ {
+ _hasDefaultValue = value;
+ RaisePropertyChanged();
+ }
+ }
+ }
+
+ public NamedSetting Setting { get; }
+
+ protected object SettingValue
+ {
+ get => Setting.Value;
+ set
+ {
+ if (!Setting.Value.Equals(value))
+ {
+ Setting.Value = value!;
+ RaisePropertyChanged(nameof(SettingViewModelBase.Value));
+ // update additional properties
+ HasChanged = !value.Equals(OriginalValue);
+ HasNonDefaultValue = !value.Equals(Setting.DefaultValue);
+ }
+ }
+ }
+
+ public virtual void ResetToDefault()
+ {
+ SettingValue = Setting.DefaultValue;
+ }
+}
+
+public abstract class SettingViewModelBase : SettingViewModelBase
+{
+ protected readonly SettingsPageViewModelBase Parent;
+ protected readonly ITranslationService TranslationService;
+
+ protected SettingViewModelBase(NamedSetting setting,
+ SettingsPageViewModelBase parent,
+ ITranslationService translationService) : base(setting)
+ {
+ Parent = parent;
+ TranslationService = translationService;
+ }
+
+ public string DisplayName => TranslationService.Translate(Setting.Name);
+
+ public abstract TValue Value { get; set; }
+
+ protected Task> ShowSelectionDialogAsync(IEnumerable items) where T : notnull
+ => Parent.DialogService.ShowSelectionDialogAsync(
+ items,
+ TranslationService.Translate(Setting.Name),
+ TranslationService.Translate("Cancel"),
+ Parent.DisappearingToken);
+}
diff --git a/BrickController2/BrickController2/UI/ViewModels/Settings/SettingsPageViewModelBase.cs b/BrickController2/BrickController2/UI/ViewModels/Settings/SettingsPageViewModelBase.cs
new file mode 100644
index 00000000..3e271435
--- /dev/null
+++ b/BrickController2/BrickController2/UI/ViewModels/Settings/SettingsPageViewModelBase.cs
@@ -0,0 +1,115 @@
+using BrickController2.Settings;
+using BrickController2.UI.Commands;
+using BrickController2.UI.Services.Dialog;
+using BrickController2.UI.Services.Navigation;
+using BrickController2.UI.Services.Translation;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading;
+using System.Windows.Input;
+
+namespace BrickController2.UI.ViewModels.Settings;
+
+public abstract class SettingsPageViewModelBase : PageViewModelBase
+{
+ private CancellationTokenSource? _disappearingTokenSource;
+
+ protected SettingsPageViewModelBase(
+ INavigationService navigationService,
+ ITranslationService translationService,
+ IDialogService dialogService,
+ IEnumerable settings) : base(navigationService, translationService)
+ {
+ DialogService = dialogService;
+ // prepare grouping
+ Groups = new(settings
+ .OrderBy(x => x.Name)
+ .GroupBy(x => x.Group)
+ .Select(x => new SettingGroupViewModel(x.Key, x.Select(ToViewModel).ToArray(), translationService)));
+ AllSettings = new(Groups.SelectMany(x => x));
+ // detect grouping
+ IsGrouped = settings.Any(x => !string.IsNullOrEmpty(x.Group));
+ // subscribe to change coming from via groups
+ foreach (INotifyPropertyChanged group in Groups)
+ {
+ group.PropertyChanged += Group_PropertyChanged;
+ }
+
+ ResetToDefaultsCommand = new SafeCommand(ResetToDefaults, () => AllSettings.Any(x => x.HasNonDefaultValue));
+ ResetGroupToDefaultCommand = new SafeCommand(ResetGroupToDefaults,
+ (o) => o is SettingGroupViewModel group && group.HasNonDefaultValue);
+ }
+
+ public ICommand ResetToDefaultsCommand { get; }
+ public ICommand ResetGroupToDefaultCommand { get; }
+
+ public bool IsGrouped { get; }
+ public IEnumerable Settings => IsGrouped ? Groups : AllSettings;
+ public CancellationToken DisappearingToken => _disappearingTokenSource?.Token ?? default;
+
+ protected ObservableCollection AllSettings { get; }
+ protected ObservableCollection Groups { get; }
+ public IDialogService DialogService { get; }
+
+ public override void OnAppearing()
+ {
+ _disappearingTokenSource?.Cancel();
+ _disappearingTokenSource = new CancellationTokenSource();
+ }
+
+ public override void OnDisappearing()
+ {
+ _disappearingTokenSource?.Cancel();
+ }
+
+ protected virtual void OnSettingChanged()
+ {
+ }
+
+ protected virtual void OnDefaultValueChanged()
+ {
+ ResetToDefaultsCommand.RaiseCanExecuteChanged();
+ ResetGroupToDefaultCommand.RaiseCanExecuteChanged();
+ }
+
+ private void Group_PropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(SettingGroupViewModel.HasChanged))
+ {
+ OnSettingChanged();
+ }
+ else if (e.PropertyName == nameof(SettingGroupViewModel.HasNonDefaultValue))
+ {
+ OnDefaultValueChanged();
+ }
+ }
+
+ private SettingViewModelBase ToViewModel(NamedSetting setting)
+ {
+ if (setting.IsBoolType)
+ {
+ return new BoolSettingViewModel(setting, this, TranslationService);
+ }
+ if (setting.IsEnumType)
+ {
+ return new EnumSettingViewModel(setting, this, TranslationService);
+ }
+
+ throw new InvalidOperationException($"The specified type {setting.Type} is not supported.");
+ }
+
+ private void ResetToDefaults() => ResetToDefaults(AllSettings);
+
+ private static void ResetGroupToDefaults(SettingGroupViewModel group) => ResetToDefaults(group);
+
+ private static void ResetToDefaults(ICollection viewModels)
+ {
+ foreach (var setting in viewModels.Where(s => s.HasNonDefaultValue))
+ {
+ setting.ResetToDefault();
+ }
+ }
+}