1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 13:28:33 +00:00

Startup wizard - Show device providers from all plugins

This commit is contained in:
Robert 2024-05-25 16:45:59 +02:00
parent 3022c7df65
commit 35d5ef1743
4 changed files with 321 additions and 8 deletions

View File

@ -26,6 +26,8 @@ public partial class StartupWizardViewModel : DialogViewModelBase<bool>
private readonly ISettingsService _settingsService;
private readonly IWindowService _windowService;
private readonly IDeviceService _deviceService;
private readonly Func<PluginFeatureInfo, WizardPluginFeatureViewModel> _getPluginFeatureViewModel;
[Notify] private int _currentStep;
[Notify] private bool _showContinue;
[Notify] private bool _showFinish;
@ -36,12 +38,13 @@ public partial class StartupWizardViewModel : DialogViewModelBase<bool>
IPluginManagementService pluginManagementService,
IWindowService windowService,
IDeviceService deviceService,
ISettingsVmFactory settingsVmFactory,
LayoutFinderViewModel layoutFinderViewModel)
LayoutFinderViewModel layoutFinderViewModel,
Func<PluginFeatureInfo, WizardPluginFeatureViewModel> getPluginFeatureViewModel)
{
_settingsService = settingsService;
_windowService = windowService;
_deviceService = deviceService;
_getPluginFeatureViewModel = getPluginFeatureViewModel;
_autoRunProvider = container.Resolve<IAutoRunProvider>(IfUnresolved.ReturnDefault);
_protocolProvider = container.Resolve<IProtocolProvider>(IfUnresolved.ReturnDefault);
@ -51,11 +54,12 @@ public partial class StartupWizardViewModel : DialogViewModelBase<bool>
SelectLayout = ReactiveCommand.Create<string>(ExecuteSelectLayout);
Version = $"Version {Constants.CurrentVersion}";
// Take all compatible plugins that have an always-enabled device provider
DeviceProviders = new ObservableCollection<PluginViewModel>(pluginManagementService.GetAllPlugins()
.Where(p => p.Info.IsCompatible && p.Features.Any(f => f.AlwaysEnabled && f.FeatureType.IsAssignableTo(typeof(DeviceProvider))))
.OrderBy(p => p.Info.Name)
.Select(p => settingsVmFactory.PluginViewModel(p, ReactiveCommand.Create(() => new Unit()))));
// Take all compatible device providers and create a view model for them
DeviceProviders = new ObservableCollection<WizardPluginFeatureViewModel>(pluginManagementService.GetAllPlugins()
.Where(p => p.Info.IsCompatible)
.SelectMany(p => p.Features.Where(f => f.FeatureType.IsAssignableTo(typeof(DeviceProvider))))
.OrderBy(f => f.Name)
.Select(f => _getPluginFeatureViewModel(f)));
LayoutFinderViewModel = layoutFinderViewModel;
CurrentStep = 1;
@ -84,7 +88,7 @@ public partial class StartupWizardViewModel : DialogViewModelBase<bool>
public ReactiveCommand<string, Unit> SelectLayout { get; }
public string Version { get; }
public ObservableCollection<PluginViewModel> DeviceProviders { get; }
public ObservableCollection<WizardPluginFeatureViewModel> DeviceProviders { get; }
public LayoutFinderViewModel LayoutFinderViewModel { get; }
public bool IsAutoRunSupported => _autoRunProvider != null;

View File

@ -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>

View File

@ -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));
}
}

View File

@ -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();
});
}
}