BIT-1648: Add the copy totp code overflow option (#850)

This commit is contained in:
Oleg Semenenko
2024-01-29 16:40:54 -06:00
committed by Álison Fernandes
parent 0c6ea8d18d
commit 20dd839923
13 changed files with 283 additions and 3 deletions

View File

@@ -59,6 +59,7 @@ fun SearchContent(
is ListingItemOverflowAction.VaultAction.CopyNoteClick,
is ListingItemOverflowAction.VaultAction.CopyNumberClick,
is ListingItemOverflowAction.VaultAction.CopyPasswordClick,
is ListingItemOverflowAction.VaultAction.CopyTotpClick,
is ListingItemOverflowAction.VaultAction.CopySecurityCodeClick,
is ListingItemOverflowAction.VaultAction.CopyUsernameClick,
is ListingItemOverflowAction.VaultAction.EditClick,

View File

@@ -13,6 +13,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.baseIconUrl
import com.x8bit.bitwarden.data.platform.repository.util.baseWebSendUrl
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
@@ -190,6 +191,10 @@ class SearchViewModel @Inject constructor(
is ListingItemOverflowAction.VaultAction.ViewClick -> {
handleViewCipherClick(overflowAction)
}
is ListingItemOverflowAction.VaultAction.CopyTotpClick -> {
handleCopyTotpClick(overflowAction)
}
}
}
@@ -211,6 +216,15 @@ class SearchViewModel @Inject constructor(
sendEvent(SearchEvent.NavigateToEditSend(action.sendId))
}
private fun handleCopyTotpClick(
action: ListingItemOverflowAction.VaultAction.CopyTotpClick,
) {
viewModelScope.launch {
val result = vaultRepo.generateTotp(action.totpCode, clock.instant())
sendAction(SearchAction.Internal.GenerateTotpResultReceive(result))
}
}
private fun handleRemovePasswordClick(
action: ListingItemOverflowAction.SendAction.RemovePasswordClick,
) {
@@ -283,6 +297,10 @@ class SearchViewModel @Inject constructor(
handleDeleteSendResultReceive(action)
}
is SearchAction.Internal.GenerateTotpResultReceive -> {
handleGenerateTotpResultReceive(action)
}
is SearchAction.Internal.RemovePasswordSendResultReceive -> {
handleRemovePasswordSendResultReceive(action)
}
@@ -320,6 +338,17 @@ class SearchViewModel @Inject constructor(
}
}
private fun handleGenerateTotpResultReceive(
action: SearchAction.Internal.GenerateTotpResultReceive,
) {
when (val result = action.result) {
is GenerateTotpResult.Error -> Unit
is GenerateTotpResult.Success -> {
clipboardManager.setText(result.code)
}
}
}
private fun handleRemovePasswordSendResultReceive(
action: SearchAction.Internal.RemovePasswordSendResultReceive,
) {
@@ -758,6 +787,13 @@ sealed class SearchAction {
val result: DeleteSendResult,
) : Internal()
/**
* Indicates a result for generating a verification code has been received.
*/
data class GenerateTotpResultReceive(
val result: GenerateTotpResult,
) : Internal()
/**
* Indicates a result for removing the password protection from a send has been received.
*/

View File

@@ -66,6 +66,7 @@ fun VaultItemListingContent(
is ListingItemOverflowAction.VaultAction.EditClick,
is ListingItemOverflowAction.VaultAction.LaunchClick,
is ListingItemOverflowAction.VaultAction.ViewClick,
is ListingItemOverflowAction.VaultAction.CopyTotpClick,
null,
-> Unit
}

View File

@@ -19,6 +19,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.baseWebSendUrl
import com.x8bit.bitwarden.data.platform.repository.util.map
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
@@ -256,6 +257,15 @@ class VaultItemListingViewModel @Inject constructor(
clipboardManager.setText(action.securityCode)
}
private fun handleCopyTotpClick(
action: ListingItemOverflowAction.VaultAction.CopyTotpClick,
) {
viewModelScope.launch {
val result = vaultRepository.generateTotp(action.totpCode, clock.instant())
sendAction(VaultItemListingsAction.Internal.GenerateTotpResultReceive(result))
}
}
private fun handleCopyUsernameClick(
action: ListingItemOverflowAction.VaultAction.CopyUsernameClick,
) {
@@ -347,6 +357,10 @@ class VaultItemListingViewModel @Inject constructor(
handleCopySecurityCodeClick(overflowAction)
}
is ListingItemOverflowAction.VaultAction.CopyTotpClick -> {
handleCopyTotpClick(overflowAction)
}
is ListingItemOverflowAction.VaultAction.CopyUsernameClick -> {
handleCopyUsernameClick(overflowAction)
}
@@ -383,6 +397,10 @@ class VaultItemListingViewModel @Inject constructor(
is VaultItemListingsAction.Internal.IconLoadingSettingReceive -> {
handleIconsSettingReceived(action)
}
is VaultItemListingsAction.Internal.GenerateTotpResultReceive -> {
handleGenerateTotpResultReceive(action)
}
}
}
@@ -445,6 +463,17 @@ class VaultItemListingViewModel @Inject constructor(
}
}
private fun handleGenerateTotpResultReceive(
action: VaultItemListingsAction.Internal.GenerateTotpResultReceive,
) {
when (val result = action.result) {
is GenerateTotpResult.Error -> Unit
is GenerateTotpResult.Success -> {
clipboardManager.setText(result.code)
}
}
}
private fun handleVaultDataReceive(
action: VaultItemListingsAction.Internal.VaultDataReceive,
) {
@@ -1033,6 +1062,13 @@ sealed class VaultItemListingsAction {
*/
data class DeleteSendResultReceive(val result: DeleteSendResult) : Internal()
/**
* Indicates a result for generating a verification code has been received.
*/
data class GenerateTotpResultReceive(
val result: GenerateTotpResult,
) : Internal()
/**
* Indicates a result for removing the password protection from a send has been received.
*/

View File

@@ -97,6 +97,14 @@ sealed class ListingItemOverflowAction : Parcelable {
override val title: Text get() = R.string.copy_password.asText()
}
/**
* Click on the copy TOTP code overflow option.
*/
@Parcelize
data class CopyTotpClick(val totpCode: String) : VaultAction() {
override val title: Text get() = R.string.copy_totp.asText()
}
/**
* Click on the copy number overflow option.
*/

View File

@@ -23,6 +23,9 @@ fun CipherView.toOverflowActions(): List<ListingItemOverflowAction.VaultAction>
this.login?.password?.let {
ListingItemOverflowAction.VaultAction.CopyPasswordClick(password = it)
},
this.login?.totp
?.let { ListingItemOverflowAction.VaultAction.CopyTotpClick(totpCode = it) }
.takeIf { this.type == CipherType.LOGIN },
this.card?.number?.let {
ListingItemOverflowAction.VaultAction.CopyNumberClick(number = it)
},

View File

@@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.repository.model.DataState
import com.x8bit.bitwarden.data.platform.repository.util.baseIconUrl
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.base.util.Text
@@ -37,7 +38,9 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import java.time.Clock
import javax.inject.Inject
/**
@@ -46,10 +49,11 @@ import javax.inject.Inject
@Suppress("TooManyFunctions")
@HiltViewModel
class VaultViewModel @Inject constructor(
private val clipboardManager: BitwardenClipboardManager,
private val authRepository: AuthRepository,
private val vaultRepository: VaultRepository,
private val clipboardManager: BitwardenClipboardManager,
private val clock: Clock,
private val settingsRepository: SettingsRepository,
private val vaultRepository: VaultRepository,
) : BaseViewModel<VaultState, VaultEvent, VaultAction>(
initialState = run {
val userState = requireNotNull(authRepository.userStateFlow.value)
@@ -293,6 +297,10 @@ class VaultViewModel @Inject constructor(
handleCopySecurityCodeClick(overflowAction)
}
is ListingItemOverflowAction.VaultAction.CopyTotpClick -> {
handleCopyTotpClick(overflowAction)
}
is ListingItemOverflowAction.VaultAction.CopyUsernameClick -> {
handleCopyUsernameClick(overflowAction)
}
@@ -333,6 +341,15 @@ class VaultViewModel @Inject constructor(
clipboardManager.setText(action.securityCode)
}
private fun handleCopyTotpClick(
action: ListingItemOverflowAction.VaultAction.CopyTotpClick,
) {
viewModelScope.launch {
val result = vaultRepository.generateTotp(action.totpCode, clock.instant())
sendAction(VaultAction.Internal.GenerateTotpResultReceive(result))
}
}
private fun handleCopyUsernameClick(
action: ListingItemOverflowAction.VaultAction.CopyUsernameClick,
) {
@@ -353,6 +370,10 @@ class VaultViewModel @Inject constructor(
private fun handleInternalAction(action: VaultAction.Internal) {
when (action) {
is VaultAction.Internal.GenerateTotpResultReceive -> {
handleGenerateTotpResultReceive(action)
}
is VaultAction.Internal.PullToRefreshEnableReceive -> {
handlePullToRefreshEnableReceive(action)
}
@@ -365,6 +386,17 @@ class VaultViewModel @Inject constructor(
}
}
private fun handleGenerateTotpResultReceive(
action: VaultAction.Internal.GenerateTotpResultReceive,
) {
when (val result = action.result) {
is GenerateTotpResult.Error -> Unit
is GenerateTotpResult.Success -> {
clipboardManager.setText(result.code)
}
}
}
private fun handlePullToRefreshEnableReceive(
action: VaultAction.Internal.PullToRefreshEnableReceive,
) {
@@ -1002,6 +1034,13 @@ sealed class VaultAction {
val isIconLoadingDisabled: Boolean,
) : Internal()
/**
* Indicates a result for generating a verification code has been received.
*/
data class GenerateTotpResultReceive(
val result: GenerateTotpResult,
) : Internal()
/**
* Indicates that the pull to refresh feature toggle has changed.
*/