2026-03-26 03:41:43 +08:00
|
|
|
import { useMemo, useCallback } from 'react';
|
2026-06-02 08:14:04 +08:00
|
|
|
import { Platform, Pressable, useWindowDimensions } from 'react-native';
|
2026-06-12 23:42:09 +08:00
|
|
|
import { Tabs, usePathname, useRouter } from 'expo-router';
|
2026-03-24 14:21:31 +08:00
|
|
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
|
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
2026-06-02 08:14:04 +08:00
|
|
|
import type { PressableProps } from 'react-native';
|
2026-03-24 14:21:31 +08:00
|
|
|
|
2026-03-25 05:16:54 +08:00
|
|
|
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';
|
2026-03-26 03:41:43 +08:00
|
|
|
import { useHomeTabBarVisibilityStore, useTotalUnreadCount, useHomeTabPressStore } from '../../../src/stores';
|
2026-06-12 23:42:09 +08:00
|
|
|
import { hrefHome } from '../../../src/navigation/hrefs';
|
2026-03-24 14:21:31 +08:00
|
|
|
|
2026-03-24 15:39:28 +08:00
|
|
|
const TAB_BAR_HEIGHT = 56;
|
2026-03-24 14:21:31 +08:00
|
|
|
const TAB_BAR_FLOATING_MARGIN = 12;
|
2026-03-24 22:27:33 +08:00
|
|
|
const TAB_BAR_MARGIN = 20;
|
2026-03-24 14:21:31 +08:00
|
|
|
|
2026-06-02 08:14:04 +08:00
|
|
|
const TabBarButton = ({ children, style, ...rest }: PressableProps) => (
|
|
|
|
|
<Pressable
|
|
|
|
|
{...rest}
|
|
|
|
|
style={style}
|
|
|
|
|
android_ripple={null}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</Pressable>
|
|
|
|
|
);
|
|
|
|
|
|
2026-03-24 14:21:31 +08:00
|
|
|
export default function TabsLayout() {
|
2026-03-25 05:16:54 +08:00
|
|
|
const colors = useAppColors();
|
2026-03-24 14:21:31 +08:00
|
|
|
const { width } = useWindowDimensions();
|
|
|
|
|
const insets = useSafeAreaInsets();
|
2026-03-25 01:30:00 +08:00
|
|
|
const pathname = usePathname();
|
2026-06-12 23:42:09 +08:00
|
|
|
const router = useRouter();
|
2026-03-24 14:21:31 +08:00
|
|
|
const unreadCount = useTotalUnreadCount();
|
2026-03-25 01:30:00 +08:00
|
|
|
const scrollHideTabBar = useHomeTabBarVisibilityStore((s) => s.bottomTabBarHiddenByScroll);
|
2026-03-26 03:41:43 +08:00
|
|
|
const triggerHomeTabPress = useHomeTabPressStore((s) => s.triggerPress);
|
2026-03-24 14:21:31 +08:00
|
|
|
const hideTabBar = Platform.OS === 'web' && width >= BREAKPOINTS.desktop;
|
|
|
|
|
|
2026-03-25 01:30:00 +08:00
|
|
|
const isHomeStackRoute = pathname === '/home' || pathname.startsWith('/home/');
|
|
|
|
|
|
2026-06-12 23:42:09 +08:00
|
|
|
const handleHomeTabPress = useCallback(
|
|
|
|
|
(e: { preventDefault: () => void }) => {
|
|
|
|
|
if (!isHomeStackRoute) return;
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
if (pathname === '/home') {
|
|
|
|
|
triggerHomeTabPress();
|
|
|
|
|
} else {
|
|
|
|
|
router.navigate(hrefHome());
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[isHomeStackRoute, pathname, triggerHomeTabPress, router]
|
|
|
|
|
);
|
2026-03-26 03:41:43 +08:00
|
|
|
|
2026-03-25 01:30:00 +08:00
|
|
|
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;
|
2026-03-25 05:16:54 +08:00
|
|
|
}, [hideTabBar, insets.bottom, isHomeStackRoute, scrollHideTabBar, colors]);
|
2026-03-25 01:30:00 +08:00
|
|
|
|
2026-03-24 14:21:31 +08:00
|
|
|
return (
|
|
|
|
|
<Tabs
|
|
|
|
|
screenOptions={{
|
|
|
|
|
headerShown: false,
|
|
|
|
|
tabBarActiveTintColor: colors.primary.main,
|
|
|
|
|
tabBarInactiveTintColor: colors.text.secondary,
|
2026-03-25 01:30:00 +08:00
|
|
|
tabBarStyle,
|
2026-03-24 15:39:28 +08:00
|
|
|
tabBarItemStyle: {
|
|
|
|
|
borderRadius: 16,
|
|
|
|
|
marginHorizontal: 2,
|
|
|
|
|
paddingVertical: 1,
|
|
|
|
|
},
|
2026-06-02 08:14:04 +08:00
|
|
|
tabBarButton: (props) => <TabBarButton {...props} />,
|
2026-03-24 14:21:31 +08:00
|
|
|
tabBarShowLabel: true,
|
2026-03-24 15:39:28 +08:00
|
|
|
tabBarLabelStyle: { fontSize: 11, fontWeight: '600', marginTop: -1 },
|
2026-03-24 14:21:31 +08:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Tabs.Screen
|
|
|
|
|
name="home"
|
|
|
|
|
options={{
|
|
|
|
|
title: '首页',
|
|
|
|
|
tabBarIcon: ({ color, focused }) => (
|
|
|
|
|
<MaterialCommunityIcons
|
|
|
|
|
name={focused ? 'home' : 'home-outline'}
|
2026-03-24 15:39:28 +08:00
|
|
|
size={focused ? 24 : 22}
|
2026-03-24 14:21:31 +08:00
|
|
|
color={color}
|
|
|
|
|
/>
|
|
|
|
|
),
|
|
|
|
|
}}
|
2026-03-26 03:41:43 +08:00
|
|
|
listeners={{
|
|
|
|
|
tabPress: handleHomeTabPress,
|
|
|
|
|
}}
|
2026-03-24 14:21:31 +08:00
|
|
|
/>
|
2026-03-24 23:08:17 +08:00
|
|
|
<Tabs.Screen
|
2026-03-25 01:30:00 +08:00
|
|
|
name="apps"
|
2026-03-24 23:08:17 +08:00
|
|
|
options={{
|
2026-03-25 01:30:00 +08:00
|
|
|
title: '应用',
|
2026-03-24 23:08:17 +08:00
|
|
|
tabBarIcon: ({ color, focused }) => (
|
2026-03-25 01:29:41 +08:00
|
|
|
<MaterialCommunityIcons
|
2026-03-25 01:30:00 +08:00
|
|
|
name={focused ? 'view-grid' : 'view-grid-outline'}
|
2026-03-25 01:29:41 +08:00
|
|
|
size={focused ? 24 : 22}
|
|
|
|
|
color={color}
|
|
|
|
|
/>
|
2026-03-24 23:08:17 +08:00
|
|
|
),
|
|
|
|
|
}}
|
|
|
|
|
/>
|
2026-03-24 14:21:31 +08:00
|
|
|
<Tabs.Screen
|
2026-03-25 01:30:00 +08:00
|
|
|
name="messages"
|
2026-03-24 14:21:31 +08:00
|
|
|
options={{
|
2026-03-25 01:30:00 +08:00
|
|
|
title: '消息',
|
|
|
|
|
tabBarBadge: unreadCount > 0 ? (unreadCount > 99 ? 99 : unreadCount) : undefined,
|
2026-03-24 14:21:31 +08:00
|
|
|
tabBarIcon: ({ color, focused }) => (
|
|
|
|
|
<MaterialCommunityIcons
|
2026-03-25 01:30:00 +08:00
|
|
|
name={focused ? 'message-text' : 'message-text-outline'}
|
2026-03-24 15:39:28 +08:00
|
|
|
size={focused ? 24 : 22}
|
2026-03-24 14:21:31 +08:00
|
|
|
color={color}
|
|
|
|
|
/>
|
|
|
|
|
),
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<Tabs.Screen
|
|
|
|
|
name="profile"
|
|
|
|
|
options={{
|
|
|
|
|
title: '我的',
|
|
|
|
|
tabBarIcon: ({ color, focused }) => (
|
|
|
|
|
<MaterialCommunityIcons
|
|
|
|
|
name={focused ? 'account' : 'account-outline'}
|
2026-03-24 15:39:28 +08:00
|
|
|
size={focused ? 24 : 22}
|
2026-03-24 14:21:31 +08:00
|
|
|
color={color}
|
|
|
|
|
/>
|
|
|
|
|
),
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</Tabs>
|
|
|
|
|
);
|
|
|
|
|
}
|