Refactor & Fix issue

This commit is contained in:
zimplexing
2025-07-06 16:43:25 +08:00
parent a2428120c4
commit b2b667ae91
13 changed files with 426 additions and 453 deletions

View File

@@ -1,80 +1,114 @@
import React, { useEffect, useState } from "react";
import {
View,
Text,
StyleSheet,
Image,
ScrollView,
ActivityIndicator,
} from "react-native";
import { useLocalSearchParams, useRouter } from "expo-router";
import { ThemedView } from "@/components/ThemedView";
import { ThemedText } from "@/components/ThemedText";
import { moonTVApi, SearchResult } from "@/services/api";
import { getResolutionFromM3U8 } from "@/services/m3u8";
import { DetailButton } from "@/components/DetailButton";
import React, { useEffect, useState, useRef } from 'react';
import { View, Text, StyleSheet, Image, ScrollView, ActivityIndicator } from 'react-native';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { ThemedView } from '@/components/ThemedView';
import { ThemedText } from '@/components/ThemedText';
import { api, SearchResult } from '@/services/api';
import { getResolutionFromM3U8 } from '@/services/m3u8';
import { DetailButton } from '@/components/DetailButton';
export default function DetailScreen() {
const { source, q } = useLocalSearchParams();
const router = useRouter();
const [searchResults, setSearchResults] = useState<
(SearchResult & { resolution?: string | null })[]
>([]);
const [detail, setDetail] = useState<
(SearchResult & { resolution?: string | null }) | null
>(null);
const [searchResults, setSearchResults] = useState<(SearchResult & { resolution?: string | null })[]>([]);
const [detail, setDetail] = useState<(SearchResult & { resolution?: string | null }) | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [allSourcesLoaded, setAllSourcesLoaded] = useState(false);
const controllerRef = useRef<AbortController | null>(null);
useEffect(() => {
if (typeof source === "string" && typeof q === "string") {
const fetchDetailData = async () => {
try {
setLoading(true);
const { results } = await moonTVApi.searchVideos(q as string);
if (results && results.length > 0) {
const initialDetail =
results.find((r) => r.source === source) || results[0];
setDetail(initialDetail);
setSearchResults(results); // Set initial results first
if (controllerRef.current) {
controllerRef.current.abort();
}
controllerRef.current = new AbortController();
const signal = controllerRef.current.signal;
// Asynchronously fetch resolutions
const resultsWithResolutions = await Promise.all(
results.map(async (searchResult) => {
if (typeof q === 'string') {
const fetchDetailData = async () => {
setLoading(true);
setSearchResults([]);
setDetail(null);
setError(null);
setAllSourcesLoaded(false);
try {
const resources = await api.getResources(signal);
if (!resources || resources.length === 0) {
setError('没有可用的播放源');
setLoading(false);
return;
}
let foundFirstResult = false;
// Prioritize source from params if available
if (typeof source === 'string') {
const index = resources.findIndex(r => r.key === source);
if (index > 0) {
resources.unshift(resources.splice(index, 1)[0]);
}
}
for (const resource of resources) {
try {
const { results } = await api.searchVideo(q, resource.key, signal);
if (results && results.length > 0) {
const searchResult = results[0];
let resolution;
try {
if (
searchResult.episodes &&
searchResult.episodes.length > 0
) {
const resolution = await getResolutionFromM3U8(
searchResult.episodes[0]
);
return { ...searchResult, resolution };
if (searchResult.episodes && searchResult.episodes.length > 0) {
resolution = await getResolutionFromM3U8(searchResult.episodes[0], signal);
}
} catch (e) {
console.error("Failed to get resolution for source", e);
if ((e as Error).name !== 'AbortError') {
console.error(`Failed to get resolution for ${resource.name}`, e);
}
}
return searchResult; // Return original if fails
})
);
setSearchResults(resultsWithResolutions);
} else {
setError("未找到播放源");
const resultWithResolution = { ...searchResult, resolution };
setSearchResults(prev => [...prev, resultWithResolution]);
if (!foundFirstResult) {
setDetail(resultWithResolution);
foundFirstResult = true;
setLoading(false);
}
}
} catch (e) {
if ((e as Error).name !== 'AbortError') {
console.error(`Error searching in resource ${resource.name}:`, e);
}
}
}
if (!foundFirstResult) {
setError('未找到播放源');
setLoading(false);
}
} catch (e) {
setError(e instanceof Error ? e.message : "获取详情失败");
if ((e as Error).name !== 'AbortError') {
setError(e instanceof Error ? e.message : '获取资源列表失败');
setLoading(false);
}
} finally {
setLoading(false);
setAllSourcesLoaded(true);
}
};
fetchDetailData();
}
}, [source, q]);
return () => {
controllerRef.current?.abort();
};
}, [q, source]);
const handlePlay = (episodeName: string, episodeIndex: number) => {
if (!detail) return;
controllerRef.current?.abort(); // Cancel any ongoing fetches
router.push({
pathname: "/play",
pathname: '/play',
params: {
source: detail.source,
id: detail.id.toString(),
@@ -121,9 +155,7 @@ export default function DetailScreen() {
</ThemedText>
<View style={styles.metaContainer}>
<ThemedText style={styles.metaText}>{detail.year}</ThemedText>
<ThemedText style={styles.metaText}>
{detail.type_name}
</ThemedText>
<ThemedText style={styles.metaText}>{detail.type_name}</ThemedText>
</View>
<ScrollView style={styles.descriptionScrollView}>
<ThemedText style={styles.description}>{detail.desc}</ThemedText>
@@ -133,37 +165,28 @@ export default function DetailScreen() {
<View style={styles.bottomContainer}>
<View style={styles.sourcesContainer}>
<ThemedText style={styles.sourcesTitle}>
{searchResults.length}
</ThemedText>
<View style={styles.sourcesTitleContainer}>
<ThemedText style={styles.sourcesTitle}> {searchResults.length} </ThemedText>
{!allSourcesLoaded && <ActivityIndicator style={{ marginLeft: 10 }} />}
</View>
<View style={styles.sourceList}>
{searchResults.map((item, index) => (
<DetailButton
key={index}
onPress={() => setDetail(item)}
hasTVPreferredFocus={index === 0}
style={[
styles.sourceButton,
detail?.source === item.source &&
styles.sourceButtonSelected,
]}
style={[styles.sourceButton, detail?.source === item.source && styles.sourceButtonSelected]}
>
<ThemedText style={styles.sourceButtonText}>
{item.source_name}
</ThemedText>
<ThemedText style={styles.sourceButtonText}>{item.source_name}</ThemedText>
{item.episodes.length > 1 && (
<View style={styles.badge}>
<Text style={styles.badgeText}>
{item.episodes.length > 99
? "99+"
: `${item.episodes.length}`}
{item.episodes.length > 99 ? '99+' : `${item.episodes.length}`}
</Text>
</View>
)}
{item.resolution && (
<View
style={[styles.badge, { backgroundColor: "#28a745" }]}
>
<View style={[styles.badge, { backgroundColor: '#28a745' }]}>
<Text style={styles.badgeText}>{item.resolution}</Text>
</View>
)}
@@ -175,14 +198,8 @@ export default function DetailScreen() {
<ThemedText style={styles.episodesTitle}></ThemedText>
<ScrollView contentContainerStyle={styles.episodeList}>
{detail.episodes.map((episode, index) => (
<DetailButton
key={index}
style={styles.episodeButton}
onPress={() => handlePlay(episode, index)}
>
<ThemedText style={styles.episodeButtonText}>{`${
index + 1
}`}</ThemedText>
<DetailButton key={index} style={styles.episodeButton} onPress={() => handlePlay(episode, index)}>
<ThemedText style={styles.episodeButtonText}>{`${index + 1}`}</ThemedText>
</DetailButton>
))}
</ScrollView>
@@ -195,9 +212,9 @@ export default function DetailScreen() {
const styles = StyleSheet.create({
container: { flex: 1 },
centered: { flex: 1, justifyContent: "center", alignItems: "center" },
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
topContainer: {
flexDirection: "row",
flexDirection: 'row',
padding: 20,
},
poster: {
@@ -208,20 +225,20 @@ const styles = StyleSheet.create({
infoContainer: {
flex: 1,
marginLeft: 20,
justifyContent: "flex-start",
justifyContent: 'flex-start',
},
title: {
fontSize: 28,
fontWeight: "bold",
fontWeight: 'bold',
marginBottom: 10,
paddingTop: 20,
},
metaContainer: {
flexDirection: "row",
flexDirection: 'row',
marginBottom: 10,
},
metaText: {
color: "#aaa",
color: '#aaa',
marginRight: 10,
fontSize: 14,
},
@@ -230,7 +247,7 @@ const styles = StyleSheet.create({
},
description: {
fontSize: 14,
color: "#ccc",
color: '#ccc',
lineHeight: 22,
},
bottomContainer: {
@@ -239,67 +256,71 @@ const styles = StyleSheet.create({
sourcesContainer: {
marginTop: 20,
},
sourcesTitle: {
fontSize: 20,
fontWeight: "bold",
sourcesTitleContainer: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 10,
},
sourcesTitle: {
fontSize: 20,
fontWeight: 'bold',
},
sourceList: {
flexDirection: "row",
flexWrap: "wrap",
flexDirection: 'row',
flexWrap: 'wrap',
},
sourceButton: {
backgroundColor: "#333",
backgroundColor: '#333',
paddingHorizontal: 15,
paddingVertical: 10,
borderRadius: 8,
margin: 5,
flexDirection: "row",
alignItems: "center",
flexDirection: 'row',
alignItems: 'center',
borderWidth: 2,
borderColor: "transparent",
borderColor: 'transparent',
},
sourceButtonSelected: {
backgroundColor: "#007bff",
backgroundColor: '#007bff',
},
sourceButtonText: {
color: "white",
color: 'white',
fontSize: 16,
},
badge: {
backgroundColor: "red",
backgroundColor: 'red',
borderRadius: 10,
paddingHorizontal: 6,
paddingVertical: 2,
marginLeft: 8,
},
badgeText: {
color: "white",
color: 'white',
fontSize: 12,
fontWeight: "bold",
fontWeight: 'bold',
},
episodesContainer: {
marginTop: 20,
},
episodesTitle: {
fontSize: 20,
fontWeight: "bold",
fontWeight: 'bold',
marginBottom: 10,
},
episodeList: {
flexDirection: "row",
flexWrap: "wrap",
flexDirection: 'row',
flexWrap: 'wrap',
},
episodeButton: {
backgroundColor: "#333",
backgroundColor: '#333',
paddingHorizontal: 20,
paddingVertical: 10,
borderRadius: 8,
margin: 5,
borderWidth: 2,
borderColor: "transparent",
borderColor: 'transparent',
},
episodeButtonText: {
color: "white",
color: 'white',
},
});

View File

@@ -1,17 +1,10 @@
import React, { useState, useEffect, useCallback, useRef } from "react";
import {
View,
StyleSheet,
ActivityIndicator,
FlatList,
Pressable,
Dimensions,
} from "react-native";
import { ThemedView } from "@/components/ThemedView";
import { ThemedText } from "@/components/ThemedText";
import { moonTVApi } from "@/services/api";
import { SearchResult } from "@/services/api";
import { PlayRecord } from "@/services/storage";
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { View, StyleSheet, ActivityIndicator, FlatList, Pressable, Dimensions } from 'react-native';
import { ThemedView } from '@/components/ThemedView';
import { ThemedText } from '@/components/ThemedText';
import { api } from '@/services/api';
import { SearchResult } from '@/services/api';
import { PlayRecord } from '@/services/storage';
export type RowItem = (SearchResult | PlayRecord) & {
id: string;
@@ -26,35 +19,35 @@ export type RowItem = (SearchResult | PlayRecord) & {
year?: string;
rate?: string;
};
import VideoCard from "@/components/VideoCard.tv";
import { PlayRecordManager } from "@/services/storage";
import { useFocusEffect, useRouter } from "expo-router";
import { useColorScheme } from "react-native";
import { Search, Settings } from "lucide-react-native";
import { SettingsModal } from "@/components/SettingsModal";
import VideoCard from '@/components/VideoCard.tv';
import { PlayRecordManager } from '@/services/storage';
import { useFocusEffect, useRouter } from 'expo-router';
import { useColorScheme } from 'react-native';
import { Search, Settings } from 'lucide-react-native';
import { SettingsModal } from '@/components/SettingsModal';
// --- 类别定义 ---
interface Category {
title: string;
type?: "movie" | "tv" | "record";
type?: 'movie' | 'tv' | 'record';
tag?: string;
}
const initialCategories: Category[] = [
{ title: "最近播放", type: "record" },
{ title: "综艺", type: "tv", tag: "综艺" },
{ title: "热门剧集", type: "tv", tag: "热门" },
{ title: "热门电影", type: "movie", tag: "热门" },
{ title: "豆瓣 Top250", type: "movie", tag: "top250" },
{ title: "儿童", type: "movie", tag: "少儿" },
{ title: "美剧", type: "tv", tag: "美剧" },
{ title: "韩剧", type: "tv", tag: "韩剧" },
{ title: "日剧", type: "tv", tag: "日剧" },
{ title: "日漫", type: "tv", tag: "日本动画" },
{ title: '最近播放', type: 'record' },
{ title: '热门剧集', type: 'tv', tag: '热门' },
{ title: '综艺', type: 'tv', tag: '综艺' },
{ title: '热门电影', type: 'movie', tag: '热门' },
{ title: '豆瓣 Top250', type: 'movie', tag: 'top250' },
{ title: '儿童', type: 'movie', tag: '少儿' },
{ title: '美剧', type: 'tv', tag: '美剧' },
{ title: '韩剧', type: 'tv', tag: '韩剧' },
{ title: '日剧', type: 'tv', tag: '日剧' },
{ title: '日漫', type: 'tv', tag: '日本动画' },
];
const NUM_COLUMNS = 5;
const { width } = Dimensions.get("window");
const { width } = Dimensions.get('window');
const ITEM_WIDTH = width / NUM_COLUMNS - 24;
export default function HomeScreen() {
@@ -62,9 +55,7 @@ export default function HomeScreen() {
const colorScheme = useColorScheme();
const [categories, setCategories] = useState<Category[]>(initialCategories);
const [selectedCategory, setSelectedCategory] = useState<Category>(
categories[0]
);
const [selectedCategory, setSelectedCategory] = useState<Category>(categories[0]);
const [contentData, setContentData] = useState<RowItem[]>([]);
const [loading, setLoading] = useState(true);
@@ -82,7 +73,7 @@ export default function HomeScreen() {
const records = await PlayRecordManager.getAll();
return Object.entries(records)
.map(([key, record]) => {
const [source, id] = key.split("+");
const [source, id] = key.split('+');
return {
id,
source,
@@ -95,23 +86,20 @@ export default function HomeScreen() {
totalEpisodes: record.total_episodes,
} as RowItem;
})
.filter(
(record) =>
record.progress !== undefined &&
record.progress > 0 &&
record.progress < 1
)
.filter(record => record.progress !== undefined && record.progress > 0 && record.progress < 1)
.sort((a, b) => (b.lastPlayed || 0) - (a.lastPlayed || 0));
};
const fetchData = async (category: Category, start: number) => {
if (category.type === "record") {
const records = await fetchPlayRecords();
if (records.length === 0 && categories[0].type === "record") {
const fetchData = async (category: Category, start: number, preloadedRecords?: RowItem[]) => {
if (category.type === 'record') {
const records = preloadedRecords ?? (await fetchPlayRecords());
if (records.length === 0 && categories.some(c => c.type === 'record')) {
// 如果没有播放记录,则移除"最近播放"分类并选择第一个真实分类
const newCategories = categories.slice(1);
const newCategories = categories.filter(c => c.type !== 'record');
setCategories(newCategories);
handleCategorySelect(newCategories[0]);
if (newCategories.length > 0) {
handleCategorySelect(newCategories[0]);
}
} else {
setContentData(records);
setHasMore(false);
@@ -126,33 +114,26 @@ export default function HomeScreen() {
setError(null);
try {
const result = await moonTVApi.getDoubanData(
category.type,
category.tag,
20,
start
);
const result = await api.getDoubanData(category.type, category.tag, 20, start);
if (result.list.length === 0) {
setHasMore(false);
} else {
const newItems = result.list.map((item) => ({
const newItems = result.list.map(item => ({
...item,
id: item.title, // 临时ID
source: "douban",
source: 'douban',
})) as RowItem[];
setContentData((prev) =>
start === 0 ? newItems : [...prev, ...newItems]
);
setPageStart((prev) => prev + result.list.length);
setContentData(prev => (start === 0 ? newItems : [...prev, ...newItems]));
setPageStart(prev => prev + result.list.length);
setHasMore(true);
}
} catch (err: any) {
if (err.message === "API_URL_NOT_SET") {
setError("请点击右上角设置按钮,配置您的 API 地址");
if (err.message === 'API_URL_NOT_SET') {
setError('请点击右上角设置按钮,配置您的 API 地址');
} else {
setError("加载失败,请重试");
setError('加载失败,请重试');
}
} finally {
setLoading(false);
@@ -163,9 +144,27 @@ export default function HomeScreen() {
// --- Effects ---
useFocusEffect(
useCallback(() => {
if (selectedCategory.type === "record") {
loadInitialData();
}
const manageRecordCategory = async () => {
const records = await fetchPlayRecords();
const hasRecords = records.length > 0;
setCategories(currentCategories => {
const recordCategoryExists = currentCategories.some(c => c.type === 'record');
if (hasRecords && !recordCategoryExists) {
// Add 'Recent Plays' if records exist and the tab doesn't
return [initialCategories[0], ...currentCategories];
}
return currentCategories;
});
// If 'Recent Plays' is selected, always refresh its data.
// This will also handle removing the tab if records have disappeared.
if (selectedCategory.type === 'record') {
loadInitialData(records);
}
};
manageRecordCategory();
}, [selectedCategory])
);
@@ -173,23 +172,17 @@ export default function HomeScreen() {
loadInitialData();
}, [selectedCategory]);
const loadInitialData = () => {
const loadInitialData = (records?: RowItem[]) => {
setLoading(true);
setContentData([]);
setPageStart(0);
setHasMore(true);
flatListRef.current?.scrollToOffset({ animated: false, offset: 0 });
fetchData(selectedCategory, 0);
fetchData(selectedCategory, 0, records);
};
const loadMoreData = () => {
if (
loading ||
loadingMore ||
!hasMore ||
selectedCategory.type === "record"
)
return;
if (loading || loadingMore || !hasMore || selectedCategory.type === 'record') return;
fetchData(selectedCategory, pageStart);
};
@@ -209,14 +202,7 @@ export default function HomeScreen() {
]}
onPress={() => handleCategorySelect(item)}
>
<ThemedText
style={[
styles.categoryText,
isSelected && styles.categoryTextSelected,
]}
>
{item.title}
</ThemedText>
<ThemedText style={[styles.categoryText, isSelected && styles.categoryTextSelected]}>{item.title}</ThemedText>
</Pressable>
);
};
@@ -234,7 +220,7 @@ export default function HomeScreen() {
episodeIndex={item.episodeIndex}
sourceName={item.sourceName}
totalEpisodes={item.totalEpisodes}
api={moonTVApi}
api={api}
onRecordDeleted={loadInitialData} // For "Recent Plays"
/>
</View>
@@ -252,28 +238,16 @@ export default function HomeScreen() {
<ThemedText style={styles.headerTitle}></ThemedText>
<View style={styles.rightHeaderButtons}>
<Pressable
style={({ focused }) => [
styles.searchButton,
focused && styles.searchButtonFocused,
]}
onPress={() => router.push({ pathname: "/search" })}
style={({ focused }) => [styles.searchButton, focused && styles.searchButtonFocused]}
onPress={() => router.push({ pathname: '/search' })}
>
<Search
color={colorScheme === "dark" ? "white" : "black"}
size={24}
/>
<Search color={colorScheme === 'dark' ? 'white' : 'black'} size={24} />
</Pressable>
<Pressable
style={({ focused }) => [
styles.searchButton,
focused && styles.searchButtonFocused,
]}
style={({ focused }) => [styles.searchButton, focused && styles.searchButtonFocused]}
onPress={() => setSettingsVisible(true)}
>
<Settings
color={colorScheme === "dark" ? "white" : "black"}
size={24}
/>
<Settings color={colorScheme === 'dark' ? 'white' : 'black'} size={24} />
</Pressable>
</View>
</View>
@@ -283,7 +257,7 @@ export default function HomeScreen() {
<FlatList
data={categories}
renderItem={renderCategory}
keyExtractor={(item) => item.title}
keyExtractor={item => item.title}
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.categoryListContent}
@@ -338,25 +312,26 @@ const styles = StyleSheet.create({
},
centerContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
paddingTop: 20,
justifyContent: 'center',
alignItems: 'center',
},
// Header
headerContainer: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 24,
marginBottom: 10,
},
headerTitle: {
fontSize: 32,
fontWeight: "bold",
fontWeight: 'bold',
paddingTop: 16,
},
rightHeaderButtons: {
flexDirection: "row",
alignItems: "center",
flexDirection: 'row',
alignItems: 'center',
},
searchButton: {
padding: 10,
@@ -364,7 +339,7 @@ const styles = StyleSheet.create({
marginLeft: 10,
},
searchButtonFocused: {
backgroundColor: "#007AFF",
backgroundColor: '#007AFF',
transform: [{ scale: 1.1 }],
},
// Category Selector
@@ -381,18 +356,18 @@ const styles = StyleSheet.create({
marginHorizontal: 5,
},
categoryButtonSelected: {
backgroundColor: "#007AFF", // A bright blue for selected state
backgroundColor: '#007AFF', // A bright blue for selected state
},
categoryButtonFocused: {
backgroundColor: "#0056b3", // A darker blue for focused state
backgroundColor: '#0056b3', // A darker blue for focused state
elevation: 5,
},
categoryText: {
fontSize: 16,
fontWeight: "500",
fontWeight: '500',
},
categoryTextSelected: {
color: "#FFFFFF",
color: '#FFFFFF',
},
// Content Grid
listContent: {
@@ -402,6 +377,6 @@ const styles = StyleSheet.create({
itemContainer: {
margin: 8,
width: ITEM_WIDTH,
alignItems: "center",
alignItems: 'center',
},
});

View File

@@ -1,4 +1,4 @@
import React, { useState, useRef, useEffect } from "react";
import React, { useState, useRef, useEffect } from 'react';
import {
View,
TextInput,
@@ -9,15 +9,15 @@ import {
Text,
Keyboard,
useColorScheme,
} from "react-native";
import { ThemedView } from "@/components/ThemedView";
import { ThemedText } from "@/components/ThemedText";
import VideoCard from "@/components/VideoCard.tv";
import { moonTVApi, SearchResult } from "@/services/api";
import { Search } from "lucide-react-native";
} from 'react-native';
import { ThemedView } from '@/components/ThemedView';
import { ThemedText } from '@/components/ThemedText';
import VideoCard from '@/components/VideoCard.tv';
import { api, SearchResult } from '@/services/api';
import { Search } from 'lucide-react-native';
export default function SearchScreen() {
const [keyword, setKeyword] = useState("");
const [keyword, setKeyword] = useState('');
const [results, setResults] = useState<SearchResult[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -42,15 +42,15 @@ export default function SearchScreen() {
setLoading(true);
setError(null);
try {
const response = await moonTVApi.searchVideos(keyword);
const response = await api.searchVideos(keyword);
if (response.results.length > 0) {
setResults(response.results);
} else {
setError("没有找到相关内容");
setError('没有找到相关内容');
}
} catch (err) {
setError("搜索失败,请稍后重试。");
console.error("Search failed:", err);
setError('搜索失败,请稍后重试。');
console.error('Search failed:', err);
} finally {
setLoading(false);
}
@@ -64,7 +64,7 @@ export default function SearchScreen() {
poster={item.poster}
year={item.year}
sourceName={item.source_name}
api={moonTVApi}
api={api}
/>
);
@@ -76,13 +76,13 @@ export default function SearchScreen() {
style={[
styles.input,
{
backgroundColor: colorScheme === "dark" ? "#2c2c2e" : "#f0f0f0",
color: colorScheme === "dark" ? "white" : "black",
borderColor: isInputFocused ? "#007bff" : "transparent",
backgroundColor: colorScheme === 'dark' ? '#2c2c2e' : '#f0f0f0',
color: colorScheme === 'dark' ? 'white' : 'black',
borderColor: isInputFocused ? '#007bff' : 'transparent',
},
]}
placeholder="搜索电影、剧集..."
placeholderTextColor={colorScheme === "dark" ? "#888" : "#555"}
placeholderTextColor={colorScheme === 'dark' ? '#888' : '#555'}
value={keyword}
onChangeText={setKeyword}
onFocus={() => setIsInputFocused(true)}
@@ -94,16 +94,13 @@ export default function SearchScreen() {
style={({ focused }) => [
styles.searchButton,
{
backgroundColor: colorScheme === "dark" ? "#3a3a3c" : "#e0e0e0",
backgroundColor: colorScheme === 'dark' ? '#3a3a3c' : '#e0e0e0',
},
focused && styles.focusedButton,
]}
onPress={handleSearch}
>
<Search
size={24}
color={colorScheme === "dark" ? "white" : "black"}
/>
<Search size={24} color={colorScheme === 'dark' ? 'white' : 'black'} />
</Pressable>
</View>
@@ -139,22 +136,22 @@ const styles = StyleSheet.create({
paddingTop: 50,
},
searchContainer: {
flexDirection: "row",
flexDirection: 'row',
paddingHorizontal: 20,
marginBottom: 20,
alignItems: "center",
alignItems: 'center',
},
input: {
flex: 1,
height: 50,
backgroundColor: "#2c2c2e", // Default for dark mode, overridden inline
backgroundColor: '#2c2c2e', // Default for dark mode, overridden inline
borderRadius: 8,
paddingHorizontal: 15,
color: "white", // Default for dark mode, overridden inline
color: 'white', // Default for dark mode, overridden inline
fontSize: 18,
marginRight: 10,
borderWidth: 2,
borderColor: "transparent", // Default, overridden for focus
borderColor: 'transparent', // Default, overridden for focus
},
searchButton: {
padding: 12,
@@ -162,16 +159,16 @@ const styles = StyleSheet.create({
borderRadius: 8,
},
focusedButton: {
backgroundColor: "#007bff",
backgroundColor: '#007bff',
transform: [{ scale: 1.1 }],
},
centerContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
justifyContent: 'center',
alignItems: 'center',
},
errorText: {
color: "red",
color: 'red',
},
listContent: {
paddingHorizontal: 10,