实现直播详情页

This commit is contained in:
yuchenglong
2026-01-14 18:15:13 +08:00
parent 8dc87c9b29
commit 025b483099
18 changed files with 2497 additions and 347 deletions

156
app/live-detail/[id].tsx Normal file
View File

@@ -0,0 +1,156 @@
import { EventsTimeline } from "@/components/live-detail/events-timeline";
import { LiveLeagueInfo } from "@/components/live-detail/live-league-info";
import { LiveMatchTabs } from "@/components/live-detail/live-match-tabs";
import { LiveScoreHeader } from "@/components/live-detail/live-score-header";
import { OddsCard } from "@/components/live-detail/odds-card";
import { OtherInfoCard } from "@/components/live-detail/other-info-card";
import { StatsCard } from "@/components/live-detail/stats-card";
import { ThemedText } from "@/components/themed-text";
import { ThemedView } from "@/components/themed-view";
import { Colors } from "@/constants/theme";
import { useTheme } from "@/context/ThemeContext";
import { fetchLiveScore } from "@/lib/api";
import { LiveScoreMatch } from "@/types/api";
import { useLocalSearchParams } from "expo-router";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import {
ActivityIndicator,
ScrollView,
StyleSheet,
TouchableOpacity,
View,
} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
export default function LiveDetailScreen() {
const { id, league_id, sport_id } = useLocalSearchParams<{
id: string;
league_id: string;
sport_id: string;
}>();
const { theme } = useTheme();
const { t } = useTranslation();
const insets = useSafeAreaInsets();
const isDark = theme === "dark";
const [loading, setLoading] = useState(true);
const [match, setMatch] = useState<LiveScoreMatch | null>(null);
const [activeTab, setActiveTab] = useState("detail"); // Default to detail to show all data
useEffect(() => {
loadLiveDetail();
}, [id, league_id]);
const loadLiveDetail = async () => {
try {
setLoading(true);
// Fetch live scores for the league
const sportId = parseInt(sport_id || "1");
const leagueId = parseInt(league_id || "0");
const liveData = await fetchLiveScore(sportId, leagueId);
if (liveData && Array.isArray(liveData)) {
// Find the specific match
const found = liveData.find((m) => m.event_key.toString() === id);
if (found) {
setMatch(found);
}
}
} catch (err) {
console.error(err);
} finally {
setLoading(false);
}
};
if (loading) {
return (
<ThemedView style={styles.center}>
<ActivityIndicator size="large" color={Colors[theme].tint} />
</ThemedView>
);
}
if (!match) {
return (
<ThemedView style={styles.center}>
<ThemedText>{t("detail.not_found")}</ThemedText>
<TouchableOpacity style={styles.retryButton} onPress={loadLiveDetail}>
<ThemedText style={styles.retryText}>{t("detail.retry")}</ThemedText>
</TouchableOpacity>
</ThemedView>
);
}
const renderTabContent = () => {
switch (activeTab) {
case "stats":
return <StatsCard match={match} isDark={isDark} />;
case "odds":
return <OddsCard match={match} isDark={isDark} />;
case "detail":
return (
<>
<OddsCard match={match} isDark={isDark} />
<StatsCard match={match} isDark={isDark} />
<EventsTimeline match={match} isDark={isDark} />
<OtherInfoCard match={match} isDark={isDark} />
</>
);
default:
return (
<View style={styles.center}>
<ThemedText style={{ opacity: 0.5 }}>
{t("detail.empty_stats")}
</ThemedText>
</View>
);
}
};
return (
<ThemedView style={styles.container}>
<ScrollView
bounces={false}
showsVerticalScrollIndicator={false}
contentContainerStyle={{ paddingBottom: insets.bottom + 20 }}
>
<LiveScoreHeader match={match} topInset={insets.top} />
<LiveLeagueInfo match={match} />
<LiveMatchTabs
activeTab={activeTab}
onTabChange={setActiveTab}
isDark={isDark}
/>
{renderTabContent()}
</ScrollView>
</ThemedView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
center: {
flex: 1,
justifyContent: "center",
alignItems: "center",
padding: 20,
minHeight: 200,
},
retryButton: {
marginTop: 20,
paddingHorizontal: 20,
paddingVertical: 10,
backgroundColor: "#007AFF",
borderRadius: 8,
},
retryText: {
color: "#FFF",
fontWeight: "600",
},
});