mirror of
https://github.com/bitwarden/android.git
synced 2026-10-22 02:53:19 -05:00
chore: Minor cleanup of the BitwardenTextField and BitwardenPasswordField (#7427)
This commit is contained in:
1 parent
d999e25461
commit
8ea455d910
6 files changed
+241
-175
No files matched your search
+39
-64
@@ -1,18 +1,14 @@
|
||||
package com.bitwarden.ui.platform.components.field
|
||||
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Text
|
||||
@@ -26,13 +22,9 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalTextToolbar
|
||||
import androidx.compose.ui.platform.TextToolbar
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -45,17 +37,15 @@ import androidx.compose.ui.unit.dp
|
||||
import com.bitwarden.ui.platform.base.util.cardStyle
|
||||
import com.bitwarden.ui.platform.base.util.nullableTestTag
|
||||
import com.bitwarden.ui.platform.base.util.tabNavigation
|
||||
import com.bitwarden.ui.platform.components.button.BitwardenHelpIconButton
|
||||
import com.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
|
||||
import com.bitwarden.ui.platform.components.button.model.BitwardenHelpButtonData
|
||||
import com.bitwarden.ui.platform.components.divider.BitwardenHorizontalDivider
|
||||
import com.bitwarden.ui.platform.components.field.color.bitwardenTextFieldColors
|
||||
import com.bitwarden.ui.platform.components.field.label.BitwardenTextFieldLabel
|
||||
import com.bitwarden.ui.platform.components.field.model.TextToolbarType
|
||||
import com.bitwarden.ui.platform.components.field.toolbar.BitwardenCutCopyTextToolbar
|
||||
import com.bitwarden.ui.platform.components.field.toolbar.BitwardenEmptyTextToolbar
|
||||
import com.bitwarden.ui.platform.components.field.support.BitwardenTextFieldSupportingContent
|
||||
import com.bitwarden.ui.platform.components.field.util.toTextToolbar
|
||||
import com.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.bitwarden.ui.platform.components.row.BitwardenRowOfActions
|
||||
import com.bitwarden.ui.platform.components.support.BitwardenSupportingContent
|
||||
import com.bitwarden.ui.platform.components.util.nonLetterColorVisualTransformation
|
||||
import com.bitwarden.ui.platform.components.util.passwordVisualTransformation
|
||||
import com.bitwarden.ui.platform.resource.BitwardenDrawable
|
||||
@@ -75,6 +65,8 @@ import com.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
* @param supportingContent An optional supporting content that will appear below the text input.
|
||||
* @param supportingContentPadding The padding to be placed on the [supportingContent].
|
||||
* @param modifier Modifier for the composable.
|
||||
* @param interactionSource A [MutableInteractionSource] for observing and emitting interactions
|
||||
* for this component.
|
||||
* @param helpData An optional help button to be displayed in the label.
|
||||
* @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
|
||||
@@ -105,6 +97,7 @@ fun BitwardenPasswordField(
|
||||
supportingContent: @Composable (ColumnScope.() -> Unit)?,
|
||||
cardStyle: CardStyle,
|
||||
modifier: Modifier = Modifier,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
helpData: BitwardenHelpButtonData? = null,
|
||||
readOnly: Boolean = false,
|
||||
singleLine: Boolean = true,
|
||||
@@ -129,18 +122,12 @@ fun BitwardenPasswordField(
|
||||
textFieldValueState = textFieldValue
|
||||
}
|
||||
}
|
||||
val textToolbar = when (textToolbarType) {
|
||||
TextToolbarType.DEFAULT -> BitwardenCutCopyTextToolbar(
|
||||
value = textFieldValue,
|
||||
onValueChange = onValueChange,
|
||||
defaultTextToolbar = LocalTextToolbar.current,
|
||||
clipboardManager = LocalClipboard.current.nativeClipboard,
|
||||
focusManager = LocalFocusManager.current,
|
||||
)
|
||||
|
||||
TextToolbarType.NONE -> BitwardenEmptyTextToolbar
|
||||
}
|
||||
val textToolbar = textToolbarType.toTextToolbar(
|
||||
textFieldValue = textFieldValue,
|
||||
onValueChange = onValueChange,
|
||||
)
|
||||
var lastTextValue by remember(value) { mutableStateOf(value = value) }
|
||||
val isFocused by interactionSource.collectIsFocusedAsState()
|
||||
CompositionLocalProvider(value = LocalTextToolbar provides textToolbar) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
@@ -150,34 +137,19 @@ fun BitwardenPasswordField(
|
||||
paddingTop = 6.dp,
|
||||
paddingBottom = 0.dp,
|
||||
)
|
||||
.tabNavigation()
|
||||
.focusRequester(focusRequester = focusRequester),
|
||||
.tabNavigation(),
|
||||
) {
|
||||
var focused by remember { mutableStateOf(value = false) }
|
||||
TextField(
|
||||
colors = bitwardenTextFieldColors(),
|
||||
textStyle = BitwardenTheme.typography.sensitiveInfoSmall,
|
||||
label = label?.let {
|
||||
{
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(text = it)
|
||||
helpData?.let { helpButtonData ->
|
||||
val targetSize = if (textFieldValue.text.isEmpty() || focused) {
|
||||
16.dp
|
||||
} else {
|
||||
12.dp
|
||||
}
|
||||
val size by animateDpAsState(
|
||||
targetValue = targetSize,
|
||||
label = "${helpButtonData.contentDescription}_animation",
|
||||
)
|
||||
Spacer(modifier = Modifier.width(width = 8.dp))
|
||||
BitwardenHelpIconButton(
|
||||
helpData = helpButtonData,
|
||||
modifier = Modifier.size(size = size),
|
||||
)
|
||||
}
|
||||
}
|
||||
BitwardenTextFieldLabel(
|
||||
label = it,
|
||||
textFieldValue = textFieldValue,
|
||||
helpData = helpData,
|
||||
isFieldFocused = isFocused,
|
||||
)
|
||||
}
|
||||
},
|
||||
value = textFieldValue,
|
||||
@@ -228,26 +200,17 @@ fun BitwardenPasswordField(
|
||||
},
|
||||
)
|
||||
},
|
||||
interactionSource = interactionSource,
|
||||
modifier = Modifier
|
||||
.nullableTestTag(tag = passwordFieldTestTag)
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged { focusState -> focused = focusState.isFocused },
|
||||
.focusRequester(focusRequester = focusRequester),
|
||||
)
|
||||
BitwardenTextFieldSupportingContent(
|
||||
content = supportingContent,
|
||||
supportingContentPadding = supportingContentPadding,
|
||||
noContentCardPadding = 6.dp,
|
||||
)
|
||||
supportingContent
|
||||
?.let { content ->
|
||||
Spacer(modifier = Modifier.height(height = 6.dp))
|
||||
BitwardenHorizontalDivider(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp),
|
||||
)
|
||||
BitwardenSupportingContent(
|
||||
cardStyle = null,
|
||||
insets = supportingContentPadding,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
?: Spacer(modifier = Modifier.height(height = 6.dp))
|
||||
}
|
||||
}
|
||||
if (autoFocus) {
|
||||
@@ -266,6 +229,8 @@ fun BitwardenPasswordField(
|
||||
* @param showPasswordChange Lambda that is called when user request show/hide be toggled.
|
||||
* @param onValueChange Callback that is triggered when the password changes.
|
||||
* @param modifier Modifier for the composable.
|
||||
* @param interactionSource A [MutableInteractionSource] for observing and emitting interactions
|
||||
* for this component.
|
||||
* @param helpData An optional help button to be displayed in the label.
|
||||
* @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
|
||||
@@ -295,6 +260,7 @@ fun BitwardenPasswordField(
|
||||
onValueChange: (String) -> Unit,
|
||||
cardStyle: CardStyle,
|
||||
modifier: Modifier = Modifier,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
helpData: BitwardenHelpButtonData? = null,
|
||||
readOnly: Boolean = false,
|
||||
singleLine: Boolean = true,
|
||||
@@ -317,6 +283,7 @@ fun BitwardenPasswordField(
|
||||
showPasswordTestTag = showPasswordTestTag,
|
||||
onValueChange = onValueChange,
|
||||
modifier = modifier,
|
||||
interactionSource = interactionSource,
|
||||
helpData = helpData,
|
||||
readOnly = readOnly,
|
||||
singleLine = singleLine,
|
||||
@@ -351,6 +318,8 @@ fun BitwardenPasswordField(
|
||||
* @param value Current next on the text field.
|
||||
* @param onValueChange Callback that is triggered when the password changes.
|
||||
* @param modifier Modifier for the composable.
|
||||
* @param interactionSource A [MutableInteractionSource] for observing and emitting interactions
|
||||
* for this component.
|
||||
* @param helpData An optional help button to be displayed in the label.
|
||||
* @param initialShowPassword The initial state of the show/hide password control. A value of
|
||||
* `false` (the default) indicates that that password should begin in the hidden state.
|
||||
@@ -382,6 +351,7 @@ fun BitwardenPasswordField(
|
||||
supportingContent: @Composable (ColumnScope.() -> Unit)?,
|
||||
cardStyle: CardStyle,
|
||||
modifier: Modifier = Modifier,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
helpData: BitwardenHelpButtonData? = null,
|
||||
initialShowPassword: Boolean = false,
|
||||
readOnly: Boolean = false,
|
||||
@@ -406,6 +376,7 @@ fun BitwardenPasswordField(
|
||||
showPasswordTestTag = showPasswordTestTag,
|
||||
onValueChange = onValueChange,
|
||||
modifier = modifier,
|
||||
interactionSource = interactionSource,
|
||||
helpData = helpData,
|
||||
readOnly = readOnly,
|
||||
singleLine = singleLine,
|
||||
@@ -431,6 +402,8 @@ fun BitwardenPasswordField(
|
||||
* @param value Current next on the text field.
|
||||
* @param onValueChange Callback that is triggered when the password changes.
|
||||
* @param modifier Modifier for the composable.
|
||||
* @param interactionSource A [MutableInteractionSource] for observing and emitting interactions
|
||||
* for this component.
|
||||
* @param helpData An optional help button to be displayed in the label.
|
||||
* @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
|
||||
@@ -459,6 +432,7 @@ fun BitwardenPasswordField(
|
||||
onValueChange: (String) -> Unit,
|
||||
cardStyle: CardStyle,
|
||||
modifier: Modifier = Modifier,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
helpData: BitwardenHelpButtonData? = null,
|
||||
readOnly: Boolean = false,
|
||||
singleLine: Boolean = true,
|
||||
@@ -481,6 +455,7 @@ fun BitwardenPasswordField(
|
||||
showPassword = showPassword,
|
||||
showPasswordChange = { showPassword = !showPassword },
|
||||
onValueChange = onValueChange,
|
||||
interactionSource = interactionSource,
|
||||
helpData = helpData,
|
||||
readOnly = readOnly,
|
||||
singleLine = singleLine,
|
||||
|
||||
+74
-107
@@ -1,18 +1,15 @@
|
||||
package com.bitwarden.ui.platform.components.field
|
||||
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
@@ -20,6 +17,7 @@ import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuAnchorType
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuBoxScope
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -31,21 +29,14 @@ import androidx.compose.runtime.mutableIntStateOf
|
||||
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.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.focus.onFocusEvent
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalTextToolbar
|
||||
import androidx.compose.ui.platform.TextToolbar
|
||||
import androidx.compose.ui.semantics.CustomAccessibilityAction
|
||||
import androidx.compose.ui.semantics.customActions
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
@@ -60,18 +51,16 @@ import com.bitwarden.ui.platform.base.util.simpleVerticalScrollbar
|
||||
import com.bitwarden.ui.platform.base.util.toPx
|
||||
import com.bitwarden.ui.platform.base.util.withLineBreaksAtWidth
|
||||
import com.bitwarden.ui.platform.components.appbar.color.bitwardenMenuItemColors
|
||||
import com.bitwarden.ui.platform.components.button.BitwardenHelpIconButton
|
||||
import com.bitwarden.ui.platform.components.button.model.BitwardenHelpButtonData
|
||||
import com.bitwarden.ui.platform.components.divider.BitwardenHorizontalDivider
|
||||
import com.bitwarden.ui.platform.components.field.color.bitwardenTextFieldColors
|
||||
import com.bitwarden.ui.platform.components.field.label.BitwardenTextFieldLabel
|
||||
import com.bitwarden.ui.platform.components.field.model.TextToolbarType
|
||||
import com.bitwarden.ui.platform.components.field.toolbar.BitwardenCutCopyTextToolbar
|
||||
import com.bitwarden.ui.platform.components.field.toolbar.BitwardenEmptyTextToolbar
|
||||
import com.bitwarden.ui.platform.components.field.support.BitwardenTextFieldSupportingContent
|
||||
import com.bitwarden.ui.platform.components.field.util.toTextToolbar
|
||||
import com.bitwarden.ui.platform.components.icon.BitwardenIcon
|
||||
import com.bitwarden.ui.platform.components.icon.model.IconData
|
||||
import com.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.bitwarden.ui.platform.components.row.BitwardenRowOfActions
|
||||
import com.bitwarden.ui.platform.components.support.BitwardenSupportingContent
|
||||
import com.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@@ -84,6 +73,8 @@ import kotlinx.collections.immutable.toImmutableList
|
||||
* @param label label for the text field.
|
||||
* @param value current next on the text field.
|
||||
* @param modifier modifier for the composable.
|
||||
* @param interactionSource A [MutableInteractionSource] for observing and emitting interactions
|
||||
* for this component.
|
||||
* @param onValueChange callback that is triggered when the input of the text field changes.
|
||||
* @param helpData An optional help button to be displayed in the label.
|
||||
* @param placeholder the optional placeholder to be displayed when the text field is in focus and
|
||||
@@ -118,6 +109,7 @@ fun BitwardenTextField(
|
||||
onValueChange: (String) -> Unit,
|
||||
cardStyle: CardStyle?,
|
||||
modifier: Modifier = Modifier,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
helpData: BitwardenHelpButtonData? = null,
|
||||
placeholder: String? = null,
|
||||
leadingIconData: IconData? = null,
|
||||
@@ -146,6 +138,7 @@ fun BitwardenTextField(
|
||||
label = label,
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
interactionSource = interactionSource,
|
||||
helpData = helpData,
|
||||
placeholder = placeholder,
|
||||
leadingIconData = leadingIconData,
|
||||
@@ -188,6 +181,8 @@ fun BitwardenTextField(
|
||||
* @param label label for the text field.
|
||||
* @param value current next on the text field.
|
||||
* @param modifier modifier for the composable.
|
||||
* @param interactionSource A [MutableInteractionSource] for observing and emitting interactions
|
||||
* for this component.
|
||||
* @param helpData An optional help button to be displayed in the label.
|
||||
* @param onValueChange callback that is triggered when the input of the text field changes.
|
||||
* @param supportingContent An optional supporting content composable that will appear below the
|
||||
@@ -217,8 +212,8 @@ fun BitwardenTextField(
|
||||
* in the app bar's trailing side. This lambda extends [RowScope], allowing flexibility in
|
||||
* defining the layout of the actions.
|
||||
*/
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
fun BitwardenTextField(
|
||||
label: String?,
|
||||
@@ -227,6 +222,7 @@ fun BitwardenTextField(
|
||||
supportingContent: (@Composable ColumnScope.() -> Unit)?,
|
||||
cardStyle: CardStyle?,
|
||||
modifier: Modifier = Modifier,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
helpData: BitwardenHelpButtonData? = null,
|
||||
supportingContentPadding: PaddingValues = PaddingValues(vertical = 12.dp, horizontal = 16.dp),
|
||||
placeholder: String? = null,
|
||||
@@ -254,7 +250,7 @@ fun BitwardenTextField(
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val formattedText = if (shouldAddCustomLineBreaks) {
|
||||
value.withLineBreaksAtWidth(
|
||||
// Adjust for built in padding
|
||||
// Adjust for built-in padding
|
||||
widthPx = widthPx - 32.dp.toPx(),
|
||||
monospacedTextStyle = textStyle,
|
||||
)
|
||||
@@ -270,82 +266,45 @@ fun BitwardenTextField(
|
||||
textFieldValueState = textFieldValue
|
||||
}
|
||||
}
|
||||
val textToolbar = when (textToolbarType) {
|
||||
TextToolbarType.DEFAULT -> BitwardenCutCopyTextToolbar(
|
||||
value = textFieldValue,
|
||||
onValueChange = onValueChange,
|
||||
defaultTextToolbar = LocalTextToolbar.current,
|
||||
clipboardManager = LocalClipboard.current.nativeClipboard,
|
||||
focusManager = LocalFocusManager.current,
|
||||
)
|
||||
|
||||
TextToolbarType.NONE -> BitwardenEmptyTextToolbar
|
||||
}
|
||||
val textToolbar = textToolbarType.toTextToolbar(
|
||||
textFieldValue = textFieldValue,
|
||||
onValueChange = onValueChange,
|
||||
)
|
||||
var lastTextValue by remember(value) { mutableStateOf(value = value) }
|
||||
val isFocused by interactionSource.collectIsFocusedAsState()
|
||||
CompositionLocalProvider(value = LocalTextToolbar provides textToolbar) {
|
||||
var hasFocused by remember { mutableStateOf(value = false) }
|
||||
val filteredAutoCompleteList = autoCompleteOptions
|
||||
.filter { it.startsWith(textFieldValue.text) && it != textFieldValue.text }
|
||||
.toImmutableList()
|
||||
val isDropDownExpanded = filteredAutoCompleteList.isNotEmpty() && hasFocused
|
||||
val isDropDownExpanded = filteredAutoCompleteList.isNotEmpty() && isFocused
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = isDropDownExpanded,
|
||||
onExpandedChange = {
|
||||
hasFocused = !hasFocused
|
||||
focusRequester.requestFocus()
|
||||
// The expanded state is managed via focus and if we have options to display.
|
||||
},
|
||||
modifier = modifier.defaultMinSize(minHeight = 60.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.onGloballyPositioned { widthPx = it.size.width }
|
||||
.onFocusEvent { focusState -> hasFocused = focusState.hasFocus }
|
||||
.cardStyle(
|
||||
cardStyle = cardStyle,
|
||||
paddingTop = 6.dp,
|
||||
paddingBottom = 0.dp,
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.semantics {
|
||||
customActions = listOfNotNull(
|
||||
helpData?.let {
|
||||
CustomAccessibilityAction(
|
||||
label = it.contentDescription,
|
||||
action = {
|
||||
it.onClick()
|
||||
true
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
var focused by remember { mutableStateOf(false) }
|
||||
|
||||
TextField(
|
||||
colors = bitwardenTextFieldColors(textColor = textColor),
|
||||
enabled = enabled,
|
||||
label = label?.let {
|
||||
{
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(text = it)
|
||||
helpData?.let { helpButtonData ->
|
||||
val targetSize = if (textFieldValue.text.isEmpty() || focused) {
|
||||
16.dp
|
||||
} else {
|
||||
12.dp
|
||||
}
|
||||
val size by animateDpAsState(
|
||||
targetValue = targetSize,
|
||||
label = "${helpButtonData.contentDescription}_animation",
|
||||
)
|
||||
Spacer(modifier = Modifier.width(width = 8.dp))
|
||||
BitwardenHelpIconButton(
|
||||
helpData = helpButtonData,
|
||||
modifier = Modifier.size(size = size),
|
||||
)
|
||||
}
|
||||
}
|
||||
BitwardenTextFieldLabel(
|
||||
label = it,
|
||||
textFieldValue = textFieldValue,
|
||||
helpData = helpData,
|
||||
isFieldFocused = isFocused,
|
||||
)
|
||||
}
|
||||
},
|
||||
value = textFieldValue,
|
||||
@@ -359,7 +318,6 @@ fun BitwardenTextField(
|
||||
},
|
||||
placeholder = placeholder?.let { { Text(text = it, style = textStyle) } },
|
||||
onValueChange = {
|
||||
hasFocused = true
|
||||
textFieldValueState = it
|
||||
val stringChangedSinceLastInvocation = lastTextValue != it.text
|
||||
lastTextValue = it.text
|
||||
@@ -387,6 +345,7 @@ fun BitwardenTextField(
|
||||
},
|
||||
isError = isError,
|
||||
visualTransformation = visualTransformation,
|
||||
interactionSource = interactionSource,
|
||||
modifier = Modifier
|
||||
.nullableTestTag(tag = textFieldTestTag)
|
||||
.menuAnchor(
|
||||
@@ -394,49 +353,27 @@ fun BitwardenTextField(
|
||||
enabled = false,
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester)
|
||||
.focusRequester(focusRequester = focusRequester)
|
||||
.onFocusChanged { focusState ->
|
||||
focused = focusState.isFocused
|
||||
if (focused) {
|
||||
if (focusState.isFocused) {
|
||||
textFieldValueState = textFieldValueState.copy(
|
||||
selection = TextRange(textFieldValueState.text.length),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
supportingContent
|
||||
?.let { content ->
|
||||
Spacer(modifier = Modifier.height(height = 6.dp))
|
||||
BitwardenHorizontalDivider(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp),
|
||||
)
|
||||
BitwardenSupportingContent(
|
||||
cardStyle = null,
|
||||
insets = supportingContentPadding,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
?: Spacer(modifier = Modifier.height(height = cardStyle?.let { 6.dp } ?: 0.dp))
|
||||
}
|
||||
val scrollState = rememberScrollState()
|
||||
ExposedDropdownMenu(
|
||||
expanded = isDropDownExpanded,
|
||||
shape = BitwardenTheme.shapes.menu,
|
||||
containerColor = BitwardenTheme.colorScheme.background.primary,
|
||||
onDismissRequest = { hasFocused = false },
|
||||
scrollState = scrollState,
|
||||
modifier = Modifier.simpleVerticalScrollbar(state = scrollState),
|
||||
) {
|
||||
filteredAutoCompleteList.forEach {
|
||||
DropdownMenuItem(
|
||||
colors = bitwardenMenuItemColors(),
|
||||
text = { Text(text = it, style = textStyle) },
|
||||
onClick = { onValueChange(it) },
|
||||
)
|
||||
}
|
||||
BitwardenTextFieldSupportingContent(
|
||||
content = supportingContent,
|
||||
supportingContentPadding = supportingContentPadding,
|
||||
noContentCardPadding = cardStyle?.let { 6.dp } ?: 0.dp,
|
||||
)
|
||||
}
|
||||
AutocompleteDropDownMenu(
|
||||
isExpanded = isDropDownExpanded,
|
||||
autoCompleteList = filteredAutoCompleteList,
|
||||
onValueClicked = onValueChange,
|
||||
textStyle = textStyle,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (autoFocus) {
|
||||
@@ -444,6 +381,36 @@ fun BitwardenTextField(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ExposedDropdownMenuBoxScope.AutocompleteDropDownMenu(
|
||||
isExpanded: Boolean,
|
||||
autoCompleteList: ImmutableList<String>,
|
||||
onValueClicked: (String) -> Unit,
|
||||
textStyle: TextStyle,
|
||||
modifier: Modifier = Modifier,
|
||||
scrollState: ScrollState = rememberScrollState(),
|
||||
) {
|
||||
ExposedDropdownMenu(
|
||||
expanded = isExpanded,
|
||||
shape = BitwardenTheme.shapes.menu,
|
||||
containerColor = BitwardenTheme.colorScheme.background.primary,
|
||||
onDismissRequest = {
|
||||
// This cannot be dismissed while the TextField has focus.
|
||||
},
|
||||
scrollState = scrollState,
|
||||
modifier = modifier.simpleVerticalScrollbar(state = scrollState),
|
||||
) {
|
||||
autoCompleteList.forEach {
|
||||
DropdownMenuItem(
|
||||
colors = bitwardenMenuItemColors(),
|
||||
text = { Text(text = it, style = textStyle) },
|
||||
onClick = { onValueClicked(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun BitwardenTextField_preview() {
|
||||
|
||||
+1
-4
@@ -2,7 +2,6 @@ package com.bitwarden.ui.platform.components.field.interceptor
|
||||
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.platform.InterceptPlatformTextInput
|
||||
import androidx.compose.ui.platform.PlatformTextInputInterceptor
|
||||
import androidx.compose.ui.platform.PlatformTextInputMethodRequest
|
||||
@@ -11,8 +10,7 @@ import androidx.compose.ui.platform.PlatformTextInputSession
|
||||
/**
|
||||
* Interceptor that disables the [EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING] flag on text inputs.
|
||||
*/
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
object NoPersonalizedLearningInterceptor : PlatformTextInputInterceptor {
|
||||
private object NoPersonalizedLearningInterceptor : PlatformTextInputInterceptor {
|
||||
override suspend fun interceptStartInputMethod(
|
||||
request: PlatformTextInputMethodRequest,
|
||||
nextHandler: PlatformTextInputSession,
|
||||
@@ -35,7 +33,6 @@ object NoPersonalizedLearningInterceptor : PlatformTextInputInterceptor {
|
||||
* This is a modified version of the workaround provided by Google here:
|
||||
* https://issuetracker.google.com/issues/359257538#comment2
|
||||
*/
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun IncognitoInput(content: @Composable () -> Unit) {
|
||||
InterceptPlatformTextInput(
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package com.bitwarden.ui.platform.components.field.label
|
||||
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bitwarden.ui.platform.components.button.BitwardenHelpIconButton
|
||||
import com.bitwarden.ui.platform.components.button.model.BitwardenHelpButtonData
|
||||
import com.bitwarden.ui.platform.components.field.BitwardenPasswordField
|
||||
import com.bitwarden.ui.platform.components.field.BitwardenTextField
|
||||
|
||||
/**
|
||||
* Represents a Bitwarden-styled label for a text field.
|
||||
*
|
||||
* Intended to be reused for all implementations of a TextField, such as the [BitwardenTextField]
|
||||
* and [BitwardenPasswordField].
|
||||
*/
|
||||
@Composable
|
||||
internal fun BitwardenTextFieldLabel(
|
||||
label: String,
|
||||
textFieldValue: TextFieldValue,
|
||||
helpData: BitwardenHelpButtonData?,
|
||||
isFieldFocused: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier,
|
||||
) {
|
||||
Text(text = label)
|
||||
helpData?.let { helpButtonData ->
|
||||
val size by animateDpAsState(
|
||||
targetValue = if (textFieldValue.text.isEmpty() || isFieldFocused) 16.dp else 12.dp,
|
||||
label = "${helpButtonData.contentDescription}_animation",
|
||||
)
|
||||
Spacer(modifier = Modifier.width(width = 8.dp))
|
||||
BitwardenHelpIconButton(
|
||||
helpData = helpButtonData,
|
||||
modifier = Modifier.size(size = size),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.bitwarden.ui.platform.components.field.support
|
||||
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bitwarden.ui.platform.components.divider.BitwardenHorizontalDivider
|
||||
import com.bitwarden.ui.platform.components.field.BitwardenPasswordField
|
||||
import com.bitwarden.ui.platform.components.field.BitwardenTextField
|
||||
import com.bitwarden.ui.platform.components.support.BitwardenSupportingContent
|
||||
|
||||
/**
|
||||
* Lays out the Bitwarden-styled supporting content for a text field.
|
||||
*
|
||||
* Intended to be reused for all implementations of a TextField, such as the [BitwardenTextField]
|
||||
* and [BitwardenPasswordField].
|
||||
*/
|
||||
@Composable
|
||||
internal fun ColumnScope.BitwardenTextFieldSupportingContent(
|
||||
content: (@Composable ColumnScope.() -> Unit)?,
|
||||
supportingContentPadding: PaddingValues,
|
||||
noContentCardPadding: Dp,
|
||||
) {
|
||||
if (content != null) {
|
||||
Spacer(modifier = Modifier.height(height = 6.dp))
|
||||
BitwardenHorizontalDivider(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp),
|
||||
)
|
||||
BitwardenSupportingContent(
|
||||
cardStyle = null,
|
||||
insets = supportingContentPadding,
|
||||
content = content,
|
||||
)
|
||||
} else {
|
||||
// Pad out the card when there is no content.
|
||||
Spacer(modifier = Modifier.height(height = noContentCardPadding))
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.bitwarden.ui.platform.components.field.util
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalTextToolbar
|
||||
import androidx.compose.ui.platform.TextToolbar
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.bitwarden.ui.platform.components.field.model.TextToolbarType
|
||||
import com.bitwarden.ui.platform.components.field.toolbar.BitwardenCutCopyTextToolbar
|
||||
import com.bitwarden.ui.platform.components.field.toolbar.BitwardenEmptyTextToolbar
|
||||
|
||||
/**
|
||||
* Creates the appropriate type of [TextToolbar] based on the [TextToolbarType] and provided values.
|
||||
*/
|
||||
@Composable
|
||||
fun TextToolbarType.toTextToolbar(
|
||||
textFieldValue: TextFieldValue,
|
||||
onValueChange: (String) -> Unit,
|
||||
): TextToolbar = when (this) {
|
||||
TextToolbarType.DEFAULT -> BitwardenCutCopyTextToolbar(
|
||||
value = textFieldValue,
|
||||
onValueChange = onValueChange,
|
||||
defaultTextToolbar = LocalTextToolbar.current,
|
||||
clipboardManager = LocalClipboard.current.nativeClipboard,
|
||||
focusManager = LocalFocusManager.current,
|
||||
)
|
||||
|
||||
TextToolbarType.NONE -> BitwardenEmptyTextToolbar
|
||||
}
|
||||
Reference in new issue
Block a user