mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Enhances setup wizard workflow
Refactors the setup wizard to streamline the initial user experience. Includes the following changes: - Adds nyan cat animation during feature installations for user enjoyment - Skips essential feature selection if workshop is unreachable - Moves directly to settings after device/surface selection - Adds option to configure settings, after device configuration
This commit is contained in:
parent
6988255dce
commit
13c497f41e
@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ using Avalonia.Controls.ApplicationLifetimes;
|
|||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using DryIoc;
|
using DryIoc;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Services;
|
namespace Artemis.UI.Shared.Services;
|
||||||
|
|
||||||
@ -94,6 +95,7 @@ internal class WindowService : IWindowService
|
|||||||
.WithTitle(title)
|
.WithTitle(title)
|
||||||
.WithContent(message)
|
.WithContent(message)
|
||||||
.HavingPrimaryButton(b => b.WithText(confirm))
|
.HavingPrimaryButton(b => b.WithText(confirm))
|
||||||
|
.WithDefaultButton(ContentDialogButton.Primary)
|
||||||
.WithCloseButtonText(cancel)
|
.WithCloseButtonText(cancel)
|
||||||
.ShowAsync();
|
.ShowAsync();
|
||||||
|
|
||||||
|
|||||||
@ -34,10 +34,7 @@ internal class Program
|
|||||||
// Avalonia configuration, don't remove; also used by visual designer.
|
// Avalonia configuration, don't remove; also used by visual designer.
|
||||||
public static AppBuilder BuildAvaloniaApp()
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
{
|
{
|
||||||
return AppBuilder.Configure<App>()
|
return AppBuilder.Configure<App>().UsePlatformDetect().LogToTrace().UseReactiveUI();
|
||||||
.UsePlatformDetect()
|
|
||||||
.LogToTrace()
|
|
||||||
.UseReactiveUI();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CreateLogger(IContainer container)
|
public static void CreateLogger(IContainer container)
|
||||||
|
|||||||
1
src/Artemis.UI/Assets/Animations/nyan.json
Normal file
1
src/Artemis.UI/Assets/Animations/nyan.json
Normal file
File diff suppressed because one or more lines are too long
@ -54,8 +54,8 @@
|
|||||||
<DockPanel Grid.Row="2" LastChildFill="False" IsVisible="{CompiledBinding !Screen.HideAllButtons}" HorizontalSpacing="10">
|
<DockPanel Grid.Row="2" LastChildFill="False" IsVisible="{CompiledBinding !Screen.HideAllButtons}" HorizontalSpacing="10">
|
||||||
<Button Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding !Screen.ShowFinish}" DockPanel.Dock="Left">Skip & close</Button>
|
<Button Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding !Screen.ShowFinish}" DockPanel.Dock="Left">Skip & close</Button>
|
||||||
<Button Command="{CompiledBinding Screen.Continue}" IsVisible="{CompiledBinding !Screen.ShowFinish}" Width="80" DockPanel.Dock="Right">Continue</Button>
|
<Button Command="{CompiledBinding Screen.Continue}" IsVisible="{CompiledBinding !Screen.ShowFinish}" Width="80" DockPanel.Dock="Right">Continue</Button>
|
||||||
<Button Command="{CompiledBinding Screen.GoBack}" IsEnabled="{CompiledBinding Screen.ShowGoBack}" DockPanel.Dock="Right">Back</Button>
|
|
||||||
<Button Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding Screen.ShowFinish}" Width="80" DockPanel.Dock="Right">Finish</Button>
|
<Button Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding Screen.ShowFinish}" Width="80" DockPanel.Dock="Right">Finish</Button>
|
||||||
|
<Button Command="{CompiledBinding Screen.GoBack}" IsEnabled="{CompiledBinding Screen.ShowGoBack}" DockPanel.Dock="Right">Back</Button>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|||||||
@ -9,10 +9,10 @@
|
|||||||
<Panel>
|
<Panel>
|
||||||
<!-- Selection stage -->
|
<!-- Selection stage -->
|
||||||
<Grid RowDefinitions="Auto,*,Auto" IsVisible="{CompiledBinding CurrentEntry, Converter={x:Static ObjectConverters.IsNull}}">
|
<Grid RowDefinitions="Auto,*,Auto" IsVisible="{CompiledBinding CurrentEntry, Converter={x:Static ObjectConverters.IsNull}}">
|
||||||
<TextBlock Grid.Row="0" TextWrapping="Wrap">
|
<TextBlock Grid.Row="0" TextWrapping="Wrap" IsVisible="{CompiledBinding !FetchingDefaultEntries}">
|
||||||
Below is a list of default features that can be installed to get you started with Artemis. You can always install or uninstall features later via the Workshop.
|
Below is a list of default features that can be installed to get you started with Artemis. You can always install or uninstall features later via the Workshop.
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
|
|
||||||
<StackPanel Grid.Row="1" IsVisible="{CompiledBinding FetchingDefaultEntries}" HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="10">
|
<StackPanel Grid.Row="1" IsVisible="{CompiledBinding FetchingDefaultEntries}" HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="10">
|
||||||
<ProgressBar IsIndeterminate="True" Width="400" />
|
<ProgressBar IsIndeterminate="True" Width="400" />
|
||||||
<TextBlock TextAlignment="Center">Fetching default features from the Artemis workshop...</TextBlock>
|
<TextBlock TextAlignment="Center">Fetching default features from the Artemis workshop...</TextBlock>
|
||||||
@ -29,7 +29,7 @@
|
|||||||
</ItemsPanelTemplate>
|
</ItemsPanelTemplate>
|
||||||
</ItemsControl.ItemsPanel>
|
</ItemsControl.ItemsPanel>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
|
|
||||||
<TextBlock Classes="card-title" IsVisible="{CompiledBinding EssentialEntryViewModels.Count}">
|
<TextBlock Classes="card-title" IsVisible="{CompiledBinding EssentialEntryViewModels.Count}">
|
||||||
Essentials
|
Essentials
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
@ -40,7 +40,7 @@
|
|||||||
</ItemsPanelTemplate>
|
</ItemsPanelTemplate>
|
||||||
</ItemsControl.ItemsPanel>
|
</ItemsControl.ItemsPanel>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
|
|
||||||
<TextBlock Classes="card-title" IsVisible="{CompiledBinding OtherEntryViewModels.Count}">
|
<TextBlock Classes="card-title" IsVisible="{CompiledBinding OtherEntryViewModels.Count}">
|
||||||
Other features
|
Other features
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
@ -56,14 +56,23 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Installation stage -->
|
<!-- Installation stage -->
|
||||||
<StackPanel IsVisible="{CompiledBinding CurrentEntry, Converter={x:Static ObjectConverters.IsNotNull}}">
|
<StackPanel Width="400" Margin="100 0 0 0" IsVisible="{CompiledBinding CurrentEntry, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||||
<TextBlock Classes="card-title">
|
<Lottie Path="/Assets/Animations/nyan.json" RepeatCount="-1" Width="300" Height="300"></Lottie>
|
||||||
Currently installing
|
|
||||||
</TextBlock>
|
|
||||||
<ContentControl Content="{CompiledBinding CurrentEntry}" />
|
|
||||||
|
|
||||||
<ProgressBar ShowProgressText="True" ProgressTextFormat="{}{0}/{3} Features Installed ({1:0}%)" Minimum="0" Maximum="{CompiledBinding TotalEntries}"
|
<ProgressBar Minimum="0"
|
||||||
Value="{CompiledBinding InstalledEntries}" />
|
Maximum="{CompiledBinding TotalEntries}"
|
||||||
|
Value="{CompiledBinding CurrentEntry.InstallProgress, FallbackValue=0}"
|
||||||
|
IsIndeterminate="{CompiledBinding CurrentEntry.Enabling, FallbackValue=true}"
|
||||||
|
Margin="0 0 0 5" />
|
||||||
|
<TextBlock>
|
||||||
|
<Run>Currently installing: </Run>
|
||||||
|
<Run Text="{CompiledBinding CurrentEntry.Entry.Name}" FontWeight="Bold" />
|
||||||
|
</TextBlock>
|
||||||
|
<ProgressBar Minimum="0" Maximum="{CompiledBinding TotalEntries}" Value="{CompiledBinding InstalledEntries}" Margin="0 20 0 5" />
|
||||||
|
<TextBlock>
|
||||||
|
<Run Text="{CompiledBinding RemainingEntries}" FontWeight="Bold" />
|
||||||
|
<Run>feature(s) remaining, hold on tight!</Run>
|
||||||
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Panel>
|
</Panel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -27,17 +27,22 @@ public partial class DefaultEntriesStepViewModel : WizardStepViewModel
|
|||||||
private readonly IDeviceService _deviceService;
|
private readonly IDeviceService _deviceService;
|
||||||
private readonly IWorkshopClient _client;
|
private readonly IWorkshopClient _client;
|
||||||
private readonly Func<IEntrySummary, DefaultEntryItemViewModel> _getDefaultEntryItemViewModel;
|
private readonly Func<IEntrySummary, DefaultEntryItemViewModel> _getDefaultEntryItemViewModel;
|
||||||
|
private readonly ObservableAsPropertyHelper<int> _remainingEntries;
|
||||||
|
|
||||||
public ObservableCollection<DefaultEntryItemViewModel> DeviceProviderEntryViewModels { get; } = [];
|
public ObservableCollection<DefaultEntryItemViewModel> DeviceProviderEntryViewModels { get; } = [];
|
||||||
public ObservableCollection<DefaultEntryItemViewModel> EssentialEntryViewModels { get; } = [];
|
public ObservableCollection<DefaultEntryItemViewModel> EssentialEntryViewModels { get; } = [];
|
||||||
public ObservableCollection<DefaultEntryItemViewModel> OtherEntryViewModels { get; } = [];
|
public ObservableCollection<DefaultEntryItemViewModel> OtherEntryViewModels { get; } = [];
|
||||||
|
public int RemainingEntries => _remainingEntries.Value;
|
||||||
|
|
||||||
public DefaultEntriesStepViewModel(IWorkshopService workshopService, IDeviceService deviceService, IWorkshopClient client,
|
public DefaultEntriesStepViewModel(IWorkshopService workshopService, IDeviceService deviceService, IWorkshopClient client,
|
||||||
Func<IEntrySummary, DefaultEntryItemViewModel> getDefaultEntryItemViewModel)
|
Func<IEntrySummary, DefaultEntryItemViewModel> getDefaultEntryItemViewModel)
|
||||||
{
|
{
|
||||||
_deviceService = deviceService;
|
_deviceService = deviceService;
|
||||||
_client = client;
|
_client = client;
|
||||||
_getDefaultEntryItemViewModel = getDefaultEntryItemViewModel;
|
_getDefaultEntryItemViewModel = getDefaultEntryItemViewModel;
|
||||||
|
_remainingEntries = this.WhenAnyValue(vm => vm.InstalledEntries, vm => vm.TotalEntries)
|
||||||
|
.Select(t => t.Item2 - t.Item1)
|
||||||
|
.ToProperty(this, vm => vm.RemainingEntries);
|
||||||
|
|
||||||
ContinueText = "Install selected entries";
|
ContinueText = "Install selected entries";
|
||||||
Continue = ReactiveCommand.CreateFromTask(async ct =>
|
Continue = ReactiveCommand.CreateFromTask(async ct =>
|
||||||
|
|||||||
@ -16,12 +16,13 @@ using Artemis.WebClient.Workshop.Models;
|
|||||||
using Artemis.WebClient.Workshop.Services;
|
using Artemis.WebClient.Workshop.Services;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using StrawberryShake;
|
using Serilog;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.StartupWizard.Steps;
|
namespace Artemis.UI.Screens.StartupWizard.Steps;
|
||||||
|
|
||||||
public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
|
public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
private readonly IWorkshopService _workshopService;
|
private readonly IWorkshopService _workshopService;
|
||||||
private readonly IWindowService _windowService;
|
private readonly IWindowService _windowService;
|
||||||
private readonly IPluginManagementService _pluginManagementService;
|
private readonly IPluginManagementService _pluginManagementService;
|
||||||
@ -30,15 +31,20 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
|
|||||||
|
|
||||||
[Notify] private bool _isInstalled;
|
[Notify] private bool _isInstalled;
|
||||||
[Notify] private bool _shouldInstall;
|
[Notify] private bool _shouldInstall;
|
||||||
|
[Notify] private bool _enabling;
|
||||||
public DefaultEntryItemViewModel(IEntrySummary entry, IWorkshopService workshopService, IWindowService windowService, IPluginManagementService pluginManagementService, ISettingsVmFactory settingsVmFactory)
|
[Notify] private float _installProgress;
|
||||||
|
|
||||||
|
public DefaultEntryItemViewModel(ILogger logger, IEntrySummary entry, IWorkshopService workshopService, IWindowService windowService, IPluginManagementService pluginManagementService,
|
||||||
|
ISettingsVmFactory settingsVmFactory)
|
||||||
{
|
{
|
||||||
|
_logger = logger;
|
||||||
_workshopService = workshopService;
|
_workshopService = workshopService;
|
||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
_pluginManagementService = pluginManagementService;
|
_pluginManagementService = pluginManagementService;
|
||||||
_settingsVmFactory = settingsVmFactory;
|
_settingsVmFactory = settingsVmFactory;
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
|
|
||||||
|
_progress.ProgressChanged += (_, f) => InstallProgress = f.ProgressPercentage;
|
||||||
this.WhenActivated((CompositeDisposable _) => { IsInstalled = workshopService.GetInstalledEntry(entry.Id) != null; });
|
this.WhenActivated((CompositeDisposable _) => { IsInstalled = workshopService.GetInstalledEntry(entry.Id) != null; });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,26 +67,44 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
|
|||||||
.WithCloseButtonText("Skip and continue")
|
.WithCloseButtonText("Skip and continue")
|
||||||
.ShowAsync();
|
.ShowAsync();
|
||||||
}
|
}
|
||||||
// If the entry is a plugin, enable the plugin
|
// If the entry is a plugin, enable the plugin and all features
|
||||||
else if (result.Entry?.EntryType == EntryType.Plugin)
|
else if (result.Entry?.EntryType == EntryType.Plugin)
|
||||||
{
|
{
|
||||||
await EnablePlugin(result.Entry);
|
Enabling = true;
|
||||||
|
await EnablePluginAndFeatures(result.Entry);
|
||||||
|
Enabling = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.IsSuccess;
|
return result.IsSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EnablePlugin(InstalledEntry entry)
|
private async Task EnablePluginAndFeatures(InstalledEntry entry)
|
||||||
{
|
{
|
||||||
if (!entry.TryGetMetadata("PluginId", out Guid pluginId))
|
if (!entry.TryGetMetadata("PluginId", out Guid pluginId))
|
||||||
throw new InvalidOperationException("Plugin entry does not contain a PluginId metadata value.");
|
throw new InvalidOperationException("Plugin entry does not contain a PluginId metadata value.");
|
||||||
|
|
||||||
Plugin? plugin = _pluginManagementService.GetAllPlugins().FirstOrDefault(p => p.Guid == pluginId);
|
Plugin? plugin = _pluginManagementService.GetAllPlugins().FirstOrDefault(p => p.Guid == pluginId);
|
||||||
if (plugin == null)
|
if (plugin == null)
|
||||||
throw new InvalidOperationException($"Plugin with id '{pluginId}' does not exist.");
|
throw new InvalidOperationException($"Plugin with id '{pluginId}' does not exist.");
|
||||||
|
|
||||||
// There's quite a bit of UI involved in enabling a plugin, borrowing the PluginSettingsViewModel for this
|
// There's quite a bit of UI involved in enabling a plugin, borrowing the PluginSettingsViewModel for this
|
||||||
PluginViewModel pluginViewModel = _settingsVmFactory.PluginViewModel(plugin, ReactiveCommand.Create(() => { }));
|
PluginViewModel pluginViewModel = _settingsVmFactory.PluginViewModel(plugin, ReactiveCommand.Create(() => { }));
|
||||||
await pluginViewModel.UpdateEnabled(true);
|
await pluginViewModel.UpdateEnabled(true);
|
||||||
|
|
||||||
|
// Find features without prerequisites to enable
|
||||||
|
foreach (PluginFeatureInfo pluginFeatureInfo in plugin.Features)
|
||||||
|
{
|
||||||
|
if (pluginFeatureInfo.Instance == null || pluginFeatureInfo.Instance.IsEnabled || pluginFeatureInfo.Prerequisites.Count != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_pluginManagementService.EnablePluginFeature(pluginFeatureInfo.Instance, true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.Warning(e, "Failed to enable plugin feature '{FeatureName}', skipping", pluginFeatureInfo.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -19,7 +19,7 @@ public class SurfaceStepViewModel : WizardStepViewModel
|
|||||||
_deviceService = deviceService;
|
_deviceService = deviceService;
|
||||||
SelectLayout = ReactiveCommand.Create<string>(ExecuteSelectLayout);
|
SelectLayout = ReactiveCommand.Create<string>(ExecuteSelectLayout);
|
||||||
|
|
||||||
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<DefaultEntriesStepViewModel>());
|
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<SettingsStepViewModel>());
|
||||||
GoBack = ReactiveCommand.Create(() => Wizard.ChangeScreen<LayoutsStepViewModel>());
|
GoBack = ReactiveCommand.Create(() => Wizard.ChangeScreen<LayoutsStepViewModel>());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,6 +30,6 @@ public class SurfaceStepViewModel : WizardStepViewModel
|
|||||||
// TODO: Implement the layout
|
// TODO: Implement the layout
|
||||||
_deviceService.AutoArrangeDevices();
|
_deviceService.AutoArrangeDevices();
|
||||||
|
|
||||||
Wizard.ChangeScreen<DefaultEntriesStepViewModel>();
|
Wizard.ChangeScreen<SettingsStepViewModel>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,9 +1,15 @@
|
|||||||
namespace Artemis.UI.Screens.StartupWizard.Steps;
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.StartupWizard.Steps;
|
||||||
|
|
||||||
public class WorkshopUnreachableStepViewModel : WizardStepViewModel
|
public class WorkshopUnreachableStepViewModel : WizardStepViewModel
|
||||||
{
|
{
|
||||||
public WorkshopUnreachableStepViewModel()
|
private readonly ISettingsService _settingsService;
|
||||||
|
|
||||||
|
public WorkshopUnreachableStepViewModel(ISettingsService settingsService)
|
||||||
{
|
{
|
||||||
|
_settingsService = settingsService;
|
||||||
HideAllButtons = true;
|
HideAllButtons = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -14,6 +20,10 @@ public class WorkshopUnreachableStepViewModel : WizardStepViewModel
|
|||||||
|
|
||||||
public void Skip()
|
public void Skip()
|
||||||
{
|
{
|
||||||
|
PluginSetting<bool> setting = _settingsService.GetSetting("UI.SetupWizardCompleted", false);
|
||||||
|
setting.Value = false;
|
||||||
|
setting.Save();
|
||||||
|
|
||||||
Wizard.Close(false);
|
Wizard.Close(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user