import React, { useState, useEffect, useCallback, useRef } from "react"; import { View, FlatList, StyleSheet, ActivityIndicator, Modal, useTVEventHandler, HWEvent, Text } from "react-native"; import LivePlayer from "@/components/LivePlayer"; import { fetchAndParseM3u, getPlayableUrl, Channel } from "@/services/m3u"; import { ThemedView } from "@/components/ThemedView"; import { StyledButton } from "@/components/StyledButton"; import { useSettingsStore } from "@/stores/settingsStore"; import { useResponsiveLayout } from "@/hooks/useResponsiveLayout"; import { getCommonResponsiveStyles } from "@/utils/ResponsiveStyles"; import ResponsiveNavigation from "@/components/navigation/ResponsiveNavigation"; import ResponsiveHeader from "@/components/navigation/ResponsiveHeader"; import { DeviceUtils } from "@/utils/DeviceUtils"; export default function LiveScreen() { const { m3uUrl } = useSettingsStore(); // 响应式布局配置 const responsiveConfig = useResponsiveLayout(); const commonStyles = getCommonResponsiveStyles(responsiveConfig); const { deviceType, spacing } = responsiveConfig; const [channels, setChannels] = useState([]); const [groupedChannels, setGroupedChannels] = useState>({}); const [channelGroups, setChannelGroups] = useState([]); const [selectedGroup, setSelectedGroup] = useState(""); const [currentChannelIndex, setCurrentChannelIndex] = useState(0); const [isLoading, setIsLoading] = useState(false); const [isChannelListVisible, setIsChannelListVisible] = useState(false); const [channelTitle, setChannelTitle] = useState(null); const titleTimer = useRef(null); const selectedChannelUrl = channels.length > 0 ? getPlayableUrl(channels[currentChannelIndex].url) : null; useEffect(() => { const loadChannels = async () => { if (!m3uUrl) return; setIsLoading(true); const parsedChannels = await fetchAndParseM3u(m3uUrl); setChannels(parsedChannels); const groups: Record = parsedChannels.reduce((acc, channel) => { const groupName = channel.group || "Other"; if (!acc[groupName]) { acc[groupName] = []; } acc[groupName].push(channel); return acc; }, {} as Record); const groupNames = Object.keys(groups); setGroupedChannels(groups); setChannelGroups(groupNames); setSelectedGroup(groupNames[0] || ""); if (parsedChannels.length > 0) { showChannelTitle(parsedChannels[0].name); } setIsLoading(false); }; loadChannels(); }, [m3uUrl]); const showChannelTitle = (title: string) => { setChannelTitle(title); if (titleTimer.current) clearTimeout(titleTimer.current); titleTimer.current = setTimeout(() => setChannelTitle(null), 3000); }; const handleSelectChannel = (channel: Channel) => { const globalIndex = channels.findIndex((c) => c.id === channel.id); if (globalIndex !== -1) { setCurrentChannelIndex(globalIndex); showChannelTitle(channel.name); setIsChannelListVisible(false); } }; const changeChannel = useCallback( (direction: "next" | "prev") => { if (channels.length === 0) return; let newIndex = direction === "next" ? (currentChannelIndex + 1) % channels.length : (currentChannelIndex - 1 + channels.length) % channels.length; setCurrentChannelIndex(newIndex); showChannelTitle(channels[newIndex].name); }, [channels, currentChannelIndex] ); const handleTVEvent = useCallback( (event: HWEvent) => { if (deviceType !== 'tv') return; if (isChannelListVisible) return; if (event.eventType === "down") setIsChannelListVisible(true); else if (event.eventType === "left") changeChannel("prev"); else if (event.eventType === "right") changeChannel("next"); }, [changeChannel, isChannelListVisible, deviceType] ); useTVEventHandler(deviceType === 'tv' ? handleTVEvent : () => {}); // 动态样式 const dynamicStyles = createResponsiveStyles(deviceType, spacing); const renderLiveContent = () => ( <> {}} /> setIsChannelListVisible(false)} > 选择频道 `group-${item}-${index}`} renderItem={({ item }) => ( setSelectedGroup(item)} isSelected={selectedGroup === item} style={dynamicStyles.groupButton} textStyle={dynamicStyles.groupButtonText} /> )} /> {isLoading ? ( ) : ( `${item.id}-${item.group}-${index}`} renderItem={({ item }) => ( handleSelectChannel(item)} isSelected={channels[currentChannelIndex]?.id === item.id} hasTVPreferredFocus={channels[currentChannelIndex]?.id === item.id} style={dynamicStyles.channelItem} textStyle={dynamicStyles.channelItemText} /> )} /> )} ); const content = ( {renderLiveContent()} ); // 根据设备类型决定是否包装在响应式导航中 if (deviceType === 'tv') { return content; } return ( {content} ); } const createResponsiveStyles = (deviceType: string, spacing: number) => { const isMobile = deviceType === 'mobile'; const isTablet = deviceType === 'tablet'; const minTouchTarget = DeviceUtils.getMinTouchTargetSize(); return StyleSheet.create({ container: { flex: 1, }, modalContainer: { flex: 1, flexDirection: "row", justifyContent: isMobile ? "center" : "flex-end", backgroundColor: "transparent", }, modalContent: { width: isMobile ? '90%' : isTablet ? 400 : 450, height: "100%", backgroundColor: "rgba(0, 0, 0, 0.85)", padding: spacing, }, modalTitle: { color: "white", marginBottom: spacing / 2, textAlign: "center", fontSize: isMobile ? 18 : 16, fontWeight: "bold", }, listContainer: { flex: 1, flexDirection: isMobile ? "column" : "row", }, groupColumn: { flex: isMobile ? 0 : 1, marginRight: isMobile ? 0 : spacing / 2, marginBottom: isMobile ? spacing : 0, maxHeight: isMobile ? 120 : undefined, }, channelColumn: { flex: isMobile ? 1 : 2, }, groupButton: { paddingVertical: isMobile ? minTouchTarget / 4 : 8, paddingHorizontal: spacing / 2, marginVertical: isMobile ? 2 : 4, minHeight: isMobile ? minTouchTarget * 0.7 : undefined, }, groupButtonText: { fontSize: isMobile ? 14 : 13, }, channelItem: { paddingVertical: isMobile ? minTouchTarget / 5 : 6, paddingHorizontal: spacing, marginVertical: isMobile ? 2 : 3, minHeight: isMobile ? minTouchTarget * 0.8 : undefined, }, channelItemText: { fontSize: isMobile ? 14 : 12, }, }); };