feat(android): AlbumCard cover fallback for cached-only albums
The cached_albums → AlbumRef mapper drops coverUrl (only the
coverPath column is stored; the regular API's cover_url isn't
mirrored). Result: AlbumCards rendered from cache — Library Albums
tab, ArtistDetail album grid, Home Recently-added/Rediscover rows
on cold start — showed only the Lucide.Disc3 placeholder.
Same placeholder-URL trick as TrackRef.coverUrl:
- models/AlbumRef.kt — adds `displayCoverUrl` computed property
that returns the server-given coverUrl when populated, falls
back to `http://placeholder.invalid/api/albums/{id}/cover`
otherwise. BaseUrlInterceptor rewrites the host; Coil's shared
OkHttp picks up the auth cookie. The original `coverUrl` field
is preserved so callers that need to distinguish
"server-provided" from "derived" can.
- library/widgets/AlbumCard.kt — switches the branch from
`album.coverUrl.isEmpty()` to `album.id.isEmpty()`. The cover
Box gets a surfaceVariant background so failed image loads
(e.g. album server-side without art) degrade to a tinted
square rather than transparent. A proper error-slot fallback
icon is a future refinement.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user