import { ThemedText } from "@/components/themed-text"; import { IconSymbol } from "@/components/ui/icon-symbol"; import { Colors } from "@/constants/theme"; import { useAppState } from "@/context/AppStateContext"; import { useTheme } from "@/context/ThemeContext"; import { addFavorite, fetchLiveScore, fetchOdds, removeFavorite, } from "@/lib/api"; import { LiveScoreMatch, Match, OddsItem } from "@/types/api"; import { Image } from "expo-image"; import { LinearGradient } from "expo-linear-gradient"; import { useRouter } from "expo-router"; import React, { useEffect, useState } from "react"; import { Pressable, StyleSheet, TouchableOpacity, View } from "react-native"; interface MatchCardProps { match: Match; onPress?: (match: Match) => void; onFavoriteToggle?: (matchId: string, isFav: boolean) => void; } export function MatchCard({ match, onPress, onFavoriteToggle, }: MatchCardProps) { const router = useRouter(); const { theme } = useTheme(); const { state } = useAppState(); const [isFav, setIsFav] = useState(match.fav); const [loading, setLoading] = useState(false); const [odds, setOdds] = useState(match.odds || []); const [liveDetail, setLiveDetail] = useState(null); // console.log("MatchCard render:", JSON.stringify(match)); const oddsSettings = state.oddsSettings; const cardsSettings = state.cardsSettings; useEffect(() => { if ( oddsSettings.enabled && oddsSettings.selectedBookmakers.length > 0 && !match.odds ) { fetchOdds(match.sportId || 1, parseInt(match.id)) .then((res) => { const matchOdds = res[match.id]?.data || []; setOdds(matchOdds); }) .catch((err) => console.log("Fetch match card odds error:", err)); } }, [ oddsSettings.enabled, oddsSettings.selectedBookmakers, match.id, match.odds, ]); // Fetch live score detail for cards info useEffect(() => { if (cardsSettings.enabled && isLive && match.leagueKey) { fetchLiveScore(match.sportId || 1, Number(match.leagueKey)) .then((matches) => { const detail = matches.find((m) => String(m.event_key) === match.id); if (detail) { setLiveDetail(detail); } }) .catch((err) => console.log("Fetch live detail for cards error:", err)); } }, [cardsSettings.enabled, match.id, match.leagueKey, match.sportId]); // 当外部传入的 match.fav 改变时,更新内部状态 useEffect(() => { setIsFav(match.fav); }, [match.fav]); const isDark = theme === "dark"; const iconColor = isDark ? Colors.dark.icon : Colors.light.icon; const cardBg = isDark ? "#1C1C1E" : "#FFFFFF"; const borderColor = isDark ? "#2C2C2E" : "rgba(0,0,0,0.06)"; const scoreBorder = isDark ? "rgba(255,255,255,0.18)" : "rgba(0,0,0,0.12)"; const scoreBg = isDark ? "rgba(255,255,255,0.04)" : "rgba(255,255,255,0.6)"; const oddBadgeBg = isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.03)"; const isLive = React.useMemo(() => { return !!match.isLive; }, [match.isLive]); const timeLabel = React.useMemo(() => { const raw = (match.time || "").trim(); if (!raw) return ""; if (/^\d{1,3}$/.test(raw)) return `${raw}'`; return raw; }, [match.time]); const leagueShort = React.useMemo(() => { const league = (match.leagueName || match.league || "").trim(); if (!league) return "--"; const first = league.split(/[^A-Za-z0-9]+/).filter(Boolean)[0] || league; return first.slice(0, 3).toUpperCase(); }, [match.leagueName, match.league]); const scoreParts = React.useMemo(() => { const s = (match.scoreText || "").trim(); const m = s.match(/(\d+)\s*[-:]\s*(\d+)/); if (m) return { home: m[1], away: m[2], hasScore: true }; if (s && s !== "-" && s !== "0 - 0") return { home: s, away: "", hasScore: true }; if (s === "0 - 0" || s === "0-0") return { home: "0", away: "0", hasScore: true }; return { home: "", away: "", hasScore: false }; }, [match.scoreText]); const cardsCount = React.useMemo(() => { if (!liveDetail?.cards || !cardsSettings.enabled) { return { homeYellow: 0, homeRed: 0, awayYellow: 0, awayRed: 0 }; } let homeYellow = 0, homeRed = 0, awayYellow = 0, awayRed = 0; liveDetail.cards.forEach((card) => { const cardType = (card.card || "").toLowerCase(); const isYellow = cardType.includes("yellow"); const isRed = cardType.includes("red"); if (!isYellow && !isRed) return; const info = (card.info || "").toLowerCase(); const sideFromInfo = info.includes("home") ? "home" : info.includes("away") ? "away" : null; const sideFromFault = card.home_fault ? "home" : card.away_fault ? "away" : null; const side = sideFromInfo || sideFromFault; if (!side) return; if (side === "home") { if (isYellow) homeYellow++; if (isRed) homeRed++; } else { if (isYellow) awayYellow++; if (isRed) awayRed++; } }); return { homeYellow, homeRed, awayYellow, awayRed }; }, [liveDetail, cardsSettings.enabled]); const handlePress = () => { if (onPress) { onPress(match); } else { if (isLive) { router.push({ pathname: "/live-detail/[id]", params: { id: match.id, league_id: match.leagueId?.toString() || "", sport_id: match.sportId?.toString() || "", }, }); } else { router.push(`/match-detail/${match.id}`); } } }; const toggleFavorite = async () => { if (loading) return; setLoading(true); const newFavState = !isFav; try { if (newFavState) { await addFavorite({ matchId: parseInt(match.id), type: "match", typeId: match.id, notify: true, }); } else { await removeFavorite({ type: "match", typeId: match.id, }); } setIsFav(newFavState); if (onFavoriteToggle) { onFavoriteToggle(match.id, newFavState); } } catch (error) { console.error("Toggle favorite error:", error); } finally { setLoading(false); } }; const renderOddsRow = (bookmakerName: string, isHighlight: boolean) => { if (!oddsSettings.enabled || !bookmakerName || odds.length === 0) return null; const item = odds.find((o) => o.odd_bookmakers === bookmakerName); if (!item) return null; const val1 = item.odd_1 || item.ah0_1 || "-"; const val2 = item.odd_x || "0" || "-"; const val3 = item.odd_2 || item.ah0_2 || "-"; return ( {val1} {val2} {val3} ); }; const renderCardsInline = (yellow: number, red: number) => { if (!cardsSettings.enabled || !liveDetail) return null; if (yellow <= 0 && red <= 0) return null; return ( {yellow > 0 && ( {yellow} )} {red > 0 && ( {red} )} ); }; return ( [ styles.card, { backgroundColor: cardBg, borderColor, opacity: pressed ? 0.7 : 1 }, ]} > {isLive && ( )} {/* Left: League short + time */} {leagueShort} {timeLabel} {/* Middle: Teams & Odds Row Integration */} {match.homeTeamLogo && ( )} {match.homeTeamName || match.home} {renderCardsInline(cardsCount.homeYellow, cardsCount.homeRed)} {renderOddsRow(oddsSettings.selectedBookmakers[0], true)} {match.awayTeamLogo && ( )} {match.awayTeamName || match.away} {renderCardsInline(cardsCount.awayYellow, cardsCount.awayRed)} {renderOddsRow(oddsSettings.selectedBookmakers[1], false)} {/* Right: Score box + favorite */} {scoreParts.hasScore ? ( {scoreParts.home} {scoreParts.away} ) : ( )} { e.stopPropagation(); toggleFavorite(); }} disabled={loading} hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }} > ); } const styles = StyleSheet.create({ card: { height: 78, paddingHorizontal: 12, marginBottom: 8, borderRadius: 14, borderWidth: 1, justifyContent: "center", overflow: "hidden", shadowColor: "#000", shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.05, shadowRadius: 2, elevation: 2, }, row: { flexDirection: "row", alignItems: "center", }, left: { width: 44, alignItems: "flex-start", justifyContent: "center", gap: 4, }, leagueShortText: { fontSize: 12, fontWeight: "700", opacity: 0.8, }, timeText: { fontSize: 12, opacity: 0.5, }, timeTextLive: { color: "#FF3B30", opacity: 1, fontWeight: "800", }, middle: { flex: 1, justifyContent: "center", gap: 10, marginHorizontal: 8, }, contentRow: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", }, teamInfo: { flexDirection: "row", alignItems: "center", flex: 1, minWidth: 0, }, teamLogo: { width: 22, height: 22, }, teamName: { fontSize: 14, fontWeight: "600", marginLeft: 6, flex: 1, minWidth: 0, }, bookmakerOddsRow: { flexDirection: "row", gap: 4, }, oddBadge: { paddingHorizontal: 5, paddingVertical: 2, borderRadius: 6, minWidth: 36, alignItems: "center", justifyContent: "center", }, oddText: { fontSize: 10.5, fontWeight: "700", opacity: 0.9, }, oddTextHighlight: { color: "#FF9500", }, right: { flexDirection: "row", alignItems: "center", gap: 6, }, scoreBox: { width: 36, height: 54, borderRadius: 8, borderWidth: 1.5, alignItems: "center", justifyContent: "center", }, scoreBoxText: { fontSize: 20, fontWeight: "900", }, scoreDivider: { width: "60%", height: 1, backgroundColor: "rgba(0,0,0,0.06)", marginVertical: 1, }, scoreBoxPlaceholder: { width: 36, height: 54, }, cardsInline: { flexDirection: "row", alignItems: "center", gap: 4, marginLeft: 6, flexShrink: 0, }, cardBadge: { minWidth: 16, height: 16, borderRadius: 3, alignItems: "center", justifyContent: "center", paddingHorizontal: 3, }, cardBadgeYellow: { backgroundColor: "#FFC400", }, cardBadgeRed: { backgroundColor: "#FF3B30", }, cardBadgeText: { fontSize: 10, fontWeight: "900", lineHeight: 12, color: "#fff", }, cardBadgeTextDark: { color: "#000", }, });