Browse Source

Add updater dialog

pull/55/head
Ionite 1 year ago
parent
commit
ef0918117f
No known key found for this signature in database
  1. 5
      StabilityMatrix.Avalonia/App.axaml.cs
  2. 20
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  3. 11
      StabilityMatrix.Avalonia/DesignData/MockHttpClientFactory.cs
  4. 91
      StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs
  5. 22
      StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs
  6. 85
      StabilityMatrix.Avalonia/Views/Dialogs/UpdateDialog.axaml
  7. 18
      StabilityMatrix.Avalonia/Views/Dialogs/UpdateDialog.axaml.cs
  8. 3
      StabilityMatrix.Avalonia/Views/MainWindow.axaml
  9. 8
      StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs

5
StabilityMatrix.Avalonia/App.axaml.cs

@ -44,6 +44,7 @@ using StabilityMatrix.Core.Models.Api;
using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
using StabilityMatrix.Core.Updater;
using Application = Avalonia.Application;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
@ -125,6 +126,7 @@ public partial class App : Application
services.AddTransient<SelectModelVersionViewModel>();
services.AddTransient<SelectDataDirectoryViewModel>();
services.AddTransient<LaunchOptionsViewModel>();
services.AddSingleton<UpdateViewModel>();
// Other transients (usually sub view models)
services.AddTransient<CheckpointFolder>();
@ -144,6 +146,7 @@ public partial class App : Application
.Register(provider.GetRequiredService<SelectModelVersionViewModel>)
.Register(provider.GetRequiredService<SelectDataDirectoryViewModel>)
.Register(provider.GetRequiredService<LaunchOptionsViewModel>)
.Register(provider.GetRequiredService<UpdateViewModel>)
.Register(provider.GetRequiredService<CheckpointFolder>)
.Register(provider.GetRequiredService<CheckpointFile>)
.Register(provider.GetRequiredService<RefreshBadgeViewModel>)
@ -163,6 +166,7 @@ public partial class App : Application
// Dialogs
services.AddTransient<SelectDataDirectoryDialog>();
services.AddTransient<LaunchOptionsDialog>();
services.AddTransient<UpdateDialog>();
// Controls
services.AddTransient<RefreshBadge>();
@ -198,6 +202,7 @@ public partial class App : Application
services.AddSingleton<IPrerequisiteHelper, PrerequisiteHelper>();
services.AddSingleton<INotificationService, NotificationService>();
services.AddSingleton<IPyRunner, PyRunner>();
services.AddSingleton<IUpdateHelper, UpdateHelper>();
services.AddSingleton<MainWindowViewModel>(provider =>
new MainWindowViewModel(provider.GetRequiredService<ISettingsManager>(),

20
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Net.Http;
using AvaloniaEdit.Utils;
using Microsoft.Extensions.DependencyInjection;
using StabilityMatrix.Avalonia.Models;
@ -19,6 +20,7 @@ using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services;
using StabilityMatrix.Core.Updater;
namespace StabilityMatrix.Avalonia.DesignData;
@ -52,12 +54,17 @@ public static class DesignData
});
// General services
services.AddLogging();
services.AddSingleton<IPackageFactory, PackageFactory>()
services.AddLogging()
.AddSingleton<IPackageFactory, PackageFactory>()
.AddSingleton<IUpdateHelper, UpdateHelper>()
.AddSingleton<ModelFinder>();
// Mock services
services
.AddSingleton<INotificationService, MockNotificationService>()
.AddSingleton<ISharedFolders, MockSharedFolders>()
.AddSingleton<IDownloadService, MockDownloadService>()
.AddSingleton<ModelFinder>();
.AddSingleton<IHttpClientFactory, MockHttpClientFactory>();
// Placeholder services that nobody should need during design time
services
@ -203,6 +210,11 @@ public static class DesignData
new(new ProgressItem(Guid.NewGuid(), "Test File.exe", new ProgressReport(0.5f, "Downloading..."))),
new(new ProgressItem(Guid.NewGuid(), "Test File 2.uwu", new ProgressReport(0.25f, "Extracting...")))
};
UpdateViewModel = Services.GetRequiredService<UpdateViewModel>();
UpdateViewModel.UpdateText =
$"Stability Matrix v2.0.1 is now available! You currently have v2.0.0. Would you like to update now?";
UpdateViewModel.ReleaseNotes = "## v2.0.1\n- Fixed a bug\n- Added a feature\n- Removed a feature";
}
public static MainWindowViewModel MainWindowViewModel { get; }
@ -225,4 +237,6 @@ public static class DesignData
{
State = ProgressState.Success
};
public static UpdateViewModel UpdateViewModel { get; }
}

