fix(android): show artist images in Apple-Music search (Erkunden)

iTunes artist entities carry no artwork, so search results rendered as bare grey
circles. Mirror the web PWA: after showing results, fetch one album per artist and
borrow its cover (entity=album, limit=1), applied only if the user is still viewing
the same artist set. Also give ArtistCard a Person-icon fallback instead of an empty
grey dot, and fall back to the artist id for Subsonic (library) artist covers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Opus 4.8 committed 2026-07-09 10:04:48 +02:00
1 parent 03bc736725
commit 03046cd631
2 files changed
+49 -7

No files matched your search

@@ -283,7 +283,9 @@ private fun LibrarySearchContent(
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
items(artists, key = { it.id }) { a ->
ArtistCard(name = a.name, imageUrl = coverUrl(a.coverArt)) { onArtistClick(a) }
// search3 artist results usually omit coverArt → fall back to the artist
// id, which Navidrome's getCoverArt accepts and resolves to the artist image.
ArtistCard(name = a.name, imageUrl = coverUrl(a.coverArt ?: a.id)) { onArtistClick(a) }
}
}
Spacer(Modifier.height(8.dp))
@@ -597,15 +599,27 @@ private fun ArtistCard(name: String, imageUrl: String?, onClick: () -> Unit) {
.width(80.dp)
.clickable(onClick = onClick),
) {
AsyncImage(
model = imageUrl,
contentDescription = name,
contentScale = ContentScale.Crop,
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(72.dp)
.clip(androidx.compose.foundation.shape.CircleShape)
.background(NsSurface2),
)
) {
if (imageUrl != null) {
AsyncImage(
model = imageUrl,
contentDescription = name,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(),
)
} else {
Icon(
Icons.Default.Person, contentDescription = null,
tint = NsOnSurface2, modifier = Modifier.size(32.dp),
)
}
}
Spacer(Modifier.height(6.dp))
Text(
name, fontFamily = Outfit, fontSize = 12.sp, fontWeight = FontWeight.Medium,
@@ -110,18 +110,46 @@ class ExploreViewModel(app: Application) : AndroidViewModel(app) {
try {
val resp = ItunesClient.search(term)
val results = resp.results
val artists = results.filter { it.wrapperType == "artist" }
_state.value = _state.value.copy(
artists = results.filter { it.wrapperType == "artist" },
artists = artists,
albums = results.filter { it.wrapperType == "collection" },
tracks = results.filter { it.wrapperType == "track" && it.kind == "song" },
isSearching = false,
)
enrichArtistArt(artists)
} catch (e: Exception) {
_state.value = _state.value.copy(isSearching = false, error = e.message)
}
}
}
/**
* iTunes artist entities carry no artwork, so search results render as grey circles. Mirror the
* web PWA: fetch one album per artist and borrow its cover. Runs after the main results are shown
* (list appears instantly) and only applies if the user is still viewing the same artist set.
*/
private fun enrichArtistArt(artists: List<ItunesItem>) {
if (artists.none { it.artworkUrl100.isNullOrBlank() && it.artistName != null }) return
viewModelScope.launch {
val enriched = artists.map { a ->
async {
if (!a.artworkUrl100.isNullOrBlank()) return@async a
val name = a.artistName ?: return@async a
val art = runCatching {
ItunesClient.search(name, entity = "album", limit = 1).results
.firstOrNull { it.wrapperType == "collection" }?.artworkUrl100
}.getOrNull()
if (art != null) a.copy(artworkUrl100 = art) else a
}
}.awaitAll()
val cur = _state.value.artists
if (cur.map { it.artistId } == artists.map { it.artistId }) {
_state.value = _state.value.copy(artists = enriched)
}
}
}
fun loadAlbum(album: ItunesItem) {
val id = album.collectionId ?: return
viewModelScope.launch {