mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 09:56:45 -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")
|
@SerialName("autofillSetup")
|
||||||
AUTOFILL_SETUP,
|
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.
|
* 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.bitwarden.ui.platform.base.BaseViewModel
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
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.AuthRepository
|
||||||
|
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillEnabledManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
@ -27,6 +28,7 @@ class SetupAutoFillViewModel @Inject constructor(
|
|||||||
private val settingsRepository: SettingsRepository,
|
private val settingsRepository: SettingsRepository,
|
||||||
private val authRepository: AuthRepository,
|
private val authRepository: AuthRepository,
|
||||||
private val firstTimeActionManager: FirstTimeActionManager,
|
private val firstTimeActionManager: FirstTimeActionManager,
|
||||||
|
private val browserThirdPartyAutofillEnabledManager: BrowserThirdPartyAutofillEnabledManager,
|
||||||
) :
|
) :
|
||||||
BaseViewModel<SetupAutoFillState, SetupAutoFillEvent, SetupAutoFillAction>(
|
BaseViewModel<SetupAutoFillState, SetupAutoFillEvent, SetupAutoFillAction>(
|
||||||
// We load the state from the savedStateHandle for testing purposes.
|
// We load the state from the savedStateHandle for testing purposes.
|
||||||
@ -100,13 +102,13 @@ class SetupAutoFillViewModel @Inject constructor(
|
|||||||
|
|
||||||
private fun handleTurnOnLaterConfirmClick() {
|
private fun handleTurnOnLaterConfirmClick() {
|
||||||
firstTimeActionManager.storeShowAutoFillSettingBadge(showBadge = true)
|
firstTimeActionManager.storeShowAutoFillSettingBadge(showBadge = true)
|
||||||
updateOnboardingStatusToFinalStep()
|
updateOnboardingStatusToNextStep()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleContinueClick() {
|
private fun handleContinueClick() {
|
||||||
firstTimeActionManager.storeShowAutoFillSettingBadge(showBadge = false)
|
firstTimeActionManager.storeShowAutoFillSettingBadge(showBadge = false)
|
||||||
if (state.isInitialSetup) {
|
if (state.isInitialSetup) {
|
||||||
updateOnboardingStatusToFinalStep()
|
updateOnboardingStatusToNextStep()
|
||||||
} else {
|
} else {
|
||||||
sendEvent(SetupAutoFillEvent.NavigateBack)
|
sendEvent(SetupAutoFillEvent.NavigateBack)
|
||||||
}
|
}
|
||||||
@ -120,10 +122,18 @@ class SetupAutoFillViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateOnboardingStatusToFinalStep() =
|
private fun updateOnboardingStatusToNextStep() {
|
||||||
authRepository.setOnboardingStatus(
|
val isAutofillEnabled = settingsRepository.isAutofillEnabledStateFlow.value
|
||||||
status = OnboardingStatus.FINAL_STEP,
|
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.bitwarden.ui.util.asText
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
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.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.BiometricsEncryptionManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
@ -34,6 +35,7 @@ class SetupUnlockViewModel @Inject constructor(
|
|||||||
private val settingsRepository: SettingsRepository,
|
private val settingsRepository: SettingsRepository,
|
||||||
private val biometricsEncryptionManager: BiometricsEncryptionManager,
|
private val biometricsEncryptionManager: BiometricsEncryptionManager,
|
||||||
private val firstTimeActionManager: FirstTimeActionManager,
|
private val firstTimeActionManager: FirstTimeActionManager,
|
||||||
|
private val browserThirdPartyAutofillEnabledManager: BrowserThirdPartyAutofillEnabledManager,
|
||||||
) : BaseViewModel<SetupUnlockState, SetupUnlockEvent, SetupUnlockAction>(
|
) : BaseViewModel<SetupUnlockState, SetupUnlockEvent, SetupUnlockAction>(
|
||||||
// We load the state from the savedStateHandle for testing purposes.
|
// We load the state from the savedStateHandle for testing purposes.
|
||||||
initialState = savedStateHandle[KEY_STATE] ?: run {
|
initialState = savedStateHandle[KEY_STATE] ?: run {
|
||||||
@ -203,10 +205,14 @@ class SetupUnlockViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateOnboardingStatusToNextStep() {
|
private fun updateOnboardingStatusToNextStep() {
|
||||||
val nextStep = if (settingsRepository.isAutofillEnabledStateFlow.value) {
|
val isAutofillEnabled = settingsRepository.isAutofillEnabledStateFlow.value
|
||||||
OnboardingStatus.FINAL_STEP
|
val isBrowserAutofillUnconfigured = browserThirdPartyAutofillEnabledManager
|
||||||
} else {
|
.browserThirdPartyAutofillStatus
|
||||||
OnboardingStatus.AUTOFILL_SETUP
|
.isAnyIsAvailableAndDisabled
|
||||||
|
val nextStep = when {
|
||||||
|
!isAutofillEnabled -> OnboardingStatus.AUTOFILL_SETUP
|
||||||
|
isBrowserAutofillUnconfigured -> OnboardingStatus.BROWSER_AUTOFILL_SETUP
|
||||||
|
else -> OnboardingStatus.FINAL_STEP
|
||||||
}
|
}
|
||||||
authRepository.setOnboardingStatus(nextStep)
|
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.theme.RootTransitionProviders
|
||||||
import com.bitwarden.ui.platform.util.toObjectNavigationRoute
|
import com.bitwarden.ui.platform.util.toObjectNavigationRoute
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SetupAutofillRoute
|
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.SetupCompleteRoute
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SetupUnlockRoute
|
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.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.navigateToSetupCompleteScreen
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.navigateToSetupUnlockScreenAsRoot
|
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.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.setupCompleteDestination
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.setupUnlockDestinationAsRoot
|
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.setupUnlockDestinationAsRoot
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.auth.AuthGraphRoute
|
import com.x8bit.bitwarden.ui.auth.feature.auth.AuthGraphRoute
|
||||||
@ -107,6 +110,7 @@ fun RootNavScreen(
|
|||||||
vaultUnlockDestination()
|
vaultUnlockDestination()
|
||||||
vaultUnlockedGraph(navController)
|
vaultUnlockedGraph(navController)
|
||||||
setupUnlockDestinationAsRoot()
|
setupUnlockDestinationAsRoot()
|
||||||
|
setupBrowserAutofillDestination()
|
||||||
setupAutoFillDestinationAsRoot()
|
setupAutoFillDestinationAsRoot()
|
||||||
setupCompleteDestination()
|
setupCompleteDestination()
|
||||||
exportItemsGraph()
|
exportItemsGraph()
|
||||||
@ -140,6 +144,7 @@ fun RootNavScreen(
|
|||||||
|
|
||||||
RootNavState.OnboardingAccountLockSetup -> SetupUnlockRoute.AsRoot
|
RootNavState.OnboardingAccountLockSetup -> SetupUnlockRoute.AsRoot
|
||||||
RootNavState.OnboardingAutoFillSetup -> SetupAutofillRoute.AsRoot
|
RootNavState.OnboardingAutoFillSetup -> SetupAutofillRoute.AsRoot
|
||||||
|
RootNavState.OnboardingBrowserAutofillSetup -> SetupBrowserAutofillRoute
|
||||||
RootNavState.OnboardingStepsComplete -> SetupCompleteRoute
|
RootNavState.OnboardingStepsComplete -> SetupCompleteRoute
|
||||||
}
|
}
|
||||||
val currentRoute = navController.currentDestination?.rootLevelRoute()
|
val currentRoute = navController.currentDestination?.rootLevelRoute()
|
||||||
@ -271,6 +276,10 @@ fun RootNavScreen(
|
|||||||
navController.navigateToSetupAutoFillAsRootScreen(rootNavOptions)
|
navController.navigateToSetupAutoFillAsRootScreen(rootNavOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RootNavState.OnboardingBrowserAutofillSetup -> {
|
||||||
|
navController.navigateToSetupBrowserAutofillScreen(rootNavOptions)
|
||||||
|
}
|
||||||
|
|
||||||
RootNavState.OnboardingStepsComplete -> {
|
RootNavState.OnboardingStepsComplete -> {
|
||||||
navController.navigateToSetupCompleteScreen(rootNavOptions)
|
navController.navigateToSetupCompleteScreen(rootNavOptions)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -99,15 +99,7 @@ class RootNavViewModel @Inject constructor(
|
|||||||
|
|
||||||
userState.activeAccount.isVaultUnlocked &&
|
userState.activeAccount.isVaultUnlocked &&
|
||||||
userState.activeAccount.onboardingStatus != OnboardingStatus.COMPLETE -> {
|
userState.activeAccount.onboardingStatus != OnboardingStatus.COMPLETE -> {
|
||||||
when (userState.activeAccount.onboardingStatus) {
|
getOnboardingNavState(onboardingStatus = 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.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
userState.activeAccount.isVaultUnlocked -> {
|
userState.activeAccount.isVaultUnlocked -> {
|
||||||
@ -200,6 +192,19 @@ class RootNavViewModel @Inject constructor(
|
|||||||
mutableStateFlow.update { updatedRootNavState }
|
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(
|
private fun getRegistrationEventNavState(
|
||||||
registrationEvent: SpecialCircumstance.RegistrationEvent,
|
registrationEvent: SpecialCircumstance.RegistrationEvent,
|
||||||
): RootNavState = when (registrationEvent) {
|
): RootNavState = when (registrationEvent) {
|
||||||
@ -402,6 +407,12 @@ sealed class RootNavState : Parcelable {
|
|||||||
@Parcelize
|
@Parcelize
|
||||||
data object OnboardingAutoFillSetup : RootNavState()
|
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.
|
* App should show the onboarding steps complete screen.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -227,7 +227,10 @@ private fun AutoFillScreenContent(
|
|||||||
BrowserAutofillSettingsCard(
|
BrowserAutofillSettingsCard(
|
||||||
options = state.browserAutofillSettingsOptions,
|
options = state.browserAutofillSettingsOptions,
|
||||||
onOptionClicked = autoFillHandlers.onBrowserAutofillSelected,
|
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))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import android.os.Parcelable
|
|||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.bitwarden.core.util.isBuildVersionAtLeast
|
import com.bitwarden.core.util.isBuildVersionAtLeast
|
||||||
import com.bitwarden.core.util.persistentListOfNotNull
|
|
||||||
import com.bitwarden.ui.platform.base.BaseViewModel
|
import com.bitwarden.ui.platform.base.BaseViewModel
|
||||||
import com.bitwarden.ui.platform.resource.BitwardenString
|
import com.bitwarden.ui.platform.resource.BitwardenString
|
||||||
import com.bitwarden.ui.util.Text
|
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.SettingsRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
|
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.model.BrowserAutofillSettingsOption
|
||||||
|
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.browser.util.toBrowserAutoFillSettingsOptions
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
@ -303,23 +303,6 @@ enum class AutofillStyle(val label: Text) {
|
|||||||
POPUP(label = BitwardenString.autofill_suggestions_popup.asText()),
|
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.
|
* 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.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.bitwarden.ui.platform.base.util.cardStyle
|
import com.bitwarden.ui.platform.base.util.cardStyle
|
||||||
import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
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.model.CardStyle
|
||||||
import com.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
import com.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
||||||
import com.bitwarden.ui.platform.resource.BitwardenString
|
|
||||||
import com.bitwarden.ui.platform.theme.BitwardenTheme
|
import com.bitwarden.ui.platform.theme.BitwardenTheme
|
||||||
import com.x8bit.bitwarden.data.autofill.model.browser.BrowserPackage
|
import com.x8bit.bitwarden.data.autofill.model.browser.BrowserPackage
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.browser.model.BrowserAutofillSettingsOption
|
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 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
|
* @param onOptionClicked Lambda that is invoked when an option row is clicked and passes back the
|
||||||
* [BrowserPackage] for that option.
|
* [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
|
@Composable
|
||||||
fun BrowserAutofillSettingsCard(
|
fun BrowserAutofillSettingsCard(
|
||||||
options: ImmutableList<BrowserAutofillSettingsOption>,
|
options: ImmutableList<BrowserAutofillSettingsOption>,
|
||||||
onOptionClicked: (BrowserPackage) -> Unit,
|
onOptionClicked: (BrowserPackage) -> Unit,
|
||||||
enabled: Boolean,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
supportingText: String? = null,
|
||||||
) {
|
) {
|
||||||
if (options.isEmpty()) return
|
if (options.isEmpty()) return
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
@ -45,37 +44,31 @@ fun BrowserAutofillSettingsCard(
|
|||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
onOptionClicked(option.browserPackage)
|
onOptionClicked(option.browserPackage)
|
||||||
},
|
},
|
||||||
cardStyle = if (index == 0) {
|
cardStyle = when {
|
||||||
CardStyle.Top(
|
supportingText == null -> options.toListItemCardStyle(index = index)
|
||||||
dividerPadding = 16.dp,
|
index == 0 -> CardStyle.Top(dividerPadding = 16.dp)
|
||||||
)
|
else -> CardStyle.Middle(dividerPadding = 16.dp)
|
||||||
} else {
|
|
||||||
CardStyle.Middle(
|
|
||||||
dividerPadding = 16.dp,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
enabled = enabled,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.standardHorizontalMargin(),
|
.standardHorizontalMargin(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Text(
|
supportingText?.let {
|
||||||
text = stringResource(
|
Text(
|
||||||
id = BitwardenString
|
text = it,
|
||||||
.improves_login_filling_for_supported_websites_on_selected_browsers,
|
style = BitwardenTheme.typography.bodyMedium,
|
||||||
),
|
color = BitwardenTheme.colorScheme.text.secondary,
|
||||||
style = BitwardenTheme.typography.bodyMedium,
|
modifier = Modifier
|
||||||
color = BitwardenTheme.colorScheme.text.secondary,
|
.fillMaxWidth()
|
||||||
modifier = Modifier
|
.standardHorizontalMargin()
|
||||||
.fillMaxWidth()
|
.cardStyle(
|
||||||
.standardHorizontalMargin()
|
cardStyle = CardStyle.Bottom,
|
||||||
.cardStyle(
|
paddingHorizontal = 16.dp,
|
||||||
cardStyle = CardStyle.Bottom,
|
)
|
||||||
paddingHorizontal = 16.dp,
|
.defaultMinSize(minHeight = 48.dp),
|
||||||
)
|
)
|
||||||
.defaultMinSize(minHeight = 48.dp),
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +82,6 @@ private fun ChromeAutofillSettingsCard_preview() {
|
|||||||
BrowserAutofillSettingsOption.ChromeStable(enabled = false),
|
BrowserAutofillSettingsOption.ChromeStable(enabled = false),
|
||||||
BrowserAutofillSettingsOption.ChromeBeta(enabled = true),
|
BrowserAutofillSettingsOption.ChromeBeta(enabled = true),
|
||||||
),
|
),
|
||||||
enabled = true,
|
|
||||||
onOptionClicked = {},
|
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.datasource.disk.model.OnboardingStatus
|
||||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||||
|
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillEnabledManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
|
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
|
||||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
@ -48,6 +49,11 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() {
|
|||||||
every { userStateFlow } returns mutableUserStateFlow
|
every { userStateFlow } returns mutableUserStateFlow
|
||||||
every { setOnboardingStatus(any()) } just runs
|
every { setOnboardingStatus(any()) } just runs
|
||||||
}
|
}
|
||||||
|
private val thirdPartyAutofillEnabledManager: BrowserThirdPartyAutofillEnabledManager = mockk {
|
||||||
|
every {
|
||||||
|
browserThirdPartyAutofillStatus
|
||||||
|
} returns mockk { every { isAnyIsAvailableAndDisabled } returns false }
|
||||||
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup() {
|
fun setup() {
|
||||||
@ -131,10 +137,24 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() {
|
|||||||
fun `handleTurnOnLaterConfirmClick sets onboarding status to FINAL_STEP`() {
|
fun `handleTurnOnLaterConfirmClick sets onboarding status to FINAL_STEP`() {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
viewModel.trySendAction(SetupAutoFillAction.TurnOnLaterConfirmClick)
|
viewModel.trySendAction(SetupAutoFillAction.TurnOnLaterConfirmClick)
|
||||||
verify {
|
verify(exactly = 1) {
|
||||||
authRepository.setOnboardingStatus(
|
authRepository.setOnboardingStatus(status = OnboardingStatus.FINAL_STEP)
|
||||||
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()
|
val viewModel = createViewModel()
|
||||||
viewModel.trySendAction(SetupAutoFillAction.ContinueClick)
|
viewModel.trySendAction(SetupAutoFillAction.ContinueClick)
|
||||||
verify(exactly = 1) {
|
verify(exactly = 1) {
|
||||||
authRepository.setOnboardingStatus(
|
authRepository.setOnboardingStatus(status = OnboardingStatus.FINAL_STEP)
|
||||||
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)
|
firstTimeActionManager.storeShowAutoFillSettingBadge(showBadge = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,8 +188,9 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `handleContinueClick send NavigateBack event when not initial setup and sets first time flag to false`() =
|
fun `handleContinueClick send NavigateBack event when not initial setup and sets first time flag to false`() =
|
||||||
runTest {
|
runTest {
|
||||||
val viewModel =
|
val viewModel = createViewModel(
|
||||||
createViewModel(initialState = DEFAULT_STATE.copy(isInitialSetup = false))
|
initialState = DEFAULT_STATE.copy(isInitialSetup = false),
|
||||||
|
)
|
||||||
viewModel.eventFlow.test {
|
viewModel.eventFlow.test {
|
||||||
viewModel.trySendAction(SetupAutoFillAction.ContinueClick)
|
viewModel.trySendAction(SetupAutoFillAction.ContinueClick)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
@ -168,9 +202,7 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() {
|
|||||||
firstTimeActionManager.storeShowAutoFillSettingBadge(showBadge = false)
|
firstTimeActionManager.storeShowAutoFillSettingBadge(showBadge = false)
|
||||||
}
|
}
|
||||||
verify(exactly = 0) {
|
verify(exactly = 0) {
|
||||||
authRepository.setOnboardingStatus(
|
authRepository.setOnboardingStatus(status = OnboardingStatus.FINAL_STEP)
|
||||||
OnboardingStatus.FINAL_STEP,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,6 +236,7 @@ class SetupAutoFillViewModelTest : BaseViewModelTest() {
|
|||||||
settingsRepository = settingsRepository,
|
settingsRepository = settingsRepository,
|
||||||
authRepository = authRepository,
|
authRepository = authRepository,
|
||||||
firstTimeActionManager = firstTimeActionManager,
|
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.datasource.disk.model.OnboardingStatus
|
||||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||||
|
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillEnabledManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
|
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
|
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
|
||||||
@ -59,6 +60,11 @@ class SetupUnlockViewModelTest : BaseViewModelTest() {
|
|||||||
} returns false
|
} returns false
|
||||||
every { createCipherOrNull(DEFAULT_USER_ID) } returns CIPHER
|
every { createCipherOrNull(DEFAULT_USER_ID) } returns CIPHER
|
||||||
}
|
}
|
||||||
|
private val thirdPartyAutofillEnabledManager: BrowserThirdPartyAutofillEnabledManager = mockk {
|
||||||
|
every {
|
||||||
|
browserThirdPartyAutofillStatus
|
||||||
|
} returns mockk { every { isAnyIsAvailableAndDisabled } returns false }
|
||||||
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup() {
|
fun setup() {
|
||||||
@ -90,10 +96,9 @@ class SetupUnlockViewModelTest : BaseViewModelTest() {
|
|||||||
fun `ContinueClick should call setOnboardingStatus and set to AUTOFILL_SETUP if AutoFill is not enabled`() {
|
fun `ContinueClick should call setOnboardingStatus and set to AUTOFILL_SETUP if AutoFill is not enabled`() {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
viewModel.trySendAction(SetupUnlockAction.ContinueClick)
|
viewModel.trySendAction(SetupUnlockAction.ContinueClick)
|
||||||
verify {
|
verify(exactly = 1) {
|
||||||
authRepository.setOnboardingStatus(
|
thirdPartyAutofillEnabledManager.browserThirdPartyAutofillStatus
|
||||||
status = OnboardingStatus.AUTOFILL_SETUP,
|
authRepository.setOnboardingStatus(status = OnboardingStatus.AUTOFILL_SETUP)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,29 +136,42 @@ class SetupUnlockViewModelTest : BaseViewModelTest() {
|
|||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@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 }
|
mutableAutofillEnabledStateFlow.update { true }
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
viewModel.trySendAction(SetupUnlockAction.ContinueClick)
|
viewModel.trySendAction(SetupUnlockAction.ContinueClick)
|
||||||
verify(exactly = 1) {
|
verify(exactly = 1) {
|
||||||
authRepository.setOnboardingStatus(
|
thirdPartyAutofillEnabledManager.browserThirdPartyAutofillStatus
|
||||||
status = OnboardingStatus.FINAL_STEP,
|
authRepository.setOnboardingStatus(status = OnboardingStatus.FINAL_STEP)
|
||||||
)
|
|
||||||
firstTimeActionManager.storeShowUnlockSettingBadge(showBadge = false)
|
firstTimeActionManager.storeShowUnlockSettingBadge(showBadge = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@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 {
|
runTest {
|
||||||
mutableAutofillEnabledStateFlow.update { true }
|
mutableAutofillEnabledStateFlow.update { true }
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
viewModel.trySendAction(SetupUnlockAction.SetUpLaterClick)
|
viewModel.trySendAction(SetupUnlockAction.SetUpLaterClick)
|
||||||
verify {
|
verify(exactly = 1) {
|
||||||
authRepository.setOnboardingStatus(
|
thirdPartyAutofillEnabledManager.browserThirdPartyAutofillStatus
|
||||||
status = OnboardingStatus.FINAL_STEP,
|
authRepository.setOnboardingStatus(status = OnboardingStatus.FINAL_STEP)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,6 +413,7 @@ class SetupUnlockViewModelTest : BaseViewModelTest() {
|
|||||||
settingsRepository = settingsRepository,
|
settingsRepository = settingsRepository,
|
||||||
biometricsEncryptionManager = biometricsEncryptionManager,
|
biometricsEncryptionManager = biometricsEncryptionManager,
|
||||||
firstTimeActionManager = firstTimeActionManager,
|
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.AutofillSaveItem
|
||||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
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.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.SetupCompleteRoute
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SetupUnlockRoute
|
import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SetupUnlockRoute
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.auth.AuthGraphRoute
|
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:
|
// Make sure navigating to account setup complete works as expected:
|
||||||
rootNavStateFlow.value = RootNavState.OnboardingStepsComplete
|
rootNavStateFlow.value = RootNavState.OnboardingStepsComplete
|
||||||
composeTestRule.runOnIdle {
|
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")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `when the active user has an unlocked vault and they have a OnboardingStatus of FINAL_STEP the nav state should be OnboardingAutoFillSetup`() {
|
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()
|
.performClick()
|
||||||
|
|
||||||
composeTestRule
|
composeTestRule
|
||||||
.onNodeWithText("Use Chrome autofill integration (Beta)")
|
.onNodeWithText("Use Chrome Beta autofill integration")
|
||||||
.performScrollTo()
|
.performScrollTo()
|
||||||
.performClick()
|
.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="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="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_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="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_later">Turn on later</string>
|
||||||
<string name="turn_on_autofill_later">Turn on autofill 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="self_host_server_url">Self-host server URL</string>
|
||||||
<string name="use_brave_autofill_integration">Use Brave autofill integration</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_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="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="show_more">Show more</string>
|
||||||
<string name="no_folder">No folder</string>
|
<string name="no_folder">No folder</string>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user