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
+40
-34
@@ -33,14 +33,11 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
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.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
@@ -214,45 +211,54 @@ fun NowPlayingScreen(
|
|||||||
)
|
)
|
||||||
Spacer(Modifier.height(3.dp))
|
Spacer(Modifier.height(3.dp))
|
||||||
if (song != null && !song.artist.isNullOrBlank()) {
|
if (song != null && !song.artist.isNullOrBlank()) {
|
||||||
// Build list: prefer song.artists (OpenSubsonic, has real IDs for all),
|
// Prefer OpenSubsonic artists array (has IDs per contributor).
|
||||||
// fall back to single entry from artist/artistId
|
// Fall back to splitting artist string by ';' (same as web app).
|
||||||
val artistList = if (song.artists.size > 1) song.artists
|
val raw: List<com.novasonic.app.api.model.SongArtist> =
|
||||||
else listOf(com.novasonic.app.api.model.SongArtist(
|
if (song.artists.size > 1) song.artists
|
||||||
id = song.artistId ?: "", name = song.artist!!))
|
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) {
|
// De-duplicate: if multiple entries share the same ID (Navidrome
|
||||||
val annotated = buildAnnotatedString {
|
// 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 ->
|
artistList.forEachIndexed { idx, a ->
|
||||||
if (idx > 0) {
|
if (idx > 0) {
|
||||||
withStyle(SpanStyle(color = Color.White.copy(0.4f),
|
Text(
|
||||||
fontFamily = Outfit, fontSize = 15.sp)) { append(" · ") }
|
" · ", fontFamily = Outfit,
|
||||||
|
fontSize = 14.sp, color = Color.White.copy(0.35f),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
pushStringAnnotation("ARTIST_ID", a.id)
|
val navigable = onArtistClick != null && a.id.isNotBlank()
|
||||||
withStyle(SpanStyle(color = NsAccent,
|
Text(
|
||||||
|
text = a.name,
|
||||||
fontFamily = Outfit,
|
fontFamily = Outfit,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
fontSize = 15.sp)) { append(a.name) }
|
fontSize = 14.sp,
|
||||||
pop()
|
maxLines = 1,
|
||||||
}
|
|
||||||
}
|
|
||||||
ClickableText(
|
|
||||||
text = annotated,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
onClick = { offset ->
|
color = if (navigable) NsAccent else NsAccent.copy(alpha = 0.55f),
|
||||||
annotated.getStringAnnotations("ARTIST_ID", offset, offset)
|
modifier = if (navigable)
|
||||||
.firstOrNull()?.let { onArtistClick(it.item) }
|
Modifier.clickable { onArtistClick!!(a.id) }
|
||||||
},
|
else Modifier,
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
song.artist!!,
|
|
||||||
fontFamily = Outfit, fontWeight = FontWeight.SemiBold,
|
|
||||||
fontSize = 15.sp, color = NsAccent,
|
|
||||||
maxLines = 2, overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (!song?.album.isNullOrBlank()) {
|
if (!song?.album.isNullOrBlank()) {
|
||||||
Text(
|
Text(
|
||||||
song!!.album!!,
|
song!!.album!!,
|
||||||
|
|||||||
Reference in new issue
Block a user