Files
bvandeusen 03c13d21c6 feat(flutter): home screen on per-item rendering (Slice C)
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.
2026-05-13 21:05:43 -04:00

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'),
);
}
}