249 lines
5.9 KiB
TypeScript
249 lines
5.9 KiB
TypeScript
import { EmptyPlaceholder } from "@/components/empty-placeholder";
|
|
import { ThemedText } from "@/components/themed-text";
|
|
import { IconSymbol } from "@/components/ui/icon-symbol";
|
|
import { Colors } from "@/constants/theme";
|
|
import { useTheme } from "@/context/ThemeContext";
|
|
import { League } from "@/types/api";
|
|
import { Image } from "expo-image";
|
|
import React, { useMemo } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { ActivityIndicator, FlatList, Modal, Pressable, StyleSheet, View } from "react-native";
|
|
import { SafeAreaView } from "react-native-safe-area-context";
|
|
|
|
interface LeagueModalProps {
|
|
visible: boolean;
|
|
onClose: () => void;
|
|
leagues: League[];
|
|
selectedLeagueKey: string | null;
|
|
loading?: boolean;
|
|
onSelect: (leagueKey: string) => void;
|
|
}
|
|
|
|
export function LeagueModal({
|
|
visible,
|
|
onClose,
|
|
leagues,
|
|
selectedLeagueKey,
|
|
loading = false,
|
|
onSelect,
|
|
}: LeagueModalProps) {
|
|
const { theme } = useTheme();
|
|
const { t } = useTranslation();
|
|
const isDark = theme === "dark";
|
|
const bg = isDark ? "#1C1C1E" : "#FFFFFF";
|
|
const text = isDark ? "#FFFFFF" : "#000000";
|
|
const optionBg = isDark ? "#2C2C2E" : "#F5F5F5";
|
|
const iconBg = isDark ? "#3A3A3C" : "#E5E5EA";
|
|
|
|
const renderLeagueItem = ({ item: league }: { item: League }) => {
|
|
const isSelected = selectedLeagueKey === league.key;
|
|
|
|
return (
|
|
<Pressable
|
|
style={[styles.option, { backgroundColor: optionBg }]}
|
|
onPress={() => {
|
|
onSelect(league.key);
|
|
onClose();
|
|
}}
|
|
>
|
|
{/* 左侧图标 */}
|
|
<View style={[styles.iconContainer, { backgroundColor: iconBg }]}>
|
|
{league.logo && league.logo.trim() !== "" && !league.logo.includes("placehold") ? (
|
|
<Image
|
|
source={{ uri: league.logo }}
|
|
style={styles.leagueLogo}
|
|
contentFit="contain"
|
|
/>
|
|
) : (
|
|
<EmptyPlaceholder type="league" size={24} />
|
|
)}
|
|
</View>
|
|
|
|
{/* 中间内容 */}
|
|
<View style={styles.content}>
|
|
<ThemedText
|
|
style={[
|
|
styles.leagueName,
|
|
{ fontWeight: isSelected ? "600" : "400" },
|
|
]}
|
|
>
|
|
{league.name}
|
|
</ThemedText>
|
|
{league.countryName && (
|
|
<ThemedText style={styles.countryName}>
|
|
{league.countryName}
|
|
</ThemedText>
|
|
)}
|
|
</View>
|
|
|
|
{/* 右侧指示点 */}
|
|
<View
|
|
style={[
|
|
styles.indicator,
|
|
{
|
|
backgroundColor: isSelected
|
|
? Colors.light.tint
|
|
: isDark
|
|
? "#666666"
|
|
: "#CCCCCC",
|
|
},
|
|
]}
|
|
/>
|
|
</Pressable>
|
|
);
|
|
};
|
|
|
|
const renderContent = () => {
|
|
if (loading) {
|
|
return (
|
|
<View style={styles.loadingContainer}>
|
|
<ActivityIndicator size="large" color={Colors[theme].tint} />
|
|
<ThemedText style={styles.loadingText}>
|
|
{t("home.loading")}
|
|
</ThemedText>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
if (leagues.length === 0) {
|
|
return (
|
|
<View style={styles.emptyContainer}>
|
|
<ThemedText style={styles.emptyText}>
|
|
{t("home.no_leagues")}
|
|
</ThemedText>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<FlatList
|
|
data={leagues}
|
|
renderItem={renderLeagueItem}
|
|
keyExtractor={(item) => item.id.toString()}
|
|
showsVerticalScrollIndicator={false}
|
|
contentContainerStyle={styles.listContent}
|
|
removeClippedSubviews={true}
|
|
maxToRenderPerBatch={10}
|
|
updateCellsBatchingPeriod={50}
|
|
windowSize={10}
|
|
initialNumToRender={10}
|
|
/>
|
|
);
|
|
};
|
|
|
|
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}>
|
|
{t("home.select_league")}
|
|
</ThemedText>
|
|
<Pressable onPress={onClose} style={styles.closeButton}>
|
|
<IconSymbol name="close" size={24} color={text} />
|
|
</Pressable>
|
|
</View>
|
|
{renderContent()}
|
|
</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",
|
|
},
|
|
listContent: {
|
|
paddingBottom: 20,
|
|
},
|
|
loadingContainer: {
|
|
padding: 40,
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
},
|
|
loadingText: {
|
|
marginTop: 12,
|
|
fontSize: 14,
|
|
opacity: 0.6,
|
|
},
|
|
emptyContainer: {
|
|
padding: 40,
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
},
|
|
emptyText: {
|
|
fontSize: 16,
|
|
opacity: 0.6,
|
|
},
|
|
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,
|
|
},
|
|
leagueLogo: {
|
|
width: 32,
|
|
height: 32,
|
|
},
|
|
content: {
|
|
flex: 1,
|
|
},
|
|
leagueName: {
|
|
fontSize: 16,
|
|
marginBottom: 4,
|
|
},
|
|
countryName: {
|
|
fontSize: 12,
|
|
opacity: 0.6,
|
|
},
|
|
indicator: {
|
|
width: 8,
|
|
height: 8,
|
|
borderRadius: 4,
|
|
marginLeft: 12,
|
|
},
|
|
});
|