1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-11 04:48:46 +00:00

Merge branch 'development'

This commit is contained in:
Robert 2025-02-04 21:01:16 +01:00
commit 2acac792f2
43 changed files with 954 additions and 511 deletions

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<PreserveCompilationContext>false</PreserveCompilationContext>
<ShouldIncludeNativeSkiaSharp>false</ShouldIncludeNativeSkiaSharp>
<AssemblyTitle>Artemis.Core</AssemblyTitle>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<PreserveCompilationContext>false</PreserveCompilationContext>
<Platforms>x64</Platforms>
<Nullable>enable</Nullable>

View File

@ -8,6 +8,7 @@ using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Surface;
using Artemis.Storage.Entities.Workshop;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
namespace Artemis.Storage;
@ -35,9 +36,17 @@ public class ArtemisDbContext : DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<DeviceEntity>()
.OwnsOne(d => d.InputIdentifiers, builder => builder.ToJson())
.OwnsOne(d => d.InputMappings, builder => builder.ToJson());
.Property(e => e.InputIdentifiers)
.HasConversion(
v => JsonSerializer.Serialize(v, JsonSerializerOptions),
v => JsonSerializer.Deserialize<List<DeviceInputIdentifierEntity>>(v, JsonSerializerOptions) ?? new List<DeviceInputIdentifierEntity>());
modelBuilder.Entity<DeviceEntity>()
.Property(e => e.InputMappings)
.HasConversion(
v => JsonSerializer.Serialize(v, JsonSerializerOptions),
v => JsonSerializer.Deserialize<List<InputMappingEntity>>(v, JsonSerializerOptions) ?? new List<InputMappingEntity>());
modelBuilder.Entity<EntryEntity>()
.Property(e => e.Metadata)
.HasConversion(

View File

@ -0,0 +1,337 @@
// <auto-generated />
using System;
using Artemis.Storage;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Artemis.Storage.Migrations
{
[DbContext(typeof(ArtemisDbContext))]
[Migration("20250204194848_DevicesClearBrokenJson")]
partial class DevicesClearBrokenJson
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.1");
modelBuilder.Entity("Artemis.Storage.Entities.General.ReleaseEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("InstalledAt")
.HasColumnType("TEXT");
b.Property<string>("Version")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("InstalledAt");
b.HasIndex("Version")
.IsUnique();
b.ToTable("Releases");
});
modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<Guid>("PluginGuid")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("PluginGuid")
.IsUnique();
b.ToTable("Plugins");
});
modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginFeatureEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<Guid?>("PluginEntityId")
.HasColumnType("TEXT");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("PluginEntityId");
b.ToTable("PluginFeatures");
});
modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginSettingEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<Guid>("PluginGuid")
.HasColumnType("TEXT");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("PluginGuid");
b.HasIndex("Name", "PluginGuid")
.IsUnique();
b.ToTable("PluginSettings");
});
modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("IsCollapsed")
.HasColumnType("INTEGER");
b.Property<bool>("IsSuspended")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("TEXT");
b.Property<int>("Order")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique();
b.ToTable("ProfileCategories");
});
modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileContainerEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<byte[]>("Icon")
.IsRequired()
.HasColumnType("BLOB");
b.Property<string>("Profile")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid>("ProfileCategoryId")
.HasColumnType("TEXT");
b.Property<string>("ProfileConfiguration")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ProfileCategoryId");
b.ToTable("ProfileContainers");
});
modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b =>
{
b.Property<string>("Id")
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<float>("BlueScale")
.HasColumnType("REAL");
b.PrimitiveCollection<string>("Categories")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("DeviceProvider")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<float>("GreenScale")
.HasColumnType("REAL");
b.Property<string>("InputIdentifiers")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("InputMappings")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("LayoutParameter")
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<string>("LayoutType")
.HasMaxLength(64)
.HasColumnType("TEXT");
b.Property<string>("LogicalLayout")
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<int>("PhysicalLayout")
.HasColumnType("INTEGER");
b.Property<float>("RedScale")
.HasColumnType("REAL");
b.Property<float>("Rotation")
.HasColumnType("REAL");
b.Property<float>("Scale")
.HasColumnType("REAL");
b.Property<float>("X")
.HasColumnType("REAL");
b.Property<float>("Y")
.HasColumnType("REAL");
b.Property<int>("ZIndex")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Devices");
});
modelBuilder.Entity("Artemis.Storage.Entities.Workshop.EntryEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Author")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("AutoUpdate")
.HasColumnType("INTEGER");
b.Property<string>("Categories")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("TEXT");
b.Property<long>("Downloads")
.HasColumnType("INTEGER");
b.Property<long>("EntryId")
.HasColumnType("INTEGER");
b.Property<int>("EntryType")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("InstalledAt")
.HasColumnType("TEXT");
b.Property<bool>("IsOfficial")
.HasColumnType("INTEGER");
b.Property<long?>("LatestReleaseId")
.HasColumnType("INTEGER");
b.Property<string>("Metadata")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("ReleaseId")
.HasColumnType("INTEGER");
b.Property<string>("ReleaseVersion")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Summary")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("EntryId")
.IsUnique();
b.ToTable("Entries");
});
modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginFeatureEntity", b =>
{
b.HasOne("Artemis.Storage.Entities.Plugins.PluginEntity", null)
.WithMany("Features")
.HasForeignKey("PluginEntityId");
});
modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileContainerEntity", b =>
{
b.HasOne("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", "ProfileCategory")
.WithMany("ProfileConfigurations")
.HasForeignKey("ProfileCategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ProfileCategory");
});
modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b =>
{
b.Navigation("Features");
});
modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", b =>
{
b.Navigation("ProfileConfigurations");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Artemis.Storage.Migrations
{
/// <inheritdoc />
public partial class DevicesClearBrokenJson : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("UPDATE Devices SET InputMappings = \"[]\", InputIdentifiers = \"[]\"");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("UPDATE Devices SET InputMappings = '{\"Capacity\":0}', InputIdentifiers = '{\"Capacity\":0}'");
}
}
}

View File

@ -15,7 +15,7 @@ namespace Artemis.Storage.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.6");
modelBuilder.HasAnnotation("ProductVersion", "9.0.1");
modelBuilder.Entity("Artemis.Storage.Entities.General.ReleaseEntity", b =>
{
@ -177,7 +177,7 @@ namespace Artemis.Storage.Migrations
b.Property<float>("BlueScale")
.HasColumnType("REAL");
b.Property<string>("Categories")
b.PrimitiveCollection<string>("Categories")
.IsRequired()
.HasColumnType("TEXT");
@ -189,6 +189,14 @@ namespace Artemis.Storage.Migrations
b.Property<float>("GreenScale")
.HasColumnType("REAL");
b.Property<string>("InputIdentifiers")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("InputMappings")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
@ -311,51 +319,6 @@ namespace Artemis.Storage.Migrations
b.Navigation("ProfileCategory");
});
modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b =>
{
b.OwnsOne("System.Collections.Generic.List<Artemis.Storage.Entities.Surface.DeviceInputIdentifierEntity>", "InputIdentifiers", b1 =>
{
b1.Property<string>("DeviceEntityId")
.HasColumnType("TEXT");
b1.Property<int>("Capacity")
.HasColumnType("INTEGER");
b1.HasKey("DeviceEntityId");
b1.ToTable("Devices");
b1.ToJson("InputIdentifiers");
b1.WithOwner()
.HasForeignKey("DeviceEntityId");
});
b.OwnsOne("System.Collections.Generic.List<Artemis.Storage.Entities.Surface.InputMappingEntity>", "InputMappings", b1 =>
{
b1.Property<string>("DeviceEntityId")
.HasColumnType("TEXT");
b1.Property<int>("Capacity")
.HasColumnType("INTEGER");
b1.HasKey("DeviceEntityId");
b1.ToTable("Devices");
b1.ToJson("InputMappings");
b1.WithOwner()
.HasForeignKey("DeviceEntityId");
});
b.Navigation("InputIdentifiers")
.IsRequired();
b.Navigation("InputMappings")
.IsRequired();
});
modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b =>
{
b.Navigation("Features");

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<PlatformTarget>x64</PlatformTarget>
<Platforms>x64</Platforms>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<PlatformTarget>x64</PlatformTarget>
<Platforms>x64</Platforms>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<OutputPath>bin\</OutputPath>
<Platforms>x64</Platforms>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows10.0.17763.0</TargetFramework>
<TargetFramework>net9.0-windows10.0.17763.0</TargetFramework>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<OutputPath>bin</OutputPath>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<OutputPath>bin/</OutputPath>
<Platforms>x64</Platforms>

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;

View File

@ -26,7 +26,7 @@ public class ValidateEmailStepViewModel : SubmissionViewModel
Continue = ReactiveCommand.Create(() =>{}, Observable.Never<bool>());
Refresh = ReactiveCommand.CreateFromTask(ExecuteRefresh);
Resend = ReactiveCommand.Create(() => Utilities.OpenUrl(WorkshopConstants.AUTHORITY_URL + "/account/confirm/resend"));
Resend = ReactiveCommand.Create(() => Utilities.OpenUrl(WorkshopConstants.AUTHORITY_URL + "/account/email/confirm/resend"));
ShowGoBack = false;
ShowHeader = false;

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>

View File

@ -5,62 +5,65 @@
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="AsyncImageLoader.Avalonia" Version="3.3.0" />
<PackageVersion Include="Avalonia" Version="11.2.0" />
<PackageVersion Include="Avalonia" Version="11.2.3" />
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.1.0" />
<PackageVersion Include="Avalonia.Controls.ItemsRepeater" Version="11.1.4" />
<PackageVersion Include="Avalonia.Controls.ItemsRepeater" Version="11.1.5" />
<PackageVersion Include="Avalonia.Controls.PanAndZoom" Version="11.2.0" />
<PackageVersion Include="Avalonia.Desktop" Version="11.2.0" />
<PackageVersion Include="Avalonia.Desktop" Version="11.2.3" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.9" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.2.0" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.2.3" />
<PackageVersion Include="Avalonia.Skia.Lottie" Version="11.0.0" />
<PackageVersion Include="Avalonia.Win32" Version="11.2.0" />
<PackageVersion Include="Avalonia.Win32" Version="11.2.3" />
<PackageVersion Include="HPPH.SkiaSharp" Version="1.0.0" />
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.0" />
<PackageVersion Include="Avalonia.Xaml.Behaviors" Version="11.2.0" />
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.1" />
<PackageVersion Include="Avalonia.Xaml.Behaviors" Version="11.2.0.8" />
<PackageVersion Include="AvaloniaEdit.TextMate" Version="11.1.0" />
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
<PackageVersion Include="DryIoc.dll" Version="5.4.3" />
<PackageVersion Include="DynamicData" Version="9.0.4" />
<PackageVersion Include="DynamicData" Version="9.1.1" />
<PackageVersion Include="EmbedIO" Version="3.5.2" />
<PackageVersion Include="FluentAvalonia.ProgressRing" Version="1.69.2" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.1.0" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.2.0" />
<PackageVersion Include="HidSharp" Version="2.1.0" />
<PackageVersion Include="Humanizer.Core" Version="2.14.1" />
<PackageVersion Include="IdentityModel" Version="7.0.0" />
<PackageVersion Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageVersion Include="LiteDB" Version="5.0.21" />
<PackageVersion Include="Markdown.Avalonia.Tight" Version="11.0.2" />
<PackageVersion Include="Material.Icons.Avalonia" Version="2.1.10" />
<PackageVersion Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageVersion Include="Material.Icons.Avalonia" Version="2.2.0" />
<PackageVersion Include="McMaster.NETCore.Plugins" Version="2.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.11" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.11">
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.1" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
<PackageVersion Include="Microsoft.Win32" Version="2.0.1" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.0" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.1" />
<PackageVersion Include="NoStringEvaluating" Version="2.5.2" />
<PackageVersion Include="Octopus.Octodiff" Version="2.0.547" />
<PackageVersion Include="PropertyChanged.SourceGenerator" Version="1.1.0" />
<PackageVersion Include="Octopus.Octodiff" Version="2.0.548" />
<PackageVersion Include="PropertyChanged.SourceGenerator" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<PackageVersion Include="RGB.NET.Core" Version="3.0.0-prerelease.3" />
<PackageVersion Include="RGB.NET.Layout" Version="3.0.0-prerelease.3" />
<PackageVersion Include="RGB.NET.Presets" Version="3.0.0-prerelease.3" />
<PackageVersion Include="RawInput.Sharp" Version="0.1.3" />
<PackageVersion Include="ReactiveUI" Version="20.1.63" />
<PackageVersion Include="ReactiveUI.Validation" Version="4.1.1" />
<PackageVersion Include="Serilog" Version="4.1.0" />
<PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageVersion Include="SkiaSharp" Version="2.88.9" />
<PackageVersion Include="SkiaSharp.Vulkan.SharpVk" Version="2.88.9" />
<PackageVersion Include="Splat.DryIoc" Version="15.2.22" />
<PackageVersion Include="StrawberryShake.Server" Version="14.1.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.2.0" />
<PackageVersion Include="System.Text.Json" Version="9.0.0" />
<PackageVersion Include="TextMateSharp.Grammars" Version="1.0.64" />
<PackageVersion Include="Splat.DryIoc" Version="15.3.1" />
<PackageVersion Include="StrawberryShake.Server" Version="15.0.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.3.1" />
<PackageVersion Include="System.Text.Json" Version="9.0.1" />
<PackageVersion Include="TextMateSharp.Grammars" Version="1.0.66" />
</ItemGroup>
</Project>