commit
649b52af1d
@ -16,22 +16,23 @@ using System.Linq;
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationAvalonia.ViewModels
|
namespace LibationAvalonia.ViewModels
|
||||||
{
|
{
|
||||||
public class ProductsDisplayViewModel : ViewModelBase
|
public class ProductsDisplayViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
/// <summary>Number of visible rows has changed</summary>
|
/// <summary>Number of visible rows has changed</summary>
|
||||||
public event EventHandler<int> VisibleCountChanged;
|
public event EventHandler<int>? VisibleCountChanged;
|
||||||
public event EventHandler<int> RemovableCountChanged;
|
public event EventHandler<int>? RemovableCountChanged;
|
||||||
|
|
||||||
/// <summary>Backing list of all grid entries</summary>
|
/// <summary>Backing list of all grid entries</summary>
|
||||||
private readonly AvaloniaList<IGridEntry> SOURCE = new();
|
private readonly AvaloniaList<IGridEntry> SOURCE = new();
|
||||||
/// <summary>Grid entries included in the filter set. If null, all grid entries are shown</summary>
|
/// <summary>Grid entries included in the filter set. If null, all grid entries are shown</summary>
|
||||||
private HashSet<IGridEntry> FilteredInGridEntries;
|
private HashSet<IGridEntry>? FilteredInGridEntries;
|
||||||
public string FilterString { get; private set; }
|
public string? FilterString { get; private set; }
|
||||||
|
|
||||||
private DataGridCollectionView _gridEntries;
|
private DataGridCollectionView? _gridEntries;
|
||||||
public DataGridCollectionView GridEntries
|
public DataGridCollectionView? GridEntries
|
||||||
{
|
{
|
||||||
get => _gridEntries;
|
get => _gridEntries;
|
||||||
private set => this.RaiseAndSetIfChanged(ref _gridEntries, value);
|
private set => this.RaiseAndSetIfChanged(ref _gridEntries, value);
|
||||||
@ -60,14 +61,14 @@ namespace LibationAvalonia.ViewModels
|
|||||||
VisibleCountChanged?.Invoke(this, 0);
|
VisibleCountChanged?.Invoke(this, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly System.Reflection.MethodInfo SetFlagsMethod;
|
private static readonly System.Reflection.MethodInfo? SetFlagsMethod;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tells the <see cref="DataGridCollectionView"/> whether it should process changes to the underlying collection
|
/// Tells the <see cref="DataGridCollectionView"/> whether it should process changes to the underlying collection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks> DataGridCollectionView.CollectionViewFlags.ShouldProcessCollectionChanged = 4</remarks>
|
/// <remarks> DataGridCollectionView.CollectionViewFlags.ShouldProcessCollectionChanged = 4</remarks>
|
||||||
private void SetShouldProcessCollectionChanged(bool flagSet)
|
private void SetShouldProcessCollectionChanged(bool flagSet)
|
||||||
=> SetFlagsMethod.Invoke(GridEntries, new object[] { 4, flagSet });
|
=> SetFlagsMethod?.Invoke(GridEntries, new object[] { 4, flagSet });
|
||||||
|
|
||||||
static ProductsDisplayViewModel()
|
static ProductsDisplayViewModel()
|
||||||
{
|
{
|
||||||
@ -131,13 +132,16 @@ namespace LibationAvalonia.ViewModels
|
|||||||
//Saves ~500 ms on a library of ~4500 books.
|
//Saves ~500 ms on a library of ~4500 books.
|
||||||
//Perform on UI thread for safety, but at this time, merely setting the DataGridCollectionView
|
//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.
|
//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();
|
GridEntries_CollectionChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GridEntries_CollectionChanged(object sender = null, EventArgs e = null)
|
private void GridEntries_CollectionChanged(object? sender = null, EventArgs? e = null)
|
||||||
{
|
{
|
||||||
var count
|
var count
|
||||||
= FilteredInGridEntries?.OfType<ILibraryBookEntry>().Count()
|
= FilteredInGridEntries?.OfType<ILibraryBookEntry>().Count()
|
||||||
@ -151,7 +155,12 @@ namespace LibationAvalonia.ViewModels
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal async Task UpdateGridAsync(List<LibraryBook> dbBooks)
|
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
|
#region Add new or update existing grid entries
|
||||||
|
|
||||||
@ -203,7 +212,8 @@ namespace LibationAvalonia.ViewModels
|
|||||||
await Filter(FilterString);
|
await Filter(FilterString);
|
||||||
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true);
|
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true);
|
||||||
|
|
||||||
GridEntries.CollectionChanged += GridEntries_CollectionChanged;
|
if (GridEntries != null)
|
||||||
|
GridEntries.CollectionChanged += 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())
|
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);
|
GridEntries.Remove(removed);
|
||||||
else
|
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)
|
if (existingBookEntry is null)
|
||||||
// Add the new product to top
|
// Add the new product to top
|
||||||
@ -232,7 +242,7 @@ namespace LibationAvalonia.ViewModels
|
|||||||
existingBookEntry.UpdateLibraryBook(book);
|
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)
|
if (existingEpisodeEntry is null)
|
||||||
{
|
{
|
||||||
@ -282,10 +292,13 @@ namespace LibationAvalonia.ViewModels
|
|||||||
|
|
||||||
private async Task refreshGrid()
|
private async Task refreshGrid()
|
||||||
{
|
{
|
||||||
if (GridEntries.IsEditingItem)
|
if (GridEntries != null)
|
||||||
await Dispatcher.UIThread.InvokeAsync(GridEntries.CommitEdit);
|
{
|
||||||
|
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)
|
public async Task ToggleSeriesExpanded(ISeriesEntry seriesEntry)
|
||||||
@ -299,7 +312,7 @@ namespace LibationAvalonia.ViewModels
|
|||||||
|
|
||||||
#region Filtering
|
#region Filtering
|
||||||
|
|
||||||
public async Task Filter(string searchString)
|
public async Task Filter(string? searchString)
|
||||||
{
|
{
|
||||||
FilterString = searchString;
|
FilterString = searchString;
|
||||||
|
|
||||||
@ -323,7 +336,7 @@ namespace LibationAvalonia.ViewModels
|
|||||||
return FilteredInGridEntries.Contains(item);
|
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);
|
var filterResults = SOURCE.FilterEntries(FilterString);
|
||||||
|
|
||||||
@ -366,9 +379,9 @@ namespace LibationAvalonia.ViewModels
|
|||||||
foreach (var book in selectedBooks)
|
foreach (var book in selectedBooks)
|
||||||
book.PropertyChanged -= GridEntry_PropertyChanged;
|
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;
|
return;
|
||||||
|
|
||||||
//After DisplayBooks() re-creates the list,
|
//After DisplayBooks() re-creates the list,
|
||||||
@ -380,7 +393,8 @@ namespace LibationAvalonia.ViewModels
|
|||||||
GridEntries.CollectionChanged -= BindingList_CollectionChanged;
|
GridEntries.CollectionChanged -= BindingList_CollectionChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
GridEntries.CollectionChanged += BindingList_CollectionChanged;
|
if (GridEntries != null)
|
||||||
|
GridEntries.CollectionChanged += BindingList_CollectionChanged;
|
||||||
|
|
||||||
//The RemoveBooksAsync will fire LibrarySizeChanged, which calls ProductsDisplay2.Display(),
|
//The RemoveBooksAsync will fire LibrarySizeChanged, which calls ProductsDisplay2.Display(),
|
||||||
//so there's no need to remove books from the grid display here.
|
//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);
|
int removeCount = GetAllBookEntries().Count(lbe => lbe.Remove is true);
|
||||||
RemovableCountChanged?.Invoke(this, removeCount);
|
RemovableCountChanged?.Invoke(this, removeCount);
|
||||||
|
|||||||
@ -6,10 +6,20 @@
|
|||||||
x:DataType="vm:ProcessBookViewModel"
|
x:DataType="vm:ProcessBookViewModel"
|
||||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="87" MaxHeight="87" MinHeight="87" MinWidth="300"
|
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="87" MaxHeight="87" MinHeight="87" MinWidth="300"
|
||||||
x:Class="LibationAvalonia.Views.ProcessBookControl" Background="{CompiledBinding BackgroundColor}">
|
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">
|
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||||
|
|
||||||
<Panel Grid.Column="0" Margin="3" Width="80" Height="80" HorizontalAlignment="Left">
|
<Panel Grid.Column="0" Margin="3" Width="80" Height="80" HorizontalAlignment="Left">
|
||||||
<Image Width="80" Height="80" Source="{CompiledBinding Cover}" Stretch="Uniform" />
|
<Image Width="80" Height="80" Source="{CompiledBinding Cover}" Stretch="Uniform" />
|
||||||
</Panel>
|
</Panel>
|
||||||
@ -29,7 +39,7 @@
|
|||||||
<TextBlock IsVisible="{CompiledBinding !IsDownloading}" Text="{CompiledBinding StatusText}"/>
|
<TextBlock IsVisible="{CompiledBinding !IsDownloading}" Text="{CompiledBinding StatusText}"/>
|
||||||
</Panel>
|
</Panel>
|
||||||
</Grid>
|
</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>
|
<Grid.Styles>
|
||||||
<Style Selector="Button">
|
<Style Selector="Button">
|
||||||
<Setter Property="Padding" Value="0,1,0,1" />
|
<Setter Property="Padding" Value="0,1,0,1" />
|
||||||
@ -42,22 +52,22 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</Grid.Styles>
|
</Grid.Styles>
|
||||||
<StackPanel IsVisible="{CompiledBinding Queued}" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Right" Orientation="Vertical">
|
<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}" />
|
<Path VerticalAlignment="Top" Data="{StaticResource FirstButtonIcon}" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button Click="MoveUp_Click">
|
<Button ToolTip.Tip="Move book up in queue" Click="MoveUp_Click">
|
||||||
<Path VerticalAlignment="Top" Data="{StaticResource UpButtonIcon}" />
|
<Path VerticalAlignment="Top" Data="{StaticResource UpButtonIcon}" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button Click="MoveDown_Click">
|
<Button ToolTip.Tip="Move book down in queue" Click="MoveDown_Click">
|
||||||
<Path VerticalAlignment="Bottom" Data="{StaticResource DownButtonIcon}" />
|
<Path VerticalAlignment="Bottom" Data="{StaticResource DownButtonIcon}" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button Click="MoveLast_Click">
|
<Button ToolTip.Tip="Move book to bottom of queue" Click="MoveLast_Click">
|
||||||
<Path VerticalAlignment="Bottom" Data="{StaticResource LastButtonIcon}" />
|
<Path VerticalAlignment="Bottom" Data="{StaticResource LastButtonIcon}" />
|
||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Panel Margin="3,0,0,0" Grid.Column="1" VerticalAlignment="Top">
|
<Panel Margin="3,0,0,0" Grid.Column="1" VerticalAlignment="Top" IsVisible="{CompiledBinding !IsFinished}">
|
||||||
<Button Height="32" Background="{DynamicResource CancelRed}" Width="22" IsVisible="{CompiledBinding !IsFinished}" CornerRadius="11" Click="Cancel_Click">
|
<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}" />
|
<Path Fill="{DynamicResource ProcessQueueBookDefaultBrush}" VerticalAlignment="Center" Data="{StaticResource CancelButtonIcon}" RenderTransform="{StaticResource Rotate45Transform}" />
|
||||||
</Button>
|
</Button>
|
||||||
</Panel>
|
</Panel>
|
||||||
@ -65,7 +75,7 @@
|
|||||||
<Panel Margin="3" Width="50" Grid.Column="2">
|
<Panel Margin="3" Width="50" Grid.Column="2">
|
||||||
<TextPresenter FontSize="9" VerticalAlignment="Bottom" HorizontalAlignment="Right" IsVisible="{CompiledBinding IsDownloading}" Text="{CompiledBinding ETA}" />
|
<TextPresenter FontSize="9" VerticalAlignment="Bottom" HorizontalAlignment="Right" IsVisible="{CompiledBinding IsDownloading}" Text="{CompiledBinding ETA}" />
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
using DataLayer;
|
using DataLayer;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace LibationUiBase.GridView
|
namespace LibationUiBase.GridView
|
||||||
@ -47,9 +48,11 @@ namespace LibationUiBase.GridView
|
|||||||
otherSet is not null &&
|
otherSet is not null &&
|
||||||
searchSet.Intersect(otherSet).Count() != searchSet.Count);
|
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);
|
var searchResultSet = SearchEngineCommands.Search(searchString);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user