03c13d21c6
End-to-end pilot of the per-item architecture. Home now reads from the new homeIndexProvider (drift-first over CachedHomeIndex with /api/home/index discovery + SWR), then each tile is a small ConsumerWidget watching its own albumTileProvider/artistTileProvider/ trackTileProvider. Tiles render a matched-dimension skeleton while their entity is still hydrating, and swap in the real card once drift emits the populated row. Track hydration is wired up — /api/tracks/:id already existed so the queue's case 'track' just calls api.getTrack(id) and persists. The visible behavior: * Cold visit: small /api/home/index round-trip (IDs only, ~10× smaller than /api/home), then sections appear shaped with skeleton tiles; each tile materializes as the hydration queue drains. No more "30s blank → everything pops in at once." * Warm visit: drift index emits instantly, drift entity rows emit instantly, no network. Page paints fully in the first frame. * Mid-state: scrolling through a partially-hydrated section sees real cards next to skeleton cards. Layout doesn't shift because skeletons match real-card dimensions exactly. CachedHomeSnapshot (and the legacy homeProvider) stay in place but unconsumed by Flutter — left in for now so revert is cheap if the new path needs reworking. Cleanup follow-up in a later slice. Old /api/home endpoint untouched, so the web client keeps working unchanged.
42 lines
1.4 KiB
Dart
42 lines
1.4 KiB
Dart
/// Mirrors internal/api/types.go HomeIndexPayload. Five flat slices of
|
|
/// entity ID strings — the per-item rendering variant of HomeData.
|
|
/// Section name implies entity type; no per-entry type tag is needed.
|
|
///
|
|
/// Slices are non-null after fromJson so callers can branch on `length`
|
|
/// instead of dealing with null sections.
|
|
class HomeIndex {
|
|
const HomeIndex({
|
|
required this.recentlyAddedAlbums,
|
|
required this.rediscoverAlbums,
|
|
required this.rediscoverArtists,
|
|
required this.mostPlayedTracks,
|
|
required this.lastPlayedArtists,
|
|
});
|
|
|
|
final List<String> recentlyAddedAlbums;
|
|
final List<String> rediscoverAlbums;
|
|
final List<String> rediscoverArtists;
|
|
final List<String> mostPlayedTracks;
|
|
final List<String> lastPlayedArtists;
|
|
|
|
static const empty = HomeIndex(
|
|
recentlyAddedAlbums: [],
|
|
rediscoverAlbums: [],
|
|
rediscoverArtists: [],
|
|
mostPlayedTracks: [],
|
|
lastPlayedArtists: [],
|
|
);
|
|
|
|
factory HomeIndex.fromJson(Map<String, dynamic> j) {
|
|
List<String> ids(String key) =>
|
|
((j[key] as List?) ?? const []).map((e) => e.toString()).toList();
|
|
return HomeIndex(
|
|
recentlyAddedAlbums: ids('recently_added_albums'),
|
|
rediscoverAlbums: ids('rediscover_albums'),
|
|
rediscoverArtists: ids('rediscover_artists'),
|
|
mostPlayedTracks: ids('most_played_tracks'),
|
|
lastPlayedArtists: ids('last_played_artists'),
|
|
);
|
|
}
|
|
}
|