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>(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 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 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 = {}; final artistIds = {}; 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 f) { f.then((_) {}).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((ref) { return MetadataPrefetcher(ref); });