forked from Ninjalama/streamyfin_mirror
feat: [Jellyseerr] Show recent requests #324
- Added recent requests slide - updated JellyseerrPoster.tsx to handle more options
This commit is contained in:
@@ -1,28 +1,42 @@
|
||||
import { TouchableJellyseerrRouter } from "@/components/common/JellyseerrItemRouter";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import {TouchableJellyseerrRouter} from "@/components/common/JellyseerrItemRouter";
|
||||
import {Text} from "@/components/common/Text";
|
||||
import JellyseerrMediaIcon from "@/components/jellyseerr/JellyseerrMediaIcon";
|
||||
import JellyseerrStatusIcon from "@/components/jellyseerr/JellyseerrStatusIcon";
|
||||
import { useJellyseerr } from "@/hooks/useJellyseerr";
|
||||
import { useJellyseerrCanRequest } from "@/utils/_jellyseerr/useJellyseerrCanRequest";
|
||||
import { MediaType } from "@/utils/jellyseerr/server/constants/media";
|
||||
import { MovieResult, TvResult } from "@/utils/jellyseerr/server/models/Search";
|
||||
import { Image } from "expo-image";
|
||||
import { useMemo } from "react";
|
||||
import { View, ViewProps } from "react-native";
|
||||
import Animated, {
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
} from "react-native-reanimated";
|
||||
import {useJellyseerr} from "@/hooks/useJellyseerr";
|
||||
import {useJellyseerrCanRequest} from "@/utils/_jellyseerr/useJellyseerrCanRequest";
|
||||
import {MovieResult, TvResult} from "@/utils/jellyseerr/server/models/Search";
|
||||
import {Image} from "expo-image";
|
||||
import {useMemo} from "react";
|
||||
import {View, ViewProps} from "react-native";
|
||||
import Animated, {useAnimatedStyle, useSharedValue, withTiming,} from "react-native-reanimated";
|
||||
import {TvDetails} from "@/utils/jellyseerr/server/models/Tv";
|
||||
import {MovieDetails} from "@/utils/jellyseerr/server/models/Movie";
|
||||
import type {DownloadingItem} from "@/utils/jellyseerr/server/lib/downloadtracker";
|
||||
import MediaRequest from "@/utils/jellyseerr/server/entity/MediaRequest";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {MediaStatus} from "@/utils/jellyseerr/server/constants/media";
|
||||
import {textShadowStyle} from "@/components/jellyseerr/discover/GenericSlideCard";
|
||||
import {Colors} from "@/constants/Colors";
|
||||
import {Tags} from "@/components/GenreTags";
|
||||
|
||||
interface Props extends ViewProps {
|
||||
item: MovieResult | TvResult;
|
||||
item: MovieResult | TvResult | MovieDetails | TvDetails;
|
||||
horizontal?: boolean;
|
||||
showDownloadInfo?: boolean;
|
||||
mediaRequest?: MediaRequest;
|
||||
}
|
||||
|
||||
const JellyseerrPoster: React.FC<Props> = ({ item, ...props }) => {
|
||||
const { jellyseerrApi } = useJellyseerr();
|
||||
const JellyseerrPoster: React.FC<Props> = ({
|
||||
item,
|
||||
horizontal,
|
||||
showDownloadInfo,
|
||||
mediaRequest,
|
||||
...props
|
||||
}) => {
|
||||
const { jellyseerrApi, getTitle, getYear, getMediaType, isJellyseerrResult } = useJellyseerr();
|
||||
const loadingOpacity = useSharedValue(1);
|
||||
const imageOpacity = useSharedValue(0);
|
||||
const {t} = useTranslation();
|
||||
|
||||
const loadingAnimatedStyle = useAnimatedStyle(() => ({
|
||||
opacity: loadingOpacity.value,
|
||||
@@ -38,27 +52,64 @@ const JellyseerrPoster: React.FC<Props> = ({ item, ...props }) => {
|
||||
};
|
||||
|
||||
const imageSrc = useMemo(
|
||||
() => jellyseerrApi?.imageProxy(item.posterPath, "w300_and_h450_face"),
|
||||
[item, jellyseerrApi]
|
||||
() => jellyseerrApi?.imageProxy(
|
||||
horizontal ? item.backdropPath : item.posterPath,
|
||||
horizontal ? "w1920_and_h800_multi_faces" : "w300_and_h450_face"
|
||||
),
|
||||
[item, jellyseerrApi, horizontal]
|
||||
);
|
||||
|
||||
const title = useMemo(
|
||||
() => (item.mediaType === MediaType.MOVIE ? item.title : item.name),
|
||||
[item]
|
||||
);
|
||||
const title = useMemo(() => getTitle(item), [item]);
|
||||
const releaseYear = useMemo(() => getYear(item), [item]);
|
||||
const mediaType = useMemo(() => getMediaType(item), [item]);
|
||||
|
||||
const releaseYear = useMemo(
|
||||
() =>
|
||||
new Date(
|
||||
item.mediaType === MediaType.MOVIE
|
||||
? item.releaseDate
|
||||
: item.firstAirDate
|
||||
).getFullYear(),
|
||||
[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 (
|
||||
<TouchableJellyseerrRouter
|
||||
result={item}
|
||||
@@ -66,9 +117,10 @@ const JellyseerrPoster: React.FC<Props> = ({ item, ...props }) => {
|
||||
releaseYear={releaseYear}
|
||||
canRequest={canRequest}
|
||||
posterSrc={imageSrc!!}
|
||||
mediaType={mediaType}
|
||||
>
|
||||
<View className="flex flex-col w-28 mr-2">
|
||||
<View className="relative rounded-lg overflow-hidden border border-neutral-900 w-28 aspect-[10/15]">
|
||||
<View className={`flex flex-col mr-2 h-auto`}>
|
||||
<View className={`relative rounded-lg overflow-hidden border border-neutral-900 ${size} aspect-[${ratio}]`}>
|
||||
<Animated.View style={imageAnimatedStyle}>
|
||||
<Image
|
||||
key={item.id}
|
||||
@@ -77,26 +129,65 @@ const JellyseerrPoster: React.FC<Props> = ({ item, ...props }) => {
|
||||
cachePolicy={"memory-disk"}
|
||||
contentFit="cover"
|
||||
style={{
|
||||
aspectRatio: "10/15",
|
||||
width: "100%",
|
||||
aspectRatio: ratio,
|
||||
[horizontal ? 'height' : 'width']: "100%"
|
||||
}}
|
||||
onLoad={handleImageLoad}
|
||||
/>
|
||||
</Animated.View>
|
||||
{mediaRequest && showDownloadInfo && (
|
||||
<>
|
||||
<View className={`absolute w-full h-full bg-black ${!available ? 'opacity-70' : 'opacity-0'}`} />
|
||||
{!available && !Number.isNaN(progress) && (
|
||||
<>
|
||||
<View
|
||||
className="absolute left-0 h-full opacity-40"
|
||||
style={{
|
||||
width: `${progress || 0}%`,
|
||||
backgroundColor: Colors.primaryRGB,
|
||||
}}
|
||||
/>
|
||||
<View className="absolute w-full h-full justify-center items-center">
|
||||
<Text
|
||||
className="font-bold"
|
||||
style={textShadowStyle.shadow}
|
||||
>
|
||||
{progress?.toFixed(0)}%
|
||||
</Text>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
<Text
|
||||
className="absolute right-1 top-1 text-right font-bold"
|
||||
style={textShadowStyle.shadow}
|
||||
>
|
||||
{mediaRequest?.requestedBy.displayName}
|
||||
</Text>
|
||||
{requestedSeasons.length > 0 && (
|
||||
<Tags
|
||||
className="absolute bottom-1 left-0.5 w-32"
|
||||
tagProps={{
|
||||
className: "bg-black rounded-full px-1"
|
||||
}}
|
||||
tags={requestedSeasons}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<JellyseerrStatusIcon
|
||||
className="absolute bottom-1 right-1"
|
||||
showRequestIcon={canRequest}
|
||||
mediaStatus={item?.mediaInfo?.status}
|
||||
mediaStatus={mediaRequest?.media?.status || item?.mediaInfo?.status}
|
||||
/>
|
||||
<JellyseerrMediaIcon
|
||||
className="absolute top-1 left-1"
|
||||
mediaType={item?.mediaType}
|
||||
mediaType={mediaType}
|
||||
/>
|
||||
</View>
|
||||
<View className="mt-2 flex flex-col">
|
||||
<Text numberOfLines={2}>{title}</Text>
|
||||
<Text className="text-xs opacity-50 align-bottom">{releaseYear}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View className={`mt-2 flex flex-col ${horizontal ? 'w-44' : 'w-28'}`}>
|
||||
<Text numberOfLines={2}>{title}</Text>
|
||||
<Text className="text-xs opacity-50 align-bottom">{releaseYear}</Text>
|
||||
</View>
|
||||
</TouchableJellyseerrRouter>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user