mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 00:06:22 -06:00
PM-21255: Implement type-safe navigation (#5131)
This commit is contained in:
parent
1d68c1fdf6
commit
6fec95cb84
@ -6,65 +6,82 @@ 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.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import com.x8bit.bitwarden.ui.platform.util.toObjectRoute
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Route constant for navigating to the [SetupAutoFillScreen].
|
||||
* The type-safe route for the setup autofill screen.
|
||||
*/
|
||||
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}"
|
||||
sealed class SetupAutofillRoute {
|
||||
/**
|
||||
* The [isInitialSetup] value used in the setup autofill screen.
|
||||
*/
|
||||
abstract val isInitialSetup: Boolean
|
||||
|
||||
/**
|
||||
* The type-safe route for the standard setup autofill screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object Standard : SetupAutofillRoute() {
|
||||
override val isInitialSetup: Boolean get() = false
|
||||
}
|
||||
|
||||
/**
|
||||
* The type-safe route for the root setup autofill screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object AsRoot : SetupAutofillRoute() {
|
||||
override val isInitialSetup: Boolean get() = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Arguments for the [SetupAutoFillScreen] using [SavedStateHandle].
|
||||
*/
|
||||
data class SetupAutoFillScreenArgs(val isInitialSetup: Boolean) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
isInitialSetup = requireNotNull(savedStateHandle[SETUP_AUTO_FILL_NAV_ARG]),
|
||||
)
|
||||
data class SetupAutoFillScreenArgs(val isInitialSetup: Boolean)
|
||||
|
||||
/**
|
||||
* Constructs a [SetupAutoFillScreenArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toSetupAutoFillArgs(): SetupAutoFillScreenArgs {
|
||||
val route = (this.toObjectRoute<SetupAutofillRoute.AsRoot>()
|
||||
?: this.toObjectRoute<SetupAutofillRoute.Standard>())
|
||||
return route
|
||||
?.let { SetupAutoFillScreenArgs(isInitialSetup = it.isInitialSetup) }
|
||||
?: throw IllegalStateException("Missing correct route for SetupAutofillScreen")
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the setup auto-fill screen.
|
||||
* Navigate to the setup autofill screen.
|
||||
*/
|
||||
fun NavController.navigateToSetupAutoFillScreen(navOptions: NavOptions? = null) {
|
||||
this.navigate("$SETUP_AUTO_FILL_PREFIX/false", navOptions)
|
||||
this.navigate(route = SetupAutofillRoute.Standard, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the setup auto-fill screen as the root.
|
||||
* Navigate to the setup autofill screen as the root.
|
||||
*/
|
||||
fun NavController.navigateToSetupAutoFillAsRootScreen(navOptions: NavOptions? = null) {
|
||||
this.navigate("$SETUP_AUTO_FILL_AS_ROOT_PREFIX/true", navOptions)
|
||||
this.navigate(route = SetupAutofillRoute.AsRoot, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the setup auto-fil screen to the nav graph.
|
||||
* Add the setup autofill screen to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.setupAutoFillDestination(onNavigateBack: () -> Unit) {
|
||||
composableWithSlideTransitions(
|
||||
route = SETUP_AUTO_FILL_ROUTE,
|
||||
arguments = setupAutofillNavArgs,
|
||||
) {
|
||||
composableWithSlideTransitions<SetupAutofillRoute.Standard> {
|
||||
SetupAutoFillScreen(onNavigateBack = onNavigateBack)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the setup autofil screen to the root nav graph.
|
||||
* Add the setup autofill screen to the root nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.setupAutoFillDestinationAsRoot() {
|
||||
composableWithPushTransitions(
|
||||
route = SETUP_AUTO_FILL_AS_ROOT_ROUTE,
|
||||
arguments = setupAutofillNavArgs,
|
||||
) {
|
||||
composableWithPushTransitions<SetupAutofillRoute.AsRoot> {
|
||||
SetupAutoFillScreen(
|
||||
onNavigateBack = {
|
||||
// No-Op
|
||||
@ -72,9 +89,3 @@ fun NavGraphBuilder.setupAutoFillDestinationAsRoot() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val setupAutofillNavArgs = listOf(
|
||||
navArgument(SETUP_AUTO_FILL_NAV_ARG) {
|
||||
type = NavType.BoolType
|
||||
},
|
||||
)
|
||||
|
||||
@ -32,7 +32,7 @@ class SetupAutoFillViewModel @Inject constructor(
|
||||
// We load the state from the savedStateHandle for testing purposes.
|
||||
initialState = savedStateHandle[KEY_STATE] ?: run {
|
||||
val userId = requireNotNull(authRepository.userStateFlow.value).activeUserId
|
||||
val isInitialSetup = SetupAutoFillScreenArgs(savedStateHandle).isInitialSetup
|
||||
val isInitialSetup = savedStateHandle.toSetupAutoFillArgs().isInitialSetup
|
||||
SetupAutoFillState(
|
||||
userId = userId,
|
||||
dialogState = null,
|
||||
|
||||
@ -7,26 +7,26 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Route name for [SetupCompleteScreen].
|
||||
* The type-safe route for the setup complete screen.
|
||||
*/
|
||||
const val SETUP_COMPLETE_ROUTE = "setup_complete"
|
||||
@Serializable
|
||||
data object SetupCompleteRoute
|
||||
|
||||
/**
|
||||
* Navigate to the setup complete screen.
|
||||
*/
|
||||
fun NavController.navigateToSetupCompleteScreen(navOptions: NavOptions? = null) {
|
||||
this.navigate(SETUP_COMPLETE_ROUTE, navOptions)
|
||||
this.navigate(route = SetupCompleteRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the setup complete screen to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.setupCompleteDestination() {
|
||||
composableWithPushTransitions(
|
||||
route = SETUP_COMPLETE_ROUTE,
|
||||
) {
|
||||
composableWithPushTransitions<SetupCompleteRoute> {
|
||||
SetupCompleteScreen()
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,45 +6,68 @@ 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.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import com.x8bit.bitwarden.ui.platform.util.toObjectRoute
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Route constants for [SetupUnlockScreen]
|
||||
* The type-safe route for the setup unlock screen.
|
||||
*/
|
||||
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}"
|
||||
sealed class SetupUnlockRoute {
|
||||
/**
|
||||
* The [isInitialSetup] value used in the setup unlock screen.
|
||||
*/
|
||||
abstract val isInitialSetup: Boolean
|
||||
|
||||
/**
|
||||
* The type-safe route for the standard setup unlock screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object Standard : SetupUnlockRoute() {
|
||||
override val isInitialSetup: Boolean get() = false
|
||||
}
|
||||
|
||||
/**
|
||||
* The type-safe route for the root setup unlock screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object AsRoot : SetupUnlockRoute() {
|
||||
override val isInitialSetup: Boolean get() = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to retrieve setup unlock arguments from the [SavedStateHandle].
|
||||
*/
|
||||
data class SetupUnlockArgs(
|
||||
val isInitialSetup: Boolean,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
isInitialSetup = requireNotNull(savedStateHandle[SETUP_UNLOCK_INITIAL_SETUP_ARG]),
|
||||
)
|
||||
|
||||
/**
|
||||
* Constructs a [SetupUnlockArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toSetupUnlockArgs(): SetupUnlockArgs {
|
||||
val route = this.toObjectRoute<SetupUnlockRoute.AsRoot>()
|
||||
?: this.toObjectRoute<SetupUnlockRoute.Standard>()
|
||||
return route
|
||||
?.let { SetupUnlockArgs(isInitialSetup = it.isInitialSetup) }
|
||||
?: throw IllegalStateException("Missing correct route for SetupUnlockScreen")
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the setup unlock screen.
|
||||
*/
|
||||
fun NavController.navigateToSetupUnlockScreen(navOptions: NavOptions? = null) {
|
||||
this.navigate("$SETUP_UNLOCK_PREFIX/false", navOptions)
|
||||
this.navigate(route = SetupUnlockRoute.Standard, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the setup unlock screen as root.
|
||||
*/
|
||||
fun NavController.navigateToSetupUnlockScreenAsRoot(navOptions: NavOptions? = null) {
|
||||
this.navigate("$SETUP_UNLOCK_AS_ROOT_PREFIX/true", navOptions)
|
||||
this.navigate(route = SetupUnlockRoute.AsRoot, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -53,10 +76,7 @@ fun NavController.navigateToSetupUnlockScreenAsRoot(navOptions: NavOptions? = nu
|
||||
fun NavGraphBuilder.setupUnlockDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = SETUP_UNLOCK_ROUTE,
|
||||
arguments = setupUnlockArguments,
|
||||
) {
|
||||
composableWithSlideTransitions<SetupUnlockRoute.Standard> {
|
||||
SetupUnlockScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
@ -67,10 +87,7 @@ fun NavGraphBuilder.setupUnlockDestination(
|
||||
* Add the setup unlock screen to the root nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.setupUnlockDestinationAsRoot() {
|
||||
composableWithPushTransitions(
|
||||
route = SETUP_UNLOCK_AS_ROOT_ROUTE,
|
||||
arguments = setupUnlockArguments,
|
||||
) {
|
||||
composableWithPushTransitions<SetupUnlockRoute.AsRoot> {
|
||||
SetupUnlockScreen(
|
||||
onNavigateBack = {
|
||||
// No-Op
|
||||
@ -78,12 +95,3 @@ fun NavGraphBuilder.setupUnlockDestinationAsRoot() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val setupUnlockArguments = listOf(
|
||||
navArgument(
|
||||
name = SETUP_UNLOCK_INITIAL_SETUP_ARG,
|
||||
builder = {
|
||||
type = NavType.BoolType
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@ -43,7 +43,7 @@ class SetupUnlockViewModel @Inject constructor(
|
||||
cipher = biometricsEncryptionManager.getOrCreateCipher(userId = userId),
|
||||
)
|
||||
// whether or not the user has completed the initial setup prior to this.
|
||||
val isInitialSetup = SetupUnlockArgs(savedStateHandle).isInitialSetup
|
||||
val isInitialSetup = savedStateHandle.toSetupUnlockArgs().isInitialSetup
|
||||
SetupUnlockState(
|
||||
userId = userId,
|
||||
isUnlockWithPasswordEnabled = authRepository
|
||||
|
||||
@ -21,7 +21,7 @@ import com.x8bit.bitwarden.ui.auth.feature.enterprisesignon.navigateToEnterprise
|
||||
import com.x8bit.bitwarden.ui.auth.feature.environment.environmentDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.environment.navigateToEnvironment
|
||||
import com.x8bit.bitwarden.ui.auth.feature.expiredregistrationlink.expiredRegistrationLinkDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.landing.LANDING_ROUTE
|
||||
import com.x8bit.bitwarden.ui.auth.feature.landing.LandingRoute
|
||||
import com.x8bit.bitwarden.ui.auth.feature.landing.landingDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.landing.navigateToLanding
|
||||
import com.x8bit.bitwarden.ui.auth.feature.login.loginDestination
|
||||
@ -46,8 +46,13 @@ import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.twoFactorLoginDestinat
|
||||
import com.x8bit.bitwarden.ui.auth.feature.welcome.welcomeDestination
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.navigateToPreAuthSettings
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.preAuthSettingsDestinations
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
const val AUTH_GRAPH_ROUTE: String = "auth_graph"
|
||||
/**
|
||||
* The type-safe route for the auth graph.
|
||||
*/
|
||||
@Serializable
|
||||
data object AuthGraphRoute
|
||||
|
||||
/**
|
||||
* Add auth destinations to the nav graph.
|
||||
@ -56,9 +61,8 @@ const val AUTH_GRAPH_ROUTE: String = "auth_graph"
|
||||
fun NavGraphBuilder.authGraph(
|
||||
navController: NavHostController,
|
||||
) {
|
||||
navigation(
|
||||
startDestination = LANDING_ROUTE,
|
||||
route = AUTH_GRAPH_ROUTE,
|
||||
navigation<AuthGraphRoute>(
|
||||
startDestination = LandingRoute,
|
||||
) {
|
||||
createAccountDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
@ -67,7 +71,7 @@ fun NavGraphBuilder.authGraph(
|
||||
emailAddress = emailAddress,
|
||||
captchaToken = captchaToken,
|
||||
navOptions = navOptions {
|
||||
popUpTo(LANDING_ROUTE)
|
||||
popUpTo(route = LandingRoute)
|
||||
},
|
||||
)
|
||||
},
|
||||
@ -82,7 +86,7 @@ fun NavGraphBuilder.authGraph(
|
||||
)
|
||||
},
|
||||
onNavigateToCheckEmail = { emailAddress ->
|
||||
navController.navigateToCheckEmail(emailAddress)
|
||||
navController.navigateToCheckEmail(emailAddress = emailAddress)
|
||||
},
|
||||
onNavigateToEnvironment = { navController.navigateToEnvironment() },
|
||||
)
|
||||
@ -102,7 +106,7 @@ fun NavGraphBuilder.authGraph(
|
||||
emailAddress = emailAddress,
|
||||
captchaToken = captchaToken,
|
||||
navOptions = navOptions {
|
||||
popUpTo(LANDING_ROUTE)
|
||||
popUpTo(route = LandingRoute)
|
||||
},
|
||||
)
|
||||
},
|
||||
@ -203,14 +207,14 @@ fun NavGraphBuilder.authGraph(
|
||||
onNavigateToStartRegistration = {
|
||||
navController.navigateToStartRegistration(
|
||||
navOptions = navOptions {
|
||||
popUpTo(LANDING_ROUTE)
|
||||
popUpTo(route = LandingRoute)
|
||||
},
|
||||
)
|
||||
},
|
||||
onNavigateToLogin = {
|
||||
navController.navigateToLanding(
|
||||
navOptions = navOptions {
|
||||
popUpTo(LANDING_ROUTE)
|
||||
popUpTo(route = LandingRoute)
|
||||
},
|
||||
)
|
||||
},
|
||||
@ -226,5 +230,5 @@ fun NavGraphBuilder.authGraph(
|
||||
fun NavController.navigateToAuthGraph(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(AUTH_GRAPH_ROUTE, navOptions)
|
||||
navigate(route = AuthGraphRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -6,19 +6,24 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val EMAIL: String = "email"
|
||||
private const val CHECK_EMAIL_ROUTE: String = "check_email/{$EMAIL}"
|
||||
/**
|
||||
* The type-safe route for the check email screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class CheckEmailRoute(
|
||||
val emailAddress: String,
|
||||
)
|
||||
|
||||
/**
|
||||
* Navigate to the check email screen.
|
||||
*/
|
||||
fun NavController.navigateToCheckEmail(emailAddress: String, navOptions: NavOptions? = null) {
|
||||
this.navigate("check_email/$emailAddress", navOptions)
|
||||
this.navigate(route = CheckEmailRoute(emailAddress = emailAddress), navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -26,10 +31,14 @@ fun NavController.navigateToCheckEmail(emailAddress: String, navOptions: NavOpti
|
||||
*/
|
||||
data class CheckEmailArgs(
|
||||
val emailAddress: String,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
emailAddress = checkNotNull(savedStateHandle.get<String>(EMAIL)),
|
||||
)
|
||||
|
||||
/**
|
||||
* Constructs a [CheckEmailArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toCheckEmailArgs(): CheckEmailArgs {
|
||||
val route = this.toRoute<CheckEmailRoute>()
|
||||
return CheckEmailArgs(emailAddress = route.emailAddress)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,12 +47,7 @@ data class CheckEmailArgs(
|
||||
fun NavGraphBuilder.checkEmailDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = CHECK_EMAIL_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(EMAIL) { type = NavType.StringType },
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<CheckEmailRoute> {
|
||||
CheckEmailScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
|
||||
@ -21,7 +21,7 @@ class CheckEmailViewModel @Inject constructor(
|
||||
) : BaseViewModel<CheckEmailState, CheckEmailEvent, CheckEmailAction>(
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: CheckEmailState(
|
||||
email = CheckEmailArgs(savedStateHandle).emailAddress,
|
||||
email = savedStateHandle.toCheckEmailArgs().emailAddress,
|
||||
),
|
||||
) {
|
||||
init {
|
||||
|
||||
@ -6,17 +6,20 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val EMAIL_ADDRESS: String = "email_address"
|
||||
private const val VERIFICATION_TOKEN: String = "verification_token"
|
||||
private const val FROM_EMAIL: String = "from_email"
|
||||
private const val COMPLETE_REGISTRATION_PREFIX = "complete_registration"
|
||||
private const val COMPLETE_REGISTRATION_ROUTE =
|
||||
"$COMPLETE_REGISTRATION_PREFIX/{$EMAIL_ADDRESS}/{$VERIFICATION_TOKEN}/{$FROM_EMAIL}"
|
||||
/**
|
||||
* The type-safe route for the complete registration screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class CompleteRegistrationRoute(
|
||||
val emailAddress: String,
|
||||
val verificationToken: String,
|
||||
val fromEmail: Boolean,
|
||||
)
|
||||
|
||||
/**
|
||||
* Class to retrieve complete registration arguments from the [SavedStateHandle].
|
||||
@ -25,11 +28,17 @@ data class CompleteRegistrationArgs(
|
||||
val emailAddress: String,
|
||||
val verificationToken: String,
|
||||
val fromEmail: Boolean,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
emailAddress = checkNotNull(savedStateHandle.get<String>(EMAIL_ADDRESS)),
|
||||
verificationToken = checkNotNull(savedStateHandle.get<String>(VERIFICATION_TOKEN)),
|
||||
fromEmail = checkNotNull(savedStateHandle.get<Boolean>(FROM_EMAIL)),
|
||||
)
|
||||
|
||||
/**
|
||||
* Constructs a [CompleteRegistrationArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toCompleteRegistrationArgs(): CompleteRegistrationArgs {
|
||||
val route = this.toRoute<CompleteRegistrationRoute>()
|
||||
return CompleteRegistrationArgs(
|
||||
emailAddress = route.emailAddress,
|
||||
verificationToken = route.verificationToken,
|
||||
fromEmail = route.fromEmail,
|
||||
)
|
||||
}
|
||||
|
||||
@ -43,8 +52,12 @@ fun NavController.navigateToCompleteRegistration(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
this.navigate(
|
||||
"$COMPLETE_REGISTRATION_PREFIX/$emailAddress/$verificationToken/$fromEmail",
|
||||
navOptions,
|
||||
route = CompleteRegistrationRoute(
|
||||
emailAddress = emailAddress,
|
||||
verificationToken = verificationToken,
|
||||
fromEmail = fromEmail,
|
||||
),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
@ -57,14 +70,7 @@ fun NavGraphBuilder.completeRegistrationDestination(
|
||||
onNavigateToPreventAccountLockout: () -> Unit,
|
||||
onNavigateToLogin: (email: String, token: String?) -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = COMPLETE_REGISTRATION_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(EMAIL_ADDRESS) { type = NavType.StringType },
|
||||
navArgument(VERIFICATION_TOKEN) { type = NavType.StringType },
|
||||
navArgument(FROM_EMAIL) { type = NavType.BoolType },
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<CompleteRegistrationRoute> {
|
||||
CompleteRegistrationScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToPasswordGuidance = onNavigateToPasswordGuidance,
|
||||
@ -78,5 +84,5 @@ fun NavGraphBuilder.completeRegistrationDestination(
|
||||
* Pop up to the complete registration screen.
|
||||
*/
|
||||
fun NavController.popUpToCompleteRegistration() {
|
||||
popBackStack(route = COMPLETE_REGISTRATION_ROUTE, inclusive = false)
|
||||
this.popBackStack(route = CompleteRegistrationRoute, inclusive = false)
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ class CompleteRegistrationViewModel @Inject constructor(
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||
) : BaseViewModel<CompleteRegistrationState, CompleteRegistrationEvent, CompleteRegistrationAction>(
|
||||
initialState = savedStateHandle[KEY_STATE] ?: run {
|
||||
val args = CompleteRegistrationArgs(savedStateHandle)
|
||||
val args = savedStateHandle.toCompleteRegistrationArgs()
|
||||
CompleteRegistrationState(
|
||||
userEmail = args.emailAddress,
|
||||
emailVerificationToken = args.verificationToken,
|
||||
|
||||
@ -7,14 +7,19 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val CREATE_ACCOUNT_ROUTE = "create_account"
|
||||
/**
|
||||
* The type-safe route for the create account screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object CreateAccountRoute
|
||||
|
||||
/**
|
||||
* Navigate to the create account screen.
|
||||
*/
|
||||
fun NavController.navigateToCreateAccount(navOptions: NavOptions? = null) {
|
||||
this.navigate(CREATE_ACCOUNT_ROUTE, navOptions)
|
||||
this.navigate(route = CreateAccountRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -24,9 +29,7 @@ fun NavGraphBuilder.createAccountDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToLogin: (emailAddress: String, captchaToken: String) -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = CREATE_ACCOUNT_ROUTE,
|
||||
) {
|
||||
composableWithSlideTransitions<CreateAccountRoute> {
|
||||
CreateAccountScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToLogin = onNavigateToLogin,
|
||||
|
||||
@ -6,22 +6,30 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val ENTERPRISE_SIGN_ON_PREFIX = "enterprise_sign_on "
|
||||
private const val EMAIL_ADDRESS: String = "email_address"
|
||||
private const val ENTERPRISE_SIGN_ON_ROUTE = "$ENTERPRISE_SIGN_ON_PREFIX/{$EMAIL_ADDRESS}"
|
||||
/**
|
||||
* The type-safe route for the enterprise sign-on screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class EnterpriseSignOnRoute(
|
||||
val emailAddress: String,
|
||||
)
|
||||
|
||||
/**
|
||||
* Class to retrieve login arguments from the [SavedStateHandle].
|
||||
*/
|
||||
data class EnterpriseSignOnArgs(val emailAddress: String) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
checkNotNull(savedStateHandle[EMAIL_ADDRESS]) as String,
|
||||
)
|
||||
data class EnterpriseSignOnArgs(val emailAddress: String)
|
||||
|
||||
/**
|
||||
* Constructs a [EnterpriseSignOnArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toEnterpriseSignOnArgs(): EnterpriseSignOnArgs {
|
||||
val route = this.toRoute<EnterpriseSignOnRoute>()
|
||||
return EnterpriseSignOnArgs(emailAddress = route.emailAddress)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -31,7 +39,10 @@ fun NavController.navigateToEnterpriseSignOn(
|
||||
emailAddress: String,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
this.navigate("$ENTERPRISE_SIGN_ON_PREFIX/$emailAddress", navOptions)
|
||||
this.navigate(
|
||||
route = EnterpriseSignOnRoute(emailAddress = emailAddress),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,12 +53,7 @@ fun NavGraphBuilder.enterpriseSignOnDestination(
|
||||
onNavigateToSetPassword: () -> Unit,
|
||||
onNavigateToTwoFactorLogin: (emailAddress: String, orgIdentifier: String) -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = ENTERPRISE_SIGN_ON_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(EMAIL_ADDRESS) { type = NavType.StringType },
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<EnterpriseSignOnRoute> {
|
||||
EnterpriseSignOnScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToSetPassword = onNavigateToSetPassword,
|
||||
|
||||
@ -191,7 +191,7 @@ class EnterpriseSignOnViewModel @Inject constructor(
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
sendEvent(
|
||||
EnterpriseSignOnEvent.NavigateToTwoFactorLogin(
|
||||
emailAddress = EnterpriseSignOnArgs(savedStateHandle).emailAddress,
|
||||
emailAddress = savedStateHandle.toEnterpriseSignOnArgs().emailAddress,
|
||||
orgIdentifier = state.orgIdentifierInput,
|
||||
),
|
||||
)
|
||||
@ -434,7 +434,7 @@ class EnterpriseSignOnViewModel @Inject constructor(
|
||||
viewModelScope.launch {
|
||||
val result = authRepository
|
||||
.login(
|
||||
email = EnterpriseSignOnArgs(savedStateHandle).emailAddress,
|
||||
email = savedStateHandle.toEnterpriseSignOnArgs().emailAddress,
|
||||
ssoCode = ssoCallbackResult.code,
|
||||
ssoCodeVerifier = ssoData.codeVerifier,
|
||||
ssoRedirectUri = SSO_URI,
|
||||
@ -459,7 +459,7 @@ class EnterpriseSignOnViewModel @Inject constructor(
|
||||
viewModelScope.launch {
|
||||
if (featureFlagManager.getFeatureFlag(key = FlagKey.VerifiedSsoDomainEndpoint)) {
|
||||
val result = authRepository.getVerifiedOrganizationDomainSsoDetails(
|
||||
email = EnterpriseSignOnArgs(savedStateHandle).emailAddress,
|
||||
email = savedStateHandle.toEnterpriseSignOnArgs().emailAddress,
|
||||
)
|
||||
sendAction(
|
||||
EnterpriseSignOnAction.Internal.OnVerifiedOrganizationDomainSsoDetailsReceive(
|
||||
@ -468,7 +468,7 @@ class EnterpriseSignOnViewModel @Inject constructor(
|
||||
)
|
||||
} else {
|
||||
val result = authRepository.getOrganizationDomainSsoDetails(
|
||||
email = EnterpriseSignOnArgs(savedStateHandle).emailAddress,
|
||||
email = savedStateHandle.toEnterpriseSignOnArgs().emailAddress,
|
||||
)
|
||||
sendAction(
|
||||
EnterpriseSignOnAction.Internal.OnOrganizationDomainSsoDetailsReceive(result),
|
||||
|
||||
@ -7,25 +7,28 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
|
||||
private const val ENVIRONMENT_ROUTE = "environment"
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Add settings destinations to the nav graph.
|
||||
* The type-safe route for the environment screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object EnvironmentRoute
|
||||
|
||||
/**
|
||||
* Add the environment destination to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.environmentDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = ENVIRONMENT_ROUTE,
|
||||
) {
|
||||
composableWithSlideTransitions<EnvironmentRoute> {
|
||||
EnvironmentScreen(onNavigateBack = onNavigateBack)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the about screen.
|
||||
* Navigate to the environment screen.
|
||||
*/
|
||||
fun NavController.navigateToEnvironment(navOptions: NavOptions? = null) {
|
||||
navigate(ENVIRONMENT_ROUTE, navOptions)
|
||||
this.navigate(route = EnvironmentRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -7,14 +7,19 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val EXPIRED_REGISTRATION_LINK_ROUTE = "expired_registration_link"
|
||||
/**
|
||||
* The type-safe route for the expired registration link screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object ExpiredRegistrationLinkRoute
|
||||
|
||||
/**
|
||||
* Navigate to the expired registration link screen.
|
||||
*/
|
||||
fun NavController.navigateToExpiredRegistrationLinkScreen(navOptions: NavOptions? = null) {
|
||||
this.navigate(route = EXPIRED_REGISTRATION_LINK_ROUTE, navOptions = navOptions)
|
||||
this.navigate(route = ExpiredRegistrationLinkRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,9 +30,7 @@ fun NavGraphBuilder.expiredRegistrationLinkDestination(
|
||||
onNavigateToStartRegistration: () -> Unit,
|
||||
onNavigateToLogin: () -> Unit,
|
||||
) {
|
||||
composableWithPushTransitions(
|
||||
route = EXPIRED_REGISTRATION_LINK_ROUTE,
|
||||
) {
|
||||
composableWithPushTransitions<ExpiredRegistrationLinkRoute> {
|
||||
ExpiredRegistrationLinkScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToStartRegistration = onNavigateToStartRegistration,
|
||||
|
||||
@ -7,14 +7,19 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithStayTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
const val LANDING_ROUTE: String = "landing"
|
||||
/**
|
||||
* The type-safe route for the landing screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object LandingRoute
|
||||
|
||||
/**
|
||||
* Navigate to the landing screen.
|
||||
*/
|
||||
fun NavController.navigateToLanding(navOptions: NavOptions? = null) {
|
||||
this.navigate(LANDING_ROUTE, navOptions)
|
||||
this.navigate(route = LandingRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -27,9 +32,7 @@ fun NavGraphBuilder.landingDestination(
|
||||
onNavigateToStartRegistration: () -> Unit,
|
||||
onNavigateToPreAuthSettings: () -> Unit,
|
||||
) {
|
||||
composableWithStayTransitions(
|
||||
route = LANDING_ROUTE,
|
||||
) {
|
||||
composableWithStayTransitions<LandingRoute> {
|
||||
LandingScreen(
|
||||
onNavigateToCreateAccount = onNavigateToCreateAccount,
|
||||
onNavigateToLogin = onNavigateToLogin,
|
||||
|
||||
@ -6,22 +6,33 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val EMAIL_ADDRESS: String = "email_address"
|
||||
private const val CAPTCHA_TOKEN = "captcha_token"
|
||||
private const val LOGIN_ROUTE: String = "login/{$EMAIL_ADDRESS}?$CAPTCHA_TOKEN={$CAPTCHA_TOKEN}"
|
||||
/**
|
||||
* The type-safe route for the login screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class LoginRoute(
|
||||
val emailAddress: String,
|
||||
val captchaToken: String?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Class to retrieve login arguments from the [SavedStateHandle].
|
||||
*/
|
||||
data class LoginArgs(val emailAddress: String, val captchaToken: String?) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
checkNotNull(savedStateHandle[EMAIL_ADDRESS]) as String,
|
||||
savedStateHandle[CAPTCHA_TOKEN],
|
||||
data class LoginArgs(val emailAddress: String, val captchaToken: String?)
|
||||
|
||||
/**
|
||||
* Constructs a [LoginArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toLoginArgs(): LoginArgs {
|
||||
val route = this.toRoute<LoginRoute>()
|
||||
return LoginArgs(
|
||||
emailAddress = route.emailAddress,
|
||||
captchaToken = route.captchaToken,
|
||||
)
|
||||
}
|
||||
|
||||
@ -34,8 +45,8 @@ fun NavController.navigateToLogin(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
this.navigate(
|
||||
"login/$emailAddress?$CAPTCHA_TOKEN=$captchaToken",
|
||||
navOptions,
|
||||
route = LoginRoute(emailAddress = emailAddress, captchaToken = captchaToken),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
@ -53,16 +64,7 @@ fun NavGraphBuilder.loginDestination(
|
||||
isNewDeviceVerification: Boolean,
|
||||
) -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = LOGIN_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(EMAIL_ADDRESS) { type = NavType.StringType },
|
||||
navArgument(CAPTCHA_TOKEN) {
|
||||
type = NavType.StringType
|
||||
nullable = true
|
||||
},
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<LoginRoute> {
|
||||
LoginScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToMasterPasswordHint = onNavigateToMasterPasswordHint,
|
||||
|
||||
@ -6,6 +6,8 @@ import android.net.Uri
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.ui.util.Text
|
||||
import com.bitwarden.ui.util.asText
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.KnownDeviceResult
|
||||
@ -16,8 +18,6 @@ import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.bitwarden.ui.util.Text
|
||||
import com.bitwarden.ui.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummaries
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
@ -43,16 +43,23 @@ class LoginViewModel @Inject constructor(
|
||||
) : BaseViewModel<LoginState, LoginEvent, LoginAction>(
|
||||
// We load the state from the savedStateHandle for testing purposes.
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: LoginState(
|
||||
emailAddress = LoginArgs(savedStateHandle).emailAddress,
|
||||
?: run {
|
||||
val args = savedStateHandle.toLoginArgs()
|
||||
LoginState(
|
||||
emailAddress = args.emailAddress,
|
||||
isLoginButtonEnabled = false,
|
||||
passwordInput = "",
|
||||
environmentLabel = environmentRepository.environment.label,
|
||||
dialogState = LoginState.DialogState.Loading(R.string.loading.asText()),
|
||||
captchaToken = LoginArgs(savedStateHandle).captchaToken,
|
||||
accountSummaries = authRepository.userStateFlow.value?.toAccountSummaries().orEmpty(),
|
||||
captchaToken = args.captchaToken,
|
||||
accountSummaries = authRepository
|
||||
.userStateFlow
|
||||
.value
|
||||
?.toAccountSummaries()
|
||||
.orEmpty(),
|
||||
shouldShowLoginWithDevice = false,
|
||||
),
|
||||
)
|
||||
},
|
||||
) {
|
||||
|
||||
init {
|
||||
|
||||
@ -6,17 +6,20 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.model.LoginWithDeviceType
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val EMAIL_ADDRESS: String = "email_address"
|
||||
private const val LOGIN_WITH_DEVICE_PREFIX = "login_with_device"
|
||||
private const val LOGIN_TYPE: String = "login_type"
|
||||
private const val LOGIN_WITH_DEVICE_ROUTE =
|
||||
"$LOGIN_WITH_DEVICE_PREFIX/{$EMAIL_ADDRESS}/{$LOGIN_TYPE}"
|
||||
/**
|
||||
* The type-safe route for the login with device screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class LoginWithDeviceRoute(
|
||||
val emailAddress: String,
|
||||
val loginType: LoginWithDeviceType,
|
||||
)
|
||||
|
||||
/**
|
||||
* Class to retrieve login with device arguments from the [SavedStateHandle].
|
||||
@ -24,11 +27,14 @@ private const val LOGIN_WITH_DEVICE_ROUTE =
|
||||
data class LoginWithDeviceArgs(
|
||||
val emailAddress: String,
|
||||
val loginType: LoginWithDeviceType,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
emailAddress = checkNotNull(savedStateHandle.get<String>(EMAIL_ADDRESS)),
|
||||
loginType = checkNotNull(savedStateHandle.get<LoginWithDeviceType>(LOGIN_TYPE)),
|
||||
)
|
||||
|
||||
/**
|
||||
* Constructs a [LoginWithDeviceArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toLoginWithDeviceArgs(): LoginWithDeviceArgs {
|
||||
val route = this.toRoute<LoginWithDeviceRoute>()
|
||||
return LoginWithDeviceArgs(emailAddress = route.emailAddress, loginType = route.loginType)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,7 +46,10 @@ fun NavController.navigateToLoginWithDevice(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
this.navigate(
|
||||
route = "$LOGIN_WITH_DEVICE_PREFIX/$emailAddress/$loginType",
|
||||
route = LoginWithDeviceRoute(
|
||||
emailAddress = emailAddress,
|
||||
loginType = loginType,
|
||||
),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
@ -52,13 +61,7 @@ fun NavGraphBuilder.loginWithDeviceDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToTwoFactorLogin: (emailAddress: String) -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = LOGIN_WITH_DEVICE_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(EMAIL_ADDRESS) { type = NavType.StringType },
|
||||
navArgument(LOGIN_TYPE) { type = NavType.EnumType(LoginWithDeviceType::class.java) },
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<LoginWithDeviceRoute> {
|
||||
LoginWithDeviceScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToTwoFactorLogin = onNavigateToTwoFactorLogin,
|
||||
|
||||
@ -4,6 +4,8 @@ import android.net.Uri
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.ui.util.Text
|
||||
import com.bitwarden.ui.util.asText
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.manager.model.CreateAuthRequestResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
@ -14,8 +16,6 @@ import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.model.LoginWithDevice
|
||||
import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.util.toAuthRequestType
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.BackgroundEvent
|
||||
import com.bitwarden.ui.util.Text
|
||||
import com.bitwarden.ui.util.asText
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@ -39,7 +39,7 @@ class LoginWithDeviceViewModel @Inject constructor(
|
||||
) : BaseViewModel<LoginWithDeviceState, LoginWithDeviceEvent, LoginWithDeviceAction>(
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: run {
|
||||
val args = LoginWithDeviceArgs(savedStateHandle)
|
||||
val args = savedStateHandle.toLoginWithDeviceArgs()
|
||||
LoginWithDeviceState(
|
||||
loginWithDeviceType = args.loginType,
|
||||
emailAddress = args.emailAddress,
|
||||
|
||||
@ -7,14 +7,19 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val MASTER_PASSWORD_GENERATOR = "master_password_generator"
|
||||
/**
|
||||
* The type-safe route for the master password generator screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object MasterPasswordGeneratorRoute
|
||||
|
||||
/**
|
||||
* Navigate to master password generator screen.
|
||||
*/
|
||||
fun NavController.navigateToMasterPasswordGenerator(navOptions: NavOptions? = null) {
|
||||
this.navigate(MASTER_PASSWORD_GENERATOR, navOptions)
|
||||
this.navigate(route = MasterPasswordGeneratorRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,9 +30,7 @@ fun NavGraphBuilder.masterPasswordGeneratorDestination(
|
||||
onNavigateToPreventLockout: () -> Unit,
|
||||
onNavigateBackWithPassword: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = MASTER_PASSWORD_GENERATOR,
|
||||
) {
|
||||
composableWithSlideTransitions<MasterPasswordGeneratorRoute> {
|
||||
MasterPasswordGeneratorScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToPreventLockout = onNavigateToPreventLockout,
|
||||
|
||||
@ -7,14 +7,19 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val MASTER_PASSWORD_GUIDANCE = "master_password_guidance"
|
||||
/**
|
||||
* The type-safe route for the master password guidance screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object MasterPasswordGuidanceRoute
|
||||
|
||||
/**
|
||||
* Navigate to the master password guidance screen.
|
||||
*/
|
||||
fun NavController.navigateToMasterPasswordGuidance(navOptions: NavOptions? = null) {
|
||||
this.navigate(MASTER_PASSWORD_GUIDANCE, navOptions)
|
||||
this.navigate(route = MasterPasswordGuidanceRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -24,9 +29,7 @@ fun NavGraphBuilder.masterPasswordGuidanceDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToGeneratePassword: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = MASTER_PASSWORD_GUIDANCE,
|
||||
) {
|
||||
composableWithSlideTransitions<MasterPasswordGuidanceRoute> {
|
||||
MasterPasswordGuidanceScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToGeneratePassword = onNavigateToGeneratePassword,
|
||||
|
||||
@ -6,21 +6,30 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val EMAIL_ADDRESS: String = "email_address"
|
||||
private const val MASTER_PASSWORD_HINT_ROUTE: String = "master_password_hint/{$EMAIL_ADDRESS}"
|
||||
/**
|
||||
* The type-safe route for the master password hint screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class MasterPasswordHintRoute(
|
||||
val emailAddress: String,
|
||||
)
|
||||
|
||||
/**
|
||||
* Class to retrieve login arguments from the [SavedStateHandle].
|
||||
*/
|
||||
data class MasterPasswordHintArgs(val emailAddress: String) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
checkNotNull(savedStateHandle[EMAIL_ADDRESS]) as String,
|
||||
)
|
||||
data class MasterPasswordHintArgs(val emailAddress: String)
|
||||
|
||||
/**
|
||||
* Constructs a [MasterPasswordHintArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toMasterPasswordHintArgs(): MasterPasswordHintArgs {
|
||||
val route = this.toRoute<MasterPasswordHintRoute>()
|
||||
return MasterPasswordHintArgs(emailAddress = route.emailAddress)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -30,7 +39,10 @@ fun NavController.navigateToMasterPasswordHint(
|
||||
emailAddress: String,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
this.navigate("master_password_hint/$emailAddress", navOptions)
|
||||
this.navigate(
|
||||
route = MasterPasswordHintRoute(emailAddress = emailAddress),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,12 +51,7 @@ fun NavController.navigateToMasterPasswordHint(
|
||||
fun NavGraphBuilder.masterPasswordHintDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = MASTER_PASSWORD_HINT_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(EMAIL_ADDRESS) { type = NavType.StringType },
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<MasterPasswordHintRoute> {
|
||||
MasterPasswordHintScreen(onNavigateBack = onNavigateBack)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,13 +3,13 @@ package com.x8bit.bitwarden.ui.auth.feature.masterpasswordhint
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.ui.util.Text
|
||||
import com.bitwarden.ui.util.asText
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.PasswordHintResult
|
||||
import com.x8bit.bitwarden.data.platform.manager.network.NetworkConnectionManager
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.bitwarden.ui.util.Text
|
||||
import com.bitwarden.ui.util.asText
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
@ -31,7 +31,7 @@ class MasterPasswordHintViewModel @Inject constructor(
|
||||
) : BaseViewModel<MasterPasswordHintState, MasterPasswordHintEvent, MasterPasswordHintAction>(
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: MasterPasswordHintState(
|
||||
emailInput = MasterPasswordHintArgs(savedStateHandle).emailAddress,
|
||||
emailInput = savedStateHandle.toMasterPasswordHintArgs().emailAddress,
|
||||
),
|
||||
) {
|
||||
init {
|
||||
|
||||
@ -7,14 +7,19 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val PREVENT_ACCOUNT_LOCKOUT = "prevent_account_lockout"
|
||||
/**
|
||||
* The type-safe route for the prevent account lockout screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object PreventAccountLockoutRoute
|
||||
|
||||
/**
|
||||
* Navigate to prevent account lockout screen.
|
||||
*/
|
||||
fun NavController.navigateToPreventAccountLockout(navOptions: NavOptions? = null) {
|
||||
this.navigate(PREVENT_ACCOUNT_LOCKOUT, navOptions)
|
||||
this.navigate(route = PreventAccountLockoutRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -23,9 +28,7 @@ fun NavController.navigateToPreventAccountLockout(navOptions: NavOptions? = null
|
||||
fun NavGraphBuilder.preventAccountLockoutDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = PREVENT_ACCOUNT_LOCKOUT,
|
||||
) {
|
||||
composableWithSlideTransitions<PreventAccountLockoutRoute> {
|
||||
PreventAccountLockoutScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
|
||||
@ -7,19 +7,19 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.compose.composable
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* The route for navigating to the [RemovePasswordScreen].
|
||||
* The type-safe route for the remove password screen.
|
||||
*/
|
||||
const val REMOVE_PASSWORD_ROUTE: String = "remove_password"
|
||||
@Serializable
|
||||
data object RemovePasswordRoute
|
||||
|
||||
/**
|
||||
* Add the Remove Password screen to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.removePasswordDestination() {
|
||||
composable(
|
||||
route = REMOVE_PASSWORD_ROUTE,
|
||||
) {
|
||||
composable<RemovePasswordRoute> {
|
||||
RemovePasswordScreen()
|
||||
}
|
||||
}
|
||||
@ -30,5 +30,5 @@ fun NavGraphBuilder.removePasswordDestination() {
|
||||
fun NavController.navigateToRemovePassword(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
this.navigate(REMOVE_PASSWORD_ROUTE, navOptions)
|
||||
this.navigate(route = RemovePasswordRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -7,8 +7,13 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.compose.composable
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
const val RESET_PASSWORD_ROUTE: String = "reset_password"
|
||||
/**
|
||||
* The type-safe route for the reset password screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object ResetPasswordRoute
|
||||
|
||||
/**
|
||||
* Add the Reset Password screen to the nav graph.
|
||||
@ -16,9 +21,7 @@ const val RESET_PASSWORD_ROUTE: String = "reset_password"
|
||||
fun NavGraphBuilder.resetPasswordDestination(
|
||||
onNavigateToPreventAccountLockOut: () -> Unit,
|
||||
) {
|
||||
composable(
|
||||
route = RESET_PASSWORD_ROUTE,
|
||||
) {
|
||||
composable<ResetPasswordRoute> {
|
||||
ResetPasswordScreen(onNavigateToPreventAccountLockOut = onNavigateToPreventAccountLockOut)
|
||||
}
|
||||
}
|
||||
@ -29,5 +32,5 @@ fun NavGraphBuilder.resetPasswordDestination(
|
||||
fun NavController.navigateToResetPasswordScreen(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
this.navigate(RESET_PASSWORD_ROUTE, navOptions)
|
||||
this.navigate(route = ResetPasswordRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -7,16 +7,19 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.compose.composable
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
const val SET_PASSWORD_ROUTE: String = "set_password"
|
||||
/**
|
||||
* The type-safe route for the set password screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object SetPasswordRoute
|
||||
|
||||
/**
|
||||
* Add the Set Password screen to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.setPasswordDestination() {
|
||||
composable(
|
||||
route = SET_PASSWORD_ROUTE,
|
||||
) {
|
||||
composable<SetPasswordRoute> {
|
||||
SetPasswordScreen()
|
||||
}
|
||||
}
|
||||
@ -27,5 +30,5 @@ fun NavGraphBuilder.setPasswordDestination() {
|
||||
fun NavController.navigateToSetPassword(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
this.navigate(SET_PASSWORD_ROUTE, navOptions)
|
||||
this.navigate(route = SetPasswordRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -7,14 +7,19 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val START_REGISTRATION_ROUTE = "start_registration"
|
||||
/**
|
||||
* The type-safe route for the start registration screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object StartRegistrationRoute
|
||||
|
||||
/**
|
||||
* Navigate to the start registration screen.
|
||||
*/
|
||||
fun NavController.navigateToStartRegistration(navOptions: NavOptions? = null) {
|
||||
this.navigate(START_REGISTRATION_ROUTE, navOptions)
|
||||
this.navigate(route = StartRegistrationRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -29,9 +34,7 @@ fun NavGraphBuilder.startRegistrationDestination(
|
||||
onNavigateToCheckEmail: (email: String) -> Unit,
|
||||
onNavigateToEnvironment: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = START_REGISTRATION_ROUTE,
|
||||
) {
|
||||
composableWithSlideTransitions<StartRegistrationRoute> {
|
||||
StartRegistrationScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToCompleteRegistration = onNavigateToCompleteRegistration,
|
||||
|
||||
@ -15,16 +15,20 @@ import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.navigateToTwoFactorLog
|
||||
import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.twoFactorLoginDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.navigateToTdeVaultUnlock
|
||||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.tdeVaultUnlockDestination
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
const val TRUSTED_DEVICE_GRAPH_ROUTE: String = "trusted_device_graph"
|
||||
/**
|
||||
* The type-safe route for the trusted device graph.
|
||||
*/
|
||||
@Serializable
|
||||
data object TrustedDeviceGraphRoute
|
||||
|
||||
/**
|
||||
* Add trusted device destinations to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.trustedDeviceGraph(navController: NavHostController) {
|
||||
navigation(
|
||||
startDestination = TRUSTED_DEVICE_ROUTE,
|
||||
route = TRUSTED_DEVICE_GRAPH_ROUTE,
|
||||
navigation<TrustedDeviceGraphRoute>(
|
||||
startDestination = TrustedDeviceRoute,
|
||||
) {
|
||||
loginWithDeviceDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
@ -66,5 +70,5 @@ fun NavGraphBuilder.trustedDeviceGraph(navController: NavHostController) {
|
||||
fun NavController.navigateToTrustedDeviceGraph(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(TRUSTED_DEVICE_GRAPH_ROUTE, navOptions)
|
||||
navigate(route = TrustedDeviceGraphRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -7,11 +7,13 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* The route for navigating to the [TrustedDeviceScreen].
|
||||
* The type-safe route for the trusted device screen.
|
||||
*/
|
||||
const val TRUSTED_DEVICE_ROUTE: String = "trusted_device"
|
||||
@Serializable
|
||||
data object TrustedDeviceRoute
|
||||
|
||||
/**
|
||||
* Add the Trusted Device Screen to the nav graph.
|
||||
@ -21,9 +23,7 @@ fun NavGraphBuilder.trustedDeviceDestination(
|
||||
onNavigateToLoginWithOtherDevice: (emailAddress: String) -> Unit,
|
||||
onNavigateToLock: (emailAddress: String) -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = TRUSTED_DEVICE_ROUTE,
|
||||
) {
|
||||
composableWithSlideTransitions<TrustedDeviceRoute> {
|
||||
TrustedDeviceScreen(
|
||||
onNavigateToAdminApproval = onNavigateToAdminApproval,
|
||||
onNavigateToLoginWithOtherDevice = onNavigateToLoginWithOtherDevice,
|
||||
@ -38,5 +38,5 @@ fun NavGraphBuilder.trustedDeviceDestination(
|
||||
fun NavController.navigateToTrustedDevice(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
this.navigate(TRUSTED_DEVICE_ROUTE, navOptions)
|
||||
this.navigate(route = TrustedDeviceRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -6,23 +6,21 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.bitwarden.network.util.base64UrlDecodeOrNull
|
||||
import com.bitwarden.network.util.base64UrlEncode
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val EMAIL_ADDRESS = "email_address"
|
||||
private const val PASSWORD = "password"
|
||||
private const val ORG_IDENTIFIER = "org_identifier"
|
||||
private const val TWO_FACTOR_LOGIN_PREFIX = "two_factor_login"
|
||||
private const val NEW_DEVICE_VERIFICATION = "new_device_verification"
|
||||
private const val TWO_FACTOR_LOGIN_ROUTE =
|
||||
"$TWO_FACTOR_LOGIN_PREFIX/{$EMAIL_ADDRESS}?" +
|
||||
"$PASSWORD={$PASSWORD}&" +
|
||||
"$ORG_IDENTIFIER={$ORG_IDENTIFIER}&" +
|
||||
"$NEW_DEVICE_VERIFICATION={$NEW_DEVICE_VERIFICATION}"
|
||||
/**
|
||||
* The type-safe route for the two-factor login screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class TwoFactorLoginRoute(
|
||||
val emailAddress: String,
|
||||
val password: String?,
|
||||
val orgIdentifier: String?,
|
||||
val isNewDeviceVerification: Boolean,
|
||||
)
|
||||
|
||||
/**
|
||||
* Class to retrieve Two-Factor Login arguments from the [SavedStateHandle].
|
||||
@ -32,12 +30,18 @@ data class TwoFactorLoginArgs(
|
||||
val password: String?,
|
||||
val orgIdentifier: String?,
|
||||
val isNewDeviceVerification: Boolean,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
emailAddress = checkNotNull(savedStateHandle[EMAIL_ADDRESS]) as String,
|
||||
password = savedStateHandle.get<String>(PASSWORD)?.base64UrlDecodeOrNull(),
|
||||
orgIdentifier = savedStateHandle.get<String>(ORG_IDENTIFIER)?.base64UrlDecodeOrNull(),
|
||||
isNewDeviceVerification = savedStateHandle.get<Boolean>(NEW_DEVICE_VERIFICATION) ?: false,
|
||||
)
|
||||
|
||||
/**
|
||||
* Constructs a [TwoFactorLoginArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toTwoFactorLoginArgs(): TwoFactorLoginArgs {
|
||||
val route = this.toRoute<TwoFactorLoginRoute>()
|
||||
return TwoFactorLoginArgs(
|
||||
emailAddress = route.emailAddress,
|
||||
password = route.password,
|
||||
orgIdentifier = route.orgIdentifier,
|
||||
isNewDeviceVerification = route.isNewDeviceVerification,
|
||||
)
|
||||
}
|
||||
|
||||
@ -48,14 +52,16 @@ fun NavController.navigateToTwoFactorLogin(
|
||||
emailAddress: String,
|
||||
password: String?,
|
||||
orgIdentifier: String?,
|
||||
navOptions: NavOptions? = null,
|
||||
isNewDeviceVerification: Boolean = false,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
this.navigate(
|
||||
route = "$TWO_FACTOR_LOGIN_PREFIX/$emailAddress?" +
|
||||
"$PASSWORD=${password?.base64UrlEncode()}&" +
|
||||
"$ORG_IDENTIFIER=${orgIdentifier?.base64UrlEncode()}&" +
|
||||
"$NEW_DEVICE_VERIFICATION=$isNewDeviceVerification",
|
||||
route = TwoFactorLoginRoute(
|
||||
emailAddress = emailAddress,
|
||||
password = password,
|
||||
orgIdentifier = orgIdentifier,
|
||||
isNewDeviceVerification = isNewDeviceVerification,
|
||||
),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
@ -66,23 +72,7 @@ fun NavController.navigateToTwoFactorLogin(
|
||||
fun NavGraphBuilder.twoFactorLoginDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = TWO_FACTOR_LOGIN_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(EMAIL_ADDRESS) { type = NavType.StringType },
|
||||
navArgument(PASSWORD) {
|
||||
type = NavType.StringType
|
||||
nullable = true
|
||||
},
|
||||
navArgument(ORG_IDENTIFIER) {
|
||||
type = NavType.StringType
|
||||
nullable = true
|
||||
},
|
||||
navArgument(NEW_DEVICE_VERIFICATION) {
|
||||
type = NavType.BoolType
|
||||
},
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<TwoFactorLoginRoute> {
|
||||
TwoFactorLoginScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
|
||||
@ -57,7 +57,7 @@ class TwoFactorLoginViewModel @Inject constructor(
|
||||
) : BaseViewModel<TwoFactorLoginState, TwoFactorLoginEvent, TwoFactorLoginAction>(
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: run {
|
||||
val args = TwoFactorLoginArgs(savedStateHandle)
|
||||
val args = savedStateHandle.toTwoFactorLoginArgs()
|
||||
TwoFactorLoginState(
|
||||
authMethod = authRepository.twoFactorResponse.preferredAuthMethod,
|
||||
availableAuthMethods = authRepository.twoFactorResponse.availableAuthMethods,
|
||||
|
||||
@ -6,28 +6,55 @@ import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navArgument
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.model.UnlockType
|
||||
import com.x8bit.bitwarden.ui.platform.util.toObjectRoute
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val VAULT_UNLOCK_TYPE: String = "unlock_type"
|
||||
private const val TDE_VAULT_UNLOCK_ROUTE_PREFIX: String = "tde_vault_unlock"
|
||||
private const val TDE_VAULT_UNLOCK_ROUTE: String =
|
||||
"$TDE_VAULT_UNLOCK_ROUTE_PREFIX/{$VAULT_UNLOCK_TYPE}"
|
||||
private const val VAULT_UNLOCK_ROUTE_PREFIX: String = "vault_unlock"
|
||||
const val VAULT_UNLOCK_ROUTE: String = "$VAULT_UNLOCK_ROUTE_PREFIX/{$VAULT_UNLOCK_TYPE}"
|
||||
/**
|
||||
* The type-safe route for the vault unlock screen.
|
||||
*/
|
||||
@Serializable
|
||||
sealed class VaultUnlockRoute {
|
||||
/**
|
||||
* The underlying [UnlockType] used in the vault unlock screen.
|
||||
*/
|
||||
abstract val unlockType: UnlockType
|
||||
|
||||
/**
|
||||
* The type-safe route for the standard vault unlock screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object Standard : VaultUnlockRoute() {
|
||||
override val unlockType: UnlockType get() = UnlockType.STANDARD
|
||||
}
|
||||
|
||||
/**
|
||||
* The type-safe route for the TDE vault unlock screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object Tde : VaultUnlockRoute() {
|
||||
override val unlockType: UnlockType get() = UnlockType.TDE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to retrieve vault unlock arguments from the [SavedStateHandle].
|
||||
*/
|
||||
data class VaultUnlockArgs(
|
||||
val unlockType: UnlockType,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
unlockType = checkNotNull(savedStateHandle.get<UnlockType>(VAULT_UNLOCK_TYPE)),
|
||||
)
|
||||
|
||||
/**
|
||||
* Constructs a [VaultUnlockArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toVaultUnlockArgs(): VaultUnlockArgs {
|
||||
val route = this.toObjectRoute<VaultUnlockRoute.Tde>()
|
||||
?: this.toObjectRoute<VaultUnlockRoute.Standard>()
|
||||
return route
|
||||
?.let { VaultUnlockArgs(unlockType = it.unlockType) }
|
||||
?: throw IllegalStateException("Missing correct route for VaultUnlockScreen")
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,7 +64,7 @@ fun NavController.navigateToVaultUnlock(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(
|
||||
route = "$VAULT_UNLOCK_ROUTE_PREFIX/${UnlockType.STANDARD}",
|
||||
route = VaultUnlockRoute.Standard,
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
@ -46,12 +73,7 @@ fun NavController.navigateToVaultUnlock(
|
||||
* Add the Vault Unlock screen to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.vaultUnlockDestination() {
|
||||
composable(
|
||||
route = VAULT_UNLOCK_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(VAULT_UNLOCK_TYPE) { type = NavType.EnumType(UnlockType::class.java) },
|
||||
),
|
||||
) {
|
||||
composable<VaultUnlockRoute.Standard> {
|
||||
VaultUnlockScreen()
|
||||
}
|
||||
}
|
||||
@ -63,7 +85,7 @@ fun NavController.navigateToTdeVaultUnlock(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(
|
||||
route = "$TDE_VAULT_UNLOCK_ROUTE_PREFIX/${UnlockType.TDE}",
|
||||
route = VaultUnlockRoute.Tde,
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
@ -72,12 +94,7 @@ fun NavController.navigateToTdeVaultUnlock(
|
||||
* Add the Vault Unlock screen to the TDE nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.tdeVaultUnlockDestination() {
|
||||
composable(
|
||||
route = TDE_VAULT_UNLOCK_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(VAULT_UNLOCK_TYPE) { type = NavType.EnumType(UnlockType::class.java) },
|
||||
),
|
||||
) {
|
||||
composable<VaultUnlockRoute.Tde> {
|
||||
VaultUnlockScreen()
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ class VaultUnlockViewModel @Inject constructor(
|
||||
val specialCircumstance = specialCircumstanceManager.specialCircumstance
|
||||
|
||||
val showAccountMenu =
|
||||
VaultUnlockArgs(savedStateHandle).unlockType == UnlockType.STANDARD &&
|
||||
savedStateHandle.toVaultUnlockArgs().unlockType == UnlockType.STANDARD &&
|
||||
(specialCircumstance !is SpecialCircumstance.Fido2GetCredentials &&
|
||||
specialCircumstance !is SpecialCircumstance.Fido2Assertion)
|
||||
VaultUnlockState(
|
||||
|
||||
@ -7,14 +7,19 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithStayTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val WELCOME_ROUTE: String = "welcome"
|
||||
/**
|
||||
* The type-safe route for the welcome screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object WelcomeRoute
|
||||
|
||||
/**
|
||||
* Navigate to the welcome screen.
|
||||
*/
|
||||
fun NavController.navigateToWelcome(navOptions: NavOptions? = null) {
|
||||
this.navigate(WELCOME_ROUTE, navOptions)
|
||||
this.navigate(route = WelcomeRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,9 +30,7 @@ fun NavGraphBuilder.welcomeDestination(
|
||||
onNavigateToLogin: () -> Unit,
|
||||
onNavigateToStartRegistration: () -> Unit,
|
||||
) {
|
||||
composableWithStayTransitions(
|
||||
route = WELCOME_ROUTE,
|
||||
) {
|
||||
composableWithStayTransitions<WelcomeRoute> {
|
||||
WelcomeScreen(
|
||||
onNavigateToCreateAccount = onNavigateToCreateAccount,
|
||||
onNavigateToLogin = onNavigateToLogin,
|
||||
|
||||
@ -1,28 +1,28 @@
|
||||
@file:OmitFromCoverage
|
||||
|
||||
package com.x8bit.bitwarden.ui.platform.base.util
|
||||
|
||||
import androidx.compose.animation.AnimatedContentScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.NamedNavArgument
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavDeepLink
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.composable
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.theme.TransitionProviders
|
||||
import kotlin.reflect.KType
|
||||
|
||||
/**
|
||||
* A wrapper around [NavGraphBuilder.composable] that supplies slide up/down transitions.
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
fun NavGraphBuilder.composableWithSlideTransitions(
|
||||
route: String,
|
||||
arguments: List<NamedNavArgument> = emptyList(),
|
||||
inline fun <reified T : Any> NavGraphBuilder.composableWithSlideTransitions(
|
||||
typeMap: Map<KType, @JvmSuppressWildcards NavType<*>> = emptyMap(),
|
||||
deepLinks: List<NavDeepLink> = emptyList(),
|
||||
content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
|
||||
noinline content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
|
||||
) {
|
||||
this.composable(
|
||||
route = route,
|
||||
arguments = arguments,
|
||||
this.composable<T>(
|
||||
typeMap = typeMap,
|
||||
deepLinks = deepLinks,
|
||||
enterTransition = TransitionProviders.Enter.slideUp,
|
||||
exitTransition = TransitionProviders.Exit.stay,
|
||||
@ -35,21 +35,19 @@ fun NavGraphBuilder.composableWithSlideTransitions(
|
||||
/**
|
||||
* A wrapper around [NavGraphBuilder.composable] that supplies "stay" transitions.
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
fun NavGraphBuilder.composableWithStayTransitions(
|
||||
route: String,
|
||||
arguments: List<NamedNavArgument> = emptyList(),
|
||||
inline fun <reified T : Any> NavGraphBuilder.composableWithStayTransitions(
|
||||
typeMap: Map<KType, @JvmSuppressWildcards NavType<*>> = emptyMap(),
|
||||
deepLinks: List<NavDeepLink> = emptyList(),
|
||||
content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
|
||||
noinline content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
|
||||
) {
|
||||
this.composable(
|
||||
route = route,
|
||||
arguments = arguments,
|
||||
this.composable<T>(
|
||||
typeMap = typeMap,
|
||||
deepLinks = deepLinks,
|
||||
enterTransition = TransitionProviders.Enter.stay,
|
||||
exitTransition = TransitionProviders.Exit.stay,
|
||||
popEnterTransition = TransitionProviders.Enter.stay,
|
||||
popExitTransition = TransitionProviders.Exit.stay,
|
||||
sizeTransform = null,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
@ -60,21 +58,19 @@ fun NavGraphBuilder.composableWithStayTransitions(
|
||||
* This is suitable for screens deeper within a hierarchy that uses push transitions; the root
|
||||
* screen of such a hierarchy should use [composableWithRootPushTransitions].
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
fun NavGraphBuilder.composableWithPushTransitions(
|
||||
route: String,
|
||||
arguments: List<NamedNavArgument> = emptyList(),
|
||||
inline fun <reified T : Any> NavGraphBuilder.composableWithPushTransitions(
|
||||
typeMap: Map<KType, @JvmSuppressWildcards NavType<*>> = emptyMap(),
|
||||
deepLinks: List<NavDeepLink> = emptyList(),
|
||||
content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
|
||||
noinline content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
|
||||
) {
|
||||
this.composable(
|
||||
route = route,
|
||||
arguments = arguments,
|
||||
this.composable<T>(
|
||||
typeMap = typeMap,
|
||||
deepLinks = deepLinks,
|
||||
enterTransition = TransitionProviders.Enter.pushLeft,
|
||||
exitTransition = TransitionProviders.Exit.stay,
|
||||
popEnterTransition = TransitionProviders.Enter.stay,
|
||||
popExitTransition = TransitionProviders.Exit.pushRight,
|
||||
sizeTransform = null,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
@ -83,21 +79,19 @@ fun NavGraphBuilder.composableWithPushTransitions(
|
||||
* A wrapper around [NavGraphBuilder.composable] that supplies push transitions to the root screen
|
||||
* in a nested graph that uses push transitions.
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
fun NavGraphBuilder.composableWithRootPushTransitions(
|
||||
route: String,
|
||||
arguments: List<NamedNavArgument> = emptyList(),
|
||||
inline fun <reified T : Any> NavGraphBuilder.composableWithRootPushTransitions(
|
||||
typeMap: Map<KType, @JvmSuppressWildcards NavType<*>> = emptyMap(),
|
||||
deepLinks: List<NavDeepLink> = emptyList(),
|
||||
content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
|
||||
noinline content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
|
||||
) {
|
||||
this.composable(
|
||||
route = route,
|
||||
arguments = arguments,
|
||||
this.composable<T>(
|
||||
typeMap = typeMap,
|
||||
deepLinks = deepLinks,
|
||||
enterTransition = TransitionProviders.Enter.stay,
|
||||
exitTransition = TransitionProviders.Exit.pushLeft,
|
||||
popEnterTransition = TransitionProviders.Enter.pushRight,
|
||||
popExitTransition = TransitionProviders.Exit.fadeOut,
|
||||
sizeTransform = null,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@ -6,14 +6,19 @@ import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val DEBUG_MENU = "debug_menu"
|
||||
/**
|
||||
* The type-safe route for the debug screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object DebugRoute
|
||||
|
||||
/**
|
||||
* Navigate to the setup unlock screen.
|
||||
*/
|
||||
fun NavController.navigateToDebugMenuScreen() {
|
||||
this.navigate(DEBUG_MENU) {
|
||||
this.navigate(route = DebugRoute) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
}
|
||||
@ -25,9 +30,7 @@ fun NavGraphBuilder.debugMenuDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
onSplashScreenRemoved: () -> Unit,
|
||||
) {
|
||||
composableWithPushTransitions(
|
||||
route = DEBUG_MENU,
|
||||
) {
|
||||
composableWithPushTransitions<DebugRoute> {
|
||||
DebugMenuScreen(onNavigateBack = onNavigateBack)
|
||||
// If we are displaying the debug screen, then we can just hide the splash screen.
|
||||
onSplashScreenRemoved()
|
||||
|
||||
@ -17,48 +17,49 @@ import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.navOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
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.SetupAutofillRoute
|
||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SetupCompleteRoute
|
||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SetupUnlockRoute
|
||||
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.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
|
||||
import com.x8bit.bitwarden.ui.auth.feature.auth.AuthGraphRoute
|
||||
import com.x8bit.bitwarden.ui.auth.feature.auth.authGraph
|
||||
import com.x8bit.bitwarden.ui.auth.feature.auth.navigateToAuthGraph
|
||||
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.navigateToCompleteRegistration
|
||||
import com.x8bit.bitwarden.ui.auth.feature.expiredregistrationlink.navigateToExpiredRegistrationLinkScreen
|
||||
import com.x8bit.bitwarden.ui.auth.feature.preventaccountlockout.navigateToPreventAccountLockout
|
||||
import com.x8bit.bitwarden.ui.auth.feature.removepassword.REMOVE_PASSWORD_ROUTE
|
||||
import com.x8bit.bitwarden.ui.auth.feature.removepassword.RemovePasswordRoute
|
||||
import com.x8bit.bitwarden.ui.auth.feature.removepassword.navigateToRemovePassword
|
||||
import com.x8bit.bitwarden.ui.auth.feature.removepassword.removePasswordDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.resetpassword.RESET_PASSWORD_ROUTE
|
||||
import com.x8bit.bitwarden.ui.auth.feature.resetpassword.ResetPasswordRoute
|
||||
import com.x8bit.bitwarden.ui.auth.feature.resetpassword.navigateToResetPasswordScreen
|
||||
import com.x8bit.bitwarden.ui.auth.feature.resetpassword.resetPasswordDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.setpassword.SET_PASSWORD_ROUTE
|
||||
import com.x8bit.bitwarden.ui.auth.feature.setpassword.SetPasswordRoute
|
||||
import com.x8bit.bitwarden.ui.auth.feature.setpassword.navigateToSetPassword
|
||||
import com.x8bit.bitwarden.ui.auth.feature.trusteddevice.TRUSTED_DEVICE_GRAPH_ROUTE
|
||||
import com.x8bit.bitwarden.ui.auth.feature.trusteddevice.TrustedDeviceGraphRoute
|
||||
import com.x8bit.bitwarden.ui.auth.feature.trusteddevice.navigateToTrustedDeviceGraph
|
||||
import com.x8bit.bitwarden.ui.auth.feature.trusteddevice.trustedDeviceGraph
|
||||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.VAULT_UNLOCK_ROUTE
|
||||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.VaultUnlockRoute
|
||||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.navigateToVaultUnlock
|
||||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.vaultUnlockDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.welcome.navigateToWelcome
|
||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberBitwardenNavController
|
||||
import com.x8bit.bitwarden.ui.platform.feature.rootnav.util.toVaultItemListingType
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.loginapproval.navigateToLoginApproval
|
||||
import com.x8bit.bitwarden.ui.platform.feature.splash.SPLASH_ROUTE
|
||||
import com.x8bit.bitwarden.ui.platform.feature.splash.SplashRoute
|
||||
import com.x8bit.bitwarden.ui.platform.feature.splash.navigateToSplash
|
||||
import com.x8bit.bitwarden.ui.platform.feature.splash.splashDestination
|
||||
import com.x8bit.bitwarden.ui.platform.feature.vaultunlocked.VAULT_UNLOCKED_GRAPH_ROUTE
|
||||
import com.x8bit.bitwarden.ui.platform.feature.vaultunlocked.VaultUnlockedGraphRoute
|
||||
import com.x8bit.bitwarden.ui.platform.feature.vaultunlocked.navigateToVaultUnlockedGraph
|
||||
import com.x8bit.bitwarden.ui.platform.feature.vaultunlocked.vaultUnlockedGraph
|
||||
import com.x8bit.bitwarden.ui.platform.theme.NonNullEnterTransitionProvider
|
||||
import com.x8bit.bitwarden.ui.platform.theme.NonNullExitTransitionProvider
|
||||
import com.x8bit.bitwarden.ui.platform.theme.RootTransitionProviders
|
||||
import com.x8bit.bitwarden.ui.platform.util.toObjectNavigationRoute
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.model.AddSendType
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.navigateToAddSend
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditArgs
|
||||
@ -89,7 +90,7 @@ fun RootNavScreen(
|
||||
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = SPLASH_ROUTE,
|
||||
startDestination = SplashRoute,
|
||||
enterTransition = { toEnterTransition()(this) },
|
||||
exitTransition = { toExitTransition()(this) },
|
||||
popEnterTransition = { toEnterTransition()(this) },
|
||||
@ -116,14 +117,14 @@ fun RootNavScreen(
|
||||
is RootNavState.CompleteOngoingRegistration,
|
||||
RootNavState.AuthWithWelcome,
|
||||
RootNavState.ExpiredRegistrationLink,
|
||||
-> AUTH_GRAPH_ROUTE
|
||||
-> AuthGraphRoute
|
||||
|
||||
RootNavState.ResetPassword -> RESET_PASSWORD_ROUTE
|
||||
RootNavState.SetPassword -> SET_PASSWORD_ROUTE
|
||||
RootNavState.RemovePassword -> REMOVE_PASSWORD_ROUTE
|
||||
RootNavState.Splash -> SPLASH_ROUTE
|
||||
RootNavState.TrustedDevice -> TRUSTED_DEVICE_GRAPH_ROUTE
|
||||
RootNavState.VaultLocked -> VAULT_UNLOCK_ROUTE
|
||||
RootNavState.ResetPassword -> ResetPasswordRoute
|
||||
RootNavState.SetPassword -> SetPasswordRoute
|
||||
RootNavState.RemovePassword -> RemovePasswordRoute
|
||||
RootNavState.Splash -> SplashRoute
|
||||
RootNavState.TrustedDevice -> TrustedDeviceGraphRoute
|
||||
RootNavState.VaultLocked -> VaultUnlockRoute.Standard
|
||||
is RootNavState.VaultUnlocked,
|
||||
is RootNavState.VaultUnlockedForAutofillSave,
|
||||
is RootNavState.VaultUnlockedForAutofillSelection,
|
||||
@ -133,11 +134,11 @@ fun RootNavScreen(
|
||||
is RootNavState.VaultUnlockedForFido2Save,
|
||||
is RootNavState.VaultUnlockedForFido2Assertion,
|
||||
is RootNavState.VaultUnlockedForFido2GetCredentials,
|
||||
-> VAULT_UNLOCKED_GRAPH_ROUTE
|
||||
-> VaultUnlockedGraphRoute
|
||||
|
||||
RootNavState.OnboardingAccountLockSetup -> SETUP_UNLOCK_AS_ROOT_ROUTE
|
||||
RootNavState.OnboardingAutoFillSetup -> SETUP_AUTO_FILL_AS_ROOT_ROUTE
|
||||
RootNavState.OnboardingStepsComplete -> SETUP_COMPLETE_ROUTE
|
||||
RootNavState.OnboardingAccountLockSetup -> SetupUnlockRoute.AsRoot
|
||||
RootNavState.OnboardingAutoFillSetup -> SetupAutofillRoute.AsRoot
|
||||
RootNavState.OnboardingStepsComplete -> SetupCompleteRoute
|
||||
}
|
||||
val currentRoute = navController.currentDestination?.rootLevelRoute()
|
||||
|
||||
@ -145,7 +146,9 @@ fun RootNavScreen(
|
||||
// death. In this case, the NavHost already restores state, so we don't have to navigate.
|
||||
// However, if the route is correct but the underlying state is different, we should still
|
||||
// proceed in order to get a fresh version of that route.
|
||||
if (currentRoute == targetRoute && previousStateReference.get() == state) {
|
||||
if (currentRoute == targetRoute.toObjectNavigationRoute() &&
|
||||
previousStateReference.get() == state
|
||||
) {
|
||||
previousStateReference.set(state)
|
||||
return
|
||||
}
|
||||
@ -291,13 +294,13 @@ private fun NavDestination?.rootLevelRoute(): String? {
|
||||
@Suppress("MaxLineLength")
|
||||
private fun AnimatedContentTransitionScope<NavBackStackEntry>.toEnterTransition(): NonNullEnterTransitionProvider =
|
||||
when (targetState.destination.rootLevelRoute()) {
|
||||
RESET_PASSWORD_ROUTE -> RootTransitionProviders.Enter.slideUp
|
||||
ResetPasswordRoute.toObjectNavigationRoute() -> RootTransitionProviders.Enter.slideUp
|
||||
else -> when (initialState.destination.rootLevelRoute()) {
|
||||
// Disable transitions when coming from the splash screen
|
||||
SPLASH_ROUTE -> RootTransitionProviders.Enter.none
|
||||
SplashRoute.toObjectNavigationRoute() -> RootTransitionProviders.Enter.none
|
||||
// The RESET_PASSWORD_ROUTE animation should be stay but due to an issue when combining
|
||||
// certain animations, we are just using a fadeIn instead.
|
||||
RESET_PASSWORD_ROUTE -> RootTransitionProviders.Enter.fadeIn
|
||||
ResetPasswordRoute.toObjectNavigationRoute() -> RootTransitionProviders.Enter.fadeIn
|
||||
else -> RootTransitionProviders.Enter.fadeIn
|
||||
}
|
||||
}
|
||||
@ -306,13 +309,14 @@ private fun AnimatedContentTransitionScope<NavBackStackEntry>.toEnterTransition(
|
||||
* Define the exit transition for each route.
|
||||
*/
|
||||
@Suppress("MaxLineLength")
|
||||
private fun AnimatedContentTransitionScope<NavBackStackEntry>.toExitTransition(): NonNullExitTransitionProvider =
|
||||
when (initialState.destination.rootLevelRoute()) {
|
||||
private fun AnimatedContentTransitionScope<NavBackStackEntry>.toExitTransition(): NonNullExitTransitionProvider {
|
||||
return when (initialState.destination.rootLevelRoute()) {
|
||||
// Disable transitions when coming from the splash screen
|
||||
SPLASH_ROUTE -> RootTransitionProviders.Exit.none
|
||||
RESET_PASSWORD_ROUTE -> RootTransitionProviders.Exit.slideDown
|
||||
SplashRoute.toObjectNavigationRoute() -> RootTransitionProviders.Exit.none
|
||||
ResetPasswordRoute.toObjectNavigationRoute() -> RootTransitionProviders.Exit.slideDown
|
||||
else -> when (targetState.destination.rootLevelRoute()) {
|
||||
RESET_PASSWORD_ROUTE -> RootTransitionProviders.Exit.stay
|
||||
ResetPasswordRoute.toObjectNavigationRoute() -> RootTransitionProviders.Exit.stay
|
||||
else -> RootTransitionProviders.Exit.fadeOut
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,46 +6,79 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditArgs
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemArgs
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val SEARCH_TYPE: String = "search_type"
|
||||
private const val SEARCH_TYPE_SEND_ALL: String = "search_type_sends_all"
|
||||
private const val SEARCH_TYPE_SEND_TEXT: String = "search_type_sends_text"
|
||||
private const val SEARCH_TYPE_SEND_FILE: String = "search_type_sends_file"
|
||||
private const val SEARCH_TYPE_VAULT_ALL: String = "search_type_vault_all"
|
||||
private const val SEARCH_TYPE_VAULT_LOGINS: String = "search_type_vault_logins"
|
||||
private const val SEARCH_TYPE_VAULT_CARDS: String = "search_type_vault_cards"
|
||||
private const val SEARCH_TYPE_VAULT_IDENTITIES: String = "search_type_vault_identities"
|
||||
private const val SEARCH_TYPE_VAULT_SECURE_NOTES: String = "search_type_vault_secure_notes"
|
||||
private const val SEARCH_TYPE_VAULT_COLLECTION: String = "search_type_vault_collection"
|
||||
private const val SEARCH_TYPE_VAULT_NO_FOLDER: String = "search_type_vault_no_folder"
|
||||
private const val SEARCH_TYPE_VAULT_FOLDER: String = "search_type_vault_folder"
|
||||
private const val SEARCH_TYPE_VAULT_TRASH: String = "search_type_vault_trash"
|
||||
private const val SEARCH_TYPE_VAULT_VERIFICATION_CODES: String =
|
||||
"search_type_vault_verification_codes"
|
||||
private const val SEARCH_TYPE_ID: String = "search_type_id"
|
||||
private const val SEARCH_TYPE_VAULT_SSH_KEYS: String = "search_type_vault_ssh_keys"
|
||||
/**
|
||||
* The type-safe route for the search screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class SearchRoute(
|
||||
val searchableItemType: SearchableItemType,
|
||||
val id: String?,
|
||||
)
|
||||
|
||||
private const val SEARCH_ROUTE_PREFIX: String = "search"
|
||||
private const val SEARCH_ROUTE: String = "$SEARCH_ROUTE_PREFIX/{$SEARCH_TYPE}/{$SEARCH_TYPE_ID}"
|
||||
/**
|
||||
* Represents the various types of searchable items.
|
||||
*/
|
||||
@Serializable
|
||||
enum class SearchableItemType {
|
||||
SENDS_ALL,
|
||||
SENDS_TEXTS,
|
||||
SENDS_FILES,
|
||||
VAULT_ALL,
|
||||
VAULT_LOGINS,
|
||||
VAULT_CARDS,
|
||||
VAULT_IDENTITIES,
|
||||
VAULT_SECURE_NOTES,
|
||||
VAULT_SSH_KEYS,
|
||||
VAULT_COLLECTIONS,
|
||||
VAULT_NO_FOLDER,
|
||||
VAULT_FOLDER,
|
||||
VAULT_TRASH,
|
||||
VAULT_VERIFICATION_CODES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to retrieve search arguments from the [SavedStateHandle].
|
||||
*/
|
||||
data class SearchArgs(
|
||||
val type: SearchType,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
type = determineSearchType(
|
||||
searchTypeString = requireNotNull(savedStateHandle.get<String>(SEARCH_TYPE)),
|
||||
id = savedStateHandle.get<String>(SEARCH_TYPE_ID),
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Constructs a [SearchArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
fun SavedStateHandle.toSearchArgs(): SearchArgs {
|
||||
val route = this.toRoute<SearchRoute>()
|
||||
return SearchArgs(
|
||||
type = when (route.searchableItemType) {
|
||||
SearchableItemType.SENDS_ALL -> SearchType.Sends.All
|
||||
SearchableItemType.SENDS_TEXTS -> SearchType.Sends.Texts
|
||||
SearchableItemType.SENDS_FILES -> SearchType.Sends.Files
|
||||
SearchableItemType.VAULT_ALL -> SearchType.Vault.All
|
||||
SearchableItemType.VAULT_LOGINS -> SearchType.Vault.Logins
|
||||
SearchableItemType.VAULT_CARDS -> SearchType.Vault.Cards
|
||||
SearchableItemType.VAULT_IDENTITIES -> SearchType.Vault.Identities
|
||||
SearchableItemType.VAULT_SECURE_NOTES -> SearchType.Vault.SecureNotes
|
||||
SearchableItemType.VAULT_SSH_KEYS -> SearchType.Vault.SshKeys
|
||||
SearchableItemType.VAULT_NO_FOLDER -> SearchType.Vault.NoFolder
|
||||
SearchableItemType.VAULT_TRASH -> SearchType.Vault.Trash
|
||||
SearchableItemType.VAULT_VERIFICATION_CODES -> SearchType.Vault.VerificationCodes
|
||||
SearchableItemType.VAULT_FOLDER -> SearchType.Vault.Folder(
|
||||
folderId = requireNotNull(route.id),
|
||||
)
|
||||
|
||||
SearchableItemType.VAULT_COLLECTIONS -> SearchType.Vault.Collection(
|
||||
collectionId = requireNotNull(route.id),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@ -58,16 +91,7 @@ fun NavGraphBuilder.searchDestination(
|
||||
onNavigateToEditCipher: (args: VaultAddEditArgs) -> Unit,
|
||||
onNavigateToViewCipher: (args: VaultItemArgs) -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = SEARCH_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(SEARCH_TYPE) { type = NavType.StringType },
|
||||
navArgument(SEARCH_TYPE_ID) {
|
||||
type = NavType.StringType
|
||||
nullable = true
|
||||
},
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<SearchRoute> {
|
||||
SearchScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToEditSend = onNavigateToEditSend,
|
||||
@ -84,50 +108,31 @@ fun NavController.navigateToSearch(
|
||||
searchType: SearchType,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(
|
||||
route = "$SEARCH_ROUTE_PREFIX/${searchType.toTypeString()}/${searchType.toIdOrNull()}",
|
||||
this.navigate(
|
||||
route = SearchRoute(
|
||||
searchableItemType = searchType.toSearchableItemType(),
|
||||
id = searchType.toIdOrNull(),
|
||||
),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
private fun determineSearchType(
|
||||
searchTypeString: String,
|
||||
id: String?,
|
||||
): SearchType =
|
||||
when (searchTypeString) {
|
||||
SEARCH_TYPE_SEND_ALL -> SearchType.Sends.All
|
||||
SEARCH_TYPE_SEND_TEXT -> SearchType.Sends.Texts
|
||||
SEARCH_TYPE_SEND_FILE -> SearchType.Sends.Files
|
||||
SEARCH_TYPE_VAULT_ALL -> SearchType.Vault.All
|
||||
SEARCH_TYPE_VAULT_LOGINS -> SearchType.Vault.Logins
|
||||
SEARCH_TYPE_VAULT_CARDS -> SearchType.Vault.Cards
|
||||
SEARCH_TYPE_VAULT_IDENTITIES -> SearchType.Vault.Identities
|
||||
SEARCH_TYPE_VAULT_SECURE_NOTES -> SearchType.Vault.SecureNotes
|
||||
SEARCH_TYPE_VAULT_COLLECTION -> SearchType.Vault.Collection(requireNotNull(id))
|
||||
SEARCH_TYPE_VAULT_NO_FOLDER -> SearchType.Vault.NoFolder
|
||||
SEARCH_TYPE_VAULT_FOLDER -> SearchType.Vault.Folder(requireNotNull(id))
|
||||
SEARCH_TYPE_VAULT_TRASH -> SearchType.Vault.Trash
|
||||
SEARCH_TYPE_VAULT_VERIFICATION_CODES -> SearchType.Vault.VerificationCodes
|
||||
SEARCH_TYPE_VAULT_SSH_KEYS -> SearchType.Vault.SshKeys
|
||||
else -> throw IllegalArgumentException("Invalid Search Type")
|
||||
}
|
||||
|
||||
private fun SearchType.toTypeString(): String =
|
||||
private fun SearchType.toSearchableItemType(): SearchableItemType =
|
||||
when (this) {
|
||||
SearchType.Sends.All -> SEARCH_TYPE_SEND_ALL
|
||||
SearchType.Sends.Files -> SEARCH_TYPE_SEND_FILE
|
||||
SearchType.Sends.Texts -> SEARCH_TYPE_SEND_TEXT
|
||||
SearchType.Vault.All -> SEARCH_TYPE_VAULT_ALL
|
||||
SearchType.Vault.Cards -> SEARCH_TYPE_VAULT_CARDS
|
||||
is SearchType.Vault.Collection -> SEARCH_TYPE_VAULT_COLLECTION
|
||||
is SearchType.Vault.Folder -> SEARCH_TYPE_VAULT_FOLDER
|
||||
SearchType.Vault.Identities -> SEARCH_TYPE_VAULT_IDENTITIES
|
||||
SearchType.Vault.Logins -> SEARCH_TYPE_VAULT_LOGINS
|
||||
SearchType.Vault.NoFolder -> SEARCH_TYPE_VAULT_NO_FOLDER
|
||||
SearchType.Vault.SecureNotes -> SEARCH_TYPE_VAULT_SECURE_NOTES
|
||||
SearchType.Vault.Trash -> SEARCH_TYPE_VAULT_TRASH
|
||||
SearchType.Vault.VerificationCodes -> SEARCH_TYPE_VAULT_VERIFICATION_CODES
|
||||
SearchType.Vault.SshKeys -> SEARCH_TYPE_VAULT_SSH_KEYS
|
||||
SearchType.Sends.All -> SearchableItemType.SENDS_ALL
|
||||
SearchType.Sends.Files -> SearchableItemType.SENDS_FILES
|
||||
SearchType.Sends.Texts -> SearchableItemType.SENDS_TEXTS
|
||||
SearchType.Vault.All -> SearchableItemType.VAULT_ALL
|
||||
SearchType.Vault.Cards -> SearchableItemType.VAULT_CARDS
|
||||
is SearchType.Vault.Collection -> SearchableItemType.VAULT_COLLECTIONS
|
||||
is SearchType.Vault.Folder -> SearchableItemType.VAULT_FOLDER
|
||||
SearchType.Vault.Identities -> SearchableItemType.VAULT_IDENTITIES
|
||||
SearchType.Vault.Logins -> SearchableItemType.VAULT_LOGINS
|
||||
SearchType.Vault.NoFolder -> SearchableItemType.VAULT_NO_FOLDER
|
||||
SearchType.Vault.SecureNotes -> SearchableItemType.VAULT_SECURE_NOTES
|
||||
SearchType.Vault.Trash -> SearchableItemType.VAULT_TRASH
|
||||
SearchType.Vault.VerificationCodes -> SearchableItemType.VAULT_VERIFICATION_CODES
|
||||
SearchType.Vault.SshKeys -> SearchableItemType.VAULT_SSH_KEYS
|
||||
}
|
||||
|
||||
private fun SearchType.toIdOrNull(): String? =
|
||||
|
||||
@ -87,7 +87,7 @@ class SearchViewModel @Inject constructor(
|
||||
// We load the state from the savedStateHandle for testing purposes.
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: run {
|
||||
val searchType = SearchArgs(savedStateHandle).type
|
||||
val searchType = savedStateHandle.toSearchArgs().type
|
||||
val userState = requireNotNull(authRepo.userStateFlow.value)
|
||||
val specialCircumstance = specialCircumstanceManager.specialCircumstance
|
||||
val searchTerm = (specialCircumstance as? SpecialCircumstance.SearchShortcut)
|
||||
|
||||
@ -7,8 +7,6 @@ import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.navArgument
|
||||
import androidx.navigation.navOptions
|
||||
import androidx.navigation.navigation
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
@ -33,20 +31,57 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.other.otherDestination
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.vault.navigateToVaultSettings
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.vault.vaultSettingsDestination
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||
import com.x8bit.bitwarden.ui.platform.util.toObjectRoute
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val IS_PRE_AUTH: String = "isPreAuth"
|
||||
private const val PRE_AUTH_SETTINGS_ROUTE = "pre_auth_settings"
|
||||
/**
|
||||
* The type-safe route for the settings graph.
|
||||
*/
|
||||
@Serializable
|
||||
data object SettingsGraphRoute
|
||||
|
||||
const val SETTINGS_GRAPH_ROUTE: String = "settings_graph"
|
||||
const val SETTINGS_ROUTE: String = "settings"
|
||||
/**
|
||||
* The type-safe route for the settings screen.
|
||||
*/
|
||||
@Serializable
|
||||
sealed class SettingsRoute {
|
||||
/**
|
||||
* Indicates that the settings screen should be shown as a pre-authentication.
|
||||
*/
|
||||
abstract val isPreAuth: Boolean
|
||||
|
||||
/**
|
||||
* The type-safe route for the settings screen in the settings graph.
|
||||
*/
|
||||
@Serializable
|
||||
data object Standard : SettingsRoute() {
|
||||
override val isPreAuth: Boolean get() = false
|
||||
}
|
||||
|
||||
/**
|
||||
* The type-safe route for the pre-auth settings screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object PreAuth : SettingsRoute() {
|
||||
override val isPreAuth: Boolean get() = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to retrieve settings arguments from the [SavedStateHandle].
|
||||
*/
|
||||
data class SettingsArgs(val isPreAuth: Boolean) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
isPreAuth = requireNotNull(savedStateHandle[IS_PRE_AUTH]),
|
||||
)
|
||||
data class SettingsArgs(val isPreAuth: Boolean)
|
||||
|
||||
/**
|
||||
* Constructs a [SettingsArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toSettingsArgs(): SettingsArgs {
|
||||
val route = this.toObjectRoute<SettingsRoute.PreAuth>()
|
||||
?: this.toObjectRoute<SettingsRoute.Standard>()
|
||||
return route
|
||||
?.let { SettingsArgs(isPreAuth = it.isPreAuth) }
|
||||
?: this.toObjectRoute<SettingsGraphRoute>()?.let { SettingsArgs(isPreAuth = false) }
|
||||
?: throw IllegalStateException("Missing correct route for SettingsScreen")
|
||||
}
|
||||
|
||||
/**
|
||||
@ -65,19 +100,10 @@ fun NavGraphBuilder.settingsGraph(
|
||||
onNavigateToRecordedLogs: () -> Unit,
|
||||
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||
) {
|
||||
navigation(
|
||||
startDestination = SETTINGS_ROUTE,
|
||||
route = SETTINGS_GRAPH_ROUTE,
|
||||
) {
|
||||
composableWithRootPushTransitions(
|
||||
route = SETTINGS_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(name = IS_PRE_AUTH) {
|
||||
type = NavType.BoolType
|
||||
defaultValue = false
|
||||
},
|
||||
),
|
||||
navigation<SettingsGraphRoute>(
|
||||
startDestination = SettingsRoute.Standard,
|
||||
) {
|
||||
composableWithRootPushTransitions<SettingsRoute.Standard> {
|
||||
SettingsScreen(
|
||||
onNavigateBack = {},
|
||||
onNavigateToAbout = { navController.navigateToAbout(isPreAuth = false) },
|
||||
@ -129,15 +155,7 @@ fun NavGraphBuilder.settingsGraph(
|
||||
fun NavGraphBuilder.preAuthSettingsDestinations(
|
||||
navController: NavController,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = PRE_AUTH_SETTINGS_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(name = IS_PRE_AUTH) {
|
||||
type = NavType.BoolType
|
||||
defaultValue = true
|
||||
},
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<SettingsRoute.PreAuth> {
|
||||
SettingsScreen(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToAbout = { navController.navigateToAbout(isPreAuth = true) },
|
||||
@ -176,7 +194,7 @@ fun NavGraphBuilder.preAuthSettingsDestinations(
|
||||
* Navigate to the settings graph.
|
||||
*/
|
||||
fun NavController.navigateToSettingsGraph(navOptions: NavOptions? = null) {
|
||||
navigate(SETTINGS_GRAPH_ROUTE, navOptions)
|
||||
this.navigate(route = SettingsGraphRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -194,12 +212,12 @@ fun NavController.navigateToSettingsGraphRoot() {
|
||||
},
|
||||
)
|
||||
// Then ensures that we are at the root
|
||||
popBackStack(route = SETTINGS_ROUTE, inclusive = false)
|
||||
popBackStack(route = SettingsRoute.Standard, inclusive = false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the pre-auth settings screen.
|
||||
*/
|
||||
fun NavController.navigateToPreAuthSettings(navOptions: NavOptions? = null) {
|
||||
navigate(PRE_AUTH_SETTINGS_ROUTE, navOptions)
|
||||
this.navigate(route = SettingsRoute.PreAuth, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ class SettingsViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<SettingsState, SettingsEvent, SettingsAction>(
|
||||
initialState = SettingsState(
|
||||
isPreAuth = SettingsArgs(savedStateHandle = savedStateHandle).isPreAuth,
|
||||
isPreAuth = savedStateHandle.toSettingsArgs().isPreAuth,
|
||||
securityCount = firstTimeActionManager.allSecuritySettingsBadgeCountFlow.value,
|
||||
autoFillCount = firstTimeActionManager.allAutofillSettingsBadgeCountFlow.value,
|
||||
vaultCount = firstTimeActionManager.allVaultSettingsBadgeCountFlow.value,
|
||||
|
||||
@ -7,9 +7,25 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val PRE_AUTH_ABOUT_ROUTE = "pre_auth_settings_about"
|
||||
private const val ABOUT_ROUTE = "settings_about"
|
||||
/**
|
||||
* The type-safe route for the settings about screen.
|
||||
*/
|
||||
@Serializable
|
||||
sealed class SettingsAboutRoute {
|
||||
/**
|
||||
* The type-safe route for the settings about screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object Standard : SettingsAboutRoute()
|
||||
|
||||
/**
|
||||
* The type-safe route for the pre-auth settings about screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object PreAuth : SettingsAboutRoute()
|
||||
}
|
||||
|
||||
/**
|
||||
* Add settings destinations to the nav graph.
|
||||
@ -20,15 +36,23 @@ fun NavGraphBuilder.aboutDestination(
|
||||
onNavigateToFlightRecorder: () -> Unit,
|
||||
onNavigateToRecordedLogs: () -> Unit,
|
||||
) {
|
||||
composableWithPushTransitions(
|
||||
route = getRoute(isPreAuth = isPreAuth),
|
||||
) {
|
||||
if (isPreAuth) {
|
||||
composableWithPushTransitions<SettingsAboutRoute.PreAuth> {
|
||||
AboutScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToFlightRecorder = onNavigateToFlightRecorder,
|
||||
onNavigateToRecordedLogs = onNavigateToRecordedLogs,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
composableWithPushTransitions<SettingsAboutRoute.Standard> {
|
||||
AboutScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToFlightRecorder = onNavigateToFlightRecorder,
|
||||
onNavigateToRecordedLogs = onNavigateToRecordedLogs,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,9 +62,8 @@ fun NavController.navigateToAbout(
|
||||
isPreAuth: Boolean,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(route = getRoute(isPreAuth = isPreAuth), navOptions = navOptions)
|
||||
navigate(
|
||||
route = if (isPreAuth) SettingsAboutRoute.PreAuth else SettingsAboutRoute.Standard,
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getRoute(
|
||||
isPreAuth: Boolean,
|
||||
): String = if (isPreAuth) PRE_AUTH_ABOUT_ROUTE else ABOUT_ROUTE
|
||||
|
||||
@ -7,8 +7,13 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val ACCOUNT_SECURITY_ROUTE = "settings_account_security"
|
||||
/**
|
||||
* The type-safe route for the account security screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object AccountSecurityRoute
|
||||
|
||||
/**
|
||||
* Add settings destinations to the nav graph.
|
||||
@ -19,9 +24,7 @@ fun NavGraphBuilder.accountSecurityDestination(
|
||||
onNavigateToPendingRequests: () -> Unit,
|
||||
onNavigateToSetupUnlockScreen: () -> Unit,
|
||||
) {
|
||||
composableWithPushTransitions(
|
||||
route = ACCOUNT_SECURITY_ROUTE,
|
||||
) {
|
||||
composableWithPushTransitions<AccountSecurityRoute> {
|
||||
AccountSecurityScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToDeleteAccount = onNavigateToDeleteAccount,
|
||||
@ -35,5 +38,5 @@ fun NavGraphBuilder.accountSecurityDestination(
|
||||
* Navigate to the account security screen.
|
||||
*/
|
||||
fun NavController.navigateToAccountSecurity(navOptions: NavOptions? = null) {
|
||||
navigate(ACCOUNT_SECURITY_ROUTE, navOptions)
|
||||
this.navigate(route = AccountSecurityRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -7,8 +7,13 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val DELETE_ACCOUNT_ROUTE = "delete_account"
|
||||
/**
|
||||
* The type-safe route for the delete account screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object DeleteAccountRoute
|
||||
|
||||
/**
|
||||
* Add delete account destinations to the nav graph.
|
||||
@ -17,9 +22,7 @@ fun NavGraphBuilder.deleteAccountDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToDeleteAccountConfirmation: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = DELETE_ACCOUNT_ROUTE,
|
||||
) {
|
||||
composableWithSlideTransitions<DeleteAccountRoute> {
|
||||
DeleteAccountScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToDeleteAccountConfirmation = onNavigateToDeleteAccountConfirmation,
|
||||
@ -31,5 +34,5 @@ fun NavGraphBuilder.deleteAccountDestination(
|
||||
* Navigate to the delete account screen.
|
||||
*/
|
||||
fun NavController.navigateToDeleteAccount(navOptions: NavOptions? = null) {
|
||||
navigate(DELETE_ACCOUNT_ROUTE, navOptions)
|
||||
this.navigate(route = DeleteAccountRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -7,8 +7,13 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val DELETE_ACCOUNT_CONFIRMATION_ROUTE = "delete_account_confirmation"
|
||||
/**
|
||||
* The type-safe route for the delete account confirmation screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object DeleteAccountConfirmationRoute
|
||||
|
||||
/**
|
||||
* Add delete account confirmation destinations to the nav graph.
|
||||
@ -16,9 +21,7 @@ private const val DELETE_ACCOUNT_CONFIRMATION_ROUTE = "delete_account_confirmati
|
||||
fun NavGraphBuilder.deleteAccountConfirmationDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = DELETE_ACCOUNT_CONFIRMATION_ROUTE,
|
||||
) {
|
||||
composableWithSlideTransitions<DeleteAccountConfirmationRoute> {
|
||||
DeleteAccountConfirmationScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
@ -29,5 +32,5 @@ fun NavGraphBuilder.deleteAccountConfirmationDestination(
|
||||
* Navigate to the [DeleteAccountConfirmationScreen].
|
||||
*/
|
||||
fun NavController.navigateToDeleteAccountConfirmation(navOptions: NavOptions? = null) {
|
||||
navigate(DELETE_ACCOUNT_CONFIRMATION_ROUTE, navOptions)
|
||||
this.navigate(route = DeleteAccountConfirmationRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -6,22 +6,30 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val FINGERPRINT: String = "fingerprint"
|
||||
private const val LOGIN_APPROVAL_PREFIX = "login_approval"
|
||||
private const val LOGIN_APPROVAL_ROUTE = "$LOGIN_APPROVAL_PREFIX?$FINGERPRINT={$FINGERPRINT}"
|
||||
/**
|
||||
* The type-safe route for the login approval screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class LoginApprovalRoute(
|
||||
val fingerprint: String?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Class to retrieve login approval arguments from the [SavedStateHandle].
|
||||
*/
|
||||
data class LoginApprovalArgs(val fingerprint: String?) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
fingerprint = savedStateHandle.get<String>(FINGERPRINT),
|
||||
)
|
||||
data class LoginApprovalArgs(val fingerprint: String?)
|
||||
|
||||
/**
|
||||
* Constructs a [LoginApprovalArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toLoginApprovalArgs(): LoginApprovalArgs {
|
||||
val route = this.toRoute<LoginApprovalRoute>()
|
||||
return LoginApprovalArgs(fingerprint = route.fingerprint)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -30,16 +38,7 @@ data class LoginApprovalArgs(val fingerprint: String?) {
|
||||
fun NavGraphBuilder.loginApprovalDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = LOGIN_APPROVAL_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(FINGERPRINT) {
|
||||
type = NavType.StringType
|
||||
nullable = true
|
||||
defaultValue = null
|
||||
},
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<LoginApprovalRoute> {
|
||||
LoginApprovalScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
@ -53,5 +52,5 @@ fun NavController.navigateToLoginApproval(
|
||||
fingerprint: String?,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate("$LOGIN_APPROVAL_PREFIX?$FINGERPRINT=$fingerprint", navOptions)
|
||||
this.navigate(route = LoginApprovalRoute(fingerprint = fingerprint), navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.loginap
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.ui.util.Text
|
||||
import com.bitwarden.ui.util.asText
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestResult
|
||||
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestUpdatesResult
|
||||
@ -12,8 +14,6 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.bitwarden.ui.util.Text
|
||||
import com.bitwarden.ui.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.util.toFormattedPattern
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@ -45,7 +45,7 @@ class LoginApprovalViewModel @Inject constructor(
|
||||
specialCircumstance = specialCircumstance,
|
||||
fingerprint = specialCircumstance
|
||||
?.let { "" }
|
||||
?: requireNotNull(LoginApprovalArgs(savedStateHandle).fingerprint),
|
||||
?: requireNotNull(savedStateHandle.toLoginApprovalArgs().fingerprint),
|
||||
masterPasswordHash = null,
|
||||
publicKey = "",
|
||||
requestId = "",
|
||||
|
||||
@ -7,8 +7,13 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val PENDING_REQUESTS_ROUTE = "pending_requests"
|
||||
/**
|
||||
* The type-safe route for the pending requests screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object PendingRequestsRoute
|
||||
|
||||
/**
|
||||
* Add pending requests destinations to the nav graph.
|
||||
@ -17,9 +22,7 @@ fun NavGraphBuilder.pendingRequestsDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToLoginApproval: (fingerprintPhrase: String) -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = PENDING_REQUESTS_ROUTE,
|
||||
) {
|
||||
composableWithSlideTransitions<PendingRequestsRoute> {
|
||||
PendingRequestsScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToLoginApproval = onNavigateToLoginApproval,
|
||||
@ -31,5 +34,5 @@ fun NavGraphBuilder.pendingRequestsDestination(
|
||||
* Navigate to the Pending Login Requests screen.
|
||||
*/
|
||||
fun NavController.navigateToPendingRequests(navOptions: NavOptions? = null) {
|
||||
navigate(PENDING_REQUESTS_ROUTE, navOptions)
|
||||
this.navigate(route = PendingRequestsRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -7,9 +7,25 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val PRE_AUTH_APPEARANCE_ROUTE = "pre_auth_settings_appearance"
|
||||
private const val APPEARANCE_ROUTE = "settings_appearance"
|
||||
/**
|
||||
* The type-safe route for the settings appearance screen.
|
||||
*/
|
||||
@Serializable
|
||||
sealed class SettingsAppearanceRoute {
|
||||
/**
|
||||
* The type-safe route for the settings appearance screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object Standard : SettingsAppearanceRoute()
|
||||
|
||||
/**
|
||||
* The type-safe route for the pre-auth settings appearance screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object PreAuth : SettingsAppearanceRoute()
|
||||
}
|
||||
|
||||
/**
|
||||
* Add settings destinations to the nav graph.
|
||||
@ -18,11 +34,15 @@ fun NavGraphBuilder.appearanceDestination(
|
||||
isPreAuth: Boolean,
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithPushTransitions(
|
||||
route = getRoute(isPreAuth = isPreAuth),
|
||||
) {
|
||||
if (isPreAuth) {
|
||||
composableWithPushTransitions<SettingsAppearanceRoute.PreAuth> {
|
||||
AppearanceScreen(onNavigateBack = onNavigateBack)
|
||||
}
|
||||
} else {
|
||||
composableWithPushTransitions<SettingsAppearanceRoute.Standard> {
|
||||
AppearanceScreen(onNavigateBack = onNavigateBack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -32,9 +52,12 @@ fun NavController.navigateToAppearance(
|
||||
isPreAuth: Boolean,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(route = getRoute(isPreAuth = isPreAuth), navOptions = navOptions)
|
||||
this.navigate(
|
||||
route = if (isPreAuth) {
|
||||
SettingsAppearanceRoute.PreAuth
|
||||
} else {
|
||||
SettingsAppearanceRoute.Standard
|
||||
},
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getRoute(
|
||||
isPreAuth: Boolean,
|
||||
): String = if (isPreAuth) PRE_AUTH_APPEARANCE_ROUTE else APPEARANCE_ROUTE
|
||||
|
||||
@ -7,8 +7,13 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val AUTO_FILL_ROUTE = "settings_auto_fill"
|
||||
/**
|
||||
* The type-safe route for the autofill screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object AutofillRoute
|
||||
|
||||
/**
|
||||
* Add settings destinations to the nav graph.
|
||||
@ -18,9 +23,7 @@ fun NavGraphBuilder.autoFillDestination(
|
||||
onNavigateToBlockAutoFillScreen: () -> Unit,
|
||||
onNavigateToSetupAutofill: () -> Unit,
|
||||
) {
|
||||
composableWithPushTransitions(
|
||||
route = AUTO_FILL_ROUTE,
|
||||
) {
|
||||
composableWithPushTransitions<AutofillRoute> {
|
||||
AutoFillScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToBlockAutoFillScreen = onNavigateToBlockAutoFillScreen,
|
||||
@ -33,5 +36,5 @@ fun NavGraphBuilder.autoFillDestination(
|
||||
* Navigate to the auto-fill screen.
|
||||
*/
|
||||
fun NavController.navigateToAutoFill(navOptions: NavOptions? = null) {
|
||||
navigate(AUTO_FILL_ROUTE, navOptions)
|
||||
this.navigate(route = AutofillRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -7,8 +7,13 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val BLOCK_AUTO_FILL_ROUTE = "settings_block_auto_fill"
|
||||
/**
|
||||
* The type-safe route for the block autofill settings screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object BlockAutofillSettingsRoute
|
||||
|
||||
/**
|
||||
* Add block auto-fill destination to the nav graph.
|
||||
@ -16,9 +21,7 @@ private const val BLOCK_AUTO_FILL_ROUTE = "settings_block_auto_fill"
|
||||
fun NavGraphBuilder.blockAutoFillDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithPushTransitions(
|
||||
route = BLOCK_AUTO_FILL_ROUTE,
|
||||
) {
|
||||
composableWithPushTransitions<BlockAutofillSettingsRoute> {
|
||||
BlockAutoFillScreen(onNavigateBack = onNavigateBack)
|
||||
}
|
||||
}
|
||||
@ -27,5 +30,5 @@ fun NavGraphBuilder.blockAutoFillDestination(
|
||||
* Navigate to the block auto-fill screen.
|
||||
*/
|
||||
fun NavController.navigateToBlockAutoFillScreen(navOptions: NavOptions? = null) {
|
||||
navigate(BLOCK_AUTO_FILL_ROUTE, navOptions)
|
||||
this.navigate(route = BlockAutofillSettingsRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -7,8 +7,13 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val EXPORT_VAULT_ROUTE = "export_vault"
|
||||
/**
|
||||
* The type-safe route for the pending requests screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object ExportVaultRoute
|
||||
|
||||
/**
|
||||
* Add the Export Vault screen to the nav graph.
|
||||
@ -16,9 +21,7 @@ private const val EXPORT_VAULT_ROUTE = "export_vault"
|
||||
fun NavGraphBuilder.exportVaultDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = EXPORT_VAULT_ROUTE,
|
||||
) {
|
||||
composableWithSlideTransitions<ExportVaultRoute> {
|
||||
ExportVaultScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
@ -29,5 +32,5 @@ fun NavGraphBuilder.exportVaultDestination(
|
||||
* Navigate to the Export Vault screen.
|
||||
*/
|
||||
fun NavController.navigateToExportVault(navOptions: NavOptions? = null) {
|
||||
this.navigate(EXPORT_VAULT_ROUTE, navOptions)
|
||||
this.navigate(route = ExportVaultRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -7,9 +7,25 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val PRE_AUTH_FLIGHT_RECORDER_ROUTE = "pre_auth_flight_recorder_config"
|
||||
private const val FLIGHT_RECORDER_ROUTE = "flight_recorder_config"
|
||||
/**
|
||||
* The type-safe route for the flight recorder screen.
|
||||
*/
|
||||
@Serializable
|
||||
sealed class FlightRecorderRoute {
|
||||
/**
|
||||
* The type-safe route for the flight recorder screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object Standard : FlightRecorderRoute()
|
||||
|
||||
/**
|
||||
* The type-safe route for the pre-auth flight recorder screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object PreAuth : FlightRecorderRoute()
|
||||
}
|
||||
|
||||
/**
|
||||
* Add flight recorder destination to the nav graph.
|
||||
@ -18,13 +34,19 @@ fun NavGraphBuilder.flightRecorderDestination(
|
||||
isPreAuth: Boolean,
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = getRoute(isPreAuth = isPreAuth),
|
||||
) {
|
||||
if (isPreAuth) {
|
||||
composableWithSlideTransitions<FlightRecorderRoute.PreAuth> {
|
||||
FlightRecorderScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
composableWithSlideTransitions<FlightRecorderRoute.Standard> {
|
||||
FlightRecorderScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -34,9 +56,8 @@ fun NavController.navigateToFlightRecorder(
|
||||
isPreAuth: Boolean,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(route = getRoute(isPreAuth = isPreAuth), navOptions = navOptions)
|
||||
navigate(
|
||||
route = if (isPreAuth) FlightRecorderRoute.PreAuth else FlightRecorderRoute.Standard,
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getRoute(
|
||||
isPreAuth: Boolean,
|
||||
): String = if (isPreAuth) PRE_AUTH_FLIGHT_RECORDER_ROUTE else FLIGHT_RECORDER_ROUTE
|
||||
|
||||
@ -7,10 +7,25 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val PRE_AUTH_FLIGHT_RECORDER_RECORDED_LOGS_ROUTE =
|
||||
"pre_auth_flight_recorder_recorded_logs"
|
||||
private const val FLIGHT_RECORDER_RECORDED_LOGS_ROUTE = "flight_recorder_recorded_logs"
|
||||
/**
|
||||
* The type-safe route for the recorded logs screen.
|
||||
*/
|
||||
@Serializable
|
||||
sealed class RecordedLogsRoute {
|
||||
/**
|
||||
* The type-safe route for the recorded logs screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object Standard : RecordedLogsRoute()
|
||||
|
||||
/**
|
||||
* The type-safe route for the pre-auth recorded logs screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object PreAuth : RecordedLogsRoute()
|
||||
}
|
||||
|
||||
/**
|
||||
* Add recorded logs destination to the nav graph.
|
||||
@ -19,13 +34,19 @@ fun NavGraphBuilder.recordedLogsDestination(
|
||||
isPreAuth: Boolean,
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = getRoute(isPreAuth = isPreAuth),
|
||||
) {
|
||||
if (isPreAuth) {
|
||||
composableWithSlideTransitions<RecordedLogsRoute.PreAuth> {
|
||||
RecordedLogsScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
composableWithSlideTransitions<RecordedLogsRoute.Standard> {
|
||||
RecordedLogsScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,14 +56,8 @@ fun NavController.navigateToRecordedLogs(
|
||||
isPreAuth: Boolean,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(route = getRoute(isPreAuth = isPreAuth), navOptions = navOptions)
|
||||
}
|
||||
|
||||
private fun getRoute(
|
||||
isPreAuth: Boolean,
|
||||
): String =
|
||||
if (isPreAuth) {
|
||||
PRE_AUTH_FLIGHT_RECORDER_RECORDED_LOGS_ROUTE
|
||||
} else {
|
||||
FLIGHT_RECORDER_RECORDED_LOGS_ROUTE
|
||||
navigate(
|
||||
route = if (isPreAuth) RecordedLogsRoute.PreAuth else RecordedLogsRoute.Standard,
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
@ -7,8 +7,13 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val FOLDERS_ROUTE = "settings_folders"
|
||||
/**
|
||||
* The type-safe route for the folders screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object FoldersRoute
|
||||
|
||||
/**
|
||||
* Add folders destinations to the nav graph.
|
||||
@ -18,9 +23,7 @@ fun NavGraphBuilder.foldersDestination(
|
||||
onNavigateToAddFolderScreen: () -> Unit,
|
||||
onNavigateToEditFolderScreen: (folderId: String) -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = FOLDERS_ROUTE,
|
||||
) {
|
||||
composableWithSlideTransitions<FoldersRoute> {
|
||||
FoldersScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToAddFolderScreen = onNavigateToAddFolderScreen,
|
||||
@ -33,5 +36,5 @@ fun NavGraphBuilder.foldersDestination(
|
||||
* Navigate to the folders screen.
|
||||
*/
|
||||
fun NavController.navigateToFolders(navOptions: NavOptions? = null) {
|
||||
navigate(FOLDERS_ROUTE, navOptions)
|
||||
this.navigate(route = FoldersRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -6,23 +6,37 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.folders.model.FolderAddEditType
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val ADD_TYPE: String = "add"
|
||||
private const val EDIT_TYPE: String = "edit"
|
||||
private const val EDIT_ITEM_ID: String = "folder_edit_id"
|
||||
private const val PARENT_FOLDER_NAME: String = "parent_folder_name"
|
||||
|
||||
private const val ADD_EDIT_ITEM_PREFIX: String = "folder_add_edit_item"
|
||||
private const val ADD_EDIT_ITEM_TYPE: String = "folder_add_edit_type"
|
||||
|
||||
private const val ADD_EDIT_ITEM_ROUTE: String =
|
||||
"$ADD_EDIT_ITEM_PREFIX/{$ADD_EDIT_ITEM_TYPE}" +
|
||||
"?$EDIT_ITEM_ID={$EDIT_ITEM_ID}&$PARENT_FOLDER_NAME={$PARENT_FOLDER_NAME}"
|
||||
/**
|
||||
* The type-safe route for the login approval screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class FolderAddEditRoute(
|
||||
val actionType: FolderActionType,
|
||||
val folderId: String?,
|
||||
val parentFolderName: String?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents the action being done with a folder.
|
||||
*/
|
||||
@Serializable
|
||||
enum class FolderActionType {
|
||||
ADD,
|
||||
EDIT,
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to retrieve folder add & edit arguments from the [SavedStateHandle].
|
||||
@ -30,14 +44,19 @@ private const val ADD_EDIT_ITEM_ROUTE: String =
|
||||
data class FolderAddEditArgs(
|
||||
val folderAddEditType: FolderAddEditType,
|
||||
val parentFolderName: String?,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
folderAddEditType = when (requireNotNull(savedStateHandle[ADD_EDIT_ITEM_TYPE])) {
|
||||
ADD_TYPE -> FolderAddEditType.AddItem
|
||||
EDIT_TYPE -> FolderAddEditType.EditItem(requireNotNull(savedStateHandle[EDIT_ITEM_ID]))
|
||||
else -> throw IllegalStateException("Unknown FolderAddEditType.")
|
||||
)
|
||||
|
||||
/**
|
||||
* Constructs a [FolderAddEditArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toFolderAddEditArgs(): FolderAddEditArgs {
|
||||
val route = this.toRoute<FolderAddEditRoute>()
|
||||
return FolderAddEditArgs(
|
||||
folderAddEditType = when (route.actionType) {
|
||||
FolderActionType.ADD -> FolderAddEditType.AddItem
|
||||
FolderActionType.EDIT -> FolderAddEditType.EditItem(requireNotNull(route.folderId))
|
||||
},
|
||||
parentFolderName = savedStateHandle[PARENT_FOLDER_NAME],
|
||||
parentFolderName = route.parentFolderName,
|
||||
)
|
||||
}
|
||||
|
||||
@ -47,20 +66,7 @@ data class FolderAddEditArgs(
|
||||
fun NavGraphBuilder.folderAddEditDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = ADD_EDIT_ITEM_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(ADD_EDIT_ITEM_TYPE) { type = NavType.StringType },
|
||||
navArgument(EDIT_ITEM_ID) {
|
||||
nullable = true
|
||||
type = NavType.StringType
|
||||
},
|
||||
navArgument(PARENT_FOLDER_NAME) {
|
||||
nullable = true
|
||||
type = NavType.StringType
|
||||
},
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<FolderAddEditRoute> {
|
||||
FolderAddEditScreen(onNavigateBack = onNavigateBack)
|
||||
}
|
||||
}
|
||||
@ -73,18 +79,20 @@ fun NavController.navigateToFolderAddEdit(
|
||||
parentFolderName: String? = null,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(
|
||||
route = "$ADD_EDIT_ITEM_PREFIX/${folderAddEditType.toTypeString()}" +
|
||||
"?$EDIT_ITEM_ID=${folderAddEditType.toIdOrNull()}" +
|
||||
"&$PARENT_FOLDER_NAME=$parentFolderName",
|
||||
this.navigate(
|
||||
route = FolderAddEditRoute(
|
||||
actionType = folderAddEditType.toFolderActionType(),
|
||||
folderId = folderAddEditType.toIdOrNull(),
|
||||
parentFolderName = parentFolderName,
|
||||
),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
private fun FolderAddEditType.toTypeString(): String =
|
||||
private fun FolderAddEditType.toFolderActionType(): FolderActionType =
|
||||
when (this) {
|
||||
is FolderAddEditType.AddItem -> ADD_TYPE
|
||||
is FolderAddEditType.EditItem -> EDIT_TYPE
|
||||
is FolderAddEditType.AddItem -> FolderActionType.ADD
|
||||
is FolderAddEditType.EditItem -> FolderActionType.EDIT
|
||||
}
|
||||
|
||||
private fun FolderAddEditType.toIdOrNull(): String? =
|
||||
|
||||
@ -39,7 +39,7 @@ class FolderAddEditViewModel @Inject constructor(
|
||||
// We load the state from the savedStateHandle for testing purposes.
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: run {
|
||||
val folderAddEditArgs = FolderAddEditArgs(savedStateHandle)
|
||||
val folderAddEditArgs = savedStateHandle.toFolderAddEditArgs()
|
||||
FolderAddEditState(
|
||||
folderAddEditType = folderAddEditArgs.folderAddEditType,
|
||||
viewState = when (folderAddEditArgs.folderAddEditType) {
|
||||
|
||||
@ -6,22 +6,52 @@ 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.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import com.x8bit.bitwarden.ui.platform.util.toObjectRoute
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val IS_PRE_AUTH: String = "isPreAuth"
|
||||
private const val PRE_AUTH_OTHER_ROUTE = "pre_auth_settings_other"
|
||||
private const val OTHER_ROUTE = "settings_other"
|
||||
/**
|
||||
* The type-safe route for the settings other screen.
|
||||
*/
|
||||
@Serializable
|
||||
sealed class SettingsOtherRoute {
|
||||
/**
|
||||
* Indicates that the settings other screen should be shown as a pre-authentication.
|
||||
*/
|
||||
abstract val isPreAuth: Boolean
|
||||
|
||||
/**
|
||||
* The type-safe route for the settings other screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object Standard : SettingsOtherRoute() {
|
||||
override val isPreAuth: Boolean get() = false
|
||||
}
|
||||
|
||||
/**
|
||||
* The type-safe route for the pre-auth settings other screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object PreAuth : SettingsOtherRoute() {
|
||||
override val isPreAuth: Boolean get() = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to retrieve other settings arguments from the [SavedStateHandle].
|
||||
*/
|
||||
data class OtherArgs(val isPreAuth: Boolean) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
isPreAuth = requireNotNull(savedStateHandle[IS_PRE_AUTH]),
|
||||
)
|
||||
data class OtherArgs(val isPreAuth: Boolean)
|
||||
|
||||
/**
|
||||
* Constructs a [OtherArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toOtherArgs(): OtherArgs {
|
||||
val route = this.toObjectRoute<SettingsOtherRoute.PreAuth>()
|
||||
?: this.toObjectRoute<SettingsOtherRoute.Standard>()
|
||||
return route
|
||||
?.let { OtherArgs(isPreAuth = it.isPreAuth) }
|
||||
?: throw IllegalStateException("Missing correct route for SettingsOtherScreen")
|
||||
}
|
||||
|
||||
/**
|
||||
@ -31,17 +61,15 @@ fun NavGraphBuilder.otherDestination(
|
||||
isPreAuth: Boolean,
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithPushTransitions(
|
||||
route = getRoute(isPreAuth = isPreAuth),
|
||||
arguments = listOf(
|
||||
navArgument(name = IS_PRE_AUTH) {
|
||||
type = NavType.BoolType
|
||||
defaultValue = isPreAuth
|
||||
},
|
||||
),
|
||||
) {
|
||||
if (isPreAuth) {
|
||||
composableWithPushTransitions<SettingsOtherRoute.PreAuth> {
|
||||
OtherScreen(onNavigateBack = onNavigateBack)
|
||||
}
|
||||
} else {
|
||||
composableWithPushTransitions<SettingsOtherRoute.Standard> {
|
||||
OtherScreen(onNavigateBack = onNavigateBack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -51,9 +79,8 @@ fun NavController.navigateToOther(
|
||||
isPreAuth: Boolean,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(route = getRoute(isPreAuth = isPreAuth), navOptions = navOptions)
|
||||
this.navigate(
|
||||
route = if (isPreAuth) SettingsOtherRoute.PreAuth else SettingsOtherRoute.Standard,
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getRoute(
|
||||
isPreAuth: Boolean,
|
||||
): String = if (isPreAuth) PRE_AUTH_OTHER_ROUTE else OTHER_ROUTE
|
||||
|
||||
@ -40,7 +40,7 @@ class OtherViewModel @Inject constructor(
|
||||
) : BaseViewModel<OtherState, OtherEvent, OtherAction>(
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: OtherState(
|
||||
isPreAuth = OtherArgs(savedStateHandle = savedStateHandle).isPreAuth,
|
||||
isPreAuth = savedStateHandle.toOtherArgs().isPreAuth,
|
||||
allowScreenCapture = settingsRepo.isScreenCaptureAllowed,
|
||||
allowSyncOnRefresh = settingsRepo.getPullToRefreshEnabledFlow().value,
|
||||
clearClipboardFrequency = settingsRepo.clearClipboardFrequency,
|
||||
|
||||
@ -8,8 +8,13 @@ import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val VAULT_SETTINGS_ROUTE = "vault_settings"
|
||||
/**
|
||||
* The type-safe route for the vault settings screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object VaultSettingsRoute
|
||||
|
||||
/**
|
||||
* Add Vault Settings destinations to the nav graph.
|
||||
@ -20,9 +25,7 @@ fun NavGraphBuilder.vaultSettingsDestination(
|
||||
onNavigateToFolders: () -> Unit,
|
||||
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||
) {
|
||||
composableWithPushTransitions(
|
||||
route = VAULT_SETTINGS_ROUTE,
|
||||
) {
|
||||
composableWithPushTransitions<VaultSettingsRoute> {
|
||||
VaultSettingsScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToExportVault = onNavigateToExportVault,
|
||||
@ -36,5 +39,5 @@ fun NavGraphBuilder.vaultSettingsDestination(
|
||||
* Navigate to the Vault Settings screen.
|
||||
*/
|
||||
fun NavController.navigateToVaultSettings(navOptions: NavOptions? = null) {
|
||||
navigate(VAULT_SETTINGS_ROUTE, navOptions)
|
||||
this.navigate(route = VaultSettingsRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -7,14 +7,19 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.compose.composable
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
const val SPLASH_ROUTE: String = "splash"
|
||||
/**
|
||||
* The type-safe route for the splash screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object SplashRoute
|
||||
|
||||
/**
|
||||
* Add splash destinations to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.splashDestination() {
|
||||
composable(SPLASH_ROUTE) { SplashScreen() }
|
||||
composable<SplashRoute> { SplashScreen() }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -23,5 +28,5 @@ fun NavGraphBuilder.splashDestination() {
|
||||
fun NavController.navigateToSplash(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(SPLASH_ROUTE, navOptions)
|
||||
navigate(SplashRoute, navOptions)
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.folders.addedit.navigate
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.folders.foldersDestination
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.folders.model.FolderAddEditType
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.folders.navigateToFolders
|
||||
import com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar.VAULT_UNLOCKED_NAV_BAR_ROUTE
|
||||
import com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar.VaultUnlockedNavbarRoute
|
||||
import com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar.vaultUnlockedNavBarDestination
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.generatorModalDestination
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorPasswordHistoryMode
|
||||
@ -57,14 +57,19 @@ import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.navigateToVaultMo
|
||||
import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.vaultMoveToOrganizationDestination
|
||||
import com.x8bit.bitwarden.ui.vault.feature.qrcodescan.navigateToQrCodeScanScreen
|
||||
import com.x8bit.bitwarden.ui.vault.feature.qrcodescan.vaultQrCodeScanDestination
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
const val VAULT_UNLOCKED_GRAPH_ROUTE: String = "vault_unlocked_graph"
|
||||
/**
|
||||
* The type-safe route for the vault unlocked graph.
|
||||
*/
|
||||
@Serializable
|
||||
data object VaultUnlockedGraphRoute
|
||||
|
||||
/**
|
||||
* Navigate to the vault unlocked screen.
|
||||
*/
|
||||
fun NavController.navigateToVaultUnlockedGraph(navOptions: NavOptions? = null) {
|
||||
navigate(VAULT_UNLOCKED_GRAPH_ROUTE, navOptions)
|
||||
navigate(route = VaultUnlockedGraphRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -74,9 +79,8 @@ fun NavController.navigateToVaultUnlockedGraph(navOptions: NavOptions? = null) {
|
||||
fun NavGraphBuilder.vaultUnlockedGraph(
|
||||
navController: NavController,
|
||||
) {
|
||||
navigation(
|
||||
startDestination = VAULT_UNLOCKED_NAV_BAR_ROUTE,
|
||||
route = VAULT_UNLOCKED_GRAPH_ROUTE,
|
||||
navigation<VaultUnlockedGraphRoute>(
|
||||
startDestination = VaultUnlockedNavbarRoute,
|
||||
) {
|
||||
vaultItemListingDestinationAsRoot(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
|
||||
@ -11,17 +11,19 @@ import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditArgs
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemArgs
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* The functions below pertain to entry into the [VaultUnlockedNavBarScreen].
|
||||
* The type-safe route for the vault unlocked navbar screen.
|
||||
*/
|
||||
const val VAULT_UNLOCKED_NAV_BAR_ROUTE: String = "VaultUnlockedNavBar"
|
||||
@Serializable
|
||||
data object VaultUnlockedNavbarRoute
|
||||
|
||||
/**
|
||||
* Navigate to the [VaultUnlockedNavBarScreen].
|
||||
*/
|
||||
fun NavController.navigateToVaultUnlockedNavBar(navOptions: NavOptions? = null) {
|
||||
navigate(VAULT_UNLOCKED_NAV_BAR_ROUTE, navOptions)
|
||||
navigate(route = VaultUnlockedNavbarRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -48,9 +50,7 @@ fun NavGraphBuilder.vaultUnlockedNavBarDestination(
|
||||
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
|
||||
onNavigateToAddFolderScreen: (selectedFolderName: String?) -> Unit,
|
||||
) {
|
||||
composableWithStayTransitions(
|
||||
route = VAULT_UNLOCKED_NAV_BAR_ROUTE,
|
||||
) {
|
||||
composableWithStayTransitions<VaultUnlockedNavbarRoute> {
|
||||
VaultUnlockedNavBarScreen(
|
||||
onNavigateToVaultAddItem = onNavigateToVaultAddItem,
|
||||
onNavigateToVaultItem = onNavigateToVaultItem,
|
||||
|
||||
@ -42,7 +42,7 @@ import com.x8bit.bitwarden.ui.tools.feature.send.navigateToSendGraph
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.sendGraph
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditArgs
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemArgs
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.VAULT_GRAPH_ROUTE
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultGraphRoute
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.navigateToVaultGraph
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.vaultGraph
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@ -220,7 +220,7 @@ private fun VaultUnlockedNavBarScaffold(
|
||||
// - consume the IME insets.
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = VAULT_GRAPH_ROUTE,
|
||||
startDestination = VaultGraphRoute,
|
||||
enterTransition = RootTransitionProviders.Enter.fadeIn,
|
||||
exitTransition = RootTransitionProviders.Exit.fadeOut,
|
||||
popEnterTransition = RootTransitionProviders.Enter.fadeIn,
|
||||
|
||||
@ -3,14 +3,15 @@ package com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar.model
|
||||
import android.os.Parcelable
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.NavigationItem
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.SETTINGS_GRAPH_ROUTE
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.SETTINGS_ROUTE
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GENERATOR_GRAPH_ROUTE
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GENERATOR_ROUTE
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.SEND_GRAPH_ROUTE
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.SEND_ROUTE
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.VAULT_GRAPH_ROUTE
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.VAULT_ROUTE
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.SettingsGraphRoute
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.SettingsRoute
|
||||
import com.x8bit.bitwarden.ui.platform.util.toObjectNavigationRoute
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorGraphRoute
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorRoute
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.SendGraphRoute
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.SendRoute
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultGraphRoute
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultRoute
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
@ -33,8 +34,8 @@ sealed class VaultUnlockedNavBarTab : NavigationItem, Parcelable {
|
||||
override val iconRes get() = R.drawable.ic_generator
|
||||
override val labelRes get() = R.string.generator
|
||||
override val contentDescriptionRes get() = R.string.generator
|
||||
override val graphRoute: String get() = GENERATOR_GRAPH_ROUTE
|
||||
override val startDestinationRoute get() = GENERATOR_ROUTE
|
||||
override val graphRoute get() = GeneratorGraphRoute.toObjectNavigationRoute()
|
||||
override val startDestinationRoute get() = GeneratorRoute.Standard.toObjectNavigationRoute()
|
||||
override val testTag get() = "GeneratorTab"
|
||||
override val notificationCount get() = 0
|
||||
}
|
||||
@ -48,8 +49,8 @@ sealed class VaultUnlockedNavBarTab : NavigationItem, Parcelable {
|
||||
override val iconRes get() = R.drawable.ic_send
|
||||
override val labelRes get() = R.string.send
|
||||
override val contentDescriptionRes get() = R.string.send
|
||||
override val graphRoute: String get() = SEND_GRAPH_ROUTE
|
||||
override val startDestinationRoute get() = SEND_ROUTE
|
||||
override val graphRoute get() = SendGraphRoute.toObjectNavigationRoute()
|
||||
override val startDestinationRoute get() = SendRoute.toObjectNavigationRoute()
|
||||
override val testTag get() = "SendTab"
|
||||
override val notificationCount get() = 0
|
||||
}
|
||||
@ -64,8 +65,8 @@ sealed class VaultUnlockedNavBarTab : NavigationItem, Parcelable {
|
||||
) : VaultUnlockedNavBarTab() {
|
||||
override val iconResSelected get() = R.drawable.ic_vault_filled
|
||||
override val iconRes get() = R.drawable.ic_vault
|
||||
override val graphRoute: String get() = VAULT_GRAPH_ROUTE
|
||||
override val startDestinationRoute get() = VAULT_ROUTE
|
||||
override val graphRoute get() = VaultGraphRoute.toObjectNavigationRoute()
|
||||
override val startDestinationRoute get() = VaultRoute.toObjectNavigationRoute()
|
||||
override val testTag get() = "VaultTab"
|
||||
override val notificationCount get() = 0
|
||||
}
|
||||
@ -81,8 +82,8 @@ sealed class VaultUnlockedNavBarTab : NavigationItem, Parcelable {
|
||||
override val iconRes get() = R.drawable.ic_settings
|
||||
override val labelRes get() = R.string.settings
|
||||
override val contentDescriptionRes get() = R.string.settings
|
||||
override val graphRoute: String get() = SETTINGS_GRAPH_ROUTE
|
||||
override val startDestinationRoute get() = SETTINGS_ROUTE
|
||||
override val graphRoute get() = SettingsGraphRoute.toObjectNavigationRoute()
|
||||
override val startDestinationRoute get() = SettingsRoute.Standard.toObjectNavigationRoute()
|
||||
override val testTag get() = "SettingsTab"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
package com.x8bit.bitwarden.ui.platform.manager.snackbar
|
||||
|
||||
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Models a relay key to be mapped to an instance of [BitwardenSnackbarData] being sent
|
||||
* between producers and consumers of the data.
|
||||
*/
|
||||
@Serializable
|
||||
enum class SnackbarRelay {
|
||||
VAULT_SETTINGS_RELAY,
|
||||
MY_VAULT_RELAY,
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
package com.x8bit.bitwarden.ui.platform.util
|
||||
|
||||
import kotlinx.serialization.InternalSerializationApi
|
||||
import kotlinx.serialization.serializer
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Gets the route string for an object.
|
||||
*/
|
||||
@OptIn(InternalSerializationApi::class)
|
||||
fun <T : Any> T.toObjectNavigationRoute(): String = this::class.toObjectKClassNavigationRoute()
|
||||
|
||||
/**
|
||||
* Gets the route string for a [KClass] of an object.
|
||||
*/
|
||||
@OptIn(InternalSerializationApi::class)
|
||||
fun <T : Any> KClass<T>.toObjectKClassNavigationRoute(): String =
|
||||
this.serializer().descriptor.serialName
|
||||
@ -0,0 +1,24 @@
|
||||
package com.x8bit.bitwarden.ui.platform.util
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.toRoute
|
||||
|
||||
/**
|
||||
* Determines if the [SavedStateHandle] contains a route for the specified object class.
|
||||
*
|
||||
* This will return the object instance if the route is correct, `null` otherwise.
|
||||
*/
|
||||
inline fun <reified T : Any> SavedStateHandle.toObjectRoute(): T? =
|
||||
this
|
||||
.get<Intent>(key = NavController.KEY_DEEP_LINK_INTENT)
|
||||
?.data
|
||||
?.pathSegments
|
||||
.orEmpty()
|
||||
.takeIf { segments -> segments.any { it == T::class.toObjectKClassNavigationRoute() } }
|
||||
?.let { _ ->
|
||||
// This will get the instance for us. We only do this after the checks above as it
|
||||
// will always return the object instance even if it is not the correct one.
|
||||
this.toRoute<T>()
|
||||
}
|
||||
@ -7,8 +7,13 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.navigation
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
const val GENERATOR_GRAPH_ROUTE: String = "generator_graph"
|
||||
/**
|
||||
* The type-safe route for the generator graph.
|
||||
*/
|
||||
@Serializable
|
||||
data object GeneratorGraphRoute
|
||||
|
||||
/**
|
||||
* Add generator destination to the root nav graph.
|
||||
@ -17,9 +22,8 @@ fun NavGraphBuilder.generatorGraph(
|
||||
onNavigateToPasswordHistory: () -> Unit,
|
||||
onDimNavBarRequest: (Boolean) -> Unit,
|
||||
) {
|
||||
navigation(
|
||||
route = GENERATOR_GRAPH_ROUTE,
|
||||
startDestination = GENERATOR_ROUTE,
|
||||
navigation<GeneratorGraphRoute>(
|
||||
startDestination = GeneratorRoute.Standard,
|
||||
) {
|
||||
generatorDestination(
|
||||
onNavigateToPasswordHistory = onNavigateToPasswordHistory,
|
||||
@ -32,5 +36,5 @@ fun NavGraphBuilder.generatorGraph(
|
||||
* Navigate to the generator graph.
|
||||
*/
|
||||
fun NavController.navigateToGeneratorGraph(navOptions: NavOptions? = null) {
|
||||
navigate(GENERATOR_GRAPH_ROUTE, navOptions)
|
||||
this.navigate(route = GeneratorGraphRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -6,44 +6,71 @@ import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navArgument
|
||||
import androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* The functions below pertain to entry into the [GeneratorScreen].
|
||||
* The type-safe route for the generator screen.
|
||||
*/
|
||||
private const val GENERATOR_MODAL_ROUTE_PREFIX: String = "generator_modal"
|
||||
private const val GENERATOR_MODE_TYPE: String = "generator_mode_type"
|
||||
private const val GENERATOR_WEBSITE: String = "generator_website"
|
||||
private const val USERNAME_GENERATOR: String = "username_generator"
|
||||
private const val PASSWORD_GENERATOR: String = "password_generator"
|
||||
@Serializable
|
||||
sealed class GeneratorRoute {
|
||||
/**
|
||||
* The type-safe route for the standard generator screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object Standard : GeneratorRoute()
|
||||
|
||||
const val GENERATOR_ROUTE: String = "generator"
|
||||
private const val GENERATOR_MODAL_ROUTE: String =
|
||||
"$GENERATOR_MODAL_ROUTE_PREFIX/{$GENERATOR_MODE_TYPE}?$GENERATOR_WEBSITE={$GENERATOR_WEBSITE}"
|
||||
/**
|
||||
* The type-safe route for the modal generator screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class Modal(
|
||||
val type: ModalType,
|
||||
val website: String?,
|
||||
) : GeneratorRoute()
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the type of modal to be displayed.
|
||||
*/
|
||||
@Serializable
|
||||
enum class ModalType {
|
||||
PASSWORD,
|
||||
USERNAME,
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to retrieve vault item listing arguments from the [SavedStateHandle].
|
||||
*/
|
||||
data class GeneratorArgs(
|
||||
val type: GeneratorMode,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
type = when (savedStateHandle.get<String>(GENERATOR_MODE_TYPE)) {
|
||||
USERNAME_GENERATOR -> GeneratorMode.Modal.Username(
|
||||
website = savedStateHandle[GENERATOR_WEBSITE],
|
||||
)
|
||||
|
||||
PASSWORD_GENERATOR -> GeneratorMode.Modal.Password
|
||||
else -> GeneratorMode.Default
|
||||
/**
|
||||
* Constructs a [GeneratorArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toGeneratorArgs(): GeneratorArgs {
|
||||
return GeneratorArgs(
|
||||
type = try {
|
||||
this.toModalGeneratorMode()
|
||||
} catch (_: Exception) {
|
||||
GeneratorMode.Default
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun SavedStateHandle.toModalGeneratorMode(): GeneratorMode.Modal {
|
||||
val route = this.toRoute<GeneratorRoute.Modal>()
|
||||
return when (route.type) {
|
||||
ModalType.PASSWORD -> GeneratorMode.Modal.Password
|
||||
ModalType.USERNAME -> GeneratorMode.Modal.Username(website = route.website)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add generator destination to the root nav graph.
|
||||
*/
|
||||
@ -51,7 +78,7 @@ fun NavGraphBuilder.generatorDestination(
|
||||
onNavigateToPasswordHistory: () -> Unit,
|
||||
onDimNavBarRequest: (Boolean) -> Unit,
|
||||
) {
|
||||
composable(GENERATOR_ROUTE) {
|
||||
composable<GeneratorRoute.Standard> {
|
||||
GeneratorScreen(
|
||||
onNavigateToPasswordHistory = onNavigateToPasswordHistory,
|
||||
onNavigateBack = {},
|
||||
@ -66,16 +93,7 @@ fun NavGraphBuilder.generatorDestination(
|
||||
fun NavGraphBuilder.generatorModalDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = GENERATOR_MODAL_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(GENERATOR_MODE_TYPE) { type = NavType.StringType },
|
||||
navArgument(GENERATOR_WEBSITE) {
|
||||
type = NavType.StringType
|
||||
nullable = true
|
||||
},
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<GeneratorRoute.Modal> {
|
||||
GeneratorScreen(
|
||||
onNavigateToPasswordHistory = {},
|
||||
onNavigateBack = onNavigateBack,
|
||||
@ -91,13 +109,14 @@ fun NavController.navigateToGeneratorModal(
|
||||
mode: GeneratorMode.Modal,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
val generatorModeType = when (mode) {
|
||||
GeneratorMode.Modal.Password -> PASSWORD_GENERATOR
|
||||
is GeneratorMode.Modal.Username -> USERNAME_GENERATOR
|
||||
}
|
||||
val website = (mode as? GeneratorMode.Modal.Username)?.website
|
||||
navigate(
|
||||
route = "$GENERATOR_MODAL_ROUTE_PREFIX/$generatorModeType?$GENERATOR_WEBSITE=$website",
|
||||
route = GeneratorRoute.Modal(
|
||||
type = when (mode) {
|
||||
GeneratorMode.Modal.Password -> ModalType.PASSWORD
|
||||
is GeneratorMode.Modal.Username -> ModalType.USERNAME
|
||||
},
|
||||
website = (mode as? GeneratorMode.Modal.Username)?.website,
|
||||
),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ class GeneratorViewModel @Inject constructor(
|
||||
private val featureFlagManager: FeatureFlagManager,
|
||||
) : BaseViewModel<GeneratorState, GeneratorEvent, GeneratorAction>(
|
||||
initialState = savedStateHandle[KEY_STATE] ?: run {
|
||||
val generatorMode = GeneratorArgs(savedStateHandle).type
|
||||
val generatorMode = savedStateHandle.toGeneratorArgs().type
|
||||
GeneratorState(
|
||||
generatedText = NO_GENERATED_TEXT,
|
||||
selectedType = when (generatorMode) {
|
||||
|
||||
@ -6,38 +6,48 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorPasswordHistoryMode
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val DEFAULT_MODE: String = "default"
|
||||
private const val ITEM_MODE: String = "item"
|
||||
/**
|
||||
* The type-safe route for the password history screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class PasswordHistoryRoute(
|
||||
val passwordHistoryType: PasswordHistoryType,
|
||||
val itemId: String?,
|
||||
)
|
||||
|
||||
private const val PASSWORD_HISTORY_PREFIX: String = "password_history"
|
||||
private const val PASSWORD_HISTORY_MODE: String = "password_history_mode"
|
||||
private const val PASSWORD_HISTORY_ITEM_ID: String = "password_history_id"
|
||||
|
||||
private const val PASSWORD_HISTORY_ROUTE: String =
|
||||
PASSWORD_HISTORY_PREFIX +
|
||||
"/{$PASSWORD_HISTORY_MODE}" +
|
||||
"?$PASSWORD_HISTORY_ITEM_ID={$PASSWORD_HISTORY_ITEM_ID}"
|
||||
/**
|
||||
* Indicates the type of password to be displayed.
|
||||
*/
|
||||
@Serializable
|
||||
enum class PasswordHistoryType {
|
||||
DEFAULT,
|
||||
ITEM,
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to retrieve password history arguments from the [SavedStateHandle].
|
||||
*/
|
||||
data class PasswordHistoryArgs(
|
||||
val passwordHistoryMode: GeneratorPasswordHistoryMode,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
passwordHistoryMode = when (requireNotNull(savedStateHandle[PASSWORD_HISTORY_MODE])) {
|
||||
DEFAULT_MODE -> GeneratorPasswordHistoryMode.Default
|
||||
ITEM_MODE -> GeneratorPasswordHistoryMode.Item(
|
||||
requireNotNull(savedStateHandle[PASSWORD_HISTORY_ITEM_ID]),
|
||||
)
|
||||
|
||||
else -> throw IllegalStateException("Unknown VaultAddEditType.")
|
||||
/**
|
||||
* Constructs a [PasswordHistoryArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toPasswordHistoryArgs(): PasswordHistoryArgs {
|
||||
val route = this.toRoute<PasswordHistoryRoute>()
|
||||
return PasswordHistoryArgs(
|
||||
passwordHistoryMode = when (route.passwordHistoryType) {
|
||||
PasswordHistoryType.DEFAULT -> GeneratorPasswordHistoryMode.Default
|
||||
PasswordHistoryType.ITEM -> GeneratorPasswordHistoryMode.Item(
|
||||
itemId = requireNotNull(route.itemId),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -48,12 +58,7 @@ data class PasswordHistoryArgs(
|
||||
fun NavGraphBuilder.passwordHistoryDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = PASSWORD_HISTORY_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(PASSWORD_HISTORY_MODE) { type = NavType.StringType },
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<PasswordHistoryRoute> {
|
||||
PasswordHistoryScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
@ -68,20 +73,13 @@ fun NavController.navigateToPasswordHistory(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(
|
||||
route = "$PASSWORD_HISTORY_PREFIX/${passwordHistoryMode.toModeString()}" +
|
||||
"?$PASSWORD_HISTORY_ITEM_ID=${passwordHistoryMode.toIdOrNull()}",
|
||||
route = PasswordHistoryRoute(
|
||||
passwordHistoryType = when (passwordHistoryMode) {
|
||||
GeneratorPasswordHistoryMode.Default -> PasswordHistoryType.DEFAULT
|
||||
is GeneratorPasswordHistoryMode.Item -> PasswordHistoryType.ITEM
|
||||
},
|
||||
itemId = (passwordHistoryMode as? GeneratorPasswordHistoryMode.Item)?.itemId,
|
||||
),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
private fun GeneratorPasswordHistoryMode.toModeString(): String =
|
||||
when (this) {
|
||||
is GeneratorPasswordHistoryMode.Default -> DEFAULT_MODE
|
||||
is GeneratorPasswordHistoryMode.Item -> ITEM_MODE
|
||||
}
|
||||
|
||||
private fun GeneratorPasswordHistoryMode.toIdOrNull(): String? =
|
||||
when (this) {
|
||||
is GeneratorPasswordHistoryMode.Default -> null
|
||||
is GeneratorPasswordHistoryMode.Item -> itemId
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@ class PasswordHistoryViewModel @Inject constructor(
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: run {
|
||||
PasswordHistoryState(
|
||||
passwordHistoryMode = PasswordHistoryArgs(savedStateHandle).passwordHistoryMode,
|
||||
passwordHistoryMode = savedStateHandle.toPasswordHistoryArgs().passwordHistoryMode,
|
||||
viewState = PasswordHistoryState.ViewState.Loading,
|
||||
)
|
||||
},
|
||||
|
||||
@ -11,8 +11,13 @@ import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.navigateToSendItemListing
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.sendItemListingDestination
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
const val SEND_GRAPH_ROUTE: String = "send_graph"
|
||||
/**
|
||||
* The type-safe route for the send graph.
|
||||
*/
|
||||
@Serializable
|
||||
data object SendGraphRoute
|
||||
|
||||
/**
|
||||
* Add send destination to the nav graph.
|
||||
@ -23,9 +28,8 @@ fun NavGraphBuilder.sendGraph(
|
||||
onNavigateToEditSend: (sendItemId: String) -> Unit,
|
||||
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
|
||||
) {
|
||||
navigation(
|
||||
startDestination = SEND_ROUTE,
|
||||
route = SEND_GRAPH_ROUTE,
|
||||
navigation<SendGraphRoute>(
|
||||
startDestination = SendRoute,
|
||||
) {
|
||||
sendDestination(
|
||||
onNavigateToAddSend = onNavigateToAddSend,
|
||||
@ -52,5 +56,5 @@ fun NavGraphBuilder.sendGraph(
|
||||
* via [sendGraph].
|
||||
*/
|
||||
fun NavController.navigateToSendGraph(navOptions: NavOptions? = null) {
|
||||
navigate(SEND_GRAPH_ROUTE, navOptions)
|
||||
navigate(route = SendGraphRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -8,8 +8,13 @@ import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithRootPushTransitions
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
const val SEND_ROUTE: String = "send"
|
||||
/**
|
||||
* The type-safe route for the send screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object SendRoute
|
||||
|
||||
/**
|
||||
* Add send destination to the nav graph.
|
||||
@ -21,9 +26,7 @@ fun NavGraphBuilder.sendDestination(
|
||||
onNavigateToSendTextList: () -> Unit,
|
||||
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
|
||||
) {
|
||||
composableWithRootPushTransitions(
|
||||
route = SEND_ROUTE,
|
||||
) {
|
||||
composableWithRootPushTransitions<SendRoute> {
|
||||
SendScreen(
|
||||
onNavigateToAddSend = onNavigateToAddSend,
|
||||
onNavigateToEditSend = onNavigateToEditSend,
|
||||
@ -39,5 +42,5 @@ fun NavGraphBuilder.sendDestination(
|
||||
* via [sendDestination].
|
||||
*/
|
||||
fun NavController.navigateToSend(navOptions: NavOptions? = null) {
|
||||
navigate(SEND_ROUTE, navOptions)
|
||||
navigate(route = SendRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -6,51 +6,65 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.model.AddSendType
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val ADD_TYPE: String = "add"
|
||||
private const val EDIT_TYPE: String = "edit"
|
||||
private const val EDIT_ITEM_ID: String = "edit_send_id"
|
||||
/**
|
||||
* The type-safe route for the add send screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class AddSendRoute(
|
||||
val type: ModeType,
|
||||
val editSendId: String?,
|
||||
)
|
||||
|
||||
private const val ADD_SEND_ITEM_PREFIX: String = "add_send_item"
|
||||
private const val ADD_SEND_ITEM_TYPE: String = "add_send_item_type"
|
||||
|
||||
const val ADD_SEND_ROUTE: String =
|
||||
"$ADD_SEND_ITEM_PREFIX/{$ADD_SEND_ITEM_TYPE}?$EDIT_ITEM_ID={$EDIT_ITEM_ID}"
|
||||
/**
|
||||
* Indicates the mode of send to be displayed.
|
||||
*/
|
||||
@Serializable
|
||||
enum class ModeType {
|
||||
ADD,
|
||||
EDIT,
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to retrieve send add & edit arguments from the [SavedStateHandle].
|
||||
*/
|
||||
data class AddSendArgs(
|
||||
val sendAddType: AddSendType,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
sendAddType = when (requireNotNull(savedStateHandle.get<String>(ADD_SEND_ITEM_TYPE))) {
|
||||
ADD_TYPE -> AddSendType.AddItem
|
||||
EDIT_TYPE -> AddSendType.EditItem(requireNotNull(savedStateHandle[EDIT_ITEM_ID]))
|
||||
else -> throw IllegalStateException("Unknown VaultAddEditType.")
|
||||
)
|
||||
|
||||
/**
|
||||
* Constructs a [AddSendArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toAddSendArgs(): AddSendArgs {
|
||||
val route = this.toRoute<AddSendRoute>()
|
||||
return AddSendArgs(
|
||||
sendAddType = when (route.type) {
|
||||
ModeType.ADD -> AddSendType.AddItem
|
||||
ModeType.EDIT -> AddSendType.EditItem(sendItemId = requireNotNull(route.editSendId))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun SavedStateHandle.toAddSendType(): AddSendType {
|
||||
val route = this.toRoute<AddSendRoute>()
|
||||
return when (route.type) {
|
||||
ModeType.ADD -> AddSendType.AddItem
|
||||
ModeType.EDIT -> AddSendType.EditItem(sendItemId = requireNotNull(route.editSendId))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the new send screen to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.addSendDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = ADD_SEND_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(ADD_SEND_ITEM_TYPE) {
|
||||
type = NavType.StringType
|
||||
},
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<AddSendRoute> {
|
||||
AddSendScreen(onNavigateBack = onNavigateBack)
|
||||
}
|
||||
}
|
||||
@ -62,18 +76,14 @@ fun NavController.navigateToAddSend(
|
||||
sendAddType: AddSendType,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(
|
||||
route = "$ADD_SEND_ITEM_PREFIX/${sendAddType.toTypeString()}" +
|
||||
"?${EDIT_ITEM_ID}=${sendAddType.toIdOrNull()}",
|
||||
this.navigate(
|
||||
route = AddSendRoute(
|
||||
type = when (sendAddType) {
|
||||
AddSendType.AddItem -> ModeType.ADD
|
||||
is AddSendType.EditItem -> ModeType.EDIT
|
||||
},
|
||||
editSendId = (sendAddType as? AddSendType.EditItem)?.sendItemId,
|
||||
),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
private fun AddSendType.toTypeString(): String =
|
||||
when (this) {
|
||||
is AddSendType.AddItem -> ADD_TYPE
|
||||
is AddSendType.EditItem -> EDIT_TYPE
|
||||
}
|
||||
|
||||
private fun AddSendType.toIdOrNull(): String? =
|
||||
(this as? AddSendType.EditItem)?.sendItemId
|
||||
|
||||
@ -78,7 +78,7 @@ class AddSendViewModel @Inject constructor(
|
||||
// Check to see if we are navigating here from an external source
|
||||
val specialCircumstance = specialCircumstanceManager.specialCircumstance
|
||||
val shareSendType = specialCircumstance.toSendType()
|
||||
val sendAddType = AddSendArgs(savedStateHandle).sendAddType
|
||||
val sendAddType = savedStateHandle.toAddSendArgs().sendAddType
|
||||
AddSendState(
|
||||
shouldFinishOnComplete = specialCircumstance.shouldFinishOnComplete(),
|
||||
isShared = shareSendType != null,
|
||||
|
||||
@ -6,38 +6,35 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val ADD_TYPE: String = "add"
|
||||
private const val EDIT_TYPE: String = "edit"
|
||||
private const val CLONE_TYPE: String = "clone"
|
||||
private const val EDIT_ITEM_ID: String = "vault_edit_id"
|
||||
/**
|
||||
* The type-safe route for the vault add/edit screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class VaultAddEditRoute(
|
||||
val vaultAddEditMode: VaultAddEditMode,
|
||||
val vaultItemId: String?,
|
||||
val vaultItemCipherType: VaultItemCipherType,
|
||||
val selectedFolderId: String? = null,
|
||||
val selectedCollectionId: String? = null,
|
||||
)
|
||||
|
||||
private const val LOGIN: String = "login"
|
||||
private const val CARD: String = "card"
|
||||
private const val IDENTITY: String = "identity"
|
||||
private const val SECURE_NOTE: String = "secure_note"
|
||||
private const val SSH_KEY: String = "ssh_key"
|
||||
private const val CIPHER_TYPE: String = "vault_item_type"
|
||||
|
||||
private const val ADD_EDIT_ITEM_PREFIX: String = "vault_add_edit_item"
|
||||
private const val ADD_EDIT_ITEM_TYPE: String = "vault_add_edit_type"
|
||||
private const val ADD_SELECTED_FOLDER_ID: String = "vault_add_selected_folder_id"
|
||||
private const val ADD_SELECTED_COLLECTION_ID: String = "vault_add_selected_collection_id"
|
||||
|
||||
private const val ADD_EDIT_ITEM_ROUTE: String =
|
||||
ADD_EDIT_ITEM_PREFIX +
|
||||
"/{$ADD_EDIT_ITEM_TYPE}" +
|
||||
"?$EDIT_ITEM_ID={$EDIT_ITEM_ID}" +
|
||||
"?$CIPHER_TYPE={$CIPHER_TYPE}" +
|
||||
"?$ADD_SELECTED_FOLDER_ID={$ADD_SELECTED_FOLDER_ID}" +
|
||||
"?$ADD_SELECTED_COLLECTION_ID={$ADD_SELECTED_COLLECTION_ID}"
|
||||
/**
|
||||
* The mode in which the vault add/edit screen should be displayed.
|
||||
*/
|
||||
@Serializable
|
||||
enum class VaultAddEditMode {
|
||||
ADD,
|
||||
EDIT,
|
||||
CLONE,
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to retrieve vault add & edit arguments from the [SavedStateHandle].
|
||||
@ -47,24 +44,27 @@ data class VaultAddEditArgs(
|
||||
val vaultItemCipherType: VaultItemCipherType,
|
||||
val selectedFolderId: String? = null,
|
||||
val selectedCollectionId: String? = null,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
vaultAddEditType = when (requireNotNull(savedStateHandle[ADD_EDIT_ITEM_TYPE])) {
|
||||
ADD_TYPE -> VaultAddEditType.AddItem
|
||||
EDIT_TYPE -> VaultAddEditType.EditItem(
|
||||
vaultItemId = requireNotNull(savedStateHandle[EDIT_ITEM_ID]),
|
||||
)
|
||||
|
||||
CLONE_TYPE -> VaultAddEditType.CloneItem(
|
||||
vaultItemId = requireNotNull(savedStateHandle[EDIT_ITEM_ID]),
|
||||
)
|
||||
/**
|
||||
* Constructs a [VaultAddEditArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toVaultAddEditArgs(): VaultAddEditArgs {
|
||||
val route = this.toRoute<VaultAddEditRoute>()
|
||||
return VaultAddEditArgs(
|
||||
vaultAddEditType = when (route.vaultAddEditMode) {
|
||||
VaultAddEditMode.ADD -> VaultAddEditType.AddItem
|
||||
VaultAddEditMode.EDIT -> {
|
||||
VaultAddEditType.EditItem(vaultItemId = requireNotNull(route.vaultItemId))
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException("Unknown VaultAddEditType.")
|
||||
VaultAddEditMode.CLONE -> {
|
||||
VaultAddEditType.CloneItem(vaultItemId = requireNotNull(route.vaultItemId))
|
||||
}
|
||||
},
|
||||
vaultItemCipherType = requireNotNull(savedStateHandle.get<String>(CIPHER_TYPE))
|
||||
.toVaultItemCipherType(),
|
||||
selectedFolderId = savedStateHandle[ADD_SELECTED_FOLDER_ID],
|
||||
selectedCollectionId = savedStateHandle[ADD_SELECTED_COLLECTION_ID],
|
||||
vaultItemCipherType = route.vaultItemCipherType,
|
||||
selectedFolderId = route.selectedFolderId,
|
||||
selectedCollectionId = route.selectedCollectionId,
|
||||
)
|
||||
}
|
||||
|
||||
@ -80,25 +80,7 @@ fun NavGraphBuilder.vaultAddEditDestination(
|
||||
onNavigateToAttachments: (cipherId: String) -> Unit,
|
||||
onNavigateToMoveToOrganization: (cipherId: String, showOnlyCollections: Boolean) -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = ADD_EDIT_ITEM_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(ADD_EDIT_ITEM_TYPE) { type = NavType.StringType },
|
||||
navArgument(CIPHER_TYPE) { type = NavType.StringType },
|
||||
navArgument(ADD_SELECTED_FOLDER_ID) {
|
||||
type = NavType.StringType
|
||||
nullable = true
|
||||
},
|
||||
navArgument(ADD_SELECTED_COLLECTION_ID) {
|
||||
type = NavType.StringType
|
||||
nullable = true
|
||||
},
|
||||
navArgument(ADD_SELECTED_COLLECTION_ID) {
|
||||
type = NavType.StringType
|
||||
nullable = true
|
||||
},
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<VaultAddEditRoute> {
|
||||
VaultAddEditScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToManualCodeEntryScreen = onNavigateToManualCodeEntryScreen,
|
||||
@ -118,46 +100,17 @@ fun NavController.navigateToVaultAddEdit(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(
|
||||
route = "$ADD_EDIT_ITEM_PREFIX/${args.vaultAddEditType.toTypeString()}" +
|
||||
"?$EDIT_ITEM_ID=${args.vaultAddEditType.toIdOrNull()}" +
|
||||
"?$CIPHER_TYPE=${args.vaultItemCipherType.toTypeString()}" +
|
||||
"?$ADD_SELECTED_FOLDER_ID=${args.selectedFolderId}" +
|
||||
"?$ADD_SELECTED_COLLECTION_ID=${args.selectedCollectionId}",
|
||||
route = VaultAddEditRoute(
|
||||
vaultAddEditMode = when (args.vaultAddEditType) {
|
||||
VaultAddEditType.AddItem -> VaultAddEditMode.ADD
|
||||
is VaultAddEditType.CloneItem -> VaultAddEditMode.CLONE
|
||||
is VaultAddEditType.EditItem -> VaultAddEditMode.EDIT
|
||||
},
|
||||
vaultItemId = args.vaultAddEditType.vaultItemId,
|
||||
vaultItemCipherType = args.vaultItemCipherType,
|
||||
selectedFolderId = args.selectedFolderId,
|
||||
selectedCollectionId = args.selectedFolderId,
|
||||
),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
private fun VaultAddEditType.toTypeString(): String =
|
||||
when (this) {
|
||||
is VaultAddEditType.AddItem -> ADD_TYPE
|
||||
is VaultAddEditType.EditItem -> EDIT_TYPE
|
||||
is VaultAddEditType.CloneItem -> CLONE_TYPE
|
||||
}
|
||||
|
||||
private fun VaultAddEditType.toIdOrNull(): String? =
|
||||
when (this) {
|
||||
is VaultAddEditType.AddItem -> null
|
||||
is VaultAddEditType.CloneItem -> vaultItemId
|
||||
is VaultAddEditType.EditItem -> vaultItemId
|
||||
}
|
||||
|
||||
private fun VaultItemCipherType.toTypeString(): String =
|
||||
when (this) {
|
||||
VaultItemCipherType.LOGIN -> LOGIN
|
||||
VaultItemCipherType.CARD -> CARD
|
||||
VaultItemCipherType.IDENTITY -> IDENTITY
|
||||
VaultItemCipherType.SECURE_NOTE -> SECURE_NOTE
|
||||
VaultItemCipherType.SSH_KEY -> SSH_KEY
|
||||
}
|
||||
|
||||
private fun String.toVaultItemCipherType(): VaultItemCipherType =
|
||||
when (this) {
|
||||
LOGIN -> VaultItemCipherType.LOGIN
|
||||
CARD -> VaultItemCipherType.CARD
|
||||
IDENTITY -> VaultItemCipherType.IDENTITY
|
||||
SECURE_NOTE -> VaultItemCipherType.SECURE_NOTE
|
||||
SSH_KEY -> VaultItemCipherType.SSH_KEY
|
||||
else -> throw IllegalStateException(
|
||||
"Edit Item string arguments for VaultAddEditNavigation must match!",
|
||||
)
|
||||
}
|
||||
|
||||
@ -126,7 +126,7 @@ class VaultAddEditViewModel @Inject constructor(
|
||||
// We load the state from the savedStateHandle for testing purposes.
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: run {
|
||||
val args = VaultAddEditArgs(savedStateHandle = savedStateHandle)
|
||||
val args = savedStateHandle.toVaultAddEditArgs()
|
||||
val vaultAddEditType = args.vaultAddEditType
|
||||
val vaultCipherType = args.vaultItemCipherType
|
||||
val selectedFolderId = args.selectedFolderId
|
||||
|
||||
@ -6,22 +6,30 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val ATTACHMENTS_CIPHER_ID = "cipher_id"
|
||||
private const val ATTACHMENTS_ROUTE_PREFIX = "attachments"
|
||||
private const val ATTACHMENTS_ROUTE = "$ATTACHMENTS_ROUTE_PREFIX/{$ATTACHMENTS_CIPHER_ID}"
|
||||
/**
|
||||
* The type-safe route for the attachments screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class AttachmentsRoute(
|
||||
val cipherId: String,
|
||||
)
|
||||
|
||||
/**
|
||||
* Class to retrieve arguments from the [SavedStateHandle].
|
||||
*/
|
||||
data class AttachmentsArgs(val cipherId: String) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
cipherId = checkNotNull(savedStateHandle.get<String>(ATTACHMENTS_CIPHER_ID)),
|
||||
)
|
||||
data class AttachmentsArgs(val cipherId: String)
|
||||
|
||||
/**
|
||||
* Constructs a [AttachmentsArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toAttachmentsArgs(): AttachmentsArgs {
|
||||
val route = this.toRoute<AttachmentsRoute>()
|
||||
return AttachmentsArgs(cipherId = route.cipherId)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -30,12 +38,7 @@ data class AttachmentsArgs(val cipherId: String) {
|
||||
fun NavGraphBuilder.attachmentDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = ATTACHMENTS_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(ATTACHMENTS_CIPHER_ID) { type = NavType.StringType },
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<AttachmentsRoute> {
|
||||
AttachmentsScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
@ -50,7 +53,7 @@ fun NavController.navigateToAttachment(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(
|
||||
route = "$ATTACHMENTS_ROUTE_PREFIX/$cipherId",
|
||||
route = AttachmentsRoute(cipherId = cipherId),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ class AttachmentsViewModel @Inject constructor(
|
||||
?: run {
|
||||
val isPremiumUser = authRepo.userStateFlow.value?.activeAccount?.isPremium == true
|
||||
AttachmentsState(
|
||||
cipherId = AttachmentsArgs(savedStateHandle).cipherId,
|
||||
cipherId = savedStateHandle.toAttachmentsArgs().cipherId,
|
||||
viewState = AttachmentsState.ViewState.Loading,
|
||||
dialogState = AttachmentsState.DialogState.Error(
|
||||
title = null,
|
||||
|
||||
@ -6,25 +6,31 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val IMPORT_LOGINS_PREFIX = "import-logins"
|
||||
private const val IMPORT_LOGINS_NAV_ARG = "snackbarRelay"
|
||||
private const val IMPORT_LOGINS_ROUTE = "$IMPORT_LOGINS_PREFIX/{$IMPORT_LOGINS_NAV_ARG}"
|
||||
/**
|
||||
* The type-safe route for the import logins screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class ImportLoginsRoute(
|
||||
val snackbarRelay: SnackbarRelay,
|
||||
)
|
||||
|
||||
/**
|
||||
* Arguments for the [ImportLoginsScreen] using [SavedStateHandle].
|
||||
*/
|
||||
data class ImportLoginsArgs(val snackBarRelay: SnackbarRelay) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
snackBarRelay = SnackbarRelay.valueOf(
|
||||
requireNotNull(savedStateHandle[IMPORT_LOGINS_NAV_ARG]),
|
||||
),
|
||||
)
|
||||
data class ImportLoginsArgs(val snackBarRelay: SnackbarRelay)
|
||||
|
||||
/**
|
||||
* Constructs a [ImportLoginsArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toImportLoginsArgs(): ImportLoginsArgs {
|
||||
val route = this.toRoute<ImportLoginsRoute>()
|
||||
return ImportLoginsArgs(snackBarRelay = route.snackbarRelay)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -34,7 +40,7 @@ fun NavController.navigateToImportLoginsScreen(
|
||||
snackbarRelay: SnackbarRelay,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(route = "$IMPORT_LOGINS_PREFIX/$snackbarRelay", navOptions = navOptions)
|
||||
navigate(route = ImportLoginsRoute(snackbarRelay = snackbarRelay), navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,15 +49,7 @@ fun NavController.navigateToImportLoginsScreen(
|
||||
fun NavGraphBuilder.importLoginsScreenDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = IMPORT_LOGINS_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(IMPORT_LOGINS_NAV_ARG) {
|
||||
type = NavType.StringType
|
||||
nullable = false
|
||||
},
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<ImportLoginsRoute> {
|
||||
ImportLoginsScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
|
||||
@ -41,7 +41,7 @@ class ImportLoginsViewModel @Inject constructor(
|
||||
showBottomSheet = false,
|
||||
// attempt to trim the scheme of the vault url
|
||||
currentWebVaultUrl = vaultUrl.toUriOrNull()?.host ?: vaultUrl,
|
||||
snackbarRelay = ImportLoginsArgs(savedStateHandle).snackBarRelay,
|
||||
snackbarRelay = savedStateHandle.toImportLoginsArgs().snackBarRelay,
|
||||
)
|
||||
},
|
||||
) {
|
||||
|
||||
@ -6,24 +6,21 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditArgs
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val LOGIN: String = "login"
|
||||
private const val CARD: String = "card"
|
||||
private const val IDENTITY: String = "identity"
|
||||
private const val SECURE_NOTE: String = "secure_note"
|
||||
private const val SSH_KEY: String = "ssh_key"
|
||||
private const val VAULT_ITEM_CIPHER_TYPE: String = "vault_item_cipher_type"
|
||||
|
||||
private const val VAULT_ITEM_PREFIX = "vault_item"
|
||||
private const val VAULT_ITEM_ID = "vault_item_id"
|
||||
private const val VAULT_ITEM_ROUTE = "$VAULT_ITEM_PREFIX/{$VAULT_ITEM_ID}" +
|
||||
"?$VAULT_ITEM_CIPHER_TYPE={$VAULT_ITEM_CIPHER_TYPE}"
|
||||
/**
|
||||
* The type-safe route for the vault item screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class VaultItemRoute(
|
||||
val vaultItemId: String,
|
||||
val cipherType: VaultItemCipherType,
|
||||
)
|
||||
|
||||
/**
|
||||
* Class to retrieve vault item arguments from the [SavedStateHandle].
|
||||
@ -31,12 +28,14 @@ private const val VAULT_ITEM_ROUTE = "$VAULT_ITEM_PREFIX/{$VAULT_ITEM_ID}" +
|
||||
data class VaultItemArgs(
|
||||
val vaultItemId: String,
|
||||
val cipherType: VaultItemCipherType,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
vaultItemId = checkNotNull(savedStateHandle.get<String>(VAULT_ITEM_ID)),
|
||||
cipherType = requireNotNull(savedStateHandle.get<String>(VAULT_ITEM_CIPHER_TYPE))
|
||||
.toVaultItemCipherType(),
|
||||
)
|
||||
|
||||
/**
|
||||
* Constructs a [VaultItemArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toVaultItemArgs(): VaultItemArgs {
|
||||
val route = this.toRoute<VaultItemRoute>()
|
||||
return VaultItemArgs(vaultItemId = route.vaultItemId, cipherType = route.cipherType)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -49,13 +48,7 @@ fun NavGraphBuilder.vaultItemDestination(
|
||||
onNavigateToAttachments: (vaultItemId: String) -> Unit,
|
||||
onNavigateToPasswordHistory: (vaultItemId: String) -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = VAULT_ITEM_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(VAULT_ITEM_ID) { type = NavType.StringType },
|
||||
navArgument(VAULT_ITEM_CIPHER_TYPE) { type = NavType.StringType },
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<VaultItemRoute> {
|
||||
VaultItemScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToVaultAddEditItem = onNavigateToVaultEditItem,
|
||||
@ -74,29 +67,10 @@ fun NavController.navigateToVaultItem(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(
|
||||
route = "$VAULT_ITEM_PREFIX/${args.vaultItemId}" +
|
||||
"?$VAULT_ITEM_CIPHER_TYPE=${args.cipherType.toTypeString()}",
|
||||
route = VaultItemRoute(
|
||||
vaultItemId = args.vaultItemId,
|
||||
cipherType = args.cipherType,
|
||||
),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
private fun VaultItemCipherType.toTypeString(): String =
|
||||
when (this) {
|
||||
VaultItemCipherType.LOGIN -> LOGIN
|
||||
VaultItemCipherType.CARD -> CARD
|
||||
VaultItemCipherType.IDENTITY -> IDENTITY
|
||||
VaultItemCipherType.SECURE_NOTE -> SECURE_NOTE
|
||||
VaultItemCipherType.SSH_KEY -> SSH_KEY
|
||||
}
|
||||
|
||||
private fun String.toVaultItemCipherType(): VaultItemCipherType =
|
||||
when (this) {
|
||||
LOGIN -> VaultItemCipherType.LOGIN
|
||||
CARD -> VaultItemCipherType.CARD
|
||||
IDENTITY -> VaultItemCipherType.IDENTITY
|
||||
SECURE_NOTE -> VaultItemCipherType.SECURE_NOTE
|
||||
SSH_KEY -> VaultItemCipherType.SSH_KEY
|
||||
else -> throw IllegalStateException(
|
||||
"Edit Item string arguments for VaultAddEditNavigation must match!",
|
||||
)
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ class VaultItemViewModel @Inject constructor(
|
||||
) : BaseViewModel<VaultItemState, VaultItemEvent, VaultItemAction>(
|
||||
// We load the state from the savedStateHandle for testing purposes.
|
||||
initialState = savedStateHandle[KEY_STATE] ?: run {
|
||||
val args = VaultItemArgs(savedStateHandle)
|
||||
val args = savedStateHandle.toVaultItemArgs()
|
||||
VaultItemState(
|
||||
vaultItemId = args.vaultItemId,
|
||||
cipherType = args.cipherType,
|
||||
|
||||
@ -6,8 +6,7 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithStayTransitions
|
||||
@ -15,45 +14,97 @@ import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditArgs
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemArgs
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val CARD: String = "card"
|
||||
private const val COLLECTION: String = "collection"
|
||||
private const val FOLDER: String = "folder"
|
||||
private const val IDENTITY: String = "identity"
|
||||
private const val LOGIN: String = "login"
|
||||
private const val SSH_KEY: String = "ssh_key"
|
||||
private const val SECURE_NOTE: String = "secure_note"
|
||||
private const val SEND_FILE: String = "send_file"
|
||||
private const val SEND_TEXT: String = "send_text"
|
||||
private const val TRASH: String = "trash"
|
||||
private const val VAULT_ITEM_LISTING_PREFIX: String = "vault_item_listing"
|
||||
private const val VAULT_ITEM_LISTING_AS_ROOT_PREFIX: String = "vault_item_listing_as_root"
|
||||
private const val VAULT_ITEM_LISTING_TYPE: String = "vault_item_listing_type"
|
||||
private const val ID: String = "id"
|
||||
private const val VAULT_ITEM_LISTING_ROUTE: String =
|
||||
"$VAULT_ITEM_LISTING_PREFIX/{$VAULT_ITEM_LISTING_TYPE}" +
|
||||
"?$ID={$ID}"
|
||||
private const val VAULT_ITEM_LISTING_AS_ROOT_ROUTE: String =
|
||||
"$VAULT_ITEM_LISTING_AS_ROOT_PREFIX/{$VAULT_ITEM_LISTING_TYPE}" +
|
||||
"?$ID={$ID}"
|
||||
private const val SEND_ITEM_LISTING_PREFIX: String = "send_item_listing"
|
||||
private const val SEND_ITEM_LISTING_ROUTE: String =
|
||||
"$SEND_ITEM_LISTING_PREFIX/{$VAULT_ITEM_LISTING_TYPE}" +
|
||||
"?$ID={$ID}"
|
||||
/**
|
||||
* The type-safe route for the vault item listing screen.
|
||||
*/
|
||||
@Serializable
|
||||
sealed class VaultItemListingRoute {
|
||||
/**
|
||||
* The type of item to be displayed.
|
||||
*/
|
||||
abstract val type: ItemListingType
|
||||
|
||||
/**
|
||||
* The optional item ID used for folder and collection types.
|
||||
*/
|
||||
abstract val itemId: String?
|
||||
|
||||
/**
|
||||
* The type-safe route for the cipher specific vault item listing screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class CipherItemListing(
|
||||
override val type: ItemListingType,
|
||||
override val itemId: String?,
|
||||
) : VaultItemListingRoute()
|
||||
|
||||
/**
|
||||
* The type-safe route for the send specific vault item listing screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class SendItemListing(
|
||||
override val type: ItemListingType,
|
||||
override val itemId: String?,
|
||||
) : VaultItemListingRoute()
|
||||
|
||||
/**
|
||||
* The type-safe route for the root vault item listing screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class AsRoot(
|
||||
override val type: ItemListingType,
|
||||
override val itemId: String?,
|
||||
) : VaultItemListingRoute()
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of items to be displayed.
|
||||
*/
|
||||
@Serializable
|
||||
enum class ItemListingType {
|
||||
LOGIN,
|
||||
IDENTITY,
|
||||
SECURE_NOTE,
|
||||
CARD,
|
||||
SSH_KEY,
|
||||
TRASH,
|
||||
FOLDER,
|
||||
COLLECTION,
|
||||
SEND_FILE,
|
||||
SEND_TEXT,
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to retrieve vault item listing arguments from the [SavedStateHandle].
|
||||
*/
|
||||
data class VaultItemListingArgs(
|
||||
val vaultItemListingType: VaultItemListingType,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
vaultItemListingType = determineVaultItemListingType(
|
||||
vaultItemListingTypeString = checkNotNull(
|
||||
savedStateHandle[VAULT_ITEM_LISTING_TYPE],
|
||||
) as String,
|
||||
id = savedStateHandle[ID],
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Constructs a [VaultItemListingArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toVaultItemListingArgs(): VaultItemListingArgs {
|
||||
// We just need to pull the serializable data out of the route, since they are always the same
|
||||
// it does not matter which instance we fetch.
|
||||
val route = this.toRoute<VaultItemListingRoute.SendItemListing>()
|
||||
return VaultItemListingArgs(
|
||||
vaultItemListingType = when (route.type) {
|
||||
ItemListingType.LOGIN -> VaultItemListingType.Login
|
||||
ItemListingType.CARD -> VaultItemListingType.Card
|
||||
ItemListingType.IDENTITY -> VaultItemListingType.Identity
|
||||
ItemListingType.SECURE_NOTE -> VaultItemListingType.SecureNote
|
||||
ItemListingType.SSH_KEY -> VaultItemListingType.SshKey
|
||||
ItemListingType.TRASH -> VaultItemListingType.Trash
|
||||
ItemListingType.SEND_FILE -> VaultItemListingType.SendFile
|
||||
ItemListingType.SEND_TEXT -> VaultItemListingType.SendText
|
||||
ItemListingType.FOLDER -> VaultItemListingType.Folder(folderId = route.itemId)
|
||||
ItemListingType.COLLECTION -> VaultItemListingType.Collection(
|
||||
collectionId = requireNotNull(route.itemId),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@ -70,8 +121,7 @@ fun NavGraphBuilder.vaultItemListingDestination(
|
||||
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
|
||||
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
||||
) {
|
||||
internalVaultItemListingDestination(
|
||||
route = VAULT_ITEM_LISTING_ROUTE,
|
||||
internalVaultItemListingDestination<VaultItemListingRoute.CipherItemListing>(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToAddSendItem = { },
|
||||
onNavigateToEditSendItem = { },
|
||||
@ -96,17 +146,7 @@ fun NavGraphBuilder.vaultItemListingDestinationAsRoot(
|
||||
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
|
||||
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
||||
) {
|
||||
composableWithStayTransitions(
|
||||
route = VAULT_ITEM_LISTING_AS_ROOT_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(
|
||||
name = VAULT_ITEM_LISTING_TYPE,
|
||||
builder = {
|
||||
type = NavType.StringType
|
||||
},
|
||||
),
|
||||
),
|
||||
) {
|
||||
composableWithStayTransitions<VaultItemListingRoute.AsRoot> {
|
||||
VaultItemListingScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToVaultItemScreen = onNavigateToVaultItemScreen,
|
||||
@ -130,8 +170,7 @@ fun NavGraphBuilder.sendItemListingDestination(
|
||||
onNavigateToEditSendItem: (sendId: String) -> Unit,
|
||||
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
|
||||
) {
|
||||
internalVaultItemListingDestination(
|
||||
route = SEND_ITEM_LISTING_ROUTE,
|
||||
internalVaultItemListingDestination<VaultItemListingRoute.SendItemListing>(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToAddSendItem = onNavigateToAddSendItem,
|
||||
onNavigateToEditSendItem = onNavigateToEditSendItem,
|
||||
@ -147,37 +186,19 @@ fun NavGraphBuilder.sendItemListingDestination(
|
||||
/**
|
||||
* Add the [VaultItemListingScreen] to the nav graph.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
private fun NavGraphBuilder.internalVaultItemListingDestination(
|
||||
route: String,
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToVaultItemScreen: (args: VaultItemArgs) -> Unit,
|
||||
onNavigateToVaultEditItemScreen: (args: VaultAddEditArgs) -> Unit,
|
||||
onNavigateToVaultItemListing: (vaultItemListingType: VaultItemListingType) -> Unit,
|
||||
onNavigateToVaultAddItemScreen: (args: VaultAddEditArgs) -> Unit,
|
||||
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
|
||||
onNavigateToAddSendItem: () -> Unit,
|
||||
onNavigateToEditSendItem: (sendId: String) -> Unit,
|
||||
onNavigateToSearch: (searchType: SearchType) -> Unit,
|
||||
) {
|
||||
composableWithPushTransitions(
|
||||
route = route,
|
||||
arguments = listOf(
|
||||
navArgument(
|
||||
name = VAULT_ITEM_LISTING_TYPE,
|
||||
builder = {
|
||||
type = NavType.StringType
|
||||
},
|
||||
),
|
||||
navArgument(
|
||||
name = ID,
|
||||
builder = {
|
||||
type = NavType.StringType
|
||||
nullable = true
|
||||
},
|
||||
),
|
||||
),
|
||||
@Suppress("LongParameterList", "MaxLineLength")
|
||||
private inline fun <reified T : VaultItemListingRoute> NavGraphBuilder.internalVaultItemListingDestination(
|
||||
noinline onNavigateBack: () -> Unit,
|
||||
noinline onNavigateToVaultItemScreen: (args: VaultItemArgs) -> Unit,
|
||||
noinline onNavigateToVaultEditItemScreen: (args: VaultAddEditArgs) -> Unit,
|
||||
noinline onNavigateToVaultItemListing: (vaultItemListingType: VaultItemListingType) -> Unit,
|
||||
noinline onNavigateToVaultAddItemScreen: (args: VaultAddEditArgs) -> Unit,
|
||||
noinline onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
|
||||
noinline onNavigateToAddSendItem: () -> Unit,
|
||||
noinline onNavigateToEditSendItem: (sendId: String) -> Unit,
|
||||
noinline onNavigateToSearch: (searchType: SearchType) -> Unit,
|
||||
) {
|
||||
composableWithPushTransitions<T> {
|
||||
VaultItemListingScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToVaultItemScreen = onNavigateToVaultItemScreen,
|
||||
@ -199,9 +220,11 @@ fun NavController.navigateToVaultItemListing(
|
||||
vaultItemListingType: VaultItemListingType,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(
|
||||
route = "$VAULT_ITEM_LISTING_PREFIX/${vaultItemListingType.toTypeString()}" +
|
||||
"?$ID=${vaultItemListingType.toIdOrNull()}",
|
||||
this.navigate(
|
||||
route = VaultItemListingRoute.CipherItemListing(
|
||||
type = vaultItemListingType.toItemListingType(),
|
||||
itemId = vaultItemListingType.toIdOrNull(),
|
||||
),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
@ -214,8 +237,10 @@ fun NavController.navigateToVaultItemListingAsRoot(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(
|
||||
route = "$VAULT_ITEM_LISTING_AS_ROOT_PREFIX/${vaultItemListingType.toTypeString()}" +
|
||||
"?$ID=${vaultItemListingType.toIdOrNull()}",
|
||||
route = VaultItemListingRoute.AsRoot(
|
||||
type = vaultItemListingType.toItemListingType(),
|
||||
itemId = vaultItemListingType.toIdOrNull(),
|
||||
),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
@ -227,25 +252,27 @@ fun NavController.navigateToSendItemListing(
|
||||
vaultItemListingType: VaultItemListingType,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(
|
||||
route = "$SEND_ITEM_LISTING_PREFIX/${vaultItemListingType.toTypeString()}" +
|
||||
"?$ID=${vaultItemListingType.toIdOrNull()}",
|
||||
this.navigate(
|
||||
route = VaultItemListingRoute.SendItemListing(
|
||||
type = vaultItemListingType.toItemListingType(),
|
||||
itemId = vaultItemListingType.toIdOrNull(),
|
||||
),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
private fun VaultItemListingType.toTypeString(): String {
|
||||
private fun VaultItemListingType.toItemListingType(): ItemListingType {
|
||||
return when (this) {
|
||||
is VaultItemListingType.Card -> CARD
|
||||
is VaultItemListingType.Collection -> COLLECTION
|
||||
is VaultItemListingType.Folder -> FOLDER
|
||||
is VaultItemListingType.Identity -> IDENTITY
|
||||
is VaultItemListingType.Login -> LOGIN
|
||||
is VaultItemListingType.SecureNote -> SECURE_NOTE
|
||||
is VaultItemListingType.Trash -> TRASH
|
||||
is VaultItemListingType.SendFile -> SEND_FILE
|
||||
is VaultItemListingType.SendText -> SEND_TEXT
|
||||
is VaultItemListingType.SshKey -> SSH_KEY
|
||||
is VaultItemListingType.Card -> ItemListingType.CARD
|
||||
is VaultItemListingType.Collection -> ItemListingType.COLLECTION
|
||||
is VaultItemListingType.Folder -> ItemListingType.FOLDER
|
||||
is VaultItemListingType.Identity -> ItemListingType.IDENTITY
|
||||
is VaultItemListingType.Login -> ItemListingType.LOGIN
|
||||
is VaultItemListingType.SecureNote -> ItemListingType.SECURE_NOTE
|
||||
is VaultItemListingType.Trash -> ItemListingType.TRASH
|
||||
is VaultItemListingType.SendFile -> ItemListingType.SEND_FILE
|
||||
is VaultItemListingType.SendText -> ItemListingType.SEND_TEXT
|
||||
is VaultItemListingType.SshKey -> ItemListingType.SSH_KEY
|
||||
}
|
||||
}
|
||||
|
||||
@ -262,23 +289,3 @@ private fun VaultItemListingType.toIdOrNull(): String? =
|
||||
is VaultItemListingType.SendText -> null
|
||||
is VaultItemListingType.SshKey -> null
|
||||
}
|
||||
|
||||
private fun determineVaultItemListingType(
|
||||
vaultItemListingTypeString: String,
|
||||
id: String?,
|
||||
): VaultItemListingType {
|
||||
return when (vaultItemListingTypeString) {
|
||||
LOGIN -> VaultItemListingType.Login
|
||||
CARD -> VaultItemListingType.Card
|
||||
IDENTITY -> VaultItemListingType.Identity
|
||||
SECURE_NOTE -> VaultItemListingType.SecureNote
|
||||
SSH_KEY -> VaultItemListingType.SshKey
|
||||
TRASH -> VaultItemListingType.Trash
|
||||
FOLDER -> VaultItemListingType.Folder(folderId = id)
|
||||
COLLECTION -> VaultItemListingType.Collection(collectionId = requireNotNull(id))
|
||||
SEND_FILE -> VaultItemListingType.SendFile
|
||||
SEND_TEXT -> VaultItemListingType.SendText
|
||||
// This should never occur, vaultItemListingTypeString must match
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,7 +133,8 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
val fido2GetCredentialsRequest = specialCircumstance?.toFido2GetCredentialsRequestOrNull()
|
||||
val fido2AssertCredentialRequest = specialCircumstance?.toFido2AssertionRequestOrNull()
|
||||
VaultItemListingState(
|
||||
itemListingType = VaultItemListingArgs(savedStateHandle = savedStateHandle)
|
||||
itemListingType = savedStateHandle
|
||||
.toVaultItemListingArgs()
|
||||
.vaultItemListingType
|
||||
.toItemListingType(),
|
||||
activeAccountSummary = activeAccountSummary,
|
||||
|
||||
@ -7,8 +7,13 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val MANUAL_CODE_ENTRY_ROUTE: String = "manual_code_entry"
|
||||
/**
|
||||
* The type-safe route for the manual code entry screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object ManualCodeEntryRoute
|
||||
|
||||
/**
|
||||
* Add the manual code entry screen to the nav graph.
|
||||
@ -17,9 +22,7 @@ fun NavGraphBuilder.vaultManualCodeEntryDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToQrCodeScreen: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = MANUAL_CODE_ENTRY_ROUTE,
|
||||
) {
|
||||
composableWithSlideTransitions<ManualCodeEntryRoute> {
|
||||
ManualCodeEntryScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToQrCodeScreen = onNavigateToQrCodeScreen,
|
||||
@ -33,5 +36,5 @@ fun NavGraphBuilder.vaultManualCodeEntryDestination(
|
||||
fun NavController.navigateToManualCodeEntryScreen(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
this.navigate(MANUAL_CODE_ENTRY_ROUTE, navOptions)
|
||||
this.navigate(route = ManualCodeEntryRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -6,19 +6,19 @@ 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 androidx.navigation.toRoute
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val VAULT_MOVE_TO_ORGANIZATION_PREFIX = "vault_move_to_organization"
|
||||
private const val VAULT_MOVE_TO_ORGANIZATION_ID = "vault_move_to_organization_id"
|
||||
private const val VAULT_MOVE_TO_ORGANIZATION_ONLY_COLLECTIONS =
|
||||
"vault_move_to_organization_only_collections"
|
||||
private const val VAULT_MOVE_TO_ORGANIZATION_ROUTE =
|
||||
VAULT_MOVE_TO_ORGANIZATION_PREFIX +
|
||||
"/{$VAULT_MOVE_TO_ORGANIZATION_ID}" +
|
||||
"/{$VAULT_MOVE_TO_ORGANIZATION_ONLY_COLLECTIONS}"
|
||||
/**
|
||||
* The type-safe route for the vault move to organization screen.
|
||||
*/
|
||||
@Serializable
|
||||
data class VaultMoveToOrganizationRoute(
|
||||
val vaultItemId: String,
|
||||
val showOnlyCollections: Boolean,
|
||||
)
|
||||
|
||||
/**
|
||||
* Class to retrieve vault move to organization arguments from the [SavedStateHandle].
|
||||
@ -26,12 +26,16 @@ private const val VAULT_MOVE_TO_ORGANIZATION_ROUTE =
|
||||
data class VaultMoveToOrganizationArgs(
|
||||
val vaultItemId: String,
|
||||
val showOnlyCollections: Boolean,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
vaultItemId = checkNotNull(savedStateHandle[VAULT_MOVE_TO_ORGANIZATION_ID]) as String,
|
||||
showOnlyCollections =
|
||||
(checkNotNull(savedStateHandle[VAULT_MOVE_TO_ORGANIZATION_ONLY_COLLECTIONS]) as String)
|
||||
.toBoolean(),
|
||||
)
|
||||
|
||||
/**
|
||||
* Constructs a [VaultMoveToOrganizationArgs] from the [SavedStateHandle] and internal route data.
|
||||
*/
|
||||
fun SavedStateHandle.toVaultMoveToOrganizationArgs(): VaultMoveToOrganizationArgs {
|
||||
val route = this.toRoute<VaultMoveToOrganizationRoute>()
|
||||
return VaultMoveToOrganizationArgs(
|
||||
vaultItemId = route.vaultItemId,
|
||||
showOnlyCollections = route.showOnlyCollections,
|
||||
)
|
||||
}
|
||||
|
||||
@ -41,15 +45,7 @@ data class VaultMoveToOrganizationArgs(
|
||||
fun NavGraphBuilder.vaultMoveToOrganizationDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = VAULT_MOVE_TO_ORGANIZATION_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(VAULT_MOVE_TO_ORGANIZATION_ID) { type = NavType.StringType },
|
||||
navArgument(VAULT_MOVE_TO_ORGANIZATION_ONLY_COLLECTIONS) {
|
||||
type = NavType.StringType
|
||||
},
|
||||
),
|
||||
) {
|
||||
composableWithSlideTransitions<VaultMoveToOrganizationRoute> {
|
||||
VaultMoveToOrganizationScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
@ -64,8 +60,11 @@ fun NavController.navigateToVaultMoveToOrganization(
|
||||
showOnlyCollections: Boolean,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(
|
||||
route = "$VAULT_MOVE_TO_ORGANIZATION_PREFIX/$vaultItemId/$showOnlyCollections",
|
||||
this.navigate(
|
||||
route = VaultMoveToOrganizationRoute(
|
||||
vaultItemId = vaultItemId,
|
||||
showOnlyCollections = showOnlyCollections,
|
||||
),
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
@ -5,6 +5,9 @@ import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.core.data.repository.util.combineDataStates
|
||||
import com.bitwarden.ui.util.Text
|
||||
import com.bitwarden.ui.util.asText
|
||||
import com.bitwarden.ui.util.concat
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.bitwarden.vault.CollectionView
|
||||
import com.x8bit.bitwarden.R
|
||||
@ -13,9 +16,6 @@ import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.ShareCipherResult
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.bitwarden.ui.util.Text
|
||||
import com.bitwarden.ui.util.asText
|
||||
import com.bitwarden.ui.util.concat
|
||||
import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.util.toViewState
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCollection
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
@ -42,9 +42,10 @@ class VaultMoveToOrganizationViewModel @Inject constructor(
|
||||
) : BaseViewModel<VaultMoveToOrganizationState, VaultMoveToOrganizationEvent, VaultMoveToOrganizationAction>(
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: run {
|
||||
val args = savedStateHandle.toVaultMoveToOrganizationArgs()
|
||||
VaultMoveToOrganizationState(
|
||||
vaultItemId = VaultMoveToOrganizationArgs(savedStateHandle).vaultItemId,
|
||||
onlyShowCollections = VaultMoveToOrganizationArgs(savedStateHandle).showOnlyCollections,
|
||||
vaultItemId = args.vaultItemId,
|
||||
onlyShowCollections = args.showOnlyCollections,
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Loading,
|
||||
dialogState = null,
|
||||
)
|
||||
|
||||
@ -7,8 +7,13 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val QR_CODE_SCAN_ROUTE: String = "qr_code_scan"
|
||||
/**
|
||||
* The type-safe route for the QR code scan screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object QrCodeScanRoute
|
||||
|
||||
/**
|
||||
* Add the QR code scan screen to the nav graph.
|
||||
@ -17,9 +22,7 @@ fun NavGraphBuilder.vaultQrCodeScanDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToManualCodeEntryScreen: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = QR_CODE_SCAN_ROUTE,
|
||||
) {
|
||||
composableWithSlideTransitions<QrCodeScanRoute> {
|
||||
QrCodeScanScreen(
|
||||
onNavigateToManualCodeEntryScreen = onNavigateToManualCodeEntryScreen,
|
||||
onNavigateBack = onNavigateBack,
|
||||
@ -33,5 +36,5 @@ fun NavGraphBuilder.vaultQrCodeScanDestination(
|
||||
fun NavController.navigateToQrCodeScanScreen(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
this.navigate(QR_CODE_SCAN_ROUTE, navOptions)
|
||||
this.navigate(route = QrCodeScanRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -15,8 +15,13 @@ import com.x8bit.bitwarden.ui.vault.feature.itemlisting.navigateToVaultItemListi
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.vaultItemListingDestination
|
||||
import com.x8bit.bitwarden.ui.vault.feature.verificationcode.navigateToVerificationCodeScreen
|
||||
import com.x8bit.bitwarden.ui.vault.feature.verificationcode.vaultVerificationCodeDestination
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
const val VAULT_GRAPH_ROUTE: String = "vault_graph"
|
||||
/**
|
||||
* The type-safe route for the vault graph.
|
||||
*/
|
||||
@Serializable
|
||||
data object VaultGraphRoute
|
||||
|
||||
/**
|
||||
* Add vault destinations to the nav graph.
|
||||
@ -33,9 +38,8 @@ fun NavGraphBuilder.vaultGraph(
|
||||
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
|
||||
onNavigateToAboutScreen: () -> Unit,
|
||||
) {
|
||||
navigation(
|
||||
route = VAULT_GRAPH_ROUTE,
|
||||
startDestination = VAULT_ROUTE,
|
||||
navigation<VaultGraphRoute>(
|
||||
startDestination = VaultRoute,
|
||||
) {
|
||||
vaultDestination(
|
||||
onNavigateToVaultAddItemScreen = { onNavigateToVaultAddItemScreen(it) },
|
||||
@ -75,5 +79,5 @@ fun NavGraphBuilder.vaultGraph(
|
||||
* Navigate to the vault graph.
|
||||
*/
|
||||
fun NavController.navigateToVaultGraph(navOptions: NavOptions? = null) {
|
||||
navigate(VAULT_GRAPH_ROUTE, navOptions)
|
||||
this.navigate(route = VaultGraphRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -12,8 +12,13 @@ import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditArgs
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemArgs
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
const val VAULT_ROUTE: String = "vault"
|
||||
/**
|
||||
* The type-safe route for the vault screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object VaultRoute
|
||||
|
||||
/**
|
||||
* Add vault destination to the nav graph.
|
||||
@ -31,9 +36,7 @@ fun NavGraphBuilder.vaultDestination(
|
||||
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
|
||||
onNavigateToAboutScreen: () -> Unit,
|
||||
) {
|
||||
composableWithRootPushTransitions(
|
||||
route = VAULT_ROUTE,
|
||||
) {
|
||||
composableWithRootPushTransitions<VaultRoute> {
|
||||
VaultScreen(
|
||||
onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen,
|
||||
onNavigateToVaultItemScreen = onNavigateToVaultItemScreen,
|
||||
@ -53,5 +56,5 @@ fun NavGraphBuilder.vaultDestination(
|
||||
* Navigate to the [VaultScreen].
|
||||
*/
|
||||
fun NavController.navigateToVault(navOptions: NavOptions? = null) {
|
||||
navigate(VAULT_ROUTE, navOptions)
|
||||
this.navigate(route = VaultRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -8,8 +8,13 @@ import androidx.navigation.NavOptions
|
||||
import com.bitwarden.core.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemArgs
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private const val VERIFICATION_CODE_ROUTE: String = "verification_code"
|
||||
/**
|
||||
* The type-safe route for the verification code screen.
|
||||
*/
|
||||
@Serializable
|
||||
data object VerificationCodeRoute
|
||||
|
||||
/**
|
||||
* Add the verification code screen to the nav graph.
|
||||
@ -19,9 +24,7 @@ fun NavGraphBuilder.vaultVerificationCodeDestination(
|
||||
onNavigateToSearchVault: () -> Unit,
|
||||
onNavigateToVaultItemScreen: (args: VaultItemArgs) -> Unit,
|
||||
) {
|
||||
composableWithPushTransitions(
|
||||
route = VERIFICATION_CODE_ROUTE,
|
||||
) {
|
||||
composableWithPushTransitions<VerificationCodeRoute> {
|
||||
VerificationCodeScreen(
|
||||
onNavigateToVaultItemScreen = onNavigateToVaultItemScreen,
|
||||
onNavigateToSearch = onNavigateToSearchVault,
|
||||
@ -36,5 +39,5 @@ fun NavGraphBuilder.vaultVerificationCodeDestination(
|
||||
fun NavController.navigateToVerificationCodeScreen(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
this.navigate(VERIFICATION_CODE_ROUTE, navOptions)
|
||||
this.navigate(route = VerificationCodeRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
package com.x8bit.bitwarden.ui.vault.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents different types of ciphers that can be added/viewed.
|
||||
*/
|
||||
@Serializable
|
||||
enum class VaultItemCipherType {
|
||||
|
||||
/**
|
||||
|
||||
@ -12,14 +12,18 @@ import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class SetupAutoFillViewModelTest : BaseViewModelTest() {
|
||||
@ -45,6 +49,16 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() {
|
||||
every { setOnboardingStatus(any()) } just runs
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockkStatic(SavedStateHandle::toSetupAutoFillArgs)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(SavedStateHandle::toSetupAutoFillArgs)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleAutofillEnabledUpdateReceive updates autofillEnabled state`() {
|
||||
val viewModel = createViewModel()
|
||||
@ -183,12 +197,10 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() {
|
||||
private fun createViewModel(
|
||||
initialState: SetupAutoFillState? = null,
|
||||
) = SetupAutoFillViewModel(
|
||||
savedStateHandle = SavedStateHandle(
|
||||
mapOf(
|
||||
"state" to initialState,
|
||||
"isInitialSetup" to true,
|
||||
),
|
||||
),
|
||||
savedStateHandle = SavedStateHandle().apply {
|
||||
set(key = "state", value = initialState)
|
||||
every { toSetupAutoFillArgs() } returns SetupAutoFillScreenArgs(isInitialSetup = true)
|
||||
},
|
||||
settingsRepository = settingsRepository,
|
||||
authRepository = authRepository,
|
||||
firstTimeActionManager = firstTimeActionManager,
|
||||
|
||||
@ -20,12 +20,16 @@ import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import javax.crypto.Cipher
|
||||
|
||||
@ -56,6 +60,16 @@ class SetupUnlockViewModelTest : BaseViewModelTest() {
|
||||
every { createCipherOrNull(DEFAULT_USER_ID) } returns CIPHER
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockkStatic(SavedStateHandle::toSetupUnlockArgs)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(SavedStateHandle::toSetupUnlockArgs)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct`() {
|
||||
val viewModel = createViewModel()
|
||||
@ -373,12 +387,10 @@ class SetupUnlockViewModelTest : BaseViewModelTest() {
|
||||
state: SetupUnlockState? = null,
|
||||
): SetupUnlockViewModel =
|
||||
SetupUnlockViewModel(
|
||||
savedStateHandle = SavedStateHandle(
|
||||
mapOf(
|
||||
"state" to state,
|
||||
"isInitialSetup" to true,
|
||||
),
|
||||
),
|
||||
savedStateHandle = SavedStateHandle().apply {
|
||||
set(key = "state", value = state)
|
||||
every { toSetupUnlockArgs() } returns SetupUnlockArgs(isInitialSetup = true)
|
||||
},
|
||||
authRepository = authRepository,
|
||||
settingsRepository = settingsRepository,
|
||||
biometricsEncryptionManager = biometricsEncryptionManager,
|
||||
|
||||
@ -2,21 +2,26 @@ package com.x8bit.bitwarden.ui.auth.feature.checkemail
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class CheckEmailViewModelTest : BaseViewModelTest() {
|
||||
private val mutableFeatureFlagFlow = MutableStateFlow(false)
|
||||
private val featureFlagManager = mockk<FeatureFlagManager>(relaxed = true) {
|
||||
every { getFeatureFlag(FlagKey.OnboardingFlow) } returns false
|
||||
every { getFeatureFlagFlow(FlagKey.OnboardingFlow) } returns mutableFeatureFlagFlow
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockkStatic(SavedStateHandle::toCheckEmailArgs)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(SavedStateHandle::toCheckEmailArgs)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -76,16 +81,14 @@ class CheckEmailViewModelTest : BaseViewModelTest() {
|
||||
|
||||
private fun createViewModel(state: CheckEmailState? = null): CheckEmailViewModel =
|
||||
CheckEmailViewModel(
|
||||
savedStateHandle = SavedStateHandle().also {
|
||||
it["email"] = EMAIL
|
||||
it["state"] = state
|
||||
savedStateHandle = SavedStateHandle().apply {
|
||||
set(key = "state", value = state)
|
||||
every { toCheckEmailArgs() } returns CheckEmailArgs(emailAddress = EMAIL)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val EMAIL = "test@gmail.com"
|
||||
private val DEFAULT_STATE = CheckEmailState(
|
||||
email = EMAIL,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user