From 6c3d2c9099b7dd78439fb3f23c2c3675227b128d Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Thu, 6 Nov 2025 20:02:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=9B=B4=E6=96=B0=E5=BE=85=E5=8A=9E?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=AF=B9=E8=AF=9D=E6=A1=86=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/common.dart | 51 +++ lib/pages/todo_page.dart | 400 ++++++++++++------ linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 64 +++ pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 9 files changed, 407 insertions(+), 120 deletions(-) create mode 100644 lib/models/common.dart diff --git a/lib/models/common.dart b/lib/models/common.dart new file mode 100644 index 0000000..8d3e635 --- /dev/null +++ b/lib/models/common.dart @@ -0,0 +1,51 @@ +import 'package:awesome_dialog/awesome_dialog.dart'; +import 'package:flutter/material.dart'; + +BoxDecoration buildBoxDecoration() { + return BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey[200]!, width: 1), + ); +} + +Container buildBody({Widget? child}) { + return Container( + color: Colors.grey[50], + padding: EdgeInsets.all(5), + child: SingleChildScrollView(child: child), + ); +} + +Container buildCard({Widget? child}) { + return Container( + decoration: buildBoxDecoration(), + padding: EdgeInsets.all(10), + child: child, + ); +} + +void showAwesomeDialog({ + required BuildContext context, + required Widget body, + required VoidCallback onOk, + required VoidCallback onCancel, +}) { + AwesomeDialog( + context: context, + dialogType: DialogType.noHeader, + animType: AnimType.scale, + body: body, + dialogBackgroundColor: Colors.white, + btnOkText: "确认", + btnCancelText: "取消", + btnOkColor: Colors.orange, + btnCancelColor: Colors.grey, + buttonsBorderRadius: BorderRadius.circular(10), + headerAnimationLoop: false, + dismissOnTouchOutside: false, + dismissOnBackKeyPress: true, + btnOkOnPress: onOk, + btnCancelOnPress: onCancel, + ).show(); +} diff --git a/lib/pages/todo_page.dart b/lib/pages/todo_page.dart index 0992cde..e6cce8a 100644 --- a/lib/pages/todo_page.dart +++ b/lib/pages/todo_page.dart @@ -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 { // 统计数据 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 { // 待办事项列表 Expanded( - child: _filteredTodos.isEmpty ? _buildEmptyState() : _buildTodoList(), + child: _filteredTodos.isEmpty + ? _buildEmptyState() + : _buildTodoList(), ), ], ), @@ -287,7 +300,8 @@ class _TodoPageState extends State { // 构建待办事项副标题 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 { // 显示添加待办事项对话框 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 { _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( 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 _selectDueDate() async { + // 在对话框中选择日期 + Future _selectDueDateInDialog() async { final DateTime? picked = await showDatePicker( context: context, initialDate: DateTime.now(), @@ -446,12 +577,16 @@ class _TodoPageState extends State { setState(() { _selectedDueDate = picked; }); + // 重新显示对话框以更新日期显示 + _showAddTodoDialog(); } } // 验证表单 bool _validateForm() { - return _titleController.text.trim().isNotEmpty; + return _titleController.text + .trim() + .isNotEmpty; } // 重置表单 @@ -468,9 +603,14 @@ class _TodoPageState extends State { 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 { 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 { 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('取消'), + 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 { 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 { // 导出待办事项 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)); diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..92ce211 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) rive_native_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "RiveNativePlugin"); + rive_native_plugin_register_with_registrar(rive_native_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..b7557d4 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + rive_native ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 724bb2a..092fa1a 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,10 @@ import FlutterMacOS import Foundation +import rive_native import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + RiveNativePlugin.register(with: registry.registrar(forPlugin: "RiveNativePlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 1a22c04..2376400 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -9,6 +17,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.12.0" + awesome_dialog: + dependency: "direct main" + description: + name: awesome_dialog + sha256: "4c5821a0a637ceee022084e78c1b8237dd4b8bfca4dd24ac2484662a56707338" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.3.0" boolean_selector: dependency: transitive description: @@ -96,6 +112,30 @@ packages: description: flutter source: sdk version: "0.0.0" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.2" + http: + dependency: transitive + description: + name: http + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.5.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.2" leak_tracker: dependency: transitive description: @@ -216,6 +256,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "6.1.5+1" + rive: + dependency: transitive + description: + name: rive + sha256: "20d91e17b1bae5f4030d252336227f990782c942cb44e41685a13ae5f885e641" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.14.0-dev.13" + rive_native: + dependency: transitive + description: + name: rive_native + sha256: "1b65b473939b008feb30c1f43785996063cefaf4ceddfa1a2ffe6051201e8d3a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.16" shared_preferences: dependency: "direct main" description: @@ -325,6 +381,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.7.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e1b3582..2033182 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,6 +36,7 @@ dependencies: cupertino_icons: ^1.0.8 provider: ^6.1.1 # 状态管理 shared_preferences: ^2.2.2 # 本地存储 + awesome_dialog: ^3.3.0 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d468..1a3db62 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + RiveNativePluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("RiveNativePlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c3..22806d1 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + rive_native ) list(APPEND FLUTTER_FFI_PLUGIN_LIST