diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenReadOnlyTextFieldWithActions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenReadOnlyTextFieldWithActions.kt deleted file mode 100644 index 0f6b04e66..000000000 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenReadOnlyTextFieldWithActions.kt +++ /dev/null @@ -1,99 +0,0 @@ -package com.x8bit.bitwarden.ui.platform.components - -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.size -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.clearAndSetSemantics -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.text -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.input.VisualTransformation -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.x8bit.bitwarden.R - -/** - * Represents a Bitwarden-styled text field accompanied by a series of actions. - * This component allows for a more versatile design by accepting - * icons or actions next to the text field. This composable is read-only and because it uses - * the BitwardenTextField we clear the semantics here to prevent talk back from clarifying the - * component is "editable" or "disabled". - * - * @param label Label for the text field. - * @param value Current text in the text field. - * @param modifier [Modifier] applied to this layout composable. - * @param singleLine when `true`, this text field becomes a single line that horizontally scrolls - * instead of wrapping onto multiple lines. - * @param textStyle An optional style that may be used to override the default used. - * @param shouldAddCustomLineBreaks If `true`, line breaks will be inserted to allow for filling - * an entire line before breaking. `false` by default. - * @param visualTransformation Transforms the visual representation of the input [value]. - * @param actions A lambda containing the set of actions (usually icons or similar) to display - * next to the text field. This lambda extends [RowScope], - * providing flexibility in the layout definition. - */ -@Composable -fun BitwardenReadOnlyTextFieldWithActions( - label: String, - value: String, - modifier: Modifier = Modifier, - singleLine: Boolean = true, - textStyle: TextStyle? = null, - shouldAddCustomLineBreaks: Boolean = false, - visualTransformation: VisualTransformation = VisualTransformation.None, - actions: @Composable RowScope.() -> Unit = {}, -) { - Row( - modifier = modifier - .fillMaxWidth() - .semantics(mergeDescendants = true) {}, - verticalAlignment = Alignment.CenterVertically, - ) { - BitwardenTextField( - modifier = Modifier - .weight(1f) - .clearAndSetSemantics { - contentDescription = "$label, $value" - text = AnnotatedString(label) - }, - readOnly = true, - singleLine = singleLine, - label = label, - value = value, - onValueChange = {}, - textStyle = textStyle, - shouldAddCustomLineBreaks = shouldAddCustomLineBreaks, - visualTransformation = visualTransformation, - ) - BitwardenRowOfActions(actions) - } -} - -@Preview(showBackground = true) -@Composable -private fun BitwardenReadOnlyTextFieldWithActions_preview() { - BitwardenReadOnlyTextFieldWithActions( - label = "Username", - value = "john.doe", - actions = { - Icon( - painter = painterResource(id = R.drawable.ic_tooltip), - contentDescription = "", - modifier = Modifier.size(24.dp), - ) - Icon( - painter = painterResource(id = R.drawable.ic_tooltip), - contentDescription = "", - modifier = Modifier.size(24.dp), - ) - }, - ) -} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenStepper.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenStepper.kt index 86348b7ea..e2c730b41 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenStepper.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenStepper.kt @@ -33,7 +33,7 @@ fun BitwardenStepper( if (clampedValue != value && clampedValue != null) { onValueChange(clampedValue) } - BitwardenReadOnlyTextFieldWithActions( + BitwardenTextFieldWithActions( label = label, // we use a space instead of empty string to make sure label is shown small and above // the input @@ -68,6 +68,8 @@ fun BitwardenStepper( isEnabled = isIncrementEnabled, ) }, + readOnly = true, + onValueChange = {}, modifier = modifier, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenTextFieldWithActions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenTextFieldWithActions.kt index 55bc2e9e5..76ab77391 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenTextFieldWithActions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenTextFieldWithActions.kt @@ -8,7 +8,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.tooling.preview.Preview import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme @@ -36,6 +39,9 @@ fun BitwardenTextFieldWithActions( value: String, onValueChange: (String) -> Unit, modifier: Modifier = Modifier, + textStyle: TextStyle? = null, + shouldAddCustomLineBreaks: Boolean = false, + visualTransformation: VisualTransformation = VisualTransformation.None, readOnly: Boolean = false, singleLine: Boolean = true, keyboardType: KeyboardType = KeyboardType.Text, @@ -43,7 +49,9 @@ fun BitwardenTextFieldWithActions( actions: @Composable RowScope.() -> Unit = {}, ) { Row( - modifier = modifier.fillMaxWidth(), + modifier = modifier + .fillMaxWidth() + .semantics(mergeDescendants = true) { }, verticalAlignment = Alignment.CenterVertically, ) { BitwardenTextField( @@ -56,6 +64,9 @@ fun BitwardenTextFieldWithActions( onValueChange = onValueChange, keyboardType = keyboardType, trailingIconContent = trailingIconContent, + textStyle = textStyle, + shouldAddCustomLineBreaks = shouldAddCustomLineBreaks, + visualTransformation = visualTransformation, ) BitwardenRowOfActions(actions) } @@ -65,9 +76,10 @@ fun BitwardenTextFieldWithActions( @Composable private fun BitwardenTextFieldWithActions_preview() { BitwardenTheme { - BitwardenReadOnlyTextFieldWithActions( + BitwardenTextFieldWithActions( label = "Username", value = "user@example.com", + onValueChange = {}, actions = { Icon( painter = painterResource(id = R.drawable.ic_tooltip), diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt index 67c3db7b7..cef186504 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt @@ -53,10 +53,10 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenMediumTopAppBar import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem import com.x8bit.bitwarden.ui.platform.components.BitwardenPasswordField -import com.x8bit.bitwarden.ui.platform.components.BitwardenReadOnlyTextFieldWithActions import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold import com.x8bit.bitwarden.ui.platform.components.BitwardenStepper import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField +import com.x8bit.bitwarden.ui.platform.components.BitwardenTextFieldWithActions import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData import com.x8bit.bitwarden.ui.platform.components.model.IconResource @@ -290,7 +290,7 @@ private fun GeneratedStringItem( onCopyClick: () -> Unit, onRegenerateClick: () -> Unit, ) { - BitwardenReadOnlyTextFieldWithActions( + BitwardenTextFieldWithActions( label = "", value = generatedText, singleLine = false, @@ -310,6 +310,8 @@ private fun GeneratedStringItem( onClick = onRegenerateClick, ) }, + onValueChange = {}, + readOnly = true, textStyle = LocalNonMaterialTypography.current.sensitiveInfoSmall, shouldAddCustomLineBreaks = true, visualTransformation = nonLetterColorVisualTransformation(), diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt index adc396de4..139576c34 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt @@ -7,13 +7,13 @@ import androidx.compose.ui.test.assert import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsOff import androidx.compose.ui.test.assertIsOn +import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.filterToOne import androidx.compose.ui.test.hasAnyAncestor import androidx.compose.ui.test.hasContentDescription import androidx.compose.ui.test.hasProgressBarRangeInfo import androidx.compose.ui.test.isDialog import androidx.compose.ui.test.onAllNodesWithText -import androidx.compose.ui.test.onChildren import androidx.compose.ui.test.onLast import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText @@ -230,15 +230,17 @@ class GeneratorScreenTest : BaseComposeTest() { .assertIsOff() composeTestRule - .onNodeWithContentDescription("Minimum numbers, 1") - .onChildren() + .onNodeWithText("Minimum numbers") + .assertTextEquals("Minimum numbers", "1") + .onSiblings() .filterToOne(hasContentDescription("\u2212")) .performScrollTo() .assertIsDisplayed() composeTestRule - .onNodeWithContentDescription("Minimum numbers, 1") - .onChildren() + .onNodeWithText("Minimum numbers") + .assertTextEquals("Minimum numbers", "1") + .onSiblings() .filterToOne(hasContentDescription("+")) .performScrollTo() .assertIsDisplayed() @@ -367,8 +369,9 @@ class GeneratorScreenTest : BaseComposeTest() { fun `in Passcode_Password state, decrementing the minimum numbers counter should send MinNumbersCounterChange action`() { val initialMinNumbers = 1 - composeTestRule.onNodeWithContentDescription("Minimum numbers, 1") - .onChildren() + composeTestRule.onNodeWithText("Minimum numbers") + .assertTextEquals("Minimum numbers", "1") + .onSiblings() .filterToOne(hasContentDescription("\u2212")) .performScrollTo() .performClick() @@ -387,8 +390,9 @@ class GeneratorScreenTest : BaseComposeTest() { fun `in Passcode_Password state, incrementing the minimum numbers counter should send MinNumbersCounterChange action`() { val initialMinNumbers = 1 - composeTestRule.onNodeWithContentDescription("Minimum numbers, 1") - .onChildren() + composeTestRule.onNodeWithText("Minimum numbers") + .assertTextEquals("Minimum numbers", "1") + .onSiblings() .filterToOne(hasContentDescription("+")) .performScrollTo() .performClick() @@ -420,8 +424,9 @@ class GeneratorScreenTest : BaseComposeTest() { ), ) - composeTestRule.onNodeWithContentDescription("Minimum numbers, $initialMinNumbers") - .onChildren() + composeTestRule.onNodeWithText("Minimum numbers") + .assertTextEquals("Minimum numbers", "$initialMinNumbers") + .onSiblings() .filterToOne(hasContentDescription("\u2212")) .performScrollTo() .performClick() @@ -447,8 +452,9 @@ class GeneratorScreenTest : BaseComposeTest() { ), ) - composeTestRule.onNodeWithContentDescription("Minimum numbers, $initialMinNumbers") - .onChildren() + composeTestRule.onNodeWithText("Minimum numbers") + .assertTextEquals("Minimum numbers", "$initialMinNumbers") + .onSiblings() .filterToOne(hasContentDescription("+")) .performScrollTo() .performClick() @@ -461,8 +467,9 @@ class GeneratorScreenTest : BaseComposeTest() { fun `in Passcode_Password state, decrementing the minimum special characters counter should send MinSpecialCharactersChange action`() { val initialSpecialChars = 1 - composeTestRule.onNodeWithContentDescription("Minimum special, 1") - .onChildren() + composeTestRule.onNodeWithText("Minimum special") + .assertTextEquals("Minimum special", "1") + .onSiblings() .filterToOne(hasContentDescription("\u2212")) .performScrollTo() .performClick() @@ -481,8 +488,9 @@ class GeneratorScreenTest : BaseComposeTest() { fun `in Passcode_Password state, incrementing the minimum special characters counter should send MinSpecialCharactersChange action`() { val initialSpecialChars = 1 - composeTestRule.onNodeWithContentDescription("Minimum special, 1") - .onChildren() + composeTestRule.onNodeWithText("Minimum special") + .assertTextEquals("Minimum special", "1") + .onSiblings() .filterToOne(hasContentDescription("+")) .performScrollTo() .performClick() @@ -514,8 +522,9 @@ class GeneratorScreenTest : BaseComposeTest() { ), ) - composeTestRule.onNodeWithContentDescription("Minimum special, $initialSpecialChars") - .onChildren() + composeTestRule.onNodeWithText("Minimum special") + .assertTextEquals("Minimum special", "$initialSpecialChars") + .onSiblings() .filterToOne(hasContentDescription("\u2212")) .performScrollTo() .performClick() @@ -541,8 +550,9 @@ class GeneratorScreenTest : BaseComposeTest() { ), ) - composeTestRule.onNodeWithContentDescription("Minimum special, $initialSpecialChars") - .onChildren() + composeTestRule.onNodeWithText("Minimum special") + .assertTextEquals("Minimum special", "$initialSpecialChars") + .onSiblings() .filterToOne(hasContentDescription("+")) .performScrollTo() .performClick() @@ -595,8 +605,9 @@ class GeneratorScreenTest : BaseComposeTest() { // Unicode for "minus" used for content description composeTestRule - .onNodeWithContentDescription("Number of words, $initialNumWords") - .onChildren() + .onNodeWithText("Number of words") + .assertTextEquals("Number of words", "$initialNumWords") + .onSiblings() .filterToOne(hasContentDescription("\u2212")) .performScrollTo() .performClick() @@ -629,8 +640,9 @@ class GeneratorScreenTest : BaseComposeTest() { // Unicode for "minus" used for content description composeTestRule - .onNodeWithContentDescription("Number of words, $initialNumWords") - .onChildren() + .onNodeWithText("Number of words") + .assertTextEquals("Number of words", "$initialNumWords") + .onSiblings() .filterToOne(hasContentDescription("\u2212")) .performScrollTo() .performClick() @@ -656,8 +668,9 @@ class GeneratorScreenTest : BaseComposeTest() { // Unicode for "minus" used for content description composeTestRule - .onNodeWithContentDescription("Number of words, $initialNumWords") - .onChildren() + .onNodeWithText("Number of words") + .assertTextEquals("Number of words", "$initialNumWords") + .onSiblings() .filterToOne(hasContentDescription("+")) .performScrollTo() .performClick() @@ -681,8 +694,9 @@ class GeneratorScreenTest : BaseComposeTest() { ) composeTestRule - .onNodeWithContentDescription("Number of words, 3") - .onChildren() + .onNodeWithText("Number of words") + .assertTextEquals("Number of words", "3") + .onSiblings() .filterToOne(hasContentDescription("+")) .performScrollTo() .performClick()