mirror of
https://github.com/bitwarden/android.git
synced 2026-03-14 06:04:20 -05:00
PM-23321: Replace two-factor screen toasts with snackbars (#5473)
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
package com.x8bit.bitwarden.ui.auth.feature.twofactorlogin
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@@ -26,7 +25,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
@@ -59,6 +57,8 @@ import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField
|
||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarHost
|
||||
import com.x8bit.bitwarden.ui.platform.components.snackbar.rememberBitwardenSnackbarHostState
|
||||
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.composition.LocalNfcManager
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
@@ -78,7 +78,6 @@ fun TwoFactorLoginScreen(
|
||||
nfcManager: NfcManager = LocalNfcManager.current,
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val context = LocalContext.current
|
||||
LifecycleEventEffect { _, event ->
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_RESUME -> {
|
||||
@@ -96,6 +95,7 @@ fun TwoFactorLoginScreen(
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
val snackbarHostState = rememberBitwardenSnackbarHostState()
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
TwoFactorLoginEvent.NavigateBack -> onNavigateBack()
|
||||
@@ -116,9 +116,7 @@ fun TwoFactorLoginScreen(
|
||||
intentManager.startCustomTabsActivity(uri = event.uri)
|
||||
}
|
||||
|
||||
is TwoFactorLoginEvent.ShowToast -> {
|
||||
Toast.makeText(context, event.message(context.resources), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
is TwoFactorLoginEvent.ShowSnackbar -> snackbarHostState.showSnackbar(event.data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,6 +169,9 @@ fun TwoFactorLoginScreen(
|
||||
},
|
||||
)
|
||||
},
|
||||
snackbarHost = {
|
||||
BitwardenSnackbarHost(bitwardenHostState = snackbarHostState)
|
||||
},
|
||||
) {
|
||||
TwoFactorLoginScreenContent(
|
||||
state = state,
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.util.imageRes
|
||||
import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.util.isContinueButtonEnabled
|
||||
import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.util.shouldUseNfc
|
||||
import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.util.showPasswordInput
|
||||
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@@ -249,7 +250,7 @@ class TwoFactorLoginViewModel @Inject constructor(
|
||||
)
|
||||
TwoFactorLoginEvent.NavigateToWebAuth(uri = uri)
|
||||
}
|
||||
?: TwoFactorLoginEvent.ShowToast(
|
||||
?: TwoFactorLoginEvent.ShowSnackbar(
|
||||
message = R.string.there_was_an_error_starting_web_authn_two_factor_authentication.asText(),
|
||||
),
|
||||
)
|
||||
@@ -453,7 +454,7 @@ class TwoFactorLoginViewModel @Inject constructor(
|
||||
ResendEmailResult.Success -> {
|
||||
if (action.isUserInitiated) {
|
||||
sendEvent(
|
||||
TwoFactorLoginEvent.ShowToast(
|
||||
TwoFactorLoginEvent.ShowSnackbar(
|
||||
message = R.string.verification_email_sent.asText(),
|
||||
),
|
||||
)
|
||||
@@ -710,11 +711,25 @@ sealed class TwoFactorLoginEvent {
|
||||
data class NavigateToRecoveryCode(val uri: Uri) : TwoFactorLoginEvent()
|
||||
|
||||
/**
|
||||
* Shows a toast with the given [message].
|
||||
* Shows a snackbar with the given [data].
|
||||
*/
|
||||
data class ShowToast(
|
||||
val message: Text,
|
||||
) : TwoFactorLoginEvent()
|
||||
data class ShowSnackbar(
|
||||
val data: BitwardenSnackbarData,
|
||||
) : TwoFactorLoginEvent() {
|
||||
constructor(
|
||||
message: Text,
|
||||
messageHeader: Text? = null,
|
||||
actionLabel: Text? = null,
|
||||
withDismissAction: Boolean = false,
|
||||
) : this(
|
||||
data = BitwardenSnackbarData(
|
||||
message = message,
|
||||
messageHeader = messageHeader,
|
||||
actionLabel = actionLabel,
|
||||
withDismissAction = withDismissAction,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.network.model.TwoFactorAuthMethod
|
||||
import com.bitwarden.ui.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.manager.nfc.NfcManager
|
||||
import io.mockk.every
|
||||
@@ -60,6 +61,15 @@ class TwoFactorLoginScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ShowSnackbar should display snackbar content`() {
|
||||
val message = "message"
|
||||
val data = BitwardenSnackbarData(message = message.asText())
|
||||
composeTestRule.onNodeWithText(text = message).assertDoesNotExist()
|
||||
mutableEventFlow.tryEmit(TwoFactorLoginEvent.ShowSnackbar(data = data))
|
||||
composeTestRule.onNodeWithText(text = message).assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `basicDialog should update according to state`() {
|
||||
composeTestRule.onNodeWithText("Error message").assertDoesNotExist()
|
||||
|
||||
@@ -513,7 +513,7 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `ContinueButtonClick login should emit ShowToast when auth method is WEB_AUTH and data is null`() =
|
||||
fun `ContinueButtonClick login should emit ShowSnackbar when auth method is WEB_AUTH and data is null`() =
|
||||
runTest {
|
||||
val response = GetTokenResponseJson.TwoFactorRequired(
|
||||
authMethodsData = emptyMap(),
|
||||
@@ -528,8 +528,10 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(TwoFactorLoginAction.ContinueButtonClick)
|
||||
assertEquals(
|
||||
TwoFactorLoginEvent.ShowToast(
|
||||
message = R.string.there_was_an_error_starting_web_authn_two_factor_authentication.asText(),
|
||||
TwoFactorLoginEvent.ShowSnackbar(
|
||||
message = R.string
|
||||
.there_was_an_error_starting_web_authn_two_factor_authentication
|
||||
.asText(),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
@@ -899,7 +901,7 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ResendEmailClick returns success should emit ShowToast`() = runTest {
|
||||
fun `ResendEmailClick returns success should emit ShowSnackbar`() = runTest {
|
||||
coEvery {
|
||||
authRepository.resendVerificationCodeEmail()
|
||||
} returns ResendEmailResult.Success
|
||||
@@ -924,7 +926,9 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
TwoFactorLoginEvent.ShowToast(message = R.string.verification_email_sent.asText()),
|
||||
TwoFactorLoginEvent.ShowSnackbar(
|
||||
message = R.string.verification_email_sent.asText(),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
@@ -1050,7 +1054,7 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `ReceiveResendEmailResult with ResendEmailResult Success and isUserInitiated true should ShowToast`() =
|
||||
fun `ReceiveResendEmailResult with ResendEmailResult Success and isUserInitiated true should ShowSnackbar`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
@@ -1061,7 +1065,7 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
TwoFactorLoginEvent.ShowToast(
|
||||
TwoFactorLoginEvent.ShowSnackbar(
|
||||
message = R.string.verification_email_sent.asText(),
|
||||
),
|
||||
awaitItem(),
|
||||
@@ -1192,8 +1196,7 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `ReceiveResendEmailResult with ResendEmailResult Success should ShowToast`() =
|
||||
fun `ReceiveResendEmailResult with ResendEmailResult Success should ShowSnackbar`() =
|
||||
runTest {
|
||||
val initialState = DEFAULT_STATE.copy(
|
||||
authMethod = TwoFactorAuthMethod.EMAIL,
|
||||
@@ -1208,7 +1211,7 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
TwoFactorLoginEvent.ShowToast(
|
||||
TwoFactorLoginEvent.ShowSnackbar(
|
||||
message = R.string.verification_email_sent.asText(),
|
||||
),
|
||||
awaitItem(),
|
||||
|
||||
Reference in New Issue
Block a user