revert previous changes and go back to hash solution

This commit is contained in:
Philip Cappelli
2025-03-31 17:05:19 -04:00
parent 3926fbca7f
commit a4fb50d3d8
9 changed files with 108 additions and 72 deletions

View File

@@ -50,7 +50,6 @@ private const val AUTH_CODE_SPACING_INTERVAL = 3
fun VaultItemLoginContent(
commonState: VaultItemState.ViewState.Content.Common,
loginItemState: VaultItemState.ViewState.Content.ItemType.Login,
totpCodeItemData: TotpCodeItemData?,
vaultCommonItemTypeHandlers: VaultCommonItemTypeHandlers,
vaultLoginItemTypeHandlers: VaultLoginItemTypeHandlers,
modifier: Modifier = Modifier,
@@ -137,7 +136,7 @@ fun VaultItemLoginContent(
}
}
totpCodeItemData?.let { totpCodeItemData ->
loginItemState.totpCodeItemData?.let { totpCodeItemData ->
item(key = "totpCode") {
Spacer(modifier = Modifier.height(8.dp))
TotpField(
@@ -423,7 +422,7 @@ private fun PasswordField(
private fun TotpField(
totpCodeItemData: TotpCodeItemData,
enabled: Boolean,
onCopyTotpClick: (String) -> Unit,
onCopyTotpClick: () -> Unit,
onAuthenticatorHelpToolTipClick: () -> Unit,
modifier: Modifier = Modifier,
) {
@@ -449,7 +448,7 @@ private fun TotpField(
BitwardenStandardIconButton(
vectorIconRes = R.drawable.ic_copy,
contentDescription = stringResource(id = R.string.copy_totp),
onClick = { onCopyTotpClick(totpCodeItemData.totpCode) },
onClick = onCopyTotpClick,
modifier = Modifier.testTag(tag = "LoginCopyTotpButton"),
)
},

View File

@@ -45,7 +45,6 @@ import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultCommonItemTypeHan
import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultIdentityItemTypeHandlers
import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultLoginItemTypeHandlers
import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultSshKeyItemTypeHandlers
import com.x8bit.bitwarden.ui.vault.feature.item.model.TotpCodeItemData
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
/**
@@ -261,7 +260,6 @@ fun VaultItemScreen(
) {
VaultItemContent(
viewState = state.viewState,
totpCodeItemData = state.totpCodeItemData,
modifier = Modifier
.fillMaxSize(),
vaultCommonItemTypeHandlers = remember(viewModel) {
@@ -352,7 +350,6 @@ private fun VaultItemDialogs(
@Composable
private fun VaultItemContent(
viewState: VaultItemState.ViewState,
totpCodeItemData: TotpCodeItemData?,
vaultCommonItemTypeHandlers: VaultCommonItemTypeHandlers,
vaultLoginItemTypeHandlers: VaultLoginItemTypeHandlers,
vaultCardItemTypeHandlers: VaultCardItemTypeHandlers,
@@ -373,7 +370,6 @@ private fun VaultItemContent(
VaultItemLoginContent(
commonState = viewState.common,
loginItemState = viewState.type,
totpCodeItemData = totpCodeItemData,
vaultCommonItemTypeHandlers = vaultCommonItemTypeHandlers,
vaultLoginItemTypeHandlers = vaultLoginItemTypeHandlers,
modifier = modifier,

View File

@@ -78,7 +78,6 @@ class VaultItemViewModel @Inject constructor(
vaultItemId = args.vaultItemId,
cipherType = args.cipherType,
viewState = VaultItemState.ViewState.Loading,
totpCodeItemData = null,
dialog = null,
baseIconUrl = environmentRepository.environment.environmentUrlData.baseIconUrl,
isIconLoadingDisabled = settingsRepository.isIconLoadingDisabled,
@@ -102,16 +101,26 @@ class VaultItemViewModel @Inject constructor(
combine(
vaultRepository.getVaultItemStateFlow(state.vaultItemId),
authRepository.userStateFlow,
vaultRepository.getAuthCodeFlow(state.vaultItemId),
vaultRepository.collectionsStateFlow,
vaultRepository.foldersStateFlow,
) { cipherViewState, userState, collectionsState, folderState ->
) { cipherViewState, userState, authCodeState, collectionsState, folderState ->
val totpCodeData = authCodeState.data?.let {
TotpCodeItemData(
periodSeconds = it.periodSeconds,
timeLeftSeconds = it.timeLeftSeconds,
totpCode = it.totpCode,
verificationCode = it.code,
)
}
VaultItemAction.Internal.VaultDataReceive(
userState = userState,
vaultDataState = combineDataStates(
cipherViewState,
authCodeState,
collectionsState,
folderState,
) { _, _, _ ->
) { _, _, _, _ ->
// We are only combining the DataStates to know the overall state,
// we map it to the appropriate value below.
}
@@ -156,6 +165,7 @@ class VaultItemViewModel @Inject constructor(
VaultItemStateData(
cipher = cipherView,
totpCodeItemData = totpCodeData,
canDelete = canDelete,
canAssociateToCollections = canAssignToCollections,
canEdit = canEdit,
@@ -171,21 +181,6 @@ class VaultItemViewModel @Inject constructor(
.map { VaultItemAction.Internal.IsIconLoadingDisabledUpdateReceive(it) }
.onEach(::sendAction)
.launchIn(viewModelScope)
vaultRepository.getAuthCodeFlow(state.vaultItemId)
.map {
VaultItemAction.Internal.TotpDataReceive(
totpData = TotpCodeItemData(
periodSeconds = it.data?.periodSeconds ?: 0,
timeLeftSeconds = it.data?.timeLeftSeconds ?: 0,
totpCode = it.data?.totpCode ?: "",
verificationCode = it.data?.code ?: "",
)
)
}
.onEach(::sendAction)
.launchIn(viewModelScope)
}
override fun handleAction(action: VaultItemAction) {
@@ -607,7 +602,7 @@ class VaultItemViewModel @Inject constructor(
}
is VaultItemAction.ItemType.Login.CopyTotpClick -> {
handleCopyTotpClick(action)
handleCopyTotpClick()
}
is VaultItemAction.ItemType.Login.CopyUriClick -> {
@@ -668,11 +663,14 @@ class VaultItemViewModel @Inject constructor(
}
}
private fun handleCopyTotpClick(action: VaultItemAction.ItemType.Login.CopyTotpClick) {
clipboardManager.setText(
text = action.code,
toastDescriptorOverride = R.string.totp.asText(),
)
private fun handleCopyTotpClick() {
onLoginContent { _, login ->
val code = login.totpCodeItemData?.verificationCode ?: return@onLoginContent
clipboardManager.setText(
text = code,
toastDescriptorOverride = R.string.totp.asText(),
)
}
}
private fun handleCopyUriClick(action: VaultItemAction.ItemType.Login.CopyUriClick) {
@@ -1095,10 +1093,6 @@ class VaultItemViewModel @Inject constructor(
is VaultItemAction.Internal.IsIconLoadingDisabledUpdateReceive -> {
handleIsIconLoadingDisabledUpdateReceive(action)
}
is VaultItemAction.Internal.TotpDataReceive -> {
handleTotpDataReceive(action)
}
}
}
@@ -1204,6 +1198,7 @@ class VaultItemViewModel @Inject constructor(
previousState = state.viewState.asContentOrNull(),
isPremiumUser = account.isPremium,
hasMasterPassword = account.hasMasterPassword,
totpCodeItemData = this.data?.totpCodeItemData,
canDelete = this.data?.canDelete == true,
canAssignToCollections = this.data?.canAssociateToCollections == true,
canEdit = this.data?.canEdit == true,
@@ -1358,12 +1353,6 @@ class VaultItemViewModel @Inject constructor(
mutableStateFlow.update { it.copy(isIconLoadingDisabled = action.isDisabled) }
}
private fun handleTotpDataReceive(
action: VaultItemAction.Internal.TotpDataReceive,
) {
mutableStateFlow.update { it.copy(totpCodeItemData = action.totpData) }
}
//endregion Internal Type Handlers
private fun updateDialogState(dialog: VaultItemState.DialogState?) {
@@ -1451,7 +1440,6 @@ data class VaultItemState(
val vaultItemId: String,
val cipherType: VaultItemCipherType,
val viewState: ViewState,
val totpCodeItemData: TotpCodeItemData?,
val dialog: DialogState?,
val baseIconUrl: String,
val isIconLoadingDisabled: Boolean,
@@ -1609,11 +1597,14 @@ data class VaultItemState(
* Represents a custom field, TextField, HiddenField, BooleanField, or LinkedField.
*/
sealed class Custom : Parcelable {
abstract val id: String
/**
* Represents the data for displaying a custom text field.
*/
@Parcelize
data class TextField(
override val id: String,
val name: String,
val value: String,
val isCopyable: Boolean,
@@ -1624,6 +1615,7 @@ data class VaultItemState(
*/
@Parcelize
data class HiddenField(
override val id: String,
val name: String,
val value: String,
val isCopyable: Boolean,
@@ -1635,6 +1627,7 @@ data class VaultItemState(
*/
@Parcelize
data class BooleanField(
override val id: String,
val name: String,
val value: Boolean,
) : Custom()
@@ -1644,6 +1637,7 @@ data class VaultItemState(
*/
@Parcelize
data class LinkedField(
override val id: String,
val vaultLinkedFieldType: VaultLinkedFieldType,
val name: String,
) : Custom()
@@ -1684,6 +1678,7 @@ data class VaultItemState(
val passwordData: PasswordData?,
val uris: List<UriData>,
val passwordRevisionDate: String?,
val totpCodeItemData: TotpCodeItemData?,
val isPremiumUser: Boolean,
val canViewTotpCode: Boolean,
val fido2CredentialCreationDateText: Text?,
@@ -1695,8 +1690,10 @@ data class VaultItemState(
val hasLoginCredentials: Boolean
get() = username != null ||
passwordData != null ||
totpCodeItemData != null ||
fido2CredentialCreationDateText != null
/**
* A wrapper for the password data.
*
@@ -2136,9 +2133,7 @@ sealed class VaultItemAction {
/**
* The user has clicked the copy button for the TOTP code.
*/
data class CopyTotpClick(
val code: String,
) : Login()
data object CopyTotpClick : Login()
/**
* The user has clicked the copy button for a URI.
@@ -2296,10 +2291,6 @@ sealed class VaultItemAction {
val vaultDataState: DataState<VaultItemStateData>,
) : Internal()
data class TotpDataReceive(
val totpData: TotpCodeItemData?,
) : Internal()
/**
* Indicates that the verify password result has been received.
*/

View File

@@ -11,7 +11,7 @@ import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemViewModel
data class VaultLoginItemTypeHandlers(
val onCheckForBreachClick: () -> Unit,
val onCopyPasswordClick: () -> Unit,
val onCopyTotpCodeClick: (String) -> Unit,
val onCopyTotpCodeClick: () -> Unit,
val onAuthenticatorHelpToolTipClick: () -> Unit,
val onCopyUriClick: (String) -> Unit,
val onCopyUsernameClick: () -> Unit,
@@ -35,7 +35,7 @@ data class VaultLoginItemTypeHandlers(
viewModel.trySendAction(VaultItemAction.ItemType.Login.CopyPasswordClick)
},
onCopyTotpCodeClick = {
viewModel.trySendAction(VaultItemAction.ItemType.Login.CopyTotpClick(it))
viewModel.trySendAction(VaultItemAction.ItemType.Login.CopyTotpClick)
},
onAuthenticatorHelpToolTipClick = {
viewModel.trySendAction(

View File

@@ -7,6 +7,7 @@ import kotlinx.collections.immutable.ImmutableList
* The state containing totp code item information and the cipher for the item.
*
* @property cipher The cipher view for the item.
* @property totpCodeItemData The data for the totp code.
* @property canDelete Whether the item can be deleted.
* @property canAssociateToCollections Whether the item can be associated to a collection.
* @property canEdit Whether the item can be edited.
@@ -14,6 +15,7 @@ import kotlinx.collections.immutable.ImmutableList
*/
data class VaultItemStateData(
val cipher: CipherView?,
val totpCodeItemData: TotpCodeItemData?,
val canDelete: Boolean,
val canAssociateToCollections: Boolean,
val canEdit: Boolean,

View File

@@ -42,6 +42,7 @@ fun CipherView.toViewState(
previousState: VaultItemState.ViewState.Content?,
isPremiumUser: Boolean,
hasMasterPassword: Boolean,
totpCodeItemData: TotpCodeItemData?,
clock: Clock = Clock.systemDefaultZone(),
canDelete: Boolean,
canAssignToCollections: Boolean,
@@ -56,7 +57,14 @@ fun CipherView.toViewState(
name = name,
requiresReprompt = (reprompt == CipherRepromptType.PASSWORD && hasMasterPassword) &&
previousState?.common?.requiresReprompt != false,
customFields = fields.orEmpty().map { it.toCustomField() },
customFields = fields.orEmpty().map { fieldView ->
fieldView.toCustomField(
previousState = previousState
?.common
?.customFields
?.find { it.id == fieldView.hashCode().toString() },
)
},
lastUpdated = revisionDate.toFormattedPattern(
pattern = LAST_UPDATED_DATE_TIME_PATTERN,
clock = clock,
@@ -124,6 +132,7 @@ fun CipherView.toViewState(
),
isPremiumUser = isPremiumUser,
canViewTotpCode = isPremiumUser || this.organizationUseTotp,
totpCodeItemData = totpCodeItemData,
fido2CredentialCreationDateText = loginValues
.fido2Credentials
?.firstOrNull()
@@ -193,27 +202,40 @@ fun CipherView.toViewState(
},
)
private fun FieldView.toCustomField(): VaultItemState.ViewState.Content.Common.Custom =
/**
* Transforms [FieldView] into [VaultItemState.ViewState.Content.Common.Custom].
*/
fun FieldView.toCustomField(
previousState: VaultItemState.ViewState.Content.Common.Custom?,
): VaultItemState.ViewState.Content.Common.Custom =
when (type) {
FieldType.TEXT -> VaultItemState.ViewState.Content.Common.Custom.TextField(
id = this.hashCode().toString(),
name = name.orEmpty(),
value = value.orZeroWidthSpace(),
isCopyable = !value.isNullOrBlank(),
)
FieldType.HIDDEN -> VaultItemState.ViewState.Content.Common.Custom.HiddenField(
id = this.hashCode().toString(),
name = name.orEmpty(),
value = value.orZeroWidthSpace(),
isCopyable = !value.isNullOrBlank(),
isVisible = false,
isVisible = (previousState as?
VaultItemState.ViewState.Content.Common.Custom.HiddenField)
?.isVisible
?: false,
)
FieldType.BOOLEAN -> VaultItemState.ViewState.Content.Common.Custom.BooleanField(
id = this.hashCode().toString(),
name = name.orEmpty(),
value = value?.toBoolean() ?: false,
)
FieldType.LINKED -> VaultItemState.ViewState.Content.Common.Custom.LinkedField(
id = this.hashCode().toString(),
vaultLinkedFieldType = VaultLinkedFieldType.fromId(requireNotNull(linkedId)),
name = name.orEmpty(),
)

View File

@@ -776,6 +776,7 @@ class VaultItemScreenTest : BaseComposeTest() {
@Test
fun `on show hidden field click should send HiddenFieldVisibilityClicked`() {
val textField = VaultItemState.ViewState.Content.Common.Custom.HiddenField(
id = "12345",
name = "hidden",
value = "hidden password",
isCopyable = true,
@@ -814,6 +815,7 @@ class VaultItemScreenTest : BaseComposeTest() {
@Test
fun `copy hidden field button should be displayed according to state`() {
val hiddenField = VaultItemState.ViewState.Content.Common.Custom.HiddenField(
id = "12345",
name = "hidden",
value = "hidden password",
isCopyable = true,
@@ -855,6 +857,7 @@ class VaultItemScreenTest : BaseComposeTest() {
@Test
fun `on copy hidden field click should send CopyCustomHiddenFieldClick`() {
val hiddenField = VaultItemState.ViewState.Content.Common.Custom.HiddenField(
id = "12345",
name = "hidden",
value = "hidden password",
isCopyable = true,
@@ -890,6 +893,7 @@ class VaultItemScreenTest : BaseComposeTest() {
@Test
fun `on copy text field click should send CopyCustomTextFieldClick`() {
val textField = VaultItemState.ViewState.Content.Common.Custom.TextField(
id = "12345",
name = "text",
value = "value",
isCopyable = true,
@@ -924,6 +928,7 @@ class VaultItemScreenTest : BaseComposeTest() {
@Test
fun `text field copy button should be displayed according to state`() {
val textField = VaultItemState.ViewState.Content.Common.Custom.TextField(
id = "12345",
name = "text",
value = "value",
isCopyable = true,
@@ -1552,6 +1557,7 @@ class VaultItemScreenTest : BaseComposeTest() {
// Adding a custom field so that we can scroll to it
// So we can see the Copy notes button but not have it covered by the FAB
val textField = VaultItemState.ViewState.Content.Common.Custom.TextField(
id = "12345",
name = "text",
value = "value",
isCopyable = true,
@@ -1587,6 +1593,7 @@ class VaultItemScreenTest : BaseComposeTest() {
// Adding a custom field so that we can scroll to it
// So we can see the Copy notes button but not have it covered by the FAB
val textField = VaultItemState.ViewState.Content.Common.Custom.TextField(
id = "12345",
name = "text",
value = "value",
isCopyable = true,
@@ -1642,6 +1649,7 @@ class VaultItemScreenTest : BaseComposeTest() {
// Adding a custom field so that we can scroll to it
// So we can see the Copy notes button but not have it covered by the FAB
val textField = VaultItemState.ViewState.Content.Common.Custom.TextField(
id = "12345",
name = "text",
value = "value",
isCopyable = true,
@@ -1885,11 +1893,13 @@ class VaultItemScreenTest : BaseComposeTest() {
fun `in login state, linked custom fields should be displayed according to state`() {
val linkedFieldUserName =
VaultItemState.ViewState.Content.Common.Custom.LinkedField(
id = "12345",
name = "linked username",
vaultLinkedFieldType = VaultLinkedFieldType.USERNAME,
)
val linkedFieldsPassword = VaultItemState.ViewState.Content.Common.Custom.LinkedField(
id = "12345",
name = "linked password",
vaultLinkedFieldType = VaultLinkedFieldType.PASSWORD,
)
@@ -3149,17 +3159,20 @@ private val DEFAULT_COMMON: VaultItemState.ViewState.Content.Common =
notes = "Lots of notes",
customFields = listOf(
VaultItemState.ViewState.Content.Common.Custom.TextField(
id = "12345",
name = "text",
value = "value",
isCopyable = true,
),
VaultItemState.ViewState.Content.Common.Custom.HiddenField(
id = "12345",
name = "hidden",
value = "hidden password",
isCopyable = true,
isVisible = false,
),
VaultItemState.ViewState.Content.Common.Custom.BooleanField(
id = "12345",
name = "boolean",
value = true,
),

View File

@@ -1070,6 +1070,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
runTest {
val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_VIEW_STATE)
val field = VaultItemState.ViewState.Content.Common.Custom.HiddenField(
id = "12345",
name = "hidden",
value = "value",
isCopyable = true,
@@ -1135,6 +1136,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
runTest {
val hiddenField =
VaultItemState.ViewState.Content.Common.Custom.HiddenField(
id = "12345",
name = "hidden",
value = "value",
isCopyable = true,
@@ -3702,25 +3704,30 @@ class VaultItemViewModelTest : BaseViewModelTest() {
notes = "Lots of notes",
customFields = listOf(
VaultItemState.ViewState.Content.Common.Custom.TextField(
id = "12345",
name = "text",
value = "value",
isCopyable = true,
),
VaultItemState.ViewState.Content.Common.Custom.HiddenField(
id = "12345",
name = "hidden",
value = "value",
isCopyable = true,
isVisible = false,
),
VaultItemState.ViewState.Content.Common.Custom.BooleanField(
id = "12345",
name = "boolean",
value = true,
),
VaultItemState.ViewState.Content.Common.Custom.LinkedField(
id = "12345",
name = "linked username",
vaultLinkedFieldType = VaultLinkedFieldType.USERNAME,
),
VaultItemState.ViewState.Content.Common.Custom.LinkedField(
id = "12345",
name = "linked password",
vaultLinkedFieldType = VaultLinkedFieldType.PASSWORD,
),

View File

@@ -18,7 +18,6 @@ import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.model.IconData
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemState
import com.x8bit.bitwarden.ui.vault.feature.item.model.TotpCodeItemData
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
import kotlinx.collections.immutable.persistentListOf
import java.time.Instant
@@ -188,29 +187,36 @@ fun createCommonContent(
lastUpdated = "1/1/70 12:16 AM",
notes = "Lots of notes",
customFields = listOf(
VaultItemState.ViewState.Content.Common.Custom.TextField(
FieldView(
name = "text",
value = "value",
isCopyable = true,
),
VaultItemState.ViewState.Content.Common.Custom.HiddenField(
type = FieldType.TEXT,
linkedId = null,
).toCustomField(null),
FieldView(
name = "hidden",
value = "value",
isCopyable = true,
isVisible = false,
),
VaultItemState.ViewState.Content.Common.Custom.BooleanField(
type = FieldType.HIDDEN,
linkedId = null,
).toCustomField(null),
FieldView(
name = "boolean",
value = true,
),
VaultItemState.ViewState.Content.Common.Custom.LinkedField(
value = "true",
type = FieldType.BOOLEAN,
linkedId = null,
).toCustomField(null),
FieldView(
name = "linked username",
vaultLinkedFieldType = VaultLinkedFieldType.USERNAME,
),
VaultItemState.ViewState.Content.Common.Custom.LinkedField(
value = null,
type = FieldType.LINKED,
linkedId = 100U,
).toCustomField(null),
FieldView(
name = "linked password",
vaultLinkedFieldType = VaultLinkedFieldType.PASSWORD,
),
value = null,
type = FieldType.LINKED,
linkedId = 101U,
).toCustomField(null),
),
requiresReprompt = true,
requiresCloneConfirmation = true,