media: the player card with its cover sits beside every tab, Up next comes after Now playing, and Browse's header gets room

This commit is contained in:
Seb3thehacker committed 2026-10-26 01:25:45 -07:00
1 parent 15d6721705
commit 324112ca8b
2 files changed
+30 -26

No files matched your search

@@ -84,14 +84,15 @@ internal fun BrowsePanel(browse: app.seb3thehacker.gearslip.media.BrowseState, m
// row could say that the app's name in the top-left corner hasn't already - showing it // row could say that the app's name in the top-left corner hasn't already - showing it
// again here (as "Browse", or as the app's own name) was just the same label twice. // again here (as "Browse", or as the app's own name) was just the same label twice.
if (media.canGoUp || browse.trail.isNotEmpty()) { if (media.canGoUp || browse.trail.isNotEmpty()) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(Modifier.padding(bottom = 12.dp), verticalAlignment = Alignment.CenterVertically) {
if (media.canGoUp) { if (media.canGoUp) {
GsIconButton(Icons.Filled.ArrowBack, "Up", media::up) GsIconButton(Icons.Filled.ArrowBack, "Up", media::up, size = 44.dp, shape = CircleShape)
Spacer(Modifier.width(14.dp))
} }
browse.trail.lastOrNull()?.let { browse.trail.lastOrNull()?.let {
Text( Text(
it, it,
style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold,
maxLines = 1, overflow = TextOverflow.Ellipsis, maxLines = 1, overflow = TextOverflow.Ellipsis,
) )
} }
@@ -251,9 +252,9 @@ private fun QueueRow(track: QueueTrack, media: CarMedia) {
color = MaterialTheme.colorScheme.surfaceContainer, color = MaterialTheme.colorScheme.surfaceContainer,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) { ) {
Row(Modifier.padding(10.dp), verticalAlignment = Alignment.CenterVertically) { Row(Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically) {
RowArt(icon, MediaIcons.MusicNote) RowArt(icon, MediaIcons.MusicNote)
Column(Modifier.weight(1f).padding(horizontal = 12.dp)) { Column(Modifier.weight(1f).padding(horizontal = 14.dp)) {
Text(track.title, style = MaterialTheme.typography.titleMedium, maxLines = 1, overflow = TextOverflow.Ellipsis) Text(track.title, style = MaterialTheme.typography.titleMedium, maxLines = 1, overflow = TextOverflow.Ellipsis)
if (track.subtitle.isNotEmpty()) { if (track.subtitle.isNotEmpty()) {
Text(track.subtitle, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis) Text(track.subtitle, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis)
@@ -276,9 +277,9 @@ internal fun EntryRow(entry: MediaEntry, media: CarMedia, onClick: () -> Unit =
color = MaterialTheme.colorScheme.surfaceContainer, color = MaterialTheme.colorScheme.surfaceContainer,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) { ) {
Row(Modifier.padding(10.dp), verticalAlignment = Alignment.CenterVertically) { Row(Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically) {
RowArt(icon, if (entry.browsable) MediaIcons.Folder else MediaIcons.MusicNote) RowArt(icon, if (entry.browsable) MediaIcons.Folder else MediaIcons.MusicNote)
Column(Modifier.weight(1f).padding(horizontal = 12.dp)) { Column(Modifier.weight(1f).padding(horizontal = 14.dp)) {
Text(entry.title, style = MaterialTheme.typography.titleMedium, maxLines = 1, overflow = TextOverflow.Ellipsis) Text(entry.title, style = MaterialTheme.typography.titleMedium, maxLines = 1, overflow = TextOverflow.Ellipsis)
if (entry.subtitle.isNotEmpty()) { if (entry.subtitle.isNotEmpty()) {
Text(entry.subtitle, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis) Text(entry.subtitle, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis)
@@ -296,7 +297,7 @@ internal fun EntryRow(entry: MediaEntry, media: CarMedia, onClick: () -> Unit =
@Composable @Composable
private fun RowArt(icon: android.graphics.Bitmap?, fallback: ImageVector) { private fun RowArt(icon: android.graphics.Bitmap?, fallback: ImageVector) {
Box( Box(
Modifier.size(62.dp).clip(MaterialTheme.shapes.medium).background(MaterialTheme.colorScheme.surfaceContainerHigh), Modifier.size(56.dp).clip(RoundedCornerShape(12.dp)).background(MaterialTheme.colorScheme.surfaceContainerHigh),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
if (icon != null) Image(icon.asImageBitmap(), null, Modifier.fillMaxSize(), contentScale = ContentScale.Crop) if (icon != null) Image(icon.asImageBitmap(), null, Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
@@ -175,14 +175,14 @@ fun MediaScreen(app: MediaApp, onExit: () -> Unit) {
if (tab == Tab.NOW_PLAYING) { if (tab == Tab.NOW_PLAYING) {
CoverArt(art, Modifier.weight(1f).fillMaxWidth()) CoverArt(art, Modifier.weight(1f).fillMaxWidth())
} else { } else {
CompactPlayer(now, media, Modifier.weight(1f).fillMaxWidth()) CompactPlayer(now, media, art, Modifier.weight(1f).fillMaxWidth())
} }
} }
Column(Modifier.weight(1.2f).fillMaxHeight()) { Column(Modifier.weight(1.2f).fillMaxHeight()) {
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
MediaTab("Now playing", tab == Tab.NOW_PLAYING, Modifier.weight(1f)) { tab = Tab.NOW_PLAYING } MediaTab("Now playing", tab == Tab.NOW_PLAYING, Modifier.weight(1f)) { tab = Tab.NOW_PLAYING }
MediaTab("Browse", tab == Tab.BROWSE, Modifier.weight(1f)) { tab = Tab.BROWSE }
MediaTab("Up next", tab == Tab.QUEUE, Modifier.weight(1f)) { tab = Tab.QUEUE } MediaTab("Up next", tab == Tab.QUEUE, Modifier.weight(1f)) { tab = Tab.QUEUE }
MediaTab("Browse", tab == Tab.BROWSE, Modifier.weight(1f)) { tab = Tab.BROWSE }
MediaTab("Lyrics", tab == Tab.LYRICS, Modifier.weight(1f)) { tab = Tab.LYRICS } MediaTab("Lyrics", tab == Tab.LYRICS, Modifier.weight(1f)) { tab = Tab.LYRICS }
if (now.canSearch || media.canSearchLibrary) { if (now.canSearch || media.canSearchLibrary) {
GsIconButton( GsIconButton(
@@ -603,8 +603,8 @@ internal fun NowPlayingPage(now: NowPlaying, media: CarMedia, modifier: Modifier
/** The cover, as big a square as its space allows, centred in it. */ /** The cover, as big a square as its space allows, centred in it. */
@Composable @Composable
private fun CoverArt(art: android.graphics.Bitmap?, modifier: Modifier) { private fun CoverArt(art: android.graphics.Bitmap?, modifier: Modifier, alignment: Alignment = Alignment.Center) {
BoxWithConstraints(modifier, contentAlignment = Alignment.Center) { BoxWithConstraints(modifier, contentAlignment = alignment) {
val shape = RoundedCornerShape(22.dp) val shape = RoundedCornerShape(22.dp)
Surface( Surface(
shape = shape, shape = shape,
@@ -707,30 +707,33 @@ private fun NowPlayingControls(now: NowPlaying, media: CarMedia, modifier: Modif
} }
/** /**
* The player beside the browse list, the queue and the lyrics: no art, which the list needs the * The player beside the browse list, the queue and the lyrics: the same card as beside the map on
* room for, just what's playing, the seek bar and the controls. * Home, the cover taking whatever the title and controls leave, so every tab keeps the song in
* sight and the controls in one place.
*/ */
@Composable @Composable
private fun CompactPlayer(now: NowPlaying, media: CarMedia, modifier: Modifier) { private fun CompactPlayer(now: NowPlaying, media: CarMedia, art: android.graphics.Bitmap?, modifier: Modifier) {
Column( val shape = RoundedCornerShape(24.dp)
modifier.verticalScroll(rememberScrollState()), Surface(modifier.gsPanelShadow(shape), shape = shape, color = MaterialTheme.colorScheme.surfaceContainer) {
verticalArrangement = Arrangement.spacedBy(14.dp), AlbumGlow(art, Modifier.fillMaxSize())
) { Column(Modifier.fillMaxSize().padding(14.dp), horizontalAlignment = Alignment.CenterHorizontally) {
Column { CoverArt(art, Modifier.weight(1f).fillMaxWidth(), Alignment.BottomCenter)
Spacer(Modifier.height(10.dp))
Text( Text(
now.title.ifEmpty { "Nothing playing" }, now.title.ifEmpty { "Nothing playing" },
style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold,
maxLines = 2, overflow = TextOverflow.Ellipsis, maxLines = 1, overflow = TextOverflow.Ellipsis,
) )
Text( Text(
now.artist, now.artist,
style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1, overflow = TextOverflow.Ellipsis, maxLines = 1, overflow = TextOverflow.Ellipsis,
) )
PlayerProgress(now, media, Modifier.fillMaxWidth().padding(vertical = 2.dp), showTimes = false)
TransportRow(now, media, skip = 52.dp, modifier = Modifier.fillMaxWidth())
Spacer(Modifier.height(8.dp))
PlayModeRow(now, media, size = 46.dp, modifier = Modifier.fillMaxWidth())
} }
PlayerProgress(now, media, Modifier.fillMaxWidth())
TransportRow(now, media, skip = 52.dp, modifier = Modifier.fillMaxWidth())
PlayModeRow(now, media, size = 48.dp, modifier = Modifier.fillMaxWidth())
} }
} }