diff --git a/app.json b/app.json index 7bcbe84..aec78e5 100644 --- a/app.json +++ b/app.json @@ -45,7 +45,8 @@ "backgroundColor": "#000000" } } - ] + ], + "expo-font" ], "experiments": { "typedRoutes": true, diff --git a/components/live-detail/stats-card.tsx b/components/live-detail/stats-card.tsx index 1335b32..e4bd2bd 100644 --- a/components/live-detail/stats-card.tsx +++ b/components/live-detail/stats-card.tsx @@ -7,6 +7,7 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { Image, + LayoutChangeEvent, Modal, Pressable, StyleSheet, @@ -14,6 +15,12 @@ import { TouchableOpacity, View, } from "react-native"; +import Svg, { + Defs, + Path, + Stop, + LinearGradient as SvgLinearGradient, +} from "react-native-svg"; interface StatsCardProps { match: LiveScoreMatch; @@ -25,6 +32,11 @@ export function StatsCard({ match, isDark }: StatsCardProps) { const [flagSwitch, setFlagSwitch] = useState(true); const [cardSwitch, setCardSwitch] = useState(true); const [showInfo, setShowInfo] = useState(false); + const [chartWidth, setChartWidth] = useState(0); + + const onChartLayout = (event: LayoutChangeEvent) => { + setChartWidth(event.nativeEvent.layout.width); + }; // 实时时间与进度计算 const getInitialMinutes = () => { @@ -94,15 +106,50 @@ export function StatsCard({ match, isDark }: StatsCardProps) { parseInt(possession.away.toString().replace("%", "")) || 50; // 用随机数据模拟压力曲线 - const generateData = () => { - return Array.from({ length: 90 }, (_, i) => ({ - time: i, - home: Math.sin(i / 5) * 20 + Math.random() * 10, - away: Math.cos(i / 4) * 15 + Math.random() * 12, - })); + const generateData = React.useMemo(() => { + const data = []; + let h = 10; + let a = 10; + for (let i = 0; i < 90; i++) { + h = Math.max(0, Math.min(40, h + (Math.random() - 0.5) * 10)); + a = Math.max(0, Math.min(40, a + (Math.random() - 0.5) * 10)); + data.push({ + time: i, + home: h, + away: a, + }); + } + return data; + }, [match.event_key]); + + const getWavePath = ( + data: any[], + type: "home" | "away", + width: number, + height: number, + ) => { + if (width === 0) return ""; + const baseY = height / 2; + const points = data.map((d, i) => { + const x = (i / 89) * width; + const val = type === "home" ? d.home : d.away; + const y = + type === "home" + ? baseY - (val / 50) * baseY + : baseY + (val / 50) * baseY; + return { x, y }; + }); + + // 创建平滑曲线 (使用简单指令) + let d = `M 0 ${baseY}`; + points.forEach((p) => { + d += ` L ${p.x} ${p.y}`; + }); + d += ` L ${width} ${baseY} Z`; + return d; }; - const chartData = generateData(); + const chartData = generateData; const iconTintColor = isDark ? "#888" : "#CCC"; const switchBg = isDark ? "#2C2C2E" : "#F0F0F0"; @@ -171,72 +218,120 @@ export function StatsCard({ match, isDark }: StatsCardProps) { {["0'", "15'", "30'", "HT", "60'", "75'", "90'"].map((label, i) => ( - - {label} + + + {label} + ))} - {/* 压力曲线模拟 (使用密集的小条形模拟波形) */} - - {chartData.map((d, i) => ( - - + {chartWidth > 0 && ( + + + + + + + + + + + + {/* Home Wave */} + - - - ))} + + )} - {/* 比赛事件标记 (假点位) */} - - - - - - - - - - - - - - - + {/* 动态标记:红黄牌 */} + {chartWidth > 0 && + cardSwitch && + match.cards?.map((card, idx) => { + const time = parseInt(card.time) || 0; + const left = (time / 90) * chartWidth; + const isHome = card.home_fault !== ""; + const isYellow = (card.card || "") + .toLowerCase() + .includes("yellow"); + return ( + + + + ); + })} + + {/* 动态标记:进球 */} + {chartWidth > 0 && + flagSwitch && + match.goalscorers?.map((goal, idx) => { + const time = parseInt(goal.time) || 0; + const left = (time / 90) * chartWidth; + const isHome = !!goal.home_scorer; + return ( + + + + + + ); + })} diff --git a/package-lock.json b/package-lock.json index 0be2760..05230e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "expo-constants": "~18.0.13", "expo-dev-client": "~6.0.20", "expo-file-system": "^19.0.21", - "expo-font": "~14.0.10", + "expo-font": "~14.0.11", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", "expo-image-picker": "^17.0.10", @@ -29,7 +29,7 @@ "expo-linear-gradient": "^15.0.8", "expo-linking": "~8.0.11", "expo-localization": "^17.0.8", - "expo-router": "~6.0.21", + "expo-router": "~6.0.22", "expo-splash-screen": "~31.0.13", "expo-status-bar": "~3.0.9", "expo-symbols": "~1.0.8", @@ -46,6 +46,7 @@ "react-native-reanimated": "~4.1.1", "react-native-safe-area-context": "~5.6.0", "react-native-screens": "~4.16.0", + "react-native-svg": "15.12.1", "react-native-toast-message": "^2.3.3", "react-native-web": "~0.21.0", "react-native-webview": "13.15.0", @@ -4606,6 +4607,12 @@ "node": ">=0.6" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, "node_modules/bplist-creator": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", @@ -5150,6 +5157,56 @@ "hyphenate-style-name": "^1.0.3" } }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -5375,6 +5432,61 @@ "node": ">=0.10.0" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", @@ -5443,6 +5555,18 @@ "node": ">= 0.8" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-editor": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/env-editor/-/env-editor-0.4.2.tgz", @@ -6292,9 +6416,9 @@ } }, "node_modules/expo-font": { - "version": "14.0.10", - "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.10.tgz", - "integrity": "sha512-UqyNaaLKRpj4pKAP4HZSLnuDQqueaO5tB1c/NWu5vh1/LF9ulItyyg2kF/IpeOp0DeOLk0GY0HrIXaKUMrwB+Q==", + "version": "14.0.11", + "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.11.tgz", + "integrity": "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg==", "license": "MIT", "peer": true, "dependencies": { @@ -6460,9 +6584,9 @@ } }, "node_modules/expo-router": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-6.0.21.tgz", - "integrity": "sha512-wjTUjrnWj6gRYjaYl1kYfcRnNE4ZAQ0kz0+sQf6/mzBd/OU6pnOdD7WrdAW3pTTpm52Q8sMoeX98tNQEddg2uA==", + "version": "6.0.22", + "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-6.0.22.tgz", + "integrity": "sha512-6eOwobaVZQRsSQv0IoWwVlPbJru1zbreVsuPFIWwk7HApENStU2MggrceHXJqXjGho+FKeXxUop/gqOFDzpOMg==", "license": "MIT", "dependencies": { "@expo/metro-runtime": "^6.1.2", @@ -6494,7 +6618,7 @@ "@react-navigation/drawer": "^7.5.0", "@testing-library/react-native": ">= 12.0.0", "expo": "*", - "expo-constants": "^18.0.12", + "expo-constants": "^18.0.13", "expo-linking": "^8.0.11", "react": "*", "react-dom": "*", @@ -9205,6 +9329,12 @@ "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0" + }, "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", @@ -9787,6 +9917,18 @@ "node": ">=10" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/nullthrows": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", @@ -10803,6 +10945,21 @@ "react-native": "*" } }, + "node_modules/react-native-svg": { + "version": "15.12.1", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.12.1.tgz", + "integrity": "sha512-vCuZJDf8a5aNC2dlMovEv4Z0jjEUET53lm/iILFnFewa15b4atjVxU6Wirm6O9y6dEsdjDZVD7Q3QM4T1wlI8g==", + "license": "MIT", + "dependencies": { + "css-select": "^5.1.0", + "css-tree": "^1.1.3", + "warn-once": "0.1.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-toast-message": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/react-native-toast-message/-/react-native-toast-message-2.3.3.tgz", diff --git a/package.json b/package.json index 368ee99..529812a 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "expo-constants": "~18.0.13", "expo-dev-client": "~6.0.20", "expo-file-system": "^19.0.21", - "expo-font": "~14.0.10", + "expo-font": "~14.0.11", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", "expo-image-picker": "^17.0.10", @@ -31,7 +31,7 @@ "expo-linear-gradient": "^15.0.8", "expo-linking": "~8.0.11", "expo-localization": "^17.0.8", - "expo-router": "~6.0.21", + "expo-router": "~6.0.22", "expo-splash-screen": "~31.0.13", "expo-status-bar": "~3.0.9", "expo-symbols": "~1.0.8", @@ -48,6 +48,7 @@ "react-native-reanimated": "~4.1.1", "react-native-safe-area-context": "~5.6.0", "react-native-screens": "~4.16.0", + "react-native-svg": "15.12.1", "react-native-toast-message": "^2.3.3", "react-native-web": "~0.21.0", "react-native-webview": "13.15.0",