From 1c8e4c5f057a3e8fe143657c4437f750464b278e Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 1 Apr 2026 11:20:13 -0500 Subject: [PATCH] Checkpoint android fixes --- .../{Monolith.ref.tsx => Monolith.ref} | 0 src/components/Composer/index.tsx | 55 ++++++++++++++++--- .../Messages/components/MessageComposer.tsx | 2 +- 3 files changed, 47 insertions(+), 10 deletions(-) rename src/components/Composer/{Monolith.ref.tsx => Monolith.ref} (100%) diff --git a/src/components/Composer/Monolith.ref.tsx b/src/components/Composer/Monolith.ref similarity index 100% rename from src/components/Composer/Monolith.ref.tsx rename to src/components/Composer/Monolith.ref diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index a5f0e799b3..894b95863d 100644 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -48,7 +48,7 @@ import { } from '#/components/Autocomplete' import {useOnKeyboard} from '#/components/hooks/useOnKeyboard' import {Span, Text} from '#/components/Typography' -import {IS_IOS, IS_WEB, IS_WEB_TOUCH_DEVICE} from '#/env' +import {IS_ANDROID, IS_IOS, IS_WEB, IS_WEB_TOUCH_DEVICE} from '#/env' /* * ─── Types ──────────────────────────────────────────────────────────────────── @@ -216,9 +216,17 @@ export function Composer({ const xh = maxNumberOfLines ? lineHeight * maxNumberOfLines + verticalSpace : 999 + /* + * On web, we set an initial height and auto-resize via DOM measurement. + * On Android, we drive height explicitly via onContentSizeChange to avoid + * sub-pixel oscillation that causes layout jumpiness. + * On iOS, minHeight/maxHeight works fine natively. + */ const tas = IS_WEB ? {height: lineHeight + verticalSpace} - : {minHeight: mh, maxHeight: xh} + : IS_ANDROID + ? {height: mh} + : {minHeight: mh, maxHeight: xh} if (IS_IOS) { delete ts.lineHeight @@ -227,6 +235,14 @@ export function Composer({ return {textStyle: ts, textAreaStyle: tas, minHeight: mh, maxHeight: xh} }, [t, fonts, padding, rawTextStyle, initialNumberOfLines, maxNumberOfLines]) + /* + * On Android, multiline TextInput oscillates between slightly different + * contentSize values on consecutive layout passes (sub-pixel rounding). + * This causes visible jumpiness when using minHeight/maxHeight. Instead, + * we drive the height explicitly and ceil the value to stabilize it. + */ + const [androidInputHeight, setAndroidInputHeight] = useState(minHeight) + // ─── Height auto-resize + sift positioning ──────────────────────────── const updateAutocompletePosition = useCallback(() => { @@ -253,14 +269,26 @@ export function Composer({ return } - textInputRef.current?.measure((_x, _y, _w, h) => { - if (h !== prevHeight.current) { - prevHeight.current = h - updateAutocompletePosition() - } - }) + if (IS_IOS) { + textInputRef.current?.measure((_x, _y, _w, h) => { + if (h !== prevHeight.current) { + prevHeight.current = h + updateAutocompletePosition() + } + }) + } }, [tapper.state.text, minHeight, maxHeight, updateAutocompletePosition]) + /* + * On Android, height is driven by onContentSizeChange (see the TextInput + * below), so we update the autocomplete position when that height changes. + */ + useEffect(() => { + if (IS_ANDROID) { + updateAutocompletePosition() + } + }, [androidInputHeight, updateAutocompletePosition]) + // ─── Scroll sync ────────────────────────────────────────────────────── const previewScrollStyle = useAnimatedStyle(() => ({ @@ -356,7 +384,7 @@ export function Composer({ textAlignVertical: 'top', includeFontPadding: false, }, - textAreaStyle, + IS_ANDROID ? {height: androidInputHeight} : textAreaStyle, web({ resize: 'none', outline: 'none', @@ -388,6 +416,15 @@ export function Composer({ inputScrollSharedValue.value = e.nativeEvent.contentOffset.y } }} + onContentSizeChange={ + IS_ANDROID + ? e => { + const h = Math.ceil(e.nativeEvent.contentSize.height) + const clamped = Math.min(Math.max(h, minHeight), maxHeight) + setAndroidInputHeight(clamped) + } + : undefined + } // @ts-ignore web only onCompositionStart={() => { isComposing.current = true diff --git a/src/screens/Messages/components/MessageComposer.tsx b/src/screens/Messages/components/MessageComposer.tsx index e46895fb4e..8717c16a61 100644 --- a/src/screens/Messages/components/MessageComposer.tsx +++ b/src/screens/Messages/components/MessageComposer.tsx @@ -139,7 +139,7 @@ export function MessageComposer({ padding={{ paddingLeft: IS_WEB ? 30 + 8 : 16, paddingTop: 12, - paddingBottom: IS_ANDROID ? 4 : 12, + paddingBottom: 12, paddingRight: 35 + a.p_sm.padding, }} textStyle={[a.text_md, a.leading_snug]}