diff --git a/app/(auth)/(tabs)/(home,libraries,search)/series/[id].tsx b/app/(auth)/(tabs)/(home,libraries,search)/series/[id].tsx index ecee672b..e9c9fdea 100644 --- a/app/(auth)/(tabs)/(home,libraries,search)/series/[id].tsx +++ b/app/(auth)/(tabs)/(home,libraries,search)/series/[id].tsx @@ -1,8 +1,11 @@ import { Text } from "@/components/common/Text"; import { DownloadItems } from "@/components/DownloadItem"; import { ParallaxScrollView } from "@/components/ParallaxPage"; +import { Ratings } from "@/components/Ratings"; import { NextUp } from "@/components/series/NextUp"; import { SeasonPicker } from "@/components/series/SeasonPicker"; +import { SeriesActions } from "@/components/series/SeriesActions"; +import { SeriesHeader } from "@/components/series/SeriesHeader"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl"; import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById"; @@ -70,6 +73,7 @@ const page: React.FC = () => { }); return res?.data.Items || []; }, + staleTime: 60, enabled: !!api && !!user?.Id && !!item?.Id, }); @@ -133,10 +137,7 @@ const page: React.FC = () => { } > - - {item?.Name} - {item?.Overview} - + diff --git a/app/(auth)/trailer/page.tsx b/app/(auth)/trailer/page.tsx new file mode 100644 index 00000000..c2a84809 --- /dev/null +++ b/app/(auth)/trailer/page.tsx @@ -0,0 +1,53 @@ +import { useGlobalSearchParams, useNavigation } from "expo-router"; +import { useState, useCallback, useEffect, useMemo } from "react"; +import { Button, Dimensions } from "react-native"; +import { Alert, View } from "react-native"; +import YoutubePlayer, { PLAYER_STATES } from "react-native-youtube-iframe"; + +export default function page() { + const searchParams = useGlobalSearchParams(); + const navigation = useNavigation(); + console.log(searchParams); + + const { url } = searchParams as { url: string }; + + const videoId = useMemo(() => { + return url.split("v=")[1]; + }, [url]); + + const [playing, setPlaying] = useState(false); + + const onStateChange = useCallback((state: PLAYER_STATES) => { + if (state === "ended") { + setPlaying(false); + Alert.alert("video has finished playing!"); + } + }, []); + + const togglePlaying = useCallback(() => { + setPlaying((prev) => !prev); + }, []); + + useEffect(() => { + navigation.setOptions({ + headerShown: false, + }); + + togglePlaying(); + }, []); + + const screenWidth = Dimensions.get("screen").width; + const screenHeight = Dimensions.get("screen").height; + + return ( + + + + ); +} diff --git a/app/_layout.tsx b/app/_layout.tsx index fc8eb4cc..444ec7da 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -335,6 +335,13 @@ function Layout() { header: () => null, }} /> + = ({ item, initialSeasonIndex }) => { return response.data.Items; }, + staleTime: 60, enabled: !!api && !!user?.Id && !!item.Id, }); diff --git a/components/series/SeriesActions.tsx b/components/series/SeriesActions.tsx new file mode 100644 index 00000000..7e87247f --- /dev/null +++ b/components/series/SeriesActions.tsx @@ -0,0 +1,32 @@ +import { Ionicons } from "@expo/vector-icons"; +import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client"; +import { useRouter } from "expo-router"; +import { useCallback, useMemo } from "react"; +import { View, TouchableOpacity, ViewProps } from "react-native"; + +interface Props extends ViewProps { + item: BaseItemDto; +} + +export const SeriesActions = ({ item, ...props }: Props) => { + const router = useRouter(); + + const trailerLink = useMemo(() => item.RemoteTrailers?.[0]?.Url, [item]); + + const openTrailer = useCallback(async () => { + if (!trailerLink) return; + + const encodedTrailerLink = encodeURIComponent(trailerLink); + router.push(`/trailer/page?url=${encodedTrailerLink}`); + }, [router, trailerLink]); + + return ( + + {trailerLink && ( + + + + )} + + ); +}; diff --git a/components/series/SeriesHeader.tsx b/components/series/SeriesHeader.tsx new file mode 100644 index 00000000..3fa5ca67 --- /dev/null +++ b/components/series/SeriesHeader.tsx @@ -0,0 +1,64 @@ +import { View } from "react-native"; +import { Text } from "../common/Text"; +import { Ratings } from "../Ratings"; +import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client"; +import { useMemo } from "react"; +import { SeriesActions } from "./SeriesActions"; + +interface Props { + item: BaseItemDto; +} + +export const SeriesHeader = ({ item }: Props) => { + const startYear = useMemo(() => { + if (item?.StartDate) { + return new Date(item.StartDate) + .toLocaleDateString("sv-SE", { + calendar: "gregory", + year: "numeric", + }) + .toString() + .trim(); + } + return item.ProductionYear?.toString().trim(); + }, [item]); + + const endYear = useMemo(() => { + if (item.EndDate) { + return new Date(item.EndDate) + .toLocaleDateString("sv-SE", { + calendar: "gregory", + year: "numeric", + }) + .toString() + .trim(); + } + return ""; + }, [item]); + + const yearString = useMemo(() => { + if (startYear && endYear) { + if (startYear === endYear) return startYear; + return `${startYear} - ${endYear}`; + } + if (startYear) { + return startYear; + } + if (endYear) { + return endYear; + } + return ""; + }, [startYear, endYear]); + + return ( + + {item?.Name} + {yearString} + + + + + {item?.Overview} + + ); +}; diff --git a/package.json b/package.json index a2e3b635..50a4de63 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,8 @@ "react-native-video": "^6.7.0", "react-native-volume-manager": "^1.10.0", "react-native-web": "~0.19.13", + "react-native-webview": "^13.12.5", + "react-native-youtube-iframe": "^2.3.0", "sonner-native": "^0.14.2", "tailwindcss": "3.3.2", "use-debounce": "^10.0.4",