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

Wizard - Refactored code into separate view models per step

Wizard - Skip layout and surface steps when no devices are found
Workshop - Fixed crash when opening library
This commit is contained in:
Robert 2024-12-29 13:31:06 +01:00
parent 25786c6951
commit df95e64567
27 changed files with 536 additions and 427 deletions

View File

@ -4,6 +4,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:ui="clr-namespace:Artemis.UI"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.StartupWizard.StartupWizardView"
x:DataType="startupWizard:StartupWizardViewModel"
@ -12,14 +14,50 @@
Width="1000"
Height="735"
WindowStartupLocation="CenterOwner">
<Grid Margin="15" RowDefinitions="*,Auto" ColumnDefinitions="Auto,*">
<controls:Frame Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Name="Frame" />
<Grid Margin="15" RowDefinitions="Auto,*,Auto" ColumnDefinitions="Auto,*">
<Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" RowDefinitions="*,*" ColumnDefinitions="Auto,*,Auto">
<Image Grid.Column="0" Grid.RowSpan="2" Width="65" Height="65" VerticalAlignment="Center" Source="/Assets/Images/Logo/bow.png" Margin="0 0 20 0" />
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom">
Artemis 2
</TextBlock>
<Button Grid.Row="1" Grid.Column="0" Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding !ShowFinish}">Skip &amp; close</Button>
<StackPanel Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right" Orientation="Horizontal" Spacing="5" Margin="0 15 0 0">
<Button Command="{CompiledBinding GoBack}" IsEnabled="{CompiledBinding ShowGoBack}">Back</Button>
<Button Command="{CompiledBinding Continue}" IsVisible="{CompiledBinding !ShowFinish}" IsEnabled="{CompiledBinding ShowContinue}" Width="80">Continue</Button>
<Button Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding ShowFinish}" Width="80">Finish</Button>
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
<HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=wizard">
<avalonia:MaterialIcon Kind="Web" />
</HyperlinkButton>
<HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub repository" NavigateUri="https://github.com/Artemis-RGB/Artemis">
<avalonia:MaterialIcon Kind="Github" />
</HyperlinkButton>
<HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=wizard">
<avalonia:MaterialIcon Kind="BookOpenOutline" />
</HyperlinkButton>
</StackPanel>
<TextBlock Grid.Row="1"
Grid.Column="1"
VerticalAlignment="Top"
Classes="subtitle"
Text="{CompiledBinding Version}" />
<HyperlinkButton Grid.Row="1"
Grid.Column="2"
VerticalAlignment="Top"
NavigateUri="https://github.com/Artemis-RGB/Artemis/blob/master/LICENSE">
PolyForm Noncommercial License 1.0.0
</HyperlinkButton>
</Grid>
<controls:Frame Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Name="Frame" IsNavigationStackEnabled="False" CacheSize="0">
<controls:Frame.NavigationPageFactory>
<ui:PageFactory/>
</controls:Frame.NavigationPageFactory>
</controls:Frame>
<Button Grid.Row="2" Grid.Column="0" Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding !Screen.ShowFinish}">Skip &amp; close</Button>
<StackPanel Grid.Row="2" Grid.Column="1" HorizontalAlignment="Right" Orientation="Horizontal" Spacing="5" Margin="0 15 0 0">
<Button Command="{CompiledBinding Screen.GoBack}" IsEnabled="{CompiledBinding Screen.ShowGoBack}">Back</Button>
<Button Command="{CompiledBinding Screen.Continue}" IsVisible="{CompiledBinding !Screen.ShowFinish}" Width="80">Continue</Button>
<Button Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding Screen.ShowFinish}" Width="80">Finish</Button>
</StackPanel>
</Grid>

View File

