feat(flutter): extend metadata prefetch to library tabs + artist detail

MetadataPrefetcher gains warmAlbums(ids) / warmArtists(ids) public
methods so callers can fan out drift-cache warm-ups for whatever
collection just landed.

Wired into:
- Library Artists tab — warms first 8 artists from the page on every
  data emit (initial + paginate + refresh).
- Library Albums tab — same for first 8 albums.
- Artist detail album grid — warms first 8 albums from the artist's
  album list as soon as it loads, so tapping into any of them is a
  drift hit.

Hard-cap of 8 per call (same as the home prefetch). Set spans across
calls aren't deduped at this layer because providers themselves
short-circuit on cached values.

Also instrument the artist-detail play button: try/catch around the
artistTracksProvider read + playTracks call, snackbar on
empty-tracks or thrown-error so silent failures stop being silent.
The current behavior was an early return on tracks.isEmpty with no
visible feedback.
This commit is contained in:
2026-05-11 18:52:37 -04:00
parent 22152b1ba3
commit 4bd069430b
3 changed files with 78 additions and 15 deletions
+16 -4
View File
@@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart';
import '../api/endpoints/library_lists.dart';
import '../api/endpoints/likes.dart';
import '../api/endpoints/me.dart';
import '../cache/metadata_prefetcher.dart';
import '../library/library_providers.dart' show dioProvider;
import '../models/album.dart';
import '../models/artist.dart';
@@ -207,7 +208,12 @@ class _ArtistsTab extends ConsumerWidget {
return ref.watch(_libraryArtistsProvider).when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('$e', style: TextStyle(color: fs.error))),
data: (page) => page.items.isEmpty
data: (page) {
// Warm details for the first screenful so taps are instant.
ref
.read(metadataPrefetcherProvider)
.warmArtists(page.items.map((a) => a.id));
return page.items.isEmpty
? Center(child: Text("No artists yet — scan a library folder via the server's config.", style: TextStyle(color: fs.ash), textAlign: TextAlign.center))
: RefreshIndicator(
onRefresh: () async {
@@ -247,7 +253,8 @@ class _ArtistsTab extends ConsumerWidget {
},
),
),
),
);
},
);
}
}
@@ -261,7 +268,11 @@ class _AlbumsTab extends ConsumerWidget {
return ref.watch(_libraryAlbumsProvider).when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('$e', style: TextStyle(color: fs.error))),
data: (page) => page.items.isEmpty
data: (page) {
ref
.read(metadataPrefetcherProvider)
.warmAlbums(page.items.map((a) => a.id));
return page.items.isEmpty
? Center(child: Text("No albums yet — scan a library folder via the server's config.", style: TextStyle(color: fs.ash), textAlign: TextAlign.center))
: RefreshIndicator(
onRefresh: () async {
@@ -315,7 +326,8 @@ class _AlbumsTab extends ConsumerWidget {
),
);
}),
),
);
},
);
}
}