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 574f18fb2..6264d0245 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,6 +1,8 @@ package com.x8bit.bitwarden.ui.auth.feature.accountsetup import androidx.lifecycle.viewModelScope +import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus +import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.platform.repository.SettingsRepository import com.x8bit.bitwarden.ui.platform.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel @@ -16,9 +18,13 @@ import javax.inject.Inject @HiltViewModel class SetupAutoFillViewModel @Inject constructor( private val settingsRepository: SettingsRepository, + private val authRepository: AuthRepository, ) : BaseViewModel( - initialState = SetupAutoFillState(dialogState = null, autofillEnabled = false), + initialState = run { + val userId = requireNotNull(authRepository.userStateFlow.value).activeUserId + SetupAutoFillState(userId = userId, dialogState = null, autofillEnabled = false) + }, ) { init { @@ -73,11 +79,11 @@ class SetupAutoFillViewModel @Inject constructor( private fun handleTurnOnLaterConfirmClick() { // TODO PM-10631 record user chose to turn on later for settings badging. - // TODO PM-10632 update status to complete setup step. + updateOnboardingStatusToNextStep() } private fun handleContinueClick() { - // TODO PM-10632 update status to complete setup step. + updateOnboardingStatusToNextStep() } private fun handleAutofillServiceChanged(action: SetupAutoFillAction.AutofillServiceChanged) { @@ -87,12 +93,20 @@ class SetupAutoFillViewModel @Inject constructor( settingsRepository.disableAutofill() } } + + private fun updateOnboardingStatusToNextStep() = + authRepository + .setOnboardingStatus( + userId = state.userId, + status = OnboardingStatus.FINAL_STEP, + ) } /** * UI State for the Auto-fill setup screen. */ data class SetupAutoFillState( + val userId: String, val dialogState: SetupAutoFillDialogState?, val autofillEnabled: Boolean, ) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupCompleteNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupCompleteNavigation.kt new file mode 100644 index 000000000..623e88097 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupCompleteNavigation.kt @@ -0,0 +1,29 @@ +package com.x8bit.bitwarden.ui.auth.feature.accountsetup + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions + +/** + * Route name for [SetupCompleteScreen]. + */ +const val SETUP_COMPLETE_ROUTE = "setup_complete" + +/** + * Navigate to the setup complete screen. + */ +fun NavController.navigateToSetupCompleteScreen(navOptions: NavOptions? = null) { + this.navigate(SETUP_COMPLETE_ROUTE, navOptions) +} + +/** + * Add the setup complete screen to the nav graph. + */ +fun NavGraphBuilder.setupCompleteDestination() { + composableWithPushTransitions( + route = SETUP_COMPLETE_ROUTE, + ) { + SetupCompleteScreen() + } +} 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 36874e7a9..fefdde037 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 @@ -16,10 +16,13 @@ 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_COMPLETE_ROUTE import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SETUP_UNLOCK_ROUTE import com.x8bit.bitwarden.ui.auth.feature.accountsetup.navigateToSetupAutoFillScreen +import com.x8bit.bitwarden.ui.auth.feature.accountsetup.navigateToSetupCompleteScreen 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.setupCompleteDestination import com.x8bit.bitwarden.ui.auth.feature.accountsetup.setupUnlockDestination import com.x8bit.bitwarden.ui.auth.feature.auth.AUTH_GRAPH_ROUTE import com.x8bit.bitwarden.ui.auth.feature.auth.authGraph @@ -98,6 +101,7 @@ fun RootNavScreen( setupDebugMenuDestination(onNavigateBack = { navController.popBackStack() }) setupUnlockDestination() setupAutoFillDestination() + setupCompleteDestination() } val targetRoute = when (state) { @@ -125,6 +129,7 @@ fun RootNavScreen( RootNavState.OnboardingAccountLockSetup -> SETUP_UNLOCK_ROUTE RootNavState.OnboardingAutoFillSetup -> SETUP_AUTO_FILL_ROUTE + RootNavState.OnboardingStepsComplete -> SETUP_COMPLETE_ROUTE } val currentRoute = navController.currentDestination?.rootLevelRoute() @@ -236,6 +241,10 @@ fun RootNavScreen( RootNavState.OnboardingAutoFillSetup -> { navController.navigateToSetupAutoFillScreen(rootNavOptions) } + + RootNavState.OnboardingStepsComplete -> { + navController.navigateToSetupCompleteScreen(rootNavOptions) + } } } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModel.kt index 8288cad07..00c3c217d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModel.kt @@ -97,8 +97,8 @@ class RootNavViewModel @Inject constructor( OnboardingStatus.ACCOUNT_LOCK_SETUP, -> RootNavState.OnboardingAccountLockSetup OnboardingStatus.AUTOFILL_SETUP -> RootNavState.OnboardingAutoFillSetup + OnboardingStatus.FINAL_STEP -> RootNavState.OnboardingStepsComplete OnboardingStatus.COMPLETE -> throw IllegalStateException("Should not have entered here.") - OnboardingStatus.FINAL_STEP -> TODO("PM-12076 complete navigation wiring") } } @@ -345,6 +345,12 @@ sealed class RootNavState : Parcelable { */ @Parcelize data object OnboardingAutoFillSetup : RootNavState() + + /** + * App should show the onboarding steps complete screen. + */ + @Parcelize + data object OnboardingStepsComplete : RootNavState() } /** 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 b96e69ed4..a76c64e5e 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,6 +1,9 @@ package com.x8bit.bitwarden.ui.auth.feature.accountsetup import app.cash.turbine.test +import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus +import com.x8bit.bitwarden.data.auth.repository.AuthRepository +import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.data.platform.repository.SettingsRepository import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest import io.mockk.every @@ -24,6 +27,15 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() { every { disableAutofill() } just runs } + private val mockUserState = mockk { + every { activeUserId } returns DEFAULT_USER_ID + } + private val mutableUserStateFlow = MutableStateFlow(mockUserState) + private val authRepository: AuthRepository = mockk { + every { userStateFlow } returns mutableUserStateFlow + every { setOnboardingStatus(any(), any()) } just runs + } + @Test fun `handleAutofillEnabledUpdateReceive updates autofillEnabled state`() { val viewModel = createViewModel() @@ -92,5 +104,31 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() { ) } - private fun createViewModel() = SetupAutoFillViewModel(settingsRepository) + @Test + fun `handleTurnOnLaterConfirmClick sets onboarding status to FINAL_STEP`() { + val viewModel = createViewModel() + viewModel.trySendAction(SetupAutoFillAction.TurnOnLaterConfirmClick) + verify { + authRepository.setOnboardingStatus( + DEFAULT_USER_ID, + OnboardingStatus.FINAL_STEP, + ) + } + } + + @Test + fun `handleContinueClick sets onboarding status to FINAL_STEP`() { + val viewModel = createViewModel() + viewModel.trySendAction(SetupAutoFillAction.ContinueClick) + verify { + authRepository.setOnboardingStatus( + DEFAULT_USER_ID, + OnboardingStatus.FINAL_STEP, + ) + } + } + + private fun createViewModel() = SetupAutoFillViewModel(settingsRepository, authRepository) } + +private const val DEFAULT_USER_ID = "userId" 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 e53e98de4..9215a555b 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 @@ -208,4 +208,8 @@ class SetupAutofillScreenTest : BaseComposeTest() { } } -private val DEFAULT_STATE = SetupAutoFillState(dialogState = null, autofillEnabled = false) +private val DEFAULT_STATE = SetupAutoFillState( + userId = "userId", + dialogState = null, + autofillEnabled = false, +) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupCompleteViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupCompleteViewModelTest.kt index c07ccc283..c9612460b 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupCompleteViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupCompleteViewModelTest.kt @@ -5,7 +5,9 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest import io.mockk.every +import io.mockk.just import io.mockk.mockk +import io.mockk.runs import io.mockk.verify import kotlinx.coroutines.flow.MutableStateFlow import org.junit.jupiter.api.Assertions.assertEquals @@ -18,8 +20,9 @@ class SetupCompleteViewModelTest : BaseViewModelTest() { every { activeUserId } returns DEFAULT_USER_ID } private val mutableUserStateFlow = MutableStateFlow(mockUserState) - private val authRepository: AuthRepository = mockk(relaxed = true) { + private val authRepository: AuthRepository = mockk { every { userStateFlow } returns mutableUserStateFlow + every { setOnboardingStatus(any(), any()) } just runs } @Test 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 dc58dc69c..a737bd122 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 @@ -226,7 +226,7 @@ class RootNavScreenTest : BaseComposeTest() { ) } - // Make sure navigating to onboarding graph works as expected: + // Make sure navigating to account lock setup works as expected: rootNavStateFlow.value = RootNavState.OnboardingAccountLockSetup composeTestRule.runOnIdle { @@ -235,6 +235,26 @@ class RootNavScreenTest : BaseComposeTest() { navOptions = expectedNavOptions, ) } + + // Make sure navigating to account autofill setup works as expected: + rootNavStateFlow.value = + RootNavState.OnboardingAutoFillSetup + composeTestRule.runOnIdle { + fakeNavHostController.assertLastNavigation( + route = "setup_auto_fill", + navOptions = expectedNavOptions, + ) + } + + // Make sure navigating to account setup complete works as expected: + rootNavStateFlow.value = + RootNavState.OnboardingStepsComplete + composeTestRule.runOnIdle { + fakeNavHostController.assertLastNavigation( + route = "setup_complete", + navOptions = expectedNavOptions, + ) + } } } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModelTest.kt index 7c1ddcc62..c691b434d 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModelTest.kt @@ -1032,6 +1032,41 @@ class RootNavViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `when the active user has an unlocked vault and they have a OnboardingStatus of FINAL_STEP the nav state should be OnboardingAutoFillSetup`() { + mutableUserStateFlow.tryEmit( + UserState( + activeUserId = "activeUserId", + accounts = listOf( + UserState.Account( + userId = "activeUserId", + name = "name", + email = "email", + avatarColorHex = "avatarColorHex", + environment = Environment.Us, + isPremium = true, + isLoggedIn = true, + isVaultUnlocked = true, + needsPasswordReset = false, + isBiometricsEnabled = false, + organizations = emptyList(), + needsMasterPassword = false, + trustedDevice = null, + hasMasterPassword = true, + isUsingKeyConnector = false, + onboardingStatus = OnboardingStatus.FINAL_STEP, + ), + ), + ), + ) + val viewModel = createViewModel() + assertEquals( + RootNavState.OnboardingStepsComplete, + viewModel.stateFlow.value, + ) + } + @Suppress("MaxLineLength") @Test fun `when the active user has an locked vault and they have a OnboardingStatus of NOT_STARTED the nav state should be VaultLocked`() {