This commit is contained in:
Forrest 2026-08-04 16:08:51 -04:00
parent d558aba246
commit 3bd713d667
17 changed files with 1566 additions and 132 deletions

View file

@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../downloads/download_manager.dart';
import '../history/play_history.dart';
import '../library/browse_query.dart';
import '../library/library_index.dart';
import '../playback/playback_engine.dart';
import '../playlists/playlists.dart';
@ -17,7 +18,9 @@ import 'favorites.dart';
// `BrowseMode`/`SearchMode` are defined in the settings store (so they can be
// persisted) but historically lived here — re-export so existing importers of
// this file keep resolving them.
export '../settings/settings_store.dart' show BrowseMode, SearchMode;
export '../settings/settings_store.dart'
show BrowseMode, SearchMode, AlbumSort, TrackSort;
export '../library/browse_query.dart' show BrowseFilter;
// ---- Connection ---------------------------------------------------------
@ -296,6 +299,58 @@ final libraryIndexProvider =
return controller;
});
// ---- Browse filtering / sorting -----------------------------------------
/// Session-only genre/year filter for the Albums grid (resets on restart).
final albumFilterProvider =
StateProvider<BrowseFilter>((_) => const BrowseFilter());
/// Session-only genre/year filter for the Tracks list (resets on restart).
final trackFilterProvider =
StateProvider<BrowseFilter>((_) => const BrowseFilter());
/// Distinct genres present across all albums, for the album genre picker.
final albumGenresProvider = Provider<List<String>>((ref) {
final albums = ref.watch(albumsProvider).valueOrNull ?? const <Album>[];
return distinctGenres(albums.map((a) => a.genre));
});
/// Distinct release years present across all albums, newest first.
final albumYearsProvider = Provider<List<int>>((ref) {
final albums = ref.watch(albumsProvider).valueOrNull ?? const <Album>[];
return distinctYears(albums.map((a) => a.year));
});
/// Albums after applying the session filter and the persisted sort. Async so
/// callers keep the underlying load/error states from [albumsProvider].
final visibleAlbumsProvider = Provider<AsyncValue<List<Album>>>((ref) {
final filter = ref.watch(albumFilterProvider);
final sort = ref.watch(settingsProvider.select((s) => s.albumSort));
return ref
.watch(albumsProvider)
.whenData((albums) => applyAlbumQuery(albums, filter, sort));
});
/// Distinct genres present across all indexed tracks.
final trackGenresProvider = Provider<List<String>>((ref) {
final songs = ref.watch(libraryIndexProvider).songs;
return distinctGenres(songs.map((s) => s.genre));
});
/// Distinct release years present across all indexed tracks, newest first.
final trackYearsProvider = Provider<List<int>>((ref) {
final songs = ref.watch(libraryIndexProvider).songs;
return distinctYears(songs.map((s) => s.year));
});
/// Indexed tracks after applying the session filter and the persisted sort.
final visibleTracksProvider = Provider<List<Song>>((ref) {
final filter = ref.watch(trackFilterProvider);
final sort = ref.watch(settingsProvider.select((s) => s.trackSort));
final songs = ref.watch(libraryIndexProvider).songs;
return applyTrackQuery(songs, filter, sort);
});
/// Recently-added albums (`getAlbumList2` type `newest`) — a discovery shelf on
/// the Home tab. Small page; the Home shelf shows the first handful.
final newestAlbumsProvider = FutureProvider<List<Album>>((ref) async {
@ -418,6 +473,11 @@ final downloadManagerProvider =
ref.listen<String?>(serverKeyProvider, (_, _) {
controller.reloadForServer();
});
// Raising the concurrency cap should launch queued downloads right away.
ref.listen<int>(
settingsProvider.select((s) => s.maxConcurrentDownloads),
(_, _) => controller.onConcurrencyChanged(),
);
return controller;
});
@ -451,6 +511,30 @@ final realPlaylistsProvider = Provider<List<Playlist>>((ref) => ref
final tagsProvider = Provider<List<Playlist>>(
(ref) => ref.watch(playlistsProvider).playlists.where(isTagPlaylist).toList());
/// The signed-in user's name on the active server, or null when disconnected.
/// Used to split owned playlists from ones shared by other users.
final currentUsernameProvider = Provider<String?>(
(ref) => ref.watch(connectionProvider).credentials?.username);
/// The user's own playlists (owned, or owner unknown). Backs the main list and
/// the "add to playlist" sheet — you can only add tracks to your own playlists.
final myPlaylistsProvider = Provider<List<Playlist>>((ref) {
final me = ref.watch(currentUsernameProvider);
return ref
.watch(realPlaylistsProvider)
.where((p) => isOwnedBy(p, me))
.toList();
});
/// Playlists shared by *other* users on the same server (public, owner != me).
final sharedPlaylistsProvider = Provider<List<Playlist>>((ref) {
final me = ref.watch(currentUsernameProvider);
return ref
.watch(realPlaylistsProvider)
.where((p) => !isOwnedBy(p, me))
.toList();
});
// ---- Playback -----------------------------------------------------------
final playbackProvider =