Move filter query and RowComparer into UI base

This commit is contained in:
Mbucari 2023-04-10 13:05:38 -06:00
parent e52772826a
commit 666b5d83df
4 changed files with 106 additions and 95 deletions

View File

@ -23,7 +23,7 @@ namespace LibationAvalonia.ViewModels
/// <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 List<IGridEntry> FilteredInGridEntries; private HashSet<IGridEntry> FilteredInGridEntries;
public string FilterString { get; private set; } public string FilterString { get; private set; }
public DataGridCollectionView GridEntries { get; private set; } public DataGridCollectionView GridEntries { get; private set; }
@ -117,7 +117,7 @@ namespace LibationAvalonia.ViewModels
} }
//Create the filtered-in list before adding entries to avoid a refresh //Create the filtered-in list before adding entries to avoid a refresh
FilteredInGridEntries = QueryResults(geList.Union(geList.OfType<ISeriesEntry>().SelectMany(s => s.Children)), FilterString); FilteredInGridEntries = geList.Union(geList.OfType<ISeriesEntry>().SelectMany(s => s.Children)).FilterEntries(FilterString);
SOURCE.AddRange(geList.OrderByDescending(e => e.DateAdded)); SOURCE.AddRange(geList.OrderByDescending(e => e.DateAdded));
//Add all children beneath their parent //Add all children beneath their parent
@ -301,7 +301,7 @@ namespace LibationAvalonia.ViewModels
if (SOURCE.Count == 0) if (SOURCE.Count == 0)
return; return;
FilteredInGridEntries = QueryResults(SOURCE, searchString); FilteredInGridEntries = SOURCE.FilterEntries(searchString);
await refreshGrid(); await refreshGrid();
} }
@ -318,23 +318,9 @@ namespace LibationAvalonia.ViewModels
return FilteredInGridEntries.Contains(item); return FilteredInGridEntries.Contains(item);
} }
private static List<IGridEntry> QueryResults(IEnumerable<IGridEntry> entries, string searchString)
{
if (string.IsNullOrEmpty(searchString)) return null;
var searchResultSet = SearchEngineCommands.Search(searchString);
var booksFilteredIn = entries.BookEntries().Join(searchResultSet.Docs, lbe => lbe.AudibleProductId, d => d.ProductId, (lbe, d) => (IGridEntry)lbe);
//Find all series containing children that match the search criteria
var seriesFilteredIn = entries.SeriesEntries().Where(s => s.Children.Join(searchResultSet.Docs, lbe => lbe.AudibleProductId, d => d.ProductId, (lbe, d) => lbe).Any());
return booksFilteredIn.Concat(seriesFilteredIn).ToList();
}
private async void SearchEngineCommands_SearchEngineUpdated(object sender, EventArgs e) private async void SearchEngineCommands_SearchEngineUpdated(object sender, EventArgs e)
{ {
var filterResults = QueryResults(SOURCE, FilterString); var filterResults = SOURCE.FilterEntries(FilterString);
if (filterResults is not null && FilteredInGridEntries.Intersect(filterResults).Count() != FilteredInGridEntries.Count) if (filterResults is not null && FilteredInGridEntries.Intersect(filterResults).Count() != FilteredInGridEntries.Count)
{ {

View File

@ -1,25 +1,17 @@
using Avalonia.Controls; using Avalonia.Controls;
using LibationUiBase.GridView; using LibationUiBase.GridView;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Reflection; using System.Reflection;
namespace LibationAvalonia.ViewModels namespace LibationAvalonia.ViewModels
{ {
/// <summary> internal class RowComparer : RowComparerBase
/// This compare class ensures that all top-level grid entries (standalone books or series parents)
/// are sorted by PropertyName while all episodes remain immediately beneath their parents and remain
/// sorted by series index, ascending. Stable sorting is achieved by comparing the GridEntry.ListIndex
/// properties when 2 items compare equal.
/// </summary>
internal class RowComparer : IComparer, IComparer<IGridEntry>, IComparer<object>
{ {
private static readonly PropertyInfo HeaderCellPi = typeof(DataGridColumn).GetProperty("HeaderCell", BindingFlags.NonPublic | BindingFlags.Instance); private static readonly PropertyInfo HeaderCellPi = typeof(DataGridColumn).GetProperty("HeaderCell", BindingFlags.NonPublic | BindingFlags.Instance);
private static readonly PropertyInfo CurrentSortingStatePi = typeof(DataGridColumnHeader).GetProperty("CurrentSortingState", BindingFlags.NonPublic | BindingFlags.Instance); private static readonly PropertyInfo CurrentSortingStatePi = typeof(DataGridColumnHeader).GetProperty("CurrentSortingState", BindingFlags.NonPublic | BindingFlags.Instance);
public DataGridColumn Column { get; init; } private DataGridColumn Column { get; init; }
public string PropertyName { get; private set; } public override string PropertyName { get; set; }
public RowComparer(DataGridColumn column) public RowComparer(DataGridColumn column)
{ {
@ -27,72 +19,8 @@ namespace LibationAvalonia.ViewModels
PropertyName = Column.SortMemberPath; PropertyName = Column.SortMemberPath;
} }
public int Compare(object x, object y)
{
if (x is null && y is not null) return -1;
if (x is not null && y is null) return 1;
if (x is null && y is null) return 0;
var geA = (IGridEntry)x;
var geB = (IGridEntry)y;
var sortDirection = GetSortOrder();
ISeriesEntry parentA = null;
ISeriesEntry parentB = null;
if (geA is ILibraryBookEntry lbA && lbA.Parent is ISeriesEntry seA)
parentA = seA;
if (geB is ILibraryBookEntry lbB && lbB.Parent is ISeriesEntry seB)
parentB = seB;
//both a and b are top-level grid entries
if (parentA is null && parentB is null)
return InternalCompare(geA, geB);
//a is top-level, b is a child
if (parentA is null && parentB is not null)
{
// b is a child of a, parent is always first
if (parentB == geA)
return sortDirection is ListSortDirection.Ascending ? -1 : 1;
else
return InternalCompare(geA, parentB);
}
//a is a child, b is a top-level
if (parentA is not null && parentB is null)
{
// a is a child of b, parent is always first
if (parentA == geB)
return sortDirection is ListSortDirection.Ascending ? 1 : -1;
else
return InternalCompare(parentA, geB);
}
//both are children of the same series
if (parentA == parentB)
return InternalCompare(geA, geB);
//a and b are children of different series.
return InternalCompare(parentA, parentB);
}
//Avalonia doesn't expose the column's CurrentSortingState, so we must get it through reflection //Avalonia doesn't expose the column's CurrentSortingState, so we must get it through reflection
private ListSortDirection? GetSortOrder() protected override ListSortDirection? GetSortOrder()
=> CurrentSortingStatePi.GetValue(HeaderCellPi.GetValue(Column)) as ListSortDirection?; => CurrentSortingStatePi.GetValue(HeaderCellPi.GetValue(Column)) as ListSortDirection?;
private int InternalCompare(IGridEntry x, IGridEntry y)
{
var val1 = x.GetMemberValue(PropertyName);
var val2 = y.GetMemberValue(PropertyName);
return x.GetMemberComparer(val1.GetType()).Compare(val1, val2); ;
}
public int Compare(IGridEntry x, IGridEntry y)
{
return Compare((object)x, y);
}
} }
} }

View File

@ -1,4 +1,5 @@
using DataLayer; using ApplicationServices;
using DataLayer;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -39,6 +40,20 @@ namespace LibationUiBase.GridView
return null; return null;
} }
} }
public static HashSet<IGridEntry>? FilterEntries(this IEnumerable<IGridEntry> entries, string searchString)
{
if (string.IsNullOrEmpty(searchString)) return null;
var searchResultSet = SearchEngineCommands.Search(searchString);
var booksFilteredIn = entries.BookEntries().Join(searchResultSet.Docs, lbe => lbe.AudibleProductId, d => d.ProductId, (lbe, d) => (IGridEntry)lbe);
//Find all series containing children that match the search criteria
var seriesFilteredIn = entries.SeriesEntries().Where(s => s.Children.Join(searchResultSet.Docs, lbe => lbe.AudibleProductId, d => d.ProductId, (lbe, d) => lbe).Any());
return booksFilteredIn.Concat(seriesFilteredIn).ToHashSet();
}
} }
#nullable disable #nullable disable
} }

