feat: 更新隐私同意弹窗和应用名称

- 将应用名称修改为“每日普拉提”,提升品牌识别度
- 新增隐私同意弹窗,确保用户在使用应用前同意隐私政策
- 更新 Redux 状态管理,添加隐私同意状态的处理
- 优化用户信息页面,确保体重和身高的格式化显示
- 更新今日训练页面标题为“快速训练”,提升用户体验
- 添加开发工具函数,便于测试隐私同意功能
This commit is contained in:
2025-08-15 20:44:06 +08:00
parent 6b6c4fdbad
commit 97e89b9bf0
15 changed files with 326 additions and 26 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 = () => {

View File

@@ -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() {

View File

@@ -427,7 +427,7 @@ export default function TodayWorkoutScreen() {
<SafeAreaView style={styles.contentWrapper}>
<HeaderBar
title="今日训练"
title="快速训练"
onBack={() => router.back()}
withSafeTop={false}
transparent={true}