From d7ce7d644a210d8bc679f63853439de0509e73da Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Fri, 7 Nov 2025 00:08:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0=E4=BB=A3=E5=8A=9E?= =?UTF-8?q?=E4=BA=8B=E9=A1=B9=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main.dart | 8 +- lib/pages/todo_page.dart | 166 +++++++------------------------ lib/store/todo_dialog_store.dart | 82 +++++++++++++++ lib/widgets/todo_dialog.dart | 141 +++++++++++++++----------- 4 files changed, 208 insertions(+), 189 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 0c3877c..6b4efcf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'layout/main_screen.dart'; +import 'store/todo_dialog_store.dart'; void main() { runApp(const MyApp()); @@ -13,8 +14,11 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (context) => AppProvider(), + return MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => AppProvider()), + ChangeNotifierProvider(create: (_) => TodoDialogStore()), // 新增 + ], child: MaterialApp( title: '闪灵', theme: ThemeData( diff --git a/lib/pages/todo_page.dart b/lib/pages/todo_page.dart index 2436874..21e8526 100644 --- a/lib/pages/todo_page.dart +++ b/lib/pages/todo_page.dart @@ -1,10 +1,12 @@ +import 'package:flisp_app/store/todo_dialog_store.dart'; +import 'package:flisp_app/widgets/todo_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:awesome_dialog/awesome_dialog.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'; class TodoPage extends StatefulWidget { final VoidCallback? onAddTodoPressed; @@ -16,28 +18,21 @@ class TodoPage extends StatefulWidget { } class TodoPageState extends State { - // 添加一个公共方法供外部调用 - void showAddTodoDialog() { - _showAddTodoDialog(); - } - // 待办事项列表 - final List _todos = [ - // ... 原有的待办事项数据保持不变 - ]; + final List _todos = []; TodoTab _currentTab = TodoTab.all; - final TextEditingController _titleController = TextEditingController(); - final TextEditingController _descriptionController = TextEditingController(); - DateTime? _selectedDueDate; - TodoPriority _selectedPriority = TodoPriority.medium; - String? _selectedCategory; // 获取过滤后的待办事项 List get _activeTodos { return getActiveTodos(_currentTab, _todos); } + // 添加一个公共方法供外部调用 + void showAddTodoDialog() { + _showAddTodoDialog(); + } + @override Widget build(BuildContext context) { return buildBody( @@ -79,144 +74,64 @@ class TodoPageState extends State { // 显示添加待办事项对话框 void _showAddTodoDialog() { - _resetForm(); + final store = Provider.of(context, listen: false); + store.reset(); showAwesomeDialog( context: context, - body: _buildTodoDialogContent(isEditing: false), - onOk: () => _saveTodo(false, null), - onCancel: () => _resetForm(), + body: TodoDialog(isEditing: false), + onOk: () { + if (store.validate()) { + _saveTodo(false, null); + } + }, + onCancel: () { + store.reset(); + }, ); } // 显示编辑待办事项对话框 void _showEditTodoDialog(TodoItem todo) { - _titleController.text = todo.title; - _descriptionController.text = todo.description ?? ''; - _selectedDueDate = todo.dueDate; - _selectedPriority = todo.priority; - _selectedCategory = todo.category; + final store = Provider.of(context, listen: false); + store.initEditData(todo); showAwesomeDialog( context: context, - body: _buildTodoDialogContent(isEditing: true), - onOk: () => _saveTodo(true, todo), - onCancel: () => _resetForm(), - ); - } - - // 构建待办事项对话框 - 现在使用新的组件 - Widget _buildTodoDialogContent({bool isEditing = false}) { - 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(); + body: TodoDialog(isEditing: true, initialTodo: todo), + onOk: () { + if (store.validate()) { + _saveTodo(true, todo); } }, - onSelectDueDate: () => _selectDueDateInDialog(isEditing), + onCancel: () { + store.reset(); + }, ); } - // 在对话框中选择日期 - Future _selectDueDateInDialog(bool isEditing) async { - final DateTime? picked = await showDatePicker( - context: context, - initialDate: DateTime.now(), - firstDate: DateTime.now(), - lastDate: DateTime(2100), - ); - - if (picked != null) { - setState(() { - _selectedDueDate = picked; - }); - // 重新显示对话框以更新日期显示 - 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; - } - - // 重置表单 - void _resetForm() { - _titleController.clear(); - _descriptionController.clear(); - _selectedDueDate = null; - _selectedPriority = TodoPriority.medium; - _selectedCategory = null; - } - // 保存待办事项 void _saveTodo(bool isEditing, TodoItem? todo) { - if (!_validateForm()) return; + final store = Provider.of(context, listen: false); - 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, + final newTodo = store.createTodoItem( + id: isEditing ? todo!.id : null, + isCompleted: isEditing ? todo!.isCompleted : false, + createdAt: isEditing ? todo!.createdAt : null, ); setState(() { 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; } } else { _todos.insert(0, newTodo); } }); - _resetForm(); - - // 显示成功提示 + store.reset(); _showSuccessDialog(isEditing ? '更新成功' : '添加成功'); } @@ -245,11 +160,4 @@ class TodoPageState extends State { } }); } - - @override - void dispose() { - _titleController.dispose(); - _descriptionController.dispose(); - super.dispose(); - } } \ No newline at end of file diff --git a/lib/store/todo_dialog_store.dart b/lib/store/todo_dialog_store.dart index e69de29..592dfb9 100644 --- a/lib/store/todo_dialog_store.dart +++ b/lib/store/todo_dialog_store.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:flisp_app/models/todo.dart'; + +class TodoDialogStore extends ChangeNotifier { + String _title = ''; + String _description = ''; + DateTime? _dueDate; + TodoPriority _priority = TodoPriority.medium; + String? _category; + + // Getters + String get title => _title; + String get description => _description; + DateTime? get dueDate => _dueDate; + TodoPriority get priority => _priority; + String? get category => _category; + + // Setters + set title(String value) { + _title = value; + notifyListeners(); + } + + set description(String value) { + _description = value; + notifyListeners(); + } + + set dueDate(DateTime? value) { + _dueDate = value; + notifyListeners(); + } + + set priority(TodoPriority value) { + _priority = value; + notifyListeners(); + } + + set category(String? value) { + _category = value; + notifyListeners(); + } + + // 初始化编辑数据 + void initEditData(TodoItem todo) { + _title = todo.title; + _description = todo.description ?? ''; + _dueDate = todo.dueDate; + _priority = todo.priority; + _category = todo.category; + notifyListeners(); + } + + // 重置表单数据 + void reset() { + _title = ''; + _description = ''; + _dueDate = null; + _priority = TodoPriority.medium; + _category = null; + notifyListeners(); + } + + // 验证表单 + bool validate() { + return _title.trim().isNotEmpty; + } + + // 创建待办事项对象 + TodoItem createTodoItem({String? id, bool isCompleted = false, DateTime? createdAt}) { + return TodoItem( + id: id ?? DateTime.now().millisecondsSinceEpoch.toString(), + title: _title.trim(), + description: _description.trim().isEmpty ? null : _description.trim(), + dueDate: _dueDate, + priority: _priority, + category: _category, + isCompleted: isCompleted, + createdAt: createdAt ?? DateTime.now(), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/todo_dialog.dart b/lib/widgets/todo_dialog.dart index 3664724..d3aedbf 100644 --- a/lib/widgets/todo_dialog.dart +++ b/lib/widgets/todo_dialog.dart @@ -1,43 +1,80 @@ +import 'package:flisp_app/store/todo_dialog_store.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:flisp_app/models/todo.dart'; import 'package:flisp_app/utils/date_utils.dart'; import 'package:flisp_app/widgets/common.dart'; -class TodoDialogContent extends StatefulWidget { +class TodoDialog extends StatefulWidget { final bool isEditing; - final TextEditingController titleController; - final TextEditingController descriptionController; - final DateTime? selectedDueDate; - final TodoPriority selectedPriority; - final String? selectedCategory; - final Function(DateTime?) onDueDateChanged; - final Function(TodoPriority) onPriorityChanged; - final Function(String?) onCategoryChanged; - final VoidCallback onClearDueDate; - final VoidCallback onSelectDueDate; + final TodoItem? initialTodo; - const TodoDialogContent({ + const TodoDialog({ super.key, required this.isEditing, - required this.titleController, - required this.descriptionController, - required this.selectedDueDate, - required this.selectedPriority, - required this.selectedCategory, - required this.onDueDateChanged, - required this.onPriorityChanged, - required this.onCategoryChanged, - required this.onClearDueDate, - required this.onSelectDueDate, + this.initialTodo, }); @override - State createState() => _TodoDialogContentState(); + State createState() => _TodoDialogState(); } -class _TodoDialogContentState extends State { +class _TodoDialogState extends State { + late TextEditingController _titleController; + late TextEditingController _descriptionController; + + @override + void initState() { + super.initState(); + + _titleController = TextEditingController(); + _descriptionController = TextEditingController(); + + // 如果是编辑模式,初始化数据 + if (widget.isEditing && widget.initialTodo != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + final store = Provider.of(context, listen: false); + store.initEditData(widget.initialTodo!); + }); + } + } + + @override + void dispose() { + _titleController.dispose(); + _descriptionController.dispose(); + super.dispose(); + } + + Future _selectDueDate(BuildContext context) async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime(2100), + ); + + if (picked != null) { + final store = Provider.of(context, listen: false); + store.dueDate = picked; + } + } + @override Widget build(BuildContext context) { + final store = Provider.of(context); + + // 同步控制器文本 + if (_titleController.text != store.title) { + _titleController.text = store.title; + _titleController.selection = TextSelection.collapsed(offset: store.title.length); + } + + if (_descriptionController.text != store.description) { + _descriptionController.text = store.description; + _descriptionController.selection = TextSelection.collapsed(offset: store.description.length); + } + return Padding( padding: const EdgeInsets.all(16), child: Column( @@ -62,16 +99,15 @@ class _TodoDialogContentState extends State { // 标题输入框 TextField( - controller: widget.titleController, + controller: _titleController, + onChanged: (value) => store.title = value, decoration: InputDecoration( labelText: '标题', hintText: '请输入待办事项标题...', counterText: '', - suffixText: '${widget.titleController.text.length}/20', + suffixText: '${store.title.length}/20', suffixStyle: TextStyle( - color: widget.titleController.text.length > 20 - ? Colors.red - : Colors.grey, + color: store.title.length > 20 ? Colors.red : Colors.grey, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), @@ -88,32 +124,25 @@ class _TodoDialogContentState extends State { filled: true, fillColor: Colors.white, prefixIcon: Icon(Icons.title, color: Colors.black87), - contentPadding: EdgeInsets.symmetric( - horizontal: 16, - vertical: 14, - ), + contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), ), style: TextStyle(fontSize: 16), maxLength: 20, - onChanged: (value) { - if (mounted) setState(() {}); - }, ), SizedBox(height: 12), // 描述输入框 TextField( - controller: widget.descriptionController, + controller: _descriptionController, + onChanged: (value) => store.description = value, decoration: InputDecoration( labelText: '内容', hintText: '请输入待办事项内容...', counterText: '', - suffixText: '${widget.titleController.text.length}/50', + suffixText: '${store.description.length}/50', suffixStyle: TextStyle( - color: widget.titleController.text.length > 20 - ? Colors.red - : Colors.grey, + color: store.description.length > 50 ? Colors.red : Colors.grey, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), @@ -130,10 +159,7 @@ class _TodoDialogContentState extends State { filled: true, fillColor: Colors.white, prefixIcon: Icon(Icons.description, color: Colors.black87), - contentPadding: EdgeInsets.symmetric( - horizontal: 16, - vertical: 14, - ), + contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), ), style: TextStyle(fontSize: 16), maxLines: 2, @@ -148,22 +174,22 @@ class _TodoDialogContentState extends State { child: ListTile( leading: Icon(Icons.calendar_today, color: Colors.blue), title: Text( - widget.selectedDueDate == null + store.dueDate == null ? '选择截止日期' - : '截止: ${formatDate(widget.selectedDueDate!)}', + : '截止: ${formatDate(store.dueDate!)}', style: TextStyle( - color: widget.selectedDueDate == null - ? Colors.grey - : Colors.black87, + color: store.dueDate == null ? Colors.grey : Colors.black87, ), ), - trailing: widget.selectedDueDate != null + trailing: store.dueDate != null ? IconButton( icon: Icon(Icons.clear, size: 18), - onPressed: widget.onClearDueDate, + onPressed: () { + store.dueDate = null; + }, ) : null, - onTap: widget.onSelectDueDate, + onTap: () => _selectDueDate(context), ), ), SizedBox(height: 12), @@ -175,12 +201,12 @@ class _TodoDialogContentState extends State { leading: Container( padding: EdgeInsets.all(6), decoration: BoxDecoration( - color: widget.selectedPriority.color.withAlpha(10), + color: store.priority.color.withAlpha(10), shape: BoxShape.circle, ), child: Icon( Icons.flag, - color: widget.selectedPriority.color, + color: store.priority.color, size: 18, ), ), @@ -193,7 +219,7 @@ class _TodoDialogContentState extends State { ), ), trailing: DropdownButton( - value: widget.selectedPriority, + value: store.priority, underline: SizedBox(), icon: Icon(Icons.arrow_drop_down, color: Colors.grey.shade600), iconSize: 20, @@ -201,8 +227,7 @@ class _TodoDialogContentState extends State { borderRadius: BorderRadius.circular(12), onChanged: (value) { if (value != null) { - widget.onPriorityChanged(value); - if (mounted) setState(() {}); + store.priority = value; } }, items: TodoPriority.values.map((priority) {