Ios #14

Merged
tapframe merged 88 commits from ios into main 2025-06-20 13:54:29 +00:00
5 changed files with 161 additions and 51 deletions
Showing only changes of commit c0a63b3c53 - Show all commits

View file

@ -393,16 +393,16 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
<View style={styles.episodeRow}>
<Text style={[styles.episodeText, { color: currentTheme.colors.mediumEmphasis }]}>
Season {item.season}
</Text>
{item.episodeTitle && (
</Text>
{item.episodeTitle && (
<Text
style={[styles.episodeTitle, { color: currentTheme.colors.mediumEmphasis }]}
numberOfLines={1}
>
{item.episodeTitle}
</Text>
)}
</View>
{item.episodeTitle}
</Text>
)}
</View>
);
} else {
return (
@ -416,15 +416,15 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
{/* Progress Bar */}
<View style={styles.wideProgressContainer}>
<View style={styles.wideProgressTrack}>
<View
style={[
<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

View file

@ -40,16 +40,17 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
const { width } = useWindowDimensions();
const isTablet = width > 768;
const isDarkMode = useColorScheme() === 'dark';
const [episodeProgress, setEpisodeProgress] = useState<{ [key: string]: { currentTime: number; duration: number } }>({});
const [episodeProgress, setEpisodeProgress] = useState<{ [key: string]: { currentTime: number; duration: number; lastUpdated: number } }>({});
// Add ref for the season selector ScrollView
// Add refs for the scroll views
const seasonScrollViewRef = useRef<ScrollView | null>(null);
const episodeScrollViewRef = useRef<ScrollView | null>(null);
const loadEpisodesProgress = async () => {
if (!metadata?.id) return;
const allProgress = await storageService.getAllWatchProgress();
const progress: { [key: string]: { currentTime: number; duration: number } } = {};
const progress: { [key: string]: { currentTime: number; duration: number; lastUpdated: number } } = {};
episodes.forEach(episode => {
const episodeId = episode.stremioId || `${metadata.id}:${episode.season_number}:${episode.episode_number}`;
@ -57,7 +58,8 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
if (allProgress[key]) {
progress[episodeId] = {
currentTime: allProgress[key].currentTime,
duration: allProgress[key].duration
duration: allProgress[key].duration,
lastUpdated: allProgress[key].lastUpdated
};
}
});
@ -65,6 +67,67 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
setEpisodeProgress(progress);
};
// Function to find and scroll to the most recently watched episode
const scrollToMostRecentEpisode = () => {
if (!metadata?.id || !episodeScrollViewRef.current || settings.episodeLayoutStyle !== 'horizontal') {
console.log('[SeriesContent] Scroll conditions not met:', {
hasMetadataId: !!metadata?.id,
hasScrollRef: !!episodeScrollViewRef.current,
isHorizontal: settings.episodeLayoutStyle === 'horizontal'
});
return;
}
const currentSeasonEpisodes = groupedEpisodes[selectedSeason] || [];
if (currentSeasonEpisodes.length === 0) {
console.log('[SeriesContent] No episodes in current season:', selectedSeason);
return;
}
// Find the most recently watched episode in the current season
let mostRecentEpisodeIndex = -1;
let mostRecentTimestamp = 0;
let mostRecentEpisodeName = '';
currentSeasonEpisodes.forEach((episode, index) => {
const episodeId = episode.stremioId || `${metadata.id}:${episode.season_number}:${episode.episode_number}`;
const progress = episodeProgress[episodeId];
if (progress && progress.lastUpdated > mostRecentTimestamp && progress.currentTime > 0) {
mostRecentTimestamp = progress.lastUpdated;
mostRecentEpisodeIndex = index;
mostRecentEpisodeName = episode.name;
}
});
console.log('[SeriesContent] Episode scroll analysis:', {
totalEpisodes: currentSeasonEpisodes.length,
mostRecentIndex: mostRecentEpisodeIndex,
mostRecentEpisode: mostRecentEpisodeName,
selectedSeason
});
// Scroll to the most recently watched episode if found
if (mostRecentEpisodeIndex >= 0) {
const cardWidth = isTablet ? width * 0.4 + 16 : width * 0.85 + 16;
const scrollPosition = mostRecentEpisodeIndex * cardWidth;
console.log('[SeriesContent] Scrolling to episode:', {
index: mostRecentEpisodeIndex,
cardWidth,
scrollPosition,
episodeName: mostRecentEpisodeName
});
setTimeout(() => {
episodeScrollViewRef.current?.scrollTo({
x: scrollPosition,
animated: true
});
}, 500); // Delay to ensure the season has loaded
}
};
// Initial load of watch progress
useEffect(() => {
loadEpisodesProgress();
@ -96,6 +159,13 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
}
}, [selectedSeason, groupedEpisodes]);
// Add effect to scroll to most recently watched episode when season changes or progress loads
useEffect(() => {
if (Object.keys(episodeProgress).length > 0 && selectedSeason) {
scrollToMostRecentEpisode();
}
}, [selectedSeason, episodeProgress, settings.episodeLayoutStyle, groupedEpisodes]);
if (loadingSeasons) {
return (
<View style={styles.centeredContainer}>
@ -480,6 +550,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
{settings.episodeLayoutStyle === 'horizontal' ? (
// Horizontal Layout (Netflix-style)
<ScrollView
ref={episodeScrollViewRef}
horizontal
showsHorizontalScrollIndicator={false}
style={styles.episodeList}

View file

@ -11,6 +11,7 @@ import { logger } from '../utils/logger';
import { usePersistentSeasons } from './usePersistentSeasons';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Stream } from '../types/metadata';
import { storageService } from '../services/storageService';
// Constants for timeouts and retries
const API_TIMEOUT = 10000; // 10 seconds
@ -589,14 +590,61 @@ export const useMetadata = ({ id, type }: UseMetadataProps): UseMetadataReturn =
// Get the first available season as fallback
const firstSeason = Math.min(...Object.keys(allEpisodes).map(Number));
// Get saved season from persistence, fallback to first season if not found
const persistedSeason = getSeason(id, firstSeason);
// Check for watch progress to auto-select season
let selectedSeasonNumber = firstSeason;
// Set the selected season from persistence
setSelectedSeason(persistedSeason);
try {
// Check watch progress for auto-season selection
const allProgress = await storageService.getAllWatchProgress();
// Find the most recently watched episode for this series
let mostRecentEpisodeId = '';
let mostRecentTimestamp = 0;
Object.entries(allProgress).forEach(([key, progress]) => {
if (key.includes(`series:${id}:`)) {
const episodeId = key.split(`series:${id}:`)[1];
if (progress.lastUpdated > mostRecentTimestamp && progress.currentTime > 0) {
mostRecentTimestamp = progress.lastUpdated;
mostRecentEpisodeId = episodeId;
}
}
});
if (mostRecentEpisodeId) {
// Parse season number from episode ID
const parts = mostRecentEpisodeId.split(':');
if (parts.length === 3) {
const watchProgressSeason = parseInt(parts[1], 10);
if (transformedEpisodes[watchProgressSeason]) {
selectedSeasonNumber = watchProgressSeason;
logger.log(`[useMetadata] Auto-selected season ${selectedSeasonNumber} based on most recent watch progress for ${mostRecentEpisodeId}`);
}
} else {
// Try to find episode by stremioId to get season
const allEpisodesList = Object.values(transformedEpisodes).flat();
const episode = allEpisodesList.find(ep => ep.stremioId === mostRecentEpisodeId);
if (episode) {
selectedSeasonNumber = episode.season_number;
logger.log(`[useMetadata] Auto-selected season ${selectedSeasonNumber} based on most recent watch progress for episode with stremioId ${mostRecentEpisodeId}`);
}
}
} else {
// No watch progress found, use persistent storage as fallback
selectedSeasonNumber = getSeason(id, firstSeason);
logger.log(`[useMetadata] No watch progress found, using persistent season ${selectedSeasonNumber}`);
}
} catch (error) {
logger.error('[useMetadata] Error checking watch progress for season selection:', error);
// Fall back to persistent storage
selectedSeasonNumber = getSeason(id, firstSeason);
}
// Set the selected season
setSelectedSeason(selectedSeasonNumber);
// Set episodes for the selected season
setEpisodes(transformedEpisodes[persistedSeason] || []);
setEpisodes(transformedEpisodes[selectedSeasonNumber] || []);
}
} catch (error) {
console.error('Failed to load episodes:', error);

View file

@ -103,36 +103,27 @@ export const useWatchProgress = (
setWatchProgress(null);
}
} else {
// Find the first unfinished episode
const unfinishedEpisode = episodes.find(ep => {
const epId = ep.stremioId || `${id}:${ep.season_number}:${ep.episode_number}`;
const progress = seriesProgresses.find(p => p.episodeId === epId);
if (!progress) return true;
const percent = (progress.progress.currentTime / progress.progress.duration) * 100;
return percent < 95;
});
if (unfinishedEpisode) {
const epId = unfinishedEpisode.stremioId ||
`${id}:${unfinishedEpisode.season_number}:${unfinishedEpisode.episode_number}`;
const progress = await storageService.getWatchProgress(id, type, epId);
if (progress) {
setWatchProgress({
...progress,
episodeId: epId,
traktSynced: progress.traktSynced,
traktProgress: progress.traktProgress
});
} else {
setWatchProgress({
currentTime: 0,
duration: 0,
lastUpdated: Date.now(),
episodeId: epId,
traktSynced: false
});
}
// FIXED: Find the most recently watched episode instead of first unfinished
// Sort by lastUpdated timestamp (most recent first)
const sortedProgresses = seriesProgresses.sort((a, b) =>
b.progress.lastUpdated - a.progress.lastUpdated
);
if (sortedProgresses.length > 0) {
// Use the most recently watched episode
const mostRecentProgress = sortedProgresses[0];
const progress = mostRecentProgress.progress;
logger.log(`[useWatchProgress] Using most recent progress for ${mostRecentProgress.episodeId}, updated at ${new Date(progress.lastUpdated).toLocaleString()}`);
setWatchProgress({
...progress,
episodeId: mostRecentProgress.episodeId,
traktSynced: progress.traktSynced,
traktProgress: progress.traktProgress
});
} else {
// No watched episodes found
setWatchProgress(null);
}
}

View file

@ -533,9 +533,9 @@ const HomeScreen = () => {
console.log('[HomeScreen] Refreshing continue watching...');
if (continueWatchingRef.current) {
try {
const hasContent = await continueWatchingRef.current.refresh();
const hasContent = await continueWatchingRef.current.refresh();
console.log(`[HomeScreen] Continue watching has content: ${hasContent}`);
setHasContinueWatching(hasContent);
setHasContinueWatching(hasContent);
// Debug: Let's check what's in storage
const allProgress = await storageService.getAllWatchProgress();
@ -667,7 +667,7 @@ const HomeScreen = () => {
</TouchableOpacity>
</View>
<ContinueWatchingSection ref={continueWatchingRef} />
<ContinueWatchingSection ref={continueWatchingRef} />
{catalogs.length > 0 ? (
catalogs.map((catalog, index) => (