import { Ionicons } from "@expo/vector-icons"; import { BottomSheetBackdrop, type BottomSheetBackdropProps, BottomSheetModal, BottomSheetScrollView, } from "@gorhom/bottom-sheet"; import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; import { useQuery } from "@tanstack/react-query"; import { Image } from "expo-image"; import { useAtom } from "jotai"; import React, { useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { useTranslation } from "react-i18next"; import { ActivityIndicator, StyleSheet, TouchableOpacity, View, } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Input } from "@/components/common/Input"; import { Text } from "@/components/common/Text"; import { useAddToPlaylist } from "@/hooks/usePlaylistMutations"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; interface Props { open: boolean; setOpen: (open: boolean) => void; trackToAdd: BaseItemDto | null; onCreateNew: () => void; } export const PlaylistPickerSheet: React.FC = ({ open, setOpen, trackToAdd, onCreateNew, }) => { const bottomSheetModalRef = useRef(null); const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); const insets = useSafeAreaInsets(); const { t } = useTranslation(); const addToPlaylist = useAddToPlaylist(); const [search, setSearch] = useState(""); const snapPoints = useMemo(() => ["75%"], []); // Fetch all playlists const { data: playlists, isLoading } = useQuery({ queryKey: ["music-playlists-picker", user?.Id], queryFn: async () => { if (!api || !user?.Id) return []; const response = await getItemsApi(api).getItems({ userId: user.Id, includeItemTypes: ["Playlist"], sortBy: ["SortName"], sortOrder: ["Ascending"], recursive: true, mediaTypes: ["Audio"], }); return response.data.Items || []; }, enabled: Boolean(api && user?.Id && open), }); const filteredPlaylists = useMemo(() => { if (!playlists) return []; if (!search) return playlists; return playlists.filter((playlist) => playlist.Name?.toLowerCase().includes(search.toLowerCase()), ); }, [playlists, search]); const showSearch = (playlists?.length || 0) > 10; useEffect(() => { if (open) { setSearch(""); bottomSheetModalRef.current?.present(); } else { bottomSheetModalRef.current?.dismiss(); } }, [open]); const handleSheetChanges = useCallback( (index: number) => { if (index === -1) { setOpen(false); } }, [setOpen], ); const renderBackdrop = useCallback( (props: BottomSheetBackdropProps) => ( ), [], ); const handleSelectPlaylist = useCallback( async (playlist: BaseItemDto) => { if (!trackToAdd?.Id || !playlist.Id) return; await addToPlaylist.mutateAsync({ playlistId: playlist.Id, trackIds: [trackToAdd.Id], playlistName: playlist.Name || undefined, }); setOpen(false); }, [trackToAdd, addToPlaylist, setOpen], ); const handleCreateNew = useCallback(() => { setOpen(false); setTimeout(() => { onCreateNew(); }, 300); }, [onCreateNew, setOpen]); const getPlaylistImageUrl = useCallback( (playlist: BaseItemDto) => { if (!api) return null; return `${api.basePath}/Items/${playlist.Id}/Images/Primary?maxHeight=100&maxWidth=100`; }, [api], ); return ( {t("music.track_options.add_to_playlist")} {trackToAdd?.Name} {showSearch && ( )} {/* Create New Playlist Button */} {t("music.playlists.create_new")} {isLoading ? ( ) : filteredPlaylists.length === 0 ? ( {search ? t("search.no_results") : t("music.no_playlists")} ) : ( {filteredPlaylists.map((playlist, index) => ( handleSelectPlaylist(playlist)} className='flex-row items-center px-4 py-3' disabled={addToPlaylist.isPending} > {playlist.Name} {playlist.ChildCount} {t("music.tabs.tracks")} {addToPlaylist.isPending && ( )} {index < filteredPlaylists.length - 1 && ( )} ))} )} ); }; const styles = StyleSheet.create({ separator: { height: StyleSheet.hairlineWidth, backgroundColor: "#404040", }, });