using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AsyncAwaitBestPractices; using Avalonia.Controls; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Exceptionless.DateTimeExtensions; using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Media.Animation; using Semver; using StabilityMatrix.Avalonia.Languages; using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.Views.Settings; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Models.Update; using StabilityMatrix.Core.Processes; using StabilityMatrix.Core.Services; using StabilityMatrix.Core.Updater; using Symbol = FluentIcons.Common.Symbol; using SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource; namespace StabilityMatrix.Avalonia.ViewModels.Settings; [View(typeof(UpdateSettingsPage))] [Singleton, ManagedService] public partial class UpdateSettingsViewModel : PageViewModelBase { private readonly IUpdateHelper updateHelper; private readonly IAccountsService accountsService; private readonly INavigationService settingsNavService; [ObservableProperty] [NotifyPropertyChangedFor(nameof(IsUpdateAvailable))] [NotifyPropertyChangedFor(nameof(HeaderText))] [NotifyPropertyChangedFor(nameof(SubtitleText))] private UpdateStatusChangedEventArgs? updateStatus; public bool IsUpdateAvailable => UpdateStatus?.LatestUpdate != null; public string HeaderText => IsUpdateAvailable ? Resources.Label_UpdateAvailable : Resources.Label_YouAreUpToDate; public string? SubtitleText => UpdateStatus is null ? null : string.Format( Resources.TextTemplate_LastChecked, UpdateStatus.CheckedAt.ToApproximateAgeString() ); [ObservableProperty] private bool isAutoCheckUpdatesEnabled = true; [ObservableProperty] [NotifyPropertyChangedFor(nameof(SelectedUpdateChannelCard))] private UpdateChannel preferredUpdateChannel = UpdateChannel.Stable; public UpdateChannelCard? SelectedUpdateChannelCard { get => AvailableUpdateChannelCards.First(c => c.UpdateChannel == PreferredUpdateChannel); set => PreferredUpdateChannel = value?.UpdateChannel ?? UpdateChannel.Stable; } public IReadOnlyList AvailableUpdateChannelCards { get; } = new UpdateChannelCard[] { new() { UpdateChannel = UpdateChannel.Development, Description = Resources.Label_UpdatesDevChannelDescription }, new() { UpdateChannel = UpdateChannel.Preview, Description = Resources.Label_UpdatesPreviewChannelDescription }, new() { UpdateChannel = UpdateChannel.Stable } }; public UpdateSettingsViewModel( ISettingsManager settingsManager, IUpdateHelper updateHelper, IAccountsService accountsService, INavigationService settingsNavService ) { this.updateHelper = updateHelper; this.accountsService = accountsService; this.settingsNavService = settingsNavService; settingsManager.RelayPropertyFor( this, vm => vm.PreferredUpdateChannel, settings => settings.PreferredUpdateChannel, true ); settingsManager.RelayPropertyFor( this, vm => vm.IsAutoCheckUpdatesEnabled, settings => settings.CheckForUpdates, true ); accountsService.LykosAccountStatusUpdate += (_, args) => { var isBetaChannelsEnabled = args.User?.IsActiveSupporter == true; foreach ( var card in AvailableUpdateChannelCards.Where(c => c.UpdateChannel > UpdateChannel.Stable) ) { card.IsSelectable = isBetaChannelsEnabled; } }; // On update status changed updateHelper.UpdateStatusChanged += (_, args) => { UpdateStatus = args; }; } /// public override async Task OnLoadedAsync() { if (UpdateStatus is null) { await CheckForUpdates(); } OnPropertyChanged(nameof(SubtitleText)); } [RelayCommand] private async Task CheckForUpdates() { if (Design.IsDesignMode) { return; } await updateHelper.CheckForUpdate(); } /// /// Verify a new channel selection is valid, else returns false. /// /// /// public bool VerifyChannelSelection(UpdateChannelCard card) { if (card.UpdateChannel == UpdateChannel.Stable) { return true; } if (accountsService.LykosStatus?.User?.IsActiveSupporter == true) { return true; } return false; } public void ShowLoginRequiredDialog() { Dispatcher .UIThread.InvokeAsync(async () => { var dialog = DialogHelper.CreateTaskDialog( "Become a Supporter", "" + "Support the Stability Matrix Team and get access to early development builds and be the first to test new features. " ); dialog.Buttons = new[] { new(Resources.Label_Accounts, TaskDialogStandardResult.OK), TaskDialogButton.CloseButton }; dialog.Commands = new[] { new TaskDialogCommand { Text = "Patreon", Description = "https://patreon.com/StabilityMatrix", Command = new RelayCommand(() => { ProcessRunner.OpenUrl("https://patreon.com/StabilityMatrix"); }) } }; if (await dialog.ShowAsync(true) is TaskDialogStandardResult.OK) { settingsNavService.NavigateTo( new SuppressNavigationTransitionInfo() ); } }) .SafeFireAndForget(); } partial void OnUpdateStatusChanged(UpdateStatusChangedEventArgs? value) { // Update the update channel cards // Use maximum version from platforms equal or lower than current foreach (var card in AvailableUpdateChannelCards) { card.LatestVersion = value ?.UpdateChannels .Where(kv => kv.Key <= card.UpdateChannel) .Select(kv => kv.Value) .MaxBy(info => info.Version, SemVersion.PrecedenceComparer) ?.Version; } } partial void OnPreferredUpdateChannelChanged(UpdateChannel value) { CheckForUpdatesCommand.ExecuteAsync(null).SafeFireAndForget(); } /// public override string Title => "Updates"; /// public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Settings, IsFilled = true }; }