BIT-1337 Adding new section for verification codes (#567)

This commit is contained in:
Oleg Semenenko
2024-01-11 15:49:51 -06:00
committed by Álison Fernandes
parent 9e6c49fb7c
commit fc5529e2ad
8 changed files with 264 additions and 10 deletions

View File

@@ -27,6 +27,7 @@ fun VaultContent(
vaultItemClick: (VaultState.ViewState.VaultItem) -> Unit,
folderClick: (VaultState.ViewState.FolderItem) -> Unit,
collectionClick: (VaultState.ViewState.CollectionItem) -> Unit,
totpItemsClick: () -> Unit,
loginGroupClick: () -> Unit,
cardGroupClick: () -> Unit,
identityGroupClick: () -> Unit,
@@ -37,6 +38,31 @@ fun VaultContent(
LazyColumn(
modifier = modifier,
) {
if (state.totpItemsCount > 0) {
item {
BitwardenListHeaderTextWithSupportLabel(
label = stringResource(id = R.string.totp),
supportingLabel = "1",
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
}
item {
BitwardenGroupItem(
startIcon = painterResource(id = R.drawable.access_time),
label = stringResource(id = R.string.verification_codes),
supportingLabel = state.totpItemsCount.toString(),
onClick = totpItemsClick,
showDivider = true,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
)
}
}
if (state.favoriteItems.isNotEmpty()) {

View File

@@ -34,6 +34,7 @@ import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.base.util.IntentHandler
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.base.util.showNotYetImplementedToast
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
import com.x8bit.bitwarden.ui.platform.components.BitwardenAccountActionItem
import com.x8bit.bitwarden.ui.platform.components.BitwardenAccountSwitcher
@@ -80,6 +81,11 @@ fun VaultScreen(
.show()
}
is VaultEvent.NavigateToVerificationCodeScreen -> {
// TODO Add Verification codes detail screen (BIT-1338)
showNotYetImplementedToast(context = context)
}
is VaultEvent.NavigateToVaultItem -> onNavigateToVaultItemScreen(event.itemId)
is VaultEvent.NavigateToEditVaultItem -> onNavigateToVaultEditItemScreen(event.itemId)
@@ -140,6 +146,9 @@ fun VaultScreen(
viewModel.trySendAction(VaultAction.CollectionClick(collectionItem))
}
},
verificationCodesClick = remember(viewModel) {
{ viewModel.trySendAction(VaultAction.VerificationCodesClick) }
},
loginGroupClick = remember(viewModel) {
{ viewModel.trySendAction(VaultAction.LoginGroupClick) }
},
@@ -186,6 +195,7 @@ private fun VaultScreenScaffold(
vaultItemClick: (VaultState.ViewState.VaultItem) -> Unit,
folderClick: (VaultState.ViewState.FolderItem) -> Unit,
collectionClick: (VaultState.ViewState.CollectionItem) -> Unit,
verificationCodesClick: () -> Unit,
loginGroupClick: () -> Unit,
cardGroupClick: () -> Unit,
identityGroupClick: () -> Unit,
@@ -345,6 +355,7 @@ private fun VaultScreenScaffold(
cardGroupClick = cardGroupClick,
identityGroupClick = identityGroupClick,
secureNoteGroupClick = secureNoteGroupClick,
totpItemsClick = verificationCodesClick,
trashClick = trashClick,
modifier = innerModifier,
)

View File

@@ -56,6 +56,7 @@ class VaultViewModel @Inject constructor(
accountSummaries = accountSummaries,
vaultFilterData = vaultFilterData,
viewState = VaultState.ViewState.Loading,
isPremium = userState.activeAccount.isPremium,
)
},
) {
@@ -86,6 +87,7 @@ class VaultViewModel @Inject constructor(
is VaultAction.FolderClick -> handleFolderItemClick(action)
is VaultAction.CollectionClick -> handleCollectionItemClick(action)
is VaultAction.IdentityGroupClick -> handleIdentityClick()
is VaultAction.VerificationCodesClick -> handleVerificationCodeClick()
is VaultAction.LoginGroupClick -> handleLoginClick()
is VaultAction.SearchIconClick -> handleSearchIconClick()
is VaultAction.LockAccountClick -> handleLockAccountClick(action)
@@ -133,6 +135,10 @@ class VaultViewModel @Inject constructor(
)
}
private fun handleVerificationCodeClick() {
sendEvent(VaultEvent.NavigateToVerificationCodeScreen)
}
private fun handleIdentityClick() {
sendEvent(VaultEvent.NavigateToItemListing(VaultItemListingType.Identity))
}
@@ -268,6 +274,7 @@ class VaultViewModel @Inject constructor(
mutableStateFlow.updateToErrorStateOrDialog(
vaultData = vaultData.data,
vaultFilterType = vaultFilterTypeOrDefault,
isPremium = state.isPremium,
errorTitle = R.string.an_error_has_occurred.asText(),
errorMessage = R.string.generic_error_message.asText(),
)
@@ -283,7 +290,10 @@ class VaultViewModel @Inject constructor(
}
mutableStateFlow.update {
it.copy(
viewState = vaultData.data.toViewState(vaultFilterTypeOrDefault),
viewState = vaultData.data.toViewState(
isPremium = state.isPremium,
vaultFilterType = vaultFilterTypeOrDefault,
),
dialog = null,
)
}
@@ -297,6 +307,7 @@ class VaultViewModel @Inject constructor(
mutableStateFlow.updateToErrorStateOrDialog(
vaultData = vaultData.data,
vaultFilterType = vaultFilterTypeOrDefault,
isPremium = state.isPremium,
errorTitle = R.string.internet_connection_required_title.asText(),
errorMessage = R.string.internet_connection_required_message.asText(),
)
@@ -305,7 +316,12 @@ class VaultViewModel @Inject constructor(
private fun vaultPendingReceive(vaultData: DataState.Pending<VaultData>) {
// TODO update state to refresh state BIT-505
mutableStateFlow.update {
it.copy(viewState = vaultData.data.toViewState(vaultFilterTypeOrDefault))
it.copy(
viewState = vaultData.data.toViewState(
isPremium = state.isPremium,
vaultFilterType = vaultFilterTypeOrDefault,
),
)
}
}
@@ -321,6 +337,7 @@ class VaultViewModel @Inject constructor(
* @property viewState The specific view state representing loading, no items, or content state.
* @property dialog Information about any dialogs that may need to be displayed.
* @property isSwitchingAccounts Whether or not we are actively switching accounts.
* @property isPremium Whether the user is a premium user.
*/
@Parcelize
data class VaultState(
@@ -333,6 +350,7 @@ data class VaultState(
val dialog: DialogState? = null,
// Internal-use properties
val isSwitchingAccounts: Boolean = false,
val isPremium: Boolean,
) : Parcelable {
/**
@@ -390,6 +408,7 @@ data class VaultState(
/**
* Content state for the [VaultScreen] showing the actual content or items.
*
* @property totpItemsCount The count of totp code items.
* @property loginItemsCount The count of Login type items.
* @property cardItemsCount The count of Card type items.
* @property identityItemsCount The count of Identity type items.
@@ -402,6 +421,7 @@ data class VaultState(
*/
@Parcelize
data class Content(
val totpItemsCount: Int,
val loginItemsCount: Int,
val cardItemsCount: Int,
val identityItemsCount: Int,
@@ -608,6 +628,11 @@ sealed class VaultEvent {
val itemListingType: VaultItemListingType,
) : VaultEvent()
/**
* Navigate to the verification code screen.
*/
data object NavigateToVerificationCodeScreen : VaultEvent()
/**
* Navigate out of the app.
*/
@@ -706,6 +731,11 @@ sealed class VaultAction {
val collectionItem: VaultState.ViewState.CollectionItem,
) : VaultAction()
/**
* User clicked on the verification codes button.
*/
data object VerificationCodesClick : VaultAction()
/**
* User clicked the login types button.
*/
@@ -765,13 +795,17 @@ sealed class VaultAction {
private fun MutableStateFlow<VaultState>.updateToErrorStateOrDialog(
vaultData: VaultData?,
vaultFilterType: VaultFilterType,
isPremium: Boolean,
errorTitle: Text,
errorMessage: Text,
) {
this.update {
if (vaultData != null) {
it.copy(
viewState = vaultData.toViewState(vaultFilterType = vaultFilterType),
viewState = vaultData.toViewState(
isPremium = isPremium,
vaultFilterType = vaultFilterType,
),
dialog = VaultState.DialogState.Error(
title = errorTitle,
message = errorMessage,

View File

@@ -13,6 +13,7 @@ import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
* Transforms [VaultData] into [VaultState.ViewState] using the given [vaultFilterType].
*/
fun VaultData.toViewState(
isPremium: Boolean,
vaultFilterType: VaultFilterType,
): VaultState.ViewState {
val filteredCipherViewList = cipherViewList.toFilteredList(vaultFilterType)
@@ -23,6 +24,11 @@ fun VaultData.toViewState(
VaultState.ViewState.NoItems
} else {
VaultState.ViewState.Content(
totpItemsCount = if (isPremium) {
filteredCipherViewList.count { it.login?.totp != null }
} else {
0
},
loginItemsCount = filteredCipherViewList.count { it.type == CipherType.LOGIN },
cardItemsCount = filteredCipherViewList.count { it.type == CipherType.CARD },
identityItemsCount = filteredCipherViewList.count { it.type == CipherType.IDENTITY },