From 7f93377c2f5b9a1143f3a73b83758d80dfc49608 Mon Sep 17 00:00:00 2001 From: Jonathan Nobels Date: Wed, 29 Jan 2025 13:17:12 -0500 Subject: [PATCH] android/tv: fix focus highlighting on back buttons (#605) updates tailscale/corp#26199 The back buttons would not highlight properly when they are focussed on Android TV. This pulls in what was implemented for the Avatar. Some small tweaks to the action animation so that it has a nice radius instead of a square box. Signed-off-by: Jonathan Nobels --- .../java/com/tailscale/ipn/ui/view/Avatar.kt | 2 +- .../com/tailscale/ipn/ui/view/SharedViews.kt | 31 ++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/Avatar.kt b/android/src/main/java/com/tailscale/ipn/ui/view/Avatar.kt index 89ef4d9..be06272 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/Avatar.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/Avatar.kt @@ -42,7 +42,7 @@ fun Avatar( action: (() -> Unit)? = null, isFocusable: Boolean = false ) { - var isFocused = remember { mutableStateOf(false) } + val isFocused = remember { mutableStateOf(false) } val focusManager = LocalFocusManager.current // Outer Box for the larger focusable and clickable area diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/SharedViews.kt b/android/src/main/java/com/tailscale/ipn/ui/view/SharedViews.kt index e18bcaf..e0b5169 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/SharedViews.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/SharedViews.kt @@ -4,6 +4,7 @@ package com.tailscale.ipn.ui.view import androidx.annotation.StringRes +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.focusable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -11,6 +12,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.CheckCircle @@ -24,10 +26,14 @@ import androidx.compose.material3.TopAppBar import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.tailscale.ipn.ui.theme.topAppBar @@ -77,23 +83,32 @@ fun Header( @Composable fun BackArrow(action: () -> Unit, focusRequester: FocusRequester) { - val modifier = + val isFocused = remember { mutableStateOf(false) } + + val boxModifier = if (isAndroidTV()) { Modifier.focusRequester(focusRequester) - .focusable() // Ensure the composable can receive focus + .clip(CircleShape) // Ensure both the focus and click area are circular + .background( + if (isFocused.value) MaterialTheme.colorScheme.surface else Color.Transparent, + ) + .onFocusChanged { focusState -> isFocused.value = focusState.isFocused } + .focusable() } else { Modifier } - Box(modifier = modifier.padding(start = 8.dp, end = 8.dp)) { + val iconModifier = + Modifier.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(bounded = false, radius = 24.dp), + onClick = action) + + Box(modifier = boxModifier.padding(start = 8.dp, end = 8.dp)) { Icon( Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Go back to the previous screen", - modifier = - Modifier.clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(bounded = true), - onClick = action)) + modifier = iconModifier) } }