From 6d661f6c859d3941276808915b5e0822c0429ccd Mon Sep 17 00:00:00 2001 From: tapframe Date: Mon, 7 Jul 2025 16:00:44 +0530 Subject: [PATCH] checkpoint, made some fixes on continuewatchng --- .../home/ContinueWatchingSection.tsx | 118 ++++++++++++++---- src/components/metadata/HeroSection.tsx | 38 +++++- src/screens/MetadataScreen.tsx | 48 ++++++- src/services/storageService.ts | 17 ++- 4 files changed, 185 insertions(+), 36 deletions(-) diff --git a/src/components/home/ContinueWatchingSection.tsx b/src/components/home/ContinueWatchingSection.tsx index 80735d29..a6fb3e2c 100644 --- a/src/components/home/ContinueWatchingSection.tsx +++ b/src/components/home/ContinueWatchingSection.tsx @@ -116,10 +116,67 @@ const ContinueWatchingSection = React.forwardRef((props, re const episodeId = episodeIdParts.length > 0 ? episodeIdParts.join(':') : undefined; const progress = allProgress[key]; - // Skip items that are more than 85% complete (effectively finished) + // For series, skip episodes that are essentially finished (≥85%) + // For movies we still include them so users can "Watch Again" const progressPercent = (progress.currentTime / progress.duration) * 100; - if (progressPercent >= 85) { + // Skip fully watched movies + if (type === 'movie' && progressPercent >= 85) { + continue; + } + + if (type === 'series' && progressPercent >= 85) { + // Determine next episode ID by incrementing episode number + let nextSeason: number | undefined; + let nextEpisode: number | undefined; + let nextEpisodeId: string | undefined; + + if (episodeId) { + // Pattern 1: s1e1 + const match = episodeId.match(/s(\d+)e(\d+)/i); + if (match) { + const currentSeason = parseInt(match[1], 10); + const currentEpisode = parseInt(match[2], 10); + nextSeason = currentSeason; + nextEpisode = currentEpisode + 1; + nextEpisodeId = `s${nextSeason}e${nextEpisode}`; + } else { + // Pattern 2: id:season:episode + const parts = episodeId.split(':'); + if (parts.length >= 2) { + const seasonNum = parseInt(parts[parts.length - 2], 10); + const episodeNum = parseInt(parts[parts.length - 1], 10); + if (!isNaN(seasonNum) && !isNaN(episodeNum)) { + nextSeason = seasonNum; + nextEpisode = episodeNum + 1; + nextEpisodeId = `${id}:${nextSeason}:${nextEpisode}`; + } + } + } + } + + // Push placeholder for next episode with 0% progress + if (nextEpisodeId !== undefined) { + const basicContent = await catalogService.getBasicContentDetails(type, id); + const nextEpisodeItem = { + ...basicContent, + id, + type, + progress: 0, + lastUpdated: progress.lastUpdated, + season: nextSeason, + episode: nextEpisode, + episodeTitle: `Episode ${nextEpisode}`, + } as ContinueWatchingItem; + + // Store in latestEpisodes to ensure single entry per show + const existingLatest = latestEpisodes[id]; + if (!existingLatest || existingLatest.lastUpdated < nextEpisodeItem.lastUpdated) { + latestEpisodes[id] = nextEpisodeItem; + } + } + + // Skip adding the finished episode itself continue; } @@ -411,15 +468,22 @@ const ContinueWatchingSection = React.forwardRef((props, re {/* Content Details */} - - {item.name} - - - {Math.round(item.progress)}% - + {(() => { + const isUpNext = item.progress === 0; + return ( + + + {item.name} + + + {isUpNext ? 'Up Next' : `${Math.round(item.progress)}%`} + + + ); + })()} {/* Episode Info or Year */} @@ -450,22 +514,24 @@ const ContinueWatchingSection = React.forwardRef((props, re })()} {/* Progress Bar */} - - - + {item.progress > 0 && ( + + + + + + {Math.round(item.progress)}% watched + - - {Math.round(item.progress)}% watched - - + )} )} diff --git a/src/components/metadata/HeroSection.tsx b/src/components/metadata/HeroSection.tsx index 21625f26..36aa99f2 100644 --- a/src/components/metadata/HeroSection.tsx +++ b/src/components/metadata/HeroSection.tsx @@ -143,11 +143,36 @@ const ActionButtons = React.memo(({ }, [isWatched]); const finalPlayButtonText = useMemo(() => { - if (isWatched) { + if (!isWatched) { + return playButtonText; + } + + // If content is a movie, keep existing "Watch Again" label + if (type === 'movie') { return 'Watch Again'; } - return playButtonText; - }, [isWatched, playButtonText]); + + // For series, attempt to show the next episode label (e.g., "Play S02E05") + if (type === 'series' && watchProgress?.episodeId) { + 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}`; + } + } + // Fallback label if parsing fails + return 'Play Next Episode'; + } + + // Default fallback + return 'Play'; + }, [isWatched, playButtonText, type, watchProgress]); return ( @@ -157,7 +182,12 @@ const ActionButtons = React.memo(({ activeOpacity={0.85} > { + if (isWatched) { + return type === 'movie' ? 'replay' : 'play-arrow'; + } + return playButtonText === 'Resume' ? 'play-circle-outline' : 'play-arrow'; + })()} size={24} color={isWatched ? "#fff" : "#000"} /> diff --git a/src/screens/MetadataScreen.tsx b/src/screens/MetadataScreen.tsx index e771f007..cfb63ab3 100644 --- a/src/screens/MetadataScreen.tsx +++ b/src/screens/MetadataScreen.tsx @@ -209,15 +209,57 @@ const MetadataScreen: React.FC = () => { const handleShowStreams = useCallback(() => { const { watchProgress } = watchProgressData; + + // Helper to build episodeId from episode object + const buildEpisodeId = (ep: any): string => { + return ep.stremioId || `${id}:${ep.season_number}:${ep.episode_number}`; + }; + if (type === 'series') { - const targetEpisodeId = watchProgress?.episodeId || episodeId || (episodes.length > 0 ? - (episodes[0].stremioId || `${id}:${episodes[0].season_number}:${episodes[0].episode_number}`) : undefined); - + // Determine if current episode is finished + let progressPercent = 0; + if (watchProgress && watchProgress.duration > 0) { + progressPercent = (watchProgress.currentTime / watchProgress.duration) * 100; + } + + let targetEpisodeId: string | undefined; + + if (progressPercent >= 85 && watchProgress?.episodeId) { + // Try to navigate to next episode + 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); + } + } + } + + // Fallback logic: if not finished or nextEp not found + if (!targetEpisodeId) { + targetEpisodeId = watchProgress?.episodeId || episodeId || (episodes.length > 0 ? buildEpisodeId(episodes[0]) : undefined); + } + if (targetEpisodeId) { navigation.navigate('Streams', { id, type, episodeId: targetEpisodeId }); return; } } + + // Default movie or unknown flow navigation.navigate('Streams', { id, type, episodeId }); }, [navigation, id, type, episodes, episodeId, watchProgressData.watchProgress]); diff --git a/src/services/storageService.ts b/src/services/storageService.ts index 61d49820..9ef38bc8 100644 --- a/src/services/storageService.ts +++ b/src/services/storageService.ts @@ -224,13 +224,24 @@ class StorageService { try { const existingProgress = await this.getWatchProgress(id, type, episodeId); if (existingProgress) { + // Preserve the highest Trakt progress and currentTime values to avoid accidental regressions + const highestTraktProgress = (() => { + if (traktProgress === undefined) return existingProgress.traktProgress; + if (existingProgress.traktProgress === undefined) return traktProgress; + return Math.max(traktProgress, existingProgress.traktProgress); + })(); + + const highestCurrentTime = (() => { + if (!exactTime || exactTime <= 0) return existingProgress.currentTime; + return Math.max(exactTime, existingProgress.currentTime); + })(); + const updatedProgress: WatchProgress = { ...existingProgress, traktSynced, traktLastSynced: traktSynced ? Date.now() : existingProgress.traktLastSynced, - traktProgress: traktProgress !== undefined ? traktProgress : existingProgress.traktProgress, - // Update current time with exact time if provided - ...(exactTime && exactTime > 0 && { currentTime: exactTime }) + traktProgress: highestTraktProgress, + currentTime: highestCurrentTime, }; await this.setWatchProgress(id, type, updatedProgress, episodeId); }