feat(家庭健康): 优化家庭组加入流程,移除自动创建家庭组逻辑
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { HeaderBar } from '@/components/ui/HeaderBar';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import {
|
||||
createFamilyGroup,
|
||||
fetchFamilyGroup,
|
||||
generateInviteCode,
|
||||
selectFamilyGroup,
|
||||
@@ -13,16 +12,16 @@ import { Ionicons } from '@expo/vector-icons';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { Stack } from 'expo-router';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Modal,
|
||||
ScrollView,
|
||||
Share,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Modal,
|
||||
ScrollView,
|
||||
Share,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
@@ -47,11 +46,6 @@ export default function FamilyInviteScreen() {
|
||||
// 处理邀请按钮点击
|
||||
const handleInvite = async () => {
|
||||
try {
|
||||
// 如果没有家庭组,先创建一个
|
||||
if (!familyGroup) {
|
||||
await dispatch(createFamilyGroup('我的家庭')).unwrap();
|
||||
}
|
||||
|
||||
// 生成邀请码
|
||||
await dispatch(generateInviteCode(24)).unwrap();
|
||||
|
||||
|
||||
@@ -3,20 +3,29 @@ import { BasicInfoTab } from '@/components/health/tabs/BasicInfoTab';
|
||||
import { CheckupRecordsTab } from '@/components/health/tabs/CheckupRecordsTab';
|
||||
import { HealthHistoryTab } from '@/components/health/tabs/HealthHistoryTab';
|
||||
import { MedicalRecordsTab } from '@/components/health/tabs/MedicalRecordsTab';
|
||||
import { ConfirmationSheet } from '@/components/ui/ConfirmationSheet';
|
||||
import { HeaderBar } from '@/components/ui/HeaderBar';
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { ROUTES } from '@/constants/Routes';
|
||||
import { useAppSelector } from '@/hooks/redux';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import {
|
||||
fetchFamilyGroup,
|
||||
joinFamilyGroup,
|
||||
selectFamilyGroup,
|
||||
} from '@/store/familyHealthSlice';
|
||||
import { selectHealthHistoryProgress } from '@/store/healthSlice';
|
||||
import { DEFAULT_MEMBER_NAME } from '@/store/userSlice';
|
||||
import { Toast } from '@/utils/toast.utils';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
|
||||
import { Image } from 'expo-image';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { Stack, useRouter } from 'expo-router';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
export default function HealthProfileScreen() {
|
||||
@@ -25,8 +34,19 @@ export default function HealthProfileScreen() {
|
||||
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
|
||||
const colorTokens = Colors[theme];
|
||||
const { t } = useI18n();
|
||||
const dispatch = useAppDispatch();
|
||||
const { ensureLoggedIn } = useAuthGuard();
|
||||
const glassAvailable = isLiquidGlassAvailable();
|
||||
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const [joinModalVisible, setJoinModalVisible] = useState(false);
|
||||
const [inviteCodeInput, setInviteCodeInput] = useState('');
|
||||
const [relationshipInput, setRelationshipInput] = useState('');
|
||||
const [isJoining, setIsJoining] = useState(false);
|
||||
const [joinError, setJoinError] = useState<string | null>(null);
|
||||
|
||||
// Redux state
|
||||
const familyGroup = useAppSelector(selectFamilyGroup);
|
||||
|
||||
// Mock user data - in a real app this would come from Redux/Context
|
||||
const userProfile = useAppSelector((state) => state.user.profile);
|
||||
@@ -59,6 +79,61 @@ export default function HealthProfileScreen() {
|
||||
return Math.round((filledCount / totalFields) * 100);
|
||||
}, [userProfile.height, userProfile.weight, userProfile.waistCircumference]);
|
||||
|
||||
// 初始化获取家庭组信息
|
||||
useEffect(() => {
|
||||
dispatch(fetchFamilyGroup());
|
||||
}, [dispatch]);
|
||||
|
||||
// 重置弹窗状态
|
||||
useEffect(() => {
|
||||
if (!joinModalVisible) {
|
||||
setInviteCodeInput('');
|
||||
setRelationshipInput('');
|
||||
setJoinError(null);
|
||||
}
|
||||
}, [joinModalVisible]);
|
||||
|
||||
// 打开加入弹窗
|
||||
const handleOpenJoin = useCallback(async () => {
|
||||
const ok = await ensureLoggedIn();
|
||||
if (!ok) return;
|
||||
setJoinModalVisible(true);
|
||||
}, [ensureLoggedIn]);
|
||||
|
||||
// 提交加入家庭组
|
||||
const handleSubmitJoin = useCallback(async () => {
|
||||
if (isJoining) return;
|
||||
const ok = await ensureLoggedIn();
|
||||
if (!ok) return;
|
||||
|
||||
const code = inviteCodeInput.trim().toUpperCase();
|
||||
const relationship = relationshipInput.trim();
|
||||
|
||||
if (!code) {
|
||||
setJoinError('请输入邀请码');
|
||||
return;
|
||||
}
|
||||
if (!relationship) {
|
||||
setJoinError('请输入与创建者的关系');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsJoining(true);
|
||||
setJoinError(null);
|
||||
|
||||
try {
|
||||
await dispatch(joinFamilyGroup({ inviteCode: code, relationship })).unwrap();
|
||||
await dispatch(fetchFamilyGroup());
|
||||
setJoinModalVisible(false);
|
||||
Toast.success('成功加入家庭组');
|
||||
} catch (error) {
|
||||
const message = typeof error === 'string' ? error : '加入失败,请检查邀请码是否正确';
|
||||
setJoinError(message);
|
||||
} finally {
|
||||
setIsJoining(false);
|
||||
}
|
||||
}, [dispatch, ensureLoggedIn, inviteCodeInput, isJoining, relationshipInput]);
|
||||
|
||||
const gradientColors: [string, string] =
|
||||
theme === 'dark'
|
||||
? ['#1f2230', '#10131e']
|
||||
@@ -107,6 +182,25 @@ export default function HealthProfileScreen() {
|
||||
transparent
|
||||
right={
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
{/* 加入家庭组按钮 - 仅在未加入家庭组时显示 */}
|
||||
{!familyGroup && (
|
||||
<TouchableOpacity activeOpacity={0.85} onPress={handleOpenJoin} style={{ marginRight: 10 }}>
|
||||
{glassAvailable ? (
|
||||
<GlassView
|
||||
style={styles.joinButtonGlass}
|
||||
glassEffectStyle="regular"
|
||||
tintColor="rgba(255,255,255,0.18)"
|
||||
isInteractive
|
||||
>
|
||||
<Text style={styles.joinButtonLabel}>加入</Text>
|
||||
</GlassView>
|
||||
) : (
|
||||
<View style={[styles.joinButtonGlass, styles.joinButtonFallback]}>
|
||||
<Text style={[styles.joinButtonLabel, { color: colorTokens.text }]}>加入</Text>
|
||||
</View>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity style={{ marginRight: 12 }}>
|
||||
<Ionicons name="settings-outline" size={22} color="#1F2937" />
|
||||
</TouchableOpacity>
|
||||
@@ -125,7 +219,10 @@ export default function HealthProfileScreen() {
|
||||
<Image source={{ uri: avatarUrl }} style={styles.miniAvatar} />
|
||||
<Text style={styles.miniAvatarName}>{displayName}</Text>
|
||||
</View>
|
||||
<TouchableOpacity style={styles.addButton}>
|
||||
<TouchableOpacity
|
||||
style={styles.addButton}
|
||||
onPress={() => router.push(ROUTES.HEALTH_FAMILY_INVITE)}
|
||||
>
|
||||
<Ionicons name="add" size={16} color="#6B7280" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
@@ -205,7 +302,44 @@ export default function HealthProfileScreen() {
|
||||
|
||||
</ScrollView>
|
||||
|
||||
{/* Privacy Warning Footer - Removed as requested */}
|
||||
{/* 加入家庭组弹窗 */}
|
||||
<ConfirmationSheet
|
||||
visible={joinModalVisible}
|
||||
onClose={() => setJoinModalVisible(false)}
|
||||
onConfirm={handleSubmitJoin}
|
||||
title="加入家庭组"
|
||||
description="输入家人分享的邀请码,加入家庭健康管理"
|
||||
confirmText={isJoining ? '加入中...' : '加入'}
|
||||
cancelText="取消"
|
||||
loading={isJoining}
|
||||
content={
|
||||
<View style={styles.modalInputWrapper}>
|
||||
<TextInput
|
||||
style={styles.modalInput}
|
||||
placeholder="请输入邀请码"
|
||||
placeholderTextColor="#9ca3af"
|
||||
value={inviteCodeInput}
|
||||
onChangeText={(text) => setInviteCodeInput(text.toUpperCase())}
|
||||
autoCapitalize="characters"
|
||||
autoCorrect={false}
|
||||
keyboardType="default"
|
||||
maxLength={12}
|
||||
/>
|
||||
<TextInput
|
||||
style={[styles.modalInput, { marginTop: 12 }]}
|
||||
placeholder="与创建者的关系(如:配偶、父母、子女)"
|
||||
placeholderTextColor="#9ca3af"
|
||||
value={relationshipInput}
|
||||
onChangeText={setRelationshipInput}
|
||||
autoCorrect={false}
|
||||
maxLength={20}
|
||||
/>
|
||||
{joinError && joinModalVisible ? (
|
||||
<Text style={styles.modalError}>{joinError}</Text>
|
||||
) : null}
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -339,5 +473,45 @@ const styles = StyleSheet.create({
|
||||
textAlign: 'center',
|
||||
lineHeight: 18,
|
||||
},
|
||||
|
||||
joinButtonGlass: {
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 16,
|
||||
minWidth: 60,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: 'rgba(255,255,255,0.45)',
|
||||
},
|
||||
joinButtonLabel: {
|
||||
fontSize: 12,
|
||||
fontWeight: '700',
|
||||
color: '#0f1528',
|
||||
letterSpacing: 0.5,
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
joinButtonFallback: {
|
||||
backgroundColor: 'rgba(255,255,255,0.7)',
|
||||
},
|
||||
modalInputWrapper: {
|
||||
borderRadius: 14,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e7eb',
|
||||
backgroundColor: '#f8fafc',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 10,
|
||||
gap: 6,
|
||||
},
|
||||
modalInput: {
|
||||
paddingVertical: 12,
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
letterSpacing: 0.5,
|
||||
color: '#0f1528',
|
||||
},
|
||||
modalError: {
|
||||
marginTop: 10,
|
||||
fontSize: 12,
|
||||
color: '#ef4444',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -265,9 +265,9 @@ export default function WeightRecordsPage() {
|
||||
<Text style={styles.mainStatUnit}>kg</Text>
|
||||
</View>
|
||||
<View style={styles.totalLossTag}>
|
||||
<Ionicons name={totalWeightLoss <= 0 ? "trending-down" : "trending-up"} size={16} color="#ffffff" />
|
||||
<Ionicons name={totalWeightLoss > 0 ? "trending-down" : "trending-up"} size={16} color="#ffffff" />
|
||||
<Text style={styles.totalLossText}>
|
||||
{totalWeightLoss > 0 ? '+' : ''}{totalWeightLoss.toFixed(1)} kg
|
||||
{totalWeightLoss > 0 ? '-' : totalWeightLoss < 0 ? '+' : ''}{Math.abs(totalWeightLoss).toFixed(1)} kg
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user