import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import { type QueryFunction, type QueryKey, useInfiniteQuery, } from "@tanstack/react-query"; import { useTranslation } from "react-i18next"; import { ActivityIndicator, ScrollView, View, type ViewProps, } from "react-native"; import { Text } from "@/components/common/Text"; import MoviePoster from "@/components/posters/MoviePoster"; import ContinueWatchingPoster from "../ContinueWatchingPoster"; import { TouchableItemRouter } from "../common/TouchableItemRouter"; import { ItemCardText } from "../ItemCardText"; import SeriesPoster from "../posters/SeriesPoster"; interface Props extends ViewProps { title?: string | null; orientation?: "horizontal" | "vertical"; disabled?: boolean; queryKey: QueryKey; queryFn: QueryFunction; hideIfEmpty?: boolean; pageSize?: number; } export const InfiniteScrollingCollectionList: React.FC = ({ title, orientation = "vertical", disabled = false, queryFn, queryKey, hideIfEmpty = false, pageSize = 20, ...props }) => { const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = useInfiniteQuery({ queryKey: queryKey, queryFn: ({ pageParam = 0, ...context }) => queryFn({ ...context, queryKey, pageParam }), getNextPageParam: (lastPage, allPages) => { // If the last page has fewer items than pageSize, we've reached the end if (lastPage.length < pageSize) { return undefined; } // Otherwise, return the next start index return allPages.length * pageSize; }, initialPageParam: 0, staleTime: 0, refetchOnMount: true, refetchOnWindowFocus: true, refetchOnReconnect: true, }); const { t } = useTranslation(); // Flatten all pages into a single array const allItems = data?.pages.flat() || []; if (hideIfEmpty === true && allItems.length === 0 && !isLoading) return null; if (disabled || !title) return null; const handleScroll = (event: any) => { const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent; const paddingToBottom = 20; // Check if we're near the end of the scroll if ( layoutMeasurement.width + contentOffset.x >= contentSize.width - paddingToBottom ) { if (hasNextPage && !isFetchingNextPage) { fetchNextPage(); } } }; return ( {title} {isLoading === false && allItems.length === 0 && ( {t("home.no_items")} )} {isLoading ? ( {[1, 2, 3].map((i) => ( Nisi mollit voluptate amet. Lorem ipsum ))} ) : ( {allItems.map((item) => ( {item.Type === "Episode" && orientation === "horizontal" && ( )} {item.Type === "Episode" && orientation === "vertical" && ( )} {item.Type === "Movie" && orientation === "horizontal" && ( )} {item.Type === "Movie" && orientation === "vertical" && ( )} {item.Type === "Series" && orientation === "vertical" && ( )} {item.Type === "Series" && orientation === "horizontal" && ( )} {item.Type === "Program" && ( )} {item.Type === "BoxSet" && orientation === "vertical" && ( )} {item.Type === "BoxSet" && orientation === "horizontal" && ( )} {item.Type === "Playlist" && orientation === "vertical" && ( )} {item.Type === "Playlist" && orientation === "horizontal" && ( )} {item.Type === "Video" && orientation === "vertical" && ( )} {item.Type === "Video" && orientation === "horizontal" && ( )} ))} {/* Loading indicator for next page */} {isFetchingNextPage && ( )} )} ); };