feat: 添加用户登录和法律协议页面

- 新增登录页面,支持 Apple 登录和游客登录功能
- 添加用户协议和隐私政策页面,用户需同意后才能登录
- 更新首页逻辑,首次进入时自动跳转到登录页面
- 修改个人信息页面,移除单位选择功能,统一使用 kg 和 cm
- 更新依赖,添加 expo-apple-authentication 库以支持 Apple 登录
- 更新布局以适应新功能的展示和交互
This commit is contained in:
richarjiang
2025-08-12 19:21:07 +08:00
parent 8ffebfb297
commit c3d4630801
13 changed files with 326 additions and 103 deletions

View File

@@ -187,7 +187,13 @@ export default function ExploreScreen() {
) : ( ) : (
<Text style={styles.stepsValue}>/2000</Text> <Text style={styles.stepsValue}>/2000</Text>
)} )}
<ProgressBar progress={Math.min(1, Math.max(0, (stepCount ?? 0) / 2000))} height={12} trackColor="#FFEBCB" fillColor="#FFC365" /> <ProgressBar
progress={Math.min(1, Math.max(0, (stepCount ?? 0) / 2000))}
height={18}
trackColor="#FFEBCB"
fillColor="#FFC365"
showLabel={false}
/>
</View> </View>
</View> </View>
</View> </View>

View File

