286 lines
6.7 KiB
TypeScript
286 lines
6.7 KiB
TypeScript
import { ThemedText } from "@/components/themed-text";
|
|
import { MatchDetailData } from "@/types/api";
|
|
import { LinearGradient } from "expo-linear-gradient";
|
|
import React, { useEffect } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { StyleSheet, View } from "react-native";
|
|
|
|
interface BasketballOverallStatsProps {
|
|
data: MatchDetailData;
|
|
isDark: boolean;
|
|
}
|
|
|
|
type StatItem = {
|
|
type: string;
|
|
home: string | number;
|
|
away: string | number;
|
|
};
|
|
|
|
function toNumber(v: unknown): number {
|
|
if (typeof v === "number") return Number.isFinite(v) ? v : 0;
|
|
if (typeof v === "string") {
|
|
const cleaned = v.trim().replace("%", "");
|
|
const n = Number(cleaned);
|
|
return Number.isFinite(n) ? n : 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
export function BasketballOverallStats({ data, isDark }: BasketballOverallStatsProps) {
|
|
const { t } = useTranslation();
|
|
|
|
useEffect(() => {
|
|
console.log("=== Basketball Overall Stats Loaded ===");
|
|
console.log(JSON.stringify(data.match.stats, null, 2));
|
|
}, [data.match.stats]);
|
|
|
|
const stats = (data.match.stats || []) as StatItem[];
|
|
|
|
const getStatLabel = (type: string): string => {
|
|
const key = `detail.overall_stats.${type.toLowerCase().replace(/\s+/g, "_")}`;
|
|
const translated = t(key);
|
|
return translated !== key ? translated : type;
|
|
};
|
|
|
|
if (!Array.isArray(stats) || stats.length === 0) {
|
|
return (
|
|
<View style={styles.empty}>
|
|
<ThemedText style={[styles.emptyText, { color: isDark ? "#A1A1AA" : "#6B7280" }]}>
|
|
{t("detail.empty_stats")}
|
|
</ThemedText>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View
|
|
style={[
|
|
styles.wrap,
|
|
{ backgroundColor: isDark ? "#151517" : "#F3F4F6" },
|
|
]}
|
|
>
|
|
{/* Card */}
|
|
<View
|
|
style={[
|
|
styles.card,
|
|
{ backgroundColor: isDark ? "#1C1C1E" : "#FFFFFF" },
|
|
]}
|
|
>
|
|
{stats.map((item, index) => {
|
|
const home = toNumber(item.home);
|
|
const away = toNumber(item.away);
|
|
const maxV = Math.max(home, away, 1);
|
|
|
|
const homeLeading = home >= away;
|
|
const awayLeading = away > home;
|
|
|
|
return (
|
|
<View
|
|
key={`${item.type}-${index}`}
|
|
style={[
|
|
styles.row,
|
|
index !== stats.length - 1 && {
|
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
borderBottomColor: isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.06)",
|
|
},
|
|
]}
|
|
>
|
|
<View style={styles.rowHeader}>
|
|
<ValuePill
|
|
text={`${home}`}
|
|
variant="home"
|
|
isDark={isDark}
|
|
/>
|
|
|
|
<ThemedText
|
|
style={[
|
|
styles.title,
|
|
{ color: isDark ? "#EAEAF0" : "#111827" },
|
|
]}
|
|
numberOfLines={1}
|
|
>
|
|
{getStatLabel(item.type)}
|
|
</ThemedText>
|
|
|
|
<ValuePill
|
|
text={`${away}`}
|
|
variant={awayLeading ? "awayLeading" : "away"}
|
|
isDark={isDark}
|
|
/>
|
|
</View>
|
|
|
|
<CompareBar
|
|
home={home}
|
|
away={away}
|
|
maxV={maxV}
|
|
isDark={isDark}
|
|
homeLeading={homeLeading}
|
|
awayLeading={awayLeading}
|
|
/>
|
|
</View>
|
|
);
|
|
})}
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
function ValuePill({
|
|
text,
|
|
variant,
|
|
isDark,
|
|
}: {
|
|
text: string;
|
|
variant: "home" | "away" | "awayLeading";
|
|
isDark: boolean;
|
|
}) {
|
|
const common = {
|
|
start: { x: 0, y: 0 },
|
|
end: { x: 1, y: 0 },
|
|
style: styles.pill,
|
|
} as const;
|
|
|
|
// 蓝色:主队/默认;金色:客队领先
|
|
const blue = isDark
|
|
? ["#0B2A73", "#0E4BFF"]
|
|
: ["#1D4ED8", "#2563EB"];
|
|
const gold = isDark
|
|
? ["#3A2A00", "#C08B00"]
|
|
: ["#B45309", "#D97706"];
|
|
|
|
const colors =
|
|
variant === "awayLeading"
|
|
? gold
|
|
: blue;
|
|
|
|
return (
|
|
<LinearGradient colors={[...colors] as [string, string]} {...common}>
|
|
<ThemedText style={styles.pillText}>{text}</ThemedText>
|
|
</LinearGradient>
|
|
);
|
|
}
|
|
|
|
function CompareBar({
|
|
home,
|
|
away,
|
|
maxV,
|
|
isDark,
|
|
homeLeading,
|
|
awayLeading,
|
|
}: {
|
|
home: number;
|
|
away: number;
|
|
maxV: number;
|
|
isDark: boolean;
|
|
homeLeading: boolean;
|
|
awayLeading: boolean;
|
|
}) {
|
|
const SIDE_MAX = 150;
|
|
const GAP = 10;
|
|
|
|
const homeW = Math.max(2, Math.round((home / maxV) * SIDE_MAX));
|
|
const awayW = Math.max(2, Math.round((away / maxV) * SIDE_MAX));
|
|
|
|
const track = isDark ? "rgba(255,255,255,0.10)" : "rgba(0,0,0,0.10)";
|
|
const muted = isDark ? "rgba(255,255,255,0.18)" : "rgba(0,0,0,0.18)";
|
|
const blue = "#1F5BFF";
|
|
const gold = "#C08B00";
|
|
|
|
const homeColor = homeLeading ? blue : muted;
|
|
const awayColor = awayLeading ? gold : muted;
|
|
|
|
return (
|
|
<View style={[styles.barWrap, { width: SIDE_MAX * 2 + GAP }]}>
|
|
<View style={[styles.barTrack, { backgroundColor: track }]} />
|
|
<View style={[styles.barSide, { width: SIDE_MAX, justifyContent: "flex-end" }]}>
|
|
<View style={[styles.barFill, { width: homeW, backgroundColor: homeColor }]} />
|
|
</View>
|
|
<View style={{ width: GAP }} />
|
|
<View style={[styles.barSide, { width: SIDE_MAX, justifyContent: "flex-start" }]}>
|
|
<View style={[styles.barFill, { width: awayW, backgroundColor: awayColor }]} />
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
/* ----------------- Styles ----------------- */
|
|
|
|
const styles = StyleSheet.create({
|
|
wrap: {
|
|
paddingHorizontal: 14,
|
|
paddingTop: 12,
|
|
paddingBottom: 16,
|
|
},
|
|
card: {
|
|
borderRadius: 18,
|
|
paddingVertical: 6,
|
|
overflow: "hidden",
|
|
},
|
|
|
|
row: {
|
|
paddingHorizontal: 14,
|
|
paddingVertical: 14,
|
|
},
|
|
rowHeader: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
},
|
|
|
|
pill: {
|
|
minWidth: 54,
|
|
height: 30,
|
|
borderRadius: 16,
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
paddingHorizontal: 10,
|
|
},
|
|
pillText: {
|
|
color: "#FFFFFF",
|
|
fontSize: 14,
|
|
fontWeight: "800",
|
|
letterSpacing: 0.3,
|
|
},
|
|
|
|
title: {
|
|
flex: 1,
|
|
textAlign: "center",
|
|
fontSize: 15,
|
|
fontWeight: "700",
|
|
paddingHorizontal: 10,
|
|
},
|
|
|
|
barWrap: {
|
|
alignSelf: "center",
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
marginTop: 10,
|
|
height: 8,
|
|
position: "relative",
|
|
},
|
|
barTrack: {
|
|
position: "absolute",
|
|
left: 0,
|
|
right: 0,
|
|
height: 6,
|
|
borderRadius: 999,
|
|
},
|
|
barSide: {
|
|
height: 8,
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
},
|
|
barFill: {
|
|
height: 6,
|
|
borderRadius: 999,
|
|
},
|
|
|
|
empty: {
|
|
padding: 40,
|
|
alignItems: "center",
|
|
},
|
|
emptyText: {
|
|
fontSize: 14,
|
|
opacity: 0.85,
|
|
},
|
|
});
|