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, fetchSports, fetchTodayMatches, } from "@/lib/api"; import { storage } from "@/lib/storage"; import { League, Match, Sport } from "@/types/api"; 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 { 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 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 ); const token = await storage.getAccessToken(); let listWithFavStatus = list; if (token) { // 直接传递 match.id 查询是否收藏,并更新列表状态 listWithFavStatus = await Promise.all( list.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 sortedList = [...listWithFavStatus].sort((a, b) => { if (a.fav === b.fav) return 0; return a.fav ? -1 : 1; }); 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, }, });