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.

339 lines
9.3 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';
3 weeks ago
import AsyncStorage from '@react-native-async-storage/async-storage';
import Ionicons from '@expo/vector-icons/Ionicons';
import { useNavigation, useRoute, useIsFocused } 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 useUserStore from '../store/user';
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 { user } = useUserStore();
const [showLoginPrompt, setShowLoginPrompt] = useState(false);
const [keyboardVisible, setKeyboardVisible] = useState(false);
3 weeks ago
const [promptShownBefore, setPromptShownBefore] = useState(false);
const [currentTab, setCurrentTab] = useState<string>('');
const isFocused = useIsFocused();
// 动画值
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();
};
}, []);
3 weeks ago
// 检查是否已经显示过弹窗
useEffect(() => {
const checkPromptShown = async () => {
try {
const value = await AsyncStorage.getItem('loginPromptShown');
setPromptShownBefore(value === 'true');
} catch (error) {
console.error('Error checking prompt status:', error);
}
};
checkPromptShown();
}, []);
// 监听当前tab页面变化及用户登录状态,决定是否显示登录提示
useEffect(() => {
if (isFocused && (currentTab === 'Home' || currentTab === 'Cart')) {
if (!user?.user_id && !promptShownBefore) {
showLoginModal();
}
}
}, [currentTab, user?.user_id, isFocused, promptShownBefore]);
const showLoginModal = () => {
setShowLoginPrompt(true);
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 1,
duration: 200,
useNativeDriver: true,
}),
Animated.timing(slideAnim, {
toValue: 0,
duration: 200,
useNativeDriver: true,
})
]).start();
};
const handleCloseModal = async () => {
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 0,
duration: 200,
useNativeDriver: true,
}),
Animated.timing(slideAnim, {
toValue: 300,
duration: 200,
useNativeDriver: true,
})
]).start(() => {
setShowLoginPrompt(false);
});
// 记录已经显示过登录提示
try {
await AsyncStorage.setItem('loginPromptShown', 'true');
setPromptShownBefore(true);
} catch (error) {
console.error('Error saving prompt status:', error);
}
};
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,
}}
screenListeners={({ route }) => ({
focus: () => {
setCurrentTab(route.name);
},
})}
>
<Tab.Screen
name="Home"
component={HomeScreen}
options={{
3 weeks ago
tabBarLabel: t('home'),
tabBarIcon: ({ color, size }: TabBarIconProps) => (
<IconComponent name="home-outline" size={size} color={color} />
),
}}
/>
<Tab.Screen
name="productCollection"
component={CategoryScreen}
options={{
3 weeks ago
tabBarLabel: t('categories'),
tabBarIcon: ({ color, size }: TabBarIconProps) => (
<IconComponent name="grid-outline" size={size} color={color} />
),
}}
/>
<Tab.Screen
name="Chat"
component={ChatScreen}
options={{
3 weeks ago
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.cart'),
tabBarIcon: ({ color, size }: TabBarIconProps) => (
<IconComponent name="cart-outline" size={size} color={color} />
),
}}
/>
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
3 weeks ago
tabBarLabel: t('my'),
tabBarIcon: ({ color, size }: TabBarIconProps) => (
<IconComponent name="person-outline" size={size} color={color} />
),
}}
/>
</Tab.Navigator>
{showLoginPrompt && (
<Modal
animationType="none"
transparent={true}
visible={showLoginPrompt}
onRequestClose={handleCloseModal}
>
<View style={styles.modalContainer}>
<Animated.View
style={[
styles.modalOverlay,
{ opacity: fadeAnim }
]}
>
<TouchableOpacity
style={styles.modalOverlayTouch}
onPress={handleCloseModal}
activeOpacity={1}
/>
</Animated.View>
<Animated.View
style={[
styles.modalContent,
{
transform: [{ translateY: slideAnim }]
}
]}
>
<View style={styles.modalHeader}>
<View style={styles.modalIndicator} />
</View>
<TouchableOpacity style={styles.closeButton} onPress={handleCloseModal}>
<Text style={styles.closeButtonText}>×</Text>
</TouchableOpacity>
<Text style={styles.modalTitle}>{t('login.loginRequired')}</Text>
<Text style={styles.modalText}>{t('login.loginPrompt')}</Text>
<TouchableOpacity style={styles.modalButton} onPress={handleGoToLogin}>
<Text style={styles.modalButtonText}>{t('login.loginNow')}</Text>
</TouchableOpacity>
</Animated.View>
</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',
},
});