feat: favorite button toggle for movies/episodes

This commit is contained in:
Fredrik Burmester
2025-01-01 18:11:18 +01:00
parent 6a17ac02af
commit f6c0513d2d
3 changed files with 100 additions and 1 deletions

View File

@@ -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<Props> = ({ 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<BaseItemDto>([
"item",
item.Id,
]);
queryClient.setQueryData<BaseItemDto>(["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<BaseItemDto>([
"item",
item.Id,
]);
queryClient.setQueryData<BaseItemDto>(["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 (
<View {...props}>
<RoundButton
size="large"
icon={isFavorite ? "heart" : "heart-outline"}
fillColor={isFavorite ? "primary" : undefined}
onPress={() => {
if (isFavorite) {
unmarkFavoriteMutation.mutate();
} else {
markFavoriteMutation.mutate();
}
}}
/>
</View>
);
};

View File

@@ -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(
<View className="flex flex-row items-center space-x-2">
<DownloadSingleItem item={item} size="large" />
<PlayedStatus item={item} />
<AddToFavorites item={item} />
</View>
)}
</View>

View File

@@ -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";