mirror of
https://github.com/bitwarden/android.git
synced 2024-11-21 17:05:44 +03:00
This PR adds the TOTP matching flow to the app (#4042)
This commit is contained in:
parent
641a48fe44
commit
e7450171cd
14 changed files with 449 additions and 67 deletions
|
@ -89,6 +89,15 @@
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="otpauth" />
|
||||||
|
<data android:host="totp" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
|
|
@ -24,6 +24,7 @@ import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
|
||||||
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSaveItemOrNull
|
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSaveItemOrNull
|
||||||
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSelectionDataOrNull
|
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSelectionDataOrNull
|
||||||
import com.x8bit.bitwarden.data.platform.manager.util.toFido2RequestOrNull
|
import com.x8bit.bitwarden.data.platform.manager.util.toFido2RequestOrNull
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.util.toTotpDataOrNull
|
||||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.takeUntilLoaded
|
import com.x8bit.bitwarden.data.platform.repository.util.takeUntilLoaded
|
||||||
|
@ -53,6 +54,7 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toItemType
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toViewState
|
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toViewState
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.validateCipherOrReturnErrorState
|
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.validateCipherOrReturnErrorState
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toCipherView
|
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toCipherView
|
||||||
|
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||||
|
@ -108,33 +110,31 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
.getActivePolicies(type = PolicyTypeJson.PERSONAL_OWNERSHIP)
|
.getActivePolicies(type = PolicyTypeJson.PERSONAL_OWNERSHIP)
|
||||||
.any()
|
.any()
|
||||||
|
|
||||||
|
val specialCircumstance = specialCircumstanceManager.specialCircumstance
|
||||||
// Check for autofill data to pre-populate
|
// Check for autofill data to pre-populate
|
||||||
val autofillSaveItem = specialCircumstanceManager
|
val autofillSaveItem = specialCircumstance?.toAutofillSaveItemOrNull()
|
||||||
.specialCircumstance
|
val autofillSelectionData = specialCircumstance?.toAutofillSelectionDataOrNull()
|
||||||
?.toAutofillSaveItemOrNull()
|
// Check for totp data to pre-populate
|
||||||
val autofillSelectionData = specialCircumstanceManager
|
val totpData = specialCircumstance?.toTotpDataOrNull()
|
||||||
.specialCircumstance
|
// Check for Fido2 data to pre-populate
|
||||||
?.toAutofillSelectionDataOrNull()
|
val fido2CreationRequest = specialCircumstance?.toFido2RequestOrNull()
|
||||||
|
val fido2AttestationOptions = fido2CreationRequest?.let { request ->
|
||||||
|
fido2CredentialManager.getPasskeyAttestationOptionsOrNull(request.requestJson)
|
||||||
|
}
|
||||||
|
|
||||||
val fido2CreationRequest = specialCircumstanceManager
|
// Exit on save if handling an autofill, Fido2 Attestation, or TOTP link
|
||||||
.specialCircumstance
|
val shouldExitOnSave = autofillSaveItem != null ||
|
||||||
?.toFido2RequestOrNull()
|
fido2AttestationOptions != null ||
|
||||||
|
totpData != null
|
||||||
|
|
||||||
val fido2AttestationOptions = fido2CreationRequest
|
val dialogState = if (!settingsRepository.initialAutofillDialogShown &&
|
||||||
?.let { request ->
|
vaultAddEditType is VaultAddEditType.AddItem &&
|
||||||
fido2CredentialManager
|
autofillSelectionData == null
|
||||||
.getPasskeyAttestationOptionsOrNull(request.requestJson)
|
) {
|
||||||
}
|
VaultAddEditState.DialogState.InitialAutofillPrompt
|
||||||
|
} else {
|
||||||
val dialogState =
|
null
|
||||||
if (!settingsRepository.initialAutofillDialogShown &&
|
}
|
||||||
vaultAddEditType is VaultAddEditType.AddItem &&
|
|
||||||
autofillSelectionData == null
|
|
||||||
) {
|
|
||||||
VaultAddEditState.DialogState.InitialAutofillPrompt
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
VaultAddEditState(
|
VaultAddEditState(
|
||||||
vaultAddEditType = vaultAddEditType,
|
vaultAddEditType = vaultAddEditType,
|
||||||
|
@ -142,13 +142,12 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
is VaultAddEditType.AddItem -> {
|
is VaultAddEditType.AddItem -> {
|
||||||
autofillSelectionData
|
autofillSelectionData
|
||||||
?.toDefaultAddTypeContent(isIndividualVaultDisabled)
|
?.toDefaultAddTypeContent(isIndividualVaultDisabled)
|
||||||
?: autofillSaveItem
|
?: autofillSaveItem?.toDefaultAddTypeContent(isIndividualVaultDisabled)
|
||||||
?.toDefaultAddTypeContent(isIndividualVaultDisabled)
|
?: fido2CreationRequest?.toDefaultAddTypeContent(
|
||||||
?: fido2CreationRequest
|
attestationOptions = fido2AttestationOptions,
|
||||||
?.toDefaultAddTypeContent(
|
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
||||||
attestationOptions = fido2AttestationOptions,
|
)
|
||||||
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
?: totpData?.toDefaultAddTypeContent(isIndividualVaultDisabled)
|
||||||
)
|
|
||||||
?: VaultAddEditState.ViewState.Content(
|
?: VaultAddEditState.ViewState.Content(
|
||||||
common = VaultAddEditState.ViewState.Content.Common(),
|
common = VaultAddEditState.ViewState.Content.Common(),
|
||||||
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
||||||
|
@ -160,9 +159,10 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
is VaultAddEditType.CloneItem -> VaultAddEditState.ViewState.Loading
|
is VaultAddEditType.CloneItem -> VaultAddEditState.ViewState.Loading
|
||||||
},
|
},
|
||||||
dialog = dialogState,
|
dialog = dialogState,
|
||||||
|
totpData = totpData,
|
||||||
// Set special conditions for autofill and fido2 save
|
// Set special conditions for autofill and fido2 save
|
||||||
shouldShowCloseButton = autofillSaveItem == null && fido2AttestationOptions == null,
|
shouldShowCloseButton = autofillSaveItem == null && fido2AttestationOptions == null,
|
||||||
shouldExitOnSave = autofillSaveItem != null || fido2AttestationOptions != null,
|
shouldExitOnSave = shouldExitOnSave,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -1412,18 +1412,14 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is CreateCipherResult.Success -> {
|
is CreateCipherResult.Success -> {
|
||||||
|
specialCircumstanceManager.specialCircumstance = null
|
||||||
if (state.shouldExitOnSave) {
|
if (state.shouldExitOnSave) {
|
||||||
specialCircumstanceManager.specialCircumstance = null
|
sendEvent(event = VaultAddEditEvent.ExitApp)
|
||||||
sendEvent(
|
|
||||||
event = VaultAddEditEvent.ExitApp,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
sendEvent(
|
sendEvent(
|
||||||
event = VaultAddEditEvent.ShowToast(R.string.new_item_created.asText()),
|
event = VaultAddEditEvent.ShowToast(R.string.new_item_created.asText()),
|
||||||
)
|
)
|
||||||
sendEvent(
|
sendEvent(event = VaultAddEditEvent.NavigateBack)
|
||||||
event = VaultAddEditEvent.NavigateBack,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1444,10 +1440,13 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is UpdateCipherResult.Success -> {
|
is UpdateCipherResult.Success -> {
|
||||||
sendEvent(
|
specialCircumstanceManager.specialCircumstance = null
|
||||||
event = VaultAddEditEvent.ShowToast(R.string.item_updated.asText()),
|
if (state.shouldExitOnSave) {
|
||||||
)
|
sendEvent(event = VaultAddEditEvent.ExitApp)
|
||||||
sendEvent(VaultAddEditEvent.NavigateBack)
|
} else {
|
||||||
|
sendEvent(event = VaultAddEditEvent.ShowToast(R.string.item_updated.asText()))
|
||||||
|
sendEvent(event = VaultAddEditEvent.NavigateBack)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1544,15 +1543,19 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
) { currentAccount, cipherView ->
|
) { currentAccount, cipherView ->
|
||||||
// Derive the view state from the current Cipher for Edit mode
|
// Derive the view state from the current Cipher for Edit mode
|
||||||
// or use the current state for Add
|
// or use the current state for Add
|
||||||
(cipherView?.toViewState(
|
(cipherView
|
||||||
isClone = isCloneMode,
|
?.toViewState(
|
||||||
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
isClone = isCloneMode,
|
||||||
resourceManager = resourceManager,
|
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
||||||
clock = clock,
|
totpData = totpData,
|
||||||
) ?: viewState)
|
resourceManager = resourceManager,
|
||||||
|
clock = clock,
|
||||||
|
)
|
||||||
|
?: viewState)
|
||||||
.appendFolderAndOwnerData(
|
.appendFolderAndOwnerData(
|
||||||
folderViewList = vaultData.folderViewList,
|
folderViewList = vaultData.folderViewList,
|
||||||
collectionViewList = vaultData.collectionViewList
|
collectionViewList = vaultData
|
||||||
|
.collectionViewList
|
||||||
.filter { !it.readOnly },
|
.filter { !it.readOnly },
|
||||||
activeAccount = currentAccount,
|
activeAccount = currentAccount,
|
||||||
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
||||||
|
@ -1911,6 +1914,7 @@ data class VaultAddEditState(
|
||||||
val shouldShowCloseButton: Boolean = true,
|
val shouldShowCloseButton: Boolean = true,
|
||||||
// Internal
|
// Internal
|
||||||
val shouldExitOnSave: Boolean = false,
|
val shouldExitOnSave: Boolean = false,
|
||||||
|
val totpData: TotpData? = null,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -18,6 +18,7 @@ import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
||||||
import com.x8bit.bitwarden.ui.platform.util.toFormattedPattern
|
import com.x8bit.bitwarden.ui.platform.util.toFormattedPattern
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
||||||
|
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||||
|
@ -37,6 +38,7 @@ private const val PASSKEY_CREATION_TIME_PATTERN: String = "hh:mm a"
|
||||||
fun CipherView.toViewState(
|
fun CipherView.toViewState(
|
||||||
isClone: Boolean,
|
isClone: Boolean,
|
||||||
isIndividualVaultDisabled: Boolean,
|
isIndividualVaultDisabled: Boolean,
|
||||||
|
totpData: TotpData?,
|
||||||
resourceManager: ResourceManager,
|
resourceManager: ResourceManager,
|
||||||
clock: Clock,
|
clock: Clock,
|
||||||
): VaultAddEditState.ViewState =
|
): VaultAddEditState.ViewState =
|
||||||
|
@ -46,7 +48,7 @@ fun CipherView.toViewState(
|
||||||
VaultAddEditState.ViewState.Content.ItemType.Login(
|
VaultAddEditState.ViewState.Content.ItemType.Login(
|
||||||
username = login?.username.orEmpty(),
|
username = login?.username.orEmpty(),
|
||||||
password = login?.password.orEmpty(),
|
password = login?.password.orEmpty(),
|
||||||
totp = login?.totp,
|
totp = totpData?.uri ?: login?.totp,
|
||||||
canViewPassword = this.viewPassword,
|
canViewPassword = this.viewPassword,
|
||||||
canEditItem = this.edit,
|
canEditItem = this.edit,
|
||||||
uriList = login?.uris.toUriItems(),
|
uriList = login?.uris.toUriItems(),
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.x8bit.bitwarden.ui.vault.feature.addedit.util
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
||||||
|
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns pre-filled content that may be used for an "add" type
|
||||||
|
* [VaultAddEditState.ViewState.Content] during a TOTP creation event.
|
||||||
|
*/
|
||||||
|
fun TotpData.toDefaultAddTypeContent(
|
||||||
|
isIndividualVaultDisabled: Boolean,
|
||||||
|
): VaultAddEditState.ViewState.Content = VaultAddEditState.ViewState.Content(
|
||||||
|
common = VaultAddEditState.ViewState.Content.Common(
|
||||||
|
name = (this.issuer ?: this.accountName).orEmpty(),
|
||||||
|
),
|
||||||
|
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
||||||
|
type = VaultAddEditState.ViewState.Content.ItemType.Login(totp = this.uri),
|
||||||
|
)
|
|
@ -15,6 +15,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenMasterPasswordDialog
|
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenMasterPasswordDialog
|
||||||
|
@ -27,6 +28,7 @@ import com.x8bit.bitwarden.ui.platform.components.listitem.SelectionItemData
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.toIconResources
|
import com.x8bit.bitwarden.ui.platform.components.model.toIconResources
|
||||||
import com.x8bit.bitwarden.ui.platform.components.text.BitwardenPolicyWarningText
|
import com.x8bit.bitwarden.ui.platform.components.text.BitwardenPolicyWarningText
|
||||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||||
|
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
|
||||||
|
@ -38,6 +40,7 @@ import kotlinx.collections.immutable.toPersistentList
|
||||||
fun VaultItemListingContent(
|
fun VaultItemListingContent(
|
||||||
state: VaultItemListingState.ViewState.Content,
|
state: VaultItemListingState.ViewState.Content,
|
||||||
policyDisablesSend: Boolean,
|
policyDisablesSend: Boolean,
|
||||||
|
showAddTotpBanner: Boolean,
|
||||||
collectionClick: (id: String) -> Unit,
|
collectionClick: (id: String) -> Unit,
|
||||||
folderClick: (id: String) -> Unit,
|
folderClick: (id: String) -> Unit,
|
||||||
vaultItemClick: (id: String) -> Unit,
|
vaultItemClick: (id: String) -> Unit,
|
||||||
|
@ -100,8 +103,23 @@ fun VaultItemListingContent(
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
|
item {
|
||||||
|
if (showAddTotpBanner) {
|
||||||
|
Spacer(modifier = Modifier.height(height = 12.dp))
|
||||||
|
BitwardenPolicyWarningText(
|
||||||
|
text = stringResource(id = R.string.add_this_authenticator_key_to_a_login),
|
||||||
|
style = BitwardenTheme.typography.bodyMedium,
|
||||||
|
textAlign = TextAlign.Start,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
if (policyDisablesSend) {
|
if (policyDisablesSend) {
|
||||||
|
Spacer(modifier = Modifier.height(height = 12.dp))
|
||||||
BitwardenPolicyWarningText(
|
BitwardenPolicyWarningText(
|
||||||
text = stringResource(id = R.string.send_disabled_warning),
|
text = stringResource(id = R.string.send_disabled_warning),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.x8bit.bitwarden.ui.vault.feature.itemlisting
|
package com.x8bit.bitwarden.ui.vault.feature.itemlisting
|
||||||
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
@ -45,11 +46,13 @@ import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.rememberBitwardenPullToRefreshState
|
import com.x8bit.bitwarden.ui.platform.components.scaffold.rememberBitwardenPullToRefreshState
|
||||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||||
import com.x8bit.bitwarden.ui.platform.composition.LocalBiometricsManager
|
import com.x8bit.bitwarden.ui.platform.composition.LocalBiometricsManager
|
||||||
|
import com.x8bit.bitwarden.ui.platform.composition.LocalExitManager
|
||||||
import com.x8bit.bitwarden.ui.platform.composition.LocalFido2CompletionManager
|
import com.x8bit.bitwarden.ui.platform.composition.LocalFido2CompletionManager
|
||||||
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.PinInputDialog
|
import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.PinInputDialog
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricsManager
|
import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricsManager
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.handlers.VaultItemListingHandlers
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.handlers.VaultItemListingHandlers
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.handlers.VaultItemListingUserVerificationHandlers
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.handlers.VaultItemListingUserVerificationHandlers
|
||||||
|
@ -74,6 +77,7 @@ fun VaultItemListingScreen(
|
||||||
onNavigateToEditSendItem: (sendId: String) -> Unit,
|
onNavigateToEditSendItem: (sendId: String) -> Unit,
|
||||||
onNavigateToSearch: (searchType: SearchType) -> Unit,
|
onNavigateToSearch: (searchType: SearchType) -> Unit,
|
||||||
intentManager: IntentManager = LocalIntentManager.current,
|
intentManager: IntentManager = LocalIntentManager.current,
|
||||||
|
exitManager: ExitManager = LocalExitManager.current,
|
||||||
fido2CompletionManager: Fido2CompletionManager = LocalFido2CompletionManager.current,
|
fido2CompletionManager: Fido2CompletionManager = LocalFido2CompletionManager.current,
|
||||||
biometricsManager: BiometricsManager = LocalBiometricsManager.current,
|
biometricsManager: BiometricsManager = LocalBiometricsManager.current,
|
||||||
viewModel: VaultItemListingViewModel = hiltViewModel(),
|
viewModel: VaultItemListingViewModel = hiltViewModel(),
|
||||||
|
@ -168,6 +172,8 @@ fun VaultItemListingScreen(
|
||||||
is VaultItemListingEvent.CompleteFido2GetCredentialsRequest -> {
|
is VaultItemListingEvent.CompleteFido2GetCredentialsRequest -> {
|
||||||
fido2CompletionManager.completeFido2GetCredentialRequest(event.result)
|
fido2CompletionManager.completeFido2GetCredentialRequest(event.result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VaultItemListingEvent.ExitApp -> exitManager.exitApplication()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,12 +258,15 @@ fun VaultItemListingScreen(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val vaultItemListingHandlers = remember(viewModel) {
|
||||||
|
VaultItemListingHandlers.create(viewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
BackHandler(onBack = vaultItemListingHandlers.backClick)
|
||||||
VaultItemListingScaffold(
|
VaultItemListingScaffold(
|
||||||
state = state,
|
state = state,
|
||||||
pullToRefreshState = pullToRefreshState,
|
pullToRefreshState = pullToRefreshState,
|
||||||
vaultItemListingHandlers = remember(viewModel) {
|
vaultItemListingHandlers = vaultItemListingHandlers,
|
||||||
VaultItemListingHandlers.create(viewModel)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,6 +460,7 @@ private fun VaultItemListingScaffold(
|
||||||
is VaultItemListingState.ViewState.Content -> {
|
is VaultItemListingState.ViewState.Content -> {
|
||||||
VaultItemListingContent(
|
VaultItemListingContent(
|
||||||
state = state.viewState,
|
state = state.viewState,
|
||||||
|
showAddTotpBanner = state.isTotp,
|
||||||
policyDisablesSend = state.policyDisablesSend &&
|
policyDisablesSend = state.policyDisablesSend &&
|
||||||
state.itemListingType is VaultItemListingState.ItemListingType.Send,
|
state.itemListingType is VaultItemListingState.ItemListingType.Send,
|
||||||
vaultItemClick = vaultItemListingHandlers.itemClick,
|
vaultItemClick = vaultItemListingHandlers.itemClick,
|
||||||
|
|
|
@ -33,6 +33,7 @@ import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSelectionDataOrN
|
||||||
import com.x8bit.bitwarden.data.platform.manager.util.toFido2AssertionRequestOrNull
|
import com.x8bit.bitwarden.data.platform.manager.util.toFido2AssertionRequestOrNull
|
||||||
import com.x8bit.bitwarden.data.platform.manager.util.toFido2GetCredentialsRequestOrNull
|
import com.x8bit.bitwarden.data.platform.manager.util.toFido2GetCredentialsRequestOrNull
|
||||||
import com.x8bit.bitwarden.data.platform.manager.util.toFido2RequestOrNull
|
import com.x8bit.bitwarden.data.platform.manager.util.toFido2RequestOrNull
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.util.toTotpDataOrNull
|
||||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||||
|
@ -57,7 +58,9 @@ import com.x8bit.bitwarden.ui.platform.base.util.toHostOrPathOrNull
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.IconRes
|
import com.x8bit.bitwarden.ui.platform.components.model.IconRes
|
||||||
|
import com.x8bit.bitwarden.ui.platform.feature.search.SearchTypeData
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||||
|
import com.x8bit.bitwarden.ui.platform.feature.search.util.filterAndOrganize
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.determineListingPredicate
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.determineListingPredicate
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toItemListingType
|
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toItemListingType
|
||||||
|
@ -69,6 +72,7 @@ import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummaries
|
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummaries
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toActiveAccountSummary
|
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toActiveAccountSummary
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toFilteredList
|
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toFilteredList
|
||||||
|
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType
|
import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
@ -127,6 +131,7 @@ class VaultItemListingViewModel @Inject constructor(
|
||||||
.any(),
|
.any(),
|
||||||
autofillSelectionData = specialCircumstance?.toAutofillSelectionDataOrNull(),
|
autofillSelectionData = specialCircumstance?.toAutofillSelectionDataOrNull(),
|
||||||
hasMasterPassword = userState.activeAccount.hasMasterPassword,
|
hasMasterPassword = userState.activeAccount.hasMasterPassword,
|
||||||
|
totpData = specialCircumstance?.toTotpDataOrNull(),
|
||||||
fido2CredentialRequest = fido2CredentialRequest,
|
fido2CredentialRequest = fido2CredentialRequest,
|
||||||
fido2CredentialAssertionRequest = specialCircumstance?.toFido2AssertionRequestOrNull(),
|
fido2CredentialAssertionRequest = specialCircumstance?.toFido2AssertionRequestOrNull(),
|
||||||
fido2GetCredentialsRequest = specialCircumstance?.toFido2GetCredentialsRequestOrNull(),
|
fido2GetCredentialsRequest = specialCircumstance?.toFido2GetCredentialsRequestOrNull(),
|
||||||
|
@ -184,7 +189,8 @@ class VaultItemListingViewModel @Inject constructor(
|
||||||
it
|
it
|
||||||
.filterForAutofillIfNecessary()
|
.filterForAutofillIfNecessary()
|
||||||
.filterForFido2CreationIfNecessary()
|
.filterForFido2CreationIfNecessary()
|
||||||
.filterForFidoGetCredentialsIfNecessary(),
|
.filterForFidoGetCredentialsIfNecessary()
|
||||||
|
.filterForTotpIfNecessary(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.onEach(::sendAction)
|
.onEach(::sendAction)
|
||||||
|
@ -560,6 +566,10 @@ class VaultItemListingViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
state.totpData?.let {
|
||||||
|
sendEvent(VaultItemListingEvent.NavigateToEditCipher(cipherId = action.id))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (state.isFido2Creation) {
|
if (state.isFido2Creation) {
|
||||||
handleFido2RegistrationRequestReceive(action)
|
handleFido2RegistrationRequestReceive(action)
|
||||||
|
@ -832,7 +842,11 @@ class VaultItemListingViewModel @Inject constructor(
|
||||||
|
|
||||||
private fun handleBackClick() {
|
private fun handleBackClick() {
|
||||||
sendEvent(
|
sendEvent(
|
||||||
event = VaultItemListingEvent.NavigateBack,
|
event = if (state.isTotp) {
|
||||||
|
VaultItemListingEvent.ExitApp
|
||||||
|
} else {
|
||||||
|
VaultItemListingEvent.NavigateBack
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1639,6 +1653,22 @@ class VaultItemListingViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes the given vault data and filters it for totp data.
|
||||||
|
*/
|
||||||
|
private fun DataState<VaultData>.filterForTotpIfNecessary(): DataState<VaultData> {
|
||||||
|
val totpData = state.totpData ?: return this
|
||||||
|
val query = totpData.issuer ?: totpData.accountName ?: return this
|
||||||
|
return this.map { vaultData ->
|
||||||
|
vaultData.copy(
|
||||||
|
cipherViewList = vaultData.cipherViewList.filterAndOrganize(
|
||||||
|
searchTypeData = SearchTypeData.Vault.Logins,
|
||||||
|
searchTerm = query,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt and filter the fido 2 autofill credentials.
|
* Decrypt and filter the fido 2 autofill credentials.
|
||||||
*/
|
*/
|
||||||
|
@ -1687,6 +1717,7 @@ data class VaultItemListingState(
|
||||||
val policyDisablesSend: Boolean,
|
val policyDisablesSend: Boolean,
|
||||||
// Internal
|
// Internal
|
||||||
private val isPullToRefreshSettingEnabled: Boolean,
|
private val isPullToRefreshSettingEnabled: Boolean,
|
||||||
|
val totpData: TotpData? = null,
|
||||||
val autofillSelectionData: AutofillSelectionData? = null,
|
val autofillSelectionData: AutofillSelectionData? = null,
|
||||||
val fido2CredentialRequest: Fido2CredentialRequest? = null,
|
val fido2CredentialRequest: Fido2CredentialRequest? = null,
|
||||||
val fido2CredentialAssertionRequest: Fido2CredentialAssertionRequest? = null,
|
val fido2CredentialAssertionRequest: Fido2CredentialAssertionRequest? = null,
|
||||||
|
@ -1707,6 +1738,11 @@ data class VaultItemListingState(
|
||||||
val isFido2Creation: Boolean
|
val isFido2Creation: Boolean
|
||||||
get() = fido2CredentialRequest != null
|
get() = fido2CredentialRequest != null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not this represents a listing screen for totp.
|
||||||
|
*/
|
||||||
|
val isTotp: Boolean get() = totpData != null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A displayable title for the AppBar.
|
* A displayable title for the AppBar.
|
||||||
*/
|
*/
|
||||||
|
@ -1719,6 +1755,7 @@ data class VaultItemListingState(
|
||||||
?.callingAppInfo
|
?.callingAppInfo
|
||||||
?.getFido2RpIdOrNull()
|
?.getFido2RpIdOrNull()
|
||||||
?.let { R.string.items_for_uri.asText(it) }
|
?.let { R.string.items_for_uri.asText(it) }
|
||||||
|
?: totpData?.let { R.string.items_for_uri.asText(it.issuer ?: it.accountName ?: "--") }
|
||||||
?: itemListingType.titleText
|
?: itemListingType.titleText
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1730,17 +1767,17 @@ data class VaultItemListingState(
|
||||||
/**
|
/**
|
||||||
* Whether or not the account switcher should be shown.
|
* Whether or not the account switcher should be shown.
|
||||||
*/
|
*/
|
||||||
val shouldShowAccountSwitcher: Boolean get() = isAutofill || isFido2Creation
|
val shouldShowAccountSwitcher: Boolean get() = isAutofill || isFido2Creation || isTotp
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the navigation icon should be shown.
|
* Whether or not the navigation icon should be shown.
|
||||||
*/
|
*/
|
||||||
val shouldShowNavigationIcon: Boolean get() = !isAutofill && !isFido2Creation
|
val shouldShowNavigationIcon: Boolean get() = !isAutofill && !isFido2Creation && !isTotp
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the overflow menu should be shown.
|
* Whether or not the overflow menu should be shown.
|
||||||
*/
|
*/
|
||||||
val shouldShowOverflowMenu: Boolean get() = !isAutofill && !isFido2Creation
|
val shouldShowOverflowMenu: Boolean get() = !isAutofill && !isFido2Creation && !isTotp
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the current state of any dialogs on the screen.
|
* Represents the current state of any dialogs on the screen.
|
||||||
|
@ -2081,6 +2118,11 @@ data class VaultItemListingState(
|
||||||
* Models events for the [VaultItemListingScreen].
|
* Models events for the [VaultItemListingScreen].
|
||||||
*/
|
*/
|
||||||
sealed class VaultItemListingEvent {
|
sealed class VaultItemListingEvent {
|
||||||
|
/**
|
||||||
|
* Closes the app.
|
||||||
|
*/
|
||||||
|
data object ExitApp : VaultItemListingEvent()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigates to the Create Account screen.
|
* Navigates to the Create Account screen.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -605,6 +605,7 @@ Scanning will happen automatically.</string>
|
||||||
<string name="thirty_days">30 days</string>
|
<string name="thirty_days">30 days</string>
|
||||||
<string name="custom">Custom</string>
|
<string name="custom">Custom</string>
|
||||||
<string name="share_on_save">Share this Send upon save</string>
|
<string name="share_on_save">Share this Send upon save</string>
|
||||||
|
<string name="add_this_authenticator_key_to_a_login">Add this authenticator key to an existing login, or create a new login.</string>
|
||||||
<string name="send_disabled_warning">Due to an enterprise policy, you are only able to delete an existing Send.</string>
|
<string name="send_disabled_warning">Due to an enterprise policy, you are only able to delete an existing Send.</string>
|
||||||
<string name="about_send">About Send</string>
|
<string name="about_send">About Send</string>
|
||||||
<string name="hide_email">Hide my email address from recipients</string>
|
<string name="hide_email">Hide my email address from recipients</string>
|
||||||
|
|
|
@ -62,6 +62,7 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.model.toCustomField
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.createMockPasskeyAttestationOptions
|
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.createMockPasskeyAttestationOptions
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toDefaultAddTypeContent
|
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toDefaultAddTypeContent
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toViewState
|
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toViewState
|
||||||
|
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||||
|
@ -699,6 +700,60 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `in add mode during totp fill, SaveClick should show dialog, remove it once an item is saved, and emit ExitApp`() =
|
||||||
|
runTest {
|
||||||
|
val totpData = TotpData(
|
||||||
|
uri = "otpauth://totp/issuer:accountName?secret=secret",
|
||||||
|
issuer = "issuer",
|
||||||
|
accountName = "accountName",
|
||||||
|
secret = "secret",
|
||||||
|
digits = 6,
|
||||||
|
period = 30,
|
||||||
|
algorithm = TotpData.CryptoHashAlgorithm.SHA_1,
|
||||||
|
)
|
||||||
|
specialCircumstanceManager.specialCircumstance =
|
||||||
|
SpecialCircumstance.AddTotpLoginItem(data = totpData)
|
||||||
|
val stateWithDialog = createVaultAddItemState(
|
||||||
|
vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN),
|
||||||
|
dialogState = VaultAddEditState.DialogState.Loading(R.string.saving.asText()),
|
||||||
|
commonContentViewState = createCommonContentViewState(name = "issuer"),
|
||||||
|
totpData = totpData,
|
||||||
|
shouldExitOnSave = true,
|
||||||
|
)
|
||||||
|
val stateWithName = createVaultAddItemState(
|
||||||
|
vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN),
|
||||||
|
commonContentViewState = createCommonContentViewState(name = "issuer"),
|
||||||
|
totpData = totpData,
|
||||||
|
shouldExitOnSave = true,
|
||||||
|
)
|
||||||
|
mutableVaultDataFlow.value = DataState.Loaded(createVaultData())
|
||||||
|
val viewModel = createAddVaultItemViewModel(
|
||||||
|
savedStateHandle = createSavedStateHandleWithState(
|
||||||
|
state = stateWithName,
|
||||||
|
vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
coEvery {
|
||||||
|
vaultRepository.createCipherInOrganization(any(), any())
|
||||||
|
} returns CreateCipherResult.Success
|
||||||
|
|
||||||
|
viewModel.stateEventFlow(backgroundScope) { stateTurbine, eventTurbine ->
|
||||||
|
viewModel.trySendAction(VaultAddEditAction.Common.SaveClick)
|
||||||
|
|
||||||
|
assertEquals(stateWithName, stateTurbine.awaitItem())
|
||||||
|
assertEquals(stateWithDialog, stateTurbine.awaitItem())
|
||||||
|
assertEquals(stateWithName, stateTurbine.awaitItem())
|
||||||
|
|
||||||
|
assertEquals(VaultAddEditEvent.ExitApp, eventTurbine.awaitItem())
|
||||||
|
}
|
||||||
|
assertNull(specialCircumstanceManager.specialCircumstance)
|
||||||
|
coVerify(exactly = 1) {
|
||||||
|
vaultRepository.createCipherInOrganization(any(), any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `in add mode during fido2 registration, SaveClick should show saving dialog, and request user verification when required`() =
|
fun `in add mode during fido2 registration, SaveClick should show saving dialog, and request user verification when required`() =
|
||||||
|
@ -1204,6 +1259,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
cipherView.toViewState(
|
cipherView.toViewState(
|
||||||
isClone = false,
|
isClone = false,
|
||||||
isIndividualVaultDisabled = false,
|
isIndividualVaultDisabled = false,
|
||||||
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
)
|
)
|
||||||
|
@ -1234,6 +1290,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
cipherView.toViewState(
|
cipherView.toViewState(
|
||||||
isClone = false,
|
isClone = false,
|
||||||
isIndividualVaultDisabled = false,
|
isIndividualVaultDisabled = false,
|
||||||
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
)
|
)
|
||||||
|
@ -1267,6 +1324,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
cipherView.toViewState(
|
cipherView.toViewState(
|
||||||
isClone = false,
|
isClone = false,
|
||||||
isIndividualVaultDisabled = false,
|
isIndividualVaultDisabled = false,
|
||||||
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
)
|
)
|
||||||
|
@ -1328,6 +1386,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
cipherView.toViewState(
|
cipherView.toViewState(
|
||||||
isClone = false,
|
isClone = false,
|
||||||
isIndividualVaultDisabled = false,
|
isIndividualVaultDisabled = false,
|
||||||
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
)
|
)
|
||||||
|
@ -1392,6 +1451,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
cipherView.toViewState(
|
cipherView.toViewState(
|
||||||
isClone = false,
|
isClone = false,
|
||||||
isIndividualVaultDisabled = false,
|
isIndividualVaultDisabled = false,
|
||||||
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
)
|
)
|
||||||
|
@ -1447,6 +1507,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
cipherView.toViewState(
|
cipherView.toViewState(
|
||||||
isClone = false,
|
isClone = false,
|
||||||
isIndividualVaultDisabled = false,
|
isIndividualVaultDisabled = false,
|
||||||
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
)
|
)
|
||||||
|
@ -1507,6 +1568,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
cipherView.toViewState(
|
cipherView.toViewState(
|
||||||
isClone = false,
|
isClone = false,
|
||||||
isIndividualVaultDisabled = false,
|
isIndividualVaultDisabled = false,
|
||||||
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = fixedClock,
|
clock = fixedClock,
|
||||||
)
|
)
|
||||||
|
@ -3737,13 +3799,15 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
//region Helper functions
|
//region Helper functions
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength", "LongParameterList")
|
||||||
private fun createVaultAddItemState(
|
private fun createVaultAddItemState(
|
||||||
vaultAddEditType: VaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN),
|
vaultAddEditType: VaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN),
|
||||||
commonContentViewState: VaultAddEditState.ViewState.Content.Common = createCommonContentViewState(),
|
commonContentViewState: VaultAddEditState.ViewState.Content.Common = createCommonContentViewState(),
|
||||||
isIndividualVaultDisabled: Boolean = false,
|
isIndividualVaultDisabled: Boolean = false,
|
||||||
|
shouldExitOnSave: Boolean = false,
|
||||||
typeContentViewState: VaultAddEditState.ViewState.Content.ItemType = createLoginTypeContentViewState(),
|
typeContentViewState: VaultAddEditState.ViewState.Content.ItemType = createLoginTypeContentViewState(),
|
||||||
dialogState: VaultAddEditState.DialogState? = null,
|
dialogState: VaultAddEditState.DialogState? = null,
|
||||||
|
totpData: TotpData? = null,
|
||||||
): VaultAddEditState =
|
): VaultAddEditState =
|
||||||
VaultAddEditState(
|
VaultAddEditState(
|
||||||
vaultAddEditType = vaultAddEditType,
|
vaultAddEditType = vaultAddEditType,
|
||||||
|
@ -3753,6 +3817,8 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
type = typeContentViewState,
|
type = typeContentViewState,
|
||||||
),
|
),
|
||||||
dialog = dialogState,
|
dialog = dialogState,
|
||||||
|
shouldExitOnSave = shouldExitOnSave,
|
||||||
|
totpData = totpData,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
|
|
|
@ -70,6 +70,7 @@ class CipherViewExtensionsTest {
|
||||||
val result = cipherView.toViewState(
|
val result = cipherView.toViewState(
|
||||||
isClone = false,
|
isClone = false,
|
||||||
isIndividualVaultDisabled = false,
|
isIndividualVaultDisabled = false,
|
||||||
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = FIXED_CLOCK,
|
clock = FIXED_CLOCK,
|
||||||
)
|
)
|
||||||
|
@ -115,6 +116,7 @@ class CipherViewExtensionsTest {
|
||||||
val result = cipherView.toViewState(
|
val result = cipherView.toViewState(
|
||||||
isClone = false,
|
isClone = false,
|
||||||
isIndividualVaultDisabled = true,
|
isIndividualVaultDisabled = true,
|
||||||
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = FIXED_CLOCK,
|
clock = FIXED_CLOCK,
|
||||||
)
|
)
|
||||||
|
@ -165,6 +167,7 @@ class CipherViewExtensionsTest {
|
||||||
val result = cipherView.toViewState(
|
val result = cipherView.toViewState(
|
||||||
isClone = false,
|
isClone = false,
|
||||||
isIndividualVaultDisabled = false,
|
isIndividualVaultDisabled = false,
|
||||||
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = FIXED_CLOCK,
|
clock = FIXED_CLOCK,
|
||||||
)
|
)
|
||||||
|
@ -214,6 +217,66 @@ class CipherViewExtensionsTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `toViewState should create a Login ViewState with a predefined totp`() {
|
||||||
|
val totp = "otpauth://totp/alice@google.com?secret=JBSWY3DPEHPK3PXP"
|
||||||
|
val cipherView = DEFAULT_LOGIN_CIPHER_VIEW.copy(
|
||||||
|
login = DEFAULT_LOGIN_CIPHER_VIEW.login?.copy(totp = null),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = cipherView.toViewState(
|
||||||
|
isClone = false,
|
||||||
|
isIndividualVaultDisabled = false,
|
||||||
|
totpData = mockk { every { uri } returns totp },
|
||||||
|
resourceManager = resourceManager,
|
||||||
|
clock = FIXED_CLOCK,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
VaultAddEditState.ViewState.Content(
|
||||||
|
common = VaultAddEditState.ViewState.Content.Common(
|
||||||
|
originalCipher = cipherView,
|
||||||
|
name = "cipher",
|
||||||
|
favorite = false,
|
||||||
|
masterPasswordReprompt = true,
|
||||||
|
notes = "Lots of notes",
|
||||||
|
availableFolders = emptyList(),
|
||||||
|
availableOwners = emptyList(),
|
||||||
|
customFieldData = listOf(
|
||||||
|
VaultAddEditState.Custom.BooleanField(TEST_ID, "TestBoolean", false),
|
||||||
|
VaultAddEditState.Custom.TextField(TEST_ID, "TestText", "TestText"),
|
||||||
|
VaultAddEditState.Custom.HiddenField(TEST_ID, "TestHidden", "TestHidden"),
|
||||||
|
VaultAddEditState.Custom.LinkedField(
|
||||||
|
TEST_ID,
|
||||||
|
"TestLinked",
|
||||||
|
VaultLinkedFieldType.USERNAME,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
isIndividualVaultDisabled = false,
|
||||||
|
type = VaultAddEditState.ViewState.Content.ItemType.Login(
|
||||||
|
username = "username",
|
||||||
|
password = "password",
|
||||||
|
uriList = listOf(
|
||||||
|
UriItem(
|
||||||
|
id = TEST_ID,
|
||||||
|
uri = "www.example.com",
|
||||||
|
match = null,
|
||||||
|
checksum = null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
totp = totp,
|
||||||
|
canViewPassword = false,
|
||||||
|
fido2CredentialCreationDateTime = R.string.created_xy.asText(
|
||||||
|
"10/27/23",
|
||||||
|
"12:00 PM",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `toViewState should create a Secure Notes ViewState`() {
|
fun `toViewState should create a Secure Notes ViewState`() {
|
||||||
val cipherView = DEFAULT_SECURE_NOTES_CIPHER_VIEW
|
val cipherView = DEFAULT_SECURE_NOTES_CIPHER_VIEW
|
||||||
|
@ -221,6 +284,7 @@ class CipherViewExtensionsTest {
|
||||||
val result = cipherView.toViewState(
|
val result = cipherView.toViewState(
|
||||||
isClone = false,
|
isClone = false,
|
||||||
isIndividualVaultDisabled = true,
|
isIndividualVaultDisabled = true,
|
||||||
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = FIXED_CLOCK,
|
clock = FIXED_CLOCK,
|
||||||
)
|
)
|
||||||
|
@ -255,6 +319,7 @@ class CipherViewExtensionsTest {
|
||||||
val result = cipherView.toViewState(
|
val result = cipherView.toViewState(
|
||||||
isClone = true,
|
isClone = true,
|
||||||
isIndividualVaultDisabled = false,
|
isIndividualVaultDisabled = false,
|
||||||
|
totpData = null,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
clock = FIXED_CLOCK,
|
clock = FIXED_CLOCK,
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.x8bit.bitwarden.ui.vault.feature.addedit.util
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
||||||
|
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.unmockkStatic
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
class TotpDataExtensionsTest {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
mockkStatic(UUID::class)
|
||||||
|
every { UUID.randomUUID().toString() } returns "uuid"
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkStatic(UUID::class)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `toDefaultAddTypeContent should return the correct content with totp data`() {
|
||||||
|
val uri = "otpauth://totp/issuer:accountName?secret=secret"
|
||||||
|
val totpData = TotpData(
|
||||||
|
uri = uri,
|
||||||
|
issuer = "issuer",
|
||||||
|
accountName = "accountName",
|
||||||
|
secret = "secret",
|
||||||
|
digits = 6,
|
||||||
|
period = 30,
|
||||||
|
algorithm = TotpData.CryptoHashAlgorithm.SHA_1,
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
VaultAddEditState.ViewState.Content(
|
||||||
|
common = VaultAddEditState.ViewState.Content.Common(name = "issuer"),
|
||||||
|
isIndividualVaultDisabled = false,
|
||||||
|
type = VaultAddEditState.ViewState.Content.ItemType.Login(totp = uri),
|
||||||
|
),
|
||||||
|
totpData.toDefaultAddTypeContent(isIndividualVaultDisabled = false),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,6 +35,7 @@ import com.x8bit.bitwarden.ui.platform.components.model.IconData
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.IconRes
|
import com.x8bit.bitwarden.ui.platform.components.model.IconRes
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricsManager
|
import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricsManager
|
||||||
|
import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||||
import com.x8bit.bitwarden.ui.util.assertLockOrLogoutDialogIsDisplayed
|
import com.x8bit.bitwarden.ui.util.assertLockOrLogoutDialogIsDisplayed
|
||||||
import com.x8bit.bitwarden.ui.util.assertLogoutConfirmationDialogIsDisplayed
|
import com.x8bit.bitwarden.ui.util.assertLogoutConfirmationDialogIsDisplayed
|
||||||
|
@ -84,6 +85,9 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
private var onNavigateToSearchType: SearchType? = null
|
private var onNavigateToSearchType: SearchType? = null
|
||||||
private var onNavigateToVaultItemListingScreenType: VaultItemListingType? = null
|
private var onNavigateToVaultItemListingScreenType: VaultItemListingType? = null
|
||||||
|
|
||||||
|
private val exitManager: ExitManager = mockk {
|
||||||
|
every { exitApplication() } just runs
|
||||||
|
}
|
||||||
private val intentManager: IntentManager = mockk {
|
private val intentManager: IntentManager = mockk {
|
||||||
every { shareText(any()) } just runs
|
every { shareText(any()) } just runs
|
||||||
every { launchUri(any()) } just runs
|
every { launchUri(any()) } just runs
|
||||||
|
@ -105,9 +109,10 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
mockkStatic(String::toHostOrPathOrNull)
|
mockkStatic(String::toHostOrPathOrNull)
|
||||||
every { AUTOFILL_SELECTION_DATA.uri?.toHostOrPathOrNull() } returns "www.test.com"
|
every { AUTOFILL_SELECTION_DATA.uri?.toHostOrPathOrNull() } returns "www.test.com"
|
||||||
composeTestRule.setContent {
|
setContentWithBackDispatcher {
|
||||||
VaultItemListingScreen(
|
VaultItemListingScreen(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
|
exitManager = exitManager,
|
||||||
intentManager = intentManager,
|
intentManager = intentManager,
|
||||||
fido2CompletionManager = fido2CompletionManager,
|
fido2CompletionManager = fido2CompletionManager,
|
||||||
biometricsManager = biometricsManager,
|
biometricsManager = biometricsManager,
|
||||||
|
@ -339,6 +344,23 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
||||||
assertTrue(onNavigateBackCalled)
|
assertTrue(onNavigateBackCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ExitApp event should invoke exitApplication`() {
|
||||||
|
mutableEventFlow.tryEmit(VaultItemListingEvent.ExitApp)
|
||||||
|
verify(exactly = 1) {
|
||||||
|
exitManager.exitApplication()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `back gesture should send BackClick action`() {
|
||||||
|
backDispatcher?.onBackPressed()
|
||||||
|
|
||||||
|
verify(exactly = 1) {
|
||||||
|
viewModel.trySendAction(VaultItemListingsAction.BackClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking back button should send BackClick action`() {
|
fun `clicking back button should send BackClick action`() {
|
||||||
composeTestRule
|
composeTestRule
|
||||||
|
|
|
@ -70,6 +70,7 @@ import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.createMockDisplayIt
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummaries
|
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummaries
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toActiveAccountSummary
|
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toActiveAccountSummary
|
||||||
|
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType
|
import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||||
import io.mockk.Ordering
|
import io.mockk.Ordering
|
||||||
|
@ -259,6 +260,26 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
verify { authRepository.switchAccount(userId = updatedUserId) }
|
verify { authRepository.switchAccount(userId = updatedUserId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `BackClick with TotpData should emit ExitApp`() = runTest {
|
||||||
|
val totpData = TotpData(
|
||||||
|
uri = "otpauth://totp/issuer:accountName?secret=secret",
|
||||||
|
issuer = "issuer",
|
||||||
|
accountName = "accountName",
|
||||||
|
secret = "secret",
|
||||||
|
digits = 6,
|
||||||
|
period = 30,
|
||||||
|
algorithm = TotpData.CryptoHashAlgorithm.SHA_1,
|
||||||
|
)
|
||||||
|
specialCircumstanceManager.specialCircumstance =
|
||||||
|
SpecialCircumstance.AddTotpLoginItem(data = totpData)
|
||||||
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
viewModel.trySendAction(VaultItemListingsAction.BackClick)
|
||||||
|
assertEquals(VaultItemListingEvent.ExitApp, awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `BackClick should emit NavigateBack`() = runTest {
|
fun `BackClick should emit NavigateBack`() = runTest {
|
||||||
val viewModel = createVaultItemListingViewModel()
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
@ -1400,6 +1421,61 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `vaultDataStateFlow Loaded with items and totp data filtering should update ViewState to Content with filtered data`() =
|
||||||
|
runTest {
|
||||||
|
setupMockUri()
|
||||||
|
|
||||||
|
val uri = "otpauth://totp/issuer:accountName?secret=secret"
|
||||||
|
val totpData = TotpData(
|
||||||
|
uri = uri,
|
||||||
|
issuer = null,
|
||||||
|
accountName = "Name-1",
|
||||||
|
secret = "secret",
|
||||||
|
digits = 6,
|
||||||
|
period = 30,
|
||||||
|
algorithm = TotpData.CryptoHashAlgorithm.SHA_1,
|
||||||
|
)
|
||||||
|
val cipherView1 = createMockCipherView(number = 1)
|
||||||
|
val cipherView2 = createMockCipherView(number = 2)
|
||||||
|
|
||||||
|
// Filtering comes later, so we return everything here
|
||||||
|
mockFilteredCiphers = listOf(cipherView1, cipherView2)
|
||||||
|
|
||||||
|
specialCircumstanceManager.specialCircumstance =
|
||||||
|
SpecialCircumstance.AddTotpLoginItem(data = totpData)
|
||||||
|
val dataState = DataState.Loaded(
|
||||||
|
data = VaultData(
|
||||||
|
cipherViewList = listOf(cipherView1, cipherView2),
|
||||||
|
folderViewList = listOf(createMockFolderView(number = 1)),
|
||||||
|
collectionViewList = listOf(createMockCollectionView(number = 1)),
|
||||||
|
sendViewList = listOf(createMockSendView(number = 1)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
|
||||||
|
mutableVaultDataStateFlow.value = dataState
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
createVaultItemListingState(
|
||||||
|
viewState = VaultItemListingState.ViewState.Content(
|
||||||
|
displayCollectionList = emptyList(),
|
||||||
|
displayItemList = listOf(
|
||||||
|
createMockDisplayItemForCipher(
|
||||||
|
number = 1,
|
||||||
|
secondSubtitleTestTag = "PasskeySite",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
displayFolderList = emptyList(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.copy(totpData = totpData),
|
||||||
|
viewModel.stateFlow.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `vaultDataStateFlow Loaded with items and fido2 filtering should update ViewState to Content with filtered data`() =
|
fun `vaultDataStateFlow Loaded with items and fido2 filtering should update ViewState to Content with filtered data`() =
|
||||||
|
@ -3887,7 +3963,6 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
private fun createVaultItemListingViewModel(
|
private fun createVaultItemListingViewModel(
|
||||||
savedStateHandle: SavedStateHandle = initialSavedStateHandle,
|
savedStateHandle: SavedStateHandle = initialSavedStateHandle,
|
||||||
vaultRepository: VaultRepository = this.vaultRepository,
|
|
||||||
): VaultItemListingViewModel =
|
): VaultItemListingViewModel =
|
||||||
VaultItemListingViewModel(
|
VaultItemListingViewModel(
|
||||||
savedStateHandle = savedStateHandle,
|
savedStateHandle = savedStateHandle,
|
||||||
|
@ -3922,6 +3997,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
isIconLoadingDisabled = settingsRepository.isIconLoadingDisabled,
|
isIconLoadingDisabled = settingsRepository.isIconLoadingDisabled,
|
||||||
isPullToRefreshSettingEnabled = false,
|
isPullToRefreshSettingEnabled = false,
|
||||||
dialogState = null,
|
dialogState = null,
|
||||||
|
totpData = null,
|
||||||
autofillSelectionData = null,
|
autofillSelectionData = null,
|
||||||
policyDisablesSend = false,
|
policyDisablesSend = false,
|
||||||
hasMasterPassword = true,
|
hasMasterPassword = true,
|
||||||
|
|
|
@ -16,6 +16,7 @@ fun createMockDisplayItemForCipher(
|
||||||
number: Int,
|
number: Int,
|
||||||
cipherType: CipherType = CipherType.LOGIN,
|
cipherType: CipherType = CipherType.LOGIN,
|
||||||
subtitle: String? = "mockUsername-$number",
|
subtitle: String? = "mockUsername-$number",
|
||||||
|
secondSubtitleTestTag: String? = null,
|
||||||
requiresPasswordReprompt: Boolean = true,
|
requiresPasswordReprompt: Boolean = true,
|
||||||
): VaultItemListingState.DisplayItem =
|
): VaultItemListingState.DisplayItem =
|
||||||
when (cipherType) {
|
when (cipherType) {
|
||||||
|
@ -25,11 +26,11 @@ fun createMockDisplayItemForCipher(
|
||||||
title = "mockName-$number",
|
title = "mockName-$number",
|
||||||
titleTestTag = "CipherNameLabel",
|
titleTestTag = "CipherNameLabel",
|
||||||
secondSubtitle = null,
|
secondSubtitle = null,
|
||||||
secondSubtitleTestTag = null,
|
secondSubtitleTestTag = secondSubtitleTestTag,
|
||||||
subtitle = subtitle,
|
subtitle = subtitle,
|
||||||
subtitleTestTag = "CipherSubTitleLabel",
|
subtitleTestTag = "CipherSubTitleLabel",
|
||||||
iconData = IconData.Network(
|
iconData = IconData.Network(
|
||||||
"https://vault.bitwarden.com/icons/www.mockuri.com/icon.png",
|
uri = "https://vault.bitwarden.com/icons/www.mockuri.com/icon.png",
|
||||||
fallbackIconRes = R.drawable.ic_globe,
|
fallbackIconRes = R.drawable.ic_globe,
|
||||||
),
|
),
|
||||||
extraIconList = listOf(
|
extraIconList = listOf(
|
||||||
|
@ -79,7 +80,7 @@ fun createMockDisplayItemForCipher(
|
||||||
title = "mockName-$number",
|
title = "mockName-$number",
|
||||||
titleTestTag = "CipherNameLabel",
|
titleTestTag = "CipherNameLabel",
|
||||||
secondSubtitle = null,
|
secondSubtitle = null,
|
||||||
secondSubtitleTestTag = null,
|
secondSubtitleTestTag = secondSubtitleTestTag,
|
||||||
subtitle = subtitle,
|
subtitle = subtitle,
|
||||||
subtitleTestTag = "CipherSubTitleLabel",
|
subtitleTestTag = "CipherSubTitleLabel",
|
||||||
iconData = IconData.Local(R.drawable.ic_note),
|
iconData = IconData.Local(R.drawable.ic_note),
|
||||||
|
@ -119,7 +120,7 @@ fun createMockDisplayItemForCipher(
|
||||||
title = "mockName-$number",
|
title = "mockName-$number",
|
||||||
titleTestTag = "CipherNameLabel",
|
titleTestTag = "CipherNameLabel",
|
||||||
secondSubtitle = null,
|
secondSubtitle = null,
|
||||||
secondSubtitleTestTag = null,
|
secondSubtitleTestTag = secondSubtitleTestTag,
|
||||||
subtitle = subtitle,
|
subtitle = subtitle,
|
||||||
subtitleTestTag = "CipherSubTitleLabel",
|
subtitleTestTag = "CipherSubTitleLabel",
|
||||||
iconData = IconData.Local(R.drawable.ic_payment_card),
|
iconData = IconData.Local(R.drawable.ic_payment_card),
|
||||||
|
@ -165,7 +166,7 @@ fun createMockDisplayItemForCipher(
|
||||||
title = "mockName-$number",
|
title = "mockName-$number",
|
||||||
titleTestTag = "CipherNameLabel",
|
titleTestTag = "CipherNameLabel",
|
||||||
secondSubtitle = null,
|
secondSubtitle = null,
|
||||||
secondSubtitleTestTag = null,
|
secondSubtitleTestTag = secondSubtitleTestTag,
|
||||||
subtitle = subtitle,
|
subtitle = subtitle,
|
||||||
subtitleTestTag = "CipherSubTitleLabel",
|
subtitleTestTag = "CipherSubTitleLabel",
|
||||||
iconData = IconData.Local(R.drawable.ic_id_card),
|
iconData = IconData.Local(R.drawable.ic_id_card),
|
||||||
|
|
Loading…
Reference in a new issue