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 (
+
(
+
{
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",