import { Image } from "expo-image"; import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { View, type ViewProps } from "react-native"; import Animated, { useAnimatedStyle, useSharedValue, withTiming, } from "react-native-reanimated"; import { TouchableSeerrRouter } from "@/components/common/SeerrItemRouter"; import { Text } from "@/components/common/Text"; import { Tag, Tags } from "@/components/GenreTags"; import { textShadowStyle } from "@/components/seerr/discover/GenericSlideCard"; import SeerrMediaIcon from "@/components/seerr/SeerrMediaIcon"; import SeerrStatusIcon from "@/components/seerr/SeerrStatusIcon"; import { Colors } from "@/constants/Colors"; import { useSeerr } from "@/hooks/useSeerr"; import { useSeerrCanRequest } from "@/utils/_seerr/useSeerrCanRequest"; import { MediaStatus } from "@/utils/jellyseerr/server/constants/media"; import type MediaRequest from "@/utils/jellyseerr/server/entity/MediaRequest"; import type { DownloadingItem } from "@/utils/jellyseerr/server/lib/downloadtracker"; import type { MovieDetails } from "@/utils/jellyseerr/server/models/Movie"; import { PersonCreditCast } from "@/utils/jellyseerr/server/models/Person"; import type { MovieResult, TvResult, } from "@/utils/jellyseerr/server/models/Search"; import type { TvDetails } from "@/utils/jellyseerr/server/models/Tv"; interface Props extends ViewProps { item?: MovieResult | TvResult | MovieDetails | TvDetails | PersonCreditCast; horizontal?: boolean; showDownloadInfo?: boolean; mediaRequest?: MediaRequest; } const SeerrPoster: React.FC = ({ item, horizontal, showDownloadInfo, mediaRequest, }) => { const { seerrApi, getTitle, getYear, getMediaType } = useSeerr(); const loadingOpacity = useSharedValue(1); const imageOpacity = useSharedValue(0); const { t } = useTranslation(); const imageAnimatedStyle = useAnimatedStyle(() => ({ opacity: imageOpacity.value, })); const handleImageLoad = () => { loadingOpacity.value = withTiming(0, { duration: 200 }); imageOpacity.value = withTiming(1, { duration: 300 }); }; const backdropSrc = useMemo( () => seerrApi?.imageProxy(item?.backdropPath, "w1920_and_h800_multi_faces"), [item, seerrApi, horizontal], ); const posterSrc = useMemo( () => seerrApi?.imageProxy(item?.posterPath, "w300_and_h450_face"), [item, seerrApi, horizontal], ); const title = useMemo(() => getTitle(item), [item]); const releaseYear = useMemo(() => getYear(item), [item]); const mediaType = useMemo(() => getMediaType(item), [item]); const size = useMemo(() => (horizontal ? "h-28" : "w-28"), [horizontal]); const ratio = useMemo(() => (horizontal ? "15/10" : "10/15"), [horizontal]); const [canRequest] = useSeerrCanRequest(item); const is4k = useMemo(() => mediaRequest?.is4k === true, [mediaRequest]); const downloadItems = useMemo( () => (is4k ? mediaRequest?.media.downloadStatus4k : mediaRequest?.media.downloadStatus) || [], [mediaRequest, is4k], ); const progress = useMemo(() => { const [totalSize, sizeLeft] = downloadItems.reduce( (sum: number[], next: DownloadingItem) => [ sum[0] + next.size, sum[1] + next.sizeLeft, ], [0, 0], ); return ((totalSize - sizeLeft) / totalSize) * 100; }, [downloadItems]); const requestedSeasons: string[] | undefined = useMemo(() => { const seasons = mediaRequest?.seasons?.flatMap((s) => s.seasonNumber.toString()) || []; if (seasons.length > 4) { const [first, second, third, fourth, ...rest] = seasons; return [ first, second, third, fourth, t("home.settings.plugins.seerr.plus_n_more", { n: rest.length }), ]; } return seasons; }, [mediaRequest]); const available = useMemo(() => { const status = mediaRequest?.media?.[is4k ? "status4k" : "status"]; return status === MediaStatus.AVAILABLE; }, [mediaRequest, is4k]); return ( {mediaRequest && showDownloadInfo && ( <> {!available && !Number.isNaN(progress) && ( <> {progress?.toFixed(0)}% )} {(requestedSeasons?.length ?? 0) > 0 && ( )} )} {title || ""} {releaseYear || ""} ); }; export default SeerrPoster;