Android v1 polish + Web UI flavor pass #65

Merged
bvandeusen merged 13 commits from dev into main 2026-06-01 20:17:49 -04:00
Showing only changes of commit d99de1af27 - Show all commits
@@ -101,7 +101,12 @@ import javax.inject.Inject
private const val SHARE_STOP_TIMEOUT_MS = 5_000L private const val SHARE_STOP_TIMEOUT_MS = 5_000L
private const val PLAYLIST_FETCH_TIMEOUT_MS = 8_000L private const val PLAYLIST_FETCH_TIMEOUT_MS = 8_000L
private const val BOTTOM_PADDING_FOR_MINIPLAYER_DP = 140 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 ─────────────────────────────────────────────────────────── // ─── State ───────────────────────────────────────────────────────────
@@ -512,23 +517,75 @@ private fun LazyListScope.recentlyAddedSection(
} }
return return
} }
// Chunk into stacked carousels of 25 so a large library reads as item {
// multiple rows instead of one unwieldy LazyRow. Only the first RecentlyAddedGrid(
// chunk carries the section title; the rest are continuation rows. albums = albums,
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,
onAlbumClick = onAlbumClick, onAlbumClick = onAlbumClick,
onPlayAlbum = onPlayAlbum, 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( private fun LazyListScope.rediscoverSection(
albums: List<HomeTile<AlbumRef>>, albums: List<HomeTile<AlbumRef>>,
artists: List<HomeTile<ArtistRef>>, artists: List<HomeTile<ArtistRef>>,