feat:更新待办实现对话框内容

This commit is contained in:
2025-11-06 20:02:59 +08:00
parent d76c01ba9d
commit 6c3d2c9099
9 changed files with 407 additions and 120 deletions

51
lib/models/common.dart Normal file
View File

@@ -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();
}

View File

@@ -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/material.dart';
import 'package:flutter/services.dart'; // 用于复制功能 import 'package:flutter/services.dart'; // 用于复制功能
@@ -72,6 +74,7 @@ enum TodoFilter {
today('今天'); today('今天');
final String label; final String label;
const TodoFilter(this.label); const TodoFilter(this.label);
} }
@@ -107,16 +110,24 @@ class _TodoPageState extends State<TodoPage> {
// 统计数据 // 统计数据
int get _totalCount => _todos.length; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('待办事项'), title: const Text('待办事项'),
backgroundColor: Colors.orange, backgroundColor: Colors.orange,
foregroundColor: Colors.white foregroundColor: Colors.white
), ),
body: Column( body: Column(
children: [ children: [
@@ -128,7 +139,9 @@ class _TodoPageState extends State<TodoPage> {
// 待办事项列表 // 待办事项列表
Expanded( 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) { 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; if (!hasContent) return null;
@@ -335,10 +349,11 @@ class _TodoPageState extends State<TodoPage> {
// 显示添加待办事项对话框 // 显示添加待办事项对话框
void _showAddTodoDialog() { void _showAddTodoDialog() {
_resetForm(); _resetForm();
showDialog(
context: context, showAwesomeDialog(context: context,
builder: (context) => _buildTodoDialog(isEditing: false), body: _buildTodoDialogContent(isEditing: true),
); onOk: () => _saveTodo(false, null),
onCancel: () => _resetForm());
} }
// 显示编辑待办事项对话框 // 显示编辑待办事项对话框
@@ -349,92 +364,208 @@ class _TodoPageState extends State<TodoPage> {
_selectedPriority = todo.priority; _selectedPriority = todo.priority;
_selectedCategory = todo.category; _selectedCategory = todo.category;
showDialog( showAwesomeDialog(context: context,
context: context, body: _buildTodoDialogContent(isEditing: true),
builder: (context) => _buildTodoDialog(isEditing: true, todo: todo), onOk: () => _saveTodo(false, null),
); onCancel: () => _resetForm());
} }
// 构建待办事项对话框 // 构建待办事项对话框
Widget _buildTodoDialog({bool isEditing = false, TodoItem? todo}) { Widget _buildTodoDialogContent({bool isEditing = false}) {
return AlertDialog( var title = isEditing ? '编辑待办事项' : '添加待办事项';
title: Text(isEditing ? '编辑待办事项' : '添加待办事项'), return Padding(
content: SingleChildScrollView( padding: const EdgeInsets.all(16),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
TextField( // 标题
controller: _titleController, Row(
decoration: const InputDecoration( children: [
labelText: '标题*', Icon(Icons.add_task, color: Colors.orange, size: 24),
border: OutlineInputBorder(), 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, border: OutlineInputBorder(
), borderRadius: BorderRadius.circular(12),
const SizedBox(height: 12), borderSide: BorderSide(color: Colors.grey.shade200),
TextField(
controller: _descriptionController,
decoration: const InputDecoration(
labelText: '描述',
border: OutlineInputBorder(),
), ),
maxLines: 2, enabledBorder: OutlineInputBorder(
maxLength: 200, 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), style: TextStyle(fontSize: 16),
// 截止日期选择 maxLength: 20,
ListTile( onChanged: (value) {
leading: const Icon(Icons.calendar_today), if (context.mounted) setState(() {});
title: Text(_selectedDueDate == null },
? '选择截止日期' ),
: '截止: ${_formatDate(_selectedDueDate!)}'),
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 trailing: _selectedDueDate != null
? IconButton( ? IconButton(
icon: const Icon(Icons.clear), icon: Icon(Icons.clear, size: 18),
onPressed: () => setState(() => _selectedDueDate = null), onPressed: () {
setState(() => _selectedDueDate = null);
// 重新显示对话框以更新状态
_showAddTodoDialog();
},
) )
: null, : null,
onTap: () => _selectDueDate(), onTap: () => _selectDueDateInDialog(),
), ),
// 优先级选择 ),
ListTile( SizedBox(height: 12),
leading: const Icon(Icons.flag),
title: const Text('优先级'), // 优先级选择
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>( trailing: DropdownButton<Priority>(
value: _selectedPriority, 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) { items: Priority.values.map((priority) {
return DropdownMenuItem( return DropdownMenuItem(
value: priority, value: priority,
child: Row( child: Container(
children: [ padding: EdgeInsets.symmetric(vertical: 8),
Icon(priority.icon, color: priority.color, size: 16), child: Row(
const SizedBox(width: 8), children: [
Text(priority.label), 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(), }).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( final DateTime? picked = await showDatePicker(
context: context, context: context,
initialDate: DateTime.now(), initialDate: DateTime.now(),
@@ -446,12 +577,16 @@ class _TodoPageState extends State<TodoPage> {
setState(() { setState(() {
_selectedDueDate = picked; _selectedDueDate = picked;
}); });
// 重新显示对话框以更新日期显示
_showAddTodoDialog();
} }
} }
// 验证表单 // 验证表单
bool _validateForm() { bool _validateForm() {
return _titleController.text.trim().isNotEmpty; return _titleController.text
.trim()
.isNotEmpty;
} }
// 重置表单 // 重置表单
@@ -468,9 +603,14 @@ class _TodoPageState extends State<TodoPage> {
if (!_validateForm()) return; if (!_validateForm()) return;
final newTodo = TodoItem( final newTodo = TodoItem(
id: isEditing ? todo!.id : DateTime.now().millisecondsSinceEpoch.toString(), id: isEditing ? todo!.id : DateTime
.now()
.millisecondsSinceEpoch
.toString(),
title: _titleController.text.trim(), title: _titleController.text.trim(),
description: _descriptionController.text.trim().isEmpty ? description: _descriptionController.text
.trim()
.isEmpty ?
null : _descriptionController.text.trim(), null : _descriptionController.text.trim(),
dueDate: _selectedDueDate, dueDate: _selectedDueDate,
priority: _selectedPriority, priority: _selectedPriority,
@@ -481,15 +621,32 @@ class _TodoPageState extends State<TodoPage> {
if (isEditing) { if (isEditing) {
final index = _todos.indexWhere((t) => t.id == todo!.id); final index = _todos.indexWhere((t) => t.id == todo!.id);
if (index != -1) { if (index != -1) {
_todos[index] = newTodo.copyWith(isCompleted: _todos[index].isCompleted); _todos[index] =
newTodo.copyWith(isCompleted: _todos[index].isCompleted);
} }
} else { } else {
_todos.insert(0, newTodo); _todos.insert(0, newTodo);
} }
}); });
Navigator.pop(context);
_resetForm(); _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 { Future<bool> _showDeleteConfirmation(TodoItem todo) async {
return await showDialog<bool>( return await showDialog<bool>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) =>
title: const Text('确认删除'), AlertDialog(
content: Text('确定要删除"${todo.title}"吗?此操作不可撤销。'), title: const Text('确认删除'),
actions: [ content: Text('确定要删除"${todo.title}"吗?此操作不可撤销。'),
TextButton( actions: [
onPressed: () => Navigator.pop(context, false), TextButton(
child: const Text('取消'), 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; ) ?? false;
} }
@@ -540,36 +698,37 @@ class _TodoPageState extends State<TodoPage> {
void _showTodoOptions(TodoItem todo) { void _showTodoOptions(TodoItem todo) {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (context) => Column( builder: (context) =>
mainAxisSize: MainAxisSize.min, Column(
children: [ mainAxisSize: MainAxisSize.min,
ListTile( children: [
leading: const Icon(Icons.content_copy), ListTile(
title: const Text('复制内容'), leading: const Icon(Icons.content_copy),
onTap: () { title: const Text('复制内容'),
Navigator.pop(context); onTap: () {
_copyTodoContent(todo); 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() { void _exportTodos() {
final exportText = _todos.map((todo) { 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'); }).join('\n');
Clipboard.setData(ClipboardData(text: exportText)); Clipboard.setData(ClipboardData(text: exportText));

View File

@@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <rive_native/rive_native_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { 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);
} }

View File

@@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
rive_native
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@@ -5,8 +5,10 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import rive_native
import shared_preferences_foundation import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
RiveNativePlugin.register(with: registry.registrar(forPlugin: "RiveNativePlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
} }

View File

@@ -1,6 +1,14 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.7.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@@ -9,6 +17,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.12.0" 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: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@@ -96,6 +112,30 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@@ -216,6 +256,22 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "6.1.5+1" 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: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -325,6 +381,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.7.4" 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: vector_math:
dependency: transitive dependency: transitive
description: description:

View File

@@ -36,6 +36,7 @@ dependencies:
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
provider: ^6.1.1 # 状态管理 provider: ^6.1.1 # 状态管理
shared_preferences: ^2.2.2 # 本地存储 shared_preferences: ^2.2.2 # 本地存储
awesome_dialog: ^3.3.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -6,6 +6,9 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <rive_native/rive_native_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
RiveNativePluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("RiveNativePlugin"));
} }

View File

@@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
rive_native
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST