From d709d67f54994f73b0edd44fc390c01a09eed0ba Mon Sep 17 00:00:00 2001 From: jeffrey-signal Date: Mon, 9 Feb 2026 16:27:15 -0500 Subject: [PATCH] Fix position of clearable text field remaining characters count. --- .../groups/memberlabel/MemberLabelFragment.kt | 3 +- .../core/ui/compose/ClearableTextField.kt | 49 +++++++++++-------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelFragment.kt index e5a4ff5dab..a8b5d8b344 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelFragment.kt @@ -194,7 +194,8 @@ private fun LabelTextField( imeAction = ImeAction.Done ), keyboardActions = KeyboardActions(onDone = { onSave() }), - charactersRemaining = remainingCharacters, + hasClearableContent = { labelText.isNotEmpty() || !labelEmoji.isNullOrEmpty() }, + charactersRemainingBeforeLimit = remainingCharacters, countdownConfig = ClearableTextField.CountdownConfig(displayThreshold = 9, warnThreshold = 5), colors = TextFieldDefaults.colors( unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant, diff --git a/core/ui/src/main/java/org/signal/core/ui/compose/ClearableTextField.kt b/core/ui/src/main/java/org/signal/core/ui/compose/ClearableTextField.kt index d9f0a2c2c5..3dac355650 100644 --- a/core/ui/src/main/java/org/signal/core/ui/compose/ClearableTextField.kt +++ b/core/ui/src/main/java/org/signal/core/ui/compose/ClearableTextField.kt @@ -6,7 +6,8 @@ package org.signal.core.ui.compose import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.layout.Box +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -68,6 +69,7 @@ fun ClearableTextField( singleLine: Boolean = false, clearable: Boolean = true, onClear: () -> Unit = { onValueChange("") }, + hasClearableContent: () -> Boolean = { value.isNotEmpty() }, charactersRemainingBeforeLimit: Int = Int.MAX_VALUE, countdownConfig: ClearableTextField.CountdownConfig? = null, colors: TextFieldColors = defaultTextFieldColors() @@ -86,7 +88,8 @@ fun ClearableTextField( singleLine = singleLine, clearable = clearable, onClear = onClear, - charactersRemaining = charactersRemainingBeforeLimit, + hasClearableContent = hasClearableContent, + charactersRemainingBeforeLimit = charactersRemainingBeforeLimit, countdownConfig = countdownConfig, colors = colors ) @@ -113,22 +116,23 @@ fun ClearableTextField( singleLine: Boolean = false, clearable: Boolean = true, onClear: () -> Unit = { onValueChange("") }, - charactersRemaining: Int = Int.MAX_VALUE, + hasClearableContent: () -> Boolean = { value.isNotEmpty() }, + charactersRemainingBeforeLimit: Int = Int.MAX_VALUE, countdownConfig: ClearableTextField.CountdownConfig? = null, colors: TextFieldColors = defaultTextFieldColors() ) { var focused by remember { mutableStateOf(false) } - val displayCountdown = countdownConfig != null && charactersRemaining <= countdownConfig.displayThreshold + val displayCountdown = countdownConfig != null && charactersRemainingBeforeLimit <= countdownConfig.displayThreshold val clearButton: @Composable () -> Unit = { ClearButton( - visible = focused, + visible = focused && hasClearableContent(), onClick = onClear, contentDescription = clearContentDescription ) } - Box(modifier = modifier) { + Column(modifier = modifier) { TextFields.TextField( value = value, onValueChange = onValueChange, @@ -144,25 +148,19 @@ fun ClearableTextField( .onFocusChanged { focused = it.hasFocus && clearable }, colors = colors, leadingIcon = leadingIcon, - trailingIcon = if (clearable) clearButton else null, - contentPadding = if (label == null) { - TextFieldDefaults.contentPaddingWithoutLabel(end = if (displayCountdown) 48.dp else 16.dp) - } else { - TextFieldDefaults.contentPaddingWithLabel(end = if (displayCountdown) 48.dp else 16.dp) - } + trailingIcon = if (clearable) clearButton else null ) AnimatedVisibility( visible = displayCountdown, - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(bottom = 10.dp, end = 12.dp) + modifier = Modifier.align(Alignment.End) ) { - val errorThresholdExceeded = countdownConfig != null && charactersRemaining <= countdownConfig.warnThreshold + val errorThresholdExceeded = countdownConfig != null && charactersRemainingBeforeLimit <= countdownConfig.warnThreshold Text( - text = "$charactersRemaining", + text = "$charactersRemainingBeforeLimit", style = MaterialTheme.typography.bodySmall, - color = if (errorThresholdExceeded) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.outline + color = if (errorThresholdExceeded) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.outline, + modifier = Modifier.padding(top = 4.dp, end = 16.dp) ) } } @@ -174,7 +172,11 @@ private fun ClearButton( onClick: () -> Unit, contentDescription: String ) { - AnimatedVisibility(visible = visible) { + AnimatedVisibility( + visible = visible, + enter = fadeIn(), + exit = fadeOut() + ) { IconButton( onClick = onClick ) { @@ -261,15 +263,22 @@ private fun ClearableTextFieldCharacterCountPreview() { Spacer(modifier = Modifier.size(16.dp)) + val focusRequester = remember { FocusRequester() } + ClearableTextField( value = "Very long text showing the character count warning state", onValueChange = {}, hint = "countdown warning", clearContentDescription = "Clear", charactersRemainingBeforeLimit = 8, - countdownConfig = countdownConfig + countdownConfig = countdownConfig, + modifier = Modifier.focusRequester(focusRequester) ) + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } + Spacer(modifier = Modifier.size(16.dp)) ClearableTextField(