updates
This commit is contained in:
parent
d558aba246
commit
3bd713d667
17 changed files with 1566 additions and 132 deletions
149
lib/library/browse_query.dart
Normal file
149
lib/library/browse_query.dart
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
import '../settings/settings_store.dart';
|
||||
import '../subsonic/models.dart';
|
||||
|
||||
/// Session-scoped filter for a browse view: an optional genre and release year.
|
||||
/// 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});
|
||||
|
||||
/// Case-insensitive genre match, or null for "all genres".
|
||||
final String? genre;
|
||||
|
||||
/// Exact release-year match, or null for "all years".
|
||||
final int? year;
|
||||
|
||||
bool get isActive => genre != null || year != null;
|
||||
|
||||
int get activeCount => (genre != null ? 1 : 0) + (year != null ? 1 : 0);
|
||||
|
||||
BrowseFilter copyWith({Object? genre = _unset, Object? year = _unset}) =>
|
||||
BrowseFilter(
|
||||
genre: identical(genre, _unset) ? this.genre : genre as String?,
|
||||
year: identical(year, _unset) ? this.year : year as int?,
|
||||
);
|
||||
|
||||
static const Object _unset = Object();
|
||||
}
|
||||
|
||||
/// Distinct, display-cased genres from a set of raw genre strings, sorted
|
||||
/// alphabetically. Dedupes case-insensitively (keeping first-seen casing) so a
|
||||
/// server that mixes "Rock"/"rock" collapses to one entry.
|
||||
List<String> distinctGenres(Iterable<String?> raw) {
|
||||
final byKey = <String, String>{};
|
||||
for (final g in raw) {
|
||||
final v = g?.trim();
|
||||
if (v == null || v.isEmpty) continue;
|
||||
byKey.putIfAbsent(v.toLowerCase(), () => v);
|
||||
}
|
||||
final out = byKey.values.toList()
|
||||
..sort((a, b) => a.toLowerCase().compareTo(b.toLowerCase()));
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Distinct release years present, newest first.
|
||||
List<int> distinctYears(Iterable<int?> raw) {
|
||||
final set = <int>{};
|
||||
for (final y in raw) {
|
||||
if (y != null && y > 0) set.add(y);
|
||||
}
|
||||
final out = set.toList()..sort((a, b) => b.compareTo(a));
|
||||
return out;
|
||||
}
|
||||
|
||||
bool _genreMatches(String? itemGenre, String? filterGenre) {
|
||||
if (filterGenre == null) return true;
|
||||
return itemGenre != null &&
|
||||
itemGenre.toLowerCase() == filterGenre.toLowerCase();
|
||||
}
|
||||
|
||||
int _byString(String? a, String? b) =>
|
||||
(a ?? '').toLowerCase().compareTo((b ?? '').toLowerCase());
|
||||
|
||||
/// 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}) {
|
||||
if (a == null && b == null) return 0;
|
||||
if (a == null) return 1;
|
||||
if (b == null) return -1;
|
||||
final c = a.compareTo(b);
|
||||
return descending ? -c : c;
|
||||
}
|
||||
|
||||
/// Filter then sort albums for the browse grid. Pure — no I/O.
|
||||
List<Album> applyAlbumQuery(
|
||||
List<Album> albums,
|
||||
BrowseFilter filter,
|
||||
AlbumSort sort,
|
||||
) {
|
||||
final out = albums
|
||||
.where((a) => _genreMatches(a.genre, filter.genre))
|
||||
.where((a) => filter.year == null || a.year == filter.year)
|
||||
.toList();
|
||||
|
||||
switch (sort) {
|
||||
case AlbumSort.nameAsc:
|
||||
out.sort((a, b) => _byString(a.name, b.name));
|
||||
case AlbumSort.artistAsc:
|
||||
out.sort((a, b) {
|
||||
final c = _byString(a.artist, b.artist);
|
||||
return c != 0 ? c : _byString(a.name, b.name);
|
||||
});
|
||||
case AlbumSort.yearDesc:
|
||||
out.sort((a, b) {
|
||||
final c = _nullsLast(a.year, b.year, descending: true);
|
||||
return c != 0 ? c : _byString(a.name, b.name);
|
||||
});
|
||||
case AlbumSort.yearAsc:
|
||||
out.sort((a, b) {
|
||||
final c = _nullsLast(a.year, b.year);
|
||||
return c != 0 ? c : _byString(a.name, b.name);
|
||||
});
|
||||
case AlbumSort.recentlyAdded:
|
||||
out.sort((a, b) {
|
||||
final c = _nullsLast(a.createdAt, b.createdAt, descending: true);
|
||||
return c != 0 ? c : _byString(a.name, b.name);
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Filter then sort tracks for the browse list. Pure — no I/O.
|
||||
List<Song> applyTrackQuery(
|
||||
List<Song> songs,
|
||||
BrowseFilter filter,
|
||||
TrackSort sort,
|
||||
) {
|
||||
final out = songs
|
||||
.where((s) => _genreMatches(s.genre, filter.genre))
|
||||
.where((s) => filter.year == null || s.year == filter.year)
|
||||
.toList();
|
||||
|
||||
switch (sort) {
|
||||
case TrackSort.titleAsc:
|
||||
out.sort((a, b) => _byString(a.title, b.title));
|
||||
case TrackSort.artistAsc:
|
||||
out.sort((a, b) {
|
||||
final c = _byString(a.artist, b.artist);
|
||||
return c != 0 ? c : _byString(a.title, b.title);
|
||||
});
|
||||
case TrackSort.albumAsc:
|
||||
out.sort((a, b) {
|
||||
final c = _byString(a.album, b.album);
|
||||
if (c != 0) return c;
|
||||
final t = _nullsLast(a.track, b.track);
|
||||
return t != 0 ? t : _byString(a.title, b.title);
|
||||
});
|
||||
case TrackSort.yearDesc:
|
||||
out.sort((a, b) {
|
||||
final c = _nullsLast(a.year, b.year, descending: true);
|
||||
return c != 0 ? c : _byString(a.title, b.title);
|
||||
});
|
||||
case TrackSort.recentlyAdded:
|
||||
out.sort((a, b) {
|
||||
final c = _nullsLast(a.createdAt, b.createdAt, descending: true);
|
||||
return c != 0 ? c : _byString(a.title, b.title);
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue