refactor: 架构重构 - 解耦过度耦合模块
主要改动: 1. 创建乐观更新工具函数 (optimisticUpdate.ts) - 消除 userStore.ts 中的重复代码 2. 拆分 useResponsive.ts (485行 -> 12个专注模块) - useBreakpoint: 断点检测 - useOrientation: 方向检测 - usePlatform: 平台检测 - useScreenSize: 屏幕尺寸 - useResponsiveValue: 响应式值 - useResponsiveStyle: 响应式样式 - useMediaQuery: 媒体查询 - useColumnCount: 列数计算 - useResponsiveSpacing: 响应式间距 3. 整理数据层 (Repository 层) - ApiDataSource: API数据源 - LocalDataSource: 本地数据源 - CacheDataSource: 缓存数据源 - MessageRepository: 消息仓库 4. 重构 messageManager.ts (2194行 -> 4个模块) - MessageStateManager: 状态管理 - WebSocketMessageHandler: WebSocket处理 - MessageSyncService: 消息同步 - ReadReceiptManager: 已读管理 5. 导航解耦 (MainNavigator.tsx: 1118行 -> 100行) - 创建 NavigationService 解耦层 - 拆分多个 Navigator 组件 架构改进: - 单一职责原则: 每个模块职责明确 - 依赖倒置: 通过接口解耦 - 代码复用: 工具函数可被多处使用 - 可测试性: 各模块可独立测试
This commit is contained in:
25
src/navigation/AuthNavigator.tsx
Normal file
25
src/navigation/AuthNavigator.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 认证流程导航
|
||||
* 处理登录、注册、忘记密码等认证页面
|
||||
*/
|
||||
import React from 'react';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import type { AuthStackParamList } from './types';
|
||||
|
||||
import { LoginScreen, RegisterScreen, ForgotPasswordScreen } from '../screens/auth';
|
||||
|
||||
const AuthStack = createNativeStackNavigator<AuthStackParamList>();
|
||||
|
||||
export function AuthNavigator() {
|
||||
return (
|
||||
<AuthStack.Navigator
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
}}
|
||||
>
|
||||
<AuthStack.Screen name="Login" component={LoginScreen} />
|
||||
<AuthStack.Screen name="Register" component={RegisterScreen} />
|
||||
<AuthStack.Screen name="ForgotPassword" component={ForgotPasswordScreen} />
|
||||
</AuthStack.Navigator>
|
||||
);
|
||||
}
|
||||
261
src/navigation/DesktopNavigator.tsx
Normal file
261
src/navigation/DesktopNavigator.tsx
Normal file
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
* 桌面端导航器
|
||||
* 为平板/桌面设备提供侧边栏导航体验
|
||||
*/
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
TouchableOpacity,
|
||||
Text,
|
||||
Animated,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { useSafeAreaInsets, SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
|
||||
import type { TabName, NavItemConfig } from '../infrastructure/navigation/types';
|
||||
import { NAVIGATION_CONSTANTS } from '../infrastructure/navigation/types';
|
||||
import { colors, shadows } from '../theme';
|
||||
import { useNavigationState } from '../infrastructure/navigation/hooks/useNavigationState';
|
||||
|
||||
// 导入屏幕
|
||||
import { HomeScreen } from '../screens/home';
|
||||
import { ScheduleScreen } from '../screens/schedule';
|
||||
import { MessageListScreen } from '../screens/message';
|
||||
import { ProfileScreen } from '../screens/profile';
|
||||
|
||||
// 侧边栏常量
|
||||
const { SIDEBAR_WIDTH_DESKTOP, SIDEBAR_WIDTH_TABLET, SIDEBAR_COLLAPSED_WIDTH } = NAVIGATION_CONSTANTS;
|
||||
|
||||
// 导航项配置
|
||||
const NAV_ITEMS: NavItemConfig[] = [
|
||||
{ name: 'HomeTab', label: '首页', icon: 'home', iconOutline: 'home-outline' },
|
||||
{ name: 'MessageTab', label: '消息', icon: 'message-text', iconOutline: 'message-text-outline' },
|
||||
{ name: 'ScheduleTab', label: '课表', icon: 'calendar-today', iconOutline: 'calendar-today' },
|
||||
{ name: 'ProfileTab', label: '我的', icon: 'account', iconOutline: 'account-outline' },
|
||||
];
|
||||
|
||||
interface DesktopNavigatorProps {
|
||||
unreadCount?: number;
|
||||
}
|
||||
|
||||
export function DesktopNavigator({ unreadCount = 0 }: DesktopNavigatorProps) {
|
||||
const insets = useSafeAreaInsets();
|
||||
const { currentTab, isCollapsed, setCurrentTab, toggleCollapse, setIsReady } = useNavigationState();
|
||||
const [isDesktop, setIsDesktop] = useState(false);
|
||||
|
||||
// 检测是否是桌面尺寸
|
||||
useEffect(() => {
|
||||
const checkDesktop = () => {
|
||||
const width = window.innerWidth || document.documentElement.clientWidth;
|
||||
setIsDesktop(width >= SIDEBAR_WIDTH_DESKTOP);
|
||||
};
|
||||
|
||||
checkDesktop();
|
||||
window.addEventListener('resize', checkDesktop);
|
||||
setIsReady(true);
|
||||
|
||||
return () => window.removeEventListener('resize', checkDesktop);
|
||||
}, [setIsReady]);
|
||||
|
||||
// 计算侧边栏宽度
|
||||
const sidebarWidth = isCollapsed ? SIDEBAR_COLLAPSED_WIDTH : (isDesktop ? SIDEBAR_WIDTH_DESKTOP : SIDEBAR_WIDTH_TABLET);
|
||||
|
||||
// 处理 Tab 切换
|
||||
const handleTabChange = useCallback((tab: TabName) => {
|
||||
setCurrentTab(tab);
|
||||
}, [setCurrentTab]);
|
||||
|
||||
// 渲染当前 Tab 的内容
|
||||
const renderTabContent = () => {
|
||||
switch (currentTab) {
|
||||
case 'HomeTab':
|
||||
return <HomeScreen />;
|
||||
case 'MessageTab':
|
||||
return <MessageListScreen />;
|
||||
case 'ScheduleTab':
|
||||
return <ScheduleScreen />;
|
||||
case 'ProfileTab':
|
||||
return <ProfileScreen />;
|
||||
default:
|
||||
return <HomeScreen />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* 侧边栏 */}
|
||||
<SafeAreaView style={[styles.sidebar, { width: sidebarWidth, paddingTop: insets.top, paddingBottom: insets.bottom }]}>
|
||||
{/* Logo 区域 */}
|
||||
<View style={styles.sidebarHeader}>
|
||||
<MaterialCommunityIcons name="carrot" size={32} color={colors.primary.main} />
|
||||
{!isCollapsed && (
|
||||
<Text style={styles.logoText}>胡萝卜BBS</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 导航项 */}
|
||||
<ScrollView style={styles.sidebarContent} showsVerticalScrollIndicator={false}>
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const isActive = currentTab === item.name;
|
||||
const showBadge = item.name === 'MessageTab' && unreadCount > 0;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={item.name}
|
||||
style={[
|
||||
styles.sidebarItem,
|
||||
isActive && styles.sidebarItemActive,
|
||||
isCollapsed && styles.sidebarItemCollapsed,
|
||||
]}
|
||||
onPress={() => handleTabChange(item.name)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.sidebarIconContainer}>
|
||||
<MaterialCommunityIcons
|
||||
name={isActive ? item.icon : item.iconOutline}
|
||||
size={24}
|
||||
color={isActive ? colors.primary.main : colors.text.secondary}
|
||||
/>
|
||||
{showBadge && (
|
||||
<View style={styles.badge}>
|
||||
<Text style={styles.badgeText}>{unreadCount > 99 ? '99+' : unreadCount}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
{!isCollapsed && (
|
||||
<Text style={[styles.sidebarLabel, isActive && styles.sidebarLabelActive]}>
|
||||
{item.label}
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
|
||||
{/* 折叠按钮 */}
|
||||
<TouchableOpacity
|
||||
style={[styles.collapseButton, isCollapsed && styles.collapseButtonCollapsed]}
|
||||
onPress={toggleCollapse}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<MaterialCommunityIcons
|
||||
name={isCollapsed ? 'chevron-right' : 'chevron-left'}
|
||||
size={24}
|
||||
color={colors.text.secondary}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</SafeAreaView>
|
||||
|
||||
{/* 主内容区域 */}
|
||||
<View style={styles.mainContent}>
|
||||
{renderTabContent()}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
backgroundColor: colors.background.default,
|
||||
},
|
||||
sidebar: {
|
||||
backgroundColor: colors.background.paper,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: colors.divider,
|
||||
flexDirection: 'column',
|
||||
...shadows.md,
|
||||
},
|
||||
sidebarHeader: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 20,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.divider,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
logoText: {
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
color: colors.primary.main,
|
||||
marginLeft: 8,
|
||||
},
|
||||
sidebarContent: {
|
||||
flex: 1,
|
||||
paddingTop: 8,
|
||||
},
|
||||
sidebarItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
marginHorizontal: 8,
|
||||
marginVertical: 4,
|
||||
borderRadius: 12,
|
||||
},
|
||||
sidebarItemActive: {
|
||||
backgroundColor: `${colors.primary.main}15`,
|
||||
},
|
||||
sidebarItemCollapsed: {
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 0,
|
||||
},
|
||||
sidebarIconContainer: {
|
||||
position: 'relative',
|
||||
width: 40,
|
||||
height: 40,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 12,
|
||||
},
|
||||
sidebarLabel: {
|
||||
fontSize: 15,
|
||||
fontWeight: '500',
|
||||
color: colors.text.secondary,
|
||||
marginLeft: 12,
|
||||
flex: 1,
|
||||
},
|
||||
sidebarLabelActive: {
|
||||
color: colors.primary.main,
|
||||
fontWeight: '600',
|
||||
},
|
||||
collapseButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.divider,
|
||||
},
|
||||
collapseButtonCollapsed: {
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 0,
|
||||
},
|
||||
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',
|
||||
},
|
||||
mainContent: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.background.default,
|
||||
},
|
||||
});
|
||||
49
src/navigation/HomeNavigator.tsx
Normal file
49
src/navigation/HomeNavigator.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 首页 Stack 导航
|
||||
* 处理首页相关页面:首页、搜索等
|
||||
*/
|
||||
import React from 'react';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import type { HomeStackParamList } from './types';
|
||||
|
||||
import { colors } from '../theme';
|
||||
import { HomeScreen, SearchScreen } from '../screens/home';
|
||||
|
||||
const HomeStack = createNativeStackNavigator<HomeStackParamList>();
|
||||
|
||||
export function HomeNavigator() {
|
||||
return (
|
||||
<HomeStack.Navigator
|
||||
screenOptions={{
|
||||
headerStyle: {
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
headerTintColor: colors.text.primary,
|
||||
headerTitleStyle: {
|
||||
fontWeight: '600',
|
||||
},
|
||||
headerBackTitle: '',
|
||||
headerShadowVisible: false,
|
||||
}}
|
||||
>
|
||||
<HomeStack.Screen
|
||||
name="Home"
|
||||
component={HomeScreen}
|
||||
options={{
|
||||
title: '首页',
|
||||
headerBackTitle: '',
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
<HomeStack.Screen
|
||||
name="Search"
|
||||
component={SearchScreen}
|
||||
options={{
|
||||
title: '搜索',
|
||||
headerBackTitle: '',
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
</HomeStack.Navigator>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
60
src/navigation/MessageNavigator.tsx
Normal file
60
src/navigation/MessageNavigator.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 消息 Stack 导航
|
||||
* 处理消息相关页面:消息列表、通知等
|
||||
*/
|
||||
import React from 'react';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import type { MessageStackParamList } from './types';
|
||||
|
||||
import { colors } from '../theme';
|
||||
import {
|
||||
MessageListScreen,
|
||||
NotificationsScreen,
|
||||
PrivateChatInfoScreen,
|
||||
} from '../screens/message';
|
||||
|
||||
const MessageStack = createNativeStackNavigator<MessageStackParamList>();
|
||||
|
||||
export function MessageNavigator() {
|
||||
return (
|
||||
<MessageStack.Navigator
|
||||
screenOptions={{
|
||||
headerStyle: {
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
headerTintColor: colors.text.primary,
|
||||
headerTitleStyle: {
|
||||
fontWeight: '600',
|
||||
},
|
||||
headerBackTitle: '',
|
||||
headerShadowVisible: false,
|
||||
}}
|
||||
>
|
||||
<MessageStack.Screen
|
||||
name="MessageList"
|
||||
component={MessageListScreen}
|
||||
options={{
|
||||
title: '消息',
|
||||
headerBackTitle: '',
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
<MessageStack.Screen
|
||||
name="Notifications"
|
||||
component={NotificationsScreen}
|
||||
options={{
|
||||
title: '通知',
|
||||
headerBackTitle: '',
|
||||
}}
|
||||
/>
|
||||
<MessageStack.Screen
|
||||
name="PrivateChatInfo"
|
||||
component={PrivateChatInfoScreen}
|
||||
options={{
|
||||
title: '聊天信息',
|
||||
headerBackTitle: '',
|
||||
}}
|
||||
/>
|
||||
</MessageStack.Navigator>
|
||||
);
|
||||
}
|
||||
101
src/navigation/ProfileNavigator.tsx
Normal file
101
src/navigation/ProfileNavigator.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* 个人中心 Stack 导航
|
||||
* 处理个人中心相关页面
|
||||
*/
|
||||
import React from 'react';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import type { ProfileStackParamList } from './types';
|
||||
|
||||
import { colors } from '../theme';
|
||||
import {
|
||||
ProfileScreen,
|
||||
SettingsScreen,
|
||||
EditProfileScreen,
|
||||
NotificationSettingsScreen,
|
||||
BlockedUsersScreen,
|
||||
AccountSecurityScreen,
|
||||
} from '../screens/profile';
|
||||
|
||||
const ProfileStack = createNativeStackNavigator<ProfileStackParamList>();
|
||||
|
||||
export function ProfileNavigator() {
|
||||
return (
|
||||
<ProfileStack.Navigator
|
||||
screenOptions={{
|
||||
headerStyle: {
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
headerTintColor: colors.text.primary,
|
||||
headerTitleStyle: {
|
||||
fontWeight: '600',
|
||||
},
|
||||
headerBackTitle: '',
|
||||
headerShadowVisible: false,
|
||||
}}
|
||||
>
|
||||
<ProfileStack.Screen
|
||||
name="Profile"
|
||||
component={ProfileScreen}
|
||||
options={{
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
<ProfileStack.Screen
|
||||
name="Settings"
|
||||
component={SettingsScreen}
|
||||
options={{
|
||||
title: '设置',
|
||||
headerBackTitle: '',
|
||||
}}
|
||||
/>
|
||||
<ProfileStack.Screen
|
||||
name="EditProfile"
|
||||
component={EditProfileScreen}
|
||||
options={{
|
||||
title: '编辑资料',
|
||||
headerBackTitle: '',
|
||||
}}
|
||||
/>
|
||||
<ProfileStack.Screen
|
||||
name="AccountSecurity"
|
||||
component={AccountSecurityScreen}
|
||||
options={{
|
||||
title: '账号安全',
|
||||
headerBackTitle: '',
|
||||
}}
|
||||
/>
|
||||
<ProfileStack.Screen
|
||||
name="MyPosts"
|
||||
component={ProfileScreen}
|
||||
options={{
|
||||
title: '我的帖子',
|
||||
headerBackTitle: '',
|
||||
}}
|
||||
/>
|
||||
<ProfileStack.Screen
|
||||
name="Bookmarks"
|
||||
component={ProfileScreen}
|
||||
options={{
|
||||
title: '收藏',
|
||||
headerBackTitle: '',
|
||||
}}
|
||||
/>
|
||||
<ProfileStack.Screen
|
||||
name="NotificationSettings"
|
||||
component={NotificationSettingsScreen}
|
||||
options={{
|
||||
title: '通知设置',
|
||||
headerBackTitle: '',
|
||||
}}
|
||||
/>
|
||||
<ProfileStack.Screen
|
||||
name="BlockedUsers"
|
||||
component={BlockedUsersScreen}
|
||||
options={{
|
||||
title: '黑名单',
|
||||
headerBackTitle: '',
|
||||
}}
|
||||
/>
|
||||
</ProfileStack.Navigator>
|
||||
);
|
||||
}
|
||||
265
src/navigation/RootNavigator.tsx
Normal file
265
src/navigation/RootNavigator.tsx
Normal file
@@ -0,0 +1,265 @@
|
||||
/**
|
||||
* 根导航器
|
||||
* 处理整个应用的顶级导航,包括认证状态切换
|
||||
*/
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View, ActivityIndicator, StyleSheet } from 'react-native';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
|
||||
import type { RootStackParamList } from './types';
|
||||
import { colors } from '../theme';
|
||||
import { navigationService } from '../infrastructure/navigation/navigationService';
|
||||
|
||||
import { AuthNavigator } from './AuthNavigator';
|
||||
import { SimpleMobileTabNavigator } from './SimpleMobileTabNavigator';
|
||||
import { DesktopNavigator } from './DesktopNavigator';
|
||||
|
||||
import { useResponsive } from '../hooks';
|
||||
import { useTotalUnreadCount } from '../stores';
|
||||
|
||||
// 导入全局屏幕组件
|
||||
import { PostDetailScreen } from '../screens/home';
|
||||
import { UserScreen } from '../screens/profile';
|
||||
import FollowListScreen from '../screens/profile/FollowListScreen';
|
||||
import { CreatePostScreen } from '../screens/create';
|
||||
import {
|
||||
ChatScreen,
|
||||
CreateGroupScreen,
|
||||
JoinGroupScreen,
|
||||
GroupInfoScreen,
|
||||
GroupMembersScreen,
|
||||
GroupRequestDetailScreen,
|
||||
GroupInviteDetailScreen,
|
||||
PrivateChatInfoScreen,
|
||||
} from '../screens/message';
|
||||
|
||||
const RootStack = createNativeStackNavigator<RootStackParamList>();
|
||||
|
||||
interface RootNavigatorProps {
|
||||
isAuthenticated: boolean;
|
||||
isInitializing: boolean;
|
||||
}
|
||||
|
||||
// 未认证时可访问的屏幕
|
||||
const PublicScreens = () => (
|
||||
<>
|
||||
<RootStack.Screen
|
||||
name="PostDetail"
|
||||
component={PostDetailScreen}
|
||||
options={{
|
||||
headerShown: true,
|
||||
title: '',
|
||||
headerStyle: { backgroundColor: colors.background.paper },
|
||||
headerTintColor: colors.text.primary,
|
||||
animation: 'slide_from_right',
|
||||
}}
|
||||
/>
|
||||
<RootStack.Screen
|
||||
name="UserProfile"
|
||||
component={UserScreen}
|
||||
options={{
|
||||
headerShown: true,
|
||||
title: '用户主页',
|
||||
headerStyle: { backgroundColor: colors.background.paper },
|
||||
headerTintColor: colors.text.primary,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
// 认证后可访问的屏幕
|
||||
const AuthenticatedScreens = () => (
|
||||
<>
|
||||
<RootStack.Screen
|
||||
name="PostDetail"
|
||||
component={PostDetailScreen}
|
||||
options={{
|
||||
headerShown: true,
|
||||
title: '',
|
||||
headerStyle: { backgroundColor: colors.background.paper },
|
||||
headerTintColor: colors.text.primary,
|
||||
animation: 'slide_from_right',
|
||||
}}
|
||||
/>
|
||||
<RootStack.Screen
|
||||
name="UserProfile"
|
||||
component={UserScreen}
|
||||
options={{
|
||||
headerShown: true,
|
||||
title: '用户主页',
|
||||
headerStyle: { backgroundColor: colors.background.paper },
|
||||
headerTintColor: colors.text.primary,
|
||||
}}
|
||||
/>
|
||||
<RootStack.Screen
|
||||
name="CreatePost"
|
||||
component={CreatePostScreen}
|
||||
options={{
|
||||
headerShown: true,
|
||||
title: '发布帖子',
|
||||
headerStyle: { backgroundColor: colors.background.paper },
|
||||
headerTintColor: colors.text.primary,
|
||||
presentation: 'modal',
|
||||
}}
|
||||
/>
|
||||
<RootStack.Screen
|
||||
name="Chat"
|
||||
component={ChatScreen}
|
||||
options={{
|
||||
headerShown: false,
|
||||
animation: 'slide_from_right',
|
||||
}}
|
||||
/>
|
||||
<RootStack.Screen
|
||||
name="FollowList"
|
||||
component={FollowListScreen}
|
||||
options={{
|
||||
headerShown: true,
|
||||
title: '关注列表',
|
||||
headerStyle: { backgroundColor: colors.background.paper },
|
||||
headerTintColor: colors.text.primary,
|
||||
animation: 'slide_from_right',
|
||||
}}
|
||||
/>
|
||||
<RootStack.Screen
|
||||
name="CreateGroup"
|
||||
component={CreateGroupScreen}
|
||||
options={{
|
||||
headerShown: true,
|
||||
title: '创建群聊',
|
||||
headerStyle: { backgroundColor: colors.background.paper },
|
||||
headerTintColor: colors.text.primary,
|
||||
presentation: 'modal',
|
||||
}}
|
||||
/>
|
||||
<RootStack.Screen
|
||||
name="JoinGroup"
|
||||
component={JoinGroupScreen}
|
||||
options={{
|
||||
headerShown: true,
|
||||
title: '搜索群聊',
|
||||
headerStyle: { backgroundColor: colors.background.paper },
|
||||
headerTintColor: colors.text.primary,
|
||||
animation: 'slide_from_right',
|
||||
}}
|
||||
/>
|
||||
<RootStack.Screen
|
||||
name="GroupInfo"
|
||||
component={GroupInfoScreen}
|
||||
options={{
|
||||
headerShown: true,
|
||||
title: '群信息',
|
||||
headerStyle: { backgroundColor: colors.background.paper },
|
||||
headerTintColor: colors.text.primary,
|
||||
animation: 'slide_from_right',
|
||||
}}
|
||||
/>
|
||||
<RootStack.Screen
|
||||
name="GroupMembers"
|
||||
component={GroupMembersScreen}
|
||||
options={{
|
||||
headerShown: true,
|
||||
title: '群成员',
|
||||
headerStyle: { backgroundColor: colors.background.paper },
|
||||
headerTintColor: colors.text.primary,
|
||||
animation: 'slide_from_right',
|
||||
}}
|
||||
/>
|
||||
<RootStack.Screen
|
||||
name="GroupRequestDetail"
|
||||
component={GroupRequestDetailScreen}
|
||||
options={{
|
||||
headerShown: true,
|
||||
title: '入群审批',
|
||||
headerStyle: { backgroundColor: colors.background.paper },
|
||||
headerTintColor: colors.text.primary,
|
||||
animation: 'slide_from_right',
|
||||
}}
|
||||
/>
|
||||
<RootStack.Screen
|
||||
name="GroupInviteDetail"
|
||||
component={GroupInviteDetailScreen}
|
||||
options={{
|
||||
headerShown: true,
|
||||
title: '群聊邀请',
|
||||
headerStyle: { backgroundColor: colors.background.paper },
|
||||
headerTintColor: colors.text.primary,
|
||||
animation: 'slide_from_right',
|
||||
}}
|
||||
/>
|
||||
<RootStack.Screen
|
||||
name="PrivateChatInfo"
|
||||
component={PrivateChatInfoScreen}
|
||||
options={{
|
||||
headerShown: true,
|
||||
title: '聊天信息',
|
||||
headerStyle: { backgroundColor: colors.background.paper },
|
||||
headerTintColor: colors.text.primary,
|
||||
animation: 'slide_from_right',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
export function RootNavigator({ isAuthenticated, isInitializing }: RootNavigatorProps) {
|
||||
const { isMobile } = useResponsive();
|
||||
const unreadCount = useTotalUnreadCount();
|
||||
|
||||
// 设置导航引用
|
||||
const setNavigationRef = (ref: any) => {
|
||||
navigationService.setNavigationRef(ref);
|
||||
};
|
||||
|
||||
// 加载中显示
|
||||
if (isInitializing) {
|
||||
return (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={colors.primary.main} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NavigationContainer ref={setNavigationRef}>
|
||||
<RootStack.Navigator
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
}}
|
||||
>
|
||||
{isAuthenticated ? (
|
||||
<>
|
||||
<RootStack.Screen name="Main">
|
||||
{() =>
|
||||
isMobile ? (
|
||||
<SimpleMobileTabNavigator />
|
||||
) : (
|
||||
<DesktopNavigator unreadCount={unreadCount} />
|
||||
)
|
||||
}
|
||||
</RootStack.Screen>
|
||||
<AuthenticatedScreens />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RootStack.Screen
|
||||
name="Auth"
|
||||
component={AuthNavigator}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
<PublicScreens />
|
||||
</>
|
||||
)}
|
||||
</RootStack.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
loadingContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.background.default,
|
||||
},
|
||||
});
|
||||
42
src/navigation/ScheduleNavigator.tsx
Normal file
42
src/navigation/ScheduleNavigator.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 课程表 Stack 导航
|
||||
* 处理课程表相关页面
|
||||
*/
|
||||
import React from 'react';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import type { ScheduleStackParamList } from './types';
|
||||
|
||||
import { colors } from '../theme';
|
||||
import { ScheduleScreen, CourseDetailScreen } from '../screens/schedule';
|
||||
|
||||
const ScheduleStack = createNativeStackNavigator<ScheduleStackParamList>();
|
||||
|
||||
export function ScheduleNavigator() {
|
||||
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',
|
||||
contentStyle: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</ScheduleStack.Navigator>
|
||||
);
|
||||
}
|
||||
155
src/navigation/TabNavigator.tsx
Normal file
155
src/navigation/TabNavigator.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Tab 导航器
|
||||
* 处理底部标签导航(移动端)
|
||||
*/
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
|
||||
import type { MainTabParamList } from './types';
|
||||
import { colors, shadows } from '../theme';
|
||||
|
||||
import { HomeNavigator } from './HomeNavigator';
|
||||
import { MessageNavigator } from './MessageNavigator';
|
||||
import { ScheduleNavigator } from './ScheduleNavigator';
|
||||
import { ProfileNavigator } from './ProfileNavigator';
|
||||
|
||||
const Tab = createBottomTabNavigator<MainTabParamList>();
|
||||
|
||||
// 常量
|
||||
const MOBILE_TAB_FLOATING_MARGIN = 12;
|
||||
|
||||
interface TabNavigatorProps {
|
||||
unreadCount: number;
|
||||
}
|
||||
|
||||
export function TabNavigator({ unreadCount }: TabNavigatorProps) {
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
return (
|
||||
<Tab.Navigator
|
||||
screenOptions={{
|
||||
tabBarActiveTintColor: colors.primary.main,
|
||||
tabBarInactiveTintColor: colors.text.secondary,
|
||||
tabBarHideOnKeyboard: true,
|
||||
tabBarStyle: {
|
||||
backgroundColor: colors.background.paper,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: `${colors.divider}88`,
|
||||
borderRadius: 24,
|
||||
marginHorizontal: 14,
|
||||
marginBottom: MOBILE_TAB_FLOATING_MARGIN + insets.bottom,
|
||||
height: 64,
|
||||
paddingBottom: 6,
|
||||
paddingTop: 8,
|
||||
paddingHorizontal: 8,
|
||||
position: 'absolute',
|
||||
...shadows.lg,
|
||||
},
|
||||
tabBarItemStyle: {
|
||||
borderRadius: 18,
|
||||
paddingVertical: 1,
|
||||
marginHorizontal: 2,
|
||||
},
|
||||
tabBarLabelStyle: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
marginTop: -2,
|
||||
letterSpacing: 0.2,
|
||||
},
|
||||
tabBarBadgeStyle: {
|
||||
backgroundColor: colors.error.main,
|
||||
color: colors.primary.contrast,
|
||||
fontSize: 10,
|
||||
fontWeight: '700',
|
||||
top: 4,
|
||||
},
|
||||
headerShown: false,
|
||||
}}
|
||||
>
|
||||
<Tab.Screen
|
||||
name="HomeTab"
|
||||
component={HomeNavigator}
|
||||
options={{
|
||||
tabBarLabel: '首页',
|
||||
tabBarIcon: ({ color, size, focused }) => (
|
||||
<View style={[styles.tabIconContainer, focused && styles.tabIconActive]}>
|
||||
<MaterialCommunityIcons
|
||||
name={focused ? 'home' : 'home-outline'}
|
||||
size={focused ? 26 : 24}
|
||||
color={color}
|
||||
/>
|
||||
</View>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="MessageTab"
|
||||
component={MessageNavigator}
|
||||
options={{
|
||||
tabBarLabel: '消息',
|
||||
tabBarBadge: unreadCount > 0 ? (unreadCount > 99 ? '99+' : unreadCount) : undefined,
|
||||
tabBarIcon: ({ color, size, focused }) => (
|
||||
<View style={[styles.tabIconContainer, focused && styles.tabIconActive]}>
|
||||
<MaterialCommunityIcons
|
||||
name={focused ? 'message-text' : 'message-text-outline'}
|
||||
size={focused ? 26 : 24}
|
||||
color={color}
|
||||
/>
|
||||
</View>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="ScheduleTab"
|
||||
component={ScheduleNavigator}
|
||||
options={{
|
||||
tabBarLabel: '课表',
|
||||
tabBarIcon: ({ color, size, focused }) => (
|
||||
<View style={[styles.tabIconContainer, focused && styles.tabIconActive]}>
|
||||
<MaterialCommunityIcons
|
||||
name={focused ? 'calendar-today' : 'calendar-today'}
|
||||
size={focused ? 26 : 24}
|
||||
color={color}
|
||||
/>
|
||||
</View>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="ProfileTab"
|
||||
component={ProfileNavigator}
|
||||
options={{
|
||||
tabBarLabel: '我的',
|
||||
tabBarIcon: ({ color, size, focused }) => (
|
||||
<View style={[styles.tabIconContainer, focused && styles.tabIconActive]}>
|
||||
<MaterialCommunityIcons
|
||||
name={focused ? 'account' : 'account-outline'}
|
||||
size={focused ? 26 : 24}
|
||||
color={color}
|
||||
/>
|
||||
</View>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tabIconContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: 38,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
},
|
||||
tabIconActive: {
|
||||
backgroundColor: `${colors.primary.main}20`,
|
||||
transform: [{ translateY: -1 }],
|
||||
},
|
||||
});
|
||||
30
src/navigation/index.ts
Normal file
30
src/navigation/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 导出所有导航组件
|
||||
*/
|
||||
|
||||
// 类型
|
||||
export type {
|
||||
RootStackParamList,
|
||||
MainTabParamList,
|
||||
HomeStackParamList,
|
||||
MessageStackParamList,
|
||||
ScheduleStackParamList,
|
||||
ProfileStackParamList,
|
||||
AuthStackParamList,
|
||||
TabName,
|
||||
NavItemConfig,
|
||||
} from './types';
|
||||
|
||||
// 导航器
|
||||
export { AuthNavigator } from './AuthNavigator';
|
||||
export { HomeNavigator } from './HomeNavigator';
|
||||
export { MessageNavigator } from './MessageNavigator';
|
||||
export { ScheduleNavigator } from './ScheduleNavigator';
|
||||
export { ProfileNavigator } from './ProfileNavigator';
|
||||
export { TabNavigator } from './TabNavigator';
|
||||
export { DesktopNavigator } from './DesktopNavigator';
|
||||
export { RootNavigator } from './RootNavigator';
|
||||
export { MainNavigator } from './MainNavigator';
|
||||
|
||||
// 移动端简化导航
|
||||
export { SimpleMobileTabNavigator } from './SimpleMobileTabNavigator';
|
||||
@@ -121,3 +121,14 @@ export type MessageScreenNames = keyof MessageStackParamList;
|
||||
export type ProfileScreenNames = keyof ProfileStackParamList;
|
||||
export type MainTabScreenNames = keyof MainTabParamList;
|
||||
export type RootScreenNames = keyof RootStackParamList;
|
||||
|
||||
// ==================== Tab 类型 ====================
|
||||
export type TabName = 'HomeTab' | 'MessageTab' | 'ScheduleTab' | 'ProfileTab';
|
||||
|
||||
// ==================== 导航项配置 ====================
|
||||
export interface NavItemConfig {
|
||||
name: TabName;
|
||||
label: string;
|
||||
icon: string;
|
||||
iconOutline: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user