1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 13:28:33 +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:
Robert 2025-12-04 23:09:28 +01:00
parent 6988255dce
commit 13c497f41e
10 changed files with 78 additions and 30 deletions

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Controls;

View File

@ -8,6 +8,7 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading;
using DryIoc;
using FluentAvalonia.UI.Controls;
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
namespace Artemis.UI.Shared.Services;
@ -94,6 +95,7 @@ internal class WindowService : IWindowService
.WithTitle(title)
.WithContent(message)
.HavingPrimaryButton(b => b.WithText(confirm))
.WithDefaultButton(ContentDialogButton.Primary)
.WithCloseButtonText(cancel)
.ShowAsync();

View File

@ -34,10 +34,7 @@ internal class Program
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
{
return AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace()
.UseReactiveUI();
return AppBuilder.Configure<App>().UsePlatformDetect().LogToTrace().UseReactiveUI();
}
public static void CreateLogger(IContainer container)

File diff suppressed because one or more lines are too long

View File

@ -54,8 +54,8 @@
<DockPanel Grid.Row="2" LastChildFill="False" IsVisible="{CompiledBinding !Screen.HideAllButtons}" HorizontalSpacing="10">
<Button Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding !Screen.ShowFinish}" DockPanel.Dock="Left">Skip &amp; close</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 Screen.GoBack}" IsEnabled="{CompiledBinding Screen.ShowGoBack}" DockPanel.Dock="Right">Back</Button>
</DockPanel>
</Grid>

View File

@ -9,10 +9,10 @@
<Panel>
<!-- Selection stage -->
<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.
</TextBlock>
<StackPanel Grid.Row="1" IsVisible="{CompiledBinding FetchingDefaultEntries}" HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="10">
<ProgressBar IsIndeterminate="True" Width="400" />
<TextBlock TextAlignment="Center">Fetching default features from the Artemis workshop...</TextBlock>
@ -29,7 +29,7 @@
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<TextBlock Classes="card-title" IsVisible="{CompiledBinding EssentialEntryViewModels.Count}">
Essentials
</TextBlock>
@ -40,7 +40,7 @@
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<TextBlock Classes="card-title" IsVisible="{CompiledBinding OtherEntryViewModels.Count}">
Other features
</TextBlock>
@ -56,14 +56,23 @@
</Grid>
<!-- Installation stage -->
<StackPanel IsVisible="{CompiledBinding CurrentEntry, Converter={x:Static ObjectConverters.IsNotNull}}">
<TextBlock Classes="card-title">
Currently installing
</TextBlock>
<ContentControl Content="{CompiledBinding CurrentEntry}" />
<StackPanel Width="400" Margin="100 0 0 0" IsVisible="{CompiledBinding CurrentEntry, Converter={x:Static ObjectConverters.IsNotNull}}">
<Lottie Path="/Assets/Animations/nyan.json" RepeatCount="-1" Width="300" Height="300"></Lottie>
<ProgressBar ShowProgressText="True" ProgressTextFormat="{}{0}/{3} Features Installed ({1:0}%)" Minimum="0" Maximum="{CompiledBinding TotalEntries}"
Value="{CompiledBinding InstalledEntries}" />
<ProgressBar Minimum="0"
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>
</Panel>
</UserControl>

View File

