feat(android): ArtistDetail avatar falls back to first album cover (parity map)

Ports flutter_client/lib/library/artist_detail_screen.dart:177-208
_ArtistAvatar: server coverUrl wins; when empty, use the first loaded
album's /api/albums/{id}/cover; only show the bare Lucide.User icon
when the artist has neither. Threads detail.albums.firstOrNull()?.id
through ArtistHeader → ArtistAvatar. (Seeded-loading header passes
null since the seed carries no album list.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-28 08:21:58 -04:00
parent 851d5f92bb
commit a37fe4c167
@@ -141,6 +141,7 @@ private fun ArtistBody(
ArtistHeader( ArtistHeader(
artist = detail.artist, artist = detail.artist,
artistLiked = artistLiked, artistLiked = artistLiked,
fallbackAlbumId = detail.albums.firstOrNull()?.id,
onPlay = onPlay, onPlay = onPlay,
onToggleLike = onToggleLike, onToggleLike = onToggleLike,
) )
@@ -161,6 +162,7 @@ private fun ArtistBody(
private fun ArtistHeader( private fun ArtistHeader(
artist: ArtistRef, artist: ArtistRef,
artistLiked: Boolean, artistLiked: Boolean,
fallbackAlbumId: String?,
onPlay: () -> Unit, onPlay: () -> Unit,
onToggleLike: () -> Unit, onToggleLike: () -> Unit,
) { ) {
@@ -171,7 +173,7 @@ private fun ArtistHeader(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
) { ) {
ArtistAvatar(artist) ArtistAvatar(artist, fallbackAlbumId)
Column(modifier = Modifier.weight(1f)) { Column(modifier = Modifier.weight(1f)) {
Text( Text(
text = artist.name, text = artist.name,
@@ -198,7 +200,16 @@ private fun ArtistHeader(
} }
@Composable @Composable
private fun ArtistAvatar(artist: ArtistRef) { private fun ArtistAvatar(artist: ArtistRef, fallbackAlbumId: String? = null) {
// Server coverUrl wins; otherwise mirror the server's "use the
// first album's cover" rule via the loaded album list (Flutter
// _ArtistAvatar). Bare Lucide.User only when neither exists.
val model = when {
artist.coverUrl.isNotEmpty() -> artist.coverUrl
fallbackAlbumId != null ->
"http://placeholder.invalid/api/albums/$fallbackAlbumId/cover"
else -> null
}
Box( Box(
modifier = Modifier modifier = Modifier
.size(96.dp) .size(96.dp)
@@ -206,7 +217,7 @@ private fun ArtistAvatar(artist: ArtistRef) {
.background(MaterialTheme.colorScheme.surfaceVariant), .background(MaterialTheme.colorScheme.surfaceVariant),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
if (artist.coverUrl.isEmpty()) { if (model == null) {
Icon( Icon(
imageVector = Lucide.User, imageVector = Lucide.User,
contentDescription = null, contentDescription = null,
@@ -215,7 +226,7 @@ private fun ArtistAvatar(artist: ArtistRef) {
) )
} else { } else {
AsyncImage( AsyncImage(
model = artist.coverUrl, model = model,
contentDescription = artist.name, contentDescription = artist.name,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,