diff --git a/app/(tabs)/explore.tsx b/app/(tabs)/explore.tsx
index fcf4f2c..2fcb4a3 100644
--- a/app/(tabs)/explore.tsx
+++ b/app/(tabs)/explore.tsx
@@ -187,7 +187,13 @@ export default function ExploreScreen() {
) : (
——/2000
)}
-
+
diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx
index 5b2c1d7..4a2c2e6 100644
--- a/app/(tabs)/index.tsx
+++ b/app/(tabs)/index.tsx
@@ -5,7 +5,7 @@ import { ThemedView } from '@/components/ThemedView';
import { WorkoutCard } from '@/components/WorkoutCard';
import { getChineseGreeting } from '@/utils/date';
import { useRouter } from 'expo-router';
-import React from 'react';
+import React, { useEffect, useRef } from 'react';
import { SafeAreaView, ScrollView, StyleSheet, View } from 'react-native';
const workoutData = [
@@ -24,6 +24,15 @@ const workoutData = [
export default function HomeScreen() {
const router = useRouter();
+ const hasOpenedLoginRef = useRef(false);
+
+ useEffect(() => {
+ // 仅在本次会话首次进入首页时打开登录页,可返回关闭
+ if (!hasOpenedLoginRef.current) {
+ hasOpenedLoginRef.current = true;
+ router.push('/auth/login');
+ }
+ }, [router]);
return (
diff --git a/app/(tabs)/personal.tsx b/app/(tabs)/personal.tsx
index ff0d3f9..6dc6d04 100644
--- a/app/(tabs)/personal.tsx
+++ b/app/(tabs)/personal.tsx
@@ -31,8 +31,6 @@ export default function PersonalScreen() {
const colorScheme = useColorScheme();
const colors = Colors[colorScheme ?? 'light'];
- type WeightUnit = 'kg' | 'lb';
- type HeightUnit = 'cm' | 'ft';
type UserProfile = {
fullName?: string;
email?: string;
@@ -40,7 +38,7 @@ export default function PersonalScreen() {
age?: string;
weightKg?: number;
heightCm?: number;
- unitPref?: { weight: WeightUnit; height: HeightUnit };
+ avatarUri?: string | null;
};
const [profile, setProfile] = useState({});
@@ -78,16 +76,12 @@ export default function PersonalScreen() {
const formatHeight = () => {
if (profile.heightCm == null) return '--';
- const unit = profile.unitPref?.height ?? 'cm';
- if (unit === 'cm') return `${Math.round(profile.heightCm)}cm`;
- return `${round(profile.heightCm / 30.48, 1)}ft`;
+ return `${Math.round(profile.heightCm)}cm`;
};
const formatWeight = () => {
if (profile.weightKg == null) return '--';
- const unit = profile.unitPref?.weight ?? 'kg';
- if (unit === 'kg') return `${round(profile.weightKg, 1)}kg`;
- return `${round(profile.weightKg * 2.2046226218, 1)}lb`;
+ return `${round(profile.weightKg, 1)}kg`;
};
const formatAge = () => (profile.age ? `${profile.age}岁` : '--');
diff --git a/app/_layout.tsx b/app/_layout.tsx
index cb19f69..3c4c7de 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -24,6 +24,9 @@ export default function RootLayout() {
+
+
+
diff --git a/app/auth/login.tsx b/app/auth/login.tsx
new file mode 100644
index 0000000..a2fac81
--- /dev/null
+++ b/app/auth/login.tsx
@@ -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(false);
+ const [appleAvailable, setAppleAvailable] = useState(false);
+ const [loading, setLoading] = useState(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 (
+
+
+ {/* 自定义头部,与其它页面风格一致 */}
+
+ router.back()} style={styles.backButton}>
+
+
+ 登录
+
+
+
+
+
+ Digital Pilates
+ 欢迎登录
+
+
+ {/* Apple 登录 */}
+ {appleAvailable && (
+ guardAgreement(onAppleLogin)}
+ disabled={!hasAgreed || loading}
+ style={({ pressed }) => [
+ styles.appleButton,
+ { backgroundColor: '#000000' },
+ disabledStyle,
+ pressed && { transform: [{ scale: 0.98 }] },
+ ]}
+ >
+
+ 使用 Apple 登录
+
+ )}
+
+ {/* 游客登录(弱化样式) */}
+ guardAgreement(onGuestLogin)}
+ disabled={!hasAgreed || loading}
+ style={({ pressed }) => [
+ styles.guestButton,
+ { borderColor: color.border, backgroundColor: color.surface },
+ disabledStyle,
+ pressed && { transform: [{ scale: 0.98 }] },
+ ]}
+ >
+
+ 以游客身份继续
+
+
+ {/* 协议勾选 */}
+
+ setHasAgreed((v) => !v)} style={styles.checkboxWrap} accessibilityRole="checkbox" accessibilityState={{ checked: hasAgreed }}>
+
+ {hasAgreed && }
+
+
+ 我已阅读并同意
+ router.push('/legal/privacy-policy')}>
+ 《隐私政策》
+
+ 和
+ router.push('/legal/user-agreement')}>
+ 《用户协议》
+
+
+
+ {/* 占位底部间距 */}
+
+
+
+
+ );
+}
+
+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 },
+});
+
+
diff --git a/app/legal/privacy-policy.tsx b/app/legal/privacy-policy.tsx
new file mode 100644
index 0000000..98c2305
--- /dev/null
+++ b/app/legal/privacy-policy.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import { ScrollView, Text } from 'react-native';
+
+export default function PrivacyPolicy() {
+ return (
+
+ 隐私政策(示例)
+
+ 这是占位文案,用于展示隐私政策内容。请替换为正式的隐私政策文本。
+
+
+ );
+}
+
+
diff --git a/app/legal/user-agreement.tsx b/app/legal/user-agreement.tsx
new file mode 100644
index 0000000..34245fa
--- /dev/null
+++ b/app/legal/user-agreement.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import { ScrollView, Text } from 'react-native';
+
+export default function UserAgreement() {
+ return (
+
+ 用户协议(示例)
+
+ 这是占位文案,用于说明用户协议。请在此替换为你们的正式协议内容。
+
+
+ );
+}
+
+
diff --git a/app/profile/edit.tsx b/app/profile/edit.tsx
index ff4a5cd..8dc9e10 100644
--- a/app/profile/edit.tsx
+++ b/app/profile/edit.tsx
@@ -4,7 +4,7 @@ import { Ionicons } from '@expo/vector-icons';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as ImagePicker from 'expo-image-picker';
import { router } from 'expo-router';
-import React, { useEffect, useMemo, useState } from 'react';
+import React, { useEffect, useState } from 'react';
import {
Alert,
Image,
@@ -32,10 +32,6 @@ interface UserProfile {
weightKg?: number; // kg
heightCm?: number; // cm
avatarUri?: string | null;
- unitPref?: {
- weight: WeightUnit;
- height: HeightUnit;
- };
}
const STORAGE_KEY = '@user_profile';
@@ -52,26 +48,12 @@ export default function EditProfileScreen() {
weightKg: undefined,
heightCm: undefined,
avatarUri: null,
- unitPref: { weight: 'kg', height: 'cm' },
});
const [weightInput, setWeightInput] = useState('');
const [heightInput, setHeightInput] = useState('');
- // 将存储的公制值转换为当前选择单位显示
- 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(() => {
(async () => {
@@ -89,7 +71,6 @@ export default function EditProfileScreen() {
weightKg: undefined,
heightCm: undefined,
avatarUri: null,
- unitPref: { weight: 'kg', height: 'cm' },
};
if (fromOnboarding) {
@@ -109,8 +90,8 @@ export default function EditProfileScreen() {
} catch { }
}
setProfile(next);
- setWeightInput(next.weightKg != null ? (next.unitPref?.weight === 'kg' ? String(round(next.weightKg, 1)) : String(round(kgToLb(next.weightKg), 1))) : '');
- setHeightInput(next.heightCm != null ? (next.unitPref?.height === 'cm' ? String(Math.round(next.heightCm)) : String(round(cmToFt(next.heightCm), 1))) : '');
+ setWeightInput(next.weightKg != null ? String(round(next.weightKg, 1)) : '');
+ setHeightInput(next.heightCm != null ? String(Math.round(next.heightCm)) : '');
} catch (e) {
console.warn('读取资料失败', e);
}
@@ -124,17 +105,17 @@ export default function EditProfileScreen() {
try {
const next: UserProfile = { ...profile };
- // 将当前输入反向同步为公制
+ // 将当前输入同步为公制(固定 kg/cm)
const w = parseFloat(weightInput);
if (!isNaN(w)) {
- next.weightKg = profile.unitPref?.weight === 'kg' ? w : lbToKg(w);
+ next.weightKg = w;
} else {
next.weightKg = undefined;
}
const h = parseFloat(heightInput);
if (!isNaN(h)) {
- next.heightCm = profile.unitPref?.height === 'cm' ? h : ftToCm(h);
+ next.heightCm = h;
} else {
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 () => {
try {
@@ -235,44 +196,36 @@ export default function EditProfileScreen() {
{!!profile.fullName && ✓}
- {/* 体重 */}
+ {/* 体重(kg) */}
+ kg
- toggleWeightUnit(key as WeightUnit)}
- />
- {/* 身高 */}
+ {/* 身高(cm) */}
+ cm
- toggleHeightUnit(key as HeightUnit)}
- />
{/* 性别 */}
@@ -323,29 +276,10 @@ function FieldLabel({ text }: { text: string }) {
);
}
-function SegmentedTwo(props: {
- options: { key: string; label: string }[];
- activeKey: string;
- onChange: (key: string) => void;
-}) {
- const { options, activeKey, onChange } = props;
- return (
-
- {options.map((opt) => (
- onChange(opt.key)}
- activeOpacity={0.8}
- >
- {opt.label}
-
- ))}
-
- );
-}
+// 单位切换组件已移除(固定 kg/cm)
// 工具函数
+// 转换函数不再使用,保留 round
function kgToLb(kg: number) { return kg * 2.2046226218; }
function lbToKg(lb: number) { return lb / 2.2046226218; }
function cmToFt(cm: number) { return cm / 30.48; }
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 7939600..bbcad37 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -33,6 +33,8 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
+ - ExpoAppleAuthentication (6.4.2):
+ - ExpoModulesCore
- ExpoAsset (11.1.7):
- ExpoModulesCore
- ExpoBlur (14.1.5):
@@ -2156,6 +2158,7 @@ DEPENDENCIES:
- EXConstants (from `../node_modules/expo-constants/ios`)
- EXImageLoader (from `../node_modules/expo-image-loader/ios`)
- Expo (from `../node_modules/expo`)
+ - ExpoAppleAuthentication (from `../node_modules/expo-apple-authentication/ios`)
- ExpoAsset (from `../node_modules/expo-asset/ios`)
- ExpoBlur (from `../node_modules/expo-blur/ios`)
- ExpoFileSystem (from `../node_modules/expo-file-system/ios`)
@@ -2273,6 +2276,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-image-loader/ios"
Expo:
:path: "../node_modules/expo"
+ ExpoAppleAuthentication:
+ :path: "../node_modules/expo-apple-authentication/ios"
ExpoAsset:
:path: "../node_modules/expo-asset/ios"
ExpoBlur:
@@ -2465,6 +2470,7 @@ SPEC CHECKSUMS:
EXConstants: 98bcf0f22b820f9b28f9fee55ff2daededadd2f8
EXImageLoader: 4d3d3284141f1a45006cc4d0844061c182daf7ee
Expo: a40d525c930dd1c8a158e082756ee071955baccb
+ ExpoAppleAuthentication: 8a661b6f4936affafd830f983ac22463c936dad5
ExpoAsset: ef06e880126c375f580d4923fdd1cdf4ee6ee7d6
ExpoBlur: 3c8885b9bf9eef4309041ec87adec48b5f1986a9
ExpoFileSystem: 7f92f7be2f5c5ed40a7c9efc8fa30821181d9d63
diff --git a/ios/digitalpilates.xcodeproj/project.pbxproj b/ios/digitalpilates.xcodeproj/project.pbxproj
index 29cccf3..1795b1c 100644
--- a/ios/digitalpilates.xcodeproj/project.pbxproj
+++ b/ios/digitalpilates.xcodeproj/project.pbxproj
@@ -21,7 +21,7 @@
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = digitalpilates/Images.xcassets; sourceTree = ""; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = digitalpilates/Info.plist; sourceTree = ""; };
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 = ""; };
- 7EC44F9488C227087AA8DF97 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = digitalpilates/PrivacyInfo.xcprivacy; sourceTree = ""; };
+ 7EC44F9488C227087AA8DF97 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = digitalpilates/PrivacyInfo.xcprivacy; sourceTree = ""; };
83D1B5F0EC906D7A2F599549 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-digitalpilates/ExpoModulesProvider.swift"; sourceTree = ""; };
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = digitalpilates/SplashScreen.storyboard; sourceTree = ""; };
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; };
@@ -73,7 +73,6 @@
4D6B8E20DD8E5677F8B2EAA1 /* Pods-digitalpilates.debug.xcconfig */,
EA6A757B2DE1747F7B3664B4 /* Pods-digitalpilates.release.xcconfig */,
);
- name = Pods;
path = Pods;
sourceTree = "";
};
@@ -340,6 +339,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = digitalpilates/digitalpilates.entitlements;
CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = 756WVXJ6MT;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
@@ -376,6 +376,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = digitalpilates/digitalpilates.entitlements;
CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = 756WVXJ6MT;
INFOPLIST_FILE = digitalpilates/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = (
diff --git a/ios/digitalpilates/digitalpilates.entitlements b/ios/digitalpilates/digitalpilates.entitlements
index b37fe68..c41dab6 100644
--- a/ios/digitalpilates/digitalpilates.entitlements
+++ b/ios/digitalpilates/digitalpilates.entitlements
@@ -1,10 +1,14 @@
-
- com.apple.developer.healthkit
-
- com.apple.developer.healthkit.access
-
-
-
\ No newline at end of file
+
+ com.apple.developer.applesignin
+
+ Default
+
+ com.apple.developer.healthkit
+
+ com.apple.developer.healthkit.access
+
+
+
diff --git a/package-lock.json b/package-lock.json
index f2a84e2..4af286c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,6 +15,7 @@
"@react-navigation/native": "^7.1.6",
"dayjs": "^1.11.13",
"expo": "~53.0.20",
+ "expo-apple-authentication": "6.4.2",
"expo-blur": "~14.1.5",
"expo-constants": "~17.1.7",
"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": {
"version": "11.1.7",
"resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-11.1.7.tgz",
diff --git a/package.json b/package.json
index c7f8056..e19747d 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"dayjs": "^1.11.13",
"expo": "~53.0.20",
"expo-blur": "~14.1.5",
+ "expo-apple-authentication": "6.4.2",
"expo-constants": "~17.1.7",
"expo-font": "~13.3.2",
"expo-haptics": "~14.1.4",
@@ -52,4 +53,4 @@
"typescript": "~5.8.3"
},
"private": true
-}
+}
\ No newline at end of file