BIT-2156: auto-prompt the user for biometrics (#1307)

This commit is contained in:
David Perez 2024-04-25 16:21:07 -05:00 committed by Álison Fernandes
parent bb1fd4ae4f
commit bde338ef44
4 changed files with 71 additions and 14 deletions

View file

@ -27,7 +27,6 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@ -72,11 +71,26 @@ fun VaultUnlockScreen(
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
val context = LocalContext.current
val resources = context.resources
val onBiometricsUnlockClick: () -> Unit = remember(viewModel) {
{ viewModel.trySendAction(VaultUnlockAction.BiometricsUnlockClick) }
}
val onBiometricsLockOut: () -> Unit = remember(viewModel) {
{ viewModel.trySendAction(VaultUnlockAction.BiometricsLockOut) }
}
EventsEffect(viewModel = viewModel) { event ->
when (event) {
is VaultUnlockEvent.ShowToast -> {
Toast.makeText(context, event.text(resources), Toast.LENGTH_SHORT).show()
}
VaultUnlockEvent.PromptForBiometrics -> {
biometricsManager.promptForBiometrics(
onSuccess = onBiometricsUnlockClick,
onLockOut = onBiometricsLockOut,
)
}
}
}
@ -121,12 +135,6 @@ fun VaultUnlockScreen(
)
}
val onBiometricsUnlockClick: () -> Unit = remember(viewModel) {
{ viewModel.trySendAction(VaultUnlockAction.BiometricsUnlockClick) }
}
val onBiometricsLockOut: () -> Unit = remember(viewModel) {
{ viewModel.trySendAction(VaultUnlockAction.BiometricsLockOut) }
}
// Content
BitwardenScaffold(
modifier = Modifier
@ -209,14 +217,8 @@ fun VaultUnlockScreen(
BitwardenOutlinedButton(
label = stringResource(id = R.string.use_biometrics_to_unlock),
onClick = {
biometricsManager.promptBiometrics(
biometricsManager.promptForBiometrics(
onSuccess = onBiometricsUnlockClick,
onCancel = {
// no-op
},
onError = {
// no-op
},
onLockOut = onBiometricsLockOut,
)
},
@ -266,3 +268,22 @@ fun VaultUnlockScreen(
}
}
}
/**
* Helper method for easier prompting for biometrics.
*/
private fun BiometricsManager.promptForBiometrics(
onSuccess: () -> Unit,
onLockOut: () -> Unit,
) {
promptBiometrics(
onSuccess = onSuccess,
onCancel = {
// no-op
},
onError = {
// no-op
},
onLockOut = onLockOut,
)
}

View file

@ -94,6 +94,10 @@ class VaultUnlockViewModel @Inject constructor(
sendAction(VaultUnlockAction.Internal.UserStateUpdateReceive(userState = it))
}
.launchIn(viewModelScope)
if (state.showBiometricLogin) {
sendEvent(VaultUnlockEvent.PromptForBiometrics)
}
}
override fun handleAction(action: VaultUnlockAction) {
@ -336,6 +340,11 @@ sealed class VaultUnlockEvent {
data class ShowToast(
val text: Text,
) : VaultUnlockEvent()
/**
* Prompts the user for biometrics unlock.
*/
data object PromptForBiometrics : VaultUnlockEvent()
}
/**

View file

@ -75,6 +75,19 @@ class VaultUnlockScreenTest : BaseComposeTest() {
}
}
@Test
fun `on PromptForBiometrics should call launchUri on intentManager`() {
mutableEventFlow.tryEmit(VaultUnlockEvent.PromptForBiometrics)
verify {
biometricsManager.promptBiometrics(
onSuccess = any(),
onCancel = any(),
onError = any(),
onLockOut = any(),
)
}
}
@Test
fun `account icon click should show the account switcher`() {
composeTestRule.assertSwitcherIsNotDisplayed(

View file

@ -1,6 +1,7 @@
package com.x8bit.bitwarden.ui.auth.feature.vaultunlock
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
@ -54,6 +55,19 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
every { isBiometricIntegrityValid(userId = DEFAULT_USER_STATE.activeUserId) } returns true
}
@Test
fun `on init with biometrics enabled and valid should emit PromptForBiometrics`() = runTest {
val initialState = DEFAULT_STATE.copy(
isBiometricEnabled = true,
isBiometricsValid = true,
)
val viewModel = createViewModel(state = initialState)
viewModel.eventFlow.test {
assertEquals(VaultUnlockEvent.PromptForBiometrics, awaitItem())
}
}
@Test
fun `initial state should be correct when not set`() {
val viewModel = createViewModel()