import { ThemedText } from "@/components/themed-text"; import { ThemedView } from "@/components/themed-view"; import { IconSymbol } from "@/components/ui/icon-symbol"; import { useTheme } from "@/context/ThemeContext"; import { BlurView } from "expo-blur"; import { LinearGradient } from "expo-linear-gradient"; import { Stack, useRouter } from "expo-router"; import React, { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { Animated, FlatList, ScrollView, StyleSheet, TextInput, TouchableOpacity, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; type SearchType = "team" | "player" | "league"; interface SearchEntity { id: string; type: SearchType; name: string; meta: { country?: string; league?: string; season?: string; team?: string; pos?: string; }; } // 模拟数据 const ENTITIES: SearchEntity[] = [ { id: "t_rm", type: "team", name: "Real Madrid", meta: { country: "Spain", league: "LaLiga", season: "2025/26" } }, { id: "t_fcb", type: "team", name: "FC Barcelona", meta: { country: "Spain", league: "LaLiga", season: "2025/26" } }, { id: "t_mci", type: "team", name: "Manchester City", meta: { country: "England", league: "Premier League", season: "2025/26" } }, { id: "t_liv", type: "team", name: "Liverpool", meta: { country: "England", league: "Premier League", season: "2025/26" } }, { id: "t_ars", type: "team", name: "Arsenal", meta: { country: "England", league: "Premier League", season: "2025/26" } }, { id: "p_mbappe", type: "player", name: "Kylian Mbappé", meta: { country: "France", team: "Real Madrid", league: "LaLiga", pos: "FW", season: "2025/26" } }, { id: "p_haaland", type: "player", name: "Erling Haaland", meta: { country: "Norway", team: "Manchester City", league: "Premier League", pos: "FW", season: "2025/26" } }, { id: "p_salah", type: "player", name: "Mohamed Salah", meta: { country: "Egypt", team: "Liverpool", league: "Premier League", pos: "FW", season: "2025/26" } }, { id: "p_curry", type: "player", name: "Stephen Curry", meta: { country: "USA", team: "Golden State Warriors", league: "NBA", pos: "G", season: "2025/26" } }, { id: "l_epl", type: "league", name: "Premier League", meta: { country: "England", season: "2025/26" } }, { id: "l_laliga", type: "league", name: "LaLiga", meta: { country: "Spain", season: "2025/26" } }, { id: "l_ucl", type: "league", name: "UEFA Champions League", meta: { country: "Europe", season: "2025/26" } }, { id: "l_nba", type: "league", name: "NBA", meta: { country: "USA", season: "2025/26" } }, ]; // 生成首字母 function getInitials(name: string): string { const s = name.trim(); if (!s) return "SN"; const clean = s.replace(/[^a-zA-Z0-9]+/g, " ").trim(); const parts = clean ? clean.split(/\s+/) : [s]; const a = (parts[0] || s).charAt(0) || "S"; const b = (parts[1] || "").charAt(0) || ((parts[0] || s).charAt(1) || "N"); return (a + b).toUpperCase(); } // 生成颜色哈希 function hashString(str: string): number { let hash = 2166136261; for (let i = 0; i < str.length; i++) { hash ^= str.charCodeAt(i); hash = (hash * 16777619) >>> 0; } return hash >>> 0; } // HSL 转 RGB function hslToRgb(h: number, s: number, l: number): string { s /= 100; l /= 100; const c = (1 - Math.abs(2 * l - 1)) * s; const x = c * (1 - Math.abs(((h / 60) % 2) - 1)); const m = l - c / 2; let r = 0, g = 0, b = 0; if (0 <= h && h < 60) { r = c; g = x; b = 0; } else if (60 <= h && h < 120) { r = x; g = c; b = 0; } else if (120 <= h && h < 180) { r = 0; g = c; b = x; } else if (180 <= h && h < 240) { r = 0; g = x; b = c; } else if (240 <= h && h < 300) { r = x; g = 0; b = c; } else if (300 <= h && h < 360) { r = c; g = 0; b = x; } r = Math.round((r + m) * 255); g = Math.round((g + m) * 255); b = Math.round((b + m) * 255); return `rgb(${r}, ${g}, ${b})`; } // 生成渐变颜色 function getLogoGradient(name: string): { color1: string; color2: string } { const h = hashString(name); const hue = h % 360; const hue2 = (hue + 36 + (h % 24)) % 360; return { color1: hslToRgb(hue, 85, 58), color2: hslToRgb(hue2, 85, 48), }; } // 获取副标题文本 function getSubText(entity: SearchEntity): string { const { meta } = entity; if (entity.type === "league") { return `${meta.country || ""} · ${meta.season || ""}`; } if (entity.type === "player") { return `${meta.team || ""} · ${meta.league || ""}`; } return `${meta.country || ""} · ${meta.league || ""}`; } export default function SearchScreen() { const router = useRouter(); const { theme } = useTheme(); const { t } = useTranslation(); const insets = useSafeAreaInsets(); const isDark = theme === "dark"; const [searchType, setSearchType] = useState("team"); const [query, setQuery] = useState(""); const [recentSearches, setRecentSearches] = useState([ "Real Madrid", "Premier League", "Curry", ]); const [showDetailSheet, setShowDetailSheet] = useState(false); const [selectedEntity, setSelectedEntity] = useState(null); const sheetAnim = useRef(new Animated.Value(0)).current; useEffect(() => { if (showDetailSheet) { Animated.spring(sheetAnim, { toValue: 1, useNativeDriver: true, }).start(); } else { Animated.timing(sheetAnim, { toValue: 0, duration: 200, useNativeDriver: true, }).start(); } }, [showDetailSheet]); const filteredEntities = ENTITIES.filter((e) => { if (e.type !== searchType) return false; if (!query.trim()) return true; const q = query.toLowerCase(); return ( e.name.toLowerCase().includes(q) || getSubText(e).toLowerCase().includes(q) ); }); const handleSearchTypeChange = (type: SearchType) => { setSearchType(type); setQuery(""); }; const handleRecentClick = (text: string) => { setQuery(text); }; const handleEntityPress = (entity: SearchEntity) => { setSelectedEntity(entity); setShowDetailSheet(true); }; const handleClearRecent = () => { setRecentSearches([]); }; const handleSaveToRecent = () => { if (selectedEntity) { const newRecent = [ selectedEntity.name, ...recentSearches.filter((x) => x !== selectedEntity.name), ].slice(0, 10); setRecentSearches(newRecent); } }; const handleOpenDetail = () => { if (selectedEntity) { handleSaveToRecent(); // TODO: Navigate to detail page setShowDetailSheet(false); } }; const renderSearchBar = () => ( {query.length > 0 && ( setQuery("")}> )} ); const renderChips = () => ( {(["team", "player", "league"] as SearchType[]).map((type) => ( handleSearchTypeChange(type)} > {t(`search.type.${type}`)} ))} ); const renderRecentSearches = () => { if (recentSearches.length === 0 || query.trim()) return null; return ( <> {t("search.recent")} {t("search.clear")} {recentSearches.map((item, index) => ( handleRecentClick(item)} > {item} {t(`search.type.${searchType}`)} ))} ); }; const renderEntityItem = ({ item }: { item: SearchEntity }) => { const gradient = getLogoGradient(item.name); const initials = getInitials(item.name); return ( handleEntityPress(item)} > {initials} {item.name} {getSubText(item)} {t(`search.type.${item.type}`)} ); }; const renderEmptyState = () => ( {t("search.no_results")} {t("search.no_results_hint")} ); const renderDetailSheet = () => { if (!selectedEntity) return null; const translateY = sheetAnim.interpolate({ inputRange: [0, 1], outputRange: [600, 0], }); return ( <> setShowDetailSheet(false)} > {selectedEntity.name} {t(`search.type.${selectedEntity.type}`)} ·{" "} {getSubText(selectedEntity)} setShowDetailSheet(false)} > {selectedEntity.type === "team" && ( <> {t("search.detail.country")} {selectedEntity.meta.country || "-"} {t("search.detail.league")} {selectedEntity.meta.league || "-"} {t("search.detail.season")} {selectedEntity.meta.season || "-"} )} {selectedEntity.type === "player" && ( <> {t("search.detail.team")} {selectedEntity.meta.team || "-"} {t("search.detail.league")} {selectedEntity.meta.league || "-"} {t("search.detail.position")} {selectedEntity.meta.pos || "-"} {t("search.detail.country")} {selectedEntity.meta.country || "-"} )} {selectedEntity.type === "league" && ( <> {t("search.detail.country")} {selectedEntity.meta.country || "-"} {t("search.detail.season")} {selectedEntity.meta.season || "-"} )} {t("search.detail.open")} {t("search.detail.save")} ); }; return ( router.back()} > {t("search.title")} {t("search.subtitle")} {renderSearchBar()} {renderChips()} {renderRecentSearches()} {t("search.results")} · {t(`search.type.${searchType}`)} {query.trim() ? `${filteredEntities.length} ${t("search.found")}` : t("search.tap_to_open")} item.id} renderItem={renderEntityItem} ListEmptyComponent={() => renderEmptyState()} contentContainerStyle={[ styles.listContent, { paddingBottom: insets.bottom + 20 }, ]} showsVerticalScrollIndicator={false} /> {showDetailSheet && renderDetailSheet()} ); } const styles = StyleSheet.create({ container: { flex: 1, }, nav: { flexDirection: "row", alignItems: "center", gap: 10, paddingHorizontal: 12, paddingBottom: 12, position: "sticky", top: 0, zIndex: 40, }, iconBtn: { width: 38, height: 38, borderRadius: 14, alignItems: "center", justifyContent: "center", borderWidth: 1, }, brand: { flex: 1, minWidth: 0, gap: 2, }, brandTitle: { fontSize: 18, fontWeight: "900", letterSpacing: 0.2, }, brandSub: { fontSize: 12, opacity: 0.42, fontWeight: "800", }, content: { flex: 1, }, contentContainer: { paddingHorizontal: 12, paddingTop: 8, }, listContent: { paddingHorizontal: 12, }, searchWrap: { marginBottom: 10, }, search: { flexDirection: "row", alignItems: "center", gap: 8, height: 46, paddingHorizontal: 12, borderRadius: 18, borderWidth: 1, overflow: "hidden", }, searchInput: { flex: 1, minWidth: 0, fontSize: 15, }, chips: { marginBottom: 8, }, chipsContent: { gap: 10, paddingBottom: 2, }, chip: { paddingHorizontal: 14, paddingVertical: 8, borderRadius: 999, borderWidth: 1, }, chipActive: {}, chipText: { fontSize: 14, fontWeight: "900", opacity: 0.78, }, chipTextActive: { opacity: 1, }, sectionHeader: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginTop: 12, marginBottom: 8, }, sectionTitle: { fontSize: 13, opacity: 0.55, fontWeight: "900", }, sectionAction: { fontSize: 12, opacity: 0.42, fontWeight: "800", }, recentRow: { marginBottom: 8, }, recentRowContent: { gap: 10, paddingBottom: 2, }, recentCard: { minWidth: 190, padding: 12, borderRadius: 16, borderWidth: 1, }, recentCardTitle: { fontWeight: "900", }, recentCardSub: { fontSize: 12, opacity: 0.55, marginTop: 4, }, entityRow: { flexDirection: "row", alignItems: "center", gap: 12, padding: 12, borderRadius: 18, borderWidth: 1, marginBottom: 10, }, logoDot: { width: 34, height: 34, borderRadius: 14, alignItems: "center", justifyContent: "center", borderWidth: 1, }, logoText: { fontSize: 12, fontWeight: "900", color: "rgba(255, 255, 255, 0.92)", }, entityMeta: { flex: 1, minWidth: 0, }, entityName: { fontWeight: "900", }, entitySub: { fontSize: 12, opacity: 0.55, marginTop: 3, }, entityBadge: { fontSize: 12, opacity: 0.52, fontWeight: "900", }, emptyState: { padding: 14, borderRadius: 20, borderWidth: 1, }, emptyTitle: { fontWeight: "900", }, emptySub: { marginTop: 6, opacity: 0.6, fontSize: 12, fontWeight: "800", lineHeight: 18, }, overlay: { ...StyleSheet.absoluteFillObject, zIndex: 80, }, sheet: { position: "absolute", left: 0, right: 0, bottom: 0, borderTopLeftRadius: 22, borderTopRightRadius: 22, borderWidth: 1, padding: 12, zIndex: 90, shadowColor: "#000", shadowOffset: { width: 0, height: -10 }, shadowOpacity: 0.3, shadowRadius: 30, elevation: 20, }, sheetHeader: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", gap: 10, marginBottom: 12, }, sheetTitleContainer: { flex: 1, minWidth: 0, }, sheetTitle: { fontWeight: "900", }, sheetSub: { fontSize: 12, opacity: 0.52, fontWeight: "800", marginTop: 2, }, kvContainer: { flexDirection: "row", flexWrap: "wrap", gap: 10, marginBottom: 12, }, kvItem: { flex: 1, minWidth: "45%", padding: 12, borderRadius: 18, borderWidth: 1, }, kvLabel: { fontSize: 12, opacity: 0.52, fontWeight: "900", }, kvValue: { marginTop: 6, fontWeight: "900", }, sheetActions: { flexDirection: "row", gap: 10, }, sheetBtn: { flex: 1, height: 36, paddingHorizontal: 12, borderRadius: 14, borderWidth: 1, alignItems: "center", justifyContent: "center", }, sheetBtnPrimary: {}, sheetBtnText: { fontSize: 14, fontWeight: "900", opacity: 0.86, }, sheetBtnTextPrimary: { opacity: 1, }, });