From ef8374753a0e0bc9d0ea3c9cc96ab6fff98781ec Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 1 Jun 2026 08:00:47 -0400 Subject: [PATCH] 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. --- .../minstrel/home/ui/HomeScreen.kt | 84 +++++++++++++------ 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/android/app/src/main/java/com/fabledsword/minstrel/home/ui/HomeScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/home/ui/HomeScreen.kt index e7ee2d67..03d8f3de 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/home/ui/HomeScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/home/ui/HomeScreen.kt @@ -9,7 +9,9 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth 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_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 + -// 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 +// 3 rows of MOST_PLAYED_TILE_HEIGHT_DP + 2 * 8dp inter-row spacing, +// rounded up. Mirrors Flutter (`CompactTrackCard` in +// flutter_client/lib/library/widgets/compact_track_card.dart) which +// 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 private fun SkeletonCompactTrackTile() { - Column(modifier = Modifier.width(140.dp)) { - SkeletonAlbumTile() + Row( + 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, onClick: () -> Unit, ) { - Column( + Row( modifier = Modifier - .width(140.dp) - .clickable(onClick = onClick), + .width(MOST_PLAYED_TILE_WIDTH_DP.dp) + .height(MOST_PLAYED_TILE_HEIGHT_DP.dp) + .clickable(onClick = onClick) + .padding(horizontal = 8.dp, vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically, ) { Box( modifier = Modifier - .size(140.dp) - .clip(RoundedCornerShape(6.dp)) + .size(MOST_PLAYED_COVER_DP.dp) + .clip(RoundedCornerShape(4.dp)) .background(MaterialTheme.colorScheme.surfaceVariant), contentAlignment = Alignment.Center, ) { @@ -904,7 +927,7 @@ private fun CompactTrackTile( imageVector = Lucide.Music, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(56.dp), + modifier = Modifier.size(20.dp), ) } else { AsyncImage( @@ -915,21 +938,28 @@ private fun CompactTrackTile( ) } } - Spacer(Modifier.height(8.dp)) - Text( - text = track.title, - style = MaterialTheme.typography.titleSmall, - color = MaterialTheme.colorScheme.onSurface, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - Text( - text = track.artistName.ifEmpty { " " }, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) + Spacer(Modifier.width(8.dp)) + Column( + modifier = Modifier + .weight(1f) + .fillMaxHeight(), + verticalArrangement = Arrangement.Center, + ) { + Text( + text = track.title, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + text = track.artistName.ifEmpty { " " }, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } } }