feat(android): denser Most Played tiles - horizontal row matching Flutter
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:
@@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user