@ -18,23 +18,19 @@ public partial class StartupWizardView : ReactiveAppWindow<StartupWizardViewMode
this.AttachDevTools();
#endif
this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.CurrentStep).Subscribe(ApplyCurrentStep).DisposeWith(d));
this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.Screen).WhereNotNull().Subscribe(Navigate).DisposeWith(d));
}
private void ApplyCurrentStep(int step)
private void Navigate(WizardStepViewModel viewModel)
{
if (step == 1)
Frame.NavigateToType(typeof(WelcomeStep), null, new FrameNavigationOptions());
else if (step == 2)
Frame.NavigateToType(typeof(DevicesStep), null, new FrameNavigationOptions());
else if (step == 3)
Frame.NavigateToType(typeof(LayoutsStep), null, new FrameNavigationOptions());
else if (step == 4)
Frame.NavigateToType(typeof(SurfaceStep), null, new FrameNavigationOptions());
else if (step == 5)
Frame.NavigateToType(typeof(SettingsStep), null, new FrameNavigationOptions());
else if (step == 6)
Frame.NavigateToType(typeof(FinishStep), null, new FrameNavigationOptions());
try
{
Frame.NavigateFromObject(viewModel);
}
catch (Exception e)
{
ViewModel?.WindowService.ShowExceptionDialog("Wizard screen failed to activate", e);
}
}
}

View File

