feat: 更新心情记录功能及相关组件
- 在心情日历中新增心情圆环展示,显示心情强度 - 修改心情记录编辑页面,支持使用图标替代表情 - 优化心情类型配置,使用图片资源替代原有表情 - 新增多种心情图标,丰富用户选择 - 更新相关样式,提升用户体验和界面美观性 - 更新文档,详细描述新功能和使用方法
This commit is contained in:
@@ -25,14 +25,13 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { getTabBarBottomPadding } from '@/constants/TabBar';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useAppSelector } from '@/hooks/redux';
|
||||
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { useCosUpload } from '@/hooks/useCosUpload';
|
||||
import { deleteConversation, getConversationDetail, listConversations, type AiConversationListItem } from '@/services/aiCoach';
|
||||
import { loadAiCoachSessionCache, saveAiCoachSessionCache } from '@/services/aiCoachSession';
|
||||
import { api, getAuthToken, postTextStream } from '@/services/api';
|
||||
import { updateProfile } from '@/store/userSlice';
|
||||
import dayjs from 'dayjs';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { ActionSheet } from '../../components/ui/ActionSheet';
|
||||
@@ -284,9 +283,9 @@ export default function CoachScreen() {
|
||||
{ key: 'weight', label: '#记体重', action: () => insertWeightInputCard() },
|
||||
{ key: 'diet', label: '#记饮食', action: () => insertDietInputCard() },
|
||||
{ key: 'dietPlan', label: '#饮食方案', action: () => insertDietPlanCard() },
|
||||
{
|
||||
key: 'mood',
|
||||
label: '#记心情',
|
||||
{
|
||||
key: 'mood',
|
||||
label: '#记心情',
|
||||
action: () => {
|
||||
if (Platform.OS === 'ios') {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
@@ -1345,7 +1344,7 @@ export default function CoachScreen() {
|
||||
{/* 标题部分 */}
|
||||
<View style={styles.dietPlanHeader}>
|
||||
<View style={styles.dietPlanTitleContainer}>
|
||||
<Ionicons name="restaurant-outline" size={20} color={theme.success} />
|
||||
<Ionicons name="restaurant-outline" size={20} color={theme.success} />
|
||||
<Text style={styles.dietPlanTitle}>我的饮食方案</Text>
|
||||
</View>
|
||||
<Text style={styles.dietPlanSubtitle}>MY DIET PLAN</Text>
|
||||
@@ -1774,7 +1773,7 @@ export default function CoachScreen() {
|
||||
});
|
||||
|
||||
// 发送饮食记录消息
|
||||
const dietMsg = `记录了今日饮食:${trimmedText}`;
|
||||
const dietMsg = `#记饮食:${trimmedText}`;
|
||||
await sendStream(dietMsg);
|
||||
} catch (e: any) {
|
||||
console.error('[DIET] 提交饮食记录失败:', e);
|
||||
@@ -1865,13 +1864,6 @@ export default function CoachScreen() {
|
||||
<TouchableOpacity
|
||||
style={styles.usageCountContainer}
|
||||
onPress={() => {
|
||||
// 临时测试:切换VIP状态
|
||||
const dispatch = useAppDispatch();
|
||||
dispatch(updateProfile({
|
||||
isVip: !userProfile?.isVip,
|
||||
freeUsageCount: userProfile?.isVip ? 3 : 5,
|
||||
maxUsageCount: userProfile?.isVip ? 5 : 10
|
||||
}));
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
@@ -1960,15 +1952,15 @@ export default function CoachScreen() {
|
||||
contentContainerStyle={{ paddingHorizontal: 6, gap: 8 }}
|
||||
>
|
||||
{chips.map((c) => (
|
||||
<TouchableOpacity
|
||||
key={c.key}
|
||||
<TouchableOpacity
|
||||
key={c.key}
|
||||
style={[
|
||||
styles.chip,
|
||||
{
|
||||
borderColor: c.key === 'mood' ? `${theme.success}40` : `${theme.primary}40`,
|
||||
backgroundColor: c.key === 'mood' ? `${theme.success}15` : `${theme.primary}15`
|
||||
styles.chip,
|
||||
{
|
||||
borderColor: c.key === 'mood' ? `${theme.success}40` : `${theme.primary}40`,
|
||||
backgroundColor: c.key === 'mood' ? `${theme.success}15` : `${theme.primary}15`
|
||||
}
|
||||
]}
|
||||
]}
|
||||
onPress={c.action}
|
||||
>
|
||||
<Text style={[styles.chipText, { color: c.key === 'mood' ? theme.success : theme.text }]}>{c.label}</Text>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useGlobalDialog } from '@/components/ui/DialogProvider';
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { TAB_BAR_BOTTOM_OFFSET, TAB_BAR_HEIGHT } from '@/constants/TabBar';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { clearErrors, createGoal } from '@/store/goalsSlice';
|
||||
import { clearErrors as clearTaskErrors, fetchTasks, loadMoreTasks } from '@/store/tasksSlice';
|
||||
@@ -18,7 +19,6 @@ import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import dayjs from 'dayjs';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { useRouter } from 'expo-router';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Alert, FlatList, Image, RefreshControl, SafeAreaView, StatusBar, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
@@ -26,7 +26,9 @@ export default function GoalsScreen() {
|
||||
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
|
||||
const colorTokens = Colors[theme];
|
||||
const dispatch = useAppDispatch();
|
||||
const router = useRouter();
|
||||
|
||||
const { pushIfAuthedElseLogin, isLoggedIn } = useAuthGuard();
|
||||
|
||||
const { showConfirm } = useGlobalDialog();
|
||||
|
||||
// Redux状态
|
||||
@@ -58,8 +60,11 @@ export default function GoalsScreen() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
console.log('useFocusEffect - loading tasks');
|
||||
loadTasks();
|
||||
checkAndShowGuide();
|
||||
|
||||
if (isLoggedIn) {
|
||||
loadTasks();
|
||||
checkAndShowGuide();
|
||||
}
|
||||
}, [dispatch])
|
||||
);
|
||||
|
||||
@@ -147,10 +152,10 @@ export default function GoalsScreen() {
|
||||
try {
|
||||
await dispatch(createGoal(goalData)).unwrap();
|
||||
setShowCreateModal(false);
|
||||
|
||||
|
||||
// 获取用户名
|
||||
const userName = userProfile?.name || '小海豹';
|
||||
|
||||
|
||||
// 创建目标成功后,设置定时推送
|
||||
try {
|
||||
const notificationIds = await GoalNotificationHelpers.scheduleGoalNotifications(
|
||||
@@ -165,13 +170,13 @@ export default function GoalsScreen() {
|
||||
},
|
||||
userName
|
||||
);
|
||||
|
||||
|
||||
console.log(`目标"${goalData.title}"的定时推送已创建,通知ID:`, notificationIds);
|
||||
} catch (notificationError) {
|
||||
console.error('创建目标定时推送失败:', notificationError);
|
||||
// 通知创建失败不影响目标创建的成功
|
||||
}
|
||||
|
||||
|
||||
// 使用确认弹窗显示成功消息
|
||||
showConfirm(
|
||||
{
|
||||
@@ -187,7 +192,7 @@ export default function GoalsScreen() {
|
||||
console.log('用户确认了目标创建成功');
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// 创建目标后重新加载任务列表
|
||||
loadTasks();
|
||||
} catch (error) {
|
||||
@@ -199,7 +204,7 @@ export default function GoalsScreen() {
|
||||
|
||||
// 导航到任务列表页面
|
||||
const handleNavigateToTasks = () => {
|
||||
router.push('/task-list');
|
||||
pushIfAuthedElseLogin('/task-list');
|
||||
};
|
||||
|
||||
// 计算各状态的任务数量
|
||||
@@ -213,7 +218,7 @@ export default function GoalsScreen() {
|
||||
// 根据筛选条件过滤任务,并将已完成的任务放到最后
|
||||
const filteredTasks = React.useMemo(() => {
|
||||
let filtered: TaskListItem[] = [];
|
||||
|
||||
|
||||
switch (selectedFilter) {
|
||||
case 'pending':
|
||||
filtered = tasks.filter(task => task.status === 'pending');
|
||||
@@ -228,7 +233,7 @@ export default function GoalsScreen() {
|
||||
filtered = tasks;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// 对所有筛选结果进行排序:已完成的任务放到最后
|
||||
return [...filtered].sort((a, b) => {
|
||||
// 如果a已完成而b未完成,a排在后面
|
||||
@@ -271,7 +276,7 @@ export default function GoalsScreen() {
|
||||
const renderEmptyState = () => {
|
||||
let title = '暂无任务';
|
||||
let subtitle = '创建目标后,系统会自动生成相应的任务';
|
||||
|
||||
|
||||
if (selectedFilter === 'pending') {
|
||||
title = '暂无待完成的任务';
|
||||
subtitle = '当前没有待完成的任务';
|
||||
@@ -282,7 +287,7 @@ export default function GoalsScreen() {
|
||||
title = '暂无已跳过的任务';
|
||||
subtitle = '跳过一些任务后,它们会显示在这里';
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<View style={styles.emptyState}>
|
||||
<Image
|
||||
@@ -349,19 +354,19 @@ export default function GoalsScreen() {
|
||||
{/* 标题区域 */}
|
||||
<View style={styles.header}>
|
||||
<View>
|
||||
<Text style={[styles.pageTitle, { color: '#FFFFFF' }]}>
|
||||
今日目标
|
||||
</Text>
|
||||
<Text style={[styles.pageTitle2, { color: '#FFFFFF' }]}>
|
||||
让我们检查你的目标!
|
||||
</Text>
|
||||
<Text style={[styles.pageTitle, { color: '#FFFFFF' }]}>
|
||||
今日目标
|
||||
</Text>
|
||||
<Text style={[styles.pageTitle2, { color: '#FFFFFF' }]}>
|
||||
让我们检查你的目标!
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 任务进度卡片 */}
|
||||
<View >
|
||||
<TaskProgressCard
|
||||
tasks={tasks}
|
||||
<TaskProgressCard
|
||||
tasks={tasks}
|
||||
headerButtons={
|
||||
<View style={styles.cardHeaderButtons}>
|
||||
<TouchableOpacity
|
||||
@@ -436,7 +441,7 @@ export default function GoalsScreen() {
|
||||
|
||||
{/* 开发测试按钮 */}
|
||||
<GuideTestButton visible={__DEV__} />
|
||||
|
||||
|
||||
{/* 目标通知测试按钮 */}
|
||||
{__DEV__ && (
|
||||
<TouchableOpacity
|
||||
|
||||
@@ -214,7 +214,7 @@ export default function ExploreScreen() {
|
||||
setSleepDuration(data.sleepDuration);
|
||||
// 更新健身圆环数据
|
||||
setFitnessRingsData({
|
||||
activeCalories: data.activeCalories,
|
||||
activeCalories: data.activeEnergyBurned,
|
||||
activeCaloriesGoal: data.activeCaloriesGoal,
|
||||
exerciseMinutes: data.exerciseMinutes,
|
||||
exerciseMinutesGoal: data.exerciseMinutesGoal,
|
||||
@@ -231,7 +231,7 @@ export default function ExploreScreen() {
|
||||
// 设置血氧饱和度和心率数据
|
||||
setOxygenSaturation(data.oxygenSaturation ?? null);
|
||||
setHeartRate(data.heartRate ?? null);
|
||||
|
||||
|
||||
console.log('血氧饱和度数据:', data.oxygenSaturation);
|
||||
console.log('心率数据:', data.heartRate);
|
||||
|
||||
@@ -300,20 +300,20 @@ export default function ExploreScreen() {
|
||||
}, [selectedIndex])
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// 注册任务
|
||||
registerTask({
|
||||
id: 'health-data-task',
|
||||
name: 'health-data-task',
|
||||
handler: async () => {
|
||||
try {
|
||||
await loadHealthData();
|
||||
} catch (error) {
|
||||
console.error('健康数据任务执行失败:', error);
|
||||
}
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
// useEffect(() => {
|
||||
// // 注册任务
|
||||
// registerTask({
|
||||
// id: 'health-data-task',
|
||||
// name: 'health-data-task',
|
||||
// handler: async () => {
|
||||
// try {
|
||||
// await loadHealthData();
|
||||
// } catch (error) {
|
||||
// console.error('健康数据任务执行失败:', error);
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
// }, []);
|
||||
|
||||
// 日期点击时,加载对应日期数据
|
||||
const onSelectDate = (index: number, date: Date) => {
|
||||
@@ -474,8 +474,8 @@ export default function ExploreScreen() {
|
||||
oxygenSaturation={oxygenSaturation}
|
||||
/>
|
||||
{/* 测试按钮 - 开发时使用 */}
|
||||
<Text
|
||||
style={styles.testButton}
|
||||
<Text
|
||||
style={styles.testButton}
|
||||
onPress={testOxygenData}
|
||||
>
|
||||
测试血氧数据
|
||||
|
||||
Reference in New Issue
Block a user