import React, { useState, useEffect } from 'react'; import { View, Text, TouchableOpacity, ScrollView, ActivityIndicator, Dimensions } from 'react-native'; import { MaterialIcons } from '@expo/vector-icons'; import Animated, { FadeIn, FadeOut, SlideInRight, SlideOutRight, } from 'react-native-reanimated'; import { Episode } from '../../../types/metadata'; import { EpisodeCard } from '../cards/EpisodeCard'; import { storageService } from '../../../services/storageService'; import { TraktService } from '../../../services/traktService'; import { logger } from '../../../utils/logger'; interface EpisodesModalProps { showEpisodesModal: boolean; setShowEpisodesModal: (show: boolean) => void; groupedEpisodes: { [seasonNumber: number]: Episode[] }; currentEpisode?: { season: number; episode: number }; metadata?: { poster?: string; id?: string }; onSelectEpisode: (episode: Episode) => void; } const { width } = Dimensions.get('window'); const MENU_WIDTH = Math.min(width * 0.85, 400); export const EpisodesModal: React.FC = ({ showEpisodesModal, setShowEpisodesModal, groupedEpisodes, currentEpisode, metadata, onSelectEpisode, }) => { const [selectedSeason, setSelectedSeason] = useState(currentEpisode?.season || 1); const [episodeProgress, setEpisodeProgress] = useState<{ [key: string]: { currentTime: number; duration: number; lastUpdated: number } }>({}); const [tmdbEpisodeOverrides, setTmdbEpisodeOverrides] = useState<{ [epKey: string]: { vote_average?: number; runtime?: number; still_path?: string } }>({}); const [currentTheme, setCurrentTheme] = useState({ colors: { text: '#FFFFFF', textMuted: 'rgba(255,255,255,0.6)', mediumEmphasis: 'rgba(255,255,255,0.7)', primary: '#3B82F6', white: '#FFFFFF', elevation2: 'rgba(255,255,255,0.05)' } }); // Initialize season only when modal opens useEffect(() => { if (showEpisodesModal && currentEpisode?.season) { setSelectedSeason(currentEpisode.season); } }, [showEpisodesModal, currentEpisode?.season]); const loadEpisodesProgress = async () => { if (!metadata?.id) return; const allProgress = await storageService.getAllWatchProgress(); const progress: { [key: string]: { currentTime: number; duration: number; lastUpdated: number } } = {}; const currentSeasonEpisodes = groupedEpisodes[selectedSeason] || []; currentSeasonEpisodes.forEach(episode => { const episodeId = episode.stremioId || `${metadata.id}:${episode.season_number}:${episode.episode_number}`; const key = `series:${metadata.id}:${episodeId}`; if (allProgress[key]) { progress[episodeId] = { currentTime: allProgress[key].currentTime, duration: allProgress[key].duration, lastUpdated: allProgress[key].lastUpdated }; } }); // Trakt watched-history integration try { const traktService = TraktService.getInstance(); const isAuthed = await traktService.isAuthenticated(); if (isAuthed && metadata?.id) { const historyItems = await traktService.getWatchedEpisodesHistory(1, 400); historyItems.forEach(item => { if (item.type !== 'episode') return; const showImdb = item.show?.ids?.imdb ? `tt${item.show.ids.imdb.replace(/^tt/, '')}` : null; if (!showImdb || showImdb !== metadata.id) return; const season = item.episode?.season; const epNum = item.episode?.number; if (season === undefined || epNum === undefined) return; const episodeId = `${metadata.id}:${season}:${epNum}`; const watchedAt = new Date(item.watched_at).getTime(); const traktProgressEntry = { currentTime: 1, duration: 1, lastUpdated: watchedAt, }; const existing = progress[episodeId]; const existingPercent = existing ? (existing.currentTime / existing.duration) * 100 : 0; if (!existing || existingPercent < 85) { progress[episodeId] = traktProgressEntry; } }); } } catch (err) { logger.error('[EpisodesModal] Failed to merge Trakt history:', err); } setEpisodeProgress(progress); }; useEffect(() => { loadEpisodesProgress(); }, [selectedSeason, metadata?.id]); const handleClose = () => { setShowEpisodesModal(false); }; if (!showEpisodesModal) return null; const seasons = Object.keys(groupedEpisodes).map(Number).sort((a, b) => a - b); const currentSeasonEpisodes = groupedEpisodes[selectedSeason] || []; const isEpisodeCurrent = (episode: Episode) => { return currentEpisode && episode.season_number === currentEpisode.season && episode.episode_number === currentEpisode.episode; }; return ( <> {/* Backdrop */} {/* Side Menu */} {/* Header */} Episodes {/* Season Selector */} {seasons.map((season) => ( setSelectedSeason(season)} activeOpacity={0.7} > Season {season} ))} {/* Episodes List */} {currentSeasonEpisodes.length > 0 ? ( currentSeasonEpisodes.map((episode, index) => { const isCurrent = isEpisodeCurrent(episode); return ( onSelectEpisode(episode)} currentTheme={currentTheme} isCurrent={isCurrent} /> ); }) ) : ( No episodes available for Season {selectedSeason} )} ); };