282 lines
8.5 KiB
TypeScript
282 lines
8.5 KiB
TypeScript
import { ThemedText } from "@/components/themed-text";
|
||
import { IconSymbol } from "@/components/ui/icon-symbol";
|
||
import { Colors } from "@/constants/theme";
|
||
import { useTheme } from "@/context/ThemeContext";
|
||
import { Image } from "expo-image";
|
||
import React from "react";
|
||
import { useTranslation } from "react-i18next";
|
||
import { Modal, Pressable, ScrollView, StyleSheet, View } from "react-native";
|
||
import { SafeAreaView } from "react-native-safe-area-context";
|
||
|
||
interface SelectionOption {
|
||
id: number | string;
|
||
label: string;
|
||
value: any;
|
||
icon?: string; // 图标名称
|
||
}
|
||
|
||
// 运动ID到运动键的映射(支持8种运动)
|
||
// 根据API文档:1:足球, 2:篮球, 3:网球, 4:板球
|
||
// 扩展为8种:1:足球, 2:篮球, 3:网球, 4:板球, 5:棒球, 6:羽毛球, 7:斯诺克, 8:排球
|
||
const getSportKeyById = (id: number): string => {
|
||
const sportMap: { [key: number]: string } = {
|
||
1: "football",
|
||
2: "basketball",
|
||
3: "tennis",
|
||
4: "cricket",
|
||
5: "baseball",
|
||
6: "badminton",
|
||
7: "snooker",
|
||
8: "volleyball",
|
||
};
|
||
return sportMap[id] || "football";
|
||
};
|
||
|
||
// 运动名称到图标文件名的映射(支持8种运动)
|
||
const getSportIconName = (label: string, id?: number): string => {
|
||
// 优先使用ID
|
||
if (id !== undefined) {
|
||
return getSportKeyById(id);
|
||
}
|
||
|
||
const labelLower = label.toLowerCase();
|
||
if (labelLower.includes("足球") || labelLower.includes("football") || labelLower.includes("soccer")) {
|
||
return "football";
|
||
}
|
||
if (labelLower.includes("篮球") || labelLower.includes("basketball")) {
|
||
return "basketball";
|
||
}
|
||
if (labelLower.includes("板球") || labelLower.includes("cricket")) {
|
||
return "cricket";
|
||
}
|
||
if (labelLower.includes("网球") || labelLower.includes("tennis")) {
|
||
return "tennis";
|
||
}
|
||
if (labelLower.includes("棒球") || labelLower.includes("baseball")) {
|
||
return "baseball";
|
||
}
|
||
if (labelLower.includes("羽毛球") || labelLower.includes("badminton")) {
|
||
return "badminton";
|
||
}
|
||
if (labelLower.includes("斯诺克") || labelLower.includes("snooker")) {
|
||
return "snooker";
|
||
}
|
||
if (labelLower.includes("排球") || labelLower.includes("volleyball")) {
|
||
return "volleyball";
|
||
}
|
||
|
||
// 默认图标
|
||
return "football";
|
||
};
|
||
|
||
// 获取图标资源
|
||
const getSportIconSource = (label: string, isSelected: boolean, id?: number) => {
|
||
const iconName = getSportIconName(label, id);
|
||
const state = isSelected ? "active" : "inactive";
|
||
|
||
// 使用 require 动态导入图标(8种运动)
|
||
const iconMap: { [key: string]: any } = {
|
||
"football_active": require("@/assets/balls/football_active.svg"),
|
||
"football_inactive": require("@/assets/balls/football_inactive.svg"),
|
||
"basketball_active": require("@/assets/balls/basketball_active.svg"),
|
||
"basketball_inactive": require("@/assets/balls/basketball_inactive.svg"),
|
||
"cricket_active": require("@/assets/balls/cricket_active.svg"),
|
||
"cricket_inactive": require("@/assets/balls/cricket_inactive.svg"),
|
||
"tennis_active": require("@/assets/balls/tennis_active.svg"),
|
||
"tennis_inactive": require("@/assets/balls/tennis_inactive.svg"),
|
||
"baseball_active": require("@/assets/balls/baseball_active.svg"),
|
||
"baseball_inactive": require("@/assets/balls/baseball_inactive.svg"),
|
||
"badminton_active": require("@/assets/balls/badminton_active.svg"),
|
||
"badminton_inactive": require("@/assets/balls/badminton_inactive.svg"),
|
||
"snooker_active": require("@/assets/balls/snooker_active.svg"),
|
||
"snooker_inactive": require("@/assets/balls/snooker_inactive.svg"),
|
||
"volleyball_active": require("@/assets/balls/football_active.svg"), // 使用football作为默认,因为volleyball图标不存在
|
||
"volleyball_inactive": require("@/assets/balls/football_inactive.svg"),
|
||
};
|
||
|
||
return iconMap[`${iconName}_${state}`] || iconMap["football_inactive"];
|
||
};
|
||
|
||
interface SelectionModalProps {
|
||
visible: boolean;
|
||
onClose: () => void;
|
||
title: string;
|
||
options: SelectionOption[];
|
||
onSelect: (value: any) => void;
|
||
selectedValue: any;
|
||
}
|
||
|
||
export function SelectionModal({
|
||
visible,
|
||
onClose,
|
||
title,
|
||
options,
|
||
onSelect,
|
||
selectedValue,
|
||
}: SelectionModalProps) {
|
||
const { theme } = useTheme();
|
||
const { t } = useTranslation();
|
||
const isDark = theme === "dark";
|
||
const bg = isDark ? "#1C1C1E" : "#FFFFFF";
|
||
const text = isDark ? "#FFFFFF" : "#000000";
|
||
|
||
return (
|
||
<Modal
|
||
visible={visible}
|
||
transparent
|
||
animationType="slide"
|
||
onRequestClose={onClose}
|
||
>
|
||
<Pressable style={styles.overlay} onPress={onClose}>
|
||
<View />
|
||
</Pressable>
|
||
|
||
<View style={[styles.sheet, { backgroundColor: bg }]}>
|
||
<SafeAreaView edges={["bottom"]}>
|
||
<View style={styles.header}>
|
||
<ThemedText type="subtitle" style={styles.title}>
|
||
{title}
|
||
</ThemedText>
|
||
<Pressable onPress={onClose} style={styles.closeButton}>
|
||
<IconSymbol name="close" size={24} color={text} />
|
||
</Pressable>
|
||
</View>
|
||
<ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
|
||
{options.map((opt) => {
|
||
const isSelected = selectedValue === opt.value;
|
||
const optionBg = isDark ? "#2C2C2E" : "#F5F5F5";
|
||
const iconBg = isDark ? "#3A3A3C" : "#E5E5EA";
|
||
|
||
// 获取运动键用于i18n和图标
|
||
const sportId = typeof opt.id === "number" ? opt.id : undefined;
|
||
const sportKey = sportId ? getSportKeyById(sportId) : getSportIconName(opt.label, sportId);
|
||
const sportName = t(`sports.${sportKey}`, { defaultValue: opt.label });
|
||
|
||
return (
|
||
<Pressable
|
||
key={opt.id}
|
||
style={[
|
||
styles.option,
|
||
{ backgroundColor: optionBg },
|
||
]}
|
||
onPress={() => {
|
||
onSelect(opt.value);
|
||
onClose();
|
||
}}
|
||
>
|
||
{/* 左侧图标 */}
|
||
<View style={[styles.iconContainer, { backgroundColor: iconBg }]}>
|
||
<Image
|
||
source={getSportIconSource(opt.label, isSelected, sportId)}
|
||
style={styles.iconImage}
|
||
contentFit="contain"
|
||
/>
|
||
</View>
|
||
|
||
{/* 中间内容 */}
|
||
<View style={styles.content}>
|
||
<ThemedText
|
||
style={[
|
||
styles.optionLabel,
|
||
{ fontWeight: isSelected ? "600" : "400" },
|
||
]}
|
||
>
|
||
{sportName}
|
||
</ThemedText>
|
||
<ThemedText style={styles.statusText}>
|
||
{isSelected ? t("selection.selected") : t("selection.click_to_toggle")}
|
||
</ThemedText>
|
||
</View>
|
||
|
||
{/* 右侧指示点 */}
|
||
<View
|
||
style={[
|
||
styles.indicator,
|
||
{
|
||
backgroundColor: isSelected
|
||
? Colors.light.tint
|
||
: isDark
|
||
? "#666666"
|
||
: "#CCCCCC",
|
||
},
|
||
]}
|
||
/>
|
||
</Pressable>
|
||
);
|
||
})}
|
||
</ScrollView>
|
||
</SafeAreaView>
|
||
</View>
|
||
</Modal>
|
||
);
|
||
}
|
||
|
||
const styles = StyleSheet.create({
|
||
overlay: {
|
||
flex: 1,
|
||
backgroundColor: "rgba(0,0,0,0.5)",
|
||
},
|
||
sheet: {
|
||
borderTopLeftRadius: 20,
|
||
borderTopRightRadius: 20,
|
||
padding: 20,
|
||
height: "55%",
|
||
},
|
||
header: {
|
||
flexDirection: "row",
|
||
justifyContent: "space-between",
|
||
alignItems: "center",
|
||
marginBottom: 20,
|
||
},
|
||
title: {
|
||
fontSize: 18,
|
||
fontWeight: "600",
|
||
},
|
||
closeButton: {
|
||
width: 32,
|
||
height: 32,
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
},
|
||
scrollView: {
|
||
maxHeight: 400,
|
||
},
|
||
option: {
|
||
flexDirection: "row",
|
||
alignItems: "center",
|
||
paddingVertical: 16,
|
||
paddingHorizontal: 16,
|
||
marginBottom: 12,
|
||
borderRadius: 12,
|
||
},
|
||
iconContainer: {
|
||
width: 48,
|
||
height: 48,
|
||
borderRadius: 24,
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
marginRight: 12,
|
||
},
|
||
iconImage: {
|
||
width: 32,
|
||
height: 32,
|
||
},
|
||
content: {
|
||
flex: 1,
|
||
},
|
||
optionLabel: {
|
||
fontSize: 16,
|
||
marginBottom: 4,
|
||
},
|
||
statusText: {
|
||
fontSize: 12,
|
||
opacity: 0.6,
|
||
},
|
||
indicator: {
|
||
width: 8,
|
||
height: 8,
|
||
borderRadius: 4,
|
||
marginLeft: 12,
|
||
},
|
||
});
|