mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-13 09:20:23 +01:00
@@ -0,0 +1,206 @@
|
|||||||
|
import {router, useLocalSearchParams, useNavigation, useSegments} from "expo-router";
|
||||||
|
import React, {useCallback, useEffect, useMemo, useRef, useState} from "react";
|
||||||
|
import {TouchableOpacity, View} from "react-native";
|
||||||
|
import {useQuery} from "@tanstack/react-query";
|
||||||
|
import {useJellyseerr} from "@/hooks/useJellyseerr";
|
||||||
|
import {useSafeAreaInsets} from "react-native-safe-area-context";
|
||||||
|
import {ParallaxScrollView} from "@/components/ParallaxPage";
|
||||||
|
import {Text} from "@/components/common/Text";
|
||||||
|
import {Animated} from "react-native";
|
||||||
|
import {Image} from "expo-image";
|
||||||
|
import {OverviewText} from "@/components/OverviewText";
|
||||||
|
import {orderBy} from "lodash";
|
||||||
|
import {FlashList} from "@shopify/flash-list";
|
||||||
|
import {PersonCreditCast} from "@/utils/jellyseerr/server/models/Person";
|
||||||
|
import Poster from "@/components/posters/Poster";
|
||||||
|
import JellyseerrMediaIcon from "@/components/jellyseerr/JellyseerrMediaIcon";
|
||||||
|
|
||||||
|
const ANIMATION_ENTER = 250
|
||||||
|
const ANIMATION_EXIT = 250
|
||||||
|
const BACKDROP_DURATION = 5000
|
||||||
|
|
||||||
|
export default function page() {
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
|
const local = useLocalSearchParams();
|
||||||
|
const segments = useSegments();
|
||||||
|
const {jellyseerrApi, jellyseerrUser} = useJellyseerr();
|
||||||
|
|
||||||
|
const { personId } = local as { personId: string; };
|
||||||
|
const from = segments[2];
|
||||||
|
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
|
const fadeAnim = useRef(new Animated.Value(0)).current;
|
||||||
|
|
||||||
|
const {data, isLoading, isFetching} = useQuery({
|
||||||
|
queryKey: ['jellyseerr', 'person', personId],
|
||||||
|
queryFn: async () => ({
|
||||||
|
details: await jellyseerrApi?.personDetails(personId),
|
||||||
|
combinedCredits: await jellyseerrApi?.personCombinedCredits(personId)
|
||||||
|
}),
|
||||||
|
enabled: !!jellyseerrApi && !!personId
|
||||||
|
});
|
||||||
|
|
||||||
|
const locale = useMemo(() => {
|
||||||
|
return jellyseerrUser?.settings?.locale || 'en'
|
||||||
|
}, [jellyseerrUser]);
|
||||||
|
|
||||||
|
const region = useMemo(
|
||||||
|
() => jellyseerrUser?.settings?.region || 'US',
|
||||||
|
[jellyseerrUser]
|
||||||
|
);
|
||||||
|
|
||||||
|
const castedRoles: PersonCreditCast[] = useMemo(
|
||||||
|
() => orderBy(data?.combinedCredits?.cast, ['voteCount', 'voteAverage'], 'desc'),
|
||||||
|
[data?.combinedCredits]
|
||||||
|
);
|
||||||
|
|
||||||
|
const backdrops = useMemo(
|
||||||
|
() => castedRoles.map(c => c.backdropPath),
|
||||||
|
[data?.combinedCredits]
|
||||||
|
)
|
||||||
|
|
||||||
|
const enterAnimation = useCallback(() =>
|
||||||
|
Animated.timing(fadeAnim, {
|
||||||
|
toValue: 1,
|
||||||
|
duration: ANIMATION_ENTER,
|
||||||
|
useNativeDriver: true,
|
||||||
|
}), [fadeAnim]);
|
||||||
|
|
||||||
|
const exitAnimation = useCallback(() =>
|
||||||
|
Animated.timing(fadeAnim, {
|
||||||
|
toValue: 0,
|
||||||
|
duration: ANIMATION_EXIT,
|
||||||
|
useNativeDriver: true,
|
||||||
|
}), [fadeAnim]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (backdrops?.length) {
|
||||||
|
enterAnimation().start()
|
||||||
|
const intervalId = setInterval(
|
||||||
|
() => {
|
||||||
|
exitAnimation().start(end => {
|
||||||
|
if (end.finished)
|
||||||
|
setCurrentIndex((prevIndex) => (prevIndex + 1) % backdrops?.length)
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
BACKDROP_DURATION
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
}, [backdrops, enterAnimation, exitAnimation, setCurrentIndex, currentIndex]);
|
||||||
|
|
||||||
|
const viewDetails = (credit: PersonCreditCast) => {
|
||||||
|
router.push({
|
||||||
|
//@ts-ignore
|
||||||
|
pathname: `/(auth)/(tabs)/${from}/jellyseerr/page`,
|
||||||
|
//@ts-ignore
|
||||||
|
params: {
|
||||||
|
...credit,
|
||||||
|
mediaTitle: credit.title,
|
||||||
|
releaseYear: new Date(credit.releaseDate).getFullYear(),
|
||||||
|
canRequest: "false",
|
||||||
|
posterSrc: jellyseerrApi?.imageProxy(credit.posterPath, 'w300_and_h450_face')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
className="flex-1 relative"
|
||||||
|
style={{
|
||||||
|
paddingLeft: insets.left,
|
||||||
|
paddingRight: insets.right,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ParallaxScrollView
|
||||||
|
className="flex-1 opacity-100"
|
||||||
|
headerHeight={300}
|
||||||
|
headerImage={
|
||||||
|
<Animated.Image
|
||||||
|
source={{
|
||||||
|
uri: jellyseerrApi?.imageProxy(backdrops?.[currentIndex], 'w1920_and_h800_multi_faces'),
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
opacity: fadeAnim
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
logo={
|
||||||
|
<Image
|
||||||
|
key={data?.details?.id}
|
||||||
|
id={data?.details?.id.toString()}
|
||||||
|
className="rounded-full bottom-1"
|
||||||
|
source={{uri: jellyseerrApi?.imageProxy(data?.details?.profilePath, 'w600_and_h600_bestv2'),}}
|
||||||
|
cachePolicy={"memory-disk"}
|
||||||
|
contentFit="cover"
|
||||||
|
style={{
|
||||||
|
width: 125,
|
||||||
|
height: 125,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<View className="flex flex-col space-y-4 px-4">
|
||||||
|
<View className="flex flex-row justify-between w-full">
|
||||||
|
<View className="flex flex-col w-full">
|
||||||
|
<Text className="font-bold text-2xl mb-1">
|
||||||
|
{data?.details?.name}
|
||||||
|
</Text>
|
||||||
|
<Text className="opacity-50">
|
||||||
|
Born {new Date(data?.details?.birthday!!).toLocaleDateString(`${locale}-${region}`, {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
})} | {data?.details?.placeOfBirth}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<OverviewText text={data?.details?.biography} className="mt-4"/>
|
||||||
|
|
||||||
|
<View>
|
||||||
|
<FlashList
|
||||||
|
data={castedRoles}
|
||||||
|
ListEmptyComponent={
|
||||||
|
<View className="flex flex-col items-center justify-center h-full">
|
||||||
|
<Text className="font-bold text-xl text-neutral-500">No results</Text>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
contentInsetAdjustmentBehavior="automatic"
|
||||||
|
ListHeaderComponent={
|
||||||
|
<Text className="text-lg font-bold my-2">Appearances</Text>
|
||||||
|
}
|
||||||
|
renderItem={({item}) =>
|
||||||
|
<TouchableOpacity
|
||||||
|
className="w-full flex flex-col"
|
||||||
|
onPress={() => viewDetails(item)}
|
||||||
|
>
|
||||||
|
<Poster
|
||||||
|
id={item.id.toString()}
|
||||||
|
url={jellyseerrApi?.imageProxy(item.posterPath)}
|
||||||
|
/>
|
||||||
|
<JellyseerrMediaIcon
|
||||||
|
className="absolute top-1 left-1"
|
||||||
|
mediaType={(item.mediaType as "movie" | "tv")}
|
||||||
|
/>
|
||||||
|
{/*<Text numberOfLines={1}>{item.title}</Text>*/}
|
||||||
|
<Text className="text-xs opacity-50 align-bottom" numberOfLines={1}>as {item.character}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
}
|
||||||
|
keyExtractor={(item) => item.id.toString()}
|
||||||
|
estimatedItemSize={255}
|
||||||
|
numColumns={3}
|
||||||
|
contentContainerStyle={{paddingBottom: 24}}
|
||||||
|
ItemSeparatorComponent={() => (
|
||||||
|
<View className="h-2 w-2"/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</ParallaxScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ import { JellyserrRatings } from "@/components/Ratings";
|
|||||||
import MediaRequest from "@/utils/jellyseerr/server/entity/MediaRequest";
|
import MediaRequest from "@/utils/jellyseerr/server/entity/MediaRequest";
|
||||||
import DetailFacts from "@/components/jellyseerr/DetailFacts";
|
import DetailFacts from "@/components/jellyseerr/DetailFacts";
|
||||||
import {ItemActions} from "@/components/series/SeriesActions";
|
import {ItemActions} from "@/components/series/SeriesActions";
|
||||||
|
import Cast from "@/components/jellyseerr/Cast";
|
||||||
|
|
||||||
const Page: React.FC = () => {
|
const Page: React.FC = () => {
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
@@ -156,7 +157,7 @@ const Page: React.FC = () => {
|
|||||||
height: "100%",
|
height: "100%",
|
||||||
}}
|
}}
|
||||||
source={{
|
source={{
|
||||||
uri: `https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${result.backdropPath}`,
|
uri: jellyseerrApi?.imageProxy(result.backdropPath, 'w1920_and_h800_multi_faces'),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -240,6 +241,10 @@ const Page: React.FC = () => {
|
|||||||
className="p-2 border border-neutral-800 bg-neutral-900 rounded-xl"
|
className="p-2 border border-neutral-800 bg-neutral-900 rounded-xl"
|
||||||
details={details}
|
details={details}
|
||||||
/>
|
/>
|
||||||
|
<Cast
|
||||||
|
className="px-4"
|
||||||
|
details={details}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</ParallaxScrollView>
|
</ParallaxScrollView>
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export default function SearchLayout() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen name="jellyseerr/page" options={commonScreenOptions} />
|
<Stack.Screen name="jellyseerr/page" options={commonScreenOptions} />
|
||||||
|
<Stack.Screen name="jellyseerr/[personId]" options={commonScreenOptions} />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,12 +31,13 @@ import { Platform, ScrollView, TouchableOpacity, View } from "react-native";
|
|||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { useDebounce } from "use-debounce";
|
import { useDebounce } from "use-debounce";
|
||||||
import { useJellyseerr } from "@/hooks/useJellyseerr";
|
import { useJellyseerr } from "@/hooks/useJellyseerr";
|
||||||
import { MovieResult, TvResult } from "@/utils/jellyseerr/server/models/Search";
|
import {MovieResult, PersonResult, TvResult} from "@/utils/jellyseerr/server/models/Search";
|
||||||
import { MediaType } from "@/utils/jellyseerr/server/constants/media";
|
import { MediaType } from "@/utils/jellyseerr/server/constants/media";
|
||||||
import JellyseerrPoster from "@/components/posters/JellyseerrPoster";
|
import JellyseerrPoster from "@/components/posters/JellyseerrPoster";
|
||||||
import { Tag } from "@/components/GenreTags";
|
import { Tag } from "@/components/GenreTags";
|
||||||
import DiscoverSlide from "@/components/jellyseerr/DiscoverSlide";
|
import DiscoverSlide from "@/components/jellyseerr/DiscoverSlide";
|
||||||
import { sortBy } from "lodash";
|
import { sortBy } from "lodash";
|
||||||
|
import PersonPoster from "@/components/jellyseerr/PersonPoster";
|
||||||
|
|
||||||
type SearchType = "Library" | "Discover";
|
type SearchType = "Library" | "Discover";
|
||||||
|
|
||||||
@@ -191,6 +192,14 @@ export default function search() {
|
|||||||
[jellyseerrResults]
|
[jellyseerrResults]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const jellyseerrPersonResults: PersonResult[] | undefined = useMemo(
|
||||||
|
() =>
|
||||||
|
jellyseerrResults?.filter(
|
||||||
|
(r) => r.mediaType === "person"
|
||||||
|
) as PersonResult[],
|
||||||
|
[jellyseerrResults]
|
||||||
|
);
|
||||||
|
|
||||||
const { data: series, isFetching: l2 } = useQuery({
|
const { data: series, isFetching: l2 } = useQuery({
|
||||||
queryKey: ["search", "series", debouncedSearch],
|
queryKey: ["search", "series", debouncedSearch],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
@@ -486,6 +495,19 @@ export default function search() {
|
|||||||
<JellyseerrPoster item={item} key={item.id} />
|
<JellyseerrPoster item={item} key={item.id} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<SearchItemWrapper
|
||||||
|
header="Actors"
|
||||||
|
items={jellyseerrPersonResults}
|
||||||
|
renderItem={(item: PersonResult) => (
|
||||||
|
<PersonPoster
|
||||||
|
className="mr-2"
|
||||||
|
key={item.id}
|
||||||
|
id={item.id.toString()}
|
||||||
|
name={item.name}
|
||||||
|
posterPath={item.profilePath}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
34
components/jellyseerr/Cast.tsx
Normal file
34
components/jellyseerr/Cast.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import {View, ViewProps} from "react-native";
|
||||||
|
import {MovieDetails} from "@/utils/jellyseerr/server/models/Movie";
|
||||||
|
import {TvDetails} from "@/utils/jellyseerr/server/models/Tv";
|
||||||
|
import React from "react";
|
||||||
|
import {FlashList} from "@shopify/flash-list";
|
||||||
|
import {Text} from "@/components/common/Text";
|
||||||
|
import PersonPoster from "@/components/jellyseerr/PersonPoster";
|
||||||
|
|
||||||
|
const CastSlide: React.FC<{ details?: MovieDetails | TvDetails } & ViewProps> = ({ details, ...props }) => {
|
||||||
|
return (
|
||||||
|
details?.credits?.cast?.length && details?.credits?.cast?.length > 0 &&
|
||||||
|
<View {...props}>
|
||||||
|
<Text className="text-lg font-bold mb-2">Cast</Text>
|
||||||
|
<FlashList
|
||||||
|
horizontal
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
data={details?.credits.cast}
|
||||||
|
ItemSeparatorComponent={() => <View className="w-2"/>}
|
||||||
|
estimatedItemSize={15}
|
||||||
|
keyExtractor={item => item?.id?.toString()}
|
||||||
|
renderItem={({item}) =>
|
||||||
|
<PersonPoster
|
||||||
|
id={item.id.toString()}
|
||||||
|
posterPath={item.profilePath}
|
||||||
|
name={item.name}
|
||||||
|
subName={item.character}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CastSlide;
|
||||||
42
components/jellyseerr/PersonPoster.tsx
Normal file
42
components/jellyseerr/PersonPoster.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import {TouchableOpacity, View, ViewProps} from "react-native";
|
||||||
|
import React from "react";
|
||||||
|
import {Text} from "@/components/common/Text";
|
||||||
|
import Poster from "@/components/posters/Poster";
|
||||||
|
import {useRouter, useSegments} from "expo-router";
|
||||||
|
import {useJellyseerr} from "@/hooks/useJellyseerr";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: string
|
||||||
|
posterPath?: string
|
||||||
|
name: string
|
||||||
|
subName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const PersonPoster: React.FC<Props & ViewProps> = ({
|
||||||
|
id,
|
||||||
|
posterPath,
|
||||||
|
name,
|
||||||
|
subName,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const {jellyseerrApi} = useJellyseerr();
|
||||||
|
const router = useRouter();
|
||||||
|
const segments = useSegments();
|
||||||
|
const from = segments[2];
|
||||||
|
|
||||||
|
if (from === "(home)" || from === "(search)" || from === "(libraries)")
|
||||||
|
return (
|
||||||
|
<TouchableOpacity onPress={() => router.push(`/(auth)/(tabs)/${from}/jellyseerr/${id}`)}>
|
||||||
|
<View className="flex flex-col w-28" {...props}>
|
||||||
|
<Poster
|
||||||
|
id={id}
|
||||||
|
url={jellyseerrApi?.imageProxy(posterPath, 'w600_and_h900_bestv2')}
|
||||||
|
/>
|
||||||
|
<Text className="mt-2">{name}</Text>
|
||||||
|
{subName && <Text className="text-xs opacity-50">{subName}</Text>}
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PersonPoster;
|
||||||
@@ -20,10 +20,8 @@ const JellyseerrPoster: React.FC<Props> = ({
|
|||||||
const {jellyseerrUser, jellyseerrApi} = useJellyseerr();
|
const {jellyseerrUser, jellyseerrApi} = useJellyseerr();
|
||||||
// const imageSource =
|
// const imageSource =
|
||||||
|
|
||||||
const imageSrc = useMemo(() =>
|
const imageSrc = useMemo(
|
||||||
item.posterPath ?
|
() => jellyseerrApi?.imageProxy(item.posterPath, 'w300_and_h450_face'),
|
||||||
`https://image.tmdb.org/t/p/w300_and_h450_face${item.posterPath}`
|
|
||||||
: jellyseerrApi?.axios?.defaults.baseURL + `/images/overseerr_poster_not_found_logo_top.png`,
|
|
||||||
[item, jellyseerrApi]
|
[item, jellyseerrApi]
|
||||||
)
|
)
|
||||||
const title = useMemo(() => item.mediaType === MediaType.MOVIE ? item.title : item.name, [item])
|
const title = useMemo(() => item.mediaType === MediaType.MOVIE ? item.title : item.name, [item])
|
||||||
@@ -57,7 +55,7 @@ const JellyseerrPoster: React.FC<Props> = ({
|
|||||||
mediaTitle={title}
|
mediaTitle={title}
|
||||||
releaseYear={releaseYear}
|
releaseYear={releaseYear}
|
||||||
canRequest={canRequest}
|
canRequest={canRequest}
|
||||||
posterSrc={imageSrc}
|
posterSrc={imageSrc!!}
|
||||||
>
|
>
|
||||||
<View className="flex flex-col w-28 mr-2">
|
<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="relative rounded-lg overflow-hidden border border-neutral-900 w-28 aspect-[10/15]">
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
import {
|
|
||||||
BaseItemDto,
|
|
||||||
BaseItemPerson,
|
|
||||||
} from "@jellyfin/sdk/lib/generated-client/models";
|
|
||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
|
|
||||||
type PosterProps = {
|
type PosterProps = {
|
||||||
item?: BaseItemDto | BaseItemPerson | null;
|
id?: string | null;
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
showProgress?: boolean;
|
showProgress?: boolean;
|
||||||
blurhash?: string | null;
|
blurhash?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Poster: React.FC<PosterProps> = ({ item, url, blurhash }) => {
|
const Poster: React.FC<PosterProps> = ({ id, url, blurhash }) => {
|
||||||
if (!item)
|
if (!id && !url)
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
className="border border-neutral-900"
|
className="border border-neutral-900"
|
||||||
@@ -33,8 +29,8 @@ const Poster: React.FC<PosterProps> = ({ item, url, blurhash }) => {
|
|||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
key={item.Id}
|
key={id}
|
||||||
id={item.Id}
|
id={id!!}
|
||||||
source={
|
source={
|
||||||
url
|
url
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export const CastAndCrew: React.FC<Props> = ({ item, loading, ...props }) => {
|
|||||||
}}
|
}}
|
||||||
className="flex flex-col w-28"
|
className="flex flex-col w-28"
|
||||||
>
|
>
|
||||||
<Poster item={i} url={getPrimaryImageUrl({ api, item: i })} />
|
<Poster id={i.id} url={getPrimaryImageUrl({ api, item: i })} />
|
||||||
<Text className="mt-2">{i.Name}</Text>
|
<Text className="mt-2">{i.Name}</Text>
|
||||||
<Text className="text-xs opacity-50">{i.Role}</Text>
|
<Text className="text-xs opacity-50">{i.Role}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const CurrentSeries: React.FC<Props> = ({ item, ...props }) => {
|
|||||||
className="flex flex-col space-y-2 w-28"
|
className="flex flex-col space-y-2 w-28"
|
||||||
>
|
>
|
||||||
<Poster
|
<Poster
|
||||||
item={item}
|
id={item.id}
|
||||||
url={getPrimaryImageUrlById({ api, id: item.ParentId })}
|
url={getPrimaryImageUrlById({ api, id: item.ParentId })}
|
||||||
/>
|
/>
|
||||||
<Text>{item.SeriesName}</Text>
|
<Text>{item.SeriesName}</Text>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const RenderItem = ({ item, index }: any) => {
|
|||||||
key={item.id}
|
key={item.id}
|
||||||
id={item.id}
|
id={item.id}
|
||||||
source={{
|
source={{
|
||||||
uri: jellyseerrApi?.tvStillImageProxy(item.stillPath),
|
uri: jellyseerrApi?.imageProxy(item.stillPath),
|
||||||
}}
|
}}
|
||||||
cachePolicy={"memory-disk"}
|
cachePolicy={"memory-disk"}
|
||||||
contentFit="cover"
|
contentFit="cover"
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ import Issue from "@/utils/jellyseerr/server/entity/Issue";
|
|||||||
import { RTRating } from "@/utils/jellyseerr/server/api/rating/rottentomatoes";
|
import { RTRating } from "@/utils/jellyseerr/server/api/rating/rottentomatoes";
|
||||||
import { writeErrorLog } from "@/utils/log";
|
import { writeErrorLog } from "@/utils/log";
|
||||||
import DiscoverSlider from "@/utils/jellyseerr/server/entity/DiscoverSlider";
|
import DiscoverSlider from "@/utils/jellyseerr/server/entity/DiscoverSlider";
|
||||||
|
import {
|
||||||
|
CombinedCredit,
|
||||||
|
PersonDetails
|
||||||
|
} from "@/utils/jellyseerr/server/models/Person";
|
||||||
|
|
||||||
interface SearchParams {
|
interface SearchParams {
|
||||||
query: string;
|
query: string;
|
||||||
@@ -55,6 +59,8 @@ export enum Endpoints {
|
|||||||
API_V1 = "/api/v1",
|
API_V1 = "/api/v1",
|
||||||
SEARCH = "/search",
|
SEARCH = "/search",
|
||||||
REQUEST = "/request",
|
REQUEST = "/request",
|
||||||
|
PERSON = "/person",
|
||||||
|
COMBINED_CREDITS = "/combined_credits",
|
||||||
MOVIE = "/movie",
|
MOVIE = "/movie",
|
||||||
RATINGS = "/ratings",
|
RATINGS = "/ratings",
|
||||||
ISSUE = "/issue",
|
ISSUE = "/issue",
|
||||||
@@ -204,6 +210,22 @@ export class JellyseerrApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async personDetails(id: number | string): Promise<PersonDetails> {
|
||||||
|
return this.axios
|
||||||
|
?.get<PersonDetails>(Endpoints.API_V1 + Endpoints.PERSON + `/${id}`)
|
||||||
|
.then((response) => {
|
||||||
|
return response?.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async personCombinedCredits(id: number | string): Promise<CombinedCredit> {
|
||||||
|
return this.axios
|
||||||
|
?.get<CombinedCredit>(Endpoints.API_V1 + Endpoints.PERSON + `/${id}` + Endpoints.COMBINED_CREDITS)
|
||||||
|
.then((response) => {
|
||||||
|
return response?.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async movieRatings(id: number) {
|
async movieRatings(id: number) {
|
||||||
return this.axios
|
return this.axios
|
||||||
?.get<RTRating>(
|
?.get<RTRating>(
|
||||||
@@ -238,14 +260,15 @@ export class JellyseerrApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
tvStillImageProxy(path: string, width: number = 1920, quality: number = 75) {
|
imageProxy(path?: string, tmdbPath: string = 'original', width: number = 1920, quality: number = 75) {
|
||||||
return (
|
return path ? (
|
||||||
this.axios.defaults.baseURL +
|
this.axios.defaults.baseURL +
|
||||||
`/_next/image?` +
|
`/_next/image?` +
|
||||||
new URLSearchParams(
|
new URLSearchParams(
|
||||||
`url=https://image.tmdb.org/t/p/original/${path}&w=${width}&q=${quality}`
|
`url=https://image.tmdb.org/t/p/${tmdbPath}/${path}&w=${width}&q=${quality}`
|
||||||
).toString()
|
).toString()
|
||||||
);
|
) :
|
||||||
|
this.axios?.defaults.baseURL + `/images/overseerr_poster_not_found_logo_top.png`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async submitIssue(mediaId: number, issueType: IssueType, message: string) {
|
async submitIssue(mediaId: number, issueType: IssueType, message: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user