diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/series/[id].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/series/[id].tsx
index 9a284d33..eb9b660d 100644
--- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/series/[id].tsx
+++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/series/[id].tsx
@@ -84,7 +84,7 @@ const page: React.FC = () => {
allEpisodes &&
allEpisodes.length > 0 && (
-
+
{!Platform.isTV && (
<>
= ({ item, type, ...props }) => {
- const queryClient = useQueryClient();
- const [api] = useAtom(apiAtom);
- const [user] = useAtom(userAtom);
-
- const isFavorite = useMemo(() => {
- return item.UserData?.IsFavorite;
- }, [item.UserData?.IsFavorite]);
-
- const updateItemInQueries = (newData: Partial) => {
- queryClient.setQueryData(
- [type, item.Id],
- (old) => {
- if (!old) return old;
- return {
- ...old,
- ...newData,
- UserData: { ...old.UserData, ...newData.UserData },
- };
- }
- );
- };
-
- const markFavoriteMutation = useMutation({
- mutationFn: async () => {
- if (api && user) {
- await getUserLibraryApi(api).markFavoriteItem({
- userId: user.Id,
- itemId: item.Id!,
- });
- }
- },
- onMutate: async () => {
- await queryClient.cancelQueries({ queryKey: [type, item.Id] });
- const previousItem = queryClient.getQueryData([
- type,
- item.Id,
- ]);
- updateItemInQueries({ UserData: { IsFavorite: true } });
-
- return { previousItem };
- },
- onError: (err, variables, context) => {
- if (context?.previousItem) {
- queryClient.setQueryData([type, item.Id], context.previousItem);
- }
- },
- onSettled: () => {
- queryClient.invalidateQueries({ queryKey: [type, item.Id] });
- queryClient.invalidateQueries({ queryKey: ["home", "favorites"] });
- },
- });
-
- const unmarkFavoriteMutation = useMutation({
- mutationFn: async () => {
- if (api && user) {
- await getUserLibraryApi(api).unmarkFavoriteItem({
- userId: user.Id,
- itemId: item.Id!,
- });
- }
- },
- onMutate: async () => {
- await queryClient.cancelQueries({ queryKey: [type, item.Id] });
- const previousItem = queryClient.getQueryData([
- type,
- item.Id,
- ]);
- updateItemInQueries({ UserData: { IsFavorite: false } });
-
- return { previousItem };
- },
- onError: (err, variables, context) => {
- if (context?.previousItem) {
- queryClient.setQueryData([type, item.Id], context.previousItem);
- }
- },
- onSettled: () => {
- queryClient.invalidateQueries({ queryKey: [type, item.Id] });
- queryClient.invalidateQueries({ queryKey: ["home", "favorites"] });
- },
- });
-
+export const AddToFavorites = ({ item, ...props }) => {
+ const { isFavorite, toggleFavorite, _} = useFavorite(item);
+
return (
{
- if (isFavorite) {
- unmarkFavoriteMutation.mutate();
- } else {
- markFavoriteMutation.mutate();
- }
- }}
+ onPress={toggleFavorite}
/>
);
diff --git a/components/ItemContent.tsx b/components/ItemContent.tsx
index 8fb538dd..1f9077f8 100644
--- a/components/ItemContent.tsx
+++ b/components/ItemContent.tsx
@@ -98,7 +98,7 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo(
)}
-
+
)}
diff --git a/components/common/TouchableItemRouter.tsx b/components/common/TouchableItemRouter.tsx
index d4d53e79..44aceafe 100644
--- a/components/common/TouchableItemRouter.tsx
+++ b/components/common/TouchableItemRouter.tsx
@@ -1,4 +1,5 @@
import { useMarkAsPlayed } from "@/hooks/useMarkAsPlayed";
+import { useFavorite } from "@/hooks/useFavorite";
import {
BaseItemDto,
BaseItemPerson,
@@ -7,7 +8,6 @@ import { useRouter, useSegments } from "expo-router";
import { PropsWithChildren, useCallback } from "react";
import { TouchableOpacity, TouchableOpacityProps } from "react-native";
import { useActionSheet } from "@expo/react-native-action-sheet";
-import { useHaptic } from "@/hooks/useHaptic";
interface Props extends TouchableOpacityProps {
item: BaseItemDto;
@@ -57,14 +57,14 @@ export const TouchableItemRouter: React.FC> = ({
const segments = useSegments();
const { showActionSheetWithOptions } = useActionSheet();
const markAsPlayedStatus = useMarkAsPlayed([item]);
-
+ const { isFavorite, toggleFavorite, _} = useFavorite(item);
+
const from = segments[2];
const showActionSheet = useCallback(() => {
- if (!(item.Type === "Movie" || item.Type === "Episode")) return;
-
- const options = ["Mark as Played", "Mark as Not Played", "Cancel"];
- const cancelButtonIndex = 2;
+ if (!(item.Type === "Movie" || item.Type === "Episode" || item.Type === "Series")) return;
+ const options = ["Mark as Played", "Mark as Not Played", isFavorite ? "Unmark as Favorite" : "Mark as Favorite", "Cancel"];
+ const cancelButtonIndex = 3;
showActionSheetWithOptions(
{
@@ -74,14 +74,14 @@ export const TouchableItemRouter: React.FC> = ({
async (selectedIndex) => {
if (selectedIndex === 0) {
await markAsPlayedStatus(true);
- // Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
} else if (selectedIndex === 1) {
await markAsPlayedStatus(false);
- // Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
+ } else if (selectedIndex === 2) {
+ toggleFavorite()
}
}
);
- }, [showActionSheetWithOptions, markAsPlayedStatus]);
+ }, [showActionSheetWithOptions, isFavorite, markAsPlayedStatus]);
if (
from === "(home)" ||
diff --git a/hooks/useFavorite.ts b/hooks/useFavorite.ts
new file mode 100644
index 00000000..437d290e
--- /dev/null
+++ b/hooks/useFavorite.ts
@@ -0,0 +1,109 @@
+import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
+import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
+import { getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { useAtom } from "jotai";
+import { useEffect, useState, useMemo } from "react";
+
+export const useFavorite = (item: BaseItemDto) => {
+ const queryClient = useQueryClient();
+ const [api] = useAtom(apiAtom);
+ const [user] = useAtom(userAtom);
+ const type = "item";
+ const [isFavorite, setIsFavorite] = useState(item.UserData?.IsFavorite);
+
+ useEffect(() => {
+ setIsFavorite(item.UserData?.IsFavorite);
+ }, [item.UserData?.IsFavorite]);
+
+ const updateItemInQueries = (newData: Partial) => {
+ queryClient.setQueryData(
+ [type, item.Id],
+ (old) => {
+ if (!old) return old;
+ return {
+ ...old,
+ ...newData,
+ UserData: { ...old.UserData, ...newData.UserData },
+ };
+ }
+ );
+ };
+
+ const markFavoriteMutation = useMutation({
+ mutationFn: async () => {
+ if (api && user) {
+ await getUserLibraryApi(api).markFavoriteItem({
+ userId: user.Id,
+ itemId: item.Id!,
+ });
+ }
+ },
+ onMutate: async () => {
+ await queryClient.cancelQueries({ queryKey: [type, item.Id] });
+ const previousItem = queryClient.getQueryData([
+ type,
+ item.Id,
+ ]);
+ updateItemInQueries({ UserData: { IsFavorite: true } });
+
+ return { previousItem };
+ },
+ onError: (err, variables, context) => {
+ if (context?.previousItem) {
+ queryClient.setQueryData([type, item.Id], context.previousItem);
+ }
+ },
+ onSettled: () => {
+ queryClient.invalidateQueries({ queryKey: [type, item.Id] });
+ queryClient.invalidateQueries({ queryKey: ["home", "favorites"] });
+ setIsFavorite(true);
+ },
+ });
+
+ const unmarkFavoriteMutation = useMutation({
+ mutationFn: async () => {
+ if (api && user) {
+ await getUserLibraryApi(api).unmarkFavoriteItem({
+ userId: user.Id,
+ itemId: item.Id!,
+ });
+ }
+ },
+ onMutate: async () => {
+ await queryClient.cancelQueries({ queryKey: [type, item.Id] });
+ const previousItem = queryClient.getQueryData([
+ type,
+ item.Id,
+ ]);
+ updateItemInQueries({ UserData: { IsFavorite: false } });
+
+ return { previousItem };
+ },
+ onError: (err, variables, context) => {
+ if (context?.previousItem) {
+ queryClient.setQueryData([type, item.Id], context.previousItem);
+ }
+ },
+ onSettled: () => {
+ queryClient.invalidateQueries({ queryKey: [type, item.Id] });
+ queryClient.invalidateQueries({ queryKey: ["home", "favorites"] });
+ setIsFavorite(false);
+ },
+ });
+
+ const toggleFavorite = () => {
+ if (isFavorite) {
+ unmarkFavoriteMutation.mutate();
+ } else {
+ markFavoriteMutation.mutate();
+ }
+ };
+
+ return {
+ isFavorite,
+ toggleFavorite,
+ markFavoriteMutation,
+ unmarkFavoriteMutation,
+ };
+};