mirror of
https://github.com/bitwarden/android.git
synced 2024-11-23 01:46:00 +03:00
Navigate to the Vault Listing screen from autofill (#810)
This commit is contained in:
parent
0411ccd3f9
commit
f2a7998bb0
16 changed files with 374 additions and 27 deletions
|
@ -4,6 +4,7 @@ import android.content.Intent
|
|||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.data.autofill.util.getAutofillSelectionDataOrNull
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
|
@ -91,8 +92,19 @@ class MainViewModel @Inject constructor(
|
|||
intent: Intent,
|
||||
isFirstIntent: Boolean,
|
||||
) {
|
||||
val autofillSelectionData = intent.getAutofillSelectionDataOrNull()
|
||||
val shareData = intentManager.getShareDataFromIntent(intent)
|
||||
when {
|
||||
autofillSelectionData != null -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.AutofillSelection(
|
||||
autofillSelectionData = autofillSelectionData,
|
||||
// Allow users back into the already-running app when completing the
|
||||
// autofill task when this is not the first intent.
|
||||
shouldFinishWhenComplete = isFirstIntent,
|
||||
)
|
||||
}
|
||||
|
||||
shareData != null -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.ShareNewSend(
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package com.x8bit.bitwarden.data.autofill.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* Represents data for a manual autofill selection.
|
||||
*
|
||||
* @property type The type of autofill selection that must be made.
|
||||
* @property uri A URI representing the location where data should be filled (if available).
|
||||
*/
|
||||
@Parcelize
|
||||
class AutofillSelectionData(
|
||||
val type: Type,
|
||||
val uri: String?,
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
* The type of selection the user must make.
|
||||
*/
|
||||
enum class Type {
|
||||
CARD,
|
||||
LOGIN,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
@file:OmitFromCoverage
|
||||
|
||||
package com.x8bit.bitwarden.data.autofill.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import com.x8bit.bitwarden.MainActivity
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
|
||||
private const val AUTOFILL_SELECTION_DATA_KEY = "autofill-selection-data"
|
||||
|
||||
/**
|
||||
* Creates an [Intent] in order to send the user to a manual selection process for autofill.
|
||||
*/
|
||||
fun createAutofillSelectionIntent(
|
||||
context: Context,
|
||||
type: AutofillSelectionData.Type,
|
||||
uri: String?,
|
||||
): Intent =
|
||||
Intent(
|
||||
context,
|
||||
MainActivity::class.java,
|
||||
)
|
||||
.apply {
|
||||
putExtra(
|
||||
AUTOFILL_SELECTION_DATA_KEY,
|
||||
AutofillSelectionData(
|
||||
type = type,
|
||||
uri = uri,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given [Intent] contains data about an ongoing manual autofill selection process.
|
||||
* The [AutofillSelectionData] will be returned when present.
|
||||
*/
|
||||
fun Intent.getAutofillSelectionDataOrNull(): AutofillSelectionData? {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
this.getParcelableExtra(AUTOFILL_SELECTION_DATA_KEY, AutofillSelectionData::class.java)
|
||||
} else {
|
||||
this.getParcelableExtra(AUTOFILL_SELECTION_DATA_KEY)
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@ package com.x8bit.bitwarden.data.autofill.util
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.service.autofill.Dataset
|
||||
import android.service.autofill.Presentations
|
||||
|
@ -11,8 +10,9 @@ import android.view.autofill.AutofillValue
|
|||
import android.widget.RemoteViews
|
||||
import android.widget.inline.InlinePresentationSpec
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.x8bit.bitwarden.MainActivity
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillAppInfo
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillPartition
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledData
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledItem
|
||||
import com.x8bit.bitwarden.ui.autofill.buildVaultItemAutofillRemoteViews
|
||||
|
@ -31,11 +31,14 @@ val FilledData.fillableAutofillIds: List<AutofillId>
|
|||
fun FilledData.buildVaultItemDataset(
|
||||
autofillAppInfo: AutofillAppInfo,
|
||||
): Dataset {
|
||||
val intent = Intent(
|
||||
autofillAppInfo.context,
|
||||
MainActivity::class.java,
|
||||
val intent = createAutofillSelectionIntent(
|
||||
context = autofillAppInfo.context,
|
||||
type = when (this.originalPartition) {
|
||||
is AutofillPartition.Card -> AutofillSelectionData.Type.CARD
|
||||
is AutofillPartition.Login -> AutofillSelectionData.Type.LOGIN
|
||||
},
|
||||
uri = this.uri,
|
||||
)
|
||||
// TODO: Add additional data to the Intent to be pulled out in the app (BIT-1296)
|
||||
|
||||
val pendingIntent = PendingIntent
|
||||
.getActivity(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.x8bit.bitwarden.data.platform.manager.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
|
@ -17,4 +18,13 @@ sealed class SpecialCircumstance : Parcelable {
|
|||
val data: IntentManager.ShareData,
|
||||
val shouldFinishWhenComplete: Boolean,
|
||||
) : SpecialCircumstance()
|
||||
|
||||
/**
|
||||
* The app was launched in order to allow the user to manually select data for autofill.
|
||||
*/
|
||||
@Parcelize
|
||||
data class AutofillSelection(
|
||||
val autofillSelectionData: AutofillSelectionData,
|
||||
val shouldFinishWhenComplete: Boolean,
|
||||
) : SpecialCircumstance()
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import com.x8bit.bitwarden.ui.auth.feature.auth.navigateToAuthGraph
|
|||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.VAULT_UNLOCK_ROUTE
|
||||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.navigateToVaultUnlock
|
||||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.vaultUnlockDestination
|
||||
import com.x8bit.bitwarden.ui.platform.feature.rootnav.util.toVaultItemListingType
|
||||
import com.x8bit.bitwarden.ui.platform.feature.splash.SPLASH_ROUTE
|
||||
import com.x8bit.bitwarden.ui.platform.feature.splash.navigateToSplash
|
||||
import com.x8bit.bitwarden.ui.platform.feature.splash.splashDestination
|
||||
|
@ -26,6 +27,7 @@ import com.x8bit.bitwarden.ui.platform.feature.vaultunlocked.vaultUnlockedGraph
|
|||
import com.x8bit.bitwarden.ui.platform.theme.RootTransitionProviders
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.model.AddSendType
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.navigateToAddSend
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.navigateToVaultItemListingAsRoot
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
@ -76,7 +78,8 @@ fun RootNavScreen(
|
|||
RootNavState.Splash -> SPLASH_ROUTE
|
||||
RootNavState.VaultLocked -> VAULT_UNLOCK_ROUTE
|
||||
is RootNavState.VaultUnlocked,
|
||||
RootNavState.VaultUnlockedForNewSend,
|
||||
is RootNavState.VaultUnlockedForAutofillSelection,
|
||||
is RootNavState.VaultUnlockedForNewSend,
|
||||
-> VAULT_UNLOCKED_GRAPH_ROUTE
|
||||
}
|
||||
val currentRoute = navController.currentDestination?.rootLevelRoute()
|
||||
|
@ -102,7 +105,7 @@ fun RootNavScreen(
|
|||
restoreState = false
|
||||
}
|
||||
|
||||
when (state) {
|
||||
when (val currentState = state) {
|
||||
RootNavState.Auth -> navController.navigateToAuthGraph(rootNavOptions)
|
||||
RootNavState.Splash -> navController.navigateToSplash(rootNavOptions)
|
||||
RootNavState.VaultLocked -> navController.navigateToVaultUnlock(rootNavOptions)
|
||||
|
@ -114,6 +117,14 @@ fun RootNavScreen(
|
|||
navOptions = rootNavOptions,
|
||||
)
|
||||
}
|
||||
|
||||
is RootNavState.VaultUnlockedForAutofillSelection -> {
|
||||
navController.navigateToVaultUnlockedGraph(rootNavOptions)
|
||||
navController.navigateToVaultItemListingAsRoot(
|
||||
vaultItemListingType = currentState.type.toVaultItemListingType(),
|
||||
navOptions = rootNavOptions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.os.Parcelable
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
|
@ -64,10 +65,15 @@ class RootNavViewModel @Inject constructor(
|
|||
|
||||
userState.activeAccount.isVaultUnlocked -> {
|
||||
when (specialCircumstance) {
|
||||
is SpecialCircumstance.AutofillSelection -> {
|
||||
RootNavState.VaultUnlockedForAutofillSelection(
|
||||
type = specialCircumstance.autofillSelectionData.type,
|
||||
)
|
||||
}
|
||||
|
||||
is SpecialCircumstance.ShareNewSend -> RootNavState.VaultUnlockedForNewSend
|
||||
|
||||
null,
|
||||
-> {
|
||||
null -> {
|
||||
RootNavState.VaultUnlocked(
|
||||
activeUserId = userState.activeAccount.userId,
|
||||
)
|
||||
|
@ -111,6 +117,14 @@ sealed class RootNavState : Parcelable {
|
|||
val activeUserId: String,
|
||||
) : RootNavState()
|
||||
|
||||
/**
|
||||
* App should show a selection screen for autofill for an unlocked user.
|
||||
*/
|
||||
@Parcelize
|
||||
data class VaultUnlockedForAutofillSelection(
|
||||
val type: AutofillSelectionData.Type,
|
||||
) : RootNavState()
|
||||
|
||||
/**
|
||||
* App should show the new send screen for an unlocked user.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.rootnav.util
|
||||
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||
|
||||
/**
|
||||
* Derives a [VaultItemListingType] from the given [AutofillSelectionData.Type].
|
||||
*/
|
||||
fun AutofillSelectionData.Type.toVaultItemListingType(): VaultItemListingType =
|
||||
when (this) {
|
||||
AutofillSelectionData.Type.CARD -> VaultItemListingType.Card
|
||||
AutofillSelectionData.Type.LOGIN -> VaultItemListingType.Login
|
||||
}
|
|
@ -34,6 +34,7 @@ import com.x8bit.bitwarden.ui.vault.feature.attachments.attachmentDestination
|
|||
import com.x8bit.bitwarden.ui.vault.feature.attachments.navigateToAttachment
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.navigateToVaultItem
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.vaultItemDestination
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.vaultItemListingDestinationAsRoot
|
||||
import com.x8bit.bitwarden.ui.vault.feature.manualcodeentry.navigateToManualCodeEntryScreen
|
||||
import com.x8bit.bitwarden.ui.vault.feature.manualcodeentry.vaultManualCodeEntryDestination
|
||||
import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.navigateToVaultMoveToOrganization
|
||||
|
@ -62,6 +63,17 @@ fun NavGraphBuilder.vaultUnlockedGraph(
|
|||
startDestination = VAULT_UNLOCKED_NAV_BAR_ROUTE,
|
||||
route = VAULT_UNLOCKED_GRAPH_ROUTE,
|
||||
) {
|
||||
vaultItemListingDestinationAsRoot(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToVaultItemScreen = { navController.navigateToVaultItem(vaultItemId = it) },
|
||||
onNavigateToVaultAddItemScreen = {
|
||||
navController.navigateToVaultAddEdit(VaultAddEditType.AddItem)
|
||||
},
|
||||
onNavigateToSearchVault = { navController.navigateToSearch(searchType = it) },
|
||||
onNavigateToVaultEditItemScreen = {
|
||||
navController.navigateToVaultAddEdit(VaultAddEditType.EditItem(it))
|
||||
},
|
||||
)
|
||||
vaultUnlockedNavBarDestination(
|
||||
onNavigateToExportVault = { navController.navigateToExportVault() },
|
||||
onNavigateToFolders = { navController.navigateToFolders() },
|
||||
|
|
|
@ -8,6 +8,7 @@ import androidx.navigation.NavType
|
|||
import androidx.navigation.navArgument
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithStayTransitions
|
||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||
|
||||
|
@ -21,11 +22,15 @@ private const val SEND_FILE: String = "send_file"
|
|||
private const val SEND_TEXT: String = "send_text"
|
||||
private const val TRASH: String = "trash"
|
||||
private const val VAULT_ITEM_LISTING_PREFIX: String = "vault_item_listing"
|
||||
private const val VAULT_ITEM_LISTING_AS_ROOT_PREFIX: String = "vault_item_listing_as_root"
|
||||
private const val VAULT_ITEM_LISTING_TYPE: String = "vault_item_listing_type"
|
||||
private const val ID: String = "id"
|
||||
private const val VAULT_ITEM_LISTING_ROUTE: String =
|
||||
"$VAULT_ITEM_LISTING_PREFIX/{$VAULT_ITEM_LISTING_TYPE}" +
|
||||
"?$ID={$ID}"
|
||||
private const val VAULT_ITEM_LISTING_AS_ROOT_ROUTE: String =
|
||||
"$VAULT_ITEM_LISTING_AS_ROOT_PREFIX/{$VAULT_ITEM_LISTING_TYPE}" +
|
||||
"?$ID={$ID}"
|
||||
private const val SEND_ITEM_LISTING_PREFIX: String = "send_item_listing"
|
||||
private const val SEND_ITEM_LISTING_ROUTE: String =
|
||||
"$SEND_ITEM_LISTING_PREFIX/{$VAULT_ITEM_LISTING_TYPE}" +
|
||||
|
@ -70,6 +75,39 @@ fun NavGraphBuilder.vaultItemListingDestination(
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the [VaultItemListingScreen] to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.vaultItemListingDestinationAsRoot(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToVaultItemScreen: (id: String) -> Unit,
|
||||
onNavigateToVaultEditItemScreen: (cipherId: String) -> Unit,
|
||||
onNavigateToVaultAddItemScreen: () -> Unit,
|
||||
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
||||
) {
|
||||
composableWithStayTransitions(
|
||||
route = VAULT_ITEM_LISTING_AS_ROOT_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(
|
||||
name = VAULT_ITEM_LISTING_TYPE,
|
||||
builder = {
|
||||
type = NavType.StringType
|
||||
},
|
||||
),
|
||||
),
|
||||
) {
|
||||
VaultItemListingScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToVaultItem = onNavigateToVaultItemScreen,
|
||||
onNavigateToVaultEditItemScreen = onNavigateToVaultEditItemScreen,
|
||||
onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen,
|
||||
onNavigateToSearch = { onNavigateToSearchVault(it as SearchType.Vault) },
|
||||
onNavigateToAddSendItem = {},
|
||||
onNavigateToEditSendItem = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the [VaultItemListingScreen] to the nav graph.
|
||||
*/
|
||||
|
@ -110,7 +148,9 @@ private fun NavGraphBuilder.internalVaultItemListingDestination(
|
|||
arguments = listOf(
|
||||
navArgument(
|
||||
name = VAULT_ITEM_LISTING_TYPE,
|
||||
builder = { type = NavType.StringType },
|
||||
builder = {
|
||||
type = NavType.StringType
|
||||
},
|
||||
),
|
||||
navArgument(
|
||||
name = ID,
|
||||
|
@ -147,6 +187,20 @@ fun NavController.navigateToVaultItemListing(
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the [VaultItemListingScreen] for vault.
|
||||
*/
|
||||
fun NavController.navigateToVaultItemListingAsRoot(
|
||||
vaultItemListingType: VaultItemListingType,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(
|
||||
route = "$VAULT_ITEM_LISTING_AS_ROOT_PREFIX/${vaultItemListingType.toTypeString()}" +
|
||||
"?$ID=${vaultItemListingType.toIdOrNull()}",
|
||||
navOptions = navOptions,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the [VaultItemListingScreen] for sends.
|
||||
*/
|
||||
|
|
|
@ -4,7 +4,10 @@ import android.os.Parcelable
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
|
@ -44,7 +47,7 @@ import javax.inject.Inject
|
|||
* and launches [VaultItemListingEvent] for the [VaultItemListingScreen].
|
||||
*/
|
||||
@HiltViewModel
|
||||
@Suppress("MagicNumber", "TooManyFunctions")
|
||||
@Suppress("MagicNumber", "TooManyFunctions", "LongParameterList")
|
||||
class VaultItemListingViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val clock: Clock,
|
||||
|
@ -52,19 +55,26 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
private val vaultRepository: VaultRepository,
|
||||
private val environmentRepository: EnvironmentRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||
) : BaseViewModel<VaultItemListingState, VaultItemListingEvent, VaultItemListingsAction>(
|
||||
initialState = VaultItemListingState(
|
||||
itemListingType = VaultItemListingArgs(savedStateHandle = savedStateHandle)
|
||||
.vaultItemListingType
|
||||
.toItemListingType(),
|
||||
viewState = VaultItemListingState.ViewState.Loading,
|
||||
vaultFilterType = vaultRepository.vaultFilterType,
|
||||
baseWebSendUrl = environmentRepository.environment.environmentUrlData.baseWebSendUrl,
|
||||
baseIconUrl = environmentRepository.environment.environmentUrlData.baseIconUrl,
|
||||
isIconLoadingDisabled = settingsRepository.isIconLoadingDisabled,
|
||||
isPullToRefreshSettingEnabled = settingsRepository.getPullToRefreshEnabledFlow().value,
|
||||
dialogState = null,
|
||||
),
|
||||
initialState = run {
|
||||
val specialCircumstance =
|
||||
specialCircumstanceManager.specialCircumstance as? SpecialCircumstance.AutofillSelection
|
||||
VaultItemListingState(
|
||||
itemListingType = VaultItemListingArgs(savedStateHandle = savedStateHandle)
|
||||
.vaultItemListingType
|
||||
.toItemListingType(),
|
||||
viewState = VaultItemListingState.ViewState.Loading,
|
||||
vaultFilterType = vaultRepository.vaultFilterType,
|
||||
baseWebSendUrl = environmentRepository.environment.environmentUrlData.baseWebSendUrl,
|
||||
baseIconUrl = environmentRepository.environment.environmentUrlData.baseIconUrl,
|
||||
isIconLoadingDisabled = settingsRepository.isIconLoadingDisabled,
|
||||
isPullToRefreshSettingEnabled = settingsRepository.getPullToRefreshEnabledFlow().value,
|
||||
dialogState = null,
|
||||
autofillSelectionData = specialCircumstance?.autofillSelectionData,
|
||||
shouldFinishOnComplete = specialCircumstance?.shouldFinishWhenComplete ?: false,
|
||||
)
|
||||
},
|
||||
) {
|
||||
|
||||
init {
|
||||
|
@ -521,7 +531,10 @@ data class VaultItemListingState(
|
|||
val baseIconUrl: String,
|
||||
val isIconLoadingDisabled: Boolean,
|
||||
val dialogState: DialogState?,
|
||||
// Internal
|
||||
private val isPullToRefreshSettingEnabled: Boolean,
|
||||
private val autofillSelectionData: AutofillSelectionData? = null,
|
||||
private val shouldFinishOnComplete: Boolean = false,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
@ -530,6 +543,12 @@ data class VaultItemListingState(
|
|||
val isPullToRefreshEnabled: Boolean
|
||||
get() = isPullToRefreshSettingEnabled && viewState.isPullToRefreshEnabled
|
||||
|
||||
/**
|
||||
* Whether or not this represents a listing screen for autofill.
|
||||
*/
|
||||
val isAutofill: Boolean
|
||||
get() = autofillSelectionData != null
|
||||
|
||||
/**
|
||||
* Represents the current state of any dialogs on the screen.
|
||||
*/
|
||||
|
|
|
@ -5,7 +5,8 @@ import androidx.lifecycle.SavedStateHandle
|
|||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.getCaptchaCallbackTokenResult
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.autofill.util.getAutofillSelectionDataOrNull
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
|
@ -107,7 +108,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val shareData = mockk<IntentManager.ShareData>()
|
||||
every { mockIntent.getCaptchaCallbackTokenResult() } returns null
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||
every { intentManager.getShareDataFromIntent(mockIntent) } returns shareData
|
||||
|
||||
viewModel.trySendAction(
|
||||
|
@ -124,13 +125,36 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveFirstIntent with autofill data should set the special circumstance to AutofillSelection`() {
|
||||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val autofillSelectionData = mockk<AutofillSelectionData>()
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns autofillSelectionData
|
||||
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
||||
|
||||
viewModel.trySendAction(
|
||||
MainAction.ReceiveFirstIntent(
|
||||
intent = mockIntent,
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
SpecialCircumstance.AutofillSelection(
|
||||
autofillSelectionData = autofillSelectionData,
|
||||
shouldFinishWhenComplete = true,
|
||||
),
|
||||
specialCircumstanceManager.specialCircumstance,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveNewIntent with share data should set the special circumstance to ShareNewSend`() {
|
||||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val shareData = mockk<IntentManager.ShareData>()
|
||||
every { mockIntent.getCaptchaCallbackTokenResult() } returns null
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||
every { intentManager.getShareDataFromIntent(mockIntent) } returns shareData
|
||||
|
||||
viewModel.trySendAction(
|
||||
|
@ -147,6 +171,29 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveNewIntent with autofill data should set the special circumstance to AutofillSelection`() {
|
||||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val autofillSelectionData = mockk<AutofillSelectionData>()
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns autofillSelectionData
|
||||
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
||||
|
||||
viewModel.trySendAction(
|
||||
MainAction.ReceiveNewIntent(
|
||||
intent = mockIntent,
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
SpecialCircumstance.AutofillSelection(
|
||||
autofillSelectionData = autofillSelectionData,
|
||||
shouldFinishWhenComplete = false,
|
||||
),
|
||||
specialCircumstanceManager.specialCircumstance,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `changes in the allowed screen capture value should result in emissions of ScreenCaptureSettingChange `() =
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.rootnav
|
||||
|
||||
import androidx.navigation.navOptions
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.FakeNavHostController
|
||||
import io.mockk.every
|
||||
|
@ -98,5 +99,17 @@ class RootNavScreenTest : BaseComposeTest() {
|
|||
navOptions = expectedNavOptions,
|
||||
)
|
||||
}
|
||||
|
||||
// Make sure navigating to vault unlocked for autofill works as expected:
|
||||
rootNavStateFlow.value =
|
||||
RootNavState.VaultUnlockedForAutofillSelection(
|
||||
type = AutofillSelectionData.Type.LOGIN,
|
||||
)
|
||||
composeTestRule.runOnIdle {
|
||||
fakeNavHostController.assertLastNavigation(
|
||||
route = "vault_item_listing_as_root/login",
|
||||
navOptions = expectedNavOptions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.platform.feature.rootnav
|
|||
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
|
@ -145,6 +146,46 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when the active user has an unlocked vault but there is an AutofillSelection special circumstance the nav state should be VaultUnlockedForAutofillSelection`() {
|
||||
val autofillSelectionData = AutofillSelectionData(
|
||||
type = AutofillSelectionData.Type.LOGIN,
|
||||
uri = "uri",
|
||||
)
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.AutofillSelection(
|
||||
autofillSelectionData = autofillSelectionData,
|
||||
shouldFinishWhenComplete = true,
|
||||
)
|
||||
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,
|
||||
isBiometricsEnabled = false,
|
||||
organizations = emptyList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(
|
||||
RootNavState.VaultUnlockedForAutofillSelection(
|
||||
type = AutofillSelectionData.Type.LOGIN,
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when the active user has a locked vault the nav state should be VaultLocked`() {
|
||||
mutableUserStateFlow.tryEmit(
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.rootnav.util
|
||||
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class AutofillSelectionDataExtensionsTest {
|
||||
@Test
|
||||
fun `toVaultItemListingType should return the correct result for each type`() {
|
||||
mapOf(
|
||||
AutofillSelectionData.Type.CARD to VaultItemListingType.Card,
|
||||
AutofillSelectionData.Type.LOGIN to VaultItemListingType.Login,
|
||||
)
|
||||
.forEach { (type, expected) ->
|
||||
assertEquals(
|
||||
expected,
|
||||
type.toVaultItemListingType(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import android.net.Uri
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
|
@ -76,6 +77,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
every { isIconLoadingDisabledFlow } returns mutableIsIconLoadingDisabledFlow
|
||||
every { getPullToRefreshEnabledFlow() } returns mutablePullToRefreshEnabledFlow
|
||||
}
|
||||
private val specialCircumstanceManager = SpecialCircumstanceManagerImpl()
|
||||
private val initialState = createVaultItemListingState()
|
||||
private val initialSavedStateHandle = createSavedStateHandleWithVaultItemListingType(
|
||||
vaultItemListingType = VaultItemListingType.Login,
|
||||
|
@ -941,6 +943,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
vaultRepository = vaultRepository,
|
||||
environmentRepository = environmentRepository,
|
||||
settingsRepository = settingsRepository,
|
||||
specialCircumstanceManager = specialCircumstanceManager,
|
||||
)
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
|
@ -957,5 +960,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
isIconLoadingDisabled = settingsRepository.isIconLoadingDisabled,
|
||||
isPullToRefreshSettingEnabled = false,
|
||||
dialogState = null,
|
||||
autofillSelectionData = null,
|
||||
shouldFinishOnComplete = false,
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue