Release: web UX overhaul + Android native port + server polish #59
@@ -18,6 +18,9 @@ import androidx.compose.foundation.layout.width
|
|||||||
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.LazyRow
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
|
||||||
|
import androidx.compose.foundation.lazy.grid.items as gridItems
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
@@ -813,24 +816,64 @@ private fun MostPlayedRow(
|
|||||||
tracks: List<HomeTile<TrackRef>>,
|
tracks: List<HomeTile<TrackRef>>,
|
||||||
onTap: (List<TrackRef>, Int) -> Unit,
|
onTap: (List<TrackRef>, Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
HorizontalScrollRow(title = "Most played") {
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
items(items = tracks, key = { it.id }) { tile ->
|
Text(
|
||||||
val track = tile.value
|
text = "Most played",
|
||||||
if (track == null) {
|
style = MaterialTheme.typography.titleLarge,
|
||||||
SkeletonCompactTrackTile()
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
} else {
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
CompactTrackTile(
|
)
|
||||||
track = track,
|
// Match web's chunked-rows-in-shared-scroller layout
|
||||||
onClick = {
|
// (web/src/routes/+page.svelte:196): tracks are dealt into
|
||||||
val playable = tracks.mapNotNull { it.value }
|
// MOST_PLAYED_ROWS rows of equal width, all stacked inside one
|
||||||
onTap(playable, playable.indexOfFirst { it.id == track.id })
|
// horizontal scroller. Web is row-major (ranks 0..24 across the
|
||||||
},
|
// top row, 25..49 across the middle, 50..74 across the bottom);
|
||||||
)
|
// LazyHorizontalGrid fills column-major, so we re-flatten the
|
||||||
|
// chunked input column-by-column to match the web order.
|
||||||
|
val perRow = (tracks.size + MOST_PLAYED_ROWS - 1) / MOST_PLAYED_ROWS
|
||||||
|
val rows = tracks.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 MOST_PLAYED_ROWS) {
|
||||||
|
rows.getOrNull(r)?.getOrNull(c)?.let { add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LazyHorizontalGrid(
|
||||||
|
rows = GridCells.Fixed(MOST_PLAYED_ROWS),
|
||||||
|
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(MOST_PLAYED_GRID_HEIGHT_DP.dp),
|
||||||
|
) {
|
||||||
|
gridItems(items = ordered, key = { it.id }) { tile ->
|
||||||
|
val track = tile.value
|
||||||
|
if (track == null) {
|
||||||
|
SkeletonCompactTrackTile()
|
||||||
|
} else {
|
||||||
|
CompactTrackTile(
|
||||||
|
track = track,
|
||||||
|
onClick = {
|
||||||
|
val playable = tracks.mapNotNull { it.value }
|
||||||
|
onTap(playable, playable.indexOfFirst { it.id == track.id })
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val MOST_PLAYED_ROWS = 3
|
||||||
|
|
||||||
|
// 3 rows of CompactTrackTile (~188dp intrinsic each: 140dp cover +
|
||||||
|
// 8dp spacer + 2 lines of text) + 2 * 8dp inter-row spacing = ~580dp;
|
||||||
|
// 600dp gives breathing room without leaving an obvious gap.
|
||||||
|
private const val MOST_PLAYED_GRID_HEIGHT_DP = 600
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SkeletonCompactTrackTile() {
|
private fun SkeletonCompactTrackTile() {
|
||||||
Column(modifier = Modifier.width(140.dp)) {
|
Column(modifier = Modifier.width(140.dp)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user