From f6c0513d2dc94e414330157983761812d6167c0f Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Wed, 1 Jan 2025 18:11:18 +0100 Subject: [PATCH] feat: favorite button toggle for movies/episodes --- components/AddToFavorites.tsx | 97 +++++++++++++++++++++++++++++++++++ components/ItemContent.tsx | 2 + components/RoundButton.tsx | 2 +- 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 components/AddToFavorites.tsx diff --git a/components/AddToFavorites.tsx b/components/AddToFavorites.tsx new file mode 100644 index 00000000..b2672b10 --- /dev/null +++ b/components/AddToFavorites.tsx @@ -0,0 +1,97 @@ +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 { useMemo } from "react"; +import { TouchableOpacityProps, View, ViewProps } from "react-native"; +import { RoundButton } from "./RoundButton"; + +interface Props extends ViewProps { + item: BaseItemDto; +} + +export const AddToFavorites: React.FC = ({ item, ...props }) => { + const queryClient = useQueryClient(); + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + + const isFavorite = useMemo(() => { + return item.UserData?.IsFavorite; + }, [item.UserData?.IsFavorite]); + + const markFavoriteMutation = useMutation({ + mutationFn: async () => { + if (api && user) { + await getUserLibraryApi(api).markFavoriteItem({ + userId: user.Id, + itemId: item.Id!, + }); + } + }, + onMutate: async () => { + await queryClient.cancelQueries({ queryKey: ["item", item.Id] }); + const previousItem = queryClient.getQueryData([ + "item", + item.Id, + ]); + queryClient.setQueryData(["item", item.Id], (old) => ({ + ...old!, + UserData: { ...old!.UserData, IsFavorite: true }, + })); + return { previousItem }; + }, + onError: (err, variables, context) => { + queryClient.setQueryData(["item", item.Id], context?.previousItem); + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ["item", item.Id] }); + }, + }); + + const unmarkFavoriteMutation = useMutation({ + mutationFn: async () => { + if (api && user) { + await getUserLibraryApi(api).unmarkFavoriteItem({ + userId: user.Id, + itemId: item.Id!, + }); + } + }, + onMutate: async () => { + await queryClient.cancelQueries({ queryKey: ["item", item.Id] }); + const previousItem = queryClient.getQueryData([ + "item", + item.Id, + ]); + queryClient.setQueryData(["item", item.Id], (old) => ({ + ...old!, + UserData: { ...old!.UserData, IsFavorite: false }, + })); + return { previousItem }; + }, + onError: (err, variables, context) => { + queryClient.setQueryData(["item", item.Id], context?.previousItem); + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ["item", item.Id] }); + }, + }); + + return ( + + { + if (isFavorite) { + unmarkFavoriteMutation.mutate(); + } else { + markFavoriteMutation.mutate(); + } + }} + /> + + ); +}; diff --git a/components/ItemContent.tsx b/components/ItemContent.tsx index 20331d85..b17ee483 100644 --- a/components/ItemContent.tsx +++ b/components/ItemContent.tsx @@ -34,6 +34,7 @@ import { ItemHeader } from "./ItemHeader"; import { ItemTechnicalDetails } from "./ItemTechnicalDetails"; import { MediaSourceSelector } from "./MediaSourceSelector"; import { MoreMoviesWithActor } from "./MoreMoviesWithActor"; +import { AddToFavorites } from "./AddToFavorites"; export type SelectedOptions = { bitrate: Bitrate; @@ -90,6 +91,7 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo( + )} diff --git a/components/RoundButton.tsx b/components/RoundButton.tsx index 6feafae5..049c5ed0 100644 --- a/components/RoundButton.tsx +++ b/components/RoundButton.tsx @@ -9,7 +9,7 @@ import { import * as Haptics from "expo-haptics"; interface Props extends TouchableOpacityProps { - onPress?: () => void, + onPress?: () => void; icon?: keyof typeof Ionicons.glyphMap; background?: boolean; size?: "default" | "large";