Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import com.bitwarden.ui.platform.base.util.withLineBreaksAtWidth
import com.bitwarden.ui.platform.base.util.withVisualTransformation
import com.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
import com.bitwarden.ui.platform.components.model.CardStyle
import com.bitwarden.ui.platform.components.util.compoundVisualTransformation
import com.bitwarden.ui.platform.components.util.forceLtrVisualTransformation
import com.bitwarden.ui.platform.components.util.nonLetterColorVisualTransformation
import com.bitwarden.ui.platform.resource.BitwardenDrawable
import com.bitwarden.ui.platform.resource.BitwardenString
Expand Down Expand Up @@ -61,7 +63,10 @@ fun PasswordHistoryListItem(
)
Text(
text = formattedText.withVisualTransformation(
visualTransformation = nonLetterColorVisualTransformation(),
visualTransformation = compoundVisualTransformation(
nonLetterColorVisualTransformation(),
forceLtrVisualTransformation(),
),
),
style = textStyle,
color = BitwardenTheme.colorScheme.text.primary,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
import com.bitwarden.ui.platform.base.util.toListItemCardStyle
Expand All @@ -27,6 +28,7 @@ import com.bitwarden.ui.platform.components.header.BitwardenListHeaderText
import com.bitwarden.ui.platform.components.icon.model.IconData
import com.bitwarden.ui.platform.components.model.CardStyle
import com.bitwarden.ui.platform.components.text.BitwardenHyperTextLink
import com.bitwarden.ui.platform.components.util.forceLtrVisualTransformation
import com.bitwarden.ui.platform.resource.BitwardenDrawable
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.platform.theme.BitwardenTheme
Expand Down Expand Up @@ -149,6 +151,7 @@ fun VaultItemIdentityContent(
index = identityState.propertyList.indexOf(element = ssn),
dividerPadding = 0.dp,
),
forceLtr = true,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin()
Expand All @@ -171,6 +174,7 @@ fun VaultItemIdentityContent(
index = identityState.propertyList.indexOf(element = passportNumber),
dividerPadding = 0.dp,
),
forceLtr = true,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin()
Expand All @@ -193,6 +197,7 @@ fun VaultItemIdentityContent(
index = identityState.propertyList.indexOf(element = licenseNumber),
dividerPadding = 0.dp,
),
forceLtr = true,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin()
Expand All @@ -215,6 +220,7 @@ fun VaultItemIdentityContent(
index = identityState.propertyList.indexOf(element = email),
dividerPadding = 0.dp,
),
forceLtr = true,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin()
Expand All @@ -237,6 +243,7 @@ fun VaultItemIdentityContent(
index = identityState.propertyList.indexOf(element = phone),
dividerPadding = 0.dp,
),
forceLtr = true,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin()
Expand Down Expand Up @@ -422,6 +429,7 @@ private fun IdentityCopyField(
onCopyClick: () -> Unit,
cardStyle: CardStyle,
modifier: Modifier = Modifier,
forceLtr: Boolean = false,
) {
BitwardenTextField(
label = label,
Expand All @@ -439,6 +447,11 @@ private fun IdentityCopyField(
},
textFieldTestTag = textFieldTestTag,
cardStyle = cardStyle,
visualTransformation = if (forceLtr) {
forceLtrVisualTransformation()
} else {
VisualTransformation.None
},
modifier = modifier,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import com.bitwarden.ui.platform.components.model.CardStyle
import com.bitwarden.ui.platform.components.model.TooltipData
import com.bitwarden.ui.platform.components.text.BitwardenClickableText
import com.bitwarden.ui.platform.components.text.BitwardenHyperTextLink
import com.bitwarden.ui.platform.components.util.forceLtrVisualTransformation
import com.bitwarden.ui.platform.resource.BitwardenDrawable
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.platform.theme.BitwardenTheme
Expand Down Expand Up @@ -473,6 +474,7 @@ private fun TotpField(
},
textFieldTestTag = "LoginTotpEntry",
cardStyle = CardStyle.Full,
visualTransformation = forceLtrVisualTransformation(),
modifier = modifier,
)
} else {
Expand Down Expand Up @@ -528,6 +530,7 @@ private fun UriField(
},
textFieldTestTag = "LoginUriEntry",
cardStyle = cardStyle,
visualTransformation = forceLtrVisualTransformation(),
modifier = modifier,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.ui.platform.components.util.LRO
import com.bitwarden.ui.platform.components.util.PDF
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorPasswordHistoryMode
Expand Down Expand Up @@ -98,7 +100,7 @@ class PasswordHistoryScreenTest : BitwardenComposeTest() {
)
}

composeTestRule.onNodeWithText(password.password).assertIsDisplayed()
composeTestRule.onNodeWithText("$LRO${password.password}$PDF").assertIsDisplayed()
composeTestRule.onNodeWithContentDescription("Copy").performClick()

verify {
Expand Down Expand Up @@ -164,6 +166,6 @@ class PasswordHistoryScreenTest : BitwardenComposeTest() {
)
}

composeTestRule.onNodeWithText("Password1").assertIsDisplayed()
composeTestRule.onNodeWithText("${LRO}Password1$PDF").assertIsDisplayed()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import androidx.compose.ui.test.performTouchInput
import androidx.core.net.toUri
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData
import com.bitwarden.ui.platform.components.util.LRO
import com.bitwarden.ui.platform.components.util.PDF
import com.bitwarden.ui.platform.manager.IntentManager
import com.bitwarden.ui.util.asText
import com.bitwarden.ui.util.assertNoDialogExists
Expand Down Expand Up @@ -684,7 +686,7 @@ class VaultAddEditScreenTest : BitwardenComposeTest() {
.performClick()
composeTestRule
.onNodeWithText("Password")
.assertTextEquals("Password", "p@ssw0rd")
.assertTextEquals("Password", "${LRO}p@ssw0rd$PDF")
.assertIsEnabled()
composeTestRule
.onNodeWithContentDescription("Hide")
Expand Down Expand Up @@ -1006,7 +1008,7 @@ class VaultAddEditScreenTest : BitwardenComposeTest() {

composeTestRule
.onNodeWithText("Authenticator key")
.assertTextEquals("Authenticator key", "TestCode")
.assertTextEquals("Authenticator key", "${LRO}TestCode$PDF")
.assertIsEnabled()

mutableStateFlow.update { currentState ->
Expand All @@ -1015,7 +1017,7 @@ class VaultAddEditScreenTest : BitwardenComposeTest() {

composeTestRule
.onNodeWithTextAfterScroll("Authenticator key")
.assertTextEquals("Authenticator key", "NewTestCode")
.assertTextEquals("Authenticator key", "${LRO}NewTestCode$PDF")

mutableStateFlow.update { currentState ->
updateLoginType(currentState) { copy(totp = null) }
Expand All @@ -1042,7 +1044,7 @@ class VaultAddEditScreenTest : BitwardenComposeTest() {

composeTestRule
.onNodeWithText("Authenticator key")
.assertTextEquals("Authenticator key", "TestCode")
.assertTextEquals("Authenticator key", "${LRO}TestCode$PDF")
.assertIsEnabled()

composeTestRule
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import androidx.core.net.toUri
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.ui.platform.components.icon.model.IconData
import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData
import com.bitwarden.ui.platform.components.util.LRO
import com.bitwarden.ui.platform.components.util.PDF
import com.bitwarden.ui.platform.manager.IntentManager
import com.bitwarden.ui.platform.resource.BitwardenDrawable
import com.bitwarden.ui.platform.resource.BitwardenString
Expand Down Expand Up @@ -1002,7 +1004,7 @@ class VaultItemScreenTest : BitwardenComposeTest() {

composeTestRule
.onNodeWithText("Password")
.assertTextEquals("Password", "p@ssw0rd")
.assertTextEquals("Password", "${LRO}p@ssw0rd$PDF")
.assertIsEnabled()
composeTestRule
.onNodeWithTextAfterScroll("Check password for data breaches")
Expand Down Expand Up @@ -2799,7 +2801,7 @@ class VaultItemScreenTest : BitwardenComposeTest() {

composeTestRule
.onNodeWithText("Number")
.assertTextEquals("Number", "number")
.assertTextEquals("Number", "${LRO}number$PDF")
.assertIsEnabled()
composeTestRule
.onNodeWithTextAfterScroll("Number")
Expand Down Expand Up @@ -2949,7 +2951,7 @@ class VaultItemScreenTest : BitwardenComposeTest() {

composeTestRule
.onNodeWithText("Security code")
.assertTextEquals("Security code", "123")
.assertTextEquals("Security code", "${LRO}123$PDF")
.assertIsEnabled()
composeTestRule
.onNodeWithContentDescription("Copy security code")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.bitwarden.ui.platform.base.util.cardStyle
Expand All @@ -56,6 +55,8 @@ import com.bitwarden.ui.platform.components.model.CardStyle
import com.bitwarden.ui.platform.components.model.TooltipData
import com.bitwarden.ui.platform.components.row.BitwardenRowOfActions
import com.bitwarden.ui.platform.components.support.BitwardenSupportingContent
import com.bitwarden.ui.platform.components.util.compoundVisualTransformation
import com.bitwarden.ui.platform.components.util.forceLtrVisualTransformation
import com.bitwarden.ui.platform.components.util.nonLetterColorVisualTransformation
import com.bitwarden.ui.platform.resource.BitwardenDrawable
import com.bitwarden.ui.platform.resource.BitwardenString
Expand Down Expand Up @@ -140,6 +141,18 @@ fun BitwardenPasswordField(
TextToolbarType.NONE -> BitwardenEmptyTextToolbar
}
var lastTextValue by remember(value) { mutableStateOf(value = value) }

val visualTransformation = when {
!showPassword -> PasswordVisualTransformation()

readOnly -> compoundVisualTransformation(
nonLetterColorVisualTransformation(),
forceLtrVisualTransformation(),
)

else -> forceLtrVisualTransformation()
}

CompositionLocalProvider(value = LocalTextToolbar provides textToolbar) {
Column(
modifier = modifier
Expand Down Expand Up @@ -191,11 +204,7 @@ fun BitwardenPasswordField(
onValueChange(it.text)
}
},
visualTransformation = when {
!showPassword -> PasswordVisualTransformation()
readOnly -> nonLetterColorVisualTransformation()
else -> VisualTransformation.None
},
visualTransformation = visualTransformation,
singleLine = singleLine,
readOnly = readOnly,
keyboardOptions = KeyboardOptions(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.bitwarden.ui.platform.components.util

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.input.OffsetMapping
import androidx.compose.ui.text.input.TransformedText
import androidx.compose.ui.text.input.VisualTransformation

/**
* A [VisualTransformation] that chains multiple other [VisualTransformation]s.
*
* This is useful for applying multiple transformations to a text field. The
* transformations are applied in the order they are provided, with each transformation's
* output becoming the input for the next transformation.
*
* ## Example Usage
*
* Combining password masking with LTR direction enforcement:
* ```kotlin
* val visualTransformation = compoundVisualTransformation(
* PasswordVisualTransformation(),
* forceLtrVisualTransformation()
* )
* TextField(
* value = password,
* visualTransformation = visualTransformation
* )
* ```
*
* Combining color transformation with LTR for readonly fields:
* ```kotlin
* val visualTransformation = compoundVisualTransformation(
* nonLetterColorVisualTransformation(),
* forceLtrVisualTransformation()
* )
* ```
*
* ## Important Notes
*
* - Offset mapping is correctly composed through all transformations
* - The order of transformations matters (first applied is first in the list)
* - Use [compoundVisualTransformation] function for proper `remember` optimization
*
* @param transformations The visual transformations to apply in order
* @see compoundVisualTransformation
* @see forceLtrVisualTransformation
*/
internal class CompoundVisualTransformation(
vararg val transformations: VisualTransformation,
) : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
return transformations.fold(
TransformedText(
text,
OffsetMapping.Identity,
),
) { acc, transformation ->
val result = transformation.filter(acc.text)

val composedMapping = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
val originalTransformed = acc.offsetMapping.originalToTransformed(offset)
return result.offsetMapping.originalToTransformed(originalTransformed)
}

override fun transformedToOriginal(offset: Int): Int {
val resultOriginal = result.offsetMapping.transformedToOriginal(offset)
return acc.offsetMapping.transformedToOriginal(resultOriginal)
}
}
TransformedText(result.text, composedMapping)
}
}
}

/**
* Remembers a [CompoundVisualTransformation] for the given [transformations].
*
* This is an optimization to avoid creating a new [CompoundVisualTransformation] on every
* recomposition.
*/
@Composable
fun compoundVisualTransformation(
vararg transformations: VisualTransformation,
): VisualTransformation =
remember(*transformations) {
CompoundVisualTransformation(*transformations)
}
Loading
Loading