import { noop } from 'lodash' import { RefObject, useCallback, useRef } from 'react' import { FlatList, LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, Platform, } from 'react-native' const REST_OFFSET = 1 /** * iOS starts a sheet's interactive dismissal once the content scroll view sits at its * top content edge — for an inverted list, wherever the newest message rests. Resting * the list 1pt in from that edge keeps a drag at the bottom of a conversation scrolling * history instead of swiping the modal away. */ export const useRestAwayFromDismissEdge = ({ listRef, enabled = Platform.OS === 'ios', }: { listRef: RefObject enabled?: boolean }) => { const layoutHeight = useRef(0) const contentHeight = useRef(0) const didSetInitialOffset = useRef(false) const canRestAwayFromEdge = useCallback( () => layoutHeight.current > 0 && contentHeight.current - layoutHeight.current >= REST_OFFSET, [] ) const restAwayFromDismissEdge = useCallback(() => { if (!canRestAwayFromEdge()) return listRef.current?.scrollToOffset({ animated: false, offset: REST_OFFSET }) }, [canRestAwayFromEdge, listRef]) const applyInitialOffsetOnceScrollable = useCallback(() => { if (didSetInitialOffset.current || !canRestAwayFromEdge()) return didSetInitialOffset.current = true restAwayFromDismissEdge() }, [canRestAwayFromEdge, restAwayFromDismissEdge]) const handleLayout = useCallback( (event: LayoutChangeEvent) => { layoutHeight.current = event.nativeEvent.layout.height applyInitialOffsetOnceScrollable() }, [applyInitialOffsetOnceScrollable] ) const handleContentSizeChange = useCallback( (_width: number, height: number) => { contentHeight.current = height applyInitialOffsetOnceScrollable() }, [applyInitialOffsetOnceScrollable] ) // A negative offset means the bounce is still animating; let it settle first. const handleScrollSettled = useCallback( (event: NativeSyntheticEvent) => { const offsetY = event.nativeEvent.contentOffset.y if (offsetY >= 0 && offsetY < REST_OFFSET) { restAwayFromDismissEdge() } }, [restAwayFromDismissEdge] ) if (!enabled) { return { restOffset: 0, onLayout: noop, onContentSizeChange: noop, onScrollEndDrag: noop, onMomentumScrollEnd: noop, } } return { restOffset: REST_OFFSET, onLayout: handleLayout, onContentSizeChange: handleContentSizeChange, onScrollEndDrag: handleScrollSettled, onMomentumScrollEnd: handleScrollSettled, } }