Merge branch 'main' into QA-1126b/adding-native-sanity-test

This commit is contained in:
ifernandezdiaz
2025-08-11 11:06:36 -03:00
15 changed files with 268 additions and 111 deletions

View File

@@ -9,16 +9,9 @@ on:
permissions: {}
jobs:
check-run:
name: Check PR run
uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main
permissions:
contents: read
sast:
name: Checkmarx
uses: bitwarden/gh-actions/.github/workflows/_checkmarx.yml@main
needs: check-run
secrets:
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
@@ -32,7 +25,6 @@ jobs:
quality:
name: Sonar
uses: bitwarden/gh-actions/.github/workflows/_sonar.yml@main
needs: check-run
secrets:
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}

View File

@@ -1,8 +1,15 @@
package com.x8bit.bitwarden.ui.platform.components.dropdown
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -12,6 +19,7 @@ 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.nullableTestTag
import com.bitwarden.ui.platform.components.button.BitwardenTextSelectionButton
import com.bitwarden.ui.platform.components.model.CardStyle
import com.bitwarden.ui.platform.components.model.TooltipData
@@ -19,8 +27,10 @@ import com.bitwarden.ui.platform.theme.BitwardenTheme
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenSelectionDialog
import com.x8bit.bitwarden.ui.platform.components.dialog.row.BitwardenSelectionRow
import com.x8bit.bitwarden.ui.platform.components.dropdown.model.MultiSelectOption
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
/**
* A custom composable representing a multi-select button.
@@ -32,8 +42,8 @@ import kotlinx.collections.immutable.persistentListOf
* @param options A list of strings representing the available options in the dialog.
* @param selectedOption The currently selected option that is displayed in the [OutlinedTextField]
* (or `null` if no option is selected).
* @param onOptionSelected A lambda that is invoked when an option
* is selected from the dropdown menu.
* @param onOptionSelected A lambda that is invoked when an option is selected from the dropdown
* menu.
* @param isEnabled Whether or not the button is enabled.
* @param cardStyle Indicates the type of card style to be applied.
* @param modifier A [Modifier] that you can use to apply custom modifications to the composable.
@@ -61,18 +71,84 @@ fun BitwardenMultiSelectButton(
textFieldTestTag: String? = null,
actionsPadding: PaddingValues = PaddingValues(end = 4.dp),
actions: @Composable RowScope.() -> Unit = {},
) {
BitwardenMultiSelectButton(
label = label,
options = options.map { MultiSelectOption.Row(it) }.toImmutableList(),
selectedOption = selectedOption?.let { MultiSelectOption.Row(it) },
onOptionSelected = { onOptionSelected(it.title) },
cardStyle = cardStyle,
modifier = modifier,
isEnabled = isEnabled,
supportingContent = supportingText?.let {
{
Text(
text = it,
style = BitwardenTheme.typography.bodySmall,
color = BitwardenTheme.colorScheme.text.secondary,
modifier = Modifier.fillMaxWidth(),
)
}
},
tooltip = tooltip,
insets = insets,
textFieldTestTag = textFieldTestTag,
actionsPadding = actionsPadding,
actions = actions,
)
}
/**
* A custom composable representing a multi-select button.
*
* This composable displays an [OutlinedTextField] with a dropdown icon as a trailing icon.
* When the field is clicked, a dropdown menu appears with a list of options to select from.
*
* @param label The descriptive text label for the [OutlinedTextField].
* @param options A list of [MultiSelectOption] representing the available options in the dialog.
* @param selectedOption The currently selected option that is displayed in the [OutlinedTextField]
* (or `null` if no option is selected).
* @param onOptionSelected A lambda that is invoked when an option is selected from the dropdown
* menu.
* @param isEnabled Whether or not the button is enabled.
* @param supportingContent An optional supporting content that will appear below the button.
* @param cardStyle Indicates the type of card style to be applied.
* @param modifier A [Modifier] that you can use to apply custom modifications to the composable.
* @param tooltip A nullable [TooltipData], representing the tooltip icon.
* @param insets Inner padding to be applied withing the card.
* @param textFieldTestTag The optional test tag associated with the inner text field.
* @param actionsPadding Padding to be applied to the [actions] block.
* @param actions A lambda containing the set of actions (usually icons or similar) to display
* in the app bar's trailing side. This lambda extends [RowScope], allowing flexibility in
* defining the layout of the actions.
*/
@Composable
fun BitwardenMultiSelectButton(
label: String,
options: ImmutableList<MultiSelectOption>,
selectedOption: MultiSelectOption.Row?,
onOptionSelected: (MultiSelectOption.Row) -> Unit,
cardStyle: CardStyle?,
modifier: Modifier = Modifier,
isEnabled: Boolean = true,
supportingContent: @Composable (ColumnScope.() -> Unit)?,
tooltip: TooltipData? = null,
insets: PaddingValues = PaddingValues(),
textFieldTestTag: String? = null,
actionsPadding: PaddingValues = PaddingValues(end = 4.dp),
actions: @Composable RowScope.() -> Unit = {},
) {
var shouldShowDialog by rememberSaveable { mutableStateOf(false) }
BitwardenTextSelectionButton(
label = label,
selectedOption = selectedOption,
selectedOption = selectedOption?.title,
onClick = {
shouldShowDialog = true
},
cardStyle = cardStyle,
enabled = isEnabled,
supportingText = supportingText,
supportingContent = supportingContent,
tooltip = tooltip,
insets = insets,
textFieldTestTag = textFieldTestTag,
@@ -87,14 +163,60 @@ fun BitwardenMultiSelectButton(
title = label,
onDismissRequest = { shouldShowDialog = false },
) {
options.forEach { optionString ->
BitwardenMultiSelectDialogContent(
options = options,
selectedOption = selectedOption,
onOptionSelected = { selectedItem ->
shouldShowDialog = false
onOptionSelected(selectedItem)
},
)
}
}
}
/**
* Renders the list of items within a multi-select dialog.
*
* This composable is typically used as the content for [BitwardenSelectionDialog].
*
* @param options A list of strings representing the available options in the dialog.
* @param selectedOption The currently selected option that is displayed in the [OutlinedTextField]
* (or `null` if no option is selected).
* @param onOptionSelected A lambda that is invoked when an option
* is selected from the dropdown menu.
*/
@Composable
fun ColumnScope.BitwardenMultiSelectDialogContent(
options: ImmutableList<MultiSelectOption>,
selectedOption: MultiSelectOption.Row?,
onOptionSelected: (MultiSelectOption.Row) -> Unit,
) {
options.forEach {
when (it) {
is MultiSelectOption.Header -> {
Column(
modifier = Modifier
.nullableTestTag(tag = it.testTag)
.padding(horizontal = 16.dp)
.fillMaxWidth(),
) {
Spacer(modifier = Modifier.height(height = 4.dp))
Text(
modifier = Modifier.fillMaxWidth(),
text = it.title,
color = BitwardenTheme.colorScheme.text.secondary,
style = BitwardenTheme.typography.titleSmall,
)
}
}
is MultiSelectOption.Row -> {
BitwardenSelectionRow(
text = optionString.asText(),
isSelected = optionString == selectedOption,
onClick = {
shouldShowDialog = false
onOptionSelected(optionString)
},
text = it.title.asText(),
isSelected = it == selectedOption,
onClick = { onOptionSelected(it) },
modifier = Modifier.nullableTestTag(tag = it.testTag),
)
}
}

View File

@@ -0,0 +1,28 @@
package com.x8bit.bitwarden.ui.platform.components.dropdown.model
/**
* Represents an option in a multi-select list, which can either be a header or a selectable row.
*/
sealed class MultiSelectOption {
/**
* The text to display for the option.
*/
abstract val title: String
/**
* Represents a header item in a multi-select list. Headers are used to visually group related
* options within the list.
*/
data class Header(
override val title: String,
val testTag: String? = null,
) : MultiSelectOption()
/**
* Represents a selectable row item in a multi-select list.
*/
data class Row(
override val title: String,
val testTag: String? = null,
) : MultiSelectOption()
}

View File

@@ -31,6 +31,7 @@ fun <T : Any> FlagKey<T>.ListItemContent(
FlagKey.CipherKeyEncryption,
FlagKey.UserManagedPrivilegedApps,
FlagKey.RemoveCardPolicy,
FlagKey.EnrollAeadOnKeyRotation,
-> {
@Suppress("UNCHECKED_CAST")
BooleanFlagItem(
@@ -83,4 +84,8 @@ private fun <T : Any> FlagKey<T>.getDisplayLabel(): String = when (this) {
FlagKey.BitwardenAuthenticationEnabled -> {
stringResource(BitwardenString.bitwarden_authentication_enabled)
}
FlagKey.EnrollAeadOnKeyRotation -> {
stringResource(BitwardenString.enroll_aead_on_key_rotation)
}
}

View File

@@ -124,9 +124,7 @@ class AppearanceScreenTest : BitwardenComposeTest() {
@Test
fun `on theme row click should display theme selection dialog`() {
composeTestRule
.onNodeWithContentDescription(
label = "Default (System). Theme. Change the application's color theme",
)
.onNodeWithContentDescription(label = "Default (System). Theme")
.performScrollTo()
.performClick()
composeTestRule
@@ -138,9 +136,7 @@ class AppearanceScreenTest : BitwardenComposeTest() {
@Test
fun `on theme selection dialog item click should send ThemeChange`() {
composeTestRule
.onNodeWithContentDescription(
label = "Default (System). Theme. Change the application's color theme",
)
.onNodeWithContentDescription(label = "Default (System). Theme")
.performScrollTo()
.performClick()
composeTestRule
@@ -161,9 +157,7 @@ class AppearanceScreenTest : BitwardenComposeTest() {
@Test
fun `on theme selection dialog cancel click should dismiss dialog`() {
composeTestRule
.onNodeWithContentDescription(
label = "Default (System). Theme. Change the application's color theme",
)
.onNodeWithContentDescription(label = "Default (System). Theme")
.performScrollTo()
.performClick()
composeTestRule

View File

@@ -240,9 +240,7 @@ class AutoFillScreenTest : BitwardenComposeTest() {
}
composeTestRule
.onNodeWithContentDescription(
label = "Popup (shows over input field). Display autofill suggestions. " +
"Choose how your autofill suggestions will appear when you sign in " +
"to other apps on your device.",
label = "Popup (shows over input field). Display autofill suggestions",
)
.performScrollTo()
.performClick()
@@ -267,18 +265,14 @@ class AutoFillScreenTest : BitwardenComposeTest() {
}
composeTestRule
.onNodeWithContentDescription(
label = "Inline (shows in keyboard). Display autofill suggestions. " +
"Choose how your autofill suggestions will appear when you sign in " +
"to other apps on your device.",
label = "Inline (shows in keyboard). Display autofill suggestions",
)
.performScrollTo()
.assertIsDisplayed()
mutableStateFlow.update { it.copy(autofillStyle = AutofillStyle.POPUP) }
composeTestRule
.onNodeWithContentDescription(
label = "Popup (shows over input field). Display autofill suggestions. " +
"Choose how your autofill suggestions will appear when you sign in " +
"to other apps on your device.",
label = "Popup (shows over input field). Display autofill suggestions",
)
.performScrollTo()
.assertIsDisplayed()
@@ -292,9 +286,7 @@ class AutoFillScreenTest : BitwardenComposeTest() {
composeTestRule
.onNodeWithContentDescription(
label = "Inline (shows in keyboard). Display autofill suggestions. " +
"Choose how your autofill suggestions will appear when you sign in " +
"to other apps on your device.",
label = "Inline (shows in keyboard). Display autofill suggestions",
)
.performScrollTo()
.assertIsDisplayed()
@@ -305,9 +297,7 @@ class AutoFillScreenTest : BitwardenComposeTest() {
composeTestRule
.onNodeWithContentDescription(
label = "Inline (shows in keyboard). Display autofill suggestions. " +
"Choose how your autofill suggestions will appear when you sign in " +
"to other apps on your device.",
label = "Inline (shows in keyboard). Display autofill suggestions",
)
.assertDoesNotExist()
}
@@ -350,9 +340,7 @@ class AutoFillScreenTest : BitwardenComposeTest() {
composeTestRule
.onNodeWithContentDescription(
label = "Inline (shows in keyboard). Display autofill suggestions. " +
"Choose how your autofill suggestions will appear when you sign in " +
"to other apps on your device.",
label = "Inline (shows in keyboard). Display autofill suggestions",
)
.performScrollTo()
.assertIsDisplayed()
@@ -363,9 +351,7 @@ class AutoFillScreenTest : BitwardenComposeTest() {
composeTestRule
.onNodeWithContentDescription(
label = "Inline (shows in keyboard). Display autofill suggestions. " +
"Choose how your autofill suggestions will appear when you sign in " +
"to other apps on your device.",
label = "Inline (shows in keyboard). Display autofill suggestions",
)
.assertDoesNotExist()
}
@@ -418,7 +404,7 @@ class AutoFillScreenTest : BitwardenComposeTest() {
fun `on default URI match type click should display dialog`() {
composeTestRule.assertNoDialogExists()
composeTestRule
.onNodeWithContentDescription(label = "Default URI match detection.", substring = true)
.onNodeWithText(text = "Default URI match detection")
.performScrollTo()
.assert(!hasAnyAncestor(isDialog()))
.performClick()
@@ -432,7 +418,7 @@ class AutoFillScreenTest : BitwardenComposeTest() {
@Test
fun `on default URI match type dialog item click should send DefaultUriMatchTypeSelect and close the dialog`() {
composeTestRule
.onNodeWithContentDescription(label = "Default URI match detection.", substring = true)
.onNodeWithText(text = "Default URI match detection")
.performScrollTo()
.performClick()
@@ -454,7 +440,7 @@ class AutoFillScreenTest : BitwardenComposeTest() {
@Test
fun `on default URI match type dialog cancel click should close the dialog`() {
composeTestRule
.onNodeWithContentDescription(label = "Default URI match detection.", substring = true)
.onNodeWithText(text = "Default URI match detection")
.performScrollTo()
.performClick()

View File

@@ -1,13 +1,16 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.filterToOne
import androidx.compose.ui.test.hasAnyAncestor
import androidx.compose.ui.test.isDialog
import androidx.compose.ui.test.isDisplayed
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.printToLog
import androidx.core.net.toUri
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.ui.util.assertNoDialogExists
@@ -83,13 +86,14 @@ class FlightRecorderScreenTest : BitwardenComposeTest() {
@Test
fun `on logging duration click should display select dialog`() {
composeTestRule.assertNoDialogExists()
composeTestRule.onRoot().printToLog("BRAIN")
composeTestRule
.onNodeWithContentDescription(label = "1 hour. Logging duration")
.performScrollTo()
.performClick()
composeTestRule
.onNodeWithText(text = "Logging duration")
.assert(hasAnyAncestor(isDialog()))
.onAllNodesWithText(text = "Logging duration")
.filterToOne(hasAnyAncestor(isDialog()))
.isDisplayed()
}

View File

@@ -97,10 +97,7 @@ class OtherScreenTest : BitwardenComposeTest() {
@Test
fun `on clear clipboard row click should show show clipboard selection dialog`() {
composeTestRule
.onNodeWithContentDescription(
label = "Never. Clear clipboard. " +
"Automatically clear copied values from your clipboard.",
)
.onNodeWithContentDescription(label = "Never. Clear clipboard")
.performScrollTo()
.performClick()
composeTestRule
@@ -112,10 +109,7 @@ class OtherScreenTest : BitwardenComposeTest() {
@Test
fun `on clear clipboard dialog item click should send ClearClipboardFrequencyChange`() {
composeTestRule
.onNodeWithContentDescription(
label = "Never. Clear clipboard. " +
"Automatically clear copied values from your clipboard.",
)
.onNodeWithContentDescription(label = "Never. Clear clipboard")
.performScrollTo()
.performClick()
composeTestRule
@@ -136,10 +130,7 @@ class OtherScreenTest : BitwardenComposeTest() {
@Test
fun `on clear clipboard dialog cancel should dismiss dialog`() {
composeTestRule
.onNodeWithContentDescription(
label = "Never. Clear clipboard. " +
"Automatically clear copied values from your clipboard.",
)
.onNodeWithContentDescription(label = "Never. Clear clipboard")
.performScrollTo()
.performClick()
composeTestRule.onNodeWithText("Cancel").performClick()

View File

@@ -229,7 +229,6 @@ class GeneratorScreenTest : BitwardenComposeTest() {
.assertDoesNotExist()
}
@Suppress("MaxLineLength")
@Test
fun `clicking a UsernameOption should send UsernameTypeOption action`() {
updateState(
@@ -242,9 +241,7 @@ class GeneratorScreenTest : BitwardenComposeTest() {
// Opens the menu
composeTestRule
.onNodeWithContentDescription(
label = "Plus addressed email. Username type. Use your email provider's subaddress capabilities",
)
.onNodeWithContentDescription(label = "Plus addressed email. Username type")
.performClick()
// Choose the option from the menu
@@ -1459,12 +1456,8 @@ class GeneratorScreenTest : BitwardenComposeTest() {
fun `in Username state, clicking the tooltip icon should send the TooltipClick action`() {
updateState(DEFAULT_STATE.copy(selectedType = GeneratorState.MainType.Username()))
@Suppress("MaxLineLength")
composeTestRule
.onNodeWithContentDescription(
label = "Plus addressed email. Username type. Use your email provider's subaddress capabilities",
useUnmergedTree = true,
)
.onNodeWithContentDescription(label = "Plus addressed email. Username type")
// Find the button
.onChildren()
.filterToOne(hasClickAction())

View File

@@ -121,7 +121,7 @@ class VaultMoveToOrganizationScreenTest : BitwardenComposeTest() {
@Test
fun `the organization option field description should update according to state`() {
composeTestRule
.onNodeWithContentDescription(label = "Choose an organization that", substring = true)
.onNodeWithText(text = "Choose an organization that", substring = true)
.assertIsDisplayed()
mutableStateFlow.update { currentState ->
@@ -130,7 +130,7 @@ class VaultMoveToOrganizationScreenTest : BitwardenComposeTest() {
composeTestRule
composeTestRule
.onNodeWithContentDescription(label = "Choose an organization that", substring = true)
.onNodeWithText(text = "Choose an organization that", substring = true)
.assertIsNotDisplayed()
}
@@ -169,12 +169,7 @@ class VaultMoveToOrganizationScreenTest : BitwardenComposeTest() {
@Test
fun `selecting an organization should send OrganizationSelect action`() {
composeTestRule
.onNodeWithContentDescriptionAfterScroll(
label = "mockOrganizationName-1. Organization. " +
"Choose an organization that you wish to move this item to. Moving to an " +
"organization transfers ownership of the item to that organization. You " +
"will no longer be the direct owner of this item once it has been moved.",
)
.onNodeWithContentDescriptionAfterScroll(label = "mockOrganizationName-1. Organization")
.performClick()
// Choose the option from the menu
composeTestRule
@@ -205,12 +200,7 @@ class VaultMoveToOrganizationScreenTest : BitwardenComposeTest() {
@Test
fun `the organization option field should display according to state`() {
composeTestRule
.onNodeWithContentDescriptionAfterScroll(
label = "mockOrganizationName-1. Organization. " +
"Choose an organization that you wish to move this item to. Moving to an " +
"organization transfers ownership of the item to that organization. You " +
"will no longer be the direct owner of this item once it has been moved.",
)
.onNodeWithContentDescriptionAfterScroll(label = "mockOrganizationName-1. Organization")
.assertIsDisplayed()
mutableStateFlow.update { currentState ->
@@ -223,12 +213,7 @@ class VaultMoveToOrganizationScreenTest : BitwardenComposeTest() {
}
composeTestRule
.onNodeWithContentDescriptionAfterScroll(
label = "mockOrganizationName-2. Organization. " +
"Choose an organization that you wish to move this item to. Moving to an " +
"organization transfers ownership of the item to that organization. You " +
"will no longer be the direct owner of this item once it has been moved.",
)
.onNodeWithContentDescriptionAfterScroll(label = "mockOrganizationName-2. Organization")
.assertIsDisplayed()
}

View File

@@ -28,6 +28,7 @@ fun <T : Any> FlagKey<T>.ListItemContent(
FlagKey.CredentialExchangeProtocolImport,
FlagKey.RemoveCardPolicy,
FlagKey.UserManagedPrivilegedApps,
FlagKey.EnrollAeadOnKeyRotation,
-> BooleanFlagItem(
label = flagKey.getDisplayLabel(),
key = flagKey as FlagKey<Boolean>,
@@ -76,4 +77,8 @@ private fun <T : Any> FlagKey<T>.getDisplayLabel(): String = when (this) {
FlagKey.BitwardenAuthenticationEnabled -> {
stringResource(BitwardenString.bitwarden_authentication_enabled)
}
FlagKey.EnrollAeadOnKeyRotation -> {
stringResource(BitwardenString.enroll_aead_on_key_rotation)
}
}

View File

@@ -89,6 +89,14 @@ sealed class FlagKey<out T : Any> {
override val defaultValue: Boolean = false
}
/**
* Represents the feature flag to enable the enrollment of AEAD on key rotation.
*/
data object EnrollAeadOnKeyRotation : FlagKey<Boolean>() {
override val keyName: String = "enroll-aead-on-key-rotation"
override val defaultValue: Boolean = false
}
//region Dummy keys for testing
/**
* Data object holding the key for a [Boolean] flag to be used in tests.

View File

@@ -32,6 +32,10 @@ class FlagKeyTest {
FlagKey.BitwardenAuthenticationEnabled.keyName,
"bitwarden-authentication-enabled",
)
assertEquals(
FlagKey.EnrollAeadOnKeyRotation.keyName,
"enroll-aead-on-key-rotation",
)
}
@Test
@@ -44,6 +48,7 @@ class FlagKeyTest {
FlagKey.UserManagedPrivilegedApps,
FlagKey.RemoveCardPolicy,
FlagKey.BitwardenAuthenticationEnabled,
FlagKey.EnrollAeadOnKeyRotation,
).all {
!it.defaultValue
},

View File

@@ -2,6 +2,7 @@ package com.bitwarden.ui.platform.components.button
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
@@ -20,10 +21,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.CustomAccessibilityAction
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.customActions
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -42,7 +43,6 @@ import com.bitwarden.ui.platform.theme.BitwardenTheme
/**
* A button which uses a read-only text field for layout and style purposes.
*/
@Suppress("LongMethod")
@Composable
fun BitwardenTextSelectionButton(
label: String,
@@ -58,15 +58,60 @@ fun BitwardenTextSelectionButton(
semanticRole: Role = Role.Button,
actionsPadding: PaddingValues = PaddingValues(end = 4.dp),
actions: @Composable RowScope.() -> Unit = {},
) {
BitwardenTextSelectionButton(
label = label,
selectedOption = selectedOption,
onClick = onClick,
cardStyle = cardStyle,
modifier = modifier,
enabled = enabled,
tooltip = tooltip,
insets = insets,
textFieldTestTag = textFieldTestTag,
semanticRole = semanticRole,
actionsPadding = actionsPadding,
actions = actions,
supportingContent = supportingText?.let {
{
Text(
text = it,
style = BitwardenTheme.typography.bodySmall,
color = BitwardenTheme.colorScheme.text.secondary,
modifier = Modifier.fillMaxWidth(),
)
}
},
)
}
/**
*
* A button which uses a read-only text field for layout and style purposes.
*/
@Suppress("LongMethod")
@Composable
fun BitwardenTextSelectionButton(
label: String,
selectedOption: String?,
onClick: () -> Unit,
cardStyle: CardStyle?,
modifier: Modifier = Modifier,
enabled: Boolean = true,
tooltip: TooltipData? = null,
insets: PaddingValues = PaddingValues(),
textFieldTestTag: String? = null,
semanticRole: Role = Role.Button,
actionsPadding: PaddingValues = PaddingValues(end = 4.dp),
supportingContent: @Composable (ColumnScope.() -> Unit)?,
actions: @Composable RowScope.() -> Unit = {},
) {
Column(
modifier = modifier
.defaultMinSize(minHeight = 60.dp)
.clearAndSetSemantics {
.semantics {
role = semanticRole
contentDescription = supportingText
?.let { "$selectedOption. $label. $it" }
?: "$selectedOption. $label"
contentDescription = "$selectedOption. $label"
customActions = persistentListOfNotNull(
tooltip?.let {
CustomAccessibilityAction(
@@ -132,7 +177,7 @@ fun BitwardenTextSelectionButton(
.nullableTestTag(tag = textFieldTestTag)
.fillMaxWidth(),
)
supportingText
supportingContent
?.let { content ->
Spacer(modifier = Modifier.height(height = 6.dp))
BitwardenHorizontalDivider(
@@ -145,14 +190,7 @@ fun BitwardenTextSelectionButton(
modifier = Modifier
.defaultMinSize(minHeight = 48.dp)
.padding(vertical = 12.dp, horizontal = 16.dp),
content = {
Text(
text = content,
style = BitwardenTheme.typography.bodySmall,
color = BitwardenTheme.colorScheme.text.secondary,
modifier = Modifier.fillMaxWidth(),
)
},
content = content,
)
}
?: Spacer(modifier = Modifier.height(height = cardStyle?.let { 6.dp } ?: 0.dp))

View File

@@ -28,6 +28,7 @@
<string name="import_format_label_2fas_json">2FAS (no password)</string>
<string name="import_format_label_lastpass_json">LastPass (.json)</string>
<string name="import_format_label_aegis_json">Aegis (.json)</string>
<string name="enroll_aead_on_key_rotation">Enroll AEAD on key rotation</string>
<!-- endregion Debug Menu -->
</resources>