From 829a05a598cfdc2b327ba22b39d6ea6b8d10602b Mon Sep 17 00:00:00 2001 From: David Perez Date: Mon, 13 May 2024 13:28:27 -0500 Subject: [PATCH] Add keyboard navigation logic for password fields to handle the tab button (#1365) --- .../platform/base/util/ModifierExtensions.kt | 33 +++++++++++++++++++ .../appbar/BitwardenSearchTopAppBar.kt | 2 ++ .../field/BitwardenPasswordField.kt | 5 ++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/ModifierExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/ModifierExtensions.kt index 159941985..764858f38 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/ModifierExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/ModifierExtensions.kt @@ -10,8 +10,16 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.draw.scale +import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.isShiftPressed +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.input.key.type +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection @@ -87,3 +95,28 @@ fun Modifier.mirrorIfRtl(): Modifier = } else { this } + +/** + * This is a [Modifier] extension for ensuring that the tab button navigates properly when using + * a physical keyboard. + */ +@OmitFromCoverage +@Stable +@Composable +fun Modifier.tabNavigation(): Modifier { + val focusManager = LocalFocusManager.current + return onPreviewKeyEvent { keyEvent -> + if (keyEvent.key == Key.Tab && keyEvent.type == KeyEventType.KeyDown) { + focusManager.moveFocus( + if (keyEvent.isShiftPressed) { + FocusDirection.Previous + } else { + FocusDirection.Next + }, + ) + true + } else { + false + } + } +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/appbar/BitwardenSearchTopAppBar.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/appbar/BitwardenSearchTopAppBar.kt index 0999f425b..48a139fc0 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/appbar/BitwardenSearchTopAppBar.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/appbar/BitwardenSearchTopAppBar.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.platform.base.util.mirrorIfRtl +import com.x8bit.bitwarden.ui.platform.base.util.tabNavigation import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter /** @@ -91,6 +92,7 @@ fun BitwardenSearchTopAppBar( }, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), modifier = Modifier + .tabNavigation() .focusRequester(focusRequester) .fillMaxWidth(), ) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenPasswordField.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenPasswordField.kt index 1f52c46fb..ae26c1242 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenPasswordField.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenPasswordField.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.tooling.preview.Preview import com.x8bit.bitwarden.R +import com.x8bit.bitwarden.ui.platform.base.util.tabNavigation import com.x8bit.bitwarden.ui.platform.components.util.nonLetterColorVisualTransformation import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter @@ -70,7 +71,9 @@ fun BitwardenPasswordField( ) { val focusRequester = remember { FocusRequester() } OutlinedTextField( - modifier = modifier.focusRequester(focusRequester), + modifier = modifier + .tabNavigation() + .focusRequester(focusRequester), textStyle = MaterialTheme.typography.bodyLarge, label = { Text(text = label) }, value = value,