From 65bab9f27a4834f80418ea1d94b61e63f20bcb35 Mon Sep 17 00:00:00 2001 From: David Perez Date: Fri, 11 Sep 2026 14:09:57 +0000 Subject: [PATCH] PM-43353, PM-43354: bug: Update accessibility roles for improved TalkBack support (#7378) --- .../components/listitem/BitwardenGroupItem.kt | 2 + .../components/listitem/BitwardenListItem.kt | 2 + .../feature/addedit/VaultAddEditScreen.kt | 55 ++++++++++--------- .../feature/generator/GeneratorScreenTest.kt | 40 +++++++------- .../send/addedit/AddEditSendScreenTest.kt | 6 +- .../feature/itemlisting/ItemListingScreen.kt | 4 +- .../listitem/VaultVerificationCodeItem.kt | 2 + .../feature/edititem/EditItemScreenTest.kt | 4 +- .../platform/base/util/ModifierExtensions.kt | 9 +++ .../account/BitwardenAccountSwitcher.kt | 3 + .../action/BitwardenOverflowActionItem.kt | 5 +- .../card/BitwardenActionCardSmall.kt | 5 +- .../dialog/row/BitwardenBasicDialogRow.kt | 2 + .../dialog/row/BitwardenSelectionRow.kt | 21 ++----- .../components/field/BitwardenTextField.kt | 5 +- .../header/BitwardenExpandingHeader.kt | 2 + .../components/row/BitwardenPushRow.kt | 2 + .../components/row/BitwardenTextRow.kt | 2 + .../components/stepper/BitwardenStepper.kt | 14 ++++- .../components/text/BitwardenClickableText.kt | 2 + .../components/toggle/BitwardenSwitch.kt | 5 +- ui/src/main/res/values/strings.xml | 2 + 22 files changed, 119 insertions(+), 75 deletions(-) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/components/listitem/BitwardenGroupItem.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/components/listitem/BitwardenGroupItem.kt index 8e1c462b57..74072c63fd 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/components/listitem/BitwardenGroupItem.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/components/listitem/BitwardenGroupItem.kt @@ -12,6 +12,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.bitwarden.ui.platform.base.util.cardStyle @@ -51,6 +52,7 @@ fun BitwardenGroupItem( .cardStyle( cardStyle = cardStyle, onClick = onClick, + role = Role.Button, paddingHorizontal = 16.dp, ), verticalAlignment = Alignment.CenterVertically, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/components/listitem/BitwardenListItem.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/components/listitem/BitwardenListItem.kt index 560a177b69..2617c6dec1 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/components/listitem/BitwardenListItem.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/components/listitem/BitwardenListItem.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextOverflow @@ -84,6 +85,7 @@ fun BitwardenListItem( .cardStyle( cardStyle = cardStyle, onClick = onClick, + role = Role.Button, paddingStart = 16.dp, paddingEnd = 4.dp, ), diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt index 5ad79311bd..5574497242 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt @@ -4,12 +4,13 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState @@ -32,6 +33,10 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.selected +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import androidx.core.net.toUri import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel @@ -724,16 +729,16 @@ private fun FolderSelectionBottomSheetContent( Row( modifier = Modifier .fillMaxWidth() + .defaultMinSize(minHeight = 60.dp) .cardStyle( - cardStyle = if (index == 0) { - CardStyle.Top() - } else { - CardStyle.Middle() - }, - onClick = { - onOptionSelected(option) - }, - ), + cardStyle = if (index == 0) CardStyle.Top() else CardStyle.Middle(), + paddingHorizontal = 16.dp, + onClick = { onOptionSelected(option) }, + ) + .semantics(mergeDescendants = true) { + this.selected = selectedOption == option + this.role = Role.RadioButton + }, horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { @@ -741,15 +746,12 @@ private fun FolderSelectionBottomSheetContent( text = option, color = BitwardenTheme.colorScheme.text.primary, style = BitwardenTheme.typography.bodyLarge, - modifier = Modifier - .weight(1f) - .padding(horizontal = 16.dp), + modifier = Modifier.weight(weight = 1f), ) + Spacer(modifier = Modifier.width(width = 16.dp)) BitwardenRadioButton( isSelected = selectedOption == option, - onClick = { - onOptionSelected(option) - }, + onClick = null, ) } } @@ -866,12 +868,16 @@ private fun OwnerSelectionBottomSheetContent( Row( modifier = Modifier .fillMaxWidth() + .defaultMinSize(minHeight = 60.dp) .cardStyle( cardStyle = options.toListItemCardStyle(index = index), - onClick = { - onOptionSelected(option) - }, - ), + paddingHorizontal = 16.dp, + onClick = { onOptionSelected(option) }, + ) + .semantics(mergeDescendants = true) { + this.selected = selectedOwner == option + this.role = Role.RadioButton + }, horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { @@ -879,15 +885,12 @@ private fun OwnerSelectionBottomSheetContent( text = option.name(), color = BitwardenTheme.colorScheme.text.primary, style = BitwardenTheme.typography.bodyLarge, - modifier = Modifier - .weight(weight = 1f) - .padding(horizontal = 16.dp), + modifier = Modifier.weight(weight = 1f), ) + Spacer(modifier = Modifier.width(width = 16.dp)) BitwardenRadioButton( isSelected = selectedOwner == option, - onClick = { - onOptionSelected(option) - }, + onClick = null, ) } } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt index 0e457791c1..4e968fcc06 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt @@ -302,7 +302,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { .onNodeWithText("Minimum numbers") .assertTextEquals("Minimum numbers", "1") .onChildren() - .filterToOne(hasContentDescription("\u2212")) + .filterToOne(hasContentDescription("Decrease Minimum numbers")) .performScrollTo() .assertIsDisplayed() @@ -310,7 +310,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { .onNodeWithText("Minimum numbers") .assertTextEquals("Minimum numbers", "1") .onChildren() - .filterToOne(hasContentDescription("+")) + .filterToOne(hasContentDescription("Increase Minimum numbers")) .performScrollTo() .assertIsDisplayed() @@ -433,7 +433,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { composeTestRule.onNodeWithText("Minimum numbers") .assertTextEquals("Minimum numbers", "1") .onChildren() - .filterToOne(hasContentDescription("\u2212")) + .filterToOne(hasContentDescription("Decrease Minimum numbers")) .performScrollTo() .performClick() @@ -454,7 +454,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { composeTestRule.onNodeWithText("Minimum numbers") .assertTextEquals("Minimum numbers", "1") .onChildren() - .filterToOne(hasContentDescription("+")) + .filterToOne(hasContentDescription("Increase Minimum numbers")) .performScrollTo() .performClick() @@ -479,7 +479,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { composeTestRule.onNodeWithText("Minimum numbers") .assertTextEquals("Minimum numbers", "$initialMinNumbers") .onChildren() - .filterToOne(hasContentDescription("\u2212")) + .filterToOne(hasContentDescription("Decrease Minimum numbers")) .performScrollTo() .performClick() @@ -499,7 +499,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { composeTestRule.onNodeWithText("Minimum numbers") .assertTextEquals("Minimum numbers", "$initialMinNumbers") .onChildren() - .filterToOne(hasContentDescription("+")) + .filterToOne(hasContentDescription("Increase Minimum numbers")) .performScrollTo() .performClick() @@ -515,7 +515,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { composeTestRule.onNodeWithText("Minimum special") .assertTextEquals("Minimum special", "1") .onChildren() - .filterToOne(hasContentDescription("\u2212")) + .filterToOne(hasContentDescription("Decrease Minimum special")) .performScrollTo() .performClick() @@ -536,7 +536,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { composeTestRule.onNodeWithText("Minimum special") .assertTextEquals("Minimum special", "1") .onChildren() - .filterToOne(hasContentDescription("+")) + .filterToOne(hasContentDescription("Increase Minimum special")) .performScrollTo() .performClick() @@ -562,7 +562,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { composeTestRule.onNodeWithText("Minimum special") .assertTextEquals("Minimum special", "$initialSpecialChars") .onChildren() - .filterToOne(hasContentDescription("\u2212")) + .filterToOne(hasContentDescription("Decrease Minimum special")) .performScrollTo() .performClick() verify(exactly = 1) { viewModel.trySendAction(GeneratorAction.LifecycleResume) } @@ -571,7 +571,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { @Suppress("MaxLineLength") @Test - fun `in Password state, decrementing the minimum special characters above 9 should do nothing`() { + fun `in Password state, incrementing the minimum special characters above 9 should do nothing`() { val initialSpecialChars = 9 updateState( DEFAULT_STATE.copy( @@ -582,7 +582,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { composeTestRule.onNodeWithText("Minimum special") .assertTextEquals("Minimum special", "$initialSpecialChars") .onChildren() - .filterToOne(hasContentDescription("+")) + .filterToOne(hasContentDescription("Increase Minimum special")) .performScrollTo() .performClick() verify(exactly = 1) { viewModel.trySendAction(GeneratorAction.LifecycleResume) } @@ -675,7 +675,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { composeTestRule.onNodeWithText("Minimum numbers") .assertTextEquals("Minimum numbers", "5") .onChildren() - .filterToOne(hasContentDescription("\u2212")) + .filterToOne(hasContentDescription("Decrease Minimum numbers")) .performScrollTo() .performClick() @@ -706,7 +706,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { .onNodeWithText("Minimum numbers") .assertTextEquals("Minimum numbers", "7") .onChildren() - .filterToOne(hasContentDescription("\u2212")) + .filterToOne(hasContentDescription("Decrease Minimum numbers")) .performScrollTo() .assertIsDisplayed() } @@ -726,7 +726,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { composeTestRule.onNodeWithText("Minimum special") .assertTextEquals("Minimum special", "5") .onChildren() - .filterToOne(hasContentDescription("\u2212")) + .filterToOne(hasContentDescription("Decrease Minimum special")) .performScrollTo() .performClick() @@ -756,7 +756,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { composeTestRule.onNodeWithText("Minimum special") .assertTextEquals("Minimum special", "7") .onChildren() - .filterToOne(hasContentDescription("\u2212")) + .filterToOne(hasContentDescription("Decrease Minimum special")) .performScrollTo() .performClick() } @@ -806,7 +806,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { composeTestRule.onNodeWithText("Number of words") .assertTextEquals("Number of words", "5") .onChildren() - .filterToOne(hasContentDescription("\u2212")) + .filterToOne(hasContentDescription("Decrease Number of words")) .performScrollTo() .performClick() @@ -836,7 +836,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { .onNodeWithText("Number of words") .assertTextEquals("Number of words", "$initialNumWords") .onChildren() - .filterToOne(hasContentDescription("\u2212")) + .filterToOne(hasContentDescription("Decrease Number of words")) .performScrollTo() .performClick() @@ -863,7 +863,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { .onNodeWithText("Number of words") .assertTextEquals("Number of words", "$initialNumWords") .onChildren() - .filterToOne(hasContentDescription("\u2212")) + .filterToOne(hasContentDescription("Decrease Number of words")) .performScrollTo() .performClick() verify(exactly = 1) { viewModel.trySendAction(GeneratorAction.LifecycleResume) } @@ -884,7 +884,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { .onNodeWithText("Number of words") .assertTextEquals("Number of words", "$initialNumWords") .onChildren() - .filterToOne(hasContentDescription("+")) + .filterToOne(hasContentDescription("Increase Number of words")) .performScrollTo() .performClick() verify(exactly = 1) { viewModel.trySendAction(GeneratorAction.LifecycleResume) } @@ -905,7 +905,7 @@ class GeneratorScreenTest : BitwardenComposeTest() { .onNodeWithText("Number of words") .assertTextEquals("Number of words", "3") .onChildren() - .filterToOne(hasContentDescription("+")) + .filterToOne(hasContentDescription("Increase Number of words")) .performScrollTo() .performClick() diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/addedit/AddEditSendScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/addedit/AddEditSendScreenTest.kt index 8fdbe0cb7c..83925bc16e 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/addedit/AddEditSendScreenTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/addedit/AddEditSendScreenTest.kt @@ -758,7 +758,7 @@ class AddEditSendScreenTest : BitwardenComposeTest() { .performClick() composeTestRule - .onNodeWithContentDescription("\u2212") + .onNodeWithContentDescription("Decrease Maximum access count") .performScrollTo() .performClick() } @@ -779,7 +779,7 @@ class AddEditSendScreenTest : BitwardenComposeTest() { .performClick() composeTestRule - .onNodeWithContentDescription("\u2212") + .onNodeWithContentDescription("Decrease Maximum access count") .performScrollTo() .performClick() verify { viewModel.trySendAction(AddEditSendAction.MaxAccessCountChange(2)) } @@ -794,7 +794,7 @@ class AddEditSendScreenTest : BitwardenComposeTest() { .performClick() composeTestRule - .onNodeWithContentDescription("+") + .onNodeWithContentDescription("Increase Maximum access count") .performScrollTo() .performClick() verify { viewModel.trySendAction(AddEditSendAction.MaxAccessCountChange(1)) } 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 47c332ee18..51e66267f0 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 @@ -164,7 +164,7 @@ fun ItemListingScreen( label = BitwardenString.scan_a_qr_code.asText(), icon = IconData.Local( iconRes = BitwardenDrawable.ic_camera_small, - contentDescription = BitwardenString.scan_a_qr_code.asText(), + contentDescription = null, testTag = "ScanQRCodeButton", ), onFabOptionClick = { launcher.launch(Manifest.permission.CAMERA) }, @@ -173,7 +173,7 @@ fun ItemListingScreen( label = BitwardenString.enter_key_manually.asText(), icon = IconData.Local( iconRes = BitwardenDrawable.ic_lock_encrypted_small, - contentDescription = BitwardenString.enter_key_manually.asText(), + contentDescription = null, testTag = "EnterSetupKeyButton", ), onFabOptionClick = { diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/VaultVerificationCodeItem.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/VaultVerificationCodeItem.kt index 2ce301328d..eb2319c529 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/VaultVerificationCodeItem.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/VaultVerificationCodeItem.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextDirection @@ -116,6 +117,7 @@ fun VaultVerificationCodeItem( .cardStyle( cardStyle = cardStyle, onClick = onItemClick, + role = Role.Button, paddingStart = 16.dp, paddingEnd = 4.dp, ), diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreenTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreenTest.kt index 9b7fb4ccd2..30c596cd4c 100644 --- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreenTest.kt +++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreenTest.kt @@ -357,7 +357,7 @@ class EditItemScreenTest : AuthenticatorComposeTest() { composeTestRule .onNodeWithTextAfterScroll(text = "Number of digits") .onChildren() - .filterToOne(hasContentDescription("+")) + .filterToOne(hasContentDescription("Increase Number of digits")) .performClick() verify(exactly = 1) { @@ -373,7 +373,7 @@ class EditItemScreenTest : AuthenticatorComposeTest() { composeTestRule .onNodeWithTextAfterScroll(text = "Number of digits") .onChildren() - .filterToOne(hasContentDescription("\u2212")) + .filterToOne(hasContentDescription("Decrease Number of digits")) .performClick() verify(exactly = 1) { diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/base/util/ModifierExtensions.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/base/util/ModifierExtensions.kt index 2ce277af70..b6ab000f43 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/base/util/ModifierExtensions.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/base/util/ModifierExtensions.kt @@ -44,6 +44,7 @@ import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection @@ -145,6 +146,7 @@ fun Modifier.nullableClickable( indicationColor: Color = BitwardenTheme.colorScheme.background.pressed, enabled: Boolean = true, onClick: (() -> Unit)?, + role: Role? = null, ): Modifier = onClick ?.let { @@ -153,6 +155,7 @@ fun Modifier.nullableClickable( indication = ripple(color = indicationColor), onClick = it, enabled = enabled, + role = role, ) } ?: this @@ -394,6 +397,7 @@ private data class StandardHorizontalMarginElement( fun Modifier.cardStyle( cardStyle: CardStyle?, onClick: (() -> Unit)? = null, + role: Role? = null, clickEnabled: Boolean = true, paddingHorizontal: Dp = 0.dp, paddingVertical: Dp = 12.dp, @@ -403,6 +407,7 @@ fun Modifier.cardStyle( this.cardStyle( cardStyle = cardStyle, onClick = onClick, + role = role, clickEnabled = clickEnabled, padding = PaddingValues( horizontal = paddingHorizontal, @@ -421,6 +426,7 @@ fun Modifier.cardStyle( fun Modifier.cardStyle( cardStyle: CardStyle?, onClick: (() -> Unit)? = null, + role: Role? = null, clickEnabled: Boolean = true, paddingStart: Dp = 0.dp, paddingTop: Dp = 12.dp, @@ -432,6 +438,7 @@ fun Modifier.cardStyle( this.cardStyle( cardStyle = cardStyle, onClick = onClick, + role = role, clickEnabled = clickEnabled, padding = PaddingValues( start = paddingStart, @@ -452,6 +459,7 @@ fun Modifier.cardStyle( fun Modifier.cardStyle( cardStyle: CardStyle?, onClick: (() -> Unit)? = null, + role: Role? = null, clickEnabled: Boolean = true, padding: PaddingValues = PaddingValues(horizontal = 0.dp, vertical = 12.dp), containerColor: Color = BitwardenTheme.colorScheme.background.secondary, @@ -466,6 +474,7 @@ fun Modifier.cardStyle( onClick = onClick, enabled = clickEnabled, indicationColor = indicationColor, + role = role, ) .cardPadding( cardStyle = cardStyle, diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/account/BitwardenAccountSwitcher.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/account/BitwardenAccountSwitcher.kt index b0313173b0..0bddac60a8 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/account/BitwardenAccountSwitcher.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/account/BitwardenAccountSwitcher.kt @@ -44,6 +44,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -294,6 +295,7 @@ private fun AccountSummaryItem( ), onClick = { onSwitchAccountClick(accountSummary) }, onLongClick = { onSwitchAccountLongClick(accountSummary) }, + role = Role.Button, ) .padding(vertical = 8.dp) .then(modifier), @@ -422,6 +424,7 @@ private fun AddAccountItem( color = BitwardenTheme.colorScheme.background.pressed, ), onClick = onClick, + role = Role.Button, ) .padding(vertical = 8.dp) .then(modifier), diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/appbar/action/BitwardenOverflowActionItem.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/appbar/action/BitwardenOverflowActionItem.kt index 975011c9a2..f2a774978e 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/appbar/action/BitwardenOverflowActionItem.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/appbar/action/BitwardenOverflowActionItem.kt @@ -17,8 +17,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.hideFromAccessibility +import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.tooling.preview.Preview @@ -109,7 +111,8 @@ private fun BitwardenDropdownMenuItem( DropdownMenuItem( modifier = modifier .semantics(mergeDescendants = true) { - contentDescription = contentDescriptionText + this.contentDescription = contentDescriptionText + this.role = Role.Button } .testTag(tag = "FloatingOptionsItem"), colors = bitwardenMenuItemColors( diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/card/BitwardenActionCardSmall.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/card/BitwardenActionCardSmall.kt index 0cff52403e..c5e7b151c3 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/card/BitwardenActionCardSmall.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/card/BitwardenActionCardSmall.kt @@ -21,6 +21,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.VectorPainter +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.bitwarden.ui.platform.components.card.color.bitwardenCardColors @@ -45,10 +48,10 @@ fun BitwardenActionCardSmall( Card( onClick = onCardClicked, shape = BitwardenTheme.shapes.actionCard, - modifier = modifier, colors = colors, elevation = CardDefaults.elevatedCardElevation(), border = BorderStroke(width = 1.dp, color = BitwardenTheme.colorScheme.stroke.border), + modifier = modifier.semantics { this.role = Role.Button }, ) { Row( modifier = Modifier diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/dialog/row/BitwardenBasicDialogRow.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/dialog/row/BitwardenBasicDialogRow.kt index bc3f99b703..259c5f971c 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/dialog/row/BitwardenBasicDialogRow.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/dialog/row/BitwardenBasicDialogRow.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import com.bitwarden.ui.platform.components.dialog.BitwardenSelectionDialog import com.bitwarden.ui.platform.theme.BitwardenTheme @@ -38,6 +39,7 @@ fun BitwardenBasicDialogRow( color = BitwardenTheme.colorScheme.background.pressed, ), onClick = onClick, + role = Role.Button, ) .padding( vertical = 16.dp, diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/dialog/row/BitwardenSelectionRow.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/dialog/row/BitwardenSelectionRow.kt index 70b19fd00c..acb6c97e58 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/dialog/row/BitwardenSelectionRow.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/dialog/row/BitwardenSelectionRow.kt @@ -1,19 +1,15 @@ package com.bitwarden.ui.platform.components.dialog.row -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.selection.selectable import androidx.compose.material3.Text -import androidx.compose.material3.ripple import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag -import androidx.compose.ui.semantics.selected -import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import com.bitwarden.ui.platform.components.radio.BitwardenRadioButton import com.bitwarden.ui.platform.theme.BitwardenTheme @@ -37,16 +33,11 @@ fun BitwardenSelectionRow( modifier = modifier .fillMaxWidth() .testTag("AlertRadioButtonOption") - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple( - color = BitwardenTheme.colorScheme.background.pressed, - ), + .selectable( + selected = isSelected, onClick = onClick, - ) - .semantics(mergeDescendants = true) { - selected = isSelected - }, + role = Role.RadioButton, + ), verticalAlignment = Alignment.CenterVertically, ) { BitwardenRadioButton( diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/field/BitwardenTextField.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/field/BitwardenTextField.kt index d2b2f6770e..db1b1ee598 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/field/BitwardenTextField.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/field/BitwardenTextField.kt @@ -389,7 +389,10 @@ fun BitwardenTextField( visualTransformation = visualTransformation, modifier = Modifier .nullableTestTag(tag = textFieldTestTag) - .menuAnchor(type = ExposedDropdownMenuAnchorType.PrimaryEditable) + .menuAnchor( + type = ExposedDropdownMenuAnchorType.PrimaryEditable, + enabled = false, + ) .fillMaxWidth() .focusRequester(focusRequester) .onFocusChanged { focusState -> diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/header/BitwardenExpandingHeader.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/header/BitwardenExpandingHeader.kt index eacf13bd97..3893824a8f 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/header/BitwardenExpandingHeader.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/header/BitwardenExpandingHeader.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp @@ -57,6 +58,7 @@ fun BitwardenExpandingHeader( }, ), onClick = onClick, + role = Role.Button, ) .minimumInteractiveComponentSize() .padding(horizontal = 16.dp) diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/row/BitwardenPushRow.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/row/BitwardenPushRow.kt index 3db736bb39..64510dc528 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/row/BitwardenPushRow.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/row/BitwardenPushRow.kt @@ -13,6 +13,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.bitwarden.ui.platform.base.util.cardStyle @@ -53,6 +54,7 @@ fun BitwardenPushRow( .cardStyle( cardStyle = cardStyle, onClick = onClick, + role = Role.Button, paddingStart = leadingIcon?.let { 12.dp } ?: 16.dp, paddingEnd = 20.dp, paddingTop = 6.dp, diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/row/BitwardenTextRow.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/row/BitwardenTextRow.kt index 36591766e3..e701b47887 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/row/BitwardenTextRow.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/row/BitwardenTextRow.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.AnnotatedString @@ -75,6 +76,7 @@ fun BitwardenTextRow( cardStyle = cardStyle, onClick = onClick, clickEnabled = clickable, + role = Role.Button, paddingHorizontal = 16.dp, ) .semantics(mergeDescendants = true) { }, diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/stepper/BitwardenStepper.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/stepper/BitwardenStepper.kt index 2f428596a9..d086387d01 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/stepper/BitwardenStepper.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/stepper/BitwardenStepper.kt @@ -7,6 +7,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.bitwarden.ui.platform.base.util.orNullIfBlank @@ -15,6 +16,7 @@ import com.bitwarden.ui.platform.components.field.BitwardenTextField import com.bitwarden.ui.platform.components.field.color.bitwardenTextFieldColors import com.bitwarden.ui.platform.components.model.CardStyle import com.bitwarden.ui.platform.resource.BitwardenDrawable +import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.platform.theme.BitwardenTheme /** @@ -88,7 +90,7 @@ fun BitwardenStepper( * @param textFieldReadOnly whether the text field should be read only. The stepper * increment and decrement buttons function regardless of this value. */ -@Suppress("CyclomaticComplexMethod") +@Suppress("CyclomaticComplexMethod", "LongMethod") @Composable fun BitwardenStepper( label: String?, @@ -116,7 +118,10 @@ fun BitwardenStepper( actions = { BitwardenFilledIconButton( vectorIconRes = BitwardenDrawable.ic_minus, - contentDescription = "\u2212", + contentDescription = stringResource( + id = BitwardenString.stepper_decrement_format, + formatArgs = arrayOf(label ?: stringResource(id = BitwardenString.value)), + ), onClick = { val decrementedValue = ((clampedValue ?: 0) - 1).coerceIn(range) if (decrementedValue != clampedValue) { @@ -128,7 +133,10 @@ fun BitwardenStepper( ) BitwardenFilledIconButton( vectorIconRes = BitwardenDrawable.ic_plus, - contentDescription = "+", + contentDescription = stringResource( + id = BitwardenString.stepper_increment_format, + formatArgs = arrayOf(label ?: stringResource(id = BitwardenString.value)), + ), onClick = { val incrementedValue = ((clampedValue ?: 0) + 1).coerceIn(range) if (incrementedValue != clampedValue) { diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/text/BitwardenClickableText.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/text/BitwardenClickableText.kt index 265c020891..090bde8165 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/text/BitwardenClickableText.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/text/BitwardenClickableText.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.hideFromAccessibility import androidx.compose.ui.semantics.semantics @@ -72,6 +73,7 @@ fun BitwardenClickableText( interactionSource = remember { MutableInteractionSource() }, enabled = isEnabled, onClick = onClick, + role = Role.Button, ) .padding(paddingValues = innerPadding) .semantics(mergeDescendants = true) { diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/toggle/BitwardenSwitch.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/toggle/BitwardenSwitch.kt index 7c0b60c1c9..e38fffeee6 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/toggle/BitwardenSwitch.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/toggle/BitwardenSwitch.kt @@ -19,8 +19,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.hideFromAccessibility +import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.toggleableState import androidx.compose.ui.state.ToggleableState @@ -262,8 +264,9 @@ fun BitwardenSwitch( paddingBottom = 0.dp, ) .semantics(mergeDescendants = true) { - toggleableState = ToggleableState(isChecked) this.contentDescription = contentDescription ?: label.text + this.role = Role.Switch + this.toggleableState = ToggleableState(value = isChecked) }, ) { Row( diff --git a/ui/src/main/res/values/strings.xml b/ui/src/main/res/values/strings.xml index 5af3d2dde2..281b36b0e3 100644 --- a/ui/src/main/res/values/strings.xml +++ b/ui/src/main/res/values/strings.xml @@ -1441,4 +1441,6 @@ Do you want to switch to this account? Receive 24/7 priority support Account creation restricted %1$s only allows invited users to create accounts. + Decrease %1$s + Increase %1$s