From a096c86fe2f094c3384534b960e6635736c3ec06 Mon Sep 17 00:00:00 2001 From: lostb1t Date: Thu, 6 Nov 2025 16:22:20 +0100 Subject: [PATCH] Refactor page.tsx to use local search params --- .../items/page.tsx | 394 ++++-------------- 1 file changed, 87 insertions(+), 307 deletions(-) diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/items/page.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/items/page.tsx index ae8c7b50..cecb8671 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/items/page.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/items/page.tsx @@ -1,319 +1,99 @@ -import type { - BaseItemDto, - MediaSourceInfo, -} from "@jellyfin/sdk/lib/generated-client/models"; -import { Image } from "expo-image"; -import { useNavigation } from "expo-router"; -import { useAtom } from "jotai"; -import React, { useEffect, useMemo, useState } from "react"; +import { useLocalSearchParams } from "expo-router"; +import type React from "react"; +import { useEffect } from "react"; import { useTranslation } from "react-i18next"; -import { Platform, View } from "react-native"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { type Bitrate } from "@/components/BitrateSelector"; -import { ItemImage } from "@/components/common/ItemImage"; -import { DownloadSingleItem } from "@/components/DownloadItem"; -import { OverviewText } from "@/components/OverviewText"; -import { ParallaxScrollView } from "@/components/ParallaxPage"; -// const PlayButton = !Platform.isTV ? require("@/components/PlayButton") : null; -import { PlayButton } from "@/components/PlayButton"; -import { PlayedStatus } from "@/components/PlayedStatus"; -import { SimilarItems } from "@/components/SimilarItems"; -import { CastAndCrew } from "@/components/series/CastAndCrew"; -import { CurrentSeries } from "@/components/series/CurrentSeries"; -import { SeasonEpisodesCarousel } from "@/components/series/SeasonEpisodesCarousel"; -import useDefaultPlaySettings from "@/hooks/useDefaultPlaySettings"; -import { useImageColorsReturn } from "@/hooks/useImageColorsReturn"; -import { useOrientation } from "@/hooks/useOrientation"; -import * as ScreenOrientation from "@/packages/expo-screen-orientation"; -import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; -import { useSettings } from "@/utils/atoms/settings"; -import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById"; -import { AddToFavorites } from "./AddToFavorites"; -import { BitrateSheet } from "./BitRateSheet"; -import { ItemHeader } from "./ItemHeader"; -import { ItemTechnicalDetails } from "./ItemTechnicalDetails"; -import { MediaSourceSheet } from "./MediaSourceSheet"; -import { MoreMoviesWithActor } from "./MoreMoviesWithActor"; -import { PlayInRemoteSessionButton } from "./PlayInRemoteSession"; -import { TrackSheet } from "./TrackSheet"; +import { View } from "react-native"; +import Animated, { + runOnJS, + useAnimatedStyle, + useSharedValue, + withTiming, +} from "react-native-reanimated"; +import { Text } from "@/components/common/Text"; +import { ItemContent } from "@/components/ItemContent"; +import { useItemQuery } from "@/hooks/useItemQuery"; -const Chromecast = !Platform.isTV ? require("./Chromecast") : null; +const Page: React.FC = () => { + const { id } = useLocalSearchParams() as { id: string }; + const { t } = useTranslation(); -export type SelectedOptions = { - bitrate: Bitrate; - mediaSource: MediaSourceInfo | undefined; - audioIndex: number | undefined; - subtitleIndex: number; -}; + const { offline } = useLocalSearchParams() as { offline?: string }; + const isOffline = offline === "true"; -interface ItemContentProps { - item: BaseItemDto; - isOffline: boolean; - mediaSourcesitem: BaseItemDto; -} + const { data: item, isError } = useItemQuery(itemId, false, undefined, [ItemFields.MediaSources]); + const { data: mediaSourcesitem, isError } = useItemQuery(id, isOffline); -export const ItemContent: React.FC = React.memo( - ({ item, isOffline, mediaSourcesitem }) => { - const [api] = useAtom(apiAtom); - const { settings } = useSettings(); - const { orientation } = useOrientation(); - const navigation = useNavigation(); - const insets = useSafeAreaInsets(); - const [user] = useAtom(userAtom); - const { t } = useTranslation(); + const opacity = useSharedValue(1); + const animatedStyle = useAnimatedStyle(() => { + return { + opacity: opacity.value, + }; + }); - const itemColors = useImageColorsReturn({ item }); + const fadeOut = (callback: any) => { + setTimeout(() => { + opacity.value = withTiming(0, { duration: 500 }, (finished) => { + if (finished) { + runOnJS(callback)(); + } + }); + }, 100); + }; - const [loadingLogo, setLoadingLogo] = useState(true); - const [headerHeight, setHeaderHeight] = useState(350); + const fadeIn = (callback: any) => { + setTimeout(() => { + opacity.value = withTiming(1, { duration: 500 }, (finished) => { + if (finished) { + runOnJS(callback)(); + } + }); + }, 100); + }; - const [selectedOptions, setSelectedOptions] = useState< - SelectedOptions | undefined - >(undefined); - - const { - defaultAudioIndex, - defaultBitrate, - defaultMediaSource, - defaultSubtitleIndex, - } = useDefaultPlaySettings(item!, settings); - - const logoUrl = useMemo( - () => (item ? getLogoImageUrlById({ api, item }) : null), - [api, item], - ); - - const loading = useMemo(() => { - return Boolean(logoUrl && loadingLogo); - }, [loadingLogo, logoUrl]); - - // Needs to automatically change the selected to the default values for default indexes. - useEffect(() => { - setSelectedOptions(() => ({ - bitrate: defaultBitrate, - mediaSource: defaultMediaSource, - subtitleIndex: defaultSubtitleIndex ?? -1, - audioIndex: defaultAudioIndex, - })); - }, [ - defaultAudioIndex, - defaultBitrate, - defaultSubtitleIndex, - defaultMediaSource, - ]); - - useEffect(() => { - if (!Platform.isTV) { - navigation.setOptions({ - headerRight: () => - item && - (Platform.OS === "ios" ? ( - - - {item.Type !== "Program" && ( - - {!Platform.isTV && ( - - )} - {user?.Policy?.IsAdministrator && ( - - )} - - - - - )} - - ) : ( - - - {item.Type !== "Program" && ( - - {!Platform.isTV && ( - - )} - {user?.Policy?.IsAdministrator && ( - - )} - - - - - )} - - )), - }); - } - }, [item, navigation, user]); - - useEffect(() => { - if (item) { - if (orientation !== ScreenOrientation.OrientationLock.PORTRAIT_UP) - setHeaderHeight(230); - else if (item.Type === "Movie") setHeaderHeight(500); - else setHeaderHeight(350); - } - }, [item, orientation]); - - if (!item || !selectedOptions) return null; + useEffect(() => { + if (item) { + fadeOut(() => {}); + } else { + fadeIn(() => {}); + } + }, [item]); + if (isError) return ( - - - - - } - logo={ - logoUrl ? ( - setLoadingLogo(false)} - onError={() => setLoadingLogo(false)} - /> - ) : ( - - ) - } - > - - - - {item.Type !== "Program" && !Platform.isTV && !isOffline && ( - - - setSelectedOptions( - (prev) => prev && { ...prev, bitrate: val }, - ) - } - selected={selectedOptions.bitrate} - /> - - setSelectedOptions( - (prev) => - prev && { - ...prev, - mediaSource: val, - }, - ) - } - selected={selectedOptions.mediaSource} - /> - { - setSelectedOptions( - (prev) => - prev && { - ...prev, - audioIndex: val, - }, - ); - }} - selected={selectedOptions.audioIndex} - /> - - setSelectedOptions( - (prev) => - prev && { - ...prev, - subtitleIndex: val, - }, - ) - } - selected={selectedOptions.subtitleIndex} - /> - - )} - - - - - {item.Type === "Episode" && ( - - )} - - {!isOffline && ( - - )} - - - {item.Type !== "Program" && ( - <> - {item.Type === "Episode" && !isOffline && ( - - )} - - {!isOffline && ( - - )} - - {item.People && item.People.length > 0 && !isOffline && ( - - {item.People.slice(0, 3).map((person, idx) => ( - - ))} - - )} - - {!isOffline && } - - )} - - + + {t("item_card.could_not_load_item")} ); - }, -); + + return ( + + + + + + + + + + + + + + + + + {item && } + + ); +}; + +export default Page;