From c51f61c5859247eb324e181078ffbf1855f064fc Mon Sep 17 00:00:00 2001 From: Philip Cappelli Date: Thu, 27 Mar 2025 17:32:53 -0400 Subject: [PATCH] WIP --- .../feature/item/VaultItemLoginContent.kt | 7 +- .../ui/vault/feature/item/VaultItemScreen.kt | 4 ++ .../vault/feature/item/VaultItemViewModel.kt | 71 +++++++++++-------- .../handlers/VaultLoginItemTypeHandlers.kt | 4 +- .../feature/item/model/VaultItemStateData.kt | 2 - .../feature/item/util/CipherViewExtensions.kt | 2 - 6 files changed, 53 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt index 368344edb3..33b82df592 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt @@ -50,6 +50,7 @@ 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, @@ -136,7 +137,7 @@ fun VaultItemLoginContent( } } - loginItemState.totpCodeItemData?.let { totpCodeItemData -> + totpCodeItemData?.let { totpCodeItemData -> item(key = "totpCode") { Spacer(modifier = Modifier.height(8.dp)) TotpField( @@ -422,7 +423,7 @@ private fun PasswordField( private fun TotpField( totpCodeItemData: TotpCodeItemData, enabled: Boolean, - onCopyTotpClick: () -> Unit, + onCopyTotpClick: (String) -> Unit, onAuthenticatorHelpToolTipClick: () -> Unit, modifier: Modifier = Modifier, ) { @@ -448,7 +449,7 @@ private fun TotpField( BitwardenStandardIconButton( vectorIconRes = R.drawable.ic_copy, contentDescription = stringResource(id = R.string.copy_totp), - onClick = onCopyTotpClick, + onClick = { onCopyTotpClick(totpCodeItemData.totpCode) }, modifier = Modifier.testTag(tag = "LoginCopyTotpButton"), ) }, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt index f5fdf71b88..5ed3540c21 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt @@ -45,6 +45,7 @@ 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 /** @@ -260,6 +261,7 @@ fun VaultItemScreen( ) { VaultItemContent( viewState = state.viewState, + totpCodeItemData = state.totpCodeItemData, modifier = Modifier .fillMaxSize(), vaultCommonItemTypeHandlers = remember(viewModel) { @@ -350,6 +352,7 @@ private fun VaultItemDialogs( @Composable private fun VaultItemContent( viewState: VaultItemState.ViewState, + totpCodeItemData: TotpCodeItemData?, vaultCommonItemTypeHandlers: VaultCommonItemTypeHandlers, vaultLoginItemTypeHandlers: VaultLoginItemTypeHandlers, vaultCardItemTypeHandlers: VaultCardItemTypeHandlers, @@ -370,6 +373,7 @@ private fun VaultItemContent( VaultItemLoginContent( commonState = viewState.common, loginItemState = viewState.type, + totpCodeItemData = totpCodeItemData, vaultCommonItemTypeHandlers = vaultCommonItemTypeHandlers, vaultLoginItemTypeHandlers = vaultLoginItemTypeHandlers, modifier = modifier, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt index 62263aea8c..d3c30857a9 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt @@ -104,23 +104,14 @@ class VaultItemViewModel @Inject constructor( vaultRepository.getAuthCodeFlow(state.vaultItemId), vaultRepository.collectionsStateFlow, vaultRepository.foldersStateFlow, - ) { cipherViewState, userState, authCodeState, collectionsState, folderState -> - val totpCodeData = authCodeState.data?.let { - TotpCodeItemData( - periodSeconds = it.periodSeconds, - timeLeftSeconds = it.timeLeftSeconds, - totpCode = it.totpCode, - verificationCode = it.code, - ) - } + ) { cipherViewState, userState, collectionsState, folderState -> 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. } @@ -165,7 +156,6 @@ class VaultItemViewModel @Inject constructor( VaultItemStateData( cipher = cipherView, - totpCodeItemData = totpCodeData, canDelete = canDelete, canAssociateToCollections = canAssignToCollections, canEdit = canEdit, @@ -181,6 +171,21 @@ 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) { @@ -602,7 +607,7 @@ class VaultItemViewModel @Inject constructor( } is VaultItemAction.ItemType.Login.CopyTotpClick -> { - handleCopyTotpClick() + handleCopyTotpClick(action) } is VaultItemAction.ItemType.Login.CopyUriClick -> { @@ -663,14 +668,11 @@ class VaultItemViewModel @Inject constructor( } } - private fun handleCopyTotpClick() { - onLoginContent { _, login -> - val code = login.totpCodeItemData?.verificationCode ?: return@onLoginContent - clipboardManager.setText( - text = code, - toastDescriptorOverride = R.string.totp.asText(), - ) - } + private fun handleCopyTotpClick(action: VaultItemAction.ItemType.Login.CopyTotpClick) { + clipboardManager.setText( + text = action.code, + toastDescriptorOverride = R.string.totp.asText(), + ) } private fun handleCopyUriClick(action: VaultItemAction.ItemType.Login.CopyUriClick) { @@ -1093,6 +1095,10 @@ class VaultItemViewModel @Inject constructor( is VaultItemAction.Internal.IsIconLoadingDisabledUpdateReceive -> { handleIsIconLoadingDisabledUpdateReceive(action) } + + is VaultItemAction.Internal.TotpDataReceive -> { + handleTotpDataReceive(action) + } } } @@ -1198,7 +1204,6 @@ 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, @@ -1353,6 +1358,12 @@ 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?) { @@ -1440,6 +1451,7 @@ data class VaultItemState( val vaultItemId: String, val cipherType: VaultItemCipherType, val viewState: ViewState, + val totpCodeItemData: TotpCodeItemData?, val dialog: DialogState?, val baseIconUrl: String, val isIconLoadingDisabled: Boolean, @@ -1653,7 +1665,6 @@ data class VaultItemState( * @property uris The URI associated with the login item. * @property passwordRevisionDate An optional string indicating the last time the * password was changed. - * @property totpCodeItemData The optional data related the TOTP code. * @property isPremiumUser Indicates if the user has subscribed to a premium * account. * @property canViewTotpCode Indicates if the user can view an associated TOTP code. @@ -1662,7 +1673,7 @@ data class VaultItemState( * * **NOTE** [canViewTotpCode] currently supports a deprecated edge case where an * organization supports TOTP but not through the current premium model. - * This additional field is added to allow for [isPremiumUser] to be an independent + * This additional field is added to allow for [] to be an independent * value. * @see [CipherView.organizationUseTotp] * @@ -1673,7 +1684,6 @@ data class VaultItemState( val passwordData: PasswordData?, val uris: List, val passwordRevisionDate: String?, - val totpCodeItemData: TotpCodeItemData?, val isPremiumUser: Boolean, val canViewTotpCode: Boolean, val fido2CredentialCreationDateText: Text?, @@ -1685,8 +1695,7 @@ data class VaultItemState( val hasLoginCredentials: Boolean get() = username != null || passwordData != null || - fido2CredentialCreationDateText != null || - totpCodeItemData != null + fido2CredentialCreationDateText != null /** * A wrapper for the password data. @@ -2127,7 +2136,9 @@ sealed class VaultItemAction { /** * The user has clicked the copy button for the TOTP code. */ - data object CopyTotpClick : Login() + data class CopyTotpClick( + val code: String, + ) : Login() /** * The user has clicked the copy button for a URI. @@ -2285,6 +2296,10 @@ sealed class VaultItemAction { val vaultDataState: DataState, ) : Internal() + data class TotpDataReceive( + val totpData: TotpCodeItemData?, + ) : Internal() + /** * Indicates that the verify password result has been received. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/handlers/VaultLoginItemTypeHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/handlers/VaultLoginItemTypeHandlers.kt index a6c37500ca..a5188192b9 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/handlers/VaultLoginItemTypeHandlers.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/handlers/VaultLoginItemTypeHandlers.kt @@ -11,7 +11,7 @@ import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemViewModel data class VaultLoginItemTypeHandlers( val onCheckForBreachClick: () -> Unit, val onCopyPasswordClick: () -> Unit, - val onCopyTotpCodeClick: () -> Unit, + val onCopyTotpCodeClick: (String) -> 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) + viewModel.trySendAction(VaultItemAction.ItemType.Login.CopyTotpClick(it)) }, onAuthenticatorHelpToolTipClick = { viewModel.trySendAction( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/model/VaultItemStateData.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/model/VaultItemStateData.kt index 862e81c1b0..60a9b6d739 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/model/VaultItemStateData.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/model/VaultItemStateData.kt @@ -7,7 +7,6 @@ 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. @@ -15,7 +14,6 @@ import kotlinx.collections.immutable.ImmutableList */ data class VaultItemStateData( val cipher: CipherView?, - val totpCodeItemData: TotpCodeItemData?, val canDelete: Boolean, val canAssociateToCollections: Boolean, val canEdit: Boolean, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt index 40b5fa1603..9a7d6e1f86 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt @@ -42,7 +42,6 @@ fun CipherView.toViewState( previousState: VaultItemState.ViewState.Content?, isPremiumUser: Boolean, hasMasterPassword: Boolean, - totpCodeItemData: TotpCodeItemData?, clock: Clock = Clock.systemDefaultZone(), canDelete: Boolean, canAssignToCollections: Boolean, @@ -125,7 +124,6 @@ fun CipherView.toViewState( ), isPremiumUser = isPremiumUser, canViewTotpCode = isPremiumUser || this.organizationUseTotp, - totpCodeItemData = totpCodeItemData, fido2CredentialCreationDateText = loginValues .fido2Credentials ?.firstOrNull()