From 05d958d1829ce68fe3e807419429fa33d5901f4a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 20 May 2026 22:21:20 +0000 Subject: [PATCH] fix(ui): restore outlined visual on EditPostView + ForwardZapTo TextFields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrating the mention TextFields to BasicTextField(state) routed them through ThinPaddingTextField, which uses TextFieldDefaults.DecorationBox (filled, surfaceVariant container). That replaced the original OutlinedTextField look — visible rounded border + transparent inside — with a filled gray rectangle. On ForwardZapTo the change was especially visible since the original outlined border was the only visual cue that the search row was an input. Add OutlinedThinPaddingTextField, a sibling of ThinPaddingTextField that wraps the same TextFieldState pipeline with OutlinedTextFieldDefaults.DecorationBox + Container. Same thin padding, same onTextChanged/inputTransformation/outputTransformation surface, but genuinely outlined (notched label, transparent inside). - ForwardZapTo: switch to the new component, drop the focused/unfocusedIndicatorColor=Transparent overrides — defaults give the proper outlined border the original OutlinedTextField had. - EditPostView.MessageField: switch to the new component and use OutlinedTextFieldDefaults.colors(focusedBorderColor=Transparent, unfocusedBorderColor=Transparent) to keep the inner border invisible so the existing 1dp Modifier.border(surface, RoundedCornerShape(8.dp)) remains the only visible frame, matching the original. --- .../amethyst/ui/actions/EditPostView.kt | 12 +- .../OutlinedThinPaddingTextField.kt | 177 ++++++++++++++++++ .../note/creators/zapsplits/ForwardZapTo.kt | 11 +- 3 files changed, 185 insertions(+), 15 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/OutlinedThinPaddingTextField.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt index aae12541f5..ffcea5175a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt @@ -42,10 +42,10 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.IconButton import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -80,7 +80,7 @@ import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery import com.vitorpamplona.amethyst.ui.components.BechLink import com.vitorpamplona.amethyst.ui.components.LoadUrlPreview -import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField +import com.vitorpamplona.amethyst.ui.components.OutlinedThinPaddingTextField import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.topbars.PostingTopBar import com.vitorpamplona.amethyst.ui.note.NoteCompose @@ -380,7 +380,7 @@ private fun MessageField(postViewModel: EditPostViewModel) { } } - ThinPaddingTextField( + OutlinedThinPaddingTextField( state = postViewModel.message, onTextChanged = { postViewModel.onMessageChanged() }, inputTransformation = MentionPreservingInputTransformation, @@ -409,9 +409,9 @@ private fun MessageField(postViewModel: EditPostViewModel) { ) }, colors = - TextFieldDefaults.colors( - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, + OutlinedTextFieldDefaults.colors( + focusedBorderColor = Color.Transparent, + unfocusedBorderColor = Color.Transparent, ), textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content), ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/OutlinedThinPaddingTextField.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/OutlinedThinPaddingTextField.kt new file mode 100644 index 0000000000..dc7ec8145a --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/OutlinedThinPaddingTextField.kt @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.components + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.input.InputTransformation +import androidx.compose.foundation.text.input.OutputTransformation +import androidx.compose.foundation.text.input.TextFieldLineLimits +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.foundation.text.selection.LocalTextSelectionColors +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.TextFieldColors +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.takeOrElse +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.dp +import com.vitorpamplona.amethyst.ui.theme.placeholderText + +/** + * Outlined sibling of [ThinPaddingTextField]. Wraps the new [TextFieldState] API + * with [OutlinedTextFieldDefaults.DecorationBox] so callers that want the + * Material3 outlined look (visible rounded border, transparent inside, notched + * label) can use the same `outputTransformation` / `inputTransformation` + * mention pipeline as the filled `ThinPaddingTextField`. + */ +@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) +@Composable +fun OutlinedThinPaddingTextField( + state: TextFieldState, + modifier: Modifier = Modifier, + onTextChanged: (() -> Unit)? = null, + enabled: Boolean = true, + readOnly: Boolean = false, + textStyle: TextStyle = LocalTextStyle.current, + inputTransformation: InputTransformation? = null, + outputTransformation: OutputTransformation? = null, + label: @Composable (() -> Unit)? = null, + placeholder: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, + prefix: @Composable (() -> Unit)? = null, + suffix: @Composable (() -> Unit)? = null, + supportingText: @Composable (() -> Unit)? = null, + isError: Boolean = false, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + singleLine: Boolean = false, + maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, + minLines: Int = 1, + interactionSource: MutableInteractionSource? = null, + shape: Shape = OutlinedTextFieldDefaults.shape, + colors: TextFieldColors = OutlinedTextFieldDefaults.colors(), + contentPadding: PaddingValues = + OutlinedTextFieldDefaults.contentPadding( + start = 10.dp, + top = 12.dp, + end = 10.dp, + bottom = 12.dp, + ), +) { + @Suppress("NAME_SHADOWING") + val interactionSource = interactionSource ?: remember { MutableInteractionSource() } + + if (onTextChanged != null) { + val callback by rememberUpdatedState(onTextChanged) + LaunchedEffect(state) { + snapshotFlow { state.text } + .collect { callback() } + } + } + + val textColor = + textStyle.color.takeOrElse { + val focused by interactionSource.collectIsFocusedAsState() + when { + !enabled -> MaterialTheme.colorScheme.placeholderText + isError -> MaterialTheme.colorScheme.onSurface + focused -> MaterialTheme.colorScheme.onSurface + else -> MaterialTheme.colorScheme.onSurface + } + } + val mergedTextStyle = textStyle.merge(TextStyle(color = textColor)) + + val lineLimits = + if (singleLine) { + TextFieldLineLimits.SingleLine + } else { + TextFieldLineLimits.MultiLine(minLines, maxLines) + } + + CompositionLocalProvider(LocalTextSelectionColors provides colors.textSelectionColors) { + BasicTextField( + state = state, + modifier = + modifier + .defaultMinSize( + minWidth = TextFieldDefaults.MinWidth, + minHeight = 36.dp, + ), + enabled = enabled, + readOnly = readOnly, + textStyle = mergedTextStyle, + cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), + keyboardOptions = keyboardOptions, + lineLimits = lineLimits, + interactionSource = interactionSource, + inputTransformation = inputTransformation, + outputTransformation = outputTransformation, + decorator = { innerTextField -> + OutlinedTextFieldDefaults.DecorationBox( + value = state.text.toString(), + visualTransformation = VisualTransformation.None, + innerTextField = innerTextField, + placeholder = placeholder, + label = label, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, + prefix = prefix, + suffix = suffix, + supportingText = supportingText, + singleLine = singleLine, + enabled = enabled, + isError = isError, + interactionSource = interactionSource, + colors = colors, + contentPadding = contentPadding, + container = { + OutlinedTextFieldDefaults.Container( + enabled = enabled, + isError = isError, + interactionSource = interactionSource, + colors = colors, + shape = shape, + ) + }, + ) + }, + ) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zapsplits/ForwardZapTo.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zapsplits/ForwardZapTo.kt index 5c9e15baa7..16e7dc2be8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zapsplits/ForwardZapTo.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zapsplits/ForwardZapTo.kt @@ -31,11 +31,9 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Slider import androidx.compose.material3.Text -import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.text.style.TextOverflow @@ -44,7 +42,7 @@ import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.actions.MentionPreservingInputTransformation import com.vitorpamplona.amethyst.ui.actions.UrlUserTagOutputTransformation -import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField +import com.vitorpamplona.amethyst.ui.components.OutlinedThinPaddingTextField import com.vitorpamplona.amethyst.ui.note.BaseUserPicture import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -126,7 +124,7 @@ fun ForwardZapTo( } } - ThinPaddingTextField( + OutlinedThinPaddingTextField( state = postViewModel.forwardZapToEditting, onTextChanged = postViewModel::onForwardZapTextChanged, inputTransformation = MentionPreservingInputTransformation, @@ -140,11 +138,6 @@ fun ForwardZapTo( ) }, singleLine = true, - colors = - TextFieldDefaults.colors( - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - ), textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content), ) }