import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; // 用于复制功能 class TodoPage extends StatefulWidget { const TodoPage({super.key}); @override State createState() => _TodoPageState(); } // 待办事项数据模型 class TodoItem { String id; String title; String? description; bool isCompleted; DateTime createdAt; DateTime? dueDate; Priority priority; String? category; TodoItem({ required this.id, required this.title, this.description, this.isCompleted = false, DateTime? createdAt, this.dueDate, this.priority = Priority.medium, this.category, }) : createdAt = createdAt ?? DateTime.now(); TodoItem copyWith({ String? id, String? title, String? description, bool? isCompleted, DateTime? createdAt, DateTime? dueDate, Priority? priority, String? category, }) { return TodoItem( id: id ?? this.id, title: title ?? this.title, description: description ?? this.description, isCompleted: isCompleted ?? this.isCompleted, createdAt: createdAt ?? this.createdAt, dueDate: dueDate ?? this.dueDate, priority: priority ?? this.priority, category: category ?? this.category, ); } } enum Priority { low('低', Colors.grey, Icons.low_priority), medium('中', Colors.orange, Icons.flag), high('高', Colors.red, Icons.warning); final String label; final Color color; final IconData icon; const Priority(this.label, this.color, this.icon); } enum TodoFilter { all('全部'), active('待完成'), completed('已完成'), today('今天'); final String label; const TodoFilter(this.label); } class _TodoPageState extends State { // 待办事项列表 List _todos = []; TodoFilter _currentFilter = TodoFilter.all; final TextEditingController _titleController = TextEditingController(); final TextEditingController _descriptionController = TextEditingController(); DateTime? _selectedDueDate; Priority _selectedPriority = Priority.medium; String? _selectedCategory; // 获取过滤后的待办事项 List get _filteredTodos { switch (_currentFilter) { case TodoFilter.active: return _todos.where((todo) => !todo.isCompleted).toList(); case TodoFilter.completed: return _todos.where((todo) => todo.isCompleted).toList(); case TodoFilter.today: final today = DateTime.now(); return _todos.where((todo) => todo.dueDate != null && todo.dueDate!.year == today.year && todo.dueDate!.month == today.month && todo.dueDate!.day == today.day ).toList(); default: return _todos; } } // 统计数据 int get _totalCount => _todos.length; int get _activeCount => _todos.where((todo) => !todo.isCompleted).length; int get _completedCount => _todos.where((todo) => todo.isCompleted).length; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('待办事项'), backgroundColor: Colors.orange, foregroundColor: Colors.white ), body: Column( children: [ // 统计信息卡片 _buildStatsCard(), // 过滤器选项卡 _buildFilterTabs(), // 待办事项列表 Expanded( child: _filteredTodos.isEmpty ? _buildEmptyState() : _buildTodoList(), ), ], ), floatingActionButton: FloatingActionButton( onPressed: _showAddTodoDialog, backgroundColor: Colors.orange, child: const Icon(Icons.add), ), ); } // 构建统计信息卡片 Widget _buildStatsCard() { return Card( margin: const EdgeInsets.all(12), child: Padding( padding: const EdgeInsets.all(16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildStatItem('总计', _totalCount, Colors.blue), _buildStatItem('待完成', _activeCount, Colors.orange), _buildStatItem('已完成', _completedCount, Colors.green), ], ), ), ); } Widget _buildStatItem(String label, int count, Color color) { return Column( children: [ Text( count.toString(), style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: color, ), ), const SizedBox(height: 4), Text( label, style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], ); } // 构建过滤器选项卡 Widget _buildFilterTabs() { return SizedBox( height: 50, child: ListView( scrollDirection: Axis.horizontal, children: TodoFilter.values.map((filter) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: FilterChip( label: Text(filter.label), selected: _currentFilter == filter, onSelected: (selected) { setState(() { _currentFilter = filter; }); }, ), ); }).toList(), ), ); } // 构建空状态 Widget _buildEmptyState() { final messages = { TodoFilter.all: '还没有待办事项\n点击+号添加第一个任务', TodoFilter.active: '没有待完成的任务\n享受轻松时光吧', TodoFilter.completed: '还没有完成的任务\n加油哦!', TodoFilter.today: '今天没有安排任务\n好好放松一下吧', }; return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.checklist, size: 80, color: Colors.grey.shade300), const SizedBox(height: 20), Text( messages[_currentFilter] ?? '暂无数据', textAlign: TextAlign.center, style: TextStyle( fontSize: 16, color: Colors.grey.shade500, ), ), ], ), ); } // 构建待办事项列表 Widget _buildTodoList() { return ListView.builder( itemCount: _filteredTodos.length, itemBuilder: (context, index) { final todo = _filteredTodos[index]; return _buildTodoItem(todo); }, ); } // 构建单个待办事项项 Widget _buildTodoItem(TodoItem todo) { final isOverdue = todo.dueDate != null && todo.dueDate!.isBefore(DateTime.now()) && !todo.isCompleted; return Dismissible( key: Key(todo.id), direction: DismissDirection.endToStart, background: Container( color: Colors.red, alignment: Alignment.centerRight, padding: const EdgeInsets.only(right: 20), child: const Icon(Icons.delete, color: Colors.white), ), confirmDismiss: (direction) async { return await _showDeleteConfirmation(todo); }, onDismissed: (direction) => _deleteTodo(todo.id), child: Card( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), color: todo.isCompleted ? Colors.grey.shade50 : null, child: ListTile( leading: Checkbox( value: todo.isCompleted, onChanged: (value) => _toggleTodo(todo.id), ), title: Text( todo.title, style: TextStyle( decoration: todo.isCompleted ? TextDecoration.lineThrough : null, color: todo.isCompleted ? Colors.grey : null, fontWeight: FontWeight.w500, ), ), subtitle: _buildTodoSubtitle(todo, isOverdue), trailing: _buildPriorityBadge(todo.priority), onTap: () => _showEditTodoDialog(todo), onLongPress: () => _showTodoOptions(todo), ), ), ); } // 构建待办事项副标题 Widget? _buildTodoSubtitle(TodoItem todo, bool isOverdue) { final hasContent = todo.description?.isNotEmpty == true || todo.dueDate != null; if (!hasContent) return null; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (todo.description?.isNotEmpty == true) Text( todo.description!, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 12, color: Colors.grey.shade600, ), ), if (todo.dueDate != null) Text( '截止: ${_formatDate(todo.dueDate!)}', style: TextStyle( fontSize: 11, color: isOverdue ? Colors.red : Colors.grey, fontWeight: isOverdue ? FontWeight.bold : FontWeight.normal, ), ), ], ); } // 构建优先级徽章 Widget _buildPriorityBadge(Priority priority) { return Chip( label: Text(priority.label), backgroundColor: priority.color.withOpacity(0.1), labelStyle: TextStyle( color: priority.color, fontSize: 10, fontWeight: FontWeight.bold, ), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, visualDensity: VisualDensity.compact, ); } // 显示添加待办事项对话框 void _showAddTodoDialog() { _resetForm(); showDialog( context: context, builder: (context) => _buildTodoDialog(isEditing: false), ); } // 显示编辑待办事项对话框 void _showEditTodoDialog(TodoItem todo) { _titleController.text = todo.title; _descriptionController.text = todo.description ?? ''; _selectedDueDate = todo.dueDate; _selectedPriority = todo.priority; _selectedCategory = todo.category; showDialog( context: context, builder: (context) => _buildTodoDialog(isEditing: true, todo: todo), ); } // 构建待办事项对话框 Widget _buildTodoDialog({bool isEditing = false, TodoItem? todo}) { return AlertDialog( title: Text(isEditing ? '编辑待办事项' : '添加待办事项'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: _titleController, decoration: const InputDecoration( labelText: '标题*', border: OutlineInputBorder(), ), maxLength: 100, ), const SizedBox(height: 12), TextField( controller: _descriptionController, decoration: const InputDecoration( labelText: '描述', border: OutlineInputBorder(), ), maxLines: 2, maxLength: 200, ), const SizedBox(height: 12), // 截止日期选择 ListTile( leading: const Icon(Icons.calendar_today), title: Text(_selectedDueDate == null ? '选择截止日期' : '截止: ${_formatDate(_selectedDueDate!)}'), trailing: _selectedDueDate != null ? IconButton( icon: const Icon(Icons.clear), onPressed: () => setState(() => _selectedDueDate = null), ) : null, onTap: () => _selectDueDate(), ), // 优先级选择 ListTile( leading: const Icon(Icons.flag), title: const Text('优先级'), trailing: DropdownButton( value: _selectedPriority, onChanged: (value) => setState(() => _selectedPriority = value!), items: Priority.values.map((priority) { return DropdownMenuItem( value: priority, child: Row( children: [ Icon(priority.icon, color: priority.color, size: 16), const SizedBox(width: 8), Text(priority.label), ], ), ); }).toList(), ), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('取消'), ), ElevatedButton( onPressed: _validateForm() ? () => _saveTodo(isEditing, todo) : null, child: Text(isEditing ? '保存' : '添加'), ), ], ); } // 选择截止日期 Future _selectDueDate() async { final DateTime? picked = await showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime.now(), lastDate: DateTime(2100), ); if (picked != null) { setState(() { _selectedDueDate = picked; }); } } // 验证表单 bool _validateForm() { return _titleController.text.trim().isNotEmpty; } // 重置表单 void _resetForm() { _titleController.clear(); _descriptionController.clear(); _selectedDueDate = null; _selectedPriority = Priority.medium; _selectedCategory = null; } // 保存待办事项 void _saveTodo(bool isEditing, TodoItem? todo) { if (!_validateForm()) return; final newTodo = TodoItem( id: isEditing ? todo!.id : DateTime.now().millisecondsSinceEpoch.toString(), title: _titleController.text.trim(), description: _descriptionController.text.trim().isEmpty ? null : _descriptionController.text.trim(), dueDate: _selectedDueDate, priority: _selectedPriority, category: _selectedCategory, ); setState(() { if (isEditing) { final index = _todos.indexWhere((t) => t.id == todo!.id); if (index != -1) { _todos[index] = newTodo.copyWith(isCompleted: _todos[index].isCompleted); } } else { _todos.insert(0, newTodo); } }); Navigator.pop(context); _resetForm(); } // 切换待办事项完成状态 void _toggleTodo(String id) { setState(() { final index = _todos.indexWhere((todo) => todo.id == id); if (index != -1) { _todos[index] = _todos[index].copyWith( isCompleted: !_todos[index].isCompleted, ); } }); } // 显示删除确认 Future _showDeleteConfirmation(TodoItem todo) async { return await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('确认删除'), content: Text('确定要删除"${todo.title}"吗?此操作不可撤销。'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('取消'), ), TextButton( onPressed: () => Navigator.pop(context, true), child: const Text('删除', style: TextStyle(color: Colors.red)), ), ], ), ) ?? false; } // 删除待办事项 void _deleteTodo(String id) { setState(() { _todos.removeWhere((todo) => todo.id == id); }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('待办事项已删除')), ); } // 显示待办事项选项 void _showTodoOptions(TodoItem todo) { showModalBottomSheet( context: context, builder: (context) => Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.content_copy), title: const Text('复制内容'), onTap: () { Navigator.pop(context); _copyTodoContent(todo); }, ), ListTile( leading: Icon(todo.isCompleted ? Icons.undo : Icons.done_all), title: Text(todo.isCompleted ? '标记为未完成' : '标记为已完成'), onTap: () { Navigator.pop(context); _toggleTodo(todo.id); }, ), const Divider(), ListTile( leading: const Icon(Icons.delete, color: Colors.red), title: const Text('删除', style: TextStyle(color: Colors.red)), onTap: () { Navigator.pop(context); _deleteTodo(todo.id); }, ), ], ), ); } // 复制待办事项内容 void _copyTodoContent(TodoItem todo) { final content = '${todo.title}\n${todo.description ?? ''}'.trim(); Clipboard.setData(ClipboardData(text: content)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('内容已复制到剪贴板')), ); } // 清除已完成的任务 void _clearCompleted() { setState(() { _todos.removeWhere((todo) => todo.isCompleted); }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('已清除所有已完成的任务')), ); } // 处理菜单操作 void _handleMenuAction(String value) { switch (value) { case 'export': _exportTodos(); break; case 'sort': _sortTodos(); break; } } // 导出待办事项 void _exportTodos() { final exportText = _todos.map((todo) { return '${todo.isCompleted ? '[✓]' : '[ ]'} ${todo.title}${todo.dueDate != null ? ' (截止: ${_formatDate(todo.dueDate!)})' : ''}'; }).join('\n'); Clipboard.setData(ClipboardData(text: exportText)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('待办事项已导出到剪贴板')), ); } // 排序待办事项 void _sortTodos() { setState(() { _todos.sort((a, b) { // 按优先级排序(高优先级在前) if (a.priority.index != b.priority.index) { return b.priority.index.compareTo(a.priority.index); } // 按截止日期排序(有截止日期的在前,然后按时间顺序) if (a.dueDate != null && b.dueDate != null) { return a.dueDate!.compareTo(b.dueDate!); } else if (a.dueDate != null) { return -1; } else if (b.dueDate != null) { return 1; } // 按创建时间排序(新的在前) return b.createdAt.compareTo(a.createdAt); }); }); } // 格式化日期 String _formatDate(DateTime date) { return '${date.month}月${date.day}日'; } @override void dispose() { _titleController.dispose(); _descriptionController.dispose(); super.dispose(); } }