1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2026-01-01 02:03:32 +00:00

UI - Implemented plugin feature VMs

This commit is contained in:
Robert Beekman 2021-11-08 17:13:06 +01:00
parent 8a7f8cff96
commit c178fc6cf8
21 changed files with 222 additions and 274 deletions

View File

@ -76,15 +76,11 @@ namespace Artemis.UI.Avalonia.Shared.Services.Builders
return this; return this;
} }
public ContentDialogBuilder WithViewModel<T>(params (string name, object value)[] parameters) public ContentDialogBuilder WithViewModel<T>(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<IParameter>().ToArray();
{ viewModel = _kernel.Get<T>(paramsArray);
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray(); _contentDialog.Content = _kernel.Get<T>();
_contentDialog.Content = _kernel.Get<T>(paramsArray);
}
else
_contentDialog.Content = _kernel.Get<T>();
return this; return this;
} }

View File

@ -4,6 +4,7 @@ using Artemis.UI.Avalonia.Shared.Utilities;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Threading; using Avalonia.Threading;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using ReactiveUI;
using Button = Avalonia.Controls.Button; using Button = Avalonia.Controls.Button;
namespace Artemis.UI.Avalonia.Shared.Services.Builders namespace Artemis.UI.Avalonia.Shared.Services.Builders
@ -106,7 +107,7 @@ namespace Artemis.UI.Avalonia.Shared.Services.Builders
public IControl Build() public IControl Build()
{ {
return _action != null return _action != null
? new Button {Content = _text, Command = new DelegateCommand(_ => _action())} ? new Button {Content = _text, Command = ReactiveCommand.Create(() => _action)}
: new Button {Content = _text}; : new Button {Content = _text};
} }
} }

View File

@ -8,11 +8,11 @@ namespace Artemis.UI.Avalonia.Shared.Services.Interfaces
public interface IWindowService : IArtemisSharedUIService public interface IWindowService : IArtemisSharedUIService
{ {
/// <summary> /// <summary>
/// Creates a view model instance of type <typeparamref name="T" /> and shows its corresponding View as a window /// Creates a view model instance of type <typeparamref name="TViewModel" /> and shows its corresponding View as a window
/// </summary> /// </summary>
/// <typeparam name="T">The type of view model to create</typeparam> /// <typeparam name="TViewModel">The type of view model to create</typeparam>
/// <returns>The created view model</returns> /// <returns>The created view model</returns>
T ShowWindow<T>(); TViewModel ShowWindow<TViewModel>(params (string name, object value)[] parameters);
/// <summary> /// <summary>
/// Given a ViewModel, show its corresponding View as a window /// 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); void ShowExceptionDialog(string title, Exception exception);
/// <summary> /// <summary>
/// Given a ViewModel, show its corresponding View as a Dialog /// Given an existing ViewModel, show its corresponding View as a Dialog
/// </summary> /// </summary>
/// <typeparam name="T">The return type</typeparam> /// <typeparam name="TResult">The return type</typeparam>
/// <param name="viewModel">ViewModel to show the View for</param> /// <param name="viewModel">ViewModel to show the View for</param>
/// <returns>A task containing the return value of type <typeparamref name="T" /></returns> /// <returns>A task containing the return value of type <typeparamref name="TResult" /></returns>
Task<T> ShowDialogAsync<T>(object viewModel); Task<TResult> ShowDialogAsync<TResult>(DialogViewModelBase<TResult> viewModel);
/// <summary>
/// Creates a view model instance of type <typeparamref name="TViewModel"/> and shows its corresponding View as a Dialog
/// </summary>
/// <typeparam name="TViewModel">The view model type</typeparam>
/// <typeparam name="TResult">The return type</typeparam>
/// <returns>A task containing the return value of type <typeparamref name="TResult" /></returns>
Task<TResult> ShowDialogAsync<TViewModel, TResult>(params (string name, object value)[] parameters) where TViewModel : DialogViewModelBase<TResult>;
/// <summary> /// <summary>
/// Creates an open file dialog, use the fluent API to configure it /// 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(); ContentDialogBuilder CreateContentDialog();
ConfirmDialogBuilder CreateConfirmDialog();
Window GetCurrentWindow(); Window GetCurrentWindow();
} }
} }

View File

