Workshop - Avoid crashes when auto-updating without internet

Workshop - Added vote based entry score system
This commit is contained in:
Robert committed 2024-07-24 11:39:56 +02:00
1 parent 7f5bb589af
commit 1e8c68bbeb
19 files changed
+408 -43

No files matched your search

@@ -0,0 +1,65 @@
<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:entries="clr-namespace:Artemis.UI.Screens.Workshop.Entries"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Entries.EntryVoteView"
x:DataType="entries:EntryVoteViewModel">
<UserControl.Styles>
<Styles>
<Style Selector="Button.vote-button avalonia|MaterialIcon">
<Setter Property="Transitions">
<Transitions>
<BrushTransition Property="Foreground" Duration="0:0:0.2" Easing="CubicEaseOut" />
</Transitions>
</Setter>
</Style>
<Style Selector="Button.vote-button.upvote:pointerover avalonia|MaterialIcon">
<Setter Property="Foreground" Value="#F57634"></Setter>
</Style>
<Style Selector="Button.vote-button.downvote:pointerover avalonia|MaterialIcon">
<Setter Property="Foreground" Value="#7193FF"></Setter>
</Style>
<Style Selector="TextBlock.upvoted">
<Setter Property="Foreground" Value="#F57634"></Setter>
</Style>
<Style Selector="TextBlock.downvoted">
<Setter Property="Foreground" Value="#7193FF"></Setter>
</Style>
</Styles>
</UserControl.Styles>
<!-- Voting -->
<StackPanel Spacing="4" VerticalAlignment="Center">
<Button IsEnabled="{CompiledBinding IsLoggedIn^}"
HorizontalAlignment="Stretch"
Theme="{StaticResource TransparentButton}"
Classes="vote-button upvote"
Command="{CompiledBinding CastVote}"
CommandParameter="{x:True}">
<Panel>
<avalonia:MaterialIcon Kind="ArrowUp" IsVisible="{CompiledBinding !Upvoted}" />
<avalonia:MaterialIcon Kind="ArrowUpThick" IsVisible="{CompiledBinding Upvoted}" Foreground="#F57634" />
</Panel>
</Button>
<TextBlock Text="{CompiledBinding Score, FallbackValue=0}"
HorizontalAlignment="Stretch"
TextAlignment="Center"
Classes.upvoted="{CompiledBinding Upvoted}"
Classes.downvoted="{CompiledBinding Downvoted}" />
<Button IsEnabled="{CompiledBinding IsLoggedIn^}"
HorizontalAlignment="Stretch"
Theme="{StaticResource TransparentButton}"
Classes="vote-button downvote"
Command="{CompiledBinding CastVote}"
CommandParameter="{x:False}">
<Panel>
<avalonia:MaterialIcon Kind="ArrowDown" IsVisible="{CompiledBinding !Downvoted}" />
<avalonia:MaterialIcon Kind="ArrowDownThick" IsVisible="{CompiledBinding Downvoted}" Foreground="#7193FF" />
</Panel>
</Button>
</StackPanel>
</UserControl>
@@ -0,0 +1,14 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Entries;
public partial class EntryVoteView : ReactiveUserControl<EntryVoteViewModel>
{
public EntryVoteView()
{
InitializeComponent();
}
}
@@ -0,0 +1,91 @@
using System;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Services;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Entries;
public partial class EntryVoteViewModel : ActivatableViewModelBase
{
private readonly IEntrySummary _entry;
private readonly INotificationService _notificationService;
private readonly IVoteClient _voteClient;
private bool _voting;
[Notify] private int _score;
[Notify] private bool _upvoted;
[Notify] private bool _downvoted;
public EntryVoteViewModel(IEntrySummary entry, IAuthenticationService authenticationService, INotificationService notificationService, IVoteClient voteClient)
{
_entry = entry;
_notificationService = notificationService;
_voteClient = voteClient;
IsLoggedIn = authenticationService.IsLoggedIn;
Score = entry.UpvoteCount - entry.DownvoteCount;
this.WhenActivated(d => IsLoggedIn.Subscribe(l => _ = GetVoteStatus(l)).DisposeWith(d));
}
public IObservable<bool> IsLoggedIn { get; }
public async Task CastVote(bool upvote)
{
// Could use a ReactiveCommand to achieve the same thing but that disables the button
// while executing which grays it out for a fraction of a second and looks bad
if (_voting)
return;
_voting = true;
try
{
IVoteCount? result;
// If the vote was removed, reset the upvote/downvote state
if ((Upvoted && upvote) || (Downvoted && !upvote))
{
result = await _voteClient.ClearVote(_entry.Id);
Upvoted = false;
Downvoted = false;
}
else
{
result = await _voteClient.CastVote(_entry.Id, upvote);
Upvoted = upvote;
Downvoted = !upvote;
}
if (result != null)
Score = result.UpvoteCount - result.DownvoteCount;
else
_notificationService.CreateNotification().WithTitle("Failed to cast vote").WithMessage("Please try again later.").WithSeverity(NotificationSeverity.Error).Show();
}
finally
{
_voting = false;
}
}
private async Task GetVoteStatus(bool isLoggedIn)
{
if (!isLoggedIn)
{
Upvoted = false;
Downvoted = false;
}
else
{
bool? vote = await _voteClient.GetVote(_entry.Id);
if (vote != null)
{
Upvoted = vote.Value;
Downvoted = !vote.Value;
}
}
}
}
@@ -21,6 +21,7 @@
<ComboBoxItem>Recently updated</ComboBoxItem>
<ComboBoxItem>Recently added</ComboBoxItem>
<ComboBoxItem>Download count</ComboBoxItem>
<ComboBoxItem>Score</ComboBoxItem>
</ComboBox>
</StackPanel>
<TextBlock Grid.Column="3" VerticalAlignment="Center" Margin="5 0 0 0" MinWidth="75" TextAlignment="Right">
@@ -21,9 +21,12 @@
HorizontalContentAlignment="Stretch"
Command="{CompiledBinding NavigateToEntry}"
IsVisible="{CompiledBinding Entry, Converter={x:Static ObjectConverters.IsNotNull}}">
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="*, Auto">
<Grid ColumnDefinitions="Auto, Auto,*,Auto" RowDefinitions="*, Auto">
<!-- Score -->
<ContentControl Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Width="40" Margin="0 0 10 0" Content="{CompiledBinding VoteViewModel}"/>
<!-- Icon -->
<Border Grid.Column="0"
<Border Grid.Column="1"
Grid.Row="0"
Grid.RowSpan="2"
CornerRadius="6"
@@ -36,7 +39,7 @@
</Border>
<!-- Body -->
<Grid Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto">
<Grid Grid.Column="2" Grid.Row="0" Grid.RowSpan="2" VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto">
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBlock Margin="0 0 0 5" TextTrimming="CharacterEllipsis">
<Run Classes="h5" Text="{CompiledBinding Entry.Name, FallbackValue=Title}" />
@@ -79,7 +82,7 @@
</Grid>
<!-- Info -->
<StackPanel Grid.Column="2" Grid.Row="0" Margin="0 0 4 0" HorizontalAlignment="Right">
<StackPanel Grid.Column="3" Grid.Row="0" Margin="0 0 4 0" HorizontalAlignment="Right">
<TextBlock TextAlignment="Right" Text="{CompiledBinding Entry.CreatedAt, FallbackValue=01-01-1337, Converter={StaticResource DateTimeConverter}}" />
<TextBlock TextAlignment="Right">
<avalonia:MaterialIcon Kind="Downloads" />
@@ -89,7 +92,7 @@
</StackPanel>
<!-- Install state -->
<StackPanel Grid.Column="2" Grid.Row="1" Margin="0 0 4 0" HorizontalAlignment="Right" VerticalAlignment="Bottom" IsVisible="{CompiledBinding IsInstalled}">
<StackPanel Grid.Column="3" Grid.Row="1" Margin="0 0 4 0" HorizontalAlignment="Right" VerticalAlignment="Bottom" IsVisible="{CompiledBinding IsInstalled}">
<TextBlock TextAlignment="Right" IsVisible="{CompiledBinding !UpdateAvailable}">
<avalonia:MaterialIcon Kind="CheckCircle" Foreground="{DynamicResource SystemAccentColorLight1}" Width="20" Height="20" />
<Run>installed</Run>
@@ -1,5 +1,4 @@
using System;
using System.Reactive;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using Artemis.UI.Shared;
@@ -18,12 +17,12 @@ public partial class EntryListItemViewModel : ActivatableViewModelBase
[Notify] private bool _isInstalled;
[Notify] private bool _updateAvailable;
public EntryListItemViewModel(IEntrySummary entry, IRouter router, IWorkshopService workshopService)
public EntryListItemViewModel(IEntrySummary entry, IRouter router, IWorkshopService workshopService, Func<IEntrySummary, EntryVoteViewModel> getEntryVoteViewModel)
{
_router = router;
Entry = entry;
NavigateToEntry = ReactiveCommand.CreateFromTask(ExecuteNavigateToEntry);
VoteViewModel = getEntryVoteViewModel(entry);
this.WhenActivated((CompositeDisposable _) =>
{
@@ -34,9 +33,9 @@ public partial class EntryListItemViewModel : ActivatableViewModelBase
}
public IEntrySummary Entry { get; }
public ReactiveCommand<Unit, Unit> NavigateToEntry { get; }
public EntryVoteViewModel VoteViewModel { get; }
private async Task ExecuteNavigateToEntry()
public async Task NavigateToEntry()
{
switch (Entry.EntryType)
{
@@ -137,6 +137,9 @@ public partial class EntryListViewModel : RoutableScreen
if (InputViewModel.SortBy == 2)
return new[] {new EntrySortInput {Downloads = SortEnumType.Desc}};
// Sort by score
if (InputViewModel.SortBy == 3)
return new[] {new EntrySortInput {Score = SortEnumType.Desc}};
// Sort by latest release, then by created at
return new[]
@@ -11,7 +11,6 @@
x:DataType="tabs:InstalledTabItemViewModel">
<UserControl.Resources>
<converters:EntryIconUriConverter x:Key="EntryIconUriConverter" />
<converters:DateTimeConverter x:Key="DateTimeConverter" />
</UserControl.Resources>
<Button MinHeight="110"
MaxHeight="140"
@@ -19,9 +18,12 @@
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Command="{CompiledBinding ViewWorkshopPage}">
<Grid ColumnDefinitions="Auto,*,Auto,Auto" RowDefinitions="*, Auto">
<Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto" RowDefinitions="*, Auto">
<!-- Score -->
<ContentControl Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Width="40" Margin="0 0 10 0" Content="{CompiledBinding VoteViewModel}"/>
<!-- Icon -->
<Border Grid.Column="0"
<Border Grid.Column="1"
Grid.Row="0"
Grid.RowSpan="2"
CornerRadius="6"
@@ -34,7 +36,7 @@
</Border>
<!-- Body -->
<Grid Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto">
<Grid Grid.Column="2" Grid.Row="0" Grid.RowSpan="2" VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto">
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBlock Margin="0 0 0 5" TextTrimming="CharacterEllipsis">
<Run Classes="h5" Text="{CompiledBinding Entry.Name, FallbackValue=Title}" />
@@ -76,7 +78,7 @@
</Grid>
<!-- Info -->
<StackPanel Grid.Column="2" Grid.Row="0" Margin="0 0 4 0" HorizontalAlignment="Right">
<StackPanel Grid.Column="3" Grid.Row="0" Margin="0 0 4 0" HorizontalAlignment="Right">
<TextBlock TextAlignment="Right">
<avalonia:MaterialIcon Kind="Harddisk" />
<Run Text="{CompiledBinding Entry.ReleaseVersion}" />
@@ -84,7 +86,7 @@
</StackPanel>
<!-- Install state -->
<StackPanel Grid.Column="2" Grid.Row="1" Margin="0 0 4 0" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<StackPanel Grid.Column="3" Grid.Row="1" Margin="0 0 4 0" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<TextBlock TextAlignment="Right" IsVisible="{CompiledBinding UpdateAvailable}">
<avalonia:MaterialIcon Kind="Update" Foreground="{DynamicResource SystemAccentColorLight1}" Width="20" Height="20" />
<Run>update available</Run>
@@ -92,7 +94,7 @@
</StackPanel>
<!-- Management -->
<Border Grid.Column="3" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{DynamicResource ButtonBorderBrush}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0">
<Border Grid.Column="4" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{DynamicResource ButtonBorderBrush}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0">
<StackPanel VerticalAlignment="Center">
<StackPanel Orientation="Horizontal" Spacing="5">
<Button Command="{CompiledBinding ViewLocal}" HorizontalAlignment="Stretch" >Open</Button>
@@ -10,6 +10,7 @@ using Artemis.Core.Services;
using Artemis.UI.DryIoc.Factories;
using Artemis.UI.Extensions;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Screens.Workshop.Entries;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
@@ -35,6 +36,7 @@ public partial class InstalledTabItemViewModel : ActivatableViewModelBase
[Notify] private bool _updateAvailable;
[Notify] private bool _autoUpdate;
[Notify] private EntryVoteViewModel _voteViewModel;
public InstalledTabItemViewModel(InstalledEntry entry,
IWorkshopClient client,
@@ -43,7 +45,8 @@ public partial class InstalledTabItemViewModel : ActivatableViewModelBase
IRouter router,
IWindowService windowService,
IPluginManagementService pluginManagementService,
ISettingsVmFactory settingsVmFactory)
ISettingsVmFactory settingsVmFactory,
Func<IEntrySummary, EntryVoteViewModel> getEntryVoteViewModel)
{
_client = client;
_workshopService = workshopService;
@@ -53,9 +56,9 @@ public partial class InstalledTabItemViewModel : ActivatableViewModelBase
_pluginManagementService = pluginManagementService;
_settingsVmFactory = settingsVmFactory;
_autoUpdate = entry.AutoUpdate;
Entry = entry;
Entry = entry;
this.WhenActivatedAsync(async _ =>
{
// Grab the latest entry summary from the workshop
@@ -65,6 +68,7 @@ public partial class InstalledTabItemViewModel : ActivatableViewModelBase
if (entrySummary.Data?.Entry != null)
{
Entry.ApplyEntrySummary(entrySummary.Data.Entry);
VoteViewModel = getEntryVoteViewModel(entrySummary.Data.Entry);
_workshopService.SaveInstalledEntry(Entry);
}
}
@@ -73,7 +77,7 @@ public partial class InstalledTabItemViewModel : ActivatableViewModelBase
UpdateAvailable = Entry.ReleaseId != Entry.LatestReleaseId;
}
});
this.WhenAnyValue(vm => vm.AutoUpdate).Skip(1).Subscribe(_ => AutoUpdateToggled());
}
@@ -122,10 +126,10 @@ public partial class InstalledTabItemViewModel : ActivatableViewModelBase
private void AutoUpdateToggled()
{
_workshopService.SetAutoUpdate(Entry, AutoUpdate);
if (!AutoUpdate)
return;
Task.Run(async () =>
{
await _workshopUpdateService.AutoUpdateEntry(Entry);