From 283706c4f8807db361967420fc8d6b89fb826db2 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Tue, 26 May 2026 08:17:08 -0400 Subject: [PATCH] feat(android): AlbumCard cover fallback for cached-only albums MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../minstrel/library/widgets/AlbumCard.kt | 15 ++++++++++++--- .../com/fabledsword/minstrel/models/AlbumRef.kt | 16 +++++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) 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" } +}