4bd069430b
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.
99 lines
3.4 KiB
Dart
99 lines
3.4 KiB
Dart
import 'package:flutter/foundation.dart' show debugPrint;
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../library/library_providers.dart';
|
|
import '../models/home_data.dart';
|
|
|
|
/// Pre-warms the drift cache for entities the user is most likely to
|
|
/// tap. The home prefetch fires when /api/home returns. Library
|
|
/// tabs and artist detail expose `warmAlbums` / `warmArtists` so they
|
|
/// can fan out reads from their own AsyncNotifier callbacks.
|
|
///
|
|
/// All reads are unawaited and idempotent — providers that already
|
|
/// have a cached value short-circuit. Readers/writers don't conflict
|
|
/// because each provider de-duplicates concurrent subscribers.
|
|
class MetadataPrefetcher {
|
|
MetadataPrefetcher(this._ref) {
|
|
_ref.listen<AsyncValue<HomeData>>(homeProvider, (_, next) {
|
|
next.whenData(_warmHome);
|
|
});
|
|
}
|
|
|
|
final Ref _ref;
|
|
|
|
/// Cap per section so a 50-album recently-added row doesn't fan
|
|
/// out 50 fetches the moment the screen appears. Top-N covers what
|
|
/// the user can see without scrolling on a typical phone.
|
|
static const _topN = 8;
|
|
|
|
/// Pre-warm drift for each album id (no-op past the first dedup).
|
|
/// Called from the library Albums tab + artist detail album grid.
|
|
void warmAlbums(Iterable<String> ids) {
|
|
var n = 0;
|
|
for (final id in ids) {
|
|
if (id.isEmpty) continue;
|
|
if (n++ >= _topN) break;
|
|
_swallow(_ref.read(albumProvider(id).future));
|
|
}
|
|
if (n > 0) debugPrint('metadataPrefetcher: warming $n albums');
|
|
}
|
|
|
|
/// Pre-warm drift for each artist id.
|
|
void warmArtists(Iterable<String> ids) {
|
|
var n = 0;
|
|
for (final id in ids) {
|
|
if (id.isEmpty) continue;
|
|
if (n++ >= _topN) break;
|
|
_swallow(_ref.read(artistProvider(id).future));
|
|
}
|
|
if (n > 0) debugPrint('metadataPrefetcher: warming $n artists');
|
|
}
|
|
|
|
void _warmHome(HomeData h) {
|
|
final albumIds = <String>{};
|
|
final artistIds = <String>{};
|
|
|
|
for (final a in h.recentlyAddedAlbums.take(_topN)) {
|
|
if (a.id.isNotEmpty) albumIds.add(a.id);
|
|
if (a.artistId.isNotEmpty) artistIds.add(a.artistId);
|
|
}
|
|
for (final a in h.rediscoverAlbums.take(_topN)) {
|
|
if (a.id.isNotEmpty) albumIds.add(a.id);
|
|
if (a.artistId.isNotEmpty) artistIds.add(a.artistId);
|
|
}
|
|
for (final ar in h.rediscoverArtists.take(_topN)) {
|
|
if (ar.id.isNotEmpty) artistIds.add(ar.id);
|
|
}
|
|
for (final ar in h.lastPlayedArtists.take(_topN)) {
|
|
if (ar.id.isNotEmpty) artistIds.add(ar.id);
|
|
}
|
|
for (final t in h.mostPlayedTracks.take(_topN)) {
|
|
if (t.albumId.isNotEmpty) albumIds.add(t.albumId);
|
|
if (t.artistId.isNotEmpty) artistIds.add(t.artistId);
|
|
}
|
|
|
|
debugPrint(
|
|
'metadataPrefetcher: warming ${albumIds.length} albums + ${artistIds.length} artists');
|
|
|
|
for (final id in albumIds) {
|
|
_swallow(_ref.read(albumProvider(id).future));
|
|
}
|
|
for (final id in artistIds) {
|
|
_swallow(_ref.read(artistProvider(id).future));
|
|
}
|
|
}
|
|
|
|
/// Discards the return value and any error from a fire-and-forget
|
|
/// provider read. We don't care about the value here — we only want
|
|
/// the side effect of writing drift.
|
|
void _swallow(Future<Object?> f) {
|
|
f.then<void>((_) {}).onError((_, __) {});
|
|
}
|
|
}
|
|
|
|
/// Read once at app start to activate the prefetcher (e.g. wire it
|
|
/// from a top-level Consumer or main.dart container override).
|
|
final metadataPrefetcherProvider = Provider<MetadataPrefetcher>((ref) {
|
|
return MetadataPrefetcher(ref);
|
|
});
|