Ionite
1 year ago
19 changed files with 773 additions and 30 deletions
@ -0,0 +1,32 @@ |
|||||||
|
using System; |
||||||
|
using System.Globalization; |
||||||
|
using Avalonia.Data.Converters; |
||||||
|
using StabilityMatrix.Core.Extensions; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Converters; |
||||||
|
|
||||||
|
public class EnumStringConverter : IValueConverter |
||||||
|
{ |
||||||
|
/// <inheritdoc /> |
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||||
|
{ |
||||||
|
if (value is not Enum enumValue) |
||||||
|
return null; |
||||||
|
|
||||||
|
return enumValue.GetStringValue(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
public object? ConvertBack( |
||||||
|
object? value, |
||||||
|
Type targetType, |
||||||
|
object? parameter, |
||||||
|
CultureInfo culture |
||||||
|
) |
||||||
|
{ |
||||||
|
if (value is not string stringValue) |
||||||
|
return null; |
||||||
|
|
||||||
|
return Enum.Parse(targetType, stringValue); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
using System; |
||||||
|
using System.Globalization; |
||||||
|
using Avalonia.Data; |
||||||
|
using Avalonia.Data.Converters; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Converters; |
||||||
|
|
||||||
|
public class EnumToBooleanConverter : IValueConverter |
||||||
|
{ |
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||||
|
{ |
||||||
|
return value?.Equals(parameter); |
||||||
|
} |
||||||
|
|
||||||
|
public object? ConvertBack( |
||||||
|
object? value, |
||||||
|
Type targetType, |
||||||
|
object? parameter, |
||||||
|
CultureInfo culture |
||||||
|
) |
||||||
|
{ |
||||||
|
return value?.Equals(true) == true ? parameter : BindingOperations.DoNothing; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel; |
||||||
|
using Semver; |
||||||
|
using StabilityMatrix.Core.Extensions; |
||||||
|
using StabilityMatrix.Core.Helper; |
||||||
|
using StabilityMatrix.Core.Models.Update; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Models; |
||||||
|
|
||||||
|
public partial class UpdateChannelCard : ObservableObject |
||||||
|
{ |
||||||
|
public UpdateChannel UpdateChannel { get; init; } |
||||||
|
|
||||||
|
public string DisplayName => UpdateChannel.GetStringValue(); |
||||||
|
|
||||||
|
public string? Description { get; init; } |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
[NotifyPropertyChangedFor(nameof(LatestVersionString))] |
||||||
|
[NotifyPropertyChangedFor(nameof(IsLatestVersionUpdateable))] |
||||||
|
private SemVersion? latestVersion; |
||||||
|
|
||||||
|
public string? LatestVersionString => |
||||||
|
LatestVersion is null ? null : $"Latest: v{LatestVersion}"; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private bool isSelectable = true; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Whether the <see cref="LatestVersion"/> is available for update. |
||||||
|
/// </summary> |
||||||
|
public bool IsLatestVersionUpdateable |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
if (LatestVersion is null) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
switch (LatestVersion.ComparePrecedenceTo(Compat.AppVersion)) |
||||||
|
{ |
||||||
|
case > 0: |
||||||
|
// Newer version available |
||||||
|
return true; |
||||||
|
case 0: |
||||||
|
{ |
||||||
|
// Same version available, check if we both have commit hash metadata |
||||||
|
var updateHash = LatestVersion.Metadata; |
||||||
|
var appHash = Compat.AppVersion.Metadata; |
||||||
|
// If different, we can update |
||||||
|
if (updateHash != appHash) |
||||||
|
{ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,237 @@ |
|||||||
|
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 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.FluentAvalonia.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<SettingsViewModel> 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<UpdateChannelCard> 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<SettingsViewModel> 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 isSelectable = args.IsPatreonConnected; |
||||||
|
|
||||||
|
foreach ( |
||||||
|
var card in AvailableUpdateChannelCards.Where( |
||||||
|
c => c.UpdateChannel > UpdateChannel.Stable |
||||||
|
) |
||||||
|
) |
||||||
|
{ |
||||||
|
card.IsSelectable = isSelectable; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// On update status changed |
||||||
|
updateHelper.UpdateStatusChanged += (_, args) => |
||||||
|
{ |
||||||
|
UpdateStatus = args; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
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(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Verify a new channel selection is valid, else returns false. |
||||||
|
/// </summary> |
||||||
|
/// <param name="card"></param> |
||||||
|
/// <returns></returns> |
||||||
|
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", "uwu"); |
||||||
|
|
||||||
|
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<AccountSettingsViewModel>( |
||||||
|
new SuppressNavigationTransitionInfo() |
||||||
|
); |
||||||
|
} |
||||||
|
}) |
||||||
|
.SafeFireAndForget(); |
||||||
|
} |
||||||
|
|
||||||
|
partial void OnUpdateStatusChanged(UpdateStatusChangedEventArgs? value) |
||||||
|
{ |
||||||
|
// Update the update channel cards |
||||||
|
foreach (var card in AvailableUpdateChannelCards) |
||||||
|
{ |
||||||
|
card.LatestVersion = value?.UpdateChannels |
||||||
|
.GetValueOrDefault(card.UpdateChannel) |
||||||
|
?.Version; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
partial void OnPreferredUpdateChannelChanged(UpdateChannel oldValue, UpdateChannel newValue) |
||||||
|
{ |
||||||
|
if (newValue == UpdateChannel.Stable) |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (accountsService.LykosStatus?.User?.IsActiveSupporter == true) |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
PreferredUpdateChannel = UpdateChannel.Stable; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
public override string Title => "Updates"; |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
public override IconSource IconSource => |
||||||
|
new SymbolIconSource { Symbol = Symbol.Settings, IsFilled = true }; |
||||||
|
} |
@ -0,0 +1,143 @@ |
|||||||
|
<controls:UserControlBase |
||||||
|
x:Class="StabilityMatrix.Avalonia.Views.Settings.UpdateSettingsPage" |
||||||
|
xmlns="https://github.com/avaloniaui" |
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||||
|
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" |
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||||
|
xmlns:fluentIcons="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia" |
||||||
|
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" |
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||||
|
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" |
||||||
|
xmlns:models="clr-namespace:StabilityMatrix.Avalonia.Models" |
||||||
|
xmlns:sg="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia" |
||||||
|
xmlns:sm="clr-namespace:StabilityMatrix.Avalonia" |
||||||
|
xmlns:system="clr-namespace:System;assembly=System.Runtime" |
||||||
|
xmlns:ui="using:FluentAvalonia.UI.Controls" |
||||||
|
xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels" |
||||||
|
xmlns:vmSettings="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Settings" |
||||||
|
d:DataContext="{x:Static mocks:DesignData.UpdateSettingsViewModel}" |
||||||
|
d:DesignHeight="550" |
||||||
|
d:DesignWidth="800" |
||||||
|
x:DataType="vmSettings:UpdateSettingsViewModel" |
||||||
|
mc:Ignorable="d"> |
||||||
|
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto"> |
||||||
|
<StackPanel Margin="16,16,24,16" Spacing="2"> |
||||||
|
|
||||||
|
<sg:SpacedGrid Margin="0,4,0,16" ColumnDefinitions="*,Auto"> |
||||||
|
<StackPanel Orientation="Horizontal" Spacing="16"> |
||||||
|
<fluentIcons:SymbolIcon |
||||||
|
Width="64" |
||||||
|
Height="64" |
||||||
|
FontSize="64" |
||||||
|
IsFilled="True" |
||||||
|
Symbol="ArrowSync" /> |
||||||
|
|
||||||
|
<StackPanel VerticalAlignment="Center"> |
||||||
|
<TextBlock |
||||||
|
Margin="-1,0,0,0" |
||||||
|
HorizontalAlignment="Left" |
||||||
|
Text="{Binding HeaderText}" |
||||||
|
Theme="{DynamicResource SubtitleTextBlockStyle}" /> |
||||||
|
<TextBlock |
||||||
|
HorizontalAlignment="Left" |
||||||
|
Foreground="{DynamicResource TextFillColorTertiaryBrush}" |
||||||
|
Text="{Binding SubtitleText}" /> |
||||||
|
</StackPanel> |
||||||
|
</StackPanel> |
||||||
|
|
||||||
|
<Button |
||||||
|
Grid.Column="1" |
||||||
|
Classes="accent" |
||||||
|
Content="{x:Static lang:Resources.Action_CheckForUpdates}" |
||||||
|
Command="{Binding CheckForUpdatesCommand}" |
||||||
|
HorizontalAlignment="Right"> |
||||||
|
</Button> |
||||||
|
</sg:SpacedGrid> |
||||||
|
|
||||||
|
<!-- Auto updates toggle --> |
||||||
|
<ui:SettingsExpander |
||||||
|
Description="Periodically checks for updates" |
||||||
|
Header="Notify when updates are ready to install"> |
||||||
|
<ui:SettingsExpander.IconSource> |
||||||
|
<fluentIcons:SymbolIconSource Symbol="Megaphone" /> |
||||||
|
</ui:SettingsExpander.IconSource> |
||||||
|
<ui:SettingsExpander.Footer> |
||||||
|
<ToggleSwitch IsChecked="{Binding IsAutoCheckUpdatesEnabled}" /> |
||||||
|
</ui:SettingsExpander.Footer> |
||||||
|
</ui:SettingsExpander> |
||||||
|
|
||||||
|
<!-- Channel radio buttons --> |
||||||
|
<ui:SettingsExpander Header="Preferred Update Channel" IsExpanded="True"> |
||||||
|
<ui:SettingsExpander.IconSource> |
||||||
|
<fluentIcons:SymbolIconSource Symbol="Branch" /> |
||||||
|
</ui:SettingsExpander.IconSource> |
||||||
|
|
||||||
|
<ui:SettingsExpanderItem> |
||||||
|
<ListBox |
||||||
|
SelectionChanged="ChannelListBox_OnSelectionChanged" |
||||||
|
IsEnabled="{Binding IsAutoCheckUpdatesEnabled}" |
||||||
|
ItemContainerTheme="{DynamicResource ListBoxItemBorderlessTheme}" |
||||||
|
ItemsSource="{Binding AvailableUpdateChannelCards}" |
||||||
|
SelectedItem="{Binding SelectedUpdateChannelCard}"> |
||||||
|
<ListBox.ItemTemplate> |
||||||
|
<DataTemplate DataType="{x:Type models:UpdateChannelCard}"> |
||||||
|
<RadioButton |
||||||
|
GroupName="UpdateChannel" |
||||||
|
IsEnabled="{Binding IsSelectable}" |
||||||
|
IsChecked="{Binding $parent[ListBoxItem].IsSelected}" > |
||||||
|
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Left"> |
||||||
|
<StackPanel Orientation="Horizontal" Spacing="5"> |
||||||
|
<TextBlock |
||||||
|
IsEnabled="False" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Text="{Binding DisplayName}" /> |
||||||
|
<Ellipse |
||||||
|
HorizontalAlignment="Left" |
||||||
|
VerticalAlignment="Center" |
||||||
|
IsVisible="{Binding LatestVersionString, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" |
||||||
|
Width="4" |
||||||
|
Height="4" |
||||||
|
Fill="{DynamicResource TextFillColorTertiaryBrush}" /> |
||||||
|
<TextBlock |
||||||
|
VerticalAlignment="Center" |
||||||
|
FontSize="13" |
||||||
|
Foreground="{DynamicResource TextFillColorTertiaryBrush}" |
||||||
|
IsVisible="{Binding LatestVersionString, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" |
||||||
|
Text="{Binding LatestVersionString}" /> |
||||||
|
|
||||||
|
<!-- Update available icon --> |
||||||
|
<fluentIcons:SymbolIcon |
||||||
|
ToolTip.Tip="{x:Static lang:Resources.Label_UpdateAvailable}" |
||||||
|
IsVisible="{Binding IsLatestVersionUpdateable}" |
||||||
|
VerticalAlignment="Center" |
||||||
|
FontSize="16" |
||||||
|
Foreground="#3592c4" |
||||||
|
Symbol="ArrowCircleUpRight" /> |
||||||
|
</StackPanel> |
||||||
|
|
||||||
|
<TextBlock |
||||||
|
FontSize="13" |
||||||
|
TextAlignment="Start" |
||||||
|
Margin="0,0,0,0" |
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" |
||||||
|
Text="{Binding Description}" |
||||||
|
Theme="{DynamicResource CaptionTextBlockStyle}" /> |
||||||
|
</StackPanel> |
||||||
|
</RadioButton> |
||||||
|
</DataTemplate> |
||||||
|
</ListBox.ItemTemplate> |
||||||
|
<ListBox.ItemsPanel> |
||||||
|
<ItemsPanelTemplate> |
||||||
|
<StackPanel Spacing="8" /> |
||||||
|
</ItemsPanelTemplate> |
||||||
|
</ListBox.ItemsPanel> |
||||||
|
</ListBox> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
|
||||||
|
|
||||||
|
</ui:SettingsExpander> |
||||||
|
|
||||||
|
</StackPanel> |
||||||
|
</ScrollViewer> |
||||||
|
</controls:UserControlBase> |
@ -0,0 +1,41 @@ |
|||||||
|
using System.Linq; |
||||||
|
using Avalonia.Controls; |
||||||
|
using StabilityMatrix.Avalonia.Controls; |
||||||
|
using StabilityMatrix.Avalonia.Models; |
||||||
|
using StabilityMatrix.Avalonia.ViewModels.Settings; |
||||||
|
using StabilityMatrix.Core.Attributes; |
||||||
|
using StabilityMatrix.Core.Models.Update; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Views.Settings; |
||||||
|
|
||||||
|
[Singleton] |
||||||
|
public partial class UpdateSettingsPage : UserControlBase |
||||||
|
{ |
||||||
|
public UpdateSettingsPage() |
||||||
|
{ |
||||||
|
InitializeComponent(); |
||||||
|
} |
||||||
|
|
||||||
|
private void ChannelListBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) |
||||||
|
{ |
||||||
|
var listBox = (ListBox)sender!; |
||||||
|
|
||||||
|
if (e.AddedItems.Count == 0 || e.AddedItems[0] is not UpdateChannelCard item) |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
var vm = (UpdateSettingsViewModel)DataContext!; |
||||||
|
|
||||||
|
if (!vm.VerifyChannelSelection(item)) |
||||||
|
{ |
||||||
|
listBox.Selection.Clear(); |
||||||
|
|
||||||
|
listBox.Selection.SelectedItem = vm.AvailableUpdateChannelCards.First( |
||||||
|
c => c.UpdateChannel == UpdateChannel.Stable |
||||||
|
); |
||||||
|
|
||||||
|
vm.ShowLoginRequiredDialog(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,9 +1,13 @@ |
|||||||
namespace StabilityMatrix.Core.Models.Api.Lykos; |
namespace StabilityMatrix.Core.Models.Api.Lykos; |
||||||
|
|
||||||
public record GetUserResponse( |
public record GetUserResponse |
||||||
string Id, |
{ |
||||||
LykosAccount Account, |
public required string Id { get; init; } |
||||||
int UserLevel, |
public required LykosAccount Account { get; init; } |
||||||
string PatreonId, |
public required HashSet<LykosRole> UserRoles { get; init; } |
||||||
bool IsEmailVerified |
public string? PatreonId { get; init; } |
||||||
); |
public bool IsEmailVerified { get; init; } |
||||||
|
|
||||||
|
public bool IsActiveSupporter => |
||||||
|
UserRoles.Contains(LykosRole.PatreonSupporter) || UserRoles.Contains(LykosRole.Insider); |
||||||
|
} |
||||||
|
@ -0,0 +1,12 @@ |
|||||||
|
namespace StabilityMatrix.Core.Models.Api.Lykos; |
||||||
|
|
||||||
|
public enum LykosRole |
||||||
|
{ |
||||||
|
Unknown = -1, |
||||||
|
Basic = 0, |
||||||
|
Supporter = 1, |
||||||
|
PatreonSupporter = 2, |
||||||
|
Insider = 3, |
||||||
|
BetaTester = 4, |
||||||
|
Developer = 5 |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
using StabilityMatrix.Core.Models.Update; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Core.Updater; |
||||||
|
|
||||||
|
public class UpdateStatusChangedEventArgs : EventArgs |
||||||
|
{ |
||||||
|
public UpdateInfo? LatestUpdate { get; init; } |
||||||
|
|
||||||
|
public IReadOnlyDictionary<UpdateChannel, UpdateInfo> UpdateChannels { get; init; } = |
||||||
|
new Dictionary<UpdateChannel, UpdateInfo>(); |
||||||
|
|
||||||
|
public DateTimeOffset CheckedAt { get; init; } = DateTimeOffset.UtcNow; |
||||||
|
} |
Loading…
Reference in new issue