updates
This commit is contained in:
parent
2ce32cac4e
commit
b6639fb1c9
26 changed files with 454 additions and 204 deletions
|
|
@ -5,7 +5,7 @@ import '../subsonic/models.dart';
|
|||
/// Held in `state/providers.dart` (not persisted) so the app never silently
|
||||
/// reopens filtered. Sort order *is* persisted (see [AppSettings]).
|
||||
class BrowseFilter {
|
||||
const BrowseFilter({this.genre, this.year});
|
||||
const BrowseFilter({this.genre, this.year, this.minRating});
|
||||
|
||||
/// Case-insensitive genre match, or null for "all genres".
|
||||
final String? genre;
|
||||
|
|
@ -13,14 +13,27 @@ class BrowseFilter {
|
|||
/// Exact release-year match, or null for "all years".
|
||||
final int? year;
|
||||
|
||||
bool get isActive => genre != null || year != null;
|
||||
/// Minimum star rating (1–5) a track must meet, or null for "any rating".
|
||||
/// Tracks are kept when their effective rating is `>= minRating`.
|
||||
final int? minRating;
|
||||
|
||||
int get activeCount => (genre != null ? 1 : 0) + (year != null ? 1 : 0);
|
||||
bool get isActive => genre != null || year != null || minRating != null;
|
||||
|
||||
BrowseFilter copyWith({Object? genre = _unset, Object? year = _unset}) =>
|
||||
int get activeCount =>
|
||||
(genre != null ? 1 : 0) +
|
||||
(year != null ? 1 : 0) +
|
||||
(minRating != null ? 1 : 0);
|
||||
|
||||
BrowseFilter copyWith({
|
||||
Object? genre = _unset,
|
||||
Object? year = _unset,
|
||||
Object? minRating = _unset,
|
||||
}) =>
|
||||
BrowseFilter(
|
||||
genre: identical(genre, _unset) ? this.genre : genre as String?,
|
||||
year: identical(year, _unset) ? this.year : year as int?,
|
||||
minRating:
|
||||
identical(minRating, _unset) ? this.minRating : minRating as int?,
|
||||
);
|
||||
|
||||
static const Object _unset = Object();
|
||||
|
|
@ -60,6 +73,15 @@ bool _genreMatches(String? itemGenre, String? filterGenre) {
|
|||
int _byString(String? a, String? b) =>
|
||||
(a ?? '').toLowerCase().compareTo((b ?? '').toLowerCase());
|
||||
|
||||
/// A track's effective 0–5 rating: the live value from the favorites map (see
|
||||
/// `state/favorites.dart`) when present, else the index-time [Song.userRating],
|
||||
/// else 0 (unrated). Mirrors the star UI on the Now Playing screen.
|
||||
int _effectiveRating(Song s, Map<String, int> ratings) {
|
||||
final live = ratings[s.id];
|
||||
if (live != null && live > 0) return live;
|
||||
return s.userRating ?? 0;
|
||||
}
|
||||
|
||||
/// Compare where a null [a]/[b] always sorts *last*, regardless of [descending].
|
||||
/// Takes bare [Comparable] so both `int` (`Comparable<num>`) and `DateTime` work.
|
||||
int _nullsLast(Comparable? a, Comparable? b, {bool descending = false}) {
|
||||
|
|
@ -112,11 +134,15 @@ List<Album> applyAlbumQuery(
|
|||
List<Song> applyTrackQuery(
|
||||
List<Song> songs,
|
||||
BrowseFilter filter,
|
||||
TrackSort sort,
|
||||
) {
|
||||
TrackSort sort, {
|
||||
Map<String, int> ratings = const {},
|
||||
}) {
|
||||
final out = songs
|
||||
.where((s) => _genreMatches(s.genre, filter.genre))
|
||||
.where((s) => filter.year == null || s.year == filter.year)
|
||||
.where((s) =>
|
||||
filter.minRating == null ||
|
||||
_effectiveRating(s, ratings) >= filter.minRating!)
|
||||
.toList();
|
||||
|
||||
switch (sort) {
|
||||
|
|
@ -144,6 +170,12 @@ List<Song> applyTrackQuery(
|
|||
final c = _nullsLast(a.createdAt, b.createdAt, descending: true);
|
||||
return c != 0 ? c : _byString(a.title, b.title);
|
||||
});
|
||||
case TrackSort.ratingDesc:
|
||||
out.sort((a, b) {
|
||||
// Descending: highest rating first; unrated (0) naturally sinks last.
|
||||
final c = _effectiveRating(b, ratings) - _effectiveRating(a, ratings);
|
||||
return c != 0 ? c : _byString(a.title, b.title);
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue