diff --git a/src/components/metadata/SeriesContent.tsx b/src/components/metadata/SeriesContent.tsx index 3bc6734cb..996ae6bc0 100644 --- a/src/components/metadata/SeriesContent.tsx +++ b/src/components/metadata/SeriesContent.tsx @@ -45,6 +45,8 @@ export const SeriesContent: React.FC = ({ // Add refs for the scroll views const seasonScrollViewRef = useRef(null); const episodeScrollViewRef = useRef(null); + + const loadEpisodesProgress = async () => { if (!metadata?.id) return; @@ -166,6 +168,8 @@ export const SeriesContent: React.FC = ({ } }, [selectedSeason, episodeProgress, settings.episodeLayoutStyle, groupedEpisodes]); + + if (loadingSeasons) { return ( @@ -185,10 +189,11 @@ export const SeriesContent: React.FC = ({ } const renderSeasonSelector = () => { + // Show selector if we have grouped episodes data or can derive from episodes if (!groupedEpisodes || Object.keys(groupedEpisodes).length <= 1) { return null; } - + const seasons = Object.keys(groupedEpisodes).map(Number).sort((a, b) => a - b); return ( @@ -229,6 +234,12 @@ export const SeriesContent: React.FC = ({ {selectedSeason === season && ( )} + {/* Show episode count badge, including when there are no episodes */} + + + {seasonEpisodes.length} ep{seasonEpisodes.length !== 1 ? 's' : ''} + + = ({ entering={FadeIn.duration(300).delay(100)} > - {episodes.length} {episodes.length === 1 ? 'Episode' : 'Episodes'} + {currentSeasonEpisodes.length} {currentSeasonEpisodes.length === 1 ? 'Episode' : 'Episodes'} - {settings.episodeLayoutStyle === 'horizontal' ? ( - // Horizontal Layout (Netflix-style) - - {currentSeasonEpisodes.map((episode, index) => ( - - {renderHorizontalEpisodeCard(episode)} - - ))} - - ) : ( - // Vertical Layout (Traditional) - - {isTablet ? ( - - {currentSeasonEpisodes.map((episode, index) => ( + {/* Show message when no episodes are available for selected season */} + {currentSeasonEpisodes.length === 0 && ( + + + + No episodes available for Season {selectedSeason} + + + Episodes may not be released yet + + + )} + + {/* Only render episode list if there are episodes */} + {currentSeasonEpisodes.length > 0 && ( + settings.episodeLayoutStyle === 'horizontal' ? ( + // Horizontal Layout (Netflix-style) + + {currentSeasonEpisodes.map((episode, index) => ( + + {renderHorizontalEpisodeCard(episode)} + + ))} + + ) : ( + // Vertical Layout (Traditional) + + {isTablet ? ( + + {currentSeasonEpisodes.map((episode, index) => ( + + {renderVerticalEpisodeCard(episode)} + + ))} + + ) : ( + currentSeasonEpisodes.map((episode, index) => ( {renderVerticalEpisodeCard(episode)} - ))} - - ) : ( - currentSeasonEpisodes.map((episode, index) => ( - - {renderVerticalEpisodeCard(episode)} - - )) - )} - + )) + )} + + ) )} @@ -625,6 +652,12 @@ const styles = StyleSheet.create({ fontSize: 16, textAlign: 'center', }, + centeredSubText: { + marginTop: 8, + fontSize: 14, + textAlign: 'center', + opacity: 0.8, + }, sectionTitle: { fontSize: 20, fontWeight: '700', @@ -963,4 +996,18 @@ const styles = StyleSheet.create({ selectedSeasonButtonText: { fontWeight: '700', }, + episodeCountBadge: { + position: 'absolute', + top: 8, + right: 8, + backgroundColor: 'rgba(0,0,0,0.8)', + paddingHorizontal: 4, + paddingVertical: 2, + borderRadius: 4, + }, + episodeCountText: { + color: '#fff', + fontSize: 12, + fontWeight: '600', + }, }); \ No newline at end of file diff --git a/src/screens/MetadataScreen.tsx b/src/screens/MetadataScreen.tsx index d55d06bb1..3c70a783d 100644 --- a/src/screens/MetadataScreen.tsx +++ b/src/screens/MetadataScreen.tsx @@ -399,7 +399,7 @@ const MetadataScreen: React.FC = () => { {type === 'series' ? ( void; index: number; isLoading?: boolean; statusMessage?: string; theme: any; - isExiting?: boolean; }) => { const styles = React.useMemo(() => createStyles(theme.colors), [theme.colors]); @@ -80,87 +79,8 @@ const StreamCard = ({ stream, onPress, index, isLoading, statusMessage, theme, i const displayTitle = isHDRezka ? `HDRezka ${stream.title}` : (stream.name || stream.title || 'Unnamed Stream'); const displayAddonName = isHDRezka ? '' : (stream.title || ''); - // Animation delay based on index - stagger effect (only if not exiting) - const enterDelay = isExiting ? 0 : 100 + (index * 30); - - // Use simple View when exiting to prevent animation conflicts - if (isExiting) { - return ( - - - - - - - {displayTitle} - - {displayAddonName && displayAddonName !== displayTitle && ( - - {displayAddonName} - - )} - - - {/* Show loading indicator if stream is loading */} - {isLoading && ( - - - - {statusMessage || "Loading..."} - - - )} - - - - {quality && quality >= "720" && ( - - )} - - {isDolby && ( - - )} - - {size && ( - - {size} - - )} - - {isDebrid && ( - - DEBRID - - )} - - {/* Special badge for HDRezka streams */} - {isHDRezka && ( - - HDREZKA - - )} - - - - - - - - - ); - } + // Animation delay based on index - stagger effect + const enterDelay = 100 + (index * 30); return ( { const loadStartTimeRef = useRef(0); const hasDoneInitialLoadRef = useRef(false); - // Add state for handling orientation transition - const [isTransitioning, setIsTransitioning] = useState(false); - - // Add state to prevent animation conflicts during exit - const [isExiting, setIsExiting] = useState(false); + // Add timing logs const [loadStartTime, setLoadStartTime] = useState(0); @@ -506,9 +422,6 @@ export const StreamsScreen = () => { // Memoize handlers const handleBack = useCallback(() => { - // Set exit state to prevent animation conflicts and hide content immediately - setIsExiting(true); - const cleanup = () => { headerOpacity.value = withTiming(0, { duration: 100 }); heroScale.value = withTiming(0.95, { duration: 100 }); @@ -1065,10 +978,9 @@ export const StreamsScreen = () => { isLoading={isLoading} statusMessage={undefined} theme={currentTheme} - isExiting={isExiting} /> ); - }, [handleStreamPress, currentTheme, isExiting]); + }, [handleStreamPress, currentTheme]); const renderSectionHeader = useCallback(({ section }: { section: { title: string; addonId: string } }) => { const isProviderLoading = loadingProviders[section.addonId]; @@ -1101,43 +1013,7 @@ export const StreamsScreen = () => { }; }, []); - // Add orientation handling when screen comes into focus - useFocusEffect( - useCallback(() => { - // Set transitioning state to mask any visual glitches - setIsTransitioning(true); - - // Immediately lock to portrait when returning to this screen - const lockToPortrait = async () => { - try { - await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP); - // Small delay then unlock to allow natural portrait orientation - setTimeout(async () => { - try { - await ScreenOrientation.unlockAsync(); - // Clear transition state after orientation is handled - setTimeout(() => { - setIsTransitioning(false); - }, 100); - } catch (error) { - logger.error('[StreamsScreen] Error unlocking orientation:', error); - setIsTransitioning(false); - } - }, 200); - } catch (error) { - logger.error('[StreamsScreen] Error locking to portrait:', error); - setIsTransitioning(false); - } - }; - lockToPortrait(); - - return () => { - // Cleanup when screen loses focus - setIsTransitioning(false); - }; - }, []) - ); return ( @@ -1146,18 +1022,7 @@ export const StreamsScreen = () => { backgroundColor="transparent" barStyle="light-content" /> - - {/* Instant overlay when exiting to prevent glitches */} - {isExiting && ( - - )} - - {/* Transition overlay to mask orientation changes */} - {isTransitioning && ( - - - - )} +