From ab33693dd9401dce2da74cfcb094df2a0a0e5d37 Mon Sep 17 00:00:00 2001 From: sarendsen Date: Mon, 6 Jan 2025 13:25:49 +0100 Subject: [PATCH] wip --- app/(auth)/(tabs)/(home)/_layout.tsx | 2 +- app/(auth)/(tabs)/(home)/index.tsx | 52 ++-- app/(auth)/(tabs)/(home)/settings.tsx | 8 +- .../albums/[albumId].tsx | 20 +- .../collections/[collectionId].tsx | 2 +- .../jellyseerr/page.tsx | 2 +- app/(auth)/(tabs)/(libraries)/[libraryId].tsx | 2 +- app/(auth)/(tabs)/(libraries)/_layout.tsx | 2 +- app/(auth)/player/direct-player.tsx | 2 +- app/(auth)/player/music-player.tsx | 2 +- app/(auth)/player/transcoding-player.tsx | 2 +- app/_layout.tsx | 288 ++++++++++-------- bun.lockb | Bin 595756 -> 595756 bytes components/AudioTrackSelector.tsx | 2 +- components/BitrateSelector.tsx | 2 +- components/Button.tsx | 2 +- components/ContextMenu.native.ts | 0 components/ContextMenu.ts | 1 + components/DropdownMenu.native.ts | 0 components/DropdownMenu.ts | 1 + components/ItemContent.tsx | 40 +-- components/MediaSourceSelector.tsx | 2 +- components/PlayButton.tsx | 2 +- components/RoundButton.tsx | 2 +- components/SubtitleTrackSelector.tsx | 2 +- components/common/JellyseerrItemRouter.tsx | 105 ++++--- components/common/TouchableItemRouter.tsx | 2 +- components/downloads/ActiveDownloads.tsx | 11 +- components/downloads/EpisodeCard.tsx | 2 +- components/downloads/MovieCard.tsx | 2 +- components/home/LargeMovieCarousel.tsx | 2 +- components/series/SeasonDropdown.tsx | 2 +- components/settings/AudioToggles.tsx | 2 +- components/settings/DownloadSettings.tsx | 2 +- components/settings/OtherSettings.tsx | 13 +- components/settings/QuickConnect.tsx | 2 +- components/settings/StorageSettings.tsx | 2 +- components/settings/SubtitleToggles.tsx | 2 +- .../video-player/controls/AudioSlider.tsx | 9 +- .../controls/BrightnessSlider.tsx | 7 +- components/video-player/controls/Controls.tsx | 2 +- .../controls/dropdown/DropdownViewDirect.tsx | 2 +- .../dropdown/DropdownViewTranscoding.tsx | 2 +- hooks/useCreditSkipper.ts | 2 +- hooks/useImageColors.ts | 6 +- hooks/useIntroSkipper.ts | 2 +- hooks/useMarkAsPlayed.ts | 2 +- hooks/useOrientation.ts | 9 +- hooks/useOrientationSettings.ts | 5 +- hooks/useRemuxHlsToMp4.ts | 27 +- packages/expo-haptics.native.ts | 0 packages/expo-haptics.ts | 1 + packages/expo-screen-orientation.native.ts | 68 +++++ packages/expo-screen-orientation.ts | 1 + providers/DownloadProvider.tsx | 29 +- utils/OrientationLockConverter.ts | 5 +- utils/atoms/orientation.ts | 2 +- utils/atoms/settings.ts | 2 +- utils/background-tasks.ts | 5 +- 59 files changed, 474 insertions(+), 303 deletions(-) create mode 100644 components/ContextMenu.native.ts create mode 100644 components/ContextMenu.ts create mode 100644 components/DropdownMenu.native.ts create mode 100644 components/DropdownMenu.ts create mode 100644 packages/expo-haptics.native.ts create mode 100644 packages/expo-haptics.ts create mode 100644 packages/expo-screen-orientation.native.ts create mode 100644 packages/expo-screen-orientation.ts diff --git a/app/(auth)/(tabs)/(home)/_layout.tsx b/app/(auth)/(tabs)/(home)/_layout.tsx index 3509be51..5d5054b0 100644 --- a/app/(auth)/(tabs)/(home)/_layout.tsx +++ b/app/(auth)/(tabs)/(home)/_layout.tsx @@ -23,7 +23,7 @@ export default function IndexLayout() { headerShadowVisible: false, headerRight: () => ( - + {!Platform.isTV && } { router.push("/(auth)/settings"); diff --git a/app/(auth)/(tabs)/(home)/index.tsx b/app/(auth)/(tabs)/(home)/index.tsx index 0f777a45..98c1603d 100644 --- a/app/(auth)/(tabs)/(home)/index.tsx +++ b/app/(auth)/(tabs)/(home)/index.tsx @@ -27,6 +27,7 @@ import { QueryFunction, useQuery, useQueryClient } from "@tanstack/react-query"; import { useNavigation, useRouter } from "expo-router"; import { useAtomValue } from "jotai"; import { useCallback, useEffect, useMemo, useState } from "react"; +import { Platform } from "react-native"; import { ActivityIndicator, RefreshControl, @@ -64,30 +65,33 @@ export default function index() { const [isConnected, setIsConnected] = useState(null); const [loadingRetry, setLoadingRetry] = useState(false); - const { downloadedFiles, cleanCacheDirectory } = useDownload(); const navigation = useNavigation(); const insets = useSafeAreaInsets(); - useEffect(() => { - const hasDownloads = downloadedFiles && downloadedFiles.length > 0; - navigation.setOptions({ - headerLeft: () => ( - { - router.push("/(auth)/downloads"); - }} - className="p-2" - > - - - ), - }); - }, [downloadedFiles, navigation, router]); + if (!Platform.isTV) { + const { downloadedFiles, cleanCacheDirectory } = useDownload(); + + useEffect(() => { + const hasDownloads = downloadedFiles && downloadedFiles.length > 0; + navigation.setOptions({ + headerLeft: () => ( + { + router.push("/(auth)/downloads"); + }} + className="p-2" + > + + + ), + }); + }, [downloadedFiles, navigation, router]); + } const checkConnection = useCallback(async () => { setLoadingRetry(true); @@ -107,9 +111,11 @@ export default function index() { setIsConnected(state.isConnected); }); - cleanCacheDirectory().catch((e) => - console.error("Something went wrong cleaning cache directory") - ); + if (!Platform.isTV) { + cleanCacheDirectory().catch((e) => + console.error("Something went wrong cleaning cache directory") + ); + } return () => { unsubscribe(); }; diff --git a/app/(auth)/(tabs)/(home)/settings.tsx b/app/(auth)/(tabs)/(home)/settings.tsx index 8f6d102a..4c14f754 100644 --- a/app/(auth)/(tabs)/(home)/settings.tsx +++ b/app/(auth)/(tabs)/(home)/settings.tsx @@ -1,3 +1,4 @@ +import { Platform } from "react-native"; import { Text } from "@/components/common/Text"; import { ListGroup } from "@/components/list/ListGroup"; import { ListItem } from "@/components/list/ListItem"; @@ -13,7 +14,8 @@ import { SubtitleToggles } from "@/components/settings/SubtitleToggles"; import { UserInfo } from "@/components/settings/UserInfo"; import { useJellyfin } from "@/providers/JellyfinProvider"; import { clearLogs } from "@/utils/log"; -import * as Haptics from "expo-haptics"; +// const Haptics = !Platform.isTV ? require("expo-haptics") : null; +import * as Haptics from "@/packages/expo-haptics"; import { useNavigation, useRouter } from "expo-router"; import { useEffect } from "react"; import { ScrollView, TouchableOpacity, View } from "react-native"; @@ -26,7 +28,9 @@ export default function settings() { const onClearLogsClicked = async () => { clearLogs(); - Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + if (!Platform.isTV) { + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + } }; const navigation = useNavigation(); diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/albums/[albumId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/albums/[albumId].tsx index 565f84c8..de7d10e7 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/albums/[albumId].tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/albums/[albumId].tsx @@ -12,7 +12,7 @@ import { useQuery } from "@tanstack/react-query"; import { router, useLocalSearchParams, useNavigation } from "expo-router"; import { useAtom } from "jotai"; import { useEffect, useState } from "react"; -import { ScrollView, TouchableOpacity, View } from "react-native"; +import { Platform, ScrollView, TouchableOpacity, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; export default function page() { @@ -28,15 +28,17 @@ export default function page() { const navigation = useNavigation(); - useEffect(() => { - navigation.setOptions({ - headerRight: () => ( - - - - ), + if (!Platform.isTV) { + useEffect(() => { + navigation.setOptions({ + headerRight: () => ( + + + + ), + }); }); - }); + } const { data: album } = useQuery({ queryKey: ["album", albumId, artistId], diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/collections/[collectionId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/collections/[collectionId].tsx index 4c2b72ae..6a840acb 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/collections/[collectionId].tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/collections/[collectionId].tsx @@ -29,7 +29,7 @@ import { import { FlashList } from "@shopify/flash-list"; import { useInfiniteQuery, useQuery } from "@tanstack/react-query"; import { useLocalSearchParams, useNavigation } from "expo-router"; -import * as ScreenOrientation from "expo-screen-orientation"; +import * as ScreenOrientation from "@/packages/expo-screen-orientation"; import { useAtom } from "jotai"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import { FlatList, View } from "react-native"; 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 b839708d..d6aae61a 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx @@ -34,7 +34,7 @@ import { IssueType, IssueTypeName, } from "@/utils/jellyseerr/server/constants/issue"; -import * as DropdownMenu from "zeego/dropdown-menu"; +import * as DropdownMenu from "@/components/DropdownMenu"; import { TvDetails } from "@/utils/jellyseerr/server/models/Tv"; import JellyseerrSeasons from "@/components/series/JellyseerrSeasons"; import { JellyserrRatings } from "@/components/Ratings"; diff --git a/app/(auth)/(tabs)/(libraries)/[libraryId].tsx b/app/(auth)/(tabs)/(libraries)/[libraryId].tsx index 7c0dbc91..5252f612 100644 --- a/app/(auth)/(tabs)/(libraries)/[libraryId].tsx +++ b/app/(auth)/(tabs)/(libraries)/[libraryId].tsx @@ -1,6 +1,6 @@ import { useInfiniteQuery, useQuery } from "@tanstack/react-query"; import { useLocalSearchParams, useNavigation } from "expo-router"; -import * as ScreenOrientation from "expo-screen-orientation"; +import * as ScreenOrientation from "@/packages/expo-screen-orientation"; import { useAtom } from "jotai"; import React, { useCallback, useEffect, useMemo } from "react"; import { FlatList, useWindowDimensions, View } from "react-native"; diff --git a/app/(auth)/(tabs)/(libraries)/_layout.tsx b/app/(auth)/(tabs)/(libraries)/_layout.tsx index 17813ed1..542d8af3 100644 --- a/app/(auth)/(tabs)/(libraries)/_layout.tsx +++ b/app/(auth)/(tabs)/(libraries)/_layout.tsx @@ -3,7 +3,7 @@ import { useSettings } from "@/utils/atoms/settings"; import { Ionicons } from "@expo/vector-icons"; import { Stack } from "expo-router"; import { Platform } from "react-native"; -import * as DropdownMenu from "zeego/dropdown-menu"; +import * as DropdownMenu from "@/components/DropdownMenu"; export default function IndexLayout() { const [settings, updateSettings] = useSettings(); diff --git a/app/(auth)/player/direct-player.tsx b/app/(auth)/player/direct-player.tsx index 4d924938..fa27fac0 100644 --- a/app/(auth)/player/direct-player.tsx +++ b/app/(auth)/player/direct-player.tsx @@ -27,7 +27,7 @@ import { getUserLibraryApi, } from "@jellyfin/sdk/lib/utils/api"; import { useQuery } from "@tanstack/react-query"; -import * as Haptics from "expo-haptics"; +import * as Haptics from "@/packages/expo-haptics"; import { useFocusEffect, useGlobalSearchParams } from "expo-router"; import { useAtomValue } from "jotai"; import React, { diff --git a/app/(auth)/player/music-player.tsx b/app/(auth)/player/music-player.tsx index eca16b4c..5419cafd 100644 --- a/app/(auth)/player/music-player.tsx +++ b/app/(auth)/player/music-player.tsx @@ -17,7 +17,7 @@ import { getUserLibraryApi, } from "@jellyfin/sdk/lib/utils/api"; import { useQuery } from "@tanstack/react-query"; -import * as Haptics from "expo-haptics"; +import * as Haptics from "@/packages/expo-haptics"; import { Image } from "expo-image"; import { useFocusEffect, useLocalSearchParams } from "expo-router"; import { useAtomValue } from "jotai"; diff --git a/app/(auth)/player/transcoding-player.tsx b/app/(auth)/player/transcoding-player.tsx index bcb9a6e4..06b78722 100644 --- a/app/(auth)/player/transcoding-player.tsx +++ b/app/(auth)/player/transcoding-player.tsx @@ -20,7 +20,7 @@ import { getUserLibraryApi, } from "@jellyfin/sdk/lib/utils/api"; import { useQuery } from "@tanstack/react-query"; -import * as Haptics from "expo-haptics"; +import * as Haptics from "@/packages/expo-haptics"; import { useFocusEffect, useLocalSearchParams } from "expo-router"; import { useAtomValue } from "jotai"; import React, { diff --git a/app/_layout.tsx b/app/_layout.tsx index 23512523..57b0e1e0 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,4 +1,5 @@ import "@/augmentations"; +import { Platform } from "react-native"; import { Text } from "@/components/common/Text"; import { DownloadProvider } from "@/providers/DownloadProvider"; import { @@ -18,23 +19,28 @@ import { cancelJobById, getAllJobsByDeviceId } from "@/utils/optimize-server"; import { ActionSheetProvider } from "@expo/react-native-action-sheet"; import { BottomSheetModalProvider } from "@gorhom/bottom-sheet"; import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client"; -import { - checkForExistingDownloads, - completeHandler, - download, -} from "@kesha-antonov/react-native-background-downloader"; +// import { +// checkForExistingDownloads, +// completeHandler, +// download, +// } from "@kesha-antonov/react-native-background-downloader"; +const BackGroundDownloader = !Platform.isTV + ? require("@kesha-antonov/react-native-background-downloader") + : null; import { DarkTheme, ThemeProvider } from "@react-navigation/native"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import * as BackgroundFetch from "expo-background-fetch"; +const BackgroundFetch = !Platform.isTV + ? require("expo-background-fetch") + : null; import * as FileSystem from "expo-file-system"; import { useFonts } from "expo-font"; import { useKeepAwake } from "expo-keep-awake"; import * as Linking from "expo-linking"; -import * as Notifications from "expo-notifications"; +const Notifications = !Platform.isTV ? require("expo-notifications") : null; import { router, Stack } from "expo-router"; -import * as ScreenOrientation from "expo-screen-orientation"; +import * as ScreenOrientation from "@/packages/expo-screen-orientation"; import * as SplashScreen from "expo-splash-screen"; -import * as TaskManager from "expo-task-manager"; +const TaskManager = !Platform.isTV ? require("expo-task-manager") : null; import { Provider as JotaiProvider, useAtom } from "jotai"; import { useEffect, useRef } from "react"; import { Appearance, AppState, TouchableOpacity } from "react-native"; @@ -45,15 +51,19 @@ import { Toaster } from "sonner-native"; SplashScreen.preventAutoHideAsync(); -Notifications.setNotificationHandler({ - handleNotification: async () => ({ - shouldShowAlert: true, - shouldPlaySound: true, - shouldSetBadge: false, - }), -}); +if (!Platform.isTV) { + Notifications.setNotificationHandler({ + handleNotification: async () => ({ + shouldShowAlert: true, + shouldPlaySound: true, + shouldSetBadge: false, + }), + }); +} function useNotificationObserver() { + if (Platform.isTV) return; + useEffect(() => { let isMounted = true; @@ -84,99 +94,101 @@ function useNotificationObserver() { }, []); } -TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => { - console.log("TaskManager ~ trigger"); +if (!Platform.isTV) { + TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => { + console.log("TaskManager ~ trigger"); - const now = Date.now(); + const now = Date.now(); - const settingsData = storage.getString("settings"); + const settingsData = storage.getString("settings"); - if (!settingsData) return BackgroundFetch.BackgroundFetchResult.NoData; + if (!settingsData) return BackgroundFetch.BackgroundFetchResult.NoData; - const settings: Partial = JSON.parse(settingsData); - const url = settings?.optimizedVersionsServerUrl; + const settings: Partial = JSON.parse(settingsData); + const url = settings?.optimizedVersionsServerUrl; - if (!settings?.autoDownload || !url) - return BackgroundFetch.BackgroundFetchResult.NoData; + if (!settings?.autoDownload || !url) + return BackgroundFetch.BackgroundFetchResult.NoData; - const token = getTokenFromStorage(); - const deviceId = getOrSetDeviceId(); - const baseDirectory = FileSystem.documentDirectory; + const token = getTokenFromStorage(); + const deviceId = getOrSetDeviceId(); + const baseDirectory = FileSystem.documentDirectory; - if (!token || !deviceId || !baseDirectory) - return BackgroundFetch.BackgroundFetchResult.NoData; + if (!token || !deviceId || !baseDirectory) + return BackgroundFetch.BackgroundFetchResult.NoData; - const jobs = await getAllJobsByDeviceId({ - deviceId, - authHeader: token, - url, - }); + const jobs = await getAllJobsByDeviceId({ + deviceId, + authHeader: token, + url, + }); - console.log("TaskManager ~ Active jobs: ", jobs.length); + console.log("TaskManager ~ Active jobs: ", jobs.length); - for (let job of jobs) { - if (job.status === "completed") { - const downloadUrl = url + "download/" + job.id; - const tasks = await checkForExistingDownloads(); + for (let job of jobs) { + if (job.status === "completed") { + const downloadUrl = url + "download/" + job.id; + const tasks = await BackGroundDownloader.checkForExistingDownloads(); - if (tasks.find((task) => task.id === job.id)) { - console.log("TaskManager ~ Download already in progress: ", job.id); - continue; + if (tasks.find((task) => task.id === job.id)) { + console.log("TaskManager ~ Download already in progress: ", job.id); + continue; + } + + BackGroundDownloader.download({ + id: job.id, + url: downloadUrl, + destination: `${baseDirectory}${job.item.Id}.mp4`, + headers: { + Authorization: token, + }, + }) + .begin(() => { + console.log("TaskManager ~ Download started: ", job.id); + }) + .done(() => { + console.log("TaskManager ~ Download completed: ", job.id); + saveDownloadedItemInfo(job.item); + BackGroundDownloader.completeHandler(job.id); + cancelJobById({ + authHeader: token, + id: job.id, + url: url, + }); + Notifications.scheduleNotificationAsync({ + content: { + title: job.item.Name, + body: "Download completed", + data: { + url: `/downloads`, + }, + }, + trigger: null, + }); + }) + .error((error) => { + console.log("TaskManager ~ Download error: ", job.id, error); + completeHandler(job.id); + Notifications.scheduleNotificationAsync({ + content: { + title: job.item.Name, + body: "Download failed", + data: { + url: `/downloads`, + }, + }, + trigger: null, + }); + }); } - - download({ - id: job.id, - url: downloadUrl, - destination: `${baseDirectory}${job.item.Id}.mp4`, - headers: { - Authorization: token, - }, - }) - .begin(() => { - console.log("TaskManager ~ Download started: ", job.id); - }) - .done(() => { - console.log("TaskManager ~ Download completed: ", job.id); - saveDownloadedItemInfo(job.item); - completeHandler(job.id); - cancelJobById({ - authHeader: token, - id: job.id, - url: url, - }); - Notifications.scheduleNotificationAsync({ - content: { - title: job.item.Name, - body: "Download completed", - data: { - url: `/downloads`, - }, - }, - trigger: null, - }); - }) - .error((error) => { - console.log("TaskManager ~ Download error: ", job.id, error); - completeHandler(job.id); - Notifications.scheduleNotificationAsync({ - content: { - title: job.item.Name, - body: "Download failed", - data: { - url: `/downloads`, - }, - }, - trigger: null, - }); - }); } - } - console.log(`Auto download started: ${new Date(now).toISOString()}`); + console.log(`Auto download started: ${new Date(now).toISOString()}`); - // Be sure to return the successful result type! - return BackgroundFetch.BackgroundFetchResult.NewData; -}); + // Be sure to return the successful result type! + return BackgroundFetch.BackgroundFetchResult.NewData; + }); +} const checkAndRequestPermissions = async () => { try { @@ -250,55 +262,61 @@ function Layout() { const [orientation, setOrientation] = useAtom(orientationAtom); useKeepAwake(); - useNotificationObserver(); - - useEffect(() => { - checkAndRequestPermissions(); - }, []); - - useEffect(() => { - if (settings?.autoRotate === true) - ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.DEFAULT); - else - ScreenOrientation.lockAsync( - ScreenOrientation.OrientationLock.PORTRAIT_UP - ); - }, [settings]); const appState = useRef(AppState.currentState); - useEffect(() => { - const subscription = AppState.addEventListener("change", (nextAppState) => { - if ( - appState.current.match(/inactive|background/) && - nextAppState === "active" - ) { - checkForExistingDownloads(); - } - }); + if (!Platform.isTV) { + useNotificationObserver(); - checkForExistingDownloads(); + useEffect(() => { + checkAndRequestPermissions(); + }, []); - return () => { - subscription.remove(); - }; - }, []); + useEffect(() => { + if (settings?.autoRotate === true) + ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.DEFAULT); + else + ScreenOrientation.lockAsync( + ScreenOrientation.OrientationLock.PORTRAIT_UP + ); + }, [settings]); - useEffect(() => { - const subscription = ScreenOrientation.addOrientationChangeListener( - (event) => { - setOrientation(event.orientationInfo.orientation); - } - ); + useEffect(() => { + const subscription = AppState.addEventListener( + "change", + (nextAppState) => { + if ( + appState.current.match(/inactive|background/) && + nextAppState === "active" + ) { + BackGroundDownloader.checkForExistingDownloads(); + } + } + ); - ScreenOrientation.getOrientationAsync().then((initialOrientation) => { - setOrientation(initialOrientation); - }); + BackGroundDownloader.checkForExistingDownloads(); - return () => { - ScreenOrientation.removeOrientationChangeListener(subscription); - }; - }, []); + return () => { + subscription.remove(); + }; + }, []); + + useEffect(() => { + const subscription = ScreenOrientation.addOrientationChangeListener( + (event) => { + setOrientation(event.orientationInfo.orientation); + } + ); + + ScreenOrientation.getOrientationAsync().then((initialOrientation) => { + setOrientation(initialOrientation); + }); + + return () => { + ScreenOrientation.removeOrientationChangeListener(subscription); + }; + }, []); + } const url = Linking.useURL(); diff --git a/bun.lockb b/bun.lockb index 902c36ca2d70d6d6403a72b52bc4bdf3cefaf46d..ec0c8c95373d277e90414750a445d69fc4af61ad 100755 GIT binary patch delta 5173 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 I|6u-q0J42L`v3p{ delta 5247 zcmeI0d2m(b8OG0dZoZQo1PsuWKmsKV2_TYmnd z*aGALFP2@QMQRm_cx*>H+)h6jFsgZ|=Dgr_=t`KfNDnX6UGrx?At2$92l=g~J9bPDuE{N?B*}sX{4l%Y!D32jl z<}OkAcMZM+{Kqz5RN%?UE>M)>C7${2(o*G!$6tSer*Od1?1KE_+{Hy)Z@#;zY)Q7F zoGBJcDC+M=eZ;Ej%EE%Ayi!FeC=p5+GBzQD2eJaOGolBP2NZ$G11ZPAICg^P0S4Rg z0k{qyTPBnU#0W$#cL|X@EG=77=E)zZ%q#a#AiHSUfSl~HrS8B8=cr2S%!J*+@d|$j z z)=~9bC6rDmyb`hZ@vz><`yD?&E<+9d&4q(2KPlh%tH%$|KJ<>fb#YC?L@QyU2AREu8XgBvbv#L~P^` z^Sef;Jc?ADdD89FzeZ{}>SMb3hMtPEH3UwWt{g+JfJ^rGn`x-8d#Gg*Mso|))mp>1 z%+TX;+EU>ZQ+$vb0+RsaFui$(B)DYrwhD7m@_LwDimX2D|hFyDP}JbRSPGxRUu(%}@-i%oq3hk6hk z4hdW#9IqNm&NcK`;dl*1tGfcni+~kfYKX3MzfcU7Q*ng-qmw6*^lylM7s=M51q2Q!|DLLGVCrCiB4J;$leT}f$W z=A@!&_Va-{2gE?urEvC_-Md+4E+=w@1Sb>&Khd`DoV@2Vc3s<{&|$1Bb-)c zxp>lDrQ(0@bNsvUk0bl5cD#-l1b@r+w-E*W9h={^`8RgFkLW=Dw{|{XB!l6D5t+9` zpn{(vyGi={Ba`~Bd`Aj=Mh7+7I+m2aw21n-e5Cwg4QFQIc z?Jk1ln9mro!1l$6ec%aET0L~NCd69bERv#rJYZOr9}n37_kh*mfZ>oi4wqATm1B(P zL%u4<49cx?bfB1OfPPd`4G>-paE?KLit+-SW>Dh=7(h)7D!l+Hs{!K4yBeVHYJg@2 ziIlho;5>uvYXAn)Ck*P>0E}1*kV0G60;H}5xXZvGR}H{b28U_@o}xPpcGdt)s|9e< zfm(o3wE(gXAe|=F0o-G7g27M{^#F(K0NnKeE;_~_qaGk^9l+C+y$&E`9l%=*Mo_2q z0BP`0Kzu_oMSMCqBa7YW>B*cU>r3usN4vUvI$@Uc{c&{ z-2~9gU=k%Z0GwyAy#ZhfeZru=0bs;tfN8XKGeGKQfV&K4kZTLTRR)K)0L-L240dh- zn6?#Q79H3MFlsA+Yy_A?lNtf;F*w0sE(sIha3g@*1jwdi3^GiBux$YIDSI10$Tont z7`Um^c7Qh+ly3)EL_P+&+W}&B04%1G9RT4w0M0SUrziqA&7g(=3aN=fB>|-D1n`h| zCqUnw0L=_a==ZAt&NJwNQ)neT?#i>DmZS&_ZVL^k<+ZjO}0xt_Y@SxSv>h!?z zFO|7$eJrQ+ZyX-Bb@wI&S@$N$$k=qgHSi_>0j{@vuZV$a!T2YT!I$4ysNQC(&G<6B zktS8hEf32lS;29yic>3Fo@n-=Di)F2-?I`Gqm3I#!$mEI_C z$fyjY`Cgm~Jq4vfPG~Un7!+)c-YGYziF{j}291NpLnEMJPhVA zzX5(K;yxU`WJG?09SDtrTsGsF0zVlViS9PRPeOVO^ei+Q;_@S*K@gY!1)_Pl2vTM}@B*#Hx8KGUg!71aBo(OTw zf#yN8piC$Ong+F5zTJal2lI- zZ9$9-eBM!JZ8^68kC#2%GU^Y+Au6Jeo3(Ite-Yhl)~X`|Uz!|K$B*-F2;OU##JlLV zBHH#j+C1W+)1PY#)icGEa0N9BO6b`u+WV1#Pe2_feMWb8zgpco#zah6i7lh#tJ>Je zz{jDfSN2WI{@vl5D1kA=;SFsPg;K@E5r#v4`jo4cZW^@`TgTTAl# z4J12fM$^QEKO77n{wQaE1@*k9g+~Uy7`@|eSYP{Iy!oia@d}!J?FVb09N34O*R=;<=x(|bbtq+4j?jNMn j?%*C2+A}1GhK&vmwk{kAnHxqQycF`CHRV*uh3WqWG$}~0 diff --git a/components/AudioTrackSelector.tsx b/components/AudioTrackSelector.tsx index 75fd659c..11a895fc 100644 --- a/components/AudioTrackSelector.tsx +++ b/components/AudioTrackSelector.tsx @@ -1,7 +1,7 @@ import { MediaSourceInfo } from "@jellyfin/sdk/lib/generated-client/models"; import { useMemo } from "react"; import { TouchableOpacity, View } from "react-native"; -import * as DropdownMenu from "zeego/dropdown-menu"; +import * as DropdownMenu from "@/components/DropdownMenu"; import { Text } from "./common/Text"; interface Props extends React.ComponentProps { diff --git a/components/BitrateSelector.tsx b/components/BitrateSelector.tsx index 0f1bd28b..cb284f4b 100644 --- a/components/BitrateSelector.tsx +++ b/components/BitrateSelector.tsx @@ -1,5 +1,5 @@ import { TouchableOpacity, View } from "react-native"; -import * as DropdownMenu from "zeego/dropdown-menu"; +import * as DropdownMenu from "@/components/DropdownMenu"; import { Text } from "./common/Text"; import { useMemo } from "react"; diff --git a/components/Button.tsx b/components/Button.tsx index fb685f4c..a1005e49 100644 --- a/components/Button.tsx +++ b/components/Button.tsx @@ -1,4 +1,4 @@ -import * as Haptics from "expo-haptics"; +import * as Haptics from "@/packages/expo-haptics"; import React, { PropsWithChildren, ReactNode, useMemo } from "react"; import { Text, TouchableOpacity, View } from "react-native"; import { Loader } from "./Loader"; diff --git a/components/ContextMenu.native.ts b/components/ContextMenu.native.ts new file mode 100644 index 00000000..e69de29b diff --git a/components/ContextMenu.ts b/components/ContextMenu.ts new file mode 100644 index 00000000..4be160e5 --- /dev/null +++ b/components/ContextMenu.ts @@ -0,0 +1 @@ +export * as ContextMenu from "zeego/context-menu"; diff --git a/components/DropdownMenu.native.ts b/components/DropdownMenu.native.ts new file mode 100644 index 00000000..e69de29b diff --git a/components/DropdownMenu.ts b/components/DropdownMenu.ts new file mode 100644 index 00000000..c457d736 --- /dev/null +++ b/components/DropdownMenu.ts @@ -0,0 +1 @@ +export * as DropdownMenu from "zeego/dropdown-menu"; diff --git a/components/ItemContent.tsx b/components/ItemContent.tsx index 402f3171..f1f3bcde 100644 --- a/components/ItemContent.tsx +++ b/components/ItemContent.tsx @@ -24,10 +24,10 @@ import { } from "@jellyfin/sdk/lib/generated-client/models"; import { Image } from "expo-image"; import { useNavigation } from "expo-router"; -import * as ScreenOrientation from "expo-screen-orientation"; +import * as ScreenOrientation from "@/packages/expo-screen-orientation"; import { useAtom } from "jotai"; import React, { useEffect, useMemo, useState } from "react"; -import { View } from "react-native"; +import { Platform, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Chromecast } from "./Chromecast"; import { ItemHeader } from "./ItemHeader"; @@ -81,23 +81,25 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo( defaultMediaSource, ]); - useEffect(() => { - navigation.setOptions({ - headerRight: () => - item && ( - - - {item.Type !== "Program" && ( - - - - - - )} - - ), - }); - }, [item]); + if (!Platform.isTV) { + useEffect(() => { + navigation.setOptions({ + headerRight: () => + item && ( + + + {item.Type !== "Program" && ( + + + + + + )} + + ), + }); + }, [item]); + } useEffect(() => { if (orientation !== ScreenOrientation.OrientationLock.PORTRAIT_UP) diff --git a/components/MediaSourceSelector.tsx b/components/MediaSourceSelector.tsx index 34f02fd9..4b55a352 100644 --- a/components/MediaSourceSelector.tsx +++ b/components/MediaSourceSelector.tsx @@ -5,7 +5,7 @@ import { } from "@jellyfin/sdk/lib/generated-client/models"; import { useEffect, useMemo } from "react"; import { TouchableOpacity, View } from "react-native"; -import * as DropdownMenu from "zeego/dropdown-menu"; +import * as DropdownMenu from "@/components/DropdownMenu"; import { Text } from "./common/Text"; import { convertBitsToMegabitsOrGigabits } from "@/utils/bToMb"; diff --git a/components/PlayButton.tsx b/components/PlayButton.tsx index e5c5dd87..27a9021e 100644 --- a/components/PlayButton.tsx +++ b/components/PlayButton.tsx @@ -32,7 +32,7 @@ import Animated, { import { Button } from "./Button"; import { SelectedOptions } from "./ItemContent"; import { chromecastProfile } from "@/utils/profiles/chromecast"; -import * as Haptics from "expo-haptics"; +import * as Haptics from "@/packages/expo-haptics"; interface Props extends React.ComponentProps { item: BaseItemDto; diff --git a/components/RoundButton.tsx b/components/RoundButton.tsx index 049c5ed0..e16613a1 100644 --- a/components/RoundButton.tsx +++ b/components/RoundButton.tsx @@ -6,7 +6,7 @@ import { TouchableOpacity, TouchableOpacityProps, } from "react-native"; -import * as Haptics from "expo-haptics"; +import * as Haptics from "@/packages/expo-haptics"; interface Props extends TouchableOpacityProps { onPress?: () => void; diff --git a/components/SubtitleTrackSelector.tsx b/components/SubtitleTrackSelector.tsx index 087363a3..8e05532b 100644 --- a/components/SubtitleTrackSelector.tsx +++ b/components/SubtitleTrackSelector.tsx @@ -2,7 +2,7 @@ import { tc } from "@/utils/textTools"; import { MediaSourceInfo } from "@jellyfin/sdk/lib/generated-client/models"; import { useMemo } from "react"; import { Platform, TouchableOpacity, View } from "react-native"; -import * as DropdownMenu from "zeego/dropdown-menu"; +import * as DropdownMenu from "@/components/DropdownMenu"; import { Text } from "./common/Text"; import { SubtitleHelper } from "@/utils/SubtitleHelper"; diff --git a/components/common/JellyseerrItemRouter.tsx b/components/common/JellyseerrItemRouter.tsx index 90f9c336..198d5a45 100644 --- a/components/common/JellyseerrItemRouter.tsx +++ b/components/common/JellyseerrItemRouter.tsx @@ -1,11 +1,14 @@ -import {useRouter, useSegments} from "expo-router"; -import React, {PropsWithChildren, useCallback, useMemo} from "react"; -import {TouchableOpacity, TouchableOpacityProps} from "react-native"; -import * as ContextMenu from "zeego/context-menu"; -import {MovieResult, TvResult} from "@/utils/jellyseerr/server/models/Search"; -import {useJellyseerr} from "@/hooks/useJellyseerr"; -import {hasPermission, Permission} from "@/utils/jellyseerr/server/lib/permissions"; -import {MediaType} from "@/utils/jellyseerr/server/constants/media"; +import { useRouter, useSegments } from "expo-router"; +import React, { PropsWithChildren, useCallback, useMemo } from "react"; +import { TouchableOpacity, TouchableOpacityProps } from "react-native"; +import * as ContextMenu from "@/components/ContextMenu"; +import { MovieResult, TvResult } from "@/utils/jellyseerr/server/models/Search"; +import { useJellyseerr } from "@/hooks/useJellyseerr"; +import { + hasPermission, + Permission, +} from "@/utils/jellyseerr/server/lib/permissions"; +import { MediaType } from "@/utils/jellyseerr/server/constants/media"; interface Props extends TouchableOpacityProps { result: MovieResult | TvResult; @@ -26,26 +29,27 @@ export const TouchableJellyseerrRouter: React.FC> = ({ }) => { const router = useRouter(); const segments = useSegments(); - const {jellyseerrApi, jellyseerrUser, requestMedia} = useJellyseerr() + const { jellyseerrApi, jellyseerrUser, requestMedia } = useJellyseerr(); const from = segments[2]; const autoApprove = useMemo(() => { - return jellyseerrUser && hasPermission( - Permission.AUTO_APPROVE, - jellyseerrUser.permissions, - {type: 'or'} - ) - }, [jellyseerrApi, jellyseerrUser]) + return ( + jellyseerrUser && + hasPermission(Permission.AUTO_APPROVE, jellyseerrUser.permissions, { + type: "or", + }) + ); + }, [jellyseerrApi, jellyseerrUser]); - const request = useCallback(() => + const request = useCallback( + () => requestMedia(mediaTitle, { mediaId: result.id, - mediaType: result.mediaType - } - ), + mediaType: result.mediaType, + }), [jellyseerrApi, result] - ) + ); if (from === "(home)" || from === "(search)" || from === "(libraries)") return ( @@ -55,7 +59,16 @@ export const TouchableJellyseerrRouter: React.FC> = ({ { // @ts-ignore - router.push({pathname: `/(auth)/(tabs)/${from}/jellyseerr/page`, params: {...result, mediaTitle, releaseYear, canRequest, posterSrc}}); + router.push({ + pathname: `/(auth)/(tabs)/${from}/jellyseerr/page`, + params: { + ...result, + mediaTitle, + releaseYear, + canRequest, + posterSrc, + }, + }); }} {...props} > @@ -71,31 +84,33 @@ export const TouchableJellyseerrRouter: React.FC> = ({ > Actions {canRequest && result.mediaType === MediaType.MOVIE && ( - { - if (autoApprove) { - request() - } + { + if (autoApprove) { + request(); + } + }} + shouldDismissMenuOnSelect + > + + Request + + - Request - - - )} + androidIconName="download" + /> + + )} diff --git a/components/common/TouchableItemRouter.tsx b/components/common/TouchableItemRouter.tsx index c13e9821..b1b886bd 100644 --- a/components/common/TouchableItemRouter.tsx +++ b/components/common/TouchableItemRouter.tsx @@ -6,7 +6,7 @@ import { import { useRouter, useSegments } from "expo-router"; import { PropsWithChildren } from "react"; import { TouchableOpacity, TouchableOpacityProps } from "react-native"; -import * as ContextMenu from "zeego/context-menu"; +import * as ContextMenu from "@/components/ContextMenu"; interface Props extends TouchableOpacityProps { item: BaseItemDto; diff --git a/components/downloads/ActiveDownloads.tsx b/components/downloads/ActiveDownloads.tsx index 556ae8c7..14b7de15 100644 --- a/components/downloads/ActiveDownloads.tsx +++ b/components/downloads/ActiveDownloads.tsx @@ -5,13 +5,18 @@ import { useSettings } from "@/utils/atoms/settings"; import { JobStatus } from "@/utils/optimize-server"; import { formatTimeString } from "@/utils/time"; import { Ionicons } from "@expo/vector-icons"; -import { checkForExistingDownloads } from "@kesha-antonov/react-native-background-downloader"; +// import { checkForExistingDownloads } from "@kesha-antonov/react-native-background-downloader"; +const BackGroundDownloader = !Platform.isTV + ? require("@kesha-antonov/react-native-background-downloader") + : null; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useRouter } from "expo-router"; -import { FFmpegKit } from "ffmpeg-kit-react-native"; +// import { FFmpegKit } from "ffmpeg-kit-react-native"; +const FFmpegKit = !Platform.isTV ? require("ffmpeg-kit-react-native") : null; import { useAtom } from "jotai"; import { ActivityIndicator, + Platform, TouchableOpacity, TouchableOpacityProps, View, @@ -64,7 +69,7 @@ const DownloadCard = ({ process, ...props }: DownloadCardProps) => { if (settings?.downloadMethod === "optimized") { try { - const tasks = await checkForExistingDownloads(); + const tasks = await BackGroundDownloader.checkForExistingDownloads(); for (const task of tasks) { if (task.id === id) { task.stop(); diff --git a/components/downloads/EpisodeCard.tsx b/components/downloads/EpisodeCard.tsx index e8387da5..14b0eefa 100644 --- a/components/downloads/EpisodeCard.tsx +++ b/components/downloads/EpisodeCard.tsx @@ -1,5 +1,5 @@ import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; -import * as Haptics from "expo-haptics"; +import * as Haptics from "@/packages/expo-haptics"; import React, { useCallback, useMemo } from "react"; import { TouchableOpacity, TouchableOpacityProps, View } from "react-native"; import { diff --git a/components/downloads/MovieCard.tsx b/components/downloads/MovieCard.tsx index 3073bd0a..28af9cb1 100644 --- a/components/downloads/MovieCard.tsx +++ b/components/downloads/MovieCard.tsx @@ -3,7 +3,7 @@ import { useActionSheet, } from "@expo/react-native-action-sheet"; import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; -import * as Haptics from "expo-haptics"; +import * as Haptics from "@/packages/expo-haptics"; import React, { useCallback, useMemo } from "react"; import { TouchableOpacity, View } from "react-native"; diff --git a/components/home/LargeMovieCarousel.tsx b/components/home/LargeMovieCarousel.tsx index a22c586f..7d310c9b 100644 --- a/components/home/LargeMovieCarousel.tsx +++ b/components/home/LargeMovieCarousel.tsx @@ -22,7 +22,7 @@ import { itemRouter, TouchableItemRouter } from "../common/TouchableItemRouter"; import { Loader } from "../Loader"; import { Gesture, GestureDetector } from "react-native-gesture-handler"; import { useRouter, useSegments } from "expo-router"; -import * as Haptics from "expo-haptics"; +import * as Haptics from "@/packages/expo-haptics"; interface Props extends ViewProps {} diff --git a/components/series/SeasonDropdown.tsx b/components/series/SeasonDropdown.tsx index 5c333f2e..dbdbe679 100644 --- a/components/series/SeasonDropdown.tsx +++ b/components/series/SeasonDropdown.tsx @@ -1,7 +1,7 @@ import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import { useEffect, useMemo } from "react"; import { TouchableOpacity, View } from "react-native"; -import * as DropdownMenu from "zeego/dropdown-menu"; +import * as DropdownMenu from "@/components/DropdownMenu"; import { Text } from "../common/Text"; type Props = { diff --git a/components/settings/AudioToggles.tsx b/components/settings/AudioToggles.tsx index 62aea437..8a185c77 100644 --- a/components/settings/AudioToggles.tsx +++ b/components/settings/AudioToggles.tsx @@ -1,5 +1,5 @@ import { TouchableOpacity, View, ViewProps } from "react-native"; -import * as DropdownMenu from "zeego/dropdown-menu"; +import * as DropdownMenu from "@/components/DropdownMenu"; import { Text } from "../common/Text"; import { useMedia } from "./MediaContext"; import { Switch } from "react-native-gesture-handler"; diff --git a/components/settings/DownloadSettings.tsx b/components/settings/DownloadSettings.tsx index f330dc04..fd87c098 100644 --- a/components/settings/DownloadSettings.tsx +++ b/components/settings/DownloadSettings.tsx @@ -6,7 +6,7 @@ import { useQueryClient } from "@tanstack/react-query"; import { useRouter } from "expo-router"; import React from "react"; import { Switch, TouchableOpacity, View } from "react-native"; -import * as DropdownMenu from "zeego/dropdown-menu"; +import * as DropdownMenu from "@/components/DropdownMenu"; import { Text } from "../common/Text"; import { ListGroup } from "../list/ListGroup"; import { ListItem } from "../list/ListItem"; diff --git a/components/settings/OtherSettings.tsx b/components/settings/OtherSettings.tsx index d280a167..5e750e40 100644 --- a/components/settings/OtherSettings.tsx +++ b/components/settings/OtherSettings.tsx @@ -1,3 +1,4 @@ +import { Platform } from "react-native"; import { ScreenOrientationEnum, useSettings } from "@/utils/atoms/settings"; import { BACKGROUND_FETCH_TASK, @@ -5,13 +6,15 @@ import { unregisterBackgroundFetchAsync, } from "@/utils/background-tasks"; import { Ionicons } from "@expo/vector-icons"; -import * as BackgroundFetch from "expo-background-fetch"; -import * as ScreenOrientation from "expo-screen-orientation"; -import * as TaskManager from "expo-task-manager"; +const BackgroundFetch = !Platform.isTV + ? require("expo-background-fetch") + : null; +import * as ScreenOrientation from "@/packages/expo-screen-orientation"; +const TaskManager = !Platform.isTV ? require("expo-task-manager") : null; import React, { useEffect } from "react"; import { Linking, Switch, TouchableOpacity, ViewProps } from "react-native"; import { toast } from "sonner-native"; -import * as DropdownMenu from "zeego/dropdown-menu"; +import * as DropdownMenu from "@/components/DropdownMenu"; import { Text } from "../common/Text"; import { ListGroup } from "../list/ListGroup"; import { ListItem } from "../list/ListItem"; @@ -25,6 +28,8 @@ export const OtherSettings: React.FC = () => { * Background task *******************/ const checkStatusAsync = async () => { + if (Platform.isTV) return; + await BackgroundFetch.getStatusAsync(); return await TaskManager.isTaskRegisteredAsync(BACKGROUND_FETCH_TASK); }; diff --git a/components/settings/QuickConnect.tsx b/components/settings/QuickConnect.tsx index 9efbec43..78fe7d61 100644 --- a/components/settings/QuickConnect.tsx +++ b/components/settings/QuickConnect.tsx @@ -7,7 +7,7 @@ import { BottomSheetView, } from "@gorhom/bottom-sheet"; import { getQuickConnectApi } from "@jellyfin/sdk/lib/utils/api"; -import * as Haptics from "expo-haptics"; +import * as Haptics from "@/packages/expo-haptics"; import { useAtom } from "jotai"; import React, { useCallback, useRef, useState } from "react"; import { Alert, View, ViewProps } from "react-native"; diff --git a/components/settings/StorageSettings.tsx b/components/settings/StorageSettings.tsx index 5b693acd..9f682e50 100644 --- a/components/settings/StorageSettings.tsx +++ b/components/settings/StorageSettings.tsx @@ -4,7 +4,7 @@ import { useDownload } from "@/providers/DownloadProvider"; import { clearLogs } from "@/utils/log"; import { useQuery } from "@tanstack/react-query"; import * as FileSystem from "expo-file-system"; -import * as Haptics from "expo-haptics"; +import * as Haptics from "@/packages/expo-haptics"; import { View } from "react-native"; import * as Progress from "react-native-progress"; import { toast } from "sonner-native"; diff --git a/components/settings/SubtitleToggles.tsx b/components/settings/SubtitleToggles.tsx index 66c514b1..24e3d173 100644 --- a/components/settings/SubtitleToggles.tsx +++ b/components/settings/SubtitleToggles.tsx @@ -1,5 +1,5 @@ import { TouchableOpacity, View, ViewProps } from "react-native"; -import * as DropdownMenu from "zeego/dropdown-menu"; +import * as DropdownMenu from "@/components/DropdownMenu"; import { Text } from "../common/Text"; import { useMedia } from "./MediaContext"; import { Switch } from "react-native-gesture-handler"; diff --git a/components/video-player/controls/AudioSlider.tsx b/components/video-player/controls/AudioSlider.tsx index 65ab7b9f..90326204 100644 --- a/components/video-player/controls/AudioSlider.tsx +++ b/components/video-player/controls/AudioSlider.tsx @@ -1,8 +1,11 @@ import React, { useEffect, useRef } from "react"; -import { View, StyleSheet } from "react-native"; +import { View, StyleSheet, Platform } from "react-native"; import { useSharedValue } from "react-native-reanimated"; import { Slider } from "react-native-awesome-slider"; -import { VolumeManager } from "react-native-volume-manager"; +// import { VolumeManager } from "react-native-volume-manager"; +const VolumeManager = !Platform.isTV + ? require("react-native-volume-manager") + : null; import { Ionicons } from "@expo/vector-icons"; interface AudioSliderProps { @@ -10,6 +13,8 @@ interface AudioSliderProps { } const AudioSlider: React.FC = ({ setVisibility }) => { + if (Platform.isTV) return; + const volume = useSharedValue(50); // Explicitly type as number const min = useSharedValue(0); // Explicitly type as number const max = useSharedValue(100); // Explicitly type as number diff --git a/components/video-player/controls/BrightnessSlider.tsx b/components/video-player/controls/BrightnessSlider.tsx index f7b0f392..be74e9cc 100644 --- a/components/video-player/controls/BrightnessSlider.tsx +++ b/components/video-player/controls/BrightnessSlider.tsx @@ -1,12 +1,15 @@ import React, { useEffect } from "react"; -import { View, StyleSheet } from "react-native"; +import { View, StyleSheet, Platform } from "react-native"; import { useSharedValue } from "react-native-reanimated"; import { Slider } from "react-native-awesome-slider"; -import * as Brightness from "expo-brightness"; +// import * as Brightness from "expo-brightness"; +const Brightness = !Platform.isTV ? require("expo-brightness") : null; import { Ionicons } from "@expo/vector-icons"; import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons"; const BrightnessSlider = () => { + if (Platform.isTV) return; + const brightness = useSharedValue(50); const min = useSharedValue(0); const max = useSharedValue(100); diff --git a/components/video-player/controls/Controls.tsx b/components/video-player/controls/Controls.tsx index 2fd1cba3..b82455ff 100644 --- a/components/video-player/controls/Controls.tsx +++ b/components/video-player/controls/Controls.tsx @@ -29,7 +29,7 @@ import { BaseItemDto, MediaSourceInfo, } from "@jellyfin/sdk/lib/generated-client"; -import * as Haptics from "expo-haptics"; +import * as Haptics from "@/packages/expo-haptics"; import { Image } from "expo-image"; import { useLocalSearchParams, useRouter } from "expo-router"; import { useAtom } from "jotai"; diff --git a/components/video-player/controls/dropdown/DropdownViewDirect.tsx b/components/video-player/controls/dropdown/DropdownViewDirect.tsx index 28b55fa0..c75104cf 100644 --- a/components/video-player/controls/dropdown/DropdownViewDirect.tsx +++ b/components/video-player/controls/dropdown/DropdownViewDirect.tsx @@ -1,7 +1,7 @@ import React, { useMemo, useState } from "react"; import { View, TouchableOpacity } from "react-native"; import { Ionicons } from "@expo/vector-icons"; -import * as DropdownMenu from "zeego/dropdown-menu"; +import * as DropdownMenu from "@/components/DropdownMenu"; import { useControlContext } from "../contexts/ControlContext"; import { useVideoContext } from "../contexts/VideoContext"; import { EmbeddedSubtitle, ExternalSubtitle } from "../types"; diff --git a/components/video-player/controls/dropdown/DropdownViewTranscoding.tsx b/components/video-player/controls/dropdown/DropdownViewTranscoding.tsx index d57cc126..09ab8ef2 100644 --- a/components/video-player/controls/dropdown/DropdownViewTranscoding.tsx +++ b/components/video-player/controls/dropdown/DropdownViewTranscoding.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useMemo, useState } from "react"; import { View, TouchableOpacity } from "react-native"; import { Ionicons } from "@expo/vector-icons"; -import * as DropdownMenu from "zeego/dropdown-menu"; +import * as DropdownMenu from "@/components/DropdownMenu"; import { useControlContext } from "../contexts/ControlContext"; import { useVideoContext } from "../contexts/VideoContext"; import { TranscodedSubtitle } from "../types"; diff --git a/hooks/useCreditSkipper.ts b/hooks/useCreditSkipper.ts index 1430e7c9..37a82de6 100644 --- a/hooks/useCreditSkipper.ts +++ b/hooks/useCreditSkipper.ts @@ -5,7 +5,7 @@ import { apiAtom } from "@/providers/JellyfinProvider"; import { getAuthHeaders } from "@/utils/jellyfin/jellyfin"; import { writeToLog } from "@/utils/log"; import { msToSeconds, secondsToMs } from "@/utils/time"; -import * as Haptics from "expo-haptics"; +import * as Haptics from "@/packages/expo-haptics"; interface CreditTimestamps { Introduction: { diff --git a/hooks/useImageColors.ts b/hooks/useImageColors.ts index c7250c86..927e251e 100644 --- a/hooks/useImageColors.ts +++ b/hooks/useImageColors.ts @@ -10,7 +10,9 @@ import { storage } from "@/utils/mmkv"; import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client"; import { useAtom, useAtomValue } from "jotai"; import { useEffect, useMemo } from "react"; -import { getColors } from "react-native-image-colors"; +import { Platform } from "react-native"; +// import { getColors } from "react-native-image-colors"; +const getColors = !Platform.isTV ? require("react-native-image-colors") : null; /** * Custom hook to extract and manage image colors for a given item. @@ -28,6 +30,8 @@ export const useImageColors = ({ url?: string | null; disabled?: boolean; }) => { + if (Platform.isTV) return; + const api = useAtomValue(apiAtom); const [, setPrimaryColor] = useAtom(itemThemeColorAtom); diff --git a/hooks/useIntroSkipper.ts b/hooks/useIntroSkipper.ts index 15aaff05..80ea1bf9 100644 --- a/hooks/useIntroSkipper.ts +++ b/hooks/useIntroSkipper.ts @@ -5,7 +5,7 @@ import { apiAtom } from "@/providers/JellyfinProvider"; import { getAuthHeaders } from "@/utils/jellyfin/jellyfin"; import { writeToLog } from "@/utils/log"; import { msToSeconds, secondsToMs } from "@/utils/time"; -import * as Haptics from "expo-haptics"; +import * as Haptics from "@/packages/expo-haptics"; interface IntroTimestamps { EpisodeId: string; diff --git a/hooks/useMarkAsPlayed.ts b/hooks/useMarkAsPlayed.ts index ff039cc8..01704b74 100644 --- a/hooks/useMarkAsPlayed.ts +++ b/hooks/useMarkAsPlayed.ts @@ -3,7 +3,7 @@ import { markAsNotPlayed } from "@/utils/jellyfin/playstate/markAsNotPlayed"; import { markAsPlayed } from "@/utils/jellyfin/playstate/markAsPlayed"; import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import { useQueryClient } from "@tanstack/react-query"; -import * as Haptics from "expo-haptics"; +import * as Haptics from "@/packages/expo-haptics"; import { useAtom } from "jotai"; export const useMarkAsPlayed = (item: BaseItemDto) => { diff --git a/hooks/useOrientation.ts b/hooks/useOrientation.ts index 1ecb31ac..dff4015d 100644 --- a/hooks/useOrientation.ts +++ b/hooks/useOrientation.ts @@ -1,12 +1,17 @@ import orientationToOrientationLock from "@/utils/OrientationLockConverter"; -import * as ScreenOrientation from "expo-screen-orientation"; +import * as ScreenOrientation from "@/packages/expo-screen-orientation"; import { useEffect, useState } from "react"; +import { Platform } from "react-native"; export const useOrientation = () => { const [orientation, setOrientation] = useState( - ScreenOrientation.OrientationLock.UNKNOWN + Platform.isTV + ? ScreenOrientation.OrientationLock.LANDSCAPE + : ScreenOrientation.OrientationLock.UNKNOWN ); + if (Platform.isTV) return { orientation, setOrientation }; + useEffect(() => { const orientationSubscription = ScreenOrientation.addOrientationChangeListener((event) => { diff --git a/hooks/useOrientationSettings.ts b/hooks/useOrientationSettings.ts index 85b8a113..7bb749a4 100644 --- a/hooks/useOrientationSettings.ts +++ b/hooks/useOrientationSettings.ts @@ -1,8 +1,11 @@ import { useSettings } from "@/utils/atoms/settings"; -import * as ScreenOrientation from "expo-screen-orientation"; +import * as ScreenOrientation from "@/packages/expo-screen-orientation"; import { useEffect } from "react"; +import { Platform } from "react-native"; export const useOrientationSettings = () => { + if (Platform.isTV) return; + const [settings] = useSettings(); useEffect(() => { diff --git a/hooks/useRemuxHlsToMp4.ts b/hooks/useRemuxHlsToMp4.ts index 25492e33..da922e2d 100644 --- a/hooks/useRemuxHlsToMp4.ts +++ b/hooks/useRemuxHlsToMp4.ts @@ -9,7 +9,8 @@ import { import { useQueryClient } from "@tanstack/react-query"; import * as FileSystem from "expo-file-system"; import { useRouter } from "expo-router"; -import { FFmpegKit, FFmpegSession, Statistics } from "ffmpeg-kit-react-native"; +// import { FFmpegKit, FFmpegSession, Statistics } from "ffmpeg-kit-react-native"; +const FFmpegKit = !Platform.isTV ? require("ffmpeg-kit-react-native") : null; import { useAtomValue } from "jotai"; import { useCallback } from "react"; import { toast } from "sonner-native"; @@ -18,6 +19,7 @@ import useDownloadHelper from "@/utils/download"; import { Api } from "@jellyfin/sdk"; import { useSettings } from "@/utils/atoms/settings"; import { JobStatus } from "@/utils/optimize-server"; +import { Platform } from "react-native"; const createFFmpegCommand = (url: string, output: string) => [ "-y", // overwrite output files without asking @@ -53,7 +55,12 @@ export const useRemuxHlsToMp4 = () => { const [settings] = useSettings(); const { saveImage } = useImageStorage(); const { saveSeriesPrimaryImage } = useDownloadHelper(); - const { saveDownloadedItemInfo, setProcesses, processes, APP_CACHE_DOWNLOAD_DIRECTORY } = useDownload(); + const { + saveDownloadedItemInfo, + setProcesses, + processes, + APP_CACHE_DOWNLOAD_DIRECTORY, + } = useDownload(); const onSaveAssets = async (api: Api, item: BaseItemDto) => { await saveSeriesPrimaryImage(item); @@ -77,9 +84,9 @@ export const useRemuxHlsToMp4 = () => { if (returnCode.isValueSuccess()) { const stat = await session.getLastReceivedStatistics(); await FileSystem.moveAsync({ - from: `${APP_CACHE_DOWNLOAD_DIRECTORY}${item.Id}.mp4`, - to: `${FileSystem.documentDirectory}${item.Id}.mp4` - }) + from: `${APP_CACHE_DOWNLOAD_DIRECTORY}${item.Id}.mp4`, + to: `${FileSystem.documentDirectory}${item.Id}.mp4`, + }); await queryClient.invalidateQueries({ queryKey: ["downloadedItems"], }); @@ -131,12 +138,16 @@ export const useRemuxHlsToMp4 = () => { const startRemuxing = useCallback( async (item: BaseItemDto, url: string, mediaSource: MediaSourceInfo) => { - const cacheDir = await FileSystem.getInfoAsync(APP_CACHE_DOWNLOAD_DIRECTORY); + const cacheDir = await FileSystem.getInfoAsync( + APP_CACHE_DOWNLOAD_DIRECTORY + ); if (!cacheDir.exists) { - await FileSystem.makeDirectoryAsync(APP_CACHE_DOWNLOAD_DIRECTORY, {intermediates: true}) + await FileSystem.makeDirectoryAsync(APP_CACHE_DOWNLOAD_DIRECTORY, { + intermediates: true, + }); } - const output = APP_CACHE_DOWNLOAD_DIRECTORY + `${item.Id}.mp4` + const output = APP_CACHE_DOWNLOAD_DIRECTORY + `${item.Id}.mp4`; if (!api) throw new Error("API is not defined"); if (!item.Id) throw new Error("Item must have an Id"); diff --git a/packages/expo-haptics.native.ts b/packages/expo-haptics.native.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/expo-haptics.ts b/packages/expo-haptics.ts new file mode 100644 index 00000000..522187df --- /dev/null +++ b/packages/expo-haptics.ts @@ -0,0 +1 @@ +export * as Haptics from "expo-haptics"; diff --git a/packages/expo-screen-orientation.native.ts b/packages/expo-screen-orientation.native.ts new file mode 100644 index 00000000..b658398b --- /dev/null +++ b/packages/expo-screen-orientation.native.ts @@ -0,0 +1,68 @@ +// export { Orientation, OrientationLock } from "expo-screen-orientation"; + +export enum Orientation { + /** + * An unknown screen orientation. For example, the device is flat, perhaps on a table. + */ + UNKNOWN = 0, + /** + * Right-side up portrait interface orientation. + */ + PORTRAIT_UP = 1, + /** + * Upside down portrait interface orientation. + */ + PORTRAIT_DOWN = 2, + /** + * Left landscape interface orientation. + */ + LANDSCAPE_LEFT = 3, + /** + * Right landscape interface orientation. + */ + LANDSCAPE_RIGHT = 4, +} + +export enum OrientationLock { + /** + * The default orientation. On iOS, this will allow all orientations except `Orientation.PORTRAIT_DOWN`. + * On Android, this lets the system decide the best orientation. + */ + DEFAULT = 0, + /** + * All four possible orientations + */ + ALL = 1, + /** + * Any portrait orientation. + */ + PORTRAIT = 2, + /** + * Right-side up portrait only. + */ + PORTRAIT_UP = 3, + /** + * Upside down portrait only. + */ + PORTRAIT_DOWN = 4, + /** + * Any landscape orientation. + */ + LANDSCAPE = 5, + /** + * Left landscape only. + */ + LANDSCAPE_LEFT = 6, + /** + * Right landscape only. + */ + LANDSCAPE_RIGHT = 7, + /** + * A platform specific orientation. This is not a valid policy that can be applied in [`lockAsync`](#screenorientationlockasyncorientationlock). + */ + OTHER = 8, + /** + * An unknown screen orientation lock. This is not a valid policy that can be applied in [`lockAsync`](#screenorientationlockasyncorientationlock). + */ + UNKNOWN = 9, +} diff --git a/packages/expo-screen-orientation.ts b/packages/expo-screen-orientation.ts new file mode 100644 index 00000000..7b29bfe1 --- /dev/null +++ b/packages/expo-screen-orientation.ts @@ -0,0 +1 @@ +export * from "expo-screen-orientation"; diff --git a/providers/DownloadProvider.tsx b/providers/DownloadProvider.tsx index 78fbbe6f..f581dd2e 100644 --- a/providers/DownloadProvider.tsx +++ b/providers/DownloadProvider.tsx @@ -13,12 +13,15 @@ import { BaseItemDto, MediaSourceInfo, } from "@jellyfin/sdk/lib/generated-client/models"; -import { - checkForExistingDownloads, - completeHandler, - download, - setConfig, -} from "@kesha-antonov/react-native-background-downloader"; +// import { +// checkForExistingDownloads, +// completeHandler, +// download, +// setConfig, +// } from "@kesha-antonov/react-native-background-downloader"; +const BackGroundDownloader = !Platform.isTV + ? require("@kesha-antonov/react-native-background-downloader") + : null; import MMKV from "react-native-mmkv"; import { focusManager, @@ -42,13 +45,14 @@ import React, { import { AppState, AppStateStatus, Platform } from "react-native"; import { toast } from "sonner-native"; import { apiAtom } from "./JellyfinProvider"; -import * as Notifications from "expo-notifications"; +// import * as Notifications from "expo-notifications"; +const Notifications = !Platform.isTV ? require("expo-notifications") : null; import { getItemImage } from "@/utils/getItemImage"; import useImageStorage from "@/hooks/useImageStorage"; import { storage } from "@/utils/mmkv"; import useDownloadHelper from "@/utils/download"; import { FileInfo } from "expo-file-system"; -import * as Haptics from "expo-haptics"; +import * as Haptics from "@/packages/expo-haptics"; import * as Application from "expo-application"; export type DownloadedItem = { @@ -67,6 +71,7 @@ const DownloadContext = createContext | null>(null); function useDownloadProvider() { + if (Platform.isTV) return; const queryClient = useQueryClient(); const [settings] = useSettings(); const router = useRouter(); @@ -170,7 +175,7 @@ function useDownloadProvider() { useEffect(() => { const checkIfShouldStartDownload = async () => { if (processes.length === 0) return; - await checkForExistingDownloads(); + await BackGroundDownloader.checkForExistingDownloads(); }; checkIfShouldStartDownload(); @@ -214,7 +219,7 @@ function useDownloadProvider() { ) ); - setConfig({ + BackGroundDownloader.setConfig({ isLogsEnabled: true, progressInterval: 500, headers: { @@ -234,7 +239,7 @@ function useDownloadProvider() { const baseDirectory = FileSystem.documentDirectory; - download({ + BackGroundDownloader.download({ id: process.id, url: settings?.optimizedVersionsServerUrl + "download/" + process.id, destination: `${baseDirectory}/${process.item.Id}.mp4`, @@ -284,7 +289,7 @@ function useDownloadProvider() { }, }); setTimeout(() => { - completeHandler(process.id); + BackGroundDownloader.completeHandler(process.id); removeProcess(process.id); }, 1000); }) diff --git a/utils/OrientationLockConverter.ts b/utils/OrientationLockConverter.ts index 748ffcc6..498e01cb 100644 --- a/utils/OrientationLockConverter.ts +++ b/utils/OrientationLockConverter.ts @@ -1,4 +1,7 @@ -import { Orientation, OrientationLock } from "expo-screen-orientation"; +import { + Orientation, + OrientationLock, +} from "@/packages/expo-screen-orientation"; function orientationToOrientationLock( orientation: Orientation diff --git a/utils/atoms/orientation.ts b/utils/atoms/orientation.ts index e4680fe3..4ee340a2 100644 --- a/utils/atoms/orientation.ts +++ b/utils/atoms/orientation.ts @@ -1,4 +1,4 @@ -import * as ScreenOrientation from "expo-screen-orientation"; +import * as ScreenOrientation from "@/packages/expo-screen-orientation"; import { atom } from "jotai"; export const orientationAtom = atom( diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts index c37dd4eb..fd669530 100644 --- a/utils/atoms/settings.ts +++ b/utils/atoms/settings.ts @@ -1,6 +1,6 @@ import { atom, useAtom } from "jotai"; import { useEffect } from "react"; -import * as ScreenOrientation from "expo-screen-orientation"; +import * as ScreenOrientation from "@/packages/expo-screen-orientation"; import { storage } from "../mmkv"; import { Platform } from "react-native"; import { diff --git a/utils/background-tasks.ts b/utils/background-tasks.ts index 1d7f0a70..0e9a408a 100644 --- a/utils/background-tasks.ts +++ b/utils/background-tasks.ts @@ -1,4 +1,7 @@ -import * as BackgroundFetch from "expo-background-fetch"; +import { Platform } from "react-native"; +const BackgroundFetch = !Platform.isTV + ? require("expo-background-fetch") + : null; export const BACKGROUND_FETCH_TASK = "background-fetch";