mirror of
https://github.com/bitwarden/android.git
synced 2026-03-21 22:00:42 -05:00
PM-15599: Allow for custom TextToolbars (#4440)
This commit is contained in:
@@ -4,11 +4,14 @@ import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalTextToolbar
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.color.bitwardenTextFieldColors
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.toolbar.BitwardenEmptyTextToolbar
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
|
||||
/**
|
||||
@@ -24,19 +27,21 @@ fun BitwardenHiddenPasswordField(
|
||||
value: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = modifier,
|
||||
textStyle = BitwardenTheme.typography.sensitiveInfoSmall,
|
||||
label = { Text(text = label) },
|
||||
value = value,
|
||||
onValueChange = { },
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
singleLine = true,
|
||||
enabled = false,
|
||||
readOnly = true,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||
colors = bitwardenTextFieldColors(),
|
||||
)
|
||||
CompositionLocalProvider(value = LocalTextToolbar provides BitwardenEmptyTextToolbar) {
|
||||
OutlinedTextField(
|
||||
modifier = modifier,
|
||||
textStyle = BitwardenTheme.typography.sensitiveInfoSmall,
|
||||
label = { Text(text = label) },
|
||||
value = value,
|
||||
onValueChange = { },
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
singleLine = true,
|
||||
enabled = false,
|
||||
readOnly = true,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||
colors = bitwardenTextFieldColors(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
|
||||
@@ -5,7 +5,9 @@ import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -14,18 +16,25 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalTextToolbar
|
||||
import androidx.compose.ui.platform.TextToolbar
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.testTag
|
||||
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 com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.tabNavigation
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.color.bitwardenTextFieldColors
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.toolbar.BitwardenCutCopyTextToolbar
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.toolbar.BitwardenEmptyTextToolbar
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.TextToolbarType
|
||||
import com.x8bit.bitwarden.ui.platform.components.util.nonLetterColorVisualTransformation
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
|
||||
@@ -51,7 +60,9 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
* the password field.
|
||||
* @param imeAction the preferred IME action for the keyboard to have.
|
||||
* @param keyboardActions the callbacks of keyboard actions.
|
||||
* @param textToolbarType The type of [TextToolbar] to use on the text field.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun BitwardenPasswordField(
|
||||
label: String,
|
||||
@@ -68,52 +79,82 @@ fun BitwardenPasswordField(
|
||||
keyboardType: KeyboardType = KeyboardType.Password,
|
||||
imeAction: ImeAction = ImeAction.Default,
|
||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||
textToolbarType: TextToolbarType = TextToolbarType.DEFAULT,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
OutlinedTextField(
|
||||
modifier = modifier
|
||||
.tabNavigation()
|
||||
.focusRequester(focusRequester),
|
||||
colors = bitwardenTextFieldColors(),
|
||||
textStyle = BitwardenTheme.typography.sensitiveInfoSmall,
|
||||
label = { Text(text = label) },
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
visualTransformation = when {
|
||||
!showPassword -> PasswordVisualTransformation()
|
||||
readOnly -> nonLetterColorVisualTransformation()
|
||||
else -> VisualTransformation.None
|
||||
},
|
||||
singleLine = singleLine,
|
||||
readOnly = readOnly,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = keyboardType,
|
||||
imeAction = imeAction,
|
||||
),
|
||||
keyboardActions = keyboardActions,
|
||||
supportingText = hint?.let {
|
||||
{
|
||||
Text(
|
||||
text = hint,
|
||||
style = BitwardenTheme.typography.bodySmall,
|
||||
var textFieldValueState by remember { mutableStateOf(TextFieldValue(text = value)) }
|
||||
val textFieldValue = textFieldValueState.copy(text = value)
|
||||
SideEffect {
|
||||
if (textFieldValue.selection != textFieldValueState.selection ||
|
||||
textFieldValue.composition != textFieldValueState.composition
|
||||
) {
|
||||
textFieldValueState = textFieldValue
|
||||
}
|
||||
}
|
||||
val textToolbar = when (textToolbarType) {
|
||||
TextToolbarType.DEFAULT -> BitwardenCutCopyTextToolbar(
|
||||
value = textFieldValue,
|
||||
onValueChange = onValueChange,
|
||||
defaultTextToolbar = LocalTextToolbar.current,
|
||||
clipboardManager = LocalClipboardManager.current.nativeClipboard,
|
||||
)
|
||||
|
||||
TextToolbarType.NONE -> BitwardenEmptyTextToolbar
|
||||
}
|
||||
var lastTextValue by remember(value) { mutableStateOf(value = value) }
|
||||
CompositionLocalProvider(value = LocalTextToolbar provides textToolbar) {
|
||||
OutlinedTextField(
|
||||
modifier = modifier
|
||||
.tabNavigation()
|
||||
.focusRequester(focusRequester),
|
||||
colors = bitwardenTextFieldColors(),
|
||||
textStyle = BitwardenTheme.typography.sensitiveInfoSmall,
|
||||
label = { Text(text = label) },
|
||||
value = textFieldValue,
|
||||
onValueChange = {
|
||||
textFieldValueState = it
|
||||
val stringChangedSinceLastInvocation = lastTextValue != it.text
|
||||
lastTextValue = it.text
|
||||
if (stringChangedSinceLastInvocation) {
|
||||
onValueChange(it.text)
|
||||
}
|
||||
},
|
||||
visualTransformation = when {
|
||||
!showPassword -> PasswordVisualTransformation()
|
||||
readOnly -> nonLetterColorVisualTransformation()
|
||||
else -> VisualTransformation.None
|
||||
},
|
||||
singleLine = singleLine,
|
||||
readOnly = readOnly,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = keyboardType,
|
||||
imeAction = imeAction,
|
||||
),
|
||||
keyboardActions = keyboardActions,
|
||||
supportingText = hint?.let {
|
||||
{
|
||||
Text(
|
||||
text = hint,
|
||||
style = BitwardenTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
},
|
||||
trailingIcon = {
|
||||
BitwardenStandardIconButton(
|
||||
modifier = Modifier.semantics { showPasswordTestTag?.let { testTag = it } },
|
||||
vectorIconRes = if (showPassword) {
|
||||
R.drawable.ic_eye_slash
|
||||
} else {
|
||||
R.drawable.ic_eye
|
||||
},
|
||||
contentDescription = stringResource(
|
||||
id = if (showPassword) R.string.hide else R.string.show,
|
||||
),
|
||||
onClick = { showPasswordChange.invoke(!showPassword) },
|
||||
)
|
||||
}
|
||||
},
|
||||
trailingIcon = {
|
||||
BitwardenStandardIconButton(
|
||||
modifier = Modifier.semantics { showPasswordTestTag?.let { testTag = it } },
|
||||
vectorIconRes = if (showPassword) {
|
||||
R.drawable.ic_eye_slash
|
||||
} else {
|
||||
R.drawable.ic_eye
|
||||
},
|
||||
contentDescription = stringResource(
|
||||
id = if (showPassword) R.string.hide else R.string.show,
|
||||
),
|
||||
onClick = { showPasswordChange.invoke(!showPassword) },
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (autoFocus) {
|
||||
LaunchedEffect(Unit) { focusRequester.requestFocus() }
|
||||
}
|
||||
|
||||
@@ -10,12 +10,14 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.TextToolbar
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.testTag
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenTonalIconButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.TextToolbarType
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
|
||||
/**
|
||||
@@ -33,6 +35,7 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
* @param readOnly `true` if the input should be read-only and not accept user interactions.
|
||||
* @param singleLine when `true`, this text field becomes a single line that horizontally scrolls
|
||||
* instead of wrapping onto multiple lines.
|
||||
* @param textToolbarType The type of [TextToolbar] to use on the text field.
|
||||
* @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.
|
||||
@@ -49,6 +52,7 @@ fun BitwardenPasswordFieldWithActions(
|
||||
singleLine: Boolean = false,
|
||||
showPasswordTestTag: String? = null,
|
||||
passwordFieldTestTag: String? = null,
|
||||
textToolbarType: TextToolbarType = TextToolbarType.DEFAULT,
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
) {
|
||||
Row(
|
||||
@@ -68,6 +72,7 @@ fun BitwardenPasswordFieldWithActions(
|
||||
.weight(1f)
|
||||
.padding(end = 8.dp),
|
||||
showPasswordTestTag = showPasswordTestTag,
|
||||
textToolbarType = textToolbarType,
|
||||
)
|
||||
actions()
|
||||
}
|
||||
|
||||
@@ -5,24 +5,34 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalTextToolbar
|
||||
import androidx.compose.ui.platform.TextToolbar
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
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.x8bit.bitwarden.ui.platform.base.util.toPx
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.withLineBreaksAtWidth
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.color.bitwardenTextFieldColors
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.toolbar.BitwardenCutCopyTextToolbar
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.toolbar.BitwardenEmptyTextToolbar
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.TextToolbarType
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
|
||||
/**
|
||||
@@ -46,7 +56,9 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
* 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.
|
||||
* @param textToolbarType The type of [TextToolbar] to use on the text field.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun BitwardenTextField(
|
||||
label: String,
|
||||
@@ -66,6 +78,7 @@ fun BitwardenTextField(
|
||||
isError: Boolean = false,
|
||||
autoFocus: Boolean = false,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
textToolbarType: TextToolbarType = TextToolbarType.DEFAULT,
|
||||
) {
|
||||
var widthPx by remember { mutableIntStateOf(0) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
@@ -78,48 +91,76 @@ fun BitwardenTextField(
|
||||
} else {
|
||||
value
|
||||
}
|
||||
var textFieldValueState by remember { mutableStateOf(TextFieldValue(text = formattedText)) }
|
||||
val textFieldValue = textFieldValueState.copy(text = value)
|
||||
SideEffect {
|
||||
if (textFieldValue.selection != textFieldValueState.selection ||
|
||||
textFieldValue.composition != textFieldValueState.composition
|
||||
) {
|
||||
textFieldValueState = textFieldValue
|
||||
}
|
||||
}
|
||||
val textToolbar = when (textToolbarType) {
|
||||
TextToolbarType.DEFAULT -> BitwardenCutCopyTextToolbar(
|
||||
value = textFieldValue,
|
||||
onValueChange = onValueChange,
|
||||
defaultTextToolbar = LocalTextToolbar.current,
|
||||
clipboardManager = LocalClipboardManager.current.nativeClipboard,
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
colors = bitwardenTextFieldColors(),
|
||||
modifier = modifier
|
||||
.onGloballyPositioned { widthPx = it.size.width }
|
||||
.focusRequester(focusRequester),
|
||||
enabled = enabled,
|
||||
label = { Text(text = label) },
|
||||
value = formattedText,
|
||||
leadingIcon = leadingIconResource?.let { iconResource ->
|
||||
{
|
||||
Icon(
|
||||
painter = iconResource.iconPainter,
|
||||
contentDescription = iconResource.contentDescription,
|
||||
)
|
||||
}
|
||||
},
|
||||
trailingIcon = trailingIconContent,
|
||||
placeholder = placeholder?.let {
|
||||
{
|
||||
Text(
|
||||
text = it,
|
||||
style = textStyle,
|
||||
)
|
||||
}
|
||||
},
|
||||
supportingText = hint?.let {
|
||||
{
|
||||
Text(
|
||||
text = hint,
|
||||
style = BitwardenTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
},
|
||||
onValueChange = onValueChange,
|
||||
singleLine = singleLine,
|
||||
readOnly = readOnly,
|
||||
textStyle = textStyle,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = keyboardType),
|
||||
isError = isError,
|
||||
visualTransformation = visualTransformation,
|
||||
)
|
||||
TextToolbarType.NONE -> BitwardenEmptyTextToolbar
|
||||
}
|
||||
var lastTextValue by remember(value) { mutableStateOf(value = value) }
|
||||
CompositionLocalProvider(value = LocalTextToolbar provides textToolbar) {
|
||||
OutlinedTextField(
|
||||
colors = bitwardenTextFieldColors(),
|
||||
modifier = modifier
|
||||
.onGloballyPositioned { widthPx = it.size.width }
|
||||
.focusRequester(focusRequester),
|
||||
enabled = enabled,
|
||||
label = { Text(text = label) },
|
||||
value = textFieldValue,
|
||||
leadingIcon = leadingIconResource?.let { iconResource ->
|
||||
{
|
||||
Icon(
|
||||
painter = iconResource.iconPainter,
|
||||
contentDescription = iconResource.contentDescription,
|
||||
)
|
||||
}
|
||||
},
|
||||
trailingIcon = trailingIconContent,
|
||||
placeholder = placeholder?.let {
|
||||
{
|
||||
Text(
|
||||
text = it,
|
||||
style = textStyle,
|
||||
)
|
||||
}
|
||||
},
|
||||
supportingText = hint?.let {
|
||||
{
|
||||
Text(
|
||||
text = hint,
|
||||
style = BitwardenTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
},
|
||||
onValueChange = {
|
||||
textFieldValueState = it
|
||||
val stringChangedSinceLastInvocation = lastTextValue != it.text
|
||||
lastTextValue = it.text
|
||||
if (stringChangedSinceLastInvocation) {
|
||||
onValueChange(it.text)
|
||||
}
|
||||
},
|
||||
singleLine = singleLine,
|
||||
readOnly = readOnly,
|
||||
textStyle = textStyle,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = keyboardType),
|
||||
isError = isError,
|
||||
visualTransformation = visualTransformation,
|
||||
)
|
||||
}
|
||||
if (autoFocus) {
|
||||
LaunchedEffect(Unit) { focusRequester.requestFocus() }
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.TextToolbar
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.testTag
|
||||
@@ -15,6 +16,7 @@ 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.components.model.TextToolbarType
|
||||
import com.x8bit.bitwarden.ui.platform.components.row.BitwardenRowOfActions
|
||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
@@ -42,6 +44,7 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
* providing flexibility in the layout definition.
|
||||
* @param actionsTestTag The test tag to use for the row of actions, or null if there is none.
|
||||
* @param textFieldTestTag The test tag to be used on the text field.
|
||||
* @param textToolbarType The type of [TextToolbar] to use on the text field.
|
||||
*/
|
||||
@Composable
|
||||
fun BitwardenTextFieldWithActions(
|
||||
@@ -59,6 +62,7 @@ fun BitwardenTextFieldWithActions(
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
actionsTestTag: String? = null,
|
||||
textFieldTestTag: String? = null,
|
||||
textToolbarType: TextToolbarType = TextToolbarType.DEFAULT,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
@@ -80,6 +84,7 @@ fun BitwardenTextFieldWithActions(
|
||||
textStyle = textStyle,
|
||||
shouldAddCustomLineBreaks = shouldAddCustomLineBreaks,
|
||||
visualTransformation = visualTransformation,
|
||||
textToolbarType = textToolbarType,
|
||||
)
|
||||
BitwardenRowOfActions(
|
||||
modifier = Modifier.run { actionsTestTag?.let { testTag(it) } ?: this },
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.x8bit.bitwarden.ui.platform.components.field.toolbar
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.platform.TextToolbar
|
||||
import androidx.compose.ui.platform.TextToolbarStatus
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.input.getSelectedText
|
||||
import androidx.core.os.persistableBundleOf
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
|
||||
/**
|
||||
* A custom [TextToolbar] that is obfuscates the copied or cut text.
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
class BitwardenCutCopyTextToolbar(
|
||||
private val value: TextFieldValue,
|
||||
private val onValueChange: (String) -> Unit,
|
||||
private val defaultTextToolbar: TextToolbar,
|
||||
private val clipboardManager: ClipboardManager,
|
||||
) : TextToolbar {
|
||||
override val status: TextToolbarStatus get() = defaultTextToolbar.status
|
||||
|
||||
override fun hide() = defaultTextToolbar.hide()
|
||||
|
||||
override fun showMenu(
|
||||
rect: Rect,
|
||||
onCopyRequested: (() -> Unit)?,
|
||||
onPasteRequested: (() -> Unit)?,
|
||||
onCutRequested: (() -> Unit)?,
|
||||
onSelectAllRequested: (() -> Unit)?,
|
||||
) {
|
||||
defaultTextToolbar.showMenu(
|
||||
rect = rect,
|
||||
onCopyRequested = onCopyRequested?.let { _ ->
|
||||
{
|
||||
clipboardManager.setPrimaryClip(
|
||||
ClipData
|
||||
.newPlainText("", value.getSelectedText())
|
||||
.apply {
|
||||
description.extras = persistableBundleOf(
|
||||
"android.content.extra.IS_SENSITIVE" to true,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
onPasteRequested = onPasteRequested,
|
||||
onCutRequested = onCutRequested?.let { _ ->
|
||||
{
|
||||
clipboardManager.setPrimaryClip(
|
||||
ClipData
|
||||
.newPlainText("", value.getSelectedText())
|
||||
.apply {
|
||||
description.extras = persistableBundleOf(
|
||||
"android.content.extra.IS_SENSITIVE" to true,
|
||||
)
|
||||
},
|
||||
)
|
||||
onValueChange("")
|
||||
}
|
||||
},
|
||||
onSelectAllRequested = onSelectAllRequested,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.x8bit.bitwarden.ui.platform.components.field.toolbar
|
||||
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.platform.TextToolbar
|
||||
import androidx.compose.ui.platform.TextToolbarStatus
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
|
||||
/**
|
||||
* A custom [TextToolbar] that is completely empty.
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
object BitwardenEmptyTextToolbar : TextToolbar {
|
||||
override val status: TextToolbarStatus = TextToolbarStatus.Hidden
|
||||
|
||||
override fun hide() = Unit
|
||||
|
||||
override fun showMenu(
|
||||
rect: Rect,
|
||||
onCopyRequested: (() -> Unit)?,
|
||||
onPasteRequested: (() -> Unit)?,
|
||||
onCutRequested: (() -> Unit)?,
|
||||
onSelectAllRequested: (() -> Unit)?,
|
||||
) = Unit
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.x8bit.bitwarden.ui.platform.components.model
|
||||
|
||||
import androidx.compose.ui.platform.TextToolbar
|
||||
|
||||
/**
|
||||
* Indicated the type of [TextToolbar] that should be displayed.
|
||||
*/
|
||||
enum class TextToolbarType {
|
||||
DEFAULT,
|
||||
NONE,
|
||||
}
|
||||
@@ -47,6 +47,7 @@ import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextFieldWithActions
|
||||
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.TextToolbarType
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.TooltipData
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.TopAppBarDividerStyle
|
||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||
@@ -394,6 +395,7 @@ private fun GeneratedStringItem(
|
||||
shouldAddCustomLineBreaks = true,
|
||||
visualTransformation = nonLetterColorVisualTransformation(),
|
||||
modifier = modifier.padding(horizontal = 16.dp),
|
||||
textToolbarType = TextToolbarType.NONE,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user