import { Ionicons } from "@expo/vector-icons"; import { useInfiniteQuery } from "@tanstack/react-query"; import { Image } from "expo-image"; import { uniqBy } from "lodash"; import React, { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { Animated, FlatList, Pressable, View } from "react-native"; import { Text } from "@/components/common/Text"; import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation"; import { useScaledTVTypography } from "@/constants/TVTypography"; import useRouter from "@/hooks/useAppRouter"; import { type DiscoverEndpoint, Endpoints, useJellyseerr, } from "@/hooks/useJellyseerr"; import { DiscoverSliderType } from "@/utils/jellyseerr/server/constants/discover"; import { MediaStatus } from "@/utils/jellyseerr/server/constants/media"; import type DiscoverSlider from "@/utils/jellyseerr/server/entity/DiscoverSlider"; import type { MovieResult, TvResult, } from "@/utils/jellyseerr/server/models/Search"; const SCALE_PADDING = 20; interface TVDiscoverPosterProps { item: MovieResult | TvResult; isFirstItem?: boolean; } const TVDiscoverPoster: React.FC = ({ item, isFirstItem = false, }) => { const typography = useScaledTVTypography(); const router = useRouter(); const { jellyseerrApi, getTitle, getYear } = useJellyseerr(); const { focused, handleFocus, handleBlur, animatedStyle } = useTVFocusAnimation({ scaleAmount: 1.05 }); const posterUrl = item.posterPath ? jellyseerrApi?.imageProxy(item.posterPath, "w342") : null; const title = getTitle(item); const year = getYear(item); const isInLibrary = item.mediaInfo?.status === MediaStatus.AVAILABLE || item.mediaInfo?.status === MediaStatus.PARTIALLY_AVAILABLE; const handlePress = () => { router.push({ pathname: "/(auth)/(tabs)/(search)/jellyseerr/page", params: { id: String(item.id), mediaType: item.mediaType, }, }); }; return ( {posterUrl ? ( ) : ( )} {isInLibrary && ( )} {title} {year && ( {year} )} ); }; interface TVDiscoverSlideProps { slide: DiscoverSlider; isFirstSlide?: boolean; } export const TVDiscoverSlide: React.FC = ({ slide, isFirstSlide = false, }) => { const typography = useScaledTVTypography(); const { t } = useTranslation(); const { jellyseerrApi, isJellyseerrMovieOrTvResult } = useJellyseerr(); const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({ queryKey: ["jellyseerr", "discover", "tv", slide.id], queryFn: async ({ pageParam }) => { let endpoint: DiscoverEndpoint | undefined; let params: Record = { page: Number(pageParam), }; switch (slide.type) { case DiscoverSliderType.TRENDING: endpoint = Endpoints.DISCOVER_TRENDING; break; case DiscoverSliderType.POPULAR_MOVIES: case DiscoverSliderType.UPCOMING_MOVIES: endpoint = Endpoints.DISCOVER_MOVIES; if (slide.type === DiscoverSliderType.UPCOMING_MOVIES) params = { ...params, primaryReleaseDateGte: new Date().toISOString().split("T")[0], }; break; case DiscoverSliderType.POPULAR_TV: case DiscoverSliderType.UPCOMING_TV: endpoint = Endpoints.DISCOVER_TV; if (slide.type === DiscoverSliderType.UPCOMING_TV) params = { ...params, firstAirDateGte: new Date().toISOString().split("T")[0], }; break; } return endpoint ? jellyseerrApi?.discover(endpoint, params) : null; }, initialPageParam: 1, getNextPageParam: (lastPage, pages) => (lastPage?.page || pages?.findLast((p) => p?.results.length)?.page || 1) + 1, enabled: !!jellyseerrApi, staleTime: 0, }); const flatData = useMemo( () => uniqBy( data?.pages ?.filter((p) => p?.results.length) .flatMap((p) => p?.results.filter((r) => isJellyseerrMovieOrTvResult(r)), ), "id", ) as (MovieResult | TvResult)[], [data, isJellyseerrMovieOrTvResult], ); const slideTitle = t( `search.${DiscoverSliderType[slide.type].toString().toLowerCase()}`, ); if (!flatData || flatData.length === 0) return null; return ( {slideTitle} item.id.toString()} showsHorizontalScrollIndicator={false} contentContainerStyle={{ paddingHorizontal: SCALE_PADDING, paddingVertical: SCALE_PADDING, gap: 20, }} style={{ overflow: "visible" }} onEndReached={() => { if (hasNextPage) fetchNextPage(); }} onEndReachedThreshold={0.5} renderItem={({ item, index }) => ( )} /> ); };