Skip to content

Commit

Permalink
PM-13067 Navigate to setup unlock screen from action card in security…
Browse files Browse the repository at this point in the history
… settings (#4023)
  • Loading branch information
dseverns-livefront authored Oct 4, 2024
1 parent 83652c9 commit 8ae6433
Show file tree
Hide file tree
Showing 14 changed files with 299 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -1,29 +1,88 @@
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 for [SetupUnlockScreen]
* Route constants for [SetupUnlockScreen]
*/
const val SETUP_UNLOCK_ROUTE = "setup_unlock"
private const val SETUP_UNLOCK_PREFIX = "setup_unlock"
private const val SETUP_UNLOCK_AS_ROOT_PREFIX = "${SETUP_UNLOCK_PREFIX}_as_root"
private const val SETUP_UNLOCK_INITIAL_SETUP_ARG = "isInitialSetup"
const val SETUP_UNLOCK_AS_ROOT_ROUTE = "$SETUP_UNLOCK_AS_ROOT_PREFIX/" +
"{$SETUP_UNLOCK_INITIAL_SETUP_ARG}"
private const val SETUP_UNLOCK_ROUTE = "$SETUP_UNLOCK_PREFIX/{$SETUP_UNLOCK_INITIAL_SETUP_ARG}"

/**
* Class to retrieve setup unlock arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class SetupUnlockArgs(
val isInitialSetup: Boolean,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
isInitialSetup = requireNotNull(savedStateHandle[SETUP_UNLOCK_INITIAL_SETUP_ARG]),
)
}

/**
* Navigate to the setup unlock screen.
*/
fun NavController.navigateToSetupUnlockScreen(navOptions: NavOptions? = null) {
this.navigate(SETUP_UNLOCK_ROUTE, navOptions)
this.navigate("$SETUP_UNLOCK_PREFIX/false", navOptions)
}

/**
* Add the setup unlock screen to the nav graph.
* Navigate to the setup unlock screen as root.
*/
fun NavGraphBuilder.setupUnlockDestination() {
composableWithPushTransitions(
fun NavController.navigateToSetupUnlockScreenAsRoot(navOptions: NavOptions? = null) {
this.navigate("$SETUP_UNLOCK_AS_ROOT_PREFIX/true", navOptions)
}

/**
* Add the setup unlock screen to a nav graph.
*/
fun NavGraphBuilder.setupUnlockDestination(
onNavigateBack: () -> Unit,
) {
composableWithSlideTransitions(
route = SETUP_UNLOCK_ROUTE,
arguments = setupUnlockArguments,
) {
SetupUnlockScreen()
SetupUnlockScreen(
onNavigateBack = onNavigateBack,
)
}
}

/**
* Add the setup unlock screen to the root nav graph.
*/
fun NavGraphBuilder.setupUnlockDestinationAsRoot() {
composableWithPushTransitions(
route = SETUP_UNLOCK_AS_ROOT_ROUTE,
arguments = setupUnlockArguments,
) {
SetupUnlockScreen(
onNavigateBack = {
// No-Op
},
)
}
}

private val setupUnlockArguments = listOf(
navArgument(
name = SETUP_UNLOCK_INITIAL_SETUP_ARG,
builder = {
type = NavType.BoolType
},
),
)
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import com.x8bit.bitwarden.ui.auth.feature.accountsetup.handlers.SetupUnlockHand
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
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
Expand All @@ -60,10 +61,12 @@ import com.x8bit.bitwarden.ui.platform.util.isPortrait
* Top level composable for the setup unlock screen.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Suppress("LongMethod")
@Composable
fun SetupUnlockScreen(
viewModel: SetupUnlockViewModel = hiltViewModel(),
biometricsManager: BiometricsManager = LocalBiometricsManager.current,
onNavigateBack: () -> Unit,
) {
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
val handler = remember(viewModel) { SetupUnlockHandler.create(viewModel = viewModel) }
Expand All @@ -83,6 +86,8 @@ fun SetupUnlockScreen(
cipher = event.cipher,
)
}

SetupUnlockEvent.NavigateBack -> onNavigateBack()
}
}

Expand All @@ -100,9 +105,27 @@ fun SetupUnlockScreen(
.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.set_up_unlock
},
),
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(SetupUnlockAction.CloseClick)
}
},
)
},
)
},
) { innerPadding ->
Expand Down Expand Up @@ -169,14 +192,16 @@ private fun SetupUnlockScreenContent(
)

Spacer(modifier = Modifier.height(height = 12.dp))
SetUpLaterButton(
onConfirmClick = handler.onSetUpLaterClick,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
if (state.isInitialSetup) {
SetUpLaterButton(
onConfirmClick = handler.onSetUpLaterClick,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)

Spacer(modifier = Modifier.height(height = 12.dp))
Spacer(modifier = Modifier.height(height = 12.dp))
}
Spacer(modifier = Modifier.navigationBarsPadding())
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ private const val KEY_STATE = "state"
/**
* Models logic for the setup unlock screen.
*/
@Suppress("TooManyFunctions")
@HiltViewModel
class SetupUnlockViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
Expand All @@ -38,6 +39,8 @@ class SetupUnlockViewModel @Inject constructor(
userId = userId,
cipher = biometricsEncryptionManager.getOrCreateCipher(userId = userId),
)
// whether or not the user has completed the initial setup prior to this.
val isInitialSetup = SetupUnlockArgs(savedStateHandle).isInitialSetup
SetupUnlockState(
userId = userId,
isUnlockWithPasswordEnabled = authRepository
Expand All @@ -49,6 +52,7 @@ class SetupUnlockViewModel @Inject constructor(
isUnlockWithBiometricsEnabled = settingsRepository.isUnlockWithBiometricsEnabled &&
isBiometricsValid,
dialogState = null,
isInitialSetup = isInitialSetup,
)
},
) {
Expand All @@ -64,11 +68,20 @@ class SetupUnlockViewModel @Inject constructor(

is SetupUnlockAction.UnlockWithPinToggle -> handleUnlockWithPinToggle(action)
is SetupUnlockAction.Internal -> handleInternalActions(action)
SetupUnlockAction.CloseClick -> handleCloseClick()
}
}

private fun handleCloseClick() {
sendEvent(SetupUnlockEvent.NavigateBack)
}

private fun handleContinueClick() {
updateOnboardingStatusToNextStep()
if (state.isInitialSetup) {
updateOnboardingStatusToNextStep()
} else {
sendEvent(SetupUnlockEvent.NavigateBack)
}
}

private fun handleEnableBiometricsClick() {
Expand Down Expand Up @@ -196,6 +209,7 @@ data class SetupUnlockState(
val isUnlockWithPinEnabled: Boolean,
val isUnlockWithBiometricsEnabled: Boolean,
val dialogState: DialogState?,
val isInitialSetup: Boolean,
) : Parcelable {
/**
* Indicates whether the continue button should be enabled or disabled.
Expand Down Expand Up @@ -237,6 +251,11 @@ sealed class SetupUnlockEvent {
data class ShowBiometricsPrompt(
val cipher: Cipher,
) : SetupUnlockEvent()

/**
* Navigates back to the previous screen.
*/
data object NavigateBack : SetupUnlockEvent()
}

/**
Expand Down Expand Up @@ -277,6 +296,11 @@ sealed class SetupUnlockAction {
*/
data object DismissDialog : SetupUnlockAction()

/**
* The user has clicked the close button.
*/
data object CloseClick : SetupUnlockAction()

/**
* Models actions that can be sent by the view model itself.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ 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.SETUP_UNLOCK_AS_ROOT_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.navigateToSetupUnlockScreenAsRoot
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.setupUnlockDestinationAsRoot
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.navigateToAuthGraph
Expand Down Expand Up @@ -99,7 +99,7 @@ fun RootNavScreen(
vaultUnlockDestination()
vaultUnlockedGraph(navController)
setupDebugMenuDestination(onNavigateBack = { navController.popBackStack() })
setupUnlockDestination()
setupUnlockDestinationAsRoot()
setupAutoFillDestination()
setupCompleteDestination()
}
Expand Down Expand Up @@ -127,7 +127,7 @@ fun RootNavScreen(
is RootNavState.VaultUnlockedForFido2GetCredentials,
-> VAULT_UNLOCKED_GRAPH_ROUTE

RootNavState.OnboardingAccountLockSetup -> SETUP_UNLOCK_ROUTE
RootNavState.OnboardingAccountLockSetup -> SETUP_UNLOCK_AS_ROOT_ROUTE
RootNavState.OnboardingAutoFillSetup -> SETUP_AUTO_FILL_ROUTE
RootNavState.OnboardingStepsComplete -> SETUP_COMPLETE_ROUTE
}
Expand Down Expand Up @@ -235,7 +235,7 @@ fun RootNavScreen(
}

RootNavState.OnboardingAccountLockSetup -> {
navController.navigateToSetupUnlockScreen(rootNavOptions)
navController.navigateToSetupUnlockScreenAsRoot(rootNavOptions)
}

RootNavState.OnboardingAutoFillSetup -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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
Expand All @@ -26,6 +27,7 @@ private const val SETTINGS_ROUTE: String = "settings"
/**
* Add settings destinations to the nav graph.
*/
@Suppress("LongParameterList")
fun NavGraphBuilder.settingsGraph(
navController: NavController,
onNavigateToDeleteAccount: () -> Unit,
Expand Down Expand Up @@ -54,6 +56,7 @@ fun NavGraphBuilder.settingsGraph(
onNavigateBack = { navController.popBackStack() },
onNavigateToDeleteAccount = onNavigateToDeleteAccount,
onNavigateToPendingRequests = onNavigateToPendingRequests,
onNavigateToSetupUnlockScreen = { navController.navigateToSetupUnlockScreen() },
)
appearanceDestination(onNavigateBack = { navController.popBackStack() })
autoFillDestination(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ fun NavGraphBuilder.accountSecurityDestination(
onNavigateBack: () -> Unit,
onNavigateToDeleteAccount: () -> Unit,
onNavigateToPendingRequests: () -> Unit,
onNavigateToSetupUnlockScreen: () -> Unit,
) {
composableWithPushTransitions(
route = ACCOUNT_SECURITY_ROUTE,
Expand All @@ -22,6 +23,7 @@ fun NavGraphBuilder.accountSecurityDestination(
onNavigateBack = onNavigateBack,
onNavigateToDeleteAccount = onNavigateToDeleteAccount,
onNavigateToPendingRequests = onNavigateToPendingRequests,
onNavigateToSetupUnlockScreen = onNavigateToSetupUnlockScreen,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ fun AccountSecurityScreen(
onNavigateBack: () -> Unit,
onNavigateToDeleteAccount: () -> Unit,
onNavigateToPendingRequests: () -> Unit,
onNavigateToSetupUnlockScreen: () -> Unit,
viewModel: AccountSecurityViewModel = hiltViewModel(),
biometricsManager: BiometricsManager = LocalBiometricsManager.current,
intentManager: IntentManager = LocalIntentManager.current,
Expand Down Expand Up @@ -140,6 +141,8 @@ fun AccountSecurityScreen(
is AccountSecurityEvent.ShowToast -> {
Toast.makeText(context, event.text(resources), Toast.LENGTH_SHORT).show()
}

AccountSecurityEvent.NavigateToSetupUnlockScreen -> onNavigateToSetupUnlockScreen()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ class AccountSecurityViewModel @Inject constructor(

private fun handleUnlockCardCtaClick() {
dismissUnlockNotificationBadge()
// TODO: Navigate to unlock set up screen PM-13067
sendEvent(AccountSecurityEvent.NavigateToSetupUnlockScreen)
}

private fun handleAccountFingerprintPhraseClick() {
Expand Down Expand Up @@ -564,6 +564,11 @@ sealed class AccountSecurityEvent {
data class ShowToast(
val text: Text,
) : AccountSecurityEvent()

/**
* Navigate to the setup unlock screen.
*/
data object NavigateToSetupUnlockScreen : AccountSecurityEvent()
}

/**
Expand Down
Loading

0 comments on commit 8ae6433

Please sign in to comment.