自定义底部导航栏

This commit is contained in:
xianyi
2026-01-13 10:26:04 +08:00
parent 98134c5be9
commit ceee6fe699
5 changed files with 338 additions and 61 deletions

View File

@@ -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 (
<Tabs
tabBar={(props) => <CustomTabBar {...props} />}
screenOptions={{
tabBarActiveTintColor: Colors[theme].tint,
tabBarInactiveTintColor: Colors[theme].tabIconDefault,
headerShown: false,
tabBarButton: HapticTab,
tabBarBackground: TabBarBackground,
tabBarStyle: Platform.select({
ios: {
position: "absolute",
},
default: {},
}),
}}
>
<Tabs.Screen

View File

@@ -0,0 +1,296 @@
import { BottomTabBarProps } from "@react-navigation/bottom-tabs";
import { BlurView } from "expo-blur";
import * as Haptics from "expo-haptics";
import React, { useEffect, useRef } from "react";
import { LayoutChangeEvent, Platform, StyleSheet, Text, TouchableOpacity, View } from "react-native";
import Animated, {
useAnimatedStyle,
useSharedValue,
withTiming,
} from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { IconSymbol } from "@/components/ui/icon-symbol";
import { Colors } from "@/constants/theme";
import { useTheme } from "@/context/ThemeContext";
// 图标名称映射
const TAB_ICONS: Record<string, string> = {
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 (
<TouchableOpacity
onPress={onPress}
onLayout={onLayout}
style={styles.tabItem}
activeOpacity={1}
>
<View style={styles.iconContainer}>
<IconSymbol
name={iconName as any}
size={22}
color={isFocused ? activeColor : inactiveColor}
/>
</View>
<Text
style={[
styles.tabLabel,
{ color: isFocused ? activeColor : inactiveColor },
]}
>
{label}
</Text>
</TouchableOpacity>
);
};
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<number>(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 (
<View
style={[
styles.safeArea,
{
bottom: bottomInset,
},
]}
>
<BlurView
intensity={80}
tint={isDark ? "dark" : "light"}
onLayout={handleContainerLayout}
style={[
styles.tabBarContainer,
{
backgroundColor: isDark
? "rgba(25, 25, 25, 0.75)"
: "rgba(255, 255, 255, 0.75)",
borderColor: isDark
? "rgba(255, 255, 255, 0.1)"
: "rgba(0, 0, 0, 0.1)",
},
]}
>
{/* 胶囊背景指示器 */}
<Animated.View
style={[
styles.indicator,
{
backgroundColor: indicatorBackgroundColor,
},
indicatorStyle,
]}
/>
{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 (
<TabItem
key={route.key}
route={route}
isFocused={isFocused}
onPress={onPress}
onLayout={handleTabLayout(index)}
label={typeof label === "string" ? label : route.name}
activeColor={activeColor}
inactiveColor={inactiveColor}
/>
);
})}
</BlurView>
</View>
);
}
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",
},
});

View File

@@ -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,

70
package-lock.json generated
View File

@@ -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"

View File

@@ -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",