diff --git a/app/(tabs)/personal.tsx b/app/(tabs)/personal.tsx index d36f2e8..500434f 100644 --- a/app/(tabs)/personal.tsx +++ b/app/(tabs)/personal.tsx @@ -2,9 +2,11 @@ import { Colors } from '@/constants/Colors'; import { getTabBarBottomPadding } from '@/constants/TabBar'; import { useColorScheme } from '@/hooks/useColorScheme'; import { Ionicons } from '@expo/vector-icons'; +import AsyncStorage from '@react-native-async-storage/async-storage'; import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; import React, { useMemo, useState } from 'react'; import { + Alert, SafeAreaView, ScrollView, StatusBar, @@ -27,6 +29,32 @@ export default function PersonalScreen() { const colorScheme = useColorScheme(); const colors = Colors[colorScheme ?? 'light']; + const handleResetOnboarding = () => { + Alert.alert( + '重置引导', + '确定要重置引导流程吗?下次启动应用时将重新显示引导页面。', + [ + { + text: '取消', + style: 'cancel', + }, + { + text: '确定', + style: 'destructive', + onPress: async () => { + try { + await AsyncStorage.multiRemove(['@onboarding_completed', '@user_personal_info']); + Alert.alert('成功', '引导状态已重置,请重启应用查看效果。'); + } catch (error) { + console.error('重置引导状态失败:', error); + Alert.alert('错误', '重置失败,请稍后重试。'); + } + }, + }, + ] + ); + }; + const UserInfoSection = () => ( @@ -79,10 +107,14 @@ export default function PersonalScreen() { {title} {items.map((item, index) => ( - + - + {item.title} @@ -197,6 +229,16 @@ export default function PersonalScreen() { }, ]; + const developerItems = [ + { + icon: 'refresh-outline', + iconBg: '#FFE8E8', + iconColor: '#FF4444', + title: '重置引导流程', + onPress: handleResetOnboarding, + }, + ]; + return ( @@ -211,6 +253,7 @@ export default function PersonalScreen() { + {/* 底部浮动按钮 */} diff --git a/app/_layout.tsx b/app/_layout.tsx index 220acf2..a15fdaf 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -20,6 +20,7 @@ export default function RootLayout() { return ( + diff --git a/app/index.tsx b/app/index.tsx new file mode 100644 index 0000000..a15e621 --- /dev/null +++ b/app/index.tsx @@ -0,0 +1,71 @@ +import { ThemedView } from '@/components/ThemedView'; +import { useThemeColor } from '@/hooks/useThemeColor'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { router } from 'expo-router'; +import React, { useEffect, useState } from 'react'; +import { ActivityIndicator, Text, View } from 'react-native'; + +const ONBOARDING_COMPLETED_KEY = '@onboarding_completed'; + +export default function SplashScreen() { + const backgroundColor = useThemeColor({}, 'background'); + const primaryColor = useThemeColor({}, 'primary'); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + checkOnboardingStatus(); + }, []); + + const checkOnboardingStatus = async () => { + try { + const onboardingCompleted = await AsyncStorage.getItem(ONBOARDING_COMPLETED_KEY); + + // 添加一个短暂的延迟以显示启动画面 + setTimeout(() => { + if (onboardingCompleted === 'true') { + router.replace('/(tabs)'); + } else { + router.replace('/onboarding'); + } + setIsLoading(false); + }, 1000); + } catch (error) { + console.error('检查引导状态失败:', error); + // 如果出现错误,默认显示引导页面 + setTimeout(() => { + router.replace('/onboarding'); + setIsLoading(false); + }, 1000); + } + }; + + if (!isLoading) { + return null; + } + + return ( + + + + 🧘‍♀️ + + + + + ); +} diff --git a/app/onboarding/_layout.tsx b/app/onboarding/_layout.tsx new file mode 100644 index 0000000..2de8510 --- /dev/null +++ b/app/onboarding/_layout.tsx @@ -0,0 +1,10 @@ +import { Stack } from 'expo-router'; + +export default function OnboardingLayout() { + return ( + + + + + ); +} diff --git a/app/onboarding/index.tsx b/app/onboarding/index.tsx new file mode 100644 index 0000000..7b549a9 --- /dev/null +++ b/app/onboarding/index.tsx @@ -0,0 +1,233 @@ +import { ThemedText } from '@/components/ThemedText'; +import { ThemedView } from '@/components/ThemedView'; +import { useColorScheme } from '@/hooks/useColorScheme'; +import { useThemeColor } from '@/hooks/useThemeColor'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { router } from 'expo-router'; +import React from 'react'; +import { + Dimensions, + StatusBar, + StyleSheet, + Text, + TouchableOpacity, + View +} from 'react-native'; + +const { width, height } = Dimensions.get('window'); + +export default function WelcomeScreen() { + const colorScheme = useColorScheme(); + const backgroundColor = useThemeColor({}, 'background'); + const primaryColor = useThemeColor({}, 'primary'); + const textColor = useThemeColor({}, 'text'); + + const handleGetStarted = () => { + router.push('/onboarding/personal-info'); + }; + + const handleSkip = async () => { + try { + await AsyncStorage.setItem('@onboarding_completed', 'true'); + router.replace('/(tabs)'); + } catch (error) { + console.error('保存引导状态失败:', error); + router.replace('/(tabs)'); + } + }; + + return ( + + + + {/* 跳过按钮 */} + + + 跳过 + + + + {/* 主要内容区域 */} + + {/* Logo 或插图区域 */} + + + 🧘‍♀️ + + + + {/* 标题和描述 */} + + + 欢迎来到数字普拉提 + + + 让我们一起开始您的健康之旅{'\n'} + 个性化的普拉提体验正等着您 + + + + {/* 特色功能点 */} + + {[ + { icon: '📊', title: '个性化训练', desc: '根据您的身体状况定制训练计划' }, + { icon: '🤖', title: 'AI 姿态分析', desc: '实时纠正您的动作姿态' }, + { icon: '📈', title: '进度追踪', desc: '记录您的每一次进步' }, + ].map((feature, index) => ( + + {feature.icon} + + + {feature.title} + + + {feature.desc} + + + + ))} + + + + {/* 底部按钮 */} + + + 开始体验 + + + + + 稍后再说 + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + paddingTop: StatusBar.currentHeight || 44, + }, + skipButton: { + position: 'absolute', + top: StatusBar.currentHeight ? StatusBar.currentHeight + 16 : 60, + right: 20, + zIndex: 10, + padding: 8, + }, + skipText: { + fontSize: 16, + fontWeight: '500', + }, + contentContainer: { + flex: 1, + paddingHorizontal: 24, + justifyContent: 'center', + }, + imageContainer: { + alignItems: 'center', + marginBottom: 40, + }, + logoPlaceholder: { + width: 120, + height: 120, + borderRadius: 60, + justifyContent: 'center', + alignItems: 'center', + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 4, + }, + shadowOpacity: 0.1, + shadowRadius: 8, + elevation: 8, + }, + logoText: { + fontSize: 48, + }, + textContainer: { + alignItems: 'center', + marginBottom: 48, + }, + title: { + textAlign: 'center', + marginBottom: 16, + fontWeight: '700', + }, + subtitle: { + fontSize: 16, + textAlign: 'center', + lineHeight: 24, + paddingHorizontal: 12, + }, + featuresContainer: { + marginBottom: 40, + }, + featureItem: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 24, + paddingHorizontal: 8, + }, + featureIcon: { + fontSize: 32, + marginRight: 16, + width: 40, + textAlign: 'center', + }, + featureTextContainer: { + flex: 1, + }, + featureTitle: { + fontSize: 18, + fontWeight: '600', + marginBottom: 4, + }, + featureDesc: { + fontSize: 14, + lineHeight: 20, + }, + buttonContainer: { + paddingHorizontal: 24, + paddingBottom: 48, + }, + getStartedButton: { + height: 56, + borderRadius: 16, + justifyContent: 'center', + alignItems: 'center', + marginBottom: 16, + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 4, + }, + getStartedButtonText: { + color: '#192126', + fontSize: 18, + fontWeight: '600', + }, + laterButton: { + height: 48, + justifyContent: 'center', + alignItems: 'center', + }, + laterButtonText: { + fontSize: 16, + fontWeight: '500', + }, +}); diff --git a/app/onboarding/personal-info.tsx b/app/onboarding/personal-info.tsx new file mode 100644 index 0000000..0b73f02 --- /dev/null +++ b/app/onboarding/personal-info.tsx @@ -0,0 +1,426 @@ +import { ThemedText } from '@/components/ThemedText'; +import { ThemedView } from '@/components/ThemedView'; +import { useColorScheme } from '@/hooks/useColorScheme'; +import { useThemeColor } from '@/hooks/useThemeColor'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { router } from 'expo-router'; +import React, { useState } from 'react'; +import { + Alert, + Dimensions, + ScrollView, + StatusBar, + StyleSheet, + Text, + TextInput, + TouchableOpacity, + View, +} from 'react-native'; + +const { width } = Dimensions.get('window'); + +interface PersonalInfo { + gender: 'male' | 'female' | ''; + age: string; + height: string; + weight: string; +} + +export default function PersonalInfoScreen() { + const colorScheme = useColorScheme(); + const backgroundColor = useThemeColor({}, 'background'); + const primaryColor = useThemeColor({}, 'primary'); + const textColor = useThemeColor({}, 'text'); + const iconColor = useThemeColor({}, 'icon'); + + const [personalInfo, setPersonalInfo] = useState({ + gender: '', + age: '', + height: '', + weight: '', + }); + + const [currentStep, setCurrentStep] = useState(0); + + const steps = [ + { + title: '请选择您的性别', + subtitle: '这将帮助我们为您制定更合适的训练计划', + type: 'gender' as const, + }, + { + title: '请输入您的年龄', + subtitle: '年龄信息有助于调整训练强度', + type: 'age' as const, + }, + { + title: '请输入您的身高', + subtitle: '身高信息用于计算身体比例', + type: 'height' as const, + }, + { + title: '请输入您的体重', + subtitle: '体重信息用于个性化训练方案', + type: 'weight' as const, + }, + ]; + + const handleGenderSelect = (gender: 'male' | 'female') => { + setPersonalInfo(prev => ({ ...prev, gender })); + }; + + const handleInputChange = (field: keyof PersonalInfo, value: string) => { + setPersonalInfo(prev => ({ ...prev, [field]: value })); + }; + + const handleNext = () => { + const currentStepType = steps[currentStep].type; + + // 验证当前步骤是否已填写 + if (currentStepType === 'gender' && !personalInfo.gender) { + Alert.alert('提示', '请选择您的性别'); + return; + } + if (currentStepType === 'age' && !personalInfo.age) { + Alert.alert('提示', '请输入您的年龄'); + return; + } + if (currentStepType === 'height' && !personalInfo.height) { + Alert.alert('提示', '请输入您的身高'); + return; + } + if (currentStepType === 'weight' && !personalInfo.weight) { + Alert.alert('提示', '请输入您的体重'); + return; + } + + if (currentStep < steps.length - 1) { + setCurrentStep(currentStep + 1); + } else { + handleComplete(); + } + }; + + const handlePrevious = () => { + if (currentStep > 0) { + setCurrentStep(currentStep - 1); + } + }; + + const handleSkip = async () => { + try { + await AsyncStorage.setItem('@onboarding_completed', 'true'); + router.replace('/(tabs)'); + } catch (error) { + console.error('保存引导状态失败:', error); + router.replace('/(tabs)'); + } + }; + + const handleComplete = async () => { + try { + // 保存用户信息和引导完成状态 + await AsyncStorage.multiSet([ + ['@onboarding_completed', 'true'], + ['@user_personal_info', JSON.stringify(personalInfo)], + ]); + console.log('用户信息:', personalInfo); + router.replace('/(tabs)'); + } catch (error) { + console.error('保存用户信息失败:', error); + router.replace('/(tabs)'); + } + }; + + const renderGenderSelection = () => ( + + handleGenderSelect('female')} + > + 👩 + 女性 + + + handleGenderSelect('male')} + > + 👨 + 男性 + + + ); + + const renderNumberInput = ( + field: 'age' | 'height' | 'weight', + placeholder: string, + unit: string + ) => ( + + + handleInputChange(field, value)} + placeholder={placeholder} + placeholderTextColor={iconColor} + keyboardType="numeric" + maxLength={field === 'age' ? 3 : 4} + /> + {unit} + + + ); + + const renderStepContent = () => { + const step = steps[currentStep]; + switch (step.type) { + case 'gender': + return renderGenderSelection(); + case 'age': + return renderNumberInput('age', '请输入年龄', '岁'); + case 'height': + return renderNumberInput('height', '请输入身高', 'cm'); + case 'weight': + return renderNumberInput('weight', '请输入体重', 'kg'); + default: + return null; + } + }; + + const isStepCompleted = () => { + const currentStepType = steps[currentStep].type; + switch (currentStepType) { + case 'gender': + return !!personalInfo.gender; + case 'age': + return !!personalInfo.age; + case 'height': + return !!personalInfo.height; + case 'weight': + return !!personalInfo.weight; + default: + return false; + } + }; + + return ( + + + + {/* 顶部导航 */} + + {currentStep > 0 && ( + + ‹ 返回 + + )} + + + 跳过 + + + + {/* 进度条 */} + + + + + + {currentStep + 1} / {steps.length} + + + + + {/* 标题区域 */} + + + {steps[currentStep].title} + + + {steps[currentStep].subtitle} + + + + {/* 内容区域 */} + {renderStepContent()} + + + {/* 底部按钮 */} + + + + {currentStep === steps.length - 1 ? '完成' : '下一步'} + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + paddingTop: StatusBar.currentHeight || 44, + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 20, + paddingVertical: 16, + }, + backButton: { + padding: 8, + }, + backText: { + fontSize: 16, + fontWeight: '500', + }, + skipButton: { + padding: 8, + }, + skipText: { + fontSize: 16, + fontWeight: '500', + }, + progressContainer: { + paddingHorizontal: 20, + marginBottom: 32, + }, + progressBackground: { + height: 4, + borderRadius: 2, + marginBottom: 8, + }, + progressBar: { + height: '100%', + borderRadius: 2, + }, + progressText: { + fontSize: 12, + textAlign: 'right', + }, + content: { + flex: 1, + }, + contentContainer: { + paddingHorizontal: 24, + paddingBottom: 24, + }, + titleContainer: { + marginBottom: 48, + }, + title: { + textAlign: 'center', + marginBottom: 16, + fontWeight: '700', + }, + subtitle: { + fontSize: 16, + textAlign: 'center', + lineHeight: 24, + }, + optionsContainer: { + flexDirection: 'row', + justifyContent: 'space-around', + paddingHorizontal: 20, + }, + genderOption: { + width: width * 0.35, + height: 120, + borderRadius: 16, + borderWidth: 1, + justifyContent: 'center', + alignItems: 'center', + padding: 16, + }, + genderIcon: { + fontSize: 48, + marginBottom: 8, + }, + genderText: { + fontSize: 16, + fontWeight: '600', + }, + inputContainer: { + alignItems: 'center', + }, + inputWrapper: { + flexDirection: 'row', + alignItems: 'center', + borderWidth: 1, + borderRadius: 12, + paddingHorizontal: 16, + width: width * 0.6, + height: 56, + }, + numberInput: { + flex: 1, + fontSize: 18, + fontWeight: '600', + textAlign: 'center', + }, + unitText: { + fontSize: 16, + fontWeight: '500', + marginLeft: 8, + }, + buttonContainer: { + paddingHorizontal: 24, + paddingBottom: 48, + }, + nextButton: { + height: 56, + borderRadius: 16, + justifyContent: 'center', + alignItems: 'center', + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 4, + }, + nextButtonText: { + fontSize: 18, + fontWeight: '600', + }, +}); diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 47f7bd4..deabe58 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1857,6 +1857,30 @@ PODS: - React-utils (= 0.79.5) - RNAppleHealthKit (1.7.0): - React + - RNCAsyncStorage (2.2.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - RNGestureHandler (2.24.0): - DoubleConversion - glog @@ -2161,6 +2185,7 @@ DEPENDENCIES: - ReactCodegen (from `build/generated/ios`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - RNAppleHealthKit (from `../node_modules/react-native-health`) + - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNReanimated (from `../node_modules/react-native-reanimated`) - RNScreens (from `../node_modules/react-native-screens`) @@ -2355,6 +2380,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" RNAppleHealthKit: :path: "../node_modules/react-native-health" + RNCAsyncStorage: + :path: "../node_modules/@react-native-async-storage/async-storage" RNGestureHandler: :path: "../node_modules/react-native-gesture-handler" RNReanimated: @@ -2456,6 +2483,7 @@ SPEC CHECKSUMS: ReactCodegen: 6cb6e0d0b52471abc883541c76589d1c367c64c7 ReactCommon: 1ab5451fc5da87c4cc4c3046e19a8054624ca763 RNAppleHealthKit: 86ef7ab70f762b802f5c5289372de360cca701f9 + RNCAsyncStorage: a1c8cc8a99c32de1244a9cf707bf9d83d0de0f71 RNGestureHandler: 7d0931a61d7ba0259f32db0ba7d0963c3ed15d2b RNReanimated: 2313402fe27fecb7237619e9c6fcee3177f08a65 RNScreens: 482e9707f9826230810c92e765751af53826d509 diff --git a/ios/digitalpilates.xcodeproj/project.pbxproj b/ios/digitalpilates.xcodeproj/project.pbxproj index ce5e7da..f5b494d 100644 --- a/ios/digitalpilates.xcodeproj/project.pbxproj +++ b/ios/digitalpilates.xcodeproj/project.pbxproj @@ -269,6 +269,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", @@ -282,6 +283,7 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", @@ -450,7 +452,10 @@ LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -505,7 +510,10 @@ ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = NO; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; diff --git a/package-lock.json b/package-lock.json index 9ec735b..f703d0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "dependencies": { "@expo/vector-icons": "^14.1.0", + "@react-native-async-storage/async-storage": "^2.2.0", "@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/elements": "^2.3.8", "@react-navigation/native": "^7.1.6", @@ -2745,6 +2746,18 @@ } } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "2.2.0", + "resolved": "https://mirrors.tencent.com/npm/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", + "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.79.5", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.5.tgz", @@ -7667,6 +7680,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://mirrors.tencent.com/npm/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -8653,6 +8675,18 @@ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "license": "MIT" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://mirrors.tencent.com/npm/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", diff --git a/package.json b/package.json index a02ce2b..dd1d860 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@expo/vector-icons": "^14.1.0", + "@react-native-async-storage/async-storage": "^2.2.0", "@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/elements": "^2.3.8", "@react-navigation/native": "^7.1.6",