@ -2,7 +2,7 @@
namespace Artemis.UI.Avalonia.Shared.Services namespace Artemis.UI.Avalonia.Shared.Services
{ {
public class ExceptionDialogViewModel : ActivatableViewModelBase public class ExceptionDialogViewModel : DialogViewModelBase<object>
{ {
public ExceptionDialogViewModel(string title, Exception exception) public ExceptionDialogViewModel(string title, Exception exception)
{ {

View File

@ -8,6 +8,7 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Ninject; using Ninject;
using Ninject.Parameters;
namespace Artemis.UI.Avalonia.Shared.Services namespace Artemis.UI.Avalonia.Shared.Services
{ {
@ -21,9 +22,10 @@ namespace Artemis.UI.Avalonia.Shared.Services
_kernel = kernel; _kernel = kernel;
} }
public T ShowWindow<T>() public T ShowWindow<T>(params (string name, object value)[] parameters)
{ {
T viewModel = _kernel.Get<T>()!; IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
T viewModel = _kernel.Get<T>(paramsArray)!;
ShowWindow(viewModel); ShowWindow(viewModel);
return viewModel; return viewModel;
} }
@ -34,49 +36,69 @@ namespace Artemis.UI.Avalonia.Shared.Services
Type? type = viewModel.GetType().Assembly.GetType(name); Type? type = viewModel.GetType().Assembly.GetType(name);
if (type == null) if (type == null)
{
throw new ArtemisSharedUIException($"Failed to find a window named {name}."); throw new ArtemisSharedUIException($"Failed to find a window named {name}.");
}
if (!type.IsAssignableTo(typeof(Window))) if (!type.IsAssignableTo(typeof(Window)))
{
throw new ArtemisSharedUIException($"Type {name} is not a window."); throw new ArtemisSharedUIException($"Type {name} is not a window.");
}
Window window = (Window) Activator.CreateInstance(type)!; Window window = (Window) Activator.CreateInstance(type)!;
window.DataContext = viewModel; window.DataContext = viewModel;
window.Show(); window.Show();
} }
/// <inheritdoc /> public async Task<TResult> ShowDialogAsync<TViewModel, TResult>(params (string name, object value)[] parameters) where TViewModel : DialogViewModelBase<TResult>
public void ShowExceptionDialog(string title, Exception exception)
{ {
if (_exceptionDialogOpen) IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
return; TViewModel viewModel = _kernel.Get<TViewModel>(paramsArray)!;
return await ShowDialogAsync(viewModel);
try
{
_exceptionDialogOpen = true;
ShowDialogAsync<object>(new ExceptionDialogViewModel(title, exception)).GetAwaiter().GetResult();
}
finally
{
_exceptionDialogOpen = false;
}
} }
public async Task<T> ShowDialogAsync<T>(object viewModel) public async Task<TResult> ShowDialogAsync<TResult>(DialogViewModelBase<TResult> viewModel)
{ {
if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime classic) 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"); string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View");
Type? type = viewModel.GetType().Assembly.GetType(name); Type? type = viewModel.GetType().Assembly.GetType(name);
if (type == null) if (type == null)
{
throw new ArtemisSharedUIException($"Failed to find a window named {name}."); throw new ArtemisSharedUIException($"Failed to find a window named {name}.");
}
if (!type.IsAssignableTo(typeof(Window))) if (!type.IsAssignableTo(typeof(Window)))
{
throw new ArtemisSharedUIException($"Type {name} is not a window."); throw new ArtemisSharedUIException($"Type {name} is not a window.");
}
Window window = (Window) Activator.CreateInstance(type)!; Window window = (Window) Activator.CreateInstance(type)!;
window.DataContext = viewModel; window.DataContext = viewModel;
Window parent = classic.Windows.FirstOrDefault(w => w.IsActive) ?? classic.MainWindow; Window parent = classic.Windows.FirstOrDefault(w => w.IsActive) ?? classic.MainWindow;
return await window.ShowDialog<T>(parent); return await window.ShowDialog<TResult>(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() public ContentDialogBuilder CreateContentDialog()
@ -97,7 +119,9 @@ namespace Artemis.UI.Avalonia.Shared.Services
public Window GetCurrentWindow() public Window GetCurrentWindow()
{ {
if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime classic) 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.");
}
Window parent = classic.Windows.FirstOrDefault(w => w.IsActive) ?? classic.MainWindow; Window parent = classic.Windows.FirstOrDefault(w => w.IsActive) ?? classic.MainWindow;
return parent; return parent;

View File

@ -1,60 +0,0 @@
using System;
using System.Windows.Input;
namespace Artemis.UI.Avalonia.Shared.Utilities
{
/// <summary>
/// Provides a command that simply calls a delegate when invoked
/// </summary>
public class DelegateCommand : ICommand
{
private readonly Predicate<object?>? _canExecute;
private readonly Action<object?> _execute;
/// <summary>
/// Creates a new instance of the <see cref="DelegateCommand" /> class
/// </summary>
/// <param name="execute">The delegate to execute</param>
public DelegateCommand(Action<object?> execute) : this(execute, null)
{
}
/// <summary>
/// Creates a new instance of the <see cref="DelegateCommand" /> class with a predicate indicating whether the command
/// can be executed
/// </summary>
/// <param name="execute">The delegate to execute</param>
/// <param name="canExecute">The predicate that determines whether the command can execute</param>
public DelegateCommand(Action<object?> execute, Predicate<object?>? canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
/// <summary>
/// Invokes the <see cref="CanExecuteChanged" /> event
/// </summary>
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
/// <inheritdoc />
public event EventHandler? CanExecuteChanged;
/// <inheritdoc />
public bool CanExecute(object? parameter)
{
if (_canExecute == null)
return true;
return _canExecute(parameter);
}
/// <inheritdoc />
public void Execute(object? parameter)
{
_execute(parameter);
}
}
}

View File

@ -6,14 +6,14 @@ using ReactiveUI;
namespace Artemis.UI.Avalonia.Shared namespace Artemis.UI.Avalonia.Shared
{ {
/// <summary> /// <summary>
/// Represents the base class for Artemis view models /// Represents the base class for Artemis view models
/// </summary> /// </summary>
public abstract class ViewModelBase : ReactiveObject public abstract class ViewModelBase : ReactiveObject
{ {
private string? _displayName; private string? _displayName;
/// <summary> /// <summary>
/// Gets or sets the display name of the view model /// Gets or sets the display name of the view model
/// </summary> /// </summary>
public string? DisplayName public string? DisplayName
{ {
@ -23,7 +23,7 @@ namespace Artemis.UI.Avalonia.Shared
} }
/// <summary> /// <summary>
/// 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
/// </summary> /// </summary>
public abstract class ActivatableViewModelBase : ViewModelBase, IActivatableViewModel, IDisposable public abstract class ActivatableViewModelBase : ViewModelBase, IActivatableViewModel, IDisposable
{ {
@ -34,17 +34,14 @@ namespace Artemis.UI.Avalonia.Shared
} }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
/// <param name="disposing"> /// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources; /// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources. /// <see langword="false" /> to release only unmanaged resources.
/// </param> /// </param>
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing)
{
}
} }
/// <inheritdoc /> /// <inheritdoc />
@ -59,11 +56,10 @@ namespace Artemis.UI.Avalonia.Shared
} }
/// <summary> /// <summary>
/// Represents the base class for Artemis view models used to drive dialogs /// Represents the base class for Artemis view models used to drive dialogs
/// </summary> /// </summary>
public abstract class DialogViewModelBase<TResult> : ActivatableViewModelBase public abstract class DialogViewModelBase<TResult> : ActivatableViewModelBase
{ {
/// <inheritdoc /> /// <inheritdoc />
protected DialogViewModelBase() protected DialogViewModelBase()
{ {
@ -71,9 +67,8 @@ namespace Artemis.UI.Avalonia.Shared
Cancel = ReactiveCommand.Create(() => { }); Cancel = ReactiveCommand.Create(() => { });
} }
/// <summary> /// <summary>
/// Closes the dialog with a given result /// Closes the dialog with a given result
/// </summary> /// </summary>
public ReactiveCommand<TResult, TResult> Close { get; } public ReactiveCommand<TResult, TResult> Close { get; }

View File

@ -2,6 +2,7 @@
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Avalonia.Screens.Device; using Artemis.UI.Avalonia.Screens.Device;
using Artemis.UI.Avalonia.Screens.Device.Tabs.ViewModels; 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.Root.ViewModels;
using Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels; using Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels;
using ReactiveUI; using ReactiveUI;
@ -33,4 +34,9 @@ namespace Artemis.UI.Avalonia.Ninject.Factories
SurfaceDeviceViewModel SurfaceDeviceViewModel(ArtemisDevice device); SurfaceDeviceViewModel SurfaceDeviceViewModel(ArtemisDevice device);
ListDeviceViewModel ListDeviceViewModel(ArtemisDevice device); ListDeviceViewModel ListDeviceViewModel(ArtemisDevice device);
} }
public interface IPrerequisitesVmFactory : IVmFactory
{
PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall);
}
} }

View File

@ -6,11 +6,12 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Avalonia.Shared;
using Artemis.UI.Avalonia.Shared.Services.Builders; using Artemis.UI.Avalonia.Shared.Services.Builders;
using Artemis.UI.Avalonia.Shared.Services.Interfaces; using Artemis.UI.Avalonia.Shared.Services.Interfaces;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.ViewModels namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
{ {
public class PluginFeatureViewModel : ActivatableViewModelBase public class PluginFeatureViewModel : ActivatableViewModelBase
{ {

View File

@ -1,10 +1,7 @@
using System; using Artemis.Core;
using System.ComponentModel;
using Artemis.Core;
using Artemis.UI.Avalonia;
using Artemis.UI.Avalonia.Shared; using Artemis.UI.Avalonia.Shared;
namespace Artemis.UI.Screens.Plugins namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
{ {
public class PluginPrerequisiteActionViewModel : ViewModelBase public class PluginPrerequisiteActionViewModel : ViewModelBase
{ {

View File

@ -1,26 +1,51 @@
using System; using System.Collections.ObjectModel;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.UI.Avalonia.Shared;
using Artemis.UI.Shared.Services; using DynamicData;
using Stylet; using ReactiveUI;
namespace Artemis.UI.Screens.Plugins namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
{ {
public class PluginPrerequisiteViewModel : Conductor<PluginPrerequisiteActionViewModel>.Collection.OneActive public class PluginPrerequisiteViewModel : ActivatableViewModelBase
{ {
private readonly bool _uninstall; private readonly bool _uninstall;
private readonly ObservableAsPropertyHelper<bool> _busy;
private readonly ObservableAsPropertyHelper<int> _activeStepNumber;
private bool _installing; private bool _installing;
private bool _uninstalling; private bool _uninstalling;
private bool _isMet; private bool _isMet;
private PluginPrerequisiteActionViewModel? _activeAction;
public PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall) public PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall)
{ {
_uninstall = uninstall; _uninstall = uninstall;
PluginPrerequisite = pluginPrerequisite; PluginPrerequisite = pluginPrerequisite;
Actions = new ObservableCollection<PluginPrerequisiteActionViewModel>(!_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<PluginPrerequisiteActionViewModel> Actions { get; }
public PluginPrerequisiteActionViewModel? ActiveAction
{
get => _activeAction;
set => this.RaiseAndSetIfChanged(ref _activeAction , value);
} }
public PluginPrerequisite PluginPrerequisite { get; } public PluginPrerequisite PluginPrerequisite { get; }
@ -28,32 +53,24 @@ namespace Artemis.UI.Screens.Plugins
public bool Installing public bool Installing
{ {
get => _installing; get => _installing;
set set => this.RaiseAndSetIfChanged(ref _installing, value);
{
SetAndNotify(ref _installing, value);
NotifyOfPropertyChange(nameof(Busy));
}
} }
public bool Uninstalling public bool Uninstalling
{ {
get => _uninstalling; get => _uninstalling;
set set => this.RaiseAndSetIfChanged(ref _uninstalling, value);
{
SetAndNotify(ref _uninstalling, value);
NotifyOfPropertyChange(nameof(Busy));
}
} }
public bool IsMet public bool IsMet
{ {
get => _isMet; get => _isMet;
set => SetAndNotify(ref _isMet, value); set => this.RaiseAndSetIfChanged(ref _isMet, value);
} }
public bool Busy => Installing || Uninstalling; public bool Busy => _busy.Value;
public int ActiveStemNumber => Items.IndexOf(ActiveItem) + 1; public int ActiveStepNumber => _activeStepNumber.Value;
public bool HasMultipleActions => Items.Count > 1; public bool HasMultipleActions => Actions.Count > 1;
public async Task Install(CancellationToken cancellationToken) 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)) if (e.PropertyName == nameof(PluginPrerequisite.CurrentAction))
ActivateCurrentAction(); ActivateCurrentAction();
@ -97,37 +114,27 @@ namespace Artemis.UI.Screens.Plugins
private void ActivateCurrentAction() private void ActivateCurrentAction()
{ {
PluginPrerequisiteActionViewModel newActiveItem = Items.FirstOrDefault(i => i.Action == PluginPrerequisite.CurrentAction); PluginPrerequisiteActionViewModel? activeAction = Actions.FirstOrDefault(i => i.Action == PluginPrerequisite.CurrentAction);
if (newActiveItem == null) if (activeAction == null)
return; return;
ActiveItem = newActiveItem; ActiveAction = activeAction;
NotifyOfPropertyChange(nameof(ActiveStemNumber));
} }
#region Overrides of Screen #region IDisposable
/// <inheritdoc /> /// <inheritdoc />
protected override void OnClose() protected override void Dispose(bool disposing)
{ {
PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged; if (disposing)
base.OnClose(); {
} PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged;
}
/// <inheritdoc /> base.Dispose(disposing);
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();
} }
#endregion #endregion
} }
} }

View File

@ -5,10 +5,12 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Avalonia.Ninject.Factories;
using Artemis.UI.Avalonia.Shared; using Artemis.UI.Avalonia.Shared;
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.Plugins namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
{ {
public class PluginPrerequisitesInstallDialogViewModel : DialogViewModelBase<bool> public class PluginPrerequisitesInstallDialogViewModel : DialogViewModelBase<bool>
{ {
@ -20,14 +22,14 @@ namespace Artemis.UI.Screens.Plugins
private bool _showProgress; private bool _showProgress;
private CancellationTokenSource? _tokenSource; private CancellationTokenSource? _tokenSource;
public PluginPrerequisitesInstallDialogViewModel(List<IPrerequisitesSubject> subjects, IPrerequisitesVmFactory prerequisitesVmFactory, IDialogService dialogService) public PluginPrerequisitesInstallDialogViewModel(List<IPrerequisitesSubject> subjects, IPrerequisitesVmFactory prerequisitesVmFactory)
{ {
Prerequisites = new ObservableCollection<PluginPrerequisiteViewModel>(); Prerequisites = new ObservableCollection<PluginPrerequisiteViewModel>();
foreach (IPrerequisitesSubject prerequisitesSubject in subjects) foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.Prerequisites))
Prerequisites.AddRange(prerequisitesSubject.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, false))); Prerequisites.Add(prerequisitesVmFactory.PluginPrerequisiteViewModel(prerequisite, false));
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites) CanInstall = false;
pluginPrerequisiteViewModel.ConductWith(this); Task.Run(() => CanInstall = Prerequisites.Any(p => !p.PluginPrerequisite.IsMet()));
} }
public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; } public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
@ -68,7 +70,7 @@ namespace Artemis.UI.Screens.Plugins
set => this.RaiseAndSetIfChanged(ref _canInstall, value); set => this.RaiseAndSetIfChanged(ref _canInstall, value);
} }
public async void Install() public async Task Install()
{ {
CanInstall = false; CanInstall = false;
ShowFailed = false; ShowFailed = false;
@ -116,13 +118,12 @@ namespace Artemis.UI.Screens.Plugins
public void Accept() public void Accept()
{ {
Result = true; Close.Execute(true);
Close.Execute();
} }
public static Task<object> Show(IDialogService dialogService, List<IPrerequisitesSubject> subjects) public static async Task<bool> Show(IWindowService windowService, List<IPrerequisitesSubject> subjects)
{ {
return dialogService.ShowDialog<PluginPrerequisitesInstallDialogViewModel>(new Dictionary<string, object> {{"subjects", subjects}}); return await windowService.ShowDialogAsync<PluginPrerequisitesInstallDialogViewModel, bool>(("subjects", subjects));
} }
/// <inheritdoc /> /// <inheritdoc />
@ -136,18 +137,5 @@ namespace Artemis.UI.Screens.Plugins
base.Dispose(disposing); base.Dispose(disposing);
} }
#region Overrides of Screen
/// <inheritdoc />
protected override void OnInitialActivate()
{
CanInstall = false;
Task.Run(() => CanInstall = Prerequisites.Any(p => !p.PluginPrerequisite.IsMet()));
base.OnInitialActivate();
}
#endregion
} }
} }

