diff --git a/api/mutations/functions/playlists.ts b/api/mutations/functions/playlists.ts index 24ac1270..395066c0 100644 --- a/api/mutations/functions/playlists.ts +++ b/api/mutations/functions/playlists.ts @@ -1,7 +1,16 @@ +import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import Client from "../../../api/client"; import { getPlaylistsApi } from "@jellyfin/sdk/lib/utils/api"; - +export async function addToPlaylist(track: BaseItemDto, playlist: BaseItemDto) { + return getPlaylistsApi(Client.api!) + .addItemToPlaylist({ + ids: [ + track.Id! + ], + playlistId: playlist.Id! + }) +} export async function reorderPlaylist(playlistId: string, itemId: string, to: number) { return getPlaylistsApi(Client.api!) diff --git a/components/Global/components/favorite-button.tsx b/components/Global/components/favorite-button.tsx index 6df6c747..c9fcb8be 100644 --- a/components/Global/components/favorite-button.tsx +++ b/components/Global/components/favorite-button.tsx @@ -10,6 +10,7 @@ import Client from "../../../api/client"; import { usePlayerContext } from "../../..//player/provider"; import { queryClient } from "../../../constants/query-client"; import { QueryKeys } from "../../../enums/query-keys"; +import { trigger } from "react-native-haptic-feedback"; interface SetFavoriteMutation { item: BaseItemDto, @@ -37,6 +38,8 @@ export default function FavoriteButton({ }) }, onSuccess: () => { + trigger("notificationSuccess"); + setIsFavorite(true); onToggle ? onToggle() : {}; @@ -48,11 +51,7 @@ export default function FavoriteButton({ if (item.Type === 'Audio') { queryClient.invalidateQueries({ - queryKey: [QueryKeys.AlbumTracks] - }); - - queryClient.invalidateQueries({ - queryKey: [QueryKeys.Playlist] + queryKey: [QueryKeys.ItemTracks] }); } } diff --git a/components/ItemDetail/component.tsx b/components/ItemDetail/component.tsx index bf4334e9..b08b03f6 100644 --- a/components/ItemDetail/component.tsx +++ b/components/ItemDetail/component.tsx @@ -32,7 +32,7 @@ export default function ItemDetail({ switch (item.Type) { case "Audio": { - options = TrackOptions({ item, navigation, isNested }); + options = TrackOptions({ track: item, navigation, isNested }); break; } diff --git a/components/ItemDetail/helpers/TrackOptions.tsx b/components/ItemDetail/helpers/TrackOptions.tsx index 4872d054..9af38556 100644 --- a/components/ItemDetail/helpers/TrackOptions.tsx +++ b/components/ItemDetail/helpers/TrackOptions.tsx @@ -3,15 +3,23 @@ import { useItem } from "../../../api/queries/item"; import { StackParamList } from "../../../components/types"; import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import { NativeStackNavigationProp } from "@react-navigation/native-stack"; -import { Spacer, Spinner, XStack, YGroup, YStack } from "tamagui"; +import { ListItem, Spacer, Spinner, XStack, YGroup, YStack } from "tamagui"; import { QueuingType } from "../../../enums/queuing-type"; import { useSafeAreaFrame } from "react-native-safe-area-context"; import IconButton from "../../../components/Global/helpers/icon-button"; import { Text } from "../../../components/Global/helpers/text"; import { useUserPlaylists } from "../../../api/queries/playlist"; +import React from "react"; +import BlurhashedImage from "@/components/Global/components/blurhashed-image"; +import { useMutation } from "@tanstack/react-query"; +import { AddToPlaylistMutation } from "../types"; +import { addToPlaylist } from "@/api/mutations/functions/playlists"; +import { trigger } from "react-native-haptic-feedback"; +import { queryClient } from "@/constants/query-client"; +import { QueryKeys } from "@/enums/query-keys"; interface TrackOptionsProps { - item: BaseItemDto; + track: BaseItemDto; navigation: NativeStackNavigationProp; /** @@ -21,12 +29,12 @@ interface TrackOptionsProps { } export default function TrackOptions({ - item, + track, navigation, isNested } : TrackOptionsProps) : React.JSX.Element { - const { data: album, isSuccess: albumFetchSuccess } = useItem(item.AlbumId ?? ""); + const { data: album, isSuccess: albumFetchSuccess } = useItem(track.AlbumId ?? ""); const { data: playlists, isPending : playlistsFetchPending, isSuccess: playlistsFetchSuccess } = useUserPlaylists(); @@ -64,7 +72,7 @@ export default function TrackOptions({ title="Play Next" onPress={() => { useAddToQueue.mutate({ - track: item, + track: track, queuingType: QueuingType.PlayingNext }) }} @@ -77,21 +85,72 @@ export default function TrackOptions({ title="Queue" onPress={() => { useAddToQueue.mutate({ - track: item + track: track }) }} size={width / 5} /> + + { playlistsFetchPending && ( )} - Add to Playlist - - { playlists?.map} - + { playlistsFetchSuccess && ( + <> + + Add to Playlist + + + + { playlists.map(playlist => { + + const useAddToPlaylist = useMutation({ + mutationFn: ({ track, playlist }: AddToPlaylistMutation) => { + return addToPlaylist(track, playlist) + }, + onSuccess: (data, { playlist }) => { + trigger("notificationSuccess") + + queryClient.invalidateQueries({ + queryKey: [QueryKeys.ItemTracks, playlist.Id!, false], + exact: true + }); + }, + onError: () => { + trigger("notificationError") + } + }) + + return ( + + { + useAddToPlaylist.mutate({ + track, + playlist + }) + }}> + + + + {playlist.Name ?? "Untitled Playlist"} + + + + ) + })} + + + )} + ) } \ No newline at end of file diff --git a/components/ItemDetail/types.ts b/components/ItemDetail/types.ts new file mode 100644 index 00000000..dfc6f5f1 --- /dev/null +++ b/components/ItemDetail/types.ts @@ -0,0 +1,6 @@ +import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; + +export interface AddToPlaylistMutation { + track: BaseItemDto; + playlist: BaseItemDto; +} \ No newline at end of file diff --git a/constants/query-client.ts b/constants/query-client.ts index 2f49eecc..09f02157 100644 --- a/constants/query-client.ts +++ b/constants/query-client.ts @@ -6,4 +6,4 @@ export const queryClient = new QueryClient({ gcTime: (1000 * 60 * 24 * 24) * 5 // 5 days } } -}) \ No newline at end of file +}); \ No newline at end of file