import { Colors } from '@/constants/Colors'; import { useColorScheme } from '@/hooks/useColorScheme'; import { Ionicons } from '@expo/vector-icons'; import dayjs from 'dayjs'; import React, { useMemo } from 'react'; import { Dimensions, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { TodoItem } from './TodoCard'; interface TimelineEvent { id: string; title: string; startTime: string; endTime?: string; category: TodoItem['category']; isCompleted?: boolean; color?: string; } interface TimelineScheduleProps { events: TimelineEvent[]; selectedDate: Date; onEventPress?: (event: TimelineEvent) => void; } const { width: screenWidth } = Dimensions.get('window'); const HOUR_HEIGHT = 60; const TIME_LABEL_WIDTH = 60; // 生成24小时的时间标签 const generateTimeLabels = () => { const labels = []; for (let hour = 0; hour < 24; hour++) { labels.push(dayjs().hour(hour).minute(0).format('HH:mm')); } return labels; }; // 获取事件在时间轴上的位置和高度 const getEventStyle = (event: TimelineEvent) => { const startTime = dayjs(event.startTime); const endTime = event.endTime ? dayjs(event.endTime) : startTime.add(1, 'hour'); const startMinutes = startTime.hour() * 60 + startTime.minute(); const endMinutes = endTime.hour() * 60 + endTime.minute(); const durationMinutes = endMinutes - startMinutes; const top = (startMinutes / 60) * HOUR_HEIGHT; const height = Math.max((durationMinutes / 60) * HOUR_HEIGHT, 30); // 最小高度30 return { top, height }; }; // 获取分类颜色 const getCategoryColor = (category: TodoItem['category']) => { switch (category) { case 'workout': return '#FF6B6B'; case 'finance': return '#4ECDC4'; case 'personal': return '#45B7D1'; case 'work': return '#96CEB4'; case 'health': return '#FFEAA7'; default: return '#DDA0DD'; } }; export function TimelineSchedule({ events, selectedDate, onEventPress }: TimelineScheduleProps) { const theme = useColorScheme() ?? 'light'; const colorTokens = Colors[theme]; const timeLabels = generateTimeLabels(); // 按开始时间分组事件,处理同一时间的多个事件 const groupedEvents = useMemo(() => { const groups: { [key: string]: TimelineEvent[] } = {}; events.forEach(event => { const startHour = dayjs(event.startTime).format('HH:mm'); if (!groups[startHour]) { groups[startHour] = []; } groups[startHour].push(event); }); return groups; }, [events]); const renderTimelineEvent = (event: TimelineEvent, index: number, groupSize: number) => { const { top, height } = getEventStyle(event); const categoryColor = getCategoryColor(event.category); // 计算水平偏移和宽度,用于处理重叠事件 const eventWidth = (screenWidth - TIME_LABEL_WIDTH - 40) / Math.max(groupSize, 1); const leftOffset = index * eventWidth; return ( onEventPress?.(event)} activeOpacity={0.7} > {event.title} {dayjs(event.startTime).format('HH:mm')} {event.endTime && ` - ${dayjs(event.endTime).format('HH:mm')}`} {event.isCompleted && ( )} ); }; return ( {/* 日期标题 */} {dayjs(selectedDate).format('YYYY年M月D日 dddd')} {events.length} 项任务 {/* 时间轴 */} {/* 时间标签 */} {timeLabels.map((label, index) => ( {label} ))} {/* 事件容器 */} {Object.entries(groupedEvents).map(([time, timeEvents]) => timeEvents.map((event, index) => renderTimelineEvent(event, index, timeEvents.length) ) )} {/* 当前时间线 */} {dayjs(selectedDate).isSame(dayjs(), 'day') && ( )} {/* 空状态 */} {events.length === 0 && ( 今天暂无安排 )} ); } // 当前时间指示线组件 function CurrentTimeLine() { const theme = useColorScheme() ?? 'light'; const colorTokens = Colors[theme]; const now = dayjs(); const currentMinutes = now.hour() * 60 + now.minute(); const top = (currentMinutes / 60) * HOUR_HEIGHT; return ( ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: 'transparent', }, dateHeader: { paddingHorizontal: 20, paddingVertical: 16, borderBottomWidth: 1, borderBottomColor: '#E5E7EB', }, dateText: { fontSize: 18, fontWeight: '700', marginBottom: 4, }, eventCount: { fontSize: 14, fontWeight: '500', }, timelineContainer: { flex: 1, }, timeline: { position: 'relative', minHeight: 24 * HOUR_HEIGHT, }, timeLabelsContainer: { paddingLeft: 20, }, timeLabelRow: { flexDirection: 'row', alignItems: 'flex-start', paddingTop: 8, }, timeLabel: { width: TIME_LABEL_WIDTH, fontSize: 12, fontWeight: '500', textAlign: 'right', paddingRight: 12, }, timeLine: { flex: 1, borderBottomWidth: 1, marginLeft: 8, marginRight: 20, }, eventsContainer: { position: 'absolute', top: 0, left: 0, right: 0, height: 24 * HOUR_HEIGHT, }, eventContainer: { position: 'absolute', borderRadius: 8, borderLeftWidth: 4, shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, eventContent: { flex: 1, padding: 8, justifyContent: 'space-between', }, eventTitle: { fontSize: 12, fontWeight: '600', lineHeight: 16, }, eventTime: { fontSize: 10, fontWeight: '500', marginTop: 2, }, completedIcon: { position: 'absolute', top: 4, right: 4, }, currentTimeLine: { position: 'absolute', left: TIME_LABEL_WIDTH + 20, right: 20, height: 2, zIndex: 10, }, currentTimeDot: { position: 'absolute', left: -6, top: -4, width: 10, height: 10, borderRadius: 5, }, emptyState: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingVertical: 60, }, emptyText: { fontSize: 16, fontWeight: '500', marginTop: 12, }, });