import { Ionicons } from "@expo/vector-icons"; import { BottomSheetBackdrop, type BottomSheetBackdropProps, BottomSheetModal, BottomSheetView, } from "@gorhom/bottom-sheet"; import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useRef, } from "react"; import { useTranslation } from "react-i18next"; import { ActivityIndicator, StyleSheet, TouchableOpacity, View, } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Text } from "@/components/common/Text"; import useRouter from "@/hooks/useAppRouter"; import { useAddToWatchlist, useRemoveFromWatchlist, } from "@/hooks/useWatchlistMutations"; import { useItemInWatchlists, useMyWatchlistsQuery, } from "@/hooks/useWatchlists"; import type { StreamystatsWatchlist } from "@/utils/streamystats/types"; export interface WatchlistSheetRef { open: (item: BaseItemDto) => void; close: () => void; } interface WatchlistRowProps { watchlist: StreamystatsWatchlist; isInWatchlist: boolean; isCompatible: boolean; onToggle: () => void; isLoading: boolean; } const WatchlistRow: React.FC = ({ watchlist, isInWatchlist, isCompatible, onToggle, isLoading, }) => { const disabled = !isCompatible && !isInWatchlist; return ( {watchlist.name} {watchlist.allowedItemType && ( {watchlist.allowedItemType} )} {watchlist.description && ( {watchlist.description} )} {watchlist.itemCount ?? 0} items {isLoading ? ( ) : isInWatchlist ? ( ) : isCompatible ? ( ) : ( )} ); }; interface WatchlistSheetContentProps { item: BaseItemDto; onClose: () => void; } const WatchlistSheetContent: React.FC = ({ item, onClose, }) => { const { t } = useTranslation(); const router = useRouter(); const insets = useSafeAreaInsets(); const { data: myWatchlists, isLoading: watchlistsLoading } = useMyWatchlistsQuery(); const { data: watchlistsContainingItem, isLoading: checkingLoading } = useItemInWatchlists(item.Id); const addToWatchlist = useAddToWatchlist(); const removeFromWatchlist = useRemoveFromWatchlist(); const isLoading = watchlistsLoading || checkingLoading; // Sort watchlists: ones containing item first, then compatible ones, then incompatible const sortedWatchlists = useMemo(() => { if (!myWatchlists) return []; return [...myWatchlists].sort((a, b) => { const aInWatchlist = watchlistsContainingItem?.includes(a.id) ?? false; const bInWatchlist = watchlistsContainingItem?.includes(b.id) ?? false; const aCompatible = !a.allowedItemType || a.allowedItemType === item.Type; const bCompatible = !b.allowedItemType || b.allowedItemType === item.Type; // Items in watchlist first if (aInWatchlist && !bInWatchlist) return -1; if (!aInWatchlist && bInWatchlist) return 1; // Then compatible items if (aCompatible && !bCompatible) return -1; if (!aCompatible && bCompatible) return 1; // Then alphabetically return a.name.localeCompare(b.name); }); }, [myWatchlists, watchlistsContainingItem, item.Type]); const handleToggle = useCallback( async (watchlist: StreamystatsWatchlist) => { if (!item.Id) return; const isInWatchlist = watchlistsContainingItem?.includes(watchlist.id); if (isInWatchlist) { await removeFromWatchlist.mutateAsync({ watchlistId: watchlist.id, itemId: item.Id, watchlistName: watchlist.name, }); } else { await addToWatchlist.mutateAsync({ watchlistId: watchlist.id, itemId: item.Id, watchlistName: watchlist.name, }); } }, [item.Id, watchlistsContainingItem, addToWatchlist, removeFromWatchlist], ); const handleCreateNew = useCallback(() => { onClose(); router.push("/(auth)/(tabs)/(watchlists)/create"); }, [onClose, router]); const isItemCompatible = useCallback( (watchlist: StreamystatsWatchlist) => { if (!watchlist.allowedItemType) return true; return watchlist.allowedItemType === item.Type; }, [item.Type], ); if (isLoading) { return ( {t("watchlists.loading")} ); } return ( {/* Header */} {t("watchlists.select_watchlist")} {item.Name} {/* Watchlist List */} {sortedWatchlists.length === 0 ? ( {t("watchlists.empty_title")} {t("watchlists.empty_description")} ) : ( {sortedWatchlists.map((watchlist, index) => ( handleToggle(watchlist)} isLoading={ addToWatchlist.isPending || removeFromWatchlist.isPending } /> {index < sortedWatchlists.length - 1 && ( )} ))} )} {/* Create New Button */} {t("watchlists.create_new")} ); }; export const WatchlistSheet = forwardRef( (_props, ref) => { const bottomSheetModalRef = useRef(null); const [currentItem, setCurrentItem] = React.useState( null, ); const insets = useSafeAreaInsets(); useImperativeHandle(ref, () => ({ open: (item: BaseItemDto) => { setCurrentItem(item); bottomSheetModalRef.current?.present(); }, close: () => { bottomSheetModalRef.current?.dismiss(); }, })); const handleClose = useCallback(() => { bottomSheetModalRef.current?.dismiss(); }, []); const renderBackdrop = useCallback( (props: BottomSheetBackdropProps) => ( ), [], ); return ( {currentItem && ( )} ); }, );