mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
checkpoint, made some fixes on continuewatchng
This commit is contained in:
parent
379bcc7507
commit
6d661f6c85
4 changed files with 185 additions and 36 deletions
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue