Merge pull request #1174 from Mbucari/master

Null safety & UI tweak
This commit is contained in:
rmcrackan 2025-03-04 13:10:51 -05:00 committed by GitHub
commit 649b52af1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 67 additions and 40 deletions

View File

@ -16,22 +16,23 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
#nullable enable
namespace LibationAvalonia.ViewModels
{
public class ProductsDisplayViewModel : ViewModelBase
{
/// <summary>Number of visible rows has changed</summary>
public event EventHandler<int> VisibleCountChanged;
public event EventHandler<int> RemovableCountChanged;
public event EventHandler<int>? VisibleCountChanged;
public event EventHandler<int>? RemovableCountChanged;
/// <summary>Backing list of all grid entries</summary>
private readonly AvaloniaList<IGridEntry> SOURCE = new();
/// <summary>Grid entries included in the filter set. If null, all grid entries are shown</summary>
private HashSet<IGridEntry> FilteredInGridEntries;
public string FilterString { get; private set; }
private HashSet<IGridEntry>? FilteredInGridEntries;
public string? FilterString { get; private set; }
private DataGridCollectionView _gridEntries;
public DataGridCollectionView GridEntries
private DataGridCollectionView? _gridEntries;
public DataGridCollectionView? GridEntries
{
get => _gridEntries;
private set => this.RaiseAndSetIfChanged(ref _gridEntries, value);
@ -60,14 +61,14 @@ namespace LibationAvalonia.ViewModels
VisibleCountChanged?.Invoke(this, 0);
}
private static readonly System.Reflection.MethodInfo SetFlagsMethod;
private static readonly System.Reflection.MethodInfo? SetFlagsMethod;
/// <summary>
/// Tells the <see cref="DataGridCollectionView"/> whether it should process changes to the underlying collection
/// </summary>
/// <remarks> DataGridCollectionView.CollectionViewFlags.ShouldProcessCollectionChanged = 4</remarks>
private void SetShouldProcessCollectionChanged(bool flagSet)
=> SetFlagsMethod.Invoke(GridEntries, new object[] { 4, flagSet });
=> SetFlagsMethod?.Invoke(GridEntries, new object[] { 4, flagSet });
static ProductsDisplayViewModel()
{
@ -131,13 +132,16 @@ namespace LibationAvalonia.ViewModels
//Saves ~500 ms on a library of ~4500 books.
//Perform on UI thread for safety, but at this time, merely setting the DataGridCollectionView
//does not trigger UI actions in the way that modifying the list after it's been linked does.
await Dispatcher.UIThread.InvokeAsync(() => GridEntries = new(SOURCE) { Filter = CollectionFilter });
await Dispatcher.UIThread.InvokeAsync(() =>
{
GridEntries = new(SOURCE) { Filter = CollectionFilter };
GridEntries.CollectionChanged += GridEntries_CollectionChanged;
});
GridEntries.CollectionChanged += GridEntries_CollectionChanged;
GridEntries_CollectionChanged();
}
private void GridEntries_CollectionChanged(object sender = null, EventArgs e = null)
private void GridEntries_CollectionChanged(object? sender = null, EventArgs? e = null)
{
var count
= FilteredInGridEntries?.OfType<ILibraryBookEntry>().Count()
@ -151,7 +155,12 @@ namespace LibationAvalonia.ViewModels
/// </summary>
internal async Task UpdateGridAsync(List<LibraryBook> dbBooks)
{
GridEntries.CollectionChanged -= GridEntries_CollectionChanged;
if (GridEntries == null)
{
//always bind before updating. Binding creates GridEntries.
await BindToGridAsync(dbBooks);
return;
}
#region Add new or update existing grid entries
@ -203,7 +212,8 @@ namespace LibationAvalonia.ViewModels
await Filter(FilterString);
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true);
GridEntries.CollectionChanged += GridEntries_CollectionChanged;
if (GridEntries != null)
GridEntries.CollectionChanged += GridEntries_CollectionChanged;
GridEntries_CollectionChanged();
}
@ -211,7 +221,7 @@ namespace LibationAvalonia.ViewModels
{
foreach (var removed in removedBooks.Cast<IGridEntry>().Concat(removedSeries).Where(b => b is not null).ToList())
{
if (GridEntries.PassesFilter(removed))
if (GridEntries?.PassesFilter(removed) ?? false)
GridEntries.Remove(removed);
else
{
@ -222,7 +232,7 @@ namespace LibationAvalonia.ViewModels
}
}
private void UpsertBook(LibraryBook book, ILibraryBookEntry existingBookEntry)
private void UpsertBook(LibraryBook book, ILibraryBookEntry? existingBookEntry)
{
if (existingBookEntry is null)
// Add the new product to top
@ -232,7 +242,7 @@ namespace LibationAvalonia.ViewModels
existingBookEntry.UpdateLibraryBook(book);
}
private void UpsertEpisode(LibraryBook episodeBook, ILibraryBookEntry existingEpisodeEntry, List<ISeriesEntry> seriesEntries, IEnumerable<LibraryBook> dbBooks)
private void UpsertEpisode(LibraryBook episodeBook, ILibraryBookEntry? existingEpisodeEntry, List<ISeriesEntry> seriesEntries, IEnumerable<LibraryBook> dbBooks)
{
if (existingEpisodeEntry is null)
{
@ -282,10 +292,13 @@ namespace LibationAvalonia.ViewModels
private async Task refreshGrid()
{
if (GridEntries.IsEditingItem)
await Dispatcher.UIThread.InvokeAsync(GridEntries.CommitEdit);
if (GridEntries != null)
{
if (GridEntries.IsEditingItem)
await Dispatcher.UIThread.InvokeAsync(GridEntries.CommitEdit);
await Dispatcher.UIThread.InvokeAsync(GridEntries.Refresh);
await Dispatcher.UIThread.InvokeAsync(GridEntries.Refresh);
}
}
public async Task ToggleSeriesExpanded(ISeriesEntry seriesEntry)
@ -299,7 +312,7 @@ namespace LibationAvalonia.ViewModels
#region Filtering
public async Task Filter(string searchString)
public async Task Filter(string? searchString)
{
FilterString = searchString;
@ -323,7 +336,7 @@ namespace LibationAvalonia.ViewModels
return FilteredInGridEntries.Contains(item);
}
private async void SearchEngineCommands_SearchEngineUpdated(object sender, EventArgs e)
private async void SearchEngineCommands_SearchEngineUpdated(object? sender, EventArgs? e)
{
var filterResults = SOURCE.FilterEntries(FilterString);
@ -366,9 +379,9 @@ namespace LibationAvalonia.ViewModels
foreach (var book in selectedBooks)
book.PropertyChanged -= GridEntry_PropertyChanged;
void BindingList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
void BindingList_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs? e)
{
if (e.Action != System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
if (e?.Action != System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
return;
//After DisplayBooks() re-creates the list,
@ -380,7 +393,8 @@ namespace LibationAvalonia.ViewModels
GridEntries.CollectionChanged -= BindingList_CollectionChanged;
}
GridEntries.CollectionChanged += BindingList_CollectionChanged;
if (GridEntries != null)
GridEntries.CollectionChanged += BindingList_CollectionChanged;
//The RemoveBooksAsync will fire LibrarySizeChanged, which calls ProductsDisplay2.Display(),
//so there's no need to remove books from the grid display here.
@ -432,9 +446,9 @@ namespace LibationAvalonia.ViewModels
}
}
private void GridEntry_PropertyChanged(object sender, PropertyChangedEventArgs e)
private void GridEntry_PropertyChanged(object? sender, PropertyChangedEventArgs? e)
{
if (e.PropertyName == nameof(IGridEntry.Remove) && sender is ILibraryBookEntry)
if (e?.PropertyName == nameof(IGridEntry.Remove) && sender is ILibraryBookEntry)
{
int removeCount = GetAllBookEntries().Count(lbe => lbe.Remove is true);
RemovableCountChanged?.Invoke(this, removeCount);

View File

@ -7,7 +7,17 @@
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="87" MaxHeight="87" MinHeight="87" MinWidth="300"
x:Class="LibationAvalonia.Views.ProcessBookControl" Background="{CompiledBinding BackgroundColor}">
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseMediumBrush}" BorderThickness="0,0,0,1">
<UserControl.Styles>
<Style Selector="Border#QueuedItemBorder:not(:pointerover) Button">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="Border#QueuedItemBorder:pointerover Button">
<Setter Property="IsVisible" Value="True" />
</Style>
</UserControl.Styles>
<Border Name="QueuedItemBorder" Background="Transparent" BorderBrush="{DynamicResource SystemControlForegroundBaseMediumBrush}" BorderThickness="0,0,0,1">
<Grid ColumnDefinitions="Auto,*,Auto">
<Panel Grid.Column="0" Margin="3" Width="80" Height="80" HorizontalAlignment="Left">
@ -29,7 +39,7 @@
<TextBlock IsVisible="{CompiledBinding !IsDownloading}" Text="{CompiledBinding StatusText}"/>
</Panel>
</Grid>
<Grid Margin="3" Grid.Column="2" HorizontalAlignment="Right" ColumnDefinitions="Auto,Auto">
<Grid Name="ButtonsGrid" Margin="3" Grid.Column="2" HorizontalAlignment="Right" ColumnDefinitions="Auto,Auto">
<Grid.Styles>
<Style Selector="Button">
<Setter Property="Padding" Value="0,1,0,1" />
@ -43,21 +53,21 @@
</Grid.Styles>
<StackPanel IsVisible="{CompiledBinding Queued}" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Right" Orientation="Vertical">
<Button Click="MoveFirst_Click">
<Button ToolTip.Tip="Move book to top of queue" Click="MoveFirst_Click">
<Path VerticalAlignment="Top" Data="{StaticResource FirstButtonIcon}" />
</Button>
<Button Click="MoveUp_Click">
<Button ToolTip.Tip="Move book up in queue" Click="MoveUp_Click">
<Path VerticalAlignment="Top" Data="{StaticResource UpButtonIcon}" />
</Button>
<Button Click="MoveDown_Click">
<Button ToolTip.Tip="Move book down in queue" Click="MoveDown_Click">
<Path VerticalAlignment="Bottom" Data="{StaticResource DownButtonIcon}" />
</Button>
<Button Click="MoveLast_Click">
<Button ToolTip.Tip="Move book to bottom of queue" Click="MoveLast_Click">
<Path VerticalAlignment="Bottom" Data="{StaticResource LastButtonIcon}" />
</Button>
</StackPanel>
<Panel Margin="3,0,0,0" Grid.Column="1" VerticalAlignment="Top">
<Button Height="32" Background="{DynamicResource CancelRed}" Width="22" IsVisible="{CompiledBinding !IsFinished}" CornerRadius="11" Click="Cancel_Click">
<Panel Margin="3,0,0,0" Grid.Column="1" VerticalAlignment="Top" IsVisible="{CompiledBinding !IsFinished}">
<Button Height="32" Background="{DynamicResource CancelRed}" Width="22" CornerRadius="11" Click="Cancel_Click">
<Path Fill="{DynamicResource ProcessQueueBookDefaultBrush}" VerticalAlignment="Center" Data="{StaticResource CancelButtonIcon}" RenderTransform="{StaticResource Rotate45Transform}" />
</Button>
</Panel>

View File

@ -2,6 +2,7 @@
using DataLayer;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace LibationUiBase.GridView
@ -47,9 +48,11 @@ namespace LibationUiBase.GridView
otherSet is not null &&
searchSet.Intersect(otherSet).Count() != searchSet.Count);
public static HashSet<IGridEntry>? FilterEntries(this IEnumerable<IGridEntry> entries, string searchString)
[return: NotNullIfNotNull(nameof(searchString))]
public static HashSet<IGridEntry>? FilterEntries(this IEnumerable<IGridEntry> entries, string? searchString)
{
if (string.IsNullOrEmpty(searchString)) return null;
if (string.IsNullOrEmpty(searchString))
return null;
var searchResultSet = SearchEngineCommands.Search(searchString);