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.
This commit is contained in:
@@ -3,6 +3,7 @@ import 'package:dio/dio.dart';
|
||||
import '../../models/album.dart';
|
||||
import '../../models/artist.dart';
|
||||
import '../../models/home_data.dart';
|
||||
import '../../models/home_index.dart';
|
||||
import '../../models/track.dart';
|
||||
|
||||
/// LibraryApi wraps the server's native /api/* library surface.
|
||||
@@ -27,6 +28,24 @@ class LibraryApi {
|
||||
return HomeData.fromJson(r.data ?? const {});
|
||||
}
|
||||
|
||||
/// GET /api/home/index — per-item rendering variant. Returns just IDs
|
||||
/// per section; client hydrates each tile via the per-entity
|
||||
/// endpoints. ~10× smaller than /api/home on populated libraries so
|
||||
/// the cold-visit round-trip is correspondingly short.
|
||||
Future<HomeIndex> getHomeIndex() async {
|
||||
final r = await _dio.get<Map<String, dynamic>>('/api/home/index');
|
||||
return HomeIndex.fromJson(r.data ?? const {});
|
||||
}
|
||||
|
||||
/// GET /api/tracks/{id}. Returns the canonical TrackRef. Used by
|
||||
/// the HydrationQueue to populate cached_tracks on a per-tile miss
|
||||
/// — the existing endpoint already joins album + artist so the
|
||||
/// response carries everything TrackRef needs.
|
||||
Future<TrackRef> getTrack(String id) async {
|
||||
final r = await _dio.get<Map<String, dynamic>>('/api/tracks/$id');
|
||||
return TrackRef.fromJson(r.data ?? const {});
|
||||
}
|
||||
|
||||
/// GET /api/artists/{id}. Server returns ArtistDetail which embeds
|
||||
/// ArtistRef inline; ArtistRef.fromJson already reads only the fields
|
||||
/// it cares about, so passing the whole body is correct.
|
||||
|
||||
Reference in New Issue
Block a user