import 'package:awesome_dialog/awesome_dialog.dart'; import 'package:flisp_app/models/common.dart'; 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(); showAwesomeDialog(context: context, body: _buildTodoDialogContent(isEditing: true), onOk: () => _saveTodo(false, null), onCancel: () => _resetForm()); } // 显示编辑待办事项对话框 void _showEditTodoDialog(TodoItem todo) { _titleController.text = todo.title; _descriptionController.text = todo.description ?? ''; _selectedDueDate = todo.dueDate; _selectedPriority = todo.priority; _selectedCategory = todo.category; showAwesomeDialog(context: context, body: _buildTodoDialogContent(isEditing: true), onOk: () => _saveTodo(false, null), onCancel: () => _resetForm()); } // 构建待办事项对话框 Widget _buildTodoDialogContent({bool isEditing = false}) { var title = isEditing ? '编辑待办事项' : '添加待办事项'; return Padding( padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, children: [ // 标题 Row( children: [ Icon(Icons.add_task, color: Colors.orange, size: 24), SizedBox(width: 8), Text(title, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.orange)), ], ), SizedBox(height: 20), // 标题输入框 TextField( controller: _titleController, decoration: InputDecoration( labelText: '标题', hintText: '请输入待办事项标题...', counterText: '', suffixText: '${_titleController.text.length}/20', suffixStyle: TextStyle( color: _titleController.text.length > 20 ? Colors.red : Colors.grey, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade200), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade200), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.orange, width: 1), ), filled: true, fillColor: Colors.white, prefixIcon: Icon(Icons.title, color: Colors.black87), contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), ), style: TextStyle(fontSize: 16), maxLength: 20, onChanged: (value) { if (context.mounted) setState(() {}); }, ), SizedBox(height: 12), // 描述输入框 TextField( controller: _descriptionController, decoration: InputDecoration( labelText: '内容', hintText: '请输入待办事项内容...', counterText: '', suffixText: '${_titleController.text.length}/50', suffixStyle: TextStyle( color: _titleController.text.length > 20 ? Colors.red : Colors.grey, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade200), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade200), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.orange, width: 1), ), filled: true, fillColor: Colors.white, prefixIcon: Icon(Icons.description, color: Colors.black87), contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), ), style: TextStyle(fontSize: 16), maxLines: 2, maxLength: 50 ), SizedBox(height: 12), // 截止日期选择 Container( decoration: buildBoxDecoration(), child: ListTile( leading: Icon(Icons.calendar_today, color: Colors.blue), title: Text( _selectedDueDate == null ? '选择截止日期' : '截止: ${_formatDate(_selectedDueDate!)}', style: TextStyle( color: _selectedDueDate == null ? Colors.grey : Colors .black87, ), ), trailing: _selectedDueDate != null ? IconButton( icon: Icon(Icons.clear, size: 18), onPressed: () { setState(() => _selectedDueDate = null); // 重新显示对话框以更新状态 _showAddTodoDialog(); }, ) : null, onTap: () => _selectDueDateInDialog(), ), ), SizedBox(height: 12), // 优先级选择 Container( decoration: buildBoxDecoration(), child: ListTile( leading: Container( padding: EdgeInsets.all(6), decoration: BoxDecoration( color: _selectedPriority.color.withAlpha(10), shape: BoxShape.circle, ), child: Icon( Icons.flag, color: _selectedPriority.color, size: 18, ), ), title: Text( '优先级', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: Colors.grey.shade700, ), ), trailing: DropdownButton( value: _selectedPriority, underline: SizedBox(), icon: Icon(Icons.arrow_drop_down, color: Colors.grey.shade600), iconSize: 20, dropdownColor: Colors.white, borderRadius: BorderRadius.circular(12), onChanged: (value) { setState(() => _selectedPriority = value!); }, items: Priority.values.map((priority) { return DropdownMenuItem( value: priority, child: Container( padding: EdgeInsets.symmetric(vertical: 8), child: Row( children: [ Container( padding: EdgeInsets.all(4), decoration: buildBoxDecoration(), child: Icon( priority.icon, color: priority.color, size: 14, ), ), SizedBox(width: 12), Text( priority.label, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, ), ), ], ), ), ); }).toList(), ), contentPadding: EdgeInsets.symmetric(horizontal: 16), minLeadingWidth: 0, ), ), ], ), ); } // 在对话框中选择日期 Future _selectDueDateInDialog() async { final DateTime? picked = await showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime.now(), lastDate: DateTime(2100), ); if (picked != null) { setState(() { _selectedDueDate = picked; }); // 重新显示对话框以更新日期显示 _showAddTodoDialog(); } } // 验证表单 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); } }); _resetForm(); // 显示成功提示 _showSuccessDialog(isEditing ? '更新成功' : '添加成功'); } // 显示成功提示 void _showSuccessDialog(String message) { AwesomeDialog( context: context, dialogType: DialogType.success, animType: AnimType.scale, title: message, btnOkText: "好的", btnOkColor: Colors.green, btnOkOnPress: () {}, autoHide: Duration(seconds: 2), // 2秒后自动关闭 ).show(); } // 切换待办事项完成状态 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(); } }