View File

@ -1,76 +1,79 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Avalonia.Ninject.Factories;
using Artemis.UI.Shared.Services; using Artemis.UI.Avalonia.Shared;
using MaterialDesignThemes.Wpf; using Artemis.UI.Avalonia.Shared.Services.Interfaces;
using Stylet; using DynamicData;
using ReactiveUI;
namespace Artemis.UI.Screens.Plugins namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
{ {
public class PluginPrerequisitesUninstallDialogViewModel : DialogViewModelBase public class PluginPrerequisitesUninstallDialogViewModel : DialogViewModelBase<bool>
{ {
private readonly IDialogService _dialogService;
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly List<IPrerequisitesSubject> _subjects; private readonly List<IPrerequisitesSubject> _subjects;
private PluginPrerequisiteViewModel _activePrerequisite; private readonly IWindowService _windowService;
private bool _canUninstall; private bool _canUninstall;
private bool _isFinished; private bool _isFinished;
private CancellationTokenSource _tokenSource; private PluginPrerequisiteViewModel? _activePrerequisite;
private CancellationTokenSource? _tokenSource;
public PluginPrerequisitesUninstallDialogViewModel(List<IPrerequisitesSubject> subjects, string cancelLabel, IPrerequisitesVmFactory prerequisitesVmFactory, public PluginPrerequisitesUninstallDialogViewModel(List<IPrerequisitesSubject> subjects, string cancelLabel, IPrerequisitesVmFactory prerequisitesVmFactory, IWindowService windowService,
IDialogService dialogService, IPluginManagementService pluginManagementService) IPluginManagementService pluginManagementService)
{ {
_subjects = subjects; _subjects = subjects;
_dialogService = dialogService; _windowService = windowService;
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
CancelLabel = cancelLabel; CancelLabel = cancelLabel;
Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>(); Prerequisites = new ObservableCollection<PluginPrerequisiteViewModel>();
foreach (IPrerequisitesSubject prerequisitesSubject in subjects) foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.Prerequisites))
Prerequisites.AddRange(prerequisitesSubject.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, true))); Prerequisites.Add(prerequisitesVmFactory.PluginPrerequisiteViewModel(prerequisite, true));
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites) // Could be slow so take it off of the UI thread
pluginPrerequisiteViewModel.ConductWith(this); Task.Run(() => CanUninstall = Prerequisites.Any(p => p.PluginPrerequisite.IsMet()));
} }
public string CancelLabel { get; } public string CancelLabel { get; }
public BindableCollection<PluginPrerequisiteViewModel> Prerequisites { get; } public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel ActivePrerequisite public PluginPrerequisiteViewModel? ActivePrerequisite
{ {
get => _activePrerequisite; get => _activePrerequisite;
set => SetAndNotify(ref _activePrerequisite, value); set => this.RaiseAndSetIfChanged(ref _activePrerequisite, value);
} }
public bool CanUninstall public bool CanUninstall
{ {
get => _canUninstall; get => _canUninstall;
set => SetAndNotify(ref _canUninstall, value); set => this.RaiseAndSetIfChanged(ref _canUninstall, value);
} }
public bool IsFinished public bool IsFinished
{ {
get => _isFinished; get => _isFinished;
set => SetAndNotify(ref _isFinished, value); set => this.RaiseAndSetIfChanged(ref _isFinished, value);
} }
#region Overrides of DialogViewModelBase
/// <inheritdoc /> /// <inheritdoc />
public override void OnDialogClosed(object sender, DialogClosingEventArgs e) protected override void Dispose(bool disposing)
{ {
_tokenSource?.Cancel(); if (disposing)
base.OnDialogClosed(sender, e); {
_tokenSource?.Cancel();
_tokenSource?.Dispose();
}
base.Dispose(disposing);
} }
#endregion public async Task Uninstall()
public async void Uninstall()
{ {
CanUninstall = false; CanUninstall = false;
@ -78,20 +81,28 @@ namespace Artemis.UI.Screens.Plugins
foreach (IPrerequisitesSubject prerequisitesSubject in _subjects) foreach (IPrerequisitesSubject prerequisitesSubject in _subjects)
{ {
if (prerequisitesSubject is PluginInfo pluginInfo) if (prerequisitesSubject is PluginInfo pluginInfo)
{
_pluginManagementService.DisablePlugin(pluginInfo.Plugin, true); _pluginManagementService.DisablePlugin(pluginInfo.Plugin, true);
}
} }
// Disable all subjects that are features if still required // Disable all subjects that are features if still required
foreach (IPrerequisitesSubject prerequisitesSubject in _subjects) foreach (IPrerequisitesSubject prerequisitesSubject in _subjects)
{ {
if (prerequisitesSubject is not PluginFeatureInfo featureInfo) if (prerequisitesSubject is not PluginFeatureInfo featureInfo)
{
continue; continue;
}
// Disable the parent plugin if the feature is AlwaysEnabled // Disable the parent plugin if the feature is AlwaysEnabled
if (featureInfo.AlwaysEnabled) if (featureInfo.AlwaysEnabled)
{
_pluginManagementService.DisablePlugin(featureInfo.Plugin, true); _pluginManagementService.DisablePlugin(featureInfo.Plugin, true);
else if (featureInfo.Instance != null) }
else if (featureInfo.Instance != null)
{
_pluginManagementService.DisablePluginFeature(featureInfo.Instance, true); _pluginManagementService.DisablePluginFeature(featureInfo.Instance, true);
}
} }
_tokenSource = new CancellationTokenSource(); _tokenSource = new CancellationTokenSource();
@ -102,14 +113,18 @@ namespace Artemis.UI.Screens.Plugins
{ {
pluginPrerequisiteViewModel.IsMet = pluginPrerequisiteViewModel.PluginPrerequisite.IsMet(); pluginPrerequisiteViewModel.IsMet = pluginPrerequisiteViewModel.PluginPrerequisite.IsMet();
if (!pluginPrerequisiteViewModel.IsMet) if (!pluginPrerequisiteViewModel.IsMet)
{
continue; continue;
}
ActivePrerequisite = pluginPrerequisiteViewModel; ActivePrerequisite = pluginPrerequisiteViewModel;
await ActivePrerequisite.Uninstall(_tokenSource.Token); await ActivePrerequisite.Uninstall(_tokenSource.Token);
// Wait after the task finished for the user to process what happened // Wait after the task finished for the user to process what happened
if (pluginPrerequisiteViewModel != Prerequisites.Last()) if (pluginPrerequisiteViewModel != Prerequisites.Last())
{
await Task.Delay(1000); await Task.Delay(1000);
}
} }
if (Prerequisites.All(p => !p.IsMet)) 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) // 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 // but at least give some feedback
Session?.Close(false); Close.Execute(false);
await _dialogService.ShowConfirmDialog( await _windowService.CreateContentDialog()
"Plugin prerequisites", .WithTitle("Plugin prerequisites")
"The plugin was not able to fully remove all prerequisites. \r\nPlease try again or contact the plugin creator.", .WithContent("The plugin was not able to fully remove all prerequisites. \r\nPlease try again or contact the plugin creator.")
"Confirm", .ShowAsync();
"" await Show(_windowService, _subjects);
);
await Show(_dialogService, _subjects);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@ -143,30 +156,12 @@ namespace Artemis.UI.Screens.Plugins
public void Accept() public void Accept()
{ {
Session?.Close(true); Close.Execute(true);
} }
public static Task<object> Show(IDialogService dialogService, List<IPrerequisitesSubject> subjects, string cancelLabel = "CANCEL") public static async Task<object> Show(IWindowService windowService, List<IPrerequisitesSubject> subjects, string cancelLabel = "Cancel")
{ {
return dialogService.ShowDialog<PluginPrerequisitesUninstallDialogViewModel>(new Dictionary<string, object> return await windowService.ShowDialogAsync<PluginPrerequisitesUninstallDialogViewModel, bool>(("subjects", subjects), ("cancelLabel", cancelLabel));
{
{"subjects", subjects},
{"cancelLabel", cancelLabel},
});
} }
#region Overrides of Screen
/// <inheritdoc />
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
} }
} }