@@ -5,7 +5,7 @@ import { ThemedView } from '@/components/ThemedView';
import { WorkoutCard } from '@/components/WorkoutCard'; import { WorkoutCard } from '@/components/WorkoutCard';
import { getChineseGreeting } from '@/utils/date'; import { getChineseGreeting } from '@/utils/date';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
import React from 'react'; import React, { useEffect, useRef } from 'react';
import { SafeAreaView, ScrollView, StyleSheet, View } from 'react-native'; import { SafeAreaView, ScrollView, StyleSheet, View } from 'react-native';
const workoutData = [ const workoutData = [
@@ -24,6 +24,15 @@ const workoutData = [
export default function HomeScreen() { export default function HomeScreen() {
const router = useRouter(); const router = useRouter();
const hasOpenedLoginRef = useRef(false);
useEffect(() => {
// 仅在本次会话首次进入首页时打开登录页,可返回关闭
if (!hasOpenedLoginRef.current) {
hasOpenedLoginRef.current = true;
router.push('/auth/login');
}
}, [router]);
return ( return (
<SafeAreaView style={styles.safeArea}> <SafeAreaView style={styles.safeArea}>
<ThemedView style={styles.container}> <ThemedView style={styles.container}>

View File

@@ -31,8 +31,6 @@ export default function PersonalScreen() {
const colorScheme = useColorScheme(); const colorScheme = useColorScheme();
const colors = Colors[colorScheme ?? 'light']; const colors = Colors[colorScheme ?? 'light'];
type WeightUnit = 'kg' | 'lb';
type HeightUnit = 'cm' | 'ft';
type UserProfile = { type UserProfile = {
fullName?: string; fullName?: string;
email?: string; email?: string;
@@ -40,7 +38,7 @@ export default function PersonalScreen() {
age?: string; age?: string;
weightKg?: number; weightKg?: number;
heightCm?: number; heightCm?: number;
unitPref?: { weight: WeightUnit; height: HeightUnit }; avatarUri?: string | null;
}; };
const [profile, setProfile] = useState<UserProfile>({}); const [profile, setProfile] = useState<UserProfile>({});
@@ -78,16 +76,12 @@ export default function PersonalScreen() {
const formatHeight = () => { const formatHeight = () => {
if (profile.heightCm == null) return '--'; if (profile.heightCm == null) return '--';
const unit = profile.unitPref?.height ?? 'cm'; return `${Math.round(profile.heightCm)}cm`;
if (unit === 'cm') return `${Math.round(profile.heightCm)}cm`;
return `${round(profile.heightCm / 30.48, 1)}ft`;
}; };
const formatWeight = () => { const formatWeight = () => {
if (profile.weightKg == null) return '--'; if (profile.weightKg == null) return '--';
const unit = profile.unitPref?.weight ?? 'kg'; return `${round(profile.weightKg, 1)}kg`;
if (unit === 'kg') return `${round(profile.weightKg, 1)}kg`;
return `${round(profile.weightKg * 2.2046226218, 1)}lb`;
}; };
const formatAge = () => (profile.age ? `${profile.age}` : '--'); const formatAge = () => (profile.age ? `${profile.age}` : '--');

View File

@@ -24,6 +24,9 @@ export default function RootLayout() {
<Stack.Screen name="(tabs)" /> <Stack.Screen name="(tabs)" />
<Stack.Screen name="profile/edit" /> <Stack.Screen name="profile/edit" />
<Stack.Screen name="ai-posture-assessment" /> <Stack.Screen name="ai-posture-assessment" />
<Stack.Screen name="auth/login" options={{ headerShown: false }} />
<Stack.Screen name="legal/user-agreement" options={{ headerShown: true, title: '用户协议' }} />
<Stack.Screen name="legal/privacy-policy" options={{ headerShown: true, title: '隐私政策' }} />
<Stack.Screen name="+not-found" /> <Stack.Screen name="+not-found" />
</Stack> </Stack>
<StatusBar style="auto" /> <StatusBar style="auto" />

225
app/auth/login.tsx Normal file
View File

@@ -0,0 +1,225 @@
import { Ionicons } from '@expo/vector-icons';
import * as AppleAuthentication from 'expo-apple-authentication';
import { useRouter } from 'expo-router';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Alert, Pressable, SafeAreaView, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { ThemedText } from '@/components/ThemedText';
import { ThemedView } from '@/components/ThemedView';
import { Colors } from '@/constants/Colors';
import { useColorScheme } from '@/hooks/useColorScheme';
export default function LoginScreen() {
const router = useRouter();
const scheme = (useColorScheme() ?? 'light') as 'light' | 'dark';
const color = Colors[scheme];
const [hasAgreed, setHasAgreed] = useState<boolean>(false);
const [appleAvailable, setAppleAvailable] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => {
AppleAuthentication.isAvailableAsync().then(setAppleAvailable).catch(() => setAppleAvailable(false));
}, []);
const guardAgreement = useCallback((action: () => void) => {
if (!hasAgreed) {
Alert.alert('请先阅读并同意', '勾选“我已阅读并同意用户协议与隐私政策”后才可继续登录');
return;
}
action();
}, [hasAgreed]);
const onAppleLogin = useCallback(async () => {
if (!appleAvailable) return;
try {
setLoading(true);
const credential = await AppleAuthentication.signInAsync({
requestedScopes: [
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
AppleAuthentication.AppleAuthenticationScope.EMAIL,
],
});
// TODO: 将 credential 发送到后端换取应用会话。这里先直接返回上一页。
router.back();
} catch (err: any) {
if (err?.code === 'ERR_CANCELED') return;
Alert.alert('登录失败', '请稍后再试');
} finally {
setLoading(false);
}
}, [appleAvailable, router]);
const onGuestLogin = useCallback(() => {
// TODO: 标记为游客身份,可在此写入本地状态/上报统计
router.back();
}, [router]);
const disabledStyle = useMemo(() => ({ opacity: hasAgreed ? 1 : 0.5 }), [hasAgreed]);
return (
<SafeAreaView style={[styles.safeArea, { backgroundColor: color.background }]}>
<ThemedView style={styles.container}>
{/* 自定义头部,与其它页面风格一致 */}
<View style={styles.header}>
<TouchableOpacity accessibilityRole="button" onPress={() => router.back()} style={styles.backButton}>
<Ionicons name="chevron-back" size={24} color={scheme === 'dark' ? '#ECEDEE' : '#192126'} />
</TouchableOpacity>
<Text style={[styles.headerTitle, { color: color.text }]}></Text>
<View style={{ width: 32 }} />
</View>
<ScrollView contentContainerStyle={styles.content} showsVerticalScrollIndicator={false}>
<View style={styles.headerWrap}>
<ThemedText style={[styles.title, { color: color.text }]}>Digital Pilates</ThemedText>
<ThemedText style={[styles.subtitle, { color: color.textMuted }]}></ThemedText>
</View>
{/* Apple 登录 */}
{appleAvailable && (
<Pressable
accessibilityRole="button"
onPress={() => guardAgreement(onAppleLogin)}
disabled={!hasAgreed || loading}
style={({ pressed }) => [
styles.appleButton,
{ backgroundColor: '#000000' },
disabledStyle,
pressed && { transform: [{ scale: 0.98 }] },
]}
>
<Ionicons name="logo-apple" size={22} color="#FFFFFF" style={{ marginRight: 10 }} />
<Text style={styles.appleText}>使 Apple </Text>
</Pressable>
)}
{/* 游客登录(弱化样式) */}
<Pressable
accessibilityRole="button"
onPress={() => guardAgreement(onGuestLogin)}
disabled={!hasAgreed || loading}
style={({ pressed }) => [
styles.guestButton,
{ borderColor: color.border, backgroundColor: color.surface },
disabledStyle,
pressed && { transform: [{ scale: 0.98 }] },
]}
>
<Ionicons name="person-circle-outline" size={22} color={Colors.light.neutral200} style={{ marginRight: 8 }} />
<Text style={[styles.guestText, { color: Colors.light.neutral200 }]}></Text>
</Pressable>
{/* 协议勾选 */}
<View style={styles.agreementRow}>
<Pressable onPress={() => setHasAgreed((v) => !v)} style={styles.checkboxWrap} accessibilityRole="checkbox" accessibilityState={{ checked: hasAgreed }}>
<View
style={[styles.checkbox, {
backgroundColor: hasAgreed ? color.primary : 'transparent',
borderColor: hasAgreed ? color.primary : color.border,
}]}
>
{hasAgreed && <Ionicons name="checkmark" size={14} color={color.onPrimary} />}
</View>
</Pressable>
<Text style={[styles.agreementText, { color: color.textMuted }]}></Text>
<Pressable onPress={() => router.push('/legal/privacy-policy')}>
<Text style={[styles.link, { color: color.primary }]}></Text>
</Pressable>
<Text style={[styles.agreementText, { color: color.textMuted }]}></Text>
<Pressable onPress={() => router.push('/legal/user-agreement')}>
<Text style={[styles.link, { color: color.primary }]}></Text>
</Pressable>
</View>
{/* 占位底部间距 */}
<View style={{ height: 40 }} />
</ScrollView>
</ThemedView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
safeArea: { flex: 1 },
container: { flex: 1 },
content: {
flexGrow: 1,
paddingHorizontal: 24,
justifyContent: 'center',
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingTop: 4,
paddingBottom: 8,
},
backButton: { width: 32, height: 32, alignItems: 'center', justifyContent: 'center' },
headerTitle: { fontSize: 18, fontWeight: '700' },
headerWrap: {
marginBottom: 36,
},
title: {
fontSize: 32,
fontWeight: '800',
letterSpacing: 0.5,
},
subtitle: {
marginTop: 8,
fontSize: 14,
fontWeight: '500',
},
appleButton: {
height: 56,
borderRadius: 28,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
marginBottom: 16,
shadowColor: '#000',
shadowOffset: { width: 0, height: 8 },
shadowOpacity: 0.15,
shadowRadius: 12,
elevation: 2,
},
appleText: {
fontSize: 16,
color: '#FFFFFF',
fontWeight: '600',
},
guestButton: {
height: 52,
borderRadius: 26,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
borderWidth: 1,
marginTop: 6,
},
guestText: {
fontSize: 15,
fontWeight: '500',
},
agreementRow: {
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap',
marginTop: 24,
},
checkboxWrap: { marginRight: 8 },
checkbox: {
width: 18,
height: 18,
borderRadius: 5,
borderWidth: 1,
alignItems: 'center',
justifyContent: 'center',
},
agreementText: { fontSize: 12 },
link: { fontSize: 12, fontWeight: '600' },
footerHint: { marginTop: 24 },
hintText: { fontSize: 12 },
});

View File

@@ -0,0 +1,15 @@
import React from 'react';
import { ScrollView, Text } from 'react-native';
export default function PrivacyPolicy() {
return (
<ScrollView style={{ flex: 1, padding: 16 }}>
<Text style={{ fontSize: 20, fontWeight: '700', marginBottom: 12 }}></Text>
<Text style={{ lineHeight: 22, color: '#4A4A4A' }}>
</Text>
</ScrollView>
);
}

View File

@@ -0,0 +1,15 @@
import React from 'react';
import { ScrollView, Text } from 'react-native';
export default function UserAgreement() {
return (
<ScrollView style={{ flex: 1, padding: 16 }}>
<Text style={{ fontSize: 20, fontWeight: '700', marginBottom: 12 }}></Text>
<Text style={{ lineHeight: 22, color: '#4A4A4A' }}>
</Text>
</ScrollView>
);
}

View File

@@ -4,7 +4,7 @@ import { Ionicons } from '@expo/vector-icons';
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import * as ImagePicker from 'expo-image-picker'; import * as ImagePicker from 'expo-image-picker';
import { router } from 'expo-router'; import { router } from 'expo-router';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { import {
Alert, Alert,
Image, Image,
@@ -32,10 +32,6 @@ interface UserProfile {
weightKg?: number; // kg weightKg?: number; // kg
heightCm?: number; // cm heightCm?: number; // cm
avatarUri?: string | null; avatarUri?: string | null;
unitPref?: {
weight: WeightUnit;
height: HeightUnit;
};
} }
const STORAGE_KEY = '@user_profile'; const STORAGE_KEY = '@user_profile';
@@ -52,26 +48,12 @@ export default function EditProfileScreen() {
weightKg: undefined, weightKg: undefined,
heightCm: undefined, heightCm: undefined,
avatarUri: null, avatarUri: null,
unitPref: { weight: 'kg', height: 'cm' },
}); });
const [weightInput, setWeightInput] = useState<string>(''); const [weightInput, setWeightInput] = useState<string>('');
const [heightInput, setHeightInput] = useState<string>(''); const [heightInput, setHeightInput] = useState<string>('');
// 将存储的公制值转换为当前选择单位显示 // 输入框字符串
const displayedWeight = useMemo(() => {
if (profile.weightKg == null || isNaN(profile.weightKg)) return '';
return profile.unitPref?.weight === 'kg'
? String(round(profile.weightKg, 1))
: String(round(kgToLb(profile.weightKg), 1));
}, [profile.weightKg, profile.unitPref?.weight]);
const displayedHeight = useMemo(() => {
if (profile.heightCm == null || isNaN(profile.heightCm)) return '';
return profile.unitPref?.height === 'cm'
? String(Math.round(profile.heightCm))
: String(round(cmToFt(profile.heightCm), 1));
}, [profile.heightCm, profile.unitPref?.height]);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
@@ -89,7 +71,6 @@ export default function EditProfileScreen() {
weightKg: undefined, weightKg: undefined,
heightCm: undefined, heightCm: undefined,
avatarUri: null, avatarUri: null,
unitPref: { weight: 'kg', height: 'cm' },
}; };
if (fromOnboarding) { if (fromOnboarding) {
@@ -109,8 +90,8 @@ export default function EditProfileScreen() {
} catch { } } catch { }
} }
setProfile(next); setProfile(next);
setWeightInput(next.weightKg != null ? (next.unitPref?.weight === 'kg' ? String(round(next.weightKg, 1)) : String(round(kgToLb(next.weightKg), 1))) : ''); setWeightInput(next.weightKg != null ? String(round(next.weightKg, 1)) : '');
setHeightInput(next.heightCm != null ? (next.unitPref?.height === 'cm' ? String(Math.round(next.heightCm)) : String(round(cmToFt(next.heightCm), 1))) : ''); setHeightInput(next.heightCm != null ? String(Math.round(next.heightCm)) : '');
} catch (e) { } catch (e) {
console.warn('读取资料失败', e); console.warn('读取资料失败', e);
} }
@@ -124,17 +105,17 @@ export default function EditProfileScreen() {
try { try {
const next: UserProfile = { ...profile }; const next: UserProfile = { ...profile };
// 将当前输入反向同步为公制 // 将当前输入同步为公制(固定 kg/cm
const w = parseFloat(weightInput); const w = parseFloat(weightInput);
if (!isNaN(w)) { if (!isNaN(w)) {
next.weightKg = profile.unitPref?.weight === 'kg' ? w : lbToKg(w); next.weightKg = w;
} else { } else {
next.weightKg = undefined; next.weightKg = undefined;
} }
const h = parseFloat(heightInput); const h = parseFloat(heightInput);
if (!isNaN(h)) { if (!isNaN(h)) {
next.heightCm = profile.unitPref?.height === 'cm' ? h : ftToCm(h); next.heightCm = h;
} else { } else {
next.heightCm = undefined; next.heightCm = undefined;
} }
@@ -147,27 +128,7 @@ export default function EditProfileScreen() {
} }
}; };
const toggleWeightUnit = (unit: WeightUnit) => { // 不再需要单位切换
if (unit === profile.unitPref?.weight) return;
const current = parseFloat(weightInput);
let nextValueStr = weightInput;
if (!isNaN(current)) {
nextValueStr = unit === 'kg' ? String(round(lbToKg(current), 1)) : String(round(kgToLb(current), 1));
}
setProfile((p) => ({ ...p, unitPref: { ...(p.unitPref || { weight: 'kg', height: 'cm' }), weight: unit } }));
setWeightInput(nextValueStr);
};
const toggleHeightUnit = (unit: HeightUnit) => {
if (unit === profile.unitPref?.height) return;
const current = parseFloat(heightInput);
let nextValueStr = heightInput;
if (!isNaN(current)) {
nextValueStr = unit === 'cm' ? String(Math.round(ftToCm(current))) : String(round(cmToFt(current), 1));
}
setProfile((p) => ({ ...p, unitPref: { ...(p.unitPref || { weight: 'kg', height: 'cm' }), height: unit } }));
setHeightInput(nextValueStr);
};
const pickAvatarFromLibrary = async () => { const pickAvatarFromLibrary = async () => {
try { try {
@@ -235,44 +196,36 @@ export default function EditProfileScreen() {
{!!profile.fullName && <Text style={{ color: '#C4C4C4' }}></Text>} {!!profile.fullName && <Text style={{ color: '#C4C4C4' }}></Text>}
</View> </View>
{/* 体重 */} {/* 体重kg */}
<FieldLabel text="体重" /> <FieldLabel text="体重" />
<View style={styles.row}> <View style={styles.row}>
<View style={[styles.inputWrapper, styles.flex1, { borderColor: '#E0E0E0' }]}> <View style={[styles.inputWrapper, styles.flex1, { borderColor: '#E0E0E0' }]}>
<TextInput <TextInput
style={[styles.textInput, { color: textColor }]} style={[styles.textInput, { color: textColor }]}
placeholder={profile.unitPref?.weight === 'kg' ? '输入体重' : '输入体重'} placeholder={'输入体重'}
placeholderTextColor={placeholderColor} placeholderTextColor={placeholderColor}
keyboardType="numeric" keyboardType="numeric"
value={weightInput} value={weightInput}
onChangeText={setWeightInput} onChangeText={setWeightInput}
/> />
<Text style={{ color: '#5E6468', marginLeft: 6 }}>kg</Text>
</View> </View>
<SegmentedTwo
options={[{ key: 'lb', label: 'LBS' }, { key: 'kg', label: 'KG' }]}
activeKey={profile.unitPref?.weight || 'kg'}
onChange={(key) => toggleWeightUnit(key as WeightUnit)}
/>
</View> </View>
{/* 身高 */} {/* 身高cm */}
<FieldLabel text="身高" /> <FieldLabel text="身高" />
<View style={styles.row}> <View style={styles.row}>
<View style={[styles.inputWrapper, styles.flex1, { borderColor: '#E0E0E0' }]}> <View style={[styles.inputWrapper, styles.flex1, { borderColor: '#E0E0E0' }]}>
<TextInput <TextInput
style={[styles.textInput, { color: textColor }]} style={[styles.textInput, { color: textColor }]}
placeholder={profile.unitPref?.height === 'cm' ? '输入身高' : '输入身高'} placeholder={'输入身高'}
placeholderTextColor={placeholderColor} placeholderTextColor={placeholderColor}
keyboardType="numeric" keyboardType="numeric"
value={heightInput} value={heightInput}
onChangeText={setHeightInput} onChangeText={setHeightInput}
/> />
<Text style={{ color: '#5E6468', marginLeft: 6 }}>cm</Text>
</View> </View>
<SegmentedTwo
options={[{ key: 'ft', label: 'FEET' }, { key: 'cm', label: 'CM' }]}
activeKey={profile.unitPref?.height || 'cm'}
onChange={(key) => toggleHeightUnit(key as HeightUnit)}
/>
</View> </View>
{/* 性别 */} {/* 性别 */}
@@ -323,29 +276,10 @@ function FieldLabel({ text }: { text: string }) {
); );
} }
function SegmentedTwo(props: { // 单位切换组件已移除(固定 kg/cm
options: { key: string; label: string }[];
activeKey: string;
onChange: (key: string) => void;
}) {
const { options, activeKey, onChange } = props;
return (
<View style={[styles.segmented, { backgroundColor: '#EFEFEF' }]}>
{options.map((opt) => (
<TouchableOpacity
key={opt.key}
style={[styles.segmentBtn, activeKey === opt.key && { backgroundColor: '#FFFFFF' }]}
onPress={() => onChange(opt.key)}
activeOpacity={0.8}
>
<Text style={{ fontSize: 14, fontWeight: '600', color: activeKey === opt.key ? '#000' : '#5E6468' }}>{opt.label}</Text>
</TouchableOpacity>
))}
</View>
);
}
// 工具函数 // 工具函数
// 转换函数不再使用,保留 round
function kgToLb(kg: number) { return kg * 2.2046226218; } function kgToLb(kg: number) { return kg * 2.2046226218; }
function lbToKg(lb: number) { return lb / 2.2046226218; } function lbToKg(lb: number) { return lb / 2.2046226218; }
function cmToFt(cm: number) { return cm / 30.48; } function cmToFt(cm: number) { return cm / 30.48; }

View File

@@ -33,6 +33,8 @@ PODS:
- ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core - ReactCommon/turbomodule/core
- Yoga - Yoga
- ExpoAppleAuthentication (6.4.2):
- ExpoModulesCore
- ExpoAsset (11.1.7): - ExpoAsset (11.1.7):
- ExpoModulesCore - ExpoModulesCore
- ExpoBlur (14.1.5): - ExpoBlur (14.1.5):
@@ -2156,6 +2158,7 @@ DEPENDENCIES:
- EXConstants (from `../node_modules/expo-constants/ios`) - EXConstants (from `../node_modules/expo-constants/ios`)
- EXImageLoader (from `../node_modules/expo-image-loader/ios`) - EXImageLoader (from `../node_modules/expo-image-loader/ios`)
- Expo (from `../node_modules/expo`) - Expo (from `../node_modules/expo`)
- ExpoAppleAuthentication (from `../node_modules/expo-apple-authentication/ios`)
- ExpoAsset (from `../node_modules/expo-asset/ios`) - ExpoAsset (from `../node_modules/expo-asset/ios`)
- ExpoBlur (from `../node_modules/expo-blur/ios`) - ExpoBlur (from `../node_modules/expo-blur/ios`)
- ExpoFileSystem (from `../node_modules/expo-file-system/ios`) - ExpoFileSystem (from `../node_modules/expo-file-system/ios`)
@@ -2273,6 +2276,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-image-loader/ios" :path: "../node_modules/expo-image-loader/ios"
Expo: Expo:
:path: "../node_modules/expo" :path: "../node_modules/expo"
ExpoAppleAuthentication:
:path: "../node_modules/expo-apple-authentication/ios"
ExpoAsset: ExpoAsset:
:path: "../node_modules/expo-asset/ios" :path: "../node_modules/expo-asset/ios"
ExpoBlur: ExpoBlur:
@@ -2465,6 +2470,7 @@ SPEC CHECKSUMS:
EXConstants: 98bcf0f22b820f9b28f9fee55ff2daededadd2f8 EXConstants: 98bcf0f22b820f9b28f9fee55ff2daededadd2f8
EXImageLoader: 4d3d3284141f1a45006cc4d0844061c182daf7ee EXImageLoader: 4d3d3284141f1a45006cc4d0844061c182daf7ee
Expo: a40d525c930dd1c8a158e082756ee071955baccb Expo: a40d525c930dd1c8a158e082756ee071955baccb
ExpoAppleAuthentication: 8a661b6f4936affafd830f983ac22463c936dad5
ExpoAsset: ef06e880126c375f580d4923fdd1cdf4ee6ee7d6 ExpoAsset: ef06e880126c375f580d4923fdd1cdf4ee6ee7d6
ExpoBlur: 3c8885b9bf9eef4309041ec87adec48b5f1986a9 ExpoBlur: 3c8885b9bf9eef4309041ec87adec48b5f1986a9
ExpoFileSystem: 7f92f7be2f5c5ed40a7c9efc8fa30821181d9d63 ExpoFileSystem: 7f92f7be2f5c5ed40a7c9efc8fa30821181d9d63

View File

@@ -21,7 +21,7 @@
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = digitalpilates/Images.xcassets; sourceTree = "<group>"; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = digitalpilates/Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = digitalpilates/Info.plist; sourceTree = "<group>"; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = digitalpilates/Info.plist; sourceTree = "<group>"; };
4D6B8E20DD8E5677F8B2EAA1 /* Pods-digitalpilates.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-digitalpilates.debug.xcconfig"; path = "Target Support Files/Pods-digitalpilates/Pods-digitalpilates.debug.xcconfig"; sourceTree = "<group>"; }; 4D6B8E20DD8E5677F8B2EAA1 /* Pods-digitalpilates.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-digitalpilates.debug.xcconfig"; path = "Target Support Files/Pods-digitalpilates/Pods-digitalpilates.debug.xcconfig"; sourceTree = "<group>"; };
7EC44F9488C227087AA8DF97 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = digitalpilates/PrivacyInfo.xcprivacy; sourceTree = "<group>"; }; 7EC44F9488C227087AA8DF97 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = digitalpilates/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
83D1B5F0EC906D7A2F599549 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-digitalpilates/ExpoModulesProvider.swift"; sourceTree = "<group>"; }; 83D1B5F0EC906D7A2F599549 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-digitalpilates/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = digitalpilates/SplashScreen.storyboard; sourceTree = "<group>"; }; AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = digitalpilates/SplashScreen.storyboard; sourceTree = "<group>"; };
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; }; BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
@@ -73,7 +73,6 @@
4D6B8E20DD8E5677F8B2EAA1 /* Pods-digitalpilates.debug.xcconfig */, 4D6B8E20DD8E5677F8B2EAA1 /* Pods-digitalpilates.debug.xcconfig */,
EA6A757B2DE1747F7B3664B4 /* Pods-digitalpilates.release.xcconfig */, EA6A757B2DE1747F7B3664B4 /* Pods-digitalpilates.release.xcconfig */,
); );
name = Pods;
path = Pods; path = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@@ -340,6 +339,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = digitalpilates/digitalpilates.entitlements; CODE_SIGN_ENTITLEMENTS = digitalpilates/digitalpilates.entitlements;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 756WVXJ6MT;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)", "$(inherited)",
@@ -376,6 +376,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = digitalpilates/digitalpilates.entitlements; CODE_SIGN_ENTITLEMENTS = digitalpilates/digitalpilates.entitlements;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 756WVXJ6MT;
INFOPLIST_FILE = digitalpilates/Info.plist; INFOPLIST_FILE = digitalpilates/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1; IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (

View File

@@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
<key>com.apple.developer.healthkit</key> <key>com.apple.developer.healthkit</key>
<true/> <true/>
<key>com.apple.developer.healthkit.access</key> <key>com.apple.developer.healthkit.access</key>

10
package-lock.json generated
View File

@@ -15,6 +15,7 @@
"@react-navigation/native": "^7.1.6", "@react-navigation/native": "^7.1.6",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"expo": "~53.0.20", "expo": "~53.0.20",
"expo-apple-authentication": "6.4.2",
"expo-blur": "~14.1.5", "expo-blur": "~14.1.5",
"expo-constants": "~17.1.7", "expo-constants": "~17.1.7",
"expo-font": "~13.3.2", "expo-font": "~13.3.2",
@@ -6329,6 +6330,15 @@
} }
} }
}, },
"node_modules/expo-apple-authentication": {
"version": "6.4.2",
"resolved": "https://mirrors.tencent.com/npm/expo-apple-authentication/-/expo-apple-authentication-6.4.2.tgz",
"integrity": "sha512-X4u1n3Ql1hOpztXHbKNq4I1l4+Ff82gC6RmEeW43Eht7VE6E8PrQBpYKw+JJv8osrCJt7R5O1PZwed6WLN5oig==",
"license": "MIT",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-asset": { "node_modules/expo-asset": {
"version": "11.1.7", "version": "11.1.7",
"resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-11.1.7.tgz", "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-11.1.7.tgz",

View File

@@ -19,6 +19,7 @@
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"expo": "~53.0.20", "expo": "~53.0.20",
"expo-blur": "~14.1.5", "expo-blur": "~14.1.5",
"expo-apple-authentication": "6.4.2",
"expo-constants": "~17.1.7", "expo-constants": "~17.1.7",
"expo-font": "~13.3.2", "expo-font": "~13.3.2",
"expo-haptics": "~14.1.4", "expo-haptics": "~14.1.4",