Checkpoint android fixes
This commit is contained in:
@@ -48,7 +48,7 @@ import {
|
|||||||
} from '#/components/Autocomplete'
|
} from '#/components/Autocomplete'
|
||||||
import {useOnKeyboard} from '#/components/hooks/useOnKeyboard'
|
import {useOnKeyboard} from '#/components/hooks/useOnKeyboard'
|
||||||
import {Span, Text} from '#/components/Typography'
|
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 ────────────────────────────────────────────────────────────────────
|
* ─── Types ────────────────────────────────────────────────────────────────────
|
||||||
@@ -216,9 +216,17 @@ export function Composer({
|
|||||||
const xh = maxNumberOfLines
|
const xh = maxNumberOfLines
|
||||||
? lineHeight * maxNumberOfLines + verticalSpace
|
? lineHeight * maxNumberOfLines + verticalSpace
|
||||||
: 999
|
: 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
|
const tas = IS_WEB
|
||||||
? {height: lineHeight + verticalSpace}
|
? {height: lineHeight + verticalSpace}
|
||||||
: {minHeight: mh, maxHeight: xh}
|
: IS_ANDROID
|
||||||
|
? {height: mh}
|
||||||
|
: {minHeight: mh, maxHeight: xh}
|
||||||
|
|
||||||
if (IS_IOS) {
|
if (IS_IOS) {
|
||||||
delete ts.lineHeight
|
delete ts.lineHeight
|
||||||
@@ -227,6 +235,14 @@ export function Composer({
|
|||||||
return {textStyle: ts, textAreaStyle: tas, minHeight: mh, maxHeight: xh}
|
return {textStyle: ts, textAreaStyle: tas, minHeight: mh, maxHeight: xh}
|
||||||
}, [t, fonts, padding, rawTextStyle, initialNumberOfLines, maxNumberOfLines])
|
}, [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 ────────────────────────────
|
// ─── Height auto-resize + sift positioning ────────────────────────────
|
||||||
|
|
||||||
const updateAutocompletePosition = useCallback(() => {
|
const updateAutocompletePosition = useCallback(() => {
|
||||||
@@ -253,14 +269,26 @@ export function Composer({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
textInputRef.current?.measure((_x, _y, _w, h) => {
|
if (IS_IOS) {
|
||||||
if (h !== prevHeight.current) {
|
textInputRef.current?.measure((_x, _y, _w, h) => {
|
||||||
prevHeight.current = h
|
if (h !== prevHeight.current) {
|
||||||
updateAutocompletePosition()
|
prevHeight.current = h
|
||||||
}
|
updateAutocompletePosition()
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}, [tapper.state.text, minHeight, maxHeight, 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 ──────────────────────────────────────────────────────
|
// ─── Scroll sync ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
const previewScrollStyle = useAnimatedStyle(() => ({
|
const previewScrollStyle = useAnimatedStyle(() => ({
|
||||||
@@ -356,7 +384,7 @@ export function Composer({
|
|||||||
textAlignVertical: 'top',
|
textAlignVertical: 'top',
|
||||||
includeFontPadding: false,
|
includeFontPadding: false,
|
||||||
},
|
},
|
||||||
textAreaStyle,
|
IS_ANDROID ? {height: androidInputHeight} : textAreaStyle,
|
||||||
web({
|
web({
|
||||||
resize: 'none',
|
resize: 'none',
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
@@ -388,6 +416,15 @@ export function Composer({
|
|||||||
inputScrollSharedValue.value = e.nativeEvent.contentOffset.y
|
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
|
// @ts-ignore web only
|
||||||
onCompositionStart={() => {
|
onCompositionStart={() => {
|
||||||
isComposing.current = true
|
isComposing.current = true
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ export function MessageComposer({
|
|||||||
padding={{
|
padding={{
|
||||||
paddingLeft: IS_WEB ? 30 + 8 : 16,
|
paddingLeft: IS_WEB ? 30 + 8 : 16,
|
||||||
paddingTop: 12,
|
paddingTop: 12,
|
||||||
paddingBottom: IS_ANDROID ? 4 : 12,
|
paddingBottom: 12,
|
||||||
paddingRight: 35 + a.p_sm.padding,
|
paddingRight: 35 + a.p_sm.padding,
|
||||||
}}
|
}}
|
||||||
textStyle={[a.text_md, a.leading_snug]}
|
textStyle={[a.text_md, a.leading_snug]}
|
||||||
|
|||||||
Reference in New Issue
Block a user