diff --git a/flutter_client/lib/cache/adapters.dart b/flutter_client/lib/cache/adapters.dart index ba19be86..0785284f 100644 --- a/flutter_client/lib/cache/adapters.dart +++ b/flutter_client/lib/cache/adapters.dart @@ -88,6 +88,12 @@ extension TrackRefDriftWrite on TrackRef { extension CachedPlaylistAdapter on CachedPlaylist { /// `ownerUsername` is server-derived; cache stores the userId only. /// Pass empty unless a join supplies it. + /// `coverUrl` is reconstructed deterministically from the playlist + /// id — the server serves the cached collage at this path + /// (handleGetPlaylistCover). Mirrors the album-cover trick. When + /// the server hasn't built a collage yet (system playlists with no + /// tracks at build time), the endpoint 404s and PlaylistCard's + /// ServerImage falls back to its slate placeholder. Playlist toRef({String ownerUsername = ''}) => Playlist( id: id, userId: userId, @@ -96,7 +102,7 @@ extension CachedPlaylistAdapter on CachedPlaylist { isPublic: isPublic, systemVariant: systemVariant, trackCount: trackCount, - coverUrl: '', // server-derived; cache doesn't persist + coverUrl: '/api/playlists/$id/cover', ownerUsername: ownerUsername, createdAt: '', updatedAt: '', diff --git a/flutter_client/lib/playlists/playlists_list_screen.dart b/flutter_client/lib/playlists/playlists_list_screen.dart index 217599b6..7cf4172a 100644 --- a/flutter_client/lib/playlists/playlists_list_screen.dart +++ b/flutter_client/lib/playlists/playlists_list_screen.dart @@ -83,7 +83,11 @@ class _PlaylistTile extends StatelessWidget { color: fs.slate, child: playlist.coverUrl.isEmpty ? Icon(Icons.queue_music, color: fs.ash) - : ServerImage(url: playlist.coverUrl, fit: BoxFit.cover), + : ServerImage( + url: playlist.coverUrl, + fit: BoxFit.cover, + fallback: Icon(Icons.queue_music, color: fs.ash), + ), ), ), const SizedBox(width: 12), diff --git a/flutter_client/lib/playlists/widgets/playlist_card.dart b/flutter_client/lib/playlists/widgets/playlist_card.dart index e6781f37..060351a4 100644 --- a/flutter_client/lib/playlists/widgets/playlist_card.dart +++ b/flutter_client/lib/playlists/widgets/playlist_card.dart @@ -31,9 +31,19 @@ class PlaylistCard extends StatelessWidget { width: 144, height: 144, color: fs.slate, + // coverUrl is deterministic per playlist (see + // CachedPlaylistAdapter.toRef). When the server's + // collage isn't built yet, ServerImage's + // errorBuilder shows the queue_music icon over the + // slate background. child: playlist.coverUrl.isEmpty ? Icon(Icons.queue_music, color: fs.ash, size: 56) - : ServerImage(url: playlist.coverUrl, fit: BoxFit.cover), + : ServerImage( + url: playlist.coverUrl, + fit: BoxFit.cover, + fallback: + Icon(Icons.queue_music, color: fs.ash, size: 56), + ), ), ), const SizedBox(height: 8),