import { getLiveTvApi } from "@jellyfin/sdk/lib/utils/api"; import { useQuery } from "@tanstack/react-query"; import { useAtomValue } from "jotai"; import React, { useCallback } from "react"; import { useTranslation } from "react-i18next"; import { ActivityIndicator, ScrollView, StyleSheet, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Text } from "@/components/common/Text"; import { useScaledTVTypography } from "@/constants/TVTypography"; import useRouter from "@/hooks/useAppRouter"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { TVChannelCard } from "./TVChannelCard"; const HORIZONTAL_PADDING = 60; const GRID_GAP = 16; export const TVChannelsGrid: React.FC = () => { const { t } = useTranslation(); const typography = useScaledTVTypography(); const insets = useSafeAreaInsets(); const router = useRouter(); const api = useAtomValue(apiAtom); const user = useAtomValue(userAtom); // Fetch all channels const { data: channelsData, isLoading } = useQuery({ queryKey: ["livetv", "channels-grid", "all"], queryFn: async () => { if (!api || !user?.Id) return null; const res = await getLiveTvApi(api).getLiveTvChannels({ enableFavoriteSorting: true, userId: user.Id, addCurrentProgram: false, enableUserData: false, enableImageTypes: ["Primary"], }); return res.data; }, enabled: !!api && !!user?.Id, staleTime: 5 * 60 * 1000, // 5 minutes }); const channels = channelsData?.Items ?? []; const handleChannelPress = useCallback( (channelId: string | undefined) => { if (channelId) { // Navigate directly to the player to start the channel const queryParams = new URLSearchParams({ itemId: channelId, audioIndex: "", subtitleIndex: "", mediaSourceId: "", bitrateValue: "", }); router.push(`/player/direct-player?${queryParams.toString()}`); } }, [router], ); if (isLoading) { return ( ); } if (channels.length === 0) { return ( {t("live_tv.no_channels")} ); } return ( {channels.map((channel, index) => ( handleChannelPress(channel.Id)} // No hasTVPreferredFocus - tab buttons handle initial focus /> ))} ); }; const styles = StyleSheet.create({ container: { flex: 1, }, contentContainer: { paddingTop: 24, }, grid: { flexDirection: "row", flexWrap: "wrap", justifyContent: "flex-start", gap: GRID_GAP, overflow: "visible", paddingVertical: 10, // Extra padding for focus scale animation }, loadingContainer: { flex: 1, justifyContent: "center", alignItems: "center", }, emptyContainer: { flex: 1, justifyContent: "center", alignItems: "center", }, emptyText: { color: "rgba(255, 255, 255, 0.6)", }, });