View File

@ -0,0 +1,82 @@
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
namespace LibationUiBase.GridView
{
/// <summary>
/// This compare class ensures that all top-level grid entries (standalone books or series parents)
/// are sorted by PropertyName while all episodes remain immediately beneath their parents and remain
/// sorted by series index, ascending.
/// </summary>
public abstract class RowComparerBase : IComparer, IComparer<IGridEntry>, IComparer<object>
{
public abstract string PropertyName { get; set; }
public int Compare(object x, object y)
{
if (x is null && y is not null) return -1;
if (x is not null && y is null) return 1;
if (x is null && y is null) return 0;
var geA = (IGridEntry)x;
var geB = (IGridEntry)y;
var sortDirection = GetSortOrder();
ISeriesEntry parentA = null;
ISeriesEntry parentB = null;
if (geA is ILibraryBookEntry lbA && lbA.Parent is ISeriesEntry seA)
parentA = seA;
if (geB is ILibraryBookEntry lbB && lbB.Parent is ISeriesEntry seB)
parentB = seB;
//both a and b are top-level grid entries
if (parentA is null && parentB is null)
return InternalCompare(geA, geB);
//a is top-level, b is a child
if (parentA is null && parentB is not null)
{
// b is a child of a, parent is always first
if (parentB == geA)
return sortDirection is ListSortDirection.Ascending ? -1 : 1;
else
return InternalCompare(geA, parentB);
}
//a is a child, b is a top-level
if (parentA is not null && parentB is null)
{
// a is a child of b, parent is always first
if (parentA == geB)
return sortDirection is ListSortDirection.Ascending ? 1 : -1;
else
return InternalCompare(parentA, geB);
}
//both are children of the same series
if (parentA == parentB)
return InternalCompare(geA, geB);
//a and b are children of different series.
return InternalCompare(parentA, parentB);
}
protected abstract ListSortDirection? GetSortOrder();
private int InternalCompare(IGridEntry x, IGridEntry y)
{
var val1 = x.GetMemberValue(PropertyName);
var val2 = y.GetMemberValue(PropertyName);
return x.GetMemberComparer(val1.GetType()).Compare(val1, val2); ;
}
public int Compare(IGridEntry x, IGridEntry y)
{
return Compare((object)x, y);
}
}
}