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 && (
+
- ))}
+
+ )}
- {/* 比赛事件标记 (假点位) */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {/* 动态标记:红黄牌 */}
+ {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",