mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
PM-12076 remaining state based navigation linkup for onboarding (#3923)
This commit is contained in:
parent
503c966177
commit
37f1da1ec2
9 changed files with 166 additions and 8 deletions
|
@ -1,6 +1,8 @@
|
||||||
package com.x8bit.bitwarden.ui.auth.feature.accountsetup
|
package com.x8bit.bitwarden.ui.auth.feature.accountsetup
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
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.data.platform.repository.SettingsRepository
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
@ -16,9 +18,13 @@ import javax.inject.Inject
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class SetupAutoFillViewModel @Inject constructor(
|
class SetupAutoFillViewModel @Inject constructor(
|
||||||
private val settingsRepository: SettingsRepository,
|
private val settingsRepository: SettingsRepository,
|
||||||
|
private val authRepository: AuthRepository,
|
||||||
) :
|
) :
|
||||||
BaseViewModel<SetupAutoFillState, SetupAutoFillEvent, SetupAutoFillAction>(
|
BaseViewModel<SetupAutoFillState, SetupAutoFillEvent, SetupAutoFillAction>(
|
||||||
initialState = SetupAutoFillState(dialogState = null, autofillEnabled = false),
|
initialState = run {
|
||||||
|
val userId = requireNotNull(authRepository.userStateFlow.value).activeUserId
|
||||||
|
SetupAutoFillState(userId = userId, dialogState = null, autofillEnabled = false)
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -73,11 +79,11 @@ class SetupAutoFillViewModel @Inject constructor(
|
||||||
|
|
||||||
private fun handleTurnOnLaterConfirmClick() {
|
private fun handleTurnOnLaterConfirmClick() {
|
||||||
// TODO PM-10631 record user chose to turn on later for settings badging.
|
// 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() {
|
private fun handleContinueClick() {
|
||||||
// TODO PM-10632 update status to complete setup step.
|
updateOnboardingStatusToNextStep()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAutofillServiceChanged(action: SetupAutoFillAction.AutofillServiceChanged) {
|
private fun handleAutofillServiceChanged(action: SetupAutoFillAction.AutofillServiceChanged) {
|
||||||
|
@ -87,12 +93,20 @@ class SetupAutoFillViewModel @Inject constructor(
|
||||||
settingsRepository.disableAutofill()
|
settingsRepository.disableAutofill()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateOnboardingStatusToNextStep() =
|
||||||
|
authRepository
|
||||||
|
.setOnboardingStatus(
|
||||||
|
userId = state.userId,
|
||||||
|
status = OnboardingStatus.FINAL_STEP,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UI State for the Auto-fill setup screen.
|
* UI State for the Auto-fill setup screen.
|
||||||
*/
|
*/
|
||||||
data class SetupAutoFillState(
|
data class SetupAutoFillState(
|
||||||
|
val userId: String,
|
||||||
val dialogState: SetupAutoFillDialogState?,
|
val dialogState: SetupAutoFillDialogState?,
|
||||||
val autofillEnabled: Boolean,
|
val autofillEnabled: Boolean,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,10 +16,13 @@ import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.navigation.navOptions
|
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_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.SETUP_UNLOCK_ROUTE
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.navigateToSetupAutoFillScreen
|
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.navigateToSetupUnlockScreen
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.setupAutoFillDestination
|
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.accountsetup.setupUnlockDestination
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.auth.AUTH_GRAPH_ROUTE
|
import com.x8bit.bitwarden.ui.auth.feature.auth.AUTH_GRAPH_ROUTE
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.auth.authGraph
|
import com.x8bit.bitwarden.ui.auth.feature.auth.authGraph
|
||||||
|
@ -98,6 +101,7 @@ fun RootNavScreen(
|
||||||
setupDebugMenuDestination(onNavigateBack = { navController.popBackStack() })
|
setupDebugMenuDestination(onNavigateBack = { navController.popBackStack() })
|
||||||
setupUnlockDestination()
|
setupUnlockDestination()
|
||||||
setupAutoFillDestination()
|
setupAutoFillDestination()
|
||||||
|
setupCompleteDestination()
|
||||||
}
|
}
|
||||||
|
|
||||||
val targetRoute = when (state) {
|
val targetRoute = when (state) {
|
||||||
|
@ -125,6 +129,7 @@ fun RootNavScreen(
|
||||||
|
|
||||||
RootNavState.OnboardingAccountLockSetup -> SETUP_UNLOCK_ROUTE
|
RootNavState.OnboardingAccountLockSetup -> SETUP_UNLOCK_ROUTE
|
||||||
RootNavState.OnboardingAutoFillSetup -> SETUP_AUTO_FILL_ROUTE
|
RootNavState.OnboardingAutoFillSetup -> SETUP_AUTO_FILL_ROUTE
|
||||||
|
RootNavState.OnboardingStepsComplete -> SETUP_COMPLETE_ROUTE
|
||||||
}
|
}
|
||||||
val currentRoute = navController.currentDestination?.rootLevelRoute()
|
val currentRoute = navController.currentDestination?.rootLevelRoute()
|
||||||
|
|
||||||
|
@ -236,6 +241,10 @@ fun RootNavScreen(
|
||||||
RootNavState.OnboardingAutoFillSetup -> {
|
RootNavState.OnboardingAutoFillSetup -> {
|
||||||
navController.navigateToSetupAutoFillScreen(rootNavOptions)
|
navController.navigateToSetupAutoFillScreen(rootNavOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RootNavState.OnboardingStepsComplete -> {
|
||||||
|
navController.navigateToSetupCompleteScreen(rootNavOptions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,8 +97,8 @@ class RootNavViewModel @Inject constructor(
|
||||||
OnboardingStatus.ACCOUNT_LOCK_SETUP,
|
OnboardingStatus.ACCOUNT_LOCK_SETUP,
|
||||||
-> RootNavState.OnboardingAccountLockSetup
|
-> RootNavState.OnboardingAccountLockSetup
|
||||||
OnboardingStatus.AUTOFILL_SETUP -> RootNavState.OnboardingAutoFillSetup
|
OnboardingStatus.AUTOFILL_SETUP -> RootNavState.OnboardingAutoFillSetup
|
||||||
|
OnboardingStatus.FINAL_STEP -> RootNavState.OnboardingStepsComplete
|
||||||
OnboardingStatus.COMPLETE -> throw IllegalStateException("Should not have entered here.")
|
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
|
@Parcelize
|
||||||
data object OnboardingAutoFillSetup : RootNavState()
|
data object OnboardingAutoFillSetup : RootNavState()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App should show the onboarding steps complete screen.
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data object OnboardingStepsComplete : RootNavState()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package com.x8bit.bitwarden.ui.auth.feature.accountsetup
|
package com.x8bit.bitwarden.ui.auth.feature.accountsetup
|
||||||
|
|
||||||
import app.cash.turbine.test
|
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.data.platform.repository.SettingsRepository
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
|
@ -24,6 +27,15 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() {
|
||||||
every { disableAutofill() } just runs
|
every { disableAutofill() } just runs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val mockUserState = mockk<UserState> {
|
||||||
|
every { activeUserId } returns DEFAULT_USER_ID
|
||||||
|
}
|
||||||
|
private val mutableUserStateFlow = MutableStateFlow<UserState?>(mockUserState)
|
||||||
|
private val authRepository: AuthRepository = mockk {
|
||||||
|
every { userStateFlow } returns mutableUserStateFlow
|
||||||
|
every { setOnboardingStatus(any(), any()) } just runs
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `handleAutofillEnabledUpdateReceive updates autofillEnabled state`() {
|
fun `handleAutofillEnabledUpdateReceive updates autofillEnabled state`() {
|
||||||
val viewModel = createViewModel()
|
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"
|
||||||
|
|
|
@ -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,
|
||||||
|
)
|
||||||
|
|
|
@ -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.data.auth.repository.model.UserState
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
|
import io.mockk.just
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
import io.mockk.runs
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
@ -18,8 +20,9 @@ class SetupCompleteViewModelTest : BaseViewModelTest() {
|
||||||
every { activeUserId } returns DEFAULT_USER_ID
|
every { activeUserId } returns DEFAULT_USER_ID
|
||||||
}
|
}
|
||||||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(mockUserState)
|
private val mutableUserStateFlow = MutableStateFlow<UserState?>(mockUserState)
|
||||||
private val authRepository: AuthRepository = mockk(relaxed = true) {
|
private val authRepository: AuthRepository = mockk {
|
||||||
every { userStateFlow } returns mutableUserStateFlow
|
every { userStateFlow } returns mutableUserStateFlow
|
||||||
|
every { setOnboardingStatus(any(), any()) } just runs
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -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 =
|
rootNavStateFlow.value =
|
||||||
RootNavState.OnboardingAccountLockSetup
|
RootNavState.OnboardingAccountLockSetup
|
||||||
composeTestRule.runOnIdle {
|
composeTestRule.runOnIdle {
|
||||||
|
@ -235,6 +235,26 @@ class RootNavScreenTest : BaseComposeTest() {
|
||||||
navOptions = expectedNavOptions,
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `when the active user has an locked vault and they have a OnboardingStatus of NOT_STARTED the nav state should be VaultLocked`() {
|
fun `when the active user has an locked vault and they have a OnboardingStatus of NOT_STARTED the nav state should be VaultLocked`() {
|
||||||
|
|
Loading…
Reference in a new issue