PM-13068 Navigate from settings to setup autofill screen. (#4034)

This commit is contained in:
Dave Severns 2024-10-08 10:29:40 -04:00 committed by GitHub
parent bc057932a0
commit 641a48fe44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 301 additions and 44 deletions

View file

@ -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
},
)

View file

@ -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<SetupAutoFillState, SetupAutoFillEvent, SetupAutoFillAction>(
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.
*/

View file

@ -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 = {},

View file

@ -33,6 +33,7 @@ class SetupUnlockViewModel @Inject constructor(
private val settingsRepository: SettingsRepository,
private val biometricsEncryptionManager: BiometricsEncryptionManager,
) : BaseViewModel<SetupUnlockState, SetupUnlockEvent, SetupUnlockAction>(
// 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(

View file

@ -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 -> {

View file

@ -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(

View file

@ -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,
)
}
}

View file

@ -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()
}
}

View file

@ -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()
}
/**

View file

@ -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()
},
)
}
}

View file

@ -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,
)
}
}

View file

@ -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,
)
}
}

View file

@ -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,
)

View file

@ -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<SetupAutoFillEvent>()
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,
)

View file

@ -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,
)
}

View file

@ -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<AutoFillEvent>()
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(

View file

@ -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,

View file

@ -53,6 +53,8 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
onNavigateToPendingRequests = {},
onNavigateToSearchVault = {},
onNavigateToSearchSend = {},
onNavigateToSetupAutoFillScreen = {},
onNavigateToSetupUnlockScreen = {},
)
}
}