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
2 months ago
|
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' ? 88 : 60,
|
||
|
paddingBottom: Platform.OS === 'ios' ? 28 : 8,
|
||
|
paddingTop: 8,
|
||
|
backgroundColor: '#FFFFFF',
|
||
|
borderTopWidth: 1,
|
||
|
borderTopColor: '#F0F0F0',
|
||
|
display: keyboardVisible ? 'none' : 'flex',
|
||
|
},
|
||
|
headerShown: false,
|
||
|
}}
|
||
|
>
|
||
|
<Tab.Screen
|
||
|
name="Home"
|
||
|
component={HomeScreen}
|
||
|
options={{
|
||
|
tabBarLabel: '首页',
|
||
|
tabBarIcon: ({ color, size }: TabBarIconProps) => (
|
||
|
<IconComponent name="home-outline" size={size} color={color} />
|
||
|
),
|
||
|
}}
|
||
|
/>
|
||
|
<Tab.Screen
|
||
|
name="Category"
|
||
|
component={CategoryScreen}
|
||
|
options={{
|
||
|
tabBarLabel: '分类',
|
||
|
tabBarIcon: ({ color, size }: TabBarIconProps) => (
|
||
|
<IconComponent name="grid-outline" size={size} color={color} />
|
||
|
),
|
||
|
}}
|
||
|
/>
|
||
|
<Tab.Screen
|
||
|
name="Chat"
|
||
|
component={ChatScreen}
|
||
|
options={{
|
||
|
tabBarLabel: '聊天',
|
||
|
tabBarIcon: ({ color, size }: TabBarIconProps) => (
|
||
|
<IconComponent name="chatbubble-outline" size={size} color={color} />
|
||
|
),
|
||
|
}}
|
||
|
/>
|
||
|
<Tab.Screen
|
||
|
name="Cart"
|
||
|
component={CartScreen}
|
||
|
options={{
|
||
|
tabBarLabel: '购物车',
|
||
|
tabBarIcon: ({ color, size }: TabBarIconProps) => (
|
||
|
<IconComponent name="cart-outline" size={size} color={color} />
|
||
|
),
|
||
|
}}
|
||
|
/>
|
||
|
<Tab.Screen
|
||
|
name="Profile"
|
||
|
component={ProfileScreen}
|
||
|
options={{
|
||
|
tabBarLabel: '我的',
|
||
|
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',
|
||
|
},
|
||
|
});
|