mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-04-22 08:44:41 +01:00
wip
This commit is contained in:
@@ -23,7 +23,7 @@ export default function IndexLayout() {
|
|||||||
headerShadowVisible: false,
|
headerShadowVisible: false,
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<View className="flex flex-row items-center space-x-2">
|
<View className="flex flex-row items-center space-x-2">
|
||||||
<Chromecast />
|
{!Platform.isTV && <Chromecast />}
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
router.push("/(auth)/settings");
|
router.push("/(auth)/settings");
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { QueryFunction, useQuery, useQueryClient } from "@tanstack/react-query";
|
|||||||
import { useNavigation, useRouter } from "expo-router";
|
import { useNavigation, useRouter } from "expo-router";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import { Platform } from "react-native";
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
@@ -64,30 +65,33 @@ export default function index() {
|
|||||||
const [isConnected, setIsConnected] = useState<boolean | null>(null);
|
const [isConnected, setIsConnected] = useState<boolean | null>(null);
|
||||||
const [loadingRetry, setLoadingRetry] = useState(false);
|
const [loadingRetry, setLoadingRetry] = useState(false);
|
||||||
|
|
||||||
const { downloadedFiles, cleanCacheDirectory } = useDownload();
|
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
|
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
useEffect(() => {
|
if (!Platform.isTV) {
|
||||||
const hasDownloads = downloadedFiles && downloadedFiles.length > 0;
|
const { downloadedFiles, cleanCacheDirectory } = useDownload();
|
||||||
navigation.setOptions({
|
|
||||||
headerLeft: () => (
|
useEffect(() => {
|
||||||
<TouchableOpacity
|
const hasDownloads = downloadedFiles && downloadedFiles.length > 0;
|
||||||
onPress={() => {
|
navigation.setOptions({
|
||||||
router.push("/(auth)/downloads");
|
headerLeft: () => (
|
||||||
}}
|
<TouchableOpacity
|
||||||
className="p-2"
|
onPress={() => {
|
||||||
>
|
router.push("/(auth)/downloads");
|
||||||
<Feather
|
}}
|
||||||
name="download"
|
className="p-2"
|
||||||
color={hasDownloads ? Colors.primary : "white"}
|
>
|
||||||
size={22}
|
<Feather
|
||||||
/>
|
name="download"
|
||||||
</TouchableOpacity>
|
color={hasDownloads ? Colors.primary : "white"}
|
||||||
),
|
size={22}
|
||||||
});
|
/>
|
||||||
}, [downloadedFiles, navigation, router]);
|
</TouchableOpacity>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}, [downloadedFiles, navigation, router]);
|
||||||
|
}
|
||||||
|
|
||||||
const checkConnection = useCallback(async () => {
|
const checkConnection = useCallback(async () => {
|
||||||
setLoadingRetry(true);
|
setLoadingRetry(true);
|
||||||
@@ -107,9 +111,11 @@ export default function index() {
|
|||||||
setIsConnected(state.isConnected);
|
setIsConnected(state.isConnected);
|
||||||
});
|
});
|
||||||
|
|
||||||
cleanCacheDirectory().catch((e) =>
|
if (!Platform.isTV) {
|
||||||
console.error("Something went wrong cleaning cache directory")
|
cleanCacheDirectory().catch((e) =>
|
||||||
);
|
console.error("Something went wrong cleaning cache directory")
|
||||||
|
);
|
||||||
|
}
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Platform } from "react-native";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { ListGroup } from "@/components/list/ListGroup";
|
import { ListGroup } from "@/components/list/ListGroup";
|
||||||
import { ListItem } from "@/components/list/ListItem";
|
import { ListItem } from "@/components/list/ListItem";
|
||||||
@@ -13,7 +14,8 @@ import { SubtitleToggles } from "@/components/settings/SubtitleToggles";
|
|||||||
import { UserInfo } from "@/components/settings/UserInfo";
|
import { UserInfo } from "@/components/settings/UserInfo";
|
||||||
import { useJellyfin } from "@/providers/JellyfinProvider";
|
import { useJellyfin } from "@/providers/JellyfinProvider";
|
||||||
import { clearLogs } from "@/utils/log";
|
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 { useNavigation, useRouter } from "expo-router";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { ScrollView, TouchableOpacity, View } from "react-native";
|
import { ScrollView, TouchableOpacity, View } from "react-native";
|
||||||
@@ -26,7 +28,9 @@ export default function settings() {
|
|||||||
|
|
||||||
const onClearLogsClicked = async () => {
|
const onClearLogsClicked = async () => {
|
||||||
clearLogs();
|
clearLogs();
|
||||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
if (!Platform.isTV) {
|
||||||
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { router, useLocalSearchParams, useNavigation } from "expo-router";
|
import { router, useLocalSearchParams, useNavigation } from "expo-router";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { useEffect, useState } from "react";
|
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";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
|
||||||
export default function page() {
|
export default function page() {
|
||||||
@@ -28,15 +28,17 @@ export default function page() {
|
|||||||
|
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
|
|
||||||
useEffect(() => {
|
if (!Platform.isTV) {
|
||||||
navigation.setOptions({
|
useEffect(() => {
|
||||||
headerRight: () => (
|
navigation.setOptions({
|
||||||
<View className="">
|
headerRight: () => (
|
||||||
<Chromecast />
|
<View className="">
|
||||||
</View>
|
<Chromecast />
|
||||||
),
|
</View>
|
||||||
|
),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
const { data: album } = useQuery({
|
const { data: album } = useQuery({
|
||||||
queryKey: ["album", albumId, artistId],
|
queryKey: ["album", albumId, artistId],
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
import { FlashList } from "@shopify/flash-list";
|
import { FlashList } from "@shopify/flash-list";
|
||||||
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
|
||||||
import { useLocalSearchParams, useNavigation } from "expo-router";
|
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 { useAtom } from "jotai";
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { FlatList, View } from "react-native";
|
import { FlatList, View } from "react-native";
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import {
|
|||||||
IssueType,
|
IssueType,
|
||||||
IssueTypeName,
|
IssueTypeName,
|
||||||
} from "@/utils/jellyseerr/server/constants/issue";
|
} 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 { TvDetails } from "@/utils/jellyseerr/server/models/Tv";
|
||||||
import JellyseerrSeasons from "@/components/series/JellyseerrSeasons";
|
import JellyseerrSeasons from "@/components/series/JellyseerrSeasons";
|
||||||
import { JellyserrRatings } from "@/components/Ratings";
|
import { JellyserrRatings } from "@/components/Ratings";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
|
||||||
import { useLocalSearchParams, useNavigation } from "expo-router";
|
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 { useAtom } from "jotai";
|
||||||
import React, { useCallback, useEffect, useMemo } from "react";
|
import React, { useCallback, useEffect, useMemo } from "react";
|
||||||
import { FlatList, useWindowDimensions, View } from "react-native";
|
import { FlatList, useWindowDimensions, View } from "react-native";
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useSettings } from "@/utils/atoms/settings";
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { Stack } from "expo-router";
|
import { Stack } from "expo-router";
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import * as DropdownMenu from "zeego/dropdown-menu";
|
import * as DropdownMenu from "@/components/DropdownMenu";
|
||||||
|
|
||||||
export default function IndexLayout() {
|
export default function IndexLayout() {
|
||||||
const [settings, updateSettings] = useSettings();
|
const [settings, updateSettings] = useSettings();
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
getUserLibraryApi,
|
getUserLibraryApi,
|
||||||
} from "@jellyfin/sdk/lib/utils/api";
|
} from "@jellyfin/sdk/lib/utils/api";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
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 { useFocusEffect, useGlobalSearchParams } from "expo-router";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import React, {
|
import React, {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
getUserLibraryApi,
|
getUserLibraryApi,
|
||||||
} from "@jellyfin/sdk/lib/utils/api";
|
} from "@jellyfin/sdk/lib/utils/api";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
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 { Image } from "expo-image";
|
||||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
getUserLibraryApi,
|
getUserLibraryApi,
|
||||||
} from "@jellyfin/sdk/lib/utils/api";
|
} from "@jellyfin/sdk/lib/utils/api";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
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 { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import React, {
|
import React, {
|
||||||
|
|||||||
288
app/_layout.tsx
288
app/_layout.tsx
@@ -1,4 +1,5 @@
|
|||||||
import "@/augmentations";
|
import "@/augmentations";
|
||||||
|
import { Platform } from "react-native";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { DownloadProvider } from "@/providers/DownloadProvider";
|
import { DownloadProvider } from "@/providers/DownloadProvider";
|
||||||
import {
|
import {
|
||||||
@@ -18,23 +19,28 @@ import { cancelJobById, getAllJobsByDeviceId } from "@/utils/optimize-server";
|
|||||||
import { ActionSheetProvider } from "@expo/react-native-action-sheet";
|
import { ActionSheetProvider } from "@expo/react-native-action-sheet";
|
||||||
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
|
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
|
||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||||
import {
|
// import {
|
||||||
checkForExistingDownloads,
|
// checkForExistingDownloads,
|
||||||
completeHandler,
|
// completeHandler,
|
||||||
download,
|
// download,
|
||||||
} from "@kesha-antonov/react-native-background-downloader";
|
// } 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 { DarkTheme, ThemeProvider } from "@react-navigation/native";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
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 * as FileSystem from "expo-file-system";
|
||||||
import { useFonts } from "expo-font";
|
import { useFonts } from "expo-font";
|
||||||
import { useKeepAwake } from "expo-keep-awake";
|
import { useKeepAwake } from "expo-keep-awake";
|
||||||
import * as Linking from "expo-linking";
|
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 { 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 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 { Provider as JotaiProvider, useAtom } from "jotai";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { Appearance, AppState, TouchableOpacity } from "react-native";
|
import { Appearance, AppState, TouchableOpacity } from "react-native";
|
||||||
@@ -45,15 +51,19 @@ import { Toaster } from "sonner-native";
|
|||||||
|
|
||||||
SplashScreen.preventAutoHideAsync();
|
SplashScreen.preventAutoHideAsync();
|
||||||
|
|
||||||
Notifications.setNotificationHandler({
|
if (!Platform.isTV) {
|
||||||
handleNotification: async () => ({
|
Notifications.setNotificationHandler({
|
||||||
shouldShowAlert: true,
|
handleNotification: async () => ({
|
||||||
shouldPlaySound: true,
|
shouldShowAlert: true,
|
||||||
shouldSetBadge: false,
|
shouldPlaySound: true,
|
||||||
}),
|
shouldSetBadge: false,
|
||||||
});
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function useNotificationObserver() {
|
function useNotificationObserver() {
|
||||||
|
if (Platform.isTV) return;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
|
|
||||||
@@ -84,99 +94,101 @@ function useNotificationObserver() {
|
|||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
|
if (!Platform.isTV) {
|
||||||
console.log("TaskManager ~ trigger");
|
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<Settings> = JSON.parse(settingsData);
|
const settings: Partial<Settings> = JSON.parse(settingsData);
|
||||||
const url = settings?.optimizedVersionsServerUrl;
|
const url = settings?.optimizedVersionsServerUrl;
|
||||||
|
|
||||||
if (!settings?.autoDownload || !url)
|
if (!settings?.autoDownload || !url)
|
||||||
return BackgroundFetch.BackgroundFetchResult.NoData;
|
return BackgroundFetch.BackgroundFetchResult.NoData;
|
||||||
|
|
||||||
const token = getTokenFromStorage();
|
const token = getTokenFromStorage();
|
||||||
const deviceId = getOrSetDeviceId();
|
const deviceId = getOrSetDeviceId();
|
||||||
const baseDirectory = FileSystem.documentDirectory;
|
const baseDirectory = FileSystem.documentDirectory;
|
||||||
|
|
||||||
if (!token || !deviceId || !baseDirectory)
|
if (!token || !deviceId || !baseDirectory)
|
||||||
return BackgroundFetch.BackgroundFetchResult.NoData;
|
return BackgroundFetch.BackgroundFetchResult.NoData;
|
||||||
|
|
||||||
const jobs = await getAllJobsByDeviceId({
|
const jobs = await getAllJobsByDeviceId({
|
||||||
deviceId,
|
deviceId,
|
||||||
authHeader: token,
|
authHeader: token,
|
||||||
url,
|
url,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("TaskManager ~ Active jobs: ", jobs.length);
|
console.log("TaskManager ~ Active jobs: ", jobs.length);
|
||||||
|
|
||||||
for (let job of jobs) {
|
for (let job of jobs) {
|
||||||
if (job.status === "completed") {
|
if (job.status === "completed") {
|
||||||
const downloadUrl = url + "download/" + job.id;
|
const downloadUrl = url + "download/" + job.id;
|
||||||
const tasks = await checkForExistingDownloads();
|
const tasks = await BackGroundDownloader.checkForExistingDownloads();
|
||||||
|
|
||||||
if (tasks.find((task) => task.id === job.id)) {
|
if (tasks.find((task) => task.id === job.id)) {
|
||||||
console.log("TaskManager ~ Download already in progress: ", job.id);
|
console.log("TaskManager ~ Download already in progress: ", job.id);
|
||||||
continue;
|
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!
|
// Be sure to return the successful result type!
|
||||||
return BackgroundFetch.BackgroundFetchResult.NewData;
|
return BackgroundFetch.BackgroundFetchResult.NewData;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const checkAndRequestPermissions = async () => {
|
const checkAndRequestPermissions = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -250,55 +262,61 @@ function Layout() {
|
|||||||
const [orientation, setOrientation] = useAtom(orientationAtom);
|
const [orientation, setOrientation] = useAtom(orientationAtom);
|
||||||
|
|
||||||
useKeepAwake();
|
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);
|
const appState = useRef(AppState.currentState);
|
||||||
|
|
||||||
useEffect(() => {
|
if (!Platform.isTV) {
|
||||||
const subscription = AppState.addEventListener("change", (nextAppState) => {
|
useNotificationObserver();
|
||||||
if (
|
|
||||||
appState.current.match(/inactive|background/) &&
|
|
||||||
nextAppState === "active"
|
|
||||||
) {
|
|
||||||
checkForExistingDownloads();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
checkForExistingDownloads();
|
useEffect(() => {
|
||||||
|
checkAndRequestPermissions();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return () => {
|
useEffect(() => {
|
||||||
subscription.remove();
|
if (settings?.autoRotate === true)
|
||||||
};
|
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.DEFAULT);
|
||||||
}, []);
|
else
|
||||||
|
ScreenOrientation.lockAsync(
|
||||||
|
ScreenOrientation.OrientationLock.PORTRAIT_UP
|
||||||
|
);
|
||||||
|
}, [settings]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = ScreenOrientation.addOrientationChangeListener(
|
const subscription = AppState.addEventListener(
|
||||||
(event) => {
|
"change",
|
||||||
setOrientation(event.orientationInfo.orientation);
|
(nextAppState) => {
|
||||||
}
|
if (
|
||||||
);
|
appState.current.match(/inactive|background/) &&
|
||||||
|
nextAppState === "active"
|
||||||
|
) {
|
||||||
|
BackGroundDownloader.checkForExistingDownloads();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
ScreenOrientation.getOrientationAsync().then((initialOrientation) => {
|
BackGroundDownloader.checkForExistingDownloads();
|
||||||
setOrientation(initialOrientation);
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
ScreenOrientation.removeOrientationChangeListener(subscription);
|
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();
|
const url = Linking.useURL();
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { MediaSourceInfo } from "@jellyfin/sdk/lib/generated-client/models";
|
import { MediaSourceInfo } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { TouchableOpacity, View } from "react-native";
|
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 { Text } from "./common/Text";
|
||||||
|
|
||||||
interface Props extends React.ComponentProps<typeof View> {
|
interface Props extends React.ComponentProps<typeof View> {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TouchableOpacity, View } from "react-native";
|
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 { Text } from "./common/Text";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
|||||||
@@ -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 React, { PropsWithChildren, ReactNode, useMemo } from "react";
|
||||||
import { Text, TouchableOpacity, View } from "react-native";
|
import { Text, TouchableOpacity, View } from "react-native";
|
||||||
import { Loader } from "./Loader";
|
import { Loader } from "./Loader";
|
||||||
|
|||||||
0
components/ContextMenu.native.ts
Normal file
0
components/ContextMenu.native.ts
Normal file
1
components/ContextMenu.ts
Normal file
1
components/ContextMenu.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * as ContextMenu from "zeego/context-menu";
|
||||||
0
components/DropdownMenu.native.ts
Normal file
0
components/DropdownMenu.native.ts
Normal file
1
components/DropdownMenu.ts
Normal file
1
components/DropdownMenu.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * as DropdownMenu from "zeego/dropdown-menu";
|
||||||
@@ -24,10 +24,10 @@ import {
|
|||||||
} from "@jellyfin/sdk/lib/generated-client/models";
|
} from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import { useNavigation } from "expo-router";
|
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 { useAtom } from "jotai";
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
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 { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { Chromecast } from "./Chromecast";
|
import { Chromecast } from "./Chromecast";
|
||||||
import { ItemHeader } from "./ItemHeader";
|
import { ItemHeader } from "./ItemHeader";
|
||||||
@@ -81,23 +81,25 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo(
|
|||||||
defaultMediaSource,
|
defaultMediaSource,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
if (!Platform.isTV) {
|
||||||
navigation.setOptions({
|
useEffect(() => {
|
||||||
headerRight: () =>
|
navigation.setOptions({
|
||||||
item && (
|
headerRight: () =>
|
||||||
<View className="flex flex-row items-center space-x-2">
|
item && (
|
||||||
<Chromecast background="blur" width={22} height={22} />
|
<View className="flex flex-row items-center space-x-2">
|
||||||
{item.Type !== "Program" && (
|
<Chromecast background="blur" width={22} height={22} />
|
||||||
<View className="flex flex-row items-center space-x-2">
|
{item.Type !== "Program" && (
|
||||||
<DownloadSingleItem item={item} size="large" />
|
<View className="flex flex-row items-center space-x-2">
|
||||||
<PlayedStatus item={item} />
|
<DownloadSingleItem item={item} size="large" />
|
||||||
<AddToFavorites item={item} type="item" />
|
<PlayedStatus item={item} />
|
||||||
</View>
|
<AddToFavorites item={item} type="item" />
|
||||||
)}
|
</View>
|
||||||
</View>
|
)}
|
||||||
),
|
</View>
|
||||||
});
|
),
|
||||||
}, [item]);
|
});
|
||||||
|
}, [item]);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (orientation !== ScreenOrientation.OrientationLock.PORTRAIT_UP)
|
if (orientation !== ScreenOrientation.OrientationLock.PORTRAIT_UP)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
} from "@jellyfin/sdk/lib/generated-client/models";
|
} from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { TouchableOpacity, View } from "react-native";
|
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 { Text } from "./common/Text";
|
||||||
import { convertBitsToMegabitsOrGigabits } from "@/utils/bToMb";
|
import { convertBitsToMegabitsOrGigabits } from "@/utils/bToMb";
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import Animated, {
|
|||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
import { SelectedOptions } from "./ItemContent";
|
import { SelectedOptions } from "./ItemContent";
|
||||||
import { chromecastProfile } from "@/utils/profiles/chromecast";
|
import { chromecastProfile } from "@/utils/profiles/chromecast";
|
||||||
import * as Haptics from "expo-haptics";
|
import * as Haptics from "@/packages/expo-haptics";
|
||||||
|
|
||||||
interface Props extends React.ComponentProps<typeof Button> {
|
interface Props extends React.ComponentProps<typeof Button> {
|
||||||
item: BaseItemDto;
|
item: BaseItemDto;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
TouchableOpacityProps,
|
TouchableOpacityProps,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import * as Haptics from "expo-haptics";
|
import * as Haptics from "@/packages/expo-haptics";
|
||||||
|
|
||||||
interface Props extends TouchableOpacityProps {
|
interface Props extends TouchableOpacityProps {
|
||||||
onPress?: () => void;
|
onPress?: () => void;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { tc } from "@/utils/textTools";
|
|||||||
import { MediaSourceInfo } from "@jellyfin/sdk/lib/generated-client/models";
|
import { MediaSourceInfo } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { Platform, TouchableOpacity, View } from "react-native";
|
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 { Text } from "./common/Text";
|
||||||
import { SubtitleHelper } from "@/utils/SubtitleHelper";
|
import { SubtitleHelper } from "@/utils/SubtitleHelper";
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import {useRouter, useSegments} from "expo-router";
|
import { useRouter, useSegments } from "expo-router";
|
||||||
import React, {PropsWithChildren, useCallback, useMemo} from "react";
|
import React, { PropsWithChildren, useCallback, useMemo } from "react";
|
||||||
import {TouchableOpacity, TouchableOpacityProps} from "react-native";
|
import { TouchableOpacity, TouchableOpacityProps } from "react-native";
|
||||||
import * as ContextMenu from "zeego/context-menu";
|
import * as ContextMenu from "@/components/ContextMenu";
|
||||||
import {MovieResult, TvResult} from "@/utils/jellyseerr/server/models/Search";
|
import { MovieResult, TvResult } from "@/utils/jellyseerr/server/models/Search";
|
||||||
import {useJellyseerr} from "@/hooks/useJellyseerr";
|
import { useJellyseerr } from "@/hooks/useJellyseerr";
|
||||||
import {hasPermission, Permission} from "@/utils/jellyseerr/server/lib/permissions";
|
import {
|
||||||
import {MediaType} from "@/utils/jellyseerr/server/constants/media";
|
hasPermission,
|
||||||
|
Permission,
|
||||||
|
} from "@/utils/jellyseerr/server/lib/permissions";
|
||||||
|
import { MediaType } from "@/utils/jellyseerr/server/constants/media";
|
||||||
|
|
||||||
interface Props extends TouchableOpacityProps {
|
interface Props extends TouchableOpacityProps {
|
||||||
result: MovieResult | TvResult;
|
result: MovieResult | TvResult;
|
||||||
@@ -26,26 +29,27 @@ export const TouchableJellyseerrRouter: React.FC<PropsWithChildren<Props>> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const segments = useSegments();
|
const segments = useSegments();
|
||||||
const {jellyseerrApi, jellyseerrUser, requestMedia} = useJellyseerr()
|
const { jellyseerrApi, jellyseerrUser, requestMedia } = useJellyseerr();
|
||||||
|
|
||||||
const from = segments[2];
|
const from = segments[2];
|
||||||
|
|
||||||
const autoApprove = useMemo(() => {
|
const autoApprove = useMemo(() => {
|
||||||
return jellyseerrUser && hasPermission(
|
return (
|
||||||
Permission.AUTO_APPROVE,
|
jellyseerrUser &&
|
||||||
jellyseerrUser.permissions,
|
hasPermission(Permission.AUTO_APPROVE, jellyseerrUser.permissions, {
|
||||||
{type: 'or'}
|
type: "or",
|
||||||
)
|
})
|
||||||
}, [jellyseerrApi, jellyseerrUser])
|
);
|
||||||
|
}, [jellyseerrApi, jellyseerrUser]);
|
||||||
|
|
||||||
const request = useCallback(() =>
|
const request = useCallback(
|
||||||
|
() =>
|
||||||
requestMedia(mediaTitle, {
|
requestMedia(mediaTitle, {
|
||||||
mediaId: result.id,
|
mediaId: result.id,
|
||||||
mediaType: result.mediaType
|
mediaType: result.mediaType,
|
||||||
}
|
}),
|
||||||
),
|
|
||||||
[jellyseerrApi, result]
|
[jellyseerrApi, result]
|
||||||
)
|
);
|
||||||
|
|
||||||
if (from === "(home)" || from === "(search)" || from === "(libraries)")
|
if (from === "(home)" || from === "(search)" || from === "(libraries)")
|
||||||
return (
|
return (
|
||||||
@@ -55,7 +59,16 @@ export const TouchableJellyseerrRouter: React.FC<PropsWithChildren<Props>> = ({
|
|||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
// @ts-ignore
|
// @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}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -71,31 +84,33 @@ export const TouchableJellyseerrRouter: React.FC<PropsWithChildren<Props>> = ({
|
|||||||
>
|
>
|
||||||
<ContextMenu.Label key="label-1">Actions</ContextMenu.Label>
|
<ContextMenu.Label key="label-1">Actions</ContextMenu.Label>
|
||||||
{canRequest && result.mediaType === MediaType.MOVIE && (
|
{canRequest && result.mediaType === MediaType.MOVIE && (
|
||||||
<ContextMenu.Item
|
<ContextMenu.Item
|
||||||
key="item-1"
|
key="item-1"
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
if (autoApprove) {
|
if (autoApprove) {
|
||||||
request()
|
request();
|
||||||
}
|
}
|
||||||
|
}}
|
||||||
|
shouldDismissMenuOnSelect
|
||||||
|
>
|
||||||
|
<ContextMenu.ItemTitle key="item-1-title">
|
||||||
|
Request
|
||||||
|
</ContextMenu.ItemTitle>
|
||||||
|
<ContextMenu.ItemIcon
|
||||||
|
ios={{
|
||||||
|
name: "arrow.down.to.line",
|
||||||
|
pointSize: 18,
|
||||||
|
weight: "semibold",
|
||||||
|
scale: "medium",
|
||||||
|
hierarchicalColor: {
|
||||||
|
dark: "purple",
|
||||||
|
light: "purple",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
shouldDismissMenuOnSelect
|
androidIconName="download"
|
||||||
>
|
/>
|
||||||
<ContextMenu.ItemTitle key="item-1-title">Request</ContextMenu.ItemTitle>
|
</ContextMenu.Item>
|
||||||
<ContextMenu.ItemIcon
|
)}
|
||||||
ios={{
|
|
||||||
name: "arrow.down.to.line",
|
|
||||||
pointSize: 18,
|
|
||||||
weight: "semibold",
|
|
||||||
scale: "medium",
|
|
||||||
hierarchicalColor: {
|
|
||||||
dark: "purple",
|
|
||||||
light: "purple",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
androidIconName="download"
|
|
||||||
/>
|
|
||||||
</ContextMenu.Item>
|
|
||||||
)}
|
|
||||||
</ContextMenu.Content>
|
</ContextMenu.Content>
|
||||||
</ContextMenu.Root>
|
</ContextMenu.Root>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
import { useRouter, useSegments } from "expo-router";
|
import { useRouter, useSegments } from "expo-router";
|
||||||
import { PropsWithChildren } from "react";
|
import { PropsWithChildren } from "react";
|
||||||
import { TouchableOpacity, TouchableOpacityProps } from "react-native";
|
import { TouchableOpacity, TouchableOpacityProps } from "react-native";
|
||||||
import * as ContextMenu from "zeego/context-menu";
|
import * as ContextMenu from "@/components/ContextMenu";
|
||||||
|
|
||||||
interface Props extends TouchableOpacityProps {
|
interface Props extends TouchableOpacityProps {
|
||||||
item: BaseItemDto;
|
item: BaseItemDto;
|
||||||
|
|||||||
@@ -5,13 +5,18 @@ import { useSettings } from "@/utils/atoms/settings";
|
|||||||
import { JobStatus } from "@/utils/optimize-server";
|
import { JobStatus } from "@/utils/optimize-server";
|
||||||
import { formatTimeString } from "@/utils/time";
|
import { formatTimeString } from "@/utils/time";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
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 { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useRouter } from "expo-router";
|
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 { useAtom } from "jotai";
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
|
Platform,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
TouchableOpacityProps,
|
TouchableOpacityProps,
|
||||||
View,
|
View,
|
||||||
@@ -64,7 +69,7 @@ const DownloadCard = ({ process, ...props }: DownloadCardProps) => {
|
|||||||
|
|
||||||
if (settings?.downloadMethod === "optimized") {
|
if (settings?.downloadMethod === "optimized") {
|
||||||
try {
|
try {
|
||||||
const tasks = await checkForExistingDownloads();
|
const tasks = await BackGroundDownloader.checkForExistingDownloads();
|
||||||
for (const task of tasks) {
|
for (const task of tasks) {
|
||||||
if (task.id === id) {
|
if (task.id === id) {
|
||||||
task.stop();
|
task.stop();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
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 React, { useCallback, useMemo } from "react";
|
||||||
import { TouchableOpacity, TouchableOpacityProps, View } from "react-native";
|
import { TouchableOpacity, TouchableOpacityProps, View } from "react-native";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
useActionSheet,
|
useActionSheet,
|
||||||
} from "@expo/react-native-action-sheet";
|
} from "@expo/react-native-action-sheet";
|
||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
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 React, { useCallback, useMemo } from "react";
|
||||||
import { TouchableOpacity, View } from "react-native";
|
import { TouchableOpacity, View } from "react-native";
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { itemRouter, TouchableItemRouter } from "../common/TouchableItemRouter";
|
|||||||
import { Loader } from "../Loader";
|
import { Loader } from "../Loader";
|
||||||
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
||||||
import { useRouter, useSegments } from "expo-router";
|
import { useRouter, useSegments } from "expo-router";
|
||||||
import * as Haptics from "expo-haptics";
|
import * as Haptics from "@/packages/expo-haptics";
|
||||||
|
|
||||||
interface Props extends ViewProps {}
|
interface Props extends ViewProps {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { TouchableOpacity, View } from "react-native";
|
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 { Text } from "../common/Text";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TouchableOpacity, View, ViewProps } from "react-native";
|
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 { Text } from "../common/Text";
|
||||||
import { useMedia } from "./MediaContext";
|
import { useMedia } from "./MediaContext";
|
||||||
import { Switch } from "react-native-gesture-handler";
|
import { Switch } from "react-native-gesture-handler";
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
|||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Switch, TouchableOpacity, View } from "react-native";
|
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 { Text } from "../common/Text";
|
||||||
import { ListGroup } from "../list/ListGroup";
|
import { ListGroup } from "../list/ListGroup";
|
||||||
import { ListItem } from "../list/ListItem";
|
import { ListItem } from "../list/ListItem";
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Platform } from "react-native";
|
||||||
import { ScreenOrientationEnum, useSettings } from "@/utils/atoms/settings";
|
import { ScreenOrientationEnum, useSettings } from "@/utils/atoms/settings";
|
||||||
import {
|
import {
|
||||||
BACKGROUND_FETCH_TASK,
|
BACKGROUND_FETCH_TASK,
|
||||||
@@ -5,13 +6,15 @@ import {
|
|||||||
unregisterBackgroundFetchAsync,
|
unregisterBackgroundFetchAsync,
|
||||||
} from "@/utils/background-tasks";
|
} from "@/utils/background-tasks";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import * as BackgroundFetch from "expo-background-fetch";
|
const BackgroundFetch = !Platform.isTV
|
||||||
import * as ScreenOrientation from "expo-screen-orientation";
|
? require("expo-background-fetch")
|
||||||
import * as TaskManager from "expo-task-manager";
|
: null;
|
||||||
|
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
|
||||||
|
const TaskManager = !Platform.isTV ? require("expo-task-manager") : null;
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { Linking, Switch, TouchableOpacity, ViewProps } from "react-native";
|
import { Linking, Switch, TouchableOpacity, ViewProps } from "react-native";
|
||||||
import { toast } from "sonner-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 { Text } from "../common/Text";
|
||||||
import { ListGroup } from "../list/ListGroup";
|
import { ListGroup } from "../list/ListGroup";
|
||||||
import { ListItem } from "../list/ListItem";
|
import { ListItem } from "../list/ListItem";
|
||||||
@@ -25,6 +28,8 @@ export const OtherSettings: React.FC = () => {
|
|||||||
* Background task
|
* Background task
|
||||||
*******************/
|
*******************/
|
||||||
const checkStatusAsync = async () => {
|
const checkStatusAsync = async () => {
|
||||||
|
if (Platform.isTV) return;
|
||||||
|
|
||||||
await BackgroundFetch.getStatusAsync();
|
await BackgroundFetch.getStatusAsync();
|
||||||
return await TaskManager.isTaskRegisteredAsync(BACKGROUND_FETCH_TASK);
|
return await TaskManager.isTaskRegisteredAsync(BACKGROUND_FETCH_TASK);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
BottomSheetView,
|
BottomSheetView,
|
||||||
} from "@gorhom/bottom-sheet";
|
} from "@gorhom/bottom-sheet";
|
||||||
import { getQuickConnectApi } from "@jellyfin/sdk/lib/utils/api";
|
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 { useAtom } from "jotai";
|
||||||
import React, { useCallback, useRef, useState } from "react";
|
import React, { useCallback, useRef, useState } from "react";
|
||||||
import { Alert, View, ViewProps } from "react-native";
|
import { Alert, View, ViewProps } from "react-native";
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useDownload } from "@/providers/DownloadProvider";
|
|||||||
import { clearLogs } from "@/utils/log";
|
import { clearLogs } from "@/utils/log";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import * as FileSystem from "expo-file-system";
|
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 { View } from "react-native";
|
||||||
import * as Progress from "react-native-progress";
|
import * as Progress from "react-native-progress";
|
||||||
import { toast } from "sonner-native";
|
import { toast } from "sonner-native";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TouchableOpacity, View, ViewProps } from "react-native";
|
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 { Text } from "../common/Text";
|
||||||
import { useMedia } from "./MediaContext";
|
import { useMedia } from "./MediaContext";
|
||||||
import { Switch } from "react-native-gesture-handler";
|
import { Switch } from "react-native-gesture-handler";
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import React, { useEffect, useRef } from "react";
|
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 { useSharedValue } from "react-native-reanimated";
|
||||||
import { Slider } from "react-native-awesome-slider";
|
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";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
|
|
||||||
interface AudioSliderProps {
|
interface AudioSliderProps {
|
||||||
@@ -10,6 +13,8 @@ interface AudioSliderProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AudioSlider: React.FC<AudioSliderProps> = ({ setVisibility }) => {
|
const AudioSlider: React.FC<AudioSliderProps> = ({ setVisibility }) => {
|
||||||
|
if (Platform.isTV) return;
|
||||||
|
|
||||||
const volume = useSharedValue<number>(50); // Explicitly type as number
|
const volume = useSharedValue<number>(50); // Explicitly type as number
|
||||||
const min = useSharedValue<number>(0); // Explicitly type as number
|
const min = useSharedValue<number>(0); // Explicitly type as number
|
||||||
const max = useSharedValue<number>(100); // Explicitly type as number
|
const max = useSharedValue<number>(100); // Explicitly type as number
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import React, { useEffect } from "react";
|
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 { useSharedValue } from "react-native-reanimated";
|
||||||
import { Slider } from "react-native-awesome-slider";
|
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 { Ionicons } from "@expo/vector-icons";
|
||||||
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
|
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
|
||||||
|
|
||||||
const BrightnessSlider = () => {
|
const BrightnessSlider = () => {
|
||||||
|
if (Platform.isTV) return;
|
||||||
|
|
||||||
const brightness = useSharedValue(50);
|
const brightness = useSharedValue(50);
|
||||||
const min = useSharedValue(0);
|
const min = useSharedValue(0);
|
||||||
const max = useSharedValue(100);
|
const max = useSharedValue(100);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
BaseItemDto,
|
BaseItemDto,
|
||||||
MediaSourceInfo,
|
MediaSourceInfo,
|
||||||
} from "@jellyfin/sdk/lib/generated-client";
|
} 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 { Image } from "expo-image";
|
||||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { View, TouchableOpacity } from "react-native";
|
import { View, TouchableOpacity } from "react-native";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
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 { useControlContext } from "../contexts/ControlContext";
|
||||||
import { useVideoContext } from "../contexts/VideoContext";
|
import { useVideoContext } from "../contexts/VideoContext";
|
||||||
import { EmbeddedSubtitle, ExternalSubtitle } from "../types";
|
import { EmbeddedSubtitle, ExternalSubtitle } from "../types";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useCallback, useMemo, useState } from "react";
|
import React, { useCallback, useMemo, useState } from "react";
|
||||||
import { View, TouchableOpacity } from "react-native";
|
import { View, TouchableOpacity } from "react-native";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
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 { useControlContext } from "../contexts/ControlContext";
|
||||||
import { useVideoContext } from "../contexts/VideoContext";
|
import { useVideoContext } from "../contexts/VideoContext";
|
||||||
import { TranscodedSubtitle } from "../types";
|
import { TranscodedSubtitle } from "../types";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { apiAtom } from "@/providers/JellyfinProvider";
|
|||||||
import { getAuthHeaders } from "@/utils/jellyfin/jellyfin";
|
import { getAuthHeaders } from "@/utils/jellyfin/jellyfin";
|
||||||
import { writeToLog } from "@/utils/log";
|
import { writeToLog } from "@/utils/log";
|
||||||
import { msToSeconds, secondsToMs } from "@/utils/time";
|
import { msToSeconds, secondsToMs } from "@/utils/time";
|
||||||
import * as Haptics from "expo-haptics";
|
import * as Haptics from "@/packages/expo-haptics";
|
||||||
|
|
||||||
interface CreditTimestamps {
|
interface CreditTimestamps {
|
||||||
Introduction: {
|
Introduction: {
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import { storage } from "@/utils/mmkv";
|
|||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||||
import { useAtom, useAtomValue } from "jotai";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
import { useEffect, useMemo } from "react";
|
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.
|
* Custom hook to extract and manage image colors for a given item.
|
||||||
@@ -28,6 +30,8 @@ export const useImageColors = ({
|
|||||||
url?: string | null;
|
url?: string | null;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
|
if (Platform.isTV) return;
|
||||||
|
|
||||||
const api = useAtomValue(apiAtom);
|
const api = useAtomValue(apiAtom);
|
||||||
const [, setPrimaryColor] = useAtom(itemThemeColorAtom);
|
const [, setPrimaryColor] = useAtom(itemThemeColorAtom);
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { apiAtom } from "@/providers/JellyfinProvider";
|
|||||||
import { getAuthHeaders } from "@/utils/jellyfin/jellyfin";
|
import { getAuthHeaders } from "@/utils/jellyfin/jellyfin";
|
||||||
import { writeToLog } from "@/utils/log";
|
import { writeToLog } from "@/utils/log";
|
||||||
import { msToSeconds, secondsToMs } from "@/utils/time";
|
import { msToSeconds, secondsToMs } from "@/utils/time";
|
||||||
import * as Haptics from "expo-haptics";
|
import * as Haptics from "@/packages/expo-haptics";
|
||||||
|
|
||||||
interface IntroTimestamps {
|
interface IntroTimestamps {
|
||||||
EpisodeId: string;
|
EpisodeId: string;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { markAsNotPlayed } from "@/utils/jellyfin/playstate/markAsNotPlayed";
|
|||||||
import { markAsPlayed } from "@/utils/jellyfin/playstate/markAsPlayed";
|
import { markAsPlayed } from "@/utils/jellyfin/playstate/markAsPlayed";
|
||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import * as Haptics from "expo-haptics";
|
import * as Haptics from "@/packages/expo-haptics";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
|
|
||||||
export const useMarkAsPlayed = (item: BaseItemDto) => {
|
export const useMarkAsPlayed = (item: BaseItemDto) => {
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import orientationToOrientationLock from "@/utils/OrientationLockConverter";
|
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 { useEffect, useState } from "react";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
|
||||||
export const useOrientation = () => {
|
export const useOrientation = () => {
|
||||||
const [orientation, setOrientation] = useState(
|
const [orientation, setOrientation] = useState(
|
||||||
ScreenOrientation.OrientationLock.UNKNOWN
|
Platform.isTV
|
||||||
|
? ScreenOrientation.OrientationLock.LANDSCAPE
|
||||||
|
: ScreenOrientation.OrientationLock.UNKNOWN
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (Platform.isTV) return { orientation, setOrientation };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const orientationSubscription =
|
const orientationSubscription =
|
||||||
ScreenOrientation.addOrientationChangeListener((event) => {
|
ScreenOrientation.addOrientationChangeListener((event) => {
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { useSettings } from "@/utils/atoms/settings";
|
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 { useEffect } from "react";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
|
||||||
export const useOrientationSettings = () => {
|
export const useOrientationSettings = () => {
|
||||||
|
if (Platform.isTV) return;
|
||||||
|
|
||||||
const [settings] = useSettings();
|
const [settings] = useSettings();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import {
|
|||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import * as FileSystem from "expo-file-system";
|
import * as FileSystem from "expo-file-system";
|
||||||
import { useRouter } from "expo-router";
|
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 { useAtomValue } from "jotai";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { toast } from "sonner-native";
|
import { toast } from "sonner-native";
|
||||||
@@ -18,6 +19,7 @@ import useDownloadHelper from "@/utils/download";
|
|||||||
import { Api } from "@jellyfin/sdk";
|
import { Api } from "@jellyfin/sdk";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
import { JobStatus } from "@/utils/optimize-server";
|
import { JobStatus } from "@/utils/optimize-server";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
|
||||||
const createFFmpegCommand = (url: string, output: string) => [
|
const createFFmpegCommand = (url: string, output: string) => [
|
||||||
"-y", // overwrite output files without asking
|
"-y", // overwrite output files without asking
|
||||||
@@ -53,7 +55,12 @@ export const useRemuxHlsToMp4 = () => {
|
|||||||
const [settings] = useSettings();
|
const [settings] = useSettings();
|
||||||
const { saveImage } = useImageStorage();
|
const { saveImage } = useImageStorage();
|
||||||
const { saveSeriesPrimaryImage } = useDownloadHelper();
|
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) => {
|
const onSaveAssets = async (api: Api, item: BaseItemDto) => {
|
||||||
await saveSeriesPrimaryImage(item);
|
await saveSeriesPrimaryImage(item);
|
||||||
@@ -77,9 +84,9 @@ export const useRemuxHlsToMp4 = () => {
|
|||||||
if (returnCode.isValueSuccess()) {
|
if (returnCode.isValueSuccess()) {
|
||||||
const stat = await session.getLastReceivedStatistics();
|
const stat = await session.getLastReceivedStatistics();
|
||||||
await FileSystem.moveAsync({
|
await FileSystem.moveAsync({
|
||||||
from: `${APP_CACHE_DOWNLOAD_DIRECTORY}${item.Id}.mp4`,
|
from: `${APP_CACHE_DOWNLOAD_DIRECTORY}${item.Id}.mp4`,
|
||||||
to: `${FileSystem.documentDirectory}${item.Id}.mp4`
|
to: `${FileSystem.documentDirectory}${item.Id}.mp4`,
|
||||||
})
|
});
|
||||||
await queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
queryKey: ["downloadedItems"],
|
queryKey: ["downloadedItems"],
|
||||||
});
|
});
|
||||||
@@ -131,12 +138,16 @@ export const useRemuxHlsToMp4 = () => {
|
|||||||
|
|
||||||
const startRemuxing = useCallback(
|
const startRemuxing = useCallback(
|
||||||
async (item: BaseItemDto, url: string, mediaSource: MediaSourceInfo) => {
|
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) {
|
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 (!api) throw new Error("API is not defined");
|
||||||
if (!item.Id) throw new Error("Item must have an Id");
|
if (!item.Id) throw new Error("Item must have an Id");
|
||||||
|
|||||||
0
packages/expo-haptics.native.ts
Normal file
0
packages/expo-haptics.native.ts
Normal file
1
packages/expo-haptics.ts
Normal file
1
packages/expo-haptics.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * as Haptics from "expo-haptics";
|
||||||
68
packages/expo-screen-orientation.native.ts
Normal file
68
packages/expo-screen-orientation.native.ts
Normal file
@@ -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,
|
||||||
|
}
|
||||||
1
packages/expo-screen-orientation.ts
Normal file
1
packages/expo-screen-orientation.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "expo-screen-orientation";
|
||||||
@@ -13,12 +13,15 @@ import {
|
|||||||
BaseItemDto,
|
BaseItemDto,
|
||||||
MediaSourceInfo,
|
MediaSourceInfo,
|
||||||
} from "@jellyfin/sdk/lib/generated-client/models";
|
} from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import {
|
// import {
|
||||||
checkForExistingDownloads,
|
// checkForExistingDownloads,
|
||||||
completeHandler,
|
// completeHandler,
|
||||||
download,
|
// download,
|
||||||
setConfig,
|
// setConfig,
|
||||||
} from "@kesha-antonov/react-native-background-downloader";
|
// } 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 MMKV from "react-native-mmkv";
|
||||||
import {
|
import {
|
||||||
focusManager,
|
focusManager,
|
||||||
@@ -42,13 +45,14 @@ import React, {
|
|||||||
import { AppState, AppStateStatus, Platform } from "react-native";
|
import { AppState, AppStateStatus, Platform } from "react-native";
|
||||||
import { toast } from "sonner-native";
|
import { toast } from "sonner-native";
|
||||||
import { apiAtom } from "./JellyfinProvider";
|
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 { getItemImage } from "@/utils/getItemImage";
|
||||||
import useImageStorage from "@/hooks/useImageStorage";
|
import useImageStorage from "@/hooks/useImageStorage";
|
||||||
import { storage } from "@/utils/mmkv";
|
import { storage } from "@/utils/mmkv";
|
||||||
import useDownloadHelper from "@/utils/download";
|
import useDownloadHelper from "@/utils/download";
|
||||||
import { FileInfo } from "expo-file-system";
|
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";
|
import * as Application from "expo-application";
|
||||||
|
|
||||||
export type DownloadedItem = {
|
export type DownloadedItem = {
|
||||||
@@ -67,6 +71,7 @@ const DownloadContext = createContext<ReturnType<
|
|||||||
> | null>(null);
|
> | null>(null);
|
||||||
|
|
||||||
function useDownloadProvider() {
|
function useDownloadProvider() {
|
||||||
|
if (Platform.isTV) return;
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [settings] = useSettings();
|
const [settings] = useSettings();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -170,7 +175,7 @@ function useDownloadProvider() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkIfShouldStartDownload = async () => {
|
const checkIfShouldStartDownload = async () => {
|
||||||
if (processes.length === 0) return;
|
if (processes.length === 0) return;
|
||||||
await checkForExistingDownloads();
|
await BackGroundDownloader.checkForExistingDownloads();
|
||||||
};
|
};
|
||||||
|
|
||||||
checkIfShouldStartDownload();
|
checkIfShouldStartDownload();
|
||||||
@@ -214,7 +219,7 @@ function useDownloadProvider() {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
setConfig({
|
BackGroundDownloader.setConfig({
|
||||||
isLogsEnabled: true,
|
isLogsEnabled: true,
|
||||||
progressInterval: 500,
|
progressInterval: 500,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -234,7 +239,7 @@ function useDownloadProvider() {
|
|||||||
|
|
||||||
const baseDirectory = FileSystem.documentDirectory;
|
const baseDirectory = FileSystem.documentDirectory;
|
||||||
|
|
||||||
download({
|
BackGroundDownloader.download({
|
||||||
id: process.id,
|
id: process.id,
|
||||||
url: settings?.optimizedVersionsServerUrl + "download/" + process.id,
|
url: settings?.optimizedVersionsServerUrl + "download/" + process.id,
|
||||||
destination: `${baseDirectory}/${process.item.Id}.mp4`,
|
destination: `${baseDirectory}/${process.item.Id}.mp4`,
|
||||||
@@ -284,7 +289,7 @@ function useDownloadProvider() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
completeHandler(process.id);
|
BackGroundDownloader.completeHandler(process.id);
|
||||||
removeProcess(process.id);
|
removeProcess(process.id);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { Orientation, OrientationLock } from "expo-screen-orientation";
|
import {
|
||||||
|
Orientation,
|
||||||
|
OrientationLock,
|
||||||
|
} from "@/packages/expo-screen-orientation";
|
||||||
|
|
||||||
function orientationToOrientationLock(
|
function orientationToOrientationLock(
|
||||||
orientation: Orientation
|
orientation: Orientation
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as ScreenOrientation from "expo-screen-orientation";
|
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
|
||||||
import { atom } from "jotai";
|
import { atom } from "jotai";
|
||||||
|
|
||||||
export const orientationAtom = atom<number>(
|
export const orientationAtom = atom<number>(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { atom, useAtom } from "jotai";
|
import { atom, useAtom } from "jotai";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import * as ScreenOrientation from "expo-screen-orientation";
|
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
|
||||||
import { storage } from "../mmkv";
|
import { storage } from "../mmkv";
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -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";
|
export const BACKGROUND_FETCH_TASK = "background-fetch";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user