diff --git a/.gitignore b/.gitignore index 33ed8e6d..d4241d81 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,6 @@ credentials.json *.ipa .continuerc.json -.vscode/ \ No newline at end of file +.vscode/ + +bun.lockb \ No newline at end of file diff --git a/app/(auth)/(favorites)/_layout.tsx b/app/(auth)/(favorites)/_layout.tsx new file mode 100644 index 00000000..d8c2cbf8 --- /dev/null +++ b/app/(auth)/(favorites)/_layout.tsx @@ -0,0 +1,35 @@ +import { Chromecast } from "@/components/Chromecast"; +import { nestedTabPageScreenOptions } from "@/components/stacks/NestedTabPageStack"; +import { Feather } from "@expo/vector-icons"; +import { Stack, useRouter } from "expo-router"; +import { Platform, TouchableOpacity, View } from "react-native"; +import { Menu, Divider, Provider } from "react-native-paper"; +import {useState} from "react"; + + +export default function IndexLayout() { + const router = useRouter(); + + return ( + + + ( + + + + ), + }} + /> + + + ); +} diff --git a/app/(auth)/(favorites)/index.tsx b/app/(auth)/(favorites)/index.tsx new file mode 100644 index 00000000..82961319 --- /dev/null +++ b/app/(auth)/(favorites)/index.tsx @@ -0,0 +1,32 @@ +import { useAtom } from "jotai"; +import { useEffect, useState } from "react"; +import { View, Text, FlatList } from "react-native"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; +import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; + +export default function Favorites() { + const [favorites, setFavorites] = useState([]); + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + + useEffect(() => { + const fetchFavorites = async () => { + if (api && user) { + const response = await getItemsApi(api).getItems({ + userId: user.Id, + isFavorite: true, + }); + if (response.data.Items) { + setFavorites(response.data.Items); + } + } + }; + + fetchFavorites(); + }, [api, user]); + + return ( + + + ); +} diff --git a/app/(auth)/(tabs)/(home)/_layout.tsx b/app/(auth)/(tabs)/(home)/_layout.tsx index d3ef928a..32b4b007 100644 --- a/app/(auth)/(tabs)/(home)/_layout.tsx +++ b/app/(auth)/(tabs)/(home)/_layout.tsx @@ -3,10 +3,19 @@ import { nestedTabPageScreenOptions } from "@/components/stacks/NestedTabPageSta import { Feather } from "@expo/vector-icons"; import { Stack, useRouter } from "expo-router"; import { Platform, TouchableOpacity, View } from "react-native"; +import { Menu, Divider, Provider } from "react-native-paper"; +import {useState} from "react"; + export default function IndexLayout() { const router = useRouter(); + const [menuVisible, setMenuVisible] = useState(false); + + const openMenu = () => setMenuVisible(true); + const closeMenu = () => setMenuVisible(false); + return ( + ( + + + + } + > + { + closeMenu(); + router.push("/(auth)/(favorites)"); + }} + title="Favorites" + /> + { router.push("/(auth)/settings"); @@ -63,5 +89,6 @@ export default function IndexLayout() { }} /> + ); } diff --git a/app/(auth)/(tabs)/(home,libraries,search)/series/[id].tsx b/app/(auth)/(tabs)/(home,libraries,search)/series/[id].tsx index e9c9fdea..26db21f4 100644 --- a/app/(auth)/(tabs)/(home,libraries,search)/series/[id].tsx +++ b/app/(auth)/(tabs)/(home,libraries,search)/series/[id].tsx @@ -6,6 +6,7 @@ import { NextUp } from "@/components/series/NextUp"; import { SeasonPicker } from "@/components/series/SeasonPicker"; import { SeriesActions } from "@/components/series/SeriesActions"; import { SeriesHeader } from "@/components/series/SeriesHeader"; +import { FavoriteButton} from "@/components/series/FavoriteButton"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl"; import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById"; @@ -83,6 +84,10 @@ const page: React.FC = () => { !isLoading && allEpisodes && allEpisodes.length > 0 && ( + <> + + {item && } + { )} /> + ), }); }, [allEpisodes, isLoading]); diff --git a/bun.lockb b/bun.lockb index 38913c76..75e16b06 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components/series/FavoriteButton.tsx b/components/series/FavoriteButton.tsx new file mode 100644 index 00000000..62829f43 --- /dev/null +++ b/components/series/FavoriteButton.tsx @@ -0,0 +1,70 @@ +import Ionicons from '@expo/vector-icons/Ionicons'; +import { useAtom } from "jotai"; +import { useCallback, useState, useEffect } from "react"; +import { View } from "react-native"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; +import { getItemsApi, getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api"; +import { RoundButton } from "../RoundButton"; + +interface Props { + seriesId: string; +} + +export const FavoriteButton: React.FC = ({ seriesId }) => { + const [isFavorited, setIsFavorited] = useState(false); + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + + const checkIfFavorited = useCallback(async () => { + if (api && user) { + const response = await getItemsApi(api).getItems({ + userId: user.Id, + isFavorite: true, + recursive: true, + }); + if (!response.data.Items) return; + const isFavorite = response.data.Items.some((x: any) => x.Id === seriesId); + setIsFavorited(isFavorite); + } + }, [api, user, seriesId]); + + useEffect(() => { + checkIfFavorited(); + }, [checkIfFavorited]); + + const markFavorite = useCallback(async () => { + if (api && user) { + await getUserLibraryApi(api).markFavoriteItem({ + userId: user.Id, + itemId: seriesId, + }); + } + }, [api, user, seriesId]); + + const unmarkFavorite = useCallback(async () => { + if (api && user) { + await getUserLibraryApi(api).unmarkFavoriteItem({ + userId: user.Id, + itemId: seriesId, + }); + } + }, [api, user, seriesId]); + + const onFavorite = useCallback(async () => { + if (isFavorited) { + setIsFavorited(false); + await unmarkFavorite(); + } else { + setIsFavorited(true); + await markFavorite(); + } + }, [isFavorited, markFavorite, unmarkFavorite]); + + return ( + + + + + + ); +}; diff --git a/package.json b/package.json index 50a4de63..18715592 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "react-native-ios-utilities": "^4.5.1", "react-native-mmkv": "^2.12.2", "react-native-pager-view": "6.3.0", + "react-native-paper": "^5.12.5", "react-native-progress": "^5.0.1", "react-native-reanimated": "~3.10.1", "react-native-reanimated-carousel": "4.0.0-canary.15",