diff --git a/android/app/src/main/kotlin/com/novasonic/app/ui/screen/NowPlayingScreen.kt b/android/app/src/main/kotlin/com/novasonic/app/ui/screen/NowPlayingScreen.kt index 57079f6..489c24c 100644 --- a/android/app/src/main/kotlin/com/novasonic/app/ui/screen/NowPlayingScreen.kt +++ b/android/app/src/main/kotlin/com/novasonic/app/ui/screen/NowPlayingScreen.kt @@ -33,14 +33,11 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import coil.request.ImageRequest -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.rememberScrollState import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.text.withStyle -import androidx.compose.foundation.text.ClickableText import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -214,43 +211,52 @@ fun NowPlayingScreen( ) Spacer(Modifier.height(3.dp)) if (song != null && !song.artist.isNullOrBlank()) { - // Build list: prefer song.artists (OpenSubsonic, has real IDs for all), - // fall back to single entry from artist/artistId - val artistList = if (song.artists.size > 1) song.artists - else listOf(com.novasonic.app.api.model.SongArtist( - id = song.artistId ?: "", name = song.artist!!)) + // Prefer OpenSubsonic artists array (has IDs per contributor). + // Fall back to splitting artist string by ';' (same as web app). + val raw: List = + if (song.artists.size > 1) song.artists + else song.artist!!.split(";").map { part -> + val n = part.trim() + com.novasonic.app.api.model.SongArtist( + id = if (n.equals(song.artist?.trim(), ignoreCase = true)) song.artistId ?: "" else "", + name = n, + ) + }.filter { it.name.isNotBlank() } - if (onArtistClick != null) { - val annotated = buildAnnotatedString { - artistList.forEachIndexed { idx, a -> - if (idx > 0) { - withStyle(SpanStyle(color = Color.White.copy(0.4f), - fontFamily = Outfit, fontSize = 15.sp)) { append(" · ") } - } - pushStringAnnotation("ARTIST_ID", a.id) - withStyle(SpanStyle(color = NsAccent, - fontFamily = Outfit, - fontWeight = FontWeight.SemiBold, - fontSize = 15.sp)) { append(a.name) } - pop() + // De-duplicate: if multiple entries share the same ID (Navidrome + // sometimes copies the primary artistId to all entries), only the + // first occurrence keeps it — others become non-navigable. + val seenIds = mutableSetOf() + val artistList = raw.map { a -> + if (a.id.isNotBlank() && seenIds.add(a.id)) a + else a.copy(id = "") + } + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.horizontalScroll(rememberScrollState()), + ) { + artistList.forEachIndexed { idx, a -> + if (idx > 0) { + Text( + " · ", fontFamily = Outfit, + fontSize = 14.sp, color = Color.White.copy(0.35f), + ) } + val navigable = onArtistClick != null && a.id.isNotBlank() + Text( + text = a.name, + fontFamily = Outfit, + fontWeight = FontWeight.SemiBold, + fontSize = 14.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = if (navigable) NsAccent else NsAccent.copy(alpha = 0.55f), + modifier = if (navigable) + Modifier.clickable { onArtistClick!!(a.id) } + else Modifier, + ) } - ClickableText( - text = annotated, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - onClick = { offset -> - annotated.getStringAnnotations("ARTIST_ID", offset, offset) - .firstOrNull()?.let { onArtistClick(it.item) } - }, - ) - } else { - Text( - song.artist!!, - fontFamily = Outfit, fontWeight = FontWeight.SemiBold, - fontSize = 15.sp, color = NsAccent, - maxLines = 2, overflow = TextOverflow.Ellipsis, - ) } } if (!song?.album.isNullOrBlank()) {