import ComponentSeparator from '@components/Separator' import TimelineConversation from '@components/Timelines/Timeline/Conversation' import TimelineDefault from '@components/Timelines/Timeline/Default' import TimelineEmpty from '@components/Timelines/Timeline/Empty' import TimelineEnd from '@root/components/Timelines/Timeline/End' import TimelineHeader from '@components/Timelines/Timeline/Header' import TimelineNotifications from '@components/Timelines/Timeline/Notifications' import { useScrollToTop } from '@react-navigation/native' import { localUpdateNotification } from '@utils/slices/instancesSlice' import { StyleConstants } from '@utils/styles/constants' import React, { useCallback, useEffect, useMemo, useRef } from 'react' import { RefreshControl, StyleSheet } from 'react-native' import { FlatList } from 'react-native-gesture-handler' import { useDispatch } from 'react-redux' import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline' import { findIndex } from 'lodash' import CustomRefreshControl from '@components/CustomRefreshControl' import { InfiniteData, useQueryClient } from 'react-query' export interface Props { page: App.Pages hashtag?: Mastodon.Tag['name'] list?: Mastodon.List['id'] toot?: Mastodon.Status['id'] account?: Mastodon.Account['id'] disableRefresh?: boolean disableInfinity?: boolean } const Timeline: React.FC = ({ page, hashtag, list, toot, account, disableRefresh = false, disableInfinity = false }) => { const queryKeyParams = { page, ...(hashtag && { hashtag }), ...(list && { list }), ...(toot && { toot }), ...(account && { account }) } const queryKey: QueryKeyTimeline = ['Timeline', queryKeyParams] const { status, data, refetch, isSuccess, isFetching, hasPreviousPage, fetchPreviousPage, isFetchingPreviousPage, hasNextPage, fetchNextPage, isFetchingNextPage } = useTimelineQuery({ ...queryKeyParams, options: { getPreviousPageParam: firstPage => { return firstPage.length ? { direction: 'prev', id: firstPage[0].last_status ? firstPage[0].last_status.id : firstPage[0].id } : undefined }, getNextPageParam: lastPage => { return lastPage.length ? { direction: 'next', id: lastPage[lastPage.length - 1].last_status ? lastPage[lastPage.length - 1].last_status.id : lastPage[lastPage.length - 1].id } : undefined } } }) const flattenData = data?.pages ? data.pages.flatMap(d => [...d]) : [] // Clear unread notification badge const dispatch = useDispatch() useEffect(() => { if (page === 'Notifications' && flattenData.length) { dispatch( localUpdateNotification({ unread: false, latestTime: (flattenData[0] as Mastodon.Notification).created_at }) ) } }, [flattenData]) const flRef = useRef>(null) useEffect(() => { if (toot && isSuccess) { const pointer = findIndex(flattenData, ['id', toot]) setTimeout(() => { flRef.current?.scrollToIndex({ index: pointer, viewOffset: 100 }) }, 500) } }, [isSuccess, flattenData]) const keyExtractor = useCallback(({ id }) => id, []) const renderItem = useCallback(({ item }) => { switch (page) { case 'Conversations': return case 'Notifications': return default: return ( ) } }, []) const ItemSeparatorComponent = useCallback( ({ leadingItem }) => ( ), [] ) const flItemEmptyComponent = useMemo( () => , [status] ) const onEndReached = useCallback( () => !disableInfinity && !isFetchingNextPage && fetchNextPage(), [isFetchingNextPage] ) const ListHeaderComponent = useCallback(() => , []) const ListFooterComponent = useCallback( () => , [hasNextPage] ) const queryClient = useQueryClient() const refreshCount = useRef(0) const refreshControl = useMemo( () => ( { if (refreshCount.current < 2) { await fetchPreviousPage() refreshCount.current++ } else { queryClient.setQueryData | undefined>( queryKey, data => { if (data) { return { pages: data.pages.slice(1), pageParams: data.pageParams.slice(1) } } } ) await refetch() refreshCount.current = 0 } }} /> ), [isFetchingPreviousPage, isFetching] ) const onScrollToIndexFailed = useCallback(error => { const offset = error.averageItemLength * error.index flRef.current?.scrollToOffset({ offset }) setTimeout( () => flRef.current?.scrollToIndex({ index: error.index, viewOffset: 100 }), 350 ) }, []) useScrollToTop(flRef) return ( ) } const styles = StyleSheet.create({ flatList: { minHeight: '100%' } }) export default Timeline