import React, { useRef, useCallback } from 'react'; import { View, Text, StyleSheet, TextInput, TouchableOpacity, FlatList, ActivityIndicator, ScrollView, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useRouter } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; import { VideoCard } from '../components/VideoCard'; import { useSearch, SearchSort } from '../hooks/useSearch'; import { useTheme } from '../utils/theme'; import type { VideoItem } from '../services/types'; const SORT_OPTIONS: { key: SearchSort; label: string }[] = [ { key: 'default', label: '综合排序' }, { key: 'pubdate', label: '最新发布' }, { key: 'view', label: '最多播放' }, ]; export default function SearchScreen() { const router = useRouter(); const { keyword, setKeyword, results, loading, hasMore, search, loadMore, sort, changeSort, history, removeFromHistory, clearHistory, suggestions, hotSearches, } = useSearch(); const theme = useTheme(); const inputRef = useRef(null); const hasResults = results.length > 0; const hasSearched = hasResults || (loading && results.length === 0); const handleSearch = useCallback((kw?: string) => { const term = (kw ?? keyword).trim(); if (term) { if (kw) setKeyword(kw); search(kw ?? keyword, true); } }, [keyword, search, setKeyword]); const handleSuggestionPress = useCallback((value: string) => { setKeyword(value); search(value, true); }, [search, setKeyword]); const renderItem = useCallback( ({ item, index }: { item: VideoItem; index: number }) => { if (index % 2 !== 0) return null; const right = results[index + 1]; return ( router.push(`/video/${item.bvid}` as any)} /> {right ? ( router.push(`/video/${right.bvid}` as any)} /> ) : ( )} ); }, [results, router], ); const keyExtractor = useCallback( (_: VideoItem, index: number) => String(index), [], ); // Show pre-search panel (history + hot searches + suggestions) const showPreSearch = !hasSearched && !loading; const showSuggestions = suggestions.length > 0 && keyword.trim().length > 0 && !hasResults; const ListHeaderComponent = useCallback(() => { if (!hasResults) return null; return ( {SORT_OPTIONS.map(opt => ( changeSort(opt.key)} activeOpacity={0.85} > {opt.label} ))} ); }, [hasResults, sort, changeSort, theme.card]); const ListEmptyComponent = () => { if (loading) return null; if (!keyword.trim()) return null; return ( 没有找到相关视频 ); }; return ( {/* Search header */} router.back()} style={styles.backBtn}> handleSearch()} returnKeyType="search" autoFocus autoCapitalize="none" autoCorrect={false} /> {keyword.length > 0 && ( setKeyword('')} style={styles.clearBtn}> )} handleSearch()} activeOpacity={0.85}> 搜索 {/* Suggestions dropdown */} {showSuggestions && ( {suggestions.map((s, i) => ( handleSuggestionPress(s.value)} activeOpacity={0.85} > {s.value} ))} )} {/* Pre-search: history + hot searches */} {showPreSearch && !showSuggestions ? ( {/* Search history */} {history.length > 0 && ( 搜索历史 {history.map(h => ( handleSearch(h)} onLongPress={() => removeFromHistory(h)} activeOpacity={0.85} > {h} ))} )} {/* Hot searches */} {hotSearches.length > 0 && ( 热搜榜 {hotSearches.map((item, idx) => ( handleSearch(item.keyword)} activeOpacity={0.85} > {idx + 1} {item.show_name} ))} )} {history.length === 0 && hotSearches.length === 0 && ( 输入关键词搜索 )} ) : ( /* Results list */ } ListEmptyComponent={} ListFooterComponent={ loading && results.length > 0 ? ( ) : null } keyboardShouldPersistTaps="handled" /> )} ); } const styles = StyleSheet.create({ safe: { flex: 1, backgroundColor: '#f4f4f4' }, header: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 8, paddingVertical: 8, backgroundColor: '#fff', borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#eee', gap: 6, }, backBtn: { padding: 4 }, inputWrap: { flex: 1, height: 34, backgroundColor: '#f0f0f0', borderRadius: 17, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 12, }, input: { flex: 1, fontSize: 14, color: '#212121', padding: 0, }, clearBtn: { paddingLeft: 4 }, searchBtn: { paddingHorizontal: 10, paddingVertical: 6, }, searchBtnText: { fontSize: 14, color: '#00AEEC', fontWeight: '600' }, // Sort bar sortBar: { flexDirection: 'row', paddingHorizontal: 12, paddingVertical: 8, gap: 12, }, sortBtn: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 14, }, sortBtnActive: { backgroundColor: '#00AEEC', }, sortBtnText: { fontSize: 12, color: '#999', }, sortBtnTextActive: { color: '#fff', fontWeight: '600', }, // Suggestions suggestPanel: { borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#eee', }, suggestItem: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 10, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#eee', }, suggestIcon: { marginRight: 8 }, suggestText: { fontSize: 14, flex: 1 }, // Pre-search preSearch: { flex: 1, paddingHorizontal: 16, paddingTop: 12 }, section: { marginBottom: 20 }, sectionHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10, }, sectionTitle: { fontSize: 15, fontWeight: '600', color: '#212121', marginBottom: 2 }, tagWrap: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 }, tag: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 14, backgroundColor: '#f0f0f0', maxWidth: '45%', }, tagText: { fontSize: 13, color: '#212121' }, // Hot search list hotItem: { flexDirection: 'row', alignItems: 'center', paddingVertical: 10, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#eee', }, hotIndex: { width: 22, fontSize: 14, fontWeight: '600', color: '#999', textAlign: 'center', marginRight: 10, }, hotIndexTop: { color: '#00AEEC' }, hotText: { fontSize: 14, flex: 1 }, // Results listContent: { paddingTop: 0, paddingBottom: 20 }, row: { flexDirection: 'row', paddingHorizontal: 1, justifyContent: 'flex-start', }, leftCol: { flex: 1, marginLeft: 4, marginRight: 2 }, rightCol: { flex: 1, marginLeft: 2, marginRight: 4 }, emptyBox: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingTop: 80, gap: 12, }, emptyText: { fontSize: 14, color: '#bbb' }, footer: { height: 48, alignItems: 'center', justifyContent: 'center' }, });