import React, { useState, useEffect, useRef, forwardRef } from "react"; import { View, Text, Image, StyleSheet, TouchableOpacity, Alert, Animated } from "react-native"; import { useRouter } from "expo-router"; import { Star, Play } from "lucide-react-native"; import { PlayRecordManager } from "@/services/storage"; import { API } from "@/services/api"; import { ThemedText } from "@/components/ThemedText"; import { Colors } from "@/constants/Colors"; import { useResponsiveLayout } from "@/hooks/useResponsiveLayout"; import { DeviceUtils } from "@/utils/DeviceUtils"; import Logger from '@/utils/Logger'; const logger = Logger.withTag('VideoCardMobile'); interface VideoCardMobileProps extends React.ComponentProps { id: string; source: string; title: string; poster: string; year?: string; rate?: string; sourceName?: string; progress?: number; playTime?: number; episodeIndex?: number; totalEpisodes?: number; onFocus?: () => void; onRecordDeleted?: () => void; api: API; } const VideoCardMobile = forwardRef( ( { id, source, title, poster, year, rate, sourceName, progress, episodeIndex, onFocus, onRecordDeleted, api, playTime = 0, }: VideoCardMobileProps, ref ) => { const router = useRouter(); const { cardWidth, cardHeight, spacing } = useResponsiveLayout(); const [fadeAnim] = useState(new Animated.Value(0)); const longPressTriggered = useRef(false); const handlePress = () => { if (longPressTriggered.current) { longPressTriggered.current = false; return; } if (progress !== undefined && episodeIndex !== undefined) { router.push({ pathname: "/play", params: { source, id, episodeIndex: episodeIndex - 1, title, position: playTime * 1000 }, }); } else { router.push({ pathname: "/detail", params: { source, q: title }, }); } }; useEffect(() => { Animated.timing(fadeAnim, { toValue: 1, duration: DeviceUtils.getAnimationDuration(300), delay: Math.random() * 100, useNativeDriver: true, }).start(); }, [fadeAnim]); const handleLongPress = () => { if (progress === undefined) return; longPressTriggered.current = true; Alert.alert("删除观看记录", `确定要删除"${title}"的观看记录吗?`, [ { text: "取消", style: "cancel", }, { text: "删除", style: "destructive", onPress: async () => { try { await PlayRecordManager.remove(source, id); onRecordDeleted?.(); } catch (error) { logger.info("Failed to delete play record:", error); Alert.alert("错误", "删除观看记录失败,请重试"); } }, }, ]); }; const isContinueWatching = progress !== undefined && progress > 0 && progress < 1; const styles = createMobileStyles(cardWidth, cardHeight, spacing); return ( {/* 进度条 */} {isContinueWatching && ( )} {/* 继续观看标识 */} {isContinueWatching && ( 继续 )} {/* 评分 */} {rate && ( {rate} )} {/* 年份 */} {year && ( {year} )} {/* 来源 */} {sourceName && ( {sourceName} )} {title} {isContinueWatching && ( 第{episodeIndex! + 1}集 {Math.round((progress || 0) * 100)}% )} ); } ); VideoCardMobile.displayName = "VideoCardMobile"; const createMobileStyles = (cardWidth: number, cardHeight: number, spacing: number) => { return StyleSheet.create({ wrapper: { width: cardWidth, marginBottom: spacing, }, pressable: { alignItems: 'flex-start', }, card: { width: cardWidth, height: cardHeight, borderRadius: 8, backgroundColor: "#222", overflow: "hidden", }, poster: { width: "100%", height: "100%", resizeMode: 'cover', }, progressContainer: { position: "absolute", bottom: 0, left: 0, right: 0, height: 3, backgroundColor: "rgba(0, 0, 0, 0.6)", }, progressBar: { height: 3, backgroundColor: Colors.dark.primary, }, continueWatchingBadge: { position: 'absolute', top: 6, left: 6, flexDirection: 'row', alignItems: 'center', backgroundColor: Colors.dark.primary, paddingHorizontal: 6, paddingVertical: 3, borderRadius: 4, }, continueWatchingText: { color: "white", marginLeft: 3, fontSize: 10, fontWeight: "bold", }, ratingContainer: { position: "absolute", top: 6, right: 6, flexDirection: "row", alignItems: "center", backgroundColor: "rgba(0, 0, 0, 0.7)", borderRadius: 4, paddingHorizontal: 4, paddingVertical: 2, }, ratingText: { color: "#FFD700", fontSize: 10, fontWeight: "bold", marginLeft: 2, }, yearBadge: { position: "absolute", bottom: 24, right: 6, backgroundColor: "rgba(0, 0, 0, 0.7)", borderRadius: 4, paddingHorizontal: 4, paddingVertical: 2, }, sourceNameBadge: { position: "absolute", bottom: 6, left: 6, backgroundColor: "rgba(0, 0, 0, 0.7)", borderRadius: 4, paddingHorizontal: 4, paddingVertical: 2, }, badgeText: { color: "white", fontSize: 9, fontWeight: "500", }, infoContainer: { width: cardWidth, marginTop: 6, paddingHorizontal: 2, }, title: { fontSize: 13, lineHeight: 16, marginBottom: 2, }, continueLabel: { color: Colors.dark.primary, fontSize: 11, }, }); }; export default VideoCardMobile;