diff --git a/app.json b/app.json
index fdae57a..f7aa7ce 100644
--- a/app.json
+++ b/app.json
@@ -12,7 +12,10 @@
"supportsTablet": true,
"bundleIdentifier": "com.anonymous.digitalpilates",
"infoPlist": {
- "ITSAppUsesNonExemptEncryption": false
+ "ITSAppUsesNonExemptEncryption": false,
+ "NSCameraUsageDescription": "应用需要使用相机以拍摄您的体态照片用于AI测评。",
+ "NSPhotoLibraryUsageDescription": "应用需要访问相册以选择您的体态照片用于AI测评。",
+ "NSPhotoLibraryAddUsageDescription": "应用需要写入相册以保存拍摄的体态照片(可选)。"
}
},
"android": {
diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
index 14744cf..31aab2f 100644
--- a/app/(tabs)/_layout.tsx
+++ b/app/(tabs)/_layout.tsx
@@ -4,9 +4,13 @@ import React from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { IconSymbol } from '@/components/ui/IconSymbol';
+import { Colors } from '@/constants/Colors';
import { TAB_BAR_BOTTOM_OFFSET, TAB_BAR_HEIGHT } from '@/constants/TabBar';
+import { useColorScheme } from '@/hooks/useColorScheme';
export default function TabLayout() {
+ const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
+ const colorTokens = Colors[theme];
const pathname = usePathname();
return (
@@ -19,9 +23,9 @@ export default function TabLayout() {
return {
headerShown: false,
- tabBarActiveTintColor: '#192126',
+ tabBarActiveTintColor: colorTokens.tabIconSelected,
tabBarButton: (props) => {
- const { children, onPress } = props;
+ const { onPress } = props;
const handlePress = (event: any) => {
if (process.env.EXPO_OS === 'ios') {
@@ -30,9 +34,28 @@ export default function TabLayout() {
onPress && onPress(event);
};
+ // 基于 routeName 设置图标与标题,避免 tabBarIcon 的包装导致文字裁剪
+ const getIconAndTitle = () => {
+ switch (routeName) {
+ case 'index':
+ return { icon: 'house.fill', title: '首页' } as const;
+ case 'explore':
+ return { icon: 'paperplane.fill', title: '探索' } as const;
+ case 'personal':
+ return { icon: 'person.fill', title: '个人' } as const;
+ default:
+ return { icon: 'circle', title: '' } as const;
+ }
+ };
+
+ const { icon, title } = getIconAndTitle();
+ const activeContentColor = colorTokens.onPrimary;
+ const inactiveContentColor = colorTokens.tabIconDefault;
+
return (
- {children}
+
+
+ {isSelected && !!title && (
+
+ {title}
+
+ )}
+
);
},
@@ -55,7 +98,7 @@ export default function TabLayout() {
bottom: TAB_BAR_BOTTOM_OFFSET,
height: TAB_BAR_HEIGHT,
borderRadius: 34,
- backgroundColor: '#192126',
+ backgroundColor: colorTokens.tabBarBackground,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
diff --git a/app/ai-posture-assessment.tsx b/app/ai-posture-assessment.tsx
index be557e0..d888d98 100644
--- a/app/ai-posture-assessment.tsx
+++ b/app/ai-posture-assessment.tsx
@@ -1,9 +1,13 @@
import { Ionicons } from '@expo/vector-icons';
+import { BlurView } from 'expo-blur';
+import * as ImagePicker from 'expo-image-picker';
import { useRouter } from 'expo-router';
-import React from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
import {
+ Alert,
Image,
- ImageBackground,
+ Linking,
+ Platform,
ScrollView,
StyleSheet,
Text,
@@ -14,49 +18,157 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Colors } from '@/constants/Colors';
-type Exercise = {
- id: string;
- title: string;
- duration: string;
- imageUri: string;
+type PoseView = 'front' | 'side' | 'back';
+
+type UploadState = {
+ front?: string | null;
+ side?: string | null;
+ back?: string | null;
};
-const EXERCISES: Exercise[] = [
- {
- id: '1',
- title: 'Jumping Jacks',
- duration: '00:30',
- imageUri:
- 'https://images.unsplash.com/photo-1546483875-ad9014c88eba?q=80&w=400&auto=format&fit=crop',
- },
- {
- id: '2',
- title: 'Squats',
- duration: '00:45',
- imageUri:
- 'https://images.unsplash.com/photo-1583454110551-21f2fa2f36f0?q=80&w=400&auto=format&fit=crop',
- },
- {
- id: '3',
- title: 'Backward Lunge',
- duration: '00:40',
- imageUri:
- 'https://images.unsplash.com/photo-1597074866923-5c3bfa3b6c46?q=80&w=400&auto=format&fit=crop',
- },
- {
- id: '4',
- title: 'High Knees',
- duration: '00:30',
- imageUri:
- 'https://images.unsplash.com/photo-1596357395104-5bcae0b1a5eb?q=80&w=400&auto=format&fit=crop',
- },
-];
+type Sample = { uri: string; correct: boolean };
+
+const SAMPLES: Record = {
+ front: [
+ { uri: 'https://images.unsplash.com/photo-1594737625785-c6683fc87c73?w=400&q=80&auto=format', correct: true },
+ { uri: 'https://images.unsplash.com/photo-1544716278-ca5e3f4abd8c?w=400&q=80&auto=format', correct: false },
+ { uri: 'https://images.unsplash.com/photo-1571019614242-c5c5dee9f50b?w=400&q=80&auto=format', correct: false },
+ ],
+ side: [
+ { uri: 'https://images.unsplash.com/photo-1554463529-e27854014799?w=400&q=80&auto=format', correct: true },
+ { uri: 'https://images.unsplash.com/photo-1596357395104-5bcae0b1a5eb?w=400&q=80&auto=format', correct: false },
+ { uri: 'https://images.unsplash.com/photo-1526506118085-60ce8714f8c5?w=400&q=80&auto=format', correct: false },
+ ],
+ back: [
+ { uri: 'https://images.unsplash.com/photo-1517836357463-d25dfeac3438?w=400&q=80&auto=format', correct: true },
+ { uri: 'https://images.unsplash.com/photo-1571721797421-f4c9f2b13107?w=400&q=80&auto=format', correct: false },
+ { uri: 'https://images.unsplash.com/photo-1518611012118-696072aa579a?w=400&q=80&auto=format', correct: false },
+ ],
+};
export default function AIPostureAssessmentScreen() {
const router = useRouter();
const insets = useSafeAreaInsets();
+ const theme = Colors.dark;
- const theme = Colors.dark; // 该页面采用深色视觉
+ const [uploadState, setUploadState] = useState({});
+ const canStart = useMemo(
+ () => Boolean(uploadState.front && uploadState.side && uploadState.back),
+ [uploadState]
+ );
+
+ const [cameraPerm, setCameraPerm] = useState(null);
+ const [libraryPerm, setLibraryPerm] = useState(null);
+ const [libraryAccess, setLibraryAccess] = useState<'all' | 'limited' | 'none' | null>(null);
+ const [cameraCanAsk, setCameraCanAsk] = useState(null);
+ const [libraryCanAsk, setLibraryCanAsk] = useState(null);
+
+ useEffect(() => {
+ (async () => {
+ const cam = await ImagePicker.getCameraPermissionsAsync();
+ const lib = await ImagePicker.getMediaLibraryPermissionsAsync();
+ setCameraPerm(cam.status);
+ setLibraryPerm(lib.status);
+ setLibraryAccess(
+ (lib as any).accessPrivileges ?? (lib.status === 'granted' ? 'all' : 'none')
+ );
+ setCameraCanAsk(cam.canAskAgain);
+ setLibraryCanAsk(lib.canAskAgain);
+ })();
+ }, []);
+
+ async function requestAllPermissions() {
+ try {
+ const cam = await ImagePicker.requestCameraPermissionsAsync();
+ const lib = await ImagePicker.requestMediaLibraryPermissionsAsync();
+ setCameraPerm(cam.status);
+ setLibraryPerm(lib.status);
+ setLibraryAccess(
+ (lib as any).accessPrivileges ?? (lib.status === 'granted' ? 'all' : 'none')
+ );
+ setCameraCanAsk(cam.canAskAgain);
+ setLibraryCanAsk(lib.canAskAgain);
+ const libGranted = lib.status === 'granted' || (lib as any).accessPrivileges === 'limited';
+ if (cam.status !== 'granted' || !libGranted) {
+ Alert.alert(
+ '权限未完全授予',
+ '请在系统设置中授予相机与相册权限以完成上传',
+ [
+ { text: '取消', style: 'cancel' },
+ { text: '去设置', onPress: () => Linking.openSettings() },
+ ]
+ );
+ }
+ } catch { }
+ }
+
+ async function requestPermissionAndPick(source: 'camera' | 'library', key: PoseView) {
+ try {
+ if (source === 'camera') {
+ const resp = await ImagePicker.requestCameraPermissionsAsync();
+ setCameraPerm(resp.status);
+ setCameraCanAsk(resp.canAskAgain);
+ if (resp.status !== 'granted') {
+ Alert.alert(
+ '权限不足',
+ '需要相机权限以拍摄照片',
+ resp.canAskAgain
+ ? [{ text: '好的' }]
+ : [
+ { text: '取消', style: 'cancel' },
+ { text: '去设置', onPress: () => Linking.openSettings() },
+ ]
+ );
+ return;
+ }
+ const result = await ImagePicker.launchCameraAsync({
+ allowsEditing: true,
+ quality: 0.8,
+ aspect: [3, 4],
+ });
+ if (!result.canceled) {
+ setUploadState((s) => ({ ...s, [key]: result.assets[0]?.uri ?? null }));
+ }
+ } else {
+ const resp = await ImagePicker.requestMediaLibraryPermissionsAsync();
+ setLibraryPerm(resp.status);
+ setLibraryAccess(
+ (resp as any).accessPrivileges ?? (resp.status === 'granted' ? 'all' : 'none')
+ );
+ setLibraryCanAsk(resp.canAskAgain);
+ const libGranted = resp.status === 'granted' || (resp as any).accessPrivileges === 'limited';
+ if (!libGranted) {
+ Alert.alert(
+ '权限不足',
+ '需要相册权限以选择照片',
+ resp.canAskAgain
+ ? [{ text: '好的' }]
+ : [
+ { text: '取消', style: 'cancel' },
+ { text: '去设置', onPress: () => Linking.openSettings() },
+ ]
+ );
+ return;
+ }
+ const result = await ImagePicker.launchImageLibraryAsync({
+ allowsEditing: true,
+ quality: 0.8,
+ aspect: [3, 4],
+ });
+ if (!result.canceled) {
+ setUploadState((s) => ({ ...s, [key]: result.assets[0]?.uri ?? null }));
+ }
+ }
+ } catch (e) {
+ Alert.alert('发生错误', '选择图片失败,请重试');
+ }
+ }
+
+ function handleStart() {
+ if (!canStart) return;
+ // TODO: 调用后端或进入分析页面
+ Alert.alert('开始测评', '已收集三视角照片,准备开始AI体态分析');
+ }
return (
@@ -69,7 +181,7 @@ export default function AIPostureAssessmentScreen() {
>
- AI体态评估
+ AI体态测评
@@ -77,104 +189,142 @@ export default function AIPostureAssessmentScreen() {
contentContainerStyle={{ paddingBottom: insets.bottom + 120 }}
showsVerticalScrollIndicator={false}
>
- {/* Hero */}
-
-
-
-
+ {/* Permissions Banner (iOS 优先提示) */}
+ {Platform.OS === 'ios' && (
+ (cameraPerm !== 'granted' || !(libraryPerm === 'granted' || libraryAccess === 'limited')) && (
+
+ 需要相机与相册权限
+
+ 授权后可拍摄或选择三视角全身照片用于AI体态测评。
+
+
+ {((cameraCanAsk ?? true) || (libraryCanAsk ?? true)) ? (
+
+ 一键授权
+
+ ) : (
+ Linking.openSettings()}>
+ 去设置开启
+
+ )}
+ requestPermissionAndPick('library', 'front')}>
+ 稍后再说
+
+
+
+ )
+ )}
- {/* Floating stats */}
-
- }
- label="Time"
- value="20 min"
- />
-
- }
- label="Burn"
- value="95 kcal"
- />
-
-
-
- {/* Title & description */}
-
- Lower Body Training
+ {/* Intro */}
+
+ 上传标准姿势照片
- The lower abdomen and hips are the most difficult areas of the body to reduce when we are on
- a diet. Even so, in this area, especially the legs as a whole, you can reduce weight even if
- you don't use tools.
+ 请依次上传正面、侧面与背面全身照。保持光线均匀、背景简洁,身体立正自然放松。
- {/* Rounds header */}
-
- Rounds
- 1/8
-
+ {/* Upload sections */}
+ requestPermissionAndPick('camera', 'front')}
+ onPickLibrary={() => requestPermissionAndPick('library', 'front')}
+ samples={SAMPLES.front}
+ />
- {/* Exercise list */}
-
- {EXERCISES.map((item) => (
- )
- )}
-
+ requestPermissionAndPick('camera', 'side')}
+ onPickLibrary={() => requestPermissionAndPick('library', 'side')}
+ samples={SAMPLES.side}
+ />
+
+ requestPermissionAndPick('camera', 'back')}
+ onPickLibrary={() => requestPermissionAndPick('library', 'back')}
+ samples={SAMPLES.back}
+ />
{/* Bottom CTA */}
{ }}
- style={[styles.bottomCta, { backgroundColor: theme.primary }]}
+ disabled={!canStart}
+ activeOpacity={1}
+ onPress={handleStart}
+ style={[
+ styles.bottomCta,
+ { backgroundColor: canStart ? theme.primary : theme.neutral300 },
+ ]}
>
- Lets Workout
+
+ {canStart ? '开始测评' : '请先完成三视角上传'}
+
);
}
-function StatCard({
- icon,
+function UploadTile({
label,
value,
+ onPickCamera,
+ onPickLibrary,
+ samples,
}: {
- icon: React.ReactNode;
label: string;
- value: string;
+ value?: string | null;
+ onPickCamera: () => void;
+ onPickLibrary: () => void;
+ samples: Sample[];
}) {
return (
-
- {icon}
-
- {label}
- {value}
+
+
+ {label}
+ {value ? (
+ 可长按替换
+ ) : (
+ 需上传此视角
+ )}
-
- );
-}
-function ExerciseItem({ exercise }: { exercise: Exercise }) {
- return (
-
-
-
- {exercise.title}
- {exercise.duration}
-
-
-
+
+ {value ? (
+
+ ) : (
+
+
+
+
+ 拍摄或选择照片
+ 点击拍摄,长按从相册选择
+
+ )}
+
+
+ 示例
+
+ {samples.map((s, idx) => (
+
+
+
+ {s.correct ? '正确示范' : '错误示范'}
+
+
+ ))}
+
+
);
}
@@ -183,6 +333,57 @@ const styles = StyleSheet.create({
screen: {
flex: 1,
},
+ permBanner: {
+ marginTop: 12,
+ marginHorizontal: 16,
+ padding: 14,
+ borderRadius: 16,
+ backgroundColor: 'rgba(255,255,255,0.04)'
+ },
+ permTitle: {
+ color: '#ECEDEE',
+ fontSize: 16,
+ fontWeight: '700',
+ },
+ permDesc: {
+ color: 'rgba(255,255,255,0.75)',
+ marginTop: 6,
+ fontSize: 13,
+ },
+ permActions: {
+ flexDirection: 'row',
+ gap: 10,
+ marginTop: 10,
+ },
+ permPrimary: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ paddingHorizontal: 14,
+ height: 40,
+ borderRadius: 12,
+ backgroundColor: '#BBF246',
+ },
+ permPrimaryText: {
+ color: '#192126',
+ fontSize: 14,
+ fontWeight: '800',
+ },
+ permSecondary: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ paddingHorizontal: 14,
+ height: 40,
+ borderRadius: 12,
+ borderWidth: 1,
+ borderColor: 'rgba(255,255,255,0.18)',
+ },
+ permSecondaryText: {
+ color: 'rgba(255,255,255,0.85)',
+ fontSize: 14,
+ fontWeight: '700',
+ },
header: {
flexDirection: 'row',
alignItems: 'center',
@@ -202,73 +403,13 @@ const styles = StyleSheet.create({
color: '#ECEDEE',
fontWeight: '700',
},
- heroContainer: {
- marginTop: 14,
- marginHorizontal: 16,
- },
- heroImage: {
- height: 260,
- borderRadius: 28,
- overflow: 'hidden',
- justifyContent: 'flex-end',
- },
- heroOverlay: {
- ...StyleSheet.absoluteFillObject,
- backgroundColor: 'rgba(0,0,0,0.18)',
- borderRadius: 28,
- },
- statsFloating: {
- position: 'absolute',
- left: 22,
- right: 22,
- bottom: -26,
- height: 72,
- borderRadius: 20,
- backgroundColor: '#1E262C',
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'space-around',
- paddingHorizontal: 14,
- shadowColor: '#000',
- shadowOpacity: 0.3,
- shadowRadius: 10,
- shadowOffset: { width: 0, height: 6 },
- elevation: 6,
- },
- divider: {
- width: 1,
- height: 36,
- backgroundColor: 'rgba(255,255,255,0.08)',
- },
- statCard: {
- flexDirection: 'row',
- alignItems: 'center',
- },
- statIconWrap: {
- width: 36,
- height: 36,
- borderRadius: 12,
- backgroundColor: '#BBF246',
- alignItems: 'center',
- justifyContent: 'center',
- },
- statLabel: {
- color: 'rgba(255,255,255,0.75)',
- fontSize: 12,
- marginBottom: 2,
- },
- statValue: {
- color: '#ECEDEE',
- fontSize: 16,
- fontWeight: '700',
- },
- contentSection: {
- marginTop: 46,
+ introBox: {
+ marginTop: 12,
paddingHorizontal: 20,
- gap: 12,
+ gap: 10,
},
title: {
- fontSize: 28,
+ fontSize: 26,
color: '#ECEDEE',
fontWeight: '800',
},
@@ -277,53 +418,98 @@ const styles = StyleSheet.create({
lineHeight: 22,
color: 'rgba(255,255,255,0.75)',
},
- roundsHeader: {
- marginTop: 18,
- paddingHorizontal: 20,
+ section: {
+ marginTop: 16,
+ paddingHorizontal: 16,
+ gap: 12,
+ },
+ sectionHeader: {
+ paddingHorizontal: 4,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
- roundsTitle: {
+ sectionTitle: {
color: '#ECEDEE',
- fontSize: 22,
+ fontSize: 18,
fontWeight: '700',
},
- roundsCount: {
- color: 'rgba(255,255,255,0.6)',
- fontSize: 16,
- },
- exerciseItem: {
- flexDirection: 'row',
- alignItems: 'center',
- backgroundColor: '#1E262C',
- padding: 12,
- borderRadius: 18,
- },
- exerciseThumb: {
- width: 56,
- height: 56,
- borderRadius: 12,
- },
- exerciseTitle: {
- color: '#ECEDEE',
- fontSize: 16,
- fontWeight: '600',
- },
- exerciseDuration: {
- color: 'rgba(255,255,255,0.6)',
- marginTop: 4,
+ retakeHint: {
+ color: 'rgba(255,255,255,0.55)',
fontSize: 13,
},
- exercisePlayButton: {
+ uploader: {
+ height: 220,
+ borderRadius: 18,
+ borderWidth: 1,
+ borderStyle: 'dashed',
+ borderColor: 'rgba(255,255,255,0.18)',
+ backgroundColor: '#1E262C',
+ overflow: 'hidden',
+ },
+ preview: {
+ width: '100%',
+ height: '100%',
+ },
+ placeholder: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: 8,
+ },
+ plusBadge: {
width: 36,
height: 36,
borderRadius: 18,
- borderWidth: 1,
- borderColor: 'rgba(255,255,255,0.2)',
alignItems: 'center',
justifyContent: 'center',
- backgroundColor: '#192126',
+ backgroundColor: '#BBF246',
+ },
+ placeholderTitle: {
+ color: '#ECEDEE',
+ fontSize: 16,
+ fontWeight: '700',
+ },
+ placeholderDesc: {
+ color: 'rgba(255,255,255,0.65)',
+ fontSize: 12,
+ },
+ sampleBox: {
+ marginTop: 8,
+ borderRadius: 16,
+ padding: 12,
+ backgroundColor: 'rgba(255,255,255,0.04)',
+ },
+ sampleTitle: {
+ color: 'rgba(255,255,255,0.8)',
+ fontSize: 14,
+ marginBottom: 8,
+ fontWeight: '600',
+ },
+ sampleRow: {
+ flexDirection: 'row',
+ gap: 10,
+ },
+ sampleItem: {
+ flex: 1,
+ },
+ sampleImg: {
+ width: '100%',
+ height: 90,
+ borderRadius: 12,
+ backgroundColor: '#111',
+ },
+ sampleTag: {
+ alignSelf: 'flex-start',
+ paddingHorizontal: 8,
+ paddingVertical: 4,
+ borderRadius: 8,
+ marginTop: 6,
+ },
+ sampleTagText: {
+ color: '#192126',
+ fontSize: 12,
+ fontWeight: '700',
},
bottomCtaWrap: {
position: 'absolute',
@@ -338,10 +524,8 @@ const styles = StyleSheet.create({
justifyContent: 'center',
},
bottomCtaText: {
- color: '#192126',
fontSize: 18,
fontWeight: '800',
},
});
-
diff --git a/constants/Colors.ts b/constants/Colors.ts
index 2fe3936..5dd93b8 100644
--- a/constants/Colors.ts
+++ b/constants/Colors.ts
@@ -1,33 +1,103 @@
/**
- * Below are the colors that are used in the app. The colors are defined in the light and dark mode.
- * There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc.
+ * 应用全局配色规范(来自设计规范图)。
+ * 说明:保持原有导出结构不变,同时扩展更完整的语义令牌与原子调色板。
*/
-const primaryColor = '#BBF246'; // 应用主题色
+// 原子调色板(与设计图一致)
+export const palette = {
+ // Primary
+ primary: '#BBF246',
+ ink: '#192126',
+
+ // Secondary / Neutrals
+ neutral100: '#888F92',
+ neutral200: '#5E6468',
+ neutral300: '#384046',
+
+ // Accents
+ purple: '#A48AED',
+ red: '#ED4747',
+ orange: '#FCC46F',
+ blue: '#95CCE3',
+} as const;
+
+const primaryColor = palette.primary; // 应用主题色
const tintColorLight = primaryColor;
-const tintColorDark = '#fff';
+const tintColorDark = '#FFFFFF';
export const Colors = {
light: {
+ // 基础文本/背景
text: '#11181C',
- background: '#fff',
+ textSecondary: palette.neutral300,
+ textMuted: palette.neutral200,
+ background: '#FFFFFF',
+ surface: '#FFFFFF',
+ card: '#FFFFFF',
+
+ // 品牌与可交互主色
tint: tintColorLight,
primary: primaryColor,
+ onPrimary: palette.ink, // 与主色搭配的前景色(按钮文字/图标)
+
+ // 中性色与辅助
+ neutral100: palette.neutral100,
+ neutral200: palette.neutral200,
+ neutral300: palette.neutral300,
+
+ // 状态/反馈色
+ success: palette.primary,
+ warning: palette.orange,
+ danger: palette.red,
+ info: palette.blue,
+ accentPurple: palette.purple,
+
+ // 结构色
+ border: palette.neutral100 + '33', // 20% 透明度
+ separator: palette.neutral100 + '33',
icon: '#687076',
+
+ // Tab 相关(保持兼容)
tabIconDefault: '#687076',
- tabIconSelected: '#192126', // tab 激活时的文字/图标颜色(深色,在亮色背景上显示)
- tabBarBackground: '#192126', // tab 栏背景色
+ tabIconSelected: palette.ink, // tab 激活时的文字/图标颜色(深色,在亮色背景上显示)
+ tabBarBackground: palette.ink, // tab 栏背景色
tabBarActiveBackground: primaryColor, // tab 激活时的背景色
},
dark: {
+ // 基础文本/背景
text: '#ECEDEE',
+ textSecondary: palette.neutral100,
+ textMuted: '#9BA1A6',
background: '#151718',
+ surface: '#1A1D1E',
+ card: '#1A1D1E',
+
+ // 品牌与可交互主色
tint: tintColorDark,
primary: primaryColor,
+ onPrimary: palette.ink,
+
+ // 中性色与辅助
+ neutral100: palette.neutral100,
+ neutral200: palette.neutral200,
+ neutral300: palette.neutral300,
+
+ // 状态/反馈色
+ success: palette.primary,
+ warning: palette.orange,
+ danger: palette.red,
+ info: palette.blue,
+ accentPurple: palette.purple,
+
+ // 结构色
+ border: '#2A2F32',
+ separator: '#2A2F32',
icon: '#9BA1A6',
+
+ // Tab 相关(保持兼容)
tabIconDefault: '#9BA1A6',
- tabIconSelected: '#192126', // 在亮色背景上使用深色文字
- tabBarBackground: '#192126',
+ tabIconSelected: palette.ink, // 在亮色背景上使用深色文字
+ tabBarBackground: palette.ink,
tabBarActiveBackground: primaryColor,
},
-};
+} as const;
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 92e8bb2..e1f741b 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -3,6 +3,9 @@ PODS:
- DoubleConversion (1.1.6)
- EXConstants (17.1.7):
- ExpoModulesCore
+ - EXImageLoader (5.1.0):
+ - ExpoModulesCore
+ - React-Core
- Expo (53.0.20):
- DoubleConversion
- ExpoModulesCore
@@ -49,6 +52,8 @@ PODS:
- SDWebImageAVIFCoder (~> 0.11.0)
- SDWebImageSVGCoder (~> 1.7.0)
- SDWebImageWebPCoder (~> 0.14.6)
+ - ExpoImagePicker (16.1.4):
+ - ExpoModulesCore
- ExpoKeepAwake (14.1.4):
- ExpoModulesCore
- ExpoLinearGradient (14.1.5):
@@ -2100,6 +2105,7 @@ DEPENDENCIES:
- boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- EXConstants (from `../node_modules/expo-constants/ios`)
+ - EXImageLoader (from `../node_modules/expo-image-loader/ios`)
- Expo (from `../node_modules/expo`)
- ExpoAsset (from `../node_modules/expo-asset/ios`)
- ExpoBlur (from `../node_modules/expo-blur/ios`)
@@ -2108,6 +2114,7 @@ DEPENDENCIES:
- ExpoHaptics (from `../node_modules/expo-haptics/ios`)
- ExpoHead (from `../node_modules/expo-router/ios`)
- ExpoImage (from `../node_modules/expo-image/ios`)
+ - ExpoImagePicker (from `../node_modules/expo-image-picker/ios`)
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
- ExpoLinearGradient (from `../node_modules/expo-linear-gradient/ios`)
- ExpoLinking (from `../node_modules/expo-linking/ios`)
@@ -2212,6 +2219,8 @@ EXTERNAL SOURCES:
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
EXConstants:
:path: "../node_modules/expo-constants/ios"
+ EXImageLoader:
+ :path: "../node_modules/expo-image-loader/ios"
Expo:
:path: "../node_modules/expo"
ExpoAsset:
@@ -2228,6 +2237,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-router/ios"
ExpoImage:
:path: "../node_modules/expo-image/ios"
+ ExpoImagePicker:
+ :path: "../node_modules/expo-image-picker/ios"
ExpoKeepAwake:
:path: "../node_modules/expo-keep-awake/ios"
ExpoLinearGradient:
@@ -2400,6 +2411,7 @@ SPEC CHECKSUMS:
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
EXConstants: 98bcf0f22b820f9b28f9fee55ff2daededadd2f8
+ EXImageLoader: 4d3d3284141f1a45006cc4d0844061c182daf7ee
Expo: a40d525c930dd1c8a158e082756ee071955baccb
ExpoAsset: ef06e880126c375f580d4923fdd1cdf4ee6ee7d6
ExpoBlur: 3c8885b9bf9eef4309041ec87adec48b5f1986a9
@@ -2408,6 +2420,7 @@ SPEC CHECKSUMS:
ExpoHaptics: 0ff6e0d83cd891178a306e548da1450249d54500
ExpoHead: a7b66cbaeeb51f4a85338d335a0f5467e29a2c90
ExpoImage: e4102c93d1dbe99ff54b075452d1bc9d6ec21b7c
+ ExpoImagePicker: 0963da31800c906e01c03e25d7c849f16ebf02a2
ExpoKeepAwake: bf0811570c8da182bfb879169437d4de298376e7
ExpoLinearGradient: 7734c8059972fcf691fb4330bcdf3390960a152d
ExpoLinking: d5c183998ca6ada66ff45e407e0f965b398a8902
diff --git a/ios/digitalpilates.xcodeproj/project.pbxproj b/ios/digitalpilates.xcodeproj/project.pbxproj
index f5b494d..7e059f0 100644
--- a/ios/digitalpilates.xcodeproj/project.pbxproj
+++ b/ios/digitalpilates.xcodeproj/project.pbxproj
@@ -8,28 +8,28 @@
/* Begin PBXBuildFile section */
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
- 27B482E4BA415859EA8B0372 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732A3660B00244505674555 /* ExpoModulesProvider.swift */; };
+ 2C9C524987451393B76B9C7E /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 7EC44F9488C227087AA8DF97 /* PrivacyInfo.xcprivacy */; };
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
- 7F5A026795C1BF28BEBBFA4E /* libPods-digitalpilates.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FDAB65677BEFC9C3E52449E8 /* libPods-digitalpilates.a */; };
+ 6B6021A2D1EB466803BE19D7 /* libPods-digitalpilates.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F1A078ADDB1BCB06E0DBEFDA /* libPods-digitalpilates.a */; };
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
- CBE4D0B57478826DADA47BC9 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = C59D7448330DC33ACFEECB09 /* PrivacyInfo.xcprivacy */; };
+ DC3BFC72D3A68C7493D5B44A /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83D1B5F0EC906D7A2F599549 /* ExpoModulesProvider.swift */; };
F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
- 031BAB3982A98E78D1493449 /* 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 = ""; };
13B07F961A680F5B00A75B9A /* digitalpilates.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = digitalpilates.app; sourceTree = BUILT_PRODUCTS_DIR; };
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 = ""; };
- 7398BB3C47424238F7BEF8F9 /* Pods-digitalpilates.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-digitalpilates.release.xcconfig"; path = "Target Support Files/Pods-digitalpilates/Pods-digitalpilates.release.xcconfig"; sourceTree = ""; };
- 8732A3660B00244505674555 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-digitalpilates/ExpoModulesProvider.swift"; 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 = ""; };
+ 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 = ""; };
- C59D7448330DC33ACFEECB09 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = digitalpilates/PrivacyInfo.xcprivacy; sourceTree = ""; };
+ EA6A757B2DE1747F7B3664B4 /* Pods-digitalpilates.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-digitalpilates.release.xcconfig"; path = "Target Support Files/Pods-digitalpilates/Pods-digitalpilates.release.xcconfig"; sourceTree = ""; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
F11748412D0307B40044C1D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = digitalpilates/AppDelegate.swift; sourceTree = ""; };
F11748442D0722820044C1D9 /* digitalpilates-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "digitalpilates-Bridging-Header.h"; path = "digitalpilates/digitalpilates-Bridging-Header.h"; sourceTree = ""; };
- FDAB65677BEFC9C3E52449E8 /* libPods-digitalpilates.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-digitalpilates.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ F1A078ADDB1BCB06E0DBEFDA /* libPods-digitalpilates.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-digitalpilates.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -37,7 +37,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 7F5A026795C1BF28BEBBFA4E /* libPods-digitalpilates.a in Frameworks */,
+ 6B6021A2D1EB466803BE19D7 /* libPods-digitalpilates.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -53,7 +53,7 @@
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
- C59D7448330DC33ACFEECB09 /* PrivacyInfo.xcprivacy */,
+ 7EC44F9488C227087AA8DF97 /* PrivacyInfo.xcprivacy */,
);
name = digitalpilates;
sourceTree = "";
@@ -62,17 +62,19 @@
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
- FDAB65677BEFC9C3E52449E8 /* libPods-digitalpilates.a */,
+ F1A078ADDB1BCB06E0DBEFDA /* libPods-digitalpilates.a */,
);
name = Frameworks;
sourceTree = "";
};
- 7B79F82457416491183F46D6 /* digitalpilates */ = {
+ 3EE8D66219D64F4A63E8298D /* Pods */ = {
isa = PBXGroup;
children = (
- 8732A3660B00244505674555 /* ExpoModulesProvider.swift */,
+ 4D6B8E20DD8E5677F8B2EAA1 /* Pods-digitalpilates.debug.xcconfig */,
+ EA6A757B2DE1747F7B3664B4 /* Pods-digitalpilates.release.xcconfig */,
);
- name = digitalpilates;
+ name = Pods;
+ path = Pods;
sourceTree = "";
};
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
@@ -89,8 +91,8 @@
832341AE1AAA6A7D00B99B32 /* Libraries */,
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
- 8D14961AE80832AA51F9FFA3 /* Pods */,
- F7E6B913F90880BFADBE192E /* ExpoModulesProviders */,
+ 3EE8D66219D64F4A63E8298D /* Pods */,
+ F899CC3CCA86CFEC0C4F53F7 /* ExpoModulesProviders */,
);
indentWidth = 2;
sourceTree = "";
@@ -105,15 +107,6 @@
name = Products;
sourceTree = "";
};
- 8D14961AE80832AA51F9FFA3 /* Pods */ = {
- isa = PBXGroup;
- children = (
- 031BAB3982A98E78D1493449 /* Pods-digitalpilates.debug.xcconfig */,
- 7398BB3C47424238F7BEF8F9 /* Pods-digitalpilates.release.xcconfig */,
- );
- path = Pods;
- sourceTree = "";
- };
BB2F792B24A3F905000567C9 /* Supporting */ = {
isa = PBXGroup;
children = (
@@ -123,10 +116,18 @@
path = digitalpilates/Supporting;
sourceTree = "";
};
- F7E6B913F90880BFADBE192E /* ExpoModulesProviders */ = {
+ DFAD2B7142CEC38E9ED66053 /* digitalpilates */ = {
isa = PBXGroup;
children = (
- 7B79F82457416491183F46D6 /* digitalpilates */,
+ 83D1B5F0EC906D7A2F599549 /* ExpoModulesProvider.swift */,
+ );
+ name = digitalpilates;
+ sourceTree = "";
+ };
+ F899CC3CCA86CFEC0C4F53F7 /* ExpoModulesProviders */ = {
+ isa = PBXGroup;
+ children = (
+ DFAD2B7142CEC38E9ED66053 /* digitalpilates */,
);
name = ExpoModulesProviders;
sourceTree = "";
@@ -139,13 +140,13 @@
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "digitalpilates" */;
buildPhases = (
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */,
- E959B4C3D4071EECDD504F2E /* [Expo] Configure project */,
+ 60F566376E07CDAA8138E40B /* [Expo] Configure project */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
- 679509E8E2F87FD68CDC2915 /* [CP] Embed Pods Frameworks */,
+ 761236F3114550442BC2DA44 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -195,7 +196,7 @@
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */,
- CBE4D0B57478826DADA47BC9 /* PrivacyInfo.xcprivacy in Resources */,
+ 2C9C524987451393B76B9C7E /* PrivacyInfo.xcprivacy in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -239,7 +240,26 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
- 679509E8E2F87FD68CDC2915 /* [CP] Embed Pods Frameworks */ = {
+ 60F566376E07CDAA8138E40B /* [Expo] Configure project */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ name = "[Expo] Configure project";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-digitalpilates/expo-configure-project.sh\"\n";
+ };
+ 761236F3114550442BC2DA44 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -295,25 +315,6 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-digitalpilates/Pods-digitalpilates-resources.sh\"\n";
showEnvVarsInLog = 0;
};
- E959B4C3D4071EECDD504F2E /* [Expo] Configure project */ = {
- isa = PBXShellScriptBuildPhase;
- alwaysOutOfDate = 1;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- );
- name = "[Expo] Configure project";
- outputFileListPaths = (
- );
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-digitalpilates/expo-configure-project.sh\"\n";
- };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -322,7 +323,7 @@
buildActionMask = 2147483647;
files = (
F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */,
- 27B482E4BA415859EA8B0372 /* ExpoModulesProvider.swift in Sources */,
+ DC3BFC72D3A68C7493D5B44A /* ExpoModulesProvider.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -331,13 +332,12 @@
/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 031BAB3982A98E78D1493449 /* Pods-digitalpilates.debug.xcconfig */;
+ baseConfigurationReference = 4D6B8E20DD8E5677F8B2EAA1 /* Pods-digitalpilates.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = digitalpilates/digitalpilates.entitlements;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = 756WVXJ6MT;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
@@ -368,13 +368,12 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 7398BB3C47424238F7BEF8F9 /* Pods-digitalpilates.release.xcconfig */;
+ baseConfigurationReference = EA6A757B2DE1747F7B3664B4 /* Pods-digitalpilates.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
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/Info.plist b/ios/digitalpilates/Info.plist
index 8b18d51..56ab7c2 100644
--- a/ios/digitalpilates/Info.plist
+++ b/ios/digitalpilates/Info.plist
@@ -1,86 +1,91 @@
-
- CADisableMinimumFrameDurationOnPhone
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleDisplayName
- digital-pilates
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- $(PRODUCT_BUNDLE_PACKAGE_TYPE)
- CFBundleShortVersionString
- 1.0.0
- CFBundleSignature
- ????
- CFBundleURLTypes
-
-
- CFBundleURLSchemes
-
- digitalpilates
- digital-pilates
-
-
-
- CFBundleVersion
- 1
- LSMinimumSystemVersion
- 12.0
- LSRequiresIPhoneOS
-
- NSAppTransportSecurity
-
- NSAllowsArbitraryLoads
-
- NSAllowsLocalNetworking
-
-
- NSHealthShareUsageDescription
- 应用需要访问您的健康数据(步数与能量消耗)以展示运动统计。
- NSHealthUpdateUsageDescription
- Allow $(PRODUCT_NAME) to update health info
- NSUserActivityTypes
-
- $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
-
- UILaunchStoryboardName
- SplashScreen
- UIRequiredDeviceCapabilities
-
- arm64
-
- UIRequiresFullScreen
-
- UIStatusBarStyle
- UIStatusBarStyleDefault
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UIUserInterfaceStyle
- Automatic
- UIViewControllerBasedStatusBarAppearance
-
-
- ITSAppUsesNonExemptEncryption
-
-
-
+
+ CADisableMinimumFrameDurationOnPhone
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ digital-pilates
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0.0
+ CFBundleSignature
+ ????
+ CFBundleURLTypes
+
+
+ CFBundleURLSchemes
+
+ digitalpilates
+ com.anonymous.digitalpilates
+
+
+
+ CFBundleVersion
+ 1
+ ITSAppUsesNonExemptEncryption
+
+ LSMinimumSystemVersion
+ 12.0
+ LSRequiresIPhoneOS
+
+ NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+ NSAllowsLocalNetworking
+
+
+ NSCameraUsageDescription
+ 应用需要使用相机以拍摄您的体态照片用于AI测评。
+ NSHealthShareUsageDescription
+ 应用需要访问您的健康数据(步数与能量消耗)以展示运动统计。
+ NSHealthUpdateUsageDescription
+ Allow $(PRODUCT_NAME) to update health info
+ NSPhotoLibraryAddUsageDescription
+ 应用需要写入相册以保存拍摄的体态照片(可选)。
+ NSPhotoLibraryUsageDescription
+ 应用需要访问相册以选择您的体态照片用于AI测评。
+ NSUserActivityTypes
+
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+
+ UILaunchStoryboardName
+ SplashScreen
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UIRequiresFullScreen
+
+ UIStatusBarStyle
+ UIStatusBarStyleDefault
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIUserInterfaceStyle
+ Automatic
+ UIViewControllerBasedStatusBarAppearance
+
+
+
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 656fd1d..a365080 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,6 +20,7 @@
"expo-font": "~13.3.2",
"expo-haptics": "~14.1.4",
"expo-image": "~2.4.0",
+ "expo-image-picker": "~16.1.4",
"expo-linear-gradient": "^14.1.5",
"expo-linking": "~7.1.7",
"expo-router": "~5.1.4",
@@ -6301,6 +6302,27 @@
}
}
},
+ "node_modules/expo-image-loader": {
+ "version": "5.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/expo-image-loader/-/expo-image-loader-5.1.0.tgz",
+ "integrity": "sha512-sEBx3zDQIODWbB5JwzE7ZL5FJD+DK3LVLWBVJy6VzsqIA6nDEnSFnsnWyCfCTSvbGigMATs1lgkC2nz3Jpve1Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
+ "node_modules/expo-image-picker": {
+ "version": "16.1.4",
+ "resolved": "https://mirrors.tencent.com/npm/expo-image-picker/-/expo-image-picker-16.1.4.tgz",
+ "integrity": "sha512-bTmmxtw1AohUT+HxEBn2vYwdeOrj1CLpMXKjvi9FKSoSbpcarT4xxI0z7YyGwDGHbrJqyyic3I9TTdP2J2b4YA==",
+ "license": "MIT",
+ "dependencies": {
+ "expo-image-loader": "~5.1.0"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
"node_modules/expo-keep-awake": {
"version": "14.1.4",
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-14.1.4.tgz",
diff --git a/package.json b/package.json
index a7bbc1f..708a278 100644
--- a/package.json
+++ b/package.json
@@ -40,7 +40,8 @@
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "~4.11.1",
"react-native-web": "~0.20.0",
- "react-native-webview": "13.13.5"
+ "react-native-webview": "13.13.5",
+ "expo-image-picker": "~16.1.4"
},
"devDependencies": {
"@babel/core": "^7.25.2",