@ -1,212 +1,46 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.DeviceProviders;
using Artemis.Core.Services;
using Artemis.UI.DryIoc.Factories;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Screens.Workshop.LayoutFinder;
using Artemis.UI.Screens.StartupWizard.Steps;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Providers;
using Artemis.UI.Shared.Services;
using DryIoc;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
namespace Artemis.UI.Screens.StartupWizard;
public partial class StartupWizardViewModel : DialogViewModelBase<bool>
{
private readonly IAutoRunProvider? _autoRunProvider;
private readonly IProtocolProvider? _protocolProvider;
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;
[Notify] private bool _showGoBack;
private readonly IContainer _container;
[Notify] private WizardStepViewModel _screen;
public StartupWizardViewModel(IContainer container,
ISettingsService settingsService,
IPluginManagementService pluginManagementService,
IWindowService windowService,
IDeviceService deviceService,
LayoutFinderViewModel layoutFinderViewModel,
Func<PluginFeatureInfo, WizardPluginFeatureViewModel> getPluginFeatureViewModel)
public StartupWizardViewModel(IContainer container, IWindowService windowService)
{
_settingsService = settingsService;
_windowService = windowService;
_deviceService = deviceService;
_getPluginFeatureViewModel = getPluginFeatureViewModel;
_autoRunProvider = container.Resolve<IAutoRunProvider>(IfUnresolved.ReturnDefault);
_protocolProvider = container.Resolve<IProtocolProvider>(IfUnresolved.ReturnDefault);
_container = container;
_screen = _container.Resolve<WelcomeStepViewModel>();
_screen.Wizard = this;
Continue = ReactiveCommand.Create(ExecuteContinue);
GoBack = ReactiveCommand.Create(ExecuteGoBack);
SkipOrFinishWizard = ReactiveCommand.Create(ExecuteSkipOrFinishWizard);
SelectLayout = ReactiveCommand.Create<string>(ExecuteSelectLayout);
WindowService = windowService;
Version = $"Version {Constants.CurrentVersion}";
// 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;
SetupButtons();
this.WhenActivated(d =>
{
UIAutoRun.SettingChanged += UIAutoRunOnSettingChanged;
UIUseProtocol.SettingChanged += UIUseProtocolOnSettingChanged;
UIAutoRunDelay.SettingChanged += UIAutoRunDelayOnSettingChanged;
Disposable.Create(() =>
{
UIAutoRun.SettingChanged -= UIAutoRunOnSettingChanged;
UIUseProtocol.SettingChanged -= UIUseProtocolOnSettingChanged;
UIAutoRunDelay.SettingChanged -= UIAutoRunDelayOnSettingChanged;
_settingsService.SaveAllSettings();
}).DisposeWith(d);
});
}
public ReactiveCommand<Unit, Unit> Continue { get; }
public ReactiveCommand<Unit, Unit> GoBack { get; }
public ReactiveCommand<Unit, Unit> SkipOrFinishWizard { get; }
public ReactiveCommand<string, Unit> SelectLayout { get; }
public IWindowService WindowService { get; }
public string Version { get; }
public ObservableCollection<WizardPluginFeatureViewModel> DeviceProviders { get; }
public LayoutFinderViewModel LayoutFinderViewModel { get; }
public bool IsAutoRunSupported => _autoRunProvider != null;
public PluginSetting<bool> UIAutoRun => _settingsService.GetSetting("UI.AutoRun", false);
public PluginSetting<bool> UIUseProtocol => _settingsService.GetSetting("UI.UseProtocol", true);
public PluginSetting<int> UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15);
public PluginSetting<bool> UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true);
public PluginSetting<bool> UICheckForUpdates => _settingsService.GetSetting("UI.Updating.AutoCheck", true);
public PluginSetting<bool> UIAutoUpdate => _settingsService.GetSetting("UI.Updating.AutoInstall", true);
private void ExecuteGoBack()
public void ChangeScreen<TWizardStepViewModel>() where TWizardStepViewModel : WizardStepViewModel
{
if (CurrentStep > 1)
CurrentStep--;
// Skip the settings step if none of it's contents are supported
if (CurrentStep == 5 && !IsAutoRunSupported)
CurrentStep--;
SetupButtons();
try
{
Screen = _container.Resolve<TWizardStepViewModel>();
Screen.Wizard = this;
}
catch (Exception e)
{
WindowService.ShowExceptionDialog("Wizard screen failed to activate", e);
}
}
private void ExecuteContinue()
public void SkipOrFinishWizard()
{
if (CurrentStep < 6)
CurrentStep++;
// Skip the settings step if none of it's contents are supported
if (CurrentStep == 5 && !IsAutoRunSupported)
CurrentStep++;
SetupButtons();
}
private void SetupButtons()
{
ShowContinue = CurrentStep != 4 && CurrentStep < 6;
ShowGoBack = CurrentStep > 1;
ShowFinish = CurrentStep == 6;
}
private void ExecuteSkipOrFinishWizard()
{
PluginSetting<bool> setting = _settingsService.GetSetting("UI.SetupWizardCompleted", false);
setting.Value = true;
setting.Save();
Close(true);
}
private void ExecuteSelectLayout(string layout)
{
// TODO: Implement the layout
_deviceService.AutoArrangeDevices();
ExecuteContinue();
}
private async Task ApplyAutoRun()
{
if (_autoRunProvider == null)
return;
try
{
if (UIAutoRun.Value)
await _autoRunProvider.EnableAutoRun(false, UIAutoRunDelay.Value);
else
await _autoRunProvider.DisableAutoRun();
}
catch (Exception exception)
{
_windowService.ShowExceptionDialog("Failed to apply auto-run", exception);
}
}
private void ApplyProtocolAssociation()
{
if (_protocolProvider == null)
return;
try
{
if (UIUseProtocol.Value)
_protocolProvider.AssociateWithProtocol("artemis");
else
_protocolProvider.DisassociateWithProtocol("artemis");
}
catch (Exception exception)
{
_windowService.ShowExceptionDialog("Failed to apply protocol association", exception);
}
}
private async void UIAutoRunOnSettingChanged(object? sender, EventArgs e)
{
await ApplyAutoRun();
}
private void UIUseProtocolOnSettingChanged(object? sender, EventArgs e)
{
ApplyProtocolAssociation();
}
private async void UIAutoRunDelayOnSettingChanged(object? sender, EventArgs e)
{
if (_autoRunProvider == null || !UIAutoRun.Value)
return;
try
{
await _autoRunProvider.EnableAutoRun(true, UIAutoRunDelay.Value);
}
catch (Exception exception)
{
_windowService.ShowExceptionDialog("Failed to apply auto-run", exception);
}
}
}

View File

@ -2,10 +2,10 @@
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:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.StartupWizard.Steps.DevicesStep"
x:DataType="startupWizard:StartupWizardViewModel">
x:Class="Artemis.UI.Screens.StartupWizard.Steps.DevicesStepView"
x:DataType="steps:DevicesStepViewModel">
<Border Classes="card">
<Grid RowDefinitions="Auto,*,Auto,Auto">
<StackPanel Grid.Row="0">
@ -35,7 +35,6 @@
</ItemsControl>
</ScrollViewer>
<TextBlock Grid.Row="2" Foreground="#FFB9A40A" TextWrapping="Wrap">
Note: To avoid possible instability it's recommended to disable the device providers of brands you don't own.
</TextBlock>

