diff --git a/src/Artemis.Core/Plugins/IPluginConfigurationDialog.cs b/src/Artemis.Core/Plugins/IPluginConfigurationDialog.cs index 27736b149..5d179d0d7 100644 --- a/src/Artemis.Core/Plugins/IPluginConfigurationDialog.cs +++ b/src/Artemis.Core/Plugins/IPluginConfigurationDialog.cs @@ -1,9 +1,15 @@ -namespace Artemis.Core +using System; + +namespace Artemis.Core { /// /// Represents a configuration dialog for a /// public interface IPluginConfigurationDialog { + /// + /// The type of view model the tab contains + /// + Type Type { get; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Utilities/Utilities.cs b/src/Artemis.Core/Utilities/Utilities.cs index cd9299be2..e036fa39b 100644 --- a/src/Artemis.Core/Utilities/Utilities.cs +++ b/src/Artemis.Core/Utilities/Utilities.cs @@ -89,6 +89,28 @@ namespace Artemis.Core } } + /// + /// Occurs when the core has requested an application shutdown + /// + public static event EventHandler? ShutdownRequested; + + /// + /// Occurs when the core has requested an application restart + /// + public static event EventHandler? RestartRequested; + + /// + /// Opens the provided folder in the user's file explorer + /// + /// The full path of the folder to open + public static void OpenFolder(string path) + { + if (OperatingSystem.IsWindows()) + Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", path); + else + throw new PlatformNotSupportedException("Can't open folders yet on non-Windows systems Q.Q"); + } + /// /// Gets the current application location /// @@ -103,6 +125,11 @@ namespace Artemis.Core RestartRequested?.Invoke(null, e); } + private static void OnShutdownRequested() + { + ShutdownRequested?.Invoke(null, EventArgs.Empty); + } + #region Scaling internal static int RenderScaleMultiplier { get; set; } = 2; @@ -118,32 +145,13 @@ namespace Artemis.Core return SKRectI.Create(roundX, roundY, roundWidth, roundHeight); return SKRectI.Create( - roundX - (roundX % RenderScaleMultiplier), - roundY - (roundY % RenderScaleMultiplier), - roundWidth - (roundWidth % RenderScaleMultiplier), - roundHeight - (roundHeight % RenderScaleMultiplier) + roundX - roundX % RenderScaleMultiplier, + roundY - roundY % RenderScaleMultiplier, + roundWidth - roundWidth % RenderScaleMultiplier, + roundHeight - roundHeight % RenderScaleMultiplier ); } #endregion - - #region Events - - /// - /// Occurs when the core has requested an application shutdown - /// - public static event EventHandler? ShutdownRequested; - - /// - /// Occurs when the core has requested an application restart - /// - public static event EventHandler? RestartRequested; - - private static void OnShutdownRequested() - { - ShutdownRequested?.Invoke(null, EventArgs.Empty); - } - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.csproj.DotSettings b/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.csproj.DotSettings index 1865d232b..5230616f2 100644 --- a/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.csproj.DotSettings +++ b/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.csproj.DotSettings @@ -1,2 +1,3 @@  + True True \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.xml b/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.xml new file mode 100644 index 000000000..30012341d --- /dev/null +++ b/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.xml @@ -0,0 +1,491 @@ + + + + Artemis.UI.Avalonia.Shared + + + + + Visualizes an with optional per-LED colors + + + + + + + + + + + Occurs when a LED of the device has been clicked + + + + + Invokes the event + + + + + + Gets or sets the to display + + + + + Gets or sets the to display + + + + + Gets or sets boolean indicating whether or not to show per-LED colors + + + + + Gets or sets a boolean indicating whether or not to show per-LED colors + + + + + Gets or sets a list of LEDs to highlight + + + + + Gets or sets a list of LEDs to highlight + + + + + + + + + + + + + + Gets or sets the currently selected value + + + + + Gets or sets the currently selected value + + + + + + + + + + + Gets or sets the to display + + + + + Gets or sets the to display + + + + + Visualizes an with optional per-LED colors + + + + + Defines the property. + + + + + Defines the property. + + + + + Defines the property. + + + + + Defines the property. + + + + + + + + Gets or sets a brush used to paint the control's background. + + + + + Gets or sets a brush used to paint the control's border + + + + + Gets or sets the width of the control's border + + + + + + + + + + + Converts into . + + + + + + + + + + + Converts into . + + + + + + + + + + + Provides data about selection events raised by + + + + + Gets the data model path that was selected + + + + + Provides data about submit events raised by + + + + + The value that was submitted + + + + + Provides data on LED click events raised by the device visualizer + + + + + The device that was clicked + + + + + The LED that was clicked + + + + + Provides data on profile related events raised by the profile editor + + + + + Gets the profile the event was raised for + + + + + If applicable, the previous active profile before the event was raised + + + + + Provides data on profile element related events raised by the profile editor + + + + + Gets the profile element the event was raised for + + + + + If applicable, the previous active profile element before the event was raised + + + + + Represents errors that occur within the Artemis Shared UI library + + + + + The main of the Artemis Shared UI toolkit that binds all services + + + + + + + + + + + + + + Describes a configuration dialog for a specific plugin + + + + + + + + Represents a view model for a plugin configuration window + + + + + Creates a new instance of the class + + + + + + Gets the plugin this configuration view model is associated with + + + + + A command that closes the window + + + + + Represents a builder that can create a . + + + + + Sets the name of the filter + + + + + Adds the provided extension to the filter + + + + + Add a filter to the dialog + + + + + Represents a builder that can create a . + + + + + Indicate that the user can select multiple files. + + + + + Set the title of the dialog + + + + + Set the initial directory of the dialog + + + + + Set the initial file name of the dialog + + + + + Add a filter to the dialog + + + + + Shows the file dialog + + + A task that on completion returns an array containing the full path to the selected + files, or null if the dialog was canceled. + + + + + Represents a builder that can create a . + + + + + Set the title of the dialog + + + + + Set the initial directory of the dialog + + + + + Set the initial file name of the dialog + + + + + Set the default extension of the dialog + + + + + Add a filter to the dialog + + + + + Shows the save file dialog. + + + A task that on completion contains the full path of the save location, or null if the + dialog was canceled. + + + + + Represents a service provided by the Artemis Shared UI library + + + + + Creates a view model instance of type and shows its corresponding View as a window + + The type of view model to create + The created view model + + + + Given a ViewModel, show its corresponding View as a window + + ViewModel to show the View for + + + + Shows a dialog displaying the given exception + + The title of the dialog + The exception to display + + + + Given an existing ViewModel, show its corresponding View as a Dialog + + The return type + ViewModel to show the View for + A task containing the return value of type + + + + 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 + + + + Shows a content dialog asking the user to confirm an action + + The title of the dialog + The message of the dialog + The text of the confirm button + The text of the cancel button, if the cancel button will not be shown + A task containing the result of the dialog, if confirmed; otherwise + + + + Creates an open file dialog, use the fluent API to configure it + + The builder that can be used to configure the dialog + + + + Creates a save file dialog, use the fluent API to configure it + + The builder that can be used to configure the dialog + + + + Represents the base class for Artemis view models + + + + + Gets or sets the display name of the view model + + + + + Represents the base class for Artemis view models that are interested in the activated event + + + + + + + + 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. + + + + + + + + + + + Represents the base class for Artemis view models used to drive dialogs + + + + + + + + Closes the dialog with a given result + + + + + Closes the dialog without a result + + + + diff --git a/src/Artemis.UI.Avalonia.Shared/Plugins/PluginConfigurationDialog.cs b/src/Artemis.UI.Avalonia.Shared/Plugins/PluginConfigurationDialog.cs new file mode 100644 index 000000000..48736d54e --- /dev/null +++ b/src/Artemis.UI.Avalonia.Shared/Plugins/PluginConfigurationDialog.cs @@ -0,0 +1,21 @@ +using System; +using Artemis.Core; + +namespace Artemis.UI.Avalonia.Shared +{ + /// + public class PluginConfigurationDialog : PluginConfigurationDialog where T : PluginConfigurationViewModel + { + /// + public override Type Type => typeof(T); + } + + /// + /// Describes a configuration dialog for a specific plugin + /// + public abstract class PluginConfigurationDialog : IPluginConfigurationDialog + { + /// + public abstract Type Type { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia.Shared/Plugins/PluginConfigurationViewModel.cs b/src/Artemis.UI.Avalonia.Shared/Plugins/PluginConfigurationViewModel.cs new file mode 100644 index 000000000..008d8ccb7 --- /dev/null +++ b/src/Artemis.UI.Avalonia.Shared/Plugins/PluginConfigurationViewModel.cs @@ -0,0 +1,32 @@ +using System.Reactive; +using Artemis.Core; +using ReactiveUI; + +namespace Artemis.UI.Avalonia.Shared +{ + /// + /// Represents a view model for a plugin configuration window + /// + public abstract class PluginConfigurationViewModel : ViewModelBase, IPluginConfigurationViewModel + { + /// + /// Creates a new instance of the class + /// + /// + protected PluginConfigurationViewModel(Plugin plugin) + { + Plugin = plugin; + Close = ReactiveCommand.Create(() => { }); + } + + /// + /// Gets the plugin this configuration view model is associated with + /// + public Plugin Plugin { get; } + + /// + /// A command that closes the window + /// + public ReactiveCommand Close { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia.Shared/Services/Builders/NotificationBuilder.cs b/src/Artemis.UI.Avalonia.Shared/Services/Builders/NotificationBuilder.cs index 4d4e6d04f..7cd562f7c 100644 --- a/src/Artemis.UI.Avalonia.Shared/Services/Builders/NotificationBuilder.cs +++ b/src/Artemis.UI.Avalonia.Shared/Services/Builders/NotificationBuilder.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; -using Artemis.UI.Avalonia.Shared.Utilities; using Avalonia.Controls; using Avalonia.Threading; using FluentAvalonia.UI.Controls; diff --git a/src/Artemis.UI.Avalonia.Shared/Services/Interfaces/IWindowService.cs b/src/Artemis.UI.Avalonia.Shared/Services/Interfaces/IWindowService.cs index ea73f0756..8efcda80a 100644 --- a/src/Artemis.UI.Avalonia.Shared/Services/Interfaces/IWindowService.cs +++ b/src/Artemis.UI.Avalonia.Shared/Services/Interfaces/IWindowService.cs @@ -43,6 +43,16 @@ namespace Artemis.UI.Avalonia.Shared.Services.Interfaces /// A task containing the return value of type Task ShowDialogAsync(params (string name, object value)[] parameters) where TViewModel : DialogViewModelBase; + /// + /// Shows a content dialog asking the user to confirm an action + /// + /// The title of the dialog + /// The message of the dialog + /// The text of the confirm button + /// The text of the cancel button, if the cancel button will not be shown + /// A task containing the result of the dialog, if confirmed; otherwise + Task ShowConfirmContentDialog(string title, string message, string confirm = "Confirm", string? cancel = "Cancel"); + /// /// Creates an open file dialog, use the fluent API to configure it /// @@ -57,8 +67,6 @@ 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/ExceptionDialogView.axaml.cs b/src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogView.axaml.cs index 8457638c4..773c3f6d7 100644 --- a/src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogView.axaml.cs +++ b/src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogView.axaml.cs @@ -1,4 +1,6 @@ +using Avalonia; using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; namespace Artemis.UI.Avalonia.Shared.Services { diff --git a/src/Artemis.UI.Avalonia.Shared/Services/WindowService/WindowService.cs b/src/Artemis.UI.Avalonia.Shared/Services/WindowService/WindowService.cs index 8dfe1c199..15959fe96 100644 --- a/src/Artemis.UI.Avalonia.Shared/Services/WindowService/WindowService.cs +++ b/src/Artemis.UI.Avalonia.Shared/Services/WindowService/WindowService.cs @@ -7,6 +7,7 @@ using Artemis.UI.Avalonia.Shared.Services.Interfaces; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; +using FluentAvalonia.UI.Controls; using Ninject; using Ninject.Parameters; @@ -57,6 +58,18 @@ namespace Artemis.UI.Avalonia.Shared.Services return await ShowDialogAsync(viewModel); } + public async Task ShowConfirmContentDialog(string title, string message, string confirm = "Confirm", string? cancel = "Cancel") + { + ContentDialogResult contentDialogResult = await CreateContentDialog() + .WithTitle(title) + .WithContent(message) + .HavingPrimaryButton(b => b.WithText(confirm)) + .WithCloseButtonText(cancel) + .ShowAsync(); + + return contentDialogResult == ContentDialogResult.Primary; + } + public async Task ShowDialogAsync(DialogViewModelBase viewModel) { if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime classic) diff --git a/src/Artemis.UI.Avalonia/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI.Avalonia/Ninject/Factories/IVMFactory.cs index 34a52dbc6..735a93e58 100644 --- a/src/Artemis.UI.Avalonia/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI.Avalonia/Ninject/Factories/IVMFactory.cs @@ -22,6 +22,13 @@ namespace Artemis.UI.Avalonia.Ninject.Factories InputMappingsTabViewModel InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection selectedLeds); } + public interface ISettingsVmFactory : IVmFactory + { + PluginSettingsViewModel CreatePluginSettingsViewModel(Plugin plugin); + PluginFeatureViewModel CreatePluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield); + // DeviceSettingsViewModel CreateDeviceSettingsViewModel(ArtemisDevice device); + } + public interface ISidebarVmFactory : IVmFactory { SidebarViewModel SidebarViewModel(IScreen hostScreen); diff --git a/src/Artemis.UI.Avalonia/Ninject/UIModule.cs b/src/Artemis.UI.Avalonia/Ninject/UIModule.cs index 61eebd3ae..faf29a400 100644 --- a/src/Artemis.UI.Avalonia/Ninject/UIModule.cs +++ b/src/Artemis.UI.Avalonia/Ninject/UIModule.cs @@ -2,6 +2,7 @@ using Artemis.UI.Avalonia.Ninject.Factories; using Artemis.UI.Avalonia.Screens; using Artemis.UI.Avalonia.Services.Interfaces; +using Artemis.UI.Avalonia.Shared; using Ninject.Extensions.Conventions; using Ninject.Modules; using Ninject.Planning.Bindings.Resolvers; diff --git a/src/Artemis.UI.Avalonia/Screens/Debugger/DebugViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Debugger/DebugViewModel.cs index 967de423c..04e7fdc2a 100644 --- a/src/Artemis.UI.Avalonia/Screens/Debugger/DebugViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Debugger/DebugViewModel.cs @@ -5,6 +5,7 @@ using Artemis.UI.Avalonia.Screens.Debugger.Tabs.Logs; using Artemis.UI.Avalonia.Screens.Debugger.Tabs.Performance; using Artemis.UI.Avalonia.Screens.Debugger.Tabs.Render; using Artemis.UI.Avalonia.Services.Interfaces; +using Artemis.UI.Avalonia.Shared; using FluentAvalonia.UI.Controls; using Ninject; using Ninject.Parameters; diff --git a/src/Artemis.UI.Avalonia/Screens/Debugger/Tabs/DataModel/DataModelDebugViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Debugger/Tabs/DataModel/DataModelDebugViewModel.cs index bbc5f996e..f5da16218 100644 --- a/src/Artemis.UI.Avalonia/Screens/Debugger/Tabs/DataModel/DataModelDebugViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Debugger/Tabs/DataModel/DataModelDebugViewModel.cs @@ -1,4 +1,5 @@ -using ReactiveUI; +using Artemis.UI.Avalonia.Shared; +using ReactiveUI; namespace Artemis.UI.Avalonia.Screens.Debugger.Tabs.DataModel { diff --git a/src/Artemis.UI.Avalonia/Screens/Debugger/Tabs/Logs/LogsDebugViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Debugger/Tabs/Logs/LogsDebugViewModel.cs index bca2adce5..01eb8348f 100644 --- a/src/Artemis.UI.Avalonia/Screens/Debugger/Tabs/Logs/LogsDebugViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Debugger/Tabs/Logs/LogsDebugViewModel.cs @@ -1,4 +1,5 @@ -using ReactiveUI; +using Artemis.UI.Avalonia.Shared; +using ReactiveUI; namespace Artemis.UI.Avalonia.Screens.Debugger.Tabs.Logs { diff --git a/src/Artemis.UI.Avalonia/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs index 922bb501c..21ade068f 100644 --- a/src/Artemis.UI.Avalonia/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs @@ -1,4 +1,5 @@ -using ReactiveUI; +using Artemis.UI.Avalonia.Shared; +using ReactiveUI; namespace Artemis.UI.Avalonia.Screens.Debugger.Tabs.Performance { diff --git a/src/Artemis.UI.Avalonia/Screens/Debugger/Tabs/Render/RenderDebugViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Debugger/Tabs/Render/RenderDebugViewModel.cs index eda43abca..6f58feab8 100644 --- a/src/Artemis.UI.Avalonia/Screens/Debugger/Tabs/Render/RenderDebugViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Debugger/Tabs/Render/RenderDebugViewModel.cs @@ -3,6 +3,7 @@ using System.Reactive.Disposables; using System.Timers; using Artemis.Core; using Artemis.Core.Services; +using Artemis.UI.Avalonia.Shared; using ReactiveUI; using SkiaSharp; diff --git a/src/Artemis.UI.Avalonia/Screens/Device/DevicePropertiesViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Device/DevicePropertiesViewModel.cs index 9fe91baf9..1acc67272 100644 --- a/src/Artemis.UI.Avalonia/Screens/Device/DevicePropertiesViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Device/DevicePropertiesViewModel.cs @@ -1,12 +1,13 @@ using System.Collections.ObjectModel; using Artemis.Core; using Artemis.UI.Avalonia.Ninject.Factories; +using Artemis.UI.Avalonia.Shared; using RGB.NET.Core; using ArtemisLed = Artemis.Core.ArtemisLed; namespace Artemis.UI.Avalonia.Screens.Device { - public class DevicePropertiesViewModel : ActivatableViewModelBase + public class DevicePropertiesViewModel : DialogViewModelBase { public DevicePropertiesViewModel(ArtemisDevice device, IDeviceVmFactory deviceVmFactory) { diff --git a/src/Artemis.UI.Avalonia/Screens/Device/Tabs/ViewModels/DeviceInfoTabViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Device/Tabs/ViewModels/DeviceInfoTabViewModel.cs index 4c374130b..91d0d7d02 100644 --- a/src/Artemis.UI.Avalonia/Screens/Device/Tabs/ViewModels/DeviceInfoTabViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Device/Tabs/ViewModels/DeviceInfoTabViewModel.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Artemis.Core; +using Artemis.UI.Avalonia.Shared; using Avalonia; using RGB.NET.Core; diff --git a/src/Artemis.UI.Avalonia/Screens/Device/Tabs/ViewModels/DeviceLedsTabViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Device/Tabs/ViewModels/DeviceLedsTabViewModel.cs index ac27f82f8..212148ceb 100644 --- a/src/Artemis.UI.Avalonia/Screens/Device/Tabs/ViewModels/DeviceLedsTabViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Device/Tabs/ViewModels/DeviceLedsTabViewModel.cs @@ -4,6 +4,7 @@ using System.Collections.Specialized; using System.Linq; using System.Reactive.Disposables; using Artemis.Core; +using Artemis.UI.Avalonia.Shared; using DynamicData.Binding; using ReactiveUI; diff --git a/src/Artemis.UI.Avalonia/Screens/Device/Tabs/ViewModels/DevicePropertiesTabViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Device/Tabs/ViewModels/DevicePropertiesTabViewModel.cs index 3c04a654d..323b1c432 100644 --- a/src/Artemis.UI.Avalonia/Screens/Device/Tabs/ViewModels/DevicePropertiesTabViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Device/Tabs/ViewModels/DevicePropertiesTabViewModel.cs @@ -4,6 +4,7 @@ using System.ComponentModel; 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; diff --git a/src/Artemis.UI.Avalonia/Screens/Device/Tabs/ViewModels/InputMappingsTabViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Device/Tabs/ViewModels/InputMappingsTabViewModel.cs index 0556e4d49..095f04dae 100644 --- a/src/Artemis.UI.Avalonia/Screens/Device/Tabs/ViewModels/InputMappingsTabViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Device/Tabs/ViewModels/InputMappingsTabViewModel.cs @@ -5,6 +5,7 @@ using System.Linq; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Avalonia.Exceptions; +using Artemis.UI.Avalonia.Shared; using ReactiveUI; using RGB.NET.Core; diff --git a/src/Artemis.UI.Avalonia/Screens/MainScreenViewModel.cs b/src/Artemis.UI.Avalonia/Screens/MainScreenViewModel.cs index a4e5453e4..12502fb41 100644 --- a/src/Artemis.UI.Avalonia/Screens/MainScreenViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/MainScreenViewModel.cs @@ -1,4 +1,5 @@ -using ReactiveUI; +using Artemis.UI.Avalonia.Shared; +using ReactiveUI; namespace Artemis.UI.Avalonia.Screens { diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginFeatureViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginFeatureViewModel.cs index 0fe2c5f5d..eceac7b7e 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginFeatureViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginFeatureViewModel.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -70,7 +69,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels { try { - Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Path.Combine(Constants.DataFolder, "Logs")); + Utilities.OpenFolder(Path.Combine(Constants.DataFolder, "logs")); } catch (Exception e) { @@ -80,21 +79,21 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels public void ViewLoadException() { - if (LoadException != null) + if (LoadException != null) _windowService.ShowExceptionDialog("Feature failed to enable", LoadException); } public async Task InstallPrerequisites() { - if (FeatureInfo.Prerequisites.Any()) - await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, new List {FeatureInfo}); + if (FeatureInfo.Prerequisites.Any()) + await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, new List {FeatureInfo}); } public async Task RemovePrerequisites() { if (FeatureInfo.Prerequisites.Any(p => p.UninstallActions.Any())) { - await PluginPrerequisitesUninstallDialogViewModel.Show(_dialogService, new List {FeatureInfo}); + await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, new List {FeatureInfo}); this.RaisePropertyChanged(nameof(IsEnabled)); } } @@ -142,7 +141,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels { if (FeatureInfo.Plugin.Info.RequiresAdmin && !_coreService.IsElevated) { - bool confirmed = await _dialogService.ShowConfirmDialog("Enable feature", "The plugin of this feature requires admin rights, are you sure you want to enable it?"); + bool confirmed = await _windowService.ShowConfirmContentDialog("Enable feature", "The plugin of this feature requires admin rights, are you sure you want to enable it?"); if (!confirmed) { this.RaisePropertyChanged(nameof(IsEnabled)); @@ -153,7 +152,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels // Check if all prerequisites are met async if (!FeatureInfo.ArePrerequisitesMet()) { - await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, new List {FeatureInfo}); + await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, new List {FeatureInfo}); if (!FeatureInfo.ArePrerequisitesMet()) { this.RaisePropertyChanged(nameof(IsEnabled)); @@ -185,20 +184,14 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels private void OnFeatureEnabling(object? sender, PluginFeatureEventArgs e) { - if (e.PluginFeature != FeatureInfo.Instance) - { - return; - } + if (e.PluginFeature != FeatureInfo.Instance) return; Enabling = true; } private void OnFeatureEnableStopped(object? sender, PluginFeatureEventArgs e) { - if (e.PluginFeature != FeatureInfo.Instance) - { - return; - } + if (e.PluginFeature != FeatureInfo.Instance) return; Enabling = false; diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisiteActionViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisiteActionViewModel.cs index ce4850925..c6d02c726 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisiteActionViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisiteActionViewModel.cs @@ -5,8 +5,6 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels { public class PluginPrerequisiteActionViewModel : ViewModelBase { - - public PluginPrerequisiteActionViewModel(PluginPrerequisiteAction action) { Action = action; diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisiteViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisiteViewModel.cs index 48db1a579..7f46538d0 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisiteViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisiteViewModel.cs @@ -5,23 +5,22 @@ using System.Threading; using System.Threading.Tasks; using Artemis.Core; using Artemis.UI.Avalonia.Shared; -using DynamicData; using ReactiveUI; namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels { 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 readonly ObservableAsPropertyHelper _busy; + private readonly bool _uninstall; private PluginPrerequisiteActionViewModel? _activeAction; + private bool _installing; + private bool _isMet; + private bool _uninstalling; + public PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall) { _uninstall = uninstall; @@ -45,7 +44,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels public PluginPrerequisiteActionViewModel? ActiveAction { get => _activeAction; - set => this.RaiseAndSetIfChanged(ref _activeAction , value); + set => this.RaiseAndSetIfChanged(ref _activeAction, value); } public PluginPrerequisite PluginPrerequisite { get; } @@ -106,6 +105,14 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels } } + /// + protected override void Dispose(bool disposing) + { + if (disposing) PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged; + + base.Dispose(disposing); + } + private void PluginPrerequisiteOnPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(PluginPrerequisite.CurrentAction)) @@ -120,21 +127,5 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels ActiveAction = activeAction; } - - #region IDisposable - - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged; - } - - 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 908066f96..2e47658c4 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisitesInstallDialogViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisitesInstallDialogViewModel.cs @@ -14,7 +14,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels { public class PluginPrerequisitesInstallDialogViewModel : DialogViewModelBase { - private PluginPrerequisiteViewModel _activePrerequisite; + private PluginPrerequisiteViewModel? _activePrerequisite; private bool _canInstall; private bool _showFailed; private bool _showInstall = true; @@ -34,7 +34,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels public ObservableCollection Prerequisites { get; } - public PluginPrerequisiteViewModel ActivePrerequisite + public PluginPrerequisiteViewModel? ActivePrerequisite { get => _activePrerequisite; set => this.RaiseAndSetIfChanged(ref _activePrerequisite, value); diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisitesUninstallDialogViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisitesUninstallDialogViewModel.cs index d678d5df7..a14426227 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisitesUninstallDialogViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginPrerequisitesUninstallDialogViewModel.cs @@ -9,7 +9,6 @@ using Artemis.Core.Services; 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.Avalonia.Screens.Plugins.ViewModels @@ -19,9 +18,9 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels private readonly IPluginManagementService _pluginManagementService; private readonly List _subjects; private readonly IWindowService _windowService; + private PluginPrerequisiteViewModel? _activePrerequisite; private bool _canUninstall; private bool _isFinished; - private PluginPrerequisiteViewModel? _activePrerequisite; private CancellationTokenSource? _tokenSource; public PluginPrerequisitesUninstallDialogViewModel(List subjects, string cancelLabel, IPrerequisitesVmFactory prerequisitesVmFactory, IWindowService windowService, @@ -61,18 +60,6 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels set => this.RaiseAndSetIfChanged(ref _isFinished, value); } - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - _tokenSource?.Cancel(); - _tokenSource?.Dispose(); - } - - base.Dispose(disposing); - } - public async Task Uninstall() { CanUninstall = false; @@ -80,29 +67,18 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels // Disable all subjects that are plugins, this will disable their features too foreach (IPrerequisitesSubject prerequisitesSubject in _subjects) { - if (prerequisitesSubject is PluginInfo pluginInfo) - { - _pluginManagementService.DisablePlugin(pluginInfo.Plugin, true); - } + 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) - { - continue; - } + 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) - { - _pluginManagementService.DisablePluginFeature(featureInfo.Instance, true); - } + else if (featureInfo.Instance != null) _pluginManagementService.DisablePluginFeature(featureInfo.Instance, true); } _tokenSource = new CancellationTokenSource(); @@ -112,19 +88,13 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites) { pluginPrerequisiteViewModel.IsMet = pluginPrerequisiteViewModel.PluginPrerequisite.IsMet(); - if (!pluginPrerequisiteViewModel.IsMet) - { - continue; - } + 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 (pluginPrerequisiteViewModel != Prerequisites.Last()) await Task.Delay(1000); } if (Prerequisites.All(p => !p.IsMet)) @@ -163,5 +133,17 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels { return await windowService.ShowDialogAsync(("subjects", subjects), ("cancelLabel", cancelLabel)); } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + _tokenSource?.Cancel(); + _tokenSource?.Dispose(); + } + + base.Dispose(disposing); + } } } \ 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 b41b01d09..e7f388844 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginSettingsViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginSettingsViewModel.cs @@ -1,13 +1,15 @@ 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.Exceptions; +using Artemis.UI.Avalonia.Ninject.Factories; using Artemis.UI.Avalonia.Shared; +using Artemis.UI.Avalonia.Shared.Services.Interfaces; using Ninject; using ReactiveUI; @@ -16,8 +18,10 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels public class PluginSettingsViewModel : ActivatableViewModelBase { private readonly ICoreService _coreService; + private readonly INotificationService _notificationService; private readonly IPluginManagementService _pluginManagementService; private readonly ISettingsVmFactory _settingsVmFactory; + private readonly IWindowService _windowService; private bool _canInstallPrerequisites; private bool _canRemovePrerequisites; private bool _enabling; @@ -27,13 +31,24 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels public PluginSettingsViewModel(Plugin plugin, ISettingsVmFactory settingsVmFactory, ICoreService coreService, + IWindowService windowService, + INotificationService notificationService, IPluginManagementService pluginManagementService) { - Plugin = plugin; + _plugin = plugin; _settingsVmFactory = settingsVmFactory; _coreService = coreService; + _windowService = windowService; + _notificationService = notificationService; _pluginManagementService = pluginManagementService; + + PluginFeatures = new ObservableCollection(); + foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features) + PluginFeatures.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false)); + + _pluginManagementService.PluginDisabled += PluginManagementServiceOnPluginToggled; + _pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginToggled; } public ObservableCollection PluginFeatures { get; } @@ -49,7 +64,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels get => _enabling; set => this.RaiseAndSetIfChanged(ref _enabling, value); } - + public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name; public bool CanOpenSettings => IsEnabled && Plugin.ConfigurationDialog != null; @@ -83,18 +98,20 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels public void OpenSettings() { - PluginConfigurationDialog configurationViewModel = (PluginConfigurationDialog) Plugin.ConfigurationDialog; - if (configurationViewModel == null) + if (Plugin.ConfigurationDialog == null) return; try { - PluginConfigurationViewModel viewModel = (PluginConfigurationViewModel) Plugin.Kernel.Get(configurationViewModel.Type); - _windowManager.ShowWindow(new PluginSettingsWindowViewModel(viewModel)); + PluginConfigurationViewModel? viewModel = Plugin.Kernel!.Get(Plugin.ConfigurationDialog.Type) as PluginConfigurationViewModel; + if (viewModel == null) + throw new ArtemisUIException($"The type of a plugin configuration dialog must inherit {nameof(PluginConfigurationViewModel)}"); + + _windowService.ShowWindow(new PluginSettingsWindowViewModel(viewModel)); } catch (Exception e) { - _dialogService.ShowExceptionDialog("An exception occured while trying to show the plugin's settings window", e); + _windowService.ShowExceptionDialog("An exception occured while trying to show the plugin's settings window", e); throw; } } @@ -103,11 +120,11 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels { try { - Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Plugin.Directory.FullName); + Utilities.OpenFolder(Plugin.Directory.FullName); } catch (Exception e) { - _dialogService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e); + _windowService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e); } } @@ -116,16 +133,16 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels bool wasEnabled = IsEnabled; _pluginManagementService.UnloadPlugin(Plugin); - Items.Clear(); + PluginFeatures.Clear(); Plugin = _pluginManagementService.LoadPlugin(Plugin.Directory); foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features) - Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false)); + PluginFeatures.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false)); if (wasEnabled) await UpdateEnabled(true); - _messageService.ShowMessage("Reloaded plugin."); + _notificationService.CreateNotification().WithTitle("Reloaded plugin.").Show(); } public async Task InstallPrerequisites() @@ -134,7 +151,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled)); if (subjects.Any(s => s.Prerequisites.Any())) - await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, subjects); + await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects); } public async Task RemovePrerequisites(bool forPluginRemoval = false) @@ -144,15 +161,15 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels if (subjects.Any(s => s.Prerequisites.Any(p => p.UninstallActions.Any()))) { - await PluginPrerequisitesUninstallDialogViewModel.Show(_dialogService, subjects, forPluginRemoval ? "SKIP, REMOVE PLUGIN" : "CANCEL"); - NotifyOfPropertyChange(nameof(IsEnabled)); - NotifyOfPropertyChange(nameof(CanOpenSettings)); + await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, subjects, forPluginRemoval ? "Skip, remove plugin" : "Cancel"); + this.RaisePropertyChanged(nameof(IsEnabled)); + this.RaisePropertyChanged(nameof(CanOpenSettings)); } } public async Task RemoveSettings() { - bool confirmed = await _dialogService.ShowConfirmDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?"); + bool confirmed = await _windowService.ShowConfirmContentDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?"); if (!confirmed) return; @@ -166,12 +183,12 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels if (wasEnabled) await UpdateEnabled(true); - _messageService.ShowMessage("Cleared plugin settings."); + _notificationService.CreateNotification().WithTitle("Cleared plugin settings.").Show(); } public async Task Remove() { - bool confirmed = await _dialogService.ShowConfirmDialog("Remove plugin", "Are you sure you want to remove this plugin?"); + bool confirmed = await _windowService.ShowConfirmContentDialog("Remove plugin", "Are you sure you want to remove this plugin?"); if (!confirmed) return; @@ -184,45 +201,55 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels try { _pluginManagementService.RemovePlugin(Plugin, false); - ((PluginSettingsTabViewModel) Parent).GetPluginInstances(); } catch (Exception e) { - _dialogService.ShowExceptionDialog("Failed to remove plugin", e); + _windowService.ShowExceptionDialog("Failed to remove plugin", e); throw; } - _messageService.ShowMessage("Removed plugin."); + _notificationService.CreateNotification().WithTitle("Removed plugin.").Show(); } public void ShowLogsFolder() { try { - Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Path.Combine(Constants.DataFolder, "Logs")); + Utilities.OpenFolder(Path.Combine(Constants.DataFolder, "logs")); } catch (Exception e) { - _dialogService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e); + _windowService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e); } } public void OpenUri(Uri uri) { - Core.Utilities.OpenUrl(uri.ToString()); + Utilities.OpenUrl(uri.ToString()); } - private void PluginManagementServiceOnPluginToggled(object sender, PluginEventArgs e) + protected override void Dispose(bool disposing) { - NotifyOfPropertyChange(nameof(IsEnabled)); - NotifyOfPropertyChange(nameof(CanOpenSettings)); + if (disposing) + { + _pluginManagementService.PluginDisabled -= PluginManagementServiceOnPluginToggled; + _pluginManagementService.PluginEnabled -= PluginManagementServiceOnPluginToggled; + } + + base.Dispose(disposing); + } + + private void PluginManagementServiceOnPluginToggled(object? sender, PluginEventArgs e) + { + this.RaisePropertyChanged(nameof(IsEnabled)); + this.RaisePropertyChanged(nameof(CanOpenSettings)); } private async Task UpdateEnabled(bool enable) { if (IsEnabled == enable) { - NotifyOfPropertyChange(nameof(IsEnabled)); + this.RaisePropertyChanged(nameof(IsEnabled)); return; } @@ -232,7 +259,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels if (Plugin.Info.RequiresAdmin && !_coreService.IsElevated) { - bool confirmed = await _dialogService.ShowConfirmDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it?"); + bool confirmed = await _windowService.ShowConfirmContentDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it?"); if (!confirmed) { CancelEnable(); @@ -246,7 +273,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels if (subjects.Any(s => !s.ArePrerequisitesMet())) { - await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, subjects); + await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects); if (!subjects.All(s => s.ArePrerequisitesMet())) { CancelEnable(); @@ -262,7 +289,10 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels } catch (Exception e) { - _messageService.ShowMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder); + _notificationService.CreateNotification() + .WithMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}") + .HavingButton(b => b.WithText("View logs").WithAction(ShowLogsFolder)) + .Show(); } finally { @@ -271,17 +301,19 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels }); } else + { _pluginManagementService.DisablePlugin(Plugin, true); + } - NotifyOfPropertyChange(nameof(IsEnabled)); - NotifyOfPropertyChange(nameof(CanOpenSettings)); + this.RaisePropertyChanged(nameof(IsEnabled)); + this.RaisePropertyChanged(nameof(CanOpenSettings)); } private void CancelEnable() { Enabling = false; - NotifyOfPropertyChange(nameof(IsEnabled)); - NotifyOfPropertyChange(nameof(CanOpenSettings)); + this.RaisePropertyChanged(nameof(IsEnabled)); + this.RaisePropertyChanged(nameof(CanOpenSettings)); } private void CheckPrerequisites() @@ -291,26 +323,5 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels CanRemovePrerequisites = Plugin.Info.Prerequisites.Any(p => p.UninstallActions.Any()) || Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.Prerequisites.Any(p => p.UninstallActions.Any())); } - - #region Overrides of Screen - - protected override void OnInitialActivate() - { - foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features) - Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false)); - - _pluginManagementService.PluginDisabled += PluginManagementServiceOnPluginToggled; - _pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginToggled; - base.OnInitialActivate(); - } - - protected override void OnClose() - { - _pluginManagementService.PluginDisabled -= PluginManagementServiceOnPluginToggled; - _pluginManagementService.PluginEnabled -= PluginManagementServiceOnPluginToggled; - base.OnClose(); - } - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginSettingsWindowViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginSettingsWindowViewModel.cs index 51caca975..a2d08fde6 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginSettingsWindowViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/ViewModels/PluginSettingsWindowViewModel.cs @@ -1,32 +1,20 @@ using System; using Artemis.Core; +using Artemis.UI.Avalonia.Shared; namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels { - public class PluginSettingsWindowViewModel : Conductor + public class PluginSettingsWindowViewModel : ActivatableViewModelBase { - private readonly PluginConfigurationViewModel _configurationViewModel; - public PluginSettingsWindowViewModel(PluginConfigurationViewModel configurationViewModel) { - _configurationViewModel = configurationViewModel ?? throw new ArgumentNullException(nameof(configurationViewModel)); + ConfigurationViewModel = configurationViewModel ?? throw new ArgumentNullException(nameof(configurationViewModel)); Plugin = configurationViewModel.Plugin; + + DisplayName = $"{Plugin.Info.Name} | Settings"; } + public PluginConfigurationViewModel ConfigurationViewModel { get; } public Plugin Plugin { get; } - - protected override void OnInitialActivate() - { - ActiveItem = _configurationViewModel; - ActiveItem.Closed += ActiveItemOnClosed; - - base.OnInitialActivate(); - } - - private void ActiveItemOnClosed(object sender, CloseEventArgs e) - { - ActiveItem.Closed -= ActiveItemOnClosed; - RequestClose(); - } } } \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsWindowView.axaml b/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsWindowView.axaml index af2862d74..a8cc7d65a 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsWindowView.axaml +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsWindowView.axaml @@ -4,6 +4,6 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Avalonia.Screens.Plugins.Views.PluginSettingsWindowView" - Title="PluginSettingsWindowView"> - Welcome to Avalonia! + Title="{Binding DisplayName}"> + 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 15accec08..0b5b3e2a3 100644 --- a/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsWindowView.axaml.cs +++ b/src/Artemis.UI.Avalonia/Screens/Plugins/Views/PluginSettingsWindowView.axaml.cs @@ -1,10 +1,14 @@ +using System; +using Artemis.UI.Avalonia.Screens.Plugins.ViewModels; using Avalonia; -using Avalonia.Controls; +using Avalonia.Controls.Mixins; using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; +using ReactiveUI; namespace Artemis.UI.Avalonia.Screens.Plugins.Views { - public partial class PluginSettingsWindowView : Window + public class PluginSettingsWindowView : ReactiveWindow { public PluginSettingsWindowView() { @@ -12,6 +16,8 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.Views #if DEBUG this.AttachDevTools(); #endif + + this.WhenActivated(disposables => { ViewModel!.ConfigurationViewModel.Close.Subscribe(_ => Close()).DisposeWith(disposables); }); } private void InitializeComponent() @@ -19,4 +25,4 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.Views AvaloniaXamlLoader.Load(this); } } -} +} \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia/Screens/ProfileEditor/ViewModels/ProfileEditorViewModel.cs b/src/Artemis.UI.Avalonia/Screens/ProfileEditor/ViewModels/ProfileEditorViewModel.cs index 0e10cd877..3d5260310 100644 --- a/src/Artemis.UI.Avalonia/Screens/ProfileEditor/ViewModels/ProfileEditorViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/ProfileEditor/ViewModels/ProfileEditorViewModel.cs @@ -1,4 +1,6 @@ -namespace Artemis.UI.Avalonia.Screens.ProfileEditor.ViewModels +using Artemis.UI.Avalonia.Shared; + +namespace Artemis.UI.Avalonia.Screens.ProfileEditor.ViewModels { public class ProfileEditorViewModel : ActivatableViewModelBase { diff --git a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/RootViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/RootViewModel.cs index e3110a5d5..87577cf26 100644 --- a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/RootViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/RootViewModel.cs @@ -1,5 +1,6 @@ using Artemis.Core.Services; using Artemis.UI.Avalonia.Ninject.Factories; +using Artemis.UI.Avalonia.Shared; using ReactiveUI; namespace Artemis.UI.Avalonia.Screens.Root.ViewModels diff --git a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarCategoryViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarCategoryViewModel.cs index 9ec9fe247..2ac8aabe7 100644 --- a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarCategoryViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarCategoryViewModel.cs @@ -3,6 +3,7 @@ using System.Linq; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Avalonia.Ninject.Factories; +using Artemis.UI.Avalonia.Shared; using ReactiveUI; namespace Artemis.UI.Avalonia.Screens.Root.ViewModels diff --git a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarProfileConfigurationViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarProfileConfigurationViewModel.cs index bd5fee751..71194c5f2 100644 --- a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarProfileConfigurationViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarProfileConfigurationViewModel.cs @@ -1,5 +1,6 @@ using Artemis.Core; using Artemis.Core.Services; +using Artemis.UI.Avalonia.Shared; namespace Artemis.UI.Avalonia.Screens.Root.ViewModels { diff --git a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarScreenViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarScreenViewModel.cs index e8f997159..cc66c782d 100644 --- a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarScreenViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarScreenViewModel.cs @@ -1,4 +1,5 @@ using System; +using Artemis.UI.Avalonia.Shared; using Material.Icons; using Ninject; using Ninject.Parameters; diff --git a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarViewModel.cs index 04c2f481a..60a6e36bc 100644 --- a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarViewModel.cs @@ -9,6 +9,7 @@ using Artemis.UI.Avalonia.Screens.Home.ViewModels; using Artemis.UI.Avalonia.Screens.Settings; using Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels; using Artemis.UI.Avalonia.Screens.Workshop.ViewModels; +using Artemis.UI.Avalonia.Shared; using Material.Icons; using Ninject; using ReactiveUI; diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/SettingsViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Settings/SettingsViewModel.cs index d224356d8..387426f05 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/SettingsViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Settings/SettingsViewModel.cs @@ -1,5 +1,6 @@ using System.Collections.ObjectModel; using Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels; +using Artemis.UI.Avalonia.Shared; using ReactiveUI; namespace Artemis.UI.Avalonia.Screens.Settings diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/AboutTabViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/AboutTabViewModel.cs index c52f221e2..0f6d18e99 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/AboutTabViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/AboutTabViewModel.cs @@ -1,7 +1,9 @@ using System; +using System.Reactive.Disposables; using System.Reflection; using System.Threading.Tasks; using Artemis.Core; +using Artemis.UI.Avalonia.Shared; using Avalonia.Media.Imaging; using Flurl.Http; using ReactiveUI; @@ -19,7 +21,7 @@ namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels public AboutTabViewModel() { DisplayName = "About"; - this.WhenActivated((Action _) => Task.Run(Activate)); + this.WhenActivated((CompositeDisposable _) => Task.Run(Activate)); } public string? Version diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/DevicesTabViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/DevicesTabViewModel.cs index d101e00d0..3423b8c09 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/DevicesTabViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/DevicesTabViewModel.cs @@ -1,4 +1,6 @@ -namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels +using Artemis.UI.Avalonia.Shared; + +namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels { public class DevicesTabViewModel : ActivatableViewModelBase { diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/GeneralTabViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/GeneralTabViewModel.cs index a35fee005..d0fde3198 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/GeneralTabViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/GeneralTabViewModel.cs @@ -11,6 +11,7 @@ using Artemis.Core; using Artemis.Core.LayerBrushes; using Artemis.Core.Services; using Artemis.UI.Avalonia.Services.Interfaces; +using Artemis.UI.Avalonia.Shared; using ReactiveUI; using Serilog.Events; diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/PluginsTabViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/PluginsTabViewModel.cs index 9992aa2b5..df9af3656 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/PluginsTabViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/PluginsTabViewModel.cs @@ -1,10 +1,116 @@ -namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Threading.Tasks; +using Artemis.Core; +using Artemis.Core.Services; +using Artemis.UI.Avalonia.Extensions; +using Artemis.UI.Avalonia.Ninject.Factories; +using Artemis.UI.Avalonia.Screens.Plugins.ViewModels; +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.ViewModels { public class PluginsTabViewModel : ActivatableViewModelBase { - public PluginsTabViewModel() + private readonly IPluginManagementService _pluginManagementService; + private readonly INotificationService _notificationService; + private readonly IWindowService _windowService; + private readonly ISettingsVmFactory _settingsVmFactory; + private string? _searchPluginInput; + private List? _instances; + + public PluginsTabViewModel(IPluginManagementService pluginManagementService, INotificationService notificationService, IWindowService windowService, ISettingsVmFactory settingsVmFactory) { + _pluginManagementService = pluginManagementService; + _notificationService = notificationService; + _windowService = windowService; + _settingsVmFactory = settingsVmFactory; + DisplayName = "Plugins"; + Plugins = new ObservableCollection(); + + this.WhenAnyValue(x => x.SearchPluginInput).Throttle(TimeSpan.FromMilliseconds(300)).Subscribe(SearchPlugins); + this.WhenActivated((CompositeDisposable _) => GetPluginInstances()); + } + + public ObservableCollection Plugins { get; } + + public string? SearchPluginInput + { + get => _searchPluginInput; + set => this.RaiseAndSetIfChanged(ref _searchPluginInput, value); + } + + public void OpenUrl(string url) => Utilities.OpenUrl(url); + + public async Task ImportPlugin() + { + string[]? files = await _windowService.CreateOpenFileDialog().WithTitle("Import Artemis plugin").HavingFilter(f => f.WithExtension("zip").WithName("ZIP files")).ShowAsync(); + if (files == null) + return; + + // Take the actual import off of the UI thread + await Task.Run(() => + { + Plugin plugin = _pluginManagementService.ImportPlugin(files[0]); + + GetPluginInstances(); + SearchPluginInput = plugin.Info.Name; + + // Enable it via the VM to enable the prerequisite dialog + PluginSettingsViewModel pluginViewModel = Plugins.FirstOrDefault(i => i.Plugin == plugin); + if (pluginViewModel is { IsEnabled: false }) + pluginViewModel.IsEnabled = true; + + _notificationService.CreateNotification() + .WithTitle("Success") + .WithMessage($"Imported plugin: {plugin.Info.Name}") + .WithSeverity(NotificationSeverity.Success) + .Show(); + }); + } + + public void GetPluginInstances() + { + _instances = _pluginManagementService.GetAllPlugins() + .Select(p => _settingsVmFactory.CreatePluginSettingsViewModel(p)) + .OrderBy(i => i.Plugin.Info.Name) + .ToList(); + + SearchPlugins(SearchPluginInput); + } + + private void SearchPlugins(string? searchPluginInput) + { + if (_instances == null) + return; + + List instances = _instances; + string? search = searchPluginInput?.ToLower(); + if (!string.IsNullOrWhiteSpace(search)) + instances = instances.Where(i => i.Plugin.Info.Name.ToLower().Contains(search) || + i.Plugin.Info.Description != null && i.Plugin.Info.Description.ToLower().Contains(search)).ToList(); + + foreach (PluginSettingsViewModel pluginSettingsViewModel in instances) + { + if (!Plugins.Contains(pluginSettingsViewModel)) + Plugins.Add(pluginSettingsViewModel); + } + + foreach (PluginSettingsViewModel pluginSettingsViewModel in Plugins.ToList()) + { + if (!instances.Contains(pluginSettingsViewModel)) + Plugins.Remove(pluginSettingsViewModel); + } + + Plugins.Sort(i => i.Plugin.Info.Name); } } -} +} \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/PluginsTabView.axaml b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/PluginsTabView.axaml index 692705262..cba39cedb 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/PluginsTabView.axaml +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/PluginsTabView.axaml @@ -4,5 +4,5 @@ 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.Views.PluginsTabView"> - Welcome to Avalonia! + diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/PluginsTabView.axaml.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/PluginsTabView.axaml.cs index fa586dbf3..8396c9b3e 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/PluginsTabView.axaml.cs +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/PluginsTabView.axaml.cs @@ -1,9 +1,11 @@ +using Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Views { - public partial class PluginsTabView : UserControl + public partial class PluginsTabView : ReactiveUserControl { public PluginsTabView() { diff --git a/src/Artemis.UI.Avalonia/Screens/SurfaceEditor/ViewModels/ListDeviceViewModel.cs b/src/Artemis.UI.Avalonia/Screens/SurfaceEditor/ViewModels/ListDeviceViewModel.cs index 72666441d..e4bc25a09 100644 --- a/src/Artemis.UI.Avalonia/Screens/SurfaceEditor/ViewModels/ListDeviceViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/SurfaceEditor/ViewModels/ListDeviceViewModel.cs @@ -1,4 +1,5 @@ using Artemis.Core; +using Artemis.UI.Avalonia.Shared; using ReactiveUI; using SkiaSharp; diff --git a/src/Artemis.UI.Avalonia/Screens/SurfaceEditor/ViewModels/SurfaceDeviceViewModel.cs b/src/Artemis.UI.Avalonia/Screens/SurfaceEditor/ViewModels/SurfaceDeviceViewModel.cs index eb4e66260..572b53c37 100644 --- a/src/Artemis.UI.Avalonia/Screens/SurfaceEditor/ViewModels/SurfaceDeviceViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/SurfaceEditor/ViewModels/SurfaceDeviceViewModel.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Avalonia.Ninject.Factories; +using Artemis.UI.Avalonia.Shared; using Artemis.UI.Avalonia.Shared.Services.Interfaces; using Avalonia.Input; using ReactiveUI; @@ -105,7 +106,7 @@ namespace Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels private async Task ExecuteViewProperties(ArtemisDevice device) { - await _windowService.ShowDialogAsync(_deviceVmFactory.DevicePropertiesViewModel(device)); + await _windowService.ShowDialogAsync(_deviceVmFactory.DevicePropertiesViewModel(device)); } private bool Fits(float x, float y) diff --git a/src/Artemis.UI.Shared/Plugins/PluginConfigurationDialog.cs b/src/Artemis.UI.Shared/Plugins/PluginConfigurationDialog.cs index 23f1fd0ed..cbc4f7413 100644 --- a/src/Artemis.UI.Shared/Plugins/PluginConfigurationDialog.cs +++ b/src/Artemis.UI.Shared/Plugins/PluginConfigurationDialog.cs @@ -15,9 +15,7 @@ namespace Artemis.UI.Shared /// public abstract class PluginConfigurationDialog : IPluginConfigurationDialog { - /// - /// The type of view model the tab contains - /// + /// public abstract Type Type { get; } } } \ No newline at end of file