diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginScreen.kt index 7fd36ef10a..695a80c5b4 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginScreen.kt @@ -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, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModel.kt index 8108fa7045..8dd82dd9d9 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModel.kt @@ -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, + ), + ) + } } /** diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginScreenTest.kt index 4607c2e5d5..1372b3c7e0 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginScreenTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginScreenTest.kt @@ -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() diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModelTest.kt index e0e52d3f58..f35d0e0b75 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModelTest.kt @@ -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(),