ui: CENTER activates everything (clickable lacks DPAD_CENTER)
Compose clickable handles Enter/Space only; whether the TV's OK (DPAD_CENTER) triggers click depends on the device's input stack - worked on the API-36 host emulator, dead on the API-34 ATD and possibly some TVs. A preview-tunnelled center handler now activates drawer items, top-bar buttons, video and channel cards, and the login/search buttons deterministically, exactly once (preview consumption prevents the platform path from double-firing).
This commit is contained in:
parent
d469101eb0
commit
b8893172d4
5 changed files with 46 additions and 4 deletions
|
|
@ -62,6 +62,7 @@ import net.jeena.jtubetv.data.api.VideoChannelDto
|
|||
import net.jeena.jtubetv.ui.list.ImagePrefetcher
|
||||
import net.jeena.jtubetv.ui.screens.list.ContainerShape
|
||||
import net.jeena.jtubetv.ui.screens.list.UnfocusedCardBg
|
||||
import net.jeena.jtubetv.ui.screens.list.dpadCenterActivatable
|
||||
|
||||
/**
|
||||
* Channels, SmartTube-style: one row per LOCAL user.
|
||||
|
|
@ -417,6 +418,7 @@ fun ChannelCard(
|
|||
modifier = modifier
|
||||
.width(rowCardWidth())
|
||||
.padding(6.dp)
|
||||
.dpadCenterActivatable(onClick)
|
||||
.onFocusChanged { cardFocused = it.isFocused }
|
||||
.onPreviewKeyEvent { ke ->
|
||||
if (ke.nativeKeyEvent.action == android.view.KeyEvent.ACTION_DOWN &&
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ import net.jeena.jtubetv.data.api.VideoDto
|
|||
import net.jeena.jtubetv.ui.AppViewModel
|
||||
import net.jeena.jtubetv.ui.list.ListSource
|
||||
import net.jeena.jtubetv.ui.screens.list.VideoListScreen
|
||||
import net.jeena.jtubetv.ui.screens.list.dpadCenterActivatable
|
||||
|
||||
enum class Destination(val title: String) {
|
||||
Home("Home"),
|
||||
|
|
@ -266,6 +267,7 @@ private fun TopBar(
|
|||
else Modifier
|
||||
)
|
||||
.focusable()
|
||||
.dpadCenterActivatable(onAccountClick)
|
||||
.clickable(onClick = onAccountClick),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(8.dp),
|
||||
|
|
@ -338,6 +340,7 @@ private fun RoundTopButton(
|
|||
)
|
||||
.then(if (focusRequester != null) Modifier.focusRequester(focusRequester) else Modifier)
|
||||
.focusable()
|
||||
.dpadCenterActivatable(onClick)
|
||||
.clickable(onClick = onClick),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
|
|
@ -454,6 +457,7 @@ private fun MenuDrawerItem(
|
|||
}
|
||||
)
|
||||
.focusable()
|
||||
.dpadCenterActivatable(onClick)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 24.dp, vertical = 14.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent
|
|||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import net.jeena.jtubetv.ui.screens.list.dpadCenterActivatable
|
||||
|
||||
/**
|
||||
* Feature 0: ask for instance URL + credentials once. The password is used only for
|
||||
|
|
@ -164,9 +165,6 @@ fun SetupScreen(
|
|||
Text(message.value ?: localError.value!!, color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
Button(
|
||||
// Deliberately always focusable: on TV a permanently disabled button
|
||||
// is unreachable and leaves the user stuck without feedback (UX review).
|
||||
// Validation happens on press with an inline message instead.
|
||||
onClick = {
|
||||
when {
|
||||
url.value.isBlank() || username.value.isBlank() || password.value.isBlank() ->
|
||||
|
|
@ -178,7 +176,20 @@ fun SetupScreen(
|
|||
}
|
||||
},
|
||||
enabled = !busy.value,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.dpadCenterActivatable(
|
||||
onClick = {
|
||||
when {
|
||||
url.value.isBlank() || username.value.isBlank() || password.value.isBlank() ->
|
||||
localError.value = "All fields are required"
|
||||
else -> {
|
||||
localError.value = null
|
||||
onLogin(url.value, username.value, password.value)
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
) {
|
||||
if (busy.value) {
|
||||
CircularProgressIndicator(Modifier.width(20.dp))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
package net.jeena.jtubetv.ui.screens.list
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||
|
||||
/**
|
||||
* Compose `clickable` handles Enter/Space but NOT the TV's DPAD_CENTER; whether
|
||||
* CENTER works depends on each device's input translation (works on some emulators,
|
||||
* not on others — see the API-34 ATD case). This modifier consumes CENTER *first*
|
||||
* (preview/tunneling) so the click fires exactly once, on every device.
|
||||
*/
|
||||
fun Modifier.dpadCenterActivatable(onClick: () -> Unit): Modifier = composed {
|
||||
onPreviewKeyEvent { ke ->
|
||||
if (ke.nativeKeyEvent.action == android.view.KeyEvent.ACTION_DOWN &&
|
||||
ke.nativeKeyEvent.keyCode == android.view.KeyEvent.KEYCODE_DPAD_CENTER
|
||||
) {
|
||||
onClick()
|
||||
true
|
||||
} else false
|
||||
}
|
||||
}
|
||||
|
|
@ -69,6 +69,7 @@ fun VideoCard(
|
|||
onClick = onClick,
|
||||
modifier = modifier
|
||||
.padding(6.dp)
|
||||
.dpadCenterActivatable(onClick)
|
||||
.onFocusChanged {
|
||||
cardFocused = it.isFocused
|
||||
if (it.isFocused) onFocused?.invoke()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue