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,
},
});