自定义底部导航栏
This commit is contained in:
@@ -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
|
||||
|
||||
296
components/custom-tab-bar.tsx
Normal file
296
components/custom-tab-bar.tsx
Normal 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",
|
||||
},
|
||||
});
|
||||
@@ -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
70
package-lock.json
generated
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user