import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; import { useRoute } from "@react-navigation/native"; import { FlashList } from "@shopify/flash-list"; import { useInfiniteQuery } from "@tanstack/react-query"; import { useLocalSearchParams } from "expo-router"; import { useAtom } from "jotai"; import { useCallback, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { Dimensions, RefreshControl, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Text } from "@/components/common/Text"; import { Loader } from "@/components/Loader"; import { MusicPlaylistCard } from "@/components/music/MusicPlaylistCard"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; const ITEMS_PER_PAGE = 40; export default function PlaylistsScreen() { const localParams = useLocalSearchParams<{ libraryId?: string | string[] }>(); const route = useRoute(); const libraryId = (Array.isArray(localParams.libraryId) ? localParams.libraryId[0] : localParams.libraryId) ?? route?.params?.libraryId; const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); const insets = useSafeAreaInsets(); const { t } = useTranslation(); const isReady = Boolean(api && user?.Id && libraryId); const { data, isLoading, isError, error, fetchNextPage, hasNextPage, isFetchingNextPage, refetch, } = useInfiniteQuery({ queryKey: ["music-playlists", libraryId, user?.Id], queryFn: async ({ pageParam = 0 }) => { const response = await getItemsApi(api!).getItems({ userId: user?.Id, parentId: libraryId, includeItemTypes: ["Playlist"], sortBy: ["SortName"], sortOrder: ["Ascending"], limit: ITEMS_PER_PAGE, startIndex: pageParam, recursive: true, mediaTypes: ["Audio"], }); return { items: response.data.Items || [], totalCount: response.data.TotalRecordCount || 0, startIndex: pageParam, }; }, getNextPageParam: (lastPage) => { const nextStart = lastPage.startIndex + ITEMS_PER_PAGE; return nextStart < lastPage.totalCount ? nextStart : undefined; }, initialPageParam: 0, enabled: isReady, }); const playlists = useMemo(() => { return data?.pages.flatMap((page) => page.items) || []; }, [data]); const numColumns = 2; const screenWidth = Dimensions.get("window").width; const gap = 12; const padding = 16; const itemWidth = (screenWidth - padding * 2 - gap * (numColumns - 1)) / numColumns; const handleEndReached = useCallback(() => { if (hasNextPage && !isFetchingNextPage) { fetchNextPage(); } }, [hasNextPage, isFetchingNextPage, fetchNextPage]); if (!api || !user?.Id) { return ( ); } if (!libraryId) { return ( Missing music library id. ); } if (isLoading) { return ( ); } if (isError) { return ( Failed to load playlists:{" "} {(error as Error)?.message || "Unknown error"} ); } if (playlists.length === 0) { return ( {t("music.no_playlists")} ); } return ( } onEndReached={handleEndReached} onEndReachedThreshold={0.5} renderItem={({ item, index }) => ( )} keyExtractor={(item) => item.Id!} ListFooterComponent={ isFetchingNextPage ? ( ) : null } /> ); }