201 lines
8.3 KiB
TypeScript
201 lines
8.3 KiB
TypeScript
import ChatInSvg from "@/assets/icons/svg/chatIn.svg";
|
||
import ChatNotInSvg from "@/assets/icons/svg/chatNotIn.svg";
|
||
import PersonInSvg from "@/assets/icons/svg/personIn.svg";
|
||
import PersonNotInSvg from "@/assets/icons/svg/personNotIn.svg";
|
||
import { WebSocketStatus } from "@/lib/websocket-util";
|
||
import { router, usePathname } from "expo-router";
|
||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||
import { Dimensions, Image, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||
import Svg, { Circle, Ellipse, G, Mask, Path, Rect } from "react-native-svg";
|
||
|
||
// 使用 React.memo 包装 SVG 组件,避免不必要的重渲染
|
||
const TabIcon = React.memo(({ isActive, ActiveIcon, InactiveIcon }: {
|
||
isActive: boolean;
|
||
ActiveIcon: React.FC<{ width: number; height: number }>;
|
||
InactiveIcon: React.FC<{ width: number; height: number }>;
|
||
}) => {
|
||
const Icon = isActive ? ActiveIcon : InactiveIcon;
|
||
return <Icon width={24} height={24} />;
|
||
});
|
||
|
||
// 提取 SVG 组件,避免重复渲染
|
||
const CenterButtonSvg = React.memo(() => (
|
||
<Svg width="100%" height="100%" viewBox="0 0 85 85" fill="none">
|
||
<Mask id="mask0_1464_1669" maskUnits="userSpaceOnUse" x="0" y="0" width="85" height="85">
|
||
<Circle cx="42.5" cy="42.5" r="42.5" fill="#FFC959" />
|
||
</Mask>
|
||
<G mask="url(#mask0_1464_1669)">
|
||
<Circle cx="42.5" cy="42.5" r="42.5" fill="#FFD38D" />
|
||
<Path d="M20.2018 14.6411C21.8694 12.5551 26.4765 16.939 28.5716 19.3917L20.8604 20.0277C19.3178 19.3509 18.5342 16.7271 20.2018 14.6411Z" fill="#FFDBA3" />
|
||
<Path d="M21.3021 15.4913C22.503 13.6451 25.3001 17.1089 26.5485 19.0716L22.7323 19.2755C21.7552 18.7834 20.1012 17.3376 21.3021 15.4913Z" fill="#AC7E35" />
|
||
<Path d="M65.1253 14.6411C63.4577 12.5551 58.8506 16.939 56.7556 19.3917L64.4667 20.0277C66.0093 19.3509 66.7929 16.7271 65.1253 14.6411Z" fill="#FFDBA3" />
|
||
<Path d="M64.0255 15.4913C62.8246 13.6451 60.0276 17.1089 58.7792 19.0716L62.5953 19.2755C63.5724 18.7834 65.2264 17.3376 64.0255 15.4913Z" fill="#AC7E35" />
|
||
<Path d="M-15.3352 49.1734C10.3693 4.65192 74.6306 4.65187 100.335 49.1734L117.868 79.5409C143.572 124.062 111.442 179.714 60.0327 179.714H24.9673C-26.4417 179.714 -58.5724 124.062 -32.8679 79.5409L-15.3352 49.1734Z" fill="#FFD18A" />
|
||
<Rect x="38.5571" y="46.2812" width="2.62922" height="3.68091" rx="1.31461" transform="rotate(-180 38.5571 46.2812)" fill="#4C320C" />
|
||
<Rect x="48.0205" y="46.2812" width="2.62922" height="3.68091" rx="1.31461" transform="rotate(-180 48.0205 46.2812)" fill="#4C320C" />
|
||
<Path d="M4.8084 73.2062C22.9876 46.7781 62.0132 46.7782 80.1924 73.2062L100.897 103.306C121.776 133.659 100.046 174.982 63.2051 174.982H21.7957C-15.0453 174.982 -36.7756 133.659 -15.8963 103.306L4.8084 73.2062Z" fill="#FFF8DE" />
|
||
<Ellipse cx="79.047" cy="68.6298" rx="43.1193" ry="30.7619" fill="#FFF8DE" />
|
||
<Ellipse cx="5.69032" cy="68.6298" rx="42.8563" ry="30.7619" fill="#FFF8DE" />
|
||
<Ellipse cx="42.2365" cy="53.3803" rx="3.15507" ry="2.3663" transform="rotate(180 42.2365 53.3803)" fill="#FFB8B9" />
|
||
<Path d="M41.7813 56.0095C41.9837 55.6589 42.4897 55.6589 42.6921 56.0095L43.1475 56.7982C43.3499 57.1488 43.0969 57.587 42.6921 57.587H41.7813C41.3765 57.587 41.1235 57.1488 41.3259 56.7982L41.7813 56.0095Z" fill="#4C320C" />
|
||
</G>
|
||
</Svg>
|
||
));
|
||
|
||
interface AskNavbarProps {
|
||
wsStatus: WebSocketStatus;
|
||
}
|
||
|
||
const AskNavbar = ({ wsStatus }: AskNavbarProps) => {
|
||
// 获取设备尺寸
|
||
const { width } = useMemo(() => Dimensions.get('window'), []);
|
||
const pathname = usePathname();
|
||
|
||
const statusColor = useMemo(() => {
|
||
switch (wsStatus) {
|
||
case 'connected':
|
||
return '#4CAF50'; // Green
|
||
case 'connecting':
|
||
case 'reconnecting':
|
||
return '#FFC107'; // Amber
|
||
case 'disconnected':
|
||
default:
|
||
return '#F44336'; // Red
|
||
}
|
||
}, [wsStatus]);
|
||
|
||
// 预加载目标页面
|
||
useEffect(() => {
|
||
const preloadPages = async () => {
|
||
try {
|
||
await Promise.all([
|
||
router.prefetch('/memo-list'),
|
||
router.prefetch('/ask'),
|
||
router.prefetch('/owner')
|
||
]);
|
||
} catch (error) {
|
||
console.warn('预加载页面失败:', error);
|
||
}
|
||
};
|
||
preloadPages();
|
||
}, []);
|
||
|
||
// 使用 useCallback 缓存导航函数
|
||
const navigateTo = useCallback((route: string) => {
|
||
if (route === '/ask') {
|
||
router.push({
|
||
pathname: '/ask',
|
||
params: { newSession: "true" }
|
||
});
|
||
} else {
|
||
router.push(route as any);
|
||
}
|
||
}, []);
|
||
|
||
// 使用 useMemo 缓存样式对象
|
||
const styles = useMemo(() => StyleSheet.create({
|
||
container: {
|
||
position: 'absolute',
|
||
bottom: 0,
|
||
left: 0,
|
||
right: 0
|
||
},
|
||
backgroundImage: {
|
||
width,
|
||
height: 80,
|
||
resizeMode: 'cover'
|
||
},
|
||
navButton: {
|
||
width: width / 2, // 半屏宽度
|
||
height: 80, // 与 navbar 高度相同
|
||
justifyContent: 'center',
|
||
alignItems: 'center'
|
||
},
|
||
navContainer: {
|
||
position: 'absolute',
|
||
bottom: 0,
|
||
left: 0,
|
||
right: 0,
|
||
height: 80, // Set a fixed height for the navbar
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'center',
|
||
paddingHorizontal: 32,
|
||
backgroundColor: 'transparent', // Make sure it's transparent
|
||
},
|
||
centerButton: {
|
||
position: 'absolute',
|
||
left: width / 2,
|
||
top: -30, // Adjust this value to move the button up or down
|
||
marginLeft: -42.5, // Half of the button width (85/2)
|
||
width: 85,
|
||
height: 85,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
shadowColor: '#FFB645',
|
||
shadowOffset: { width: 0, height: 4 },
|
||
shadowOpacity: 0.3,
|
||
shadowRadius: 8,
|
||
elevation: 8,
|
||
borderRadius: 50,
|
||
backgroundColor: 'transparent',
|
||
zIndex: 10,
|
||
},
|
||
statusIndicator: {
|
||
position: 'absolute',
|
||
top: 15,
|
||
right: 15,
|
||
width: 10,
|
||
height: 10,
|
||
borderRadius: 5,
|
||
borderWidth: 1,
|
||
borderColor: '#FFF',
|
||
backgroundColor: statusColor,
|
||
zIndex: 11,
|
||
}
|
||
}), [width, statusColor]);
|
||
|
||
// 如果当前路径是ask页面,则不渲染导航栏
|
||
if (pathname != '/memo-list' && pathname != '/owner') {
|
||
return null;
|
||
}
|
||
|
||
return (
|
||
<View style={styles.container}>
|
||
<Image source={require('@/assets/images/png/owner/ask.png')} style={{ width: width * 1.18, height: 100, resizeMode: 'cover', marginLeft: -width * 0.07 }} />
|
||
<View style={styles.navContainer}>
|
||
<TouchableOpacity
|
||
onPress={() => navigateTo('/memo-list')}
|
||
style={[styles.navButton, { alignItems: "flex-start", paddingLeft: 16 }]}
|
||
>
|
||
<TabIcon
|
||
isActive={pathname === "/memo-list"}
|
||
ActiveIcon={ChatInSvg}
|
||
InactiveIcon={ChatNotInSvg}
|
||
/>
|
||
</TouchableOpacity>
|
||
|
||
<TouchableOpacity
|
||
onPress={() => navigateTo('/ask')}
|
||
style={styles.centerButton}
|
||
>
|
||
<View style={styles.statusIndicator} />
|
||
<Image source={require('@/assets/images/png/owner/askIP.png')} />
|
||
</TouchableOpacity>
|
||
|
||
<TouchableOpacity
|
||
onPress={() => navigateTo('/owner')}
|
||
style={styles.navButton}
|
||
>
|
||
<TabIcon
|
||
isActive={pathname === "/owner"}
|
||
ActiveIcon={PersonInSvg}
|
||
InactiveIcon={PersonNotInSvg}
|
||
/>
|
||
</TouchableOpacity>
|
||
</View>
|
||
</View>
|
||
);
|
||
};
|
||
|
||
export default React.memo(AskNavbar); |