Files
frontend/app/(app)/(tabs)/_layout.tsx

159 lines
4.7 KiB
TypeScript
Raw Normal View History

import { useMemo, useCallback } from 'react';
import { Platform, Pressable, useWindowDimensions } from 'react-native';
import { Tabs, usePathname, useRouter } from 'expo-router';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import type { PressableProps } from 'react-native';
import { useAppColors, shadows } from '../../../src/theme';
refactor: restructure core architecture and responsive system This commit implements a major architectural refactor to improve modularity, type safety, and maintainability across the codebase. Key changes include: - **Responsive System Refactor**: Migrated from a monolithic `useResponsive` hook to a set of specialized, granular hooks (`useBreakpoint`, `useOrientation`, `usePlatform`, etc.) located in `src/presentation/hooks/responsive`. This reduces unnecessary re-renders and improves developer experience. - **Core Service Restructuring**: Introduced a new directory structure for services, including dedicated folders for `datasources`, `mappers`, and domain-specific types (`message`, `post`). - **Data Layer Improvements**: - Centralized JSON parsing logic in `src/database/core/jsonUtils.ts`. - Cleaned up repository implementations by removing redundant local utility functions. - Refactored `MessageMapper` to use the new centralized JSON utility. - **Type System Cleanup**: - Decomposed the large `src/types/dto.ts` into a modular `src/types/dto/` directory. - Simplified `src/types/index.ts` and introduced backward-compatible aliases for core entities. - **Utility Consolidation**: - Created a centralized `src/utils/formatTime.ts` to replace fragmented date formatting logic across various screens and components. - Removed deprecated responsive utility files in favor of the new hook-based system. - **Service Logic Refinement**: Refactored `ApiClient` and `WebSocketService` to use a centralized `showVerificationModal` service, removing duplicated state management for verification prompts.
2026-05-05 19:07:33 +08:00
import { BREAKPOINTS } from '../../../src/hooks';
import { useHomeTabBarVisibilityStore, useTotalUnreadCount, useHomeTabPressStore } from '../../../src/stores';
import { hrefHome } from '../../../src/navigation/hrefs';
const TAB_BAR_HEIGHT = 56;
const TAB_BAR_FLOATING_MARGIN = 12;
const TAB_BAR_MARGIN = 20;
const TabBarButton = ({ children, style, ...rest }: PressableProps) => (
<Pressable
{...rest}
style={style}
android_ripple={null}
>
{children}
</Pressable>
);
export default function TabsLayout() {
const colors = useAppColors();
const { width } = useWindowDimensions();
const insets = useSafeAreaInsets();
const pathname = usePathname();
const router = useRouter();
const unreadCount = useTotalUnreadCount();
const scrollHideTabBar = useHomeTabBarVisibilityStore((s) => s.bottomTabBarHiddenByScroll);
const triggerHomeTabPress = useHomeTabPressStore((s) => s.triggerPress);
const hideTabBar = Platform.OS === 'web' && width >= BREAKPOINTS.desktop;
const isHomeStackRoute = pathname === '/home' || pathname.startsWith('/home/');
const handleHomeTabPress = useCallback(
(e: { preventDefault: () => void }) => {
if (!isHomeStackRoute) return;
e.preventDefault();
if (pathname === '/home') {
triggerHomeTabPress();
} else {
router.navigate(hrefHome());
}
},
[isHomeStackRoute, pathname, triggerHomeTabPress, router]
);
const tabBarStyle = useMemo(() => {
if (hideTabBar) {
return { display: 'none' as const, height: 0, overflow: 'hidden' as const };
}
const visibleStyle = {
position: 'absolute' as const,
left: 0,
right: 0,
marginHorizontal: TAB_BAR_MARGIN,
bottom: TAB_BAR_FLOATING_MARGIN + insets.bottom,
height: TAB_BAR_HEIGHT,
borderRadius: 22,
backgroundColor: colors.background.paper,
...shadows.lg,
borderTopWidth: 0,
elevation: 100,
zIndex: 100,
};
if (isHomeStackRoute && scrollHideTabBar) {
return {
...visibleStyle,
display: 'none' as const,
height: 0,
opacity: 0,
overflow: 'hidden' as const,
};
}
return visibleStyle;
}, [hideTabBar, insets.bottom, isHomeStackRoute, scrollHideTabBar, colors]);
return (
<Tabs
screenOptions={{
headerShown: false,
tabBarActiveTintColor: colors.primary.main,
tabBarInactiveTintColor: colors.text.secondary,
tabBarStyle,
tabBarItemStyle: {
borderRadius: 16,
marginHorizontal: 2,
paddingVertical: 1,
},
tabBarButton: (props) => <TabBarButton {...props} />,
tabBarShowLabel: true,
tabBarLabelStyle: { fontSize: 11, fontWeight: '600', marginTop: -1 },
}}
>
<Tabs.Screen
name="home"
options={{
title: '首页',
tabBarIcon: ({ color, focused }) => (
<MaterialCommunityIcons
name={focused ? 'home' : 'home-outline'}
size={focused ? 24 : 22}
color={color}
/>
),
}}
listeners={{
tabPress: handleHomeTabPress,
}}
/>
<Tabs.Screen
name="apps"
options={{
title: '应用',
tabBarIcon: ({ color, focused }) => (
<MaterialCommunityIcons
name={focused ? 'view-grid' : 'view-grid-outline'}
size={focused ? 24 : 22}
color={color}
/>
),
}}
/>
<Tabs.Screen
name="messages"
options={{
title: '消息',
tabBarBadge: unreadCount > 0 ? (unreadCount > 99 ? 99 : unreadCount) : undefined,
tabBarIcon: ({ color, focused }) => (
<MaterialCommunityIcons
name={focused ? 'message-text' : 'message-text-outline'}
size={focused ? 24 : 22}
color={color}
/>
),
}}
/>
<Tabs.Screen
name="profile"
options={{
title: '我的',
tabBarIcon: ({ color, focused }) => (
<MaterialCommunityIcons
name={focused ? 'account' : 'account-outline'}
size={focused ? 24 : 22}
color={color}
/>
),
}}
/>
</Tabs>
);
}