diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutoFillNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutoFillNavigation.kt index 1ff43e8f8..7f13ad4e1 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutoFillNavigation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutoFillNavigation.kt @@ -1,29 +1,79 @@ package com.x8bit.bitwarden.ui.auth.feature.accountsetup +import androidx.lifecycle.SavedStateHandle import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions +import androidx.navigation.NavType +import androidx.navigation.navArgument +import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions +import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions /** - * Route name for [SetupAutoFillScreen]. + * Route constant for navigating to the [SetupAutoFillScreen]. */ -const val SETUP_AUTO_FILL_ROUTE = "setup_auto_fill" +private const val SETUP_AUTO_FILL_PREFIX = "setup_auto_fill" +private const val SETUP_AUTO_FILL_AS_ROOT_PREFIX = "${SETUP_AUTO_FILL_PREFIX}_as_root" +private const val SETUP_AUTO_FILL_NAV_ARG = "isInitialSetup" +private const val SETUP_AUTO_FILL_ROUTE = "$SETUP_AUTO_FILL_PREFIX/{$SETUP_AUTO_FILL_NAV_ARG}" +const val SETUP_AUTO_FILL_AS_ROOT_ROUTE = + "$SETUP_AUTO_FILL_AS_ROOT_PREFIX/{$SETUP_AUTO_FILL_NAV_ARG}" + +/** + * Arguments for the [SetupAutoFillScreen] using [SavedStateHandle]. + */ +@OmitFromCoverage +data class SetupAutoFillScreenArgs(val isInitialSetup: Boolean) { + constructor(savedStateHandle: SavedStateHandle) : this( + isInitialSetup = requireNotNull(savedStateHandle[SETUP_AUTO_FILL_NAV_ARG]), + ) +} /** * Navigate to the setup auto-fill screen. */ fun NavController.navigateToSetupAutoFillScreen(navOptions: NavOptions? = null) { - this.navigate(SETUP_AUTO_FILL_ROUTE, navOptions) + this.navigate("$SETUP_AUTO_FILL_PREFIX/false", navOptions) +} + +/** + * Navigate to the setup auto-fill screen as the root. + */ +fun NavController.navigateToSetupAutoFillAsRootScreen(navOptions: NavOptions? = null) { + this.navigate("$SETUP_AUTO_FILL_AS_ROOT_PREFIX/true", navOptions) } /** * Add the setup auto-fil screen to the nav graph. */ -fun NavGraphBuilder.setupAutoFillDestination() { - composableWithPushTransitions( +fun NavGraphBuilder.setupAutoFillDestination(onNavigateBack: () -> Unit) { + composableWithSlideTransitions( route = SETUP_AUTO_FILL_ROUTE, + arguments = setupAutofillNavArgs, ) { - SetupAutoFillScreen() + SetupAutoFillScreen(onNavigateBack = onNavigateBack) } } + +/** + * Add the setup autofil screen to the root nav graph. + */ +fun NavGraphBuilder.setupAutoFillDestinationAsRoot() { + composableWithPushTransitions( + route = SETUP_AUTO_FILL_AS_ROOT_ROUTE, + arguments = setupAutofillNavArgs, + ) { + SetupAutoFillScreen( + onNavigateBack = { + // No-Op + }, + ) + } +} + +private val setupAutofillNavArgs = listOf( + navArgument(SETUP_AUTO_FILL_NAV_ARG) { + type = NavType.BoolType + }, +) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutoFillViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutoFillViewModel.kt index 78ed7e8ea..73586bd12 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutoFillViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutoFillViewModel.kt @@ -1,5 +1,7 @@ package com.x8bit.bitwarden.ui.auth.feature.accountsetup +import android.os.Parcelable +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus import com.x8bit.bitwarden.data.auth.repository.AuthRepository @@ -10,20 +12,31 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update +import kotlinx.parcelize.Parcelize import javax.inject.Inject +private const val KEY_STATE = "state" + /** * View model for the Auto-fill setup screen. */ @HiltViewModel class SetupAutoFillViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, private val settingsRepository: SettingsRepository, private val authRepository: AuthRepository, ) : BaseViewModel( - initialState = run { + // We load the state from the savedStateHandle for testing purposes. + initialState = savedStateHandle[KEY_STATE] ?: run { val userId = requireNotNull(authRepository.userStateFlow.value).activeUserId - SetupAutoFillState(userId = userId, dialogState = null, autofillEnabled = false) + val isInitialSetup = SetupAutoFillScreenArgs(savedStateHandle).isInitialSetup + SetupAutoFillState( + userId = userId, + dialogState = null, + autofillEnabled = false, + isInitialSetup = isInitialSetup, + ) }, ) { @@ -48,9 +61,15 @@ class SetupAutoFillViewModel @Inject constructor( is SetupAutoFillAction.Internal.AutofillEnabledUpdateReceive -> { handleAutofillEnabledUpdateReceive(action) } + + SetupAutoFillAction.CloseClick -> handleCloseClick() } } + private fun handleCloseClick() { + sendEvent(SetupAutoFillEvent.NavigateBack) + } + private fun handleAutofillEnabledUpdateReceive( action: SetupAutoFillAction.Internal.AutofillEnabledUpdateReceive, ) { @@ -83,7 +102,11 @@ class SetupAutoFillViewModel @Inject constructor( } private fun handleContinueClick() { - updateOnboardingStatusToNextStep() + if (state.isInitialSetup) { + updateOnboardingStatusToNextStep() + } else { + sendEvent(SetupAutoFillEvent.NavigateBack) + } } private fun handleAutofillServiceChanged(action: SetupAutoFillAction.AutofillServiceChanged) { @@ -105,24 +128,28 @@ class SetupAutoFillViewModel @Inject constructor( /** * UI State for the Auto-fill setup screen. */ +@Parcelize data class SetupAutoFillState( val userId: String, val dialogState: SetupAutoFillDialogState?, val autofillEnabled: Boolean, -) + val isInitialSetup: Boolean, +) : Parcelable /** * Dialog states for the Auto-fill setup screen. */ -sealed class SetupAutoFillDialogState { +sealed class SetupAutoFillDialogState : Parcelable { /** * Represents the turn on later dialog. */ + @Parcelize data object TurnOnLaterDialog : SetupAutoFillDialogState() /** * Represents the autofill fallback dialog. */ + @Parcelize data object AutoFillFallbackDialog : SetupAutoFillDialogState() } @@ -135,6 +162,11 @@ sealed class SetupAutoFillEvent { * Navigate to the autofill settings screen. */ data object NavigateToAutofillSettings : SetupAutoFillEvent() + + /** + * Navigate back. + */ + data object NavigateBack : SetupAutoFillEvent() } /** @@ -173,6 +205,11 @@ sealed class SetupAutoFillAction { */ data object AutoFillServiceFallback : SetupAutoFillAction() + /** + * The user has clicked the close button. + */ + data object CloseClick : SetupAutoFillAction() + /** * Internal actions not send through UI. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutofillScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutofillScreen.kt index 6aebdb1b3..b3125bf65 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutofillScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutofillScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -37,6 +38,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar +import com.x8bit.bitwarden.ui.platform.components.appbar.NavigationIcon import com.x8bit.bitwarden.ui.platform.components.button.BitwardenFilledButton import com.x8bit.bitwarden.ui.platform.components.button.BitwardenTextButton import com.x8bit.bitwarden.ui.platform.components.dialog.BasicDialogState @@ -45,6 +47,7 @@ import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialo import com.x8bit.bitwarden.ui.platform.components.image.BitwardenGifImage import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenWideSwitch +import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme @@ -57,6 +60,7 @@ import com.x8bit.bitwarden.ui.platform.util.isPortrait @OptIn(ExperimentalMaterial3Api::class) @Composable fun SetupAutoFillScreen( + onNavigateBack: () -> Unit, intentManager: IntentManager = LocalIntentManager.current, viewModel: SetupAutoFillViewModel = hiltViewModel(), ) { @@ -70,6 +74,8 @@ fun SetupAutoFillScreen( handler.sendAutoFillServiceFallback.invoke() } } + + SetupAutoFillEvent.NavigateBack -> onNavigateBack() } } when (state.dialogState) { @@ -105,14 +111,32 @@ fun SetupAutoFillScreen( .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { BitwardenTopAppBar( - title = stringResource(id = R.string.account_setup), + title = stringResource( + id = if (state.isInitialSetup) { + R.string.account_setup + } else { + R.string.turn_on_autofill + }, + ), scrollBehavior = scrollBehavior, - navigationIcon = null, + navigationIcon = if (state.isInitialSetup) { + null + } else { + NavigationIcon( + navigationIcon = rememberVectorPainter(id = R.drawable.ic_close), + navigationIconContentDescription = stringResource(id = R.string.close), + onNavigationIconClick = remember(viewModel) { + { + viewModel.trySendAction(SetupAutoFillAction.CloseClick) + } + }, + ) + }, ) }, ) { innerPadding -> SetupAutoFillContent( - autofillEnabled = state.autofillEnabled, + state = state, onAutofillServiceChanged = { handler.onAutofillServiceChanged(it) }, onContinueClick = handler.onContinueClick, onTurnOnLaterClick = handler.onTurnOnLaterClick, @@ -127,7 +151,7 @@ fun SetupAutoFillScreen( @Suppress("LongMethod") @Composable private fun SetupAutoFillContent( - autofillEnabled: Boolean, + state: SetupAutoFillState, onAutofillServiceChanged: (Boolean) -> Unit, onContinueClick: () -> Unit, onTurnOnLaterClick: () -> Unit, @@ -147,7 +171,7 @@ private fun SetupAutoFillContent( label = stringResource( R.string.autofill_services, ), - isChecked = autofillEnabled, + isChecked = state.autofillEnabled, onCheckedChange = onAutofillServiceChanged, modifier = Modifier .fillMaxWidth() @@ -162,13 +186,15 @@ private fun SetupAutoFillContent( .standardHorizontalMargin(), ) Spacer(modifier = Modifier.height(12.dp)) - BitwardenTextButton( - label = stringResource(R.string.turn_on_later), - onClick = onTurnOnLaterClick, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + if (state.isInitialSetup) { + BitwardenTextButton( + label = stringResource(R.string.turn_on_later), + onClick = onTurnOnLaterClick, + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } Spacer(modifier = Modifier.navigationBarsPadding()) } } @@ -240,7 +266,12 @@ private fun OrderedHeaderContent() { private fun SetupAutoFillContentDisabled_preview() { BitwardenTheme { SetupAutoFillContent( - autofillEnabled = false, + state = SetupAutoFillState( + userId = "disputationi", + dialogState = null, + autofillEnabled = false, + isInitialSetup = true, + ), onAutofillServiceChanged = {}, onContinueClick = {}, onTurnOnLaterClick = {}, @@ -253,7 +284,12 @@ private fun SetupAutoFillContentDisabled_preview() { private fun SetupAutoFillContentEnabled_preview() { BitwardenTheme { SetupAutoFillContent( - autofillEnabled = true, + state = SetupAutoFillState( + userId = "disputationi", + dialogState = null, + autofillEnabled = true, + isInitialSetup = true, + ), onAutofillServiceChanged = {}, onContinueClick = {}, onTurnOnLaterClick = {}, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupUnlockViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupUnlockViewModel.kt index 0137398dd..b99639e2d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupUnlockViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupUnlockViewModel.kt @@ -33,6 +33,7 @@ class SetupUnlockViewModel @Inject constructor( private val settingsRepository: SettingsRepository, private val biometricsEncryptionManager: BiometricsEncryptionManager, ) : BaseViewModel( + // We load the state from the savedStateHandle for testing purposes. initialState = savedStateHandle[KEY_STATE] ?: run { val userId = requireNotNull(authRepository.userStateFlow.value).activeUserId val isBiometricsValid = biometricsEncryptionManager.isBiometricIntegrityValid( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreen.kt index fc063ca99..3affef5ae 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreen.kt @@ -15,13 +15,13 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions -import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SETUP_AUTO_FILL_ROUTE +import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SETUP_AUTO_FILL_AS_ROOT_ROUTE import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SETUP_COMPLETE_ROUTE import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SETUP_UNLOCK_AS_ROOT_ROUTE -import com.x8bit.bitwarden.ui.auth.feature.accountsetup.navigateToSetupAutoFillScreen +import com.x8bit.bitwarden.ui.auth.feature.accountsetup.navigateToSetupAutoFillAsRootScreen import com.x8bit.bitwarden.ui.auth.feature.accountsetup.navigateToSetupCompleteScreen import com.x8bit.bitwarden.ui.auth.feature.accountsetup.navigateToSetupUnlockScreenAsRoot -import com.x8bit.bitwarden.ui.auth.feature.accountsetup.setupAutoFillDestination +import com.x8bit.bitwarden.ui.auth.feature.accountsetup.setupAutoFillDestinationAsRoot import com.x8bit.bitwarden.ui.auth.feature.accountsetup.setupCompleteDestination import com.x8bit.bitwarden.ui.auth.feature.accountsetup.setupUnlockDestinationAsRoot import com.x8bit.bitwarden.ui.auth.feature.auth.AUTH_GRAPH_ROUTE @@ -100,7 +100,7 @@ fun RootNavScreen( vaultUnlockedGraph(navController) setupDebugMenuDestination(onNavigateBack = { navController.popBackStack() }) setupUnlockDestinationAsRoot() - setupAutoFillDestination() + setupAutoFillDestinationAsRoot() setupCompleteDestination() } @@ -129,7 +129,7 @@ fun RootNavScreen( -> VAULT_UNLOCKED_GRAPH_ROUTE RootNavState.OnboardingAccountLockSetup -> SETUP_UNLOCK_AS_ROOT_ROUTE - RootNavState.OnboardingAutoFillSetup -> SETUP_AUTO_FILL_ROUTE + RootNavState.OnboardingAutoFillSetup -> SETUP_AUTO_FILL_AS_ROOT_ROUTE RootNavState.OnboardingStepsComplete -> SETUP_COMPLETE_ROUTE } val currentRoute = navController.currentDestination?.rootLevelRoute() @@ -248,7 +248,7 @@ fun RootNavScreen( } RootNavState.OnboardingAutoFillSetup -> { - navController.navigateToSetupAutoFillScreen(rootNavOptions) + navController.navigateToSetupAutoFillAsRootScreen(rootNavOptions) } RootNavState.OnboardingStepsComplete -> { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/SettingsNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/SettingsNavigation.kt index f42045a54..e433e4edf 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/SettingsNavigation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/SettingsNavigation.kt @@ -4,7 +4,6 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.navigation -import com.x8bit.bitwarden.ui.auth.feature.accountsetup.navigateToSetupUnlockScreen import com.x8bit.bitwarden.ui.platform.base.util.composableWithRootPushTransitions import com.x8bit.bitwarden.ui.platform.feature.settings.about.aboutDestination import com.x8bit.bitwarden.ui.platform.feature.settings.about.navigateToAbout @@ -34,6 +33,8 @@ fun NavGraphBuilder.settingsGraph( onNavigateToExportVault: () -> Unit, onNavigateToFolders: () -> Unit, onNavigateToPendingRequests: () -> Unit, + onNavigateToSetupUnlockScreen: () -> Unit, + onNavigateToSetupAutoFillScreen: () -> Unit, ) { navigation( startDestination = SETTINGS_ROUTE, @@ -56,12 +57,13 @@ fun NavGraphBuilder.settingsGraph( onNavigateBack = { navController.popBackStack() }, onNavigateToDeleteAccount = onNavigateToDeleteAccount, onNavigateToPendingRequests = onNavigateToPendingRequests, - onNavigateToSetupUnlockScreen = { navController.navigateToSetupUnlockScreen() }, + onNavigateToSetupUnlockScreen = onNavigateToSetupUnlockScreen, ) appearanceDestination(onNavigateBack = { navController.popBackStack() }) autoFillDestination( onNavigateBack = { navController.popBackStack() }, onNavigateToBlockAutoFillScreen = { navController.navigateToBlockAutoFillScreen() }, + onNavigateToSetupAutofill = onNavigateToSetupAutoFillScreen, ) otherDestination(onNavigateBack = { navController.popBackStack() }) vaultSettingsDestination( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillNavigation.kt index d35582a1d..fc3bf7aa5 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillNavigation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillNavigation.kt @@ -13,6 +13,7 @@ private const val AUTO_FILL_ROUTE = "settings_auto_fill" fun NavGraphBuilder.autoFillDestination( onNavigateBack: () -> Unit, onNavigateToBlockAutoFillScreen: () -> Unit, + onNavigateToSetupAutofill: () -> Unit, ) { composableWithPushTransitions( route = AUTO_FILL_ROUTE, @@ -20,6 +21,7 @@ fun NavGraphBuilder.autoFillDestination( AutoFillScreen( onNavigateBack = onNavigateBack, onNavigateToBlockAutoFillScreen = onNavigateToBlockAutoFillScreen, + onNavigateToSetupAutofill = onNavigateToSetupAutofill, ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillScreen.kt index a8b593b38..f325e11ba 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillScreen.kt @@ -64,6 +64,7 @@ fun AutoFillScreen( viewModel: AutoFillViewModel = hiltViewModel(), intentManager: IntentManager = LocalIntentManager.current, onNavigateToBlockAutoFillScreen: () -> Unit, + onNavigateToSetupAutofill: () -> Unit, ) { val state by viewModel.stateFlow.collectAsStateWithLifecycle() val context = LocalContext.current @@ -94,6 +95,8 @@ fun AutoFillScreen( AutoFillEvent.NavigateToSettings -> { intentManager.startCredentialManagerSettings(context) } + + AutoFillEvent.NavigateToSetupAutofill -> onNavigateToSetupAutofill() } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillViewModel.kt index 91a58b52c..ec0b091d5 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillViewModel.kt @@ -118,7 +118,7 @@ class AutoFillViewModel @Inject constructor( private fun handleAutoFillActionCardCtClick() { dismissShowAutofillActionCard() - // TODO PM-13068 navigate to auto fill setup screen + sendEvent(AutoFillEvent.NavigateToSetupAutofill) } private fun handleUpdateShowAutofillActionCard( @@ -261,6 +261,11 @@ sealed class AutoFillEvent { data class ShowToast( val text: Text, ) : AutoFillEvent() + + /** + * Navigates to the setup autofill screen. + */ + data object NavigateToSetupAutofill : AutoFillEvent() } /** diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlocked/VaultUnlockedNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlocked/VaultUnlockedNavigation.kt index b71da4aff..ca3e2c113 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlocked/VaultUnlockedNavigation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlocked/VaultUnlockedNavigation.kt @@ -4,6 +4,10 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.navigation +import com.x8bit.bitwarden.ui.auth.feature.accountsetup.navigateToSetupAutoFillScreen +import com.x8bit.bitwarden.ui.auth.feature.accountsetup.navigateToSetupUnlockScreen +import com.x8bit.bitwarden.ui.auth.feature.accountsetup.setupAutoFillDestination +import com.x8bit.bitwarden.ui.auth.feature.accountsetup.setupUnlockDestination import com.x8bit.bitwarden.ui.platform.feature.search.navigateToSearch import com.x8bit.bitwarden.ui.platform.feature.search.searchDestination import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.deleteaccount.deleteAccountDestination @@ -98,6 +102,8 @@ fun NavGraphBuilder.vaultUnlockedGraph( passwordHistoryMode = GeneratorPasswordHistoryMode.Default, ) }, + onNavigateToSetupUnlockScreen = { navController.navigateToSetupUnlockScreen() }, + onNavigateToSetupAutoFillScreen = { navController.navigateToSetupAutoFillScreen() }, ) deleteAccountDestination( onNavigateBack = { navController.popBackStack() }, @@ -200,5 +206,15 @@ fun NavGraphBuilder.vaultUnlockedGraph( attachmentDestination( onNavigateBack = { navController.popBackStack() }, ) + setupUnlockDestination( + onNavigateBack = { + navController.popBackStack() + }, + ) + setupAutoFillDestination( + onNavigateBack = { + navController.popBackStack() + }, + ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarNavigation.kt index 48bd04f2f..1b146bf97 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarNavigation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarNavigation.kt @@ -36,6 +36,8 @@ fun NavGraphBuilder.vaultUnlockedNavBarDestination( onNavigateToFolders: () -> Unit, onNavigateToPendingRequests: () -> Unit, onNavigateToPasswordHistory: () -> Unit, + onNavigateToSetupUnlockScreen: () -> Unit, + onNavigateToSetupAutoFillScreen: () -> Unit, ) { composableWithStayTransitions( route = VAULT_UNLOCKED_NAV_BAR_ROUTE, @@ -53,6 +55,8 @@ fun NavGraphBuilder.vaultUnlockedNavBarDestination( onNavigateToFolders = onNavigateToFolders, onNavigateToPendingRequests = onNavigateToPendingRequests, onNavigateToPasswordHistory = onNavigateToPasswordHistory, + onNavigateToSetupUnlockScreen = onNavigateToSetupUnlockScreen, + onNavigateToSetupAutoFillScreen = onNavigateToSetupAutoFillScreen, ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt index fe2ffda93..7acad49de 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt @@ -34,7 +34,6 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions -import com.x8bit.bitwarden.ui.auth.feature.accountsetup.setupUnlockDestination import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect import com.x8bit.bitwarden.ui.platform.base.util.max import com.x8bit.bitwarden.ui.platform.base.util.toDp @@ -76,6 +75,8 @@ fun VaultUnlockedNavBarScreen( onNavigateToFolders: () -> Unit, onNavigateToPendingRequests: () -> Unit, onNavigateToPasswordHistory: () -> Unit, + onNavigateToSetupUnlockScreen: () -> Unit, + onNavigateToSetupAutoFillScreen: () -> Unit, ) { val state by viewModel.stateFlow.collectAsStateWithLifecycle() @@ -133,6 +134,8 @@ fun VaultUnlockedNavBarScreen( settingsTabClickedAction = remember(viewModel) { { viewModel.trySendAction(VaultUnlockedNavBarAction.SettingsTabClick) } }, + onNavigateToSetupUnlockScreen = onNavigateToSetupUnlockScreen, + onNavigateToSetupAutoFillScreen = onNavigateToSetupAutoFillScreen, ) } @@ -160,6 +163,8 @@ private fun VaultUnlockedNavBarScaffold( navigateToFolders: () -> Unit, navigateToPendingRequests: () -> Unit, navigateToPasswordHistory: () -> Unit, + onNavigateToSetupUnlockScreen: () -> Unit, + onNavigateToSetupAutoFillScreen: () -> Unit, ) { var shouldDimNavBar by remember { mutableStateOf(false) } @@ -235,11 +240,8 @@ private fun VaultUnlockedNavBarScaffold( onNavigateToExportVault = navigateToExportVault, onNavigateToFolders = navigateToFolders, onNavigateToPendingRequests = navigateToPendingRequests, - ) - setupUnlockDestination( - onNavigateBack = { - navController.popBackStack() - }, + onNavigateToSetupUnlockScreen = onNavigateToSetupUnlockScreen, + onNavigateToSetupAutoFillScreen = onNavigateToSetupAutoFillScreen, ) } } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutoFillViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutoFillViewModelTest.kt index c3f6ae777..885689bc6 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutoFillViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutoFillViewModelTest.kt @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.ui.auth.feature.accountsetup +import androidx.lifecycle.SavedStateHandle import app.cash.turbine.test import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus import com.x8bit.bitwarden.data.auth.repository.AuthRepository @@ -128,6 +129,25 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() { ) } } + + @Test + fun `handleContinueClick send NavigateBack event when not initial setup`() = runTest { + val viewModel = createViewModel(initialState = DEFAULT_STATE.copy(isInitialSetup = false)) + viewModel.eventFlow.test { + viewModel.trySendAction(SetupAutoFillAction.ContinueClick) + assertEquals( + SetupAutoFillEvent.NavigateBack, + awaitItem(), + ) + } + verify(exactly = 0) { + authRepository.setOnboardingStatus( + DEFAULT_USER_ID, + OnboardingStatus.FINAL_STEP, + ) + } + } + @Test fun `handleTurnOnLaterConfirmClick sets showAutoFillSettingBadge to true`() { val viewModel = createViewModel() @@ -140,10 +160,34 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() { } } - private fun createViewModel() = SetupAutoFillViewModel( + @Test + fun `handleClose click sends NavigateBack event`() = runTest { + val viewModel = createViewModel() + viewModel.eventFlow.test { + viewModel.trySendAction(SetupAutoFillAction.CloseClick) + assertEquals(SetupAutoFillEvent.NavigateBack, awaitItem()) + } + } + + private fun createViewModel( + initialState: SetupAutoFillState? = null, + ) = SetupAutoFillViewModel( + savedStateHandle = SavedStateHandle( + mapOf( + "state" to initialState, + "isInitialSetup" to true, + ), + ), settingsRepository = settingsRepository, authRepository = authRepository, ) } private const val DEFAULT_USER_ID = "userId" + +private val DEFAULT_STATE = SetupAutoFillState( + userId = DEFAULT_USER_ID, + dialogState = null, + autofillEnabled = false, + isInitialSetup = true, +) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutofillScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutofillScreenTest.kt index 4a383e1ab..805e984a7 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutofillScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupAutofillScreenTest.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.test.filterToOne import androidx.compose.ui.test.hasAnyAncestor import androidx.compose.ui.test.isDialog import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollTo @@ -15,12 +16,14 @@ import com.x8bit.bitwarden.ui.util.assertNoDialogExists import io.mockk.every import io.mockk.mockk import io.mockk.verify +import junit.framework.TestCase.assertTrue import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import org.junit.Before import org.junit.Test class SetupAutofillScreenTest : BaseComposeTest() { + private var onNavigateBackCalled = false private val mutableEventFlow = bufferedMutableSharedFlow() private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE) @@ -38,6 +41,7 @@ class SetupAutofillScreenTest : BaseComposeTest() { SetupAutoFillScreen( intentManager = intentManager, viewModel = viewModel, + onNavigateBack = { onNavigateBackCalled = true }, ) } } @@ -90,6 +94,15 @@ class SetupAutofillScreenTest : BaseComposeTest() { } } + @Test + fun `Turn on later component should not be displayed when not in initial setup`() { + mutableStateFlow.update { it.copy(isInitialSetup = false) } + composeTestRule.assertNoDialogExists() + composeTestRule + .onNodeWithText(text = "Turn on later") + .assertDoesNotExist() + } + @Test fun `NavigateToAutoFillSettings should start system autofill settings activity`() { every { intentManager.startSystemAutofillSettingsActivity() } returns true @@ -207,10 +220,35 @@ class SetupAutofillScreenTest : BaseComposeTest() { } composeTestRule.assertNoDialogExists() } + + @Test + fun `on NavigateBack event should invoke onNavigateBack`() { + mutableEventFlow.tryEmit(SetupAutoFillEvent.NavigateBack) + assertTrue(onNavigateBackCalled) + } + + @Test + fun `close icon should not show when in initial setup`() { + composeTestRule + .onNodeWithContentDescription(label = "Close") + .assertDoesNotExist() + } + + @Test + fun `close icon should show when not initial setup and send action when clicked`() { + mutableStateFlow.update { it.copy(isInitialSetup = false) } + composeTestRule + .onNodeWithContentDescription(label = "Close") + .assertIsDisplayed() + .performClick() + + verify { viewModel.trySendAction(SetupAutoFillAction.CloseClick) } + } } private val DEFAULT_STATE = SetupAutoFillState( userId = "userId", dialogState = null, autofillEnabled = false, + isInitialSetup = true, ) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreenTest.kt index 2a8eed45e..224e61ead 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreenTest.kt @@ -250,7 +250,7 @@ class RootNavScreenTest : BaseComposeTest() { RootNavState.OnboardingAutoFillSetup composeTestRule.runOnIdle { fakeNavHostController.assertLastNavigation( - route = "setup_auto_fill", + route = "setup_auto_fill_as_root/true", navOptions = expectedNavOptions, ) } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillScreenTest.kt index a92fb0bfb..d7278d8f3 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillScreenTest.kt @@ -35,6 +35,7 @@ class AutoFillScreenTest : BaseComposeTest() { private var isSystemSettingsRequestSuccess = false private var onNavigateBackCalled = false private var onNavigateToBlockAutoFillScreenCalled = false + private var onNavigateToSetupAutoFillScreenCalled = false private val mutableEventFlow = bufferedMutableSharedFlow() private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE) @@ -56,6 +57,7 @@ class AutoFillScreenTest : BaseComposeTest() { onNavigateToBlockAutoFillScreen = { onNavigateToBlockAutoFillScreenCalled = true }, viewModel = viewModel, intentManager = intentManager, + onNavigateToSetupAutofill = { onNavigateToSetupAutoFillScreenCalled = true }, ) } } @@ -498,6 +500,12 @@ class AutoFillScreenTest : BaseComposeTest() { .performClick() verify { viewModel.trySendAction(AutoFillAction.DismissShowAutofillActionCard) } } + + @Test + fun `when NavigateToSetupAutofill event is sent should call onNavigateToSetupAutofill`() { + mutableEventFlow.tryEmit(AutoFillEvent.NavigateToSetupAutofill) + assertTrue(onNavigateToSetupAutoFillScreenCalled) + } } private val DEFAULT_STATE: AutoFillState = AutoFillState( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillViewModelTest.kt index 9fe456b83..b2963fdc6 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/autofill/AutoFillViewModelTest.kt @@ -321,10 +321,17 @@ class AutoFillViewModelTest : BaseViewModelTest() { @Suppress("MaxLineLength") @Test - fun `when AutoFillActionCardCtaClick action is sent should update show autofill in repository`() { + fun `when AutoFillActionCardCtaClick action is sent should update show autofill in repository and send NavigateToSetupAutofill event`() = + runTest { mutableShowAutofillActionCardFlow.update { true } val viewModel = createViewModel() - viewModel.trySendAction(AutoFillAction.AutoFillActionCardCtaClick) + viewModel.eventFlow.test { + viewModel.trySendAction(AutoFillAction.AutoFillActionCardCtaClick) + assertEquals( + AutoFillEvent.NavigateToSetupAutofill, + awaitItem(), + ) + } verify { settingsRepository.storeShowAutoFillSettingBadge( DEFAULT_STATE.activeUserId, diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreenTest.kt index 43fd5d400..55c10cc2a 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreenTest.kt @@ -53,6 +53,8 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() { onNavigateToPendingRequests = {}, onNavigateToSearchVault = {}, onNavigateToSearchSend = {}, + onNavigateToSetupAutoFillScreen = {}, + onNavigateToSetupUnlockScreen = {}, ) } }