mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-07 02:30:25 +00:00
Update AppNavigator.tsx
This commit is contained in:
parent
066bf6f15d
commit
3b210b06d5
1 changed files with 87 additions and 88 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue