mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 11:56:30 +00:00
Implemented most of the updating mechanism
This commit is contained in:
1 parent
0cd65a2ebf
commit
acd005e4a2
27 files changed
+1055
-383
No files matched your search
@@ -10,6 +10,7 @@ public class SettingsViewModel : MainScreenViewModel
|
||||
GeneralTabViewModel generalTabViewModel,
|
||||
PluginsTabViewModel pluginsTabViewModel,
|
||||
DevicesTabViewModel devicesTabViewModel,
|
||||
ReleasesTabViewModel releasesTabViewModel,
|
||||
AboutTabViewModel aboutTabViewModel) : base(hostScreen, "settings")
|
||||
{
|
||||
SettingTabs = new ObservableCollection<ActivatableViewModelBase>
|
||||
@@ -17,6 +18,7 @@ public class SettingsViewModel : MainScreenViewModel
|
||||
generalTabViewModel,
|
||||
pluginsTabViewModel,
|
||||
devicesTabViewModel,
|
||||
releasesTabViewModel,
|
||||
aboutTabViewModel
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<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:settings="clr-namespace:Artemis.UI.Screens.Settings"
|
||||
xmlns:updating="clr-namespace:Artemis.UI.Screens.Settings.Updating"
|
||||
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="1400"
|
||||
x:Class="Artemis.UI.Screens.Settings.ReleasesTabView"
|
||||
x:DataType="settings:ReleasesTabViewModel">
|
||||
<Grid ColumnDefinitions="300,*" Margin="0 10">
|
||||
<Border Classes="card-condensed" Grid.Column="0" Margin="0 0 10 0">
|
||||
<ListBox Items="{CompiledBinding ReleaseViewModels}" SelectedItem="{CompiledBinding SelectedReleaseViewModel}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="updating:ReleaseViewModel">
|
||||
<StackPanel Margin="4">
|
||||
<TextBlock Text="{CompiledBinding Version}" VerticalAlignment="Center" />
|
||||
<TextBlock Text="{CompiledBinding CreatedAt, StringFormat={}{0:g}}" VerticalAlignment="Center" Classes="subtitle" FontSize="13" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
|
||||
<ContentControl Grid.Column="1" Content="{CompiledBinding SelectedReleaseViewModel}"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,17 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings;
|
||||
|
||||
public class ReleasesTabView : ReactiveUserControl<ReleasesTabViewModel>
|
||||
{
|
||||
public ReleasesTabView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.DryIoc.Factories;
|
||||
using Artemis.UI.Extensions;
|
||||
using Artemis.UI.Screens.Settings.Updating;
|
||||
using Artemis.UI.Services.Updating;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.Builders;
|
||||
using Artemis.WebClient.Updating;
|
||||
using Avalonia.Threading;
|
||||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using ReactiveUI;
|
||||
using Serilog;
|
||||
using StrawberryShake;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings;
|
||||
|
||||
public class ReleasesTabViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUpdatingClient _updatingClient;
|
||||
private readonly INotificationService _notificationService;
|
||||
private readonly SourceList<IGetReleases_PublishedReleases_Nodes> _releases;
|
||||
private IGetReleases_PublishedReleases_PageInfo? _lastPageInfo;
|
||||
private bool _loading;
|
||||
private ReleaseViewModel? _selectedReleaseViewModel;
|
||||
|
||||
public ReleasesTabViewModel(ILogger logger, IUpdateService updateService, IUpdatingClient updatingClient, IReleaseVmFactory releaseVmFactory, INotificationService notificationService)
|
||||
{
|
||||
_logger = logger;
|
||||
_updatingClient = updatingClient;
|
||||
_notificationService = notificationService;
|
||||
|
||||
_releases = new SourceList<IGetReleases_PublishedReleases_Nodes>();
|
||||
_releases.Connect()
|
||||
.Sort(SortExpressionComparer<IGetReleases_PublishedReleases_Nodes>.Descending(p => p.CreatedAt))
|
||||
.Transform(r => releaseVmFactory.ReleaseListViewModel(r.Id, r.Version, r.CreatedAt))
|
||||
.ObserveOn(AvaloniaScheduler.Instance)
|
||||
.Bind(out ReadOnlyObservableCollection<ReleaseViewModel> releaseViewModels)
|
||||
.Subscribe();
|
||||
|
||||
DisplayName = "Releases";
|
||||
ReleaseViewModels = releaseViewModels;
|
||||
this.WhenActivated(async d =>
|
||||
{
|
||||
await updateService.CacheLatestRelease();
|
||||
await GetMoreReleases(d.AsCancellationToken());
|
||||
SelectedReleaseViewModel = ReleaseViewModels.FirstOrDefault();
|
||||
});
|
||||
}
|
||||
|
||||
public ReadOnlyObservableCollection<ReleaseViewModel> ReleaseViewModels { get; }
|
||||
|
||||
public ReleaseViewModel? SelectedReleaseViewModel
|
||||
{
|
||||
get => _selectedReleaseViewModel;
|
||||
set => RaiseAndSetIfChanged(ref _selectedReleaseViewModel, value);
|
||||
}
|
||||
|
||||
public bool Loading
|
||||
{
|
||||
get => _loading;
|
||||
private set => RaiseAndSetIfChanged(ref _loading, value);
|
||||
}
|
||||
|
||||
public async Task GetMoreReleases(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_lastPageInfo != null && !_lastPageInfo.HasNextPage)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Loading = true;
|
||||
|
||||
IOperationResult<IGetReleasesResult> result = await _updatingClient.GetReleases.ExecuteAsync("feature/gh-actions", Platform.Windows, 20, _lastPageInfo?.EndCursor, cancellationToken);
|
||||
if (result.Data?.PublishedReleases?.Nodes == null)
|
||||
return;
|
||||
|
||||
_lastPageInfo = result.Data.PublishedReleases.PageInfo;
|
||||
_releases.AddRange(result.Data.PublishedReleases.Nodes);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warning(e, "Failed to retrieve releases");
|
||||
_notificationService.CreateNotification()
|
||||
.WithTitle("Failed to retrieve releases")
|
||||
.WithMessage(e.Message)
|
||||
.WithSeverity(NotificationSeverity.Warning)
|
||||
.Show();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using Artemis.UI.Shared;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Updating;
|
||||
|
||||
public partial class ReleaseAvailableView : ReactiveCoreWindow<ReleaseAvailableViewModel>
|
||||
{
|
||||
public ReleaseAvailableView()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void Button_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Extensions;
|
||||
using Artemis.UI.Services.Updating;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.WebClient.Updating;
|
||||
using ReactiveUI;
|
||||
using Serilog;
|
||||
using StrawberryShake;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Updating;
|
||||
|
||||
public class ReleaseAvailableViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly string _nextReleaseId;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUpdateService _updateService;
|
||||
private readonly IUpdatingClient _updatingClient;
|
||||
private readonly INotificationService _notificationService;
|
||||
private IGetReleaseById_Release? _release;
|
||||
|
||||
public ReleaseAvailableViewModel(string nextReleaseId, ILogger logger, IUpdateService updateService, IUpdatingClient updatingClient, INotificationService notificationService)
|
||||
{
|
||||
_nextReleaseId = nextReleaseId;
|
||||
_logger = logger;
|
||||
_updateService = updateService;
|
||||
_updatingClient = updatingClient;
|
||||
_notificationService = notificationService;
|
||||
|
||||
CurrentVersion = _updateService.CurrentVersion ?? "Development build";
|
||||
Install = ReactiveCommand.Create(ExecuteInstall, this.WhenAnyValue(vm => vm.Release).Select(r => r != null));
|
||||
|
||||
this.WhenActivated(async d => await RetrieveRelease(d.AsCancellationToken()));
|
||||
}
|
||||
|
||||
private void ExecuteInstall()
|
||||
{
|
||||
_updateService.InstallRelease(_nextReleaseId);
|
||||
}
|
||||
|
||||
private async Task RetrieveRelease(CancellationToken cancellationToken)
|
||||
{
|
||||
IOperationResult<IGetReleaseByIdResult> result = await _updatingClient.GetReleaseById.ExecuteAsync(_nextReleaseId, cancellationToken);
|
||||
// Borrow GraphQLClientException for messaging, how lazy of me..
|
||||
if (result.Errors.Count > 0)
|
||||
{
|
||||
GraphQLClientException exception = new(result.Errors);
|
||||
_logger.Error(exception, "Failed to retrieve release details");
|
||||
_notificationService.CreateNotification().WithTitle("Failed to retrieve release details").WithMessage(exception.Message).Show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.Data?.Release == null)
|
||||
{
|
||||
_notificationService.CreateNotification().WithTitle("Failed to retrieve release details").WithMessage("Release not found").Show();
|
||||
return;
|
||||
}
|
||||
|
||||
Release = result.Data.Release;
|
||||
}
|
||||
|
||||
public string CurrentVersion { get; }
|
||||
|
||||
public IGetReleaseById_Release? Release
|
||||
{
|
||||
get => _release;
|
||||
set => RaiseAndSetIfChanged(ref _release, value);
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> Install { get; }
|
||||
public ReactiveCommand<Unit, Unit> AskLater { get; }
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<controls:CoreWindow 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:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:updating="clr-namespace:Artemis.UI.Screens.Settings.Updating"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Artemis.UI.Screens.Settings.Updating.ReleaseInstallerView"
|
||||
x:DataType="updating:ReleaseInstallerViewModel"
|
||||
Title="Artemis | Updating"
|
||||
ShowAsDialog="True"
|
||||
Width="465" Height="260"
|
||||
Padding="15"
|
||||
CanResize="False"
|
||||
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto" Width="450" Height="200">
|
||||
<TextBlock Grid.Row="0" Classes="h4" TextWrapping="Wrap">
|
||||
Downloading & installing update...
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="1" Classes="subtitle" TextWrapping="Wrap">
|
||||
This should not take long, when finished Artemis must restart.
|
||||
</TextBlock>
|
||||
|
||||
<StackPanel Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Stretch" IsVisible="{CompiledBinding !Ready}">
|
||||
<ProgressBar Value="{CompiledBinding OverallProgress}" ></ProgressBar>
|
||||
<ProgressBar Margin="0 15 0 0" Value="{CompiledBinding StepProgress}"></ProgressBar>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="2" IsVisible="{CompiledBinding Ready}" VerticalAlignment="Top" Margin="0 15 0 0">Done, click restart to apply the update 🫡</TextBlock>
|
||||
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" Spacing="15" Grid.Row="3" Margin="0 15 0 0" Height="30">
|
||||
<CheckBox IsVisible="{CompiledBinding !Ready}" IsChecked="{CompiledBinding RestartWhenFinished}">Restart when finished</CheckBox>
|
||||
<Button IsVisible="{CompiledBinding Ready}" Command="{CompiledBinding Restart}" Classes="accent">Restart</Button>
|
||||
<Button Click="Cancel_OnClick">Cancel</Button>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</controls:CoreWindow>
|
||||
@@ -1,28 +0,0 @@
|
||||
using Artemis.UI.Shared;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Updating;
|
||||
|
||||
public partial class ReleaseInstallerView : ReactiveCoreWindow<ReleaseInstallerViewModel>
|
||||
{
|
||||
public ReleaseInstallerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void Cancel_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Extensions;
|
||||
using Artemis.UI.Services.Updating;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Updating;
|
||||
|
||||
public class ReleaseInstallerViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly ReleaseInstaller _releaseInstaller;
|
||||
private readonly IWindowService _windowService;
|
||||
private ObservableAsPropertyHelper<float>? _overallProgress;
|
||||
private ObservableAsPropertyHelper<float>? _stepProgress;
|
||||
private bool _ready;
|
||||
private bool _restartWhenFinished;
|
||||
|
||||
public ReleaseInstallerViewModel(ReleaseInstaller releaseInstaller, IWindowService windowService)
|
||||
{
|
||||
_releaseInstaller = releaseInstaller;
|
||||
_windowService = windowService;
|
||||
|
||||
Restart = ReactiveCommand.Create(() => Utilities.ApplyUpdate(false));
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
_overallProgress = Observable.FromEventPattern<float>(x => _releaseInstaller.OverallProgress.ProgressChanged += x, x => _releaseInstaller.OverallProgress.ProgressChanged -= x)
|
||||
.Select(e => e.EventArgs)
|
||||
.ToProperty(this, vm => vm.OverallProgress)
|
||||
.DisposeWith(d);
|
||||
_stepProgress = Observable.FromEventPattern<float>(x => _releaseInstaller.StepProgress.ProgressChanged += x, x => _releaseInstaller.StepProgress.ProgressChanged -= x)
|
||||
.Select(e => e.EventArgs)
|
||||
.ToProperty(this, vm => vm.StepProgress)
|
||||
.DisposeWith(d);
|
||||
|
||||
Task.Run(() => InstallUpdate(d.AsCancellationToken()));
|
||||
});
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> Restart { get; }
|
||||
|
||||
public float OverallProgress => _overallProgress?.Value ?? 0;
|
||||
public float StepProgress => _stepProgress?.Value ?? 0;
|
||||
|
||||
public bool Ready
|
||||
{
|
||||
get => _ready;
|
||||
set => RaiseAndSetIfChanged(ref _ready, value);
|
||||
}
|
||||
|
||||
public bool RestartWhenFinished
|
||||
{
|
||||
get => _restartWhenFinished;
|
||||
set => RaiseAndSetIfChanged(ref _restartWhenFinished, value);
|
||||
}
|
||||
|
||||
private async Task InstallUpdate(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _releaseInstaller.InstallAsync(cancellationToken);
|
||||
Ready = true;
|
||||
if (RestartWhenFinished)
|
||||
Utilities.ApplyUpdate(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_windowService.ShowExceptionDialog("Something went wrong while installing the update", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+157
-47
@@ -1,49 +1,163 @@
|
||||
<controls:CoreWindow 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:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:updating="clr-namespace:Artemis.UI.Screens.Settings.Updating"
|
||||
xmlns:avalonia="clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:mdc="clr-namespace:Markdown.Avalonia.Controls;assembly=Markdown.Avalonia"
|
||||
xmlns:mde="clr-namespace:Markdown.Avalonia.Extensions;assembly=Markdown.Avalonia"
|
||||
xmlns:ctxt="clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia"
|
||||
x:Class="Artemis.UI.Screens.Settings.Updating.ReleaseAvailableView"
|
||||
x:DataType="updating:ReleaseAvailableViewModel"
|
||||
Title="Artemis | Update available"
|
||||
Width="750"
|
||||
Height="750"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Grid Margin="15" RowDefinitions="Auto,*,Auto">
|
||||
<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:settings="clr-namespace:Artemis.UI.Screens.Settings"
|
||||
xmlns:updating="clr-namespace:Artemis.UI.Screens.Settings.Updating"
|
||||
xmlns:avalonia="clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia"
|
||||
xmlns:mdc="clr-namespace:Markdown.Avalonia.Controls;assembly=Markdown.Avalonia"
|
||||
xmlns:mde="clr-namespace:Markdown.Avalonia.Extensions;assembly=Markdown.Avalonia"
|
||||
xmlns:ctxt="clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia"
|
||||
xmlns:avalonia1="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="1400"
|
||||
x:Class="Artemis.UI.Screens.Settings.Updating.ReleaseView"
|
||||
x:DataType="updating:ReleaseViewModel">
|
||||
<UserControl.Resources>
|
||||
<converters:BytesToStringConverter x:Key="BytesToStringConverter" />
|
||||
</UserControl.Resources>
|
||||
<UserControl.Styles>
|
||||
<Style Selector=":is(Control).fade-in">
|
||||
<Setter Property="Opacity" Value="0"></Setter>
|
||||
</Style>
|
||||
<Style Selector=":is(Control).fade-in[IsVisible=True]">
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:00:00.250" FillMode="Forward" Easing="CubicEaseInOut">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="Opacity" Value="0.0" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="Opacity" Value="1.0" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
<Style Selector="Grid.info-container">
|
||||
<Setter Property="Margin" Value="10" />
|
||||
</Style>
|
||||
<Style Selector="avalonia1|MaterialIcon.info-icon">
|
||||
<Setter Property="VerticalAlignment" Value="Top" />
|
||||
<Setter Property="Margin" Value="0 3 10 0" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock.info-title">
|
||||
<Setter Property="Margin" Value="0 0 0 5" />
|
||||
<Setter Property="Opacity" Value="0.8" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock.info-body">
|
||||
</Style>
|
||||
<Style Selector="TextBlock.info-link">
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource SystemAccentColorLight3}" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock.info-link:pointerover">
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource SystemAccentColorLight1}" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<TextBlock Grid.Row="0" Classes="h4">
|
||||
A new Artemis update is available! 🥳
|
||||
</TextBlock>
|
||||
<Grid RowDefinitions="Auto,*" IsVisible="{CompiledBinding !Loading}" Classes="fade-in">
|
||||
<Border Grid.Row="0" Classes="card" Margin="0 0 0 10">
|
||||
<StackPanel>
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Classes="h4 no-margin">Release info</TextBlock>
|
||||
|
||||
<StackPanel Grid.Row="1" IsVisible="{CompiledBinding Release, Converter={x:Static ObjectConverters.IsNull}}" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock>Retrieving release...</TextBlock>
|
||||
<ProgressBar IsIndeterminate="True"></ProgressBar>
|
||||
</StackPanel>
|
||||
<Panel Grid.Column="1" IsVisible="{CompiledBinding InstallationAvailable}">
|
||||
<!-- Install progress -->
|
||||
<Grid ColumnDefinitions="*,*"
|
||||
RowDefinitions="*,*"
|
||||
IsVisible="{CompiledBinding InstallationInProgress}">
|
||||
<ProgressBar Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Width="300"
|
||||
Value="{CompiledBinding ReleaseInstaller.Progress, FallbackValue=0}">
|
||||
</ProgressBar>
|
||||
<TextBlock Grid.Column="0"
|
||||
Grid.Row="1"
|
||||
Classes="subtitle"
|
||||
TextAlignment="Right"
|
||||
Text="{CompiledBinding ReleaseInstaller.Status, FallbackValue=Installing}" />
|
||||
<Button Grid.Column="1" Grid.Row="0" Grid.RowSpan="2"
|
||||
Classes="accent"
|
||||
Margin="15 0 0 0"
|
||||
Width="80"
|
||||
VerticalAlignment="Center"
|
||||
Command="{CompiledBinding CancelInstall}">
|
||||
Cancel
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Border Grid.Row="1" Classes="card" IsVisible="{CompiledBinding Release, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<StackPanel Grid.Row="0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="You are currently running version " />
|
||||
<TextBlock Text="{CompiledBinding CurrentVersion, Mode=OneWay}"></TextBlock>
|
||||
<TextBlock Text=" while the latest build is " />
|
||||
<TextBlock Text="{CompiledBinding Release.Version, Mode=OneWay, FallbackValue='Unknown'}"></TextBlock>
|
||||
<TextBlock Text="." />
|
||||
</StackPanel>
|
||||
<Panel IsVisible="{CompiledBinding !InstallationInProgress}" HorizontalAlignment="Right">
|
||||
<!-- Install button -->
|
||||
<Button Classes="accent"
|
||||
Width="80"
|
||||
Command="{CompiledBinding Install}"
|
||||
IsVisible="{CompiledBinding !InstallationFinished}">
|
||||
Install
|
||||
</Button>
|
||||
|
||||
<TextBlock Text="Updating Artemis will give you the latest bug(fixes), features and improvements." />
|
||||
<Separator Classes="card-separator" />
|
||||
</StackPanel>
|
||||
<!-- Restart button -->
|
||||
<Grid ColumnDefinitions="*,*" IsVisible="{CompiledBinding InstallationFinished}">
|
||||
<TextBlock Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Classes="subtitle"
|
||||
TextAlignment="Right"
|
||||
VerticalAlignment="Center">
|
||||
Ready, restart to install
|
||||
</TextBlock>
|
||||
<Button Grid.Column="1" Grid.Row="0"
|
||||
Classes="accent"
|
||||
Margin="15 0 0 0"
|
||||
Width="80"
|
||||
Command="{CompiledBinding Restart}"
|
||||
IsVisible="{CompiledBinding InstallationFinished}">
|
||||
Restart
|
||||
</Button>
|
||||
</Grid>
|
||||
</Panel>
|
||||
</Panel>
|
||||
|
||||
<avalonia:MarkdownScrollViewer Grid.Row="1"
|
||||
VerticalAlignment="Top"
|
||||
Markdown="{CompiledBinding Release.Changelog}">
|
||||
</Grid>
|
||||
<Separator Classes="card-separator" />
|
||||
<Grid Margin="-5 -10" ColumnDefinitions="*,*,*">
|
||||
<Grid Grid.Column="0" ColumnDefinitions="*,*" RowDefinitions="*,*,*" Classes="info-container" HorizontalAlignment="Left">
|
||||
<avalonia1:MaterialIcon Kind="Calendar" Grid.Column="0" Grid.RowSpan="2" Classes="info-icon" />
|
||||
<TextBlock Grid.Column="1" Grid.Row="0" Classes="info-title">Release date</TextBlock>
|
||||
<TextBlock Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
Classes="info-body"
|
||||
Text="{CompiledBinding CreatedAt, StringFormat={}{0:g}, FallbackValue=Loading...}" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Column="1" ColumnDefinitions="*,*" RowDefinitions="*,*" Classes="info-container" HorizontalAlignment="Center">
|
||||
<avalonia1:MaterialIcon Kind="Git" Grid.Column="0" Grid.RowSpan="2" Classes="info-icon" />
|
||||
<TextBlock Grid.Column="1" Grid.Row="0" Classes="info-title">Source</TextBlock>
|
||||
<TextBlock Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
Classes="info-body info-link"
|
||||
Cursor="Hand"
|
||||
PointerReleased="InputElement_OnPointerReleased"
|
||||
Text="{CompiledBinding ShortCommit, FallbackValue=Loading...}" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Column="2" ColumnDefinitions="*,*" RowDefinitions="*,*" Classes="info-container" HorizontalAlignment="Right">
|
||||
<avalonia1:MaterialIcon Kind="BoxOutline" Grid.Column="0" Grid.RowSpan="2" Classes="info-icon" />
|
||||
<TextBlock Grid.Column="1" Grid.Row="0" Classes="info-title">File size</TextBlock>
|
||||
<TextBlock Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
Classes="info-body"
|
||||
Text="{CompiledBinding FileSize, Converter={StaticResource BytesToStringConverter}, Mode=OneWay, FallbackValue=Loading...}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="1" Classes="card">
|
||||
<Grid RowDefinitions="Auto,Auto,*">
|
||||
<TextBlock Grid.Row="0" Classes="h5 no-margin">Release notes</TextBlock>
|
||||
<Separator Grid.Row="1" Classes="card-separator" />
|
||||
|
||||
<avalonia:MarkdownScrollViewer Grid.Row="2" Markdown="{CompiledBinding Changelog}">
|
||||
<avalonia:MarkdownScrollViewer.Styles>
|
||||
<Style Selector="ctxt|CTextBlock">
|
||||
<Style.Setters>
|
||||
@@ -207,10 +321,6 @@
|
||||
</avalonia:MarkdownScrollViewer>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" Spacing="5" Grid.Row="2" Margin="0 15 0 0">
|
||||
<Button Classes="accent" Command="{CompiledBinding Install}" Click="Button_OnClick">Install update</Button>
|
||||
<Button Click="Button_OnClick">Ask later</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</controls:CoreWindow>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,23 @@
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Updating;
|
||||
|
||||
public class ReleaseView : ReactiveUserControl<ReleaseViewModel>
|
||||
{
|
||||
public ReleaseView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
ViewModel?.NavigateToSource();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Extensions;
|
||||
using Artemis.UI.Services.Updating;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.Builders;
|
||||
using Artemis.WebClient.Updating;
|
||||
using ReactiveUI;
|
||||
using Serilog;
|
||||
using StrawberryShake;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Updating;
|
||||
|
||||
public class ReleaseViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly INotificationService _notificationService;
|
||||
private readonly string _releaseId;
|
||||
private readonly Platform _updatePlatform;
|
||||
private readonly IUpdatingClient _updatingClient;
|
||||
private readonly IWindowService _windowService;
|
||||
private CancellationTokenSource? _installerCts;
|
||||
private string _changelog = string.Empty;
|
||||
private string _commit = string.Empty;
|
||||
private string _shortCommit = string.Empty;
|
||||
private long _fileSize;
|
||||
private bool _installationAvailable;
|
||||
private bool _installationFinished;
|
||||
private bool _installationInProgress;
|
||||
private bool _loading = true;
|
||||
private bool _retrievedDetails;
|
||||
|
||||
public ReleaseViewModel(string releaseId,
|
||||
string version,
|
||||
DateTimeOffset createdAt,
|
||||
ILogger logger,
|
||||
IUpdatingClient updatingClient,
|
||||
INotificationService notificationService,
|
||||
IUpdateService updateService,
|
||||
IWindowService windowService)
|
||||
{
|
||||
_releaseId = releaseId;
|
||||
_logger = logger;
|
||||
_updatingClient = updatingClient;
|
||||
_notificationService = notificationService;
|
||||
_windowService = windowService;
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
_updatePlatform = Platform.Windows;
|
||||
else if (OperatingSystem.IsLinux())
|
||||
_updatePlatform = Platform.Linux;
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
_updatePlatform = Platform.Osx;
|
||||
else
|
||||
throw new PlatformNotSupportedException("Cannot auto update on the current platform");
|
||||
|
||||
Version = version;
|
||||
CreatedAt = createdAt;
|
||||
ReleaseInstaller = updateService.GetReleaseInstaller(_releaseId);
|
||||
|
||||
Install = ReactiveCommand.CreateFromTask(ExecuteInstall);
|
||||
Restart = ReactiveCommand.Create(() => Utilities.ApplyUpdate(false));
|
||||
CancelInstall = ReactiveCommand.Create(() => _installerCts?.Cancel());
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
// There's no point in running anything but the latest version of the current channel.
|
||||
// Perhaps later that won't be true anymore, then we could consider allowing to install
|
||||
// older versions with compatible database versions.
|
||||
InstallationAvailable = updateService.CachedLatestRelease?.Id == _releaseId;
|
||||
RetrieveDetails(d.AsCancellationToken()).ToObservable();
|
||||
Disposable.Create(_installerCts, cts => cts?.Cancel()).DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> Restart { get; set; }
|
||||
public ReactiveCommand<Unit, Unit> Install { get; }
|
||||
public ReactiveCommand<Unit, Unit> CancelInstall { get; }
|
||||
|
||||
public string Version { get; }
|
||||
public DateTimeOffset CreatedAt { get; }
|
||||
public ReleaseInstaller ReleaseInstaller { get; }
|
||||
|
||||
public string Changelog
|
||||
{
|
||||
get => _changelog;
|
||||
set => RaiseAndSetIfChanged(ref _changelog, value);
|
||||
}
|
||||
|
||||
public string Commit
|
||||
{
|
||||
get => _commit;
|
||||
set => RaiseAndSetIfChanged(ref _commit, value);
|
||||
}
|
||||
|
||||
public string ShortCommit
|
||||
{
|
||||
get => _shortCommit;
|
||||
set => RaiseAndSetIfChanged(ref _shortCommit, value);
|
||||
}
|
||||
|
||||
public long FileSize
|
||||
{
|
||||
get => _fileSize;
|
||||
set => RaiseAndSetIfChanged(ref _fileSize, value);
|
||||
}
|
||||
|
||||
public bool Loading
|
||||
{
|
||||
get => _loading;
|
||||
private set => RaiseAndSetIfChanged(ref _loading, value);
|
||||
}
|
||||
|
||||
public bool InstallationAvailable
|
||||
{
|
||||
get => _installationAvailable;
|
||||
set => RaiseAndSetIfChanged(ref _installationAvailable, value);
|
||||
}
|
||||
|
||||
public bool InstallationInProgress
|
||||
{
|
||||
get => _installationInProgress;
|
||||
set => RaiseAndSetIfChanged(ref _installationInProgress, value);
|
||||
}
|
||||
|
||||
public bool InstallationFinished
|
||||
{
|
||||
get => _installationFinished;
|
||||
set => RaiseAndSetIfChanged(ref _installationFinished, value);
|
||||
}
|
||||
|
||||
public void NavigateToSource()
|
||||
{
|
||||
Utilities.OpenUrl($"https://github.com/Artemis-RGB/Artemis/commit/{Commit}");
|
||||
}
|
||||
|
||||
private async Task ExecuteInstall(CancellationToken cancellationToken)
|
||||
{
|
||||
_installerCts = new CancellationTokenSource();
|
||||
try
|
||||
{
|
||||
InstallationInProgress = true;
|
||||
await ReleaseInstaller.InstallAsync(_installerCts.Token);
|
||||
InstallationFinished = true;
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_windowService.ShowExceptionDialog("Failed to install update", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
InstallationInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RetrieveDetails(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_retrievedDetails)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Loading = true;
|
||||
|
||||
IOperationResult<IGetReleaseByIdResult> result = await _updatingClient.GetReleaseById.ExecuteAsync(_releaseId, cancellationToken);
|
||||
IGetReleaseById_PublishedRelease? release = result.Data?.PublishedRelease;
|
||||
if (release == null)
|
||||
return;
|
||||
|
||||
Changelog = release.Changelog;
|
||||
Commit = release.Commit;
|
||||
ShortCommit = release.Commit.Substring(0, 7);
|
||||
FileSize = release.Artifacts.FirstOrDefault(a => a.Platform == _updatePlatform)?.FileInfo.DownloadSize ?? 0;
|
||||
|
||||
_retrievedDetails = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warning(e, "Failed to retrieve release details");
|
||||
_notificationService.CreateNotification()
|
||||
.WithTitle("Failed to retrieve details")
|
||||
.WithMessage(e.Message)
|
||||
.WithSeverity(NotificationSeverity.Warning)
|
||||
.Show();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user