Force LTR text direction for passwords and TOTP codes

Adds TextStyle extension to force left-to-right text direction with
locale-aware alignment for sensitive alphanumeric content. This ensures
passwords and TOTP codes read correctly in RTL locales while maintaining
proper alignment.

- Add TextStyle.withForcedLtr() extension in TypographyExtensions.kt
- Refactor BitwardenPasswordField to use extension
- Refactor VerificationCodeItem (app) to use extension
- Refactor VaultVerificationCodeItem (authenticator) to use extension
This commit is contained in:
Patrick Honkonen
2025-12-30 15:12:41 -05:00
parent 2d228b8496
commit de14328cfb
4 changed files with 51 additions and 3 deletions

View File

@@ -24,6 +24,7 @@ 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
import com.bitwarden.ui.platform.util.withForcedLtr
/**
* The verification code item displayed to the user.
@@ -107,7 +108,7 @@ fun VaultVerificationCodeItem(
if (!hideAuthCode) {
Text(
text = authCode.chunked(3).joinToString(" "),
style = BitwardenTheme.typography.sensitiveInfoSmall,
style = BitwardenTheme.typography.sensitiveInfoSmall.withForcedLtr(),
color = BitwardenTheme.colorScheme.text.primary,
)

View File

@@ -29,6 +29,7 @@ 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
import com.bitwarden.ui.platform.util.withForcedLtr
/**
* The verification code item displayed to the user.
@@ -155,7 +156,7 @@ fun VaultVerificationCodeItem(
Text(
modifier = Modifier.testTag(tag = "AuthCode"),
text = authCode.chunked(size = 3).joinToString(separator = " "),
style = BitwardenTheme.typography.sensitiveInfoSmall,
style = BitwardenTheme.typography.sensitiveInfoSmall.withForcedLtr(),
color = BitwardenTheme.colorScheme.text.primary,
)

View File

@@ -60,6 +60,7 @@ import com.bitwarden.ui.platform.components.util.nonLetterColorVisualTransformat
import com.bitwarden.ui.platform.resource.BitwardenDrawable
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.platform.theme.BitwardenTheme
import com.bitwarden.ui.platform.util.withForcedLtr
/**
* Represents a Bitwarden-styled password field that hoists show/hide password state to the caller.
@@ -155,7 +156,7 @@ fun BitwardenPasswordField(
var focused by remember { mutableStateOf(value = false) }
TextField(
colors = bitwardenTextFieldColors(),
textStyle = BitwardenTheme.typography.sensitiveInfoSmall,
textStyle = BitwardenTheme.typography.sensitiveInfoSmall.withForcedLtr(),
label = label?.let {
{
Row(verticalAlignment = Alignment.CenterVertically) {

View File

@@ -0,0 +1,45 @@
package com.bitwarden.ui.platform.util
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.LayoutDirection
import com.bitwarden.annotation.OmitFromCoverage
/**
* Returns a [TextStyle] that forces left-to-right text direction while maintaining
* locale-aware alignment.
*
* This extension is designed for sensitive alphanumeric content (passwords, TOTP codes)
* that must always read left-to-right regardless of system locale, but should align
* according to the layout direction (right-aligned in RTL locales, left-aligned in LTR).
*
* **Implementation:**
* - Sets `textDirection = TextDirection.Ltr` to force LTR reading order
* - Sets `textAlign` conditionally:
* - `TextAlign.End` in RTL layouts (aligns to right side)
* - `TextAlign.Start` in LTR layouts (aligns to left side)
*
* **Use cases:**
* - Password fields that should read "Pass123!" not "!321ssaP" in RTL locales
* - TOTP verification codes that should read "123 456" not "654 321"
* - Any alphanumeric content requiring LTR reading with locale-aware positioning
*
* @return A merged [TextStyle] with forced LTR direction and locale-aware alignment.
*/
@OmitFromCoverage
@Composable
fun TextStyle.withForcedLtr(): TextStyle {
val layoutDirection = LocalLayoutDirection.current
return merge(
TextStyle(
textDirection = TextDirection.Ltr,
textAlign = when (layoutDirection) {
LayoutDirection.Rtl -> TextAlign.End
LayoutDirection.Ltr -> TextAlign.Start
},
),
)
}