feat: 实现目标通知功能及相关组件

- 新增目标通知功能,支持根据用户创建目标时选择的频率和开始时间自动创建本地定时推送通知
- 实现每日、每周和每月的重复类型,用户可自定义选择提醒时间和重复规则
- 集成目标通知测试组件,方便开发者测试不同类型的通知
- 更新相关文档,详细描述目标通知功能的实现和使用方法
- 优化目标页面,确保用户体验和界面一致性
This commit is contained in:
2025-08-23 17:13:04 +08:00
parent 4382fb804f
commit 20a244e375
15 changed files with 957 additions and 156 deletions

View File

@@ -1,13 +1,13 @@
import CreateGoalModal from '@/components/CreateGoalModal';
import { DateSelector } from '@/components/DateSelector';
import { GoalItem } from '@/components/GoalCard';
import { GoalCarousel } from '@/components/GoalCarousel';
import { TimeTabSelector, TimeTabType } from '@/components/TimeTabSelector';
import { TimelineSchedule } from '@/components/TimelineSchedule';
import { TodoItem } from '@/components/TodoCard';
import { TodoCarousel } from '@/components/TodoCarousel';
import { Colors } from '@/constants/Colors';
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
import { useColorScheme } from '@/hooks/useColorScheme';
import { clearErrors, completeGoal, createGoal, fetchGoals } from '@/store/goalsSlice';
import { clearErrors, createGoal, fetchGoals } from '@/store/goalsSlice';
import { CreateGoalRequest, GoalListItem } from '@/types/goals';
import { getMonthDaysZh, getMonthTitleZh, getTodayIndexInMonth } from '@/utils/date';
import { useFocusEffect } from '@react-navigation/native';
@@ -20,8 +20,8 @@ import { Alert, SafeAreaView, ScrollView, StatusBar, StyleSheet, Text, Touchable
dayjs.extend(isBetween);
// 将目标转换为TodoItem的辅助函数
const goalToTodoItem = (goal: GoalListItem): TodoItem => {
// 将目标转换为GoalItem的辅助函数
const goalToGoalItem = (goal: GoalListItem): GoalItem => {
return {
id: goal.id,
title: goal.title,
@@ -29,7 +29,6 @@ const goalToTodoItem = (goal: GoalListItem): TodoItem => {
time: dayjs().startOf('day').add(goal.startTime, 'minute').toISOString() || '',
category: getCategoryFromGoal(goal.category),
priority: getPriorityFromGoal(goal.priority),
isCompleted: goal.status === 'completed',
};
};
@@ -43,8 +42,8 @@ const getRepeatTypeLabel = (repeatType: string): string => {
}
};
// 从目标分类获取TodoItem分类
const getCategoryFromGoal = (category?: string): TodoItem['category'] => {
// 从目标分类获取GoalItem分类
const getCategoryFromGoal = (category?: string): GoalItem['category'] => {
if (!category) return 'personal';
if (category.includes('运动') || category.includes('健身')) return 'workout';
if (category.includes('工作')) return 'work';
@@ -53,8 +52,8 @@ const getCategoryFromGoal = (category?: string): TodoItem['category'] => {
return 'personal';
};
// 从目标优先级获取TodoItem优先级
const getPriorityFromGoal = (priority: number): TodoItem['priority'] => {
// 从目标优先级获取GoalItem优先级
const getPriorityFromGoal = (priority: number): GoalItem['priority'] => {
if (priority >= 8) return 'high';
if (priority >= 5) return 'medium';
return 'low';
@@ -206,8 +205,8 @@ export default function GoalsDetailScreen() {
}
};
// 将目标转换为TodoItem数据
const todayTodos = useMemo(() => {
// 将目标转换为GoalItem数据
const todayGoals = useMemo(() => {
const today = dayjs();
const activeGoals = goals.filter(goal =>
goal.status === 'active' &&
@@ -215,7 +214,7 @@ export default function GoalsDetailScreen() {
(goal.repeatType === 'weekly' && today.day() !== 0) ||
(goal.repeatType === 'monthly' && today.date() <= 28))
);
return activeGoals.map(goalToTodoItem);
return activeGoals.map(goalToGoalItem);
}, [goals]);
// 将目标转换为时间轴事件数据
@@ -251,25 +250,11 @@ export default function GoalsDetailScreen() {
console.log('filteredTimelineEvents', filteredTimelineEvents);
const handleTodoPress = (item: TodoItem) => {
const handleGoalPress = (item: GoalItem) => {
console.log('Goal pressed:', item.title);
// 这里可以导航到目标详情页面
};
const handleToggleComplete = async (item: TodoItem) => {
try {
await dispatch(completeGoal({
goalId: item.id,
completionData: {
completionCount: 1,
notes: '通过待办卡片完成'
}
})).unwrap();
} catch (error) {
Alert.alert('错误', '记录完成失败');
}
};
const handleEventPress = (event: any) => {
console.log('Event pressed:', event.title);
// 这里可以处理时间轴事件点击
@@ -318,11 +303,10 @@ export default function GoalsDetailScreen() {
</TouchableOpacity>
</View>
{/* 今日待办事项卡片 */}
<TodoCarousel
todos={todayTodos}
onTodoPress={handleTodoPress}
onToggleComplete={handleToggleComplete}
{/* 今日目标卡片 */}
<GoalCarousel
goals={todayGoals}
onGoalPress={handleGoalPress}
/>
{/* 时间筛选选项卡 */}