mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 10:48:47 +03:00
BIT-1260: Fix line breaks for generated passwords (#424)
This commit is contained in:
parent
719bf52420
commit
44afc44829
7 changed files with 101 additions and 10 deletions
|
@ -31,6 +31,12 @@ fun IntSize.toDpSize(density: Density): DpSize = with(density) {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A function for converting [Dp] to pixels within a composable function.
|
||||
*/
|
||||
@Composable
|
||||
fun Dp.toPx(): Float = with(LocalDensity.current) { this@toPx.toPx() }
|
||||
|
||||
/**
|
||||
* Converts a [Dp] value to [TextUnit] with [TextUnitType.Sp] as its type.
|
||||
*
|
||||
|
|
|
@ -5,10 +5,13 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.core.graphics.toColorInt
|
||||
import java.net.URI
|
||||
import java.util.Locale
|
||||
import kotlin.math.floor
|
||||
|
||||
/**
|
||||
* This character takes up no space but can be used to ensure a string is not empty. It can also
|
||||
|
@ -79,6 +82,36 @@ fun String.withVisualTransformation(
|
|||
visualTransformation.filter(toAnnotatedString()).text
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new [String] that includes line breaks after [widthPx] worth of text. This is useful
|
||||
* for long values that need to smoothly flow onto the next line without the OS inserting line
|
||||
* breaks earlier at special characters.
|
||||
*
|
||||
* Note that the internal calculation used assumes that [monospacedTextStyle] is based on a
|
||||
* monospaced font like Roboto Mono.
|
||||
*/
|
||||
@Composable
|
||||
fun String.withLineBreaksAtWidth(
|
||||
widthPx: Float,
|
||||
monospacedTextStyle: TextStyle,
|
||||
): String {
|
||||
val measurer = rememberTextMeasurer()
|
||||
return remember(this, widthPx, monospacedTextStyle) {
|
||||
val characterSizePx = measurer
|
||||
.measure("*", monospacedTextStyle)
|
||||
.size
|
||||
.width
|
||||
val perLineCharacterLimit = floor(widthPx / characterSizePx).toInt()
|
||||
if (widthPx > 0) {
|
||||
this
|
||||
.chunked(perLineCharacterLimit)
|
||||
.joinToString(separator = "\n")
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [String] as an [AnnotatedString].
|
||||
*/
|
||||
|
|
|
@ -33,6 +33,8 @@ import com.x8bit.bitwarden.R
|
|||
* @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],
|
||||
|
@ -45,6 +47,7 @@ fun BitwardenReadOnlyTextFieldWithActions(
|
|||
modifier: Modifier = Modifier,
|
||||
singleLine: Boolean = true,
|
||||
textStyle: TextStyle? = null,
|
||||
shouldAddCustomLineBreaks: Boolean = false,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
) {
|
||||
|
@ -67,6 +70,7 @@ fun BitwardenReadOnlyTextFieldWithActions(
|
|||
value = value,
|
||||
onValueChange = {},
|
||||
textStyle = textStyle,
|
||||
shouldAddCustomLineBreaks = shouldAddCustomLineBreaks,
|
||||
visualTransformation = visualTransformation,
|
||||
)
|
||||
BitwardenRowOfActions(actions)
|
||||
|
|
|
@ -7,11 +7,19 @@ import androidx.compose.material3.MaterialTheme
|
|||
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
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
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 androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.toPx
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.withLineBreaksAtWidth
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
||||
|
||||
/**
|
||||
|
@ -30,6 +38,8 @@ import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
|||
* @param readOnly `true` if the input should be read-only and not accept user interactions.
|
||||
* @param enabled Whether or not the text field is enabled.
|
||||
* @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 keyboardType the preferred type of keyboard input.
|
||||
*/
|
||||
|
@ -46,14 +56,29 @@ fun BitwardenTextField(
|
|||
readOnly: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
textStyle: TextStyle? = null,
|
||||
shouldAddCustomLineBreaks: Boolean = false,
|
||||
keyboardType: KeyboardType = KeyboardType.Text,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
) {
|
||||
var widthPx by remember { mutableStateOf(0) }
|
||||
|
||||
val currentTextStyle = textStyle ?: LocalTextStyle.current
|
||||
val formattedText = if (shouldAddCustomLineBreaks) {
|
||||
value.withLineBreaksAtWidth(
|
||||
// Adjust for built in padding
|
||||
widthPx = widthPx - 16.dp.toPx(),
|
||||
monospacedTextStyle = currentTextStyle,
|
||||
)
|
||||
} else {
|
||||
value
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
modifier = modifier,
|
||||
modifier = modifier
|
||||
.onGloballyPositioned { widthPx = it.size.width },
|
||||
enabled = enabled,
|
||||
label = { Text(text = label) },
|
||||
value = value,
|
||||
value = formattedText,
|
||||
leadingIcon = leadingIconResource?.let { iconResource ->
|
||||
{
|
||||
Icon(
|
||||
|
@ -77,7 +102,7 @@ fun BitwardenTextField(
|
|||
onValueChange = onValueChange,
|
||||
singleLine = singleLine,
|
||||
readOnly = readOnly,
|
||||
textStyle = textStyle ?: LocalTextStyle.current,
|
||||
textStyle = currentTextStyle,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = keyboardType),
|
||||
visualTransformation = visualTransformation,
|
||||
)
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.platform.components.util
|
|||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
|
@ -15,11 +16,16 @@ import androidx.compose.ui.text.withStyle
|
|||
* applying different colors to the digits and special characters, letters will remain unaffected.
|
||||
*/
|
||||
@Composable
|
||||
fun nonLetterColorVisualTransformation(): VisualTransformation =
|
||||
NonLetterColorVisualTransformation(
|
||||
digitColor = MaterialTheme.colorScheme.primary,
|
||||
specialCharacterColor = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
fun nonLetterColorVisualTransformation(): VisualTransformation {
|
||||
val digitColor = MaterialTheme.colorScheme.primary
|
||||
val specialCharacterColor = MaterialTheme.colorScheme.error
|
||||
return remember(digitColor, specialCharacterColor) {
|
||||
NonLetterColorVisualTransformation(
|
||||
digitColor = digitColor,
|
||||
specialCharacterColor = specialCharacterColor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alters the visual output of the text in an input field.
|
||||
|
|
|
@ -309,6 +309,7 @@ private fun GeneratedStringItem(
|
|||
)
|
||||
},
|
||||
textStyle = LocalNonMaterialTypography.current.sensitiveInfoSmall,
|
||||
shouldAddCustomLineBreaks = true,
|
||||
visualTransformation = nonLetterColorVisualTransformation(),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
|
|
@ -10,16 +10,23 @@ import androidx.compose.material3.IconButtonDefaults
|
|||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.withLineBreaksAtWidth
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.withVisualTransformation
|
||||
import com.x8bit.bitwarden.ui.platform.components.util.nonLetterColorVisualTransformation
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialTypography
|
||||
|
||||
/**
|
||||
* A composable function for displaying a password history list item.
|
||||
|
@ -44,12 +51,21 @@ fun PasswordHistoryListItem(
|
|||
) {
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
var widthPx by remember(label) { mutableStateOf(0) }
|
||||
val textStyle = LocalNonMaterialTypography.current.sensitiveInfoMedium
|
||||
val formattedText = label.withLineBreaksAtWidth(
|
||||
widthPx = widthPx.toFloat(),
|
||||
monospacedTextStyle = textStyle,
|
||||
)
|
||||
Text(
|
||||
text = label.withVisualTransformation(
|
||||
text = formattedText.withVisualTransformation(
|
||||
visualTransformation = nonLetterColorVisualTransformation(),
|
||||
),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
style = textStyle,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onGloballyPositioned { widthPx = it.size.width },
|
||||
)
|
||||
|
||||
Text(
|
||||
|
|
Loading…
Add table
Reference in a new issue