From ceee6fe699321a4123f746624423e22151a4b4ab Mon Sep 17 00:00:00 2001 From: xianyi Date: Tue, 13 Jan 2026 10:26:04 +0800 Subject: [PATCH] =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E5=BA=95=E9=83=A8?= =?UTF-8?q?=E5=AF=BC=E8=88=AA=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/_layout.tsx | 18 +-- components/custom-tab-bar.tsx | 296 ++++++++++++++++++++++++++++++++++ constants/theme.ts | 8 +- package-lock.json | 70 ++++---- package.json | 7 +- 5 files changed, 338 insertions(+), 61 deletions(-) create mode 100644 components/custom-tab-bar.tsx diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 514ad17..9f983cd 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -1,32 +1,18 @@ import { Tabs } from "expo-router"; import React from "react"; import { useTranslation } from "react-i18next"; -import { Platform } from "react-native"; -import { HapticTab } from "@/components/haptic-tab"; +import CustomTabBar from "@/components/custom-tab-bar"; import { IconSymbol } from "@/components/ui/icon-symbol"; -import TabBarBackground from "@/components/ui/tab-bar-background"; -import { Colors } from "@/constants/theme"; -import { useTheme } from "@/context/ThemeContext"; export default function TabLayout() { - const { theme } = useTheme(); const { t } = useTranslation(); return ( } screenOptions={{ - tabBarActiveTintColor: Colors[theme].tint, - tabBarInactiveTintColor: Colors[theme].tabIconDefault, headerShown: false, - tabBarButton: HapticTab, - tabBarBackground: TabBarBackground, - tabBarStyle: Platform.select({ - ios: { - position: "absolute", - }, - default: {}, - }), }} > = { + index: "list", + live: "play-circle", + upcoming: "calendar", + finished: "checkmark-circle", + favorite: "star", +}; + +// 将十六进制颜色转换为带透明度的 rgba +function hexToRgba(hex: string, alpha: number): string { + // 移除 # 符号 + const cleanHex = hex.replace("#", ""); + + // 处理 3 位和 6 位十六进制颜色 + const r = cleanHex.length === 3 + ? parseInt(cleanHex[0] + cleanHex[0], 16) + : parseInt(cleanHex.substring(0, 2), 16); + const g = cleanHex.length === 3 + ? parseInt(cleanHex[1] + cleanHex[1], 16) + : parseInt(cleanHex.substring(2, 4), 16); + const b = cleanHex.length === 3 + ? parseInt(cleanHex[2] + cleanHex[2], 16) + : parseInt(cleanHex.substring(4, 6), 16); + + return `rgba(${r}, ${g}, ${b}, ${alpha})`; +} + +interface TabItemProps { + route: BottomTabBarProps["state"]["routes"][0]; + isFocused: boolean; + onPress: () => void; + label: string; + activeColor: string; + inactiveColor: string; +} + +interface TabItemPropsWithLayout extends TabItemProps { + onLayout: (event: LayoutChangeEvent) => void; +} + +const TabItem = ({ + route, + isFocused, + onPress, + label, + activeColor, + inactiveColor, + onLayout, +}: TabItemPropsWithLayout) => { + const iconName = TAB_ICONS[route.name] || "list"; + + return ( + + + + + + {label} + + + ); +}; + +export default function CustomTabBar({ + state, + descriptors, + navigation, +}: BottomTabBarProps) { + const { theme } = useTheme(); + const insets = useSafeAreaInsets(); + const isDark = theme === "dark"; + + // 图标和文字颜色:light 和 dark 模式都使用主题色 + const activeColor = Colors.light.tint; + const inactiveColor = Colors[theme].tabIconDefault; + + // 胶囊背景颜色:light 模式用浅白色,dark 模式用浅黑色 + const indicatorBackgroundColor = isDark + ? "rgba(40, 40, 40, 0.6)" // 浅黑色 + : "rgba(240, 240, 240, 0.8)"; // 浅白色 + + // 计算底部安全区域 + const bottomInset = Platform.OS === "ios" ? insets.bottom : 20; + + // 胶囊背景位置动画 + const indicatorPosition = useSharedValue(0); + const tabLayouts = useRef<{ [key: number]: { x: number; width: number } }>({}); + const containerWidth = useRef(0); + + // 胶囊背景宽度(动态,等于每个 tab 的宽度减去左右 margin) + const indicatorWidth = useSharedValue(0); + + // 胶囊背景的 margin + const INDICATOR_MARGIN_HORIZONTAL = 4; // 左右 margin + const INDICATOR_MARGIN_VERTICAL = 4; // 上下 margin(设为 0 让胶囊背景更高) + + // 当选中 tab 改变时,更新胶囊背景位置 + useEffect(() => { + const currentIndex = state.index; + const tabLayout = tabLayouts.current[currentIndex]; + if (tabLayout && containerWidth.current > 0) { + // 计算带 margin 的位置和宽度 + const targetX = tabLayout.x + INDICATOR_MARGIN_HORIZONTAL; + const targetWidth = tabLayout.width - INDICATOR_MARGIN_HORIZONTAL * 2; + + // 使用 withTiming 实现平滑的线性移动动画 + indicatorPosition.value = withTiming(targetX, { + duration: 200, + }); + indicatorWidth.value = withTiming(targetWidth, { + duration: 200, + }); + } + }, [state.index]); + + // 处理容器布局 + const handleContainerLayout = (event: LayoutChangeEvent) => { + containerWidth.current = event.nativeEvent.layout.width; + }; + + // 处理每个 tab 的布局事件 + const handleTabLayout = (index: number) => (event: LayoutChangeEvent) => { + const { x, width } = event.nativeEvent.layout; + tabLayouts.current[index] = { x, width }; + + // 如果是当前选中的 tab,立即更新位置 + if (index === state.index && containerWidth.current > 0) { + const INDICATOR_MARGIN_HORIZONTAL = 4; + indicatorPosition.value = x + INDICATOR_MARGIN_HORIZONTAL; + indicatorWidth.value = width - INDICATOR_MARGIN_HORIZONTAL * 2; + } + }; + + // 胶囊背景动画样式 + const indicatorStyle = useAnimatedStyle(() => { + return { + transform: [{ translateX: indicatorPosition.value }], + width: indicatorWidth.value, + }; + }); + + return ( + + + {/* 胶囊背景指示器 */} + + + {state.routes.map((route, index) => { + const { options } = descriptors[route.key]; + const isFocused = state.index === index; + + const onPress = () => { + // 触发震动反馈 (iOS Impact Light 非常细腻) + if (Platform.OS === "ios") { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + } + + const event = navigation.emit({ + type: "tabPress", + target: route.key, + canPreventDefault: true, + }); + + if (!isFocused && !event.defaultPrevented) { + navigation.navigate(route.name); + } + }; + + // 获取标签文本,优先使用 tabBarLabel,然后是 title + let label: string; + if (options.tabBarLabel !== undefined) { + label = + typeof options.tabBarLabel === "string" + ? options.tabBarLabel + : route.name; + } else if (options.title !== undefined) { + label = options.title; + } else { + label = route.name; + } + + return ( + + ); + })} + + + ); +} + +const styles = StyleSheet.create({ + safeArea: { + position: "absolute", + left: 12, + right: 12, + }, + tabBarContainer: { + flexDirection: "row", + height: 60, // 从 56 增加到 58 + borderRadius: 30, // 相应调整圆角 + overflow: "hidden", + borderWidth: 0.5, + }, + tabItem: { + flex: 1, + alignItems: "center", + justifyContent: "center", + }, + iconContainer: { + width: 48, + height: 28, + alignItems: "center", + justifyContent: "center", + marginBottom: 2, + }, + indicator: { + position: "absolute", + height: 52, // 58 (tabBarContainer 高度) - 8 (上下 margin 4px * 2) + borderRadius: 26, // 与 tabBarContainer 的 borderRadius 一致 + top: 4, // 上 margin + }, + tabLabel: { + fontSize: 10, + fontWeight: "600", + }, +}); diff --git a/constants/theme.ts b/constants/theme.ts index f06facd..4cf1f1a 100644 --- a/constants/theme.ts +++ b/constants/theme.ts @@ -5,7 +5,9 @@ import { Platform } from 'react-native'; -const tintColorLight = '#0a7ea4'; +const tintColorLight = '#ffa366'; +const tintColorLightDark = '#e8914d'; // 深一点的版本 +const tintColorLightLight = '#ffb380'; // 浅一点的版本 const tintColorDark = '#fff'; export const Colors = { @@ -13,6 +15,8 @@ export const Colors = { text: '#11181C', background: '#fff', tint: tintColorLight, + tintDark: tintColorLightDark, // 深一点的版本 + tintLight: tintColorLightLight, // 浅一点的版本 icon: '#687076', tabIconDefault: '#687076', tabIconSelected: tintColorLight, @@ -21,6 +25,8 @@ export const Colors = { text: '#ECEDEE', background: '#151718', tint: tintColorDark, + tintDark: tintColorDark, // dark 模式保持原样 + tintLight: tintColorDark, // dark 模式保持原样 icon: '#9BA1A6', tabIconDefault: '#9BA1A6', tabIconSelected: tintColorDark, diff --git a/package-lock.json b/package-lock.json index 6568fb8..35f8c28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,15 +10,15 @@ "dependencies": { "@expo/vector-icons": "^15.0.3", "@react-native-async-storage/async-storage": "^2.2.0", - "@react-navigation/bottom-tabs": "^7.4.0", + "@react-navigation/bottom-tabs": "^7.9.1", "@react-navigation/drawer": "^7.7.10", "@react-navigation/elements": "^2.6.3", - "@react-navigation/native": "^7.1.8", + "@react-navigation/native": "^7.1.27", "axios": "^1.13.2", "expo": "~54.0.31", - "expo-blur": "^15.0.8", + "expo-blur": "~15.0.8", "expo-constants": "~18.0.13", - "expo-dev-client": "^6.0.20", + "expo-dev-client": "~6.0.20", "expo-file-system": "^19.0.21", "expo-font": "~14.0.10", "expo-haptics": "~15.0.8", @@ -41,6 +41,7 @@ "react-i18next": "^16.5.2", "react-native": "0.81.5", "react-native-gesture-handler": "~2.28.0", + "react-native-haptic-feedback": "^2.3.3", "react-native-reanimated": "~4.1.1", "react-native-safe-area-context": "~5.6.0", "react-native-screens": "~4.16.0", @@ -3044,17 +3045,17 @@ "license": "MIT" }, "node_modules/@react-navigation/bottom-tabs": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.9.0.tgz", - "integrity": "sha512-024FWdHp3ZsE5rP8tmGI4vh+1z3wg8u8E9Frep8eeGoYo1h9rQhvgofQDGxknmrKsb7t8o8Dim+IZSvl57cPFQ==", + "version": "7.9.1", + "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.9.1.tgz", + "integrity": "sha512-1MHn1b5tWFa8t8LXUvaNtlg5WGVXcNTsiV00ygQDBFDusMcu0ZPOU1exqELZwHf6kDntmTQE96/NRM+Cd2QR+A==", "license": "MIT", "dependencies": { - "@react-navigation/elements": "^2.9.3", + "@react-navigation/elements": "^2.9.4", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { - "@react-navigation/native": "^7.1.26", + "@react-navigation/native": "^7.1.27", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", @@ -3085,7 +3086,6 @@ "resolved": "https://registry.npmjs.org/@react-navigation/drawer/-/drawer-7.7.10.tgz", "integrity": "sha512-FGYU5Ebd2whTa4Z+RBCxnWqmyWIQGTJ7PAAhk2RjlVrEXLU0HaFR5JGmEHuNm/Cm9xX3xCwOxYZiA0Xi/DeyAA==", "license": "MIT", - "peer": true, "dependencies": { "@react-navigation/elements": "^2.9.3", "color": "^4.2.3", @@ -3103,9 +3103,9 @@ } }, "node_modules/@react-navigation/elements": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.3.tgz", - "integrity": "sha512-3+eyvWiVPIEf6tN9UdduhOEHcTuNe3R5WovgiVkfH9+jApHMTZDc2loePTpY/i2HDJhObhhChpJzO6BVjrpdYQ==", + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.4.tgz", + "integrity": "sha512-TMFh+QzwesEuSaKpvZk4BFC5105t5gJs9eq+jG7jtfdlMKyrcFwheu2/dy1zfrW4WYbVcoMxhzFqCU4mKwI4fw==", "license": "MIT", "dependencies": { "color": "^4.2.3", @@ -3114,7 +3114,7 @@ }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", - "@react-navigation/native": "^7.1.26", + "@react-navigation/native": "^7.1.27", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" @@ -3126,11 +3126,10 @@ } }, "node_modules/@react-navigation/native": { - "version": "7.1.26", - "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.26.tgz", - "integrity": "sha512-RhKmeD0E2ejzKS6z8elAfdfwShpcdkYY8zJzvHYLq+wv183BBcElTeyMLcIX6wIn7QutXeI92Yi21t7aUWfqNQ==", + "version": "7.1.27", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.27.tgz", + "integrity": "sha512-kW7LGP/RrisktpGyizTKw6HwSeQJdXnAN9L8GyQJcJAlgL9YtfEg6yEyD5n9RWH90CL8G0cRyUhphKIAFf4lVw==", "license": "MIT", - "peer": true, "dependencies": { "@react-navigation/core": "^7.13.7", "escape-string-regexp": "^4.0.0", @@ -3329,7 +3328,6 @@ "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -3400,7 +3398,6 @@ "integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.52.0", "@typescript-eslint/types": "8.52.0", @@ -3962,7 +3959,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4665,7 +4661,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5664,7 +5659,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5861,7 +5855,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -6100,7 +6093,6 @@ "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.31.tgz", "integrity": "sha512-kQ3RDqA/a59I7y+oqQGyrPbbYlgPMUdKBOgvFLpoHbD2bCM+F75i4N0mUijy7dG5F/CUCu2qHmGGUCXBbMDkCg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "54.0.21", @@ -6179,7 +6171,6 @@ "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.13.tgz", "integrity": "sha512-FnZn12E1dRYKDHlAdIyNFhBurKTS3F9CrfrBDJI5m3D7U17KBHMQ6JEfYlSj7LG7t+Ulr+IKaj58L1k5gBwTcQ==", "license": "MIT", - "peer": true, "dependencies": { "@expo/config": "~12.0.13", "@expo/env": "~2.0.8" @@ -6283,7 +6274,6 @@ "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.10.tgz", "integrity": "sha512-UqyNaaLKRpj4pKAP4HZSLnuDQqueaO5tB1c/NWu5vh1/LF9ulItyyg2kF/IpeOp0DeOLk0GY0HrIXaKUMrwB+Q==", "license": "MIT", - "peer": true, "dependencies": { "fontfaceobserver": "^2.1.0" }, @@ -6381,7 +6371,6 @@ "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-8.0.11.tgz", "integrity": "sha512-+VSaNL5om3kOp/SSKO5qe6cFgfSIWnnQDSbA7XLs3ECkYzXRquk5unxNS3pg7eK5kNUmQ4kgLI7MhTggAEUBLA==", "license": "MIT", - "peer": true, "dependencies": { "expo-constants": "~18.0.12", "invariant": "^2.2.4" @@ -7766,7 +7755,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -10543,7 +10531,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -10563,7 +10550,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -10627,7 +10613,6 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.5.tgz", "integrity": "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==", "license": "MIT", - "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.5", @@ -10701,7 +10686,6 @@ "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.28.0.tgz", "integrity": "sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==", "license": "MIT", - "peer": true, "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", @@ -10712,6 +10696,18 @@ "react-native": "*" } }, + "node_modules/react-native-haptic-feedback": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/react-native-haptic-feedback/-/react-native-haptic-feedback-2.3.3.tgz", + "integrity": "sha512-svS4D5PxfNv8o68m9ahWfwje5NqukM3qLS48+WTdhbDkNUkOhP9rDfDSRHzlhk4zq+ISjyw95EhLeh8NkKX5vQ==", + "license": "MIT", + "workspaces": [ + "example" + ], + "peerDependencies": { + "react-native": ">=0.60.0" + } + }, "node_modules/react-native-is-edge-to-edge": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz", @@ -10727,7 +10723,6 @@ "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.6.tgz", "integrity": "sha512-F+ZJBYiok/6Jzp1re75F/9aLzkgoQCOh4yxrnwATa8392RvM3kx+fiXXFvwcgE59v48lMwd9q0nzF1oJLXpfxQ==", "license": "MIT", - "peer": true, "dependencies": { "react-native-is-edge-to-edge": "^1.2.1", "semver": "7.7.2" @@ -10756,7 +10751,6 @@ "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz", "integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==", "license": "MIT", - "peer": true, "peerDependencies": { "react": "*", "react-native": "*" @@ -10767,7 +10761,6 @@ "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz", "integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==", "license": "MIT", - "peer": true, "dependencies": { "react-freeze": "^1.0.0", "react-native-is-edge-to-edge": "^1.2.1", @@ -10793,7 +10786,6 @@ "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz", "integrity": "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", @@ -10826,7 +10818,6 @@ "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.16.0.tgz", "integrity": "sha512-Nh13xKZWW35C0dbOskD7OX01nQQavOzHbCw9XoZmar4eXCo7AvrYJ0jlUfRVVIJzqINxHlpECYLdmAdFsl9xDA==", "license": "MIT", - "peer": true, "dependencies": { "escape-string-regexp": "^4.0.0", "invariant": "2.2.4" @@ -10951,7 +10942,6 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -12272,7 +12262,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -12479,7 +12468,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 964b09b..87de54a 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,13 @@ "dependencies": { "@expo/vector-icons": "^15.0.3", "@react-native-async-storage/async-storage": "^2.2.0", - "@react-navigation/bottom-tabs": "^7.4.0", + "@react-navigation/bottom-tabs": "^7.9.1", "@react-navigation/drawer": "^7.7.10", "@react-navigation/elements": "^2.6.3", - "@react-navigation/native": "^7.1.8", + "@react-navigation/native": "^7.1.27", "axios": "^1.13.2", "expo": "~54.0.31", - "expo-blur": "^15.0.8", + "expo-blur": "~15.0.8", "expo-constants": "~18.0.13", "expo-dev-client": "~6.0.20", "expo-file-system": "^19.0.21", @@ -43,6 +43,7 @@ "react-i18next": "^16.5.2", "react-native": "0.81.5", "react-native-gesture-handler": "~2.28.0", + "react-native-haptic-feedback": "^2.3.3", "react-native-reanimated": "~4.1.1", "react-native-safe-area-context": "~5.6.0", "react-native-screens": "~4.16.0",