From 0871a2a33dc7140fc5064ba536aa4283c64829d9 Mon Sep 17 00:00:00 2001 From: David Perez Date: Mon, 30 Mar 2026 14:05:51 -0500 Subject: [PATCH] BWA-224: bug: Add sort order for Authenticator items (#6740) --- .../feature/itemlisting/ItemListingScreen.kt | 6 +- .../itemlisting/ItemListingViewModel.kt | 3 + .../SharedVerificationCodesStateExtensions.kt | 5 +- .../listitem/model/SharedCodesDisplayState.kt | 1 + .../util/SharedCodesDisplayStateExtensions.kt | 15 +++ .../VerificationCodeDisplayItemExtensions.kt | 15 +++ .../itemlisting/ItemListingScreenTest.kt | 1 + .../feature/search/ItemSearchViewModelTest.kt | 1 + .../util/SharedVerificationCodesStateTest.kt | 96 ++++++++++--------- ...SharedCodesAccountSectionExtensionsTest.kt | 67 +++++++++++++ ...rificationCodeDisplayItemExtensionsTest.kt | 93 ++++++++++++++++++ 11 files changed, 253 insertions(+), 50 deletions(-) create mode 100644 authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/util/SharedCodesDisplayStateExtensions.kt create mode 100644 authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/util/VerificationCodeDisplayItemExtensions.kt create mode 100644 authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/util/SharedCodesAccountSectionExtensionsTest.kt create mode 100644 authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/util/VerificationCodeDisplayItemExtensionsTest.kt diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt index 94f23fa38b..ca62bcd398 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt @@ -629,6 +629,7 @@ private fun EmptyListingContentPreview() { @Composable @Preview(showBackground = true) private fun ContentPreview() { + val email = "longemailaddress+verification+codes@email.com" BitwardenTheme { ItemListingContent( state = ItemListingState.ViewState.Content( @@ -652,9 +653,7 @@ private fun ContentPreview() { sections = persistentListOf( SharedCodesDisplayState.SharedCodesAccountSection( id = "id", - label = - "longemailaddress+verification+codes@email.com | Bitawrden.eu (1)" - .asText(), + label = "$email | Bitawrden.eu (1)".asText(), codes = persistentListOf( VerificationCodeDisplayItem( id = "", @@ -670,6 +669,7 @@ private fun ContentPreview() { ), ), isExpanded = true, + sortKey = email, ), ), ), diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt index 01c77fe3ea..4f882357ce 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt @@ -23,6 +23,7 @@ import com.bitwarden.authenticator.ui.authenticator.feature.util.toSharedCodesDi import com.bitwarden.authenticator.ui.platform.components.listitem.model.SharedCodesDisplayState import com.bitwarden.authenticator.ui.platform.components.listitem.model.VaultDropdownMenuAction import com.bitwarden.authenticator.ui.platform.components.listitem.model.VerificationCodeDisplayItem +import com.bitwarden.authenticator.ui.platform.components.listitem.model.util.sortAlphabetically import com.bitwarden.authenticator.ui.platform.model.SnackbarRelay import com.bitwarden.authenticatorbridge.manager.AuthenticatorBridgeManager import com.bitwarden.core.data.repository.model.DataState @@ -509,6 +510,7 @@ class ItemListingViewModel @Inject constructor( showOverflow = true, ) } + .sortAlphabetically() .toImmutableList(), itemList = localItems .filter { it.source is AuthenticatorItem.Source.Local && !it.source.isFavorite } @@ -521,6 +523,7 @@ class ItemListingViewModel @Inject constructor( showOverflow = true, ) } + .sortAlphabetically() .toImmutableList(), sharedItems = sharedItemsState, actionCard = action.sharedCodesState.toActionCard(), diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/util/SharedVerificationCodesStateExtensions.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/util/SharedVerificationCodesStateExtensions.kt index 71af61e3b7..0129854b47 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/util/SharedVerificationCodesStateExtensions.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/util/SharedVerificationCodesStateExtensions.kt @@ -4,6 +4,7 @@ import com.bitwarden.authenticator.data.authenticator.repository.model.Authentic import com.bitwarden.authenticator.data.authenticator.repository.model.SharedVerificationCodesState import com.bitwarden.authenticator.ui.platform.components.listitem.model.SharedCodesDisplayState import com.bitwarden.authenticator.ui.platform.components.listitem.model.VerificationCodeDisplayItem +import com.bitwarden.authenticator.ui.platform.components.listitem.model.util.sortAlphabetically import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.util.asText import kotlinx.collections.immutable.toImmutableList @@ -41,12 +42,14 @@ fun SharedVerificationCodesState.Success.toSharedCodesDisplayState( it.key.environmentLabel, it.value.size, ), - codes = it.value.toImmutableList(), + codes = it.value.sortAlphabetically().toImmutableList(), isExpanded = currentSections .find { section -> section.id == it.key.userId } ?.isExpanded ?: true, + sortKey = it.key.email, ) } + .sortAlphabetically() .let { SharedCodesDisplayState.Codes(it.toImmutableList()) } } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/SharedCodesDisplayState.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/SharedCodesDisplayState.kt index da949a1647..9840bc3f78 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/SharedCodesDisplayState.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/SharedCodesDisplayState.kt @@ -33,6 +33,7 @@ sealed class SharedCodesDisplayState : Parcelable { val label: Text, val codes: ImmutableList, val isExpanded: Boolean, + val sortKey: String, ) : Parcelable /** diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/util/SharedCodesDisplayStateExtensions.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/util/SharedCodesDisplayStateExtensions.kt new file mode 100644 index 0000000000..494cd974ed --- /dev/null +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/util/SharedCodesDisplayStateExtensions.kt @@ -0,0 +1,15 @@ +package com.bitwarden.authenticator.ui.platform.components.listitem.model.util + +import com.bitwarden.authenticator.ui.platform.components.listitem.model.SharedCodesDisplayState.SharedCodesAccountSection +import com.bitwarden.core.data.repository.util.SpecialCharWithPrecedenceComparator + +/** + * Sorts the data in alphabetical order by name. Using lexicographical sorting but giving + * precedence to special characters over letters and digits. + */ +fun List.sortAlphabetically(): List = + this.sortedWith( + comparator = { item1, item2 -> + SpecialCharWithPrecedenceComparator.compare(item1.sortKey, item2.sortKey) + }, + ) diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/util/VerificationCodeDisplayItemExtensions.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/util/VerificationCodeDisplayItemExtensions.kt new file mode 100644 index 0000000000..28fb15435a --- /dev/null +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/util/VerificationCodeDisplayItemExtensions.kt @@ -0,0 +1,15 @@ +package com.bitwarden.authenticator.ui.platform.components.listitem.model.util + +import com.bitwarden.authenticator.ui.platform.components.listitem.model.VerificationCodeDisplayItem +import com.bitwarden.core.data.repository.util.SpecialCharWithPrecedenceComparator + +/** + * Sorts the data in alphabetical order by name. Using lexicographical sorting but giving + * precedence to special characters over letters and digits. + */ +fun List.sortAlphabetically(): List = + this.sortedWith( + comparator = { item1, item2 -> + SpecialCharWithPrecedenceComparator.compare(item1.title, item2.title) + }, + ) diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreenTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreenTest.kt index ef22760a52..6f96f5314e 100644 --- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreenTest.kt +++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreenTest.kt @@ -570,6 +570,7 @@ private val SHARED_ACCOUNTS_SECTION = SharedCodesDisplayState.SharedCodesAccount ), ), isExpanded = true, + sortKey = "test@test.com", ) private val DEFAULT_STATE = ItemListingState( diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchViewModelTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchViewModelTest.kt index eb0aeb7477..677ed1f1e9 100644 --- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchViewModelTest.kt +++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchViewModelTest.kt @@ -410,6 +410,7 @@ private val SHARED_DISPLAY_ITEMS = SharedCodesDisplayState.Codes( ), ), isExpanded = true, + sortKey = "mockEmail-2", ), ), ) diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/util/SharedVerificationCodesStateTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/util/SharedVerificationCodesStateTest.kt index 51f3431bf3..5f70bf53c4 100644 --- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/util/SharedVerificationCodesStateTest.kt +++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/util/SharedVerificationCodesStateTest.kt @@ -61,29 +61,6 @@ class SharedVerificationCodesStateTest { ) val expected = SharedCodesDisplayState.Codes( sections = persistentListOf( - SharedCodesDisplayState.SharedCodesAccountSection( - id = "user1", - label = BitwardenString.shared_accounts_header.asText( - "John@test.com", - "bitwarden.com", - 1, - ), - codes = persistentListOf( - VerificationCodeDisplayItem( - authCode = "123456", - periodSeconds = 30, - timeLeftSeconds = 10, - id = "123", - title = "--", - subtitle = null, - favorite = false, - showOverflow = false, - alertThresholdSeconds = ALERT_THRESHOLD, - showMoveToBitwarden = false, - ), - ), - isExpanded = true, - ), SharedCodesDisplayState.SharedCodesAccountSection( id = "user1", label = BitwardenString.shared_accounts_header.asText( @@ -106,6 +83,31 @@ class SharedVerificationCodesStateTest { ), ), isExpanded = true, + sortKey = "Jane@test.com", + ), + SharedCodesDisplayState.SharedCodesAccountSection( + id = "user1", + label = BitwardenString.shared_accounts_header.asText( + "John@test.com", + "bitwarden.com", + 1, + ), + codes = persistentListOf( + VerificationCodeDisplayItem( + authCode = "123456", + periodSeconds = 30, + timeLeftSeconds = 10, + id = "123", + title = "--", + subtitle = null, + favorite = false, + showOverflow = false, + alertThresholdSeconds = ALERT_THRESHOLD, + showMoveToBitwarden = false, + ), + ), + isExpanded = true, + sortKey = "John@test.com", ), ), ) @@ -153,29 +155,6 @@ class SharedVerificationCodesStateTest { ) val expected = SharedCodesDisplayState.Codes( sections = persistentListOf( - SharedCodesDisplayState.SharedCodesAccountSection( - id = "user1", - label = BitwardenString.shared_accounts_header.asText( - "John@test.com", - "bitwarden.com", - 1, - ), - codes = persistentListOf( - VerificationCodeDisplayItem( - authCode = "123456", - periodSeconds = 30, - timeLeftSeconds = 10, - id = "123", - title = "--", - subtitle = null, - favorite = false, - showOverflow = false, - alertThresholdSeconds = ALERT_THRESHOLD, - showMoveToBitwarden = false, - ), - ), - isExpanded = false, - ), SharedCodesDisplayState.SharedCodesAccountSection( id = "user1", label = BitwardenString.shared_accounts_header.asText( @@ -198,6 +177,31 @@ class SharedVerificationCodesStateTest { ), ), isExpanded = false, + sortKey = "Jane@test.com", + ), + SharedCodesDisplayState.SharedCodesAccountSection( + id = "user1", + label = BitwardenString.shared_accounts_header.asText( + "John@test.com", + "bitwarden.com", + 1, + ), + codes = persistentListOf( + VerificationCodeDisplayItem( + authCode = "123456", + periodSeconds = 30, + timeLeftSeconds = 10, + id = "123", + title = "--", + subtitle = null, + favorite = false, + showOverflow = false, + alertThresholdSeconds = ALERT_THRESHOLD, + showMoveToBitwarden = false, + ), + ), + isExpanded = false, + sortKey = "John@test.com", ), ), ) diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/util/SharedCodesAccountSectionExtensionsTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/util/SharedCodesAccountSectionExtensionsTest.kt new file mode 100644 index 0000000000..4ace35056d --- /dev/null +++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/util/SharedCodesAccountSectionExtensionsTest.kt @@ -0,0 +1,67 @@ +package com.bitwarden.authenticator.ui.platform.components.listitem.model.util + +import com.bitwarden.authenticator.ui.platform.components.listitem.model.SharedCodesDisplayState +import com.bitwarden.ui.platform.resource.BitwardenString +import com.bitwarden.ui.util.asText +import kotlinx.collections.immutable.persistentListOf +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class SharedCodesAccountSectionExtensionsTest { + @Test + fun `toSortAlphabetically should sort ciphers by sortKey`() { + val codes = persistentListOf( + SharedCodesDisplayState.SharedCodesAccountSection( + id = "user1", + label = BitwardenString.shared_accounts_header.asText( + "John@test.com", + "bitwarden.com", + 1, + ), + codes = persistentListOf(), + isExpanded = true, + sortKey = "John@test.com", + ), + SharedCodesDisplayState.SharedCodesAccountSection( + id = "user1", + label = BitwardenString.shared_accounts_header.asText( + "Jane@test.com", + "bitwarden.eu", + 1, + ), + codes = persistentListOf(), + isExpanded = true, + sortKey = "Jane@test.com", + ), + ) + val expected = persistentListOf( + SharedCodesDisplayState.SharedCodesAccountSection( + id = "user1", + label = BitwardenString.shared_accounts_header.asText( + "Jane@test.com", + "bitwarden.eu", + 1, + ), + codes = persistentListOf(), + isExpanded = true, + sortKey = "Jane@test.com", + ), + SharedCodesDisplayState.SharedCodesAccountSection( + id = "user1", + label = BitwardenString.shared_accounts_header.asText( + "John@test.com", + "bitwarden.com", + 1, + ), + codes = persistentListOf(), + isExpanded = true, + sortKey = "John@test.com", + ), + ) + + assertEquals( + expected, + codes.sortAlphabetically(), + ) + } +} diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/util/VerificationCodeDisplayItemExtensionsTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/util/VerificationCodeDisplayItemExtensionsTest.kt new file mode 100644 index 0000000000..9cdfdab54d --- /dev/null +++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/model/util/VerificationCodeDisplayItemExtensionsTest.kt @@ -0,0 +1,93 @@ +package com.bitwarden.authenticator.ui.platform.components.listitem.model.util + +import com.bitwarden.authenticator.ui.platform.components.listitem.model.VerificationCodeDisplayItem +import kotlinx.collections.immutable.persistentListOf +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class VerificationCodeDisplayItemExtensionsTest { + @Test + fun `toSortAlphabetically should sort ciphers by title`() { + val codes = persistentListOf( + VerificationCodeDisplayItem( + authCode = "123456", + periodSeconds = 30, + timeLeftSeconds = 10, + id = "123", + title = "Bitwarden", + subtitle = null, + favorite = false, + showOverflow = false, + alertThresholdSeconds = 7, + showMoveToBitwarden = false, + ), + VerificationCodeDisplayItem( + authCode = "123456", + periodSeconds = 30, + timeLeftSeconds = 10, + id = "7643", + title = "--", + subtitle = null, + favorite = false, + showOverflow = false, + alertThresholdSeconds = 7, + showMoveToBitwarden = false, + ), + VerificationCodeDisplayItem( + authCode = "123456", + periodSeconds = 30, + timeLeftSeconds = 10, + id = "84345", + title = "bitwarden", + subtitle = null, + favorite = false, + showOverflow = false, + alertThresholdSeconds = 7, + showMoveToBitwarden = false, + ), + ) + val expected = persistentListOf( + VerificationCodeDisplayItem( + authCode = "123456", + periodSeconds = 30, + timeLeftSeconds = 10, + id = "7643", + title = "--", + subtitle = null, + favorite = false, + showOverflow = false, + alertThresholdSeconds = 7, + showMoveToBitwarden = false, + ), + VerificationCodeDisplayItem( + authCode = "123456", + periodSeconds = 30, + timeLeftSeconds = 10, + id = "84345", + title = "bitwarden", + subtitle = null, + favorite = false, + showOverflow = false, + alertThresholdSeconds = 7, + showMoveToBitwarden = false, + ), + VerificationCodeDisplayItem( + authCode = "123456", + periodSeconds = 30, + timeLeftSeconds = 10, + id = "123", + title = "Bitwarden", + subtitle = null, + favorite = false, + showOverflow = false, + alertThresholdSeconds = 7, + showMoveToBitwarden = false, + ), + ) + + assertEquals( + expected, + codes.sortAlphabetically(), + ) + } +}