From 1eb741ab5838972469f1dd61a549ae25eaeda89f Mon Sep 17 00:00:00 2001 From: Dave Severns <149429124+dseverns-livefront@users.noreply.github.com> Date: Tue, 11 Mar 2025 16:10:46 -0400 Subject: [PATCH] PM-11356 prevent extra soft-keyboard showing. (#4845) --- .../feature/vaultunlock/VaultUnlockScreen.kt | 17 ++++++++++++++++- .../vaultunlock/VaultUnlockScreenTest.kt | 5 ++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockScreen.kt index 2d365d3eef..0afbb86c18 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockScreen.kt @@ -67,8 +67,11 @@ import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricsManager import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.delay import javax.crypto.Cipher +private const val AUTO_FOCUS_DELAY = 415L + /** * The top level composable for the Vault Unlock screen. */ @@ -251,6 +254,18 @@ fun VaultUnlockScreen( ) { Spacer(modifier = Modifier.height(12.dp)) if (!state.hideInput) { + // When switching from an unlocked account to a locked account, the + // current activity is recreated and therefore the composition takes place + // twice. Adding this delay prevents the MP or Pin field + // from auto focusing on the first composition which creates a visual jank where + // the keyboard shows, disappears, and then shows again. + var autoFocusDelayCompleted by rememberSaveable { + mutableStateOf(false) + } + LaunchedEffect(Unit) { + delay(AUTO_FOCUS_DELAY) + autoFocusDelayCompleted = true + } BitwardenPasswordField( label = state.vaultUnlockType.unlockScreenInputLabel(), value = state.input, @@ -261,7 +276,7 @@ fun VaultUnlockScreen( showPasswordTestTag = state .vaultUnlockType .inputFieldVisibilityToggleTestTag, - autoFocus = state.showKeyboard, + autoFocus = state.showKeyboard && autoFocusDelayCompleted, imeAction = ImeAction.Done, keyboardActions = KeyboardActions( onDone = remember(viewModel) { diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockScreenTest.kt index e9eaa16611..29142efa34 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockScreenTest.kt @@ -23,6 +23,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsResult import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow +import com.x8bit.bitwarden.data.util.advanceTimeByAndRunCurrent import com.x8bit.bitwarden.ui.autofill.fido2.manager.Fido2CompletionManager import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest import com.x8bit.bitwarden.ui.platform.base.util.asText @@ -50,6 +51,7 @@ import io.mockk.slot import io.mockk.verify import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import javax.crypto.Cipher @@ -474,8 +476,9 @@ class VaultUnlockScreenTest : BaseComposeTest() { } @Test - fun `state with input and without biometrics should request focus on input field`() { + fun `state with input and without biometrics should request focus on input field`() = runTest { mutableStateFlow.update { it.copy(hideInput = false, isBiometricEnabled = false) } + dispatcher.advanceTimeByAndRunCurrent(500L) composeTestRule .onNodeWithText("Master password") .performScrollTo()