mirror of
https://github.com/bitwarden/android.git
synced 2024-11-27 03:49:36 +03:00
PM-15036 Show loading spinner and toast as visual feedback for exporting vault
This commit is contained in:
parent
3092ba1fc6
commit
c34ad889d8
2 changed files with 80 additions and 8 deletions
|
@ -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.datasource.sdk.model.PasswordStrength
|
||||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
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.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.ValidatePasswordResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.VerifyOtpResult
|
import com.x8bit.bitwarden.data.auth.repository.model.VerifyOtpResult
|
||||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||||
|
@ -116,9 +117,22 @@ class ExportVaultViewModel @Inject constructor(
|
||||||
is ExportVaultAction.Internal.ReceiveVerifyOneTimePasscodeResult -> {
|
is ExportVaultAction.Internal.ReceiveVerifyOneTimePasscodeResult -> {
|
||||||
handleReceiveVerifyOneTimePasscodeResult(action)
|
handleReceiveVerifyOneTimePasscodeResult(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is ExportVaultAction.Internal.OtpCodeResult -> handleOtpCodeResult(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleOtpCodeResult(action: ExportVaultAction.Internal.OtpCodeResult) {
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(dialogState = null)
|
||||||
|
}
|
||||||
|
val toastMessage = when (action.result) {
|
||||||
|
is RequestOtpResult.Error -> R.string.generic_error_message.asText()
|
||||||
|
RequestOtpResult.Success -> R.string.code_sent.asText()
|
||||||
|
}
|
||||||
|
sendEvent(ExportVaultEvent.ShowToast(message = toastMessage))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dismiss the view.
|
* Dismiss the view.
|
||||||
*/
|
*/
|
||||||
|
@ -267,8 +281,19 @@ class ExportVaultViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSendCodeClick() {
|
private fun handleSendCodeClick() {
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
dialogState = ExportVaultState.DialogState.Loading(
|
||||||
|
R.string.sending.asText(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
authRepository.requestOneTimePasscode()
|
sendAction(
|
||||||
|
ExportVaultAction.Internal.OtpCodeResult(
|
||||||
|
result = authRepository.requestOneTimePasscode(),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -572,5 +597,10 @@ sealed class ExportVaultAction {
|
||||||
data class ReceiveVerifyOneTimePasscodeResult(
|
data class ReceiveVerifyOneTimePasscodeResult(
|
||||||
val result: VerifyOtpResult,
|
val result: VerifyOtpResult,
|
||||||
) : Internal()
|
) : Internal()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that a result for requesting the one-time passcode has been received.
|
||||||
|
*/
|
||||||
|
data class OtpCodeResult(val result: RequestOtpResult) : Internal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import java.time.Clock
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
|
|
||||||
|
@Suppress("LargeClass")
|
||||||
class ExportVaultViewModelTest : BaseViewModelTest() {
|
class ExportVaultViewModelTest : BaseViewModelTest() {
|
||||||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(DEFAULT_USER_STATE)
|
private val mutableUserStateFlow = MutableStateFlow<UserState?>(DEFAULT_USER_STATE)
|
||||||
private val authRepository: AuthRepository = mockk {
|
private val authRepository: AuthRepository = mockk {
|
||||||
|
@ -479,19 +480,60 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `SendCodeClick should call requestOneTimePasscode`() {
|
fun `SendCodeClick should call requestOneTimePasscode and update dialog state to sending then back to null when request completes`() =
|
||||||
|
runTest {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
coEvery { authRepository.requestOneTimePasscode() } returns RequestOtpResult.Success
|
coEvery { authRepository.requestOneTimePasscode() } returns RequestOtpResult.Success
|
||||||
viewModel.trySendAction(ExportVaultAction.SendCodeClick)
|
viewModel.stateFlow.test {
|
||||||
|
assertEquals(DEFAULT_STATE, awaitItem())
|
||||||
assertEquals(
|
viewModel.trySendAction(ExportVaultAction.SendCodeClick)
|
||||||
DEFAULT_STATE,
|
assertEquals(
|
||||||
viewModel.stateFlow.value,
|
DEFAULT_STATE.copy(
|
||||||
)
|
dialogState = ExportVaultState.DialogState.Loading(
|
||||||
|
message = R.string.sending.asText(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
awaitItem(),
|
||||||
|
)
|
||||||
|
assertEquals(DEFAULT_STATE, awaitItem())
|
||||||
|
}
|
||||||
coVerify { authRepository.requestOneTimePasscode() }
|
coVerify { authRepository.requestOneTimePasscode() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `On successful requestOneTimePasscode should send success toast`() = runTest {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
coEvery { authRepository.requestOneTimePasscode() } returns RequestOtpResult.Success
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
viewModel.trySendAction(ExportVaultAction.SendCodeClick)
|
||||||
|
assertEquals(
|
||||||
|
ExportVaultEvent.ShowToast(
|
||||||
|
message = R.string.code_sent.asText(),
|
||||||
|
),
|
||||||
|
awaitItem(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `On failed requestOneTimePasscode should send success toast`() = runTest {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
coEvery {
|
||||||
|
authRepository.requestOneTimePasscode()
|
||||||
|
} returns RequestOtpResult.Error(message = null)
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
viewModel.trySendAction(ExportVaultAction.SendCodeClick)
|
||||||
|
assertEquals(
|
||||||
|
ExportVaultEvent.ShowToast(
|
||||||
|
message = R.string.generic_error_message.asText(),
|
||||||
|
),
|
||||||
|
awaitItem(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ReceiveExportVaultDataToStringResult should update state to error if result is error`() {
|
fun `ReceiveExportVaultDataToStringResult should update state to error if result is error`() {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
|
|
Loading…
Reference in a new issue