feat(android): denser Most Played tiles - horizontal row matching Flutter
android / Build + lint + test (push) Successful in 4m0s
android / Build signed release APK (push) Has been skipped

Operator device check 2026-06-01: the multi-row grid landed, but the
square 140dp tiles (web-matching) waste vertical space at the per-
tile level. Flutter's CompactTrackCard
(flutter_client/lib/library/widgets/compact_track_card.dart) uses a
horizontal-row pattern - 176dp wide, 56dp tall, 48dp cover thumb +
title/artist column to the right. Much denser; fits ~3x more tiles
in the same screen area.

CompactTrackTile rewritten to that shape:
- Row layout instead of Column
- 48dp square cover (down from 140dp)
- Title + artist column to the right, vertically centered
- 176dp x 56dp outer

MOST_PLAYED_GRID_HEIGHT_DP cut from 600 to 200dp (3 * 56 + 2 * 8
spacing). Skeleton matches the new dimensions.

Diverges from web (which uses square tiles same as Android's prior
shape) - intentional, operator preference. TrackActionsButton from
Flutter's card is omitted for now (would need nav callbacks threaded
through MostPlayedRow); follow-up if kebab-on-tile is wanted.
This commit is contained in:
2026-06-01 08:00:47 -04:00
parent 7b619152ab
commit ef8374753a
@@ -9,7 +9,9 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@@ -869,16 +871,34 @@ private fun MostPlayedRow(
} }
private const val MOST_PLAYED_ROWS = 3 private const val MOST_PLAYED_ROWS = 3
private const val MOST_PLAYED_TILE_WIDTH_DP = 176
private const val MOST_PLAYED_TILE_HEIGHT_DP = 56
private const val MOST_PLAYED_COVER_DP = 48
// 3 rows of CompactTrackTile (~188dp intrinsic each: 140dp cover + // 3 rows of MOST_PLAYED_TILE_HEIGHT_DP + 2 * 8dp inter-row spacing,
// 8dp spacer + 2 lines of text) + 2 * 8dp inter-row spacing = ~580dp; // rounded up. Mirrors Flutter (`CompactTrackCard` in
// 600dp gives breathing room without leaving an obvious gap. // flutter_client/lib/library/widgets/compact_track_card.dart) which
private const val MOST_PLAYED_GRID_HEIGHT_DP = 600 // uses a horizontal-row card pattern - much denser than the square
// per-track tiles that web uses (operator request 2026-06-01: "in the
// flutter iteration the tiles were different and smaller so more of
// them could fit in a smaller space").
private const val MOST_PLAYED_GRID_HEIGHT_DP = 200
@Composable @Composable
private fun SkeletonCompactTrackTile() { private fun SkeletonCompactTrackTile() {
Column(modifier = Modifier.width(140.dp)) { Row(
SkeletonAlbumTile() modifier = Modifier
.width(MOST_PLAYED_TILE_WIDTH_DP.dp)
.height(MOST_PLAYED_TILE_HEIGHT_DP.dp)
.padding(horizontal = 8.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
modifier = Modifier
.size(MOST_PLAYED_COVER_DP.dp)
.clip(RoundedCornerShape(4.dp))
.background(MaterialTheme.colorScheme.surfaceVariant),
)
} }
} }
@@ -887,15 +907,18 @@ private fun CompactTrackTile(
track: TrackRef, track: TrackRef,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
Column( Row(
modifier = Modifier modifier = Modifier
.width(140.dp) .width(MOST_PLAYED_TILE_WIDTH_DP.dp)
.clickable(onClick = onClick), .height(MOST_PLAYED_TILE_HEIGHT_DP.dp)
.clickable(onClick = onClick)
.padding(horizontal = 8.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(140.dp) .size(MOST_PLAYED_COVER_DP.dp)
.clip(RoundedCornerShape(6.dp)) .clip(RoundedCornerShape(4.dp))
.background(MaterialTheme.colorScheme.surfaceVariant), .background(MaterialTheme.colorScheme.surfaceVariant),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
@@ -904,7 +927,7 @@ private fun CompactTrackTile(
imageVector = Lucide.Music, imageVector = Lucide.Music,
contentDescription = null, contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant, tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(56.dp), modifier = Modifier.size(20.dp),
) )
} else { } else {
AsyncImage( AsyncImage(
@@ -915,21 +938,28 @@ private fun CompactTrackTile(
) )
} }
} }
Spacer(Modifier.height(8.dp)) Spacer(Modifier.width(8.dp))
Text( Column(
text = track.title, modifier = Modifier
style = MaterialTheme.typography.titleSmall, .weight(1f)
color = MaterialTheme.colorScheme.onSurface, .fillMaxHeight(),
maxLines = 1, verticalArrangement = Arrangement.Center,
overflow = TextOverflow.Ellipsis, ) {
) Text(
Text( text = track.title,
text = track.artistName.ifEmpty { " " }, style = MaterialTheme.typography.bodyMedium,
style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurface,
color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1,
maxLines = 1, overflow = TextOverflow.Ellipsis,
overflow = TextOverflow.Ellipsis, )
) Text(
text = track.artistName.ifEmpty { " " },
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
} }
} }