fix: multi-artist click in NowPlaying — individual composables per artist
Replace AnnotatedString/ClickableText (offset-based click was unreliable) with a horizontally-scrollable Row of individual Text composables. Each artist has its own explicit clickable modifier bound directly to its ID. ID de-duplication: if Navidrome copies the primary artistId to all entries in the artists array, only the first keeps the ID; others render as non-navigable text (dimmed NsAccent) instead of silently going to wrong page. Fallback: split song.artist by ';' when artists array is absent/single, matching the web app's parseArtists() logic. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
55eef2f831
commit
8c8f03503b
1 file changed
+45
-39
@@ -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<com.novasonic.app.api.model.SongArtist> =
|
||||
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<String>()
|
||||
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()) {
|
||||
|
||||
Reference in new issue
Block a user