feat: 更新隐私同意弹窗和应用名称
- 将应用名称修改为“每日普拉提”,提升品牌识别度 - 新增隐私同意弹窗,确保用户在使用应用前同意隐私政策 - 更新 Redux 状态管理,添加隐私同意状态的处理 - 优化用户信息页面,确保体重和身高的格式化显示 - 更新今日训练页面标题为“快速训练”,提升用户体验 - 添加开发工具函数,便于测试隐私同意功能
This commit is contained in:
@@ -29,6 +29,7 @@ export default function ExploreScreen() {
|
||||
const colorTokens = Colors[theme];
|
||||
const stepGoal = useAppSelector((s) => s.user.profile?.dailyStepsGoal) ?? 2000;
|
||||
const userProfile = useAppSelector((s) => s.user.profile);
|
||||
|
||||
// 使用 dayjs:当月日期与默认选中“今天”
|
||||
const days = getMonthDaysZh();
|
||||
const [selectedIndex, setSelectedIndex] = useState(getTodayIndexInMonth());
|
||||
@@ -163,16 +164,6 @@ export default function ExploreScreen() {
|
||||
})}
|
||||
</ScrollView>
|
||||
|
||||
{/* 打卡入口 */}
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginTop: 24, marginBottom: 8 }}>
|
||||
<Text style={styles.sectionTitle}>每日报告</Text>
|
||||
<TouchableOpacity onPress={() => router.push('/checkin/calendar')} accessibilityRole="button">
|
||||
<Text style={{ color: '#6B7280', fontWeight: '700' }}>查看打卡日历</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* 取消卡片内 loading,保持静默刷新提升体验 */}
|
||||
|
||||
{/* 指标行:左大卡(训练时间),右两小卡(消耗卡路里、步数) */}
|
||||
<View style={styles.metricsRow}>
|
||||
<View style={[styles.trainingCard, styles.metricsLeft]}>
|
||||
@@ -230,8 +221,8 @@ export default function ExploreScreen() {
|
||||
|
||||
{/* BMI 指数卡片 */}
|
||||
<BMICard
|
||||
weight={userProfile?.weight}
|
||||
height={userProfile?.height}
|
||||
weight={userProfile?.weight ? parseFloat(userProfile.weight) : undefined}
|
||||
height={userProfile?.height ? parseFloat(userProfile.height) : undefined}
|
||||
/>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
|
||||
@@ -301,7 +301,10 @@ export default function HomeScreen() {
|
||||
>
|
||||
<View style={styles.featureIconWrapper}>
|
||||
<View style={styles.featureIconPlaceholder}>
|
||||
<ThemedText style={styles.featureIconText}>💪</ThemedText>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/iconPlan.png')}
|
||||
style={styles.featureIconImage}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<ThemedText style={styles.featureTitle}>计划管理</ThemedText>
|
||||
|
||||
@@ -45,12 +45,12 @@ export default function PersonalScreen() {
|
||||
// 数据格式化函数
|
||||
const formatHeight = () => {
|
||||
if (userProfile.height == null) return '--';
|
||||
return `${Math.round(userProfile.height)}cm`;
|
||||
return `${parseFloat(userProfile.height).toFixed(1)}cm`;
|
||||
};
|
||||
|
||||
const formatWeight = () => {
|
||||
if (userProfile.weight == null) return '--';
|
||||
return `${Math.round(userProfile.weight * 10) / 10}kg`;
|
||||
return `${parseFloat(userProfile.weight).toFixed(1)}kg`;
|
||||
};
|
||||
|
||||
const formatAge = () => {
|
||||
|
||||
@@ -4,22 +4,60 @@ import { Stack } from 'expo-router';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import 'react-native-reanimated';
|
||||
|
||||
import { useAppDispatch } from '@/hooks/redux';
|
||||
import PrivacyConsentModal from '@/components/PrivacyConsentModal';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { clearAiCoachSessionCache } from '@/services/aiCoachSession';
|
||||
import { store } from '@/store';
|
||||
import { rehydrateUser } from '@/store/userSlice';
|
||||
import { rehydrateUser, setPrivacyAgreed } from '@/store/userSlice';
|
||||
import React from 'react';
|
||||
import RNExitApp from 'react-native-exit-app';
|
||||
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
function Bootstrapper({ children }: { children: React.ReactNode }) {
|
||||
const dispatch = useAppDispatch();
|
||||
const { privacyAgreed } = useAppSelector((state) => state.user);
|
||||
const [showPrivacyModal, setShowPrivacyModal] = React.useState(false);
|
||||
const [userDataLoaded, setUserDataLoaded] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
dispatch(rehydrateUser());
|
||||
const loadUserData = async () => {
|
||||
await dispatch(rehydrateUser());
|
||||
setUserDataLoaded(true);
|
||||
};
|
||||
|
||||
loadUserData();
|
||||
// 冷启动时清空 AI 教练会话缓存
|
||||
clearAiCoachSessionCache();
|
||||
}, [dispatch]);
|
||||
return <>{children}</>;
|
||||
|
||||
React.useEffect(() => {
|
||||
// 当用户数据加载完成后,检查是否需要显示隐私同意弹窗
|
||||
if (userDataLoaded && !privacyAgreed) {
|
||||
setShowPrivacyModal(true);
|
||||
}
|
||||
}, [userDataLoaded, privacyAgreed]);
|
||||
|
||||
const handlePrivacyAgree = () => {
|
||||
dispatch(setPrivacyAgreed());
|
||||
setShowPrivacyModal(false);
|
||||
};
|
||||
|
||||
const handlePrivacyDisagree = () => {
|
||||
RNExitApp.exitApp();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<PrivacyConsentModal
|
||||
visible={showPrivacyModal}
|
||||
onAgree={handlePrivacyAgree}
|
||||
onDisagree={handlePrivacyDisagree}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function RootLayout() {
|
||||
|
||||
@@ -427,7 +427,7 @@ export default function TodayWorkoutScreen() {
|
||||
|
||||
<SafeAreaView style={styles.contentWrapper}>
|
||||
<HeaderBar
|
||||
title="今日训练"
|
||||
title="快速训练"
|
||||
onBack={() => router.back()}
|
||||
withSafeTop={false}
|
||||
transparent={true}
|
||||
|
||||
Reference in New Issue
Block a user