11
StabilityMatrix.Avalonia/DesignData/MockHttpClientFactory.cs

@ -0,0 +1,11 @@
using System.Net.Http;
namespace StabilityMatrix.Avalonia.DesignData;
public class MockHttpClientFactory : IHttpClientFactory
{
public HttpClient CreateClient(string name)
{
throw new System.NotImplementedException();
}
}

91
StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs

@ -0,0 +1,91 @@
using System;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Models.Update;
using StabilityMatrix.Core.Services;
using StabilityMatrix.Core.Updater;
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(UpdateDialog))]
public partial class UpdateViewModel : ContentDialogViewModelBase
{
private readonly ISettingsManager settingsManager;
private readonly IHttpClientFactory httpClientFactory;
private readonly IUpdateHelper updateHelper;
[ObservableProperty] private bool isUpdateAvailable;
[ObservableProperty] private UpdateInfo? updateInfo;
[ObservableProperty] private string? releaseNotes;
[ObservableProperty] private string? updateText;
[ObservableProperty] private int progressValue;
[ObservableProperty] private bool showProgressBar;
public UpdateViewModel(
ISettingsManager settingsManager,
IHttpClientFactory httpClientFactory,
IUpdateHelper updateHelper)
{
this.settingsManager = settingsManager;
this.httpClientFactory = httpClientFactory;
this.updateHelper = updateHelper;
EventManager.Instance.UpdateAvailable += (_, info) =>
{
IsUpdateAvailable = true;
UpdateInfo = info;
};
updateHelper.StartCheckingForUpdates().SafeFireAndForget();
}
public override async Task OnLoadedAsync()
{
UpdateText = $"Stability Matrix v{UpdateInfo?.Version} is now available! You currently have v{Utilities.GetAppVersion()}. Would you like to update now?";
var client = httpClientFactory.CreateClient();
var response = await client.GetAsync(UpdateInfo?.ChangelogUrl);
if (response.IsSuccessStatusCode)
{
ReleaseNotes = await response.Content.ReadAsStringAsync();
}
else
{
ReleaseNotes = "## Unable to load release notes";
}
}
[RelayCommand]
private async Task InstallUpdate()
{
if (UpdateInfo == null)
{
return;
}
ShowProgressBar = true;
UpdateText = $"Downloading update v{UpdateInfo.Version}...";
await updateHelper.DownloadUpdate(UpdateInfo, new Progress<ProgressReport>(report =>
{
ProgressValue = Convert.ToInt32(report.Percentage);
}));
UpdateText = "Update complete. Restarting Stability Matrix in 3 seconds...";
await Task.Delay(1000);
UpdateText = "Update complete. Restarting Stability Matrix in 2 seconds...";
await Task.Delay(1000);
UpdateText = "Update complete. Restarting Stability Matrix in 1 second...";
await Task.Delay(1000);
Process.Start(UpdateHelper.ExecutablePath);
App.Shutdown();
}
}

22
StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs

