搜索页面
This commit is contained in:
619
app/search.tsx
619
app/search.tsx
@@ -1,13 +1,19 @@
|
||||
import { ThemedText } from "@/components/themed-text";
|
||||
import { ThemedView } from "@/components/themed-view";
|
||||
import { IconSymbol } from "@/components/ui/icon-symbol";
|
||||
import { Colors } from "@/constants/theme";
|
||||
import { useAppState } from "@/context/AppStateContext";
|
||||
import { useTheme } from "@/context/ThemeContext";
|
||||
import { fetchSearch } from "@/lib/api";
|
||||
import { SearchLeague, SearchPlayer, SearchTeam } from "@/types/api";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
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 {
|
||||
ActivityIndicator,
|
||||
Animated,
|
||||
FlatList,
|
||||
ScrollView,
|
||||
@@ -18,7 +24,7 @@ import {
|
||||
} from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
|
||||
type SearchType = "team" | "player" | "league";
|
||||
type SearchType = "all" | "team" | "player" | "league";
|
||||
|
||||
interface SearchEntity {
|
||||
id: string;
|
||||
@@ -33,22 +39,44 @@ interface SearchEntity {
|
||||
};
|
||||
}
|
||||
|
||||
// 模拟数据
|
||||
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" } },
|
||||
];
|
||||
// 将 API 数据转换为 SearchEntity
|
||||
function convertLeagueToEntity(league: SearchLeague): SearchEntity {
|
||||
return {
|
||||
id: `league_${league.ID}`,
|
||||
type: "league",
|
||||
name: league.name,
|
||||
meta: {
|
||||
country: league.countryName,
|
||||
season: "", // API 中没有 season 字段
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function convertPlayerToEntity(player: SearchPlayer): SearchEntity {
|
||||
return {
|
||||
id: `player_${player.ID}`,
|
||||
type: "player",
|
||||
name: player.name,
|
||||
meta: {
|
||||
country: player.countryName,
|
||||
team: player.teamName,
|
||||
league: player.leagueName,
|
||||
pos: player.position,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function convertTeamToEntity(team: SearchTeam): SearchEntity {
|
||||
return {
|
||||
id: `team_${team.ID}`,
|
||||
type: "team",
|
||||
name: team.name,
|
||||
meta: {
|
||||
country: team.countryName,
|
||||
league: team.leagueName,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// 生成首字母
|
||||
function getInitials(name: string): string {
|
||||
@@ -113,33 +141,66 @@ function getLogoGradient(name: string): { color1: string; color2: string } {
|
||||
// 获取副标题文本
|
||||
function getSubText(entity: SearchEntity): string {
|
||||
const { meta } = entity;
|
||||
const parts: string[] = [];
|
||||
|
||||
if (entity.type === "league") {
|
||||
return `${meta.country || ""} · ${meta.season || ""}`;
|
||||
// 联赛:显示国家
|
||||
if (meta.country) parts.push(meta.country);
|
||||
} else if (entity.type === "player") {
|
||||
// 球员:优先显示球队和位置,如果没有位置则显示联赛
|
||||
if (meta.team) parts.push(meta.team);
|
||||
if (meta.pos) {
|
||||
parts.push(meta.pos);
|
||||
} else if (meta.league) {
|
||||
parts.push(meta.league);
|
||||
}
|
||||
} else if (entity.type === "team") {
|
||||
// 球队:显示国家和联赛
|
||||
if (meta.country) parts.push(meta.country);
|
||||
if (meta.league) parts.push(meta.league);
|
||||
}
|
||||
if (entity.type === "player") {
|
||||
return `${meta.team || ""} · ${meta.league || ""}`;
|
||||
}
|
||||
return `${meta.country || ""} · ${meta.league || ""}`;
|
||||
|
||||
return parts.filter(Boolean).join(" · ") || "";
|
||||
}
|
||||
|
||||
const RECENT_SEARCHES_KEY = "recent_searches";
|
||||
|
||||
export default function SearchScreen() {
|
||||
const router = useRouter();
|
||||
const { theme } = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const insets = useSafeAreaInsets();
|
||||
const isDark = theme === "dark";
|
||||
const { state } = useAppState();
|
||||
|
||||
const [searchType, setSearchType] = useState<SearchType>("team");
|
||||
const [searchType, setSearchType] = useState<SearchType>("all");
|
||||
const [query, setQuery] = useState("");
|
||||
const [recentSearches, setRecentSearches] = useState<string[]>([
|
||||
"Real Madrid",
|
||||
"Premier League",
|
||||
"Curry",
|
||||
]);
|
||||
const [recentSearches, setRecentSearches] = useState<string[]>([]);
|
||||
const [showDetailSheet, setShowDetailSheet] = useState(false);
|
||||
const [selectedEntity, setSelectedEntity] = useState<SearchEntity | null>(null);
|
||||
const [entities, setEntities] = useState<SearchEntity[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const sheetAnim = useRef(new Animated.Value(0)).current;
|
||||
const searchTimeoutRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loadRecentSearches = async () => {
|
||||
try {
|
||||
const stored = await AsyncStorage.getItem(RECENT_SEARCHES_KEY);
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored);
|
||||
if (Array.isArray(parsed)) {
|
||||
setRecentSearches(parsed);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to load recent searches:", err);
|
||||
}
|
||||
};
|
||||
loadRecentSearches();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (showDetailSheet) {
|
||||
@@ -156,18 +217,70 @@ export default function SearchScreen() {
|
||||
}
|
||||
}, [showDetailSheet]);
|
||||
|
||||
const filteredEntities = ENTITIES.filter((e) => {
|
||||
// 搜索 API 调用(带防抖)
|
||||
useEffect(() => {
|
||||
if (searchTimeoutRef.current) {
|
||||
clearTimeout(searchTimeoutRef.current);
|
||||
}
|
||||
|
||||
if (!query.trim()) {
|
||||
setEntities([]);
|
||||
setError(null);
|
||||
return;
|
||||
}
|
||||
|
||||
searchTimeoutRef.current = setTimeout(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const result = await fetchSearch(
|
||||
query.trim(),
|
||||
state.selectedSportId || undefined
|
||||
);
|
||||
|
||||
const allEntities: SearchEntity[] = [
|
||||
...result.leagues.map(convertLeagueToEntity),
|
||||
...result.players.map(convertPlayerToEntity),
|
||||
...result.teams.map(convertTeamToEntity),
|
||||
];
|
||||
|
||||
setEntities(allEntities);
|
||||
if (allEntities.length > 0) {
|
||||
const searchQuery = query.trim();
|
||||
setRecentSearches((prev) => {
|
||||
const filtered = prev.filter((x) => x !== searchQuery);
|
||||
const newRecent = [searchQuery, ...filtered].slice(0, 8);
|
||||
AsyncStorage.setItem(RECENT_SEARCHES_KEY, JSON.stringify(newRecent)).catch(
|
||||
(err) => console.error("Failed to save recent searches:", err)
|
||||
);
|
||||
return newRecent;
|
||||
});
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error("Search error:", err);
|
||||
setError(err?.message || t("search.error"));
|
||||
setEntities([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, 500); // 500ms 防抖
|
||||
|
||||
return () => {
|
||||
if (searchTimeoutRef.current) {
|
||||
clearTimeout(searchTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, [query, state.selectedSportId]);
|
||||
|
||||
const filteredEntities = entities.filter((e) => {
|
||||
if (searchType === "all") return true;
|
||||
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)
|
||||
);
|
||||
return true;
|
||||
});
|
||||
|
||||
const handleSearchTypeChange = (type: SearchType) => {
|
||||
setSearchType(type);
|
||||
setQuery("");
|
||||
// 不再清空输入框,保持搜索结果
|
||||
};
|
||||
|
||||
const handleRecentClick = (text: string) => {
|
||||
@@ -181,6 +294,9 @@ export default function SearchScreen() {
|
||||
|
||||
const handleClearRecent = () => {
|
||||
setRecentSearches([]);
|
||||
AsyncStorage.removeItem(RECENT_SEARCHES_KEY).catch(
|
||||
(err) => console.error("Failed to clear recent searches:", err)
|
||||
);
|
||||
};
|
||||
|
||||
const handleSaveToRecent = () => {
|
||||
@@ -188,8 +304,11 @@ export default function SearchScreen() {
|
||||
const newRecent = [
|
||||
selectedEntity.name,
|
||||
...recentSearches.filter((x) => x !== selectedEntity.name),
|
||||
].slice(0, 10);
|
||||
].slice(0, 8);
|
||||
setRecentSearches(newRecent);
|
||||
AsyncStorage.setItem(RECENT_SEARCHES_KEY, JSON.stringify(newRecent)).catch(
|
||||
(err) => console.error("Failed to save recent searches:", err)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -242,7 +361,7 @@ export default function SearchScreen() {
|
||||
style={styles.chips}
|
||||
contentContainerStyle={styles.chipsContent}
|
||||
>
|
||||
{(["team", "player", "league"] as SearchType[]).map((type) => (
|
||||
{(["all", "team", "player", "league"] as SearchType[]).map((type) => (
|
||||
<TouchableOpacity
|
||||
key={type}
|
||||
style={[
|
||||
@@ -473,194 +592,168 @@ export default function SearchScreen() {
|
||||
<View style={styles.kvContainer}>
|
||||
{selectedEntity.type === "team" && (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
styles.kvItem,
|
||||
{
|
||||
backgroundColor: isDark
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.04)",
|
||||
borderColor: isDark
|
||||
? "rgba(255, 255, 255, 0.10)"
|
||||
: "rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemedText style={styles.kvLabel}>
|
||||
{t("search.detail.country")}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.kvValue} numberOfLines={1}>
|
||||
{selectedEntity.meta.country || "-"}
|
||||
</ThemedText>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
styles.kvItem,
|
||||
{
|
||||
backgroundColor: isDark
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.04)",
|
||||
borderColor: isDark
|
||||
? "rgba(255, 255, 255, 0.10)"
|
||||
: "rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemedText style={styles.kvLabel}>
|
||||
{t("search.detail.league")}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.kvValue} numberOfLines={1}>
|
||||
{selectedEntity.meta.league || "-"}
|
||||
</ThemedText>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
styles.kvItem,
|
||||
{
|
||||
backgroundColor: isDark
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.04)",
|
||||
borderColor: isDark
|
||||
? "rgba(255, 255, 255, 0.10)"
|
||||
: "rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemedText style={styles.kvLabel}>
|
||||
{t("search.detail.season")}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.kvValue} numberOfLines={1}>
|
||||
{selectedEntity.meta.season || "-"}
|
||||
</ThemedText>
|
||||
</View>
|
||||
{selectedEntity.meta.country && (
|
||||
<View
|
||||
style={[
|
||||
styles.kvItem,
|
||||
{
|
||||
backgroundColor: isDark
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.04)",
|
||||
borderColor: isDark
|
||||
? "rgba(255, 255, 255, 0.10)"
|
||||
: "rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemedText style={styles.kvLabel}>
|
||||
{t("search.detail.country")}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.kvValue} numberOfLines={1}>
|
||||
{selectedEntity.meta.country}
|
||||
</ThemedText>
|
||||
</View>
|
||||
)}
|
||||
{selectedEntity.meta.league && (
|
||||
<View
|
||||
style={[
|
||||
styles.kvItem,
|
||||
{
|
||||
backgroundColor: isDark
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.04)",
|
||||
borderColor: isDark
|
||||
? "rgba(255, 255, 255, 0.10)"
|
||||
: "rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemedText style={styles.kvLabel}>
|
||||
{t("search.detail.league")}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.kvValue} numberOfLines={1}>
|
||||
{selectedEntity.meta.league}
|
||||
</ThemedText>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{selectedEntity.type === "player" && (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
styles.kvItem,
|
||||
{
|
||||
backgroundColor: isDark
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.04)",
|
||||
borderColor: isDark
|
||||
? "rgba(255, 255, 255, 0.10)"
|
||||
: "rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemedText style={styles.kvLabel}>
|
||||
{t("search.detail.team")}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.kvValue} numberOfLines={1}>
|
||||
{selectedEntity.meta.team || "-"}
|
||||
</ThemedText>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
styles.kvItem,
|
||||
{
|
||||
backgroundColor: isDark
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.04)",
|
||||
borderColor: isDark
|
||||
? "rgba(255, 255, 255, 0.10)"
|
||||
: "rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemedText style={styles.kvLabel}>
|
||||
{t("search.detail.league")}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.kvValue} numberOfLines={1}>
|
||||
{selectedEntity.meta.league || "-"}
|
||||
</ThemedText>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
styles.kvItem,
|
||||
{
|
||||
backgroundColor: isDark
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.04)",
|
||||
borderColor: isDark
|
||||
? "rgba(255, 255, 255, 0.10)"
|
||||
: "rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemedText style={styles.kvLabel}>
|
||||
{t("search.detail.position")}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.kvValue} numberOfLines={1}>
|
||||
{selectedEntity.meta.pos || "-"}
|
||||
</ThemedText>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
styles.kvItem,
|
||||
{
|
||||
backgroundColor: isDark
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.04)",
|
||||
borderColor: isDark
|
||||
? "rgba(255, 255, 255, 0.10)"
|
||||
: "rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemedText style={styles.kvLabel}>
|
||||
{t("search.detail.country")}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.kvValue} numberOfLines={1}>
|
||||
{selectedEntity.meta.country || "-"}
|
||||
</ThemedText>
|
||||
</View>
|
||||
{selectedEntity.meta.team && (
|
||||
<View
|
||||
style={[
|
||||
styles.kvItem,
|
||||
{
|
||||
backgroundColor: isDark
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.04)",
|
||||
borderColor: isDark
|
||||
? "rgba(255, 255, 255, 0.10)"
|
||||
: "rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemedText style={styles.kvLabel}>
|
||||
{t("search.detail.team")}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.kvValue} numberOfLines={1}>
|
||||
{selectedEntity.meta.team}
|
||||
</ThemedText>
|
||||
</View>
|
||||
)}
|
||||
{selectedEntity.meta.pos && (
|
||||
<View
|
||||
style={[
|
||||
styles.kvItem,
|
||||
{
|
||||
backgroundColor: isDark
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.04)",
|
||||
borderColor: isDark
|
||||
? "rgba(255, 255, 255, 0.10)"
|
||||
: "rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemedText style={styles.kvLabel}>
|
||||
{t("search.detail.position")}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.kvValue} numberOfLines={1}>
|
||||
{selectedEntity.meta.pos}
|
||||
</ThemedText>
|
||||
</View>
|
||||
)}
|
||||
{selectedEntity.meta.league && (
|
||||
<View
|
||||
style={[
|
||||
styles.kvItem,
|
||||
{
|
||||
backgroundColor: isDark
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.04)",
|
||||
borderColor: isDark
|
||||
? "rgba(255, 255, 255, 0.10)"
|
||||
: "rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemedText style={styles.kvLabel}>
|
||||
{t("search.detail.league")}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.kvValue} numberOfLines={1}>
|
||||
{selectedEntity.meta.league}
|
||||
</ThemedText>
|
||||
</View>
|
||||
)}
|
||||
{selectedEntity.meta.country && (
|
||||
<View
|
||||
style={[
|
||||
styles.kvItem,
|
||||
{
|
||||
backgroundColor: isDark
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.04)",
|
||||
borderColor: isDark
|
||||
? "rgba(255, 255, 255, 0.10)"
|
||||
: "rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemedText style={styles.kvLabel}>
|
||||
{t("search.detail.country")}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.kvValue} numberOfLines={1}>
|
||||
{selectedEntity.meta.country}
|
||||
</ThemedText>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{selectedEntity.type === "league" && (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
styles.kvItem,
|
||||
{
|
||||
backgroundColor: isDark
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.04)",
|
||||
borderColor: isDark
|
||||
? "rgba(255, 255, 255, 0.10)"
|
||||
: "rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemedText style={styles.kvLabel}>
|
||||
{t("search.detail.country")}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.kvValue} numberOfLines={1}>
|
||||
{selectedEntity.meta.country || "-"}
|
||||
</ThemedText>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
styles.kvItem,
|
||||
{
|
||||
backgroundColor: isDark
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.04)",
|
||||
borderColor: isDark
|
||||
? "rgba(255, 255, 255, 0.10)"
|
||||
: "rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemedText style={styles.kvLabel}>
|
||||
{t("search.detail.season")}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.kvValue} numberOfLines={1}>
|
||||
{selectedEntity.meta.season || "-"}
|
||||
</ThemedText>
|
||||
</View>
|
||||
{selectedEntity.meta.country && (
|
||||
<View
|
||||
style={[
|
||||
styles.kvItem,
|
||||
{
|
||||
backgroundColor: isDark
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.04)",
|
||||
borderColor: isDark
|
||||
? "rgba(255, 255, 255, 0.10)"
|
||||
: "rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemedText style={styles.kvLabel}>
|
||||
{t("search.detail.country")}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.kvValue} numberOfLines={1}>
|
||||
{selectedEntity.meta.country}
|
||||
</ThemedText>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
@@ -684,7 +777,7 @@ export default function SearchScreen() {
|
||||
{t("search.detail.open")}
|
||||
</ThemedText>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
{/* <TouchableOpacity
|
||||
style={[
|
||||
styles.sheetBtn,
|
||||
styles.sheetBtnPrimary,
|
||||
@@ -707,7 +800,7 @@ export default function SearchScreen() {
|
||||
>
|
||||
{t("search.detail.save")}
|
||||
</ThemedText>
|
||||
</TouchableOpacity>
|
||||
</TouchableOpacity> */}
|
||||
</View>
|
||||
</Animated.View>
|
||||
</>
|
||||
@@ -762,26 +855,6 @@ export default function SearchScreen() {
|
||||
{t("search.subtitle")}
|
||||
</ThemedText>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.iconBtn,
|
||||
{
|
||||
backgroundColor: isDark
|
||||
? "rgba(255, 255, 255, 0.06)"
|
||||
: "rgba(0, 0, 0, 0.06)",
|
||||
borderColor: isDark
|
||||
? "rgba(255, 255, 255, 0.10)"
|
||||
: "rgba(0, 0, 0, 0.10)",
|
||||
},
|
||||
]}
|
||||
onPress={handleClearRecent}
|
||||
>
|
||||
<IconSymbol
|
||||
name="refresh"
|
||||
size={18}
|
||||
color={isDark ? "rgba(255,255,255,0.9)" : "rgba(0,0,0,0.9)"}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</BlurView>
|
||||
|
||||
<View style={styles.content}>
|
||||
@@ -790,29 +863,49 @@ export default function SearchScreen() {
|
||||
{renderChips()}
|
||||
{renderRecentSearches()}
|
||||
|
||||
<View style={styles.sectionHeader}>
|
||||
<ThemedText style={styles.sectionTitle}>
|
||||
{t("search.results")} · {t(`search.type.${searchType}`)}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.sectionAction}>
|
||||
{query.trim()
|
||||
? `${filteredEntities.length} ${t("search.found")}`
|
||||
: t("search.tap_to_open")}
|
||||
</ThemedText>
|
||||
</View>
|
||||
{query.trim() && (
|
||||
<View style={styles.sectionHeader}>
|
||||
<ThemedText style={styles.sectionTitle}>
|
||||
{t("search.results")}
|
||||
{searchType !== "all" && ` · ${t(`search.type.${searchType}`)}`}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.sectionAction}>
|
||||
{loading
|
||||
? t("search.loading")
|
||||
: error
|
||||
? t("search.error")
|
||||
: `${filteredEntities.length} ${t("search.found")}`}
|
||||
</ThemedText>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<FlatList
|
||||
data={filteredEntities}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={renderEntityItem}
|
||||
ListEmptyComponent={() => renderEmptyState()}
|
||||
contentContainerStyle={[
|
||||
styles.listContent,
|
||||
{ paddingBottom: insets.bottom + 20 },
|
||||
]}
|
||||
showsVerticalScrollIndicator={false}
|
||||
/>
|
||||
{query.trim() ? (
|
||||
loading ? (
|
||||
<ThemedView style={styles.center}>
|
||||
<ActivityIndicator size="large" color={Colors[theme].tint} />
|
||||
</ThemedView>
|
||||
) : error ? (
|
||||
<View style={styles.emptyState}>
|
||||
<ThemedText style={styles.emptyTitle}>{error}</ThemedText>
|
||||
<ThemedText style={styles.emptySub}>
|
||||
{t("search.error_hint")}
|
||||
</ThemedText>
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
data={filteredEntities}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={renderEntityItem}
|
||||
ListEmptyComponent={() => renderEmptyState()}
|
||||
contentContainerStyle={[
|
||||
styles.listContent,
|
||||
{ paddingBottom: insets.bottom + 20 },
|
||||
]}
|
||||
showsVerticalScrollIndicator={false}
|
||||
/>
|
||||
)
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{showDetailSheet && renderDetailSheet()}
|
||||
@@ -984,6 +1077,12 @@ const styles = StyleSheet.create({
|
||||
opacity: 0.52,
|
||||
fontWeight: "900",
|
||||
},
|
||||
center: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: 20,
|
||||
},
|
||||
emptyState: {
|
||||
padding: 14,
|
||||
borderRadius: 20,
|
||||
|
||||
@@ -133,7 +133,13 @@
|
||||
"tap_to_open": "Tap to open",
|
||||
"no_results": "No results",
|
||||
"no_results_hint": "Try another keyword or switch category",
|
||||
"loading": "Loading",
|
||||
"error": "Error",
|
||||
"error_hint": "Search failed, please try again",
|
||||
"start_searching": "Start searching",
|
||||
"start_searching_hint": "Enter keywords to search for teams, players or leagues",
|
||||
"type": {
|
||||
"all": "All",
|
||||
"team": "Team",
|
||||
"player": "Player",
|
||||
"league": "League"
|
||||
|
||||
@@ -133,7 +133,13 @@
|
||||
"tap_to_open": "点击打开",
|
||||
"no_results": "无结果",
|
||||
"no_results_hint": "尝试其他关键词或切换分类",
|
||||
"loading": "加载中",
|
||||
"error": "错误",
|
||||
"error_hint": "搜索失败,请重试",
|
||||
"start_searching": "开始搜索",
|
||||
"start_searching_hint": "输入关键词搜索球队、球员或联赛",
|
||||
"type": {
|
||||
"all": "全部",
|
||||
"team": "球队",
|
||||
"player": "球员",
|
||||
"league": "联赛"
|
||||
|
||||
Reference in New Issue
Block a user