You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
321 lines
8.4 KiB
321 lines
8.4 KiB
import React, { useEffect, useState, useRef } from 'react'; |
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; |
|
import { Platform, Modal, View, StyleSheet, Text, TouchableOpacity, Animated, Keyboard } from 'react-native'; |
|
import Ionicons from '@expo/vector-icons/Ionicons'; |
|
import { useNavigation, useRoute } from '@react-navigation/native'; |
|
import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; |
|
import { RootStackParamList } from '../types/navigation'; |
|
import { useAuth } from '../contexts/AuthContext'; |
|
import { useTranslation } from 'react-i18next'; |
|
|
|
import { HomeScreen } from '../screens/HomeScreen'; |
|
import { CategoryScreen } from '../screens/CategoryScreen'; |
|
import { ChatScreen } from '../screens/ChatScreen'; |
|
import { CartScreen } from '../screens/CartScreen'; |
|
import { ProfileScreen } from '../screens/ProfileScreen'; |
|
type IconProps = { |
|
name: string; |
|
size: number; |
|
color: string; |
|
}; |
|
|
|
const IconComponent = ({ name, size, color }: IconProps) => { |
|
const Icon = Ionicons as any; |
|
return <Icon name={name} size={size} color={color} />; |
|
}; |
|
|
|
type TabBarIconProps = { |
|
color: string; |
|
size: number; |
|
}; |
|
|
|
const Tab = createBottomTabNavigator(); |
|
|
|
export const TabNavigator = () => { |
|
const { t } = useTranslation(); |
|
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>(); |
|
const route = useRoute(); |
|
const { isLoggedIn } = useAuth(); |
|
const [showLoginPrompt, setShowLoginPrompt] = useState(false); |
|
const [keyboardVisible, setKeyboardVisible] = useState(false); |
|
|
|
// 动画值 |
|
const fadeAnim = useRef(new Animated.Value(0)).current; |
|
const slideAnim = useRef(new Animated.Value(300)).current; |
|
|
|
// 监听键盘事件 |
|
useEffect(() => { |
|
const keyboardDidShowListener = Keyboard.addListener( |
|
'keyboardDidShow', |
|
() => { |
|
setKeyboardVisible(true); |
|
} |
|
); |
|
const keyboardDidHideListener = Keyboard.addListener( |
|
'keyboardDidHide', |
|
() => { |
|
setKeyboardVisible(false); |
|
} |
|
); |
|
|
|
return () => { |
|
keyboardDidHideListener.remove(); |
|
keyboardDidShowListener.remove(); |
|
}; |
|
}, []); |
|
|
|
// 检查登录状态 |
|
useEffect(() => { |
|
// 如果是从 CountrySelect 页面来的,不显示登录提示 |
|
if (route.name === 'CountrySelect') { |
|
return; |
|
} |
|
|
|
if (!isLoggedIn) { |
|
setShowLoginPrompt(true); |
|
Animated.parallel([ |
|
Animated.timing(fadeAnim, { |
|
toValue: 1, |
|
duration: 300, |
|
useNativeDriver: true, |
|
}), |
|
Animated.spring(slideAnim, { |
|
toValue: 0, |
|
tension: 50, |
|
friction: 9, |
|
useNativeDriver: true, |
|
}) |
|
]).start(); |
|
} else { |
|
// 如果已登录,关闭弹窗 |
|
handleCloseModal(); |
|
} |
|
}, [isLoggedIn, route.name]); |
|
|
|
const handleCloseModal = () => { |
|
Animated.parallel([ |
|
Animated.timing(fadeAnim, { |
|
toValue: 0, |
|
duration: 200, |
|
useNativeDriver: true, |
|
}), |
|
Animated.timing(slideAnim, { |
|
toValue: 300, |
|
duration: 200, |
|
useNativeDriver: true, |
|
}) |
|
]).start(() => { |
|
setShowLoginPrompt(false); |
|
}); |
|
}; |
|
|
|
const handleGoToLogin = () => { |
|
handleCloseModal(); |
|
navigation.navigate('Login'); |
|
}; |
|
|
|
return ( |
|
<> |
|
<Tab.Navigator |
|
screenOptions={{ |
|
tabBarActiveTintColor: '#0066FF', |
|
tabBarInactiveTintColor: '#999999', |
|
tabBarStyle: { |
|
height: Platform.OS === 'ios' ? 55 : 55, |
|
paddingBottom: Platform.OS === 'ios' ? 10 : 3, |
|
paddingTop: 3, |
|
backgroundColor: '#FFFFFF', |
|
borderTopWidth: 1, |
|
borderTopColor: '#F0F0F0', |
|
display: keyboardVisible ? 'none' : 'flex', |
|
}, |
|
headerShown: false, |
|
}} |
|
> |
|
<Tab.Screen |
|
name="Home" |
|
component={HomeScreen} |
|
options={{ |
|
tabBarLabel: t('home'), |
|
tabBarIcon: ({ color, size }: TabBarIconProps) => ( |
|
<IconComponent name="home-outline" size={size} color={color} /> |
|
), |
|
}} |
|
/> |
|
<Tab.Screen |
|
name="Category" |
|
component={CategoryScreen} |
|
options={{ |
|
tabBarLabel: t('categories'), |
|
tabBarIcon: ({ color, size }: TabBarIconProps) => ( |
|
<IconComponent name="grid-outline" size={size} color={color} /> |
|
), |
|
}} |
|
/> |
|
<Tab.Screen |
|
name="Chat" |
|
component={ChatScreen} |
|
options={{ |
|
tabBarLabel: t('chat'), |
|
tabBarIcon: ({ color, size }: TabBarIconProps) => ( |
|
<IconComponent name="chatbubble-outline" size={size} color={color} /> |
|
), |
|
}} |
|
/> |
|
<Tab.Screen |
|
name="Cart" |
|
component={CartScreen} |
|
options={{ |
|
tabBarLabel: t('cart'), |
|
tabBarIcon: ({ color, size }: TabBarIconProps) => ( |
|
<IconComponent name="cart-outline" size={size} color={color} /> |
|
), |
|
}} |
|
/> |
|
<Tab.Screen |
|
name="Profile" |
|
component={ProfileScreen} |
|
options={{ |
|
tabBarLabel: t('my'), |
|
tabBarIcon: ({ color, size }: TabBarIconProps) => ( |
|
<IconComponent name="person-outline" size={size} color={color} /> |
|
), |
|
}} |
|
/> |
|
</Tab.Navigator> |
|
|
|
<Modal |
|
visible={showLoginPrompt} |
|
transparent={true} |
|
animationType="none" |
|
statusBarTranslucent={true} |
|
onRequestClose={handleCloseModal} |
|
> |
|
<Animated.View |
|
style={[ |
|
styles.modalContainer, |
|
{ |
|
opacity: fadeAnim, |
|
} |
|
]} |
|
> |
|
<Animated.View |
|
style={[ |
|
styles.modalOverlay, |
|
{ |
|
opacity: fadeAnim, |
|
} |
|
]} |
|
> |
|
<TouchableOpacity |
|
style={styles.modalOverlayTouch} |
|
activeOpacity={1} |
|
onPress={handleCloseModal} |
|
/> |
|
</Animated.View> |
|
<Animated.View |
|
style={[ |
|
styles.modalContent, |
|
{ |
|
transform: [{ translateY: slideAnim }], |
|
}, |
|
]} |
|
> |
|
<TouchableOpacity |
|
style={styles.closeButton} |
|
onPress={handleCloseModal} |
|
> |
|
<Text style={styles.closeButtonText}>✕</Text> |
|
</TouchableOpacity> |
|
<View style={styles.modalHeader}> |
|
<View style={styles.modalIndicator} /> |
|
</View> |
|
<Text style={styles.modalTitle}>{t('welcomeTitle')}</Text> |
|
<Text style={styles.modalText}> |
|
{t('welcomeMessage')} |
|
</Text> |
|
<TouchableOpacity |
|
style={styles.modalButton} |
|
onPress={handleGoToLogin} |
|
> |
|
<Text style={styles.modalButtonText}>{t('loginNow')}</Text> |
|
</TouchableOpacity> |
|
</Animated.View> |
|
</Animated.View> |
|
</Modal> |
|
</> |
|
); |
|
}; |
|
|
|
const styles = StyleSheet.create({ |
|
modalContainer: { |
|
flex: 1, |
|
justifyContent: 'flex-end', |
|
}, |
|
modalOverlay: { |
|
...StyleSheet.absoluteFillObject, |
|
backgroundColor: 'rgba(0, 0, 0, 0.5)', |
|
}, |
|
modalOverlayTouch: { |
|
flex: 1, |
|
}, |
|
modalContent: { |
|
backgroundColor: '#fff', |
|
borderTopLeftRadius: 24, |
|
borderTopRightRadius: 24, |
|
paddingHorizontal: 20, |
|
paddingBottom: Platform.OS === 'ios' ? 40 : 20, |
|
position: 'relative', |
|
}, |
|
closeButton: { |
|
position: 'absolute', |
|
top: 20, |
|
right: 20, |
|
width: 24, |
|
height: 24, |
|
justifyContent: 'center', |
|
alignItems: 'center', |
|
zIndex: 1, |
|
}, |
|
closeButtonText: { |
|
fontSize: 20, |
|
color: '#999', |
|
}, |
|
modalHeader: { |
|
alignItems: 'center', |
|
paddingTop: 12, |
|
paddingBottom: 20, |
|
}, |
|
modalIndicator: { |
|
width: 40, |
|
height: 4, |
|
backgroundColor: '#E0E0E0', |
|
borderRadius: 2, |
|
}, |
|
modalTitle: { |
|
fontSize: 20, |
|
fontWeight: '600', |
|
color: '#000', |
|
textAlign: 'center', |
|
marginBottom: 12, |
|
}, |
|
modalText: { |
|
fontSize: 16, |
|
color: '#666', |
|
textAlign: 'center', |
|
marginBottom: 24, |
|
lineHeight: 22, |
|
}, |
|
modalButton: { |
|
backgroundColor: '#0066FF', |
|
height: 50, |
|
borderRadius: 25, |
|
justifyContent: 'center', |
|
alignItems: 'center', |
|
}, |
|
modalButtonText: { |
|
color: '#fff', |
|
fontSize: 16, |
|
fontWeight: '600', |
|
}, |
|
});
|