import CelebrationAnimation, { CelebrationAnimationRef } from '@/components/CelebrationAnimation'; import GoalTemplateModal from '@/components/GoalTemplateModal'; import { GoalsPageGuide } from '@/components/GoalsPageGuide'; import { GuideTestButton } from '@/components/GuideTestButton'; import { TaskCard } from '@/components/TaskCard'; import { TaskFilterTabs, TaskFilterType } from '@/components/TaskFilterTabs'; import { TaskProgressCard } from '@/components/TaskProgressCard'; import { CreateGoalModal } from '@/components/model/CreateGoalModal'; import { useGlobalDialog } from '@/components/ui/DialogProvider'; import { Colors } from '@/constants/Colors'; import { TAB_BAR_BOTTOM_OFFSET, TAB_BAR_HEIGHT } from '@/constants/TabBar'; import { GoalTemplate } from '@/constants/goalTemplates'; 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'; import { CreateGoalRequest, TaskListItem } from '@/types/goals'; import { checkGuideCompleted, markGuideCompleted } from '@/utils/guideHelpers'; import { GoalNotificationHelpers } from '@/utils/notificationHelpers'; import MaterialIcons from '@expo/vector-icons/MaterialIcons'; import { useFocusEffect } from '@react-navigation/native'; import dayjs from 'dayjs'; import * as Haptics from 'expo-haptics'; import { LinearGradient } from 'expo-linear-gradient'; import Lottie from 'lottie-react-native'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { Alert, FlatList, Image, RefreshControl, SafeAreaView, StatusBar, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; export default function GoalsScreen() { const theme = (useColorScheme() ?? 'light') as 'light' | 'dark'; const colorTokens = Colors[theme]; const dispatch = useAppDispatch(); const { pushIfAuthedElseLogin, isLoggedIn } = useAuthGuard(); const { showConfirm } = useGlobalDialog(); // Redux状态 const { tasks, tasksLoading, tasksError, tasksPagination, completeError, skipError, } = useAppSelector((state) => state.tasks); const { createLoading, createError } = useAppSelector((state) => state.goals); const userProfile = useAppSelector((state) => state.user.profile); const [showTemplateModal, setShowTemplateModal] = useState(false); const [showCreateModal, setShowCreateModal] = useState(false); const [refreshing, setRefreshing] = useState(false); const [selectedFilter, setSelectedFilter] = useState('all'); const [modalKey, setModalKey] = useState(0); // 用于强制重新渲染弹窗 const [showGuide, setShowGuide] = useState(false); // 控制引导显示 const [selectedTemplateData, setSelectedTemplateData] = useState | undefined>(); // 庆祝动画引用 const celebrationAnimationRef = useRef(null); // 页面聚焦时重新加载数据 useFocusEffect( useCallback(() => { console.log('useFocusEffect - loading tasks isLoggedIn', isLoggedIn); if (isLoggedIn) { loadTasks(); checkAndShowGuide(); } }, [dispatch, isLoggedIn]) ); // 检查并显示用户引导 const checkAndShowGuide = async () => { try { const hasCompletedGuide = await checkGuideCompleted('GOALS_PAGE'); if (!hasCompletedGuide) { // 延迟显示引导,确保页面完全加载 setTimeout(() => { setShowGuide(true); }, 1000); } } catch (error) { console.error('检查引导状态失败:', error); } }; // 加载任务列表 const loadTasks = async () => { try { await dispatch(fetchTasks({ startDate: dayjs().startOf('day').toISOString(), endDate: dayjs().endOf('day').toISOString(), })).unwrap(); } catch (error) { console.error('Failed to load tasks:', error); } }; // 下拉刷新 const onRefresh = async () => { setRefreshing(true); try { if (!isLoggedIn) return await loadTasks(); } finally { setRefreshing(false); } }; // 加载更多任务 const handleLoadMoreTasks = async () => { if (!isLoggedIn) return if (tasksPagination.hasMore && !tasksLoading) { try { await dispatch(loadMoreTasks()).unwrap(); } catch (error) { console.error('Failed to load more tasks:', error); } } }; // 处理错误提示 useEffect(() => { if (tasksError) { Alert.alert('错误', tasksError); dispatch(clearTaskErrors()); } if (createError) { Alert.alert('创建失败', createError); dispatch(clearErrors()); } if (completeError) { Alert.alert('完成失败', completeError); dispatch(clearTaskErrors()); } if (skipError) { Alert.alert('跳过失败', skipError); dispatch(clearTaskErrors()); } }, [tasksError, createError, completeError, skipError, dispatch]); // 重置弹窗表单数据 const handleModalSuccess = () => { // 不需要在这里改变 modalKey,因为弹窗已经关闭了 // 下次打开时会自动使用新的 modalKey setSelectedTemplateData(undefined); }; // 处理模板选择 const handleSelectTemplate = (template: GoalTemplate) => { setSelectedTemplateData(template.data); setShowTemplateModal(false); setModalKey(prev => prev + 1); setShowCreateModal(true); }; // 处理创建自定义目标 const handleCreateCustomGoal = () => { setSelectedTemplateData(undefined); setShowTemplateModal(false); setModalKey(prev => prev + 1); setShowCreateModal(true); }; // 打开模板选择弹窗 const handleOpenTemplateModal = () => { setSelectedTemplateData(undefined); setShowTemplateModal(true); }; // 创建目标处理函数 const handleCreateGoal = async (goalData: CreateGoalRequest) => { try { await dispatch(createGoal(goalData)).unwrap(); setShowCreateModal(false); // 获取用户名 const userName = userProfile?.name || '主人'; // 创建目标成功后,设置定时推送 try { if (goalData.hasReminder) { const notificationIds = await GoalNotificationHelpers.scheduleGoalNotifications( { title: goalData.title, repeatType: goalData.repeatType, frequency: goalData.frequency, hasReminder: goalData.hasReminder, reminderTime: goalData.reminderTime, customRepeatRule: goalData.customRepeatRule, startTime: goalData.startTime, }, userName ); console.log(`目标"${goalData.title}"的定时推送已创建,通知ID:`, notificationIds); } } catch (notificationError) { console.error('创建目标定时推送失败:', notificationError); // 通知创建失败不影响目标创建的成功 } // 使用确认弹窗显示成功消息 showConfirm( { title: '目标创建成功', message: '恭喜!您的目标已成功创建。系统将自动生成相应的任务,帮助您实现目标。', confirmText: '确定', cancelText: '', icon: 'checkmark-circle', iconColor: '#10B981', }, () => { // 用户点击确定后的回调 console.log('用户确认了目标创建成功'); } ); // 创建目标后重新加载任务列表 loadTasks(); } catch (error) { // 错误已在useEffect中处理 } }; // 导航到任务列表页面 const handleNavigateToTasks = () => { pushIfAuthedElseLogin('/task-list'); }; // 计算各状态的任务数量 const taskCounts = { all: tasks.length, pending: tasks.filter(task => task.status === 'pending').length, completed: tasks.filter(task => task.status === 'completed').length, skipped: tasks.filter(task => task.status === 'skipped').length, }; // 根据筛选条件过滤任务,并将已完成的任务放到最后 const filteredTasks = React.useMemo(() => { let filtered: TaskListItem[] = []; switch (selectedFilter) { case 'pending': filtered = tasks.filter(task => task.status === 'pending'); break; case 'completed': filtered = tasks.filter(task => task.status === 'completed'); break; case 'skipped': filtered = tasks.filter(task => task.status === 'skipped'); break; default: filtered = tasks; break; } // 对所有筛选结果进行排序:已完成的任务放到最后 return [...filtered].sort((a, b) => { // 如果a已完成而b未完成,a排在后面 if (a.status === 'completed' && b.status !== 'completed') { return 1; } // 如果b已完成而a未完成,b排在后面 if (b.status === 'completed' && a.status !== 'completed') { return -1; } // 如果都已完成或都未完成,保持原有顺序 return 0; }); }, [tasks, selectedFilter]); // 处理筛选变化 const handleFilterChange = (filter: TaskFilterType) => { setSelectedFilter(filter); }; // 处理引导完成 const handleGuideComplete = async () => { try { await markGuideCompleted('GOALS_PAGE'); setShowGuide(false); } catch (error) { console.error('保存引导状态失败:', error); setShowGuide(false); } }; // 处理任务完成 const handleTaskCompleted = (completedTask: TaskListItem) => { // 触发震动反馈 if (process.env.EXPO_OS === 'ios') { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); } // 播放庆祝动画 celebrationAnimationRef.current?.play(); console.log(`任务 "${completedTask.title}" 已完成,播放庆祝动画`); }; // 渲染任务项 const renderTaskItem = ({ item }: { item: TaskListItem }) => ( ); // 渲染空状态 const renderEmptyState = () => { // 未登录状态下的引导 if (!isLoggedIn) { return ( {/* 清新的图标设计 */} {/* 主标题 */} 开启您的健康之旅 {/* 副标题 */} 登录后即可创建个人目标,让我们一起建立健康的生活习惯 {/* 登录按钮 */} pushIfAuthedElseLogin('/goals')} > 立即登录 ); } // 已登录但无任务的状态 let title = '暂无任务'; let subtitle = '创建目标后,系统会自动生成相应的任务'; if (selectedFilter === 'pending') { title = '暂无待完成的任务'; subtitle = '当前没有待完成的任务'; } else if (selectedFilter === 'completed') { title = '暂无已完成的任务'; subtitle = '完成一些任务后,它们会显示在这里'; } else if (selectedFilter === 'skipped') { title = '暂无已跳过的任务'; subtitle = '跳过一些任务后,它们会显示在这里'; } return ( {title} {subtitle} ); }; // 渲染加载更多 const renderLoadMore = () => { if (!tasksPagination.hasMore) return null; return ( {tasksLoading ? '加载中...' : '上拉加载更多'} ); }; return ( {/* 背景渐变 */} {/* 右下角Lottie动画 */} {/* 标题区域 */} 习惯养成 自律让我更健康 {/* 任务进度卡片 */} 历史 + } /> {/* 任务筛选标签 */} {/* 任务列表 */} item.id} contentContainerStyle={styles.taskList} showsVerticalScrollIndicator={false} refreshControl={ } onEndReached={handleLoadMoreTasks} onEndReachedThreshold={0.1} ListEmptyComponent={renderEmptyState} ListFooterComponent={renderLoadMore} /> {/* 目标模板选择弹窗 */} setShowTemplateModal(false)} onSelectTemplate={handleSelectTemplate} onCreateCustom={handleCreateCustomGoal} /> {/* 创建目标弹窗 */} { setShowCreateModal(false); setSelectedTemplateData(undefined); }} onSubmit={handleCreateGoal} onSuccess={handleModalSuccess} loading={createLoading} initialData={selectedTemplateData} /> {/* 目标页面引导 */} {/* 开发测试按钮 */} {/* 目标通知测试按钮 */} {__DEV__ && ( { // 这里可以导航到测试页面或显示测试弹窗 Alert.alert( '目标通知测试', '选择要测试的通知类型', [ { text: '取消', style: 'cancel' }, { text: '每日目标通知', onPress: async () => { try { const userName = userProfile?.name || ''; const notificationIds = await GoalNotificationHelpers.scheduleGoalNotifications( { title: '每日运动目标', repeatType: 'daily', frequency: 1, hasReminder: true, reminderTime: '09:00', }, userName ); Alert.alert('成功', `每日目标通知已创建,ID: ${notificationIds.join(', ')}`); } catch (error) { Alert.alert('错误', `创建通知失败: ${error}`); } } }, { text: '每周目标通知', onPress: async () => { try { const userName = userProfile?.name || ''; const notificationIds = await GoalNotificationHelpers.scheduleGoalNotifications( { title: '每周运动目标', repeatType: 'weekly', frequency: 1, hasReminder: true, reminderTime: '10:00', customRepeatRule: { weekdays: [1, 3, 5], // 周一、三、五 }, }, userName ); Alert.alert('成功', `每周目标通知已创建,ID: ${notificationIds.join(', ')}`); } catch (error) { Alert.alert('错误', `创建通知失败: ${error}`); } } }, { text: '目标达成通知', onPress: async () => { try { const userName = userProfile?.name || ''; await GoalNotificationHelpers.sendGoalAchievementNotification(userName, '每日运动目标'); Alert.alert('成功', '目标达成通知已发送'); } catch (error) { Alert.alert('错误', `发送通知失败: ${error}`); } } }, { text: '测试庆祝动画', onPress: () => { celebrationAnimationRef.current?.play(); } }, ] ); }} > 测试通知 )} {/* 庆祝动画组件 */} ); } const styles = StyleSheet.create({ container: { flex: 1, }, gradientBackground: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, opacity: 0.6, }, decorativeCircle1: { position: 'absolute', top: -20, right: -20, width: 60, height: 60, borderRadius: 30, backgroundColor: '#0EA5E9', opacity: 0.1, }, decorativeCircle2: { position: 'absolute', bottom: -15, left: -15, width: 40, height: 40, borderRadius: 20, backgroundColor: '#0EA5E9', opacity: 0.05, }, content: { flex: 1, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 20, paddingTop: 20, paddingBottom: 16, }, headerButtons: { flexDirection: 'row', alignItems: 'center', gap: 12, }, goalsButton: { flexDirection: 'row', alignItems: 'center', gap: 4, paddingHorizontal: 12, paddingVertical: 8, borderRadius: 20, backgroundColor: 'rgba(255, 255, 255, 0.8)', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, pageTitle: { fontSize: 28, fontWeight: '800', marginBottom: 4, }, pageTitle2: { fontSize: 16, fontWeight: '400', color: '#FFFFFF', lineHeight: 24, }, addButton: { width: 30, height: 30, borderRadius: 20, backgroundColor: '#0EA5E9', alignItems: 'center', justifyContent: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, addButtonText: { color: '#FFFFFF', fontSize: 22, fontWeight: '600', lineHeight: 22, }, taskListContainer: { flex: 1, borderTopLeftRadius: 24, borderTopRightRadius: 24, overflow: 'hidden', }, taskList: { paddingHorizontal: 20, paddingTop: 20, paddingBottom: TAB_BAR_HEIGHT + TAB_BAR_BOTTOM_OFFSET + 20, // 避让底部导航栏 + 额外间距 }, emptyState: { alignItems: 'center', justifyContent: 'center', paddingVertical: 60, }, emptyStateImage: { width: 223, height: 59, marginBottom: 20, }, emptyStateTitle: { fontSize: 18, fontWeight: '600', marginBottom: 8, }, emptyStateSubtitle: { fontSize: 14, textAlign: 'center', lineHeight: 20, }, // 未登录空状态样式 emptyStateLogin: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 24, paddingVertical: 80, position: 'relative', }, emptyStateLoginBackground: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, borderRadius: 24, }, emptyStateLoginContent: { alignItems: 'center', justifyContent: 'center', zIndex: 1, }, emptyStateLoginIconContainer: { marginBottom: 24, shadowColor: '#7A5AF8', shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.15, shadowRadius: 16, elevation: 8, }, emptyStateLoginIconGradient: { width: 80, height: 80, borderRadius: 40, alignItems: 'center', justifyContent: 'center', }, emptyStateLoginTitle: { fontSize: 24, fontWeight: '700', marginBottom: 12, textAlign: 'center', letterSpacing: -0.5, }, emptyStateLoginSubtitle: { fontSize: 16, lineHeight: 24, textAlign: 'center', marginBottom: 32, paddingHorizontal: 8, }, emptyStateLoginButton: { borderRadius: 28, shadowColor: '#7A5AF8', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.2, shadowRadius: 12, elevation: 6, }, emptyStateLoginButtonGradient: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingHorizontal: 32, paddingVertical: 16, borderRadius: 28, gap: 8, }, emptyStateLoginButtonText: { color: '#FFFFFF', fontSize: 17, fontWeight: '600', letterSpacing: -0.2, }, loadMoreContainer: { alignItems: 'center', paddingVertical: 20, }, loadMoreText: { fontSize: 14, fontWeight: '500', }, bottomRightImage: { position: 'absolute', top: 40, right: 36, width: 120, height: 120, }, // 任务进度卡片中的按钮样式 cardHeaderButtons: { flexDirection: 'row', alignItems: 'center', gap: 8, }, cardGoalsButton: { flexDirection: 'row', alignItems: 'center', gap: 4, paddingHorizontal: 10, paddingVertical: 6, borderRadius: 16, backgroundColor: '#F3F4F6', borderWidth: 1, }, cardGoalsListButton: { flexDirection: 'row', alignItems: 'center', gap: 4, paddingHorizontal: 10, paddingVertical: 6, borderRadius: 16, backgroundColor: '#F3F4F6', borderWidth: 1, }, cardGoalsButtonText: { fontSize: 12, fontWeight: '600', color: '#374151', }, cardAddButton: { width: 28, height: 28, borderRadius: 14, alignItems: 'center', justifyContent: 'center', }, cardAddButtonText: { color: '#FFFFFF', fontSize: 18, fontWeight: '600', lineHeight: 18, }, testButton: { position: 'absolute', top: 100, right: 20, backgroundColor: '#10B981', paddingHorizontal: 12, paddingVertical: 8, borderRadius: 8, zIndex: 1000, }, testButtonText: { color: '#FFFFFF', fontSize: 12, fontWeight: '600', }, });