添加图标和球类选择

This commit is contained in:
xianyi
2026-01-13 10:53:30 +08:00
parent bff2763013
commit 5bbbf1a132
25 changed files with 734 additions and 41 deletions

View File

@@ -2,7 +2,9 @@ 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";
@@ -13,42 +15,86 @@ interface SelectionOption {
icon?: string; // 图标名称
}
// 运动名称到图标的映射
const getSportIcon = (label: string, icon?: string): string => {
// 如果提供了 icon 字段,尝试直接使用
if (icon) {
// 如果 icon 已经是 Ionicons 格式,直接返回
if (icon.includes("-")) {
return icon;
}
}
// 运动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-outline";
return "football";
}
if (labelLower.includes("篮球") || labelLower.includes("basketball")) {
return "basketball-outline";
return "basketball";
}
if (labelLower.includes("板球") || labelLower.includes("cricket")) {
return "baseball-outline";
return "cricket";
}
if (labelLower.includes("网球") || labelLower.includes("tennis")) {
return "tennisball-outline";
return "tennis";
}
if (labelLower.includes("棒球") || labelLower.includes("baseball")) {
return "baseball-outline";
return "baseball";
}
if (labelLower.includes("羽毛球") || labelLower.includes("badminton")) {
return "ellipse-outline";
return "badminton";
}
if (labelLower.includes("斯诺克") || labelLower.includes("snooker")) {
return "radio-button-on-outline";
return "snooker";
}
if (labelLower.includes("排球") || labelLower.includes("volleyball")) {
return "volleyball";
}
// 默认图标
return "ellipse-outline";
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 {
@@ -69,6 +115,7 @@ export function SelectionModal({
selectedValue,
}: SelectionModalProps) {
const { theme } = useTheme();
const { t } = useTranslation();
const isDark = theme === "dark";
const bg = isDark ? "#1C1C1E" : "#FFFFFF";
const text = isDark ? "#FFFFFF" : "#000000";
@@ -99,6 +146,11 @@ export function SelectionModal({
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
@@ -114,10 +166,10 @@ export function SelectionModal({
>
{/* 左侧图标 */}
<View style={[styles.iconContainer, { backgroundColor: iconBg }]}>
<IconSymbol
name={getSportIcon(opt.label, opt.icon) as any}
size={24}
color={isSelected ? Colors.light.tint : text}
<Image
source={getSportIconSource(opt.label, isSelected, sportId)}
style={styles.iconImage}
contentFit="contain"
/>
</View>
@@ -129,10 +181,10 @@ export function SelectionModal({
{ fontWeight: isSelected ? "600" : "400" },
]}
>
{opt.label}
{sportName}
</ThemedText>
<ThemedText style={styles.statusText}>
{isSelected ? "已选择" : "点击切换"}
{isSelected ? t("selection.selected") : t("selection.click_to_toggle")}
</ThemedText>
</View>
@@ -168,7 +220,7 @@ const styles = StyleSheet.create({
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
padding: 20,
maxHeight: "70%",
height: "55%",
},
header: {
flexDirection: "row",
@@ -205,11 +257,9 @@ const styles = StyleSheet.create({
justifyContent: "center",
marginRight: 12,
},
iconPlaceholder: {
width: 24,
height: 24,
borderRadius: 12,
backgroundColor: "#999",
iconImage: {
width: 32,
height: 32,
},
content: {
flex: 1,