The info and comments panels sat under a hard-coded 48dp top padding, leaving a black strip above them; they now fill the height from the very top (#24, and the same note on #23). The info panel no longer repeats the description's links in a list underneath — they stay inline in the running text and OK opens the first one. UP/DOWN are always consumed while scrolling, so the panel can no longer strand focus in a place it can't scroll back from (#23 review). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
904020da3c
commit
f8a36a73e5
2 changed files with 28 additions and 60 deletions
|
|
@ -190,64 +190,28 @@ private fun InfoColumn(
|
|||
}
|
||||
|
||||
val descFocus = remember { FocusRequester() }
|
||||
val firstLinkFocus = remember { FocusRequester() }
|
||||
// Focus the description so the D-pad can scroll it right away (issue #23):
|
||||
// a description with no links used to have nothing focusable, so a long
|
||||
// text could never be scrolled down with the remote. Fall back to the
|
||||
// first link when there is no description text.
|
||||
// The description owns the focus for the whole panel: UP/DOWN scroll it and are
|
||||
// always consumed, so scrolling can never strand focus somewhere it can't come
|
||||
// back from (issue #23 review: "it loses focus and it's impossible to scroll up").
|
||||
LaunchedEffect(Unit) {
|
||||
kotlinx.coroutines.delay(150)
|
||||
runCatching {
|
||||
if (description.isNotBlank()) descFocus.requestFocus()
|
||||
else firstLinkFocus.requestFocus()
|
||||
}
|
||||
runCatching { descFocus.requestFocus() }
|
||||
}
|
||||
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
if (description.isNotBlank()) {
|
||||
if (description.isBlank()) {
|
||||
SidebarFallback("No description.", onDismiss)
|
||||
} else {
|
||||
DescriptionScroll(
|
||||
annotated = annotated,
|
||||
focusRequester = descFocus,
|
||||
onDismiss = onDismiss,
|
||||
onLeaveBottom = {
|
||||
if (links.isNotEmpty()) {
|
||||
runCatching { firstLinkFocus.requestFocus() }.isSuccess
|
||||
} else false
|
||||
},
|
||||
// Links stay inline in the running text (issue #23 review) instead of
|
||||
// being repeated in a list underneath; OK opens the first one.
|
||||
onActivate = { links.firstOrNull()?.let { open(it) } },
|
||||
modifier = Modifier.weight(1f, fill = false),
|
||||
)
|
||||
}
|
||||
if (links.isNotEmpty()) {
|
||||
Text(
|
||||
text = "Links",
|
||||
color = TextDim,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
modifier = Modifier.padding(start = 20.dp, top = 10.dp, bottom = 2.dp),
|
||||
)
|
||||
LazyColumn(Modifier.heightIn(max = 220.dp)) {
|
||||
links.forEachIndexed { index, url ->
|
||||
item(key = "l" + index + url) {
|
||||
SidebarRow(
|
||||
onLeft = onDismiss,
|
||||
onUp = if (index == 0 && description.isNotBlank()) {
|
||||
{ runCatching { descFocus.requestFocus() } }
|
||||
} else null,
|
||||
atTop = index == 0 && description.isBlank(),
|
||||
atBottom = index == links.size - 1,
|
||||
focusRequester = if (index == 0) firstLinkFocus else null,
|
||||
onClick = { open(url) },
|
||||
) {
|
||||
Text(
|
||||
text = linkLabel(url),
|
||||
color = TextMain,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -263,7 +227,7 @@ private fun DescriptionScroll(
|
|||
annotated: androidx.compose.ui.text.AnnotatedString,
|
||||
focusRequester: FocusRequester,
|
||||
onDismiss: () -> Unit,
|
||||
onLeaveBottom: () -> Boolean,
|
||||
onActivate: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scroll = androidx.compose.foundation.rememberScrollState()
|
||||
|
|
@ -281,19 +245,23 @@ private fun DescriptionScroll(
|
|||
if (ke.type != KeyEventType.KeyDown) return@onPreviewKeyEvent false
|
||||
val step = (viewportPx * 0.85f).coerceAtLeast(240f)
|
||||
when (ke.nativeKeyEvent.keyCode) {
|
||||
KeyEvent.KEYCODE_DPAD_DOWN ->
|
||||
if (scroll.value < scroll.maxValue) {
|
||||
scope.launch { scroll.animateScrollBy(step) }
|
||||
// UP/DOWN are always consumed: they scroll while there is room and
|
||||
// otherwise do nothing, so focus stays here and the text can always
|
||||
// be scrolled back up (issue #23 review).
|
||||
KeyEvent.KEYCODE_DPAD_DOWN -> {
|
||||
if (scroll.value < scroll.maxValue) scope.launch { scroll.animateScrollBy(step) }
|
||||
true
|
||||
} else onLeaveBottom()
|
||||
KeyEvent.KEYCODE_DPAD_UP ->
|
||||
if (scroll.value > 0) {
|
||||
scope.launch { scroll.animateScrollBy(-step) }
|
||||
}
|
||||
KeyEvent.KEYCODE_DPAD_UP -> {
|
||||
if (scroll.value > 0) scope.launch { scroll.animateScrollBy(-step) }
|
||||
true
|
||||
} else true // nothing above the description; stay put
|
||||
}
|
||||
KeyEvent.KEYCODE_DPAD_LEFT -> {
|
||||
onDismiss(); true
|
||||
}
|
||||
KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.KEYCODE_ENTER -> {
|
||||
onActivate(); true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -646,9 +646,9 @@ private fun PlayerSurface(
|
|||
closeSidebar()
|
||||
},
|
||||
onOpenVideo = onOpenVideo,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.padding(top = 48.dp),
|
||||
// Full height from the very top — the old top padding left a black
|
||||
// strip above the panel (issues #23/#24).
|
||||
modifier = Modifier.align(Alignment.TopEnd),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue