diff --git a/android/app/src/main/java/com/fabledsword/minstrel/home/ui/HomeScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/home/ui/HomeScreen.kt index c2b03bc4..ae5a0ed9 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/home/ui/HomeScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/home/ui/HomeScreen.kt @@ -101,7 +101,12 @@ import javax.inject.Inject private const val SHARE_STOP_TIMEOUT_MS = 5_000L private const val PLAYLIST_FETCH_TIMEOUT_MS = 8_000L private const val BOTTOM_PADDING_FOR_MINIPLAYER_DP = 140 -private const val RECENTLY_ADDED_CHUNK = 25 +// Recently Added is laid out in a multi-row LazyHorizontalGrid that +// scrolls as one panel (same pattern as Most Played). Two rows trades +// a tall block for fewer columns to scroll past — a 200dp tile × 2 = +// ~440dp section still leaves room for the rest of Home. +private const val RECENTLY_ADDED_GRID_ROWS = 2 +private const val RECENTLY_ADDED_GRID_HEIGHT_DP = 440 // ─── State ─────────────────────────────────────────────────────────── @@ -512,23 +517,75 @@ private fun LazyListScope.recentlyAddedSection( } return } - // Chunk into stacked carousels of 25 so a large library reads as - // multiple rows instead of one unwieldy LazyRow. Only the first - // chunk carries the section title; the rest are continuation rows. - val chunks = albums.chunked(RECENTLY_ADDED_CHUNK) - itemsIndexed( - items = chunks, - key = { index, chunk -> "recent-$index-${chunk.first().id}" }, - ) { index, chunk -> - AlbumsRow( - title = if (index == 0) "Recently added" else "", - albums = chunk, + item { + RecentlyAddedGrid( + albums = albums, onAlbumClick = onAlbumClick, onPlayAlbum = onPlayAlbum, ) } } +/** + * Multi-row horizontal grid for Recently Added, mirroring the + * Most Played pattern: tiles laid out column-major across + * [RECENTLY_ADDED_GRID_ROWS] rows inside one LazyHorizontalGrid so + * every row scrolls together as one panel. Replaces the previous + * stacked-LazyRows layout which scrolled each chunk independently. + */ +@Composable +private fun RecentlyAddedGrid( + albums: List>, + onAlbumClick: (String) -> Unit, + onPlayAlbum: suspend (String) -> Unit, +) { + Column(modifier = Modifier.fillMaxWidth()) { + Text( + text = "Recently added", + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onBackground, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + ) + // Re-flatten column-major to match Web's "ranks across the + // top row, then across the middle, then bottom" reading + // order. LazyHorizontalGrid otherwise fills column-major + // directly from a flat list and would scramble per-rank + // expectations. + val perRow = (albums.size + RECENTLY_ADDED_GRID_ROWS - 1) / RECENTLY_ADDED_GRID_ROWS + val rows = albums.chunked(perRow.coerceAtLeast(1)) + val cols = rows.maxOfOrNull { it.size } ?: 0 + val ordered = buildList { + for (c in 0 until cols) { + for (r in 0 until RECENTLY_ADDED_GRID_ROWS) { + rows.getOrNull(r)?.getOrNull(c)?.let { add(it) } + } + } + } + LazyHorizontalGrid( + rows = GridCells.Fixed(RECENTLY_ADDED_GRID_ROWS), + contentPadding = PaddingValues(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier + .fillMaxWidth() + .height(RECENTLY_ADDED_GRID_HEIGHT_DP.dp), + ) { + gridItems(items = ordered, key = { it.id }) { tile -> + val album = tile.value + if (album == null) { + SkeletonAlbumTile() + } else { + AlbumCard( + album = album, + onClick = { onAlbumClick(album.id) }, + onPlay = { onPlayAlbum(album.id) }, + ) + } + } + } + } +} + private fun LazyListScope.rediscoverSection( albums: List>, artists: List>,