import { useEffect, useRef, useState } from "react" import { Animated, Dimensions, Platform, Pressable, TextStyle, TouchableOpacity, View, ViewStyle, } from "react-native" import { BottomTabBarProps, createBottomTabNavigator } from "@react-navigation/bottom-tabs" import { useNavigation } from "@react-navigation/native" import { NativeStackNavigationProp } from "@react-navigation/native-stack" import { PanGestureHandler, PanGestureHandlerStateChangeEvent, State, } from "react-native-gesture-handler" import { useSafeAreaInsets } from "react-native-safe-area-context" import { Avatar } from "@/components/Avatar" import { Icon, IconTypes } from "@/components/Icon" import { Text } from "@/components/Text" import { useAuth } from "@/context/AuthContext" import { translate } from "@/i18n/translate" import { CommunityScreen } from "@/screens/CommunityScreen" import { ShowroomScreen } from "@/screens/ShowroomScreen/ShowroomScreen" import { useAppTheme } from "@/theme/context" import type { ThemedStyle } from "@/theme/types" import { s, fs } from "@/utils/responsive" import type { AppStackParamList, MainTabParamList } from "./navigationTypes" const Tab = createBottomTabNavigator() const { width: SCREEN_WIDTH } = Dimensions.get("window") // Tab configuration const TAB_CONFIG: { name: keyof MainTabParamList; icon: IconTypes }[] = [ { name: "Showroom", icon: "home" }, { name: "Community", icon: "barChart" }, ] /** * Header left component with profile avatar button */ function HeaderProfileButton() { const { user } = useAuth() const navigation = useNavigation>() const handlePress = () => { navigation.navigate("Profile") } return ( ) } /** * Custom floating capsule tab bar with glass effect and gesture support */ function FloatingTabBar({ state, navigation }: BottomTabBarProps) { const insets = useSafeAreaInsets() const { themed, theme } = useAppTheme() const tabIndex = state.index const tabCount = TAB_CONFIG.length const containerWidth = SCREEN_WIDTH - theme.spacing.xl * 2 const buttonWidth = (containerWidth - theme.spacing.xs * 2) / tabCount // 动画值 const basePos = useRef(new Animated.Value(tabIndex)).current const gestureX = useRef(new Animated.Value(0)).current // 视觉上的活跃索引,用于控制图标颜色 const [visualIndex, setVisualIndex] = useState(tabIndex) // 同步系统索引到视觉索引 useEffect(() => { setVisualIndex(tabIndex) Animated.spring(basePos, { toValue: tabIndex, useNativeDriver: true, tension: 100, friction: 12, }).start() }, [tabIndex, basePos]) // 监听手势位移,实时更新图标视觉状态 useEffect(() => { const listenerId = gestureX.addListener(({ value }) => { // 计算当前指示器中心位置对应的索引 const currentIndicatorPos = tabIndex * buttonWidth + value const newVisualIndex = Math.max( 0, Math.min(TAB_CONFIG.length - 1, Math.round(currentIndicatorPos / buttonWidth)), ) if (newVisualIndex !== visualIndex) { setVisualIndex(newVisualIndex) } }) return () => gestureX.removeListener(listenerId) }, [tabIndex, visualIndex, buttonWidth, gestureX]) // 手势结束处理 const onHandlerStateChange = (event: PanGestureHandlerStateChangeEvent) => { if (event.nativeEvent.state === State.END) { const { translationX, velocityX } = event.nativeEvent const movedTabs = Math.round(translationX / buttonWidth) // 快速滑动检测 let finalMovedTabs = movedTabs if (Math.abs(velocityX) > 500 && Math.abs(translationX) > 20) { finalMovedTabs = velocityX > 0 ? -1 : 1 // 注意方向:向右滑是上一个,向左滑是下一个 } // 计算新索引 let newIndex = tabIndex + finalMovedTabs newIndex = Math.max(0, Math.min(TAB_CONFIG.length - 1, newIndex)) if (newIndex !== tabIndex) { gestureX.flattenOffset() requestAnimationFrame(() => { navigation.navigate(TAB_CONFIG[newIndex].name) }) } // 弹簧动画复位 Animated.spring(gestureX, { toValue: 0, useNativeDriver: true, tension: 100, friction: 15, }).start() } } // 指示器位置 = 基准位置 + 手势偏移 const indicatorTranslateX = Animated.add( basePos.interpolate({ inputRange: TAB_CONFIG.map((_, i) => i), outputRange: TAB_CONFIG.map((_, i) => theme.spacing.xs + i * buttonWidth), }), gestureX, ) return ( {/* 滑动指示器 */} {/* Tab 按钮 */} {TAB_CONFIG.map((tab, index) => { // 图标颜色取决于 visualIndex (手势实时计算) 而不是 state.index const isActive = visualIndex === index return ( { if (tabIndex !== index) { navigation.navigate(tab.name) } }} > ) })} ) } /** * Custom header component with absolutely centered title */ function CustomHeader({ title }: { title: string }) { const { themed } = useAppTheme() const insets = useSafeAreaInsets() return ( {/* Absolutely centered title */} {title} {/* Left side - avatar */} {/* Right side - future icons go here */} ) } /** * This is the main navigator with a floating capsule tab bar. */ export function MainNavigator() { return ( , tabBarHideOnKeyboard: true, tabBarStyle: { display: "none" }, }} tabBar={(props) => } > ) } // Styles const $headerContainer: ThemedStyle = ({ colors, spacing }) => ({ backgroundColor: colors.background, height: s(56), flexDirection: "row", alignItems: "center", paddingHorizontal: spacing.lg, }) const $headerTitleWrapper: ViewStyle = { position: "absolute", left: 0, right: 0, alignItems: "center", justifyContent: "center", pointerEvents: "none", } const $headerTitle: ThemedStyle = ({ colors, typography }) => ({ color: colors.text, fontFamily: typography.primary.medium, fontSize: fs(17), }) const $headerLeft: ViewStyle = { zIndex: 1, } const $headerRight: ViewStyle = { marginLeft: "auto", zIndex: 1, } const $tabBarOverlay: ViewStyle = { position: "absolute", bottom: 0, left: 0, right: 0, alignItems: "center", } const $tabBarContainer: ThemedStyle = ({ spacing }) => ({ width: SCREEN_WIDTH - spacing.xl * 2, alignItems: "center", }) const $tabBarGlass: ThemedStyle = ({ colors, spacing, isDark }) => ({ flexDirection: "row", // 半透明背景模拟毛玻璃效果 backgroundColor: isDark ? "rgba(30, 30, 30, 0.8)" : "rgba(255, 255, 255, 0.8)", borderRadius: s(28), height: s(56), width: "100%", alignItems: "center", paddingHorizontal: spacing.xs, // iOS 阴影 ...(Platform.OS === "ios" && { shadowColor: colors.palette.neutral800, shadowOffset: { width: 0, height: s(4) }, shadowOpacity: 0.15, shadowRadius: s(12), }), // Android 阴影 elevation: 8, // 边框增加玻璃感 borderWidth: 1, borderColor: isDark ? "rgba(255, 255, 255, 0.1)" : "rgba(0, 0, 0, 0.05)", }) const $tabIndicator: ThemedStyle = ({ isDark }) => ({ position: "absolute", height: s(48), // 选择器半透明 backgroundColor: isDark ? "rgba(255, 255, 255, 0.15)" : "rgba(0, 0, 0, 0.08)", borderRadius: s(24), top: s(4), left: 0, }) const $tabButton: ViewStyle = { flex: 1, height: "100%", alignItems: "center", justifyContent: "center", zIndex: 1, }