feat(flutter): playlist cover art via deterministic /api/playlists/{id}/cover
Same fix shape as albums: drift's CachedPlaylistAdapter.toRef was returning coverUrl: '' because the cache table doesn't persist the server-derived URL. Set it to /api/playlists/<id>/cover in the adapter — handleGetPlaylistCover serves the cached collage from disk, so the URL is deterministic and the round-trip through drift no longer drops it. PlaylistCard + PlaylistsListScreen pass the existing queue_music icon as ServerImage's fallback, so when the server hasn't built a collage yet (system playlists with no tracks at build time), the endpoint 404s and the icon shows over the slate background instead of an empty box.
This commit is contained in:
+7
-1
@@ -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: '',
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user