mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 00:06:22 -06:00
PM-26025: Add browser autofill screen for onboarding flow (#5931)
This commit is contained in:
parent
c122f83fa6
commit
4cd5a1ed56
@ -27,6 +27,12 @@ enum class OnboardingStatus {
|
||||
@SerialName("autofillSetup")
|
||||
AUTOFILL_SETUP,
|
||||
|
||||
/**
|
||||
* The user is completing the browser autofill service setup.
|
||||
*/
|
||||
@SerialName("browserAutofillSetup")
|
||||
BROWSER_AUTOFILL_SETUP,
|
||||
|
||||
/**
|
||||
* The user is completing the final step of the onboarding process.
|
||||
*/
|
||||
|
||||
@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillEnabledManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
@ -27,6 +28,7 @@ class SetupAutoFillViewModel @Inject constructor(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val authRepository: AuthRepository,
|
||||
private val firstTimeActionManager: FirstTimeActionManager,
|
||||
private val browserThirdPartyAutofillEnabledManager: BrowserThirdPartyAutofillEnabledManager,
|
||||
) :
|
||||
BaseViewModel<SetupAutoFillState, SetupAutoFillEvent, SetupAutoFillAction>(
|
||||
// We load the state from the savedStateHandle for testing purposes.
|
||||
@ -100,13 +102,13 @@ class SetupAutoFillViewModel @Inject constructor(
|
||||
|
||||
private fun handleTurnOnLaterConfirmClick() {
|
||||
firstTimeActionManager.storeShowAutoFillSettingBadge(showBadge = true)
|
||||
updateOnboardingStatusToFinalStep()
|
||||
updateOnboardingStatusToNextStep()
|
||||
}
|
||||
|
||||
private fun handleContinueClick() {
|
||||
firstTimeActionManager.storeShowAutoFillSettingBadge(showBadge = false)
|
||||
if (state.isInitialSetup) {
|
||||
updateOnboardingStatusToFinalStep()
|
||||
updateOnboardingStatusToNextStep()
|
||||
} else {
|
||||
sendEvent(SetupAutoFillEvent.NavigateBack)
|
||||
}
|
||||
@ -120,10 +122,18 @@ class SetupAutoFillViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateOnboardingStatusToFinalStep() =
|
||||
authRepository.setOnboardingStatus(
|
||||
status = OnboardingStatus.FINAL_STEP,
|
||||
)
|
||||
private fun updateOnboardingStatusToNextStep() {
|
||||
val isAutofillEnabled = settingsRepository.isAutofillEnabledStateFlow.value
|
||||
val isBrowserAutofillUnconfigured = browserThirdPartyAutofillEnabledManager
|
||||
.browserThirdPartyAutofillStatus
|
||||
.isAnyIsAvailableAndDisabled
|
||||
val nextStep = when {
|
||||
!isAutofillEnabled -> OnboardingStatus.FINAL_STEP
|
||||
isBrowserAutofillUnconfigured -> OnboardingStatus.BROWSER_AUTOFILL_SETUP
|
||||
else -> OnboardingStatus.FINAL_STEP
|
||||
}
|
||||
authRepository.setOnboardingStatus(status = nextStep)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
package com.x8bit.bitwarden.ui.auth.feature.accountsetup
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import com.bitwarden.ui.platform.util.ParcelableRouteSerializer
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* The type-safe route for the setup browser autofill screen.
|
||||
*/
|
||||
@Parcelize
|
||||
@Serializable(with = SetupBrowserAutofillRoute.Serializer::class)
|
||||
data object SetupBrowserAutofillRoute : Parcelable {
|
||||
/**
|
||||
* Custom serializer for this route.
|
||||
*/
|
||||
class Serializer : ParcelableRouteSerializer<SetupBrowserAutofillRoute>(
|
||||
kClass = SetupBrowserAutofillRoute::class,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the setup browser autofill screen.
|
||||
*/
|
||||
fun NavController.navigateToSetupBrowserAutofillScreen(navOptions: NavOptions? = null) {
|
||||
this.navigate(route = SetupBrowserAutofillRoute, navOptions = navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the setup browser autofill screen to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.setupBrowserAutofillDestination() {
|
||||
composableWithPushTransitions<SetupBrowserAutofillRoute> {
|
||||
SetupBrowserAutofillScreen()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,201 @@
|
||||
package com.x8bit.bitwarden.ui.auth.feature.accountsetup
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
||||
import com.bitwarden.ui.platform.components.button.BitwardenFilledButton
|
||||
import com.bitwarden.ui.platform.components.button.BitwardenOutlinedButton
|
||||
import com.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog
|
||||
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||
import com.bitwarden.ui.platform.composition.LocalIntentManager
|
||||
import com.bitwarden.ui.platform.manager.IntentManager
|
||||
import com.bitwarden.ui.platform.resource.BitwardenString
|
||||
import com.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.data.autofill.model.browser.BrowserPackage
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.browser.BrowserAutofillSettingsCard
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.browser.model.BrowserAutofillSettingsOption
|
||||
import com.x8bit.bitwarden.ui.platform.manager.utils.startBrowserAutofillSettingsActivity
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* Top level composable for the Setup Browser Autofill screen.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SetupBrowserAutofillScreen(
|
||||
viewModel: SetupBrowserAutofillViewModel = hiltViewModel(),
|
||||
intentManager: IntentManager = LocalIntentManager.current,
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
is SetupBrowserAutofillEvent.NavigateToBrowserAutofillSettings -> {
|
||||
intentManager.startBrowserAutofillSettingsActivity(
|
||||
browserPackage = event.browserPackage,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
SetupBrowserAutofillDialogs(
|
||||
dialogState = state.dialogState,
|
||||
onDismissDialog = remember(viewModel) {
|
||||
{ viewModel.trySendAction(SetupBrowserAutofillAction.DismissDialog) }
|
||||
},
|
||||
onTurnOnLaterConfirm = remember(viewModel) {
|
||||
{ viewModel.trySendAction(SetupBrowserAutofillAction.TurnOnLaterConfirmClick) }
|
||||
},
|
||||
)
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
BitwardenTopAppBar(
|
||||
title = stringResource(id = BitwardenString.account_setup),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = null,
|
||||
)
|
||||
},
|
||||
) {
|
||||
SetupBrowserAutofillContent(
|
||||
state = state,
|
||||
onBrowserClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(SetupBrowserAutofillAction.BrowserIntegrationClick(it)) }
|
||||
},
|
||||
onContinueClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(SetupBrowserAutofillAction.ContinueClick) }
|
||||
},
|
||||
onTurnOnLaterClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(SetupBrowserAutofillAction.TurnOnLaterClick) }
|
||||
},
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SetupBrowserAutofillContent(
|
||||
state: SetupBrowserAutofillState,
|
||||
onBrowserClick: (BrowserPackage) -> Unit,
|
||||
onContinueClick: () -> Unit,
|
||||
onTurnOnLaterClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Spacer(Modifier.height(height = 24.dp))
|
||||
Text(
|
||||
text = stringResource(id = BitwardenString.turn_on_browser_autofill_integration),
|
||||
style = BitwardenTheme.typography.titleMedium,
|
||||
color = BitwardenTheme.colorScheme.text.primary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(Modifier.height(height = 8.dp))
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = BitwardenString.youre_using_a_browser_that_requires_special_permissions,
|
||||
),
|
||||
style = BitwardenTheme.typography.bodyMedium,
|
||||
color = BitwardenTheme.colorScheme.text.primary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 24.dp))
|
||||
BrowserAutofillSettingsCard(
|
||||
options = state.browserAutofillSettingsOptions,
|
||||
onOptionClicked = onBrowserClick,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 24.dp))
|
||||
BitwardenFilledButton(
|
||||
label = stringResource(id = BitwardenString.continue_text),
|
||||
onClick = onContinueClick,
|
||||
isEnabled = state.isContinueEnabled,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 12.dp))
|
||||
BitwardenOutlinedButton(
|
||||
label = stringResource(BitwardenString.turn_on_later),
|
||||
onClick = onTurnOnLaterClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SetupBrowserAutofillDialogs(
|
||||
dialogState: SetupBrowserAutofillState.DialogState?,
|
||||
onTurnOnLaterConfirm: () -> Unit,
|
||||
onDismissDialog: () -> Unit,
|
||||
) {
|
||||
when (dialogState) {
|
||||
SetupBrowserAutofillState.DialogState.TurnOnLaterDialog -> {
|
||||
BitwardenTwoButtonDialog(
|
||||
title = stringResource(BitwardenString.turn_on_autofill_later),
|
||||
message = stringResource(
|
||||
id = BitwardenString.return_to_complete_this_step_anytime_in_settings,
|
||||
),
|
||||
confirmButtonText = stringResource(id = BitwardenString.confirm),
|
||||
dismissButtonText = stringResource(id = BitwardenString.cancel),
|
||||
onConfirmClick = onTurnOnLaterConfirm,
|
||||
onDismissClick = onDismissDialog,
|
||||
onDismissRequest = onDismissDialog,
|
||||
)
|
||||
}
|
||||
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun SetupBrowserAutofillContent_preview() {
|
||||
BitwardenTheme {
|
||||
SetupBrowserAutofillContent(
|
||||
state = SetupBrowserAutofillState(
|
||||
dialogState = null,
|
||||
browserAutofillSettingsOptions = persistentListOf(
|
||||
BrowserAutofillSettingsOption.BraveStable(enabled = true),
|
||||
BrowserAutofillSettingsOption.ChromeStable(enabled = false),
|
||||
BrowserAutofillSettingsOption.ChromeBeta(enabled = true),
|
||||
),
|
||||
),
|
||||
onBrowserClick = { },
|
||||
onContinueClick = { },
|
||||
onTurnOnLaterClick = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,189 @@
|
||||
package com.x8bit.bitwarden.ui.auth.feature.accountsetup
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillEnabledManager
|
||||
import com.x8bit.bitwarden.data.autofill.model.browser.BrowserPackage
|
||||
import com.x8bit.bitwarden.data.autofill.model.browser.BrowserThirdPartyAutofillStatus
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.browser.model.BrowserAutofillSettingsOption
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.browser.util.toBrowserAutoFillSettingsOptions
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val KEY_STATE = "state"
|
||||
|
||||
/**
|
||||
* View model for the Setup Browser Autofill screen.
|
||||
*/
|
||||
@HiltViewModel
|
||||
class SetupBrowserAutofillViewModel @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
browserThirdPartyAutofillEnabledManager: BrowserThirdPartyAutofillEnabledManager,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<SetupBrowserAutofillState, SetupBrowserAutofillEvent, SetupBrowserAutofillAction>(
|
||||
// We load the state from the savedStateHandle for testing purposes.
|
||||
initialState = savedStateHandle[KEY_STATE] ?: SetupBrowserAutofillState(
|
||||
dialogState = null,
|
||||
browserAutofillSettingsOptions = browserThirdPartyAutofillEnabledManager
|
||||
.browserThirdPartyAutofillStatus
|
||||
.toBrowserAutoFillSettingsOptions(),
|
||||
),
|
||||
) {
|
||||
init {
|
||||
browserThirdPartyAutofillEnabledManager
|
||||
.browserThirdPartyAutofillStatusFlow
|
||||
.map(SetupBrowserAutofillAction.Internal::BrowserAutofillStatusReceive)
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun handleAction(action: SetupBrowserAutofillAction) {
|
||||
when (action) {
|
||||
is SetupBrowserAutofillAction.BrowserIntegrationClick -> {
|
||||
handleBrowserIntegrationClick(action)
|
||||
}
|
||||
|
||||
SetupBrowserAutofillAction.DismissDialog -> handleDismissDialog()
|
||||
SetupBrowserAutofillAction.ContinueClick -> handleContinueClick()
|
||||
SetupBrowserAutofillAction.TurnOnLaterClick -> handleTurnOnLaterClick()
|
||||
SetupBrowserAutofillAction.TurnOnLaterConfirmClick -> handleTurnOnLaterConfirmClick()
|
||||
is SetupBrowserAutofillAction.Internal -> handleInternalAction(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleInternalAction(action: SetupBrowserAutofillAction.Internal) {
|
||||
when (action) {
|
||||
is SetupBrowserAutofillAction.Internal.BrowserAutofillStatusReceive -> {
|
||||
handleBrowserAutofillStatusReceive(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBrowserIntegrationClick(
|
||||
action: SetupBrowserAutofillAction.BrowserIntegrationClick,
|
||||
) {
|
||||
sendEvent(
|
||||
SetupBrowserAutofillEvent.NavigateToBrowserAutofillSettings(action.browserPackage),
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleDismissDialog() {
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
}
|
||||
|
||||
private fun handleContinueClick() {
|
||||
authRepository.setOnboardingStatus(status = OnboardingStatus.FINAL_STEP)
|
||||
}
|
||||
|
||||
private fun handleTurnOnLaterClick() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SetupBrowserAutofillState.DialogState.TurnOnLaterDialog)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleTurnOnLaterConfirmClick() {
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
authRepository.setOnboardingStatus(status = OnboardingStatus.FINAL_STEP)
|
||||
}
|
||||
|
||||
private fun handleBrowserAutofillStatusReceive(
|
||||
action: SetupBrowserAutofillAction.Internal.BrowserAutofillStatusReceive,
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
browserAutofillSettingsOptions = action.status.toBrowserAutoFillSettingsOptions(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UI State for the Setup Browser Autofill screen.
|
||||
*/
|
||||
@Parcelize
|
||||
data class SetupBrowserAutofillState(
|
||||
val dialogState: DialogState?,
|
||||
val browserAutofillSettingsOptions: ImmutableList<BrowserAutofillSettingsOption>,
|
||||
) : Parcelable {
|
||||
/**
|
||||
* Indicates if the Continue button should be enabled or not.
|
||||
*/
|
||||
val isContinueEnabled: Boolean get() = browserAutofillSettingsOptions.any { it.isEnabled }
|
||||
|
||||
/**
|
||||
* Models dialogs that can be shown on the Setup Browser Autofill screen.
|
||||
*/
|
||||
@Parcelize
|
||||
sealed class DialogState : Parcelable {
|
||||
/**
|
||||
* Represents the turn on later dialog.
|
||||
*/
|
||||
data object TurnOnLaterDialog : DialogState()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UI Events for the Setup Browser Autofill screen.
|
||||
*/
|
||||
sealed class SetupBrowserAutofillEvent {
|
||||
/**
|
||||
* Navigate to the Autofill settings of the specified [browserPackage].
|
||||
*/
|
||||
data class NavigateToBrowserAutofillSettings(
|
||||
val browserPackage: BrowserPackage,
|
||||
) : SetupBrowserAutofillEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* UI Actions for the Setup Browser Autofill screen.
|
||||
*/
|
||||
sealed class SetupBrowserAutofillAction {
|
||||
/**
|
||||
* Indicates that a browser integration toggle was clicked.
|
||||
*/
|
||||
data class BrowserIntegrationClick(
|
||||
val browserPackage: BrowserPackage,
|
||||
) : SetupBrowserAutofillAction()
|
||||
|
||||
/**
|
||||
* Indicates that the dialog has been dismissed.
|
||||
*/
|
||||
data object DismissDialog : SetupBrowserAutofillAction()
|
||||
|
||||
/**
|
||||
* Indicates that the "Continue" button was clicked.
|
||||
*/
|
||||
data object ContinueClick : SetupBrowserAutofillAction()
|
||||
|
||||
/**
|
||||
* Indicates that the "Turn on later" button was clicked.
|
||||
*/
|
||||
data object TurnOnLaterClick : SetupBrowserAutofillAction()
|
||||
|
||||
/**
|
||||
* Indicates that the confirmation button was clicked to turn on later.
|
||||
*/
|
||||
data object TurnOnLaterConfirmClick : SetupBrowserAutofillAction()
|
||||
|
||||
/**
|
||||
* Models actions the [SetupBrowserAutofillViewModel] itself may send.
|
||||
*/
|
||||
sealed class Internal : SetupBrowserAutofillAction() {
|
||||
/**
|
||||
* Received updated [BrowserThirdPartyAutofillStatus] data.
|
||||
*/
|
||||
data class BrowserAutofillStatusReceive(
|
||||
val status: BrowserThirdPartyAutofillStatus,
|
||||
) : Internal()
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,7 @@ import com.bitwarden.ui.util.Text
|
||||
import com.bitwarden.ui.util.asText
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillEnabledManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
@ -34,6 +35,7 @@ class SetupUnlockViewModel @Inject constructor(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val biometricsEncryptionManager: BiometricsEncryptionManager,
|
||||
private val firstTimeActionManager: FirstTimeActionManager,
|
||||
private val browserThirdPartyAutofillEnabledManager: BrowserThirdPartyAutofillEnabledManager,
|
||||
) : BaseViewModel<SetupUnlockState, SetupUnlockEvent, SetupUnlockAction>(
|
||||
// We load the state from the savedStateHandle for testing purposes.
|
||||
initialState = savedStateHandle[KEY_STATE] ?: run {
|
||||
@ -203,10 +205,14 @@ class SetupUnlockViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun updateOnboardingStatusToNextStep() {
|
||||
val nextStep = if (settingsRepository.isAutofillEnabledStateFlow.value) {
|
||||
OnboardingStatus.FINAL_STEP
|
||||
} else {
|
||||
OnboardingStatus.AUTOFILL_SETUP
|
||||
val isAutofillEnabled = settingsRepository.isAutofillEnabledStateFlow.value
|
||||
val isBrowserAutofillUnconfigured = browserThirdPartyAutofillEnabledManager
|
||||
.browserThirdPartyAutofillStatus
|
||||
.isAnyIsAvailableAndDisabled
|
||||
val nextStep = when {
|
||||
!isAutofillEnabled -> OnboardingStatus.AUTOFILL_SETUP
|
||||
isBrowserAutofillUnconfigured -> OnboardingStatus.BROWSER_AUTOFILL_SETUP
|
||||
else -> OnboardingStatus.FINAL_STEP
|
||||
}
|
||||
authRepository.setOnboardingStatus(nextStep)
|
||||
}
|
||||
|
||||
@ -18,12 +18,15 @@ import com.bitwarden.ui.platform.theme.NonNullExitTransitionProvider
|
||||
import com.bitwarden.ui.platform.theme.RootTransitionProviders
|
||||
import com.bitwarden.ui.platform.util.toObjectNavigationRoute
|
||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SetupAutofillRoute
|
||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SetupBrowserAutofillRoute
|
||||
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.navigateToSetupBrowserAutofillScreen
|
||||
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.setupBrowserAutofillDestination
|
||||
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.AuthGraphRoute
|
||||
@ -107,6 +110,7 @@ fun RootNavScreen(
|
||||
vaultUnlockDestination()
|
||||
vaultUnlockedGraph(navController)
|
||||
setupUnlockDestinationAsRoot()
|
||||
setupBrowserAutofillDestination()
|
||||
setupAutoFillDestinationAsRoot()
|
||||
setupCompleteDestination()
|
||||
exportItemsGraph()
|
||||
@ -140,6 +144,7 @@ fun RootNavScreen(
|
||||
|
||||
RootNavState.OnboardingAccountLockSetup -> SetupUnlockRoute.AsRoot
|
||||
RootNavState.OnboardingAutoFillSetup -> SetupAutofillRoute.AsRoot
|
||||
RootNavState.OnboardingBrowserAutofillSetup -> SetupBrowserAutofillRoute
|
||||
RootNavState.OnboardingStepsComplete -> SetupCompleteRoute
|
||||
}
|
||||
val currentRoute = navController.currentDestination?.rootLevelRoute()
|
||||
@ -271,6 +276,10 @@ fun RootNavScreen(
|
||||
navController.navigateToSetupAutoFillAsRootScreen(rootNavOptions)
|
||||
}
|
||||
|
||||
RootNavState.OnboardingBrowserAutofillSetup -> {
|
||||
navController.navigateToSetupBrowserAutofillScreen(rootNavOptions)
|
||||
}
|
||||
|
||||
RootNavState.OnboardingStepsComplete -> {
|
||||
navController.navigateToSetupCompleteScreen(rootNavOptions)
|
||||
}
|
||||
|
||||
@ -99,15 +99,7 @@ class RootNavViewModel @Inject constructor(
|
||||
|
||||
userState.activeAccount.isVaultUnlocked &&
|
||||
userState.activeAccount.onboardingStatus != OnboardingStatus.COMPLETE -> {
|
||||
when (userState.activeAccount.onboardingStatus) {
|
||||
OnboardingStatus.NOT_STARTED,
|
||||
OnboardingStatus.ACCOUNT_LOCK_SETUP,
|
||||
-> RootNavState.OnboardingAccountLockSetup
|
||||
|
||||
OnboardingStatus.AUTOFILL_SETUP -> RootNavState.OnboardingAutoFillSetup
|
||||
OnboardingStatus.FINAL_STEP -> RootNavState.OnboardingStepsComplete
|
||||
OnboardingStatus.COMPLETE -> throw IllegalStateException("Should not have entered here.")
|
||||
}
|
||||
getOnboardingNavState(onboardingStatus = userState.activeAccount.onboardingStatus)
|
||||
}
|
||||
|
||||
userState.activeAccount.isVaultUnlocked -> {
|
||||
@ -200,6 +192,19 @@ class RootNavViewModel @Inject constructor(
|
||||
mutableStateFlow.update { updatedRootNavState }
|
||||
}
|
||||
|
||||
private fun getOnboardingNavState(
|
||||
onboardingStatus: OnboardingStatus,
|
||||
): RootNavState = when (onboardingStatus) {
|
||||
OnboardingStatus.NOT_STARTED,
|
||||
OnboardingStatus.ACCOUNT_LOCK_SETUP,
|
||||
-> RootNavState.OnboardingAccountLockSetup
|
||||
|
||||
OnboardingStatus.AUTOFILL_SETUP -> RootNavState.OnboardingAutoFillSetup
|
||||
OnboardingStatus.BROWSER_AUTOFILL_SETUP -> RootNavState.OnboardingBrowserAutofillSetup
|
||||
OnboardingStatus.FINAL_STEP -> RootNavState.OnboardingStepsComplete
|
||||
OnboardingStatus.COMPLETE -> throw IllegalStateException("Should not have entered here.")
|
||||
}
|
||||
|
||||
private fun getRegistrationEventNavState(
|
||||
registrationEvent: SpecialCircumstance.RegistrationEvent,
|
||||
): RootNavState = when (registrationEvent) {
|
||||
@ -402,6 +407,12 @@ sealed class RootNavState : Parcelable {
|
||||
@Parcelize
|
||||
data object OnboardingAutoFillSetup : RootNavState()
|
||||
|
||||
/**
|
||||
* App should show the set up browser autofill onboarding screen.
|
||||
*/
|
||||
@Parcelize
|
||||
data object OnboardingBrowserAutofillSetup : RootNavState()
|
||||
|
||||
/**
|
||||
* App should show the onboarding steps complete screen.
|
||||
*/
|
||||
|
||||
@ -227,7 +227,10 @@ private fun AutoFillScreenContent(
|
||||
BrowserAutofillSettingsCard(
|
||||
options = state.browserAutofillSettingsOptions,
|
||||
onOptionClicked = autoFillHandlers.onBrowserAutofillSelected,
|
||||
enabled = state.isAutoFillServicesEnabled,
|
||||
supportingText = stringResource(
|
||||
id = BitwardenString
|
||||
.improves_login_filling_for_supported_websites_on_selected_browsers,
|
||||
),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.core.util.isBuildVersionAtLeast
|
||||
import com.bitwarden.core.util.persistentListOfNotNull
|
||||
import com.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.bitwarden.ui.platform.resource.BitwardenString
|
||||
import com.bitwarden.ui.util.Text
|
||||
@ -18,6 +17,7 @@ import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.browser.model.BrowserAutofillSettingsOption
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.browser.util.toBrowserAutoFillSettingsOptions
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@ -303,23 +303,6 @@ enum class AutofillStyle(val label: Text) {
|
||||
POPUP(label = BitwardenString.autofill_suggestions_popup.asText()),
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private fun BrowserThirdPartyAutofillStatus.toBrowserAutoFillSettingsOptions(): ImmutableList<BrowserAutofillSettingsOption> =
|
||||
persistentListOfNotNull(
|
||||
BrowserAutofillSettingsOption.BraveStable(
|
||||
enabled = this.braveStableStatusData.isThirdPartyEnabled,
|
||||
)
|
||||
.takeIf { this.braveStableStatusData.isAvailable },
|
||||
BrowserAutofillSettingsOption.ChromeStable(
|
||||
enabled = this.chromeStableStatusData.isThirdPartyEnabled,
|
||||
)
|
||||
.takeIf { this.chromeStableStatusData.isAvailable },
|
||||
BrowserAutofillSettingsOption.ChromeBeta(
|
||||
enabled = this.chromeBetaChannelStatusData.isThirdPartyEnabled,
|
||||
)
|
||||
.takeIf { this.chromeBetaChannelStatusData.isAvailable },
|
||||
)
|
||||
|
||||
/**
|
||||
* Models events for the auto-fill screen.
|
||||
*/
|
||||
|
||||
@ -6,14 +6,13 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bitwarden.ui.platform.base.util.cardStyle
|
||||
import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.bitwarden.ui.platform.base.util.toListItemCardStyle
|
||||
import com.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
||||
import com.bitwarden.ui.platform.resource.BitwardenString
|
||||
import com.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.data.autofill.model.browser.BrowserPackage
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.browser.model.BrowserAutofillSettingsOption
|
||||
@ -27,14 +26,14 @@ import kotlinx.collections.immutable.persistentListOf
|
||||
* @param options List of data to display in the card, if the list is empty nothing will be drawn.
|
||||
* @param onOptionClicked Lambda that is invoked when an option row is clicked and passes back the
|
||||
* [BrowserPackage] for that option.
|
||||
* @param enabled Whether to show the switches for each option as enabled.
|
||||
* @param supportingText The optional supporting text in the card.
|
||||
*/
|
||||
@Composable
|
||||
fun BrowserAutofillSettingsCard(
|
||||
options: ImmutableList<BrowserAutofillSettingsOption>,
|
||||
onOptionClicked: (BrowserPackage) -> Unit,
|
||||
enabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
supportingText: String? = null,
|
||||
) {
|
||||
if (options.isEmpty()) return
|
||||
Column(modifier = modifier) {
|
||||
@ -45,37 +44,31 @@ fun BrowserAutofillSettingsCard(
|
||||
onCheckedChange = {
|
||||
onOptionClicked(option.browserPackage)
|
||||
},
|
||||
cardStyle = if (index == 0) {
|
||||
CardStyle.Top(
|
||||
dividerPadding = 16.dp,
|
||||
)
|
||||
} else {
|
||||
CardStyle.Middle(
|
||||
dividerPadding = 16.dp,
|
||||
)
|
||||
cardStyle = when {
|
||||
supportingText == null -> options.toListItemCardStyle(index = index)
|
||||
index == 0 -> CardStyle.Top(dividerPadding = 16.dp)
|
||||
else -> CardStyle.Middle(dividerPadding = 16.dp)
|
||||
},
|
||||
enabled = enabled,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = BitwardenString
|
||||
.improves_login_filling_for_supported_websites_on_selected_browsers,
|
||||
),
|
||||
style = BitwardenTheme.typography.bodyMedium,
|
||||
color = BitwardenTheme.colorScheme.text.secondary,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin()
|
||||
.cardStyle(
|
||||
cardStyle = CardStyle.Bottom,
|
||||
paddingHorizontal = 16.dp,
|
||||
)
|
||||
.defaultMinSize(minHeight = 48.dp),
|
||||
)
|
||||
supportingText?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = BitwardenTheme.typography.bodyMedium,
|
||||
color = BitwardenTheme.colorScheme.text.secondary,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin()
|
||||
.cardStyle(
|
||||
cardStyle = CardStyle.Bottom,
|
||||
paddingHorizontal = 16.dp,
|
||||
)
|
||||
.defaultMinSize(minHeight = 48.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,7 +82,6 @@ private fun ChromeAutofillSettingsCard_preview() {
|
||||
BrowserAutofillSettingsOption.ChromeStable(enabled = false),
|
||||
BrowserAutofillSettingsOption.ChromeBeta(enabled = true),
|
||||
),
|
||||
enabled = true,
|
||||
onOptionClicked = {},
|
||||
)
|
||||
}
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill.browser.util
|
||||
|
||||
import com.bitwarden.core.util.persistentListOfNotNull
|
||||
import com.x8bit.bitwarden.data.autofill.model.browser.BrowserThirdPartyAutofillStatus
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.browser.model.BrowserAutofillSettingsOption
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/**
|
||||
* Converts a [BrowserThirdPartyAutofillStatus] to a list of [BrowserAutofillSettingsOption].
|
||||
*/
|
||||
@Suppress("MaxLineLength")
|
||||
fun BrowserThirdPartyAutofillStatus.toBrowserAutoFillSettingsOptions(): ImmutableList<BrowserAutofillSettingsOption> =
|
||||
persistentListOfNotNull(
|
||||
BrowserAutofillSettingsOption.BraveStable(braveStableStatusData.isThirdPartyEnabled)
|
||||
.takeIf { this.braveStableStatusData.isAvailable },
|
||||
BrowserAutofillSettingsOption.ChromeStable(chromeStableStatusData.isThirdPartyEnabled)
|
||||
.takeIf { this.chromeStableStatusData.isAvailable },
|
||||
BrowserAutofillSettingsOption.ChromeBeta(chromeBetaChannelStatusData.isThirdPartyEnabled)
|
||||
.takeIf { this.chromeBetaChannelStatusData.isAvailable },
|
||||
)
|
||||
@ -6,6 +6,7 @@ import com.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillEnabledManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
@ -48,6 +49,11 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() {
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
every { setOnboardingStatus(any()) } just runs
|
||||
}
|
||||
private val thirdPartyAutofillEnabledManager: BrowserThirdPartyAutofillEnabledManager = mockk {
|
||||
every {
|
||||
browserThirdPartyAutofillStatus
|
||||
} returns mockk { every { isAnyIsAvailableAndDisabled } returns false }
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
@ -131,10 +137,24 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() {
|
||||
fun `handleTurnOnLaterConfirmClick sets onboarding status to FINAL_STEP`() {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(SetupAutoFillAction.TurnOnLaterConfirmClick)
|
||||
verify {
|
||||
authRepository.setOnboardingStatus(
|
||||
OnboardingStatus.FINAL_STEP,
|
||||
)
|
||||
verify(exactly = 1) {
|
||||
authRepository.setOnboardingStatus(status = OnboardingStatus.FINAL_STEP)
|
||||
thirdPartyAutofillEnabledManager.browserThirdPartyAutofillStatus
|
||||
firstTimeActionManager.storeShowAutoFillSettingBadge(showBadge = true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleTurnOnLaterConfirmClick sets onboarding status to BROWSER_AUTOFILL_SETUP`() {
|
||||
every {
|
||||
thirdPartyAutofillEnabledManager.browserThirdPartyAutofillStatus
|
||||
} returns mockk { every { isAnyIsAvailableAndDisabled } returns true }
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(SetupAutoFillAction.TurnOnLaterConfirmClick)
|
||||
verify(exactly = 1) {
|
||||
authRepository.setOnboardingStatus(status = OnboardingStatus.FINAL_STEP)
|
||||
thirdPartyAutofillEnabledManager.browserThirdPartyAutofillStatus
|
||||
firstTimeActionManager.storeShowAutoFillSettingBadge(showBadge = true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,9 +164,22 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(SetupAutoFillAction.ContinueClick)
|
||||
verify(exactly = 1) {
|
||||
authRepository.setOnboardingStatus(
|
||||
OnboardingStatus.FINAL_STEP,
|
||||
)
|
||||
authRepository.setOnboardingStatus(status = OnboardingStatus.FINAL_STEP)
|
||||
thirdPartyAutofillEnabledManager.browserThirdPartyAutofillStatus
|
||||
firstTimeActionManager.storeShowAutoFillSettingBadge(showBadge = false)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleContinueClick sets onboarding status to BROWSER_AUTOFILL_SETUP`() {
|
||||
every {
|
||||
thirdPartyAutofillEnabledManager.browserThirdPartyAutofillStatus
|
||||
} returns mockk { every { isAnyIsAvailableAndDisabled } returns true }
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(SetupAutoFillAction.ContinueClick)
|
||||
verify(exactly = 1) {
|
||||
authRepository.setOnboardingStatus(status = OnboardingStatus.FINAL_STEP)
|
||||
thirdPartyAutofillEnabledManager.browserThirdPartyAutofillStatus
|
||||
firstTimeActionManager.storeShowAutoFillSettingBadge(showBadge = false)
|
||||
}
|
||||
}
|
||||
@ -155,8 +188,9 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() {
|
||||
@Test
|
||||
fun `handleContinueClick send NavigateBack event when not initial setup and sets first time flag to false`() =
|
||||
runTest {
|
||||
val viewModel =
|
||||
createViewModel(initialState = DEFAULT_STATE.copy(isInitialSetup = false))
|
||||
val viewModel = createViewModel(
|
||||
initialState = DEFAULT_STATE.copy(isInitialSetup = false),
|
||||
)
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(SetupAutoFillAction.ContinueClick)
|
||||
assertEquals(
|
||||
@ -168,9 +202,7 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() {
|
||||
firstTimeActionManager.storeShowAutoFillSettingBadge(showBadge = false)
|
||||
}
|
||||
verify(exactly = 0) {
|
||||
authRepository.setOnboardingStatus(
|
||||
OnboardingStatus.FINAL_STEP,
|
||||
)
|
||||
authRepository.setOnboardingStatus(status = OnboardingStatus.FINAL_STEP)
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,6 +236,7 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() {
|
||||
settingsRepository = settingsRepository,
|
||||
authRepository = authRepository,
|
||||
firstTimeActionManager = firstTimeActionManager,
|
||||
browserThirdPartyAutofillEnabledManager = thirdPartyAutofillEnabledManager,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,179 @@
|
||||
package com.x8bit.bitwarden.ui.auth.feature.accountsetup
|
||||
|
||||
import androidx.compose.ui.test.assert
|
||||
import androidx.compose.ui.test.assertIsEnabled
|
||||
import androidx.compose.ui.test.assertIsNotEnabled
|
||||
import androidx.compose.ui.test.hasAnyAncestor
|
||||
import androidx.compose.ui.test.isDialog
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.ui.platform.manager.IntentManager
|
||||
import com.bitwarden.ui.util.assertNoDialogExists
|
||||
import com.x8bit.bitwarden.data.autofill.model.browser.BrowserPackage
|
||||
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.browser.model.BrowserAutofillSettingsOption
|
||||
import com.x8bit.bitwarden.ui.platform.manager.utils.startBrowserAutofillSettingsActivity
|
||||
import com.x8bit.bitwarden.ui.platform.manager.utils.startSystemAutofillSettingsActivity
|
||||
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.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class SetupBrowserAutofillScreenTest : BitwardenComposeTest() {
|
||||
private val intentManager = mockk<IntentManager>()
|
||||
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<SetupBrowserAutofillEvent>()
|
||||
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||
private val viewModel = mockk<SetupBrowserAutofillViewModel> {
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
every { trySendAction(action = any()) } just runs
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockkStatic(IntentManager::startSystemAutofillSettingsActivity)
|
||||
setContent(
|
||||
intentManager = intentManager,
|
||||
) {
|
||||
SetupBrowserAutofillScreen(
|
||||
viewModel = viewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
unmockkStatic(IntentManager::startSystemAutofillSettingsActivity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateToBrowserAutofillSettings should start system autofill settings activity`() {
|
||||
val browserPackage = BrowserPackage.CHROME_STABLE
|
||||
every { intentManager.startBrowserAutofillSettingsActivity(browserPackage) } returns true
|
||||
mutableEventFlow.tryEmit(
|
||||
value = SetupBrowserAutofillEvent.NavigateToBrowserAutofillSettings(browserPackage),
|
||||
)
|
||||
verify(exactly = 1) {
|
||||
intentManager.startBrowserAutofillSettingsActivity(browserPackage)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `BrowserIntegrationClick should emit when integration row is clicked`() {
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Use Brave autofill integration")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(
|
||||
SetupBrowserAutofillAction.BrowserIntegrationClick(BrowserPackage.BRAVE_RELEASE),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `continue button is enabled or disabled according to state`() {
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Continue")
|
||||
.assertIsEnabled()
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
browserAutofillSettingsOptions = persistentListOf(
|
||||
BrowserAutofillSettingsOption.BraveStable(enabled = false),
|
||||
),
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Continue")
|
||||
.assertIsNotEnabled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ContinueClick should emit when enabled and clicked`() {
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Continue")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(SetupBrowserAutofillAction.ContinueClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `TurnOnLaterClick should emit when clicked`() {
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Turn on later")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(SetupBrowserAutofillAction.TurnOnLaterClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `correct dialog should be displayed according to state`() {
|
||||
composeTestRule.assertNoDialogExists()
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SetupBrowserAutofillState.DialogState.TurnOnLaterDialog)
|
||||
}
|
||||
|
||||
composeTestRule.onNode(isDialog()).assertExists()
|
||||
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `DismissDialog should emit when dialog is dismissed`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SetupBrowserAutofillState.DialogState.TurnOnLaterDialog)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Cancel")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(SetupBrowserAutofillAction.DismissDialog)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `TurnOnLaterConfirmClick should emit when dialog is confirmed`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = SetupBrowserAutofillState.DialogState.TurnOnLaterDialog)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Confirm")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(SetupBrowserAutofillAction.TurnOnLaterConfirmClick)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE: SetupBrowserAutofillState = SetupBrowserAutofillState(
|
||||
dialogState = null,
|
||||
browserAutofillSettingsOptions = persistentListOf(
|
||||
BrowserAutofillSettingsOption.BraveStable(enabled = true),
|
||||
),
|
||||
)
|
||||
@ -0,0 +1,184 @@
|
||||
package com.x8bit.bitwarden.ui.auth.feature.accountsetup
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillEnabledManager
|
||||
import com.x8bit.bitwarden.data.autofill.model.browser.BrowserPackage
|
||||
import com.x8bit.bitwarden.data.autofill.model.browser.BrowserThirdPartyAutoFillData
|
||||
import com.x8bit.bitwarden.data.autofill.model.browser.BrowserThirdPartyAutofillStatus
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.browser.model.BrowserAutofillSettingsOption
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class SetupBrowserAutofillViewModelTest {
|
||||
private val authRepository: AuthRepository = mockk {
|
||||
every { setOnboardingStatus(status = any()) } just runs
|
||||
}
|
||||
private val mutableBrowserThirdPartyAutofillStatusFlow =
|
||||
MutableStateFlow(DEFAULT_BROWSER_AUTOFILL_STATUS)
|
||||
private val thirdPartyAutofillEnabledManager: BrowserThirdPartyAutofillEnabledManager = mockk {
|
||||
every {
|
||||
browserThirdPartyAutofillStatus
|
||||
} answers { mutableBrowserThirdPartyAutofillStatusFlow.value }
|
||||
every {
|
||||
browserThirdPartyAutofillStatusFlow
|
||||
} returns mutableBrowserThirdPartyAutofillStatusFlow
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `browserThirdPartyAutofillStatusFlow should update the state`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(DEFAULT_STATE, awaitItem())
|
||||
mutableBrowserThirdPartyAutofillStatusFlow.value = DEFAULT_BROWSER_AUTOFILL_STATUS.copy(
|
||||
braveStableStatusData = BrowserThirdPartyAutoFillData(
|
||||
isAvailable = true,
|
||||
isThirdPartyEnabled = true,
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
browserAutofillSettingsOptions = persistentListOf(
|
||||
BrowserAutofillSettingsOption.BraveStable(enabled = true),
|
||||
BrowserAutofillSettingsOption.ChromeStable(enabled = false),
|
||||
BrowserAutofillSettingsOption.ChromeBeta(enabled = false),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
mutableBrowserThirdPartyAutofillStatusFlow.value = DEFAULT_BROWSER_AUTOFILL_STATUS.copy(
|
||||
braveStableStatusData = BrowserThirdPartyAutoFillData(
|
||||
isAvailable = false,
|
||||
isThirdPartyEnabled = false,
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
browserAutofillSettingsOptions = persistentListOf(
|
||||
BrowserAutofillSettingsOption.ChromeStable(enabled = false),
|
||||
BrowserAutofillSettingsOption.ChromeBeta(enabled = false),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `BrowserIntegrationClick should send NavigateToBrowserAutofillSettings event`() = runTest {
|
||||
val browserPackage = BrowserPackage.BRAVE_RELEASE
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(
|
||||
SetupBrowserAutofillAction.BrowserIntegrationClick(browserPackage),
|
||||
)
|
||||
assertEquals(
|
||||
SetupBrowserAutofillEvent.NavigateToBrowserAutofillSettings(browserPackage),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `DismissDialog should clear the dialog state`() = runTest {
|
||||
val initialState = DEFAULT_STATE.copy(
|
||||
dialogState = SetupBrowserAutofillState.DialogState.TurnOnLaterDialog,
|
||||
)
|
||||
val viewModel = createViewModel(initialState = initialState)
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(initialState, awaitItem())
|
||||
viewModel.trySendAction(SetupBrowserAutofillAction.DismissDialog)
|
||||
assertEquals(
|
||||
initialState.copy(dialogState = null),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleContinueClick should set the onboarding state to FINAL_STEP`() {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(SetupBrowserAutofillAction.ContinueClick)
|
||||
verify(exactly = 1) {
|
||||
authRepository.setOnboardingStatus(status = OnboardingStatus.FINAL_STEP)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `TurnOnLaterClick should set the onboarding state to FINAL_STEP`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(DEFAULT_STATE, awaitItem())
|
||||
viewModel.trySendAction(SetupBrowserAutofillAction.TurnOnLaterClick)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = SetupBrowserAutofillState.DialogState.TurnOnLaterDialog,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `TurnOnLaterConfirmClick should set the onboarding state to FINAL_STEP`() = runTest {
|
||||
val initialState = DEFAULT_STATE.copy(
|
||||
dialogState = SetupBrowserAutofillState.DialogState.TurnOnLaterDialog,
|
||||
)
|
||||
val viewModel = createViewModel(initialState = initialState)
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(initialState, awaitItem())
|
||||
viewModel.trySendAction(SetupBrowserAutofillAction.TurnOnLaterConfirmClick)
|
||||
assertEquals(
|
||||
initialState.copy(dialogState = null),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
verify(exactly = 1) {
|
||||
authRepository.setOnboardingStatus(status = OnboardingStatus.FINAL_STEP)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
initialState: SetupBrowserAutofillState? = null,
|
||||
): SetupBrowserAutofillViewModel = SetupBrowserAutofillViewModel(
|
||||
savedStateHandle = SavedStateHandle().apply {
|
||||
set(key = "state", value = initialState)
|
||||
},
|
||||
authRepository = authRepository,
|
||||
browserThirdPartyAutofillEnabledManager = thirdPartyAutofillEnabledManager,
|
||||
)
|
||||
}
|
||||
|
||||
private val DEFAULT_BROWSER_AUTOFILL_STATUS = BrowserThirdPartyAutofillStatus(
|
||||
braveStableStatusData = BrowserThirdPartyAutoFillData(
|
||||
isAvailable = true,
|
||||
isThirdPartyEnabled = false,
|
||||
),
|
||||
chromeStableStatusData = BrowserThirdPartyAutoFillData(
|
||||
isAvailable = true,
|
||||
isThirdPartyEnabled = false,
|
||||
),
|
||||
chromeBetaChannelStatusData = BrowserThirdPartyAutoFillData(
|
||||
isAvailable = true,
|
||||
isThirdPartyEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
private val DEFAULT_STATE = SetupBrowserAutofillState(
|
||||
dialogState = null,
|
||||
browserAutofillSettingsOptions = persistentListOf(
|
||||
BrowserAutofillSettingsOption.BraveStable(enabled = false),
|
||||
BrowserAutofillSettingsOption.ChromeStable(enabled = false),
|
||||
BrowserAutofillSettingsOption.ChromeBeta(enabled = false),
|
||||
),
|
||||
)
|
||||
@ -9,6 +9,7 @@ import com.bitwarden.ui.util.asText
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillEnabledManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
|
||||
@ -59,6 +60,11 @@ class SetupUnlockViewModelTest : BaseViewModelTest() {
|
||||
} returns false
|
||||
every { createCipherOrNull(DEFAULT_USER_ID) } returns CIPHER
|
||||
}
|
||||
private val thirdPartyAutofillEnabledManager: BrowserThirdPartyAutofillEnabledManager = mockk {
|
||||
every {
|
||||
browserThirdPartyAutofillStatus
|
||||
} returns mockk { every { isAnyIsAvailableAndDisabled } returns false }
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
@ -90,10 +96,9 @@ class SetupUnlockViewModelTest : BaseViewModelTest() {
|
||||
fun `ContinueClick should call setOnboardingStatus and set to AUTOFILL_SETUP if AutoFill is not enabled`() {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(SetupUnlockAction.ContinueClick)
|
||||
verify {
|
||||
authRepository.setOnboardingStatus(
|
||||
status = OnboardingStatus.AUTOFILL_SETUP,
|
||||
)
|
||||
verify(exactly = 1) {
|
||||
thirdPartyAutofillEnabledManager.browserThirdPartyAutofillStatus
|
||||
authRepository.setOnboardingStatus(status = OnboardingStatus.AUTOFILL_SETUP)
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,29 +136,42 @@ class SetupUnlockViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `ContinueClick should call setOnboardingStatus and set to FINAL_STEP if AutoFill is already enabled and set first time value to false`() {
|
||||
fun `ContinueClick should call setOnboardingStatus and set to FINAL_STEP if AutoFill is already enabled, browsers are setup, and set first time value to false`() {
|
||||
mutableAutofillEnabledStateFlow.update { true }
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(SetupUnlockAction.ContinueClick)
|
||||
verify(exactly = 1) {
|
||||
authRepository.setOnboardingStatus(
|
||||
status = OnboardingStatus.FINAL_STEP,
|
||||
)
|
||||
thirdPartyAutofillEnabledManager.browserThirdPartyAutofillStatus
|
||||
authRepository.setOnboardingStatus(status = OnboardingStatus.FINAL_STEP)
|
||||
firstTimeActionManager.storeShowUnlockSettingBadge(showBadge = false)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `SetUpLaterClick should call setOnboardingStatus and set to FINAL_STEP if AutoFill is already enabled`() =
|
||||
fun `ContinueClick should call setOnboardingStatus and set to BROWSER_AUTOFILL_SETUP if AutoFill is already enabled and browsers are not setup`() {
|
||||
mutableAutofillEnabledStateFlow.update { true }
|
||||
every {
|
||||
thirdPartyAutofillEnabledManager.browserThirdPartyAutofillStatus
|
||||
} returns mockk { every { isAnyIsAvailableAndDisabled } returns true }
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(SetupUnlockAction.ContinueClick)
|
||||
verify(exactly = 1) {
|
||||
thirdPartyAutofillEnabledManager.browserThirdPartyAutofillStatus
|
||||
authRepository.setOnboardingStatus(status = OnboardingStatus.BROWSER_AUTOFILL_SETUP)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `SetUpLaterClick should call setOnboardingStatus and set to FINAL_STEP if AutoFill is already enabled and browsers are setup`() =
|
||||
runTest {
|
||||
mutableAutofillEnabledStateFlow.update { true }
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(SetupUnlockAction.SetUpLaterClick)
|
||||
verify {
|
||||
authRepository.setOnboardingStatus(
|
||||
status = OnboardingStatus.FINAL_STEP,
|
||||
)
|
||||
verify(exactly = 1) {
|
||||
thirdPartyAutofillEnabledManager.browserThirdPartyAutofillStatus
|
||||
authRepository.setOnboardingStatus(status = OnboardingStatus.FINAL_STEP)
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,6 +413,7 @@ class SetupUnlockViewModelTest : BaseViewModelTest() {
|
||||
settingsRepository = settingsRepository,
|
||||
biometricsEncryptionManager = biometricsEncryptionManager,
|
||||
firstTimeActionManager = firstTimeActionManager,
|
||||
browserThirdPartyAutofillEnabledManager = thirdPartyAutofillEnabledManager,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import com.bitwarden.ui.platform.base.createMockNavHostController
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SetupAutofillRoute
|
||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SetupBrowserAutofillRoute
|
||||
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.auth.AuthGraphRoute
|
||||
@ -417,6 +418,17 @@ class RootNavScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure navigating to browser autofill setup works as expected:
|
||||
rootNavStateFlow.value = RootNavState.OnboardingBrowserAutofillSetup
|
||||
composeTestRule.runOnIdle {
|
||||
verify {
|
||||
mockNavHostController.navigate(
|
||||
route = SetupBrowserAutofillRoute,
|
||||
navOptions = expectedNavOptions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure navigating to account setup complete works as expected:
|
||||
rootNavStateFlow.value = RootNavState.OnboardingStepsComplete
|
||||
composeTestRule.runOnIdle {
|
||||
|
||||
@ -1241,6 +1241,44 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when the active user has an unlocked vault and they have a OnboardingStatus of BROWSER_AUTOFILL_SETUP the nav state should be OnboardingBrowserAutofillSetup`() {
|
||||
mutableUserStateFlow.tryEmit(
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
environment = Environment.Us,
|
||||
isPremium = true,
|
||||
isLoggedIn = true,
|
||||
isVaultUnlocked = true,
|
||||
needsPasswordReset = false,
|
||||
isBiometricsEnabled = false,
|
||||
organizations = emptyList(),
|
||||
needsMasterPassword = false,
|
||||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.BROWSER_AUTOFILL_SETUP,
|
||||
firstTimeState = FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(
|
||||
RootNavState.OnboardingBrowserAutofillSetup,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when the active user has an unlocked vault and they have a OnboardingStatus of FINAL_STEP the nav state should be OnboardingAutoFillSetup`() {
|
||||
|
||||
@ -611,7 +611,7 @@ class AutoFillScreenTest : BitwardenComposeTest() {
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Use Chrome autofill integration (Beta)")
|
||||
.onNodeWithText("Use Chrome Beta autofill integration")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill.browser.util
|
||||
|
||||
import com.x8bit.bitwarden.data.autofill.model.browser.BrowserThirdPartyAutoFillData
|
||||
import com.x8bit.bitwarden.data.autofill.model.browser.BrowserThirdPartyAutofillStatus
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.browser.model.BrowserAutofillSettingsOption
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class BrowserThirdPartyAutofillStatusExtensionsTest {
|
||||
@Test
|
||||
fun `toBrowserAutoFillSettingsOptions should be empty if no options are available`() {
|
||||
val browserThirdPartyAutofillStatus = BrowserThirdPartyAutofillStatus(
|
||||
braveStableStatusData = BrowserThirdPartyAutoFillData(
|
||||
isAvailable = false,
|
||||
isThirdPartyEnabled = false,
|
||||
),
|
||||
chromeStableStatusData = BrowserThirdPartyAutoFillData(
|
||||
isAvailable = false,
|
||||
isThirdPartyEnabled = false,
|
||||
),
|
||||
chromeBetaChannelStatusData = BrowserThirdPartyAutoFillData(
|
||||
isAvailable = false,
|
||||
isThirdPartyEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
val result = browserThirdPartyAutofillStatus.toBrowserAutoFillSettingsOptions()
|
||||
|
||||
assertTrue(result.isEmpty())
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toBrowserAutoFillSettingsOptions should contain all options if all options are available`() {
|
||||
val browserThirdPartyAutofillStatus = BrowserThirdPartyAutofillStatus(
|
||||
braveStableStatusData = BrowserThirdPartyAutoFillData(
|
||||
isAvailable = true,
|
||||
isThirdPartyEnabled = false,
|
||||
),
|
||||
chromeStableStatusData = BrowserThirdPartyAutoFillData(
|
||||
isAvailable = true,
|
||||
isThirdPartyEnabled = true,
|
||||
),
|
||||
chromeBetaChannelStatusData = BrowserThirdPartyAutoFillData(
|
||||
isAvailable = true,
|
||||
isThirdPartyEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
val result = browserThirdPartyAutofillStatus.toBrowserAutoFillSettingsOptions()
|
||||
|
||||
assertEquals(
|
||||
persistentListOf(
|
||||
BrowserAutofillSettingsOption.BraveStable(enabled = false),
|
||||
BrowserAutofillSettingsOption.ChromeStable(enabled = true),
|
||||
BrowserAutofillSettingsOption.ChromeBeta(enabled = false),
|
||||
),
|
||||
result,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -758,6 +758,8 @@ Do you want to switch to this account?</string>
|
||||
<string name="allow_bitwarden_authenticator_syncing">Allow authenticator syncing</string>
|
||||
<string name="there_was_an_issue_validating_the_registration_token">There was an issue validating the registration token.</string>
|
||||
<string name="turn_on_autofill">Turn on autofill</string>
|
||||
<string name="turn_on_browser_autofill_integration">Turn on browser autofill integration</string>
|
||||
<string name="youre_using_a_browser_that_requires_special_permissions">You’re using a browser that requires special permissions for Bitwarden to autofill your passwords. Enable your preferred autofill integration below.</string>
|
||||
<string name="use_autofill_to_log_into_your_accounts">Use autofill to log into your accounts with a single tap.</string>
|
||||
<string name="turn_on_later">Turn on later</string>
|
||||
<string name="turn_on_autofill_later">Turn on autofill later?</string>
|
||||
@ -906,7 +908,7 @@ Do you want to switch to this account?</string>
|
||||
<string name="self_host_server_url">Self-host server URL</string>
|
||||
<string name="use_brave_autofill_integration">Use Brave autofill integration</string>
|
||||
<string name="use_chrome_autofill_integration">Use Chrome autofill integration</string>
|
||||
<string name="use_chrome_beta_autofill_integration">Use Chrome autofill integration (Beta)</string>
|
||||
<string name="use_chrome_beta_autofill_integration">Use Chrome Beta autofill integration</string>
|
||||
<string name="improves_login_filling_for_supported_websites_on_selected_browsers">Improves login filling for supported websites on selected browsers. Once enabled, you’ll be directed to browser settings to enable third-party autofill.</string>
|
||||
<string name="show_more">Show more</string>
|
||||
<string name="no_folder">No folder</string>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user