diff --git a/src/components/home/ContinueWatchingSection.tsx b/src/components/home/ContinueWatchingSection.tsx index d31c9f4f..69e51e38 100644 --- a/src/components/home/ContinueWatchingSection.tsx +++ b/src/components/home/ContinueWatchingSection.tsx @@ -278,10 +278,8 @@ const ContinueWatchingSection = React.forwardRef((props, re } } - // Create placeholders for each show if not already present + // Create placeholders (or update) for each show based on Trakt history for (const [showId, info] of Object.entries(latestWatchedByShow)) { - if (latestEpisodes[showId]) continue; // already handled via progress - const nextEpisode = info.episode + 1; const nextEpisodeId = `${showId}:${info.season}:${nextEpisode}`; @@ -300,7 +298,30 @@ const ContinueWatchingSection = React.forwardRef((props, re episodeTitle: `Episode ${nextEpisode}`, } as ContinueWatchingItem; - latestEpisodes[showId] = placeholder; + const existing = latestEpisodes[showId]; + if (!existing || existing.lastUpdated < info.watchedAt) { + latestEpisodes[showId] = placeholder; + } + + // Persist "watched" progress for the episode that Trakt reported + const watchedEpisodeId = `${showId}:${info.season}:${info.episode}`; + const existingProgress = allProgress[`series:${showId}:${watchedEpisodeId}`]; + const existingPercent = existingProgress ? (existingProgress.currentTime / existingProgress.duration) * 100 : 0; + + if (!existingProgress || existingPercent < 85) { + await storageService.setWatchProgress( + showId, + 'series', + { + currentTime: 1, + duration: 1, + lastUpdated: info.watchedAt, + traktSynced: true, + traktProgress: 100, + } as any, + `${info.season}:${info.episode}` + ); + } } catch (err) { logger.error('Failed to build placeholder from history:', err); } diff --git a/src/components/metadata/HeroSection.tsx b/src/components/metadata/HeroSection.tsx index 36aa99f2..a2e00cb0 100644 --- a/src/components/metadata/HeroSection.tsx +++ b/src/components/metadata/HeroSection.tsx @@ -154,18 +154,36 @@ const ActionButtons = React.memo(({ // For series, attempt to show the next episode label (e.g., "Play S02E05") if (type === 'series' && watchProgress?.episodeId) { + let seasonNum: number | null = null; + let episodeNum: number | null = null; + const parts = watchProgress.episodeId.split(':'); - if (parts.length >= 3) { - const seasonNum = parseInt(parts[parts.length - 2], 10); - const episodeNum = parseInt(parts[parts.length - 1], 10); - if (!isNaN(seasonNum) && !isNaN(episodeNum)) { - const nextSeason = seasonNum; - const nextEpisode = episodeNum + 1; - const seasonStr = nextSeason.toString().padStart(2, '0'); - const episodeStr = nextEpisode.toString().padStart(2, '0'); - return `Play S${seasonStr}E${episodeStr}`; + + if (parts.length === 3) { + // Format: showId:season:episode + seasonNum = parseInt(parts[1], 10); + episodeNum = parseInt(parts[2], 10); + } else if (parts.length === 2) { + // Format: season:episode (no show id) + seasonNum = parseInt(parts[0], 10); + episodeNum = parseInt(parts[1], 10); + } else { + // Try pattern s1e2 + const match = watchProgress.episodeId.match(/s(\d+)e(\d+)/i); + if (match) { + seasonNum = parseInt(match[1], 10); + episodeNum = parseInt(match[2], 10); } } + + if (seasonNum !== null && episodeNum !== null && !isNaN(seasonNum) && !isNaN(episodeNum)) { + // For watched episodes, show the NEXT episode number + const nextEpisode = episodeNum + 1; + const seasonStr = seasonNum.toString().padStart(2, '0'); + const episodeStr = nextEpisode.toString().padStart(2, '0'); + return `Play S${seasonStr}E${episodeStr}`; + } + // Fallback label if parsing fails return 'Play Next Episode'; } diff --git a/src/screens/MetadataScreen.tsx b/src/screens/MetadataScreen.tsx index cfb63ab3..a5ad925f 100644 --- a/src/screens/MetadataScreen.tsx +++ b/src/screens/MetadataScreen.tsx @@ -225,42 +225,77 @@ const MetadataScreen: React.FC = () => { let targetEpisodeId: string | undefined; if (progressPercent >= 85 && watchProgress?.episodeId) { - // Try to navigate to next episode + // Try to navigate to next episode – support multiple episodeId formats + let currentSeason: number | null = null; + let currentEpisode: number | null = null; + const parts = watchProgress.episodeId.split(':'); - if (parts.length >= 3) { - const currentSeason = parseInt(parts[parts.length - 2], 10); - const currentEpisode = parseInt(parts[parts.length - 1], 10); - // Find next episode in episodes array - const sortedEpisodes = [...episodes].sort((a, b) => { - if (a.season_number === b.season_number) { - return a.episode_number - b.episode_number; - } - return a.season_number - b.season_number; - }); - - const currentIndex = sortedEpisodes.findIndex(ep => ep.season_number === currentSeason && ep.episode_number === currentEpisode); - - if (currentIndex !== -1 && currentIndex + 1 < sortedEpisodes.length) { - const nextEp = sortedEpisodes[currentIndex + 1]; - targetEpisodeId = buildEpisodeId(nextEp); + if (parts.length === 3) { + // showId:season:episode + currentSeason = parseInt(parts[1], 10); + currentEpisode = parseInt(parts[2], 10); + } else if (parts.length === 2) { + // season:episode + currentSeason = parseInt(parts[0], 10); + currentEpisode = parseInt(parts[1], 10); + } else { + // pattern like s5e01 + const match = watchProgress.episodeId.match(/s(\d+)e(\d+)/i); + if (match) { + currentSeason = parseInt(match[1], 10); + currentEpisode = parseInt(match[2], 10); } } + + if (currentSeason !== null && currentEpisode !== null) { + // DIRECT APPROACH: Just create the next episode ID directly + // This ensures we navigate to the next episode even if it's not yet in our episodes array + const nextEpisodeId = `${id}:${currentSeason}:${currentEpisode + 1}`; + console.log(`[MetadataScreen] Created next episode ID directly: ${nextEpisodeId}`); + + // Still try to find the episode in our list to verify it exists + const nextEpisodeExists = episodes.some(ep => + ep.season_number === currentSeason && ep.episode_number === (currentEpisode + 1) + ); + + if (nextEpisodeExists) { + console.log(`[MetadataScreen] Verified next episode S${currentSeason}E${currentEpisode + 1} exists in episodes list`); + } else { + console.log(`[MetadataScreen] Warning: Next episode S${currentSeason}E${currentEpisode + 1} not found in episodes list, but proceeding anyway`); + } + + targetEpisodeId = nextEpisodeId; + } } // Fallback logic: if not finished or nextEp not found if (!targetEpisodeId) { targetEpisodeId = watchProgress?.episodeId || episodeId || (episodes.length > 0 ? buildEpisodeId(episodes[0]) : undefined); + console.log(`[MetadataScreen] Using fallback episode ID: ${targetEpisodeId}`); } if (targetEpisodeId) { - navigation.navigate('Streams', { id, type, episodeId: targetEpisodeId }); + // Ensure the episodeId has showId prefix (id:season:episode) + const epParts = targetEpisodeId.split(':'); + let normalizedEpisodeId = targetEpisodeId; + if (epParts.length === 2) { + normalizedEpisodeId = `${id}:${epParts[0]}:${epParts[1]}`; + } + console.log(`[MetadataScreen] Navigating to streams with episodeId: ${normalizedEpisodeId}`); + navigation.navigate('Streams', { id, type, episodeId: normalizedEpisodeId }); return; } } - // Default movie or unknown flow - navigation.navigate('Streams', { id, type, episodeId }); + // Normalize fallback episodeId too + let fallbackEpisodeId = episodeId; + if (episodeId && episodeId.split(':').length === 2) { + const p = episodeId.split(':'); + fallbackEpisodeId = `${id}:${p[0]}:${p[1]}`; + } + console.log(`[MetadataScreen] Navigating with fallback episodeId: ${fallbackEpisodeId}`); + navigation.navigate('Streams', { id, type, episodeId: fallbackEpisodeId }); }, [navigation, id, type, episodes, episodeId, watchProgressData.watchProgress]); const handleEpisodeSelect = useCallback((episode: Episode) => {