View File

@ -1,11 +1,12 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public partial class DevicesStep : UserControl
public partial class DevicesStepView : ReactiveUserControl<DevicesStepViewModel>
{
public DevicesStep()
public DevicesStepView()
{
InitializeComponent();
}

View File

@ -0,0 +1,33 @@
using ReactiveUI;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core;
using Artemis.Core.DeviceProviders;
using Artemis.Core.Services;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public class DevicesStepViewModel : WizardStepViewModel
{
public DevicesStepViewModel(IPluginManagementService pluginManagementService, Func<PluginFeatureInfo, WizardPluginFeatureViewModel> getPluginFeatureViewModel, IDeviceService deviceService)
{
// 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(getPluginFeatureViewModel));
Continue = ReactiveCommand.Create(() =>
{
if (deviceService.EnabledDevices.Count == 0)
Wizard.ChangeScreen<SettingsStepViewModel>();
else
Wizard.ChangeScreen<LayoutsStepViewModel>();
});
GoBack = ReactiveCommand.Create(() => Wizard.ChangeScreen<WelcomeStepViewModel>());
}
public ObservableCollection<WizardPluginFeatureViewModel> DeviceProviders { get; }
}

View File

@ -4,9 +4,10 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.StartupWizard.Steps.FinishStep"
x:DataType="startupWizard:StartupWizardViewModel">
x:Class="Artemis.UI.Screens.StartupWizard.Steps.FinishStepView"
x:DataType="steps:FinishStepViewModel">
<Border Classes="card" VerticalAlignment="Top">
<StackPanel>
<TextBlock Classes="h4">All finished!</TextBlock>

View File

@ -1,11 +1,12 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public partial class SurfaceStep : UserControl
public partial class FinishStepView : ReactiveUserControl<FinishStepViewModel>
{
public SurfaceStep()
public FinishStepView()
{
InitializeComponent();
}

View File

@ -0,0 +1,13 @@
using Artemis.UI.Shared;
using ReactiveUI;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public class FinishStepViewModel : WizardStepViewModel
{
public FinishStepViewModel()
{
GoBack = ReactiveCommand.Create(() => Wizard.ChangeScreen<SettingsStepViewModel>());
ShowFinish = true;
}
}

View File

@ -3,11 +3,12 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.StartupWizard.Steps.LayoutsStep"
x:DataType="startupWizard:StartupWizardViewModel">
x:Class="Artemis.UI.Screens.StartupWizard.Steps.LayoutsStepView"
x:DataType="steps:LayoutsStepViewModel">
<Border Classes="card">
<Grid RowDefinitions="Auto,Auto,Auto">
<Grid RowDefinitions="Auto,Auto,*">
<StackPanel Grid.Row="0">
<TextBlock TextWrapping="Wrap">
Device layouts provide Artemis with an image of your devices and exact LED positions. <LineBreak />

View File

@ -1,12 +1,13 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public partial class LayoutsStep : UserControl
public partial class LayoutsStepView : ReactiveUserControl<LayoutsStepViewModel>
{
public LayoutsStep()
public LayoutsStepView()
{
InitializeComponent();
}

View File

@ -0,0 +1,17 @@
using Artemis.UI.Screens.Workshop.LayoutFinder;
using ReactiveUI;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public class LayoutsStepViewModel : WizardStepViewModel
{
public LayoutsStepViewModel(LayoutFinderViewModel layoutFinderViewModel)
{
LayoutFinderViewModel = layoutFinderViewModel;
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<SurfaceStepViewModel>());
GoBack = ReactiveCommand.Create(() => Wizard.ChangeScreen<DevicesStepViewModel>());
}
public LayoutFinderViewModel LayoutFinderViewModel { get; }
}

View File

@ -1,123 +0,0 @@
<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:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:behaviors="clr-namespace:Artemis.UI.Shared.Behaviors;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.StartupWizard.Steps.SettingsStep"
x:DataType="startupWizard:StartupWizardViewModel">
<Border Classes="card" VerticalAlignment="Top">
<StackPanel>
<TextBlock>
Artemis comes with a variety of settings you can change to tweak everything to your liking.
</TextBlock>
<TextBlock>
Below you can find a few relevant settings, many more can be changed later on the settings page.
</TextBlock>
<!-- Auto-run settings -->
<StackPanel IsVisible="{CompiledBinding IsAutoRunSupported}">
<TextBlock Classes="card-title">
Auto-run
</TextBlock>
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel>
<StackPanel IsVisible="{CompiledBinding IsAutoRunSupported}">
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>Auto-run on startup</TextBlock>
</StackPanel>
<ToggleSwitch Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" IsChecked="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10" />
</Grid>
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>Hide window on auto-run</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding !UIShowOnStartup.Value}" IsEnabled="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10" />
</StackPanel>
</Grid>
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>Associate with Artemis links</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
Open Artemis when navigating to artemis:// links, allows opening workshop entries from your browser.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding UIUseProtocol.Value}" OnContent="Yes" OffContent="No" MinWidth="0" Margin="0 -10" />
</StackPanel>
</Grid>
<Border Classes="card-separator"/>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>Startup delay</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
Set the amount of seconds to wait before auto-running Artemis.
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
<controls:NumberBox IsEnabled="{CompiledBinding UIAutoRun.Value}" Width="120">
<Interaction.Behaviors>
<behaviors:LostFocusNumberBoxBindingBehavior Value="{CompiledBinding UIAutoRunDelay.Value}" />
</Interaction.Behaviors>
</controls:NumberBox>
<TextBlock VerticalAlignment="Center" TextAlignment="Right" Width="30">sec</TextBlock>
</StackPanel>
</Grid>
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
<!-- Update settings -->
<StackPanel>
<TextBlock Classes="card-title">
Updating
</TextBlock>
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Check for updates
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
If enabled, we'll check for updates on startup and periodically while running.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding UICheckForUpdates.Value}" MinWidth="0" />
</StackPanel>
</Grid>
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Auto-install updates
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
If enabled, new updates will automatically be installed.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsEnabled="{CompiledBinding UICheckForUpdates.Value}" IsChecked="{CompiledBinding UIAutoUpdate.Value}" MinWidth="0" />
</StackPanel>
</Grid>
</StackPanel>
</Border>
</StackPanel>
</StackPanel>
</Border>
</UserControl>

View File

@ -0,0 +1,126 @@
<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:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:behaviors="clr-namespace:Artemis.UI.Shared.Behaviors;assembly=Artemis.UI.Shared"
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.StartupWizard.Steps.SettingsStepView"
x:DataType="steps:SettingsStepViewModel">
<Border Classes="card" VerticalAlignment="Top">
<ScrollViewer>
<StackPanel>
<TextBlock>
Artemis comes with a variety of settings you can change to tweak everything to your liking.
</TextBlock>
<TextBlock>
Below you can find a few relevant settings, many more can be changed later on the settings page.
</TextBlock>
<!-- Auto-run settings -->
<StackPanel IsVisible="{CompiledBinding IsAutoRunSupported}">
<TextBlock Classes="card-title">
Auto-run
</TextBlock>
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel>
<StackPanel IsVisible="{CompiledBinding IsAutoRunSupported}">
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>Auto-run on startup</TextBlock>
</StackPanel>
<ToggleSwitch Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" IsChecked="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10" />
</Grid>
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>Hide window on auto-run</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding !UIShowOnStartup.Value}" IsEnabled="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10" />
</StackPanel>
</Grid>
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>Associate with Artemis links</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
Open Artemis when navigating to artemis:// links, allows opening workshop entries from your browser.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding UIUseProtocol.Value}" OnContent="Yes" OffContent="No" MinWidth="0" Margin="0 -10" />
</StackPanel>
</Grid>
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>Startup delay</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
Set the amount of seconds to wait before auto-running Artemis.
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
<controls:NumberBox IsEnabled="{CompiledBinding UIAutoRun.Value}" Width="120">
<Interaction.Behaviors>
<behaviors:LostFocusNumberBoxBindingBehavior Value="{CompiledBinding UIAutoRunDelay.Value}" />
</Interaction.Behaviors>
</controls:NumberBox>
<TextBlock VerticalAlignment="Center" TextAlignment="Right" Width="30">sec</TextBlock>
</StackPanel>
</Grid>
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
<!-- Update settings -->
<StackPanel>
<TextBlock Classes="card-title">
Updating
</TextBlock>
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Check for updates
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
If enabled, we'll check for updates on startup and periodically while running.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding UICheckForUpdates.Value}" MinWidth="0" />
</StackPanel>
</Grid>
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Auto-install updates
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
If enabled, new updates will automatically be installed.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsEnabled="{CompiledBinding UICheckForUpdates.Value}" IsChecked="{CompiledBinding UIAutoUpdate.Value}" MinWidth="0" />
</StackPanel>
</Grid>
</StackPanel>
</Border>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Border>
</UserControl>

