Files
frontend/src/navigation/SimpleMobileTabNavigator.tsx

306 lines
7.8 KiB
TypeScript
Raw Normal View History

2026-03-16 17:47:10 +08:00
/**
* Tab Navigator
*
2026-03-16 17:47:10 +08:00
* 使 React Navigation Tab Navigator使
* React Navigation
*/
import React, { useEffect, useState, useCallback } from 'react';
import {
View,
StyleSheet,
TouchableOpacity,
Text,
Dimensions,
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
2026-03-16 17:47:10 +08:00
import { colors, shadows } from '../theme';
import { useTotalUnreadCount } from '../stores';
import { messageManager } from '../stores';
// ==================== 导入屏幕组件 ====================
import { HomeScreen, SearchScreen } from '../screens/home';
import { ScheduleScreen, CourseDetailScreen } from '../screens/schedule';
import {
MessageListScreen,
NotificationsScreen,
PrivateChatInfoScreen,
} from '../screens/message';
import { ProfileScreen, SettingsScreen, EditProfileScreen, NotificationSettingsScreen, BlockedUsersScreen, AccountSecurityScreen } from '../screens/profile';
// ==================== 类型定义 ====================
type TabName = 'HomeTab' | 'MessageTab' | 'ScheduleTab' | 'ProfileTab';
// Schedule Stack 类型定义
export type ScheduleStackParamList = {
Schedule: undefined;
CourseDetail: { course: any; relatedCourses?: any[] };
};
// ==================== Stack Navigators ====================
const ScheduleStack = createNativeStackNavigator<ScheduleStackParamList>();
2026-03-16 17:47:10 +08:00
// ==================== 常量 ====================
const TAB_BAR_HEIGHT = 64;
const TAB_BAR_MARGIN = 14;
const TAB_BAR_FLOATING_MARGIN = 12;
// ==================== 组件 ====================
/**
* Schedule Stack Navigator
2026-03-16 17:47:10 +08:00
*/
function ScheduleStackNavigatorComponent() {
2026-03-16 17:47:10 +08:00
return (
<ScheduleStack.Navigator
screenOptions={{
headerShown: false,
}}
>
<ScheduleStack.Screen
name="Schedule"
component={ScheduleScreen}
options={{
title: '课表',
}}
/>
<ScheduleStack.Screen
name="CourseDetail"
component={CourseDetailScreen}
options={{
headerShown: false,
presentation: 'transparentModal',
animation: 'fade',
}}
/>
</ScheduleStack.Navigator>
2026-03-16 17:47:10 +08:00
);
}
/**
* SimpleMobileTabNavigator
*/
export function SimpleMobileTabNavigator() {
const insets = useSafeAreaInsets();
const messageUnreadCount = useTotalUnreadCount();
const [activeTab, setActiveTab] = useState<TabName>('HomeTab');
// 初始化 MessageManager
useEffect(() => {
messageManager.initialize();
}, []);
// 处理 Tab 切换
const handleTabPress = useCallback((tabName: TabName) => {
setActiveTab(tabName);
}, []);
// 渲染当前 Tab 的内容
const renderTabContent = () => {
switch (activeTab) {
case 'HomeTab':
return <HomeScreen />;
case 'MessageTab':
return <MessageListScreen />;
case 'ScheduleTab':
return <ScheduleStackNavigatorComponent />;
2026-03-16 17:47:10 +08:00
case 'ProfileTab':
return <ProfileScreen />;
default:
return <HomeScreen />;
}
};
// 渲染 Tab Bar 图标
const renderTabIcon = (tabName: TabName, isActive: boolean) => {
const iconColor = isActive ? colors.primary.main : colors.text.secondary;
const iconSize = isActive ? 26 : 24;
switch (tabName) {
case 'HomeTab':
return (
<MaterialCommunityIcons
name={isActive ? 'home' : 'home-outline'}
size={iconSize}
color={iconColor}
/>
);
case 'MessageTab':
return (
<MaterialCommunityIcons
name={isActive ? 'message-text' : 'message-text-outline'}
size={iconSize}
color={iconColor}
/>
);
case 'ScheduleTab':
return (
<MaterialCommunityIcons
name={isActive ? 'calendar-today' : 'calendar-today'}
size={iconSize}
color={iconColor}
/>
);
case 'ProfileTab':
return (
<MaterialCommunityIcons
name={isActive ? 'account' : 'account-outline'}
size={iconSize}
color={iconColor}
/>
);
default:
return null;
}
};
// 渲染 Tab Bar 标签
const renderTabLabel = (tabName: TabName, isActive: boolean) => {
const labels: Record<TabName, string> = {
HomeTab: '首页',
MessageTab: '消息',
ScheduleTab: '课表',
ProfileTab: '我的',
};
return (
<Text
style={[
styles.tabLabel,
isActive && styles.tabLabelActive,
]}
>
{labels[tabName]}
</Text>
);
};
return (
<View style={styles.container}>
{/* 内容区域 */}
<View style={styles.content}>
{renderTabContent()}
</View>
{/* Tab Bar */}
<View
style={[
styles.tabBar,
{
bottom: TAB_BAR_FLOATING_MARGIN + insets.bottom,
},
]}
>
{(['HomeTab', 'MessageTab', 'ScheduleTab', 'ProfileTab'] as TabName[]).map(
(tabName) => {
const isActive = activeTab === tabName;
const showBadge = tabName === 'MessageTab' && messageUnreadCount > 0;
return (
<TouchableOpacity
key={tabName}
style={[
styles.tabItem,
isActive && styles.tabItemActive,
]}
onPress={() => handleTabPress(tabName)}
activeOpacity={0.7}
>
<View style={styles.tabIconContainer}>
{renderTabIcon(tabName, isActive)}
{showBadge && (
<View style={styles.badge}>
<Text style={styles.badgeText}>
{messageUnreadCount > 99 ? '99+' : messageUnreadCount}
</Text>
</View>
)}
</View>
{renderTabLabel(tabName, isActive)}
</TouchableOpacity>
);
}
)}
</View>
</View>
);
}
// ==================== 样式 ====================
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background.default,
},
stackContainer: {
flex: 1,
},
content: {
flex: 1,
marginBottom: TAB_BAR_HEIGHT + TAB_BAR_FLOATING_MARGIN * 2,
},
tabBar: {
position: 'absolute',
left: TAB_BAR_MARGIN,
right: TAB_BAR_MARGIN,
height: TAB_BAR_HEIGHT,
backgroundColor: colors.background.paper,
borderRadius: 24,
borderTopWidth: 1,
borderTopColor: `${colors.divider}88`,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
paddingHorizontal: 8,
...shadows.lg,
},
tabItem: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 6,
borderRadius: 18,
},
tabItemActive: {
backgroundColor: `${colors.primary.main}15`,
},
tabIconContainer: {
position: 'relative',
width: 38,
height: 30,
alignItems: 'center',
justifyContent: 'center',
borderRadius: 15,
},
tabLabel: {
fontSize: 12,
fontWeight: '600',
marginTop: -2,
letterSpacing: 0.2,
color: colors.text.secondary,
},
tabLabelActive: {
color: colors.primary.main,
},
badge: {
position: 'absolute',
top: -2,
right: -2,
backgroundColor: colors.error.main,
borderRadius: 10,
minWidth: 18,
height: 18,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 4,
},
badgeText: {
color: colors.primary.contrast,
fontSize: 10,
fontWeight: '600',
},
});