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) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
@@ -50,6 +51,8 @@ import androidx.navigation.NavHostController
|
|||||||
import com.composables.icons.lucide.ArrowLeft
|
import com.composables.icons.lucide.ArrowLeft
|
||||||
import com.composables.icons.lucide.Lucide
|
import com.composables.icons.lucide.Lucide
|
||||||
import com.composables.icons.lucide.X
|
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.AlbumRef
|
||||||
import com.fabledsword.minstrel.models.ArtistRef
|
import com.fabledsword.minstrel.models.ArtistRef
|
||||||
import com.fabledsword.minstrel.models.SearchResponseRef
|
import com.fabledsword.minstrel.models.SearchResponseRef
|
||||||
@@ -208,14 +211,16 @@ private fun LazyListScope.artistsSection(
|
|||||||
onArtistClick: (String) -> Unit,
|
onArtistClick: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (artists.isEmpty()) return
|
if (artists.isEmpty()) return
|
||||||
item { SectionHeader("Artists") }
|
item { SectionHeader("Artists", artists.size) }
|
||||||
items(items = artists, key = { "art-${it.id}" }) { artist ->
|
item {
|
||||||
TextRow(
|
LazyRow(
|
||||||
primary = artist.name,
|
contentPadding = PaddingValues(horizontal = 8.dp),
|
||||||
secondary = null,
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
onClick = { onArtistClick(artist.id) },
|
) {
|
||||||
)
|
items(items = artists, key = { "art-${it.id}" }) { artist ->
|
||||||
HorizontalDivider()
|
ArtistCard(artist = artist, onClick = { onArtistClick(artist.id) })
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,14 +229,16 @@ private fun LazyListScope.albumsSection(
|
|||||||
onAlbumClick: (String) -> Unit,
|
onAlbumClick: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (albums.isEmpty()) return
|
if (albums.isEmpty()) return
|
||||||
item { SectionHeader("Albums") }
|
item { SectionHeader("Albums", albums.size) }
|
||||||
items(items = albums, key = { "alb-${it.id}" }) { album ->
|
item {
|
||||||
TextRow(
|
LazyRow(
|
||||||
primary = album.title,
|
contentPadding = PaddingValues(horizontal = 8.dp),
|
||||||
secondary = album.artistName.takeIf { it.isNotEmpty() },
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
onClick = { onAlbumClick(album.id) },
|
) {
|
||||||
)
|
items(items = albums, key = { "alb-${it.id}" }) { album ->
|
||||||
HorizontalDivider()
|
AlbumCard(album = album, onClick = { onAlbumClick(album.id) })
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +250,7 @@ private fun LazyListScope.tracksSection(
|
|||||||
onNavigateToArtist: (String) -> Unit,
|
onNavigateToArtist: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (tracks.isEmpty()) return
|
if (tracks.isEmpty()) return
|
||||||
item { SectionHeader("Tracks") }
|
item { SectionHeader("Tracks", tracks.size) }
|
||||||
items(items = tracks, key = { "trk-${it.id}" }) { track ->
|
items(items = tracks, key = { "trk-${it.id}" }) { track ->
|
||||||
TrackResultRow(
|
TrackResultRow(
|
||||||
track = track,
|
track = track,
|
||||||
@@ -307,45 +314,24 @@ private fun TrackResultRow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SectionHeader(text: String) {
|
private fun SectionHeader(label: String, count: Int) {
|
||||||
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) {
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(onClick = onClick)
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.fillMaxWidth()) {
|
Text(
|
||||||
Text(
|
text = label,
|
||||||
text = primary,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
)
|
||||||
maxLines = 1,
|
Text(
|
||||||
overflow = TextOverflow.Ellipsis,
|
text = "$count",
|
||||||
)
|
style = MaterialTheme.typography.bodySmall,
|
||||||
if (!secondary.isNullOrEmpty()) {
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
Text(
|
)
|
||||||
text = secondary,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user