import { ThemedText } from "@/components/themed-text"; import { ThemedView } from "@/components/themed-view"; import { LiveScoreMatch } from "@/types/api"; import React from "react"; import { useTranslation } from "react-i18next"; import { StyleSheet, View } from "react-native"; import Svg, { Circle, G } from "react-native-svg"; interface TennisStatsCardProps { match: LiveScoreMatch; isDark: boolean; } // Helper for Circular Progress // Updated to match screenshot: Labels on top, circle in middle, values on sides. const CircularStat = ({ value1, value2, label, color1 = "#2196F3", // Blue color2 = "#FFC107", // Gold size = 50, strokeWidth = 5, isDark, }: { value1: string; // "67%" or "67" value2: string; label: string; color1?: string; color2?: string; size?: number; strokeWidth?: number; isDark: boolean; }) => { const parse = (v: string) => parseFloat(v.replace("%", "")) || 0; const v1 = parse(value1); const v2 = parse(value2); // Radius const r = (size - strokeWidth) / 2; const c = size / 2; const circumference = 2 * Math.PI * r; // Inner radius const innerR = r - strokeWidth - 2; // small gap const innerCircumference = 2 * Math.PI * innerR; // Colors for text need to be readable on white background if card is always white // Screenshot shows white card on dark background. // BUT the text "First Serve %" is dark/grey. // The values 67.4 are Blue/Gold. // The circle tracks are light grey maybe. const trackColor = isDark ? "#333" : "#E0E0E0"; const labelColor = isDark ? "#AAA" : "#666"; return ( {/* Label centered */} {label} {/* Left Value P1 */} {value1} {/* Circle Graphic */} {/* Outer Track */} {/* P1 Progress (Outer) */} {/* Inner Track */} {/* P2 Progress (Inner) */} {/* Right Value P2 */} {value2} ); }; // Helper for Bar Stat const BarStat = ({ label, value1, value2, color1 = "#2196F3", color2 = "#FFC107", isDark, }: { label: string; value1: string; value2: string; color1?: string; color2?: string; isDark: boolean; }) => { // Parse to number for bars const v1 = parseFloat(value1) || 0; const v2 = parseFloat(value2) || 0; const total = v1 + v2 || 1; const p1 = (v1 / total) * 100; const p2 = (v2 / total) * 100; return ( {value1} {label} {value2} ); }; export function TennisStatsCard({ match, isDark }: TennisStatsCardProps) { const { t } = useTranslation(); const statistics = match.statistics; if (!statistics || !Array.isArray(statistics) || statistics.length === 0) { return null; } // Check structure. If it's the Football type (home/away keys), we shouldn't be here (LiveDetail logic checks sport). // But safely handle: const isTennisFormat = "player_key" in statistics[0] || "name" in statistics[0]; if (!isTennisFormat) return null; // Group by name // Map Name -> { p1Value: string, p2Value: string } const statsMap: Record = {}; // Assume P1 is first_player_key // Using home_team_key as fallback/primary since first_player_key isn't in type definition // In many APIs, for tennis, home_team_key === first_player_key const p1Key = match.home_team_key; // In generic response, keys might be strings vs numbers. // Let's rely on finding two entries for same "name" and "period". // Or just iterate. (statistics as any[]).forEach((stat) => { // Filter for 'match' period (total) if (stat.period !== "match") return; const name = stat.name; if (!statsMap[name]) statsMap[name] = { p1: "0", p2: "0", type: stat.type }; // Assign to P1 or P2 // If we know p1Key if (p1Key && stat.player_key == p1Key) { statsMap[name].p1 = stat.value; } else { statsMap[name].p2 = stat.value; } }); // Define intended stats to show // Top (Circles): // "1st serve percentage" (1st Serve %) // "1st serve points won" (1st Serve Win %) // "Break Points Converted" -> "Break Success %" (Screenshot: 破发成功率) // Or match keys exactly: "1st serve percentage", "1st serve points won", "Break Points Converted" // Middle (Bars): // "Aces" (Screenshot: 发球得分? Or implies Aces). // "Double Faults" (Screenshot: 双误) // "Service Points Won" const circleStats = [ { key: "1st serve percentage", label: t("detail.stats.first_serve") }, { key: "1st serve points won", label: t("detail.stats.first_serve_points"), }, { key: "Break Points Converted", label: t("detail.stats.break_points") }, ]; const barStats = [ { key: "Aces", label: t("detail.stats.aces") }, { key: "Double Faults", label: t("detail.stats.double_faults") }, ]; // Check if any of the target stats actually exist in the map const hasAnyStat = [...circleStats, ...barStats].some((s) => statsMap[s.key]); if (!hasAnyStat) return null; return ( {t("detail.statistics")} {circleStats.map((s) => statsMap[s.key] ? ( ) : null, )} {barStats.map((s) => statsMap[s.key] ? ( ) : null, )} ); } const styles = StyleSheet.create({ container: { margin: 16, padding: 16, borderRadius: 12, }, title: { fontSize: 16, fontWeight: "bold", marginBottom: 20, }, circlesRow: { flexDirection: "row", justifyContent: "space-around", marginBottom: 24, }, circularStatContainer: { alignItems: "center", width: "30%", }, circularLabel: { fontSize: 12, textAlign: "center", opacity: 0.7, height: 32, // Fixed height for alignment }, circularRow: { flexDirection: "row", alignItems: "center", }, statValueSide: { fontSize: 12, fontWeight: "bold", }, statValue: { fontSize: 12, fontWeight: "600", }, barsColumn: { gap: 16, }, barStatContainer: { marginBottom: 8, }, barHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 8, }, pill: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12, minWidth: 40, alignItems: "center", }, pillText: { color: "white", fontWeight: "bold", fontSize: 12, }, barLabel: { opacity: 0.8, }, barTrack: { flexDirection: "row", height: 6, backgroundColor: "#333", borderRadius: 3, overflow: "hidden", }, barFill: { height: "100%", }, });