mirror of
https://github.com/bitwarden/android.git
synced 2025-02-16 11:59:57 +03:00
BIT-2156: auto-prompt the user for biometrics (#1307)
This commit is contained in:
parent
bb1fd4ae4f
commit
bde338ef44
4 changed files with 71 additions and 14 deletions
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Reference in a new issue