feat(home): integrate PagerView for tab navigation and add verification redirect
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 9m20s
Frontend CI / ota-android (push) Successful in 14m32s
Frontend CI / build-android-apk (push) Successful in 1h17m23s

Replace custom gesture handling with PagerView component for smoother tab
switching on home screen. Add verification required redirects in API and
WebSocket services that automatically navigate users to the verification
guide when receiving VERIFICATION_REQUIRED error codes.

BREAKING CHANGE: API and WS services now require router to be set via
setExpoRouter/setRouter for navigation to work properly
This commit is contained in:
lafay
2026-04-08 02:38:48 +08:00
parent 25194313ae
commit 96a5207cf8
7 changed files with 171 additions and 56 deletions

View File

@@ -23,7 +23,7 @@ import {
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
import { useRouter, useFocusEffect } from 'expo-router';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { PagerView } from '../../components/common';
import { useAppColors, useResolvedColorScheme, spacing, borderRadius, shadows, type AppColors } from '../../theme';
import { Post } from '../../types';
import { useUserStore, useHomeTabBarVisibilityStore, useHomeTabPressStore } from '../../stores';
@@ -42,8 +42,6 @@ import * as hrefs from '../../navigation/hrefs';
const TABS = ['关注', '最新', '热门'];
const TAB_ICONS = ['account-heart-outline', 'clock-outline', 'fire'];
const DEFAULT_PAGE_SIZE = 20;
const SWIPE_TRANSLATION_THRESHOLD = 40;
const SWIPE_COOLDOWN_MS = 300;
const MOBILE_TAB_BAR_HEIGHT = 64;
const MOBILE_TAB_FLOATING_MARGIN = 12;
const MOBILE_FAB_GAP = 12;
@@ -211,11 +209,13 @@ export const HomeScreen: React.FC = () => {
}
}, [showCreatePost]);
// 用于跟踪当前页面显示的帖子 ID以便从 store 同步状态
// 用于跟踪当前页面显示的帖子 ID以便从 store 同步状态
const postIdsRef = useRef<Set<string>>(new Set());
const lastSwipeAtRef = useRef(0);
const isLoadingMoreRef = useRef(false);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const pagerRef = useRef<any>(null);
const setBottomTabBarHiddenByScroll = useHomeTabBarVisibilityStore((s) => s.setBottomTabBarHiddenByScroll);
const bottomTabBarHiddenByScroll = useHomeTabBarVisibilityStore((s) => s.bottomTabBarHiddenByScroll);
@@ -505,12 +505,22 @@ export const HomeScreen: React.FC = () => {
setViewMode(prev => prev === 'list' ? 'grid' : 'list');
};
// 切换Tab手势/点击共用
// 切换Tab点击 TabBar 或手势滑动
const changeTab = useCallback((nextIndex: number) => {
if (nextIndex < 0 || nextIndex >= TABS.length || nextIndex === activeIndex) {
return;
}
setActiveIndex(nextIndex);
if (Platform.OS !== 'web') {
pagerRef.current?.setPage(nextIndex);
}
}, [activeIndex]);
const onPagerPageSelected = useCallback((e: { nativeEvent: { position: number } }) => {
const nextIndex = e.nativeEvent.position;
if (nextIndex !== activeIndex) {
setActiveIndex(nextIndex);
}
}, [activeIndex]);
useEffect(() => {
@@ -525,35 +535,6 @@ export const HomeScreen: React.FC = () => {
loadChannels();
}, []);
const handleSwipeTabChange = useCallback((translationX: number) => {
setActiveIndex(prev => (
translationX < 0
? Math.min(prev + 1, TABS.length - 1)
: Math.max(prev - 1, 0)
));
}, []);
const swipeGesture = useMemo(() => (
Gesture.Pan()
.runOnJS(true)
.activeOffsetX([-15, 15])
.failOffsetY([-20, 20])
.shouldCancelWhenOutside(true)
.onEnd((event) => {
const now = Date.now();
if (now - lastSwipeAtRef.current < SWIPE_COOLDOWN_MS) {
return;
}
if (Math.abs(event.translationX) < SWIPE_TRANSLATION_THRESHOLD) {
return;
}
lastSwipeAtRef.current = now;
handleSwipeTabChange(event.translationX);
})
), [handleSwipeTabChange]);
// 跳转到搜索页(使用内嵌模式,不再依赖导航)
const handleSearchPress = () => {
setShowSearch(true);
@@ -1014,32 +995,52 @@ export const HomeScreen: React.FC = () => {
</View>
{/* 帖子列表 */}
{isMobile ? (
// 移动端:使用 GestureDetector 支持手势切换 Tab
<GestureDetector gesture={swipeGesture}>
<View style={styles.contentContainer}>
{isInitialLoading ? (
<Loading fullScreen />
) : viewMode === 'list' ? (
renderListContent()
) : (
// 网格模式:使用响应式网格
renderResponsiveGrid()
)}
</View>
</GestureDetector>
) : (
// 桌面端/平板端:不使用 GestureDetector避免拦截侧边栏点击
{Platform.OS === 'web' ? (
<View style={styles.contentContainer}>
{isInitialLoading ? (
<Loading fullScreen />
) : viewMode === 'list' ? (
renderListContent()
) : (
// 网格模式:使用响应式网格
renderResponsiveGrid()
)}
</View>
) : (
<PagerView
ref={pagerRef}
style={styles.contentContainer}
initialPage={activeIndex}
onPageSelected={onPagerPageSelected}
overdrag
>
<View key="follow" collapsable={false} style={styles.contentContainer}>
{isInitialLoading && activeIndex === 0 ? (
<Loading fullScreen />
) : activeIndex === 0 ? (
viewMode === 'list' ? renderListContent() : renderResponsiveGrid()
) : (
<View style={styles.contentContainer} />
)}
</View>
<View key="latest" collapsable={false} style={styles.contentContainer}>
{isInitialLoading && activeIndex === 1 ? (
<Loading fullScreen />
) : activeIndex === 1 ? (
viewMode === 'list' ? renderListContent() : renderResponsiveGrid()
) : (
<View style={styles.contentContainer} />
)}
</View>
<View key="hot" collapsable={false} style={styles.contentContainer}>
{isInitialLoading && activeIndex === 2 ? (
<Loading fullScreen />
) : activeIndex === 2 ? (
viewMode === 'list' ? renderListContent() : renderResponsiveGrid()
) : (
<View style={styles.contentContainer} />
)}
</View>
</PagerView>
)}
{/* 漂浮发帖按钮 */}