import { HeaderBar } from '@/components/ui/HeaderBar'; import { Colors } from '@/constants/Colors'; import { useColorScheme } from '@/hooks/useColorScheme'; 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, useState } from 'react'; import { Alert, Image, KeyboardAvoidingView, Platform, SafeAreaView, ScrollView, StatusBar, StyleSheet, Text, TextInput, TouchableOpacity, View, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; type WeightUnit = 'kg' | 'lb'; type HeightUnit = 'cm' | 'ft'; interface UserProfile { fullName?: string; gender?: 'male' | 'female' | ''; age?: string; // 存储为字符串,方便非必填 // 以公制为基准存储 weightKg?: number; // kg heightCm?: number; // cm avatarUri?: string | null; } const STORAGE_KEY = '@user_profile'; export default function EditProfileScreen() { const colorScheme = useColorScheme(); const colors = Colors[colorScheme ?? 'light']; const insets = useSafeAreaInsets(); const [profile, setProfile] = useState({ fullName: '', gender: '', age: '', weightKg: undefined, heightCm: undefined, avatarUri: null, }); const [weightInput, setWeightInput] = useState(''); const [heightInput, setHeightInput] = useState(''); // 输入框字符串 useEffect(() => { (async () => { try { // 读取已保存资料;兼容引导页的个人信息 const [p, fromOnboarding] = await Promise.all([ AsyncStorage.getItem(STORAGE_KEY), AsyncStorage.getItem('@user_personal_info'), ]); let next: UserProfile = { fullName: '', gender: '', age: '', weightKg: undefined, heightCm: undefined, avatarUri: null, }; if (fromOnboarding) { try { const o = JSON.parse(fromOnboarding); if (o?.weight) next.weightKg = parseFloat(o.weight) || undefined; if (o?.height) next.heightCm = parseFloat(o.height) || undefined; if (o?.age) next.age = String(o.age); if (o?.gender) next.gender = o.gender; } catch { } } if (p) { try { const parsed: UserProfile = JSON.parse(p); next = { ...next, ...parsed }; } catch { } } setProfile(next); setWeightInput(next.weightKg != null ? String(round(next.weightKg, 1)) : ''); setHeightInput(next.heightCm != null ? String(Math.round(next.heightCm)) : ''); } catch (e) { console.warn('读取资料失败', e); } })(); }, []); const textColor = colors.text; const placeholderColor = colors.icon; const handleSave = async () => { try { const next: UserProfile = { ...profile }; // 将当前输入同步为公制(固定 kg/cm) const w = parseFloat(weightInput); if (!isNaN(w)) { next.weightKg = w; } else { next.weightKg = undefined; } const h = parseFloat(heightInput); if (!isNaN(h)) { next.heightCm = h; } else { next.heightCm = undefined; } await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(next)); Alert.alert('已保存', '个人资料已更新。'); router.back(); } catch (e) { Alert.alert('保存失败', '请稍后重试'); } }; // 不再需要单位切换 const pickAvatarFromLibrary = async () => { try { const resp = await ImagePicker.requestMediaLibraryPermissionsAsync(); const libGranted = resp.status === 'granted' || (resp as any).accessPrivileges === 'limited'; if (!libGranted) { Alert.alert('权限不足', '需要相册权限以选择头像'); return; } const result = await ImagePicker.launchImageLibraryAsync({ allowsEditing: true, quality: 0.9, aspect: [1, 1], mediaTypes: ImagePicker.MediaTypeOptions.Images, }); if (!result.canceled) { setProfile((p) => ({ ...p, avatarUri: result.assets?.[0]?.uri ?? null })); } } catch (e) { Alert.alert('发生错误', '选择头像失败,请重试'); } }; return ( router.back()} withSafeTop={false} transparent /> {/* 头像(带相机蒙层,点击从相册选择) */} {/* 姓名 */} setProfile((p) => ({ ...p, fullName: t }))} /> {/* 校验勾无需强制,仅装饰 */} {!!profile.fullName && } {/* 体重(kg) */} kg {/* 身高(cm) */} cm {/* 性别 */} setProfile((p) => ({ ...p, gender: 'female' }))} > 女性 setProfile((p) => ({ ...p, gender: 'male' }))} > 男性 {/* 年龄 */} setProfile((p) => ({ ...p, age: t }))} /> {/* 保存按钮 */} 保存 ); } function FieldLabel({ text }: { text: string }) { return ( {text} ); } // 单位切换组件已移除(固定 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; } function ftToCm(ft: number) { return ft * 30.48; } function round(n: number, d = 0) { const p = Math.pow(10, d); return Math.round(n * p) / p; } const styles = StyleSheet.create({ container: { flex: 1 }, avatarCircle: { width: 120, height: 120, borderRadius: 60, backgroundColor: '#E8D4F0', alignItems: 'center', justifyContent: 'center', marginBottom: 12, }, avatarImage: { width: 120, height: 120, borderRadius: 60, }, avatarOverlay: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, alignItems: 'center', justifyContent: 'center', backgroundColor: 'rgba(255,255,255,0.25)', }, inputWrapper: { height: 52, backgroundColor: '#fff', borderRadius: 12, borderWidth: 1, paddingHorizontal: 16, alignItems: 'center', flexDirection: 'row', }, textInput: { flex: 1, fontSize: 16, }, row: { flexDirection: 'row', alignItems: 'center', gap: 12, }, flex1: { flex: 1 }, segmented: { flexDirection: 'row', borderRadius: 12, padding: 4, backgroundColor: '#EFEFEF', }, segmentBtn: { paddingVertical: 12, paddingHorizontal: 16, borderRadius: 10, backgroundColor: 'transparent', minWidth: 64, alignItems: 'center', }, selector: { flexDirection: 'row', gap: 12, backgroundColor: '#FFFFFF', borderWidth: 1, borderRadius: 12, padding: 8, }, selectorItem: { flex: 1, height: 48, borderRadius: 10, alignItems: 'center', justifyContent: 'center', }, selectorEmoji: { fontSize: 16, marginBottom: 2 }, selectorText: { fontSize: 15, fontWeight: '600' }, saveBtn: { marginTop: 24, height: 56, borderRadius: 16, alignItems: 'center', justifyContent: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 4, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 0, marginBottom: 8, }, backButton: { padding: 4, width: 32 }, headerTitle: { fontSize: 18, fontWeight: '700', color: '#192126' }, });