0504cae27c
Slice A landed with three transitive imports that the analyzer correctly flagged as unused. AppDb / CachedAlbums table refs propagate through audio_cache_manager.dart's `show appDbProvider` re-export chain so the explicit db.dart import in the consumers is redundant.
124 lines
4.0 KiB
Dart
124 lines
4.0 KiB
Dart
// Per-entity tile providers for the per-item rendering architecture
|
|
// (see docs/superpowers/specs/2026-05-13-per-item-rendering-design.md).
|
|
//
|
|
// Each provider is a StreamProvider.family<EntityRef?, String> keyed
|
|
// by entity id. The stream:
|
|
// 1. Watches the entity's drift row
|
|
// 2. Yields the populated row on every emission
|
|
// 3. Yields null while the row is absent (UI shows skeleton)
|
|
// 4. On the first missing-row emission, enqueues a hydration via
|
|
// HydrationQueue so the row eventually lands
|
|
//
|
|
// The tile widget reacts to AsyncValue<EntityRef?>:
|
|
// - loading or data:null → skeleton
|
|
// - data:non-null → real card
|
|
// - error → error placeholder
|
|
//
|
|
// Subscriptions are per-tile. A 50-tile home screen creates 50 drift
|
|
// subscriptions; drift handles this fine in practice but worth
|
|
// measuring if a future surface scales to hundreds.
|
|
|
|
import 'package:drift/drift.dart' show leftOuterJoin;
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../cache/adapters.dart';
|
|
import '../cache/audio_cache_manager.dart' show appDbProvider;
|
|
import '../models/album.dart';
|
|
import '../models/artist.dart';
|
|
import '../models/track.dart';
|
|
import 'hydration_queue.dart';
|
|
|
|
/// Watches the cached_albums row for [id]. Triggers background
|
|
/// hydration on miss; yields the row once it lands.
|
|
final albumTileProvider =
|
|
StreamProvider.family<AlbumRef?, String>((ref, id) async* {
|
|
if (id.isEmpty) {
|
|
yield null;
|
|
return;
|
|
}
|
|
final db = ref.watch(appDbProvider);
|
|
final query = (db.select(db.cachedAlbums)..where((t) => t.id.equals(id)))
|
|
.join([
|
|
leftOuterJoin(db.cachedArtists,
|
|
db.cachedArtists.id.equalsExp(db.cachedAlbums.artistId)),
|
|
]);
|
|
var enqueued = false;
|
|
await for (final rows in query.watch()) {
|
|
if (rows.isEmpty) {
|
|
if (!enqueued) {
|
|
enqueued = true;
|
|
ref
|
|
.read(hydrationQueueProvider)
|
|
.enqueue(entityType: 'album', entityId: id);
|
|
}
|
|
yield null;
|
|
continue;
|
|
}
|
|
final r = rows.first;
|
|
final album = r.readTable(db.cachedAlbums);
|
|
final artist = r.readTableOrNull(db.cachedArtists);
|
|
yield album.toRef(artistName: artist?.name ?? '');
|
|
}
|
|
});
|
|
|
|
/// Watches the cached_artists row for [id]. Triggers background
|
|
/// hydration on miss; yields the row once it lands.
|
|
final artistTileProvider =
|
|
StreamProvider.family<ArtistRef?, String>((ref, id) async* {
|
|
if (id.isEmpty) {
|
|
yield null;
|
|
return;
|
|
}
|
|
final db = ref.watch(appDbProvider);
|
|
final query = db.select(db.cachedArtists)..where((t) => t.id.equals(id));
|
|
var enqueued = false;
|
|
await for (final rows in query.watch()) {
|
|
if (rows.isEmpty) {
|
|
if (!enqueued) {
|
|
enqueued = true;
|
|
ref
|
|
.read(hydrationQueueProvider)
|
|
.enqueue(entityType: 'artist', entityId: id);
|
|
}
|
|
yield null;
|
|
continue;
|
|
}
|
|
yield rows.first.toRef();
|
|
}
|
|
});
|
|
|
|
/// Watches the cached_tracks row for [id]. No hydration today — see
|
|
/// hydration_queue.dart's `case 'track':` note. The tile renders a
|
|
/// skeleton while drift is empty; once sync populates the row (or a
|
|
/// future GET /api/tracks/:id endpoint enables real hydration), the
|
|
/// stream emits the populated TrackRef.
|
|
final trackTileProvider =
|
|
StreamProvider.family<TrackRef?, String>((ref, id) async* {
|
|
if (id.isEmpty) {
|
|
yield null;
|
|
return;
|
|
}
|
|
final db = ref.watch(appDbProvider);
|
|
final query = (db.select(db.cachedTracks)..where((t) => t.id.equals(id)))
|
|
.join([
|
|
leftOuterJoin(db.cachedArtists,
|
|
db.cachedArtists.id.equalsExp(db.cachedTracks.artistId)),
|
|
leftOuterJoin(db.cachedAlbums,
|
|
db.cachedAlbums.id.equalsExp(db.cachedTracks.albumId)),
|
|
]);
|
|
await for (final rows in query.watch()) {
|
|
if (rows.isEmpty) {
|
|
yield null;
|
|
continue;
|
|
}
|
|
final r = rows.first;
|
|
final t = r.readTable(db.cachedTracks);
|
|
final a = r.readTableOrNull(db.cachedArtists);
|
|
final al = r.readTableOrNull(db.cachedAlbums);
|
|
yield t.toRef(
|
|
artistName: a?.name ?? '',
|
|
albumTitle: al?.title ?? '',
|
|
);
|
|
}
|
|
});
|