Files
digital-pilates/app/task-list.tsx
richarjiang ee84a801fb feat: 更新多个组件以使用 SafeAreaView
- 在 goals-list、task-list、explore、personal、challenge/day 和 challenge/index 组件中引入 SafeAreaView,确保内容在安全区域内显示
- 移除不必要的 SafeAreaView 导入,优化代码结构
- 更新相关样式,提升用户体验和界面一致性
2025-08-25 09:37:12 +08:00

287 lines
7.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { DateSelector } from '@/components/DateSelector';
import { TaskCard } from '@/components/TaskCard';
import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors } from '@/constants/Colors';
import { TAB_BAR_BOTTOM_OFFSET, TAB_BAR_HEIGHT } from '@/constants/TabBar';
import { useColorScheme } from '@/hooks/useColorScheme';
import { tasksApi } from '@/services/tasksApi';
import { TaskListItem } from '@/types/goals';
import { getTodayIndexInMonth } from '@/utils/date';
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, useMemo, useState } from 'react';
import { Alert, FlatList, RefreshControl, StatusBar, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function GoalsDetailScreen() {
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
const colorTokens = Colors[theme];
const router = useRouter();
// 本地状态管理
const [tasks, setTasks] = useState<TaskListItem[]>([]);
const [tasksLoading, setTasksLoading] = useState(false);
const [tasksError, setTasksError] = useState<string | null>(null);
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
const [refreshing, setRefreshing] = useState(false);
// 日期选择器相关状态
const [selectedIndex, setSelectedIndex] = useState(getTodayIndexInMonth());
// 加载任务列表
const loadTasks = async (targetDate?: Date) => {
try {
setTasksLoading(true);
setTasksError(null);
const dateToUse = targetDate || selectedDate;
console.log('Loading tasks for date:', dayjs(dateToUse).format('YYYY-MM-DD'));
const response = await tasksApi.getTasks({
startDate: dayjs(dateToUse).startOf('day').toISOString(),
endDate: dayjs(dateToUse).endOf('day').toISOString(),
});
console.log('Tasks API response:', response);
setTasks(response.list || []);
} catch (error: any) {
console.error('Failed to load tasks:', error);
setTasksError(error.message || '获取任务列表失败');
setTasks([]);
} finally {
setTasksLoading(false);
}
};
// 页面聚焦时重新加载数据
useFocusEffect(
useCallback(() => {
console.log('useFocusEffect - loading tasks');
loadTasks();
}, [])
);
// 下拉刷新
const onRefresh = async () => {
setRefreshing(true);
try {
await loadTasks();
} finally {
setRefreshing(false);
}
};
// 处理错误提示
useEffect(() => {
if (tasksError) {
Alert.alert('错误', tasksError);
setTasksError(null);
}
}, [tasksError]);
// 日期选择处理
const onSelectDate = async (index: number, date: Date) => {
console.log('Date selected:', dayjs(date).format('YYYY-MM-DD'));
setSelectedIndex(index);
setSelectedDate(date);
// 重新加载对应日期的任务数据
await loadTasks(date);
};
// 根据选中日期筛选任务,并将已完成的任务放到最后
const filteredTasks = useMemo(() => {
const selected = dayjs(selectedDate);
const filtered = tasks.filter(task => {
if (task.status === 'skipped') return false;
const taskDate = dayjs(task.startDate);
return taskDate.isSame(selected, 'day');
});
// 对筛选结果进行排序:已完成的任务放到最后
return [...filtered].sort((a, b) => {
const aCompleted = a.status === 'completed';
const bCompleted = b.status === 'completed';
// 如果a已完成而b未完成a排在后面
if (aCompleted && !bCompleted) {
return 1;
}
// 如果b已完成而a未完成b排在后面
if (bCompleted && !aCompleted) {
return -1;
}
// 如果都已完成或都未完成,保持原有顺序
return 0;
});
}, [selectedDate, tasks]);
const handleBackPress = () => {
router.back();
};
// 渲染任务项
const renderTaskItem = ({ item }: { item: TaskListItem }) => (
<TaskCard
task={item}
/>
);
// 渲染空状态
const renderEmptyState = () => {
const selectedDateStr = dayjs(selectedDate).format('YYYY年M月D日');
if (tasksLoading) {
return (
<View style={styles.emptyState}>
<Text style={[styles.emptyStateTitle, { color: colorTokens.text }]}>
...
</Text>
</View>
);
}
return (
<View style={styles.emptyState}>
<Text style={[styles.emptyStateTitle, { color: colorTokens.text }]}>
</Text>
<Text style={[styles.emptyStateSubtitle, { color: colorTokens.textSecondary }]}>
{selectedDateStr}
</Text>
</View>
);
};
return (
<SafeAreaView style={styles.container}>
<StatusBar
backgroundColor="transparent"
translucent
/>
{/* 背景渐变 */}
<LinearGradient
colors={['#F0F9FF', '#E0F2FE']}
style={styles.gradientBackground}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
/>
{/* 装饰性圆圈 */}
<View style={styles.decorativeCircle1} />
<View style={styles.decorativeCircle2} />
<View style={styles.content}>
{/* 标题区域 */}
<HeaderBar
title="任务列表"
onBack={handleBackPress}
transparent={true}
withSafeTop={false}
/>
{/* 日期选择器 */}
<View style={styles.dateSelector}>
<DateSelector
selectedIndex={selectedIndex}
onDateSelect={onSelectDate}
showMonthTitle={true}
disableFutureDates={true}
/>
</View>
{/* 任务列表 */}
<View style={styles.taskListContainer}>
<FlatList
data={filteredTasks}
renderItem={renderTaskItem}
keyExtractor={(item) => item.id}
contentContainerStyle={styles.taskList}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
colors={['#0EA5E9']}
tintColor="#0EA5E9"
/>
}
ListEmptyComponent={renderEmptyState}
/>
</View>
</View>
</SafeAreaView>
);
}
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,
},
// 日期选择器样式
dateSelector: {
paddingHorizontal: 20,
},
taskListContainer: {
flex: 1,
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
overflow: 'hidden',
},
taskList: {
paddingHorizontal: 20,
paddingBottom: TAB_BAR_HEIGHT + TAB_BAR_BOTTOM_OFFSET + 20,
},
emptyState: {
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 60,
},
emptyStateTitle: {
fontSize: 18,
fontWeight: '600',
marginBottom: 8,
},
emptyStateSubtitle: {
fontSize: 14,
textAlign: 'center',
lineHeight: 20,
},
});