mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
This update introduces the episodeThumbnail parameter in the navigation between MetadataScreen and StreamsScreen, allowing for improved image handling. The StreamsScreen now utilizes the episodeThumbnail for displaying images, with additional checks for URL validity, enhancing the user experience and ensuring more reliable image rendering.
992 lines
No EOL
31 KiB
TypeScript
992 lines
No EOL
31 KiB
TypeScript
import React, { useEffect, useRef } from 'react';
|
|
import { NavigationContainer, DefaultTheme as NavigationDefaultTheme, DarkTheme as NavigationDarkTheme, Theme, NavigationProp } from '@react-navigation/native';
|
|
import { createNativeStackNavigator, NativeStackNavigationOptions, NativeStackNavigationProp } from '@react-navigation/native-stack';
|
|
import { createBottomTabNavigator, BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
|
|
import { useColorScheme, Platform, Animated, StatusBar, TouchableOpacity, View, Text, AppState } from 'react-native';
|
|
import { PaperProvider, MD3DarkTheme, MD3LightTheme, adaptNavigationTheme } from 'react-native-paper';
|
|
import type { MD3Theme } from 'react-native-paper';
|
|
import type { BottomTabBarProps } from '@react-navigation/bottom-tabs';
|
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
import { LinearGradient } from 'expo-linear-gradient';
|
|
import { BlurView } from 'expo-blur';
|
|
import { colors } from '../styles/colors';
|
|
import { NuvioHeader } from '../components/NuvioHeader';
|
|
import { Stream } from '../types/streams';
|
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
import { useTheme } from '../contexts/ThemeContext';
|
|
|
|
// Import screens with their proper types
|
|
import HomeScreen from '../screens/HomeScreen';
|
|
import LibraryScreen from '../screens/LibraryScreen';
|
|
import SettingsScreen from '../screens/SettingsScreen';
|
|
import MetadataScreen from '../screens/MetadataScreen';
|
|
import VideoPlayer from '../components/player/VideoPlayer';
|
|
import CatalogScreen from '../screens/CatalogScreen';
|
|
import AddonsScreen from '../screens/AddonsScreen';
|
|
import SearchScreen from '../screens/SearchScreen';
|
|
import ShowRatingsScreen from '../screens/ShowRatingsScreen';
|
|
import CatalogSettingsScreen from '../screens/CatalogSettingsScreen';
|
|
import StreamsScreen from '../screens/StreamsScreen';
|
|
import CalendarScreen from '../screens/CalendarScreen';
|
|
import NotificationSettingsScreen from '../screens/NotificationSettingsScreen';
|
|
import MDBListSettingsScreen from '../screens/MDBListSettingsScreen';
|
|
import TMDBSettingsScreen from '../screens/TMDBSettingsScreen';
|
|
import HomeScreenSettings from '../screens/HomeScreenSettings';
|
|
import HeroCatalogsScreen from '../screens/HeroCatalogsScreen';
|
|
import TraktSettingsScreen from '../screens/TraktSettingsScreen';
|
|
import PlayerSettingsScreen from '../screens/PlayerSettingsScreen';
|
|
import LogoSourceSettings from '../screens/LogoSourceSettings';
|
|
import ThemeScreen from '../screens/ThemeScreen';
|
|
import ProfilesScreen from '../screens/ProfilesScreen';
|
|
import OnboardingScreen from '../screens/OnboardingScreen';
|
|
|
|
// Stack navigator types
|
|
export type RootStackParamList = {
|
|
Onboarding: undefined;
|
|
MainTabs: undefined;
|
|
Home: undefined;
|
|
Library: undefined;
|
|
Settings: undefined;
|
|
Search: undefined;
|
|
Calendar: undefined;
|
|
Metadata: {
|
|
id: string;
|
|
type: string;
|
|
episodeId?: string;
|
|
addonId?: string;
|
|
};
|
|
Streams: {
|
|
id: string;
|
|
type: string;
|
|
episodeId?: string;
|
|
episodeThumbnail?: string;
|
|
};
|
|
VideoPlayer: {
|
|
id: string;
|
|
type: string;
|
|
stream: Stream;
|
|
episodeId?: string;
|
|
};
|
|
Player: {
|
|
uri: string;
|
|
title?: string;
|
|
season?: number;
|
|
episode?: number;
|
|
episodeTitle?: string;
|
|
quality?: string;
|
|
year?: number;
|
|
streamProvider?: string;
|
|
streamName?: string;
|
|
id?: string;
|
|
type?: string;
|
|
episodeId?: string;
|
|
imdbId?: string;
|
|
availableStreams?: { [providerId: string]: { streams: any[]; addonName: string } };
|
|
};
|
|
Catalog: { id: string; type: string; addonId?: string; name?: string; genreFilter?: string };
|
|
Credits: { mediaId: string; mediaType: string };
|
|
ShowRatings: { showId: number };
|
|
Account: undefined;
|
|
Payment: undefined;
|
|
PrivacyPolicy: undefined;
|
|
About: undefined;
|
|
Addons: undefined;
|
|
CatalogSettings: undefined;
|
|
NotificationSettings: undefined;
|
|
MDBListSettings: undefined;
|
|
TMDBSettings: undefined;
|
|
HomeScreenSettings: undefined;
|
|
HeroCatalogs: undefined;
|
|
TraktSettings: undefined;
|
|
PlayerSettings: undefined;
|
|
LogoSourceSettings: undefined;
|
|
ThemeSettings: undefined;
|
|
ProfilesSettings: undefined;
|
|
};
|
|
|
|
export type RootStackNavigationProp = NativeStackNavigationProp<RootStackParamList>;
|
|
|
|
// Tab navigator types
|
|
export type MainTabParamList = {
|
|
Home: undefined;
|
|
Library: undefined;
|
|
Search: undefined;
|
|
Settings: undefined;
|
|
};
|
|
|
|
// Custom fonts that satisfy both theme types
|
|
const fonts = {
|
|
regular: {
|
|
fontFamily: 'sans-serif',
|
|
fontWeight: '400' as const,
|
|
},
|
|
medium: {
|
|
fontFamily: 'sans-serif-medium',
|
|
fontWeight: '500' as const,
|
|
},
|
|
bold: {
|
|
fontFamily: 'sans-serif',
|
|
fontWeight: '700' as const,
|
|
},
|
|
heavy: {
|
|
fontFamily: 'sans-serif',
|
|
fontWeight: '900' as const,
|
|
},
|
|
// MD3 specific fonts
|
|
displayLarge: {
|
|
fontFamily: 'sans-serif',
|
|
fontWeight: '400' as const,
|
|
letterSpacing: 0,
|
|
lineHeight: 64,
|
|
fontSize: 57,
|
|
},
|
|
displayMedium: {
|
|
fontFamily: 'sans-serif',
|
|
fontWeight: '400' as const,
|
|
letterSpacing: 0,
|
|
lineHeight: 52,
|
|
fontSize: 45,
|
|
},
|
|
displaySmall: {
|
|
fontFamily: 'sans-serif',
|
|
fontWeight: '400' as const,
|
|
letterSpacing: 0,
|
|
lineHeight: 44,
|
|
fontSize: 36,
|
|
},
|
|
headlineLarge: {
|
|
fontFamily: 'sans-serif',
|
|
fontWeight: '400' as const,
|
|
letterSpacing: 0,
|
|
lineHeight: 40,
|
|
fontSize: 32,
|
|
},
|
|
headlineMedium: {
|
|
fontFamily: 'sans-serif',
|
|
fontWeight: '400' as const,
|
|
letterSpacing: 0,
|
|
lineHeight: 36,
|
|
fontSize: 28,
|
|
},
|
|
headlineSmall: {
|
|
fontFamily: 'sans-serif',
|
|
fontWeight: '400' as const,
|
|
letterSpacing: 0,
|
|
lineHeight: 32,
|
|
fontSize: 24,
|
|
},
|
|
titleLarge: {
|
|
fontFamily: 'sans-serif',
|
|
fontWeight: '400' as const,
|
|
letterSpacing: 0,
|
|
lineHeight: 28,
|
|
fontSize: 22,
|
|
},
|
|
titleMedium: {
|
|
fontFamily: 'sans-serif-medium',
|
|
fontWeight: '500' as const,
|
|
letterSpacing: 0.15,
|
|
lineHeight: 24,
|
|
fontSize: 16,
|
|
},
|
|
titleSmall: {
|
|
fontFamily: 'sans-serif-medium',
|
|
fontWeight: '500' as const,
|
|
letterSpacing: 0.1,
|
|
lineHeight: 20,
|
|
fontSize: 14,
|
|
},
|
|
labelLarge: {
|
|
fontFamily: 'sans-serif-medium',
|
|
fontWeight: '500' as const,
|
|
letterSpacing: 0.1,
|
|
lineHeight: 20,
|
|
fontSize: 14,
|
|
},
|
|
labelMedium: {
|
|
fontFamily: 'sans-serif-medium',
|
|
fontWeight: '500' as const,
|
|
letterSpacing: 0.5,
|
|
lineHeight: 16,
|
|
fontSize: 12,
|
|
},
|
|
labelSmall: {
|
|
fontFamily: 'sans-serif-medium',
|
|
fontWeight: '500' as const,
|
|
letterSpacing: 0.5,
|
|
lineHeight: 16,
|
|
fontSize: 11,
|
|
},
|
|
bodyLarge: {
|
|
fontFamily: 'sans-serif',
|
|
fontWeight: '400' as const,
|
|
letterSpacing: 0.15,
|
|
lineHeight: 24,
|
|
fontSize: 16,
|
|
},
|
|
bodyMedium: {
|
|
fontFamily: 'sans-serif',
|
|
fontWeight: '400' as const,
|
|
letterSpacing: 0.25,
|
|
lineHeight: 20,
|
|
fontSize: 14,
|
|
},
|
|
bodySmall: {
|
|
fontFamily: 'sans-serif',
|
|
fontWeight: '400' as const,
|
|
letterSpacing: 0.4,
|
|
lineHeight: 16,
|
|
fontSize: 12,
|
|
},
|
|
} as const;
|
|
|
|
// Create navigators
|
|
const Stack = createNativeStackNavigator<RootStackParamList>();
|
|
const Tab = createBottomTabNavigator<MainTabParamList>();
|
|
|
|
// Create custom paper themes
|
|
export const CustomLightTheme: MD3Theme = {
|
|
...MD3LightTheme,
|
|
colors: {
|
|
...MD3LightTheme.colors,
|
|
primary: colors.primary,
|
|
},
|
|
fonts: MD3LightTheme.fonts,
|
|
};
|
|
|
|
export const CustomDarkTheme: MD3Theme = {
|
|
...MD3DarkTheme,
|
|
colors: {
|
|
...MD3DarkTheme.colors,
|
|
primary: colors.primary,
|
|
},
|
|
fonts: MD3DarkTheme.fonts,
|
|
};
|
|
|
|
// Create custom navigation theme
|
|
const { LightTheme, DarkTheme } = adaptNavigationTheme({
|
|
reactNavigationLight: NavigationDefaultTheme,
|
|
reactNavigationDark: NavigationDarkTheme,
|
|
});
|
|
|
|
// Add fonts to navigation themes
|
|
export const CustomNavigationLightTheme: Theme = {
|
|
...LightTheme,
|
|
colors: {
|
|
...LightTheme.colors,
|
|
background: colors.white,
|
|
card: colors.white,
|
|
text: colors.textDark,
|
|
border: colors.border,
|
|
},
|
|
fonts,
|
|
};
|
|
|
|
export const CustomNavigationDarkTheme: Theme = {
|
|
...DarkTheme,
|
|
colors: {
|
|
...DarkTheme.colors,
|
|
background: colors.darkBackground,
|
|
card: colors.darkBackground,
|
|
text: colors.text,
|
|
border: colors.border,
|
|
},
|
|
fonts,
|
|
};
|
|
|
|
type IconNameType = 'home' | 'home-outline' | 'compass' | 'compass-outline' |
|
|
'play-box-multiple' | 'play-box-multiple-outline' |
|
|
'puzzle' | 'puzzle-outline' |
|
|
'cog' | 'cog-outline' | 'feature-search' | 'feature-search-outline';
|
|
|
|
// Add TabIcon component
|
|
const TabIcon = React.memo(({ focused, color, iconName }: {
|
|
focused: boolean;
|
|
color: string;
|
|
iconName: IconNameType;
|
|
}) => {
|
|
const scaleAnim = useRef(new Animated.Value(1)).current;
|
|
|
|
useEffect(() => {
|
|
Animated.spring(scaleAnim, {
|
|
toValue: focused ? 1.1 : 1,
|
|
useNativeDriver: true,
|
|
friction: 8,
|
|
tension: 100
|
|
}).start();
|
|
}, [focused]);
|
|
|
|
const finalIconName = focused ? iconName : `${iconName}-outline` as IconNameType;
|
|
|
|
return (
|
|
<Animated.View style={{
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
transform: [{ scale: scaleAnim }]
|
|
}}>
|
|
<MaterialCommunityIcons
|
|
name={finalIconName}
|
|
size={24}
|
|
color={color}
|
|
/>
|
|
</Animated.View>
|
|
);
|
|
});
|
|
|
|
// Update the TabScreenWrapper component with fixed layout dimensions
|
|
const TabScreenWrapper: React.FC<{children: React.ReactNode}> = ({ children }) => {
|
|
// Force consistent status bar settings
|
|
useEffect(() => {
|
|
const applyStatusBarConfig = () => {
|
|
StatusBar.setBarStyle('light-content');
|
|
StatusBar.setTranslucent(true);
|
|
StatusBar.setBackgroundColor('transparent');
|
|
};
|
|
|
|
applyStatusBarConfig();
|
|
|
|
// Apply status bar config on every focus
|
|
const subscription = Platform.OS === 'android'
|
|
? AppState.addEventListener('change', (state) => {
|
|
if (state === 'active') {
|
|
applyStatusBarConfig();
|
|
}
|
|
})
|
|
: { remove: () => {} };
|
|
|
|
return () => {
|
|
subscription.remove();
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<View style={{
|
|
flex: 1,
|
|
backgroundColor: colors.darkBackground,
|
|
// Lock the layout to prevent shifts
|
|
position: 'relative',
|
|
overflow: 'hidden'
|
|
}}>
|
|
{/* Reserve consistent space for the header area on all screens */}
|
|
<View style={{
|
|
height: Platform.OS === 'android' ? 80 : 60,
|
|
width: '100%',
|
|
backgroundColor: colors.darkBackground,
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
zIndex: -1
|
|
}} />
|
|
{children}
|
|
</View>
|
|
);
|
|
};
|
|
|
|
// Add this component to wrap each screen in the tab navigator
|
|
const WrappedScreen: React.FC<{Screen: React.ComponentType<any>}> = ({ Screen }) => {
|
|
return (
|
|
<TabScreenWrapper>
|
|
<Screen />
|
|
</TabScreenWrapper>
|
|
);
|
|
};
|
|
|
|
// Tab Navigator
|
|
const MainTabs = () => {
|
|
const { currentTheme } = useTheme();
|
|
|
|
const renderTabBar = (props: BottomTabBarProps) => {
|
|
return (
|
|
<View style={{
|
|
position: 'absolute',
|
|
bottom: 0,
|
|
left: 0,
|
|
right: 0,
|
|
height: 85,
|
|
backgroundColor: 'transparent',
|
|
overflow: 'hidden',
|
|
}}>
|
|
{Platform.OS === 'ios' ? (
|
|
<BlurView
|
|
tint="dark"
|
|
intensity={75}
|
|
style={{
|
|
position: 'absolute',
|
|
height: '100%',
|
|
width: '100%',
|
|
borderTopColor: currentTheme.colors.border,
|
|
borderTopWidth: 0.5,
|
|
shadowColor: currentTheme.colors.black,
|
|
shadowOffset: { width: 0, height: -2 },
|
|
shadowOpacity: 0.1,
|
|
shadowRadius: 3,
|
|
}}
|
|
/>
|
|
) : (
|
|
<LinearGradient
|
|
colors={[
|
|
'rgba(0, 0, 0, 0)',
|
|
'rgba(0, 0, 0, 0.65)',
|
|
'rgba(0, 0, 0, 0.85)',
|
|
'rgba(0, 0, 0, 0.98)',
|
|
]}
|
|
locations={[0, 0.2, 0.4, 0.8]}
|
|
style={{
|
|
position: 'absolute',
|
|
height: '100%',
|
|
width: '100%',
|
|
}}
|
|
/>
|
|
)}
|
|
<View
|
|
style={{
|
|
height: '100%',
|
|
paddingBottom: 20,
|
|
paddingTop: 12,
|
|
backgroundColor: 'transparent',
|
|
}}
|
|
>
|
|
<View style={{ flexDirection: 'row', paddingTop: 4 }}>
|
|
{props.state.routes.map((route, index) => {
|
|
const { options } = props.descriptors[route.key];
|
|
const label =
|
|
options.tabBarLabel !== undefined
|
|
? options.tabBarLabel
|
|
: options.title !== undefined
|
|
? options.title
|
|
: route.name;
|
|
|
|
const isFocused = props.state.index === index;
|
|
|
|
const onPress = () => {
|
|
const event = props.navigation.emit({
|
|
type: 'tabPress',
|
|
target: route.key,
|
|
canPreventDefault: true,
|
|
});
|
|
|
|
if (!isFocused && !event.defaultPrevented) {
|
|
props.navigation.navigate(route.name);
|
|
}
|
|
};
|
|
|
|
let iconName: IconNameType = 'home';
|
|
switch (route.name) {
|
|
case 'Home':
|
|
iconName = 'home';
|
|
break;
|
|
case 'Library':
|
|
iconName = 'play-box-multiple';
|
|
break;
|
|
case 'Search':
|
|
iconName = 'feature-search';
|
|
break;
|
|
case 'Settings':
|
|
iconName = 'cog';
|
|
break;
|
|
}
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
key={route.key}
|
|
activeOpacity={0.7}
|
|
onPress={onPress}
|
|
style={{
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
backgroundColor: 'transparent',
|
|
}}
|
|
>
|
|
<TabIcon
|
|
focused={isFocused}
|
|
color={isFocused ? currentTheme.colors.primary : currentTheme.colors.white}
|
|
iconName={iconName}
|
|
/>
|
|
<Text
|
|
style={{
|
|
fontSize: 12,
|
|
fontWeight: '600',
|
|
marginTop: 4,
|
|
color: isFocused ? currentTheme.colors.primary : currentTheme.colors.white,
|
|
opacity: isFocused ? 1 : 0.7,
|
|
}}
|
|
>
|
|
{typeof label === 'string' ? label : ''}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
);
|
|
})}
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<View style={{ flex: 1, backgroundColor: currentTheme.colors.darkBackground }}>
|
|
{/* Common StatusBar for all tabs */}
|
|
<StatusBar
|
|
translucent
|
|
barStyle="light-content"
|
|
backgroundColor="transparent"
|
|
/>
|
|
|
|
<Tab.Navigator
|
|
tabBar={renderTabBar}
|
|
screenOptions={({ route, navigation, theme }) => ({
|
|
header: () => (route.name === 'Home' ? <NuvioHeader /> : null),
|
|
headerShown: route.name === 'Home',
|
|
tabBarShowLabel: false,
|
|
tabBarStyle: {
|
|
position: 'absolute',
|
|
borderTopWidth: 0,
|
|
elevation: 0,
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
detachInactiveScreens: false,
|
|
})}
|
|
>
|
|
<Tab.Screen
|
|
name="Home"
|
|
component={HomeScreen}
|
|
options={{
|
|
tabBarLabel: 'Home',
|
|
tabBarIcon: ({ color, size, focused }) => (
|
|
<MaterialCommunityIcons name={focused ? 'home' : 'home-outline'} size={size} color={color} />
|
|
),
|
|
}}
|
|
/>
|
|
<Tab.Screen
|
|
name="Library"
|
|
component={LibraryScreen}
|
|
options={{
|
|
tabBarLabel: 'Library',
|
|
tabBarIcon: ({ color, size, focused }) => (
|
|
<MaterialCommunityIcons name={focused ? 'play-box-multiple' : 'play-box-multiple-outline'} size={size} color={color} />
|
|
),
|
|
}}
|
|
/>
|
|
<Tab.Screen
|
|
name="Search"
|
|
component={SearchScreen}
|
|
options={{
|
|
tabBarLabel: 'Search',
|
|
tabBarIcon: ({ color, size, focused }) => (
|
|
<MaterialCommunityIcons name={focused ? 'feature-search' : 'feature-search-outline'} size={size} color={color} />
|
|
),
|
|
}}
|
|
/>
|
|
<Tab.Screen
|
|
name="Settings"
|
|
component={SettingsScreen}
|
|
options={{
|
|
tabBarLabel: 'Settings',
|
|
tabBarIcon: ({ color, size, focused }) => (
|
|
<MaterialCommunityIcons name={focused ? 'cog' : 'cog-outline'} size={size} color={color} />
|
|
),
|
|
}}
|
|
/>
|
|
</Tab.Navigator>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
// Create custom fade animation interpolator for MetadataScreen
|
|
const customFadeInterpolator = ({ current, layouts }: any) => {
|
|
return {
|
|
cardStyle: {
|
|
opacity: current.progress.interpolate({
|
|
inputRange: [0, 1],
|
|
outputRange: [0, 1],
|
|
}),
|
|
transform: [
|
|
{
|
|
scale: current.progress.interpolate({
|
|
inputRange: [0, 1],
|
|
outputRange: [0.95, 1],
|
|
}),
|
|
},
|
|
],
|
|
},
|
|
overlayStyle: {
|
|
opacity: current.progress.interpolate({
|
|
inputRange: [0, 1],
|
|
outputRange: [0, 0.3],
|
|
}),
|
|
},
|
|
};
|
|
};
|
|
|
|
// Stack Navigator
|
|
const AppNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootStackParamList }) => {
|
|
const { currentTheme } = useTheme();
|
|
|
|
// Handle Android-specific optimizations
|
|
useEffect(() => {
|
|
if (Platform.OS === 'android') {
|
|
// Ensure consistent background color for Android
|
|
StatusBar.setBackgroundColor('transparent', true);
|
|
StatusBar.setTranslucent(true);
|
|
}
|
|
}, []);
|
|
|
|
return (
|
|
<SafeAreaProvider>
|
|
<StatusBar
|
|
translucent
|
|
backgroundColor="transparent"
|
|
barStyle="light-content"
|
|
/>
|
|
<PaperProvider theme={CustomDarkTheme}>
|
|
<View style={{
|
|
flex: 1,
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
...(Platform.OS === 'android' && {
|
|
// Prevent white flashes on Android
|
|
opacity: 1,
|
|
})
|
|
}}>
|
|
<Stack.Navigator
|
|
initialRouteName={initialRouteName || 'MainTabs'}
|
|
screenOptions={{
|
|
headerShown: false,
|
|
// Use slide_from_right for consistency and smooth transitions
|
|
animation: Platform.OS === 'android' ? 'slide_from_right' : 'slide_from_right',
|
|
animationDuration: Platform.OS === 'android' ? 250 : 300,
|
|
// Ensure consistent background during transitions
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
// Improve Android performance with custom interpolator
|
|
...(Platform.OS === 'android' && {
|
|
cardStyleInterpolator: ({ current, layouts }: any) => {
|
|
return {
|
|
cardStyle: {
|
|
transform: [
|
|
{
|
|
translateX: current.progress.interpolate({
|
|
inputRange: [0, 1],
|
|
outputRange: [layouts.screen.width, 0],
|
|
}),
|
|
},
|
|
],
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
};
|
|
},
|
|
}),
|
|
}}
|
|
>
|
|
<Stack.Screen
|
|
name="Onboarding"
|
|
component={OnboardingScreen}
|
|
options={{
|
|
headerShown: false,
|
|
animation: 'fade',
|
|
animationDuration: 300,
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="MainTabs"
|
|
component={MainTabs as any}
|
|
options={{
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="Metadata"
|
|
component={MetadataScreen}
|
|
options={{
|
|
headerShown: false,
|
|
animation: 'fade',
|
|
animationDuration: Platform.OS === 'android' ? 250 : 300,
|
|
...(Platform.OS === 'ios' && {
|
|
cardStyleInterpolator: customFadeInterpolator,
|
|
animationTypeForReplace: 'push',
|
|
gestureEnabled: true,
|
|
gestureDirection: 'horizontal',
|
|
}),
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="Streams"
|
|
component={StreamsScreen as any}
|
|
options={{
|
|
headerShown: false,
|
|
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,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="Player"
|
|
component={VideoPlayer as any}
|
|
options={{
|
|
animation: 'slide_from_right',
|
|
animationDuration: Platform.OS === 'android' ? 200 : 300,
|
|
contentStyle: {
|
|
backgroundColor: '#000000', // Pure black for video player
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="Catalog"
|
|
component={CatalogScreen as any}
|
|
options={{
|
|
animation: 'slide_from_right',
|
|
animationDuration: Platform.OS === 'android' ? 250 : 300,
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="Addons"
|
|
component={AddonsScreen as any}
|
|
options={{
|
|
animation: 'slide_from_right',
|
|
animationDuration: Platform.OS === 'android' ? 250 : 300,
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="Search"
|
|
component={SearchScreen as any}
|
|
options={{
|
|
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,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="CatalogSettings"
|
|
component={CatalogSettingsScreen as any}
|
|
options={{
|
|
animation: 'slide_from_right',
|
|
animationDuration: Platform.OS === 'android' ? 250 : 300,
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="HomeScreenSettings"
|
|
component={HomeScreenSettings}
|
|
options={{
|
|
animation: Platform.OS === 'android' ? 'slide_from_right' : 'fade',
|
|
animationDuration: Platform.OS === 'android' ? 250 : 200,
|
|
presentation: 'card',
|
|
gestureEnabled: true,
|
|
gestureDirection: 'horizontal',
|
|
headerShown: false,
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="HeroCatalogs"
|
|
component={HeroCatalogsScreen}
|
|
options={{
|
|
animation: Platform.OS === 'android' ? 'slide_from_right' : 'fade',
|
|
animationDuration: Platform.OS === 'android' ? 250 : 200,
|
|
presentation: 'card',
|
|
gestureEnabled: true,
|
|
gestureDirection: 'horizontal',
|
|
headerShown: false,
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="ShowRatings"
|
|
component={ShowRatingsScreen}
|
|
options={{
|
|
animation: Platform.OS === 'android' ? 'fade_from_bottom' : 'fade',
|
|
animationDuration: Platform.OS === 'android' ? 200 : 200,
|
|
...(Platform.OS === 'ios' && { presentation: 'modal' }),
|
|
gestureEnabled: true,
|
|
gestureDirection: 'horizontal',
|
|
headerShown: false,
|
|
contentStyle: {
|
|
backgroundColor: 'transparent',
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="Calendar"
|
|
component={CalendarScreen as any}
|
|
options={{
|
|
animation: 'slide_from_right',
|
|
animationDuration: Platform.OS === 'android' ? 250 : 300,
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="NotificationSettings"
|
|
component={NotificationSettingsScreen as any}
|
|
options={{
|
|
animation: 'slide_from_right',
|
|
animationDuration: Platform.OS === 'android' ? 250 : 300,
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="MDBListSettings"
|
|
component={MDBListSettingsScreen}
|
|
options={{
|
|
animation: Platform.OS === 'android' ? 'slide_from_right' : 'fade',
|
|
animationDuration: Platform.OS === 'android' ? 250 : 200,
|
|
presentation: 'card',
|
|
gestureEnabled: true,
|
|
gestureDirection: 'horizontal',
|
|
headerShown: false,
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="TMDBSettings"
|
|
component={TMDBSettingsScreen}
|
|
options={{
|
|
animation: Platform.OS === 'android' ? 'slide_from_right' : 'fade',
|
|
animationDuration: Platform.OS === 'android' ? 250 : 200,
|
|
presentation: 'card',
|
|
gestureEnabled: true,
|
|
gestureDirection: 'horizontal',
|
|
headerShown: false,
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="TraktSettings"
|
|
component={TraktSettingsScreen}
|
|
options={{
|
|
animation: Platform.OS === 'android' ? 'slide_from_right' : 'fade',
|
|
animationDuration: Platform.OS === 'android' ? 250 : 200,
|
|
presentation: 'card',
|
|
gestureEnabled: true,
|
|
gestureDirection: 'horizontal',
|
|
headerShown: false,
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="PlayerSettings"
|
|
component={PlayerSettingsScreen}
|
|
options={{
|
|
animation: Platform.OS === 'android' ? 'slide_from_right' : 'fade',
|
|
animationDuration: Platform.OS === 'android' ? 250 : 200,
|
|
presentation: 'card',
|
|
gestureEnabled: true,
|
|
gestureDirection: 'horizontal',
|
|
headerShown: false,
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="LogoSourceSettings"
|
|
component={LogoSourceSettings}
|
|
options={{
|
|
animation: Platform.OS === 'android' ? 'slide_from_right' : 'fade',
|
|
animationDuration: Platform.OS === 'android' ? 250 : 200,
|
|
presentation: 'card',
|
|
gestureEnabled: true,
|
|
gestureDirection: 'horizontal',
|
|
headerShown: false,
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="ThemeSettings"
|
|
component={ThemeScreen}
|
|
options={{
|
|
animation: Platform.OS === 'android' ? 'slide_from_right' : 'fade',
|
|
animationDuration: Platform.OS === 'android' ? 250 : 200,
|
|
presentation: 'card',
|
|
gestureEnabled: true,
|
|
gestureDirection: 'horizontal',
|
|
headerShown: false,
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="ProfilesSettings"
|
|
component={ProfilesScreen}
|
|
options={{
|
|
animation: Platform.OS === 'android' ? 'slide_from_right' : 'fade',
|
|
animationDuration: Platform.OS === 'android' ? 250 : 200,
|
|
presentation: 'card',
|
|
gestureEnabled: true,
|
|
gestureDirection: 'horizontal',
|
|
headerShown: false,
|
|
contentStyle: {
|
|
backgroundColor: currentTheme.colors.darkBackground,
|
|
},
|
|
}}
|
|
/>
|
|
</Stack.Navigator>
|
|
</View>
|
|
</PaperProvider>
|
|
</SafeAreaProvider>
|
|
);
|
|
};
|
|
|
|
export default AppNavigator;
|