diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index 1c1cec2..86d41b1 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -780,8 +780,10 @@ const AppNavigator = () => { component={StreamsScreen as any} options={{ headerShown: false, - animation: Platform.OS === 'ios' ? 'slide_from_bottom' : 'fade_from_bottom', - animationDuration: Platform.OS === 'android' ? 200 : 300, + animation: Platform.OS === 'ios' ? 'slide_from_bottom' : 'none', + animationDuration: Platform.OS === 'android' ? 0 : 300, + gestureEnabled: true, + gestureDirection: Platform.OS === 'ios' ? 'vertical' : 'horizontal', ...(Platform.OS === 'ios' && { presentation: 'modal' }), contentStyle: { backgroundColor: currentTheme.colors.darkBackground, @@ -825,8 +827,30 @@ const AppNavigator = () => { name="Search" component={SearchScreen as any} options={{ - animation: 'fade', - animationDuration: Platform.OS === 'android' ? 300 : 350, + animation: Platform.OS === 'android' ? 'slide_from_right' : 'fade', + animationDuration: Platform.OS === 'android' ? 250 : 350, + gestureEnabled: true, + gestureDirection: 'horizontal', + ...(Platform.OS === 'android' && { + cardStyleInterpolator: ({ current, layouts }: any) => { + return { + cardStyle: { + transform: [ + { + translateX: current.progress.interpolate({ + inputRange: [0, 1], + outputRange: [layouts.screen.width, 0], + }), + }, + ], + opacity: current.progress.interpolate({ + inputRange: [0, 0.3, 1], + outputRange: [0, 0.85, 1], + }), + }, + }; + }, + }), contentStyle: { backgroundColor: currentTheme.colors.darkBackground, }, diff --git a/src/screens/SearchScreen.tsx b/src/screens/SearchScreen.tsx index 2d57e65..7ba205b 100644 --- a/src/screens/SearchScreen.tsx +++ b/src/screens/SearchScreen.tsx @@ -287,7 +287,14 @@ const SearchScreen = () => { setShowRecent(true); loadRecentSearches(); } else { - navigation.goBack(); + // Add a small delay to allow keyboard to dismiss smoothly before navigation + if (Platform.OS === 'android') { + setTimeout(() => { + navigation.goBack(); + }, 100); + } else { + navigation.goBack(); + } } }; @@ -497,7 +504,14 @@ const SearchScreen = () => { const headerHeight = headerBaseHeight + topSpacing + 60; return ( - + { )} - + ); }; diff --git a/src/screens/StreamsScreen.tsx b/src/screens/StreamsScreen.tsx index d7f2f59..c3750c5 100644 --- a/src/screens/StreamsScreen.tsx +++ b/src/screens/StreamsScreen.tsx @@ -33,6 +33,7 @@ import { useSettings } from '../hooks/useSettings'; import QualityBadge from '../components/metadata/QualityBadge'; import Animated, { FadeIn, + FadeOut, FadeInDown, SlideInDown, withSpring, @@ -55,13 +56,14 @@ const DOLBY_ICON = 'https://upload.wikimedia.org/wikipedia/en/thumb/3/3f/Dolby_V const { width, height } = Dimensions.get('window'); // Extracted Components -const StreamCard = ({ stream, onPress, index, isLoading, statusMessage, theme }: { +const StreamCard = ({ stream, onPress, index, isLoading, statusMessage, theme, isExiting }: { stream: Stream; onPress: () => void; index: number; isLoading?: boolean; statusMessage?: string; theme: any; + isExiting?: boolean; }) => { const styles = React.useMemo(() => createStyles(theme.colors), [theme.colors]); @@ -78,13 +80,92 @@ const StreamCard = ({ stream, onPress, index, isLoading, statusMessage, theme }: const displayTitle = isHDRezka ? `HDRezka ${stream.title}` : (stream.name || stream.title || 'Unnamed Stream'); const displayAddonName = isHDRezka ? '' : (stream.title || ''); - // Animation delay based on index - stagger effect - const enterDelay = 100 + (index * 50); + // 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 + + )} + + + + + + + + + ); + } return ( { // 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); @@ -400,20 +484,25 @@ 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: 200 }); - heroScale.value = withTiming(0.95, { duration: 200 }); - filterOpacity.value = withTiming(0, { duration: 200 }); + headerOpacity.value = withTiming(0, { duration: 100 }); + heroScale.value = withTiming(0.95, { duration: 100 }); + filterOpacity.value = withTiming(0, { duration: 100 }); }; cleanup(); // For series episodes, always replace current screen with metadata screen if (type === 'series') { + // Immediate navigation for series navigation.replace('Metadata', { id: id, type: type }); } else { + // Immediate navigation for movies navigation.goBack(); } }, [navigation, headerOpacity, heroScale, filterOpacity, type, id]); @@ -954,9 +1043,10 @@ export const StreamsScreen = () => { isLoading={isLoading} statusMessage={undefined} theme={currentTheme} + isExiting={isExiting} /> ); - }, [handleStreamPress, currentTheme]); + }, [handleStreamPress, currentTheme, isExiting]); const renderSectionHeader = useCallback(({ section }: { section: { title: string; addonId: string } }) => { const isProviderLoading = loadingProviders[section.addonId]; @@ -1035,6 +1125,11 @@ export const StreamsScreen = () => { barStyle="light-content" /> + {/* Instant overlay when exiting to prevent glitches */} + {isExiting && ( + + )} + {/* Transition overlay to mask orientation changes */} {isTransitioning && (