View File

@ -0,0 +1,14 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public partial class SettingsStepView : ReactiveUserControl<SettingsStepViewModel>
{
public SettingsStepView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,131 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.DeviceProviders;
using Artemis.Core.Services;
using Artemis.UI.DryIoc.Factories;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Screens.Workshop.LayoutFinder;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Providers;
using Artemis.UI.Shared.Services;
using DryIoc;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public class SettingsStepViewModel : WizardStepViewModel
{
private readonly ISettingsService _settingsService;
private readonly IWindowService _windowService;
private readonly IAutoRunProvider? _autoRunProvider;
private readonly IProtocolProvider? _protocolProvider;
public SettingsStepViewModel(IContainer container, ISettingsService settingsService, IDeviceService deviceService, IWindowService windowService)
{
_settingsService = settingsService;
_windowService = windowService;
_autoRunProvider = container.Resolve<IAutoRunProvider>(IfUnresolved.ReturnDefault);
_protocolProvider = container.Resolve<IProtocolProvider>(IfUnresolved.ReturnDefault);
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<FinishStepViewModel>());
GoBack = ReactiveCommand.Create(() =>
{
if (deviceService.EnabledDevices.Count == 0)
Wizard.ChangeScreen<DevicesStepViewModel>();
else
Wizard.ChangeScreen<SurfaceStepViewModel>();
});
this.WhenActivated(d =>
{
UIAutoRun.SettingChanged += UIAutoRunOnSettingChanged;
UIUseProtocol.SettingChanged += UIUseProtocolOnSettingChanged;
UIAutoRunDelay.SettingChanged += UIAutoRunDelayOnSettingChanged;
Disposable.Create(() =>
{
UIAutoRun.SettingChanged -= UIAutoRunOnSettingChanged;
UIUseProtocol.SettingChanged -= UIUseProtocolOnSettingChanged;
UIAutoRunDelay.SettingChanged -= UIAutoRunDelayOnSettingChanged;
_settingsService.SaveAllSettings();
}).DisposeWith(d);
});
}
public PluginSetting<bool> UIAutoRun => _settingsService.GetSetting("UI.AutoRun", false);
public PluginSetting<bool> UIUseProtocol => _settingsService.GetSetting("UI.UseProtocol", true);
public PluginSetting<int> UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15);
public PluginSetting<bool> UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true);
public PluginSetting<bool> UICheckForUpdates => _settingsService.GetSetting("UI.Updating.AutoCheck", true);
public PluginSetting<bool> UIAutoUpdate => _settingsService.GetSetting("UI.Updating.AutoInstall", true);
public bool IsAutoRunSupported => _autoRunProvider != null;
private async Task ApplyAutoRun()
{
if (_autoRunProvider == null)
return;
try
{
if (UIAutoRun.Value)
await _autoRunProvider.EnableAutoRun(false, UIAutoRunDelay.Value);
else
await _autoRunProvider.DisableAutoRun();
}
catch (Exception exception)
{
_windowService.ShowExceptionDialog("Failed to apply auto-run", exception);
}
}
private void ApplyProtocolAssociation()
{
if (_protocolProvider == null)
return;
try
{
if (UIUseProtocol.Value)
_protocolProvider.AssociateWithProtocol("artemis");
else
_protocolProvider.DisassociateWithProtocol("artemis");
}
catch (Exception exception)
{
_windowService.ShowExceptionDialog("Failed to apply protocol association", exception);
}
}
private async void UIAutoRunOnSettingChanged(object? sender, EventArgs e)
{
await ApplyAutoRun();
}
private void UIUseProtocolOnSettingChanged(object? sender, EventArgs e)
{
ApplyProtocolAssociation();
}
private async void UIAutoRunDelayOnSettingChanged(object? sender, EventArgs e)
{
if (_autoRunProvider == null || !UIAutoRun.Value)
return;
try
{
await _autoRunProvider.EnableAutoRun(true, UIAutoRunDelay.Value);
}
catch (Exception exception)
{
_windowService.ShowExceptionDialog("Failed to apply auto-run", exception);
}
}
}

