import { HomeHeader } from "@/components/home-header"; import { MatchCard } from "@/components/match-card"; import { MatchCardLeague } from "@/components/match-card-league"; import { SelectionModal } from "@/components/selection-modal"; import { CalendarModal } from "@/components/simple-calendar"; import { ThemedText } from "@/components/themed-text"; import { ThemedView } from "@/components/themed-view"; import { IconSymbol } from "@/components/ui/icon-symbol"; import { Colors } from "@/constants/theme"; import { useAppState } from "@/context/AppStateContext"; import { useTheme } from "@/context/ThemeContext"; import { checkFavorite, fetchLeagues, fetchLiveScore, fetchSports, fetchTodayMatches, } from "@/lib/api"; import { storage } from "@/lib/storage"; import { League, Match, Sport } from "@/types/api"; import { useRouter } from "expo-router"; import React, { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { ActivityIndicator, FlatList, StyleSheet, TouchableOpacity, View, } from "react-native"; export default function HomeScreen() { const router = useRouter(); const { theme } = useTheme(); const { t } = useTranslation(); const isDark = theme === "dark"; const iconColor = isDark ? Colors.dark.icon : Colors.light.icon; const filterBg = isDark ? "#2C2C2E" : "#F2F2F7"; const { state, updateSportId, updateDate, updateLeagueKey, updateTimezone } = useAppState(); const [sports, setSports] = useState([]); const [leagues, setLeagues] = useState([]); const [matches, setMatches] = useState([]); const [loading, setLoading] = useState(true); const [loadingLeagues, setLoadingLeagues] = useState(false); const [now, setNow] = useState(() => new Date()); const [liveLeagueIdByMatchId, setLiveLeagueIdByMatchId] = useState< Record >({}); const deviceTimeZone = useMemo(() => { try { console.log( "deviceTimeZone", Intl.DateTimeFormat().resolvedOptions().timeZone, ); return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC"; } catch { return "UTC"; } }, []); // 初始化时同步设备时区到 Context useEffect(() => { updateTimezone(deviceTimeZone); }, [deviceTimeZone]); // Selection States - 从 Context 读取 const selectedSportId = state.selectedSportId; const selectedDate = state.selectedDate; const selectedLeagueKey = state.selectedLeagueKey; const [filterMode, setFilterMode] = useState<"time" | "league">("time"); // 时间或联赛模式 // Modal Visibilities const [showSportModal, setShowSportModal] = useState(false); const [showCalendarModal, setShowCalendarModal] = useState(false); // Load Sports and Leagues useEffect(() => { loadSports(); loadLeagues(); }, []); // 当前时间:每秒刷新(用于展示“现在时间/时区”) useEffect(() => { const id = setInterval(() => setNow(new Date()), 1000); return () => clearInterval(id); }, []); // Load Matches when sport or date changes useEffect(() => { if (selectedSportId !== null) { loadMatches(selectedSportId); } }, [selectedSportId, selectedDate]); const timezoneLabel = useMemo(() => { // 仅展示 UTC 偏移(不展示时区名) const offsetMin = -now.getTimezoneOffset(); const sign = offsetMin >= 0 ? "+" : "-"; const abs = Math.abs(offsetMin); const hh = String(Math.floor(abs / 60)).padStart(2, "0"); const mm = String(abs % 60).padStart(2, "0"); return `UTC${sign}${hh}`; }, [now]); const nowTimeText = useMemo(() => { const hh = String(now.getHours()).padStart(2, "0"); const mm = String(now.getMinutes()).padStart(2, "0"); const ss = String(now.getSeconds()).padStart(2, "0"); return `${hh}:${mm}`; }, [now]); // Load Leagues when sport changes useEffect(() => { if (selectedSportId !== null) { loadLeagues(); } }, [selectedSportId]); const loadSports = async () => { try { const apiList = await fetchSports(); // 创建8个运动的完整列表 const defaultSports: Sport[] = [ { id: 1, name: "football", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "", }, { id: 2, name: "basketball", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "", }, { id: 3, name: "tennis", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "", }, { id: 4, name: "cricket", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "", }, { id: 5, name: "baseball", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "", }, { id: 6, name: "badminton", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "", }, { id: 7, name: "snooker", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "", }, { id: 8, name: "volleyball", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "", }, ]; // 合并API返回的运动和默认列表 const sportsMap = new Map(); apiList.forEach((sport) => { sportsMap.set(sport.id, sport); }); // 补充默认运动到8个 defaultSports.forEach((sport) => { if (!sportsMap.has(sport.id)) { sportsMap.set(sport.id, sport); } }); const allSports = Array.from(sportsMap.values()) .sort((a, b) => a.id - b.id) .slice(0, 8); setSports(allSports); } catch (e) { console.error(e); // API失败时使用默认8个运动 const defaultSports: Sport[] = [ { id: 1, name: "football", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "", }, { id: 2, name: "basketball", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "", }, { id: 3, name: "tennis", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "", }, { id: 4, name: "cricket", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "", }, { id: 5, name: "baseball", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "", }, { id: 6, name: "badminton", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "", }, { id: 7, name: "snooker", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "", }, { id: 8, name: "volleyball", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "", }, ]; setSports(defaultSports); } }; // 加载联赛 const loadLeagues = async () => { try { if (selectedSportId !== null) { setLoadingLeagues(true); const list = await fetchLeagues(selectedSportId, ""); setLeagues(list); } } catch (e) { console.error(e); } finally { setLoadingLeagues(false); } }; const loadMatches = async (sportId: number) => { setLoading(true); try { const list = await fetchTodayMatches( sportId, selectedDate, deviceTimeZone, ); //将isLive全改为true list.forEach((m) => { (m as Match).isLive = true; }); const normalizeDate = (d: Date) => { const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; }; const todayStr = normalizeDate(new Date()); const selectedStr = normalizeDate(selectedDate); const shouldMergeLive = selectedStr === todayStr; let merged: Match[] = list.map((m) => ({ ...m, date: (m as any).date || selectedStr, sport: (m as any).sport ?? sportId, })); if (shouldMergeLive) { try { const liveData = await fetchLiveScore( sportId, state.selectedLeagueKey ? parseInt(state.selectedLeagueKey) : undefined, state.timezone || deviceTimeZone, ); const formatLiveTime = (status: string, fallback: string) => { const s = (status || "").trim(); if (!s) return (fallback || "").trim(); if (/^\d{1,3}$/.test(s)) return `${s}'`; return s; }; const map: Record = {}; const liveMatches: Match[] = (liveData || []).map((item) => { const id = item.event_key.toString(); map[id] = item.league_key; return { id, league: item.league_name, time: formatLiveTime(item.event_status, item.event_time), date: item.event_date, home: item.event_home_team, away: item.event_away_team, scoreText: item.event_final_result || "0 - 0", fav: false, sport: sportId, isLive: true, }; }); setLiveLeagueIdByMatchId(map); // Merge by id: live takes precedence const byId = new Map(); liveMatches.forEach((m) => byId.set(m.id, m)); merged.forEach((m) => { if (!byId.has(m.id)) byId.set(m.id, m); }); merged = Array.from(byId.values()); } catch (e) { // Live merge failure should not block base list console.warn("Fetch live score failed:", e); } } else { setLiveLeagueIdByMatchId({}); } const token = await storage.getAccessToken(); let listWithFavStatus = merged; if (token) { // 直接传递 match.id 查询是否收藏,并更新列表状态 listWithFavStatus = await Promise.all( merged.map(async (m) => { try { // 查询比赛是否已被收藏 const favRes = await checkFavorite("match", m.id); return { ...m, fav: favRes.isFavorite }; } catch (error) { console.error(`Check favorite failed for match ${m.id}:`, error); return m; } }), ); } const isLiveRow = (m: Match) => { const t = (m.time || "").trim(); return ( /\d+'$/.test(t) || /^\d{1,3}$/.test(t) || /\b(ht|half)\b/i.test(t) ); }; // 收藏置顶,其次直播置顶 const sortedList = [...listWithFavStatus].sort((a, b) => { if (a.fav !== b.fav) return a.fav ? -1 : 1; const aLive = isLiveRow(a); const bLive = isLiveRow(b); if (aLive !== bLive) return aLive ? -1 : 1; return 0; }); setMatches(sortedList); } catch (e) { console.error(e); } finally { setLoading(false); } }; const handleFavoriteToggle = (matchId: string, isFav: boolean) => { setMatches((prev) => { const updated = prev.map((m) => m.id === matchId ? { ...m, fav: isFav } : m, ); return [...updated].sort((a, b) => { if (a.fav === b.fav) return 0; return a.fav ? -1 : 1; }); }); }; const currentSport = sports.find((s) => s.id === selectedSportId); // 获取当前运动的国际化名称 const getSportName = (sport: Sport | undefined): string => { if (!sport) return t("home.select_sport"); const sportKeyMap: { [key: number]: string } = { 1: "football", 2: "basketball", 3: "tennis", 4: "cricket", 5: "baseball", 6: "badminton", 7: "snooker", 8: "volleyball", }; const sportKey = sportKeyMap[sport.id] || sport.name.toLowerCase(); return t(`sports.${sportKey}`, { defaultValue: sport.name }); }; const handleLeagueSelect = (leagueKey: string) => { updateLeagueKey(leagueKey); console.log("Selected league:", leagueKey); }; // 缓存运动选项,避免每次渲染都重新计算 const sportOptions = useMemo(() => { return sports.map((s) => { const sportKeyMap: { [key: number]: string } = { 1: "football", 2: "basketball", 3: "tennis", 4: "cricket", 5: "baseball", 6: "badminton", 7: "snooker", 8: "volleyball", }; const sportKey = sportKeyMap[s.id] || s.name.toLowerCase(); return { id: s.id, label: s.name, value: s.id, icon: s.icon, }; }); }, [sports]); const renderHeader = () => ( {/* Time/League Filter Toggle */} setFilterMode(filterMode === "time" ? "league" : "time")} > {filterMode === "time" ? t("home.time") : t("home.league")} {/* Sport Selector */} setShowSportModal(true)} > {getSportName(currentSport)} {/* Date Selector */} setShowCalendarModal(true)} > {selectedDate.getDate()} {timezoneLabel} {nowTimeText} ); return ( {renderHeader()} {loading ? ( {t("home.loading")} ) : ( item.id} renderItem={({ item }) => filterMode === "time" ? ( ) : ( ) } contentContainerStyle={styles.listContent} ListEmptyComponent={ {t("home.no_matches")} } /> )} {/* Modals - 条件渲染,只在可见时渲染 */} {showSportModal && ( setShowSportModal(false)} title={t("home.select_sport")} options={sportOptions} selectedValue={selectedSportId} onSelect={updateSportId} /> )} {showCalendarModal && ( setShowCalendarModal(false)} selectedDate={selectedDate} onSelectDate={updateDate} /> )} ); } const styles = StyleSheet.create({ container: { flex: 1, }, center: { flex: 1, justifyContent: "center", alignItems: "center", paddingTop: 50, }, filterContainer: { flexDirection: "row", paddingHorizontal: 16, paddingVertical: 12, gap: 12, }, filterBtn: { flex: 1, height: 44, // Increased from 36 flexDirection: "column", // Stacked logic for Date, or Row for others justifyContent: "center", alignItems: "center", borderRadius: 8, // Rounded corners // iOS shadow shadowColor: "#000", shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.1, shadowRadius: 2, // Android elevation elevation: 2, }, mainFilterBtn: { flex: 2, // Wider for sport flexDirection: "row", gap: 8, }, filterText: { fontSize: 14, fontWeight: "500", }, dateDayText: { fontSize: 16, lineHeight: 16, fontWeight: "bold", }, dateMonthText: { fontSize: 12, lineHeight: 12, opacity: 0.6, }, listContent: { padding: 16, paddingTop: 8, }, });