diff --git a/android/app/src/main/java/com/fabledsword/minstrel/library/widgets/AlbumCard.kt b/android/app/src/main/java/com/fabledsword/minstrel/library/widgets/AlbumCard.kt index 65deddcc..475921e4 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/library/widgets/AlbumCard.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/library/widgets/AlbumCard.kt @@ -1,5 +1,6 @@ package com.fabledsword.minstrel.library.widgets +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -49,10 +50,11 @@ fun AlbumCard( Box( modifier = Modifier .size(144.dp) - .clip(RoundedCornerShape(FabledSwordFlatTokens.radiusSm)), + .clip(RoundedCornerShape(FabledSwordFlatTokens.radiusSm)) + .background(MaterialTheme.colorScheme.surfaceVariant), contentAlignment = Alignment.Center, ) { - if (album.coverUrl.isEmpty()) { + if (album.id.isEmpty()) { Icon( imageVector = Lucide.Disc3, contentDescription = null, @@ -60,8 +62,15 @@ fun AlbumCard( modifier = Modifier.size(56.dp), ) } else { + // Use displayCoverUrl so cached-only albums (which + // drop coverUrl in the entity→domain mapper) still + // hit /api/albums/{id}/cover via the BaseUrlInterceptor + // placeholder rewrite. On load failure (album with + // no cover server-side) the Box's surfaceVariant + // background shows through — a Disc3 error-slot + // fallback is a future refinement. AsyncImage( - model = album.coverUrl, + model = album.displayCoverUrl, contentDescription = album.title, modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop, diff --git a/android/app/src/main/java/com/fabledsword/minstrel/models/AlbumRef.kt b/android/app/src/main/java/com/fabledsword/minstrel/models/AlbumRef.kt index d5a3fbad..e432e5b7 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/models/AlbumRef.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/models/AlbumRef.kt @@ -19,4 +19,18 @@ data class AlbumRef( val trackCount: Int = 0, val durationSec: Int = 0, val coverUrl: String = "", -) +) { + /** + * Cover URL the UI should render. Returns the server-provided + * `coverUrl` when populated (regular API responses always carry it), + * otherwise derives `/api/albums/{id}/cover` via the placeholder + * host that `BaseUrlInterceptor` rewrites to the live server URL. + * + * The fallback path matters because the cached_albums entity → + * AlbumRef mapper drops coverUrl — without this property, + * AlbumCards rendered from cache (Library Albums tab, ArtistDetail + * grid, Home rows on cold-start) would show no artwork at all. + */ + val displayCoverUrl: String + get() = coverUrl.ifEmpty { "http://placeholder.invalid/api/albums/$id/cover" } +}