import { MatchCardLeague } from "@/components/match-card-league"; import { ThemedText } from "@/components/themed-text"; import { Colors } from "@/constants/theme"; import { useTheme } from "@/context/ThemeContext"; import { fetchLeagues, fetchTodayMatches } from "@/lib/api"; import { League, Match } from "@/types/api"; import { Image } from "expo-image"; import React, { useState } from "react"; import { ActivityIndicator, LayoutAnimation, Platform, ScrollView, StyleSheet, TouchableOpacity, UIManager, View, } from "react-native"; import Animated, { useAnimatedStyle, useSharedValue, withTiming, } from "react-native-reanimated"; // 开启 Android 上的 LayoutAnimation if ( Platform.OS === "android" && UIManager.setLayoutAnimationEnabledExperimental ) { UIManager.setLayoutAnimationEnabledExperimental(true); } interface MatchesByLeagueProps { sportId: number; date: Date; timezone: string; onFavoriteToggle?: (matchId: string, isFav: boolean) => void; enableCollapsible?: boolean; } export function MatchesByLeague({ sportId, date, timezone, onFavoriteToggle, enableCollapsible = true, }: MatchesByLeagueProps) { const { theme } = useTheme(); const isDark = theme === "dark"; function ChevronIcon({ isCollapsed, isDark }: { isCollapsed: boolean; isDark: boolean }) { const rotation = useSharedValue(isCollapsed ? 0 : 180); React.useEffect(() => { rotation.value = withTiming(isCollapsed ? 0 : 180, { duration: 200 }); }, [isCollapsed, rotation]); const animatedStyle = useAnimatedStyle(() => ({ transform: [{ rotate: `${rotation.value}deg` }], })); return ( ); } const [leagues, setLeagues] = useState([]); const [collapsed, setCollapsed] = useState>({}); const [matchesByLeagueKey, setMatchesByLeagueKey] = useState< Record >({}); const [loadingLeagueKey, setLoadingLeagueKey] = useState< Record >({}); const [loadingLeagues, setLoadingLeagues] = useState(true); const dateStr = React.useMemo(() => { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; }, [date]); React.useEffect(() => { let mounted = true; setLoadingLeagues(true); fetchLeagues({ sportId, date: dateStr, page: 1, pageSize: 200, sortBy: "matchCount", sortOrder: "desc", }) .then((res) => { if (!mounted) return; setLeagues(res.list); setCollapsed((prev) => { const next: Record = {}; res.list.forEach((l) => { next[l.key] = prev[l.key] !== undefined ? prev[l.key] : true; }); return next; }); setMatchesByLeagueKey({}); setLoadingLeagueKey({}); setLoadingLeagues(false); }) .catch(() => { if (!mounted) return; setLoadingLeagues(false); }); return () => { mounted = false; }; }, [sportId, dateStr]); const toggleCollapse = async (leagueKey: string) => { if (!enableCollapsible) return; LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); setCollapsed((prev) => ({ ...prev, [leagueKey]: !prev[leagueKey] })); const nextCollapsed = !collapsed[leagueKey]; if (nextCollapsed) return; if (matchesByLeagueKey[leagueKey]) return; setLoadingLeagueKey((prev) => ({ ...prev, [leagueKey]: true })); try { const res = await fetchTodayMatches({ sportId, date: dateStr, timezone, leagueKey, page: 1, pageSize: 50, }); console.log("choose", res); setMatchesByLeagueKey((prev) => ({ ...prev, [leagueKey]: res.list })); } finally { setLoadingLeagueKey((prev) => ({ ...prev, [leagueKey]: false })); } }; if (loadingLeagues) { return ( 加载中... ); } if (leagues.length === 0) { return ( 暂无联赛 ); } const cardBg = isDark ? "#1C1C1E" : "#FFFFFF"; const dividerColor = isDark ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.06)"; const skeletonBg = isDark ? "#2C2C2E" : "#E5E5E5"; const headerBg = isDark ? "#1C1C1E" : "#FBFBFB"; return ( {leagues.map((league) => { const isCollapsed = collapsed[league.key] !== false; const leagueMatches = matchesByLeagueKey[league.key] || []; const isLoading = !!loadingLeagueKey[league.key]; return ( 0 ? 0.7 : 1} onPress={() => { if (enableCollapsible && league.matchCount > 0) { toggleCollapse(league.key); } }} style={[styles.leagueHeaderWrapper, { backgroundColor: headerBg }]} > {league.name} {league.countryName || "International"} {enableCollapsible && league.matchCount > 0 && ( )} {!isCollapsed && ( {isLoading ? ( ) : ( leagueMatches.map((match, index) => ( )) )} )} ); })} ); } const styles = StyleSheet.create({ container: { flex: 1, }, leagueSection: { marginBottom: 8, }, leagueHeaderWrapper: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", paddingHorizontal: 16, paddingVertical: 14, borderRadius: 12, marginHorizontal: 16, }, leagueHeaderLeft: { flexDirection: "row", alignItems: "center", flex: 1, }, leagueLogo: { width: 24, height: 24, borderRadius: 8, marginRight: 12, marginLeft: 14, }, leagueInfoText: { justifyContent: "center", }, leagueTitle: { fontSize: 14, fontWeight: "700", marginBottom: 4, lineHeight: 16, }, countryRow: { flexDirection: "row", alignItems: "center", }, countryFlag: { width: 14, height: 14, marginRight: 6, borderRadius: 2, }, countryName: { fontSize: 12, fontWeight: "500", lineHeight: 12, }, leagueHeaderRight: { flexDirection: "row", alignItems: "center", }, chevronContainer: { width: 16, height: 16, justifyContent: "center", alignItems: "center", }, chevronIcon: { width: 16, height: 16, }, matchListContainer: { borderRadius: 16, marginHorizontal: 16, overflow: "hidden", }, leftColumn: { width: 48, justifyContent: "center", alignItems: "center", marginRight: 8, }, teamsColumn: { flex: 1, justifyContent: "center", paddingRight: 12, }, rightWrapper: { flexDirection: "row", alignItems: "center", }, matchCardWrapper: {}, matchCardDivider: { borderBottomWidth: StyleSheet.hairlineWidth, }, favoriteButton: { paddingLeft: 16, justifyContent: "center", alignItems: "center", }, skeletonRow: { flexDirection: "row", alignItems: "center", paddingHorizontal: 16, paddingVertical: 16, }, skeletonLine: { height: 10, borderRadius: 5, }, skeletonAvatar: { width: 28, height: 28, borderRadius: 14, marginRight: 12, }, skeletonTeamRow: { flexDirection: "row", alignItems: "center", }, skeletonScoreBox: { width: 40, height: 64, borderRadius: 8, }, skeletonCircle: { width: 22, height: 22, borderRadius: 11, }, emptyContainer: { flex: 1, justifyContent: "center", alignItems: "center", paddingTop: 100, }, });