From cc88c283ce99666f99ed75c81d12665215db8a80 Mon Sep 17 00:00:00 2001 From: xianyi Date: Thu, 15 Jan 2026 16:52:03 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=B7=B2=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/finished.tsx | 548 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 544 insertions(+), 4 deletions(-) diff --git a/app/(tabs)/finished.tsx b/app/(tabs)/finished.tsx index c10a665..4ee7f67 100644 --- a/app/(tabs)/finished.tsx +++ b/app/(tabs)/finished.tsx @@ -1,11 +1,503 @@ +import { HomeHeader } from "@/components/home-header"; +import { LeagueModal } from "@/components/league-modal"; +import { MatchCard } from "@/components/match-card"; +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 { StyleSheet } from "react-native"; +import { IconSymbol } from "@/components/ui/icon-symbol"; +import { Colors } from "@/constants/theme"; +import { useAppState } from "@/context/AppStateContext"; +import { useTheme } from "@/context/ThemeContext"; +import { fetchLeagues, fetchSports, fetchTodayMatches } from "@/lib/api"; +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); + const [showLeagueModal, setShowLeagueModal] = 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 + ); + setMatches(list); + } catch (e) { + console.error(e); + } finally { + setLoading(false); + } + }; + + const currentSport = sports.find((s) => s.id === selectedSportId); + + const filteredMatches = useMemo( + () => matches.filter((m) => m.scoreText && m.scoreText.trim() !== "-"), + [matches] + ); + + // 获取当前运动的国际化名称 + 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/League Selector */} + {filterMode === "time" ? ( + setShowCalendarModal(true)} + > + + {selectedDate.getDate()} + + + {timezoneLabel} {nowTimeText} + + + ) : ( + { + // 立即显示弹窗 + setShowLeagueModal(true); + // 如果联赛列表为空,立即设置loading状态并加载 + if (selectedSportId !== null) { + if (leagues.length === 0) { + setLoadingLeagues(true); + } + loadLeagues(); + } + }} + > + + + {selectedLeagueKey + ? leagues.find((l) => l.key === selectedLeagueKey)?.name || + t("home.select_league") + : t("home.select_league")} + + + )} + + ); -export default function FinishedScreen() { return ( - Finished Events + + + {renderHeader()} + + {loading ? ( + + + {t("home.loading")} + + ) : ( + item.id} + renderItem={({ item }) => } + 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} + /> + )} + + {showLeagueModal && ( + setShowLeagueModal(false)} + leagues={leagues} + selectedLeagueKey={selectedLeagueKey} + loading={loadingLeagues} + onSelect={handleLeagueSelect} + /> + )} ); } @@ -13,7 +505,55 @@ export default function FinishedScreen() { const styles = StyleSheet.create({ container: { flex: 1, - alignItems: "center", + }, + 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, }, });