feat:重构代办事项模块
This commit is contained in:
@@ -1,348 +1,79 @@
|
||||
import 'package:awesome_dialog/awesome_dialog.dart';
|
||||
import 'package:flisp_app/models/common.dart';
|
||||
import 'package:flisp_app/models/todo.dart';
|
||||
import 'package:flisp_app/utils/todo_utils.dart';
|
||||
import 'package:flisp_app/widgets/common.dart';
|
||||
import 'package:flisp_app/widgets/todo_dialog.dart';
|
||||
import 'package:flisp_app/widgets/todo_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart'; // 用于复制功能
|
||||
|
||||
class TodoPage extends StatefulWidget {
|
||||
const TodoPage({super.key});
|
||||
final VoidCallback? onAddTodoPressed;
|
||||
|
||||
const TodoPage({super.key, this.onAddTodoPressed});
|
||||
|
||||
@override
|
||||
State<TodoPage> createState() => _TodoPageState();
|
||||
State<TodoPage> 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,
|
||||
);
|
||||
class TodoPageState extends State<TodoPage> {
|
||||
// 添加一个公共方法供外部调用
|
||||
void showAddTodoDialog() {
|
||||
_showAddTodoDialog();
|
||||
}
|
||||
}
|
||||
|
||||
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<TodoPage> {
|
||||
// 待办事项列表
|
||||
List<TodoItem> _todos = [];
|
||||
TodoFilter _currentFilter = TodoFilter.all;
|
||||
final List<TodoItem> _todos = [
|
||||
// ... 原有的待办事项数据保持不变
|
||||
];
|
||||
|
||||
TodoTab _currentTab = TodoTab.all;
|
||||
final TextEditingController _titleController = TextEditingController();
|
||||
final TextEditingController _descriptionController = TextEditingController();
|
||||
DateTime? _selectedDueDate;
|
||||
Priority _selectedPriority = Priority.medium;
|
||||
TodoPriority _selectedPriority = TodoPriority.medium;
|
||||
String? _selectedCategory;
|
||||
|
||||
// 获取过滤后的待办事项
|
||||
List<TodoItem> 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;
|
||||
}
|
||||
List<TodoItem> get _activeTodos {
|
||||
return getActiveTodos(_currentTab, _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(
|
||||
return buildBody(
|
||||
child: Column(
|
||||
children: [
|
||||
// 统计信息卡片
|
||||
_buildStatsCard(),
|
||||
|
||||
// 过滤器选项卡
|
||||
_buildFilterTabs(),
|
||||
|
||||
buildStatsCard(_todos),
|
||||
// 选项卡
|
||||
buildTabs(
|
||||
currentTab: _currentTab,
|
||||
onTabChanged: (value) {
|
||||
setState(() {
|
||||
_currentTab = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
// 待办事项列表
|
||||
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,
|
||||
),
|
||||
),
|
||||
Expanded(child: _buildActiveTodoList()),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 构建待办事项列表
|
||||
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,
|
||||
Widget _buildActiveTodoList() {
|
||||
return _activeTodos.isEmpty
|
||||
? buildEmptyState(_currentTab)
|
||||
: buildTodoList(
|
||||
todos: _activeTodos,
|
||||
onToggleTodo: (todoId) {
|
||||
setState(() {
|
||||
_toggleTodo(todoId);
|
||||
});
|
||||
},
|
||||
onEditTodo: (todo) {
|
||||
_showEditTodoDialog(todo);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -350,10 +81,12 @@ class _TodoPageState extends State<TodoPage> {
|
||||
void _showAddTodoDialog() {
|
||||
_resetForm();
|
||||
|
||||
showAwesomeDialog(context: context,
|
||||
body: _buildTodoDialogContent(isEditing: true),
|
||||
onOk: () => _saveTodo(false, null),
|
||||
onCancel: () => _resetForm());
|
||||
showAwesomeDialog(
|
||||
context: context,
|
||||
body: _buildTodoDialogContent(isEditing: false),
|
||||
onOk: () => _saveTodo(false, null),
|
||||
onCancel: () => _resetForm(),
|
||||
);
|
||||
}
|
||||
|
||||
// 显示编辑待办事项对话框
|
||||
@@ -364,208 +97,57 @@ class _TodoPageState extends State<TodoPage> {
|
||||
_selectedPriority = todo.priority;
|
||||
_selectedCategory = todo.category;
|
||||
|
||||
showAwesomeDialog(context: context,
|
||||
body: _buildTodoDialogContent(isEditing: true),
|
||||
onOk: () => _saveTodo(false, null),
|
||||
onCancel: () => _resetForm());
|
||||
showAwesomeDialog(
|
||||
context: context,
|
||||
body: _buildTodoDialogContent(isEditing: true),
|
||||
onOk: () => _saveTodo(true, todo),
|
||||
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<Priority>(
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
return TodoDialogContent(
|
||||
isEditing: isEditing,
|
||||
titleController: _titleController,
|
||||
descriptionController: _descriptionController,
|
||||
selectedDueDate: _selectedDueDate,
|
||||
selectedPriority: _selectedPriority,
|
||||
selectedCategory: _selectedCategory,
|
||||
onDueDateChanged: (date) {
|
||||
setState(() {
|
||||
_selectedDueDate = date;
|
||||
});
|
||||
},
|
||||
onPriorityChanged: (priority) {
|
||||
setState(() {
|
||||
_selectedPriority = priority;
|
||||
});
|
||||
},
|
||||
onCategoryChanged: (category) {
|
||||
setState(() {
|
||||
_selectedCategory = category;
|
||||
});
|
||||
},
|
||||
onClearDueDate: () {
|
||||
setState(() {
|
||||
_selectedDueDate = null;
|
||||
});
|
||||
// 重新显示对话框以更新状态
|
||||
if (isEditing) {
|
||||
_showEditTodoDialog(_todos.firstWhere((todo) =>
|
||||
_titleController.text == todo.title &&
|
||||
_descriptionController.text == (todo.description ?? '')));
|
||||
} else {
|
||||
_showAddTodoDialog();
|
||||
}
|
||||
},
|
||||
onSelectDueDate: () => _selectDueDateInDialog(isEditing),
|
||||
);
|
||||
}
|
||||
|
||||
// 在对话框中选择日期
|
||||
Future<void> _selectDueDateInDialog() async {
|
||||
Future<void> _selectDueDateInDialog(bool isEditing) async {
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
@@ -578,15 +160,21 @@ class _TodoPageState extends State<TodoPage> {
|
||||
_selectedDueDate = picked;
|
||||
});
|
||||
// 重新显示对话框以更新日期显示
|
||||
_showAddTodoDialog();
|
||||
if (isEditing) {
|
||||
// 找到对应的待办事项重新显示编辑对话框
|
||||
final todo = _todos.firstWhere((todo) =>
|
||||
_titleController.text == todo.title &&
|
||||
_descriptionController.text == (todo.description ?? ''));
|
||||
_showEditTodoDialog(todo);
|
||||
} else {
|
||||
_showAddTodoDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 验证表单
|
||||
bool _validateForm() {
|
||||
return _titleController.text
|
||||
.trim()
|
||||
.isNotEmpty;
|
||||
return _titleController.text.trim().isNotEmpty;
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
@@ -594,7 +182,7 @@ class _TodoPageState extends State<TodoPage> {
|
||||
_titleController.clear();
|
||||
_descriptionController.clear();
|
||||
_selectedDueDate = null;
|
||||
_selectedPriority = Priority.medium;
|
||||
_selectedPriority = TodoPriority.medium;
|
||||
_selectedCategory = null;
|
||||
}
|
||||
|
||||
@@ -603,15 +191,11 @@ class _TodoPageState extends State<TodoPage> {
|
||||
if (!_validateForm()) return;
|
||||
|
||||
final newTodo = TodoItem(
|
||||
id: isEditing ? todo!.id : DateTime
|
||||
.now()
|
||||
.millisecondsSinceEpoch
|
||||
.toString(),
|
||||
id: isEditing ? todo!.id : DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
title: _titleController.text.trim(),
|
||||
description: _descriptionController.text
|
||||
.trim()
|
||||
.isEmpty ?
|
||||
null : _descriptionController.text.trim(),
|
||||
description: _descriptionController.text.trim().isEmpty
|
||||
? null
|
||||
: _descriptionController.text.trim(),
|
||||
dueDate: _selectedDueDate,
|
||||
priority: _selectedPriority,
|
||||
category: _selectedCategory,
|
||||
@@ -621,8 +205,9 @@ class _TodoPageState extends State<TodoPage> {
|
||||
if (isEditing) {
|
||||
final index = _todos.indexWhere((t) => t.id == todo!.id);
|
||||
if (index != -1) {
|
||||
_todos[index] =
|
||||
newTodo.copyWith(isCompleted: _todos[index].isCompleted);
|
||||
_todos[index] = newTodo.copyWith(
|
||||
isCompleted: _todos[index].isCompleted,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
_todos.insert(0, newTodo);
|
||||
@@ -645,7 +230,7 @@ class _TodoPageState extends State<TodoPage> {
|
||||
btnOkText: "好的",
|
||||
btnOkColor: Colors.green,
|
||||
btnOkOnPress: () {},
|
||||
autoHide: Duration(seconds: 2), // 2秒后自动关闭
|
||||
autoHide: Duration(seconds: 2),
|
||||
).show();
|
||||
}
|
||||
|
||||
@@ -661,151 +246,6 @@ class _TodoPageState extends State<TodoPage> {
|
||||
});
|
||||
}
|
||||
|
||||
// 显示删除确认
|
||||
Future<bool> _showDeleteConfirmation(TodoItem todo) async {
|
||||
return await showDialog<bool>(
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user