diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/[personId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/[personId].tsx
index 2dc2a478..5a930982 100644
--- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/[personId].tsx
+++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/[personId].tsx
@@ -223,12 +223,14 @@ export default function page() {
mediaType={item.mediaType as "movie" | "tv"}
/>
{/*{item.title}*/}
-
- as {item.character}
-
+ {item.character && (
+
+ as {item.character}
+
+ )}
)}
keyExtractor={(item) => item.id.toString()}
diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx
index 678680c7..b839708d 100644
--- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx
+++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx
@@ -42,22 +42,18 @@ import MediaRequest from "@/utils/jellyseerr/server/entity/MediaRequest";
import DetailFacts from "@/components/jellyseerr/DetailFacts";
import { ItemActions } from "@/components/series/SeriesActions";
import Cast from "@/components/jellyseerr/Cast";
+import { useJellyseerrCanRequest } from "@/utils/_jellyseerr/useJellyseerrCanRequest";
const Page: React.FC = () => {
const insets = useSafeAreaInsets();
const params = useLocalSearchParams();
- const {
- mediaTitle,
- releaseYear,
- canRequest: canRequestString,
- posterSrc,
- ...result
- } = params as unknown as {
- mediaTitle: string;
- releaseYear: number;
- canRequest: string;
- posterSrc: string;
- } & Partial;
+ const { mediaTitle, releaseYear, posterSrc, ...result } =
+ params as unknown as {
+ mediaTitle: string;
+ releaseYear: number;
+ canRequest: string;
+ posterSrc: string;
+ } & Partial;
const navigation = useNavigation();
const { jellyseerrApi, requestMedia } = useJellyseerr();
@@ -87,19 +83,7 @@ const Page: React.FC = () => {
},
});
- const canRequest = useMemo(() => {
- const pendingRequests = details?.mediaInfo?.requests?.some(
- (r: MediaRequest) =>
- r.status == MediaRequestStatus.PENDING ||
- r.status == MediaRequestStatus.APPROVED
- );
-
- return (
- (details?.mediaInfo?.status === MediaStatus.UNKNOWN &&
- !pendingRequests) ||
- (!details?.mediaInfo?.status && canRequestString === "true")
- );
- }, [canRequestString, details]);
+ const canRequest = useJellyseerrCanRequest(details);
const renderBackdrop = useCallback(
(props: BottomSheetBackdropProps) => (
@@ -225,7 +209,9 @@ const Page: React.FC = () => {
g.name) || []} />
- {canRequest ? (
+ {isLoading || isFetching ? (
+
+ ) : canRequest ? (
@@ -260,7 +246,7 @@ const Page: React.FC = () => {
className="p-2 border border-neutral-800 bg-neutral-900 rounded-xl"
details={details}
/>
-
+
diff --git a/app/(auth)/(tabs)/(search)/index.tsx b/app/(auth)/(tabs)/(search)/index.tsx
index fcf8119d..1ae0059c 100644
--- a/app/(auth)/(tabs)/(search)/index.tsx
+++ b/app/(auth)/(tabs)/(search)/index.tsx
@@ -31,13 +31,18 @@ import { Platform, ScrollView, TouchableOpacity, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useDebounce } from "use-debounce";
import { useJellyseerr } from "@/hooks/useJellyseerr";
-import {MovieResult, PersonResult, 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 JellyseerrPoster from "@/components/posters/JellyseerrPoster";
import { Tag } from "@/components/GenreTags";
import DiscoverSlide from "@/components/jellyseerr/DiscoverSlide";
import { sortBy } from "lodash";
import PersonPoster from "@/components/jellyseerr/PersonPoster";
+import { useReactNavigationQuery } from "@/utils/useReactNavigationQuery";
type SearchType = "Library" | "Discover";
@@ -150,8 +155,8 @@ export default function search() {
enabled: searchType === "Library" && debouncedSearch.length > 0,
});
- const { data: jellyseerrResults, isFetching: j1 } = useQuery({
- queryKey: ["search", "jellyseerrResults", debouncedSearch],
+ const { data: jellyseerrResults, isFetching: j1 } = useReactNavigationQuery({
+ queryKey: ["search", "jellyseerr", "results", debouncedSearch],
queryFn: async () => {
const response = await jellyseerrApi?.search({
query: new URLSearchParams(debouncedSearch).toString(),
@@ -167,14 +172,15 @@ export default function search() {
debouncedSearch.length > 0,
});
- const { data: jellyseerrDiscoverSettings, isFetching: j2 } = useQuery({
- queryKey: ["search", "jellyseerrDiscoverSettings", debouncedSearch],
- queryFn: async () => jellyseerrApi?.discoverSettings(),
- enabled:
- !!jellyseerrApi &&
- searchType === "Discover" &&
- debouncedSearch.length == 0,
- });
+ const { data: jellyseerrDiscoverSettings, isFetching: j2 } =
+ useReactNavigationQuery({
+ queryKey: ["search", "jellyseerr", "discoverSettings", debouncedSearch],
+ queryFn: async () => jellyseerrApi?.discoverSettings(),
+ enabled:
+ !!jellyseerrApi &&
+ searchType === "Discover" &&
+ debouncedSearch.length == 0,
+ });
const jellyseerrMovieResults: MovieResult[] | undefined = useMemo(
() =>
@@ -309,7 +315,7 @@ export default function search() {
paddingRight: insets.right,
}}
>
-
+
{Platform.OS === "android" && (
> = ({
{...props}
>
{loading ? (
-
+
+
+
) : (
0 && (
- Cast
+ Cast
}
estimatedItemSize={15}
keyExtractor={(item) => item?.id?.toString()}
+ contentContainerStyle={{ paddingHorizontal: 16 }}
renderItem={({ item }) => (
= ({
- item,
- ...props
-}) => {
- const {jellyseerrUser, jellyseerrApi} = useJellyseerr();
- // const imageSource =
+const JellyseerrPoster: React.FC = ({ item, ...props }) => {
+ const { jellyseerrApi } = useJellyseerr();
const imageSrc = useMemo(
- () => jellyseerrApi?.imageProxy(item.posterPath, 'w300_and_h450_face'),
+ () => jellyseerrApi?.imageProxy(item.posterPath, "w300_and_h450_face"),
[item, jellyseerrApi]
- )
- const title = useMemo(() => item.mediaType === MediaType.MOVIE ? item.title : item.name, [item])
- const releaseYear = useMemo(() =>
- new Date(item.mediaType === MediaType.MOVIE ? item.releaseDate : item.firstAirDate).getFullYear(),
+ );
+ const title = useMemo(
+ () => (item.mediaType === MediaType.MOVIE ? item.title : item.name),
[item]
- )
+ );
+ const releaseYear = useMemo(
+ () =>
+ new Date(
+ item.mediaType === MediaType.MOVIE
+ ? item.releaseDate
+ : item.firstAirDate
+ ).getFullYear(),
+ [item]
+ );
- const showRequestButton = useMemo(() =>
- jellyseerrUser && hasPermission(
- [
- Permission.REQUEST,
- item.mediaType === 'movie'
- ? Permission.REQUEST_MOVIE
- : Permission.REQUEST_TV,
- ],
- jellyseerrUser.permissions,
- {type: 'or'}
- ),
- [item, jellyseerrUser]
- )
-
- const canRequest = useMemo(() => {
- const status = item?.mediaInfo?.status
- return showRequestButton && !status || status === MediaStatus.UNKNOWN
- }, [item])
+ const canRequest = useJellyseerrCanRequest(item);
return (
= ({
= ({
- )
-}
+ );
+};
-
-export default JellyseerrPoster;
\ No newline at end of file
+export default JellyseerrPoster;
diff --git a/hooks/useJellyseerr.ts b/hooks/useJellyseerr.ts
index 4546cee1..815510fa 100644
--- a/hooks/useJellyseerr.ts
+++ b/hooks/useJellyseerr.ts
@@ -30,8 +30,9 @@ import { writeErrorLog } from "@/utils/log";
import DiscoverSlider from "@/utils/jellyseerr/server/entity/DiscoverSlider";
import {
CombinedCredit,
- PersonDetails
+ PersonDetails,
} from "@/utils/jellyseerr/server/models/Person";
+import { useQueryClient } from "@tanstack/react-query";
interface SearchParams {
query: string;
@@ -220,7 +221,12 @@ export class JellyseerrApi {
async personCombinedCredits(id: number | string): Promise {
return this.axios
- ?.get(Endpoints.API_V1 + Endpoints.PERSON + `/${id}` + Endpoints.COMBINED_CREDITS)
+ ?.get(
+ Endpoints.API_V1 +
+ Endpoints.PERSON +
+ `/${id}` +
+ Endpoints.COMBINED_CREDITS
+ )
.then((response) => {
return response?.data;
});
@@ -260,15 +266,20 @@ export class JellyseerrApi {
});
}
- imageProxy(path?: string, tmdbPath: string = 'original', width: number = 1920, quality: number = 75) {
- return path ? (
- this.axios.defaults.baseURL +
- `/_next/image?` +
- new URLSearchParams(
- `url=https://image.tmdb.org/t/p/${tmdbPath}/${path}&w=${width}&q=${quality}`
- ).toString()
- ) :
- this.axios?.defaults.baseURL + `/images/overseerr_poster_not_found_logo_top.png`;
+ imageProxy(
+ path?: string,
+ tmdbPath: string = "original",
+ width: number = 1920,
+ quality: number = 75
+ ) {
+ return path
+ ? this.axios.defaults.baseURL +
+ `/_next/image?` +
+ new URLSearchParams(
+ `url=https://image.tmdb.org/t/p/${tmdbPath}/${path}&w=${width}&q=${quality}`
+ ).toString()
+ : this.axios?.defaults.baseURL +
+ `/images/overseerr_poster_not_found_logo_top.png`;
}
async submitIssue(mediaId: number, issueType: IssueType, message: string) {
@@ -344,6 +355,7 @@ const jellyseerrUserAtom = atom(storage.get(JELLYSEERR_USER));
export const useJellyseerr = () => {
const [jellyseerrUser, setJellyseerrUser] = useAtom(jellyseerrUserAtom);
const [settings, updateSettings] = useSettings();
+ const queryClient = useQueryClient();
const jellyseerrApi = useMemo(() => {
const cookies = storage.get(JELLYSEERR_COOKIES);
@@ -361,12 +373,16 @@ export const useJellyseerr = () => {
const requestMedia = useCallback(
(title: string, request: MediaRequestBody, onSuccess?: () => void) => {
- jellyseerrApi?.request?.(request)?.then((mediaRequest) => {
+ jellyseerrApi?.request?.(request)?.then(async (mediaRequest) => {
+ await queryClient.invalidateQueries({
+ queryKey: ["search", "jellyseerr"],
+ });
+
switch (mediaRequest.status) {
case MediaRequestStatus.PENDING:
case MediaRequestStatus.APPROVED:
toast.success(`Requested ${title}!`);
- onSuccess?.()
+ onSuccess?.();
break;
case MediaRequestStatus.DECLINED:
toast.error(`You don't have permission to request!`);
diff --git a/utils/_jellyseerr/useJellyseerrCanRequest.ts b/utils/_jellyseerr/useJellyseerrCanRequest.ts
new file mode 100644
index 00000000..ba692df3
--- /dev/null
+++ b/utils/_jellyseerr/useJellyseerrCanRequest.ts
@@ -0,0 +1,52 @@
+import { useJellyseerr } from "@/hooks/useJellyseerr";
+import {
+ MediaRequestStatus,
+ MediaStatus,
+} from "@/utils/jellyseerr/server/constants/media";
+import {
+ hasPermission,
+ Permission,
+} from "@/utils/jellyseerr/server/lib/permissions";
+import { MovieResult, TvResult } from "@/utils/jellyseerr/server/models/Search";
+import { useMemo } from "react";
+import MediaRequest from "../jellyseerr/server/entity/MediaRequest";
+import { MovieDetails } from "../jellyseerr/server/models/Movie";
+import { TvDetails } from "../jellyseerr/server/models/Tv";
+
+export const useJellyseerrCanRequest = (
+ item?: MovieResult | TvResult | MovieDetails | TvDetails
+) => {
+ const { jellyseerrUser } = useJellyseerr();
+
+ const canRequest = useMemo(() => {
+ if (!jellyseerrUser || !item) return false;
+
+ const canNotRequest =
+ item?.mediaInfo?.requests?.some(
+ (r: MediaRequest) =>
+ r.status == MediaRequestStatus.PENDING ||
+ r.status == MediaRequestStatus.APPROVED
+ ) ||
+ item.mediaInfo?.status === MediaStatus.AVAILABLE ||
+ item.mediaInfo?.status === MediaStatus.BLACKLISTED ||
+ item.mediaInfo?.status === MediaStatus.PENDING ||
+ item.mediaInfo?.status === MediaStatus.PROCESSING;
+
+ if (canNotRequest) return false;
+
+ const userHasPermission = hasPermission(
+ [
+ Permission.REQUEST,
+ item?.mediaInfo?.mediaType
+ ? Permission.REQUEST_MOVIE
+ : Permission.REQUEST_TV,
+ ],
+ jellyseerrUser.permissions,
+ { type: "or" }
+ );
+
+ return userHasPermission && !canNotRequest;
+ }, [item, jellyseerrUser]);
+
+ return canRequest;
+};
diff --git a/utils/useReactNavigationQuery.ts b/utils/useReactNavigationQuery.ts
new file mode 100644
index 00000000..a0c5b307
--- /dev/null
+++ b/utils/useReactNavigationQuery.ts
@@ -0,0 +1,32 @@
+import { useFocusEffect } from "@react-navigation/core";
+import {
+ QueryKey,
+ useQuery,
+ UseQueryOptions,
+ UseQueryResult,
+} from "@tanstack/react-query";
+import { useCallback } from "react";
+
+export function useReactNavigationQuery<
+ TQueryFnData = unknown,
+ TError = unknown,
+ TData = TQueryFnData,
+ TQueryKey extends QueryKey = QueryKey
+>(
+ options: UseQueryOptions
+): UseQueryResult {
+ const useQueryReturn = useQuery(options);
+
+ useFocusEffect(
+ useCallback(() => {
+ if (
+ ((options.refetchOnWindowFocus && useQueryReturn.isStale) ||
+ options.refetchOnWindowFocus === "always") &&
+ options.enabled !== false
+ )
+ useQueryReturn.refetch();
+ }, [options.enabled, options.refetchOnWindowFocus])
+ );
+
+ return useQueryReturn;
+}