This commit is contained in:
parent
df224cc7f3
commit
fe55b90ee3
@ -35,18 +35,29 @@ namespace LibationAvalonia.ViewModels
|
|||||||
#region Model properties exposed to the view
|
#region Model properties exposed to the view
|
||||||
|
|
||||||
private Avalonia.Media.Imaging.Bitmap _cover;
|
private Avalonia.Media.Imaging.Bitmap _cover;
|
||||||
public Avalonia.Media.Imaging.Bitmap Cover { get => _cover; protected set { this.RaiseAndSetIfChanged(ref _cover, value); } }
|
private string _purchasedate;
|
||||||
public string PurchaseDate { get; protected set; }
|
private string _series;
|
||||||
public string Series { get; protected set; }
|
private string _title;
|
||||||
public string Title { get; protected set; }
|
private string _length;
|
||||||
public string Length { get; protected set; }
|
private string _authors;
|
||||||
public string Authors { get; protected set; }
|
private string _narrators;
|
||||||
public string Narrators { get; protected set; }
|
private string _category;
|
||||||
public string Category { get; protected set; }
|
private string _misc;
|
||||||
public string Misc { get; protected set; }
|
private string _description;
|
||||||
public string Description { get; protected set; }
|
private Rating _productrating;
|
||||||
public Rating ProductRating { get; protected set; }
|
|
||||||
protected Rating _myRating;
|
protected Rating _myRating;
|
||||||
|
|
||||||
|
public Avalonia.Media.Imaging.Bitmap Cover { get => _cover; protected set => this.RaiseAndSetIfChanged(ref _cover, value); }
|
||||||
|
public string PurchaseDate { get => _purchasedate; protected set => this.RaiseAndSetIfChanged(ref _purchasedate, value); }
|
||||||
|
public string Series { get => _series; protected set => this.RaiseAndSetIfChanged(ref _series, value); }
|
||||||
|
public string Title { get => _title; protected set => this.RaiseAndSetIfChanged(ref _title, value); }
|
||||||
|
public string Length { get => _length; protected set => this.RaiseAndSetIfChanged(ref _length, value); }
|
||||||
|
public string Authors { get => _authors; protected set => this.RaiseAndSetIfChanged(ref _authors, value); }
|
||||||
|
public string Narrators { get => _narrators; protected set => this.RaiseAndSetIfChanged(ref _narrators, value); }
|
||||||
|
public string Category { get => _category; protected set => this.RaiseAndSetIfChanged(ref _category, value); }
|
||||||
|
public string Misc { get => _misc; protected set => this.RaiseAndSetIfChanged(ref _misc, value); }
|
||||||
|
public string Description { get => _description; protected set => this.RaiseAndSetIfChanged(ref _description, value); }
|
||||||
|
public Rating ProductRating { get => _productrating; protected set => this.RaiseAndSetIfChanged(ref _productrating, value); }
|
||||||
public Rating MyRating
|
public Rating MyRating
|
||||||
{
|
{
|
||||||
get => _myRating;
|
get => _myRating;
|
||||||
|
|||||||
@ -58,8 +58,22 @@ namespace LibationAvalonia.ViewModels
|
|||||||
|
|
||||||
public LibraryBookEntry(LibraryBook libraryBook)
|
public LibraryBookEntry(LibraryBook libraryBook)
|
||||||
{
|
{
|
||||||
LibraryBook = libraryBook;
|
setLibraryBook(libraryBook);
|
||||||
LoadCover();
|
LoadCover();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateLibraryBook(LibraryBook libraryBook)
|
||||||
|
{
|
||||||
|
if (AudibleProductId != libraryBook.Book.AudibleProductId)
|
||||||
|
throw new Exception("Invalid grid entry update. IDs must match");
|
||||||
|
|
||||||
|
UserDefinedItem.ItemChanged -= UserDefinedItem_ItemChanged;
|
||||||
|
setLibraryBook(libraryBook);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLibraryBook(LibraryBook libraryBook)
|
||||||
|
{
|
||||||
|
LibraryBook = libraryBook;
|
||||||
|
|
||||||
Title = Book.Title;
|
Title = Book.Title;
|
||||||
Series = Book.SeriesNames();
|
Series = Book.SeriesNames();
|
||||||
@ -77,6 +91,7 @@ namespace LibationAvalonia.ViewModels
|
|||||||
Description = TrimTextToWord(LongDescription, 62);
|
Description = TrimTextToWord(LongDescription, 62);
|
||||||
SeriesIndex = Book.SeriesLink.FirstOrDefault()?.Index ?? 0;
|
SeriesIndex = Book.SeriesLink.FirstOrDefault()?.Index ?? 0;
|
||||||
|
|
||||||
|
this.RaisePropertyChanged(nameof(MyRating));
|
||||||
UserDefinedItem.ItemChanged += UserDefinedItem_ItemChanged;
|
UserDefinedItem.ItemChanged += UserDefinedItem_ItemChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,11 +20,11 @@ namespace LibationAvalonia.ViewModels
|
|||||||
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 List<GridEntry> SOURCE = new();
|
private readonly AvaloniaList<GridEntry> 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<GridEntry> FilteredInGridEntries;
|
private List<GridEntry> FilteredInGridEntries;
|
||||||
public string FilterString { get; private set; }
|
public string FilterString { get; private set; }
|
||||||
public DataGridCollectionView GridEntries { get; }
|
public DataGridCollectionView GridEntries { get; private set; }
|
||||||
|
|
||||||
private bool _removeColumnVisivle;
|
private bool _removeColumnVisivle;
|
||||||
public bool RemoveColumnVisivle { get => _removeColumnVisivle; private set => this.RaiseAndSetIfChanged(ref _removeColumnVisivle, value); }
|
public bool RemoveColumnVisivle { get => _removeColumnVisivle; private set => this.RaiseAndSetIfChanged(ref _removeColumnVisivle, value); }
|
||||||
@ -42,59 +42,60 @@ namespace LibationAvalonia.ViewModels
|
|||||||
public ProductsDisplayViewModel()
|
public ProductsDisplayViewModel()
|
||||||
{
|
{
|
||||||
SearchEngineCommands.SearchEngineUpdated += SearchEngineCommands_SearchEngineUpdated;
|
SearchEngineCommands.SearchEngineUpdated += SearchEngineCommands_SearchEngineUpdated;
|
||||||
GridEntries = new(SOURCE);
|
VisibleCountChanged?.Invoke(this, 0);
|
||||||
GridEntries.Filter = CollectionFilter;
|
}
|
||||||
|
|
||||||
GridEntries.CollectionChanged += (s, e)
|
private static readonly System.Reflection.MethodInfo SetFlagsMethod;
|
||||||
=> VisibleCountChanged?.Invoke(this, GridEntries.OfType<LibraryBookEntry>().Count());
|
|
||||||
|
/// <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 });
|
||||||
|
|
||||||
|
static ProductsDisplayViewModel()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* When a book is removed from the library, SearchEngineUpdated is fired before LibrarySizeChanged, so
|
||||||
|
* the book is removed from the filtered set and the grid is refreshed before RemoveBooks() is ever
|
||||||
|
* called.
|
||||||
|
*
|
||||||
|
* To remove an item from DataGridCollectionView, it must be be in the current filtered view. If it's
|
||||||
|
* not and you try to remove the book from the source list, the source will fire NotifyCollectionChanged
|
||||||
|
* on an invalid item and the DataGridCollectionView will throw an exception. There are two ways to
|
||||||
|
* remove an item that is filtered out of the DataGridCollectionView:
|
||||||
|
*
|
||||||
|
* (1) Re-add the item to the filtered-in list and refresh the grid so DataGridCollectionView knows
|
||||||
|
* that the item is present. This causes the whole grid to flicker to refresh twice in rapid
|
||||||
|
* succession, which is undesirable.
|
||||||
|
*
|
||||||
|
* (2) Remove it from the underlying collection and suppress NotifyCollectionChanged. This is the
|
||||||
|
* method used. Steps to complete a removal using this method:
|
||||||
|
*
|
||||||
|
* (a) Set DataGridCollectionView.CollectionViewFlags.ShouldProcessCollectionChanged to false.
|
||||||
|
* (b) Remove the item from the source list. The source will fire NotifyCollectionChanged, but the
|
||||||
|
* DataGridCollectionView will ignore it.
|
||||||
|
* (c) Reset the flag to true.
|
||||||
|
*/
|
||||||
|
|
||||||
|
SetFlagsMethod =
|
||||||
|
typeof(DataGridCollectionView)
|
||||||
|
.GetMethod("SetFlag", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Display Functions
|
#region Display Functions
|
||||||
|
|
||||||
/// <summary>
|
internal void BindToGrid(List<LibraryBook> dbBooks)
|
||||||
/// Call when there's been a change to the library
|
|
||||||
/// </summary>
|
|
||||||
public async Task DisplayBooksAsync(List<LibraryBook> dbBooks)
|
|
||||||
{
|
{
|
||||||
try
|
GridEntries = new(SOURCE)
|
||||||
{
|
{
|
||||||
var existingSeriesEntries = SOURCE.SeriesEntries().ToList();
|
Filter = CollectionFilter
|
||||||
|
};
|
||||||
|
|
||||||
FilteredInGridEntries?.Clear();
|
GridEntries.CollectionChanged += (_, _)
|
||||||
SOURCE.Clear();
|
=> VisibleCountChanged?.Invoke(this, GridEntries.OfType<LibraryBookEntry>().Count());
|
||||||
SOURCE.AddRange(CreateGridEntries(dbBooks));
|
|
||||||
|
|
||||||
//If replacing the list, preserve user's existing collapse/expand
|
|
||||||
//state. When resetting a list, default state is cosed.
|
|
||||||
foreach (var series in existingSeriesEntries)
|
|
||||||
{
|
|
||||||
var sEntry = SOURCE.FirstOrDefault(ge => ge.AudibleProductId == series.AudibleProductId);
|
|
||||||
if (sEntry is SeriesEntry se)
|
|
||||||
se.Liberate.Expanded = series.Liberate.Expanded;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Run query on new list
|
|
||||||
FilteredInGridEntries = QueryResults(SOURCE, FilterString);
|
|
||||||
|
|
||||||
await refreshGrid();
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Serilog.Log.Error(ex, "Error displaying library in {0}", nameof(ProductsDisplayViewModel));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task refreshGrid()
|
|
||||||
{
|
|
||||||
if (GridEntries.IsEditingItem)
|
|
||||||
await Dispatcher.UIThread.InvokeAsync(GridEntries.CommitEdit);
|
|
||||||
|
|
||||||
await Dispatcher.UIThread.InvokeAsync(GridEntries.Refresh);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<GridEntry> CreateGridEntries(IEnumerable<LibraryBook> dbBooks)
|
|
||||||
{
|
|
||||||
var geList = dbBooks
|
var geList = dbBooks
|
||||||
.Where(lb => lb.Book.IsProduct())
|
.Where(lb => lb.Book.IsProduct())
|
||||||
.Select(b => new LibraryBookEntry(b))
|
.Select(b => new LibraryBookEntry(b))
|
||||||
@ -103,26 +104,159 @@ namespace LibationAvalonia.ViewModels
|
|||||||
|
|
||||||
var episodes = dbBooks.Where(lb => lb.Book.IsEpisodeChild());
|
var episodes = dbBooks.Where(lb => lb.Book.IsEpisodeChild());
|
||||||
|
|
||||||
foreach (var parent in dbBooks.Where(lb => lb.Book.IsEpisodeParent()))
|
var seriesBooks = dbBooks.Where(lb => lb.Book.IsEpisodeParent()).ToList();
|
||||||
|
|
||||||
|
foreach (var parent in seriesBooks)
|
||||||
{
|
{
|
||||||
var seriesEpisodes = episodes.FindChildren(parent);
|
var seriesEpisodes = episodes.FindChildren(parent);
|
||||||
|
|
||||||
if (!seriesEpisodes.Any()) continue;
|
if (!seriesEpisodes.Any()) continue;
|
||||||
|
|
||||||
var seriesEntry = new SeriesEntry(parent, seriesEpisodes);
|
var seriesEntry = new SeriesEntry(parent, seriesEpisodes);
|
||||||
|
seriesEntry.Liberate.Expanded = false;
|
||||||
|
|
||||||
geList.Add(seriesEntry);
|
geList.Add(seriesEntry);
|
||||||
geList.AddRange(seriesEntry.Children);
|
geList.AddRange(seriesEntry.Children);
|
||||||
}
|
}
|
||||||
|
|
||||||
var bookList = geList.OrderByDescending(e => e.DateAdded).ToList();
|
//Create the filtered-in list before adding entries to avoid a refresh
|
||||||
|
FilteredInGridEntries = QueryResults(geList, FilterString);
|
||||||
|
SOURCE.AddRange(geList.OrderByDescending(e => e.DateAdded));
|
||||||
|
}
|
||||||
|
|
||||||
//ListIndex is used by RowComparer to make column sort stable
|
/// <summary>
|
||||||
int index = 0;
|
/// Call when there's been a change to the library
|
||||||
foreach (GridEntry di in bookList)
|
/// </summary>
|
||||||
di.ListIndex = index++;
|
internal async Task UpdateGridAsync(List<LibraryBook> dbBooks)
|
||||||
|
{
|
||||||
|
#region Add new or update existing grid entries
|
||||||
|
|
||||||
return bookList;
|
//Add absent entries to grid, or update existing entry
|
||||||
|
var allEntries = SOURCE.BookEntries();
|
||||||
|
var seriesEntries = SOURCE.SeriesEntries().ToList();
|
||||||
|
var parentedEpisodes = dbBooks.ParentedEpisodes();
|
||||||
|
|
||||||
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
foreach (var libraryBook in dbBooks.OrderBy(e => e.DateAdded))
|
||||||
|
{
|
||||||
|
var existingEntry = allEntries.FindByAsin(libraryBook.Book.AudibleProductId);
|
||||||
|
|
||||||
|
if (libraryBook.Book.IsProduct())
|
||||||
|
UpsertBook(libraryBook, existingEntry);
|
||||||
|
else if (parentedEpisodes.Any(lb => lb == libraryBook))
|
||||||
|
//Only try to add or update is this LibraryBook is a know child of a parent
|
||||||
|
UpsertEpisode(libraryBook, existingEntry, seriesEntries, dbBooks);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Remove entries no longer in the library
|
||||||
|
|
||||||
|
//Rapid successive book removals will cause changes to SOURCE after the update has
|
||||||
|
//begun but before it has completed, so perform all updates on a copy of the list.
|
||||||
|
var sourceSnapshot = SOURCE.ToList();
|
||||||
|
|
||||||
|
// remove deleted from grid.
|
||||||
|
// note: actual deletion from db must still occur via the RemoveBook feature. deleting from audible will not trigger this
|
||||||
|
var removedBooks =
|
||||||
|
sourceSnapshot
|
||||||
|
.BookEntries()
|
||||||
|
.ExceptBy(dbBooks.Select(lb => lb.Book.AudibleProductId), ge => ge.AudibleProductId);
|
||||||
|
|
||||||
|
//Remove books in series from their parents' Children list
|
||||||
|
foreach (var removed in removedBooks.Where(b => b.Parent is not null))
|
||||||
|
removed.Parent.RemoveChild(removed);
|
||||||
|
|
||||||
|
//Remove series that have no children
|
||||||
|
var removedSeries = sourceSnapshot.EmptySeries();
|
||||||
|
|
||||||
|
await Dispatcher.UIThread.InvokeAsync(() => RemoveBooks(removedBooks, removedSeries));
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
await Filter(FilterString);
|
||||||
|
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveBooks(IEnumerable<LibraryBookEntry> removedBooks, IEnumerable<SeriesEntry> removedSeries)
|
||||||
|
{
|
||||||
|
foreach (var removed in removedBooks.Cast<GridEntry>().Concat(removedSeries).Where(b => b is not null).ToList())
|
||||||
|
{
|
||||||
|
if (GridEntries.PassesFilter(removed))
|
||||||
|
GridEntries.Remove(removed);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetShouldProcessCollectionChanged(false);
|
||||||
|
SOURCE.Remove(removed);
|
||||||
|
SetShouldProcessCollectionChanged(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpsertBook(LibraryBook book, LibraryBookEntry existingBookEntry)
|
||||||
|
{
|
||||||
|
if (existingBookEntry is null)
|
||||||
|
// Add the new product to top
|
||||||
|
SOURCE.Insert(0, new LibraryBookEntry(book));
|
||||||
|
else
|
||||||
|
// update existing
|
||||||
|
existingBookEntry.UpdateLibraryBook(book);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpsertEpisode(LibraryBook episodeBook, LibraryBookEntry existingEpisodeEntry, List<SeriesEntry> seriesEntries, IEnumerable<LibraryBook> dbBooks)
|
||||||
|
{
|
||||||
|
if (existingEpisodeEntry is null)
|
||||||
|
{
|
||||||
|
LibraryBookEntry episodeEntry;
|
||||||
|
|
||||||
|
var seriesEntry = seriesEntries.FindSeriesParent(episodeBook);
|
||||||
|
|
||||||
|
if (seriesEntry is null)
|
||||||
|
{
|
||||||
|
//Series doesn't exist yet, so create and add it
|
||||||
|
var seriesBook = dbBooks.FindSeriesParent(episodeBook);
|
||||||
|
|
||||||
|
if (seriesBook is null)
|
||||||
|
{
|
||||||
|
//This is only possible if the user's db has some malformed
|
||||||
|
//entries from earlier Libation releases that could not be
|
||||||
|
//automatically fixed. Log, but don't throw.
|
||||||
|
Serilog.Log.Logger.Error("Episode={0}, Episode Series: {1}", episodeBook, episodeBook.Book.SeriesNames());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
seriesEntry = new SeriesEntry(seriesBook, new[] { episodeBook });
|
||||||
|
seriesEntries.Add(seriesEntry);
|
||||||
|
|
||||||
|
episodeEntry = seriesEntry.Children[0];
|
||||||
|
seriesEntry.Liberate.Expanded = true;
|
||||||
|
SOURCE.Insert(0, seriesEntry);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Series exists. Create and add episode child then update the SeriesEntry
|
||||||
|
episodeEntry = new(episodeBook) { Parent = seriesEntry };
|
||||||
|
seriesEntry.Children.Add(episodeEntry);
|
||||||
|
var seriesBook = dbBooks.Single(lb => lb.Book.AudibleProductId == seriesEntry.LibraryBook.Book.AudibleProductId);
|
||||||
|
seriesEntry.UpdateLibraryBook(seriesBook);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add episode to the grid beneath the parent
|
||||||
|
int seriesIndex = SOURCE.IndexOf(seriesEntry);
|
||||||
|
SOURCE.Insert(seriesIndex + 1, episodeEntry);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
existingEpisodeEntry.UpdateLibraryBook(episodeBook);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task refreshGrid()
|
||||||
|
{
|
||||||
|
if (GridEntries.IsEditingItem)
|
||||||
|
await Dispatcher.UIThread.InvokeAsync(GridEntries.CommitEdit);
|
||||||
|
|
||||||
|
await Dispatcher.UIThread.InvokeAsync(GridEntries.Refresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ToggleSeriesExpanded(SeriesEntry seriesEntry)
|
public async Task ToggleSeriesExpanded(SeriesEntry seriesEntry)
|
||||||
@ -138,9 +272,6 @@ namespace LibationAvalonia.ViewModels
|
|||||||
|
|
||||||
public async Task Filter(string searchString)
|
public async Task Filter(string searchString)
|
||||||
{
|
{
|
||||||
if (searchString == FilterString)
|
|
||||||
return;
|
|
||||||
|
|
||||||
FilterString = searchString;
|
FilterString = searchString;
|
||||||
|
|
||||||
if (SOURCE.Count == 0)
|
if (SOURCE.Count == 0)
|
||||||
|
|||||||
@ -14,6 +14,12 @@ namespace LibationAvalonia.ViewModels
|
|||||||
public static IEnumerable<SeriesEntry> SeriesEntries(this IEnumerable<GridEntry> gridEntries)
|
public static IEnumerable<SeriesEntry> SeriesEntries(this IEnumerable<GridEntry> gridEntries)
|
||||||
=> gridEntries.OfType<SeriesEntry>();
|
=> gridEntries.OfType<SeriesEntry>();
|
||||||
|
|
||||||
|
public static T? FindByAsin<T>(this IEnumerable<T> gridEntries, string audibleProductID) where T : GridEntry
|
||||||
|
=> gridEntries.FirstOrDefault(i => i.AudibleProductId == audibleProductID);
|
||||||
|
|
||||||
|
public static IEnumerable<SeriesEntry> EmptySeries(this IEnumerable<GridEntry> gridEntries)
|
||||||
|
=> gridEntries.SeriesEntries().Where(i => i.Children.Count == 0);
|
||||||
|
|
||||||
public static SeriesEntry? FindSeriesParent(this IEnumerable<GridEntry> gridEntries, LibraryBook seriesEpisode)
|
public static SeriesEntry? FindSeriesParent(this IEnumerable<GridEntry> gridEntries, LibraryBook seriesEpisode)
|
||||||
{
|
{
|
||||||
if (seriesEpisode.Book.SeriesLink is null) return null;
|
if (seriesEpisode.Book.SeriesLink is null) return null;
|
||||||
|
|||||||
@ -56,15 +56,36 @@ namespace LibationAvalonia.ViewModels
|
|||||||
{
|
{
|
||||||
Liberate = new LiberateButtonStatus(IsSeries);
|
Liberate = new LiberateButtonStatus(IsSeries);
|
||||||
SeriesIndex = -1;
|
SeriesIndex = -1;
|
||||||
LibraryBook = parent;
|
|
||||||
|
|
||||||
LoadCover();
|
|
||||||
|
|
||||||
Children = children
|
Children = children
|
||||||
.Select(c => new LibraryBookEntry(c) { Parent = this })
|
.Select(c => new LibraryBookEntry(c) { Parent = this })
|
||||||
.OrderBy(c => c.SeriesIndex)
|
.OrderBy(c => c.SeriesIndex)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
setLibraryBook(parent);
|
||||||
|
LoadCover();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveChild(LibraryBookEntry lbe)
|
||||||
|
{
|
||||||
|
Children.Remove(lbe);
|
||||||
|
PurchaseDate = Children.Min(c => c.LibraryBook.DateAdded).ToString("d");
|
||||||
|
int bookLenMins = Children.Sum(c => c.LibraryBook.Book.LengthInMinutes);
|
||||||
|
Length = bookLenMins == 0 ? "" : $"{bookLenMins / 60} hr {bookLenMins % 60} min";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateLibraryBook(LibraryBook libraryBook)
|
||||||
|
{
|
||||||
|
if (AudibleProductId != libraryBook.Book.AudibleProductId)
|
||||||
|
throw new Exception("Invalid grid entry update. IDs must match");
|
||||||
|
|
||||||
|
setLibraryBook(libraryBook);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLibraryBook(LibraryBook libraryBook)
|
||||||
|
{
|
||||||
|
LibraryBook = libraryBook;
|
||||||
|
|
||||||
Title = Book.Title;
|
Title = Book.Title;
|
||||||
Series = Book.SeriesNames();
|
Series = Book.SeriesNames();
|
||||||
//Ratings are changed using Update(), which is a problem for Avalonia data bindings because
|
//Ratings are changed using Update(), which is a problem for Avalonia data bindings because
|
||||||
@ -81,6 +102,8 @@ namespace LibationAvalonia.ViewModels
|
|||||||
PurchaseDate = Children.Min(c => c.LibraryBook.DateAdded).ToString("d");
|
PurchaseDate = Children.Min(c => c.LibraryBook.DateAdded).ToString("d");
|
||||||
int bookLenMins = Children.Sum(c => c.LibraryBook.Book.LengthInMinutes);
|
int bookLenMins = Children.Sum(c => c.LibraryBook.Book.LengthInMinutes);
|
||||||
Length = bookLenMins == 0 ? "" : $"{bookLenMins / 60} hr {bookLenMins % 60} min";
|
Length = bookLenMins == 0 ? "" : $"{bookLenMins / 60} hr {bookLenMins % 60} min";
|
||||||
|
|
||||||
|
this.RaisePropertyChanged(nameof(MyRating));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -51,7 +51,7 @@ namespace LibationAvalonia.Views
|
|||||||
{
|
{
|
||||||
this.LibraryLoaded += MainWindow_LibraryLoaded;
|
this.LibraryLoaded += MainWindow_LibraryLoaded;
|
||||||
|
|
||||||
LibraryCommands.LibrarySizeChanged += async (_, _) => await _viewModel.ProductsDisplay.DisplayBooksAsync(DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
|
LibraryCommands.LibrarySizeChanged += async (_, _) => await _viewModel.ProductsDisplay.UpdateGridAsync(DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
|
||||||
Closing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
Closing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||||
}
|
}
|
||||||
Closing += MainWindow_Closing;
|
Closing += MainWindow_Closing;
|
||||||
@ -67,7 +67,7 @@ namespace LibationAvalonia.Views
|
|||||||
if (QuickFilters.UseDefault)
|
if (QuickFilters.UseDefault)
|
||||||
await performFilter(QuickFilters.Filters.FirstOrDefault());
|
await performFilter(QuickFilters.Filters.FirstOrDefault());
|
||||||
|
|
||||||
await _viewModel.ProductsDisplay.DisplayBooksAsync(dbBooks);
|
_viewModel.ProductsDisplay.BindToGrid(dbBooks);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
|
|||||||
@ -44,7 +44,7 @@ namespace LibationAvalonia.Views
|
|||||||
};
|
};
|
||||||
|
|
||||||
var pdvm = new ProductsDisplayViewModel();
|
var pdvm = new ProductsDisplayViewModel();
|
||||||
_ = pdvm.DisplayBooksAsync(sampleEntries);
|
pdvm.BindToGrid(sampleEntries);
|
||||||
DataContext = pdvm;
|
DataContext = pdvm;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user