diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginScreen.kt index 47f2092ce..12419c140 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginScreen.kt @@ -43,9 +43,9 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledTonalButton import com.x8bit.bitwarden.ui.platform.components.BitwardenLoadingDialog import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold -import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitch import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar +import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager @@ -83,29 +83,12 @@ fun TwoFactorLoginScreen( } } - when (val dialog = state.dialogState) { - is TwoFactorLoginState.DialogState.Error -> { - BitwardenBasicDialog( - visibilityState = BasicDialogState.Shown( - title = dialog.title ?: R.string.an_error_has_occurred.asText(), - message = dialog.message, - ), - onDismissRequest = remember(viewModel) { - { viewModel.trySendAction(TwoFactorLoginAction.DialogDismiss) } - }, - ) - } - - is TwoFactorLoginState.DialogState.Loading -> { - BitwardenLoadingDialog( - visibilityState = LoadingDialogState.Shown( - text = dialog.message, - ), - ) - } - - null -> Unit - } + TwoFactorLoginDialogs( + dialogState = state.dialogState, + onDismissRequest = remember(viewModel) { + { viewModel.trySendAction(TwoFactorLoginAction.DialogDismiss) } + }, + ) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) BitwardenScaffold( @@ -164,7 +147,28 @@ fun TwoFactorLoginScreen( } @Composable -@Suppress("LongMethod") +private fun TwoFactorLoginDialogs( + dialogState: TwoFactorLoginState.DialogState?, + onDismissRequest: () -> Unit, +) { + when (dialogState) { + is TwoFactorLoginState.DialogState.Error -> BitwardenBasicDialog( + visibilityState = BasicDialogState.Shown( + title = dialogState.title ?: R.string.an_error_has_occurred.asText(), + message = dialogState.message, + ), + onDismissRequest = onDismissRequest, + ) + + is TwoFactorLoginState.DialogState.Loading -> BitwardenLoadingDialog( + visibilityState = LoadingDialogState.Shown(text = dialogState.message), + ) + + null -> Unit + } +} + +@Composable private fun TwoFactorLoginScreenContent( state: TwoFactorLoginState, onCodeInputChange: (String) -> Unit, @@ -203,7 +207,7 @@ private fun TwoFactorLoginScreenContent( Spacer(modifier = Modifier.height(12.dp)) - BitwardenSwitch( + BitwardenWideSwitch( label = stringResource(id = R.string.remember_me), isChecked = state.isRememberMeEnabled, onCheckedChange = onRememberMeToggle, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModel.kt index 6c9923e2a..933506edd 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModel.kt @@ -20,6 +20,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.Text import com.x8bit.bitwarden.ui.platform.base.util.asText import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -60,13 +61,8 @@ class TwoFactorLoginViewModel @Inject constructor( // Automatically attempt to login again if a captcha token is received. authRepository .captchaTokenResultFlow - .onEach { - sendAction( - TwoFactorLoginAction.Internal.ReceiveCaptchaToken( - tokenResult = it, - ), - ) - } + .map { TwoFactorLoginAction.Internal.ReceiveCaptchaToken(tokenResult = it) } + .onEach(::sendAction) .launchIn(viewModelScope) } @@ -79,20 +75,27 @@ class TwoFactorLoginViewModel @Inject constructor( is TwoFactorLoginAction.RememberMeToggle -> handleRememberMeToggle(action) TwoFactorLoginAction.ResendEmailClick -> handleResendEmailClick() is TwoFactorLoginAction.SelectAuthMethod -> handleSelectAuthMethod(action) + is TwoFactorLoginAction.Internal -> handleInternalAction(action) + } + } + private fun handleInternalAction(action: TwoFactorLoginAction.Internal) { + when (action) { + is TwoFactorLoginAction.Internal.ReceiveLoginResult -> handleReceiveLoginResult(action) is TwoFactorLoginAction.Internal.ReceiveCaptchaToken -> { - handleCaptchaTokenReceived(action.tokenResult) + handleCaptchaTokenReceived(action) } - is TwoFactorLoginAction.Internal.ReceiveLoginResult -> handleReceiveLoginResult(action) is TwoFactorLoginAction.Internal.ReceiveResendEmailResult -> { handleReceiveResendEmailResult(action) } } } - private fun handleCaptchaTokenReceived(tokenResult: CaptchaCallbackTokenResult) { - when (tokenResult) { + private fun handleCaptchaTokenReceived( + action: TwoFactorLoginAction.Internal.ReceiveCaptchaToken, + ) { + when (val tokenResult = action.tokenResult) { CaptchaCallbackTokenResult.MissingToken -> { mutableStateFlow.update { it.copy( @@ -138,26 +141,31 @@ class TwoFactorLoginViewModel @Inject constructor( } // If the user is manually entering a code, remove any white spaces, just in case. - val code = mutableStateFlow.value.codeInput.let { rawCode -> - if (mutableStateFlow.value.authMethod == TwoFactorAuthMethod.AUTHENTICATOR_APP || - mutableStateFlow.value.authMethod == TwoFactorAuthMethod.EMAIL - ) { - rawCode.replace(" ", "") - } else { - rawCode - } + val code = when (state.authMethod) { + TwoFactorAuthMethod.AUTHENTICATOR_APP, + TwoFactorAuthMethod.EMAIL, + -> state.codeInput.replace(" ", "") + + TwoFactorAuthMethod.YUBI_KEY, + TwoFactorAuthMethod.DUO, + TwoFactorAuthMethod.U2F, + TwoFactorAuthMethod.REMEMBER, + TwoFactorAuthMethod.DUO_ORGANIZATION, + TwoFactorAuthMethod.FIDO_2_WEB_APP, + TwoFactorAuthMethod.RECOVERY_CODE, + -> state.codeInput } viewModelScope.launch { val result = authRepository.login( - email = mutableStateFlow.value.email, - password = mutableStateFlow.value.password, + email = state.email, + password = state.password, twoFactorData = TwoFactorDataModel( code = code, - method = mutableStateFlow.value.authMethod.value.toString(), - remember = mutableStateFlow.value.isRememberMeEnabled, + method = state.authMethod.value.toString(), + remember = state.isRememberMeEnabled, ), - captchaToken = mutableStateFlow.value.captchaToken, + captchaToken = state.captchaToken, ) sendAction( TwoFactorLoginAction.Internal.ReceiveLoginResult( @@ -267,7 +275,7 @@ class TwoFactorLoginViewModel @Inject constructor( */ private fun handleResendEmailClick() { // Ensure that the user is in fact verifying with email. - if (mutableStateFlow.value.authMethod != TwoFactorAuthMethod.EMAIL) { + if (state.authMethod != TwoFactorAuthMethod.EMAIL) { return } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/util/TwoFactorAuthMethodExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/util/TwoFactorAuthMethodExtensions.kt index fc3731926..76b7780b8 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/util/TwoFactorAuthMethodExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/util/TwoFactorAuthMethodExtensions.kt @@ -13,6 +13,7 @@ val TwoFactorAuthMethod.title: Text TwoFactorAuthMethod.AUTHENTICATOR_APP -> R.string.authenticator_app_title.asText() TwoFactorAuthMethod.EMAIL -> R.string.email.asText() TwoFactorAuthMethod.RECOVERY_CODE -> R.string.recovery_code_title.asText() + TwoFactorAuthMethod.YUBI_KEY -> R.string.yubi_key_title.asText() else -> "".asText() } @@ -22,5 +23,6 @@ val TwoFactorAuthMethod.title: Text fun TwoFactorAuthMethod.description(email: String): Text = when (this) { TwoFactorAuthMethod.AUTHENTICATOR_APP -> R.string.enter_verification_code_app.asText() TwoFactorAuthMethod.EMAIL -> R.string.enter_verification_code_email.asText(email) + TwoFactorAuthMethod.YUBI_KEY -> R.string.yubi_key_instruction.asText() else -> "".asText() } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/util/TwoFactorAuthMethodExtensionTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/util/TwoFactorAuthMethodExtensionTest.kt index c5a0c68a9..bb9fbae74 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/util/TwoFactorAuthMethodExtensionTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/util/TwoFactorAuthMethodExtensionTest.kt @@ -13,7 +13,7 @@ class TwoFactorAuthMethodExtensionTest { TwoFactorAuthMethod.AUTHENTICATOR_APP to R.string.authenticator_app_title.asText(), TwoFactorAuthMethod.EMAIL to R.string.email.asText(), TwoFactorAuthMethod.DUO to "".asText(), - TwoFactorAuthMethod.YUBI_KEY to "".asText(), + TwoFactorAuthMethod.YUBI_KEY to R.string.yubi_key_title.asText(), TwoFactorAuthMethod.U2F to "".asText(), TwoFactorAuthMethod.REMEMBER to "".asText(), TwoFactorAuthMethod.DUO_ORGANIZATION to "".asText(), @@ -36,7 +36,7 @@ class TwoFactorAuthMethodExtensionTest { TwoFactorAuthMethod.EMAIL to R.string.enter_verification_code_email.asText("ex***@email.com"), TwoFactorAuthMethod.DUO to "".asText(), - TwoFactorAuthMethod.YUBI_KEY to "".asText(), + TwoFactorAuthMethod.YUBI_KEY to R.string.yubi_key_instruction.asText(), TwoFactorAuthMethod.U2F to "".asText(), TwoFactorAuthMethod.REMEMBER to "".asText(), TwoFactorAuthMethod.DUO_ORGANIZATION to "".asText(),