mirror of
https://github.com/bitwarden/android.git
synced 2024-11-21 17:05:44 +03:00
PM-11174 Action card for import logins flow (#4057)
This commit is contained in:
parent
028242c4be
commit
ba8e3a6c51
50 changed files with 856 additions and 11 deletions
|
@ -306,4 +306,19 @@ interface AuthDiskSource {
|
|||
* if any exists.
|
||||
*/
|
||||
fun getOnboardingStatusFlow(userId: String): Flow<OnboardingStatus?>
|
||||
|
||||
/**
|
||||
* Gets the show import logins flag for the given [userId].
|
||||
*/
|
||||
fun getShowImportLogins(userId: String): Boolean?
|
||||
|
||||
/**
|
||||
* Stores the show import logins flag for the given [userId].
|
||||
*/
|
||||
fun storeShowImportLogins(userId: String, showImportLogins: Boolean?)
|
||||
|
||||
/**
|
||||
* Emits updates that track [getShowImportLogins]. This will replay the last known value,
|
||||
*/
|
||||
fun getShowImportLoginsFlow(userId: String): Flow<Boolean?>
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ private const val SHOULD_TRUST_DEVICE_KEY = "shouldTrustDevice"
|
|||
private const val TDE_LOGIN_COMPLETE = "tdeLoginComplete"
|
||||
private const val USES_KEY_CONNECTOR = "usesKeyConnector"
|
||||
private const val ONBOARDING_STATUS_KEY = "onboardingStatus"
|
||||
private const val SHOW_IMPORT_LOGINS_KEY = "showImportLogins"
|
||||
|
||||
/**
|
||||
* Primary implementation of [AuthDiskSource].
|
||||
|
@ -72,6 +73,7 @@ class AuthDiskSourceImpl(
|
|||
mutableMapOf<String, MutableSharedFlow<AccountTokensJson?>>()
|
||||
private val mutableOnboardingStatusFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<OnboardingStatus?>>()
|
||||
private val mutableShowImportLoginsFlowMap = mutableMapOf<String, MutableSharedFlow<Boolean?>>()
|
||||
private val mutableUserStateFlow = bufferedMutableSharedFlow<UserStateJson?>(replay = 1)
|
||||
|
||||
override var userState: UserStateJson?
|
||||
|
@ -143,9 +145,11 @@ class AuthDiskSourceImpl(
|
|||
storeShouldUseKeyConnector(userId = userId, shouldUseKeyConnector = null)
|
||||
storeIsTdeLoginComplete(userId = userId, isTdeLoginComplete = null)
|
||||
storeAuthenticatorSyncUnlockKey(userId = userId, authenticatorSyncUnlockKey = null)
|
||||
storeShowImportLogins(userId = userId, showImportLogins = null)
|
||||
|
||||
// Do not remove the DeviceKey or PendingAuthRequest on logout, these are persisted
|
||||
// indefinitely unless the TDE flow explicitly removes them.
|
||||
// Do not remove OnboardingStatus we want to keep track of this even after logout.
|
||||
}
|
||||
|
||||
override fun getAuthenticatorSyncUnlockKey(userId: String): String? =
|
||||
|
@ -437,6 +441,22 @@ class AuthDiskSourceImpl(
|
|||
.onSubscription { emit(getOnboardingStatus(userId = userId)) }
|
||||
}
|
||||
|
||||
override fun getShowImportLogins(userId: String): Boolean? {
|
||||
return getBoolean(SHOW_IMPORT_LOGINS_KEY.appendIdentifier(userId))
|
||||
}
|
||||
|
||||
override fun storeShowImportLogins(userId: String, showImportLogins: Boolean?) {
|
||||
putBoolean(
|
||||
key = SHOW_IMPORT_LOGINS_KEY.appendIdentifier(userId),
|
||||
value = showImportLogins,
|
||||
)
|
||||
getMutableShowImportLoginsFlow(userId = userId).tryEmit(showImportLogins)
|
||||
}
|
||||
|
||||
override fun getShowImportLoginsFlow(userId: String): Flow<Boolean?> =
|
||||
getMutableShowImportLoginsFlow(userId)
|
||||
.onSubscription { emit(getShowImportLogins(userId)) }
|
||||
|
||||
private fun generateAndStoreUniqueAppId(): String =
|
||||
UUID
|
||||
.randomUUID()
|
||||
|
@ -480,6 +500,12 @@ class AuthDiskSourceImpl(
|
|||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun getMutableShowImportLoginsFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<Boolean?> = mutableShowImportLoginsFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun migrateAccountTokens() {
|
||||
userState
|
||||
?.accounts
|
||||
|
|
|
@ -393,4 +393,9 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
|
|||
* Update the value of the onboarding status for the user.
|
||||
*/
|
||||
fun setOnboardingStatus(userId: String, status: OnboardingStatus?)
|
||||
|
||||
/**
|
||||
* Update the value of the showImportLogins status for the user.
|
||||
*/
|
||||
fun setShowImportLogins(showImportLogins: Boolean)
|
||||
}
|
||||
|
|
|
@ -76,6 +76,8 @@ import com.x8bit.bitwarden.data.auth.repository.util.SsoCallbackResult
|
|||
import com.x8bit.bitwarden.data.auth.repository.util.WebAuthResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.activeUserIdChangesFlow
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.currentOnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.currentOrDefaultUserFirstTimeState
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.firstTimeStateFlow
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.onboardingStatusChangesFlow
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.policyInformation
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.toRemovedPasswordUserStateJson
|
||||
|
@ -254,6 +256,7 @@ class AuthRepositoryImpl(
|
|||
authDiskSource.userOrganizationsListFlow,
|
||||
authDiskSource.userKeyConnectorStateFlow,
|
||||
authDiskSource.onboardingStatusChangesFlow,
|
||||
authDiskSource.firstTimeStateFlow,
|
||||
vaultRepository.vaultUnlockDataStateFlow,
|
||||
mutableHasPendingAccountAdditionStateFlow,
|
||||
// Ignore the data in the merge, but trigger an update when they emit.
|
||||
|
@ -267,8 +270,9 @@ class AuthRepositoryImpl(
|
|||
val userOrganizationsList = array[2] as List<UserOrganizations>
|
||||
val userIsUsingKeyConnectorList = array[3] as List<UserKeyConnectorState>
|
||||
val onboardingStatus = array[4] as OnboardingStatus?
|
||||
val vaultState = array[5] as List<VaultUnlockData>
|
||||
val hasPendingAccountAddition = array[6] as Boolean
|
||||
val firstTimeState = array[5] as UserState.FirstTimeState
|
||||
val vaultState = array[6] as List<VaultUnlockData>
|
||||
val hasPendingAccountAddition = array[7] as Boolean
|
||||
userStateJson?.toUserState(
|
||||
vaultState = vaultState,
|
||||
userAccountTokens = userAccountTokens,
|
||||
|
@ -279,6 +283,7 @@ class AuthRepositoryImpl(
|
|||
isBiometricsEnabledProvider = ::isBiometricsEnabled,
|
||||
vaultUnlockTypeProvider = ::getVaultUnlockType,
|
||||
isDeviceTrustedProvider = ::isDeviceTrusted,
|
||||
firstTimeState = firstTimeState,
|
||||
)
|
||||
}
|
||||
.filterNot { mutableHasPendingAccountDeletionStateFlow.value }
|
||||
|
@ -298,6 +303,7 @@ class AuthRepositoryImpl(
|
|||
isBiometricsEnabledProvider = ::isBiometricsEnabled,
|
||||
vaultUnlockTypeProvider = ::getVaultUnlockType,
|
||||
isDeviceTrustedProvider = ::isDeviceTrusted,
|
||||
firstTimeState = authDiskSource.currentOrDefaultUserFirstTimeState,
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -1297,6 +1303,11 @@ class AuthRepositoryImpl(
|
|||
authDiskSource.storeOnboardingStatus(userId = userId, onboardingStatus = status)
|
||||
}
|
||||
|
||||
override fun setShowImportLogins(showImportLogins: Boolean) {
|
||||
val userId: String = activeUserId ?: return
|
||||
authDiskSource.storeShowImportLogins(userId = userId, showImportLogins = showImportLogins)
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private suspend fun validatePasswordAgainstPolicy(
|
||||
password: String,
|
||||
|
|
|
@ -29,6 +29,9 @@ data class UserState(
|
|||
val activeAccount: Account
|
||||
get() = accounts.first { it.userId == activeUserId }
|
||||
|
||||
val activeUserFirstTimeState: FirstTimeState
|
||||
get() = activeAccount.firstTimeState
|
||||
|
||||
/**
|
||||
* Basic account information about a given user.
|
||||
*
|
||||
|
@ -71,6 +74,7 @@ data class UserState(
|
|||
val vaultUnlockType: VaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
||||
val isUsingKeyConnector: Boolean,
|
||||
val onboardingStatus: OnboardingStatus,
|
||||
val firstTimeState: FirstTimeState,
|
||||
) {
|
||||
/**
|
||||
* Indicates that the user does or does not have a means to manually unlock the vault.
|
||||
|
@ -91,4 +95,21 @@ data class UserState(
|
|||
val hasLoginApprovingDevice: Boolean,
|
||||
val hasResetPasswordPermission: Boolean,
|
||||
)
|
||||
|
||||
/**
|
||||
* Model to encapsulate different states for a user's first time experience.
|
||||
*/
|
||||
data class FirstTimeState(
|
||||
val showImportLoginsCard: Boolean,
|
||||
) {
|
||||
/**
|
||||
* Constructs a [FirstTimeState] accepting nullable values. If a value is null, the default
|
||||
* is used.
|
||||
*/
|
||||
constructor(
|
||||
showImportLoginsCoachMarker: Boolean?,
|
||||
) : this(
|
||||
showImportLoginsCard = showImportLoginsCoachMarker ?: true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.UserAccountTokens
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserKeyConnectorState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserOrganizations
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserSwitchingData
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
@ -183,8 +184,50 @@ val AuthDiskSource.onboardingStatusChangesFlow: Flow<OnboardingStatus?>
|
|||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
/**
|
||||
* Returns the current [OnboardingStatus] of the active user.
|
||||
*/
|
||||
val AuthDiskSource.currentOnboardingStatus: OnboardingStatus?
|
||||
get() = this
|
||||
.userState
|
||||
?.activeUserId
|
||||
?.let { this.getOnboardingStatus(userId = it) }
|
||||
|
||||
/**
|
||||
* Returns a [Flow] that emits every time the active user's first time state is changed.
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val AuthDiskSource.firstTimeStateFlow: Flow<UserState.FirstTimeState>
|
||||
get() = activeUserIdChangesFlow
|
||||
.flatMapLatest { activeUserId ->
|
||||
combine(
|
||||
listOf(
|
||||
activeUserId
|
||||
?.let {
|
||||
getShowImportLoginsFlow(it)
|
||||
}
|
||||
?: flowOf(null),
|
||||
),
|
||||
) {
|
||||
UserState.FirstTimeState(
|
||||
showImportLoginsCoachMarker = it[0],
|
||||
)
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
/**
|
||||
* Get the current [UserState.FirstTimeState] of the active user if available, otherwise return
|
||||
* a default configuration.
|
||||
*/
|
||||
val AuthDiskSource.currentOrDefaultUserFirstTimeState
|
||||
get() = userState
|
||||
?.activeUserId
|
||||
?.let {
|
||||
UserState.FirstTimeState(
|
||||
showImportLoginsCoachMarker = getShowImportLogins(it),
|
||||
)
|
||||
}
|
||||
?: UserState.FirstTimeState(
|
||||
showImportLoginsCoachMarker = true,
|
||||
)
|
||||
|
|
|
@ -111,6 +111,7 @@ fun UserStateJson.toUserState(
|
|||
userIsUsingKeyConnectorList: List<UserKeyConnectorState>,
|
||||
hasPendingAccountAddition: Boolean,
|
||||
onboardingStatus: OnboardingStatus?,
|
||||
firstTimeState: UserState.FirstTimeState,
|
||||
isBiometricsEnabledProvider: (userId: String) -> Boolean,
|
||||
vaultUnlockTypeProvider: (userId: String) -> VaultUnlockType,
|
||||
isDeviceTrustedProvider: (userId: String) -> Boolean,
|
||||
|
@ -180,6 +181,7 @@ fun UserStateJson.toUserState(
|
|||
// If the user exists with no onboarding status we can assume they have been
|
||||
// using the app prior to the release of the onboarding flow.
|
||||
onboardingStatus = onboardingStatus ?: OnboardingStatus.COMPLETE,
|
||||
firstTimeState = firstTimeState,
|
||||
)
|
||||
},
|
||||
hasPendingAccountAddition = hasPendingAccountAddition,
|
||||
|
|
|
@ -167,6 +167,8 @@ class SettingsDiskSourceImpl(
|
|||
// The following are intentionally not cleared so they can be
|
||||
// restored after logging out and back in:
|
||||
// - screen capture allowed
|
||||
// - show autofill setting badge
|
||||
// - show unlock setting badge
|
||||
}
|
||||
|
||||
override fun getAccountBiometricIntegrityValidity(
|
||||
|
|
|
@ -30,6 +30,7 @@ sealed class FlagKey<out T : Any> {
|
|||
EmailVerification,
|
||||
OnboardingFlow,
|
||||
OnboardingCarousel,
|
||||
ImportLoginsFlow,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +71,15 @@ sealed class FlagKey<out T : Any> {
|
|||
override val isRemotelyConfigured: Boolean = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Data object holding the feature flag key for the import logins feature.
|
||||
*/
|
||||
data object ImportLoginsFlow : FlagKey<Boolean>() {
|
||||
override val keyName: String = "import-logins-flow"
|
||||
override val defaultValue: Boolean = false
|
||||
override val isRemotelyConfigured: Boolean = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Data object holding the key for a [Boolean] flag to be used in tests.
|
||||
*/
|
||||
|
|
|
@ -47,6 +47,7 @@ fun BitwardenActionCard(
|
|||
onActionClick: () -> Unit,
|
||||
onDismissClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
cardSubtitle: String? = null,
|
||||
leadingContent: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
Card(
|
||||
|
@ -70,7 +71,6 @@ fun BitwardenActionCard(
|
|||
Text(
|
||||
text = cardTitle,
|
||||
style = BitwardenTheme.typography.titleMedium,
|
||||
color = BitwardenTheme.colorScheme.text.primary,
|
||||
)
|
||||
Spacer(Modifier.weight(1f))
|
||||
BitwardenStandardIconButton(
|
||||
|
@ -80,6 +80,13 @@ fun BitwardenActionCard(
|
|||
modifier = Modifier.offset(x = 8.dp),
|
||||
)
|
||||
}
|
||||
cardSubtitle?.let {
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = it,
|
||||
style = BitwardenTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(16.dp))
|
||||
BitwardenFilledButton(
|
||||
label = actionText,
|
||||
|
@ -128,3 +135,23 @@ private fun BitwardenActionCardWithLeadingContent_preview() {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun BitwardenActionCardWithSubtitle_preview() {
|
||||
BitwardenTheme {
|
||||
BitwardenActionCard(
|
||||
cardTitle = "Title",
|
||||
cardSubtitle = "Subtitle",
|
||||
actionText = "Action",
|
||||
onActionClick = {},
|
||||
onDismissClick = {},
|
||||
leadingContent = {
|
||||
NotificationBadge(
|
||||
notificationCount = 1,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ fun <T : Any> FlagKey<T>.ListItemContent(
|
|||
FlagKey.EmailVerification,
|
||||
FlagKey.OnboardingCarousel,
|
||||
FlagKey.OnboardingFlow,
|
||||
FlagKey.ImportLoginsFlow,
|
||||
-> BooleanFlagItem(
|
||||
label = flagKey.getDisplayLabel(),
|
||||
key = flagKey as FlagKey<Boolean>,
|
||||
|
@ -67,4 +68,5 @@ private fun <T : Any> FlagKey<T>.getDisplayLabel(): String = when (this) {
|
|||
FlagKey.EmailVerification -> stringResource(R.string.email_verification)
|
||||
FlagKey.OnboardingCarousel -> stringResource(R.string.onboarding_carousel)
|
||||
FlagKey.OnboardingFlow -> stringResource(R.string.onboarding_flow)
|
||||
FlagKey.ImportLoginsFlow -> stringResource(R.string.import_logins_flow)
|
||||
}
|
||||
|
|
|
@ -33,12 +33,15 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.x8bit.bitwarden.ui.platform.components.account.BitwardenAccountActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.account.BitwardenAccountSwitcher
|
||||
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenMediumTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.appbar.action.BitwardenOverflowActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.appbar.action.BitwardenSearchActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.appbar.action.OverflowMenuItemData
|
||||
import com.x8bit.bitwarden.ui.platform.components.card.BitwardenActionCard
|
||||
import com.x8bit.bitwarden.ui.platform.components.card.actionCardExitAnimation
|
||||
import com.x8bit.bitwarden.ui.platform.components.content.BitwardenErrorContent
|
||||
import com.x8bit.bitwarden.ui.platform.components.content.BitwardenLoadingContent
|
||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BasicDialogState
|
||||
|
@ -304,11 +307,32 @@ private fun VaultScreenScaffold(
|
|||
modifier = innerModifier,
|
||||
)
|
||||
|
||||
is VaultState.ViewState.NoItems -> VaultNoItems(
|
||||
modifier = innerModifier,
|
||||
policyDisablesSend = false,
|
||||
addItemClickAction = vaultHandlers.addItemClickAction,
|
||||
)
|
||||
is VaultState.ViewState.NoItems -> {
|
||||
AnimatedVisibility(
|
||||
visible = state.showImportActionCard,
|
||||
exit = actionCardExitAnimation(),
|
||||
label = "VaultNoItemsActionCard",
|
||||
) {
|
||||
BitwardenActionCard(
|
||||
cardTitle = stringResource(R.string.import_saved_logins),
|
||||
cardSubtitle = stringResource(
|
||||
R.string.use_a_computer_to_import_logins,
|
||||
),
|
||||
actionText = stringResource(R.string.get_started),
|
||||
onActionClick = vaultHandlers.importActionCardClick,
|
||||
onDismissClick = vaultHandlers.dismissImportActionCard,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin()
|
||||
.padding(top = 12.dp),
|
||||
)
|
||||
}
|
||||
VaultNoItems(
|
||||
modifier = innerModifier,
|
||||
policyDisablesSend = false,
|
||||
addItemClickAction = vaultHandlers.addItemClickAction,
|
||||
)
|
||||
}
|
||||
|
||||
is VaultState.ViewState.Error -> BitwardenErrorContent(
|
||||
message = viewState.message(),
|
||||
|
|
|
@ -9,9 +9,11 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
|
@ -45,6 +47,7 @@ import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
|||
import com.x8bit.bitwarden.ui.vault.util.shortName
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
@ -67,6 +70,7 @@ class VaultViewModel @Inject constructor(
|
|||
private val policyManager: PolicyManager,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val vaultRepository: VaultRepository,
|
||||
private val featureFlagManager: FeatureFlagManager,
|
||||
) : BaseViewModel<VaultState, VaultEvent, VaultAction>(
|
||||
initialState = run {
|
||||
val userState = requireNotNull(authRepository.userStateFlow.value)
|
||||
|
@ -92,6 +96,7 @@ class VaultViewModel @Inject constructor(
|
|||
hasMasterPassword = userState.activeAccount.hasMasterPassword,
|
||||
hideNotificationsDialog = isBuildVersionBelow(Build.VERSION_CODES.TIRAMISU) || isFdroid,
|
||||
isRefreshing = false,
|
||||
showImportActionCard = false,
|
||||
)
|
||||
},
|
||||
) {
|
||||
|
@ -126,9 +131,15 @@ class VaultViewModel @Inject constructor(
|
|||
|
||||
authRepository
|
||||
.userStateFlow
|
||||
.onEach {
|
||||
sendAction(VaultAction.Internal.UserStateUpdateReceive(userState = it))
|
||||
.combine(
|
||||
featureFlagManager.getFeatureFlagFlow(FlagKey.ImportLoginsFlow),
|
||||
) { userState, importLoginsEnabled ->
|
||||
VaultAction.Internal.UserStateUpdateReceive(
|
||||
userState = userState,
|
||||
importLoginsFlowEnabled = importLoginsEnabled,
|
||||
)
|
||||
}
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
|
@ -163,9 +174,20 @@ class VaultViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
is VaultAction.Internal -> handleInternalAction(action)
|
||||
VaultAction.DismissImportActionCard -> handleDismissImportActionCard()
|
||||
VaultAction.ImportActionCardClick -> handleImportActionCardClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleImportActionCardClick() {
|
||||
dismissImportLoginCard()
|
||||
// TODO: PM-11179 - navigate to import logins screen
|
||||
}
|
||||
|
||||
private fun handleDismissImportActionCard() {
|
||||
dismissImportLoginCard()
|
||||
}
|
||||
|
||||
private fun handleIconLoadingSettingReceive(
|
||||
action: VaultAction.Internal.IconLoadingSettingReceive,
|
||||
) {
|
||||
|
@ -459,6 +481,7 @@ class VaultViewModel @Inject constructor(
|
|||
// Leave the current data alone if there is no UserState; we are in the process of logging
|
||||
// out.
|
||||
val userState = action.userState ?: return
|
||||
val firstTimeState = userState.activeUserFirstTimeState
|
||||
|
||||
// Avoid updating the UI if we are actively switching users to avoid changes while
|
||||
// navigating.
|
||||
|
@ -470,6 +493,8 @@ class VaultViewModel @Inject constructor(
|
|||
.any(),
|
||||
)
|
||||
val appBarTitle = vaultFilterData.toAppBarTitle()
|
||||
val shouldShowImportActionCard = action.importLoginsFlowEnabled &&
|
||||
firstTimeState.showImportLoginsCard
|
||||
mutableStateFlow.update {
|
||||
val accountSummaries = userState.toAccountSummaries()
|
||||
val activeAccountSummary = userState.toActiveAccountSummary()
|
||||
|
@ -480,6 +505,7 @@ class VaultViewModel @Inject constructor(
|
|||
accountSummaries = accountSummaries,
|
||||
vaultFilterData = vaultFilterData,
|
||||
isPremium = userState.activeAccount.isPremium,
|
||||
showImportActionCard = shouldShowImportActionCard,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -605,6 +631,11 @@ class VaultViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
//endregion VaultAction Handlers
|
||||
|
||||
private fun dismissImportLoginCard() {
|
||||
if (!state.showImportActionCard) return
|
||||
authRepository.setShowImportLogins(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -636,6 +667,7 @@ data class VaultState(
|
|||
val isIconLoadingDisabled: Boolean,
|
||||
val hideNotificationsDialog: Boolean,
|
||||
val isRefreshing: Boolean,
|
||||
val showImportActionCard: Boolean,
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
|
@ -1110,6 +1142,16 @@ sealed class VaultAction {
|
|||
*/
|
||||
data object TryAgainClick : VaultAction()
|
||||
|
||||
/**
|
||||
* The user has dismissed the import action card.
|
||||
*/
|
||||
data object DismissImportActionCard : VaultAction()
|
||||
|
||||
/**
|
||||
* The user has clicked the import action card.
|
||||
*/
|
||||
data object ImportActionCardClick : VaultAction()
|
||||
|
||||
/**
|
||||
* User clicked an overflow action.
|
||||
*/
|
||||
|
@ -1155,6 +1197,7 @@ sealed class VaultAction {
|
|||
*/
|
||||
data class UserStateUpdateReceive(
|
||||
val userState: UserState?,
|
||||
val importLoginsFlowEnabled: Boolean,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
|
|
|
@ -34,6 +34,8 @@ data class VaultHandlers(
|
|||
val dialogDismiss: () -> Unit,
|
||||
val overflowOptionClick: (ListingItemOverflowAction.VaultAction) -> Unit,
|
||||
val masterPasswordRepromptSubmit: (ListingItemOverflowAction.VaultAction, String) -> Unit,
|
||||
val dismissImportActionCard: () -> Unit,
|
||||
val importActionCardClick: () -> Unit,
|
||||
) {
|
||||
@Suppress("UndocumentedPublicClass")
|
||||
companion object {
|
||||
|
@ -89,6 +91,12 @@ data class VaultHandlers(
|
|||
),
|
||||
)
|
||||
},
|
||||
dismissImportActionCard = {
|
||||
viewModel.trySendAction(VaultAction.DismissImportActionCard)
|
||||
},
|
||||
importActionCardClick = {
|
||||
viewModel.trySendAction(VaultAction.ImportActionCardClick)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1018,4 +1018,6 @@ Do you want to switch to this account?</string>
|
|||
<string name="new_login">New login</string>
|
||||
<string name="share_files_and_data_securely_with_anyone_on_any_platform">Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.</string>
|
||||
<string name="send_sensitive_information_safely">Send sensitive information, safely</string>
|
||||
<string name="import_saved_logins">Import saved logins</string>
|
||||
<string name="use_a_computer_to_import_logins">Use a computer to import logins from an existing password manager</string>
|
||||
</resources>
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
<string name="email_verification">Email Verification</string>
|
||||
<string name="onboarding_carousel">Onboarding Carousel</string>
|
||||
<string name="onboarding_flow">Onboarding Flow</string>
|
||||
<string name="import_logins_flow">Import Logins Flow</string>
|
||||
<string name="feature_flags">Feature Flags:</string>
|
||||
<string name="debug_menu">Debug Menu</string>
|
||||
<string name="reset_values">Reset values</string>
|
||||
|
|
|
@ -1079,6 +1079,10 @@ private val DEFAULT_STATE: MainState = MainState(
|
|||
isScreenCaptureAllowed = true,
|
||||
)
|
||||
|
||||
private val DEFAULT_FIRST_TIME_STATE = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
)
|
||||
|
||||
private const val SPECIAL_CIRCUMSTANCE_KEY: String = "special-circumstance"
|
||||
private val DEFAULT_ACCOUNT = UserState.Account(
|
||||
userId = "activeUserId",
|
||||
|
@ -1097,6 +1101,7 @@ private val DEFAULT_ACCOUNT = UserState.Account(
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = DEFAULT_FIRST_TIME_STATE,
|
||||
)
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
|
|
|
@ -302,12 +302,21 @@ class AuthDiskSourceTest {
|
|||
authenticatorSyncUnlockKey = "authenticatorSyncUnlockKey",
|
||||
)
|
||||
|
||||
authDiskSource.storeOnboardingStatus(
|
||||
userId = userId,
|
||||
onboardingStatus = OnboardingStatus.AUTOFILL_SETUP,
|
||||
)
|
||||
|
||||
authDiskSource.clearData(userId = userId)
|
||||
|
||||
// We do not clear these even when you call clear storage
|
||||
assertEquals(pendingAuthRequestJson, authDiskSource.getPendingAuthRequest(userId = userId))
|
||||
assertEquals(deviceKey, authDiskSource.getDeviceKey(userId = userId))
|
||||
assertEquals(shouldTrustDevice, authDiskSource.getShouldTrustDevice(userId = userId))
|
||||
assertEquals(
|
||||
OnboardingStatus.AUTOFILL_SETUP,
|
||||
authDiskSource.getOnboardingStatus(userId = userId),
|
||||
)
|
||||
|
||||
// These should be cleared
|
||||
assertNull(authDiskSource.getUserBiometricUnlockKey(userId = userId))
|
||||
|
@ -325,6 +334,7 @@ class AuthDiskSourceTest {
|
|||
assertNull(authDiskSource.getShouldUseKeyConnector(userId = userId))
|
||||
assertNull(authDiskSource.getIsTdeLoginComplete(userId = userId))
|
||||
assertNull(authDiskSource.getAuthenticatorSyncUnlockKey(userId = userId))
|
||||
assertNull(authDiskSource.getShowImportLogins(userId = userId))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1088,7 +1098,7 @@ class AuthDiskSourceTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `getOnboardingStatus should update SharedPreferences`() {
|
||||
fun `getOnboardingStatus should pull from SharedPreferences`() {
|
||||
val onboardingStatusBaseKey = "bwPreferencesStorage:onboardingStatus"
|
||||
val mockUserId = "mockUserId"
|
||||
val expectedStatus = OnboardingStatus.AUTOFILL_SETUP
|
||||
|
@ -1154,6 +1164,43 @@ class AuthDiskSourceTest {
|
|||
// Retrieving the key from repository should give same byte array despite String conversion:
|
||||
assertTrue(authDiskSource.authenticatorSyncSymmetricKey.contentEquals(symmetricKey))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getShowImportLogins should pull from SharedPreferences`() {
|
||||
val showImportLoginsBaseKey = "bwPreferencesStorage:showImportLogins"
|
||||
val mockUserId = "mockUserId"
|
||||
fakeSharedPreferences.edit {
|
||||
putBoolean("${showImportLoginsBaseKey}_$mockUserId", true)
|
||||
}
|
||||
val actual = authDiskSource.getShowImportLogins(userId = mockUserId) ?: false
|
||||
assertTrue(actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storeShowImportLogins should update SharedPreferences`() {
|
||||
val showImportLoginsBaseKey = "bwPreferencesStorage:showImportLogins"
|
||||
val mockUserId = "mockUserId"
|
||||
authDiskSource.storeShowImportLogins(
|
||||
userId = mockUserId,
|
||||
showImportLogins = true,
|
||||
)
|
||||
val actual = fakeSharedPreferences.getBoolean(
|
||||
"${showImportLoginsBaseKey}_$mockUserId",
|
||||
false,
|
||||
)
|
||||
assertTrue(actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getShowImportLoginsFlow should react to changes from storeShowImportLogins`() = runTest {
|
||||
val mockUserId = "mockUserId"
|
||||
authDiskSource.getShowImportLoginsFlow(userId = mockUserId).test {
|
||||
// The initial values of the Flow and the property are in sync
|
||||
assertNull(awaitItem())
|
||||
authDiskSource.storeShowImportLogins(userId = mockUserId, true)
|
||||
assertTrue(awaitItem() ?: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val USER_STATE_JSON = """
|
||||
|
|
|
@ -29,6 +29,7 @@ class FakeAuthDiskSource : AuthDiskSource {
|
|||
mutableMapOf<String, MutableSharedFlow<List<SyncResponseJson.Policy>?>>()
|
||||
private val mutableAccountTokensFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<AccountTokensJson?>>()
|
||||
private val mutableShowImportLoginsFlowMap = mutableMapOf<String, MutableSharedFlow<Boolean?>>()
|
||||
|
||||
private val mutableOnboardingStatusFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<OnboardingStatus?>>()
|
||||
|
@ -55,6 +56,7 @@ class FakeAuthDiskSource : AuthDiskSource {
|
|||
private val storedAuthenticationSyncKeys = mutableMapOf<String, String?>()
|
||||
private val storedPolicies = mutableMapOf<String, List<SyncResponseJson.Policy>?>()
|
||||
private val storedOnboardingStatus = mutableMapOf<String, OnboardingStatus?>()
|
||||
private val storedShowImportLogins = mutableMapOf<String, Boolean?>()
|
||||
|
||||
override var userState: UserStateJson? = null
|
||||
set(value) {
|
||||
|
@ -269,6 +271,18 @@ class FakeAuthDiskSource : AuthDiskSource {
|
|||
getMutableOnboardingStatusFlow(userId = userId)
|
||||
.onSubscription { emit(getOnboardingStatus(userId)) }
|
||||
|
||||
override fun getShowImportLogins(userId: String): Boolean? =
|
||||
storedShowImportLogins[userId]
|
||||
|
||||
override fun storeShowImportLogins(userId: String, showImportLogins: Boolean?) {
|
||||
storedShowImportLogins[userId] = showImportLogins
|
||||
getMutableShowImportLoginsFlow(userId = userId).tryEmit(showImportLogins)
|
||||
}
|
||||
|
||||
override fun getShowImportLoginsFlow(userId: String): Flow<Boolean?> =
|
||||
getMutableShowImportLoginsFlow(userId)
|
||||
.onSubscription { emit(getShowImportLogins(userId)) }
|
||||
|
||||
/**
|
||||
* Assert the the [isTdeLoginComplete] was stored successfully using the [userId].
|
||||
*/
|
||||
|
@ -449,5 +463,11 @@ class FakeAuthDiskSource : AuthDiskSource {
|
|||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun getMutableShowImportLoginsFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<Boolean?> = mutableShowImportLoginsFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
//endregion Private helper functions
|
||||
}
|
||||
|
|
|
@ -82,6 +82,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.SetPasswordResult
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserKeyConnectorState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserOrganizations
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
|
||||
|
@ -344,6 +345,7 @@ class AuthRepositoryTest {
|
|||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { false },
|
||||
firstTimeState = FIRST_TIME_STATE,
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
|
@ -370,6 +372,7 @@ class AuthRepositoryTest {
|
|||
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = null,
|
||||
firstTimeState = FIRST_TIME_STATE,
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
|
@ -387,6 +390,7 @@ class AuthRepositoryTest {
|
|||
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = null,
|
||||
firstTimeState = FIRST_TIME_STATE,
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
|
@ -416,6 +420,7 @@ class AuthRepositoryTest {
|
|||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = null,
|
||||
firstTimeState = FIRST_TIME_STATE,
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
|
@ -645,6 +650,7 @@ class AuthRepositoryTest {
|
|||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = null,
|
||||
firstTimeState = FIRST_TIME_STATE,
|
||||
)
|
||||
val finalUserState = SINGLE_USER_STATE_2.toUserState(
|
||||
vaultState = VAULT_UNLOCK_DATA,
|
||||
|
@ -656,6 +662,7 @@ class AuthRepositoryTest {
|
|||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = null,
|
||||
firstTimeState = FIRST_TIME_STATE,
|
||||
)
|
||||
val kdf = SINGLE_USER_STATE_1.activeAccount.profile.toSdkParams()
|
||||
coEvery {
|
||||
|
@ -5363,6 +5370,7 @@ class AuthRepositoryTest {
|
|||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = null,
|
||||
firstTimeState = FIRST_TIME_STATE,
|
||||
)
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
assertEquals(
|
||||
|
@ -5397,6 +5405,7 @@ class AuthRepositoryTest {
|
|||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = null,
|
||||
firstTimeState = FIRST_TIME_STATE,
|
||||
)
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
assertEquals(
|
||||
|
@ -5429,6 +5438,7 @@ class AuthRepositoryTest {
|
|||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = null,
|
||||
firstTimeState = FIRST_TIME_STATE,
|
||||
)
|
||||
fakeAuthDiskSource.userState = MULTI_USER_STATE
|
||||
assertEquals(
|
||||
|
@ -6304,6 +6314,13 @@ class AuthRepositoryTest {
|
|||
assertNull(fakeAuthDiskSource.getOnboardingStatus(USER_ID_1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setShowImportLogins should save the showImportLogins to disk`() {
|
||||
fakeAuthDiskSource.userState = MULTI_USER_STATE
|
||||
repository.setShowImportLogins(showImportLogins = true)
|
||||
assertEquals(true, fakeAuthDiskSource.getShowImportLogins(USER_ID_1))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val UNIQUE_APP_ID = "testUniqueAppId"
|
||||
private const val NAME = "Example Name"
|
||||
|
@ -6494,5 +6511,9 @@ class AuthRepositoryTest {
|
|||
status = VaultUnlockData.Status.UNLOCKED,
|
||||
),
|
||||
)
|
||||
|
||||
private val FIRST_TIME_STATE = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.Organization
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.UserAccountTokens
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserKeyConnectorState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserOrganizations
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserSwitchingData
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.OrganizationType
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockOrganization
|
||||
|
@ -506,6 +507,48 @@ class AuthDiskSourceExtensionsTest {
|
|||
authDiskSource.currentOnboardingStatus,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `firstTimeStateFlow should emit changes when items in the first time state change`() =
|
||||
runTest {
|
||||
authDiskSource.firstTimeStateFlow.test {
|
||||
authDiskSource.userState = MOCK_USER_STATE
|
||||
assertEquals(
|
||||
UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
authDiskSource.storeShowImportLogins(MOCK_USER_ID, false)
|
||||
assertEquals(
|
||||
UserState.FirstTimeState(
|
||||
showImportLoginsCard = false,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `currentOrDefaultUserFirstTimeState should return the current first time state or a default state`() {
|
||||
authDiskSource.userState = MOCK_USER_STATE
|
||||
// Assert default state when no values set
|
||||
assertEquals(
|
||||
UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
authDiskSource.currentOrDefaultUserFirstTimeState,
|
||||
)
|
||||
authDiskSource.storeShowImportLogins(MOCK_USER_ID, false)
|
||||
|
||||
assertEquals(
|
||||
UserState.FirstTimeState(
|
||||
showImportLoginsCard = false,
|
||||
),
|
||||
authDiskSource.currentOrDefaultUserFirstTimeState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val MOCK_USER_ID: String = "mockId-1"
|
||||
|
|
|
@ -358,6 +358,7 @@ class UserStateJsonExtensionsTest {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.NOT_STARTED,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -427,6 +428,7 @@ class UserStateJsonExtensionsTest {
|
|||
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = OnboardingStatus.NOT_STARTED,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -464,6 +466,7 @@ class UserStateJsonExtensionsTest {
|
|||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.NOT_STARTED,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
|
@ -529,6 +532,7 @@ class UserStateJsonExtensionsTest {
|
|||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { false },
|
||||
onboardingStatus = OnboardingStatus.NOT_STARTED,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -572,6 +576,7 @@ class UserStateJsonExtensionsTest {
|
|||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = true,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
|
@ -640,6 +645,7 @@ class UserStateJsonExtensionsTest {
|
|||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { true },
|
||||
onboardingStatus = null,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -683,6 +689,7 @@ class UserStateJsonExtensionsTest {
|
|||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = true,
|
||||
onboardingStatus = OnboardingStatus.AUTOFILL_SETUP,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
|
@ -751,6 +758,7 @@ class UserStateJsonExtensionsTest {
|
|||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { true },
|
||||
onboardingStatus = OnboardingStatus.AUTOFILL_SETUP,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -794,6 +802,7 @@ class UserStateJsonExtensionsTest {
|
|||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = true,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
|
@ -862,6 +871,7 @@ class UserStateJsonExtensionsTest {
|
|||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { true },
|
||||
onboardingStatus = null,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -909,6 +919,7 @@ class UserStateJsonExtensionsTest {
|
|||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = true,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
|
@ -977,6 +988,7 @@ class UserStateJsonExtensionsTest {
|
|||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { true },
|
||||
onboardingStatus = null,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -1005,6 +1017,7 @@ class UserStateJsonExtensionsTest {
|
|||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
|
@ -1053,6 +1066,7 @@ class UserStateJsonExtensionsTest {
|
|||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { true },
|
||||
onboardingStatus = null,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -1081,6 +1095,7 @@ class UserStateJsonExtensionsTest {
|
|||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = true,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
|
@ -1131,6 +1146,7 @@ class UserStateJsonExtensionsTest {
|
|||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { true },
|
||||
onboardingStatus = null,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -1175,6 +1191,7 @@ class UserStateJsonExtensionsTest {
|
|||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = true,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
|
@ -1245,6 +1262,124 @@ class UserStateJsonExtensionsTest {
|
|||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { true },
|
||||
onboardingStatus = null,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toUserState should set the correct first time state values result`() {
|
||||
assertEquals(
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "activeName",
|
||||
email = "activeEmail",
|
||||
// This value is calculated from the userId
|
||||
avatarColorHex = "#ffecbc49",
|
||||
environment = Environment.Eu,
|
||||
isPremium = true,
|
||||
isLoggedIn = false,
|
||||
isVaultUnlocked = false,
|
||||
needsPasswordReset = false,
|
||||
organizations = listOf(
|
||||
Organization(
|
||||
id = "organizationId",
|
||||
name = "organizationName",
|
||||
shouldManageResetPassword = false,
|
||||
shouldUseKeyConnector = false,
|
||||
role = OrganizationType.ADMIN,
|
||||
),
|
||||
),
|
||||
isBiometricsEnabled = false,
|
||||
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
||||
needsMasterPassword = true,
|
||||
trustedDevice = UserState.TrustedDevice(
|
||||
isDeviceTrusted = true,
|
||||
hasAdminApproval = false,
|
||||
hasLoginApprovingDevice = true,
|
||||
hasResetPasswordPermission = false,
|
||||
),
|
||||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = true,
|
||||
onboardingStatus = OnboardingStatus.AUTOFILL_SETUP,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = false,
|
||||
),
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
),
|
||||
UserStateJson(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = mapOf(
|
||||
"activeUserId" to AccountJson(
|
||||
profile = mockk {
|
||||
every { userId } returns "activeUserId"
|
||||
every { name } returns "activeName"
|
||||
every { email } returns "activeEmail"
|
||||
every { avatarColorHex } returns null
|
||||
every { hasPremium } returns true
|
||||
every { forcePasswordResetReason } returns null
|
||||
every { userDecryptionOptions } returns UserDecryptionOptionsJson(
|
||||
hasMasterPassword = false,
|
||||
trustedDeviceUserDecryptionOptions = TrustedDeviceUserDecryptionOptionsJson(
|
||||
encryptedPrivateKey = null,
|
||||
encryptedUserKey = null,
|
||||
hasAdminApproval = false,
|
||||
hasLoginApprovingDevice = true,
|
||||
hasManageResetPasswordPermission = false,
|
||||
),
|
||||
keyConnectorUserDecryptionOptions = null,
|
||||
)
|
||||
},
|
||||
tokens = null,
|
||||
settings = AccountJson.Settings(
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_EU,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toUserState(
|
||||
vaultState = emptyList(),
|
||||
userAccountTokens = listOf(
|
||||
UserAccountTokens(
|
||||
userId = "activeUserId",
|
||||
accessToken = null,
|
||||
refreshToken = null,
|
||||
),
|
||||
),
|
||||
userOrganizationsList = listOf(
|
||||
UserOrganizations(
|
||||
userId = "activeUserId",
|
||||
organizations = listOf(
|
||||
Organization(
|
||||
id = "organizationId",
|
||||
name = "organizationName",
|
||||
shouldManageResetPassword = false,
|
||||
shouldUseKeyConnector = false,
|
||||
role = OrganizationType.ADMIN,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
userIsUsingKeyConnectorList = listOf(
|
||||
UserKeyConnectorState(
|
||||
userId = "activeUserId",
|
||||
isUsingKeyConnector = true,
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
isBiometricsEnabledProvider = { false },
|
||||
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
|
||||
isDeviceTrustedProvider = { true },
|
||||
onboardingStatus = OnboardingStatus.AUTOFILL_SETUP,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -537,6 +537,7 @@ private fun createMockAccounts(number: Int): List<UserState.Account> {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -143,10 +143,15 @@ class SettingsDiskSourceTest {
|
|||
value = true,
|
||||
)
|
||||
|
||||
settingsDiskSource.storeShowUnlockSettingBadge(userId = userId, showBadge = true)
|
||||
settingsDiskSource.storeShowAutoFillSettingBadge(userId = userId, showBadge = true)
|
||||
|
||||
settingsDiskSource.clearData(userId = userId)
|
||||
|
||||
// We do not clear these even when you call clear storage
|
||||
assertEquals(true, settingsDiskSource.getScreenCaptureAllowed(userId = userId))
|
||||
assertTrue(settingsDiskSource.getShowUnlockSettingBadge(userId = userId) ?: false)
|
||||
assertTrue(settingsDiskSource.getShowAutoFillSettingBadge(userId = userId) ?: false)
|
||||
|
||||
// These should be cleared
|
||||
assertNull(settingsDiskSource.getVaultTimeoutInMinutes(userId = userId))
|
||||
|
|
|
@ -10,4 +10,24 @@ class FlagKeyTest {
|
|||
fun `AuthenticatorSync default value should be false`() {
|
||||
assertFalse(FlagKey.AuthenticatorSync.defaultValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `EmailVerification default value should be false`() {
|
||||
assertFalse(FlagKey.EmailVerification.defaultValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `OnboardingCarousel default value should be false`() {
|
||||
assertFalse(FlagKey.OnboardingCarousel.defaultValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `OnboardingFlow default value should be false`() {
|
||||
assertFalse(FlagKey.OnboardingFlow.defaultValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ImportLoginsFlow default value should be false`() {
|
||||
assertFalse(FlagKey.ImportLoginsFlow.defaultValue)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -388,6 +388,7 @@ private val DEFAULT_USER_ACCOUNT = UserState.Account(
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.ACCOUNT_LOCK_SETUP,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
)
|
||||
|
||||
private val CIPHER = mockk<Cipher>()
|
||||
|
|
|
@ -88,6 +88,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -225,6 +226,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
)
|
||||
val userState = UserState(
|
||||
activeUserId = "activeUserId",
|
||||
|
@ -281,6 +283,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
)
|
||||
val userState = UserState(
|
||||
activeUserId = "activeUserId",
|
||||
|
@ -341,6 +344,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
)
|
||||
val userState = UserState(
|
||||
activeUserId = "activeUserId",
|
||||
|
@ -517,6 +521,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
)
|
||||
|
||||
val userState = UserState(
|
||||
|
@ -552,6 +557,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
)
|
||||
|
||||
val userState = UserState(
|
||||
|
|
|
@ -131,6 +131,7 @@ class LoginViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -167,6 +167,7 @@ private val DEFAULT_ACCOUNT = UserState.Account(
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
)
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
|
|
|
@ -276,6 +276,7 @@ private val DEFAULT_ACCOUNT = UserState.Account(
|
|||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
)
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
|
|
|
@ -219,6 +219,7 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -258,6 +259,8 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -1131,6 +1134,7 @@ private val DEFAULT_ACCOUNT = UserState.Account(
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
)
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
|
|
|
@ -111,6 +111,7 @@ private val DEFAULT_MAP_VALUE: Map<FlagKey<Any>, Any> = mapOf(
|
|||
FlagKey.EmailVerification to true,
|
||||
FlagKey.OnboardingCarousel to true,
|
||||
FlagKey.OnboardingFlow to true,
|
||||
FlagKey.ImportLoginsFlow to true,
|
||||
)
|
||||
|
||||
private val UPDATED_MAP_VALUE: Map<FlagKey<Any>, Any> = mapOf(
|
||||
|
@ -118,6 +119,7 @@ private val UPDATED_MAP_VALUE: Map<FlagKey<Any>, Any> = mapOf(
|
|||
FlagKey.EmailVerification to false,
|
||||
FlagKey.OnboardingCarousel to true,
|
||||
FlagKey.OnboardingFlow to false,
|
||||
FlagKey.ImportLoginsFlow to false,
|
||||
)
|
||||
|
||||
private val DEFAULT_STATE = DebugMenuState(
|
||||
|
|
|
@ -106,6 +106,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -140,6 +143,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -171,6 +177,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -212,6 +221,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -250,6 +262,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -288,6 +303,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = false,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -323,6 +341,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = true,
|
||||
|
@ -371,6 +392,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -403,6 +427,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -440,6 +467,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -480,6 +510,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -520,6 +553,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -567,6 +603,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -614,6 +653,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -656,6 +698,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -697,6 +742,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -773,6 +821,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -824,6 +875,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -883,6 +937,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -922,6 +979,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -956,6 +1016,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -988,6 +1051,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.NOT_STARTED,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -1023,6 +1089,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.ACCOUNT_LOCK_SETUP,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -1058,6 +1127,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.AUTOFILL_SETUP,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -1093,6 +1165,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.FINAL_STEP,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -1128,6 +1203,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.NOT_STARTED,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -1163,6 +1241,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -1198,6 +1279,9 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1537,6 +1537,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -792,6 +792,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -244,6 +244,7 @@ private val DEFAULT_USER_STATE: UserState = UserState(
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -372,6 +372,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -724,6 +724,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -2610,6 +2610,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -1103,6 +1103,7 @@ class AddSendViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
)
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
|
|
|
@ -3965,6 +3965,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
hasPendingAccountAddition = false,
|
||||
|
|
|
@ -513,6 +513,7 @@ class CipherViewExtensionsTest {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -564,6 +564,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -2581,6 +2581,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -4032,6 +4032,7 @@ private val DEFAULT_ACCOUNT = UserState.Account(
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
)
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
|
|
|
@ -515,6 +515,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -130,6 +130,7 @@ private fun createMockUserState(hasOrganizations: Boolean = true): UserState =
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -1141,6 +1141,59 @@ class VaultScreenTest : BaseComposeTest() {
|
|||
composeTestRule.waitForIdle()
|
||||
assertTrue(permissionsManager.hasGetLauncherBeenCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `action card for importing logins should show based on state`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = VaultState.ViewState.NoItems,
|
||||
)
|
||||
}
|
||||
val importSavedLogins = "Import saved logins"
|
||||
composeTestRule
|
||||
.onNodeWithText(importSavedLogins)
|
||||
.assertDoesNotExist()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = VaultState.ViewState.NoItems,
|
||||
showImportActionCard = true,
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText(importSavedLogins)
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when import action card is showing, clicking it should send ImportLoginsClick action`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = VaultState.ViewState.NoItems,
|
||||
showImportActionCard = true,
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText("Get started")
|
||||
.performClick()
|
||||
|
||||
verify { viewModel.trySendAction(VaultAction.ImportActionCardClick) }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when import action card is showing, dismissing it should send DismissImportActionCard action`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = VaultState.ViewState.NoItems,
|
||||
showImportActionCard = true,
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("Close")
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(VaultAction.DismissImportActionCard) }
|
||||
}
|
||||
}
|
||||
|
||||
private val ACTIVE_ACCOUNT_SUMMARY = AccountSummary(
|
||||
|
@ -1195,6 +1248,7 @@ private val DEFAULT_STATE: VaultState = VaultState(
|
|||
hasMasterPassword = true,
|
||||
hideNotificationsDialog = true,
|
||||
isRefreshing = false,
|
||||
showImportActionCard = false,
|
||||
)
|
||||
|
||||
private val DEFAULT_CONTENT_VIEW_STATE: VaultState.ViewState.Content = VaultState.ViewState.Content(
|
||||
|
|
|
@ -8,9 +8,11 @@ import com.x8bit.bitwarden.data.auth.repository.model.Organization
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
|
@ -41,6 +43,7 @@ import io.mockk.mockk
|
|||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
|
@ -84,6 +87,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||
every { hasPendingAccountAddition = any() } just runs
|
||||
every { logout(any()) } just runs
|
||||
every { switchAccount(any()) } answers { switchAccountResult }
|
||||
every { setShowImportLogins(any()) } just runs
|
||||
}
|
||||
|
||||
private val settingsRepository: SettingsRepository = mockk {
|
||||
|
@ -106,6 +110,13 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||
every { trackEvent(event = any()) } just runs
|
||||
}
|
||||
|
||||
private val mutableImportLoginsFeatureFlow = MutableStateFlow(true)
|
||||
private val featureFlagManager: FeatureFlagManager = mockk {
|
||||
every {
|
||||
getFeatureFlagFlow(FlagKey.ImportLoginsFlow)
|
||||
} returns mutableImportLoginsFeatureFlow
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct and should trigger a syncIfNecessary call`() {
|
||||
val viewModel = createViewModel()
|
||||
|
@ -198,6 +209,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = DEFAULT_FIRST_TIME_STATE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -283,6 +295,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = DEFAULT_FIRST_TIME_STATE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -1484,6 +1497,113 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when user first time state updates, vault state is updated`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE,
|
||||
awaitItem(),
|
||||
)
|
||||
mutableUserStateFlow.value = DEFAULT_USER_STATE.copy(
|
||||
accounts = DEFAULT_USER_STATE.accounts.map {
|
||||
it.copy(
|
||||
firstTimeState = DEFAULT_FIRST_TIME_STATE.copy(
|
||||
showImportLoginsCard = false,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
showImportActionCard = false,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when feature flag ImportLoginsFlow is disabled, should show action card should always be false`() =
|
||||
runTest {
|
||||
mutableImportLoginsFeatureFlow.update { false }
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(showImportActionCard = false),
|
||||
awaitItem(),
|
||||
)
|
||||
mutableUserStateFlow.value = DEFAULT_USER_STATE.copy(
|
||||
accounts = DEFAULT_USER_STATE.accounts.map {
|
||||
it.copy(
|
||||
firstTimeState = DEFAULT_FIRST_TIME_STATE.copy(
|
||||
showImportLoginsCard = true,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when DismissImportActionCard is sent, repository called to set value to false`() {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(VaultAction.DismissImportActionCard)
|
||||
verify(exactly = 1) {
|
||||
authRepository.setShowImportLogins(false)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when DismissImportActionCard is sent, repository is not called if value is already false`() {
|
||||
mutableUserStateFlow.value = DEFAULT_USER_STATE.copy(
|
||||
accounts = DEFAULT_USER_STATE.accounts.map {
|
||||
it.copy(
|
||||
firstTimeState = DEFAULT_FIRST_TIME_STATE.copy(
|
||||
showImportLoginsCard = false,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(VaultAction.DismissImportActionCard)
|
||||
verify(exactly = 0) {
|
||||
authRepository.setShowImportLogins(false)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when ImportActionCardClick is sent, repository called to set value to false`() {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(VaultAction.ImportActionCardClick)
|
||||
verify(exactly = 1) {
|
||||
authRepository.setShowImportLogins(false)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when ImportActionCardClick is sent, repository is not called if value is already false`() {
|
||||
mutableUserStateFlow.value = DEFAULT_USER_STATE.copy(
|
||||
accounts = DEFAULT_USER_STATE.accounts.map {
|
||||
it.copy(
|
||||
firstTimeState = DEFAULT_FIRST_TIME_STATE.copy(
|
||||
showImportLoginsCard = false,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(VaultAction.ImportActionCardClick)
|
||||
verify(exactly = 0) {
|
||||
authRepository.setShowImportLogins(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(): VaultViewModel =
|
||||
VaultViewModel(
|
||||
authRepository = authRepository,
|
||||
|
@ -1493,6 +1613,7 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||
settingsRepository = settingsRepository,
|
||||
vaultRepository = vaultRepository,
|
||||
organizationEventManager = organizationEventManager,
|
||||
featureFlagManager = featureFlagManager,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1513,6 +1634,10 @@ private val VAULT_FILTER_DATA = VaultFilterData(
|
|||
private val DEFAULT_STATE: VaultState =
|
||||
createMockVaultState(viewState = VaultState.ViewState.Loading)
|
||||
|
||||
private val DEFAULT_FIRST_TIME_STATE = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
)
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
|
@ -1533,6 +1658,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = DEFAULT_FIRST_TIME_STATE,
|
||||
),
|
||||
UserState.Account(
|
||||
userId = "lockedUserId",
|
||||
|
@ -1551,6 +1677,7 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = DEFAULT_FIRST_TIME_STATE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -1594,5 +1721,6 @@ private fun createMockVaultState(
|
|||
isIconLoadingDisabled = false,
|
||||
hasMasterPassword = true,
|
||||
hideNotificationsDialog = true,
|
||||
showImportActionCard = true,
|
||||
isRefreshing = false,
|
||||
)
|
||||
|
|
|
@ -87,6 +87,7 @@ class UserStateExtensionsTest {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
UserState.Account(
|
||||
userId = "lockedUserId",
|
||||
|
@ -113,6 +114,7 @@ class UserStateExtensionsTest {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
UserState.Account(
|
||||
userId = "unlockedUserId",
|
||||
|
@ -143,6 +145,7 @@ class UserStateExtensionsTest {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
UserState.Account(
|
||||
userId = "loggedOutUserId",
|
||||
|
@ -173,6 +176,7 @@ class UserStateExtensionsTest {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -218,6 +222,7 @@ class UserStateExtensionsTest {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
)
|
||||
.toAccountSummary(isActive = true),
|
||||
)
|
||||
|
@ -261,6 +266,7 @@ class UserStateExtensionsTest {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
)
|
||||
.toAccountSummary(isActive = false),
|
||||
)
|
||||
|
@ -308,6 +314,7 @@ class UserStateExtensionsTest {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -335,6 +342,7 @@ class UserStateExtensionsTest {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
)
|
||||
.toVaultFilterData(isIndividualVaultDisabled = false),
|
||||
)
|
||||
|
@ -391,6 +399,7 @@ class UserStateExtensionsTest {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
)
|
||||
.toVaultFilterData(
|
||||
isIndividualVaultDisabled = false,
|
||||
|
@ -448,6 +457,7 @@ class UserStateExtensionsTest {
|
|||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
firstTimeState = UserState.FirstTimeState(showImportLoginsCard = true),
|
||||
)
|
||||
.toVaultFilterData(
|
||||
isIndividualVaultDisabled = true,
|
||||
|
|
Loading…
Reference in a new issue