feat: artist cards with image in search + fix action bar overflow

Search: replace SmallArtistChip (text pill) with ArtistCard showing
circular profile image (72dp) + name below; works for both Navidrome
artists (uses coverArt) and iTunes artists (uses artworkUrl100 if present)

ArtistDetailScreen: convert action bar Row → LazyRow so Play / Shuffle /
Konzerte pills sit comfortably with horizontal scroll instead of cramming

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-06-15 00:24:18 +02:00
1 parent 13e9ec9d9a
commit 3855207530
2 files changed
+46 -25

No files matched your search

@@ -197,22 +197,27 @@ fun ArtistDetailScreen(
// Action bar
item {
Row(
LazyRow(
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(bottom = 8.dp),
modifier = Modifier.padding(bottom = 8.dp),
) {
ArtistActionPill(Icons.Default.PlayArrow, "Top Songs") {
if (state.topSongs.isNotEmpty()) onPlay(state.topSongs, 0)
item {
ArtistActionPill(Icons.Default.PlayArrow, "Top Songs") {
if (state.topSongs.isNotEmpty()) onPlay(state.topSongs, 0)
}
}
ArtistActionPill(Icons.Default.Shuffle, "Shuffle") {
if (state.topSongs.isNotEmpty()) onPlay(state.topSongs.shuffled(), 0)
item {
ArtistActionPill(Icons.Default.Shuffle, "Shuffle") {
if (state.topSongs.isNotEmpty()) onPlay(state.topSongs.shuffled(), 0)
}
}
val eventCount = state.events.size
val konzertLabel = if (eventCount > 0) "Konzerte ($eventCount)" else "Konzerte"
ArtistActionPill(Icons.Default.Event, konzertLabel) {
vm.openEventsSheet()
item {
val eventCount = state.events.size
val konzertLabel = if (eventCount > 0) "Konzerte ($eventCount)" else "Konzerte"
ArtistActionPill(Icons.Default.Event, konzertLabel) {
vm.openEventsSheet()
}
}
}
}
@@ -280,12 +280,13 @@ private fun LibrarySearchContent(
item {
LazyRow(
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
items(artists, key = { it.id }) { a ->
SmallArtistChip(a.name) { onArtistClick(a) }
ArtistCard(name = a.name, imageUrl = coverUrl(a.coverArt)) { onArtistClick(a) }
}
}
Spacer(Modifier.height(8.dp))
}
}
if (albums.isNotEmpty()) {
@@ -408,12 +409,16 @@ private fun GlobalExploreContent(
item {
LazyRow(
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
items(state.artists) { a ->
SmallArtistChip(a.artistName ?: "") { vm.openArtistDetail(a) }
ArtistCard(
name = a.artistName ?: "",
imageUrl = a.artworkUrl100?.replace("100x100bb", "300x300bb"),
) { vm.openArtistDetail(a) }
}
}
Spacer(Modifier.height(8.dp))
}
}
if (state.albums.isNotEmpty()) {
@@ -585,17 +590,28 @@ private fun ItunesTrackRow(
}
@Composable
private fun SmallArtistChip(name: String, onClick: () -> Unit) {
Box(
contentAlignment = Alignment.Center,
private fun ArtistCard(name: String, imageUrl: String?, onClick: () -> Unit) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.clip(RoundedCornerShape(20.dp))
.background(NsSurface2)
.clickable(onClick = onClick)
.padding(horizontal = 14.dp, vertical = 8.dp),
.width(80.dp)
.clickable(onClick = onClick),
) {
Text(name, fontFamily = Outfit, fontSize = 13.sp, color = NsOnSurface,
maxLines = 1, overflow = TextOverflow.Ellipsis)
AsyncImage(
model = imageUrl,
contentDescription = name,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(72.dp)
.clip(androidx.compose.foundation.shape.CircleShape)
.background(NsSurface2),
)
Spacer(Modifier.height(6.dp))
Text(
name, fontFamily = Outfit, fontSize = 12.sp, fontWeight = FontWeight.Medium,
color = NsOnSurface, maxLines = 2, overflow = TextOverflow.Ellipsis,
textAlign = androidx.compose.ui.text.style.TextAlign.Center,
)
}
}