feat(android): Recently Added → single LazyHorizontalGrid (#77)
android / Build + lint + test (push) Successful in 4m14s
android / Build + lint + test (push) Successful in 4m14s
Previously Recently Added chunked into rows of 25 and rendered each
chunk as its own LazyRow, so chunks scrolled independently. Same
pattern Most Played uses (one LazyHorizontalGrid where all rows
scroll as a single panel) now applies to Recently Added.
- New RECENTLY_ADDED_GRID_ROWS = 2 + RECENTLY_ADDED_GRID_HEIGHT_DP =
440 (matches a 200dp tile × 2 rows + the 8dp inter-row gap).
- New RecentlyAddedGrid composable owns the section header + the
LazyHorizontalGrid. Column-major re-flattening matches Web's
row-major reading order on screen (top row first, then bottom).
- recentlyAddedSection collapses from itemsIndexed-over-chunks to
a single item { RecentlyAddedGrid(...) } in the outer LazyColumn.
- AlbumsRow stays untouched (still used by Rediscover, etc.).
The other multi-section helpers (Rediscover, Last Played, Playlists)
are single-row by design and don't need this treatment.
This commit is contained in:
@@ -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<HomeTile<AlbumRef>>,
|
||||
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<HomeTile<AlbumRef>>,
|
||||
artists: List<HomeTile<ArtistRef>>,
|
||||
|
||||
Reference in New Issue
Block a user