mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Startup wizard - Show device providers from all plugins
This commit is contained in:
parent
3022c7df65
commit
35d5ef1743
@ -26,6 +26,8 @@ public partial class StartupWizardViewModel : DialogViewModelBase<bool>
|
|||||||
private readonly ISettingsService _settingsService;
|
private readonly ISettingsService _settingsService;
|
||||||
private readonly IWindowService _windowService;
|
private readonly IWindowService _windowService;
|
||||||
private readonly IDeviceService _deviceService;
|
private readonly IDeviceService _deviceService;
|
||||||
|
private readonly Func<PluginFeatureInfo, WizardPluginFeatureViewModel> _getPluginFeatureViewModel;
|
||||||
|
|
||||||
[Notify] private int _currentStep;
|
[Notify] private int _currentStep;
|
||||||
[Notify] private bool _showContinue;
|
[Notify] private bool _showContinue;
|
||||||
[Notify] private bool _showFinish;
|
[Notify] private bool _showFinish;
|
||||||
@ -36,12 +38,13 @@ public partial class StartupWizardViewModel : DialogViewModelBase<bool>
|
|||||||
IPluginManagementService pluginManagementService,
|
IPluginManagementService pluginManagementService,
|
||||||
IWindowService windowService,
|
IWindowService windowService,
|
||||||
IDeviceService deviceService,
|
IDeviceService deviceService,
|
||||||
ISettingsVmFactory settingsVmFactory,
|
LayoutFinderViewModel layoutFinderViewModel,
|
||||||
LayoutFinderViewModel layoutFinderViewModel)
|
Func<PluginFeatureInfo, WizardPluginFeatureViewModel> getPluginFeatureViewModel)
|
||||||
{
|
{
|
||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
_deviceService = deviceService;
|
_deviceService = deviceService;
|
||||||
|
_getPluginFeatureViewModel = getPluginFeatureViewModel;
|
||||||
_autoRunProvider = container.Resolve<IAutoRunProvider>(IfUnresolved.ReturnDefault);
|
_autoRunProvider = container.Resolve<IAutoRunProvider>(IfUnresolved.ReturnDefault);
|
||||||
_protocolProvider = container.Resolve<IProtocolProvider>(IfUnresolved.ReturnDefault);
|
_protocolProvider = container.Resolve<IProtocolProvider>(IfUnresolved.ReturnDefault);
|
||||||
|
|
||||||
@ -51,11 +54,12 @@ public partial class StartupWizardViewModel : DialogViewModelBase<bool>
|
|||||||
SelectLayout = ReactiveCommand.Create<string>(ExecuteSelectLayout);
|
SelectLayout = ReactiveCommand.Create<string>(ExecuteSelectLayout);
|
||||||
Version = $"Version {Constants.CurrentVersion}";
|
Version = $"Version {Constants.CurrentVersion}";
|
||||||
|
|
||||||
// Take all compatible plugins that have an always-enabled device provider
|
// Take all compatible device providers and create a view model for them
|
||||||
DeviceProviders = new ObservableCollection<PluginViewModel>(pluginManagementService.GetAllPlugins()
|
DeviceProviders = new ObservableCollection<WizardPluginFeatureViewModel>(pluginManagementService.GetAllPlugins()
|
||||||
.Where(p => p.Info.IsCompatible && p.Features.Any(f => f.AlwaysEnabled && f.FeatureType.IsAssignableTo(typeof(DeviceProvider))))
|
.Where(p => p.Info.IsCompatible)
|
||||||
.OrderBy(p => p.Info.Name)
|
.SelectMany(p => p.Features.Where(f => f.FeatureType.IsAssignableTo(typeof(DeviceProvider))))
|
||||||
.Select(p => settingsVmFactory.PluginViewModel(p, ReactiveCommand.Create(() => new Unit()))));
|
.OrderBy(f => f.Name)
|
||||||
|
.Select(f => _getPluginFeatureViewModel(f)));
|
||||||
LayoutFinderViewModel = layoutFinderViewModel;
|
LayoutFinderViewModel = layoutFinderViewModel;
|
||||||
|
|
||||||
CurrentStep = 1;
|
CurrentStep = 1;
|
||||||
@ -84,7 +88,7 @@ public partial class StartupWizardViewModel : DialogViewModelBase<bool>
|
|||||||
public ReactiveCommand<string, Unit> SelectLayout { get; }
|
public ReactiveCommand<string, Unit> SelectLayout { get; }
|
||||||
|
|
||||||
public string Version { get; }
|
public string Version { get; }
|
||||||
public ObservableCollection<PluginViewModel> DeviceProviders { get; }
|
public ObservableCollection<WizardPluginFeatureViewModel> DeviceProviders { get; }
|
||||||
public LayoutFinderViewModel LayoutFinderViewModel { get; }
|
public LayoutFinderViewModel LayoutFinderViewModel { get; }
|
||||||
|
|
||||||
public bool IsAutoRunSupported => _autoRunProvider != null;
|
public bool IsAutoRunSupported => _autoRunProvider != null;
|
||||||
|
|||||||
@ -0,0 +1,79 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||||
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
|
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.StartupWizard.WizardPluginFeatureView"
|
||||||
|
x:DataType="startupWizard:WizardPluginFeatureViewModel">
|
||||||
|
<Grid RowDefinitions="*,Auto">
|
||||||
|
<Grid Grid.Row="0" RowDefinitions="Auto,Auto,*" ColumnDefinitions="80,*, Auto">
|
||||||
|
<shared:ArtemisIcon Icon="{CompiledBinding Plugin.Info.ResolvedIcon}"
|
||||||
|
Width="48"
|
||||||
|
Height="48"
|
||||||
|
Margin="0 5 0 0"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.RowSpan="3"
|
||||||
|
VerticalAlignment="Top" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Column="1" Grid.Row="0" Classes="no-margin">
|
||||||
|
<Run Classes="h5" Text="{CompiledBinding PluginFeature.Name}" />
|
||||||
|
<Run Classes="subtitle" Text="{CompiledBinding Plugin.Info.Name}" />
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<ItemsControl Grid.Column="2" Grid.Row="0" IsVisible="{CompiledBinding Platforms.Count}" ItemsSource="{CompiledBinding Platforms}" HorizontalAlignment="Right">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Spacing="5" Orientation="Horizontal" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Grid.Row="1"
|
||||||
|
Classes="subtitle"
|
||||||
|
Text="{CompiledBinding Plugin.Info.Author}"
|
||||||
|
IsVisible="{CompiledBinding Plugin.Info.Author, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Grid.Row="2"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Margin="0 5"
|
||||||
|
Text="{CompiledBinding Plugin.Info.Description}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1" ColumnDefinitions="*,Auto">
|
||||||
|
<Button Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
IsVisible="{CompiledBinding OpenSettings.CanExecute^}" Command="{CompiledBinding OpenSettings}">
|
||||||
|
Settings
|
||||||
|
</Button>
|
||||||
|
<CheckBox Name="EnabledToggle"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
IsVisible="{CompiledBinding !Enabling}"
|
||||||
|
IsChecked="{CompiledBinding IsEnabled, Mode=OneWay}"
|
||||||
|
IsEnabled="{CompiledBinding Plugin.Info.IsCompatible}">
|
||||||
|
<StackPanel x:Name="EnableText" Orientation="Horizontal">
|
||||||
|
<TextBlock>Enable feature</TextBlock>
|
||||||
|
<avalonia:MaterialIcon Kind="ShieldHalfFull"
|
||||||
|
Margin="5 0 0 0"
|
||||||
|
ToolTip.Tip="Plugin requires admin rights"
|
||||||
|
IsVisible="{CompiledBinding Plugin.Info.RequiresAdmin}" />
|
||||||
|
</StackPanel>
|
||||||
|
</CheckBox>
|
||||||
|
|
||||||
|
<ProgressBar Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
IsVisible="{CompiledBinding Enabling}"
|
||||||
|
IsIndeterminate="True" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.StartupWizard;
|
||||||
|
|
||||||
|
public partial class WizardPluginFeatureView : ReactiveUserControl<WizardPluginFeatureViewModel>
|
||||||
|
{
|
||||||
|
public WizardPluginFeatureView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
EnabledToggle.Click += EnabledToggleOnClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnabledToggleOnClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() => ViewModel?.UpdateEnabled(!ViewModel.IsEnabled));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,208 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Exceptions;
|
||||||
|
using Artemis.UI.Screens.Plugins;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Artemis.UI.Shared.Services.Builders;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using Material.Icons;
|
||||||
|
using PropertyChanged.SourceGenerator;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.StartupWizard;
|
||||||
|
|
||||||
|
public partial class WizardPluginFeatureViewModel : ActivatableViewModelBase
|
||||||
|
{
|
||||||
|
private readonly ICoreService _coreService;
|
||||||
|
private readonly IPluginManagementService _pluginManagementService;
|
||||||
|
private readonly IWindowService _windowService;
|
||||||
|
private Window? _settingsWindow;
|
||||||
|
[Notify] private bool _canInstallPrerequisites;
|
||||||
|
[Notify] private bool _canRemovePrerequisites;
|
||||||
|
[Notify] private bool _enabling;
|
||||||
|
|
||||||
|
public WizardPluginFeatureViewModel(PluginFeatureInfo pluginFeature, ICoreService coreService, IWindowService windowService, IPluginManagementService pluginManagementService)
|
||||||
|
{
|
||||||
|
PluginFeature = pluginFeature;
|
||||||
|
Plugin = pluginFeature.Plugin;
|
||||||
|
|
||||||
|
_coreService = coreService;
|
||||||
|
_windowService = windowService;
|
||||||
|
_pluginManagementService = pluginManagementService;
|
||||||
|
|
||||||
|
Platforms = new ObservableCollection<PluginPlatformViewModel>();
|
||||||
|
if (Plugin.Info.Platforms != null)
|
||||||
|
{
|
||||||
|
if (Plugin.Info.Platforms.Value.HasFlag(PluginPlatform.Windows))
|
||||||
|
Platforms.Add(new PluginPlatformViewModel("Windows", MaterialIconKind.MicrosoftWindows));
|
||||||
|
if (Plugin.Info.Platforms.Value.HasFlag(PluginPlatform.Linux))
|
||||||
|
Platforms.Add(new PluginPlatformViewModel("Linux", MaterialIconKind.Linux));
|
||||||
|
if (Plugin.Info.Platforms.Value.HasFlag(PluginPlatform.OSX))
|
||||||
|
Platforms.Add(new PluginPlatformViewModel("OSX", MaterialIconKind.Apple));
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenSettings = ReactiveCommand.Create(ExecuteOpenSettings, this.WhenAnyValue(vm => vm.IsEnabled, e => e && Plugin.ConfigurationDialog != null));
|
||||||
|
|
||||||
|
this.WhenActivated(d =>
|
||||||
|
{
|
||||||
|
pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureChanged;
|
||||||
|
pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureChanged;
|
||||||
|
|
||||||
|
Disposable.Create(() =>
|
||||||
|
{
|
||||||
|
pluginManagementService.PluginFeatureEnabled -= PluginManagementServiceOnPluginFeatureChanged;
|
||||||
|
pluginManagementService.PluginFeatureDisabled -= PluginManagementServiceOnPluginFeatureChanged;
|
||||||
|
_settingsWindow?.Close();
|
||||||
|
}).DisposeWith(d);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReactiveCommand<Unit, Unit> OpenSettings { get; }
|
||||||
|
|
||||||
|
public ObservableCollection<PluginPlatformViewModel> Platforms { get; }
|
||||||
|
|
||||||
|
public Plugin Plugin { get; }
|
||||||
|
public PluginFeatureInfo PluginFeature { get; }
|
||||||
|
public bool IsEnabled => PluginFeature.Instance != null && PluginFeature.Instance.IsEnabled;
|
||||||
|
|
||||||
|
public async Task UpdateEnabled(bool enable)
|
||||||
|
{
|
||||||
|
if (Enabling)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!enable)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (PluginFeature.AlwaysEnabled)
|
||||||
|
await Task.Run(() => _pluginManagementService.DisablePlugin(Plugin, true));
|
||||||
|
else if (PluginFeature.Instance != null)
|
||||||
|
await Task.Run(() => _pluginManagementService.DisablePluginFeature(PluginFeature.Instance, true));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
await ShowUpdateEnableFailure(enable, e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Enabling = true;
|
||||||
|
if (Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
|
||||||
|
{
|
||||||
|
bool confirmed = await _windowService.ShowConfirmContentDialog("Enable feature", "This feature requires admin rights, are you sure you want to enable it?");
|
||||||
|
if (!confirmed)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all prerequisites are met async
|
||||||
|
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
|
||||||
|
subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled || f.EnabledInStorage));
|
||||||
|
|
||||||
|
if (subjects.Any(s => !s.ArePrerequisitesMet()))
|
||||||
|
{
|
||||||
|
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects);
|
||||||
|
if (!subjects.All(s => s.ArePrerequisitesMet()))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
if (!Plugin.IsEnabled)
|
||||||
|
_pluginManagementService.EnablePlugin(Plugin, true, true);
|
||||||
|
if (PluginFeature.Instance != null && !PluginFeature.Instance.IsEnabled)
|
||||||
|
_pluginManagementService.EnablePluginFeature(PluginFeature.Instance, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
await ShowUpdateEnableFailure(enable, e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Enabling = false;
|
||||||
|
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteOpenSettings()
|
||||||
|
{
|
||||||
|
if (Plugin.ConfigurationDialog == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_settingsWindow != null)
|
||||||
|
{
|
||||||
|
_settingsWindow.WindowState = WindowState.Normal;
|
||||||
|
_settingsWindow.Activate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Plugin.Resolve(Plugin.ConfigurationDialog.Type) is not PluginConfigurationViewModel viewModel)
|
||||||
|
throw new ArtemisUIException($"The type of a plugin configuration dialog must inherit {nameof(PluginConfigurationViewModel)}");
|
||||||
|
|
||||||
|
_settingsWindow = _windowService.ShowWindow(new PluginSettingsWindowViewModel(viewModel));
|
||||||
|
_settingsWindow.Closed += (_, _) => _settingsWindow = null;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_windowService.ShowExceptionDialog("An exception occured while trying to show the plugin's settings window", e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ShowUpdateEnableFailure(bool enable, Exception e)
|
||||||
|
{
|
||||||
|
string action = enable ? "enable" : "disable";
|
||||||
|
ContentDialogBuilder builder = _windowService.CreateContentDialog()
|
||||||
|
.WithTitle($"Failed to {action} plugin {Plugin.Info.Name}")
|
||||||
|
.WithContent(e.Message)
|
||||||
|
.HavingPrimaryButton(b => b.WithText("View logs").WithAction(ShowLogsFolder));
|
||||||
|
// If available, add a secondary button pointing to the support page
|
||||||
|
if (Plugin.Info.HelpPage != null)
|
||||||
|
builder = builder.HavingSecondaryButton(b => b.WithText("Open support page").WithAction(() => Utilities.OpenUrl(Plugin.Info.HelpPage.ToString())));
|
||||||
|
|
||||||
|
await builder.ShowAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowLogsFolder()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Utilities.OpenFolder(Constants.LogsFolder);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_windowService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PluginManagementServiceOnPluginFeatureChanged(object? sender, PluginFeatureEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.PluginFeature.Info != PluginFeature)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||||
|
if (!IsEnabled)
|
||||||
|
_settingsWindow?.Close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user