get iOS working properly with contentInset

This commit is contained in:
Samuel Newman
2024-11-26 23:30:34 +00:00
parent ad96441f8f
commit 06d909b405
@@ -148,6 +148,12 @@ export function MessagesList({
// we will not scroll whenever new items get prepended to the top. // we will not scroll whenever new items get prepended to the top.
const onContentSizeChange = useCallback( const onContentSizeChange = useCallback(
(_: number, height: number) => { (_: number, height: number) => {
// ignore very small changes
if (Math.abs(prevContentHeight.current - height) < 0.01) {
prevContentHeight.current = height
return
}
// Because web does not have `maintainVisibleContentPosition` support, we will need to manually scroll to the // Because web does not have `maintainVisibleContentPosition` support, we will need to manually scroll to the
// previous off whenever we add new content to the previous offset whenever we add new content to the list. // previous off whenever we add new content to the previous offset whenever we add new content to the list.
if (isWeb && isAtTop.get() && hasScrolled) { if (isWeb && isAtTop.get() && hasScrolled) {
@@ -249,6 +255,7 @@ export function MessagesList({
const keyboardHeight = useSharedValue(0) const keyboardHeight = useSharedValue(0)
const keyboardIsOpening = useSharedValue(false) const keyboardIsOpening = useSharedValue(false)
const keyboardIsInteractive = useSharedValue(false)
// In some cases - like when the emoji piker opens - we don't want to animate the scroll in the list onLayout event. // In some cases - like when the emoji piker opens - we don't want to animate the scroll in the list onLayout event.
// We use this value to keep track of when we want to disable the animation. // We use this value to keep track of when we want to disable the animation.
@@ -272,21 +279,27 @@ export function MessagesList({
onMove: e => { onMove: e => {
'worklet' 'worklet'
keyboardHeight.set(e.height) keyboardHeight.set(e.height)
if (e.height > footerHeight.get()) { if (
(e.height > footerHeight.get() || isIOS) &&
!keyboardIsInteractive.get()
) {
scrollTo(flatListRef, 0, 1e7, false) scrollTo(flatListRef, 0, 1e7, false)
} }
}, },
onInteractive: e => { onInteractive: e => {
'worklet' 'worklet'
keyboardHeight.set(e.height) keyboardHeight.set(e.height)
if (e.height > footerHeight.get()) { if (!keyboardIsInteractive.get()) {
scrollTo(flatListRef, 0, 1e7, false) keyboardIsInteractive.set(true)
} }
}, },
onEnd: e => { onEnd: e => {
'worklet' 'worklet'
keyboardHeight.set(e.height) keyboardHeight.set(e.height)
if (e.height > footerHeight.get()) { if (
(e.height > footerHeight.get() || isIOS) &&
!keyboardIsInteractive.get()
) {
scrollTo(flatListRef, 0, 1e7, false) scrollTo(flatListRef, 0, 1e7, false)
} }
keyboardIsOpening.set(false) keyboardIsOpening.set(false)
@@ -295,17 +308,17 @@ export function MessagesList({
[], [],
) )
const animatedListProps = useAnimatedProps( const animatedListProps = useAnimatedProps(() => {
() => return {
({ contentInset: {
contentInset: { top: 0,
top: 0, bottom:
bottom: Math.max(keyboardHeight.get(), inputAreaHeight), Math.max(keyboardHeight.get(), footerHeight.get()) + inputAreaHeight,
left: 0, left: 0,
right: 0, right: 0,
}, },
} satisfies Partial<ListProps>), } satisfies Partial<ListProps>
) })
const animatedStickyViewStyle = useAnimatedStyle(() => ({ const animatedStickyViewStyle = useAnimatedStyle(() => ({
transform: [ transform: [
@@ -394,15 +407,24 @@ export function MessagesList({
) )
// -- List layout changes (opening emoji keyboard, etc.) // -- List layout changes (opening emoji keyboard, etc.)
const prevLayoutHeight = useRef(0)
const onListLayout = useCallback( const onListLayout = useCallback(
(e: LayoutChangeEvent) => { (e: LayoutChangeEvent) => {
const diff = e.nativeEvent.layout.height - prevLayoutHeight.current
// is there a better way to detect this? -sfn
const isInsetAdjustment = Math.abs(diff - inputAreaHeight) < 1
prevLayoutHeight.current = e.nativeEvent.layout.height
layoutHeight.set(e.nativeEvent.layout.height) layoutHeight.set(e.nativeEvent.layout.height)
if (isWeb || !keyboardIsOpening.get()) { if (isWeb || (!keyboardIsOpening.get() && !isInsetAdjustment)) {
const skipAnimation = layoutScrollWithoutAnimation.get()
flatListRef.current?.scrollToEnd({ flatListRef.current?.scrollToEnd({
animated: !layoutScrollWithoutAnimation.get(), animated: !skipAnimation,
}) })
layoutScrollWithoutAnimation.set(false) if (skipAnimation) {
layoutScrollWithoutAnimation.set(false)
}
} }
}, },
[ [
@@ -410,9 +432,16 @@ export function MessagesList({
keyboardIsOpening, keyboardIsOpening,
layoutScrollWithoutAnimation, layoutScrollWithoutAnimation,
layoutHeight, layoutHeight,
inputAreaHeight,
], ],
) )
const onInputAreaLayout = useCallback(
(evt: LayoutChangeEvent) =>
setInputAreaHeight(evt.nativeEvent.layout.height),
[],
)
const scrollToEndOnPress = useCallback(() => { const scrollToEndOnPress = useCallback(() => {
flatListRef.current?.scrollToOffset({ flatListRef.current?.scrollToOffset({
offset: prevContentHeight.current, offset: prevContentHeight.current,
@@ -434,8 +463,10 @@ export function MessagesList({
renderItem={renderItem} renderItem={renderItem}
keyExtractor={keyExtractor} keyExtractor={keyExtractor}
animatedProps={animatedListProps} animatedProps={animatedListProps}
// required - otherwise it will overwrite the value in animatedProps
automaticallyAdjustContentInsets={false}
automaticallyAdjustsScrollIndicatorInsets={false}
disableFullWindowScroll={true} disableFullWindowScroll={true}
style={{backgroundColor: 'red'}}
disableVirtualization={true} disableVirtualization={true}
// The extra two items account for the header and the footer components // The extra two items account for the header and the footer components
initialNumToRender={isNative ? 32 : 62} initialNumToRender={isNative ? 32 : 62}
@@ -461,8 +492,12 @@ export function MessagesList({
</KeyboardGestureArea> </KeyboardGestureArea>
</ScrollProvider> </ScrollProvider>
<Animated.View <Animated.View
style={animatedStickyViewStyle} style={[
onLayout={evt => setInputAreaHeight(evt.nativeEvent.layout.height)}> a.absolute,
{bottom: 0, left: 0, right: 0},
animatedStickyViewStyle,
]}
onLayout={onInputAreaLayout}>
<BlurWrapper> <BlurWrapper>
{convoState.status === ConvoStatus.Disabled ? ( {convoState.status === ConvoStatus.Disabled ? (
<ChatDisabled /> <ChatDisabled />