mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-02-04 03:36:29 +08:00
- Add Logger utility with tagged output and environment-based control - Configure Babel to remove console calls in production builds - Replace all console.* calls across stores, services, and components with Logger - Enable development-only logging with formatted output and component tags - Optimize production builds by eliminating all logging code
240 lines
7.7 KiB
TypeScript
240 lines
7.7 KiB
TypeScript
import React, { useState, useRef, useEffect } from "react";
|
|
import { View, TextInput, StyleSheet, Alert, Keyboard, TouchableOpacity } from "react-native";
|
|
import { ThemedView } from "@/components/ThemedView";
|
|
import { ThemedText } from "@/components/ThemedText";
|
|
import VideoCard from "@/components/VideoCard";
|
|
import VideoLoadingAnimation from "@/components/VideoLoadingAnimation";
|
|
import { api, SearchResult } from "@/services/api";
|
|
import { Search, QrCode } from "lucide-react-native";
|
|
import { StyledButton } from "@/components/StyledButton";
|
|
import { useRemoteControlStore } from "@/stores/remoteControlStore";
|
|
import { RemoteControlModal } from "@/components/RemoteControlModal";
|
|
import { useSettingsStore } from "@/stores/settingsStore";
|
|
import { useRouter } from "expo-router";
|
|
import { Colors } from "@/constants/Colors";
|
|
import CustomScrollView from "@/components/CustomScrollView";
|
|
import { useResponsiveLayout } from "@/hooks/useResponsiveLayout";
|
|
import { getCommonResponsiveStyles } from "@/utils/ResponsiveStyles";
|
|
import ResponsiveNavigation from "@/components/navigation/ResponsiveNavigation";
|
|
import ResponsiveHeader from "@/components/navigation/ResponsiveHeader";
|
|
import { DeviceUtils } from "@/utils/DeviceUtils";
|
|
import Logger from '@/utils/Logger';
|
|
|
|
const logger = Logger.withTag('SearchScreen');
|
|
|
|
export default function SearchScreen() {
|
|
const [keyword, setKeyword] = useState("");
|
|
const [results, setResults] = useState<SearchResult[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const textInputRef = useRef<TextInput>(null);
|
|
const [isInputFocused, setIsInputFocused] = useState(false);
|
|
const { showModal: showRemoteModal, lastMessage, targetPage, clearMessage } = useRemoteControlStore();
|
|
const { remoteInputEnabled } = useSettingsStore();
|
|
const router = useRouter();
|
|
|
|
// 响应式布局配置
|
|
const responsiveConfig = useResponsiveLayout();
|
|
const commonStyles = getCommonResponsiveStyles(responsiveConfig);
|
|
const { deviceType, spacing } = responsiveConfig;
|
|
|
|
useEffect(() => {
|
|
if (lastMessage && targetPage === 'search') {
|
|
logger.debug("Received remote input:", lastMessage);
|
|
const realMessage = lastMessage.split("_")[0];
|
|
setKeyword(realMessage);
|
|
handleSearch(realMessage);
|
|
clearMessage(); // Clear the message after processing
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [lastMessage, targetPage]);
|
|
|
|
// useEffect(() => {
|
|
// // Focus the text input when the screen loads
|
|
// const timer = setTimeout(() => {
|
|
// textInputRef.current?.focus();
|
|
// }, 200);
|
|
// return () => clearTimeout(timer);
|
|
// }, []);
|
|
|
|
const handleSearch = async (searchText?: string) => {
|
|
const term = typeof searchText === "string" ? searchText : keyword;
|
|
if (!term.trim()) {
|
|
Keyboard.dismiss();
|
|
return;
|
|
}
|
|
Keyboard.dismiss();
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
const response = await api.searchVideos(term);
|
|
if (response.results.length > 0) {
|
|
setResults(response.results);
|
|
} else {
|
|
setError("没有找到相关内容");
|
|
}
|
|
} catch (err) {
|
|
setError("搜索失败,请稍后重试。");
|
|
logger.info("Search failed:", err);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const onSearchPress = () => handleSearch();
|
|
|
|
const handleQrPress = () => {
|
|
if (!remoteInputEnabled) {
|
|
Alert.alert("远程输入未启用", "请先在设置页面中启用远程输入功能", [
|
|
{ text: "取消", style: "cancel" },
|
|
{ text: "去设置", onPress: () => router.push("/settings") },
|
|
]);
|
|
return;
|
|
}
|
|
showRemoteModal('search');
|
|
};
|
|
|
|
const renderItem = ({ item }: { item: SearchResult; index: number }) => (
|
|
<VideoCard
|
|
id={item.id.toString()}
|
|
source={item.source}
|
|
title={item.title}
|
|
poster={item.poster}
|
|
year={item.year}
|
|
sourceName={item.source_name}
|
|
api={api}
|
|
/>
|
|
);
|
|
|
|
// 动态样式
|
|
const dynamicStyles = createResponsiveStyles(deviceType, spacing);
|
|
|
|
const renderSearchContent = () => (
|
|
<>
|
|
<View style={dynamicStyles.searchContainer}>
|
|
<TouchableOpacity
|
|
activeOpacity={1}
|
|
style={[
|
|
dynamicStyles.inputContainer,
|
|
{
|
|
borderColor: isInputFocused ? Colors.dark.primary : "transparent",
|
|
},
|
|
]}
|
|
onPress={() => textInputRef.current?.focus()}
|
|
>
|
|
<TextInput
|
|
ref={textInputRef}
|
|
style={dynamicStyles.input}
|
|
placeholder="搜索电影、剧集..."
|
|
placeholderTextColor="#888"
|
|
value={keyword}
|
|
onChangeText={setKeyword}
|
|
onSubmitEditing={onSearchPress}
|
|
onFocus={() => setIsInputFocused(true)}
|
|
onBlur={() => setIsInputFocused(false)}
|
|
returnKeyType="search"
|
|
/>
|
|
</TouchableOpacity>
|
|
<StyledButton style={dynamicStyles.searchButton} onPress={onSearchPress}>
|
|
<Search size={deviceType === 'mobile' ? 20 : 24} color="white" />
|
|
</StyledButton>
|
|
{deviceType !== 'mobile' && (
|
|
<StyledButton style={dynamicStyles.qrButton} onPress={handleQrPress}>
|
|
<QrCode size={deviceType === 'tv' ? 24 : 20} color="white" />
|
|
</StyledButton>
|
|
)}
|
|
</View>
|
|
|
|
{loading ? (
|
|
<VideoLoadingAnimation showProgressBar={false} />
|
|
) : error ? (
|
|
<View style={[commonStyles.center, { flex: 1 }]}>
|
|
<ThemedText style={dynamicStyles.errorText}>{error}</ThemedText>
|
|
</View>
|
|
) : (
|
|
<CustomScrollView
|
|
data={results}
|
|
renderItem={renderItem}
|
|
loading={loading}
|
|
error={error}
|
|
emptyMessage="输入关键词开始搜索"
|
|
/>
|
|
)}
|
|
<RemoteControlModal />
|
|
</>
|
|
);
|
|
|
|
const content = (
|
|
<ThemedView style={[commonStyles.container, dynamicStyles.container]}>
|
|
{renderSearchContent()}
|
|
</ThemedView>
|
|
);
|
|
|
|
// 根据设备类型决定是否包装在响应式导航中
|
|
if (deviceType === 'tv') {
|
|
return content;
|
|
}
|
|
|
|
return (
|
|
<ResponsiveNavigation>
|
|
<ResponsiveHeader title="搜索" showBackButton />
|
|
{content}
|
|
</ResponsiveNavigation>
|
|
);
|
|
}
|
|
|
|
const createResponsiveStyles = (deviceType: string, spacing: number) => {
|
|
const isMobile = deviceType === 'mobile';
|
|
const minTouchTarget = DeviceUtils.getMinTouchTargetSize();
|
|
|
|
return StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
paddingTop: deviceType === 'tv' ? 50 : 0,
|
|
},
|
|
searchContainer: {
|
|
flexDirection: "row",
|
|
paddingHorizontal: spacing,
|
|
marginBottom: spacing,
|
|
alignItems: "center",
|
|
paddingTop: isMobile ? spacing / 2 : 0,
|
|
},
|
|
inputContainer: {
|
|
flex: 1,
|
|
height: isMobile ? minTouchTarget : 50,
|
|
backgroundColor: "#2c2c2e",
|
|
borderRadius: isMobile ? 8 : 8,
|
|
marginRight: spacing / 2,
|
|
borderWidth: 2,
|
|
borderColor: "transparent",
|
|
justifyContent: "center",
|
|
},
|
|
input: {
|
|
flex: 1,
|
|
paddingHorizontal: spacing,
|
|
color: "white",
|
|
fontSize: isMobile ? 16 : 18,
|
|
},
|
|
searchButton: {
|
|
width: isMobile ? minTouchTarget : 50,
|
|
height: isMobile ? minTouchTarget : 50,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
borderRadius: isMobile ? 8 : 8,
|
|
marginRight: deviceType !== 'mobile' ? spacing / 2 : 0,
|
|
},
|
|
qrButton: {
|
|
width: isMobile ? minTouchTarget : 50,
|
|
height: isMobile ? minTouchTarget : 50,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
borderRadius: isMobile ? 8 : 8,
|
|
},
|
|
errorText: {
|
|
color: "red",
|
|
fontSize: isMobile ? 14 : 16,
|
|
textAlign: "center",
|
|
},
|
|
});
|
|
};
|