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 { TouchableJellyseerrRouter } from "@/components/common/JellyseerrItemRouter"; import { Text } from "@/components/common/Text"; import { Tag, Tags } from "@/components/GenreTags"; import { textShadowStyle } from "@/components/jellyseerr/discover/GenericSlideCard"; import JellyseerrMediaIcon from "@/components/jellyseerr/JellyseerrMediaIcon"; import JellyseerrStatusIcon from "@/components/jellyseerr/JellyseerrStatusIcon"; import { Colors } from "@/constants/Colors"; import { useJellyseerr } from "@/hooks/useJellyseerr"; import { useJellyseerrCanRequest } from "@/utils/_jellyseerr/useJellyseerrCanRequest"; 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 JellyseerrPoster: React.FC = ({ item, horizontal, showDownloadInfo, mediaRequest, }) => { const { jellyseerrApi, getTitle, getYear, getMediaType } = useJellyseerr(); 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( () => jellyseerrApi?.imageProxy( item?.backdropPath, "w1920_and_h800_multi_faces", ), [item, jellyseerrApi, horizontal], ); const posterSrc = useMemo( () => jellyseerrApi?.imageProxy(item?.posterPath, "w300_and_h450_face"), [item, jellyseerrApi, 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] = useJellyseerrCanRequest(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.jellyseerr.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 && ( )} )} {title || ""} {releaseYear || ""} ); }; export default JellyseerrPoster;