import ComponentSeparator from '@components/Separator' import TimelineDefault from '@components/Timeline/Default' import { useScrollToTop } from '@react-navigation/native' import { UseInfiniteQueryOptions } from '@tanstack/react-query' import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline' import { flattenPages } from '@utils/queryHooks/utils' import { getAccountStorage, setAccountStorage, useGlobalStorageListener } from '@utils/storage/actions' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' import React, { RefObject, useRef } from 'react' import { FlatList, FlatListProps, Platform, RefreshControl } from 'react-native' import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated' import TimelineEmpty from './Empty' import TimelineFooter from './Footer' import TimelineRefresh, { SEPARATION_Y_1, SEPARATION_Y_2 } from './Refresh' const AnimatedFlatList = Animated.createAnimatedComponent(FlatList) export interface Props { flRef?: RefObject> queryKey: QueryKeyTimeline queryOptions?: Omit< UseInfiniteQueryOptions, 'notifyOnChangeProps' | 'getNextPageParam' | 'getPreviousPageParam' | 'select' | 'onSuccess' > disableRefresh?: boolean disableInfinity?: boolean readMarker?: 'read_marker_following' customProps?: Partial> } const Timeline: React.FC = ({ flRef: customFLRef, queryKey, queryOptions, disableRefresh = false, disableInfinity = false, readMarker = undefined, customProps }) => { const { colors } = useTheme() const { data, refetch, isFetching, isLoading, fetchNextPage, isFetchingNextPage } = useTimelineQuery({ ...queryKey[1], options: { ...queryOptions, notifyOnChangeProps: Platform.select({ ios: ['dataUpdatedAt', 'isFetching'], android: ['dataUpdatedAt', 'isFetching', 'isLoading'] }), getNextPageParam: lastPage => lastPage?.links?.next && { ...(lastPage.links.next.isOffset ? { offset: lastPage.links.next.id } : { max_id: lastPage.links.next.id }) } } }) const flRef = useRef(null) const fetchingActive = useRef(false) const scrollY = useSharedValue(0) const fetchingType = useSharedValue<0 | 1 | 2>(0) const onScroll = useAnimatedScrollHandler( { onScroll: ({ contentOffset: { y } }) => { scrollY.value = y }, onEndDrag: ({ contentOffset: { y } }) => { if (!disableRefresh && !isFetching) { if (y <= SEPARATION_Y_2) { fetchingType.value = 2 } else if (y <= SEPARATION_Y_1) { fetchingType.value = 1 } } } }, [isFetching] ) const viewabilityConfigCallbackPairs = useRef< Pick, 'viewabilityConfigCallbackPairs'>['viewabilityConfigCallbackPairs'] >( readMarker ? [ { viewabilityConfig: { minimumViewTime: 300, itemVisiblePercentThreshold: 80, waitForInteraction: true }, onViewableItemsChanged: ({ viewableItems }) => { const marker = readMarker ? getAccountStorage.string(readMarker) : undefined const firstItemId = viewableItems.filter(item => item.isViewable)[0]?.item.id if (!fetchingActive.current && firstItemId && firstItemId > (marker || '0')) { setAccountStorage([{ key: readMarker, value: firstItemId }]) } else { // setAccountStorage([{ key: readMarker, value: '109519141378761752' }]) } } } ] : undefined ) const androidRefreshControl = Platform.select({ android: { refreshControl: ( refetch()} /> ) } }) useScrollToTop(flRef) useGlobalStorageListener('account.active', () => flRef.current?.scrollToOffset({ offset: 0, animated: false }) ) return ( <> })} initialNumToRender={6} maxToRenderPerBatch={3} onEndReached={() => !disableInfinity && !isFetchingNextPage && fetchNextPage()} onEndReachedThreshold={0.75} ListFooterComponent={ } ListEmptyComponent={} ItemSeparatorComponent={({ leadingItem }) => queryKey[1].page === 'Toot' && queryKey[1].toot === leadingItem.id ? ( ) : ( ) } viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs.current} {...(!isLoading && { maintainVisibleContentPosition: { minIndexForVisible: 0 } })} {...androidRefreshControl} {...customProps} /> ) } export default Timeline