feat: 优化 AI 教练聊天和打卡功能
- 在 AI 教练聊天界面中添加会话缓存功能,支持冷启动时恢复聊天记录 - 实现轻量防抖机制,确保会话变动时及时保存缓存 - 在打卡功能中集成按月加载打卡记录,提升用户体验 - 更新 Redux 状态管理,支持打卡记录的按月加载和缓存 - 新增打卡日历页面,允许用户查看每日打卡记录 - 优化样式以适应新功能的展示和交互
This commit is contained in:
@@ -10,6 +10,7 @@ import { ensureHealthPermissions, fetchHealthDataForDate, fetchTodayHealthData }
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { useRouter } from 'expo-router';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
SafeAreaView,
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
export default function ExploreScreen() {
|
||||
const router = useRouter();
|
||||
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
|
||||
const colorTokens = Colors[theme];
|
||||
const stepGoal = useAppSelector((s) => s.user.profile?.dailyStepsGoal) ?? 2000;
|
||||
@@ -143,8 +145,13 @@ export default function ExploreScreen() {
|
||||
})}
|
||||
</ScrollView>
|
||||
|
||||
{/* 今日报告 标题 */}
|
||||
<Text style={styles.sectionTitle}>今日报告</Text>
|
||||
{/* 打卡入口 */}
|
||||
<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,保持静默刷新提升体验 */}
|
||||
|
||||
|
||||
@@ -172,11 +172,12 @@ export default function HomeScreen() {
|
||||
level="初学者"
|
||||
progress={0}
|
||||
/>
|
||||
<Pressable onPress={() => pushIfAuthedElseLogin('/challenge')}>
|
||||
{/* 原“每周打卡”改为进入打卡日历 */}
|
||||
<Pressable onPress={() => router.push('/checkin/calendar')}>
|
||||
<PlanCard
|
||||
image={'https://plates-1251306435.cos.ap-guangzhou.myqcloud.com/images/Image30play@2x.png'}
|
||||
title="每周打卡"
|
||||
subtitle="养成训练习惯,练出好身材"
|
||||
title="打卡日历"
|
||||
subtitle="查看每日打卡记录(点亮日期)"
|
||||
progress={0.75}
|
||||
/>
|
||||
</Pressable>
|
||||
|
||||
@@ -72,15 +72,26 @@ export default function PersonalScreen() {
|
||||
|
||||
useEffect(() => { load(); }, []);
|
||||
useFocusEffect(React.useCallback(() => {
|
||||
// 每次聚焦时从后端拉取最新用户资料
|
||||
// 聚焦时只拉后端,避免与本地 load 循环触发
|
||||
dispatch(fetchMyProfile());
|
||||
load();
|
||||
return () => { };
|
||||
}, [dispatch]));
|
||||
useEffect(() => {
|
||||
if (userProfileFromRedux) {
|
||||
setProfile(userProfileFromRedux);
|
||||
}
|
||||
const r = userProfileFromRedux as any;
|
||||
if (!r) return;
|
||||
setProfile((prev) => {
|
||||
const next = { ...prev } as any;
|
||||
const nameNext = (r.name && String(r.name)) || prev.name;
|
||||
const genderNext = (r.gender === 'male' || r.gender === 'female') ? r.gender : (prev.gender ?? '');
|
||||
const avatarUriNext = typeof r.avatar === 'string' && (r.avatar.startsWith('http') || r.avatar.startsWith('data:'))
|
||||
? r.avatar
|
||||
: prev.avatarUri;
|
||||
let changed = false;
|
||||
if (next.name !== nameNext) { next.name = nameNext; changed = true; }
|
||||
if (next.gender !== genderNext) { next.gender = genderNext; changed = true; }
|
||||
if (next.avatarUri !== avatarUriNext) { next.avatarUri = avatarUriNext; changed = true; }
|
||||
return changed ? next : prev;
|
||||
});
|
||||
}, [userProfileFromRedux]);
|
||||
|
||||
const formatHeight = () => {
|
||||
|
||||
Reference in New Issue
Block a user