@ -27,17 +27,22 @@ public partial class DefaultEntriesStepViewModel : WizardStepViewModel
private readonly IDeviceService _deviceService;
private readonly IWorkshopClient _client;
private readonly Func<IEntrySummary, DefaultEntryItemViewModel> _getDefaultEntryItemViewModel;
private readonly ObservableAsPropertyHelper<int> _remainingEntries;
public ObservableCollection<DefaultEntryItemViewModel> DeviceProviderEntryViewModels { get; } = [];
public ObservableCollection<DefaultEntryItemViewModel> EssentialEntryViewModels { get; } = [];
public ObservableCollection<DefaultEntryItemViewModel> OtherEntryViewModels { get; } = [];
public int RemainingEntries => _remainingEntries.Value;
public DefaultEntriesStepViewModel(IWorkshopService workshopService, IDeviceService deviceService, IWorkshopClient client,
Func<IEntrySummary, DefaultEntryItemViewModel> getDefaultEntryItemViewModel)
{
_deviceService = deviceService;
_client = client;
_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";
Continue = ReactiveCommand.CreateFromTask(async ct =>

View File

@ -16,12 +16,13 @@ using Artemis.WebClient.Workshop.Models;
using Artemis.WebClient.Workshop.Services;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
using StrawberryShake;
using Serilog;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
{
private readonly ILogger _logger;
private readonly IWorkshopService _workshopService;
private readonly IWindowService _windowService;
private readonly IPluginManagementService _pluginManagementService;
@ -30,15 +31,20 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
[Notify] private bool _isInstalled;
[Notify] private bool _shouldInstall;
public DefaultEntryItemViewModel(IEntrySummary entry, IWorkshopService workshopService, IWindowService windowService, IPluginManagementService pluginManagementService, ISettingsVmFactory settingsVmFactory)
[Notify] private bool _enabling;
[Notify] private float _installProgress;
public DefaultEntryItemViewModel(ILogger logger, IEntrySummary entry, IWorkshopService workshopService, IWindowService windowService, IPluginManagementService pluginManagementService,
ISettingsVmFactory settingsVmFactory)
{
_logger = logger;
_workshopService = workshopService;
_windowService = windowService;
_pluginManagementService = pluginManagementService;
_settingsVmFactory = settingsVmFactory;
Entry = entry;
_progress.ProgressChanged += (_, f) => InstallProgress = f.ProgressPercentage;
this.WhenActivated((CompositeDisposable _) => { IsInstalled = workshopService.GetInstalledEntry(entry.Id) != null; });
}
@ -61,26 +67,44 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
.WithCloseButtonText("Skip and continue")
.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)
{
await EnablePlugin(result.Entry);
Enabling = true;
await EnablePluginAndFeatures(result.Entry);
Enabling = false;
}
return result.IsSuccess;
}
private async Task EnablePlugin(InstalledEntry entry)
private async Task EnablePluginAndFeatures(InstalledEntry entry)
{
if (!entry.TryGetMetadata("PluginId", out Guid pluginId))
throw new InvalidOperationException("Plugin entry does not contain a PluginId metadata value.");
Plugin? plugin = _pluginManagementService.GetAllPlugins().FirstOrDefault(p => p.Guid == pluginId);
if (plugin == null)
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
PluginViewModel pluginViewModel = _settingsVmFactory.PluginViewModel(plugin, ReactiveCommand.Create(() => { }));
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);
}
}
}
}

View File

@ -19,7 +19,7 @@ public class SurfaceStepViewModel : WizardStepViewModel
_deviceService = deviceService;
SelectLayout = ReactiveCommand.Create<string>(ExecuteSelectLayout);
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<DefaultEntriesStepViewModel>());
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<SettingsStepViewModel>());
GoBack = ReactiveCommand.Create(() => Wizard.ChangeScreen<LayoutsStepViewModel>());
}
@ -30,6 +30,6 @@ public class SurfaceStepViewModel : WizardStepViewModel
// TODO: Implement the layout
_deviceService.AutoArrangeDevices();
Wizard.ChangeScreen<DefaultEntriesStepViewModel>();
Wizard.ChangeScreen<SettingsStepViewModel>();
}
}

View File

@ -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 WorkshopUnreachableStepViewModel()
private readonly ISettingsService _settingsService;
public WorkshopUnreachableStepViewModel(ISettingsService settingsService)
{
_settingsService = settingsService;
HideAllButtons = true;
}
@ -14,6 +20,10 @@ public class WorkshopUnreachableStepViewModel : WizardStepViewModel
public void Skip()
{
PluginSetting<bool> setting = _settingsService.GetSetting("UI.SetupWizardCompleted", false);
setting.Value = false;
setting.Save();
Wizard.Close(false);
}
}