From f90752bdb724181e473dfb58344241cdde8c6f38 Mon Sep 17 00:00:00 2001 From: tapframe Date: Thu, 23 Oct 2025 14:51:41 +0530 Subject: [PATCH] Streamscreen new changes update --- src/hooks/useSettings.ts | 2 + src/screens/SettingsScreen.tsx | 17 +++- src/screens/StreamsScreen.tsx | 181 ++++++++++++++++++++++++++++++--- 3 files changed, 186 insertions(+), 14 deletions(-) diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index b01cf31..fd36dcd 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -86,6 +86,7 @@ export interface AppSettings { useCachedStreams: boolean; // Enable/disable direct player navigation from Continue Watching cache openMetadataScreenWhenCacheDisabled: boolean; // When cache disabled, open MetadataScreen instead of StreamsScreen streamCacheTTL: number; // Stream cache duration in milliseconds (default: 1 hour) + enableStreamsBackdrop: boolean; // Enable blurred backdrop background on StreamsScreen mobile } export const DEFAULT_SETTINGS: AppSettings = { @@ -145,6 +146,7 @@ export const DEFAULT_SETTINGS: AppSettings = { useCachedStreams: false, // Enable by default openMetadataScreenWhenCacheDisabled: true, // Default to StreamsScreen when cache disabled streamCacheTTL: 60 * 60 * 1000, // Default: 1 hour in milliseconds + enableStreamsBackdrop: true, // Enable by default (new behavior) }; const SETTINGS_STORAGE_KEY = 'app_settings'; diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx index 179d0e2..dd59dfc 100644 --- a/src/screens/SettingsScreen.tsx +++ b/src/screens/SettingsScreen.tsx @@ -518,9 +518,24 @@ const SettingsScreen: React.FC = () => { onValueChange={(value) => updateSetting('episodeLayoutStyle', value ? 'horizontal' : 'vertical')} /> )} - isLast={true} + isLast={isTablet} isTablet={isTablet} /> + {!isTablet && ( + ( + updateSetting('enableStreamsBackdrop', value)} + /> + )} + isLast={true} + isTablet={isTablet} + /> + )} ); diff --git a/src/screens/StreamsScreen.tsx b/src/screens/StreamsScreen.tsx index 461bce2..83e92bc 100644 --- a/src/screens/StreamsScreen.tsx +++ b/src/screens/StreamsScreen.tsx @@ -50,6 +50,7 @@ import CustomAlert from '../components/CustomAlert'; import { useToast } from '../contexts/ToastContext'; import { useDownloads } from '../contexts/DownloadsContext'; import { streamCacheService } from '../services/streamCacheService'; +import { useDominantColor } from '../hooks/useDominantColor'; import { PaperProvider } from 'react-native-paper'; import { BlurView as ExpoBlurView } from 'expo-blur'; import TabletStreamsLayout from '../components/TabletStreamsLayout'; @@ -1549,6 +1550,48 @@ export const StreamsScreen = () => { return r; }, [currentEpisode, tmdbEpisodeOverride?.runtime]); + // Mobile backdrop source selection logic + const mobileBackdropSource = useMemo(() => { + // For series episodes: prioritize episodeImage, fallback to bannerImage + if (type === 'series' || (type === 'other' && selectedEpisode)) { + if (episodeImage) { + return episodeImage; + } + if (bannerImage) { + return bannerImage; + } + } + + // For movies: prioritize bannerImage + if (type === 'movie') { + if (bannerImage) { + return bannerImage; + } + } + + // For other types or when no specific image available + return bannerImage || episodeImage; + }, [type, selectedEpisode, episodeImage, bannerImage]); + + // Backdrop source for color extraction - only episodes, not movies + const colorExtractionSource = useMemo(() => { + // Only extract colors if backdrop is enabled + if (!settings.enableStreamsBackdrop) { + return null; + } + + if (type === 'series' || (type === 'other' && selectedEpisode)) { + // Only use episodeImage - don't fallback to bannerImage + // This ensures we get episode-specific colors, not show-wide colors + return episodeImage || null; + } + // Return null for movies - no color extraction + return null; + }, [type, selectedEpisode, episodeImage, settings.enableStreamsBackdrop]); + + // Extract dominant color from backdrop for gradient + const { dominantColor } = useDominantColor(colorExtractionSource); + // Prefetch hero/backdrop and title logo when StreamsScreen opens useEffect(() => { const urls: string[] = []; @@ -1563,6 +1606,33 @@ export const StreamsScreen = () => { }); }, [episodeImage, bannerImage, metadata]); + // Helper to create gradient colors from dominant color + const createGradientColors = useCallback((baseColor: string | null): [string, string, string, string, string] => { + if (!baseColor || baseColor === '#1a1a1a') { + // Fallback to black gradient with stronger bottom edge + return ['rgba(0,0,0,0)', 'rgba(0,0,0,0.3)', 'rgba(0,0,0,0.6)', 'rgba(0,0,0,0.85)', 'rgba(0,0,0,0.95)']; + } + + // Convert hex to RGB + const r = parseInt(baseColor.substr(1, 2), 16); + const g = parseInt(baseColor.substr(3, 2), 16); + const b = parseInt(baseColor.substr(5, 2), 16); + + // Create gradient stops with much stronger opacity at bottom + return [ + `rgba(${r},${g},${b},0)`, + `rgba(${r},${g},${b},0.3)`, + `rgba(${r},${g},${b},0.6)`, + `rgba(${r},${g},${b},0.85)`, + `rgba(${r},${g},${b},0.95)`, + ]; + }, []); + + const gradientColors = useMemo(() => + createGradientColors(dominantColor), + [dominantColor, createGradientColors] + ); + const isLoading = metadata?.videos && metadata.videos.length > 1 && selectedEpisode ? loadingEpisodeStreams : loadingStreams; const streams = metadata?.videos && metadata.videos.length > 1 && selectedEpisode ? episodeStreams : groupedStreams; @@ -1695,8 +1765,52 @@ export const StreamsScreen = () => { ) : ( // PHONE LAYOUT (existing structure) <> + {/* Full Screen Background for Mobile */} + {settings.enableStreamsBackdrop ? ( + + {mobileBackdropSource ? ( + + ) : ( + + )} + {Platform.OS === 'android' && AndroidBlurView ? ( + + ) : ( + + )} + {/* Dark overlay to reduce brightness */} + {Platform.OS === 'ios' && ( + + )} + + ) : ( + + )} + {type === 'movie' && metadata && ( - + {metadata.logo && !movieLogoError ? ( { )} {metadata?.videos && metadata.videos.length > 1 && selectedEpisode && ( - + { style={styles.streamsHeroBackground} contentFit="cover" /> - + {currentEpisode ? ( @@ -1781,9 +1898,29 @@ export const StreamsScreen = () => { )} + {/* Gradient overlay to blend hero section with streams container */} + {metadata?.videos && metadata.videos.length > 1 && selectedEpisode && settings.enableStreamsBackdrop && ( + + + + )} + {!streamsEmpty && ( @@ -1969,7 +2106,7 @@ export const StreamsScreen = () => { const createStyles = (colors: any) => StyleSheet.create({ container: { flex: 1, - backgroundColor: colors.darkBackground, + backgroundColor: 'transparent', // iOS-specific fixes for navigation transition glitches ...(Platform.OS === 'ios' && { // Ensure the background is properly rendered during transitions @@ -2004,7 +2141,7 @@ const createStyles = (colors: any) => StyleSheet.create({ }, streamsMainContent: { flex: 1, - backgroundColor: colors.darkBackground, + backgroundColor: 'transparent', paddingTop: 12, zIndex: 1, // iOS-specific fixes for navigation transition glitches @@ -2225,13 +2362,13 @@ const createStyles = (colors: any) => StyleSheet.create({ height: 220, // Fixed height to prevent layout shift marginBottom: 0, position: 'relative', - backgroundColor: colors.black, + backgroundColor: 'transparent', pointerEvents: 'box-none', }, streamsHeroBackground: { width: '100%', height: '100%', - backgroundColor: colors.black, + backgroundColor: 'transparent', }, streamsHeroGradient: { ...StyleSheet.absoluteFillObject, @@ -2349,7 +2486,7 @@ const createStyles = (colors: any) => StyleSheet.create({ movieTitleContainer: { width: '100%', height: 140, - backgroundColor: colors.darkBackground, + backgroundColor: 'transparent', pointerEvents: 'box-none', justifyContent: 'center', paddingTop: Platform.OS === 'android' ? 65 : 35, @@ -2556,6 +2693,24 @@ const createStyles = (colors: any) => StyleSheet.create({ backButtonContainerTablet: { zIndex: 3, }, + mobileFullScreenBackground: { + ...StyleSheet.absoluteFillObject, + width: '100%', + height: '100%', + }, + mobileNoBackdropBackground: { + ...StyleSheet.absoluteFillObject, + backgroundColor: colors.darkBackground, + }, + heroBlendOverlay: { + position: 'absolute', + top: 220, // Height of hero container + left: 0, + right: 0, + height: 60, // Extend gradient 60px into streams area + zIndex: 0, + pointerEvents: 'none', + }, }); export default memo(StreamsScreen);