mirror of
https://github.com/bitwarden/android.git
synced 2026-08-25 17:09:55 -05:00
PM-15036 Show visual feedback for the send code on export vault. (#4346)
This commit is contained in:
+33
-1
@@ -8,6 +8,7 @@ import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.PasswordStrengthResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.RequestOtpResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.VerifyOtpResult
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
@@ -116,9 +117,24 @@ class ExportVaultViewModel @Inject constructor(
|
||||
is ExportVaultAction.Internal.ReceiveVerifyOneTimePasscodeResult -> {
|
||||
handleReceiveVerifyOneTimePasscodeResult(action)
|
||||
}
|
||||
|
||||
is ExportVaultAction.Internal.OtpCodeResult -> handleOtpCodeResult(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOtpCodeResult(action: ExportVaultAction.Internal.OtpCodeResult) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = null)
|
||||
}
|
||||
val toastMessage = when (val result = action.result) {
|
||||
is RequestOtpResult.Error -> {
|
||||
result.message?.asText() ?: R.string.generic_error_message.asText()
|
||||
}
|
||||
RequestOtpResult.Success -> R.string.code_sent.asText()
|
||||
}
|
||||
sendEvent(ExportVaultEvent.ShowToast(message = toastMessage))
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss the view.
|
||||
*/
|
||||
@@ -267,8 +283,19 @@ class ExportVaultViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun handleSendCodeClick() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = ExportVaultState.DialogState.Loading(
|
||||
message = R.string.sending.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
viewModelScope.launch {
|
||||
authRepository.requestOneTimePasscode()
|
||||
sendAction(
|
||||
ExportVaultAction.Internal.OtpCodeResult(
|
||||
result = authRepository.requestOneTimePasscode(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,5 +599,10 @@ sealed class ExportVaultAction {
|
||||
data class ReceiveVerifyOneTimePasscodeResult(
|
||||
val result: VerifyOtpResult,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that a result for requesting the one-time passcode has been received.
|
||||
*/
|
||||
data class OtpCodeResult(val result: RequestOtpResult) : Internal()
|
||||
}
|
||||
}
|
||||
|
||||
+56
-10
@@ -38,6 +38,7 @@ import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class ExportVaultViewModelTest : BaseViewModelTest() {
|
||||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(DEFAULT_USER_STATE)
|
||||
private val authRepository: AuthRepository = mockk {
|
||||
@@ -479,18 +480,63 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `SendCodeClick should call requestOneTimePasscode`() {
|
||||
val viewModel = createViewModel()
|
||||
coEvery { authRepository.requestOneTimePasscode() } returns RequestOtpResult.Success
|
||||
viewModel.trySendAction(ExportVaultAction.SendCodeClick)
|
||||
fun `SendCodeClick should call requestOneTimePasscode and update dialog state to sending then back to null when request completes and send correct event on success`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel()
|
||||
coEvery { authRepository.requestOneTimePasscode() } returns RequestOtpResult.Success
|
||||
viewModel.stateEventFlow(backgroundScope) { stateTurbine, eventTurbine ->
|
||||
assertEquals(DEFAULT_STATE, stateTurbine.awaitItem())
|
||||
viewModel.trySendAction(ExportVaultAction.SendCodeClick)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = ExportVaultState.DialogState.Loading(
|
||||
message = R.string.sending.asText(),
|
||||
),
|
||||
),
|
||||
stateTurbine.awaitItem(),
|
||||
)
|
||||
assertEquals(DEFAULT_STATE, stateTurbine.awaitItem())
|
||||
assertEquals(
|
||||
ExportVaultEvent.ShowToast(
|
||||
message = R.string.code_sent.asText(),
|
||||
),
|
||||
eventTurbine.awaitItem(),
|
||||
)
|
||||
}
|
||||
coVerify { authRepository.requestOneTimePasscode() }
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
DEFAULT_STATE,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
coVerify { authRepository.requestOneTimePasscode() }
|
||||
}
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `SendCodeClick should call requestOneTimePasscode and update dialog state to sending then back to null when request completes and send correct event on error`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel()
|
||||
coEvery {
|
||||
authRepository.requestOneTimePasscode()
|
||||
} returns RequestOtpResult.Error(message = null)
|
||||
viewModel.stateEventFlow(backgroundScope) { stateTurbine, eventTurbine ->
|
||||
assertEquals(DEFAULT_STATE, stateTurbine.awaitItem())
|
||||
viewModel.trySendAction(ExportVaultAction.SendCodeClick)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = ExportVaultState.DialogState.Loading(
|
||||
message = R.string.sending.asText(),
|
||||
),
|
||||
),
|
||||
stateTurbine.awaitItem(),
|
||||
)
|
||||
assertEquals(DEFAULT_STATE, stateTurbine.awaitItem())
|
||||
assertEquals(
|
||||
ExportVaultEvent.ShowToast(
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
eventTurbine.awaitItem(),
|
||||
)
|
||||
}
|
||||
coVerify { authRepository.requestOneTimePasscode() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ReceiveExportVaultDataToStringResult should update state to error if result is error`() {
|
||||
|
||||
Reference in New Issue
Block a user