View File

@ -4,9 +4,10 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.StartupWizard.Steps.SurfaceStep"
x:DataType="startupWizard:StartupWizardViewModel">
x:Class="Artemis.UI.Screens.StartupWizard.Steps.SurfaceStepView"
x:DataType="steps:SurfaceStepViewModel">
<Grid RowDefinitions="Auto,*,Auto,Auto" ColumnDefinitions="*,*">
<Border Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Classes="card">

View File

@ -1,11 +1,12 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public partial class SettingsStep : UserControl
public partial class SurfaceStepView : ReactiveUserControl<SurfaceStepViewModel>
{
public SettingsStep()
public SurfaceStepView()
{
InitializeComponent();
}

View File

@ -0,0 +1,35 @@
using System.Reactive;
using Artemis.UI.Shared;
using ReactiveUI;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core;
using Artemis.Core.DeviceProviders;
using Artemis.Core.Services;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public class SurfaceStepViewModel : WizardStepViewModel
{
private readonly IDeviceService _deviceService;
public SurfaceStepViewModel(IDeviceService deviceService)
{
_deviceService = deviceService;
SelectLayout = ReactiveCommand.Create<string>(ExecuteSelectLayout);
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<SettingsStepViewModel>());
GoBack = ReactiveCommand.Create(() => Wizard.ChangeScreen<LayoutsStepViewModel>());
}
public ReactiveCommand<string, Unit> SelectLayout { get; set; }
private void ExecuteSelectLayout(string layout)
{
// TODO: Implement the layout
_deviceService.AutoArrangeDevices();
Wizard.ChangeScreen<SettingsStepViewModel>();
}
}

