diff --git a/example/src/screens/usage/FlatList.tsx b/example/src/screens/usage/FlatList.tsx index d57e431..ad8fdbf 100644 --- a/example/src/screens/usage/FlatList.tsx +++ b/example/src/screens/usage/FlatList.tsx @@ -83,6 +83,7 @@ const FlatList: React.FC = () => { data={data} renderItem={renderItem} windowSize={10} + automaticallyAdjustsScrollIndicatorInsets={false} getItemLayout={(_, index) => ({ index, length: ITEM_HEIGHT, offset: index * ITEM_HEIGHT })} initialNumToRender={50} maxToRenderPerBatch={100} diff --git a/example/src/screens/usage/Inverted.tsx b/example/src/screens/usage/Inverted.tsx index c06d7b9..945be2b 100644 --- a/example/src/screens/usage/Inverted.tsx +++ b/example/src/screens/usage/Inverted.tsx @@ -3,13 +3,12 @@ import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { randParagraph, randUuid } from '@ngneat/falso'; import { BlurView } from 'expo-blur'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { StatusBar } from 'expo-status-bar'; import { Header, ScrollHeaderProps, - FlatListWithHeaders, SurfaceComponentProps, + FlashListWithHeaders, } from '@codeherence/react-native-header'; import { Avatar, BackButton } from '../../components'; import { RANDOM_IMAGE_NUM } from '../../constants'; @@ -68,20 +67,23 @@ const ChatMessage: React.FC = ({ message, type }) => { }; const Inverted: React.FC = () => { - const { bottom } = useSafeAreaInsets(); - return ( <> - } HeaderComponent={HeaderComponent} keyExtractor={(item) => item.id} - inverted + // inverted absoluteHeader containerStyle={styles.container} - contentContainerStyle={[styles.contentContainer, { paddingTop: bottom }]} + maintainVisibleContentPosition={{ + startRenderingFromBottom: true, + }} + automaticallyAdjustsScrollIndicatorInsets={false} + // eslint-disable-next-line react-native/no-inline-styles + contentContainerStyle={{ paddingHorizontal: 12 }} showsVerticalScrollIndicator indicatorStyle={'white'} /> @@ -93,6 +95,7 @@ export default Inverted; const styles = StyleSheet.create({ container: { + flex: 1, backgroundColor: 'black', }, contentContainer: { diff --git a/src/components/containers/FlashList.tsx b/src/components/containers/FlashList.tsx index 6b0c81f..ca2b55a 100644 --- a/src/components/containers/FlashList.tsx +++ b/src/components/containers/FlashList.tsx @@ -1,7 +1,7 @@ -import React, { useImperativeHandle } from 'react'; +import React, { useImperativeHandle, useMemo } from 'react'; import { StyleSheet, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import Animated, { AnimatedProps, useAnimatedRef } from 'react-native-reanimated'; +import Animated, { AnimatedProps, isSharedValue, useAnimatedRef } from 'react-native-reanimated'; import { FlashList, FlashListProps, FlashListRef } from '@shopify/flash-list'; import type { SharedScrollContainerProps } from '.'; @@ -60,6 +60,17 @@ const FlashListWithHeadersInputComp = ( const scrollRef = useAnimatedRef(); useImperativeHandle(ref, () => scrollRef.current); + const { maintainVisibleContentPosition } = rest; + const inverted = useMemo(() => { + if (maintainVisibleContentPosition === undefined) return false; + if (isSharedValue(maintainVisibleContentPosition)) return false; + + return ( + (maintainVisibleContentPosition as FlashListProps['maintainVisibleContentPosition']) + ?.startRenderingFromBottom ?? false + ); + }, [maintainVisibleContentPosition]); + const { scrollY, showNavBar, @@ -77,8 +88,9 @@ const FlashListWithHeadersInputComp = ( absoluteHeader, initialAbsoluteHeaderHeight, headerFadeInThreshold, - inverted: false, + inverted, onScrollWorklet, + isFlashList: true, }); return ( diff --git a/src/components/containers/useScrollContainerLogic.ts b/src/components/containers/useScrollContainerLogic.ts index 813b05b..2538ac8 100644 --- a/src/components/containers/useScrollContainerLogic.ts +++ b/src/components/containers/useScrollContainerLogic.ts @@ -12,6 +12,7 @@ import { } from 'react-native-reanimated'; import { useDebouncedCallback } from 'use-debounce'; import type { SharedScrollContainerProps } from './types'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; /** * The arguments for the useScrollContainerLogic hook. @@ -77,6 +78,12 @@ interface UseScrollContainerLogicArgs { * ensure that this function is a [worklet](https://docs.swmansion.com/react-native-reanimated/docs/2.x/fundamentals/worklets/). */ onScrollWorklet?: (evt: NativeScrollEvent) => void; + /** + * Whether or not the scroll container is a FlashList. This is used because FlashList actually + * implements the inverted property differently and requires a different approach to compute the + * scroll indicator insets and content container style. + */ + isFlashList?: boolean; } /** @@ -96,7 +103,9 @@ export const useScrollContainerLogic = ({ headerFadeInThreshold = 1, inverted, onScrollWorklet, + isFlashList = false, }: UseScrollContainerLogicArgs) => { + const insets = useSafeAreaInsets(); const [absoluteHeaderHeight, setAbsoluteHeaderHeight] = useState(initialAbsoluteHeaderHeight); const scrollY = useSharedValue(0); const largeHeaderHeight = useSharedValue(0); @@ -162,6 +171,19 @@ export const useScrollContainerLogic = ({ ); const scrollViewAdjustments = useMemo(() => { + if (isFlashList) { + return { + scrollIndicatorInsets: { + top: absoluteHeader && inverted ? absoluteHeaderHeight : 0, + bottom: insets.bottom, + }, + contentContainerStyle: { + paddingTop: absoluteHeader && inverted ? absoluteHeaderHeight : 0, + paddingBottom: insets.bottom, + }, + }; + } + return { scrollIndicatorInsets: { top: absoluteHeader && !inverted ? absoluteHeaderHeight : 0, @@ -172,7 +194,7 @@ export const useScrollContainerLogic = ({ paddingBottom: absoluteHeader && inverted ? absoluteHeaderHeight : 0, }, }; - }, [inverted, absoluteHeaderHeight, absoluteHeader]); + }, [inverted, absoluteHeaderHeight, absoluteHeader, isFlashList, insets]); return { scrollY,