Update AppNavigator.tsx

This commit is contained in:
AdityasahuX07 2026-01-07 17:18:37 +05:30 committed by GitHub
parent 066bf6f15d
commit 3b210b06d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -17,6 +17,7 @@ import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-cont
import { useTheme } from '../contexts/ThemeContext'; import { useTheme } from '../contexts/ThemeContext';
import { PostHogProvider } from 'posthog-react-native'; import { PostHogProvider } from 'posthog-react-native';
import { ScrollToTopProvider, useScrollToTopEmitter } from '../contexts/ScrollToTopContext'; import { ScrollToTopProvider, useScrollToTopEmitter } from '../contexts/ScrollToTopContext';
import { useTranslation } from 'react-i18next';
// Optional iOS Glass effect (expo-glass-effect) with safe fallback // Optional iOS Glass effect (expo-glass-effect) with safe fallback
let GlassViewComp: any = null; let GlassViewComp: any = null;
@ -71,7 +72,6 @@ import BackupScreen from '../screens/BackupScreen';
import ContinueWatchingSettingsScreen from '../screens/ContinueWatchingSettingsScreen'; import ContinueWatchingSettingsScreen from '../screens/ContinueWatchingSettingsScreen';
import ContributorsScreen from '../screens/ContributorsScreen'; import ContributorsScreen from '../screens/ContributorsScreen';
import DebridIntegrationScreen from '../screens/DebridIntegrationScreen'; import DebridIntegrationScreen from '../screens/DebridIntegrationScreen';
import { import {
ContentDiscoverySettingsScreen, ContentDiscoverySettingsScreen,
AppearanceSettingsScreen, AppearanceSettingsScreen,
@ -79,6 +79,7 @@ import {
PlaybackSettingsScreen, PlaybackSettingsScreen,
AboutSettingsScreen, AboutSettingsScreen,
DeveloperSettingsScreen, DeveloperSettingsScreen,
LegalScreen,
} from '../screens/settings'; } from '../screens/settings';
@ -217,6 +218,7 @@ export type RootStackParamList = {
PlaybackSettings: undefined; PlaybackSettings: undefined;
AboutSettings: undefined; AboutSettings: undefined;
DeveloperSettings: undefined; DeveloperSettings: undefined;
Legal: undefined;
}; };
@ -471,7 +473,6 @@ const TabIcon = React.memo(({ focused, color, iconName, iconLibrary = 'material'
// Update the TabScreenWrapper component with fixed layout dimensions // Update the TabScreenWrapper component with fixed layout dimensions
const TabScreenWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => { const TabScreenWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [dimensions, setDimensions] = useState(Dimensions.get('window'));
useEffect(() => { useEffect(() => {
const subscription = Dimensions.addEventListener('change', ({ window }) => { const subscription = Dimensions.addEventListener('change', ({ window }) => {
@ -546,12 +547,14 @@ const WrappedScreen: React.FC<{ Screen: React.ComponentType<any> }> = ({ Screen
// Tab Navigator // Tab Navigator
const MainTabs = () => { const MainTabs = () => {
const { t } = useTranslation();
const { currentTheme } = useTheme(); const { currentTheme } = useTheme();
const { settings } = require('../hooks/useSettings'); const { settings } = require('../hooks/useSettings');
const { useSettings: useSettingsHook } = require('../hooks/useSettings'); const { useSettings: useSettingsHook } = require('../hooks/useSettings');
const { settings: appSettings } = useSettingsHook(); const { settings: appSettings } = useSettingsHook();
const [hasUpdateBadge, setHasUpdateBadge] = React.useState(false); const [hasUpdateBadge, setHasUpdateBadge] = React.useState(false);
const [dimensions, setDimensions] = useState(Dimensions.get('window')); const [dimensions, setDimensions] = useState(Dimensions.get('window'));
const lastTapRef = useRef<Record<string, number>>({});
useEffect(() => { useEffect(() => {
const subscription = Dimensions.addEventListener('change', ({ window }) => { const subscription = Dimensions.addEventListener('change', ({ window }) => {
@ -689,17 +692,16 @@ const MainTabs = () => {
const isFocused = props.state.index === index; const isFocused = props.state.index === index;
const lastTapRef = useRef<Record<string, number>>({}); // Add this ref at the top of MainTabs component
const onPress = () => { const onPress = () => {
// Create a synthetic event object we can modify const now = Date.now();
const syntheticEvent = { const DOUBLE_TAP_DELAY = 300;
type: 'tabPress', const lastTap = lastTapRef.current[route.name] || 0;
target: route.key, const isSearchDoubleTap = route.name === 'Search' && (now - lastTap) < DOUBLE_TAP_DELAY;
canPreventDefault: true,
defaultPrevented: false, // Update last tap time
preventDefault: () => { lastTapRef.current[route.name] = now;
syntheticEvent.defaultPrevented = true;
}
};
const event = props.navigation.emit({ const event = props.navigation.emit({
type: 'tabPress', type: 'tabPress',
@ -708,14 +710,14 @@ const MainTabs = () => {
}); });
if (isFocused) { if (isFocused) {
if (route.name === 'Search') { // If double tap on Search -> Open Keyboard
// Send the signal to SearchScreen.tsx to focus input if (isSearchDoubleTap) {
DeviceEventEmitter.emit('FOCUS_SEARCH_INPUT'); DeviceEventEmitter.emit('FOCUS_SEARCH_INPUT');
syntheticEvent.preventDefault(); // Prevent scroll-to-top
} else { } else {
// Single tap on active tab -> Scroll to Top
emitScrollToTop(route.name); emitScrollToTop(route.name);
} }
} else if (!syntheticEvent.defaultPrevented && !event.defaultPrevented) { } else if (!event.defaultPrevented) {
props.navigation.navigate(route.name); props.navigation.navigate(route.name);
} }
}; };
@ -824,27 +826,29 @@ const MainTabs = () => {
const isFocused = props.state.index === index; const isFocused = props.state.index === index;
const onPress = () => { const onPress = () => {
// For Search tab, we need to handle this specially const now = Date.now();
if (isFocused && route.name === 'Search') { const DOUBLE_TAP_DELAY = 300;
// Focus search input instead of scrolling to top const lastTap = lastTapRef.current[route.name] || 0;
DeviceEventEmitter.emit('FOCUS_SEARCH_INPUT');
// Return early to prevent navigation and scroll-to-top
return;
}
const event = props.navigation.emit({ // DOUBLE TAP LOGIC: If search is pressed twice quickly
type: 'tabPress', if (route.name === 'Search' && now - lastTap < DOUBLE_TAP_DELAY) {
target: route.key, DeviceEventEmitter.emit('FOCUS_SEARCH_INPUT');
canPreventDefault: true, }
});
if (isFocused) { lastTapRef.current[route.name] = now;
// For other tabs, scroll to top
emitScrollToTop(route.name); const event = props.navigation.emit({
} else if (!event.defaultPrevented) { type: 'tabPress',
props.navigation.navigate(route.name); target: route.key,
} canPreventDefault: true,
}; });
if (isFocused) {
emitScrollToTop(route.name);
} else if (!event.defaultPrevented) {
props.navigation.navigate(route.name);
}
};
let iconName: IconNameType = 'home'; let iconName: IconNameType = 'home';
let iconLibrary: 'material' | 'feather' | 'ionicons' = 'material'; let iconLibrary: 'material' | 'feather' | 'ionicons' = 'material';
@ -941,7 +945,7 @@ const MainTabs = () => {
name="Home" name="Home"
component={HomeScreen} component={HomeScreen}
options={{ options={{
title: 'Home', title: t('navigation.home'),
tabBarIcon: () => ({ sfSymbol: 'house' }), tabBarIcon: () => ({ sfSymbol: 'house' }),
freezeOnBlur: true, freezeOnBlur: true,
}} }}
@ -957,7 +961,7 @@ const MainTabs = () => {
name="Library" name="Library"
component={LibraryScreen} component={LibraryScreen}
options={{ options={{
title: 'Library', title: t('navigation.library'),
tabBarIcon: () => ({ sfSymbol: 'heart' }), tabBarIcon: () => ({ sfSymbol: 'heart' }),
}} }}
listeners={({ navigation }: { navigation: any }) => ({ listeners={({ navigation }: { navigation: any }) => ({
@ -972,16 +976,13 @@ const MainTabs = () => {
name="Search" name="Search"
component={SearchScreen} component={SearchScreen}
options={{ options={{
title: 'Search', title: t('navigation.search'),
tabBarIcon: () => ({ sfSymbol: 'magnifyingglass' }), tabBarIcon: () => ({ sfSymbol: 'magnifyingglass' }),
}} }}
listeners={({ navigation }: { navigation: any }) => ({ listeners={({ navigation }: { navigation: any }) => ({
tabPress: (e: any) => { tabPress: (e: any) => {
if (navigation.isFocused()) { if (navigation.isFocused()) {
// Focus search input instead of scrolling to top emitScrollToTop('Search');
DeviceEventEmitter.emit('FOCUS_SEARCH_INPUT');
// Don't try to access preventDefault on native iOS tabs
// Just emit the event and let SearchScreen handle it
} }
}, },
})} })}
@ -991,7 +992,7 @@ const MainTabs = () => {
name="Downloads" name="Downloads"
component={DownloadsScreen} component={DownloadsScreen}
options={{ options={{
title: 'Downloads', title: t('navigation.downloads'),
tabBarIcon: () => ({ sfSymbol: 'arrow.down.circle' }), tabBarIcon: () => ({ sfSymbol: 'arrow.down.circle' }),
}} }}
listeners={({ navigation }: { navigation: any }) => ({ listeners={({ navigation }: { navigation: any }) => ({
@ -1007,7 +1008,7 @@ const MainTabs = () => {
name="Settings" name="Settings"
component={SettingsScreen} component={SettingsScreen}
options={{ options={{
title: 'Settings', title: t('navigation.settings'),
tabBarIcon: () => ({ sfSymbol: 'gear' }), tabBarIcon: () => ({ sfSymbol: 'gear' }),
}} }}
listeners={({ navigation }: { navigation: any }) => ({ listeners={({ navigation }: { navigation: any }) => ({
@ -1082,92 +1083,75 @@ const MainTabs = () => {
name="Home" name="Home"
component={HomeScreen} component={HomeScreen}
options={{ options={{
tabBarLabel: 'Home', tabBarLabel: t('navigation.home'),
tabBarIcon: ({ color, size, focused }) => ( tabBarIcon: ({ color, size, focused }) => (
<MaterialCommunityIcons name={focused ? 'home' : 'home-outline'} size={size} color={color} /> <MaterialCommunityIcons name={focused ? 'home' : 'home-outline'} size={size} color={color} />
), ),
freezeOnBlur: true, freezeOnBlur: true,
}} }}
listeners={({ navigation }: any) => ({
tabPress: (e: any) => {
if (navigation.isFocused()) {
emitScrollToTop('Home');
}
},
})}
/> />
<Tab.Screen <Tab.Screen
name="Library" name="Library"
component={LibraryScreen} component={LibraryScreen}
options={{ options={{
tabBarLabel: 'Library', tabBarLabel: t('navigation.library'),
tabBarIcon: ({ color, size, focused }) => ( tabBarIcon: ({ color, size, focused }) => (
<MaterialCommunityIcons name={focused ? 'heart' : 'heart-outline'} size={size} color={color} /> <MaterialCommunityIcons name={focused ? 'heart' : 'heart-outline'} size={size} color={color} />
), ),
}} }}
listeners={({ navigation }: any) => ({
tabPress: (e: any) => {
if (navigation.isFocused()) {
emitScrollToTop('Library');
}
},
})}
/> />
<Tab.Screen <Tab.Screen
name="Search" name="Search"
component={SearchScreen} component={SearchScreen}
options={{ options={{
tabBarLabel: 'Search', tabBarLabel: t('tabs.search'),
tabBarIcon: ({ color, size }) => ( tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name={'magnify'} size={size} color={color} /> <Feather name="search" size={size} color={color} />
), ),
}} tabBarButton: (props) => {
listeners={({ navigation }: any) => ({ const lastTap = useRef(0);
tabPress: (e: any) => { return (
if (navigation.isFocused()) { <TouchableOpacity
// Focus search input instead of scrolling to top {...props}
DeviceEventEmitter.emit('FOCUS_SEARCH_INPUT'); activeOpacity={0.7}
// Simply return to prevent navigation onPress={(e) => {
return; const now = Date.now();
} const DOUBLE_TAP_DELAY = 300;
// Check for double tap
if (now - lastTap.current < DOUBLE_TAP_DELAY) {
DeviceEventEmitter.emit('FOCUS_SEARCH_INPUT');
} else {
props.onPress?.(e);
}
lastTap.current = now;
}}
/>
);
}, },
})} }}
/> />
{appSettings?.enableDownloads !== false && ( {appSettings?.enableDownloads !== false && (
<Tab.Screen <Tab.Screen
name="Downloads" name="Downloads"
component={DownloadsScreen} component={DownloadsScreen}
options={{ options={{
tabBarLabel: 'Downloads', tabBarLabel: t('navigation.downloads'),
tabBarIcon: ({ color, size, focused }) => ( tabBarIcon: ({ color, size, focused }) => (
<MaterialCommunityIcons name={focused ? 'download' : 'download-outline'} size={size} color={color} /> <MaterialCommunityIcons name={focused ? 'download' : 'download-outline'} size={size} color={color} />
), ),
}} }}
listeners={({ navigation }: any) => ({
tabPress: (e: any) => {
if (navigation.isFocused()) {
emitScrollToTop('Downloads');
}
},
})}
/> />
)} )}
<Tab.Screen <Tab.Screen
name="Settings" name="Settings"
component={SettingsScreen} component={SettingsScreen}
options={{ options={{
tabBarLabel: 'Settings', tabBarLabel: t('navigation.settings'),
tabBarIcon: ({ color, size, focused }) => ( tabBarIcon: ({ color, size, focused }) => (
<MaterialCommunityIcons name={focused ? 'cog' : 'cog-outline'} size={size} color={color} /> <MaterialCommunityIcons name={focused ? 'cog' : 'cog-outline'} size={size} color={color} />
), ),
}} }}
listeners={({ navigation }: any) => ({
tabPress: (e: any) => {
if (navigation.isFocused()) {
emitScrollToTop('Settings');
}
},
})}
/> />
</Tab.Navigator> </Tab.Navigator>
</View> </View>
@ -1816,6 +1800,21 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta
}, },
}} }}
/> />
<Stack.Screen
name="Legal"
component={LegalScreen}
options={{
animation: Platform.OS === 'android' ? 'slide_from_right' : 'slide_from_right',
animationDuration: Platform.OS === 'android' ? 250 : 300,
presentation: 'card',
gestureEnabled: true,
gestureDirection: 'horizontal',
headerShown: false,
contentStyle: {
backgroundColor: currentTheme.colors.darkBackground,
},
}}
/>
</Stack.Navigator> </Stack.Navigator>
</View> </View>
</PaperProvider> </PaperProvider>