feat:更新待办实现对话框内容
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import 'package:awesome_dialog/awesome_dialog.dart';
|
||||
import 'package:flisp_app/models/common.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart'; // 用于复制功能
|
||||
|
||||
@@ -72,6 +74,7 @@ enum TodoFilter {
|
||||
today('今天');
|
||||
|
||||
final String label;
|
||||
|
||||
const TodoFilter(this.label);
|
||||
}
|
||||
|
||||
@@ -107,16 +110,24 @@ class _TodoPageState extends State<TodoPage> {
|
||||
|
||||
// 统计数据
|
||||
int get _totalCount => _todos.length;
|
||||
int get _activeCount => _todos.where((todo) => !todo.isCompleted).length;
|
||||
int get _completedCount => _todos.where((todo) => todo.isCompleted).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
|
||||
title: const Text('待办事项'),
|
||||
backgroundColor: Colors.orange,
|
||||
foregroundColor: Colors.white
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
@@ -128,7 +139,9 @@ class _TodoPageState extends State<TodoPage> {
|
||||
|
||||
// 待办事项列表
|
||||
Expanded(
|
||||
child: _filteredTodos.isEmpty ? _buildEmptyState() : _buildTodoList(),
|
||||
child: _filteredTodos.isEmpty
|
||||
? _buildEmptyState()
|
||||
: _buildTodoList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -287,7 +300,8 @@ class _TodoPageState extends State<TodoPage> {
|
||||
|
||||
// 构建待办事项副标题
|
||||
Widget? _buildTodoSubtitle(TodoItem todo, bool isOverdue) {
|
||||
final hasContent = todo.description?.isNotEmpty == true || todo.dueDate != null;
|
||||
final hasContent = todo.description?.isNotEmpty == true ||
|
||||
todo.dueDate != null;
|
||||
|
||||
if (!hasContent) return null;
|
||||
|
||||
@@ -335,10 +349,11 @@ class _TodoPageState extends State<TodoPage> {
|
||||
// 显示添加待办事项对话框
|
||||
void _showAddTodoDialog() {
|
||||
_resetForm();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => _buildTodoDialog(isEditing: false),
|
||||
);
|
||||
|
||||
showAwesomeDialog(context: context,
|
||||
body: _buildTodoDialogContent(isEditing: true),
|
||||
onOk: () => _saveTodo(false, null),
|
||||
onCancel: () => _resetForm());
|
||||
}
|
||||
|
||||
// 显示编辑待办事项对话框
|
||||
@@ -349,92 +364,208 @@ class _TodoPageState extends State<TodoPage> {
|
||||
_selectedPriority = todo.priority;
|
||||
_selectedCategory = todo.category;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => _buildTodoDialog(isEditing: true, todo: todo),
|
||||
);
|
||||
showAwesomeDialog(context: context,
|
||||
body: _buildTodoDialogContent(isEditing: true),
|
||||
onOk: () => _saveTodo(false, null),
|
||||
onCancel: () => _resetForm());
|
||||
}
|
||||
|
||||
// 构建待办事项对话框
|
||||
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(),
|
||||
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,
|
||||
),
|
||||
maxLength: 100,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: _descriptionController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '描述',
|
||||
border: OutlineInputBorder(),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: Colors.grey.shade200),
|
||||
),
|
||||
maxLines: 2,
|
||||
maxLength: 200,
|
||||
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),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// 截止日期选择
|
||||
ListTile(
|
||||
leading: const Icon(Icons.calendar_today),
|
||||
title: Text(_selectedDueDate == null
|
||||
? '选择截止日期'
|
||||
: '截止: ${_formatDate(_selectedDueDate!)}'),
|
||||
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: const Icon(Icons.clear),
|
||||
onPressed: () => setState(() => _selectedDueDate = null),
|
||||
icon: Icon(Icons.clear, size: 18),
|
||||
onPressed: () {
|
||||
setState(() => _selectedDueDate = null);
|
||||
// 重新显示对话框以更新状态
|
||||
_showAddTodoDialog();
|
||||
},
|
||||
)
|
||||
: null,
|
||||
onTap: () => _selectDueDate(),
|
||||
onTap: () => _selectDueDateInDialog(),
|
||||
),
|
||||
// 优先级选择
|
||||
ListTile(
|
||||
leading: const Icon(Icons.flag),
|
||||
title: const Text('优先级'),
|
||||
),
|
||||
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,
|
||||
onChanged: (value) => setState(() => _selectedPriority = value!),
|
||||
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: Row(
|
||||
children: [
|
||||
Icon(priority.icon, color: priority.color, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Text(priority.label),
|
||||
],
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _validateForm() ? () => _saveTodo(isEditing, todo) : null,
|
||||
child: Text(isEditing ? '保存' : '添加'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 选择截止日期
|
||||
Future<void> _selectDueDate() async {
|
||||
// 在对话框中选择日期
|
||||
Future<void> _selectDueDateInDialog() async {
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
@@ -446,12 +577,16 @@ class _TodoPageState extends State<TodoPage> {
|
||||
setState(() {
|
||||
_selectedDueDate = picked;
|
||||
});
|
||||
// 重新显示对话框以更新日期显示
|
||||
_showAddTodoDialog();
|
||||
}
|
||||
}
|
||||
|
||||
// 验证表单
|
||||
bool _validateForm() {
|
||||
return _titleController.text.trim().isNotEmpty;
|
||||
return _titleController.text
|
||||
.trim()
|
||||
.isNotEmpty;
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
@@ -468,9 +603,14 @@ 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 ?
|
||||
description: _descriptionController.text
|
||||
.trim()
|
||||
.isEmpty ?
|
||||
null : _descriptionController.text.trim(),
|
||||
dueDate: _selectedDueDate,
|
||||
priority: _selectedPriority,
|
||||
@@ -481,15 +621,32 @@ 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);
|
||||
}
|
||||
});
|
||||
|
||||
Navigator.pop(context);
|
||||
_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();
|
||||
}
|
||||
|
||||
// 切换待办事项完成状态
|
||||
@@ -508,20 +665,21 @@ 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('取消'),
|
||||
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)),
|
||||
),
|
||||
],
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('删除', style: TextStyle(color: Colors.red)),
|
||||
),
|
||||
],
|
||||
),
|
||||
) ?? false;
|
||||
}
|
||||
|
||||
@@ -540,36 +698,37 @@ class _TodoPageState extends State<TodoPage> {
|
||||
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);
|
||||
},
|
||||
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);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
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);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -609,7 +768,8 @@ class _TodoPageState extends State<TodoPage> {
|
||||
// 导出待办事项
|
||||
void _exportTodos() {
|
||||
final exportText = _todos.map((todo) {
|
||||
return '${todo.isCompleted ? '[✓]' : '[ ]'} ${todo.title}${todo.dueDate != null ? ' (截止: ${_formatDate(todo.dueDate!)})' : ''}';
|
||||
return '${todo.isCompleted ? '[✓]' : '[ ]'} ${todo.title}${todo.dueDate !=
|
||||
null ? ' (截止: ${_formatDate(todo.dueDate!)})' : ''}';
|
||||
}).join('\n');
|
||||
|
||||
Clipboard.setData(ClipboardData(text: exportText));
|
||||
|
||||
Reference in New Issue
Block a user