View File

@ -1,61 +0,0 @@
<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:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
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="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.StartupWizard.Steps.WelcomeStep"
x:DataType="startupWizard:StartupWizardViewModel">
<StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="Auto,*,Auto">
<Image Grid.Column="0" Grid.RowSpan="2" Width="65" Height="65" VerticalAlignment="Center" Source="/Assets/Images/Logo/bow.png" Margin="0 0 20 0" />
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom">
Artemis 2
</TextBlock>
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
<HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=wizard">
<avalonia:MaterialIcon Kind="Web" />
</HyperlinkButton>
<HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub repository" NavigateUri="https://github.com/Artemis-RGB/Artemis">
<avalonia:MaterialIcon Kind="Github" />
</HyperlinkButton>
<HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=wizard">
<avalonia:MaterialIcon Kind="BookOpenOutline" />
</HyperlinkButton>
</StackPanel>
<TextBlock Grid.Row="1"
Grid.Column="1"
VerticalAlignment="Top"
Classes="subtitle"
Text="{CompiledBinding Version}" />
<HyperlinkButton Grid.Row="1"
Grid.Column="2"
VerticalAlignment="Top"
NavigateUri="https://github.com/Artemis-RGB/Artemis/blob/master/LICENSE">
PolyForm Noncommercial License 1.0.0
</HyperlinkButton>
</Grid>
<Border Classes="card">
<StackPanel>
<TextBlock Classes="h4">Welcome to the Artemis startup wizard!</TextBlock>
<TextBlock TextWrapping="Wrap">
In this wizard we'll walk you through the initial configuration of Artemis.
</TextBlock>
<TextBlock TextWrapping="Wrap">
Before you can start you need to tell Artemis which devices you want to use and where they are placed on your desk.
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap" Margin="0 15 0 0">
PS: You can also skip the wizard and set things up yourself.
</TextBlock>
</StackPanel>
</Border>
</StackPanel>
</UserControl>

