From ef0918117fb6027ccbf739978aac84b96f8b2b0b Mon Sep 17 00:00:00 2001 From: Ionite Date: Sat, 15 Jul 2023 20:40:08 -0400 Subject: [PATCH] Add updater dialog --- StabilityMatrix.Avalonia/App.axaml.cs | 5 + .../DesignData/DesignData.cs | 20 +++- .../DesignData/MockHttpClientFactory.cs | 11 +++ .../ViewModels/Dialogs/UpdateViewModel.cs | 91 +++++++++++++++++++ .../ViewModels/MainWindowViewModel.cs | 22 +++++ .../Views/Dialogs/UpdateDialog.axaml | 85 +++++++++++++++++ .../Views/Dialogs/UpdateDialog.axaml.cs | 18 ++++ .../Views/MainWindow.axaml | 3 + .../Views/MainWindow.axaml.cs | 8 ++ 9 files changed, 260 insertions(+), 3 deletions(-) create mode 100644 StabilityMatrix.Avalonia/DesignData/MockHttpClientFactory.cs create mode 100644 StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs create mode 100644 StabilityMatrix.Avalonia/Views/Dialogs/UpdateDialog.axaml create mode 100644 StabilityMatrix.Avalonia/Views/Dialogs/UpdateDialog.axaml.cs diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs index 8c2d733f..bb048568 100644 --- a/StabilityMatrix.Avalonia/App.axaml.cs +++ b/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(); services.AddTransient(); services.AddTransient(); + services.AddSingleton(); // Other transients (usually sub view models) services.AddTransient(); @@ -144,6 +146,7 @@ public partial class App : Application .Register(provider.GetRequiredService) .Register(provider.GetRequiredService) .Register(provider.GetRequiredService) + .Register(provider.GetRequiredService) .Register(provider.GetRequiredService) .Register(provider.GetRequiredService) .Register(provider.GetRequiredService) @@ -163,6 +166,7 @@ public partial class App : Application // Dialogs services.AddTransient(); services.AddTransient(); + services.AddTransient(); // Controls services.AddTransient(); @@ -198,6 +202,7 @@ public partial class App : Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(provider => new MainWindowViewModel(provider.GetRequiredService(), diff --git a/StabilityMatrix.Avalonia/DesignData/DesignData.cs b/StabilityMatrix.Avalonia/DesignData/DesignData.cs index 4b1242e8..70683f85 100644 --- a/StabilityMatrix.Avalonia/DesignData/DesignData.cs +++ b/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() + services.AddLogging() + .AddSingleton() + .AddSingleton() + .AddSingleton(); + + // Mock services + services .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton(); // 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.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; } } diff --git a/StabilityMatrix.Avalonia/DesignData/MockHttpClientFactory.cs b/StabilityMatrix.Avalonia/DesignData/MockHttpClientFactory.cs new file mode 100644 index 00000000..dd0f19b8 --- /dev/null +++ b/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(); + } +} diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs new file mode 100644 index 00000000..667903d0 --- /dev/null +++ b/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(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(); + } +} diff --git a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs index e31e21f9..7edeb213 100644 --- a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs +++ b/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 footerPages = new(); public ProgressManagerViewModel ProgressManagerViewModel { get; init; } + public UpdateViewModel UpdateViewModel { get; init; } public MainWindowViewModel(ISettingsManager settingsManager, ServiceManager dialogFactory) { @@ -44,6 +46,7 @@ public partial class MainWindowViewModel : ViewModelBase this.dialogFactory = dialogFactory; ProgressManagerViewModel = dialogFactory.Get(); + UpdateViewModel = dialogFactory.Get(); } public override async Task OnLoadedAsync() @@ -133,6 +136,25 @@ public partial class MainWindowViewModel : ViewModelBase } } + public async Task ShowUpdateDialog() + { + var viewModel = dialogFactory.Get(); + 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); diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/UpdateDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/UpdateDialog.axaml new file mode 100644 index 00000000..8d30d594 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Dialogs/UpdateDialog.axaml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + +