From 59975793301a4bf1776ec562798b5c46c0563a79 Mon Sep 17 00:00:00 2001 From: David Perez Date: Mon, 9 Dec 2024 13:22:44 -0600 Subject: [PATCH] PM-15599: Allow for custom TextToolbars (#4440) --- .../field/BitwardenHiddenPasswordField.kt | 31 +++-- .../field/BitwardenPasswordField.kt | 127 ++++++++++++------ .../BitwardenPasswordFieldWithActions.kt | 5 + .../components/field/BitwardenTextField.kt | 123 +++++++++++------ .../field/BitwardenTextFieldWithActions.kt | 5 + .../toolbar/BitwardenCutCopyTextToolbar.kt | 67 +++++++++ .../toolbar/BitwardenEmptyTextToolbar.kt | 24 ++++ .../components/model/TextToolbarType.kt | 11 ++ .../feature/generator/GeneratorScreen.kt | 2 + 9 files changed, 298 insertions(+), 97 deletions(-) create mode 100644 app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/toolbar/BitwardenCutCopyTextToolbar.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/toolbar/BitwardenEmptyTextToolbar.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/ui/platform/components/model/TextToolbarType.kt diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenHiddenPasswordField.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenHiddenPasswordField.kt index 0041ac966a..644a19aa0b 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenHiddenPasswordField.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenHiddenPasswordField.kt @@ -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 diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenPasswordField.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenPasswordField.kt index 3f22034534..9cea17222a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenPasswordField.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenPasswordField.kt @@ -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() } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenPasswordFieldWithActions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenPasswordFieldWithActions.kt index 42c9eeb031..543d5ca430 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenPasswordFieldWithActions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenPasswordFieldWithActions.kt @@ -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() } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenTextField.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenTextField.kt index e29e1fa50c..00adea0f9c 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenTextField.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenTextField.kt @@ -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() } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenTextFieldWithActions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenTextFieldWithActions.kt index 71c81c3707..ac13452449 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenTextFieldWithActions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenTextFieldWithActions.kt @@ -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 }, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/toolbar/BitwardenCutCopyTextToolbar.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/toolbar/BitwardenCutCopyTextToolbar.kt new file mode 100644 index 0000000000..58a02c7407 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/toolbar/BitwardenCutCopyTextToolbar.kt @@ -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, + ) + } +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/toolbar/BitwardenEmptyTextToolbar.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/toolbar/BitwardenEmptyTextToolbar.kt new file mode 100644 index 0000000000..e68484551b --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/toolbar/BitwardenEmptyTextToolbar.kt @@ -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 +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/model/TextToolbarType.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/model/TextToolbarType.kt new file mode 100644 index 0000000000..64ff4f3c2c --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/model/TextToolbarType.kt @@ -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, +} 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 61e617ed4b..be69aa0b0a 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 @@ -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, ) }