联赛筛选
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { HomeHeader } from "@/components/home-header";
|
import { HomeHeader } from "@/components/home-header";
|
||||||
import { MatchCard } from "@/components/match-card";
|
import { MatchCard } from "@/components/match-card";
|
||||||
import { MatchCardLeague } from "@/components/match-card-league";
|
import { MatchesByLeague } from "@/components/matches-by-league";
|
||||||
import { SelectionModal } from "@/components/selection-modal";
|
import { SelectionModal } from "@/components/selection-modal";
|
||||||
import { CalendarModal } from "@/components/simple-calendar";
|
import { CalendarModal } from "@/components/simple-calendar";
|
||||||
import { ThemedText } from "@/components/themed-text";
|
import { ThemedText } from "@/components/themed-text";
|
||||||
@@ -341,7 +341,7 @@ export default function HomeScreen() {
|
|||||||
let merged: Match[] = list.map((m) => ({
|
let merged: Match[] = list.map((m) => ({
|
||||||
...m,
|
...m,
|
||||||
date: (m as any).date || selectedStr,
|
date: (m as any).date || selectedStr,
|
||||||
sport: (m as any).sport ?? sportId,
|
sportId: (m as any).sportId ?? sportId,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (shouldMergeLive) {
|
if (shouldMergeLive) {
|
||||||
@@ -374,7 +374,7 @@ export default function HomeScreen() {
|
|||||||
away: item.event_away_team,
|
away: item.event_away_team,
|
||||||
scoreText: item.event_final_result || "0 - 0",
|
scoreText: item.event_final_result || "0 - 0",
|
||||||
fav: false,
|
fav: false,
|
||||||
sport: sportId,
|
sportId: sportId,
|
||||||
isLive: true,
|
isLive: true,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -556,17 +556,13 @@ export default function HomeScreen() {
|
|||||||
<ActivityIndicator size="large" color={Colors[theme].tint} />
|
<ActivityIndicator size="large" color={Colors[theme].tint} />
|
||||||
<ThemedText style={{ marginTop: 10 }}>{t("home.loading")}</ThemedText>
|
<ThemedText style={{ marginTop: 10 }}>{t("home.loading")}</ThemedText>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : filterMode === "time" ? (
|
||||||
<FlatList
|
<FlatList
|
||||||
data={matches}
|
data={matches}
|
||||||
keyExtractor={(item) => item.id}
|
keyExtractor={(item) => item.id}
|
||||||
renderItem={({ item }) =>
|
renderItem={({ item }) => (
|
||||||
filterMode === "time" ? (
|
<MatchCard match={item} onFavoriteToggle={handleFavoriteToggle} />
|
||||||
<MatchCard match={item} onFavoriteToggle={handleFavoriteToggle} />
|
)}
|
||||||
) : (
|
|
||||||
<MatchCardLeague match={item} onFavoriteToggle={handleFavoriteToggle} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
contentContainerStyle={styles.listContent}
|
contentContainerStyle={styles.listContent}
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={
|
||||||
<View style={styles.center}>
|
<View style={styles.center}>
|
||||||
@@ -574,6 +570,11 @@ export default function HomeScreen() {
|
|||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<MatchesByLeague
|
||||||
|
matches={matches}
|
||||||
|
onFavoriteToggle={handleFavoriteToggle}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Modals - 条件渲染,只在可见时渲染 */}
|
{/* Modals - 条件渲染,只在可见时渲染 */}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { ThemedText } from "@/components/themed-text";
|
import { ThemedText } from "@/components/themed-text";
|
||||||
import { IconSymbol } from "@/components/ui/icon-symbol";
|
import { IconSymbol } from "@/components/ui/icon-symbol";
|
||||||
import { Colors } from "@/constants/theme";
|
|
||||||
import { useTheme } from "@/context/ThemeContext";
|
import { useTheme } from "@/context/ThemeContext";
|
||||||
import { addFavorite, removeFavorite } from "@/lib/api";
|
import { addFavorite, removeFavorite } from "@/lib/api";
|
||||||
import { Match } from "@/types/api";
|
import { Match } from "@/types/api";
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Pressable, StyleSheet, TouchableOpacity, View } from "react-native";
|
import { Image, Pressable, StyleSheet, TouchableOpacity, View } from "react-native";
|
||||||
|
|
||||||
interface MatchCardLeagueProps {
|
interface MatchCardLeagueProps {
|
||||||
match: Match;
|
match: Match;
|
||||||
@@ -24,15 +23,17 @@ export function MatchCardLeague({
|
|||||||
const [isFav, setIsFav] = useState(match.fav);
|
const [isFav, setIsFav] = useState(match.fav);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
// 当外部传入的 match.fav 改变时,更新内部状态
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setIsFav(match.fav);
|
setIsFav(match.fav);
|
||||||
}, [match.fav]);
|
}, [match.fav]);
|
||||||
|
|
||||||
const isDark = theme === "dark";
|
const isDark = theme === "dark";
|
||||||
const iconColor = isDark ? Colors.dark.icon : Colors.light.icon;
|
// 截图中的卡片背景通常非常深,接近纯黑
|
||||||
const cardBg = isDark ? "#1C1C1E" : "#FFFFFF";
|
const cardBg = isDark ? "#1C1C1E" : "#FFFFFF";
|
||||||
const borderColor = isDark ? "#38383A" : "#E5E5EA";
|
const textColor = isDark ? "#FFFFFF" : "#000000";
|
||||||
|
// 赢家的高亮颜色 (截图中的橙黄色)
|
||||||
|
const winnerColor = "#FF9500";
|
||||||
|
const loserColor = isDark ? "#FFFFFF" : "#000000";
|
||||||
|
|
||||||
const handlePress = () => {
|
const handlePress = () => {
|
||||||
if (onPress) {
|
if (onPress) {
|
||||||
@@ -71,109 +72,210 @@ export function MatchCardLeague({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- 数据解析与样式逻辑 ---
|
||||||
|
|
||||||
|
// 假设 match 对象中有 homeScore 和 awayScore (数字或字符串)
|
||||||
|
// 如果 API 只有 "1 - 0" 这种 scoreText,你需要在这里拆分
|
||||||
|
// 这里为了演示,假设 match 对象已经扩展了这些字段,或者我们从 scoreText 简单解析
|
||||||
|
let homeScore = 0;
|
||||||
|
let awayScore = 0;
|
||||||
|
|
||||||
|
// 简单的解析逻辑 demo (根据你的实际数据结构调整)
|
||||||
|
if (match.scoreText && match.scoreText.includes("-")) {
|
||||||
|
const parts = match.scoreText.split("-");
|
||||||
|
homeScore = parseInt(parts[0].trim()) || 0;
|
||||||
|
awayScore = parseInt(parts[1].trim()) || 0;
|
||||||
|
}
|
||||||
|
// 如果 match 对象里直接有 match.homeScore 最好:
|
||||||
|
// homeScore = match.homeScore;
|
||||||
|
// awayScore = match.awayScore;
|
||||||
|
|
||||||
|
// 判断文字颜色和背景样式
|
||||||
|
let homeColor = loserColor;
|
||||||
|
let awayColor = loserColor;
|
||||||
|
let homeScoreBg = "#2C2C2E"; // 默认深灰背景
|
||||||
|
let awayScoreBg = "#2C2C2E";
|
||||||
|
let homeBorderColor = "transparent";
|
||||||
|
let awayBorderColor = "transparent";
|
||||||
|
|
||||||
|
if (homeScore > awayScore) {
|
||||||
|
homeColor = "#000000"; // 赢家黑色文字
|
||||||
|
homeScoreBg = winnerColor; // 金色背景
|
||||||
|
homeBorderColor = winnerColor;
|
||||||
|
} else if (awayScore > homeScore) {
|
||||||
|
awayColor = "#000000"; // 赢家黑色文字
|
||||||
|
awayScoreBg = winnerColor; // 金色背景
|
||||||
|
awayBorderColor = winnerColor;
|
||||||
|
}
|
||||||
|
// 如果相等,保持默认深灰背景
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={handlePress}
|
onPress={handlePress}
|
||||||
style={({ pressed }) => [
|
style={({ pressed }) => [
|
||||||
styles.card,
|
styles.card,
|
||||||
{ backgroundColor: cardBg, borderColor, opacity: pressed ? 0.7 : 1 },
|
{ backgroundColor: cardBg, opacity: pressed ? 0.8 : 1 },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<View style={styles.header}>
|
{/* 1. 左侧:时间/状态 */}
|
||||||
<View
|
<View style={styles.leftColumn}>
|
||||||
style={[
|
{/* 如果有状态字段 match.meta (如 'FT', 'AET'), 优先显示,否则显示时间 */}
|
||||||
styles.leagueBadge,
|
<ThemedText style={styles.statusText}>
|
||||||
{ backgroundColor: isDark ? "#2C2C2E" : "#F2F2F7" },
|
{(match.meta || match.time || "").toUpperCase()}
|
||||||
]}
|
|
||||||
>
|
|
||||||
<ThemedText style={styles.leagueText} numberOfLines={1}>
|
|
||||||
{match.league}
|
|
||||||
</ThemedText>
|
|
||||||
</View>
|
|
||||||
<ThemedText style={styles.timeText}>{match.time}</ThemedText>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.teamsContainer}>
|
|
||||||
<ThemedText type="defaultSemiBold" style={styles.teamsText}>
|
|
||||||
{match.home} vs2 {match.away}
|
|
||||||
</ThemedText>
|
</ThemedText>
|
||||||
<View style={styles.scoreContainer}>
|
</View>
|
||||||
<ThemedText type="defaultSemiBold" style={styles.scoreText}>
|
|
||||||
{match.scoreText}
|
{/* 2. 中间:球队信息 (上下排布) */}
|
||||||
|
<View style={styles.teamsColumn}>
|
||||||
|
{/* 主队行 */}
|
||||||
|
<View style={styles.teamRow}>
|
||||||
|
<Image
|
||||||
|
source={{ uri: (match as any).homeLogo || match.homeTeamLogo || "https://placehold.co/24x24/png" }}
|
||||||
|
style={styles.teamLogo}
|
||||||
|
/>
|
||||||
|
<ThemedText style={[styles.teamName, { color: textColor }]} numberOfLines={1}>
|
||||||
|
{match.home || match.homeTeamName}
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 客队行 */}
|
||||||
|
<View style={[styles.teamRow, { marginTop: 6 }]}>
|
||||||
|
<Image
|
||||||
|
source={{ uri: (match as any).awayLogo || match.awayTeamLogo || "https://placehold.co/24x24/png" }}
|
||||||
|
style={styles.teamLogo}
|
||||||
|
/>
|
||||||
|
<ThemedText style={[styles.teamName, { color: textColor }]} numberOfLines={1}>
|
||||||
|
{match.away || match.awayTeamName}
|
||||||
</ThemedText>
|
</ThemedText>
|
||||||
<TouchableOpacity
|
|
||||||
onPress={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
toggleFavorite();
|
|
||||||
}}
|
|
||||||
disabled={loading}
|
|
||||||
hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
|
|
||||||
>
|
|
||||||
<IconSymbol
|
|
||||||
name={isFav ? "star" : "star-outline"}
|
|
||||||
size={24}
|
|
||||||
color={isFav ? "#FFD700" : iconColor}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{match.meta && (
|
{/* 3. 右侧:比分与铃铛 */}
|
||||||
<ThemedText style={styles.metaText}>{match.meta}</ThemedText>
|
<View style={styles.rightWrapper}>
|
||||||
)}
|
{/* 比分列 (上下排布) */}
|
||||||
|
<View style={styles.scoresColumn}>
|
||||||
|
<View style={[
|
||||||
|
styles.scoreBox,
|
||||||
|
{
|
||||||
|
backgroundColor: homeScoreBg,
|
||||||
|
borderColor: homeBorderColor,
|
||||||
|
borderWidth: homeBorderColor !== "transparent" ? 1.5 : 0,
|
||||||
|
}
|
||||||
|
]}>
|
||||||
|
<ThemedText style={[styles.scoreText, { color: homeColor }]}>
|
||||||
|
{homeScore}
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
<View style={[
|
||||||
|
styles.scoreBox,
|
||||||
|
{
|
||||||
|
backgroundColor: awayScoreBg,
|
||||||
|
borderColor: awayBorderColor,
|
||||||
|
borderWidth: awayBorderColor !== "transparent" ? 1.5 : 0,
|
||||||
|
marginTop: 6,
|
||||||
|
}
|
||||||
|
]}>
|
||||||
|
<ThemedText style={[styles.scoreText, { color: awayColor }]}>
|
||||||
|
{awayScore}
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 收藏按钮 */}
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
toggleFavorite();
|
||||||
|
}}
|
||||||
|
disabled={loading}
|
||||||
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||||
|
style={styles.favoriteButton}
|
||||||
|
>
|
||||||
|
<IconSymbol
|
||||||
|
name={isFav ? "star" : "star-outline"}
|
||||||
|
size={20}
|
||||||
|
color={isFav ? "#FFD700" : "#545458"}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
card: {
|
card: {
|
||||||
padding: 12,
|
flexDirection: "row",
|
||||||
marginBottom: 12,
|
paddingVertical: 14,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
marginHorizontal: 0,
|
||||||
|
marginBottom: 0,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
borderWidth: 1,
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
marginBottom: 8,
|
minHeight: 68,
|
||||||
},
|
},
|
||||||
leagueBadge: {
|
// 左侧时间列
|
||||||
paddingHorizontal: 8,
|
leftColumn: {
|
||||||
paddingVertical: 4,
|
width: 52,
|
||||||
borderRadius: 4,
|
justifyContent: "center",
|
||||||
marginRight: 8,
|
alignItems: "flex-start",
|
||||||
maxWidth: "70%",
|
marginRight: 12,
|
||||||
},
|
},
|
||||||
leagueText: {
|
statusText: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
opacity: 0.7,
|
color: "#8E8E93", // 次要文本颜色 (Grey)
|
||||||
|
fontWeight: "600",
|
||||||
},
|
},
|
||||||
timeText: {
|
// 中间球队列
|
||||||
fontSize: 12,
|
teamsColumn: {
|
||||||
opacity: 0.5,
|
|
||||||
},
|
|
||||||
teamsContainer: {
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
marginBottom: 4,
|
|
||||||
},
|
|
||||||
teamsText: {
|
|
||||||
fontSize: 16,
|
|
||||||
flex: 1,
|
flex: 1,
|
||||||
marginRight: 8,
|
justifyContent: "center",
|
||||||
|
paddingRight: 8,
|
||||||
},
|
},
|
||||||
scoreContainer: {
|
teamRow: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: 12,
|
},
|
||||||
|
teamLogo: {
|
||||||
|
width: 22,
|
||||||
|
height: 22,
|
||||||
|
borderRadius: 11, // 圆形图标
|
||||||
|
marginRight: 10,
|
||||||
|
backgroundColor: "#3A3A3C", // 图片加载占位
|
||||||
|
},
|
||||||
|
teamName: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: "500",
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
// 右侧整体包装
|
||||||
|
rightWrapper: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
// 比分列
|
||||||
|
scoresColumn: {
|
||||||
|
alignItems: "flex-end", // 数字右对齐
|
||||||
|
justifyContent: "center",
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
scoreBox: {
|
||||||
|
minWidth: 32,
|
||||||
|
height: 28,
|
||||||
|
borderRadius: 6,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
paddingHorizontal: 8,
|
||||||
},
|
},
|
||||||
scoreText: {
|
scoreText: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
fontWeight: "700",
|
||||||
|
lineHeight: 20,
|
||||||
},
|
},
|
||||||
metaText: {
|
// 收藏按钮
|
||||||
fontSize: 12,
|
favoriteButton: {
|
||||||
opacity: 0.5,
|
paddingHorizontal: 4,
|
||||||
marginTop: 4,
|
paddingVertical: 4,
|
||||||
},
|
justifyContent: 'center',
|
||||||
});
|
alignItems: 'center',
|
||||||
|
}
|
||||||
|
});
|
||||||
268
components/matches-by-league.tsx
Normal file
268
components/matches-by-league.tsx
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
import { MatchCardLeague } from "@/components/match-card-league";
|
||||||
|
import { ThemedText } from "@/components/themed-text";
|
||||||
|
import { useTheme } from "@/context/ThemeContext";
|
||||||
|
import { Match } from "@/types/api";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
Image,
|
||||||
|
LayoutAnimation,
|
||||||
|
Platform,
|
||||||
|
ScrollView,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
UIManager,
|
||||||
|
View,
|
||||||
|
} from "react-native";
|
||||||
|
|
||||||
|
// 开启 Android 上的 LayoutAnimation
|
||||||
|
if (
|
||||||
|
Platform.OS === "android" &&
|
||||||
|
UIManager.setLayoutAnimationEnabledExperimental
|
||||||
|
) {
|
||||||
|
UIManager.setLayoutAnimationEnabledExperimental(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MatchesByLeagueProps {
|
||||||
|
matches: Match[];
|
||||||
|
onFavoriteToggle?: (matchId: string, isFav: boolean) => void;
|
||||||
|
/**
|
||||||
|
* 是否支持折叠收起
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
enableCollapsible?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function MatchesByLeague({
|
||||||
|
matches,
|
||||||
|
onFavoriteToggle,
|
||||||
|
enableCollapsible = true,
|
||||||
|
}: MatchesByLeagueProps) {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const isDark = theme === "dark";
|
||||||
|
|
||||||
|
// 数据分组逻辑
|
||||||
|
const matchesByLeague = React.useMemo(() => {
|
||||||
|
const grouped: Record<string, Match[]> = {};
|
||||||
|
matches.forEach((match) => {
|
||||||
|
const league = match.league || match.leagueName || "其他";
|
||||||
|
if (!grouped[league]) {
|
||||||
|
grouped[league] = [];
|
||||||
|
}
|
||||||
|
grouped[league].push(match);
|
||||||
|
});
|
||||||
|
return grouped;
|
||||||
|
}, [matches]);
|
||||||
|
|
||||||
|
const leagueNames = Object.keys(matchesByLeague);
|
||||||
|
|
||||||
|
// 状态:记录哪些联赛被折叠了 (Key 为联赛名称),默认全部收起
|
||||||
|
const [collapsedSections, setCollapsedSections] = useState<
|
||||||
|
Record<string, boolean>
|
||||||
|
>({});
|
||||||
|
|
||||||
|
// 当联赛列表变化时,确保所有联赛默认都是收起状态
|
||||||
|
React.useEffect(() => {
|
||||||
|
setCollapsedSections((prev) => {
|
||||||
|
const updated: Record<string, boolean> = {};
|
||||||
|
leagueNames.forEach((name) => {
|
||||||
|
updated[name] = prev[name] !== undefined ? prev[name] : true;
|
||||||
|
});
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
}, [leagueNames.join(",")]);
|
||||||
|
|
||||||
|
// 切换折叠状态
|
||||||
|
const toggleCollapse = (leagueName: string) => {
|
||||||
|
if (!enableCollapsible) return;
|
||||||
|
|
||||||
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||||
|
setCollapsedSections((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[leagueName]: !prev[leagueName],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (leagueNames.length === 0) {
|
||||||
|
return (
|
||||||
|
<View style={styles.emptyContainer}>
|
||||||
|
<ThemedText>暂无比赛</ThemedText>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView
|
||||||
|
style={[
|
||||||
|
styles.container,
|
||||||
|
{ backgroundColor: isDark ? "#000000" : "#F2F2F7" }
|
||||||
|
]}
|
||||||
|
contentContainerStyle={{ paddingBottom: 40 }}
|
||||||
|
>
|
||||||
|
{leagueNames.map((leagueName) => {
|
||||||
|
const leagueMatches = matchesByLeague[leagueName];
|
||||||
|
// 取该组第一场比赛的数据作为头部信息的来源(图标、国家等)
|
||||||
|
const firstMatch = leagueMatches[0];
|
||||||
|
const isCollapsed = collapsedSections[leagueName];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View key={leagueName} style={styles.leagueSection}>
|
||||||
|
{/* 联赛头部 */}
|
||||||
|
<TouchableOpacity
|
||||||
|
activeOpacity={enableCollapsible ? 0.7 : 1}
|
||||||
|
onPress={() => toggleCollapse(leagueName)}
|
||||||
|
style={styles.leagueHeaderWrapper}
|
||||||
|
>
|
||||||
|
<View style={styles.leagueHeaderLeft}>
|
||||||
|
{/* 联赛 Logo */}
|
||||||
|
<Image
|
||||||
|
source={{
|
||||||
|
uri: firstMatch.leagueLogo || (firstMatch as any).leagueLogoUrl || "https://placehold.co/40x40/png",
|
||||||
|
}}
|
||||||
|
style={styles.leagueLogo}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={styles.leagueInfoText}>
|
||||||
|
{/* 联赛名称 */}
|
||||||
|
<ThemedText style={styles.leagueTitle}>{leagueName}</ThemedText>
|
||||||
|
|
||||||
|
{/* 国家信息行 */}
|
||||||
|
<View style={styles.countryRow}>
|
||||||
|
<Image
|
||||||
|
source={{ uri: firstMatch.countryLogo || (firstMatch as any).countryFlagUrl || "https://placehold.co/20x20/png" }}
|
||||||
|
style={styles.countryFlag}
|
||||||
|
/>
|
||||||
|
<ThemedText style={styles.countryName}>
|
||||||
|
{firstMatch.countryName || (firstMatch as any).countryName || "International"}
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.leagueHeaderRight}>
|
||||||
|
{/* 比赛数量 */}
|
||||||
|
<ThemedText style={styles.matchCount}>
|
||||||
|
{leagueMatches.length}
|
||||||
|
</ThemedText>
|
||||||
|
|
||||||
|
{/* 折叠箭头 (仅当支持折叠时显示) */}
|
||||||
|
{enableCollapsible && (
|
||||||
|
<ThemedText style={styles.chevron}>
|
||||||
|
{isCollapsed ? "⌄" : "⌃"}
|
||||||
|
</ThemedText>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* 比赛列表内容 (根据状态显示/隐藏) */}
|
||||||
|
{!isCollapsed && (
|
||||||
|
<View style={styles.matchListContainer}>
|
||||||
|
{leagueMatches.map((match, index) => (
|
||||||
|
<View
|
||||||
|
key={match.id}
|
||||||
|
style={[
|
||||||
|
styles.matchCardWrapper,
|
||||||
|
index < leagueMatches.length - 1 && styles.matchCardDivider
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<MatchCardLeague
|
||||||
|
match={match}
|
||||||
|
onFavoriteToggle={onFavoriteToggle}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
leagueSection: {
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
leagueHeaderWrapper: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
paddingVertical: 12,
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
leagueHeaderLeft: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
leagueLogo: {
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
borderRadius: 6,
|
||||||
|
marginRight: 12,
|
||||||
|
backgroundColor: "#3A3A3C",
|
||||||
|
},
|
||||||
|
leagueInfoText: {
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
leagueTitle: {
|
||||||
|
fontSize: 17,
|
||||||
|
fontWeight: "600",
|
||||||
|
color: "#FFFFFF",
|
||||||
|
marginBottom: 3,
|
||||||
|
},
|
||||||
|
countryRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
countryFlag: {
|
||||||
|
width: 16,
|
||||||
|
height: 12,
|
||||||
|
marginRight: 5,
|
||||||
|
borderRadius: 2,
|
||||||
|
},
|
||||||
|
countryName: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: "#8E8E93",
|
||||||
|
fontWeight: "500",
|
||||||
|
},
|
||||||
|
leagueHeaderRight: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
matchCount: {
|
||||||
|
fontSize: 15,
|
||||||
|
color: "#8E8E93",
|
||||||
|
fontWeight: "600",
|
||||||
|
},
|
||||||
|
chevron: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: "#8E8E93",
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
matchListContainer: {
|
||||||
|
backgroundColor: "#1C1C1E",
|
||||||
|
borderRadius: 12,
|
||||||
|
marginHorizontal: 16,
|
||||||
|
overflow: "hidden",
|
||||||
|
},
|
||||||
|
matchCardWrapper: {
|
||||||
|
// 卡片包装器
|
||||||
|
},
|
||||||
|
matchCardDivider: {
|
||||||
|
borderBottomWidth: 0.5,
|
||||||
|
borderBottomColor: "#3A3A3C",
|
||||||
|
},
|
||||||
|
emptyContainer: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
paddingTop: 100,
|
||||||
|
},
|
||||||
|
});
|
||||||
26
types/api.ts
26
types/api.ts
@@ -12,12 +12,26 @@ export interface Match {
|
|||||||
id: string;
|
id: string;
|
||||||
league: string;
|
league: string;
|
||||||
time: string;
|
time: string;
|
||||||
date: string;
|
date?: string;
|
||||||
home: string;
|
home: string;
|
||||||
away: string;
|
away: string;
|
||||||
scoreText: string;
|
scoreText: string;
|
||||||
fav: boolean;
|
fav: boolean;
|
||||||
// sport: string;
|
sport?: string;
|
||||||
|
homeTeamName?: string;
|
||||||
|
homeTeamKey?: string;
|
||||||
|
homeTeamLogo?: string;
|
||||||
|
awayTeamName?: string;
|
||||||
|
awayTeamKey?: string;
|
||||||
|
awayTeamLogo?: string;
|
||||||
|
leagueName?: string;
|
||||||
|
leagueKey?: string;
|
||||||
|
leagueLogo?: string;
|
||||||
|
countryName?: string;
|
||||||
|
countryKey?: string;
|
||||||
|
countryLogo?: string;
|
||||||
|
events?: string;
|
||||||
|
players?: string;
|
||||||
sportId?: number;
|
sportId?: number;
|
||||||
leagueId?: number;
|
leagueId?: number;
|
||||||
isLive?: boolean;
|
isLive?: boolean;
|
||||||
@@ -73,11 +87,11 @@ export interface LiveScoreMatch {
|
|||||||
substitutes?: {
|
substitutes?: {
|
||||||
time: string;
|
time: string;
|
||||||
home_scorer:
|
home_scorer:
|
||||||
| { in: string; out: string; in_id: number; out_id: number }
|
| { in: string; out: string; in_id: number; out_id: number }
|
||||||
| any[];
|
| any[];
|
||||||
away_scorer:
|
away_scorer:
|
||||||
| { in: string; out: string; in_id: number; out_id: number }
|
| { in: string; out: string; in_id: number; out_id: number }
|
||||||
| any[];
|
| any[];
|
||||||
info: string;
|
info: string;
|
||||||
info_time: string;
|
info_time: string;
|
||||||
score: string;
|
score: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user