feat: Deezer artist photo fallback for local artists with no Navidrome image

Niche local artists (e.g. N!GHT) whose Navidrome metadata agents have no image showed a
blank hero. Now, when Navidrome has no artist image, the hero falls back to the matched
Deezer artist's photo (Navidrome image → Deezer photo → album cover), on web + Android.

- Web (detail.js): the Deezer-photo fallback previously ran only for non-local artists;
  now it runs whenever the Navidrome image is absent, local or not.
- Android: ArtistViewModel captures the matched Deezer artist's photo (deezerImage);
  ArtistDetailScreen uses it as the hero fallback.
SW ns-v56.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Opus 4.8 committed 2026-07-31 17:07:53 +02:00
1 parent 640e475c51
commit 569af968c5
5 files changed
+26 -12

No files matched your search

@@ -218,7 +218,10 @@ fun ArtistDetailScreen(
// Hero card
item {
// Navidrome's own artist image first; if its agents have none (niche artists like N!GHT),
// fall back to the matched Deezer artist photo, then the album cover.
val imgUrl = state.info?.largeImageUrl?.takeIf { it.isNotBlank() }
?: state.deezerImage
?: coverUrl(artist.coverArt)
Box(
Modifier
@@ -43,6 +43,8 @@ data class ArtistUiState(
val showEventsSheet: Boolean = false,
// Deezer fan count of the matched artist (shown under the name), null if unknown
val fanCount: Long? = null,
// Deezer artist photo — hero fallback when Navidrome's agents have no image (niche artists)
val deezerImage: String? = null,
// Preview + download
val previewingId: Long? = null,
val downloadSheetItem: ItunesItem? = null,
@@ -121,7 +123,10 @@ class ArtistViewModel(app: Application, savedState: SavedStateHandle) : AndroidV
?: artists.firstOrNull()
)
val iArtistId = iArtist?.artistId
if (iArtist?.fanCount != null) _state.value = _state.value.copy(fanCount = iArtist.fanCount)
if (iArtist != null) _state.value = _state.value.copy(
fanCount = iArtist.fanCount ?: _state.value.fanCount,
deezerImage = iArtist.artworkUrl100?.takeIf { it.isNotBlank() } ?: _state.value.deezerImage,
)
if (iArtistId != null) {
coroutineScope {