From ec585997aee06bd07f7e058d02ad6e1c28f47a72 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 27 May 2026 12:47:05 -0400 Subject: [PATCH] feat(android): Search artists/albums as horizontal carousels Audit v2 #14. Artists + Albums search sections were rendering as plain vertical TextRows (name-only for artists, title+artist for albums). Now match Flutter: * Artists: horizontal LazyRow of ArtistCard (cover + name) * Albums: horizontal LazyRow of AlbumCard (cover + title + artist) * Section headers gain a count suffix ("Artists 12") matching Flutter's _SectionHeader pattern; also applied to the Tracks header for consistency. Removed the now-unused TextRow helper. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../minstrel/search/ui/SearchScreen.kt | 88 ++++++++----------- 1 file changed, 37 insertions(+), 51 deletions(-) diff --git a/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchScreen.kt index e548a11d..3f475258 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.CircularProgressIndicator @@ -50,6 +51,8 @@ import androidx.navigation.NavHostController import com.composables.icons.lucide.ArrowLeft import com.composables.icons.lucide.Lucide import com.composables.icons.lucide.X +import com.fabledsword.minstrel.library.widgets.AlbumCard +import com.fabledsword.minstrel.library.widgets.ArtistCard import com.fabledsword.minstrel.models.AlbumRef import com.fabledsword.minstrel.models.ArtistRef import com.fabledsword.minstrel.models.SearchResponseRef @@ -208,14 +211,16 @@ private fun LazyListScope.artistsSection( onArtistClick: (String) -> Unit, ) { if (artists.isEmpty()) return - item { SectionHeader("Artists") } - items(items = artists, key = { "art-${it.id}" }) { artist -> - TextRow( - primary = artist.name, - secondary = null, - onClick = { onArtistClick(artist.id) }, - ) - HorizontalDivider() + item { SectionHeader("Artists", artists.size) } + item { + LazyRow( + contentPadding = PaddingValues(horizontal = 8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + items(items = artists, key = { "art-${it.id}" }) { artist -> + ArtistCard(artist = artist, onClick = { onArtistClick(artist.id) }) + } + } } } @@ -224,14 +229,16 @@ private fun LazyListScope.albumsSection( onAlbumClick: (String) -> Unit, ) { if (albums.isEmpty()) return - item { SectionHeader("Albums") } - items(items = albums, key = { "alb-${it.id}" }) { album -> - TextRow( - primary = album.title, - secondary = album.artistName.takeIf { it.isNotEmpty() }, - onClick = { onAlbumClick(album.id) }, - ) - HorizontalDivider() + item { SectionHeader("Albums", albums.size) } + item { + LazyRow( + contentPadding = PaddingValues(horizontal = 8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + items(items = albums, key = { "alb-${it.id}" }) { album -> + AlbumCard(album = album, onClick = { onAlbumClick(album.id) }) + } + } } } @@ -243,7 +250,7 @@ private fun LazyListScope.tracksSection( onNavigateToArtist: (String) -> Unit, ) { if (tracks.isEmpty()) return - item { SectionHeader("Tracks") } + item { SectionHeader("Tracks", tracks.size) } items(items = tracks, key = { "trk-${it.id}" }) { track -> TrackResultRow( track = track, @@ -307,45 +314,24 @@ private fun TrackResultRow( } @Composable -private fun SectionHeader(text: String) { - Text( - text = text, - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.onBackground, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp), - ) -} - -@Composable -private fun TextRow(primary: String, secondary: String?, onClick: () -> Unit) { +private fun SectionHeader(label: String, count: Int) { Row( modifier = Modifier .fillMaxWidth() - .clickable(onClick = onClick) - .padding(horizontal = 16.dp, vertical = 12.dp), + .padding(horizontal = 16.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(12.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - Column(modifier = Modifier.fillMaxWidth()) { - Text( - text = primary, - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - if (!secondary.isNullOrEmpty()) { - Text( - text = secondary, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - } + Text( + text = label, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onBackground, + ) + Text( + text = "$count", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) } }