From f20ad671860fb52086c9065aedb91e9cbb187609 Mon Sep 17 00:00:00 2001 From: retardgerman <78982850+retardgerman@users.noreply.github.com> Date: Thu, 2 Jan 2025 08:06:30 +0100 Subject: [PATCH 01/26] fix: updated links --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a0843126..342daa48 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Check out our [Roadmap](https://github.com/users/fredrikburmester/projects/5) to Get the beta on Google Play -Or download the APKs [here on GitHub](https://github.com/fredrikburmester/streamyfin/releases) for Android. +Or download the APKs [here on GitHub](https://github.com/streamyfin/streamyfin/releases) for Android. ### Beta testing @@ -108,7 +108,7 @@ Key points of the MPL-2.0: ## 🌐 Connect with Us -Join our Discord: [https://discord.gg/BuGG9ZNhaE](https://discord.gg/BuGG9ZNhaE) +Join our Discord: [https://discord.gg/aJvAYeycyY](https://discord.gg/aJvAYeycyY) If you have questions or need support, feel free to reach out: @@ -117,7 +117,7 @@ If you have questions or need support, feel free to reach out: ## 📝 Credits -Streamyfin is developed by Fredrik Burmester and is not affiliated with Jellyfin. The app is built with Expo, React Native, and other open-source libraries. +Streamyfin is developed by [Fredrik Burmester](https://github.com/fredrikburmester) and is not affiliated with Jellyfin. The app is built with Expo, React Native, and other open-source libraries. ## ✨ Acknowledgements @@ -130,4 +130,4 @@ I'd like to thank the following people and projects for their contributions to S ## Star History -[![Star History Chart](https://api.star-history.com/svg?repos=fredrikburmester/streamyfin&type=Date)](https://star-history.com/#fredrikburmester/streamyfin&Date) +[![Star History Chart](https://api.star-history.com/svg?repos=streamyfin/streamyfin&type=Date)](https://star-history.com/#streamyfin/streamyfin&Date) From 00847c8d3dd7ce4147b7b98bc46f68687b2d0363 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Thu, 2 Jan 2025 11:45:17 +0100 Subject: [PATCH 02/26] fix: correct routing for actors after favorite tab implementation --- components/common/TouchableItemRouter.tsx | 17 ++++++++++++----- components/series/CastAndCrew.tsx | 11 +++++++++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/components/common/TouchableItemRouter.tsx b/components/common/TouchableItemRouter.tsx index b8af14b6..f57a3277 100644 --- a/components/common/TouchableItemRouter.tsx +++ b/components/common/TouchableItemRouter.tsx @@ -1,6 +1,8 @@ import { useMarkAsPlayed } from "@/hooks/useMarkAsPlayed"; -import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; -import * as Haptics from "expo-haptics"; +import { + BaseItemDto, + BaseItemPerson, +} from "@jellyfin/sdk/lib/generated-client/models"; import { useRouter, useSegments } from "expo-router"; import { PropsWithChildren } from "react"; import { TouchableOpacity, TouchableOpacityProps } from "react-native"; @@ -10,8 +12,13 @@ interface Props extends TouchableOpacityProps { item: BaseItemDto; } -export const itemRouter = (item: BaseItemDto, from: string) => { - if (item.CollectionType === "livetv") { +export const itemRouter = ( + item: BaseItemDto | BaseItemPerson, + from: string +) => { + console.log(item.Type); + + if ("CollectionType" in item && item.CollectionType === "livetv") { return `/(auth)/(tabs)/${from}/livetv`; } @@ -31,7 +38,7 @@ export const itemRouter = (item: BaseItemDto, from: string) => { return `/(auth)/(tabs)/${from}/artists/${item.Id}`; } - if (item.Type === "Person") { + if (item.Type === "Person" || item.Type === "Actor") { return `/(auth)/(tabs)/${from}/actors/${item.Id}`; } diff --git a/components/series/CastAndCrew.tsx b/components/series/CastAndCrew.tsx index 41dfdbb1..2b312f0e 100644 --- a/components/series/CastAndCrew.tsx +++ b/components/series/CastAndCrew.tsx @@ -4,13 +4,14 @@ import { BaseItemDto, BaseItemPerson, } from "@jellyfin/sdk/lib/generated-client/models"; -import { router } from "expo-router"; +import { router, useSegments } from "expo-router"; import { useAtom } from "jotai"; import React, { useMemo } from "react"; import { TouchableOpacity, View, ViewProps } from "react-native"; import { HorizontalScroll } from "../common/HorrizontalScroll"; import { Text } from "../common/Text"; import Poster from "../posters/Poster"; +import { itemRouter } from "../common/TouchableItemRouter"; interface Props extends ViewProps { item?: BaseItemDto | null; @@ -19,6 +20,8 @@ interface Props extends ViewProps { export const CastAndCrew: React.FC = ({ item, loading, ...props }) => { const [api] = useAtom(apiAtom); + const segments = useSegments(); + const from = segments[2]; const destinctPeople = useMemo(() => { const people: BaseItemPerson[] = []; @@ -33,6 +36,8 @@ export const CastAndCrew: React.FC = ({ item, loading, ...props }) => { return people; }, [item?.People]); + if (!from) return null; + return ( Cast & Crew @@ -44,7 +49,9 @@ export const CastAndCrew: React.FC = ({ item, loading, ...props }) => { renderItem={(i) => ( { - router.push(`/actors/${i.Id}`); + const url = itemRouter(i, from); + // @ts-ignore + router.push(url); }} className="flex flex-col w-28" > From 663605b9e851e4afa145afe335d7d59723ed56c1 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Thu, 2 Jan 2025 12:23:58 +0100 Subject: [PATCH 03/26] chore --- app/(auth)/(tabs)/(libraries)/[libraryId].tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/(auth)/(tabs)/(libraries)/[libraryId].tsx b/app/(auth)/(tabs)/(libraries)/[libraryId].tsx index bd0df182..414f6e90 100644 --- a/app/(auth)/(tabs)/(libraries)/[libraryId].tsx +++ b/app/(auth)/(tabs)/(libraries)/[libraryId].tsx @@ -41,7 +41,6 @@ import { } from "@jellyfin/sdk/lib/utils/api"; import { FlashList } from "@shopify/flash-list"; import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { colletionTypeToItemType } from "@/utils/collectionTypeToItemType"; const Page = () => { const searchParams = useLocalSearchParams(); From 82e50b9ba3bcf918c7e94af20d9e4712ce8d553e Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Thu, 2 Jan 2025 12:24:17 +0100 Subject: [PATCH 04/26] fix: crash if item not properly analuzed serverside --- components/ItemTechnicalDetails.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/ItemTechnicalDetails.tsx b/components/ItemTechnicalDetails.tsx index 82dae2d3..0c472192 100644 --- a/components/ItemTechnicalDetails.tsx +++ b/components/ItemTechnicalDetails.tsx @@ -175,6 +175,8 @@ const VideoStreamInfo = ({ source }: { source?: MediaSourceInfo }) => { ) as MediaStream; }, [source.MediaStreams]); + if (!videoStream) return null; + return ( Date: Thu, 2 Jan 2025 12:24:25 +0100 Subject: [PATCH 05/26] fix: chromecast on android --- components/Chromecast.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/Chromecast.tsx b/components/Chromecast.tsx index 3e5c8546..6eb1d2a9 100644 --- a/components/Chromecast.tsx +++ b/components/Chromecast.tsx @@ -34,6 +34,7 @@ export const Chromecast: React.FC = ({ useEffect(() => { (async () => { if (!discoveryManager) { + console.warn("DiscoveryManager is not initialized"); return; } @@ -64,6 +65,7 @@ export const Chromecast: React.FC = ({ }} {...props} > + ); @@ -77,6 +79,7 @@ export const Chromecast: React.FC = ({ }} {...props} > + ); From 07c7cb7ab50fa9ecaa2f0f1f9602f221676efaff Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Thu, 2 Jan 2025 16:49:04 +0100 Subject: [PATCH 06/26] feat: refactor settings --- app/(auth)/(tabs)/(custom-links)/index.tsx | 74 +- app/(auth)/(tabs)/(home)/_layout.tsx | 6 + app/(auth)/(tabs)/(home)/settings.tsx | 210 ++---- .../(home)/settings/jellyseerr/page.tsx | 78 +++ .../(tabs)/(home)/settings/logs/page.tsx | 33 + .../(home)/settings/optimized-server/page.tsx | 80 +++ augmentations/number.ts | 28 +- components/ListItem.tsx | 35 - components/list/ListGroup.tsx | 59 ++ components/list/ListItem.tsx | 124 ++++ components/settings/AudioToggles.tsx | 83 +-- components/settings/DownloadSettings.tsx | 103 +++ components/settings/Jellyseerr.tsx | 61 +- components/settings/MediaToggles.tsx | 138 ++-- components/settings/OptimizedServerForm.tsx | 43 ++ components/settings/OtherSettings.tsx | 401 +++++++++++ components/settings/QuickConnect.tsx | 59 ++ components/settings/SettingToggles.tsx | 643 ------------------ components/settings/StorageSettings.tsx | 109 +++ components/settings/SubtitleToggles.tsx | 112 ++- components/settings/UserInfo.tsx | 29 + 21 files changed, 1405 insertions(+), 1103 deletions(-) create mode 100644 app/(auth)/(tabs)/(home)/settings/jellyseerr/page.tsx create mode 100644 app/(auth)/(tabs)/(home)/settings/logs/page.tsx create mode 100644 app/(auth)/(tabs)/(home)/settings/optimized-server/page.tsx delete mode 100644 components/ListItem.tsx create mode 100644 components/list/ListGroup.tsx create mode 100644 components/list/ListItem.tsx create mode 100644 components/settings/DownloadSettings.tsx create mode 100644 components/settings/OptimizedServerForm.tsx create mode 100644 components/settings/OtherSettings.tsx create mode 100644 components/settings/QuickConnect.tsx delete mode 100644 components/settings/SettingToggles.tsx create mode 100644 components/settings/StorageSettings.tsx create mode 100644 components/settings/UserInfo.tsx diff --git a/app/(auth)/(tabs)/(custom-links)/index.tsx b/app/(auth)/(tabs)/(custom-links)/index.tsx index 76b10fb8..bf6dc46b 100644 --- a/app/(auth)/(tabs)/(custom-links)/index.tsx +++ b/app/(auth)/(tabs)/(custom-links)/index.tsx @@ -1,27 +1,29 @@ -import {FlatList, TouchableOpacity, View} from "react-native"; -import {useSafeAreaInsets} from "react-native-safe-area-context"; -import React, {useCallback, useEffect, useState} from "react"; -import {useAtom} from "jotai/index"; -import {apiAtom} from "@/providers/JellyfinProvider"; -import {ListItem} from "@/components/ListItem"; -import * as WebBrowser from 'expo-web-browser'; -import Ionicons from '@expo/vector-icons/Ionicons'; -import {Text} from "@/components/common/Text"; +import { FlatList, TouchableOpacity, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import React, { useCallback, useEffect, useState } from "react"; +import { useAtom } from "jotai/index"; +import { apiAtom } from "@/providers/JellyfinProvider"; +import { ListItem } from "@/components/list/ListItem"; +import * as WebBrowser from "expo-web-browser"; +import Ionicons from "@expo/vector-icons/Ionicons"; +import { Text } from "@/components/common/Text"; export interface MenuLink { - name: string, - url: string, - icon: string + name: string; + url: string; + icon: string; } export default function menuLinks() { const [api] = useAtom(apiAtom); - const insets = useSafeAreaInsets() - const [menuLinks, setMenuLinks] = useState([]) + const insets = useSafeAreaInsets(); + const [menuLinks, setMenuLinks] = useState([]); const getMenuLinks = useCallback(async () => { try { - const response = await api?.axiosInstance.get(api?.basePath + "/web/config.json") + const response = await api?.axiosInstance.get( + api?.basePath + "/web/config.json" + ); const config = response?.data; if (!config && !config.hasOwnProperty("menuLinks")) { @@ -29,15 +31,15 @@ export default function menuLinks() { return; } - setMenuLinks(config?.menuLinks as MenuLink[]) - } catch (error) { - console.error("Failed to retrieve config:", error); - } - }, - [api] - ) + setMenuLinks(config?.menuLinks as MenuLink[]); + } catch (error) { + console.error("Failed to retrieve config:", error); + } + }, [api]); - useEffect(() => { getMenuLinks() }, []); + useEffect(() => { + getMenuLinks(); + }, []); return ( ( - WebBrowser.openBrowserAsync(item.url) }> + renderItem={({ item }) => ( + WebBrowser.openBrowserAsync(item.url)}> } + title={item.name} + iconAfter={} /> - ) - } + )} ItemSeparatorComponent={() => ( - )} + }} + /> + )} ListEmptyComponent={ - - No links - + + No links + } - /> + /> ); -} \ No newline at end of file +} diff --git a/app/(auth)/(tabs)/(home)/_layout.tsx b/app/(auth)/(tabs)/(home)/_layout.tsx index a7cc3cb3..4005586c 100644 --- a/app/(auth)/(tabs)/(home)/_layout.tsx +++ b/app/(auth)/(tabs)/(home)/_layout.tsx @@ -49,6 +49,12 @@ export default function IndexLayout() { title: "Settings", }} /> + {Object.entries(nestedTabPageScreenOptions).map(([name, options]) => ( ))} diff --git a/app/(auth)/(tabs)/(home)/settings.tsx b/app/(auth)/(tabs)/(home)/settings.tsx index 46aecbae..63d1b5c9 100644 --- a/app/(auth)/(tabs)/(home)/settings.tsx +++ b/app/(auth)/(tabs)/(home)/settings.tsx @@ -1,176 +1,94 @@ -import { Button } from "@/components/Button"; import { Text } from "@/components/common/Text"; -import { ListItem } from "@/components/ListItem"; -import { SettingToggles } from "@/components/settings/SettingToggles"; -import {useDownload} from "@/providers/DownloadProvider"; -import { apiAtom, useJellyfin, userAtom } from "@/providers/JellyfinProvider"; -import { clearLogs, useLog } from "@/utils/log"; -import { getQuickConnectApi } from "@jellyfin/sdk/lib/utils/api"; -import { useQuery } from "@tanstack/react-query"; -import * as FileSystem from "expo-file-system"; +import { ListGroup } from "@/components/list/ListGroup"; +import { ListItem } from "@/components/list/ListItem"; +import { AudioToggles } from "@/components/settings/AudioToggles"; +import { DownloadSettings } from "@/components/settings/DownloadSettings"; +import { MediaProvider } from "@/components/settings/MediaContext"; +import { MediaToggles } from "@/components/settings/MediaToggles"; +import { OtherSettings } from "@/components/settings/OtherSettings"; +import { QuickConnect } from "@/components/settings/QuickConnect"; +import { StorageSettings } from "@/components/settings/StorageSettings"; +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"; -import { useAtom } from "jotai"; -import { Alert, ScrollView, View } from "react-native"; -import * as Progress from "react-native-progress"; +import { useNavigation, useRouter } from "expo-router"; +import { useEffect } from "react"; +import { ScrollView, TouchableOpacity, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { toast } from "sonner-native"; export default function settings() { - const { logout } = useJellyfin(); - const { deleteAllFiles, appSizeUsage } = useDownload(); - const { logs } = useLog(); - - const [api] = useAtom(apiAtom); - const [user] = useAtom(userAtom); - + const router = useRouter(); const insets = useSafeAreaInsets(); - - const { data: size, isLoading: appSizeLoading } = useQuery({ - queryKey: ["appSize", appSizeUsage], - queryFn: async () => { - const app = await appSizeUsage; - - const remaining = await FileSystem.getFreeDiskStorageAsync(); - const total = await FileSystem.getTotalDiskCapacityAsync(); - - return { app, remaining, total, used: (total - remaining) / total }; - }, - }); - - const openQuickConnectAuthCodeInput = () => { - Alert.prompt( - "Quick connect", - "Enter the quick connect code", - async (text) => { - if (text) { - try { - const res = await getQuickConnectApi(api!).authorizeQuickConnect({ - code: text, - userId: user?.Id, - }); - if (res.status === 200) { - Haptics.notificationAsync( - Haptics.NotificationFeedbackType.Success - ); - Alert.alert("Success", "Quick connect authorized"); - } else { - Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); - Alert.alert("Error", "Invalid code"); - } - } catch (e) { - Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); - Alert.alert("Error", "Invalid code"); - } - } - } - ); - }; - - const onDeleteClicked = async () => { - try { - await deleteAllFiles(); - Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); - } catch (e) { - Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); - toast.error("Error deleting files"); - } - }; + const { logout } = useJellyfin(); const onClearLogsClicked = async () => { clearLogs(); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); }; + const navigation = useNavigation(); + useEffect(() => { + navigation.setOptions({ + headerRight: () => ( + { + logout(); + }} + > + Log out + + ), + }); + }, []); + return ( - {/* */} - - User Info + + - - - - - - - + + + + + + + + - Quick connect - + + router.push("/settings/jellyseerr/page")} + title={"Jellyseerr Settings"} + showArrow + > + - - - - Storage - - {size && App usage: {size.app.bytesToReadable()}} - + + router.push("/settings/logs/page")} + showArrow + title={"Logs"} /> - {size && ( - - Available: {size.remaining?.bytesToReadable()}, Total:{" "} - {size.total?.bytesToReadable()} - - )} - - - - - - Logs - - {logs?.map((log, index) => ( - - - {log.level} - - - {log.message} - - - ))} - {logs?.length === 0 && ( - No logs available - )} - + + + + ); diff --git a/app/(auth)/(tabs)/(home)/settings/jellyseerr/page.tsx b/app/(auth)/(tabs)/(home)/settings/jellyseerr/page.tsx new file mode 100644 index 00000000..af4247d5 --- /dev/null +++ b/app/(auth)/(tabs)/(home)/settings/jellyseerr/page.tsx @@ -0,0 +1,78 @@ +import { Text } from "@/components/common/Text"; +import { JellyseerrSettings } from "@/components/settings/Jellyseerr"; +import { OptimizedServerForm } from "@/components/settings/OptimizedServerForm"; +import { apiAtom } from "@/providers/JellyfinProvider"; +import { useSettings } from "@/utils/atoms/settings"; +import { getOrSetDeviceId } from "@/utils/device"; +import { getStatistics } from "@/utils/optimize-server"; +import { useMutation } from "@tanstack/react-query"; +import { useNavigation } from "expo-router"; +import { useAtom } from "jotai"; +import { useEffect, useState } from "react"; +import { ActivityIndicator, TouchableOpacity, View } from "react-native"; +import { toast } from "sonner-native"; + +export default function page() { + const navigation = useNavigation(); + + const [api] = useAtom(apiAtom); + const [settings, updateSettings] = useSettings(); + + const [optimizedVersionsServerUrl, setOptimizedVersionsServerUrl] = + useState(settings?.optimizedVersionsServerUrl || ""); + + const saveMutation = useMutation({ + mutationFn: async (newVal: string) => { + if (newVal.length === 0 || !newVal.startsWith("http")) { + toast.error("Invalid URL"); + return; + } + + const updatedUrl = newVal.endsWith("/") ? newVal : newVal + "/"; + + updateSettings({ + optimizedVersionsServerUrl: updatedUrl, + }); + + return await getStatistics({ + url: settings?.optimizedVersionsServerUrl, + authHeader: api?.accessToken, + deviceId: getOrSetDeviceId(), + }); + }, + onSuccess: (data) => { + if (data) { + toast.success("Connected"); + } else { + toast.error("Could not connect"); + } + }, + onError: () => { + toast.error("Could not connect"); + }, + }); + + const onSave = (newVal: string) => { + saveMutation.mutate(newVal); + }; + + // useEffect(() => { + // navigation.setOptions({ + // title: "Optimized Server", + // headerRight: () => + // saveMutation.isPending ? ( + // + // ) : ( + // onSave(optimizedVersionsServerUrl)}> + // Save + // + // ), + // }); + // }, [navigation, optimizedVersionsServerUrl, saveMutation.isPending]); + + return ( + + + + ); +} diff --git a/app/(auth)/(tabs)/(home)/settings/logs/page.tsx b/app/(auth)/(tabs)/(home)/settings/logs/page.tsx new file mode 100644 index 00000000..2e023c7d --- /dev/null +++ b/app/(auth)/(tabs)/(home)/settings/logs/page.tsx @@ -0,0 +1,33 @@ +import { Text } from "@/components/common/Text"; +import { useLog } from "@/utils/log"; +import { ScrollView, View } from "react-native"; + +export default function page() { + const { logs } = useLog(); + + return ( + + + {logs?.map((log, index) => ( + + + {log.level} + + + {log.message} + + + ))} + {logs?.length === 0 && ( + No logs available + )} + + + ); +} diff --git a/app/(auth)/(tabs)/(home)/settings/optimized-server/page.tsx b/app/(auth)/(tabs)/(home)/settings/optimized-server/page.tsx new file mode 100644 index 00000000..b47d565f --- /dev/null +++ b/app/(auth)/(tabs)/(home)/settings/optimized-server/page.tsx @@ -0,0 +1,80 @@ +import { Text } from "@/components/common/Text"; +import { OptimizedServerForm } from "@/components/settings/OptimizedServerForm"; +import { apiAtom } from "@/providers/JellyfinProvider"; +import { useSettings } from "@/utils/atoms/settings"; +import { getOrSetDeviceId } from "@/utils/device"; +import { getStatistics } from "@/utils/optimize-server"; +import { useMutation } from "@tanstack/react-query"; +import { useNavigation } from "expo-router"; +import { useAtom } from "jotai"; +import { useEffect, useState } from "react"; +import { ActivityIndicator, TouchableOpacity, View } from "react-native"; +import { toast } from "sonner-native"; + +export default function page() { + const navigation = useNavigation(); + + const [api] = useAtom(apiAtom); + const [settings, updateSettings] = useSettings(); + + const [optimizedVersionsServerUrl, setOptimizedVersionsServerUrl] = + useState(settings?.optimizedVersionsServerUrl || ""); + + const saveMutation = useMutation({ + mutationFn: async (newVal: string) => { + if (newVal.length === 0 || !newVal.startsWith("http")) { + toast.error("Invalid URL"); + return; + } + + const updatedUrl = newVal.endsWith("/") ? newVal : newVal + "/"; + + updateSettings({ + optimizedVersionsServerUrl: updatedUrl, + }); + + return await getStatistics({ + url: settings?.optimizedVersionsServerUrl, + authHeader: api?.accessToken, + deviceId: getOrSetDeviceId(), + }); + }, + onSuccess: (data) => { + if (data) { + toast.success("Connected"); + } else { + toast.error("Could not connect"); + } + }, + onError: () => { + toast.error("Could not connect"); + }, + }); + + const onSave = (newVal: string) => { + saveMutation.mutate(newVal); + }; + + useEffect(() => { + navigation.setOptions({ + title: "Optimized Server", + headerRight: () => + saveMutation.isPending ? ( + + ) : ( + onSave(optimizedVersionsServerUrl)}> + Save + + ), + }); + }, [navigation, optimizedVersionsServerUrl, saveMutation.isPending]); + + return ( + + + + ); +} diff --git a/augmentations/number.ts b/augmentations/number.ts index 2b7e8dac..c0f53075 100644 --- a/augmentations/number.ts +++ b/augmentations/number.ts @@ -1,9 +1,9 @@ declare global { interface Number { bytesToReadable(): string; - secondsToMilliseconds(): number - minutesToMilliseconds(): number - hoursToMilliseconds(): number + secondsToMilliseconds(): number; + minutesToMilliseconds(): number; + hoursToMilliseconds(): number; } } @@ -11,27 +11,27 @@ Number.prototype.bytesToReadable = function () { const bytes = this.valueOf(); const gb = bytes / 1e9; - if (gb >= 1) return `${gb.toFixed(2)} GB`; + if (gb >= 1) return `${gb.toFixed(0)} GB`; const mb = bytes / 1024.0 / 1024.0; - if (mb >= 1) return `${mb.toFixed(2)} MB`; + if (mb >= 1) return `${mb.toFixed(0)} MB`; const kb = bytes / 1024.0; - if (kb >= 1) return `${kb.toFixed(2)} KB`; + if (kb >= 1) return `${kb.toFixed(0)} KB`; return `${bytes.toFixed(2)} B`; -} +}; Number.prototype.secondsToMilliseconds = function () { - return this.valueOf() * 1000 -} + return this.valueOf() * 1000; +}; Number.prototype.minutesToMilliseconds = function () { - return this.valueOf() * (60).secondsToMilliseconds() -} + return this.valueOf() * (60).secondsToMilliseconds(); +}; Number.prototype.hoursToMilliseconds = function () { - return this.valueOf() * (60).minutesToMilliseconds() -} + return this.valueOf() * (60).minutesToMilliseconds(); +}; -export {}; \ No newline at end of file +export {}; diff --git a/components/ListItem.tsx b/components/ListItem.tsx deleted file mode 100644 index 0287c690..00000000 --- a/components/ListItem.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { PropsWithChildren, ReactNode } from "react"; -import { View, ViewProps } from "react-native"; -import { Text } from "./common/Text"; - -interface Props extends ViewProps { - title?: string | null | undefined; - subTitle?: string | null | undefined; - children?: ReactNode; - iconAfter?: ReactNode; -} - -export const ListItem: React.FC> = ({ - title, - subTitle, - iconAfter, - children, - ...props -}) => { - return ( - - - {title} - {subTitle && ( - - {subTitle} - - )} - - {iconAfter} - - ); -}; diff --git a/components/list/ListGroup.tsx b/components/list/ListGroup.tsx new file mode 100644 index 00000000..87ca5441 --- /dev/null +++ b/components/list/ListGroup.tsx @@ -0,0 +1,59 @@ +import { + PropsWithChildren, + Children, + isValidElement, + cloneElement, + ReactElement, +} from "react"; +import { StyleSheet, View, ViewProps, ViewStyle } from "react-native"; +import { ListItem } from "./ListItem"; +import { Text } from "../common/Text"; + +interface Props extends ViewProps { + title?: string | null | undefined; + description?: ReactElement; +} + +export const ListGroup: React.FC> = ({ + title, + children, + description, + ...props +}) => { + const childrenArray = Children.toArray(children); + + return ( + + + {title} + + + {Children.map(childrenArray, (child, index) => { + if (isValidElement<{ style?: ViewStyle }>(child)) { + return cloneElement(child as any, { + style: StyleSheet.compose( + child.props.style, + index < childrenArray.length - 1 + ? styles.borderBottom + : undefined + ), + }); + } + return child; + })} + + {description && {description}} + + ); +}; + +const styles = StyleSheet.create({ + borderBottom: { + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: "#3D3C40", + }, +}); diff --git a/components/list/ListItem.tsx b/components/list/ListItem.tsx new file mode 100644 index 00000000..46856b00 --- /dev/null +++ b/components/list/ListItem.tsx @@ -0,0 +1,124 @@ +import { PropsWithChildren, ReactNode } from "react"; +import { + TouchableOpacity, + TouchableOpacityProps, + View, + ViewProps, +} from "react-native"; +import { Text } from "../common/Text"; +import { Ionicons } from "@expo/vector-icons"; + +interface Props extends TouchableOpacityProps, ViewProps { + title?: string | null | undefined; + value?: string | null | undefined; + children?: ReactNode; + iconAfter?: ReactNode; + icon?: keyof typeof Ionicons.glyphMap; + showArrow?: boolean; + textColor?: "default" | "blue" | "red"; + onPress?: () => void; +} + +export const ListItem: React.FC> = ({ + title, + value, + iconAfter, + children, + showArrow = false, + icon, + textColor = "default", + onPress, + disabled = false, + ...props +}) => { + if (onPress) + return ( + + + {children} + + + ); + return ( + + + {children} + + + ); +}; + +const ListItemContent = ({ + title, + textColor, + icon, + value, + showArrow, + iconAfter, + children, + ...props +}: Props) => { + return ( + <> + + {icon && ( + + + + )} + + {title} + + {value && ( + + + {value} + + + )} + {children && {children}} + {showArrow && ( + + + + )} + + {iconAfter} + + ); +}; diff --git a/components/settings/AudioToggles.tsx b/components/settings/AudioToggles.tsx index ec9d71ce..62aea437 100644 --- a/components/settings/AudioToggles.tsx +++ b/components/settings/AudioToggles.tsx @@ -3,6 +3,9 @@ import * as DropdownMenu from "zeego/dropdown-menu"; import { Text } from "../common/Text"; import { useMedia } from "./MediaContext"; import { Switch } from "react-native-gesture-handler"; +import { ListGroup } from "../list/ListGroup"; +import { ListItem } from "../list/ListItem"; +import { Ionicons } from "@expo/vector-icons"; interface Props extends ViewProps {} @@ -14,26 +17,35 @@ export const AudioToggles: React.FC = ({ ...props }) => { if (!settings) return null; return ( - - Audio - - - - Audio language - - Choose a default audio language. - - + + + Choose a default audio language. + + } + > + + + updateSettings({ rememberAudioSelections: value }) + } + /> + + - - + + {settings?.defaultAudioLanguage?.DisplayName || "None"} + = ({ ...props }) => { ))} - - - - - Use Default Audio - - Play default audio track regardless of language. - - - - updateSettings({ playDefaultAudioTrack: value }) - } - /> - - - - - - - Set Audio Track From Previous Item - - - Try to set the audio track to the closest match to the last - video. - - - - updateSettings({ rememberAudioSelections: value }) - } - /> - - - + + ); }; diff --git a/components/settings/DownloadSettings.tsx b/components/settings/DownloadSettings.tsx new file mode 100644 index 00000000..ba2ac493 --- /dev/null +++ b/components/settings/DownloadSettings.tsx @@ -0,0 +1,103 @@ +import { Stepper } from "@/components/inputs/Stepper"; +import { useDownload } from "@/providers/DownloadProvider"; +import { Settings, useSettings } from "@/utils/atoms/settings"; +import { Ionicons } from "@expo/vector-icons"; +import { useQueryClient } from "@tanstack/react-query"; +import { useRouter } from "expo-router"; +import React from "react"; +import { Switch, TouchableOpacity } from "react-native"; +import * as DropdownMenu from "zeego/dropdown-menu"; +import { Text } from "../common/Text"; +import { ListGroup } from "../list/ListGroup"; +import { ListItem } from "../list/ListItem"; + +export const DownloadSettings: React.FC = () => { + const [settings, updateSettings] = useSettings(); + const { setProcesses } = useDownload(); + const router = useRouter(); + const queryClient = useQueryClient(); + + if (!settings) return null; + + return ( + + + + + + + {settings.downloadMethod === "remux" ? "Default" : "Optimized"} + + + + + + Methods + { + updateSettings({ downloadMethod: "remux" }); + setProcesses([]); + }} + > + Default + + { + updateSettings({ downloadMethod: "optimized" }); + setProcesses([]); + queryClient.invalidateQueries({ queryKey: ["search"] }); + }} + > + Optimized + + + + + + + + updateSettings({ + remuxConcurrentLimit: value as Settings["remuxConcurrentLimit"], + }) + } + /> + + + + updateSettings({ autoDownload: value })} + /> + + + router.push("/settings/optimized-server/page")} + showArrow + title="Optimized Versions Server" + > + + ); +}; diff --git a/components/settings/Jellyseerr.tsx b/components/settings/Jellyseerr.tsx index ad4f5af7..7dd41c73 100644 --- a/components/settings/Jellyseerr.tsx +++ b/components/settings/Jellyseerr.tsx @@ -3,7 +3,7 @@ import { View } from "react-native"; import { Text } from "../common/Text"; import { useCallback, useRef, useState } from "react"; import { Input } from "../common/Input"; -import { ListItem } from "../ListItem"; +import { ListItem } from "../list/ListItem"; import { Loader } from "../Loader"; import { useSettings } from "@/utils/atoms/settings"; import { Button } from "../Button"; @@ -11,6 +11,7 @@ import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { useAtom } from "jotai"; import { toast } from "sonner-native"; import { useMutation } from "@tanstack/react-query"; +import { ListGroup } from "../list/ListGroup"; export const JellyseerrSettings = () => { const { @@ -83,41 +84,43 @@ export const JellyseerrSettings = () => { }; return ( - - Jellyseerr + {jellyseerrUser ? ( - - - - - - + <> + + + + + + + + - + ) : ( diff --git a/components/settings/MediaToggles.tsx b/components/settings/MediaToggles.tsx index c92902e2..7e4c4346 100644 --- a/components/settings/MediaToggles.tsx +++ b/components/settings/MediaToggles.tsx @@ -1,5 +1,8 @@ -import { useSettings } from "@/utils/atoms/settings"; +import React from "react"; import { TouchableOpacity, View, ViewProps } from "react-native"; +import { useSettings } from "@/utils/atoms/settings"; +import { ListGroup } from "../list/ListGroup"; +import { ListItem } from "../list/ListItem"; import { Text } from "../common/Text"; interface Props extends ViewProps {} @@ -9,86 +12,61 @@ export const MediaToggles: React.FC = ({ ...props }) => { if (!settings) return null; - return ( - - Media - - - - Forward skip length - - Choose length in seconds when skipping in video playback. - - - - - updateSettings({ - forwardSkipTime: Math.max(0, settings.forwardSkipTime - 5), - }) - } - className="w-8 h-8 bg-neutral-800 rounded-l-lg flex items-center justify-center" - > - - - - - {settings.forwardSkipTime}s - - - updateSettings({ - forwardSkipTime: Math.min(60, settings.forwardSkipTime + 5), - }) - } - > - + - - - + const renderSkipControl = ( + value: number, + onDecrease: () => void, + onIncrease: () => void + ) => ( + + + - + + + {value}s + + + + + + + ); - - - Rewind length - - Choose length in seconds when skipping in video playback. - - - - - updateSettings({ - rewindSkipTime: Math.max(0, settings.rewindSkipTime - 5), - }) - } - className="w-8 h-8 bg-neutral-800 rounded-l-lg flex items-center justify-center" - > - - - - - {settings.rewindSkipTime}s - - - updateSettings({ - rewindSkipTime: Math.min(60, settings.rewindSkipTime + 5), - }) - } - > - + - - - - + return ( + + + + {renderSkipControl( + settings.forwardSkipTime, + () => + updateSettings({ + forwardSkipTime: Math.max(0, settings.forwardSkipTime - 5), + }), + () => + updateSettings({ + forwardSkipTime: Math.min(60, settings.forwardSkipTime + 5), + }) + )} + + + + {renderSkipControl( + settings.rewindSkipTime, + () => + updateSettings({ + rewindSkipTime: Math.max(0, settings.rewindSkipTime - 5), + }), + () => + updateSettings({ + rewindSkipTime: Math.min(60, settings.rewindSkipTime + 5), + }) + )} + + ); }; diff --git a/components/settings/OptimizedServerForm.tsx b/components/settings/OptimizedServerForm.tsx new file mode 100644 index 00000000..2aa7ebda --- /dev/null +++ b/components/settings/OptimizedServerForm.tsx @@ -0,0 +1,43 @@ +import { TextInput, View, Linking } from "react-native"; +import { Text } from "../common/Text"; + +interface Props { + value: string; + onChangeValue: (value: string) => void; +} + +export const OptimizedServerForm: React.FC = ({ + value, + onChangeValue, +}) => { + const handleOpenLink = () => { + Linking.openURL("https://github.com/streamyfin/optimized-versions-server"); + }; + + return ( + + + + URL + onChangeValue(text)} + /> + + + + Enter the URL for the optimize server. The URL should include http or + https and optionally the port.{" "} + + Read more about the optimize server. + + + + ); +}; diff --git a/components/settings/OtherSettings.tsx b/components/settings/OtherSettings.tsx new file mode 100644 index 00000000..5ec5d0df --- /dev/null +++ b/components/settings/OtherSettings.tsx @@ -0,0 +1,401 @@ +import { Stepper } from "@/components/inputs/Stepper"; +import { useDownload } from "@/providers/DownloadProvider"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; +import { + ScreenOrientationEnum, + Settings, + useSettings, +} from "@/utils/atoms/settings"; +import { + BACKGROUND_FETCH_TASK, + registerBackgroundFetchAsync, + unregisterBackgroundFetchAsync, +} from "@/utils/background-tasks"; +import { Ionicons } from "@expo/vector-icons"; +import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import * as BackgroundFetch from "expo-background-fetch"; +import { useRouter } from "expo-router"; +import * as ScreenOrientation from "expo-screen-orientation"; +import * as TaskManager from "expo-task-manager"; +import { useAtom } from "jotai"; +import React, { useEffect, useState } from "react"; +import { + Linking, + Switch, + TouchableOpacity, + View, + ViewProps, +} from "react-native"; +import { toast } from "sonner-native"; +import * as DropdownMenu from "zeego/dropdown-menu"; +import { Button } from "../Button"; +import { Input } from "../common/Input"; +import { Text } from "../common/Text"; +import { ListGroup } from "../list/ListGroup"; +import { ListItem } from "../list/ListItem"; +import { Loader } from "../Loader"; + +interface Props extends ViewProps {} + +export const OtherSettings: React.FC = () => { + const [settings, updateSettings] = useSettings(); + + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + + const [marlinUrl, setMarlinUrl] = useState(""); + + /******************** + * Background task + *******************/ + const checkStatusAsync = async () => { + await BackgroundFetch.getStatusAsync(); + return await TaskManager.isTaskRegisteredAsync(BACKGROUND_FETCH_TASK); + }; + + useEffect(() => { + (async () => { + const registered = await checkStatusAsync(); + + if (settings?.autoDownload === true && !registered) { + registerBackgroundFetchAsync(); + toast.success("Background downloads enabled"); + } else if (settings?.autoDownload === false && registered) { + unregisterBackgroundFetchAsync(); + toast.info("Background downloads disabled"); + } else if (settings?.autoDownload === true && registered) { + // Don't to anything + } else if (settings?.autoDownload === false && !registered) { + // Don't to anything + } else { + updateSettings({ autoDownload: false }); + } + })(); + }, [settings?.autoDownload]); + /********************** + *********************/ + + const { + data: mediaListCollections, + isLoading: isLoadingMediaListCollections, + } = useQuery({ + queryKey: ["sf_promoted", user?.Id, settings?.usePopularPlugin], + queryFn: async () => { + if (!api || !user?.Id) return []; + + const response = await getItemsApi(api).getItems({ + userId: user.Id, + tags: ["sf_promoted"], + recursive: true, + fields: ["Tags"], + includeItemTypes: ["BoxSet"], + }); + + return response.data.Items ?? []; + }, + enabled: !!api && !!user?.Id && settings?.usePopularPlugin === true, + staleTime: 0, + }); + + if (!settings) return null; + + return ( + + + updateSettings({ autoRotate: value })} + /> + + + + + + + + {ScreenOrientationEnum[settings.defaultVideoOrientation]} + + + + + + Orientation + { + updateSettings({ + defaultVideoOrientation: + ScreenOrientation.OrientationLock.DEFAULT, + }); + }} + > + + { + ScreenOrientationEnum[ + ScreenOrientation.OrientationLock.DEFAULT + ] + } + + + { + updateSettings({ + defaultVideoOrientation: + ScreenOrientation.OrientationLock.PORTRAIT_UP, + }); + }} + > + + { + ScreenOrientationEnum[ + ScreenOrientation.OrientationLock.PORTRAIT_UP + ] + } + + + { + updateSettings({ + defaultVideoOrientation: + ScreenOrientation.OrientationLock.LANDSCAPE_LEFT, + }); + }} + > + + { + ScreenOrientationEnum[ + ScreenOrientation.OrientationLock.LANDSCAPE_LEFT + ] + } + + + { + updateSettings({ + defaultVideoOrientation: + ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT, + }); + }} + > + + { + ScreenOrientationEnum[ + ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT + ] + } + + + + + + + + + updateSettings({ safeAreaInControlsEnabled: value }) + } + /> + + + + Linking.openURL( + "https://github.com/lostb1t/jellyfin-plugin-media-lists" + ) + } + > + updateSettings({ usePopularPlugin: value })} + /> + + + {settings.usePopularPlugin && ( + + {mediaListCollections?.map((mlc) => ( + + { + if (!settings.mediaListCollectionIds) { + updateSettings({ + mediaListCollectionIds: [mlc.Id!], + }); + return; + } + + updateSettings({ + mediaListCollectionIds: + settings.mediaListCollectionIds.includes(mlc.Id!) + ? settings.mediaListCollectionIds.filter( + (id) => id !== mlc.Id + ) + : [...settings.mediaListCollectionIds, mlc.Id!], + }); + }} + /> + + ))} + {isLoadingMediaListCollections && } + {mediaListCollections?.length === 0 && ( + + No collections found. Add some in Jellyfin. + + )} + + )} + + + + + + + {settings.searchEngine} + + + + + + Orientation + { + updateSettings({ + defaultVideoOrientation: + ScreenOrientation.OrientationLock.DEFAULT, + }); + }} + > + + { + ScreenOrientationEnum[ + ScreenOrientation.OrientationLock.DEFAULT + ] + } + + + { + updateSettings({ + defaultVideoOrientation: + ScreenOrientation.OrientationLock.PORTRAIT_UP, + }); + }} + > + + { + ScreenOrientationEnum[ + ScreenOrientation.OrientationLock.PORTRAIT_UP + ] + } + + + { + updateSettings({ + defaultVideoOrientation: + ScreenOrientation.OrientationLock.LANDSCAPE_LEFT, + }); + }} + > + + { + ScreenOrientationEnum[ + ScreenOrientation.OrientationLock.LANDSCAPE_LEFT + ] + } + + + { + updateSettings({ + defaultVideoOrientation: + ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT, + }); + }} + > + + { + ScreenOrientationEnum[ + ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT + ] + } + + + + + + + {settings.searchEngine === "Marlin" && ( + + + setMarlinUrl(text)} + /> + + + + )} + + + Linking.openURL( + "https://jellyfin.org/docs/general/clients/web-config/#custom-menu-links" + ) + } + > + + updateSettings({ showCustomMenuLinks: value }) + } + /> + + + ); +}; diff --git a/components/settings/QuickConnect.tsx b/components/settings/QuickConnect.tsx new file mode 100644 index 00000000..8067187f --- /dev/null +++ b/components/settings/QuickConnect.tsx @@ -0,0 +1,59 @@ +import { Alert, View, ViewProps } from "react-native"; +import { Text } from "../common/Text"; +import { ListItem } from "../list/ListItem"; +import { Button } from "../Button"; +import { apiAtom, useJellyfin, userAtom } from "@/providers/JellyfinProvider"; +import { useAtom } from "jotai"; +import Constants from "expo-constants"; +import Application from "expo-application"; +import { ListGroup } from "../list/ListGroup"; +import { getQuickConnectApi } from "@jellyfin/sdk/lib/utils/api"; +import * as Haptics from "expo-haptics"; + +interface Props extends ViewProps {} + +export const QuickConnect: React.FC = ({ ...props }) => { + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + + const openQuickConnectAuthCodeInput = () => { + Alert.prompt( + "Quick connect", + "Enter the quick connect code", + async (text) => { + if (text) { + try { + const res = await getQuickConnectApi(api!).authorizeQuickConnect({ + code: text, + userId: user?.Id, + }); + if (res.status === 200) { + Haptics.notificationAsync( + Haptics.NotificationFeedbackType.Success + ); + Alert.alert("Success", "Quick connect authorized"); + } else { + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); + Alert.alert("Error", "Invalid code"); + } + } catch (e) { + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); + Alert.alert("Error", "Invalid code"); + } + } + } + ); + }; + + return ( + + + + + + ); +}; diff --git a/components/settings/SettingToggles.tsx b/components/settings/SettingToggles.tsx deleted file mode 100644 index a83f95f8..00000000 --- a/components/settings/SettingToggles.tsx +++ /dev/null @@ -1,643 +0,0 @@ -import { useDownload } from "@/providers/DownloadProvider"; -import { - apiAtom, - getOrSetDeviceId, - userAtom, -} from "@/providers/JellyfinProvider"; -import { - ScreenOrientationEnum, - Settings, - useSettings, -} from "@/utils/atoms/settings"; -import { - BACKGROUND_FETCH_TASK, - registerBackgroundFetchAsync, - unregisterBackgroundFetchAsync, -} from "@/utils/background-tasks"; -import { getStatistics } from "@/utils/optimize-server"; -import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; -import { useQuery, useQueryClient } from "@tanstack/react-query"; -import * as BackgroundFetch from "expo-background-fetch"; -import * as ScreenOrientation from "expo-screen-orientation"; -import * as TaskManager from "expo-task-manager"; -import { useAtom } from "jotai"; -import React, { useCallback, useEffect, useRef, useState } from "react"; -import { - Linking, - Switch, - TouchableOpacity, - View, - ViewProps, -} from "react-native"; -import { toast } from "sonner-native"; -import * as DropdownMenu from "zeego/dropdown-menu"; -import { Button } from "../Button"; -import { Input } from "../common/Input"; -import { Text } from "../common/Text"; -import { Loader } from "../Loader"; -import { MediaToggles } from "./MediaToggles"; -import { Stepper } from "@/components/inputs/Stepper"; -import { MediaProvider } from "./MediaContext"; -import { SubtitleToggles } from "./SubtitleToggles"; -import { AudioToggles } from "./AudioToggles"; -import { JellyseerrApi, useJellyseerr } from "@/hooks/useJellyseerr"; -import { ListItem } from "@/components/ListItem"; -import { JellyseerrSettings } from "./Jellyseerr"; - -interface Props extends ViewProps {} - -export const SettingToggles: React.FC = ({ ...props }) => { - const [settings, updateSettings] = useSettings(); - const { setProcesses } = useDownload(); - - const [api] = useAtom(apiAtom); - const [user] = useAtom(userAtom); - - const [marlinUrl, setMarlinUrl] = useState(""); - const [optimizedVersionsServerUrl, setOptimizedVersionsServerUrl] = - useState(settings?.optimizedVersionsServerUrl || ""); - - const queryClient = useQueryClient(); - - /******************** - * Background task - *******************/ - const checkStatusAsync = async () => { - await BackgroundFetch.getStatusAsync(); - return await TaskManager.isTaskRegisteredAsync(BACKGROUND_FETCH_TASK); - }; - - useEffect(() => { - (async () => { - const registered = await checkStatusAsync(); - - if (settings?.autoDownload === true && !registered) { - registerBackgroundFetchAsync(); - toast.success("Background downloads enabled"); - } else if (settings?.autoDownload === false && registered) { - unregisterBackgroundFetchAsync(); - toast.info("Background downloads disabled"); - } else if (settings?.autoDownload === true && registered) { - // Don't to anything - } else if (settings?.autoDownload === false && !registered) { - // Don't to anything - } else { - updateSettings({ autoDownload: false }); - } - })(); - }, [settings?.autoDownload]); - /********************** - *********************/ - - const { - data: mediaListCollections, - isLoading: isLoadingMediaListCollections, - } = useQuery({ - queryKey: ["sf_promoted", user?.Id, settings?.usePopularPlugin], - queryFn: async () => { - if (!api || !user?.Id) return []; - - const response = await getItemsApi(api).getItems({ - userId: user.Id, - tags: ["sf_promoted"], - recursive: true, - fields: ["Tags"], - includeItemTypes: ["BoxSet"], - }); - - return response.data.Items ?? []; - }, - enabled: !!api && !!user?.Id && settings?.usePopularPlugin === true, - staleTime: 0, - }); - - if (!settings) return null; - - return ( - - {/* - Look and feel - - - - Coming soon - - Options for changing the look and feel of the app. - - - - - - */} - - - - - - - - - Other - - - - - Auto rotate - - Important on android since the video player orientation is - locked to the app orientation. - - - updateSettings({ autoRotate: value })} - /> - - - - - Video orientation - - Set the full screen video player orientation. - - - - - - - {ScreenOrientationEnum[settings.defaultVideoOrientation]} - - - - - Orientation - { - updateSettings({ - defaultVideoOrientation: - ScreenOrientation.OrientationLock.DEFAULT, - }); - }} - > - - { - ScreenOrientationEnum[ - ScreenOrientation.OrientationLock.DEFAULT - ] - } - - - { - updateSettings({ - defaultVideoOrientation: - ScreenOrientation.OrientationLock.PORTRAIT_UP, - }); - }} - > - - { - ScreenOrientationEnum[ - ScreenOrientation.OrientationLock.PORTRAIT_UP - ] - } - - - { - updateSettings({ - defaultVideoOrientation: - ScreenOrientation.OrientationLock.LANDSCAPE_LEFT, - }); - }} - > - - { - ScreenOrientationEnum[ - ScreenOrientation.OrientationLock.LANDSCAPE_LEFT - ] - } - - - { - updateSettings({ - defaultVideoOrientation: - ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT, - }); - }} - > - - { - ScreenOrientationEnum[ - ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT - ] - } - - - - - - - - - Safe area in controls - - Enable safe area in video player controls - - - - updateSettings({ safeAreaInControlsEnabled: value }) - } - /> - - - - - - Use popular lists plugin - Made by: lostb1t - { - Linking.openURL( - "https://github.com/lostb1t/jellyfin-plugin-media-lists" - ); - }} - > - More info - - - - updateSettings({ usePopularPlugin: value }) - } - /> - - {settings.usePopularPlugin && ( - - {mediaListCollections?.map((mlc) => ( - - - {mlc.Name} - - { - if (!settings.mediaListCollectionIds) { - updateSettings({ - mediaListCollectionIds: [mlc.Id!], - }); - return; - } - - updateSettings({ - mediaListCollectionIds: - settings.mediaListCollectionIds.includes(mlc.Id!) - ? settings.mediaListCollectionIds.filter( - (id) => id !== mlc.Id - ) - : [...settings.mediaListCollectionIds, mlc.Id!], - }); - }} - /> - - ))} - {isLoadingMediaListCollections && ( - - - - )} - {mediaListCollections?.length === 0 && ( - - - No collections found. Add some in Jellyfin. - - - )} - - )} - - - - - - Search engine - - Choose the search engine you want to use. - - - - - - {settings.searchEngine} - - - - Profiles - { - updateSettings({ searchEngine: "Jellyfin" }); - queryClient.invalidateQueries({ queryKey: ["search"] }); - }} - > - Jellyfin - - { - updateSettings({ searchEngine: "Marlin" }); - queryClient.invalidateQueries({ queryKey: ["search"] }); - }} - > - Marlin - - - - - {settings.searchEngine === "Marlin" && ( - - - - setMarlinUrl(text)} - /> - - - - - {settings.marlinServerUrl && ( - - Current: {settings.marlinServerUrl} - - )} - - )} - - - - - Show Custom Menu Links - - Show custom menu links defined inside your Jellyfin web - config.json file - - - Linking.openURL( - "https://jellyfin.org/docs/general/clients/web-config/#custom-menu-links" - ) - } - > - More info - - - - updateSettings({ showCustomMenuLinks: value }) - } - /> - - - - - - Downloads - - - - Download method - - Choose the download method to use. Optimized requires the - optimized server. - - - - - - - {settings.downloadMethod === "remux" - ? "Default" - : "Optimized"} - - - - - Methods - { - updateSettings({ downloadMethod: "remux" }); - setProcesses([]); - }} - > - Default - - { - updateSettings({ downloadMethod: "optimized" }); - setProcesses([]); - queryClient.invalidateQueries({ queryKey: ["search"] }); - }} - > - Optimized - - - - - - - Remux max download - - This is the total media you want to be able to download at the - same time. - - - - updateSettings({ - remuxConcurrentLimit: - value as Settings["remuxConcurrentLimit"], - }) - } - /> - - - - Auto download - - This will automatically download the media file when it's - finished optimizing on the server. - - - updateSettings({ autoDownload: value })} - /> - - - - - - - Optimized versions server - - - - Set the URL for the optimized versions server for downloads. - - - - - setOptimizedVersionsServerUrl(text)} - /> - - - - - - - - - - ); -}; diff --git a/components/settings/StorageSettings.tsx b/components/settings/StorageSettings.tsx new file mode 100644 index 00000000..5b693acd --- /dev/null +++ b/components/settings/StorageSettings.tsx @@ -0,0 +1,109 @@ +import { Button } from "@/components/Button"; +import { Text } from "@/components/common/Text"; +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 { View } from "react-native"; +import * as Progress from "react-native-progress"; +import { toast } from "sonner-native"; +import { ListGroup } from "../list/ListGroup"; +import { ListItem } from "../list/ListItem"; + +export const StorageSettings = () => { + const { deleteAllFiles, appSizeUsage } = useDownload(); + + const { data: size, isLoading: appSizeLoading } = useQuery({ + queryKey: ["appSize", appSizeUsage], + queryFn: async () => { + const app = await appSizeUsage; + + const remaining = await FileSystem.getFreeDiskStorageAsync(); + const total = await FileSystem.getTotalDiskCapacityAsync(); + + return { app, remaining, total, used: (total - remaining) / total }; + }, + }); + + const onDeleteClicked = async () => { + try { + await deleteAllFiles(); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + } catch (e) { + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); + toast.error("Error deleting files"); + } + }; + + const calculatePercentage = (value: number, total: number) => { + return ((value / total) * 100).toFixed(2); + }; + + return ( + + + + Storage + {size && ( + + {Number(size.total - size.remaining).bytesToReadable()} of{" "} + {size.total?.bytesToReadable()} used + + )} + + + {size && ( + <> + + + + )} + + + {size && ( + <> + + + + App {calculatePercentage(size.app, size.total)}% + + + + + + Phone{" "} + {calculatePercentage( + size.total - size.remaining - size.app, + size.total + )} + % + + + + )} + + + + + + + ); +}; diff --git a/components/settings/SubtitleToggles.tsx b/components/settings/SubtitleToggles.tsx index 93745df2..66c514b1 100644 --- a/components/settings/SubtitleToggles.tsx +++ b/components/settings/SubtitleToggles.tsx @@ -3,6 +3,9 @@ import * as DropdownMenu from "zeego/dropdown-menu"; import { Text } from "../common/Text"; import { useMedia } from "./MediaContext"; import { Switch } from "react-native-gesture-handler"; +import { ListGroup } from "../list/ListGroup"; +import { ListItem } from "../list/ListItem"; +import { Ionicons } from "@expo/vector-icons"; import { SubtitlePlaybackMode } from "@jellyfin/sdk/lib/generated-client"; interface Props extends ViewProps {} @@ -11,6 +14,7 @@ export const SubtitleToggles: React.FC = ({ ...props }) => { const media = useMedia(); const { settings, updateSettings } = media; const cultures = media.cultures; + if (!settings) return null; const subtitleModes = [ @@ -22,26 +26,27 @@ export const SubtitleToggles: React.FC = ({ ...props }) => { ]; return ( - - Subtitle - - - - Subtitle language - - Choose a default subtitle language. - - + + + Configure subtitle preferences. + + } + > + - - + + {settings?.defaultSubtitleLanguage?.DisplayName || "None"} + = ({ ...props }) => { ))} - + - - - Subtitle Mode - - Subtitles are loaded based on the default and forced flags in the - embedded metadata. Language preferences are considered when - multiple options are available. - - + - - {settings?.subtitleMode || "Loading"} + + + {settings?.subtitleMode || "Loading"} + + = ({ ...props }) => { ))} - + - - - - - Set Subtitle Track From Previous Item - - - Try to set the subtitle track to the closest match to the last - video. - - - - updateSettings({ rememberSubtitleSelections: value }) - } - /> - - + + + updateSettings({ rememberSubtitleSelections: value }) + } + /> + - - - Subtitle Size - - Choose a default subtitle size for direct play (only works for - some subtitle formats). - - + @@ -170,7 +148,7 @@ export const SubtitleToggles: React.FC = ({ ...props }) => { > - - + {settings.subtitleSize} = ({ ...props }) => { + - - + + ); }; diff --git a/components/settings/UserInfo.tsx b/components/settings/UserInfo.tsx new file mode 100644 index 00000000..5d80a60d --- /dev/null +++ b/components/settings/UserInfo.tsx @@ -0,0 +1,29 @@ +import { View, ViewProps } from "react-native"; +import { Text } from "../common/Text"; +import { ListItem } from "../list/ListItem"; +import { Button } from "../Button"; +import { apiAtom, useJellyfin, userAtom } from "@/providers/JellyfinProvider"; +import { useAtom } from "jotai"; +import Constants from "expo-constants"; +import Application from "expo-application"; +import { ListGroup } from "../list/ListGroup"; + +interface Props extends ViewProps {} + +export const UserInfo: React.FC = ({ ...props }) => { + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + + const version = Application?.nativeApplicationVersion || "N/A"; + + return ( + + + + + + + + + ); +}; From 77f14a7d5b6194cac25c839ea4dc5bf8b997c977 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Thu, 2 Jan 2025 16:50:58 +0100 Subject: [PATCH 07/26] fix: spelling --- components/settings/QuickConnect.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/settings/QuickConnect.tsx b/components/settings/QuickConnect.tsx index 8067187f..226120bb 100644 --- a/components/settings/QuickConnect.tsx +++ b/components/settings/QuickConnect.tsx @@ -50,7 +50,7 @@ export const QuickConnect: React.FC = ({ ...props }) => { From 730823c520c1eb8d134f30716acb3ab458130789 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Thu, 2 Jan 2025 17:18:01 +0100 Subject: [PATCH 08/26] fix: marlin search settings --- app/(auth)/(tabs)/(home)/_layout.tsx | 8 +- app/(auth)/(tabs)/(home)/settings.tsx | 11 +- .../(home)/settings/marlin-search/page.tsx | 103 +++++++++++ app/(auth)/(tabs)/(search)/index.tsx | 5 + components/settings/DownloadSettings.tsx | 160 +++++++++--------- components/settings/OtherSettings.tsx | 144 +--------------- components/settings/PluginSettings.tsx | 30 ++++ 7 files changed, 235 insertions(+), 226 deletions(-) create mode 100644 app/(auth)/(tabs)/(home)/settings/marlin-search/page.tsx create mode 100644 components/settings/PluginSettings.tsx diff --git a/app/(auth)/(tabs)/(home)/_layout.tsx b/app/(auth)/(tabs)/(home)/_layout.tsx index 4005586c..8e313eaf 100644 --- a/app/(auth)/(tabs)/(home)/_layout.tsx +++ b/app/(auth)/(tabs)/(home)/_layout.tsx @@ -1,6 +1,6 @@ import { Chromecast } from "@/components/Chromecast"; import { nestedTabPageScreenOptions } from "@/components/stacks/NestedTabPageStack"; -import { Feather, Ionicons } from "@expo/vector-icons"; +import { Feather } from "@expo/vector-icons"; import { Stack, useRouter } from "expo-router"; import { Platform, TouchableOpacity, View } from "react-native"; @@ -55,6 +55,12 @@ export default function IndexLayout() { title: "", }} /> + {Object.entries(nestedTabPageScreenOptions).map(([name, options]) => ( ))} diff --git a/app/(auth)/(tabs)/(home)/settings.tsx b/app/(auth)/(tabs)/(home)/settings.tsx index 63d1b5c9..8f6d102a 100644 --- a/app/(auth)/(tabs)/(home)/settings.tsx +++ b/app/(auth)/(tabs)/(home)/settings.tsx @@ -6,6 +6,7 @@ import { DownloadSettings } from "@/components/settings/DownloadSettings"; import { MediaProvider } from "@/components/settings/MediaContext"; import { MediaToggles } from "@/components/settings/MediaToggles"; import { OtherSettings } from "@/components/settings/OtherSettings"; +import { PluginSettings } from "@/components/settings/PluginSettings"; import { QuickConnect } from "@/components/settings/QuickConnect"; import { StorageSettings } from "@/components/settings/StorageSettings"; import { SubtitleToggles } from "@/components/settings/SubtitleToggles"; @@ -63,15 +64,7 @@ export default function settings() { - - - router.push("/settings/jellyseerr/page")} - title={"Jellyseerr Settings"} - showArrow - > - - + diff --git a/app/(auth)/(tabs)/(home)/settings/marlin-search/page.tsx b/app/(auth)/(tabs)/(home)/settings/marlin-search/page.tsx new file mode 100644 index 00000000..b8255c6e --- /dev/null +++ b/app/(auth)/(tabs)/(home)/settings/marlin-search/page.tsx @@ -0,0 +1,103 @@ +import { Text } from "@/components/common/Text"; +import { ListGroup } from "@/components/list/ListGroup"; +import { ListItem } from "@/components/list/ListItem"; +import { apiAtom } from "@/providers/JellyfinProvider"; +import { useSettings } from "@/utils/atoms/settings"; +import { useQueryClient } from "@tanstack/react-query"; +import { useNavigation } from "expo-router"; +import { useAtom } from "jotai"; +import { useEffect, useState } from "react"; +import { + Linking, + Switch, + TextInput, + TouchableOpacity, + View, +} from "react-native"; +import { toast } from "sonner-native"; + +export default function page() { + const navigation = useNavigation(); + + const [settings, updateSettings] = useSettings(); + const queryClient = useQueryClient(); + + const [value, setValue] = useState(settings?.marlinServerUrl || ""); + + const onSave = (val: string) => { + updateSettings({ + marlinServerUrl: !val.endsWith("/") ? val : val.slice(0, -1), + }); + toast.success("Saved"); + }; + + const handleOpenLink = () => { + Linking.openURL("https://github.com/fredrikburmester/marlin-search"); + }; + + useEffect(() => { + navigation.setOptions({ + headerRight: () => ( + onSave(value)}> + Save + + ), + }); + }, [navigation, value]); + + if (!settings) return null; + + return ( + + + { + updateSettings({ searchEngine: "Jellyfin" }); + queryClient.invalidateQueries({ queryKey: ["search"] }); + }} + > + { + updateSettings({ searchEngine: value ? "Marlin" : "Jellyfin" }); + queryClient.invalidateQueries({ queryKey: ["search"] }); + }} + /> + + + + + + + URL + setValue(text)} + /> + + + + Enter the URL for the Marlin server. The URL should include http or + https and optionally the port.{" "} + + Read more about Marlin. + + + + + ); +} diff --git a/app/(auth)/(tabs)/(search)/index.tsx b/app/(auth)/(tabs)/(search)/index.tsx index a24a65f4..9f4ed6be 100644 --- a/app/(auth)/(tabs)/(search)/index.tsx +++ b/app/(auth)/(tabs)/(search)/index.tsx @@ -95,6 +95,8 @@ export default function search() { return (searchApi.data.SearchHints as BaseItemDto[]) || []; } else { if (!settings?.marlinServerUrl) return []; + + console.log(settings.marlinServerUrl); const url = `${ settings.marlinServerUrl }/search?q=${encodeURIComponent(query)}&includeItemTypes=${types @@ -102,6 +104,9 @@ export default function search() { .join("&includeItemTypes=")}`; const response1 = await axios.get(url); + + console.log(response1.statusText); + const ids = response1.data.ids; if (!ids || !ids.length) return []; diff --git a/components/settings/DownloadSettings.tsx b/components/settings/DownloadSettings.tsx index ba2ac493..f330dc04 100644 --- a/components/settings/DownloadSettings.tsx +++ b/components/settings/DownloadSettings.tsx @@ -5,13 +5,13 @@ import { Ionicons } from "@expo/vector-icons"; import { useQueryClient } from "@tanstack/react-query"; import { useRouter } from "expo-router"; import React from "react"; -import { Switch, TouchableOpacity } from "react-native"; +import { Switch, TouchableOpacity, View } from "react-native"; import * as DropdownMenu from "zeego/dropdown-menu"; import { Text } from "../common/Text"; import { ListGroup } from "../list/ListGroup"; import { ListItem } from "../list/ListItem"; -export const DownloadSettings: React.FC = () => { +export const DownloadSettings: React.FC = ({ ...props }) => { const [settings, updateSettings] = useSettings(); const { setProcesses } = useDownload(); const router = useRouter(); @@ -20,84 +20,92 @@ export const DownloadSettings: React.FC = () => { if (!settings) return null; return ( - - - - - - - {settings.downloadMethod === "remux" ? "Default" : "Optimized"} - - - - - - Methods - { - updateSettings({ downloadMethod: "remux" }); - setProcesses([]); - }} + + + + + + + + {settings.downloadMethod === "remux" + ? "Default" + : "Optimized"} + + + + + - Default - - { - updateSettings({ downloadMethod: "optimized" }); - setProcesses([]); - queryClient.invalidateQueries({ queryKey: ["search"] }); - }} - > - Optimized - - - - + Methods + { + updateSettings({ downloadMethod: "remux" }); + setProcesses([]); + }} + > + Default + + { + updateSettings({ downloadMethod: "optimized" }); + setProcesses([]); + queryClient.invalidateQueries({ queryKey: ["search"] }); + }} + > + Optimized + + + + - - - updateSettings({ - remuxConcurrentLimit: value as Settings["remuxConcurrentLimit"], - }) - } - /> - + + + updateSettings({ + remuxConcurrentLimit: value as Settings["remuxConcurrentLimit"], + }) + } + /> + - - updateSettings({ autoDownload: value })} - /> - + > + updateSettings({ autoDownload: value })} + /> + - router.push("/settings/optimized-server/page")} - showArrow - title="Optimized Versions Server" - > - + router.push("/settings/optimized-server/page")} + showArrow + title="Optimized Versions Server" + > + + ); }; diff --git a/components/settings/OtherSettings.tsx b/components/settings/OtherSettings.tsx index 5ec5d0df..318f54cd 100644 --- a/components/settings/OtherSettings.tsx +++ b/components/settings/OtherSettings.tsx @@ -1,11 +1,5 @@ -import { Stepper } from "@/components/inputs/Stepper"; -import { useDownload } from "@/providers/DownloadProvider"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; -import { - ScreenOrientationEnum, - Settings, - useSettings, -} from "@/utils/atoms/settings"; +import { ScreenOrientationEnum, useSettings } from "@/utils/atoms/settings"; import { BACKGROUND_FETCH_TASK, registerBackgroundFetchAsync, @@ -15,22 +9,13 @@ import { Ionicons } from "@expo/vector-icons"; import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import * as BackgroundFetch from "expo-background-fetch"; -import { useRouter } from "expo-router"; import * as ScreenOrientation from "expo-screen-orientation"; import * as TaskManager from "expo-task-manager"; import { useAtom } from "jotai"; import React, { useEffect, useState } from "react"; -import { - Linking, - Switch, - TouchableOpacity, - View, - ViewProps, -} from "react-native"; +import { Linking, Switch, TouchableOpacity, ViewProps } from "react-native"; import { toast } from "sonner-native"; import * as DropdownMenu from "zeego/dropdown-menu"; -import { Button } from "../Button"; -import { Input } from "../common/Input"; import { Text } from "../common/Text"; import { ListGroup } from "../list/ListGroup"; import { ListItem } from "../list/ListItem"; @@ -76,6 +61,8 @@ export const OtherSettings: React.FC = () => { /********************** *********************/ + const queryClient = useQueryClient(); + const { data: mediaListCollections, isLoading: isLoadingMediaListCollections, @@ -258,129 +245,6 @@ export const OtherSettings: React.FC = () => { )} )} - - - - - - - {settings.searchEngine} - - - - - - Orientation - { - updateSettings({ - defaultVideoOrientation: - ScreenOrientation.OrientationLock.DEFAULT, - }); - }} - > - - { - ScreenOrientationEnum[ - ScreenOrientation.OrientationLock.DEFAULT - ] - } - - - { - updateSettings({ - defaultVideoOrientation: - ScreenOrientation.OrientationLock.PORTRAIT_UP, - }); - }} - > - - { - ScreenOrientationEnum[ - ScreenOrientation.OrientationLock.PORTRAIT_UP - ] - } - - - { - updateSettings({ - defaultVideoOrientation: - ScreenOrientation.OrientationLock.LANDSCAPE_LEFT, - }); - }} - > - - { - ScreenOrientationEnum[ - ScreenOrientation.OrientationLock.LANDSCAPE_LEFT - ] - } - - - { - updateSettings({ - defaultVideoOrientation: - ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT, - }); - }} - > - - { - ScreenOrientationEnum[ - ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT - ] - } - - - - - - - {settings.searchEngine === "Marlin" && ( - - - setMarlinUrl(text)} - /> - - - - )} - diff --git a/components/settings/PluginSettings.tsx b/components/settings/PluginSettings.tsx new file mode 100644 index 00000000..3df0171b --- /dev/null +++ b/components/settings/PluginSettings.tsx @@ -0,0 +1,30 @@ +import { useSettings } from "@/utils/atoms/settings"; +import { useRouter } from "expo-router"; +import React from "react"; +import { View } from "react-native"; +import { ListGroup } from "../list/ListGroup"; +import { ListItem } from "../list/ListItem"; + +export const PluginSettings = () => { + const [settings, updateSettings] = useSettings(); + + const router = useRouter(); + + if (!settings) return null; + return ( + + + router.push("/settings/jellyseerr/page")} + title={"Jellyseerr Settings"} + showArrow + /> + router.push("/settings/marlin-search/page")} + title="Marlin Search" + showArrow + /> + + + ); +}; From b8dbce6bf20532b9168b3e7d8d0a0c93b6376453 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Thu, 2 Jan 2025 17:24:43 +0100 Subject: [PATCH 09/26] fix: popular lists plugin --- app/(auth)/(tabs)/(home)/_layout.tsx | 12 ++ .../(home)/settings/popular-lists/page.tsx | 106 ++++++++++++++++++ components/medialists/MediaListSection.tsx | 2 +- components/settings/OtherSettings.tsx | 59 +--------- components/settings/PluginSettings.tsx | 5 + 5 files changed, 125 insertions(+), 59 deletions(-) create mode 100644 app/(auth)/(tabs)/(home)/settings/popular-lists/page.tsx diff --git a/app/(auth)/(tabs)/(home)/_layout.tsx b/app/(auth)/(tabs)/(home)/_layout.tsx index 8e313eaf..fc135fda 100644 --- a/app/(auth)/(tabs)/(home)/_layout.tsx +++ b/app/(auth)/(tabs)/(home)/_layout.tsx @@ -61,6 +61,18 @@ export default function IndexLayout() { title: "", }} /> + + {Object.entries(nestedTabPageScreenOptions).map(([name, options]) => ( ))} diff --git a/app/(auth)/(tabs)/(home)/settings/popular-lists/page.tsx b/app/(auth)/(tabs)/(home)/settings/popular-lists/page.tsx new file mode 100644 index 00000000..5e7b1c79 --- /dev/null +++ b/app/(auth)/(tabs)/(home)/settings/popular-lists/page.tsx @@ -0,0 +1,106 @@ +import { Text } from "@/components/common/Text"; +import { ListGroup } from "@/components/list/ListGroup"; +import { ListItem } from "@/components/list/ListItem"; +import { Loader } from "@/components/Loader"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; +import { useSettings } from "@/utils/atoms/settings"; +import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useNavigation } from "expo-router"; +import { useAtom } from "jotai"; +import { Linking, Switch, View } from "react-native"; + +export default function page() { + const navigation = useNavigation(); + + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + + const [settings, updateSettings] = useSettings(); + + const handleOpenLink = () => { + Linking.openURL("https://github.com/fredrikburmester/marlin-search"); + }; + + const queryClient = useQueryClient(); + + const { + data: mediaListCollections, + isLoading: isLoadingMediaListCollections, + } = useQuery({ + queryKey: ["sf_promoted", user?.Id, settings?.usePopularPlugin], + queryFn: async () => { + if (!api || !user?.Id) return []; + + const response = await getItemsApi(api).getItems({ + userId: user.Id, + tags: ["sf_promoted"], + recursive: true, + fields: ["Tags"], + includeItemTypes: ["BoxSet"], + }); + + return response.data.Items ?? []; + }, + enabled: !!api && !!user?.Id && settings?.usePopularPlugin === true, + staleTime: 0, + }); + + if (!settings) return null; + + return ( + + + { + updateSettings({ usePopularPlugin: true }); + queryClient.invalidateQueries({ queryKey: ["search"] }); + }} + > + { + updateSettings({ usePopularPlugin: value }); + }} + /> + + + + {settings.usePopularPlugin && ( + + {mediaListCollections?.map((mlc) => ( + + { + if (!settings.mediaListCollectionIds) { + updateSettings({ + mediaListCollectionIds: [mlc.Id!], + }); + return; + } + + updateSettings({ + mediaListCollectionIds: + settings.mediaListCollectionIds.includes(mlc.Id!) + ? settings.mediaListCollectionIds.filter( + (id) => id !== mlc.Id + ) + : [...settings.mediaListCollectionIds, mlc.Id!], + }); + }} + /> + + ))} + {isLoadingMediaListCollections && } + {mediaListCollections?.length === 0 && ( + + No collections found. Add some in Jellyfin. + + )} + + )} + + ); +} diff --git a/components/medialists/MediaListSection.tsx b/components/medialists/MediaListSection.tsx index 8474b866..ff5e60a2 100644 --- a/components/medialists/MediaListSection.tsx +++ b/components/medialists/MediaListSection.tsx @@ -61,7 +61,7 @@ export const MediaListSection: React.FC = ({ return ( - + {collection.Name} { const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); - const [marlinUrl, setMarlinUrl] = useState(""); /******************** * Background task @@ -61,29 +60,6 @@ export const OtherSettings: React.FC = () => { /********************** *********************/ - const queryClient = useQueryClient(); - - const { - data: mediaListCollections, - isLoading: isLoadingMediaListCollections, - } = useQuery({ - queryKey: ["sf_promoted", user?.Id, settings?.usePopularPlugin], - queryFn: async () => { - if (!api || !user?.Id) return []; - - const response = await getItemsApi(api).getItems({ - userId: user.Id, - tags: ["sf_promoted"], - recursive: true, - fields: ["Tags"], - includeItemTypes: ["BoxSet"], - }); - - return response.data.Items ?? []; - }, - enabled: !!api && !!user?.Id && settings?.usePopularPlugin === true, - staleTime: 0, - }); if (!settings) return null; @@ -211,40 +187,7 @@ export const OtherSettings: React.FC = () => { /> - {settings.usePopularPlugin && ( - - {mediaListCollections?.map((mlc) => ( - - { - if (!settings.mediaListCollectionIds) { - updateSettings({ - mediaListCollectionIds: [mlc.Id!], - }); - return; - } - - updateSettings({ - mediaListCollectionIds: - settings.mediaListCollectionIds.includes(mlc.Id!) - ? settings.mediaListCollectionIds.filter( - (id) => id !== mlc.Id - ) - : [...settings.mediaListCollectionIds, mlc.Id!], - }); - }} - /> - - ))} - {isLoadingMediaListCollections && } - {mediaListCollections?.length === 0 && ( - - No collections found. Add some in Jellyfin. - - )} - - )} + diff --git a/components/settings/PluginSettings.tsx b/components/settings/PluginSettings.tsx index 3df0171b..0da0cd59 100644 --- a/components/settings/PluginSettings.tsx +++ b/components/settings/PluginSettings.tsx @@ -24,6 +24,11 @@ export const PluginSettings = () => { title="Marlin Search" showArrow /> + router.push("/settings/popular-lists/page")} + title="Popular Lists" + showArrow + /> ); From 2c655b9482652e0e4693523adbff4bb0a59aeb6a Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Thu, 2 Jan 2025 17:25:03 +0100 Subject: [PATCH 10/26] chore --- components/settings/OtherSettings.tsx | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/components/settings/OtherSettings.tsx b/components/settings/OtherSettings.tsx index 2651c283..d280a167 100644 --- a/components/settings/OtherSettings.tsx +++ b/components/settings/OtherSettings.tsx @@ -1,4 +1,3 @@ -import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { ScreenOrientationEnum, useSettings } from "@/utils/atoms/settings"; import { BACKGROUND_FETCH_TASK, @@ -6,30 +5,22 @@ import { unregisterBackgroundFetchAsync, } from "@/utils/background-tasks"; import { Ionicons } from "@expo/vector-icons"; -import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; -import { useQuery, useQueryClient } from "@tanstack/react-query"; import * as BackgroundFetch from "expo-background-fetch"; import * as ScreenOrientation from "expo-screen-orientation"; import * as TaskManager from "expo-task-manager"; -import { useAtom } from "jotai"; -import React, { useEffect, useState } from "react"; +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 { Text } from "../common/Text"; import { ListGroup } from "../list/ListGroup"; import { ListItem } from "../list/ListItem"; -import { Loader } from "../Loader"; interface Props extends ViewProps {} export const OtherSettings: React.FC = () => { const [settings, updateSettings] = useSettings(); - const [api] = useAtom(apiAtom); - const [user] = useAtom(userAtom); - - /******************** * Background task *******************/ @@ -60,7 +51,6 @@ export const OtherSettings: React.FC = () => { /********************** *********************/ - if (!settings) return null; return ( @@ -173,21 +163,6 @@ export const OtherSettings: React.FC = () => { /> - - Linking.openURL( - "https://github.com/lostb1t/jellyfin-plugin-media-lists" - ) - } - > - updateSettings({ usePopularPlugin: value })} - /> - - - From f7bbb20c383eaa12b1b24b13f9d722c12dd50509 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Thu, 2 Jan 2025 17:32:29 +0100 Subject: [PATCH 11/26] fix: popular lists info --- .../(tabs)/(home)/settings/popular-lists/page.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/(auth)/(tabs)/(home)/settings/popular-lists/page.tsx b/app/(auth)/(tabs)/(home)/settings/popular-lists/page.tsx index 5e7b1c79..bbb89400 100644 --- a/app/(auth)/(tabs)/(home)/settings/popular-lists/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/popular-lists/page.tsx @@ -19,7 +19,9 @@ export default function page() { const [settings, updateSettings] = useSettings(); const handleOpenLink = () => { - Linking.openURL("https://github.com/fredrikburmester/marlin-search"); + Linking.openURL( + "https://github.com/lostb1t/jellyfin-plugin-collection-import" + ); }; const queryClient = useQueryClient(); @@ -101,6 +103,13 @@ export default function page() { )} )} + + Popular Lists is a plugin that enables you to show custom Jellyfin lists + on the Streamyfin home page.{" "} + + Read more about Popular Lists. + + ); } From acae4b4544269390cde650c592c2235f8974bf3c Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Thu, 2 Jan 2025 17:32:45 +0100 Subject: [PATCH 12/26] fix: update deps --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 50a4de63..6eb97fd7 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "react-native-pager-view": "6.3.0", "react-native-progress": "^5.0.1", "react-native-reanimated": "~3.10.1", - "react-native-reanimated-carousel": "4.0.0-canary.15", + "react-native-reanimated-carousel": "4.0.0-canary.22", "react-native-safe-area-context": "4.10.5", "react-native-screens": "3.31.1", "react-native-svg": "15.2.0", From ce7e1b255f6c292be562a6b059508728e2a8d530 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Thu, 2 Jan 2025 17:32:50 +0100 Subject: [PATCH 13/26] fix: design --- components/home/LargeMovieCarousel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/home/LargeMovieCarousel.tsx b/components/home/LargeMovieCarousel.tsx index 11676b88..df82b7bc 100644 --- a/components/home/LargeMovieCarousel.tsx +++ b/components/home/LargeMovieCarousel.tsx @@ -88,7 +88,7 @@ export const LargeMovieCarousel: React.FC = ({ ...props }) => { if (!popularItems) return null; return ( - + Date: Thu, 2 Jan 2025 14:08:15 -0500 Subject: [PATCH 14/26] Refresh jellyseerr page when media is requested - refetch details when media request is successful - fix key error on tags - chore: update bun.lockb --- .../jellyseerr/page.tsx | 22 ++++++---- bun.lockb | Bin 590056 -> 589960 bytes components/GenreTags.tsx | 2 +- components/series/JellyseerrSeasons.tsx | 38 +++++++++--------- hooks/useJellyseerr.ts | 3 +- 5 files changed, 37 insertions(+), 28 deletions(-) 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 0be85e92..42edcb59 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx @@ -55,6 +55,7 @@ const Page: React.FC = () => { data: details, isFetching, isLoading, + refetch } = useQuery({ enabled: !!jellyseerrApi && !!result && !!result.id, queryKey: ["jellyseerr", "detail", result.mediaType, result.id], @@ -63,6 +64,7 @@ const Page: React.FC = () => { refetchOnReconnect: true, refetchOnWindowFocus: true, retryOnMount: true, + refetchInterval: 0, queryFn: async () => { return result.mediaType === MediaType.MOVIE ? jellyseerrApi?.movieDetails(result.id!!) @@ -94,15 +96,18 @@ const Page: React.FC = () => { }, [jellyseerrApi, details, result, issueType, issueMessage]); const request = useCallback( - () => + async () => { requestMedia(mediaTitle, { - mediaId: Number(result.id!!), - mediaType: result.mediaType!!, - tvdbId: details?.externalIds?.tvdbId, - seasons: (details as TvDetails)?.seasons - ?.filter?.((s) => s.seasonNumber !== 0) - ?.map?.((s) => s.seasonNumber), - }), + mediaId: Number(result.id!!), + mediaType: result.mediaType!!, + tvdbId: details?.externalIds?.tvdbId, + seasons: (details as TvDetails)?.seasons + ?.filter?.((s) => s.seasonNumber !== 0) + ?.map?.((s) => s.seasonNumber), + }, + refetch + ) + }, [details, result, requestMedia] ); @@ -205,6 +210,7 @@ const Page: React.FC = () => { isLoading={isLoading || isFetching} result={result as TvResult} details={details as TvDetails} + refetch={refetch} /> )} diff --git a/bun.lockb b/bun.lockb index 5712db3bd6ae099c9d606f610f47adb0b0f91ce4..89176e4bfac0991ed6ffa525d3b91a65bdf35f8d 100755 GIT binary patch delta 17066 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 E4|6GItpET3 delta 17184 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 h+|1qEEGfm6eJZKA%lj}XaHT#>Dqe8ihe = ({ tags, textClass = "text-x return ( {tags.map((tag, idx) => ( - + ))} diff --git a/components/series/JellyseerrSeasons.tsx b/components/series/JellyseerrSeasons.tsx index f0efa418..bcd9b336 100644 --- a/components/series/JellyseerrSeasons.tsx +++ b/components/series/JellyseerrSeasons.tsx @@ -15,11 +15,12 @@ import { Ionicons } from "@expo/vector-icons"; import { RoundButton } from "@/components/RoundButton"; import { useJellyseerr } from "@/hooks/useJellyseerr"; import { TvResult } from "@/utils/jellyseerr/server/models/Search"; -import { useQuery } from "@tanstack/react-query"; +import {QueryObserverResult, RefetchOptions, useQuery} from "@tanstack/react-query"; import { HorizontalScroll } from "@/components/common/HorrizontalScroll"; import { Image } from "expo-image"; import MediaRequest from "@/utils/jellyseerr/server/entity/MediaRequest"; import { Loader } from "../Loader"; +import {MovieDetails} from "@/utils/jellyseerr/server/models/Movie"; const JellyseerrSeasonEpisodes: React.FC<{ details: TvDetails; @@ -100,7 +101,8 @@ const JellyseerrSeasons: React.FC<{ isLoading: boolean; result?: TvResult; details?: TvDetails; -}> = ({ isLoading, result, details }) => { + refetch: (options?: (RefetchOptions | undefined)) => Promise>; +}> = ({ isLoading, result, details, refetch }) => { if (!details) return null; const { jellyseerrApi, requestMedia } = useJellyseerr(); @@ -168,6 +170,21 @@ const JellyseerrSeasons: React.FC<{ [requestAll] ); + const requestSeason = useCallback(async (canRequest: Boolean, seasonNumber: number) => { + if (canRequest) { + requestMedia( + `${result?.name!!}, Season ${seasonNumber}`, + { + mediaId: details.id, + mediaType: MediaType.TV, + tvdbId: details.externalIds?.tvdbId, + seasons: [seasonNumber], + }, + refetch + ) + } + }, [requestMedia]); + if (isLoading) return ( @@ -231,22 +248,7 @@ const JellyseerrSeasons: React.FC<{ return ( - requestMedia( - `${result?.name!!}, Season ${ - season.seasonNumber - }`, - { - mediaId: details.id, - mediaType: MediaType.TV, - tvdbId: details.externalIds?.tvdbId, - seasons: [season.seasonNumber], - } - ) - : undefined - } + onPress={() => requestSeason(canRequest, season.seasonNumber)} className={canRequest ? "bg-gray-700/40" : undefined} mediaStatus={ seasons?.find( diff --git a/hooks/useJellyseerr.ts b/hooks/useJellyseerr.ts index c0a8af22..8393798d 100644 --- a/hooks/useJellyseerr.ts +++ b/hooks/useJellyseerr.ts @@ -337,12 +337,13 @@ export const useJellyseerr = () => { }, []); const requestMedia = useCallback( - (title: string, request: MediaRequestBody) => { + (title: string, request: MediaRequestBody, onSuccess?: () => void) => { jellyseerrApi?.request?.(request)?.then((mediaRequest) => { switch (mediaRequest.status) { case MediaRequestStatus.PENDING: case MediaRequestStatus.APPROVED: toast.success(`Requested ${title}!`); + onSuccess?.() break; case MediaRequestStatus.DECLINED: toast.error(`You don't have permission to request!`); From eefcfb8be5623c7f81c8de8c065b8579c5fa3549 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Thu, 2 Jan 2025 20:46:34 +0100 Subject: [PATCH 15/26] fix: plugins design stuff --- app/(auth)/(tabs)/(home)/_layout.tsx | 4 + .../(home)/settings/popular-lists/page.tsx | 96 +++++++++++------- app/(auth)/(tabs)/(search)/_layout.tsx | 13 ++- bun.lockb | Bin 590056 -> 589992 bytes components/home/LargeMovieCarousel.tsx | 16 ++- components/list/ListGroup.tsx | 3 +- 6 files changed, 84 insertions(+), 48 deletions(-) diff --git a/app/(auth)/(tabs)/(home)/_layout.tsx b/app/(auth)/(tabs)/(home)/_layout.tsx index fc135fda..3509be51 100644 --- a/app/(auth)/(tabs)/(home)/_layout.tsx +++ b/app/(auth)/(tabs)/(home)/_layout.tsx @@ -1,4 +1,5 @@ import { Chromecast } from "@/components/Chromecast"; +import { Text } from "@/components/common/Text"; import { nestedTabPageScreenOptions } from "@/components/stacks/NestedTabPageStack"; import { Feather } from "@expo/vector-icons"; import { Stack, useRouter } from "expo-router"; @@ -15,6 +16,9 @@ export default function IndexLayout() { headerLargeTitle: true, headerTitle: "Home", headerBlurEffect: "prominent", + headerLargeStyle: { + backgroundColor: "black", + }, headerTransparent: Platform.OS === "ios" ? true : false, headerShadowVisible: false, headerRight: () => ( diff --git a/app/(auth)/(tabs)/(home)/settings/popular-lists/page.tsx b/app/(auth)/(tabs)/(home)/settings/popular-lists/page.tsx index bbb89400..285f6e7d 100644 --- a/app/(auth)/(tabs)/(home)/settings/popular-lists/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/popular-lists/page.tsx @@ -52,7 +52,7 @@ export default function page() { return ( - + { @@ -68,41 +68,6 @@ export default function page() { /> - - {settings.usePopularPlugin && ( - - {mediaListCollections?.map((mlc) => ( - - { - if (!settings.mediaListCollectionIds) { - updateSettings({ - mediaListCollectionIds: [mlc.Id!], - }); - return; - } - - updateSettings({ - mediaListCollectionIds: - settings.mediaListCollectionIds.includes(mlc.Id!) - ? settings.mediaListCollectionIds.filter( - (id) => id !== mlc.Id - ) - : [...settings.mediaListCollectionIds, mlc.Id!], - }); - }} - /> - - ))} - {isLoadingMediaListCollections && } - {mediaListCollections?.length === 0 && ( - - No collections found. Add some in Jellyfin. - - )} - - )} Popular Lists is a plugin that enables you to show custom Jellyfin lists on the Streamyfin home page.{" "} @@ -110,6 +75,65 @@ export default function page() { Read more about Popular Lists. + + {settings.usePopularPlugin && ( + <> + {!isLoadingMediaListCollections ? ( + <> + {mediaListCollections?.length === 0 ? ( + + No collections found. Add some in Jellyfin. + + ) : ( + <> + + {mediaListCollections?.map((mlc) => ( + + { + if (!settings.mediaListCollectionIds) { + updateSettings({ + mediaListCollectionIds: [mlc.Id!], + }); + return; + } + + updateSettings({ + mediaListCollectionIds: + settings.mediaListCollectionIds.includes( + mlc.Id! + ) + ? settings.mediaListCollectionIds.filter( + (id) => id !== mlc.Id + ) + : [ + ...settings.mediaListCollectionIds, + mlc.Id!, + ], + }); + }} + /> + + ))} + + + Popular Lists is a plugin that enables you to show custom + Jellyfin lists on the Streamyfin home page.{" "} + + Read more about Popular Lists. + + + + )} + + ) : ( + + )} + + )} ); } diff --git a/app/(auth)/(tabs)/(search)/_layout.tsx b/app/(auth)/(tabs)/(search)/_layout.tsx index 2917f1da..12cbad20 100644 --- a/app/(auth)/(tabs)/(search)/_layout.tsx +++ b/app/(auth)/(tabs)/(search)/_layout.tsx @@ -1,4 +1,7 @@ -import {commonScreenOptions, nestedTabPageScreenOptions} from "@/components/stacks/NestedTabPageStack"; +import { + commonScreenOptions, + nestedTabPageScreenOptions, +} from "@/components/stacks/NestedTabPageStack"; import { Stack } from "expo-router"; import { Platform } from "react-native"; @@ -11,6 +14,9 @@ export default function SearchLayout() { headerShown: true, headerLargeTitle: true, headerTitle: "Search", + headerLargeStyle: { + backgroundColor: "black", + }, headerBlurEffect: "prominent", headerTransparent: Platform.OS === "ios" ? true : false, headerShadowVisible: false, @@ -29,10 +35,7 @@ export default function SearchLayout() { headerShadowVisible: false, }} /> - + ); } diff --git a/bun.lockb b/bun.lockb index 5712db3bd6ae099c9d606f610f47adb0b0f91ce4..bf2b7971ad8a3175345379c0bf51bd2b160f9c4c 100755 GIT binary patch delta 13396 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 = ({ ...props }) => { const width = Dimensions.get("screen").width; + if (settings?.usePopularPlugin === false) return null; if (l1 || l2) return null; if (!popularItems) return null; return ( - + } + renderItem={({ item, index }) => } /> > = ({ const childrenArray = Children.toArray(children); return ( - + {title} {Children.map(childrenArray, (child, index) => { if (isValidElement<{ style?: ViewStyle }>(child)) { From d4c722aeac61fb79762584375b0623fc0ba6c972 Mon Sep 17 00:00:00 2001 From: herrrta <73949927+herrrta@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:08:15 -0500 Subject: [PATCH 16/26] Refresh jellyseerr page when media is requested - refetch details when media request is successful - fix key error on tags --- .../jellyseerr/page.tsx | 22 +++++++---- components/GenreTags.tsx | 2 +- components/series/JellyseerrSeasons.tsx | 38 ++++++++++--------- hooks/useJellyseerr.ts | 3 +- 4 files changed, 37 insertions(+), 28 deletions(-) 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 0be85e92..42edcb59 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx @@ -55,6 +55,7 @@ const Page: React.FC = () => { data: details, isFetching, isLoading, + refetch } = useQuery({ enabled: !!jellyseerrApi && !!result && !!result.id, queryKey: ["jellyseerr", "detail", result.mediaType, result.id], @@ -63,6 +64,7 @@ const Page: React.FC = () => { refetchOnReconnect: true, refetchOnWindowFocus: true, retryOnMount: true, + refetchInterval: 0, queryFn: async () => { return result.mediaType === MediaType.MOVIE ? jellyseerrApi?.movieDetails(result.id!!) @@ -94,15 +96,18 @@ const Page: React.FC = () => { }, [jellyseerrApi, details, result, issueType, issueMessage]); const request = useCallback( - () => + async () => { requestMedia(mediaTitle, { - mediaId: Number(result.id!!), - mediaType: result.mediaType!!, - tvdbId: details?.externalIds?.tvdbId, - seasons: (details as TvDetails)?.seasons - ?.filter?.((s) => s.seasonNumber !== 0) - ?.map?.((s) => s.seasonNumber), - }), + mediaId: Number(result.id!!), + mediaType: result.mediaType!!, + tvdbId: details?.externalIds?.tvdbId, + seasons: (details as TvDetails)?.seasons + ?.filter?.((s) => s.seasonNumber !== 0) + ?.map?.((s) => s.seasonNumber), + }, + refetch + ) + }, [details, result, requestMedia] ); @@ -205,6 +210,7 @@ const Page: React.FC = () => { isLoading={isLoading || isFetching} result={result as TvResult} details={details as TvDetails} + refetch={refetch} /> )} diff --git a/components/GenreTags.tsx b/components/GenreTags.tsx index c90ede32..cc5db670 100644 --- a/components/GenreTags.tsx +++ b/components/GenreTags.tsx @@ -26,7 +26,7 @@ export const Tags: React.FC = ({ tags, textClass = "text-x return ( {tags.map((tag, idx) => ( - + ))} diff --git a/components/series/JellyseerrSeasons.tsx b/components/series/JellyseerrSeasons.tsx index f0efa418..bcd9b336 100644 --- a/components/series/JellyseerrSeasons.tsx +++ b/components/series/JellyseerrSeasons.tsx @@ -15,11 +15,12 @@ import { Ionicons } from "@expo/vector-icons"; import { RoundButton } from "@/components/RoundButton"; import { useJellyseerr } from "@/hooks/useJellyseerr"; import { TvResult } from "@/utils/jellyseerr/server/models/Search"; -import { useQuery } from "@tanstack/react-query"; +import {QueryObserverResult, RefetchOptions, useQuery} from "@tanstack/react-query"; import { HorizontalScroll } from "@/components/common/HorrizontalScroll"; import { Image } from "expo-image"; import MediaRequest from "@/utils/jellyseerr/server/entity/MediaRequest"; import { Loader } from "../Loader"; +import {MovieDetails} from "@/utils/jellyseerr/server/models/Movie"; const JellyseerrSeasonEpisodes: React.FC<{ details: TvDetails; @@ -100,7 +101,8 @@ const JellyseerrSeasons: React.FC<{ isLoading: boolean; result?: TvResult; details?: TvDetails; -}> = ({ isLoading, result, details }) => { + refetch: (options?: (RefetchOptions | undefined)) => Promise>; +}> = ({ isLoading, result, details, refetch }) => { if (!details) return null; const { jellyseerrApi, requestMedia } = useJellyseerr(); @@ -168,6 +170,21 @@ const JellyseerrSeasons: React.FC<{ [requestAll] ); + const requestSeason = useCallback(async (canRequest: Boolean, seasonNumber: number) => { + if (canRequest) { + requestMedia( + `${result?.name!!}, Season ${seasonNumber}`, + { + mediaId: details.id, + mediaType: MediaType.TV, + tvdbId: details.externalIds?.tvdbId, + seasons: [seasonNumber], + }, + refetch + ) + } + }, [requestMedia]); + if (isLoading) return ( @@ -231,22 +248,7 @@ const JellyseerrSeasons: React.FC<{ return ( - requestMedia( - `${result?.name!!}, Season ${ - season.seasonNumber - }`, - { - mediaId: details.id, - mediaType: MediaType.TV, - tvdbId: details.externalIds?.tvdbId, - seasons: [season.seasonNumber], - } - ) - : undefined - } + onPress={() => requestSeason(canRequest, season.seasonNumber)} className={canRequest ? "bg-gray-700/40" : undefined} mediaStatus={ seasons?.find( diff --git a/hooks/useJellyseerr.ts b/hooks/useJellyseerr.ts index c0a8af22..8393798d 100644 --- a/hooks/useJellyseerr.ts +++ b/hooks/useJellyseerr.ts @@ -337,12 +337,13 @@ export const useJellyseerr = () => { }, []); const requestMedia = useCallback( - (title: string, request: MediaRequestBody) => { + (title: string, request: MediaRequestBody, onSuccess?: () => void) => { jellyseerrApi?.request?.(request)?.then((mediaRequest) => { switch (mediaRequest.status) { case MediaRequestStatus.PENDING: case MediaRequestStatus.APPROVED: toast.success(`Requested ${title}!`); + onSuccess?.() break; case MediaRequestStatus.DECLINED: toast.error(`You don't have permission to request!`); From 49ae9c6f576a74d17ee5fcf4256540758f18ac9f Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Thu, 2 Jan 2025 20:48:44 +0100 Subject: [PATCH 17/26] chore --- bun.lockb | Bin 589992 -> 590015 bytes package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/bun.lockb b/bun.lockb index bf2b7971ad8a3175345379c0bf51bd2b160f9c4c..c656b351ea11b88ed6a59c302c3ca445c9e5615c 100755 GIT binary patch delta 16179 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 TbhHP`X0tuKa Date: Thu, 2 Jan 2025 21:59:05 +0100 Subject: [PATCH 18/26] feat: save previous servers --- app/(auth)/(tabs)/(favorites)/_layout.tsx | 3 ++ app/(auth)/(tabs)/(libraries)/_layout.tsx | 3 ++ app/login.tsx | 8 +++- components/PreviousServersList.tsx | 55 +++++++++++++++++++++++ providers/JellyfinProvider.tsx | 14 ++++++ 5 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 components/PreviousServersList.tsx diff --git a/app/(auth)/(tabs)/(favorites)/_layout.tsx b/app/(auth)/(tabs)/(favorites)/_layout.tsx index f96cd516..d48dc614 100644 --- a/app/(auth)/(tabs)/(favorites)/_layout.tsx +++ b/app/(auth)/(tabs)/(favorites)/_layout.tsx @@ -11,6 +11,9 @@ export default function SearchLayout() { headerShown: true, headerLargeTitle: true, headerTitle: "Favorites", + headerLargeStyle: { + backgroundColor: "black", + }, headerBlurEffect: "prominent", headerTransparent: Platform.OS === "ios" ? true : false, headerShadowVisible: false, diff --git a/app/(auth)/(tabs)/(libraries)/_layout.tsx b/app/(auth)/(tabs)/(libraries)/_layout.tsx index 489a20e5..17813ed1 100644 --- a/app/(auth)/(tabs)/(libraries)/_layout.tsx +++ b/app/(auth)/(tabs)/(libraries)/_layout.tsx @@ -19,6 +19,9 @@ export default function IndexLayout() { headerLargeTitle: true, headerTitle: "Library", headerBlurEffect: "prominent", + headerLargeStyle: { + backgroundColor: "black", + }, headerTransparent: Platform.OS === "ios" ? true : false, headerShadowVisible: false, headerRight: () => ( diff --git a/app/login.tsx b/app/login.tsx index af81b7d2..7dba3182 100644 --- a/app/login.tsx +++ b/app/login.tsx @@ -1,6 +1,7 @@ import { Button } from "@/components/Button"; import { Input } from "@/components/common/Input"; import { Text } from "@/components/common/Text"; +import { PreviousServersList } from "@/components/PreviousServersList"; import { apiAtom, useJellyfin } from "@/providers/JellyfinProvider"; import { Ionicons } from "@expo/vector-icons"; import { PublicSystemInfo } from "@jellyfin/sdk/lib/generated-client"; @@ -294,9 +295,14 @@ const Login: React.FC = () => { textContentType="URL" maxLength={500} /> - + Make sure to include http or https + { + handleConnect(s.address); + }} + /> - - + + + From 62b00837ec530aeb2a4c669ba53d408174564e63 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Fri, 3 Jan 2025 10:26:12 +0100 Subject: [PATCH 26/26] fix: ? --- components/settings/UserInfo.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/settings/UserInfo.tsx b/components/settings/UserInfo.tsx index 5d80a60d..c42502ce 100644 --- a/components/settings/UserInfo.tsx +++ b/components/settings/UserInfo.tsx @@ -14,7 +14,10 @@ export const UserInfo: React.FC = ({ ...props }) => { const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); - const version = Application?.nativeApplicationVersion || "N/A"; + const version = + Application?.nativeApplicationVersion || + Application?.nativeBuildVersion || + "N/A"; return (