View File

@ -1,13 +0,0 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public partial class WelcomeStep : UserControl
{
public WelcomeStep()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,24 @@
<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:steps="clr-namespace:Artemis.UI.Screens.Workshop.SubmissionWizard.Steps"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.StartupWizard.Steps.WelcomeStepView"
x:DataType="steps:WelcomeStepViewModel">
<Border Classes="card">
<StackPanel>
<TextBlock Classes="h4">Welcome to the Artemis startup wizard!</TextBlock>
<TextBlock TextWrapping="Wrap">
In this wizard we'll walk you through the initial configuration of Artemis.
</TextBlock>
<TextBlock TextWrapping="Wrap">
Before you can start you need to tell Artemis which devices you want to use and where they are placed on your desk.
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap" Margin="0 15 0 0">
PS: You can also skip the wizard and set things up yourself.
</TextBlock>
</StackPanel>
</Border>
</UserControl>

View File

@ -1,11 +1,12 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public partial class FinishStep : UserControl
public partial class WelcomeStepView : ReactiveUserControl<WelcomeStepViewModel>
{
public FinishStep()
public WelcomeStepView()
{
InitializeComponent();
}

View File

@ -0,0 +1,12 @@
using ReactiveUI;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public class WelcomeStepViewModel : WizardStepViewModel
{
public WelcomeStepViewModel()
{
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<DevicesStepViewModel>());
ShowGoBack = false;
}
}

View File

@ -0,0 +1,20 @@
using System.Reactive;
using Artemis.UI.Shared;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
namespace Artemis.UI.Screens.StartupWizard;
public abstract partial class WizardStepViewModel : ValidatableViewModelBase
{
[Notify] private ReactiveCommand<Unit, Unit>? _secondary;
[Notify] private ReactiveCommand<Unit, Unit>? _continue;
[Notify] private ReactiveCommand<Unit, Unit>? _goBack;
[Notify] private string _continueText = "Continue";
[Notify] private string? _secondaryText;
[Notify] private bool _showFinish;
[Notify] private bool _showGoBack = true;
[Notify] private bool _showHeader = true;
public StartupWizardViewModel Wizard { get; set; } = null!;
}

View File

@ -25,7 +25,10 @@ public partial class InstalledTabViewModel : RoutableScreen
public InstalledTabViewModel(IWorkshopService workshopService, IRouter router, Func<InstalledEntry, InstalledTabItemViewModel> getInstalledTabItemViewModel)
{
IObservable<Func<InstalledEntry, bool>> searchFilter = this.WhenAnyValue(vm => vm.SearchEntryInput).Throttle(TimeSpan.FromMilliseconds(100)).Select(CreatePredicate);
IObservable<Func<InstalledEntry, bool>> searchFilter = this.WhenAnyValue(vm => vm.SearchEntryInput)
.Throttle(TimeSpan.FromMilliseconds(100))
.ObserveOn(RxApp.MainThreadScheduler)
.Select(CreatePredicate);
_entries.Connect()
.Filter(searchFilter)

View File

@ -38,7 +38,10 @@ public partial class SubmissionsTabViewModel : RoutableScreen
IWorkshopService workshopService,
Func<IGetSubmittedEntries_SubmittedEntries, SubmissionsTabItemViewModel> getSubmissionsTabItemViewModel)
{
IObservable<Func<IGetSubmittedEntries_SubmittedEntries, bool>> searchFilter = this.WhenAnyValue(vm => vm.SearchEntryInput).Throttle(TimeSpan.FromMilliseconds(100)).Select(CreatePredicate);
IObservable<Func<IGetSubmittedEntries_SubmittedEntries, bool>> searchFilter = this.WhenAnyValue(vm => vm.SearchEntryInput)
.Throttle(TimeSpan.FromMilliseconds(100))
.ObserveOn(RxApp.MainThreadScheduler)
.Select(CreatePredicate);
_client = client;
_windowService = windowService;