@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls.Primitives;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.Controls;
@ -37,6 +38,7 @@ public partial class MainWindowViewModel : ViewModelBase
private List<PageViewModelBase> footerPages = new();
public ProgressManagerViewModel ProgressManagerViewModel { get; init; }
public UpdateViewModel UpdateViewModel { get; init; }
public MainWindowViewModel(ISettingsManager settingsManager, ServiceManager<ViewModelBase> dialogFactory)
{
@ -44,6 +46,7 @@ public partial class MainWindowViewModel : ViewModelBase
this.dialogFactory = dialogFactory;
ProgressManagerViewModel = dialogFactory.Get<ProgressManagerViewModel>();
UpdateViewModel = dialogFactory.Get<UpdateViewModel>();
}
public override async Task OnLoadedAsync()
@ -133,6 +136,25 @@ public partial class MainWindowViewModel : ViewModelBase
}
}
public async Task ShowUpdateDialog()
{
var viewModel = dialogFactory.Get<UpdateViewModel>();
var dialog = new BetterContentDialog
{
ContentVerticalScrollBarVisibility = ScrollBarVisibility.Disabled,
DefaultButton = ContentDialogButton.Close,
IsPrimaryButtonEnabled = false,
IsSecondaryButtonEnabled = false,
IsFooterVisible = false,
Content = new UpdateDialog
{
DataContext = viewModel
}
};
await dialog.ShowAsync();
}
private void OnPageChangeRequested(object? sender, Type e)
{
CurrentPage = Pages.FirstOrDefault(p => p.GetType() == e);

85
StabilityMatrix.Avalonia/Views/Dialogs/UpdateDialog.axaml

@ -0,0 +1,85 @@
<controls:UserControlBase 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:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:dialogs="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
d:DataContext="{x:Static mocks:DesignData.UpdateViewModel}"
x:DataType="dialogs:UpdateViewModel"
mc:Ignorable="d" d:DesignWidth="700" d:DesignHeight="550"
x:Class="StabilityMatrix.Avalonia.Views.Dialogs.UpdateDialog">
<Grid RowDefinitions="Auto,Auto,Auto,Auto,*,Auto,Auto">
<!--<TitleBar Background="{ui:ThemeResource ApplicationBackgroundBrush}">
<TitleBar.Header>
<TextBlock Margin="16,8" Text="Stability Matrix - Update Available" />
</TitleBar.Header>
</TitleBar>-->
<TextBlock Grid.Row="0"
Text="A new version of Stability Matrix is available!"
HorizontalAlignment="Center"
FontWeight="Thin"
Margin="0,16,0,0"
FontSize="28"/>
<TextBlock Grid.Row="1"
HorizontalAlignment="Center"
FontSize="18"
TextWrapping="Wrap"
TextAlignment="Center"
Text="{Binding UpdateText}"
Margin="16,32,16,0"/>
<TextBlock Grid.Row="2"
Text="Release Notes"
FontSize="16"
IsVisible="{Binding !ShowProgressBar}"
Margin="32,16,32,0"/>
<ProgressBar Grid.Row="3"
Height="200"
Value="{Binding ProgressValue}"
IsVisible="{Binding ShowProgressBar}"
Margin="32"/>
<Grid Grid.Row="4"
Margin="8"
IsVisible="{Binding !ShowProgressBar}">
<Border Margin="32, 16"
CornerRadius="8"
Background="{DynamicResource ButtonBackgroundPressed}">
<mdxaml:MarkdownScrollViewer
Margin="16"
Markdown="{Binding ReleaseNotes, Mode=OneWay}"/>
</Border>
</Grid>
<ui:InfoBar Grid.Row="5"
Margin="64,0,64,16"
IsOpen="True"
IsClosable="False"
Title="The app will relaunch after updating" />
<StackPanel Grid.Row="6" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,16">
<Button Content="Remind Me Later"
Classes="info"
Margin="0,0,8,0"
FontSize="18"
Command="{Binding OnCloseButtonClick}"
IsEnabled="{Binding !InstallUpdateCommand.IsRunning}" />
<Button Content="Install Now"
Classes="success"
Margin="8,0,0,0"
FontSize="18"
Command="{Binding InstallUpdateCommand}" />
</StackPanel>
</Grid>
</controls:UserControlBase>

18
StabilityMatrix.Avalonia/Views/Dialogs/UpdateDialog.axaml.cs

@ -0,0 +1,18 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace StabilityMatrix.Avalonia.Views.Dialogs;
public partial class UpdateDialog : UserControl
{
public UpdateDialog()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

3
StabilityMatrix.Avalonia/Views/MainWindow.axaml

@ -79,6 +79,9 @@
<!-- Update button -->
<ui:NavigationViewItem Name="FooterUpdateItem"
Content="Update Available"
IsVisible="{Binding IsUpdateAvailable}"
IsEnabled="{Binding IsUpdateAvailable}"
DataContext="{Binding UpdateViewModel}"
Tapped="FooterUpdateItem_OnTapped">
<ui:NavigationViewItem.IconSource>
<ui:SymbolIconSource Symbol="Download" Foreground="LimeGreen"/>

8
StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs

@ -1,5 +1,6 @@
using System;
using System.Diagnostics.CodeAnalysis;
using AsyncAwaitBestPractices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
@ -7,12 +8,14 @@ using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Styling;
using Avalonia.Threading;
using FluentAvalonia.Styling;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Media;
using FluentAvalonia.UI.Windowing;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels;
using StabilityMatrix.Core.Processes;
namespace StabilityMatrix.Avalonia.Views;
@ -106,6 +109,11 @@ public partial class MainWindow : AppWindowBase
private void FooterUpdateItem_OnTapped(object? sender, TappedEventArgs e)
{
// show update window thing
if (DataContext is not MainWindowViewModel vm)
{
throw new NullReferenceException("DataContext is not MainWindowViewModel");
}
Dispatcher.UIThread.InvokeAsync(vm.ShowUpdateDialog).SafeFireAndForget();
}
private void FooterDiscordItem_OnTapped(object? sender, TappedEventArgs e)

Loading…
Cancel
Save