feat: 添加引导流程和个人信息收集功能
- 在应用中集成引导流程,用户首次启动时显示欢迎页面和个人信息收集页面 - 使用 AsyncStorage 存储用户的引导状态和个人信息 - 在个人页面中添加重置引导流程的功能 - 更新依赖项,添加 @react-native-async-storage/async-storage 库以支持数据存储 - 修改布局以支持新页面的导航和显示
This commit is contained in:
@@ -2,9 +2,11 @@ import { Colors } from '@/constants/Colors';
|
|||||||
import { getTabBarBottomPadding } from '@/constants/TabBar';
|
import { getTabBarBottomPadding } from '@/constants/TabBar';
|
||||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
|
Alert,
|
||||||
SafeAreaView,
|
SafeAreaView,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
StatusBar,
|
StatusBar,
|
||||||
@@ -27,6 +29,32 @@ export default function PersonalScreen() {
|
|||||||
const colorScheme = useColorScheme();
|
const colorScheme = useColorScheme();
|
||||||
const colors = Colors[colorScheme ?? 'light'];
|
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 = () => (
|
const UserInfoSection = () => (
|
||||||
<View style={styles.userInfoCard}>
|
<View style={styles.userInfoCard}>
|
||||||
@@ -79,10 +107,14 @@ export default function PersonalScreen() {
|
|||||||
<View style={styles.menuSection}>
|
<View style={styles.menuSection}>
|
||||||
<Text style={styles.sectionTitle}>{title}</Text>
|
<Text style={styles.sectionTitle}>{title}</Text>
|
||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
<TouchableOpacity key={index} style={styles.menuItem}>
|
<TouchableOpacity
|
||||||
|
key={index}
|
||||||
|
style={styles.menuItem}
|
||||||
|
onPress={item.onPress}
|
||||||
|
>
|
||||||
<View style={styles.menuItemLeft}>
|
<View style={styles.menuItemLeft}>
|
||||||
<View style={[styles.menuIcon]}>
|
<View style={[styles.menuIcon]}>
|
||||||
<Ionicons name={item.icon} size={20} color={colors.primary} />
|
<Ionicons name={item.icon} size={20} color={item.iconColor || colors.primary} />
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.menuItemText}>{item.title}</Text>
|
<Text style={styles.menuItemText}>{item.title}</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -197,6 +229,16 @@ export default function PersonalScreen() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const developerItems = [
|
||||||
|
{
|
||||||
|
icon: 'refresh-outline',
|
||||||
|
iconBg: '#FFE8E8',
|
||||||
|
iconColor: '#FF4444',
|
||||||
|
title: '重置引导流程',
|
||||||
|
onPress: handleResetOnboarding,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<StatusBar barStyle="dark-content" backgroundColor="transparent" translucent />
|
<StatusBar barStyle="dark-content" backgroundColor="transparent" translucent />
|
||||||
@@ -211,6 +253,7 @@ export default function PersonalScreen() {
|
|||||||
<MenuSection title="Account" items={accountItems} />
|
<MenuSection title="Account" items={accountItems} />
|
||||||
<MenuSection title="Notification" items={notificationItems} />
|
<MenuSection title="Notification" items={notificationItems} />
|
||||||
<MenuSection title="Other" items={otherItems} />
|
<MenuSection title="Other" items={otherItems} />
|
||||||
|
<MenuSection title="Developer" items={developerItems} />
|
||||||
|
|
||||||
{/* 底部浮动按钮 */}
|
{/* 底部浮动按钮 */}
|
||||||
<View style={[styles.floatingButtonContainer, { bottom: Math.max(30, tabBarHeight / 2) + (insets?.bottom ?? 0) }]}>
|
<View style={[styles.floatingButtonContainer, { bottom: Math.max(30, tabBarHeight / 2) + (insets?.bottom ?? 0) }]}>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export default function RootLayout() {
|
|||||||
return (
|
return (
|
||||||
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
||||||
<Stack>
|
<Stack>
|
||||||
|
<Stack.Screen name="onboarding" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="ai-posture-assessment" options={{ headerShown: false }} />
|
<Stack.Screen name="ai-posture-assessment" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="+not-found" />
|
<Stack.Screen name="+not-found" />
|
||||||
|
|||||||
71
app/index.tsx
Normal file
71
app/index.tsx
Normal file
@@ -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 (
|
||||||
|
<ThemedView style={{
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor
|
||||||
|
}}>
|
||||||
|
<View style={{
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
borderRadius: 40,
|
||||||
|
backgroundColor: primaryColor,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 20,
|
||||||
|
}}>
|
||||||
|
<Text style={{
|
||||||
|
fontSize: 32,
|
||||||
|
}}>
|
||||||
|
🧘♀️
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<ActivityIndicator size="large" color={primaryColor} />
|
||||||
|
</ThemedView>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
app/onboarding/_layout.tsx
Normal file
10
app/onboarding/_layout.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Stack } from 'expo-router';
|
||||||
|
|
||||||
|
export default function OnboardingLayout() {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Stack.Screen name="index" options={{ headerShown: false }} />
|
||||||
|
<Stack.Screen name="personal-info" options={{ headerShown: false }} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
233
app/onboarding/index.tsx
Normal file
233
app/onboarding/index.tsx
Normal file
@@ -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 (
|
||||||
|
<ThemedView style={[styles.container, { backgroundColor }]}>
|
||||||
|
<StatusBar
|
||||||
|
barStyle={colorScheme === 'dark' ? 'light-content' : 'dark-content'}
|
||||||
|
backgroundColor={backgroundColor}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 跳过按钮 */}
|
||||||
|
<TouchableOpacity style={styles.skipButton} onPress={handleSkip}>
|
||||||
|
<ThemedText style={[styles.skipText, { color: textColor }]}>
|
||||||
|
跳过
|
||||||
|
</ThemedText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* 主要内容区域 */}
|
||||||
|
<View style={styles.contentContainer}>
|
||||||
|
{/* Logo 或插图区域 */}
|
||||||
|
<View style={styles.imageContainer}>
|
||||||
|
<View style={[styles.logoPlaceholder, { backgroundColor: primaryColor }]}>
|
||||||
|
<Text style={styles.logoText}>🧘♀️</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 标题和描述 */}
|
||||||
|
<View style={styles.textContainer}>
|
||||||
|
<ThemedText type="title" style={styles.title}>
|
||||||
|
欢迎来到数字普拉提
|
||||||
|
</ThemedText>
|
||||||
|
<ThemedText style={[styles.subtitle, { color: textColor + '90' }]}>
|
||||||
|
让我们一起开始您的健康之旅{'\n'}
|
||||||
|
个性化的普拉提体验正等着您
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 特色功能点 */}
|
||||||
|
<View style={styles.featuresContainer}>
|
||||||
|
{[
|
||||||
|
{ icon: '📊', title: '个性化训练', desc: '根据您的身体状况定制训练计划' },
|
||||||
|
{ icon: '🤖', title: 'AI 姿态分析', desc: '实时纠正您的动作姿态' },
|
||||||
|
{ icon: '📈', title: '进度追踪', desc: '记录您的每一次进步' },
|
||||||
|
].map((feature, index) => (
|
||||||
|
<View key={index} style={styles.featureItem}>
|
||||||
|
<Text style={styles.featureIcon}>{feature.icon}</Text>
|
||||||
|
<View style={styles.featureTextContainer}>
|
||||||
|
<ThemedText style={[styles.featureTitle, { color: textColor }]}>
|
||||||
|
{feature.title}
|
||||||
|
</ThemedText>
|
||||||
|
<ThemedText style={[styles.featureDesc, { color: textColor + '70' }]}>
|
||||||
|
{feature.desc}
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 底部按钮 */}
|
||||||
|
<View style={styles.buttonContainer}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.getStartedButton, { backgroundColor: primaryColor }]}
|
||||||
|
onPress={handleGetStarted}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
>
|
||||||
|
<Text style={styles.getStartedButtonText}>开始体验</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.laterButton} onPress={handleSkip}>
|
||||||
|
<ThemedText style={[styles.laterButtonText, { color: textColor + '70' }]}>
|
||||||
|
稍后再说
|
||||||
|
</ThemedText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</ThemedView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
});
|
||||||
426
app/onboarding/personal-info.tsx
Normal file
426
app/onboarding/personal-info.tsx
Normal file
@@ -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<PersonalInfo>({
|
||||||
|
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 = () => (
|
||||||
|
<View style={styles.optionsContainer}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.genderOption,
|
||||||
|
{ borderColor: primaryColor },
|
||||||
|
personalInfo.gender === 'female' && { backgroundColor: primaryColor + '20', borderWidth: 2 }
|
||||||
|
]}
|
||||||
|
onPress={() => handleGenderSelect('female')}
|
||||||
|
>
|
||||||
|
<Text style={styles.genderIcon}>👩</Text>
|
||||||
|
<ThemedText style={[styles.genderText, { color: textColor }]}>女性</ThemedText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.genderOption,
|
||||||
|
{ borderColor: primaryColor },
|
||||||
|
personalInfo.gender === 'male' && { backgroundColor: primaryColor + '20', borderWidth: 2 }
|
||||||
|
]}
|
||||||
|
onPress={() => handleGenderSelect('male')}
|
||||||
|
>
|
||||||
|
<Text style={styles.genderIcon}>👨</Text>
|
||||||
|
<ThemedText style={[styles.genderText, { color: textColor }]}>男性</ThemedText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderNumberInput = (
|
||||||
|
field: 'age' | 'height' | 'weight',
|
||||||
|
placeholder: string,
|
||||||
|
unit: string
|
||||||
|
) => (
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<View style={[styles.inputWrapper, { borderColor: iconColor + '30' }]}>
|
||||||
|
<TextInput
|
||||||
|
style={[styles.numberInput, { color: textColor }]}
|
||||||
|
value={personalInfo[field]}
|
||||||
|
onChangeText={(value) => handleInputChange(field, value)}
|
||||||
|
placeholder={placeholder}
|
||||||
|
placeholderTextColor={iconColor}
|
||||||
|
keyboardType="numeric"
|
||||||
|
maxLength={field === 'age' ? 3 : 4}
|
||||||
|
/>
|
||||||
|
<ThemedText style={[styles.unitText, { color: iconColor }]}>{unit}</ThemedText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<ThemedView style={[styles.container, { backgroundColor }]}>
|
||||||
|
<StatusBar
|
||||||
|
barStyle={colorScheme === 'dark' ? 'light-content' : 'dark-content'}
|
||||||
|
backgroundColor={backgroundColor}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 顶部导航 */}
|
||||||
|
<View style={styles.header}>
|
||||||
|
{currentStep > 0 && (
|
||||||
|
<TouchableOpacity style={styles.backButton} onPress={handlePrevious}>
|
||||||
|
<ThemedText style={[styles.backText, { color: textColor }]}>‹ 返回</ThemedText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.skipButton} onPress={handleSkip}>
|
||||||
|
<ThemedText style={[styles.skipText, { color: iconColor }]}>跳过</ThemedText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 进度条 */}
|
||||||
|
<View style={styles.progressContainer}>
|
||||||
|
<View style={[styles.progressBackground, { backgroundColor: iconColor + '20' }]}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.progressBar,
|
||||||
|
{
|
||||||
|
backgroundColor: primaryColor,
|
||||||
|
width: `${((currentStep + 1) / steps.length) * 100}%`
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<ThemedText style={[styles.progressText, { color: iconColor }]}>
|
||||||
|
{currentStep + 1} / {steps.length}
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
style={styles.content}
|
||||||
|
contentContainerStyle={styles.contentContainer}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
>
|
||||||
|
{/* 标题区域 */}
|
||||||
|
<View style={styles.titleContainer}>
|
||||||
|
<ThemedText type="title" style={styles.title}>
|
||||||
|
{steps[currentStep].title}
|
||||||
|
</ThemedText>
|
||||||
|
<ThemedText style={[styles.subtitle, { color: textColor + '80' }]}>
|
||||||
|
{steps[currentStep].subtitle}
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 内容区域 */}
|
||||||
|
{renderStepContent()}
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
{/* 底部按钮 */}
|
||||||
|
<View style={styles.buttonContainer}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.nextButton,
|
||||||
|
{ backgroundColor: isStepCompleted() ? primaryColor : iconColor + '30' }
|
||||||
|
]}
|
||||||
|
onPress={handleNext}
|
||||||
|
disabled={!isStepCompleted()}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
>
|
||||||
|
<Text style={[
|
||||||
|
styles.nextButtonText,
|
||||||
|
{ color: isStepCompleted() ? '#192126' : iconColor }
|
||||||
|
]}>
|
||||||
|
{currentStep === steps.length - 1 ? '完成' : '下一步'}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</ThemedView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1857,6 +1857,30 @@ PODS:
|
|||||||
- React-utils (= 0.79.5)
|
- React-utils (= 0.79.5)
|
||||||
- RNAppleHealthKit (1.7.0):
|
- RNAppleHealthKit (1.7.0):
|
||||||
- React
|
- 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):
|
- RNGestureHandler (2.24.0):
|
||||||
- DoubleConversion
|
- DoubleConversion
|
||||||
- glog
|
- glog
|
||||||
@@ -2161,6 +2185,7 @@ DEPENDENCIES:
|
|||||||
- ReactCodegen (from `build/generated/ios`)
|
- ReactCodegen (from `build/generated/ios`)
|
||||||
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
|
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
|
||||||
- RNAppleHealthKit (from `../node_modules/react-native-health`)
|
- 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`)
|
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
||||||
- RNReanimated (from `../node_modules/react-native-reanimated`)
|
- RNReanimated (from `../node_modules/react-native-reanimated`)
|
||||||
- RNScreens (from `../node_modules/react-native-screens`)
|
- RNScreens (from `../node_modules/react-native-screens`)
|
||||||
@@ -2355,6 +2380,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../node_modules/react-native/ReactCommon"
|
:path: "../node_modules/react-native/ReactCommon"
|
||||||
RNAppleHealthKit:
|
RNAppleHealthKit:
|
||||||
:path: "../node_modules/react-native-health"
|
:path: "../node_modules/react-native-health"
|
||||||
|
RNCAsyncStorage:
|
||||||
|
:path: "../node_modules/@react-native-async-storage/async-storage"
|
||||||
RNGestureHandler:
|
RNGestureHandler:
|
||||||
:path: "../node_modules/react-native-gesture-handler"
|
:path: "../node_modules/react-native-gesture-handler"
|
||||||
RNReanimated:
|
RNReanimated:
|
||||||
@@ -2456,6 +2483,7 @@ SPEC CHECKSUMS:
|
|||||||
ReactCodegen: 6cb6e0d0b52471abc883541c76589d1c367c64c7
|
ReactCodegen: 6cb6e0d0b52471abc883541c76589d1c367c64c7
|
||||||
ReactCommon: 1ab5451fc5da87c4cc4c3046e19a8054624ca763
|
ReactCommon: 1ab5451fc5da87c4cc4c3046e19a8054624ca763
|
||||||
RNAppleHealthKit: 86ef7ab70f762b802f5c5289372de360cca701f9
|
RNAppleHealthKit: 86ef7ab70f762b802f5c5289372de360cca701f9
|
||||||
|
RNCAsyncStorage: a1c8cc8a99c32de1244a9cf707bf9d83d0de0f71
|
||||||
RNGestureHandler: 7d0931a61d7ba0259f32db0ba7d0963c3ed15d2b
|
RNGestureHandler: 7d0931a61d7ba0259f32db0ba7d0963c3ed15d2b
|
||||||
RNReanimated: 2313402fe27fecb7237619e9c6fcee3177f08a65
|
RNReanimated: 2313402fe27fecb7237619e9c6fcee3177f08a65
|
||||||
RNScreens: 482e9707f9826230810c92e765751af53826d509
|
RNScreens: 482e9707f9826230810c92e765751af53826d509
|
||||||
|
|||||||
@@ -269,6 +269,7 @@
|
|||||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle",
|
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle",
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle",
|
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle",
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_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-Core/React-Core_privacy.bundle",
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle",
|
"${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle",
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.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}/ExpoFileSystem_privacy.bundle",
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_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}/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-Core_privacy.bundle",
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle",
|
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle",
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle",
|
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle",
|
||||||
@@ -450,7 +452,10 @@
|
|||||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
|
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
OTHER_LDFLAGS = "$(inherited) ";
|
OTHER_LDFLAGS = (
|
||||||
|
"$(inherited)",
|
||||||
|
" ",
|
||||||
|
);
|
||||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
|
||||||
@@ -505,7 +510,10 @@
|
|||||||
);
|
);
|
||||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
|
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
OTHER_LDFLAGS = "$(inherited) ";
|
OTHER_LDFLAGS = (
|
||||||
|
"$(inherited)",
|
||||||
|
" ",
|
||||||
|
);
|
||||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
USE_HERMES = true;
|
USE_HERMES = true;
|
||||||
|
|||||||
34
package-lock.json
generated
34
package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^14.1.0",
|
"@expo/vector-icons": "^14.1.0",
|
||||||
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||||
"@react-navigation/bottom-tabs": "^7.3.10",
|
"@react-navigation/bottom-tabs": "^7.3.10",
|
||||||
"@react-navigation/elements": "^2.3.8",
|
"@react-navigation/elements": "^2.3.8",
|
||||||
"@react-navigation/native": "^7.1.6",
|
"@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": {
|
"node_modules/@react-native/assets-registry": {
|
||||||
"version": "0.79.5",
|
"version": "0.79.5",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.5.tgz",
|
"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"
|
"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": {
|
"node_modules/is-regex": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
"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==",
|
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/merge-stream": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^14.1.0",
|
"@expo/vector-icons": "^14.1.0",
|
||||||
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||||
"@react-navigation/bottom-tabs": "^7.3.10",
|
"@react-navigation/bottom-tabs": "^7.3.10",
|
||||||
"@react-navigation/elements": "^2.3.8",
|
"@react-navigation/elements": "^2.3.8",
|
||||||
"@react-navigation/native": "^7.1.6",
|
"@react-navigation/native": "^7.1.6",
|
||||||
|
|||||||
Reference in New Issue
Block a user