From 93cfc0e00130eb7d0b7fe0c01bc6c44c7ffb7ee1 Mon Sep 17 00:00:00 2001 From: Robert Beekman Date: Sat, 6 Nov 2021 23:08:36 +0100 Subject: [PATCH] UI - Some VM restructure and added plugin VMs (WIP) --- .../Artemis.UI.Avalonia.Shared.csproj | 5 + ...emis.UI.Avalonia.Shared.csproj.DotSettings | 2 + .../Services/Builders/NotificationBuilder.cs | 40 +++ .../Services/Interfaces/IWindowService.cs | 20 +- .../WindowService/ExceptionDialogView.axaml | 9 + .../ExceptionDialogView.axaml.cs | 20 ++ .../WindowService/ExceptionDialogViewModel.cs | 12 + .../{ => WindowService}/WindowService.cs | 18 + .../Utilities/DelegateCommand.cs | 60 ++++ .../Artemis.UI.Avalonia.csproj | 18 + .../Root/ViewModels/SidebarViewModel.cs | 2 +- .../Settings/{Views => }/SettingsView.axaml | 2 +- .../{Views => }/SettingsView.axaml.cs | 3 +- .../{ViewModels => }/SettingsViewModel.cs | 3 +- .../ViewModels/PluginFeatureViewModel.cs | 213 ++++++++++++ .../ViewModels/PluginSettingsViewModel.cs | 325 ++++++++++++++++++ .../PluginSettingsWindowViewModel.cs | 33 ++ .../Plugins/ViewModels/PluginsTabViewModel.cs | 10 + .../Plugins/Views/PluginFeatureView.axaml | 8 + .../Plugins/Views/PluginFeatureView.axaml.cs | 19 + .../Tabs/Plugins/Views/PluginFeatureView.xaml | 106 ++++++ .../Plugins/Views/PluginSettingsView.axaml | 8 + .../Plugins/Views/PluginSettingsView.axaml.cs | 19 + .../Plugins/Views/PluginSettingsView.xaml | 209 +++++++++++ .../Views/PluginSettingsWindowView.axaml | 9 + .../Views/PluginSettingsWindowView.axaml.cs | 22 ++ .../Views/PluginSettingsWindowView.xaml | 43 +++ .../Plugins}/Views/PluginsTabView.axaml | 2 +- .../Plugins}/Views/PluginsTabView.axaml.cs | 3 +- .../ViewModels/AboutTabViewModel.cs | 2 +- .../Tabs/ViewModels/DevicesTabViewModel.cs | 10 + .../ViewModels/GeneralTabViewModel.cs | 2 +- .../{ => Tabs}/Views/AboutTabView.axaml | 2 +- .../{ => Tabs}/Views/AboutTabView.axaml.cs | 6 +- .../{ => Tabs}/Views/DevicesTabView.axaml | 2 +- .../{ => Tabs}/Views/DevicesTabView.axaml.cs | 3 +- .../{ => Tabs}/Views/GeneralTabView.axaml | 2 +- .../{ => Tabs}/Views/GeneralTabView.axaml.cs | 6 +- .../ViewModels/DevicesTabViewModel.cs | 16 - .../ViewModels/PluginsTabViewModel.cs | 16 - 40 files changed, 1249 insertions(+), 61 deletions(-) create mode 100644 src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.csproj.DotSettings create mode 100644 src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogView.axaml create mode 100644 src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogView.axaml.cs create mode 100644 src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogViewModel.cs rename src/Artemis.UI.Avalonia.Shared/Services/{ => WindowService}/WindowService.cs (86%) create mode 100644 src/Artemis.UI.Avalonia.Shared/Utilities/DelegateCommand.cs rename src/Artemis.UI.Avalonia/Screens/Settings/{Views => }/SettingsView.axaml (92%) rename src/Artemis.UI.Avalonia/Screens/Settings/{Views => }/SettingsView.axaml.cs (75%) rename src/Artemis.UI.Avalonia/Screens/Settings/{ViewModels => }/SettingsViewModel.cs (87%) create mode 100644 src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/ViewModels/PluginFeatureViewModel.cs create mode 100644 src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/ViewModels/PluginSettingsViewModel.cs create mode 100644 src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/ViewModels/PluginSettingsWindowViewModel.cs create mode 100644 src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/ViewModels/PluginsTabViewModel.cs create mode 100644 src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginFeatureView.axaml create mode 100644 src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginFeatureView.axaml.cs create mode 100644 src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginFeatureView.xaml create mode 100644 src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsView.axaml create mode 100644 src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsView.axaml.cs create mode 100644 src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsView.xaml create mode 100644 src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsWindowView.axaml create mode 100644 src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsWindowView.axaml.cs create mode 100644 src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsWindowView.xaml rename src/Artemis.UI.Avalonia/Screens/Settings/{ => Tabs/Plugins}/Views/PluginsTabView.axaml (81%) rename src/Artemis.UI.Avalonia/Screens/Settings/{ => Tabs/Plugins}/Views/PluginsTabView.axaml.cs (82%) rename src/Artemis.UI.Avalonia/Screens/Settings/{ => Tabs}/ViewModels/AboutTabViewModel.cs (97%) create mode 100644 src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/DevicesTabViewModel.cs rename src/Artemis.UI.Avalonia/Screens/Settings/{ => Tabs}/ViewModels/GeneralTabViewModel.cs (99%) rename src/Artemis.UI.Avalonia/Screens/Settings/{ => Tabs}/Views/AboutTabView.axaml (99%) rename src/Artemis.UI.Avalonia/Screens/Settings/{ => Tabs}/Views/AboutTabView.axaml.cs (69%) rename src/Artemis.UI.Avalonia/Screens/Settings/{ => Tabs}/Views/DevicesTabView.axaml (81%) rename src/Artemis.UI.Avalonia/Screens/Settings/{ => Tabs}/Views/DevicesTabView.axaml.cs (82%) rename src/Artemis.UI.Avalonia/Screens/Settings/{ => Tabs}/Views/GeneralTabView.axaml (99%) rename src/Artemis.UI.Avalonia/Screens/Settings/{ => Tabs}/Views/GeneralTabView.axaml.cs (70%) delete mode 100644 src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/DevicesTabViewModel.cs delete mode 100644 src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/PluginsTabViewModel.cs diff --git a/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.csproj b/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.csproj index 09574ca8c..4abc0d292 100644 --- a/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.csproj +++ b/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.csproj @@ -26,4 +26,9 @@ ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Core.dll + + + %(Filename) + + 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 new file mode 100644 index 000000000..1865d232b --- /dev/null +++ b/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ 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 ee07690e9..ed2b6c081 100644 --- a/src/Artemis.UI.Avalonia.Shared/Services/Builders/NotificationBuilder.cs +++ b/src/Artemis.UI.Avalonia.Shared/Services/Builders/NotificationBuilder.cs @@ -1,8 +1,10 @@ using System; using System.Threading.Tasks; +using Artemis.UI.Avalonia.Shared.Utilities; using Avalonia.Controls; using Avalonia.Threading; using FluentAvalonia.UI.Controls; +using Button = Avalonia.Controls.Button; namespace Artemis.UI.Avalonia.Shared.Services.Builders { @@ -36,6 +38,19 @@ namespace Artemis.UI.Avalonia.Shared.Services.Builders return this; } + + /// + /// Add a filter to the dialog + /// + public NotificationBuilder HavingButton(Action configure) + { + NotificationButtonBuilder builder = new(); + configure(builder); + _infoBar.ActionButton = builder.Build(); + + return this; + } + public NotificationBuilder WithSeverity(NotificationSeverity severity) { _infoBar.Severity = (InfoBarSeverity) severity; @@ -71,6 +86,31 @@ namespace Artemis.UI.Avalonia.Shared.Services.Builders } } + public class NotificationButtonBuilder + { + private string _text = "Text"; + private Action? _action; + + public NotificationButtonBuilder WithText(string text) + { + _text = text; + return this; + } + + public NotificationButtonBuilder WithAction(Action action) + { + _action = action; + return this; + } + + public IControl Build() + { + return _action != null + ? new Button {Content = _text, Command = new DelegateCommand(_ => _action())} + : new Button {Content = _text}; + } + } + public enum NotificationSeverity { Informational, diff --git a/src/Artemis.UI.Avalonia.Shared/Services/Interfaces/IWindowService.cs b/src/Artemis.UI.Avalonia.Shared/Services/Interfaces/IWindowService.cs index e965ee66e..f2f688a86 100644 --- a/src/Artemis.UI.Avalonia.Shared/Services/Interfaces/IWindowService.cs +++ b/src/Artemis.UI.Avalonia.Shared/Services/Interfaces/IWindowService.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Artemis.UI.Avalonia.Shared.Services.Builders; using Avalonia.Controls; @@ -7,20 +8,27 @@ namespace Artemis.UI.Avalonia.Shared.Services.Interfaces public interface IWindowService : IArtemisSharedUIService { /// - /// Creates a view model instance of type and shows its corresponding View as a window + /// Creates a view model instance of type and shows its corresponding View as a window /// /// The type of view model to create /// The created view model T ShowWindow(); /// - /// Given a ViewModel, show its corresponding View as a window + /// Given a ViewModel, show its corresponding View as a window /// /// ViewModel to show the View for void ShowWindow(object viewModel); /// - /// Given a ViewModel, show its corresponding View as a Dialog + /// Shows a dialog displaying the given exception + /// + /// The title of the dialog + /// The exception to display + void ShowExceptionDialog(string title, Exception exception); + + /// + /// Given a ViewModel, show its corresponding View as a Dialog /// /// The return type /// ViewModel to show the View for @@ -28,13 +36,13 @@ namespace Artemis.UI.Avalonia.Shared.Services.Interfaces Task ShowDialogAsync(object viewModel); /// - /// Creates an open file dialog, use the fluent API to configure it + /// Creates an open file dialog, use the fluent API to configure it /// /// The builder that can be used to configure the dialog OpenFileDialogBuilder CreateOpenFileDialog(); /// - /// Creates a save file dialog, use the fluent API to configure it + /// Creates a save file dialog, use the fluent API to configure it /// /// The builder that can be used to configure the dialog SaveFileDialogBuilder CreateSaveFileDialog(); diff --git a/src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogView.axaml b/src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogView.axaml new file mode 100644 index 000000000..0fc655a38 --- /dev/null +++ b/src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogView.axaml @@ -0,0 +1,9 @@ + + Eh you got an exception but I didn't write the viewer yet :( + diff --git a/src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogView.axaml.cs b/src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogView.axaml.cs new file mode 100644 index 000000000..8457638c4 --- /dev/null +++ b/src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogView.axaml.cs @@ -0,0 +1,20 @@ +using Avalonia.Markup.Xaml; + +namespace Artemis.UI.Avalonia.Shared.Services +{ + public partial class ExceptionDialogView : ReactiveWindow + { + public ExceptionDialogView() + { + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogViewModel.cs b/src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogViewModel.cs new file mode 100644 index 000000000..b2de500b2 --- /dev/null +++ b/src/Artemis.UI.Avalonia.Shared/Services/WindowService/ExceptionDialogViewModel.cs @@ -0,0 +1,12 @@ +using System; + +namespace Artemis.UI.Avalonia.Shared.Services +{ + public class ExceptionDialogViewModel : ActivatableViewModelBase + { + public ExceptionDialogViewModel(string title, Exception exception) + { + + } + } +} diff --git a/src/Artemis.UI.Avalonia.Shared/Services/WindowService.cs b/src/Artemis.UI.Avalonia.Shared/Services/WindowService/WindowService.cs similarity index 86% rename from src/Artemis.UI.Avalonia.Shared/Services/WindowService.cs rename to src/Artemis.UI.Avalonia.Shared/Services/WindowService/WindowService.cs index 9ea522b6e..3fb07710a 100644 --- a/src/Artemis.UI.Avalonia.Shared/Services/WindowService.cs +++ b/src/Artemis.UI.Avalonia.Shared/Services/WindowService/WindowService.cs @@ -14,6 +14,7 @@ namespace Artemis.UI.Avalonia.Shared.Services internal class WindowService : IWindowService { private readonly IKernel _kernel; + private bool _exceptionDialogOpen; public WindowService(IKernel kernel) { @@ -42,6 +43,23 @@ namespace Artemis.UI.Avalonia.Shared.Services window.Show(); } + /// + public void ShowExceptionDialog(string title, Exception exception) + { + if (_exceptionDialogOpen) + return; + + try + { + _exceptionDialogOpen = true; + ShowDialogAsync(new ExceptionDialogViewModel(title, exception)).GetAwaiter().GetResult(); + } + finally + { + _exceptionDialogOpen = false; + } + } + public async Task ShowDialogAsync(object viewModel) { if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime classic) diff --git a/src/Artemis.UI.Avalonia.Shared/Utilities/DelegateCommand.cs b/src/Artemis.UI.Avalonia.Shared/Utilities/DelegateCommand.cs new file mode 100644 index 000000000..03fff689d --- /dev/null +++ b/src/Artemis.UI.Avalonia.Shared/Utilities/DelegateCommand.cs @@ -0,0 +1,60 @@ +using System; +using System.Windows.Input; + +namespace Artemis.UI.Avalonia.Shared.Utilities +{ + /// + /// Provides a command that simply calls a delegate when invoked + /// + public class DelegateCommand : ICommand + { + private readonly Predicate? _canExecute; + private readonly Action _execute; + + /// + /// Creates a new instance of the class + /// + /// The delegate to execute + public DelegateCommand(Action execute) : this(execute, null) + { + } + + /// + /// Creates a new instance of the class with a predicate indicating whether the command + /// can be executed + /// + /// The delegate to execute + /// The predicate that determines whether the command can execute + public DelegateCommand(Action execute, Predicate? canExecute) + { + _execute = execute; + _canExecute = canExecute; + } + + /// + /// Invokes the event + /// + public void RaiseCanExecuteChanged() + { + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + + /// + public event EventHandler? CanExecuteChanged; + + /// + public bool CanExecute(object? parameter) + { + if (_canExecute == null) + return true; + + return _canExecute(parameter); + } + + /// + public void Execute(object? parameter) + { + _execute(parameter); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj b/src/Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj index 62c11bbd0..3f8a3f1ab 100644 --- a/src/Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj +++ b/src/Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj @@ -11,6 +11,9 @@ + + + @@ -59,6 +62,9 @@ %(Filename) + + %(Filename) + SidebarView.axaml Code @@ -77,6 +83,18 @@ + + $(DefaultXamlRuntime) + MSBuild:Compile + + + $(DefaultXamlRuntime) + MSBuild:Compile + + + $(DefaultXamlRuntime) + MSBuild:Compile + $(DefaultXamlRuntime) MSBuild:Compile diff --git a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarViewModel.cs index 7456da69a..04c2f481a 100644 --- a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarViewModel.cs @@ -6,7 +6,7 @@ using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Avalonia.Ninject.Factories; using Artemis.UI.Avalonia.Screens.Home.ViewModels; -using Artemis.UI.Avalonia.Screens.Settings.ViewModels; +using Artemis.UI.Avalonia.Screens.Settings; using Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels; using Artemis.UI.Avalonia.Screens.Workshop.ViewModels; using Material.Icons; diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Views/SettingsView.axaml b/src/Artemis.UI.Avalonia/Screens/Settings/SettingsView.axaml similarity index 92% rename from src/Artemis.UI.Avalonia/Screens/Settings/Views/SettingsView.axaml rename to src/Artemis.UI.Avalonia/Screens/Settings/SettingsView.axaml index 3aaee15ec..2ff5e265b 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/Views/SettingsView.axaml +++ b/src/Artemis.UI.Avalonia/Screens/Settings/SettingsView.axaml @@ -3,7 +3,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Avalonia.Screens.Settings.Views.SettingsView"> + x:Class="Artemis.UI.Avalonia.Screens.Settings.SettingsView"> diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Views/SettingsView.axaml.cs b/src/Artemis.UI.Avalonia/Screens/Settings/SettingsView.axaml.cs similarity index 75% rename from src/Artemis.UI.Avalonia/Screens/Settings/Views/SettingsView.axaml.cs rename to src/Artemis.UI.Avalonia/Screens/Settings/SettingsView.axaml.cs index a45456fe5..8d14b404c 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/Views/SettingsView.axaml.cs +++ b/src/Artemis.UI.Avalonia/Screens/Settings/SettingsView.axaml.cs @@ -1,8 +1,7 @@ -using Artemis.UI.Avalonia.Screens.Settings.ViewModels; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; -namespace Artemis.UI.Avalonia.Screens.Settings.Views +namespace Artemis.UI.Avalonia.Screens.Settings { public class SettingsView : ReactiveUserControl { diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/SettingsViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Settings/SettingsViewModel.cs similarity index 87% rename from src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/SettingsViewModel.cs rename to src/Artemis.UI.Avalonia/Screens/Settings/SettingsViewModel.cs index 68fc97f3f..d224356d8 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/SettingsViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Settings/SettingsViewModel.cs @@ -1,7 +1,8 @@ using System.Collections.ObjectModel; +using Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels; using ReactiveUI; -namespace Artemis.UI.Avalonia.Screens.Settings.ViewModels +namespace Artemis.UI.Avalonia.Screens.Settings { public class SettingsViewModel : MainScreenViewModel { diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/ViewModels/PluginFeatureViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/ViewModels/PluginFeatureViewModel.cs new file mode 100644 index 000000000..ba532aa07 --- /dev/null +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/ViewModels/PluginFeatureViewModel.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Artemis.Core; +using Artemis.Core.Services; +using Artemis.UI.Avalonia.Shared.Services.Builders; +using Artemis.UI.Avalonia.Shared.Services.Interfaces; +using ReactiveUI; + +namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.ViewModels +{ + public class PluginFeatureViewModel : ActivatableViewModelBase + { + private readonly ICoreService _coreService; + private readonly INotificationService _notificationService; + private readonly IPluginManagementService _pluginManagementService; + private readonly IWindowService _windowService; + private bool _enabling; + + public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, + bool showShield, + ICoreService coreService, + IWindowService windowService, + INotificationService notificationService, + IPluginManagementService pluginManagementService) + { + _coreService = coreService; + _windowService = windowService; + _notificationService = notificationService; + _pluginManagementService = pluginManagementService; + + FeatureInfo = pluginFeatureInfo; + ShowShield = FeatureInfo.Plugin.Info.RequiresAdmin && showShield; + + _pluginManagementService.PluginFeatureEnabling += OnFeatureEnabling; + _pluginManagementService.PluginFeatureEnabled += OnFeatureEnableStopped; + _pluginManagementService.PluginFeatureEnableFailed += OnFeatureEnableStopped; + + FeatureInfo.Plugin.Enabled += PluginOnToggled; + FeatureInfo.Plugin.Disabled += PluginOnToggled; + } + + public PluginFeatureInfo FeatureInfo { get; } + public Exception? LoadException => FeatureInfo.LoadException; + + public bool ShowShield { get; } + + public bool Enabling + { + get => _enabling; + set => this.RaiseAndSetIfChanged(ref _enabling, value); + } + + public bool IsEnabled + { + get => FeatureInfo.Instance != null && FeatureInfo.Instance.IsEnabled; + set => Task.Run(() => UpdateEnabled(value)); + } + + public bool CanToggleEnabled => FeatureInfo.Plugin.IsEnabled && !FeatureInfo.AlwaysEnabled; + public bool CanInstallPrerequisites => FeatureInfo.Prerequisites.Any(); + public bool CanRemovePrerequisites => FeatureInfo.Prerequisites.Any(p => p.UninstallActions.Any()); + public bool IsPopupEnabled => CanInstallPrerequisites || CanRemovePrerequisites; + + public void ShowLogsFolder() + { + try + { + Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Path.Combine(Constants.DataFolder, "Logs")); + } + catch (Exception e) + { + _windowService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e); + } + } + + public void ViewLoadException() + { + 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}); + } + + public async Task RemovePrerequisites() + { + if (FeatureInfo.Prerequisites.Any(p => p.UninstallActions.Any())) + { + await PluginPrerequisitesUninstallDialogViewModel.Show(_dialogService, new List {FeatureInfo}); + this.RaisePropertyChanged(nameof(IsEnabled)); + } + } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + _pluginManagementService.PluginFeatureEnabling -= OnFeatureEnabling; + _pluginManagementService.PluginFeatureEnabled -= OnFeatureEnableStopped; + _pluginManagementService.PluginFeatureEnableFailed -= OnFeatureEnableStopped; + + FeatureInfo.Plugin.Enabled -= PluginOnToggled; + FeatureInfo.Plugin.Disabled -= PluginOnToggled; + } + + base.Dispose(disposing); + } + + private async Task UpdateEnabled(bool enable) + { + if (IsEnabled == enable) + { + this.RaisePropertyChanged(nameof(IsEnabled)); + return; + } + + if (FeatureInfo.Instance == null) + { + this.RaisePropertyChanged(nameof(IsEnabled)); + _notificationService.CreateNotification() + .WithMessage($"Feature '{FeatureInfo.Name}' is in a broken state and cannot enable.") + .HavingButton(b => b.WithText("View logs").WithAction(ShowLogsFolder)) + .WithSeverity(NotificationSeverity.Error) + .Show(); + return; + } + + if (enable) + { + Enabling = true; + + try + { + 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?"); + if (!confirmed) + { + this.RaisePropertyChanged(nameof(IsEnabled)); + return; + } + } + + // Check if all prerequisites are met async + if (!FeatureInfo.ArePrerequisitesMet()) + { + await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, new List {FeatureInfo}); + if (!FeatureInfo.ArePrerequisitesMet()) + { + this.RaisePropertyChanged(nameof(IsEnabled)); + return; + } + } + + await Task.Run(() => _pluginManagementService.EnablePluginFeature(FeatureInfo.Instance!, true)); + } + catch (Exception e) + { + _notificationService.CreateNotification() + .WithMessage($"Failed to enable '{FeatureInfo.Name}'.\r\n{e.Message}") + .HavingButton(b => b.WithText("View logs").WithAction(ShowLogsFolder)) + .WithSeverity(NotificationSeverity.Error) + .Show(); + } + finally + { + Enabling = false; + } + } + else + { + _pluginManagementService.DisablePluginFeature(FeatureInfo.Instance, true); + this.RaisePropertyChanged(nameof(IsEnabled)); + } + } + + private void OnFeatureEnabling(object? sender, PluginFeatureEventArgs e) + { + if (e.PluginFeature != FeatureInfo.Instance) + { + return; + } + + Enabling = true; + } + + private void OnFeatureEnableStopped(object? sender, PluginFeatureEventArgs e) + { + if (e.PluginFeature != FeatureInfo.Instance) + { + return; + } + + Enabling = false; + + this.RaisePropertyChanged(nameof(IsEnabled)); + this.RaisePropertyChanged(nameof(LoadException)); + } + + private void PluginOnToggled(object? sender, EventArgs e) + { + this.RaisePropertyChanged(nameof(CanToggleEnabled)); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/ViewModels/PluginSettingsViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/ViewModels/PluginSettingsViewModel.cs new file mode 100644 index 000000000..6cb395ced --- /dev/null +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/ViewModels/PluginSettingsViewModel.cs @@ -0,0 +1,325 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Artemis.Core; +using Artemis.Core.Services; +using Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.ViewModels; +using Artemis.UI.Ninject.Factories; +using Artemis.UI.Screens.Plugins; +using Artemis.UI.Shared.Services; +using Ninject; +using Stylet; + +namespace Artemis.UI.Screens.Settings.Tabs.Plugins +{ + public class PluginSettingsViewModel : Conductor.Collection.AllActive + { + private readonly ICoreService _coreService; + private readonly IDialogService _dialogService; + private readonly IMessageService _messageService; + private readonly IPluginManagementService _pluginManagementService; + private readonly ISettingsVmFactory _settingsVmFactory; + private readonly IWindowManager _windowManager; + private bool _canInstallPrerequisites; + private bool _canRemovePrerequisites; + private bool _enabling; + private bool _isSettingsPopupOpen; + private Plugin _plugin; + + public PluginSettingsViewModel(Plugin plugin, + ISettingsVmFactory settingsVmFactory, + ICoreService coreService, + IWindowManager windowManager, + IDialogService dialogService, + IPluginManagementService pluginManagementService, + IMessageService messageService) + { + Plugin = plugin; + + _settingsVmFactory = settingsVmFactory; + _coreService = coreService; + _windowManager = windowManager; + _dialogService = dialogService; + _pluginManagementService = pluginManagementService; + _messageService = messageService; + } + + public Plugin Plugin + { + get => _plugin; + set => SetAndNotify(ref _plugin, value); + } + + public bool Enabling + { + get => _enabling; + set => SetAndNotify(ref _enabling, value); + } + + public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name; + public bool CanOpenSettings => IsEnabled && Plugin.ConfigurationDialog != null; + + public bool IsEnabled + { + get => Plugin.IsEnabled; + set => Task.Run(() => UpdateEnabled(value)); + } + + public bool IsSettingsPopupOpen + { + get => _isSettingsPopupOpen; + set + { + if (!SetAndNotify(ref _isSettingsPopupOpen, value)) return; + CheckPrerequisites(); + } + } + + public bool CanInstallPrerequisites + { + get => _canInstallPrerequisites; + set => SetAndNotify(ref _canInstallPrerequisites, value); + } + + public bool CanRemovePrerequisites + { + get => _canRemovePrerequisites; + set => SetAndNotify(ref _canRemovePrerequisites, value); + } + + public void OpenSettings() + { + PluginConfigurationDialog configurationViewModel = (PluginConfigurationDialog) Plugin.ConfigurationDialog; + if (configurationViewModel == null) + return; + + try + { + PluginConfigurationViewModel viewModel = (PluginConfigurationViewModel) Plugin.Kernel.Get(configurationViewModel.Type); + _windowManager.ShowWindow(new PluginSettingsWindowViewModel(viewModel)); + } + catch (Exception e) + { + _dialogService.ShowExceptionDialog("An exception occured while trying to show the plugin's settings window", e); + throw; + } + } + + public void OpenPluginDirectory() + { + try + { + Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Plugin.Directory.FullName); + } + catch (Exception e) + { + _dialogService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e); + } + } + + public async Task Reload() + { + bool wasEnabled = IsEnabled; + + _pluginManagementService.UnloadPlugin(Plugin); + Items.Clear(); + + Plugin = _pluginManagementService.LoadPlugin(Plugin.Directory); + foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features) + Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false)); + + if (wasEnabled) + await UpdateEnabled(true); + + _messageService.ShowMessage("Reloaded plugin."); + } + + public async Task InstallPrerequisites() + { + List subjects = new() {Plugin.Info}; + subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled)); + + if (subjects.Any(s => s.Prerequisites.Any())) + await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, subjects); + } + + public async Task RemovePrerequisites(bool forPluginRemoval = false) + { + List subjects = new() {Plugin.Info}; + subjects.AddRange(!forPluginRemoval ? Plugin.Features.Where(f => f.AlwaysEnabled) : Plugin.Features); + + 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)); + } + } + + public async Task RemoveSettings() + { + bool confirmed = await _dialogService.ShowConfirmDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?"); + if (!confirmed) + return; + + bool wasEnabled = IsEnabled; + + if (IsEnabled) + await UpdateEnabled(false); + + _pluginManagementService.RemovePluginSettings(Plugin); + + if (wasEnabled) + await UpdateEnabled(true); + + _messageService.ShowMessage("Cleared plugin settings."); + } + + public async Task Remove() + { + bool confirmed = await _dialogService.ShowConfirmDialog("Remove plugin", "Are you sure you want to remove this plugin?"); + if (!confirmed) + return; + + // If the plugin or any of its features has uninstall actions, offer to run these + List subjects = new() {Plugin.Info}; + subjects.AddRange(Plugin.Features); + if (subjects.Any(s => s.Prerequisites.Any(p => p.UninstallActions.Any()))) + await RemovePrerequisites(true); + + try + { + _pluginManagementService.RemovePlugin(Plugin, false); + ((PluginSettingsTabViewModel) Parent).GetPluginInstances(); + } + catch (Exception e) + { + _dialogService.ShowExceptionDialog("Failed to remove plugin", e); + throw; + } + + _messageService.ShowMessage("Removed plugin."); + } + + public void ShowLogsFolder() + { + try + { + Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Path.Combine(Constants.DataFolder, "Logs")); + } + catch (Exception e) + { + _dialogService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e); + } + } + + public void OpenUri(Uri uri) + { + Core.Utilities.OpenUrl(uri.ToString()); + } + + private void PluginManagementServiceOnPluginToggled(object sender, PluginEventArgs e) + { + NotifyOfPropertyChange(nameof(IsEnabled)); + NotifyOfPropertyChange(nameof(CanOpenSettings)); + } + + private async Task UpdateEnabled(bool enable) + { + if (IsEnabled == enable) + { + NotifyOfPropertyChange(nameof(IsEnabled)); + return; + } + + if (enable) + { + Enabling = true; + + 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?"); + if (!confirmed) + { + CancelEnable(); + return; + } + } + + // Check if all prerequisites are met async + List subjects = new() {Plugin.Info}; + subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled || f.EnabledInStorage)); + + if (subjects.Any(s => !s.ArePrerequisitesMet())) + { + await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, subjects); + if (!subjects.All(s => s.ArePrerequisitesMet())) + { + CancelEnable(); + return; + } + } + + await Task.Run(() => + { + try + { + _pluginManagementService.EnablePlugin(Plugin, true, true); + } + catch (Exception e) + { + _messageService.ShowMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder); + } + finally + { + Enabling = false; + } + }); + } + else + _pluginManagementService.DisablePlugin(Plugin, true); + + NotifyOfPropertyChange(nameof(IsEnabled)); + NotifyOfPropertyChange(nameof(CanOpenSettings)); + } + + private void CancelEnable() + { + Enabling = false; + NotifyOfPropertyChange(nameof(IsEnabled)); + NotifyOfPropertyChange(nameof(CanOpenSettings)); + } + + private void CheckPrerequisites() + { + CanInstallPrerequisites = Plugin.Info.Prerequisites.Any() || + Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.Prerequisites.Any()); + 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/Settings/Tabs/Plugins/ViewModels/PluginSettingsWindowViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/ViewModels/PluginSettingsWindowViewModel.cs new file mode 100644 index 000000000..2e4fe165a --- /dev/null +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/ViewModels/PluginSettingsWindowViewModel.cs @@ -0,0 +1,33 @@ +using System; +using Artemis.Core; +using Stylet; + +namespace Artemis.UI.Screens.Settings.Tabs.Plugins +{ + public class PluginSettingsWindowViewModel : Conductor + { + private readonly PluginConfigurationViewModel _configurationViewModel; + + public PluginSettingsWindowViewModel(PluginConfigurationViewModel configurationViewModel) + { + _configurationViewModel = configurationViewModel ?? throw new ArgumentNullException(nameof(configurationViewModel)); + Plugin = configurationViewModel.Plugin; + } + + 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/Settings/Tabs/Plugins/ViewModels/PluginsTabViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/ViewModels/PluginsTabViewModel.cs new file mode 100644 index 000000000..9992aa2b5 --- /dev/null +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/ViewModels/PluginsTabViewModel.cs @@ -0,0 +1,10 @@ +namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels +{ + public class PluginsTabViewModel : ActivatableViewModelBase + { + public PluginsTabViewModel() + { + DisplayName = "Plugins"; + } + } +} diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginFeatureView.axaml b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginFeatureView.axaml new file mode 100644 index 000000000..a17b8fcf7 --- /dev/null +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginFeatureView.axaml @@ -0,0 +1,8 @@ + + Welcome to Avalonia! + diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginFeatureView.axaml.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginFeatureView.axaml.cs new file mode 100644 index 000000000..c5f4b468f --- /dev/null +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginFeatureView.axaml.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.Views +{ + public partial class PluginFeatureView : UserControl + { + public PluginFeatureView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginFeatureView.xaml b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginFeatureView.xaml new file mode 100644 index 000000000..38998a889 --- /dev/null +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginFeatureView.xaml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Feature enabled + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsView.axaml b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsView.axaml new file mode 100644 index 000000000..bbdf3f186 --- /dev/null +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsView.axaml @@ -0,0 +1,8 @@ + + Welcome to Avalonia! + diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsView.axaml.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsView.axaml.cs new file mode 100644 index 000000000..0afe92900 --- /dev/null +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsView.axaml.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.Views +{ + public partial class PluginSettingsView : UserControl + { + public PluginSettingsView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsView.xaml b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsView.xaml new file mode 100644 index 000000000..d20d17e5a --- /dev/null +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsView.xaml @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Plugin enabled + + + + + + + + + + + + + + Plugin features + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsWindowView.axaml b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsWindowView.axaml new file mode 100644 index 000000000..e63012f83 --- /dev/null +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsWindowView.axaml @@ -0,0 +1,9 @@ + + Welcome to Avalonia! + diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsWindowView.axaml.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsWindowView.axaml.cs new file mode 100644 index 000000000..fb3b2fc15 --- /dev/null +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsWindowView.axaml.cs @@ -0,0 +1,22 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.Views +{ + public partial class PluginSettingsWindowView : Window + { + public PluginSettingsWindowView() + { + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsWindowView.xaml b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsWindowView.xaml new file mode 100644 index 000000000..1500833d6 --- /dev/null +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginSettingsWindowView.xaml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Views/PluginsTabView.axaml b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginsTabView.axaml similarity index 81% rename from src/Artemis.UI.Avalonia/Screens/Settings/Views/PluginsTabView.axaml rename to src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginsTabView.axaml index 31dce537c..692705262 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/Views/PluginsTabView.axaml +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginsTabView.axaml @@ -3,6 +3,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Avalonia.Screens.Settings.Views.PluginsTabView"> + x:Class="Artemis.UI.Avalonia.Screens.Settings.Tabs.Views.PluginsTabView"> Welcome to Avalonia! diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Views/PluginsTabView.axaml.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginsTabView.axaml.cs similarity index 82% rename from src/Artemis.UI.Avalonia/Screens/Settings/Views/PluginsTabView.axaml.cs rename to src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginsTabView.axaml.cs index 93874fbf9..fa586dbf3 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/Views/PluginsTabView.axaml.cs +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Plugins/Views/PluginsTabView.axaml.cs @@ -1,8 +1,7 @@ -using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; -namespace Artemis.UI.Avalonia.Screens.Settings.Views +namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Views { public partial class PluginsTabView : UserControl { diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/AboutTabViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/AboutTabViewModel.cs similarity index 97% rename from src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/AboutTabViewModel.cs rename to src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/AboutTabViewModel.cs index 67c817382..c52f221e2 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/AboutTabViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/AboutTabViewModel.cs @@ -6,7 +6,7 @@ using Avalonia.Media.Imaging; using Flurl.Http; using ReactiveUI; -namespace Artemis.UI.Avalonia.Screens.Settings.ViewModels +namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels { public class AboutTabViewModel : ActivatableViewModelBase { diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/DevicesTabViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/DevicesTabViewModel.cs new file mode 100644 index 000000000..d101e00d0 --- /dev/null +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/DevicesTabViewModel.cs @@ -0,0 +1,10 @@ +namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels +{ + public class DevicesTabViewModel : ActivatableViewModelBase + { + public DevicesTabViewModel() + { + DisplayName = "Devices"; + } + } +} diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/GeneralTabViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/GeneralTabViewModel.cs similarity index 99% rename from src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/GeneralTabViewModel.cs rename to src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/GeneralTabViewModel.cs index 15d481b4f..a35fee005 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/GeneralTabViewModel.cs +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/ViewModels/GeneralTabViewModel.cs @@ -14,7 +14,7 @@ using Artemis.UI.Avalonia.Services.Interfaces; using ReactiveUI; using Serilog.Events; -namespace Artemis.UI.Avalonia.Screens.Settings.ViewModels +namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels { public class GeneralTabViewModel : ActivatableViewModelBase { diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Views/AboutTabView.axaml b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/AboutTabView.axaml similarity index 99% rename from src/Artemis.UI.Avalonia/Screens/Settings/Views/AboutTabView.axaml rename to src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/AboutTabView.axaml index e618183ba..f6b192a46 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/Views/AboutTabView.axaml +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/AboutTabView.axaml @@ -6,7 +6,7 @@ xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="1400" - x:Class="Artemis.UI.Avalonia.Screens.Settings.Views.AboutTabView"> + x:Class="Artemis.UI.Avalonia.Screens.Settings.Tabs.Views.AboutTabView"> diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Views/AboutTabView.axaml.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/AboutTabView.axaml.cs similarity index 69% rename from src/Artemis.UI.Avalonia/Screens/Settings/Views/AboutTabView.axaml.cs rename to src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/AboutTabView.axaml.cs index 89fa25025..c8d742fa5 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/Views/AboutTabView.axaml.cs +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/AboutTabView.axaml.cs @@ -1,10 +1,8 @@ -using Artemis.UI.Avalonia.Screens.Settings.ViewModels; -using Avalonia; -using Avalonia.Controls; +using Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; -namespace Artemis.UI.Avalonia.Screens.Settings.Views +namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Views { public partial class AboutTabView : ReactiveUserControl { diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Views/DevicesTabView.axaml b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/DevicesTabView.axaml similarity index 81% rename from src/Artemis.UI.Avalonia/Screens/Settings/Views/DevicesTabView.axaml rename to src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/DevicesTabView.axaml index 42e20b0cd..22fdc9002 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/Views/DevicesTabView.axaml +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/DevicesTabView.axaml @@ -3,6 +3,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Avalonia.Screens.Settings.Views.DevicesTabView"> + x:Class="Artemis.UI.Avalonia.Screens.Settings.Tabs.Views.DevicesTabView"> Welcome to Avalonia! diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Views/DevicesTabView.axaml.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/DevicesTabView.axaml.cs similarity index 82% rename from src/Artemis.UI.Avalonia/Screens/Settings/Views/DevicesTabView.axaml.cs rename to src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/DevicesTabView.axaml.cs index 66c566a98..9efeb4f18 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/Views/DevicesTabView.axaml.cs +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/DevicesTabView.axaml.cs @@ -1,8 +1,7 @@ -using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; -namespace Artemis.UI.Avalonia.Screens.Settings.Views +namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Views { public partial class DevicesTabView : UserControl { diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Views/GeneralTabView.axaml b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/GeneralTabView.axaml similarity index 99% rename from src/Artemis.UI.Avalonia/Screens/Settings/Views/GeneralTabView.axaml rename to src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/GeneralTabView.axaml index 75e81d393..ac675f20e 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/Views/GeneralTabView.axaml +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/GeneralTabView.axaml @@ -7,7 +7,7 @@ xmlns:layerBrushes="clr-namespace:Artemis.Core.LayerBrushes;assembly=Artemis.Core" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="2400" - x:Class="Artemis.UI.Avalonia.Screens.Settings.Views.GeneralTabView"> + x:Class="Artemis.UI.Avalonia.Screens.Settings.Tabs.Views.GeneralTabView"> diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/Views/GeneralTabView.axaml.cs b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/GeneralTabView.axaml.cs similarity index 70% rename from src/Artemis.UI.Avalonia/Screens/Settings/Views/GeneralTabView.axaml.cs rename to src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/GeneralTabView.axaml.cs index 9f2c0d9a0..9cbf802a6 100644 --- a/src/Artemis.UI.Avalonia/Screens/Settings/Views/GeneralTabView.axaml.cs +++ b/src/Artemis.UI.Avalonia/Screens/Settings/Tabs/Views/GeneralTabView.axaml.cs @@ -1,10 +1,8 @@ -using Artemis.UI.Avalonia.Screens.Settings.ViewModels; -using Avalonia; -using Avalonia.Controls; +using Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; -namespace Artemis.UI.Avalonia.Screens.Settings.Views +namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Views { public partial class GeneralTabView : ReactiveUserControl { diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/DevicesTabViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/DevicesTabViewModel.cs deleted file mode 100644 index b975103bc..000000000 --- a/src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/DevicesTabViewModel.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Artemis.UI.Avalonia.Screens.Settings.ViewModels -{ - public class DevicesTabViewModel : ActivatableViewModelBase - { - public DevicesTabViewModel() - { - DisplayName = "Devices"; - } - } -} diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/PluginsTabViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/PluginsTabViewModel.cs deleted file mode 100644 index 3caf01598..000000000 --- a/src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/PluginsTabViewModel.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Artemis.UI.Avalonia.Screens.Settings.ViewModels -{ - public class PluginsTabViewModel : ActivatableViewModelBase - { - public PluginsTabViewModel() - { - DisplayName = "Plugins"; - } - } -}