From 87cd33b90bc43ef5967eaa2e52ef06de6b7075a8 Mon Sep 17 00:00:00 2001 From: tapframe Date: Sat, 21 Jun 2025 19:09:24 +0530 Subject: [PATCH] Enhance SeriesContent component with episode count display and no-episode message This update modifies the SeriesContent component to include an episode count badge for each season and a message indicating when no episodes are available for the selected season. Additionally, the episodes prop is now derived from groupedEpisodes for improved data handling. These changes aim to enhance user experience by providing clearer information about available content. --- src/components/metadata/SeriesContent.tsx | 151 ++++++++++++++-------- src/screens/MetadataScreen.tsx | 2 +- src/screens/StreamsScreen.tsx | 149 +-------------------- 3 files changed, 107 insertions(+), 195 deletions(-) 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 && ( - - - - )} +