Watching bar: times at the right end; the panel fits above the bar

Port of release 0.108.73.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-11-06 20:31:13 +00:00
co-authored by Claude Opus 5.5
parent 20e4f151b8
commit 8a71df27ec
3 changed files with 20 additions and 11 deletions
@@ -42,12 +42,14 @@ internal fun ClassicProgrammeStrip(item: SampleItem, playing: Boolean, modifier:
val catching = scheduled?.catchingUp() == true
Text(if (catching) (if (playing) "FROM START" else "PAUSED") else if (playing || scheduled != null) "NOW" else "PAUSED",
Modifier.background(Yellow, RoundedCornerShape(4.dp)).padding(horizontal = 7.dp, vertical = 2.dp), color = OnAccent, fontWeight = FontWeight.Bold, fontSize = 13.sp)
Text(" " + item.title, Modifier.weight(1f, fill = false), color = Ink, fontWeight = FontWeight.Bold, fontSize = 20.sp, maxLines = 1, overflow = TextOverflow.Ellipsis)
// A short in a slot says where it sits: "Cartoon Shorts · 3 of 6" (Paul, 6 November 2026).
scheduled?.let { shortsLine(it.channel, it.programme) }?.let { line ->
Text(" $line", color = Muted, fontWeight = FontWeight.SemiBold, fontSize = 14.sp, maxLines = 1)
// The title takes the room, so the times sit at the right end, under the clock (Paul, 6 November 2026).
Row(Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) {
Text(" " + item.title, Modifier.weight(1f, fill = false), color = Ink, fontWeight = FontWeight.Bold, fontSize = 20.sp, maxLines = 1, overflow = TextOverflow.Ellipsis)
// A short in a slot says where it sits: "Cartoon Shorts · 3 of 6" (Paul, 6 November 2026).
scheduled?.let { shortsLine(it.channel, it.programme) }?.let { line ->
Text(" $line", color = Muted, fontWeight = FontWeight.SemiBold, fontSize = 14.sp, maxLines = 1)
}
}
Spacer(Modifier.weight(1f))
scheduled?.takeIf { !it.catchingUp() }?.let { Text("${clock(it.programme.startMs)} to ${clock(it.next.startMs)}", color = Muted, fontSize = 13.sp) }
}
// How far through: the accent along a track of the raised colour.
@@ -491,8 +491,15 @@ internal fun EmbyPlaybackDialog(session: EmbySampleSession, plan: EmbyPlaybackPl
// The bar and the subtitles and sound panel together (design/watching): the panel docks above the bar's right
// end, so it never hangs over it, and the bar stays while the panel is open.
val barUp = (infoVisible || trackMenu != null) && failure == null && ready && squeeze == 0f
if (barUp || trackMenu != null) Column(Modifier.align(Alignment.BottomCenter).fillMaxWidth(.86f).padding(bottom = 26.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
trackMenu?.let { Box(Modifier.fillMaxWidth()) { TrackPanel(it, trackCursor, Modifier.align(Alignment.BottomEnd)) } }
if (barUp || trackMenu != null) Column(Modifier.align(Alignment.BottomCenter).fillMaxWidth(.86f).padding(top = 24.dp, bottom = 26.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
// The bar is measured first; the panel gets the room above it and shows as many rows as fit there, so a long
// list never pushes the bar off the screen (Paul, 6 November 2026).
trackMenu?.let { rows ->
BoxWithConstraints(Modifier.fillMaxWidth().weight(1f, fill = false)) {
val fits = ((maxHeight - 56.dp) / 34.dp).toInt().coerceIn(3, 12)
TrackPanel(rows, trackCursor, Modifier.align(Alignment.BottomEnd), maxRows = fits)
}
}
if (barUp) ClassicProgrammeStrip(plan.item, playing, Modifier.fillMaxWidth(), scheduled = scheduled, panelOpen = trackMenu != null)
}
ident?.invoke()
@@ -91,15 +91,15 @@ private const val TRACK_PANEL_ROWS = 12
/** Sits to one side, so the programme keeps playing while you choose. */
@Composable
internal fun TrackPanel(rows: List<TrackRow>, cursor: Int, modifier: Modifier = Modifier) {
internal fun TrackPanel(rows: List<TrackRow>, cursor: Int, modifier: Modifier = Modifier, maxRows: Int = TRACK_PANEL_ROWS) {
// The same card as the programme bar, in the look's own colours (design/watching); the bar below carries the buttons.
val corner = RoundedCornerShape(8.dp)
// Subtitles, sound and the sleep timer can be more than the screen holds, so it shows a stretch around the cursor.
val first = (cursor - TRACK_PANEL_ROWS / 2).coerceIn(0, (rows.size - TRACK_PANEL_ROWS).coerceAtLeast(0))
val first = (cursor - maxRows / 2).coerceIn(0, (rows.size - maxRows).coerceAtLeast(0))
Column(modifier.width(300.dp).background(Deep.copy(alpha = .96f), corner).border(1.dp, Blue, corner).padding(10.dp), verticalArrangement = Arrangement.spacedBy(3.dp)) {
if (first > 0) Text("▲ more", color = Muted, fontSize = 11.sp, modifier = Modifier.padding(start = 8.dp))
rows.forEachIndexed { index, row ->
if (index < first || index >= first + TRACK_PANEL_ROWS) return@forEachIndexed
if (index < first || index >= first + maxRows) return@forEachIndexed
if (index == first || rows[index - 1].section != row.section) {
if (index != first) Box(Modifier.padding(horizontal = 8.dp, vertical = 4.dp).fillMaxWidth().height(1.dp).background(Blue))
Text(row.section.uppercase(), color = Muted, fontSize = 12.sp, fontWeight = FontWeight.Bold, letterSpacing = 2.sp, modifier = Modifier.padding(start = 8.dp, top = 2.dp))
@@ -108,6 +108,6 @@ internal fun TrackPanel(rows: List<TrackRow>, cursor: Int, modifier: Modifier =
Text((if (row.selected) "✓ " else " ") + row.label, Modifier.fillMaxWidth().background(if (on) Yellow else Color.Transparent, RoundedCornerShape(6.dp)).padding(horizontal = 10.dp, vertical = 4.dp),
color = if (on) OnAccent else Ink, fontSize = 15.sp, fontWeight = if (on || row.selected) FontWeight.SemiBold else FontWeight.Normal, maxLines = 1)
}
if (first + TRACK_PANEL_ROWS < rows.size) Text("▼ more", color = Muted, fontSize = 11.sp, modifier = Modifier.padding(start = 8.dp))
if (first + maxRows < rows.size) Text("▼ more", color = Muted, fontSize = 11.sp, modifier = Modifier.padding(start = 8.dp))
}
}