checkpoint, made some fixes on continuewatchng

This commit is contained in:
tapframe 2025-07-07 16:00:44 +05:30
parent 379bcc7507
commit 6d661f6c85
4 changed files with 185 additions and 36 deletions

View file

@ -116,10 +116,67 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((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<ContinueWatchingRef>((props, re
{/* Content Details */}
<View style={styles.contentDetails}>
<View style={styles.titleRow}>
<Text
style={[styles.contentTitle, { color: currentTheme.colors.highEmphasis }]}
numberOfLines={1}
>
{item.name}
</Text>
<View style={[styles.progressBadge, { backgroundColor: currentTheme.colors.primary }]}>
<Text style={styles.progressText}>{Math.round(item.progress)}%</Text>
</View>
{(() => {
const isUpNext = item.progress === 0;
return (
<View style={styles.titleRow}>
<Text
style={[styles.contentTitle, { color: currentTheme.colors.highEmphasis }]}
numberOfLines={1}
>
{item.name}
</Text>
<View style={[styles.progressBadge, { backgroundColor: currentTheme.colors.primary }]}>
<Text style={styles.progressText}>{isUpNext ? 'Up Next' : `${Math.round(item.progress)}%`}</Text>
</View>
</View>
);
})()}
</View>
{/* Episode Info or Year */}
@ -450,22 +514,24 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
})()}
{/* Progress Bar */}
<View style={styles.wideProgressContainer}>
<View style={styles.wideProgressTrack}>
<View
style={[
styles.wideProgressBar,
{
width: `${item.progress}%`,
backgroundColor: currentTheme.colors.primary
}
]}
/>
{item.progress > 0 && (
<View style={styles.wideProgressContainer}>
<View style={styles.wideProgressTrack}>
<View
style={[
styles.wideProgressBar,
{
width: `${item.progress}%`,
backgroundColor: currentTheme.colors.primary
}
]}
/>
</View>
<Text style={[styles.progressLabel, { color: currentTheme.colors.textMuted }]}>
{Math.round(item.progress)}% watched
</Text>
</View>
<Text style={[styles.progressLabel, { color: currentTheme.colors.textMuted }]}>
{Math.round(item.progress)}% watched
</Text>
</View>
)}
</View>
</TouchableOpacity>
)}

View file

@ -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 (
<Animated.View style={[styles.actionButtons, animatedStyle]}>
@ -157,7 +182,12 @@ const ActionButtons = React.memo(({
activeOpacity={0.85}
>
<MaterialIcons
name={isWatched ? "replay" : (playButtonText === 'Resume' ? "play-circle-outline" : "play-arrow")}
name={(() => {
if (isWatched) {
return type === 'movie' ? 'replay' : 'play-arrow';
}
return playButtonText === 'Resume' ? 'play-circle-outline' : 'play-arrow';
})()}
size={24}
color={isWatched ? "#fff" : "#000"}
/>

View file

@ -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]);

View file

@ -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);
}