diff --git a/app/(auth)/(tabs)/(home)/_layout.tsx b/app/(auth)/(tabs)/(home)/_layout.tsx index 5d5054b0..430d4ba1 100644 --- a/app/(auth)/(tabs)/(home)/_layout.tsx +++ b/app/(auth)/(tabs)/(home)/_layout.tsx @@ -1,4 +1,4 @@ -import { Chromecast } from "@/components/Chromecast"; +const Chromecast = !Platform.isTV ? require("@/components/Chromecast") : null; import { Text } from "@/components/common/Text"; import { nestedTabPageScreenOptions } from "@/components/stacks/NestedTabPageStack"; import { Feather } from "@expo/vector-icons"; diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/albums/[albumId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/albums/[albumId].tsx deleted file mode 100644 index de7d10e7..00000000 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/albums/[albumId].tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { Chromecast } from "@/components/Chromecast"; -import { ItemImage } from "@/components/common/ItemImage"; -import { Text } from "@/components/common/Text"; -import { TouchableItemRouter } from "@/components/common/TouchableItemRouter"; -import { SongsList } from "@/components/music/SongsList"; -import { ParallaxScrollView } from "@/components/ParallaxPage"; -import ArtistPoster from "@/components/posters/ArtistPoster"; -import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; -import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; -import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; -import { useQuery } from "@tanstack/react-query"; -import { router, useLocalSearchParams, useNavigation } from "expo-router"; -import { useAtom } from "jotai"; -import { useEffect, useState } from "react"; -import { Platform, ScrollView, TouchableOpacity, View } from "react-native"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; - -export default function page() { - const searchParams = useLocalSearchParams(); - const { collectionId, artistId, albumId } = searchParams as { - collectionId: string; - artistId: string; - albumId: string; - }; - - const [api] = useAtom(apiAtom); - const [user] = useAtom(userAtom); - - const navigation = useNavigation(); - - if (!Platform.isTV) { - useEffect(() => { - navigation.setOptions({ - headerRight: () => ( - - - - ), - }); - }); - } - - const { data: album } = useQuery({ - queryKey: ["album", albumId, artistId], - queryFn: async () => { - if (!api) return null; - const response = await getItemsApi(api).getItems({ - userId: user?.Id, - ids: [albumId], - }); - const data = response.data.Items?.[0]; - return data; - }, - enabled: !!api && !!user?.Id && !!albumId, - staleTime: 0, - }); - - const { - data: songs, - isLoading, - isError, - } = useQuery<{ - Items: BaseItemDto[]; - TotalRecordCount: number; - }>({ - queryKey: ["songs", artistId, albumId], - queryFn: async () => { - if (!api) - return { - Items: [], - TotalRecordCount: 0, - }; - - const response = await getItemsApi(api).getItems({ - userId: user?.Id, - parentId: albumId, - fields: [ - "ItemCounts", - "PrimaryImageAspectRatio", - "CanDelete", - "MediaSourceCount", - ], - sortBy: ["ParentIndexNumber", "IndexNumber", "SortName"], - }); - - const data = response.data.Items; - - return { - Items: data || [], - TotalRecordCount: response.data.TotalRecordCount || 0, - }; - }, - enabled: !!api && !!user?.Id, - }); - - const insets = useSafeAreaInsets(); - - if (!album) return null; - - return ( - - } - > - - {album?.Name} - - {songs?.TotalRecordCount} songs - - - - - - - ); -} diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/[artistId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/[artistId].tsx deleted file mode 100644 index 8d82d205..00000000 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/[artistId].tsx +++ /dev/null @@ -1,130 +0,0 @@ -import ArtistPoster from "@/components/posters/ArtistPoster"; -import { Text } from "@/components/common/Text"; -import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; -import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; -import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; -import { useQuery } from "@tanstack/react-query"; -import { router, useLocalSearchParams, useNavigation } from "expo-router"; -import { useAtom } from "jotai"; -import { useEffect, useState } from "react"; -import { FlatList, ScrollView, TouchableOpacity, View } from "react-native"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { ItemImage } from "@/components/common/ItemImage"; -import { ParallaxScrollView } from "@/components/ParallaxPage"; -import { TouchableItemRouter } from "@/components/common/TouchableItemRouter"; - -export default function page() { - const searchParams = useLocalSearchParams(); - const { artistId } = searchParams as { - artistId: string; - }; - - const [api] = useAtom(apiAtom); - const [user] = useAtom(userAtom); - - const navigation = useNavigation(); - - const [startIndex, setStartIndex] = useState(0); - - const { data: artist } = useQuery({ - queryKey: ["album", artistId], - queryFn: async () => { - if (!api) return null; - const response = await getItemsApi(api).getItems({ - userId: user?.Id, - ids: [artistId], - }); - const data = response.data.Items?.[0]; - return data; - }, - enabled: !!api && !!user?.Id && !!artistId, - staleTime: 0, - }); - - const { - data: albums, - isLoading, - isError, - } = useQuery<{ - Items: BaseItemDto[]; - TotalRecordCount: number; - }>({ - queryKey: ["albums", artistId, startIndex], - queryFn: async () => { - if (!api) - return { - Items: [], - TotalRecordCount: 0, - }; - - const response = await getItemsApi(api).getItems({ - userId: user?.Id, - parentId: artistId, - sortOrder: ["Descending", "Descending", "Ascending"], - includeItemTypes: ["MusicAlbum"], - recursive: true, - fields: [ - "ParentId", - "PrimaryImageAspectRatio", - "ParentId", - "PrimaryImageAspectRatio", - ], - collapseBoxSetItems: false, - albumArtistIds: [artistId], - startIndex, - limit: 100, - sortBy: ["PremiereDate", "ProductionYear", "SortName"], - }); - - const data = response.data.Items; - - return { - Items: data || [], - TotalRecordCount: response.data.TotalRecordCount || 0, - }; - }, - enabled: !!api && !!user?.Id, - }); - - const insets = useSafeAreaInsets(); - - if (!artist || !albums) return null; - - return ( - - } - > - - {artist?.Name} - - {albums.TotalRecordCount} albums - - - - {albums.Items.map((item, idx) => ( - - - - {item.Name} - {item.ProductionYear} - - - ))} - - - ); -} diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/index.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/index.tsx deleted file mode 100644 index 4827287e..00000000 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/index.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { Text } from "@/components/common/Text"; -import { TouchableItemRouter } from "@/components/common/TouchableItemRouter"; -import ArtistPoster from "@/components/posters/ArtistPoster"; -import MoviePoster from "@/components/posters/MoviePoster"; -import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; -import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; -import { getArtistsApi, getItemsApi } from "@jellyfin/sdk/lib/utils/api"; -import { useQuery } from "@tanstack/react-query"; -import { router, useLocalSearchParams } from "expo-router"; -import { useAtom } from "jotai"; -import { useMemo, useState } from "react"; -import { FlatList, TouchableOpacity, View } from "react-native"; - -export default function page() { - const searchParams = useLocalSearchParams(); - const { collectionId } = searchParams as { collectionId: string }; - - const [api] = useAtom(apiAtom); - const [user] = useAtom(userAtom); - - const { data: collection } = useQuery({ - queryKey: ["collection", collectionId], - queryFn: async () => { - if (!api) return null; - const response = await getItemsApi(api).getItems({ - userId: user?.Id, - ids: [collectionId], - }); - const data = response.data.Items?.[0]; - return data; - }, - enabled: !!api && !!user?.Id && !!collectionId, - staleTime: 0, - }); - - const [startIndex, setStartIndex] = useState(0); - - const { data, isLoading, isError } = useQuery<{ - Items: BaseItemDto[]; - TotalRecordCount: number; - }>({ - queryKey: ["collection-items", collection?.Id, startIndex], - queryFn: async () => { - if (!api || !collectionId) - return { - Items: [], - TotalRecordCount: 0, - }; - - const response = await getArtistsApi(api).getArtists({ - sortBy: ["SortName"], - sortOrder: ["Ascending"], - fields: ["PrimaryImageAspectRatio", "SortName"], - imageTypeLimit: 1, - enableImageTypes: ["Primary", "Backdrop", "Banner", "Thumb"], - parentId: collectionId, - userId: user?.Id, - }); - - const data = response.data.Items; - - return { - Items: data || [], - TotalRecordCount: response.data.TotalRecordCount || 0, - }; - }, - enabled: !!collection?.Id && !!api && !!user?.Id, - }); - - const totalItems = useMemo(() => { - return data?.TotalRecordCount; - }, [data]); - - if (!data) return null; - - return ( - - Artists - - } - nestedScrollEnabled - data={data.Items} - numColumns={3} - columnWrapperStyle={{ - justifyContent: "space-between", - }} - renderItem={({ item, index }) => ( - - - {collection?.CollectionType === "movies" && ( - - )} - {collection?.CollectionType === "music" && ( - - )} - {item.Name} - {item.ProductionYear} - - - )} - keyExtractor={(item) => item.Id || ""} - /> - ); -} diff --git a/app/(auth)/(tabs)/(search)/index.tsx b/app/(auth)/(tabs)/(search)/index.tsx index 1ae0059c..373a1fbf 100644 --- a/app/(auth)/(tabs)/(search)/index.tsx +++ b/app/(auth)/(tabs)/(search)/index.tsx @@ -441,48 +441,6 @@ export default function search() { )} /> - m.Id!)} - header="Artists" - renderItem={(item: BaseItemDto) => ( - - - - - )} - /> - m.Id!)} - header="Albums" - renderItem={(item: BaseItemDto) => ( - - - - - )} - /> - m.Id!)} - header="Songs" - renderItem={(item: BaseItemDto) => ( - - - - - )} - /> )} {searchType === "Discover" && ( diff --git a/components/Chromecast.tv.tsx b/components/Chromecast.tv.tsx new file mode 100644 index 00000000..e69de29b diff --git a/components/ItemContent.tsx b/components/ItemContent.tsx index f1f3bcde..c59ccdfb 100644 --- a/components/ItemContent.tsx +++ b/components/ItemContent.tsx @@ -3,7 +3,7 @@ import { Bitrate, BitrateSelector } from "@/components/BitrateSelector"; import { DownloadSingleItem } from "@/components/DownloadItem"; import { OverviewText } from "@/components/OverviewText"; import { ParallaxScrollView } from "@/components/ParallaxPage"; -import { PlayButton } from "@/components/PlayButton"; +const PlayButton = !Platform.isTV ? require("@/components/PlayButton") : null; import { PlayedStatus } from "@/components/PlayedStatus"; import { SimilarItems } from "@/components/SimilarItems"; import { SubtitleTrackSelector } from "@/components/SubtitleTrackSelector"; @@ -29,7 +29,7 @@ import { useAtom } from "jotai"; import React, { useEffect, useMemo, useState } from "react"; import { Platform, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { Chromecast } from "./Chromecast"; +const Chromecast = !Platform.isTV ? require("./Chromecast") : null; import { ItemHeader } from "./ItemHeader"; import { ItemTechnicalDetails } from "./ItemTechnicalDetails"; import { MediaSourceSelector } from "./MediaSourceSelector"; @@ -248,12 +248,13 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo( /> )} - - + {!Platform.isTV && ( + + )} {item.Type === "Episode" && ( diff --git a/components/PlayButton.tsx b/components/PlayButton.tsx index 27a9021e..9a403be0 100644 --- a/components/PlayButton.tsx +++ b/components/PlayButton.tsx @@ -1,3 +1,4 @@ +import { Platform } from "react-native"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { itemThemeColorAtom } from "@/utils/atoms/primaryColor"; import { useSettings } from "@/utils/atoms/settings"; @@ -31,7 +32,9 @@ import Animated, { } from "react-native-reanimated"; import { Button } from "./Button"; import { SelectedOptions } from "./ItemContent"; -import { chromecastProfile } from "@/utils/profiles/chromecast"; +const chromecastProfile = !Platform.isTV + ? require("@/utils/profiles/chromecast") + : null; import * as Haptics from "@/packages/expo-haptics"; interface Props extends React.ComponentProps { diff --git a/components/common/TouchableItemRouter.tsx b/components/common/TouchableItemRouter.tsx index b1b886bd..cb6dff12 100644 --- a/components/common/TouchableItemRouter.tsx +++ b/components/common/TouchableItemRouter.tsx @@ -26,18 +26,6 @@ export const itemRouter = ( return `/(auth)/(tabs)/${from}/series/${item.Id}`; } - if (item.Type === "MusicAlbum") { - return `/(auth)/(tabs)/${from}/albums/${item.Id}`; - } - - if (item.Type === "Audio") { - return `/(auth)/(tabs)/${from}/albums/${item.AlbumId}`; - } - - if (item.Type === "MusicArtist") { - return `/(auth)/(tabs)/${from}/artists/${item.Id}`; - } - if (item.Type === "Person" || item.Type === "Actor") { return `/(auth)/(tabs)/${from}/actors/${item.Id}`; } diff --git a/components/stacks/NestedTabPageStack.tsx b/components/stacks/NestedTabPageStack.tsx index 024b1272..2cfeed1d 100644 --- a/components/stacks/NestedTabPageStack.tsx +++ b/components/stacks/NestedTabPageStack.tsx @@ -17,14 +17,7 @@ export const commonScreenOptions: ICommonScreenOptions = { headerLeft: () => , }; -const routes = [ - "actors/[actorId]", - "albums/[albumId]", - "artists/index", - "artists/[artistId]", - "items/page", - "series/[id]", -]; +const routes = ["actors/[actorId]", "items/page", "series/[id]"]; export const nestedTabPageScreenOptions: Record = Object.fromEntries(routes.map((route) => [route, commonScreenOptions])); diff --git a/utils/jellyseerr b/utils/jellyseerr index e69d160e..4401b164 160000 --- a/utils/jellyseerr +++ b/utils/jellyseerr @@ -1 +1 @@ -Subproject commit e69d160e25f0962cd77b01c861ce248050e1ad38 +Subproject commit 4401b16414af604a7372dacac326c38b18ad8555