From 2af96988ab6a2de266a82270e1ceef67abe6771c Mon Sep 17 00:00:00 2001 From: David Perez Date: Tue, 8 Oct 2024 14:20:05 -0500 Subject: [PATCH] PM-13021: Update empty state for TOTP flow (#4051) --- .../itemlisting/VaultItemListingEmpty.kt | 2 + .../itemlisting/VaultItemListingViewModel.kt | 6 ++- .../util/VaultItemListingDataExtensions.kt | 18 +++++--- .../ui/vault/feature/vault/VaultNoItems.kt | 4 +- .../drawable-night/img_folder_question.xml | 37 ++++++++++++++++ .../main/res/drawable/img_folder_question.xml | 37 ++++++++++++++++ app/src/main/res/values/strings.xml | 1 + .../itemlisting/VaultItemListingScreenTest.kt | 10 +++++ .../VaultItemListingViewModelTest.kt | 8 ++++ .../VaultItemListingDataExtensionsTest.kt | 42 +++++++++++++++++++ 10 files changed, 158 insertions(+), 7 deletions(-) create mode 100644 app/src/main/res/drawable-night/img_folder_question.xml create mode 100644 app/src/main/res/drawable/img_folder_question.xml diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingEmpty.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingEmpty.kt index d4aa11ff4..7b1ae8b43 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingEmpty.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingEmpty.kt @@ -29,6 +29,8 @@ fun VaultItemListingEmpty( if (state.shouldShowAddButton) { VaultNoItems( policyDisablesSend = policyDisablesSend, + vectorRes = state.vectorRes, + headerText = state.header(), message = state.message(), buttonText = state.buttonText(), modifier = modifier, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt index 862ab5163..9e0be4a6b 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.ui.vault.feature.itemlisting import android.os.Parcelable +import androidx.annotation.DrawableRes import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.bitwarden.fido.Fido2CredentialAutofillView @@ -1559,6 +1560,7 @@ class VaultItemListingViewModel @Inject constructor( fido2CreationData = state.fido2CredentialRequest, fido2CredentialAutofillViews = vaultData .fido2CredentialAutofillViewList, + totpData = state.totpData, isPremiumUser = state.isPremium, ) } @@ -1897,9 +1899,11 @@ data class VaultItemListingState( * Represents a state where the [VaultItemListingScreen] has no items to display. */ data class NoItems( + val header: Text, val message: Text, - val shouldShowAddButton: Boolean, val buttonText: Text, + @DrawableRes val vectorRes: Int = R.drawable.img_vault_items, + val shouldShowAddButton: Boolean, ) : ViewState() { override val isPullToRefreshEnabled: Boolean get() = true } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt index b003cd4b0..a1ee3341c 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt @@ -33,6 +33,7 @@ import com.x8bit.bitwarden.ui.vault.feature.util.toOverflowActions import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType import com.x8bit.bitwarden.ui.vault.feature.vault.util.toFilteredList import com.x8bit.bitwarden.ui.vault.feature.vault.util.toLoginIconData +import com.x8bit.bitwarden.ui.vault.model.TotpData import java.time.Clock private const val DELETION_DATE_PATTERN: String = "MMM d, uuuu, hh:mm a" @@ -104,6 +105,7 @@ fun VaultData.toViewState( autofillSelectionData: AutofillSelectionData?, fido2CreationData: Fido2CredentialRequest?, fido2CredentialAutofillViews: List?, + totpData: TotpData?, isPremiumUser: Boolean, ): VaultItemListingState.ViewState { val filteredCipherViewList = cipherViewList @@ -171,6 +173,7 @@ fun VaultData.toViewState( ?.origin ?.toHostOrPathOrNull() ?.let { R.string.no_items_for_uri.asText(it) } + ?: totpData?.let { R.string.search_for_a_login_or_add_a_new_login.asText() } ?: run { when (itemListingType) { is VaultItemListingState.ItemListingType.Vault.Folder -> { @@ -197,13 +200,17 @@ fun VaultData.toViewState( else -> true } VaultItemListingState.ViewState.NoItems( + header = totpData + ?.let { R.string.no_items_for_uri.asText(it.issuer ?: it.accountName ?: "--") } + ?: R.string.save_and_protect_your_data.asText(), message = message, shouldShowAddButton = shouldShowAddButton, - buttonText = if (fido2CreationData != null) { - R.string.save_passkey_as_new_login.asText() - } else { - R.string.add_an_item.asText() - }, + buttonText = fido2CreationData + ?.let { R.string.save_passkey_as_new_login.asText() } + ?: R.string.add_an_item.asText(), + vectorRes = totpData + ?.let { R.drawable.img_folder_question } + ?: R.drawable.img_vault_items, ) } } @@ -226,6 +233,7 @@ fun List.toViewState( ) } else { VaultItemListingState.ViewState.NoItems( + header = R.string.save_and_protect_your_data.asText(), message = R.string.no_items.asText(), shouldShowAddButton = true, buttonText = R.string.add_an_item.asText(), diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultNoItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultNoItems.kt index 3185dcd8a..5d3fa9856 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultNoItems.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultNoItems.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.ui.vault.feature.vault import android.content.res.Configuration +import androidx.annotation.DrawableRes import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -34,6 +35,7 @@ fun VaultNoItems( addItemClickAction: () -> Unit, policyDisablesSend: Boolean, modifier: Modifier = Modifier, + @DrawableRes vectorRes: Int = R.drawable.img_vault_items, headerText: String = stringResource(id = R.string.save_and_protect_your_data), message: String = stringResource(R.string.the_vault_protects_more_than_just_passwords), buttonText: String = stringResource(R.string.new_login), @@ -54,7 +56,7 @@ fun VaultNoItems( Spacer(modifier = Modifier.weight(1F)) Image( - painter = rememberVectorPainter(id = R.drawable.img_vault_items), + painter = rememberVectorPainter(id = vectorRes), contentDescription = null, modifier = Modifier .standardHorizontalMargin() diff --git a/app/src/main/res/drawable-night/img_folder_question.xml b/app/src/main/res/drawable-night/img_folder_question.xml new file mode 100644 index 000000000..f6d85261e --- /dev/null +++ b/app/src/main/res/drawable-night/img_folder_question.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/img_folder_question.xml b/app/src/main/res/drawable/img_folder_question.xml new file mode 100644 index 000000000..3ae36085e --- /dev/null +++ b/app/src/main/res/drawable/img_folder_question.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e9ff5351d..f7df14f92 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -195,6 +195,7 @@ Translations Items for %1$s There are no items in your vault for %1$s. + Search for a login or add a new login When you select an input field and see a Bitwarden auto-fill overlay, you can tap it to launch the auto-fill service. Tap this notification to auto-fill an item from your vault. Open Accessibility Settings diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt index 15847b22e..c83167a9a 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt @@ -383,6 +383,7 @@ class VaultItemListingScreenTest : BaseComposeTest() { it.copy( itemListingType = VaultItemListingState.ItemListingType.Send.SendFile, viewState = VaultItemListingState.ViewState.NoItems( + header = "Save and protect your data".asText(), message = "There are no Sends in your account.".asText(), shouldShowAddButton = true, buttonText = "Add an Item".asText(), @@ -419,6 +420,7 @@ class VaultItemListingScreenTest : BaseComposeTest() { mutableStateFlow.update { it.copy( viewState = VaultItemListingState.ViewState.NoItems( + header = "Save and protect your data".asText(), message = "There are no items in your vault.".asText(), shouldShowAddButton = true, buttonText = "Add an Item".asText(), @@ -519,6 +521,7 @@ class VaultItemListingScreenTest : BaseComposeTest() { mutableStateFlow.update { it.copy( viewState = VaultItemListingState.ViewState.NoItems( + header = "Save and protect your data".asText(), message = "There are no items in your vault.".asText(), shouldShowAddButton = true, buttonText = "Add an Item".asText(), @@ -541,6 +544,7 @@ class VaultItemListingScreenTest : BaseComposeTest() { mutableStateFlow.update { it.copy( viewState = VaultItemListingState.ViewState.NoItems( + header = "Save and protect your data".asText(), message = "There are no items in your vault.".asText(), shouldShowAddButton = true, buttonText = "Add an Item".asText(), @@ -569,6 +573,7 @@ class VaultItemListingScreenTest : BaseComposeTest() { mutableStateFlow.update { it.copy( viewState = VaultItemListingState.ViewState.NoItems( + header = "Save and protect your data".asText(), message = "There are no items in your vault.".asText(), shouldShowAddButton = true, buttonText = "Add an Item".asText(), @@ -583,6 +588,7 @@ class VaultItemListingScreenTest : BaseComposeTest() { mutableStateFlow.update { it.copy( viewState = VaultItemListingState.ViewState.NoItems( + header = "Save and protect your data".asText(), message = "There are no items in your vault.".asText(), shouldShowAddButton = false, buttonText = "Add an Item".asText(), @@ -599,12 +605,16 @@ class VaultItemListingScreenTest : BaseComposeTest() { mutableStateFlow.update { it.copy( viewState = VaultItemListingState.ViewState.NoItems( + header = "Save and protect your data".asText(), message = "There are no items in your vault.".asText(), shouldShowAddButton = true, buttonText = "Save passkey as new login".asText(), ), ) } + composeTestRule + .onNodeWithText(text = "Save and protect your data") + .assertIsDisplayed() composeTestRule .onNodeWithText(text = "There are no items in your vault.") .assertIsDisplayed() diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt index 2bdb9611d..1928ac029 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt @@ -1654,6 +1654,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.NoItems( + header = R.string.save_and_protect_your_data.asText(), message = R.string.no_items.asText(), shouldShowAddButton = true, buttonText = R.string.add_an_item.asText(), @@ -1681,6 +1682,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.NoItems( + header = R.string.save_and_protect_your_data.asText(), message = R.string.no_items.asText(), shouldShowAddButton = true, buttonText = R.string.add_an_item.asText(), @@ -1752,6 +1754,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.NoItems( + header = R.string.save_and_protect_your_data.asText(), message = R.string.no_items.asText(), shouldShowAddButton = true, buttonText = R.string.add_an_item.asText(), @@ -1779,6 +1782,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.NoItems( + header = R.string.save_and_protect_your_data.asText(), message = R.string.no_items.asText(), shouldShowAddButton = true, buttonText = R.string.add_an_item.asText(), @@ -1862,6 +1866,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.NoItems( + header = R.string.save_and_protect_your_data.asText(), message = R.string.no_items.asText(), shouldShowAddButton = true, buttonText = R.string.add_an_item.asText(), @@ -1890,6 +1895,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.NoItems( + header = R.string.save_and_protect_your_data.asText(), message = R.string.no_items.asText(), shouldShowAddButton = true, buttonText = R.string.add_an_item.asText(), @@ -1974,6 +1980,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.NoItems( + header = R.string.save_and_protect_your_data.asText(), message = R.string.no_items.asText(), shouldShowAddButton = true, buttonText = R.string.add_an_item.asText(), @@ -2001,6 +2008,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { assertEquals( createVaultItemListingState( viewState = VaultItemListingState.ViewState.NoItems( + header = R.string.save_and_protect_your_data.asText(), message = R.string.no_items.asText(), shouldShowAddButton = true, buttonText = R.string.add_an_item.asText(), diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt index 06c1ffd49..cb378e826 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt @@ -25,6 +25,7 @@ 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.itemlisting.VaultItemListingState import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType +import com.x8bit.bitwarden.ui.vault.model.TotpData import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic @@ -401,6 +402,7 @@ class VaultItemListingDataExtensionsTest { autofillSelectionData = null, fido2CreationData = null, fido2CredentialAutofillViews = null, + totpData = null, isPremiumUser = true, ) @@ -493,6 +495,7 @@ class VaultItemListingDataExtensionsTest { ), fido2CreationData = null, fido2CredentialAutofillViews = fido2CredentialAutofillViews, + totpData = null, isPremiumUser = true, ) @@ -578,6 +581,7 @@ class VaultItemListingDataExtensionsTest { ), fido2CreationData = null, fido2CredentialAutofillViews = fido2CredentialAutofillViews, + totpData = null, isPremiumUser = true, ) @@ -622,6 +626,7 @@ class VaultItemListingDataExtensionsTest { // Trash assertEquals( VaultItemListingState.ViewState.NoItems( + header = R.string.save_and_protect_your_data.asText(), message = R.string.no_items_trash.asText(), shouldShowAddButton = false, buttonText = R.string.add_an_item.asText(), @@ -635,6 +640,7 @@ class VaultItemListingDataExtensionsTest { autofillSelectionData = null, fido2CreationData = null, fido2CredentialAutofillViews = null, + totpData = null, isPremiumUser = true, ), ) @@ -642,6 +648,7 @@ class VaultItemListingDataExtensionsTest { // Folders assertEquals( VaultItemListingState.ViewState.NoItems( + header = R.string.save_and_protect_your_data.asText(), message = R.string.no_items_folder.asText(), shouldShowAddButton = false, buttonText = R.string.add_an_item.asText(), @@ -657,6 +664,7 @@ class VaultItemListingDataExtensionsTest { autofillSelectionData = null, fido2CreationData = null, fido2CredentialAutofillViews = null, + totpData = null, isPremiumUser = true, ), ) @@ -664,6 +672,7 @@ class VaultItemListingDataExtensionsTest { // Other ciphers assertEquals( VaultItemListingState.ViewState.NoItems( + header = R.string.save_and_protect_your_data.asText(), message = R.string.no_items.asText(), shouldShowAddButton = true, buttonText = R.string.add_an_item.asText(), @@ -677,6 +686,7 @@ class VaultItemListingDataExtensionsTest { autofillSelectionData = null, fido2CreationData = null, fido2CredentialAutofillViews = null, + totpData = null, isPremiumUser = true, ), ) @@ -684,6 +694,7 @@ class VaultItemListingDataExtensionsTest { // Autofill assertEquals( VaultItemListingState.ViewState.NoItems( + header = R.string.save_and_protect_your_data.asText(), message = R.string.no_items_for_uri.asText("www.test.com"), shouldShowAddButton = true, buttonText = R.string.add_an_item.asText(), @@ -701,6 +712,7 @@ class VaultItemListingDataExtensionsTest { ), fido2CreationData = null, fido2CredentialAutofillViews = null, + totpData = null, isPremiumUser = true, ), ) @@ -708,6 +720,7 @@ class VaultItemListingDataExtensionsTest { // Autofill passkey assertEquals( VaultItemListingState.ViewState.NoItems( + header = R.string.save_and_protect_your_data.asText(), message = R.string.no_items_for_uri.asText("www.test.com"), shouldShowAddButton = true, buttonText = R.string.save_passkey_as_new_login.asText(), @@ -727,6 +740,33 @@ class VaultItemListingDataExtensionsTest { origin = "https://www.test.com", ), fido2CredentialAutofillViews = null, + totpData = null, + isPremiumUser = true, + ), + ) + + // Totp + assertEquals( + VaultItemListingState.ViewState.NoItems( + header = R.string.no_items_for_uri.asText("issuer"), + message = R.string.search_for_a_login_or_add_a_new_login.asText(), + shouldShowAddButton = false, + buttonText = R.string.add_an_item.asText(), + vectorRes = R.drawable.img_folder_question, + ), + vaultData.toViewState( + itemListingType = VaultItemListingState.ItemListingType.Vault.Trash, + vaultFilterType = VaultFilterType.AllVaults, + hasMasterPassword = true, + baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl, + isIconLoadingDisabled = false, + autofillSelectionData = null, + fido2CreationData = null, + fido2CredentialAutofillViews = null, + totpData = mockk { + every { accountName } returns "accountName" + every { issuer } returns "issuer" + }, isPremiumUser = true, ), ) @@ -869,6 +909,7 @@ class VaultItemListingDataExtensionsTest { autofillSelectionData = null, fido2CreationData = null, fido2CredentialAutofillViews = null, + totpData = null, isPremiumUser = true, ) @@ -912,6 +953,7 @@ class VaultItemListingDataExtensionsTest { autofillSelectionData = null, fido2CreationData = null, fido2CredentialAutofillViews = null, + totpData = null, isPremiumUser = true, )