fix(flutter): library Artists tab 404 + Albums tab 3-up grid

Artists tab 404: client called /api/library/artists, but the server
mounts the artists list at /api/artists (handleListArtists). Albums
sit at /api/library/albums for historical reasons — the paths aren't
symmetric. Switch listArtists() to /api/artists with sort=alpha.

Albums tab grid: matched the responsive 3-up layout we built for
artist detail. LayoutBuilder computes cellW from available width;
AlbumCard sized to the cell with titleMaxLines: 2; mainAxisExtent
matches actual content height (cover + 2-line title + artist line +
fudge). No more wide-aspect cells with empty space below the card.

Also wired `extra: ref` on the artists/albums grids and the Liked
tab so detail-screen nav hydration kicks in here too — taps from
library screens get the same instant header that home tiles do.
This commit is contained in:
2026-05-11 12:21:24 -04:00
parent 11c40c6aca
commit a77d4ceac0
2 changed files with 68 additions and 28 deletions
@@ -12,9 +12,16 @@ class LibraryListsApi {
final Dio _dio;
Future<Paged<ArtistRef>> listArtists({int limit = 50, int offset = 0}) async {
// Server mounts the artists list at /api/artists (handleListArtists),
// not /api/library/artists. Albums use /api/library/albums for
// historical reasons; the paths aren't symmetric.
final r = await _dio.get<Map<String, dynamic>>(
'/api/library/artists',
queryParameters: {'limit': limit, 'offset': offset},
'/api/artists',
queryParameters: {
'limit': limit,
'offset': offset,
'sort': 'alpha',
},
);
return Paged.fromJson(r.data ?? const {}, ArtistRef.fromJson);
}
+59 -26
View File
@@ -144,10 +144,14 @@ class _ArtistsTab extends ConsumerWidget {
childAspectRatio: 0.78,
),
itemCount: page.items.length,
itemBuilder: (ctx, i) => ArtistCard(
artist: page.items[i],
onTap: () => ctx.push('/artists/${page.items[i].id}'),
),
itemBuilder: (ctx, i) {
final artist = page.items[i];
return ArtistCard(
artist: artist,
onTap: () => ctx.push('/artists/${artist.id}',
extra: artist),
);
},
),
),
);
@@ -167,20 +171,41 @@ class _AlbumsTab extends ConsumerWidget {
? Center(child: Text("No albums yet — scan a library folder via the server's config.", style: TextStyle(color: fs.ash), textAlign: TextAlign.center))
: RefreshIndicator(
onRefresh: () async => ref.refresh(_libraryAlbumsProvider.future),
child: GridView.builder(
padding: const EdgeInsets.all(8),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 0.78,
),
itemCount: page.items.length,
itemBuilder: (ctx, i) => AlbumCard(
album: page.items[i],
onTap: () => ctx.push('/albums/${page.items[i].id}'),
),
),
// Same responsive 3-up grid as the artist detail
// album list — LayoutBuilder + AlbumCard sized to the
// cell, mainAxisExtent matched to actual card height.
child: LayoutBuilder(builder: (ctx, constraints) {
const cols = 3;
const sidePad = 8.0;
const gap = 8.0;
final cellW = (constraints.maxWidth -
sidePad * 2 -
gap * (cols - 1)) /
cols;
// cover (cellW - 16) + gap (8) + 2-line title (~36)
// + artist line (~16) + small fudge.
final cellH = (cellW - 16) + 8 + 36 + 16 + 4;
return GridView.builder(
padding: const EdgeInsets.all(sidePad),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: cols,
mainAxisExtent: cellH,
mainAxisSpacing: gap,
crossAxisSpacing: gap,
),
itemCount: page.items.length,
itemBuilder: (ctx, i) {
final album = page.items[i];
return AlbumCard(
album: album,
width: cellW,
titleMaxLines: 2,
onTap: () => ctx.push('/albums/${album.id}',
extra: album),
);
},
);
}),
),
);
}
@@ -260,10 +285,14 @@ class _LikedTab extends ConsumerWidget {
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 8),
itemCount: ar.items.length,
itemBuilder: (ctx, i) => ArtistCard(
artist: ar.items[i],
onTap: () => ctx.push('/artists/${ar.items[i].id}'),
),
itemBuilder: (ctx, i) {
final artist = ar.items[i];
return ArtistCard(
artist: artist,
onTap: () =>
ctx.push('/artists/${artist.id}', extra: artist),
);
},
),
),
],
@@ -275,10 +304,14 @@ class _LikedTab extends ConsumerWidget {
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 8),
itemCount: al.items.length,
itemBuilder: (ctx, i) => AlbumCard(
album: al.items[i],
onTap: () => ctx.push('/albums/${al.items[i].id}'),
),
itemBuilder: (ctx, i) {
final album = al.items[i];
return AlbumCard(
album: album,
onTap: () =>
ctx.push('/albums/${album.id}', extra: album),
);
},
),
),
],