From c178fc6cf800a83691700f24962592f145ccb1c2 Mon Sep 17 00:00:00 2001 From: Robert Beekman Date: Mon, 8 Nov 2021 17:13:06 +0100 Subject: [PATCH] UI - Implemented plugin feature VMs --- .../Services/Builders/ContentDialogBuilder.cs | 12 +- .../Services/Builders/NotificationBuilder.cs | 3 +- .../Services/Interfaces/IWindowService.cs | 24 ++-- .../WindowService/ExceptionDialogViewModel.cs | 2 +- .../Services/WindowService/WindowService.cs | 62 +++++++--- .../Utilities/DelegateCommand.cs | 60 --------- .../ViewModelBase.cs | 21 ++-- .../Ninject/Factories/IVMFactory.cs | 6 + .../ViewModels/PluginFeatureViewModel.cs | 3 +- .../PluginPrerequisiteActionViewModel.cs | 7 +- .../ViewModels/PluginPrerequisiteViewModel.cs | 91 +++++++------- ...uginPrerequisitesInstallDialogViewModel.cs | 36 ++---- ...inPrerequisitesUninstallDialogViewModel.cs | 117 +++++++++--------- .../ViewModels/PluginSettingsViewModel.cs | 35 ++---- .../PluginSettingsWindowViewModel.cs | 3 +- .../Plugins/Views/PluginFeatureView.axaml | 2 +- .../Plugins/Views/PluginFeatureView.axaml.cs | 3 +- .../Plugins/Views/PluginSettingsView.axaml | 2 +- .../Plugins/Views/PluginSettingsView.axaml.cs | 3 +- .../Views/PluginSettingsWindowView.axaml | 2 +- .../Views/PluginSettingsWindowView.axaml.cs | 2 +- 21 files changed, 222 insertions(+), 274 deletions(-) delete mode 100644 src/Artemis.UI.Avalonia.Shared/Utilities/DelegateCommand.cs diff --git a/src/Artemis.UI.Avalonia.Shared/Services/Builders/ContentDialogBuilder.cs b/src/Artemis.UI.Avalonia.Shared/Services/Builders/ContentDialogBuilder.cs index cdafb5371..54d17c8cc 100644 --- a/src/Artemis.UI.Avalonia.Shared/Services/Builders/ContentDialogBuilder.cs +++ b/src/Artemis.UI.Avalonia.Shared/Services/Builders/ContentDialogBuilder.cs @@ -76,15 +76,11 @@ namespace Artemis.UI.Avalonia.Shared.Services.Builders return this; } - public ContentDialogBuilder WithViewModel(params (string name, object value)[] parameters) + public ContentDialogBuilder WithViewModel(ref T viewModel, params (string name, object value)[] parameters) { - if (parameters.Length != 0) - { - IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast().ToArray(); - _contentDialog.Content = _kernel.Get(paramsArray); - } - else - _contentDialog.Content = _kernel.Get(); + IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast().ToArray(); + viewModel = _kernel.Get(paramsArray); + _contentDialog.Content = _kernel.Get(); return this; } diff --git a/src/Artemis.UI.Avalonia.Shared/Services/Builders/NotificationBuilder.cs b/src/Artemis.UI.Avalonia.Shared/Services/Builders/NotificationBuilder.cs index ed2b6c081..4d4e6d04f 100644 --- a/src/Artemis.UI.Avalonia.Shared/Services/Builders/NotificationBuilder.cs +++ b/src/Artemis.UI.Avalonia.Shared/Services/Builders/NotificationBuilder.cs @@ -4,6 +4,7 @@ using Artemis.UI.Avalonia.Shared.Utilities; using Avalonia.Controls; using Avalonia.Threading; using FluentAvalonia.UI.Controls; +using ReactiveUI; using Button = Avalonia.Controls.Button; namespace Artemis.UI.Avalonia.Shared.Services.Builders @@ -106,7 +107,7 @@ namespace Artemis.UI.Avalonia.Shared.Services.Builders public IControl Build() { return _action != null - ? new Button {Content = _text, Command = new DelegateCommand(_ => _action())} + ? new Button {Content = _text, Command = ReactiveCommand.Create(() => _action)} : new Button {Content = _text}; } } diff --git a/src/Artemis.UI.Avalonia.Shared/Services/Interfaces/IWindowService.cs b/src/Artemis.UI.Avalonia.Shared/Services/Interfaces/IWindowService.cs index f2f688a86..ea73f0756 100644 --- a/src/Artemis.UI.Avalonia.Shared/Services/Interfaces/IWindowService.cs +++ b/src/Artemis.UI.Avalonia.Shared/Services/Interfaces/IWindowService.cs @@ -8,11 +8,11 @@ namespace Artemis.UI.Avalonia.Shared.Services.Interfaces public interface IWindowService : IArtemisSharedUIService { /// - /// Creates a view model instance of type and shows its corresponding View as a window + /// Creates a view model instance of type and shows its corresponding View as a window /// - /// The type of view model to create + /// The type of view model to create /// The created view model - T ShowWindow(); + TViewModel ShowWindow(params (string name, object value)[] parameters); /// /// Given a ViewModel, show its corresponding View as a window @@ -28,12 +28,20 @@ namespace Artemis.UI.Avalonia.Shared.Services.Interfaces void ShowExceptionDialog(string title, Exception exception); /// - /// Given a ViewModel, show its corresponding View as a Dialog + /// Given an existing ViewModel, show its corresponding View as a Dialog /// - /// The return type + /// The return type /// ViewModel to show the View for - /// A task containing the return value of type - Task ShowDialogAsync(object viewModel); + /// A task containing the return value of type + Task ShowDialogAsync(DialogViewModelBase viewModel); + + /// + /// Creates a view model instance of type and shows its corresponding View as a Dialog + /// + /// The view model type + /// The return type + /// A task containing the return value of type + Task ShowDialogAsync(params (string name, object value)[] parameters) where TViewModel : DialogViewModelBase; /// /// Creates an open file dialog, use the fluent API to configure it @@ -49,6 +57,8 @@ namespace Artemis.UI.Avalonia.Shared.Services.Interfaces ContentDialogBuilder CreateContentDialog(); + ConfirmDialogBuilder CreateConfirmDialog(); + Window GetCurrentWindow(); } } \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogViewModel.cs b/src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogViewModel.cs index b2de500b2..a43a2c1f4 100644 --- a/src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogViewModel.cs +++ b/src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogViewModel.cs @@ -2,7 +2,7 @@ namespace Artemis.UI.Avalonia.Shared.Services { - public class ExceptionDialogViewModel : ActivatableViewModelBase + public class ExceptionDialogViewModel : DialogViewModelBase { public ExceptionDialogViewModel(string title, Exception exception) { diff --git a/src/Artemis.UI.Avalonia.Shared/Services/WindowService/WindowService.cs b/src/Artemis.UI.Avalonia.Shared/Services/WindowService/WindowService.cs index 3fb07710a..8dfe1c199 100644 --- a/src/Artemis.UI.Avalonia.Shared/Services/WindowService/WindowService.cs +++ b/src/Artemis.UI.Avalonia.Shared/Services/WindowService/WindowService.cs @@ -8,6 +8,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Ninject; +using Ninject.Parameters; namespace Artemis.UI.Avalonia.Shared.Services { @@ -21,9 +22,10 @@ namespace Artemis.UI.Avalonia.Shared.Services _kernel = kernel; } - public T ShowWindow() + public T ShowWindow(params (string name, object value)[] parameters) { - T viewModel = _kernel.Get()!; + IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast().ToArray(); + T viewModel = _kernel.Get(paramsArray)!; ShowWindow(viewModel); return viewModel; } @@ -34,49 +36,69 @@ namespace Artemis.UI.Avalonia.Shared.Services Type? type = viewModel.GetType().Assembly.GetType(name); if (type == null) + { throw new ArtemisSharedUIException($"Failed to find a window named {name}."); + } + if (!type.IsAssignableTo(typeof(Window))) + { throw new ArtemisSharedUIException($"Type {name} is not a window."); + } Window window = (Window) Activator.CreateInstance(type)!; window.DataContext = viewModel; window.Show(); } - /// - public void ShowExceptionDialog(string title, Exception exception) + public async Task ShowDialogAsync(params (string name, object value)[] parameters) where TViewModel : DialogViewModelBase { - if (_exceptionDialogOpen) - return; - - try - { - _exceptionDialogOpen = true; - ShowDialogAsync(new ExceptionDialogViewModel(title, exception)).GetAwaiter().GetResult(); - } - finally - { - _exceptionDialogOpen = false; - } + IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast().ToArray(); + TViewModel viewModel = _kernel.Get(paramsArray)!; + return await ShowDialogAsync(viewModel); } - public async Task ShowDialogAsync(object viewModel) + public async Task ShowDialogAsync(DialogViewModelBase viewModel) { if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime classic) - throw new ArtemisSharedUIException($"Can't show a dialog when application lifetime is not IClassicDesktopStyleApplicationLifetime."); + { + throw new ArtemisSharedUIException("Can't show a dialog when application lifetime is not IClassicDesktopStyleApplicationLifetime."); + } string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View"); Type? type = viewModel.GetType().Assembly.GetType(name); if (type == null) + { throw new ArtemisSharedUIException($"Failed to find a window named {name}."); + } + if (!type.IsAssignableTo(typeof(Window))) + { throw new ArtemisSharedUIException($"Type {name} is not a window."); + } Window window = (Window) Activator.CreateInstance(type)!; window.DataContext = viewModel; Window parent = classic.Windows.FirstOrDefault(w => w.IsActive) ?? classic.MainWindow; - return await window.ShowDialog(parent); + return await window.ShowDialog(parent); + } + + public void ShowExceptionDialog(string title, Exception exception) + { + if (_exceptionDialogOpen) + { + return; + } + + try + { + _exceptionDialogOpen = true; + ShowDialogAsync(new ExceptionDialogViewModel(title, exception)).GetAwaiter().GetResult(); + } + finally + { + _exceptionDialogOpen = false; + } } public ContentDialogBuilder CreateContentDialog() @@ -97,7 +119,9 @@ namespace Artemis.UI.Avalonia.Shared.Services public Window GetCurrentWindow() { if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime classic) + { throw new ArtemisSharedUIException("Can't show a dialog when application lifetime is not IClassicDesktopStyleApplicationLifetime."); + } Window parent = classic.Windows.FirstOrDefault(w => w.IsActive) ?? classic.MainWindow; return parent; diff --git a/src/Artemis.UI.Avalonia.Shared/Utilities/DelegateCommand.cs b/src/Artemis.UI.Avalonia.Shared/Utilities/DelegateCommand.cs deleted file mode 100644 index 03fff689d..000000000 --- a/src/Artemis.UI.Avalonia.Shared/Utilities/DelegateCommand.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Windows.Input; - -namespace Artemis.UI.Avalonia.Shared.Utilities -{ - /// - /// Provides a command that simply calls a delegate when invoked - /// - public class DelegateCommand : ICommand - { - private readonly Predicate? _canExecute; - private readonly Action _execute; - - /// - /// Creates a new instance of the class - /// - /// The delegate to execute - public DelegateCommand(Action execute) : this(execute, null) - { - } - - /// - /// Creates a new instance of the class with a predicate indicating whether the command - /// can be executed - /// - /// The delegate to execute - /// The predicate that determines whether the command can execute - public DelegateCommand(Action execute, Predicate? canExecute) - { - _execute = execute; - _canExecute = canExecute; - } - - /// - /// Invokes the event - /// - public void RaiseCanExecuteChanged() - { - CanExecuteChanged?.Invoke(this, EventArgs.Empty); - } - - /// - public event EventHandler? CanExecuteChanged; - - /// - public bool CanExecute(object? parameter) - { - if (_canExecute == null) - return true; - - return _canExecute(parameter); - } - - /// - public void Execute(object? parameter) - { - _execute(parameter); - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia.Shared/ViewModelBase.cs b/src/Artemis.UI.Avalonia.Shared/ViewModelBase.cs index 486498be8..c0a04abda 100644 --- a/src/Artemis.UI.Avalonia.Shared/ViewModelBase.cs +++ b/src/Artemis.UI.Avalonia.Shared/ViewModelBase.cs @@ -6,14 +6,14 @@ using ReactiveUI; namespace Artemis.UI.Avalonia.Shared { /// - /// Represents the base class for Artemis view models + /// Represents the base class for Artemis view models /// public abstract class ViewModelBase : ReactiveObject { private string? _displayName; /// - /// Gets or sets the display name of the view model + /// Gets or sets the display name of the view model /// public string? DisplayName { @@ -23,7 +23,7 @@ namespace Artemis.UI.Avalonia.Shared } /// - /// Represents the base class for Artemis view models that are interested in the activated event + /// Represents the base class for Artemis view models that are interested in the activated event /// public abstract class ActivatableViewModelBase : ViewModelBase, IActivatableViewModel, IDisposable { @@ -34,17 +34,14 @@ namespace Artemis.UI.Avalonia.Shared } /// - /// Releases the unmanaged resources used by the object and optionally releases the managed resources. + /// Releases the unmanaged resources used by the object and optionally releases the managed resources. /// /// - /// to release both managed and unmanaged resources; - /// to release only unmanaged resources. + /// to release both managed and unmanaged resources; + /// to release only unmanaged resources. /// protected virtual void Dispose(bool disposing) { - if (disposing) - { - } } /// @@ -59,11 +56,10 @@ namespace Artemis.UI.Avalonia.Shared } /// - /// Represents the base class for Artemis view models used to drive dialogs + /// Represents the base class for Artemis view models used to drive dialogs /// public abstract class DialogViewModelBase : ActivatableViewModelBase { - /// protected DialogViewModelBase() { @@ -71,9 +67,8 @@ namespace Artemis.UI.Avalonia.Shared Cancel = ReactiveCommand.Create(() => { }); } - /// - /// Closes the dialog with a given result + /// Closes the dialog with a given result /// public ReactiveCommand Close { get; } diff --git a/src/Artemis.UI.Avalonia/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI.Avalonia/Ninject/Factories/IVMFactory.cs index 52ee5432e..34a52dbc6 100644 --- a/src/Artemis.UI.Avalonia/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI.Avalonia/Ninject/Factories/IVMFactory.cs @@ -2,6 +2,7 @@ using Artemis.Core; using Artemis.UI.Avalonia.Screens.Device; using Artemis.UI.Avalonia.Screens.Device.Tabs.ViewModels; +using Artemis.UI.Avalonia.Screens.Plugins.ViewModels; using Artemis.UI.Avalonia.Screens.Root.ViewModels; using Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels; using ReactiveUI; @@ -33,4 +34,9 @@ namespace Artemis.UI.Avalonia.Ninject.Factories SurfaceDeviceViewModel SurfaceDeviceViewModel(ArtemisDevice device); ListDeviceViewModel ListDeviceViewModel(ArtemisDevice device); } + + public interface IPrerequisitesVmFactory : IVmFactory + { + PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall); + } } \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginFeatureViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginFeatureViewModel.cs index ba532aa07..0fe2c5f5d 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginFeatureViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginFeatureViewModel.cs @@ -6,11 +6,12 @@ using System.Linq; using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; +using Artemis.UI.Avalonia.Shared; using Artemis.UI.Avalonia.Shared.Services.Builders; using Artemis.UI.Avalonia.Shared.Services.Interfaces; using ReactiveUI; -namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.ViewModels +namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels { public class PluginFeatureViewModel : ActivatableViewModelBase { diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisiteActionViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisiteActionViewModel.cs index 0a42da735..ce4850925 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisiteActionViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisiteActionViewModel.cs @@ -1,10 +1,7 @@ -using System; -using System.ComponentModel; -using Artemis.Core; -using Artemis.UI.Avalonia; +using Artemis.Core; using Artemis.UI.Avalonia.Shared; -namespace Artemis.UI.Screens.Plugins +namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels { public class PluginPrerequisiteActionViewModel : ViewModelBase { diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisiteViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisiteViewModel.cs index 453ec9bac..48db1a579 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisiteViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisiteViewModel.cs @@ -1,26 +1,51 @@ -using System; +using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Threading; using System.Threading.Tasks; using Artemis.Core; -using Artemis.Core.Services; -using Artemis.UI.Shared.Services; -using Stylet; +using Artemis.UI.Avalonia.Shared; +using DynamicData; +using ReactiveUI; -namespace Artemis.UI.Screens.Plugins +namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels { - public class PluginPrerequisiteViewModel : Conductor.Collection.OneActive + public class PluginPrerequisiteViewModel : ActivatableViewModelBase { private readonly bool _uninstall; + private readonly ObservableAsPropertyHelper _busy; + private readonly ObservableAsPropertyHelper _activeStepNumber; + private bool _installing; private bool _uninstalling; private bool _isMet; + private PluginPrerequisiteActionViewModel? _activeAction; + public PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall) { _uninstall = uninstall; + PluginPrerequisite = pluginPrerequisite; + Actions = new ObservableCollection(!_uninstall + ? PluginPrerequisite.InstallActions.Select(a => new PluginPrerequisiteActionViewModel(a)) + : PluginPrerequisite.UninstallActions.Select(a => new PluginPrerequisiteActionViewModel(a))); + + this.WhenAnyValue(x => x.Installing, x => x.Uninstalling, (i, u) => i || u).ToProperty(this, x => x.Busy, out _busy); + this.WhenAnyValue(x => x.ActiveAction, a => Actions.IndexOf(a)).ToProperty(this, x => x.ActiveStepNumber, out _activeStepNumber); + + PluginPrerequisite.PropertyChanged += PluginPrerequisiteOnPropertyChanged; + + // Could be slow so take it off of the UI thread + Task.Run(() => IsMet = PluginPrerequisite.IsMet()); + } + + public ObservableCollection Actions { get; } + + public PluginPrerequisiteActionViewModel? ActiveAction + { + get => _activeAction; + set => this.RaiseAndSetIfChanged(ref _activeAction , value); } public PluginPrerequisite PluginPrerequisite { get; } @@ -28,32 +53,24 @@ namespace Artemis.UI.Screens.Plugins public bool Installing { get => _installing; - set - { - SetAndNotify(ref _installing, value); - NotifyOfPropertyChange(nameof(Busy)); - } + set => this.RaiseAndSetIfChanged(ref _installing, value); } public bool Uninstalling { get => _uninstalling; - set - { - SetAndNotify(ref _uninstalling, value); - NotifyOfPropertyChange(nameof(Busy)); - } + set => this.RaiseAndSetIfChanged(ref _uninstalling, value); } public bool IsMet { get => _isMet; - set => SetAndNotify(ref _isMet, value); + set => this.RaiseAndSetIfChanged(ref _isMet, value); } - public bool Busy => Installing || Uninstalling; - public int ActiveStemNumber => Items.IndexOf(ActiveItem) + 1; - public bool HasMultipleActions => Items.Count > 1; + public bool Busy => _busy.Value; + public int ActiveStepNumber => _activeStepNumber.Value; + public bool HasMultipleActions => Actions.Count > 1; public async Task Install(CancellationToken cancellationToken) { @@ -89,7 +106,7 @@ namespace Artemis.UI.Screens.Plugins } } - private void PluginPrerequisiteOnPropertyChanged(object sender, PropertyChangedEventArgs e) + private void PluginPrerequisiteOnPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(PluginPrerequisite.CurrentAction)) ActivateCurrentAction(); @@ -97,37 +114,27 @@ namespace Artemis.UI.Screens.Plugins private void ActivateCurrentAction() { - PluginPrerequisiteActionViewModel newActiveItem = Items.FirstOrDefault(i => i.Action == PluginPrerequisite.CurrentAction); - if (newActiveItem == null) + PluginPrerequisiteActionViewModel? activeAction = Actions.FirstOrDefault(i => i.Action == PluginPrerequisite.CurrentAction); + if (activeAction == null) return; - ActiveItem = newActiveItem; - NotifyOfPropertyChange(nameof(ActiveStemNumber)); + ActiveAction = activeAction; } - #region Overrides of Screen + #region IDisposable /// - protected override void OnClose() + protected override void Dispose(bool disposing) { - PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged; - base.OnClose(); - } + if (disposing) + { + PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged; + } - /// - protected override void OnInitialActivate() - { - PluginPrerequisite.PropertyChanged += PluginPrerequisiteOnPropertyChanged; - // Could be slow so take it off of the UI thread - Task.Run(() => IsMet = PluginPrerequisite.IsMet()); - - Items.AddRange(!_uninstall - ? PluginPrerequisite.InstallActions.Select(a => new PluginPrerequisiteActionViewModel(a)) - : PluginPrerequisite.UninstallActions.Select(a => new PluginPrerequisiteActionViewModel(a))); - - base.OnInitialActivate(); + base.Dispose(disposing); } #endregion + } } \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisitesInstallDialogViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisitesInstallDialogViewModel.cs index eccb78f87..908066f96 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisitesInstallDialogViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisitesInstallDialogViewModel.cs @@ -5,10 +5,12 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Artemis.Core; +using Artemis.UI.Avalonia.Ninject.Factories; using Artemis.UI.Avalonia.Shared; +using Artemis.UI.Avalonia.Shared.Services.Interfaces; using ReactiveUI; -namespace Artemis.UI.Screens.Plugins +namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels { public class PluginPrerequisitesInstallDialogViewModel : DialogViewModelBase { @@ -20,14 +22,14 @@ namespace Artemis.UI.Screens.Plugins private bool _showProgress; private CancellationTokenSource? _tokenSource; - public PluginPrerequisitesInstallDialogViewModel(List subjects, IPrerequisitesVmFactory prerequisitesVmFactory, IDialogService dialogService) + public PluginPrerequisitesInstallDialogViewModel(List subjects, IPrerequisitesVmFactory prerequisitesVmFactory) { Prerequisites = new ObservableCollection(); - foreach (IPrerequisitesSubject prerequisitesSubject in subjects) - Prerequisites.AddRange(prerequisitesSubject.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, false))); + foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.Prerequisites)) + Prerequisites.Add(prerequisitesVmFactory.PluginPrerequisiteViewModel(prerequisite, false)); - foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites) - pluginPrerequisiteViewModel.ConductWith(this); + CanInstall = false; + Task.Run(() => CanInstall = Prerequisites.Any(p => !p.PluginPrerequisite.IsMet())); } public ObservableCollection Prerequisites { get; } @@ -68,7 +70,7 @@ namespace Artemis.UI.Screens.Plugins set => this.RaiseAndSetIfChanged(ref _canInstall, value); } - public async void Install() + public async Task Install() { CanInstall = false; ShowFailed = false; @@ -116,13 +118,12 @@ namespace Artemis.UI.Screens.Plugins public void Accept() { - Result = true; - Close.Execute(); + Close.Execute(true); } - public static Task Show(IDialogService dialogService, List subjects) + public static async Task Show(IWindowService windowService, List subjects) { - return dialogService.ShowDialog(new Dictionary {{"subjects", subjects}}); + return await windowService.ShowDialogAsync(("subjects", subjects)); } /// @@ -136,18 +137,5 @@ namespace Artemis.UI.Screens.Plugins base.Dispose(disposing); } - - #region Overrides of Screen - - /// - protected override void OnInitialActivate() - { - CanInstall = false; - Task.Run(() => CanInstall = Prerequisites.Any(p => !p.PluginPrerequisite.IsMet())); - - base.OnInitialActivate(); - } - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisitesUninstallDialogViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisitesUninstallDialogViewModel.cs index 3d5ba00f0..d678d5df7 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisitesUninstallDialogViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisitesUninstallDialogViewModel.cs @@ -1,76 +1,79 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Threading; using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; -using Artemis.UI.Ninject.Factories; -using Artemis.UI.Shared.Services; -using MaterialDesignThemes.Wpf; -using Stylet; +using Artemis.UI.Avalonia.Ninject.Factories; +using Artemis.UI.Avalonia.Shared; +using Artemis.UI.Avalonia.Shared.Services.Interfaces; +using DynamicData; +using ReactiveUI; -namespace Artemis.UI.Screens.Plugins +namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels { - public class PluginPrerequisitesUninstallDialogViewModel : DialogViewModelBase + public class PluginPrerequisitesUninstallDialogViewModel : DialogViewModelBase { - private readonly IDialogService _dialogService; private readonly IPluginManagementService _pluginManagementService; private readonly List _subjects; - private PluginPrerequisiteViewModel _activePrerequisite; + private readonly IWindowService _windowService; private bool _canUninstall; private bool _isFinished; - private CancellationTokenSource _tokenSource; + private PluginPrerequisiteViewModel? _activePrerequisite; + private CancellationTokenSource? _tokenSource; - public PluginPrerequisitesUninstallDialogViewModel(List subjects, string cancelLabel, IPrerequisitesVmFactory prerequisitesVmFactory, - IDialogService dialogService, IPluginManagementService pluginManagementService) + public PluginPrerequisitesUninstallDialogViewModel(List subjects, string cancelLabel, IPrerequisitesVmFactory prerequisitesVmFactory, IWindowService windowService, + IPluginManagementService pluginManagementService) { _subjects = subjects; - _dialogService = dialogService; + _windowService = windowService; _pluginManagementService = pluginManagementService; CancelLabel = cancelLabel; - Prerequisites = new BindableCollection(); - foreach (IPrerequisitesSubject prerequisitesSubject in subjects) - Prerequisites.AddRange(prerequisitesSubject.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, true))); + Prerequisites = new ObservableCollection(); + foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.Prerequisites)) + Prerequisites.Add(prerequisitesVmFactory.PluginPrerequisiteViewModel(prerequisite, true)); - foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites) - pluginPrerequisiteViewModel.ConductWith(this); + // Could be slow so take it off of the UI thread + Task.Run(() => CanUninstall = Prerequisites.Any(p => p.PluginPrerequisite.IsMet())); } public string CancelLabel { get; } - public BindableCollection Prerequisites { get; } + public ObservableCollection Prerequisites { get; } - public PluginPrerequisiteViewModel ActivePrerequisite + public PluginPrerequisiteViewModel? ActivePrerequisite { get => _activePrerequisite; - set => SetAndNotify(ref _activePrerequisite, value); + set => this.RaiseAndSetIfChanged(ref _activePrerequisite, value); } public bool CanUninstall { get => _canUninstall; - set => SetAndNotify(ref _canUninstall, value); + set => this.RaiseAndSetIfChanged(ref _canUninstall, value); } public bool IsFinished { get => _isFinished; - set => SetAndNotify(ref _isFinished, value); + set => this.RaiseAndSetIfChanged(ref _isFinished, value); } - #region Overrides of DialogViewModelBase - /// - public override void OnDialogClosed(object sender, DialogClosingEventArgs e) + protected override void Dispose(bool disposing) { - _tokenSource?.Cancel(); - base.OnDialogClosed(sender, e); + if (disposing) + { + _tokenSource?.Cancel(); + _tokenSource?.Dispose(); + } + + base.Dispose(disposing); } - #endregion - - public async void Uninstall() + public async Task Uninstall() { CanUninstall = false; @@ -78,20 +81,28 @@ namespace Artemis.UI.Screens.Plugins foreach (IPrerequisitesSubject prerequisitesSubject in _subjects) { if (prerequisitesSubject is PluginInfo pluginInfo) + { _pluginManagementService.DisablePlugin(pluginInfo.Plugin, true); + } } // Disable all subjects that are features if still required foreach (IPrerequisitesSubject prerequisitesSubject in _subjects) { - if (prerequisitesSubject is not PluginFeatureInfo featureInfo) + if (prerequisitesSubject is not PluginFeatureInfo featureInfo) + { continue; + } // Disable the parent plugin if the feature is AlwaysEnabled if (featureInfo.AlwaysEnabled) + { _pluginManagementService.DisablePlugin(featureInfo.Plugin, true); - else if (featureInfo.Instance != null) + } + else if (featureInfo.Instance != null) + { _pluginManagementService.DisablePluginFeature(featureInfo.Instance, true); + } } _tokenSource = new CancellationTokenSource(); @@ -102,14 +113,18 @@ namespace Artemis.UI.Screens.Plugins { pluginPrerequisiteViewModel.IsMet = pluginPrerequisiteViewModel.PluginPrerequisite.IsMet(); if (!pluginPrerequisiteViewModel.IsMet) + { continue; + } ActivePrerequisite = pluginPrerequisiteViewModel; await ActivePrerequisite.Uninstall(_tokenSource.Token); // Wait after the task finished for the user to process what happened if (pluginPrerequisiteViewModel != Prerequisites.Last()) + { await Task.Delay(1000); + } } if (Prerequisites.All(p => !p.IsMet)) @@ -120,14 +135,12 @@ namespace Artemis.UI.Screens.Plugins // This shouldn't be happening and the experience isn't very nice for the user (too lazy to make a nice UI for such an edge case) // but at least give some feedback - Session?.Close(false); - await _dialogService.ShowConfirmDialog( - "Plugin prerequisites", - "The plugin was not able to fully remove all prerequisites. \r\nPlease try again or contact the plugin creator.", - "Confirm", - "" - ); - await Show(_dialogService, _subjects); + Close.Execute(false); + await _windowService.CreateContentDialog() + .WithTitle("Plugin prerequisites") + .WithContent("The plugin was not able to fully remove all prerequisites. \r\nPlease try again or contact the plugin creator.") + .ShowAsync(); + await Show(_windowService, _subjects); } catch (OperationCanceledException) { @@ -143,30 +156,12 @@ namespace Artemis.UI.Screens.Plugins public void Accept() { - Session?.Close(true); + Close.Execute(true); } - public static Task Show(IDialogService dialogService, List subjects, string cancelLabel = "CANCEL") + public static async Task Show(IWindowService windowService, List subjects, string cancelLabel = "Cancel") { - return dialogService.ShowDialog(new Dictionary - { - {"subjects", subjects}, - {"cancelLabel", cancelLabel}, - }); + return await windowService.ShowDialogAsync(("subjects", subjects), ("cancelLabel", cancelLabel)); } - - #region Overrides of Screen - - /// - protected override void OnInitialActivate() - { - CanUninstall = false; - // Could be slow so take it off of the UI thread - Task.Run(() => CanUninstall = Prerequisites.Any(p => p.PluginPrerequisite.IsMet())); - - base.OnInitialActivate(); - } - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginSettingsViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginSettingsViewModel.cs index 6cb395ced..b41b01d09 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginSettingsViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginSettingsViewModel.cs @@ -1,28 +1,23 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; -using Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.ViewModels; -using Artemis.UI.Ninject.Factories; -using Artemis.UI.Screens.Plugins; -using Artemis.UI.Shared.Services; +using Artemis.UI.Avalonia.Shared; using Ninject; -using Stylet; +using ReactiveUI; -namespace Artemis.UI.Screens.Settings.Tabs.Plugins +namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels { - public class PluginSettingsViewModel : Conductor.Collection.AllActive + public class PluginSettingsViewModel : ActivatableViewModelBase { private readonly ICoreService _coreService; - private readonly IDialogService _dialogService; - private readonly IMessageService _messageService; private readonly IPluginManagementService _pluginManagementService; private readonly ISettingsVmFactory _settingsVmFactory; - private readonly IWindowManager _windowManager; private bool _canInstallPrerequisites; private bool _canRemovePrerequisites; private bool _enabling; @@ -32,31 +27,27 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins public PluginSettingsViewModel(Plugin plugin, ISettingsVmFactory settingsVmFactory, ICoreService coreService, - IWindowManager windowManager, - IDialogService dialogService, - IPluginManagementService pluginManagementService, - IMessageService messageService) + IPluginManagementService pluginManagementService) { Plugin = plugin; _settingsVmFactory = settingsVmFactory; _coreService = coreService; - _windowManager = windowManager; - _dialogService = dialogService; _pluginManagementService = pluginManagementService; - _messageService = messageService; } + public ObservableCollection PluginFeatures { get; } + public Plugin Plugin { get => _plugin; - set => SetAndNotify(ref _plugin, value); + set => this.RaiseAndSetIfChanged(ref _plugin, value); } public bool Enabling { get => _enabling; - set => SetAndNotify(ref _enabling, value); + set => this.RaiseAndSetIfChanged(ref _enabling, value); } public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name; @@ -73,7 +64,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins get => _isSettingsPopupOpen; set { - if (!SetAndNotify(ref _isSettingsPopupOpen, value)) return; + if (!this.RaiseAndSetIfChanged(ref _isSettingsPopupOpen, value)) return; CheckPrerequisites(); } } @@ -81,13 +72,13 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins public bool CanInstallPrerequisites { get => _canInstallPrerequisites; - set => SetAndNotify(ref _canInstallPrerequisites, value); + set => this.RaiseAndSetIfChanged(ref _canInstallPrerequisites, value); } public bool CanRemovePrerequisites { get => _canRemovePrerequisites; - set => SetAndNotify(ref _canRemovePrerequisites, value); + set => this.RaiseAndSetIfChanged(ref _canRemovePrerequisites, value); } public void OpenSettings() diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginSettingsWindowViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginSettingsWindowViewModel.cs index 2e4fe165a..51caca975 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginSettingsWindowViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginSettingsWindowViewModel.cs @@ -1,8 +1,7 @@ using System; using Artemis.Core; -using Stylet; -namespace Artemis.UI.Screens.Settings.Tabs.Plugins +namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels { public class PluginSettingsWindowViewModel : Conductor { diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginFeatureView.axaml b/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginFeatureView.axaml index a17b8fcf7..fdfffda4e 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginFeatureView.axaml +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginFeatureView.axaml @@ -3,6 +3,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.Views.PluginFeatureView"> + x:Class="Artemis.UI.Avalonia.Screens.Plugins.Views.PluginFeatureView"> Welcome to Avalonia! diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginFeatureView.axaml.cs b/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginFeatureView.axaml.cs index c5f4b468f..6abc423e8 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginFeatureView.axaml.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginFeatureView.axaml.cs @@ -1,8 +1,7 @@ -using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; -namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.Views +namespace Artemis.UI.Avalonia.Screens.Plugins.Views { public partial class PluginFeatureView : UserControl { diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsView.axaml b/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsView.axaml index bbdf3f186..f66ae98a1 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsView.axaml +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsView.axaml @@ -3,6 +3,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.Views.PluginSettingsView"> + x:Class="Artemis.UI.Avalonia.Screens.Plugins.Views.PluginSettingsView"> Welcome to Avalonia! diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsView.axaml.cs b/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsView.axaml.cs index 0afe92900..e000ea68a 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsView.axaml.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsView.axaml.cs @@ -1,8 +1,7 @@ -using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; -namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.Views +namespace Artemis.UI.Avalonia.Screens.Plugins.Views { public partial class PluginSettingsView : UserControl { diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsWindowView.axaml b/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsWindowView.axaml index e63012f83..af2862d74 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsWindowView.axaml +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsWindowView.axaml @@ -3,7 +3,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.Views.PluginSettingsWindowView" + x:Class="Artemis.UI.Avalonia.Screens.Plugins.Views.PluginSettingsWindowView" Title="PluginSettingsWindowView"> Welcome to Avalonia! diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsWindowView.axaml.cs b/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsWindowView.axaml.cs index fb3b2fc15..15accec08 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsWindowView.axaml.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsWindowView.axaml.cs @@ -2,7 +2,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; -namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.Views +namespace Artemis.UI.Avalonia.Screens.Plugins.Views { public partial class PluginSettingsWindowView : Window {