View File

@ -1,28 +1,23 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.ViewModels; using Artemis.UI.Avalonia.Shared;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Shared.Services;
using Ninject; using Ninject;
using Stylet; using ReactiveUI;
namespace Artemis.UI.Screens.Settings.Tabs.Plugins namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
{ {
public class PluginSettingsViewModel : Conductor<PluginFeatureViewModel>.Collection.AllActive public class PluginSettingsViewModel : ActivatableViewModelBase
{ {
private readonly ICoreService _coreService; private readonly ICoreService _coreService;
private readonly IDialogService _dialogService;
private readonly IMessageService _messageService;
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly ISettingsVmFactory _settingsVmFactory; private readonly ISettingsVmFactory _settingsVmFactory;
private readonly IWindowManager _windowManager;
private bool _canInstallPrerequisites; private bool _canInstallPrerequisites;
private bool _canRemovePrerequisites; private bool _canRemovePrerequisites;
private bool _enabling; private bool _enabling;
@ -32,31 +27,27 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
public PluginSettingsViewModel(Plugin plugin, public PluginSettingsViewModel(Plugin plugin,
ISettingsVmFactory settingsVmFactory, ISettingsVmFactory settingsVmFactory,
ICoreService coreService, ICoreService coreService,
IWindowManager windowManager, IPluginManagementService pluginManagementService)
IDialogService dialogService,
IPluginManagementService pluginManagementService,
IMessageService messageService)
{ {
Plugin = plugin; Plugin = plugin;
_settingsVmFactory = settingsVmFactory; _settingsVmFactory = settingsVmFactory;
_coreService = coreService; _coreService = coreService;
_windowManager = windowManager;
_dialogService = dialogService;
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
_messageService = messageService;
} }
public ObservableCollection<PluginFeatureViewModel> PluginFeatures { get; }
public Plugin Plugin public Plugin Plugin
{ {
get => _plugin; get => _plugin;
set => SetAndNotify(ref _plugin, value); set => this.RaiseAndSetIfChanged(ref _plugin, value);
} }
public bool Enabling public bool Enabling
{ {
get => _enabling; get => _enabling;
set => SetAndNotify(ref _enabling, value); set => this.RaiseAndSetIfChanged(ref _enabling, value);
} }
public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name; public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name;
@ -73,7 +64,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
get => _isSettingsPopupOpen; get => _isSettingsPopupOpen;
set set
{ {
if (!SetAndNotify(ref _isSettingsPopupOpen, value)) return; if (!this.RaiseAndSetIfChanged(ref _isSettingsPopupOpen, value)) return;
CheckPrerequisites(); CheckPrerequisites();
} }
} }
@ -81,13 +72,13 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
public bool CanInstallPrerequisites public bool CanInstallPrerequisites
{ {
get => _canInstallPrerequisites; get => _canInstallPrerequisites;
set => SetAndNotify(ref _canInstallPrerequisites, value); set => this.RaiseAndSetIfChanged(ref _canInstallPrerequisites, value);
} }
public bool CanRemovePrerequisites public bool CanRemovePrerequisites
{ {
get => _canRemovePrerequisites; get => _canRemovePrerequisites;
set => SetAndNotify(ref _canRemovePrerequisites, value); set => this.RaiseAndSetIfChanged(ref _canRemovePrerequisites, value);
} }
public void OpenSettings() public void OpenSettings()

View File

@ -1,8 +1,7 @@
using System; using System;
using Artemis.Core; using Artemis.Core;
using Stylet;
namespace Artemis.UI.Screens.Settings.Tabs.Plugins namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
{ {
public class PluginSettingsWindowViewModel : Conductor<PluginConfigurationViewModel> public class PluginSettingsWindowViewModel : Conductor<PluginConfigurationViewModel>
{ {

View File

@ -3,6 +3,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" 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! Welcome to Avalonia!
</UserControl> </UserControl>

View File

@ -1,8 +1,7 @@
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; 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 public partial class PluginFeatureView : UserControl
{ {

View File

@ -3,6 +3,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" 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! Welcome to Avalonia!
</UserControl> </UserControl>

View File

@ -1,8 +1,7 @@
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; 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 public partial class PluginSettingsView : UserControl
{ {

View File

@ -3,7 +3,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" 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"> Title="PluginSettingsWindowView">
Welcome to Avalonia! Welcome to Avalonia!
</Window> </Window>

View File

@ -2,7 +2,7 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; 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 public partial class PluginSettingsWindowView : Window
{ {