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,17 +1,9 @@
import React, { useState, useEffect, useRef } from "react";
import {
Modal,
View,
Text,
TextInput,
StyleSheet,
Pressable,
useColorScheme,
} from "react-native";
import { SettingsManager } from "@/services/storage";
import { moonTVApi } from "@/services/api";
import { ThemedText } from "./ThemedText";
import { ThemedView } from "./ThemedView";
import React, { useState, useEffect, useRef } from 'react';
import { Modal, View, Text, TextInput, StyleSheet, Pressable, useColorScheme } from 'react-native';
import { SettingsManager } from '@/services/storage';
import { api } from '@/services/api';
import { ThemedText } from './ThemedText';
import { ThemedView } from './ThemedView';
interface SettingsModalProps {
visible: boolean;
@@ -19,19 +11,15 @@ interface SettingsModalProps {
onSave: () => void;
}
export const SettingsModal: React.FC<SettingsModalProps> = ({
visible,
onCancel,
onSave,
}) => {
const [apiUrl, setApiUrl] = useState("");
export const SettingsModal: React.FC<SettingsModalProps> = ({ visible, onCancel, onSave }) => {
const [apiUrl, setApiUrl] = useState('');
const [isInputFocused, setIsInputFocused] = useState(false);
const colorScheme = useColorScheme();
const inputRef = useRef<TextInput>(null);
useEffect(() => {
if (visible) {
SettingsManager.get().then((settings) => {
SettingsManager.get().then(settings => {
setApiUrl(settings.apiBaseUrl);
});
const timer = setTimeout(() => {
@@ -43,19 +31,19 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({
const handleSave = async () => {
await SettingsManager.save({ apiBaseUrl: apiUrl });
moonTVApi.setBaseUrl(apiUrl);
api.setBaseUrl(apiUrl);
onSave();
};
const styles = StyleSheet.create({
modalContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "rgba(0, 0, 0, 0.6)",
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.6)',
},
modalContent: {
width: "80%",
width: '80%',
maxWidth: 500,
padding: 24,
borderRadius: 12,
@@ -63,9 +51,9 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({
},
title: {
fontSize: 24,
fontWeight: "bold",
fontWeight: 'bold',
marginBottom: 20,
textAlign: "center",
textAlign: 'center',
},
input: {
height: 50,
@@ -74,43 +62,43 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({
paddingHorizontal: 15,
fontSize: 16,
marginBottom: 24,
backgroundColor: colorScheme === "dark" ? "#3a3a3c" : "#f0f0f0",
color: colorScheme === "dark" ? "white" : "black",
borderColor: "transparent",
backgroundColor: colorScheme === 'dark' ? '#3a3a3c' : '#f0f0f0',
color: colorScheme === 'dark' ? 'white' : 'black',
borderColor: 'transparent',
},
inputFocused: {
borderColor: "#007AFF",
shadowColor: "#007AFF",
borderColor: '#007AFF',
shadowColor: '#007AFF',
shadowOffset: { width: 0, height: 0 },
shadowOpacity: 0.8,
shadowRadius: 10,
elevation: 5,
},
buttonContainer: {
flexDirection: "row",
justifyContent: "space-around",
flexDirection: 'row',
justifyContent: 'space-around',
},
button: {
flex: 1,
paddingVertical: 12,
borderRadius: 8,
alignItems: "center",
alignItems: 'center',
marginHorizontal: 8,
},
buttonSave: {
backgroundColor: "#007AFF",
backgroundColor: '#007AFF',
},
buttonCancel: {
backgroundColor: colorScheme === "dark" ? "#444" : "#ccc",
backgroundColor: colorScheme === 'dark' ? '#444' : '#ccc',
},
buttonText: {
color: "white",
color: 'white',
fontSize: 18,
fontWeight: "500",
fontWeight: '500',
},
focusedButton: {
transform: [{ scale: 1.05 }],
shadowColor: "#000",
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 5,
@@ -119,12 +107,7 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({
});
return (
<Modal
animationType="fade"
transparent={true}
visible={visible}
onRequestClose={onCancel}
>
<Modal animationType="fade" transparent={true} visible={visible} onRequestClose={onCancel}>
<View style={styles.modalContainer}>
<ThemedView style={styles.modalContent}>
<ThemedText style={styles.title}></ThemedText>
@@ -134,7 +117,7 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({
value={apiUrl}
onChangeText={setApiUrl}
placeholder="输入 API 地址"
placeholderTextColor={colorScheme === "dark" ? "#888" : "#555"}
placeholderTextColor={colorScheme === 'dark' ? '#888' : '#555'}
autoCapitalize="none"
autoCorrect={false}
onFocus={() => setIsInputFocused(true)}
@@ -142,21 +125,13 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({
/>
<View style={styles.buttonContainer}>
<Pressable
style={({ focused }) => [
styles.button,
styles.buttonCancel,
focused && styles.focusedButton,
]}
style={({ focused }) => [styles.button, styles.buttonCancel, focused && styles.focusedButton]}
onPress={onCancel}
>
<Text style={styles.buttonText}></Text>
</Pressable>
<Pressable
style={({ focused }) => [
styles.button,
styles.buttonSave,
focused && styles.focusedButton,
]}
style={({ focused }) => [styles.button, styles.buttonSave, focused && styles.focusedButton]}
onPress={handleSave}
>
<Text style={styles.buttonText}></Text>

View File

@@ -1,23 +1,11 @@
import React, { useState, useEffect, useCallback, useRef } from "react";
import {
View,
Text,
Image,
StyleSheet,
Pressable,
TouchableOpacity,
Alert,
} from "react-native";
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
} from "react-native-reanimated";
import { useRouter } from "expo-router";
import { Heart, Star, Play, Trash2 } from "lucide-react-native";
import { FavoriteManager, PlayRecordManager } from "@/services/storage";
import { API, moonTVApi } from "@/services/api";
import { ThemedText } from "@/components/ThemedText";
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { View, Text, Image, StyleSheet, Pressable, TouchableOpacity, Alert } from 'react-native';
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
import { useRouter } from 'expo-router';
import { Heart, Star, Play, Trash2 } from 'lucide-react-native';
import { FavoriteManager, PlayRecordManager } from '@/services/storage';
import { API, api } from '@/services/api';
import { ThemedText } from '@/components/ThemedText';
interface VideoCardProps {
id: string;
@@ -71,12 +59,12 @@ export default function VideoCard({
// 如果有播放进度,直接转到播放页面
if (progress !== undefined && episodeIndex !== undefined) {
router.push({
pathname: "/play",
pathname: '/play',
params: { source, id, episodeIndex },
});
} else {
router.push({
pathname: "/detail",
pathname: '/detail',
params: { source, q: title },
});
}
@@ -100,14 +88,14 @@ export default function VideoCard({
longPressTriggered.current = true;
// Show confirmation dialog to delete play record
Alert.alert("删除观看记录", `确定要删除"${title}"的观看记录吗?`, [
Alert.alert('删除观看记录', `确定要删除"${title}"的观看记录吗?`, [
{
text: "取消",
style: "cancel",
text: '取消',
style: 'cancel',
},
{
text: "删除",
style: "destructive",
text: '删除',
style: 'destructive',
onPress: async () => {
try {
// Delete from local storage
@@ -119,11 +107,11 @@ export default function VideoCard({
}
// 如果没有回调函数,则使用导航刷新作为备选方案
else if (router.canGoBack()) {
router.replace("/");
router.replace('/');
}
} catch (error) {
console.error("Failed to delete play record:", error);
Alert.alert("错误", "删除观看记录失败,请重试");
console.error('Failed to delete play record:', error);
Alert.alert('错误', '删除观看记录失败,请重试');
}
},
},
@@ -131,8 +119,7 @@ export default function VideoCard({
};
// 是否是继续观看的视频
const isContinueWatching =
progress !== undefined && progress > 0 && progress < 1;
const isContinueWatching = progress !== undefined && progress > 0 && progress < 1;
return (
<Animated.View style={[styles.wrapper, animatedStyle]}>
@@ -146,18 +133,13 @@ export default function VideoCard({
delayLongPress={1000}
>
<View style={styles.card}>
<Image
source={{ uri: api.getImageProxyUrl(poster) }}
style={styles.poster}
/>
<Image source={{ uri: api.getImageProxyUrl(poster) }} style={styles.poster} />
{isFocused && (
<View style={styles.overlay}>
{isContinueWatching && (
<View style={styles.continueWatchingBadge}>
<Play size={16} color="#ffffff" fill="#ffffff" />
<ThemedText style={styles.continueWatchingText}>
</ThemedText>
<ThemedText style={styles.continueWatchingText}></ThemedText>
</View>
)}
</View>
@@ -166,12 +148,7 @@ export default function VideoCard({
{/* 进度条 */}
{isContinueWatching && (
<View style={styles.progressContainer}>
<View
style={[
styles.progressBar,
{ width: `${(progress || 0) * 100}%` },
]}
/>
<View style={[styles.progressBar, { width: `${(progress || 0) * 100}%` }]} />
</View>
)}
@@ -197,8 +174,7 @@ export default function VideoCard({
{isContinueWatching && !isFocused && (
<View style={styles.infoRow}>
<ThemedText style={styles.continueLabel}>
{episodeIndex! + 1} {" "}
{Math.round((progress || 0) * 100)}%
{episodeIndex! + 1} {Math.round((progress || 0) * 100)}%
</ThemedText>
</View>
)}
@@ -216,126 +192,126 @@ const styles = StyleSheet.create({
marginHorizontal: 8,
},
pressable: {
alignItems: "center",
alignItems: 'center',
},
card: {
width: CARD_WIDTH,
height: CARD_HEIGHT,
borderRadius: 8,
backgroundColor: "#222",
overflow: "hidden",
backgroundColor: '#222',
overflow: 'hidden',
},
poster: {
width: "100%",
height: "100%",
width: '100%',
height: '100%',
},
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: "rgba(0,0,0,0.3)",
justifyContent: "center",
alignItems: "center",
backgroundColor: 'rgba(0,0,0,0.3)',
justifyContent: 'center',
alignItems: 'center',
},
buttonRow: {
position: "absolute",
position: 'absolute',
top: 8,
left: 8,
flexDirection: "row",
flexDirection: 'row',
gap: 8,
},
iconButton: {
padding: 4,
},
favButton: {
position: "absolute",
position: 'absolute',
top: 8,
left: 8,
},
ratingContainer: {
position: "absolute",
position: 'absolute',
top: 8,
right: 8,
flexDirection: "row",
alignItems: "center",
backgroundColor: "rgba(0, 0, 0, 0.7)",
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.7)',
borderRadius: 6,
paddingHorizontal: 6,
paddingVertical: 3,
},
ratingText: {
color: "#FFD700",
color: '#FFD700',
fontSize: 12,
fontWeight: "bold",
fontWeight: 'bold',
marginLeft: 4,
},
infoContainer: {
width: CARD_WIDTH,
marginTop: 8,
alignItems: "flex-start", // Align items to the start
alignItems: 'flex-start', // Align items to the start
marginBottom: 16,
paddingHorizontal: 4, // Add some padding
},
infoRow: {
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
flexDirection: 'row',
justifyContent: 'space-between',
width: '100%',
},
title: {
color: "white",
color: 'white',
fontSize: 16,
fontWeight: "bold",
textAlign: "center",
fontWeight: 'bold',
textAlign: 'center',
},
yearBadge: {
position: "absolute",
position: 'absolute',
top: 8,
right: 8,
backgroundColor: "rgba(0, 0, 0, 0.7)",
backgroundColor: 'rgba(0, 0, 0, 0.7)',
borderRadius: 6,
paddingHorizontal: 6,
paddingVertical: 3,
},
sourceNameBadge: {
position: "absolute",
position: 'absolute',
top: 8,
left: 8,
backgroundColor: "rgba(0, 0, 0, 0.7)",
backgroundColor: 'rgba(0, 0, 0, 0.7)',
borderRadius: 6,
paddingHorizontal: 6,
paddingVertical: 3,
},
badgeText: {
color: "white",
color: 'white',
fontSize: 12,
fontWeight: "bold",
fontWeight: 'bold',
},
progressContainer: {
position: "absolute",
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: 3,
backgroundColor: "rgba(0, 0, 0, 0.5)",
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
progressBar: {
height: 3,
backgroundColor: "#ff0000",
backgroundColor: '#ff0000',
},
continueWatchingBadge: {
flexDirection: "row",
alignItems: "center",
backgroundColor: "rgba(255, 0, 0, 0.8)",
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'rgba(255, 0, 0, 0.8)',
paddingHorizontal: 10,
paddingVertical: 5,
borderRadius: 5,
},
continueWatchingText: {
color: "white",
color: 'white',
marginLeft: 5,
fontSize: 12,
fontWeight: "bold",
fontWeight: 'bold',
},
continueLabel: {
color: "#ff5252",
color: '#ff5252',
fontSize: 12,
},
});