diff --git a/lib/layout/app_drawer.dart b/lib/layout/app_drawer.dart index b2fb453..be3990f 100644 --- a/lib/layout/app_drawer.dart +++ b/lib/layout/app_drawer.dart @@ -61,14 +61,6 @@ class AppDrawer extends StatelessWidget { Provider.of(context, listen: false).changeTab(1); }, ), - _buildDrawerItem( - icon: Icons.notifications, - title: '提醒任务', - onTap: () { - Navigator.pop(context); - Provider.of(context, listen: false).changeTab(2); - }, - ), const Divider(), diff --git a/lib/layout/main_screen.dart b/lib/layout/main_screen.dart index cd14454..f4df9f9 100644 --- a/lib/layout/main_screen.dart +++ b/lib/layout/main_screen.dart @@ -1,6 +1,5 @@ import 'package:flisp_app/layout/app_drawer.dart'; import 'package:flisp_app/pages/flash_page.dart'; -import 'package:flisp_app/pages/reminders_page.dart'; import 'package:flisp_app/pages/todo_page.dart'; import 'package:flisp_app/provider/app_provider.dart'; import 'package:flutter/material.dart'; @@ -15,12 +14,10 @@ class MainScreen extends StatefulWidget { class _MainScreenState extends State { final GlobalKey _todoPageKey = GlobalKey(); - final GlobalKey _reminderPageKey = GlobalKey(); List navItems = [ BottomNavigationBarItem(icon: Icon(Icons.flash_on), label: '闪灵'), - BottomNavigationBarItem(icon: Icon(Icons.checklist), label: '待办'), - BottomNavigationBarItem(icon: Icon(Icons.notifications), label: '提醒'), + BottomNavigationBarItem(icon: Icon(Icons.checklist), label: '待办') ]; @override @@ -50,15 +47,10 @@ class _MainScreenState extends State { } void _onPressFloatingButton(int index) { - print(index); if (index == 1) { if (_todoPageKey.currentState != null) { _todoPageKey.currentState!.showAddDialog(); } - } else if (index == 2) { - if (_reminderPageKey.currentState != null) { - _reminderPageKey.currentState!.showAddDialog(); - } } } @@ -97,15 +89,13 @@ class _MainScreenState extends State { return const FlashPage(); case 1: return TodoPage(key: _todoPageKey); - case 2: - return RemindersPage(key: _reminderPageKey); default: return const FlashPage(); } } String _getAppBarTitle(int index) { - final titles = {0: '闪灵', 1: '待办', 2: '提醒'}; + final titles = {0: '闪灵', 1: '待办'}; return titles[index] ?? '闪灵'; } } diff --git a/lib/main.dart b/lib/main.dart index 2be61ab..5de202f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,4 @@ -import 'package:flisp_app/models/reminder.dart'; import 'package:flisp_app/provider/app_provider.dart'; -import 'package:flisp_app/provider/reminder_provider.dart'; import 'package:flisp_app/utils/notify_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; @@ -23,14 +21,15 @@ void main() async{ // 注册适配器 Hive.registerAdapter(TodoAdapter()); - Hive.registerAdapter(ReminderAdapter()); // 打开Box - final todosBox = await Hive.openBox('todos'); - // todosBox.clear(); + if (Hive.isBoxOpen('todos')) { + await Hive.box('todos').close(); + } + await Hive.deleteBoxFromDisk('todos'); - final remindersBox = await Hive.openBox('reminders'); - // remindersBox.clear(); + final todosBox = await Hive.openBox('todos'); + todosBox.clear(); // notifyService.cancelAllNotifications(); @@ -45,8 +44,7 @@ class MyApp extends StatelessWidget { return MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => AppProvider()), - ChangeNotifierProvider(create: (_) => TodoProvider()), - ChangeNotifierProvider(create: (_) => ReminderProvider()), + ChangeNotifierProvider(create: (_) => TodoProvider()) ], child: MaterialApp( title: '闪灵', diff --git a/lib/models/reminder.dart b/lib/models/reminder.dart deleted file mode 100644 index 5add4fd..0000000 --- a/lib/models/reminder.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:hive/hive.dart'; -// flutter packages pub run build_runner build -part 'reminder.g.dart'; - -@HiveType(typeId: 1) -class Reminder extends HiveObject { - @HiveField(0) - late int id; - - @HiveField(1) - late String title; - - @HiveField(2) - late String content; - - @HiveField(3) - late DateTime scheduledTime; - - @HiveField(4) - late DateTime createTime; - - @HiveField(5) - late DateTime updateTime; - - Reminder({ - int? id, - required this.title, - required this.content, - required this.scheduledTime, - DateTime? createTime, - DateTime? updateTime, - }) { - // 简单时间戳ID - this.id = id ?? DateTime.now().millisecondsSinceEpoch ~/ 1000; - this.createTime = createTime ?? DateTime.now(); - this.updateTime = updateTime ?? DateTime.now(); - } - - Reminder copyWith({String? title, String? content, DateTime? scheduledTime}) { - return Reminder( - id: id, - title: title ?? this.title, - content: content ?? this.content, - scheduledTime: scheduledTime ?? this.scheduledTime, - createTime: createTime, - updateTime: DateTime.now(), - ); - } - - static Reminder getEmpty() { - return Reminder( - id: DateTime.now().millisecondsSinceEpoch ~/ 1000, - title: '日常提醒', - content: '', - scheduledTime: DateTime.now().add(Duration(minutes: 5)), - ); - } -} diff --git a/lib/models/reminder.g.dart b/lib/models/reminder.g.dart deleted file mode 100644 index 89ab40f..0000000 --- a/lib/models/reminder.g.dart +++ /dev/null @@ -1,56 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'reminder.dart'; - -// ************************************************************************** -// TypeAdapterGenerator -// ************************************************************************** - -class ReminderAdapter extends TypeAdapter { - @override - final int typeId = 1; - - @override - Reminder read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), - }; - return Reminder( - id: fields[0] as int?, - title: fields[1] as String, - content: fields[2] as String, - scheduledTime: fields[3] as DateTime, - createTime: fields[4] as DateTime?, - updateTime: fields[5] as DateTime?, - ); - } - - @override - void write(BinaryWriter writer, Reminder obj) { - writer - ..writeByte(6) - ..writeByte(0) - ..write(obj.id) - ..writeByte(1) - ..write(obj.title) - ..writeByte(2) - ..write(obj.content) - ..writeByte(3) - ..write(obj.scheduledTime) - ..writeByte(4) - ..write(obj.createTime) - ..writeByte(5) - ..write(obj.updateTime); - } - - @override - int get hashCode => typeId.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is ReminderAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; -} diff --git a/lib/models/todo.dart b/lib/models/todo.dart index 84d0c36..6906c52 100644 --- a/lib/models/todo.dart +++ b/lib/models/todo.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; +// flutter packages pub run build_runner build part 'todo.g.dart'; @HiveType(typeId: 0) @@ -21,12 +22,15 @@ class Todo extends HiveObject { late DateTime? dueDate; @HiveField(5) - late int priorityIndex; + late DateTime? scheduledTime; @HiveField(6) - late DateTime createTime; + late int priorityIndex; @HiveField(7) + late DateTime createTime; + + @HiveField(8) late DateTime updateTime; TodoPriority get priority => TodoPriority.values[priorityIndex]; @@ -39,6 +43,7 @@ class Todo extends HiveObject { this.content = '', this.isCompleted = false, this.dueDate, + this.scheduledTime, TodoPriority priority = TodoPriority.medium, DateTime? createTime, DateTime? updateTime, @@ -55,6 +60,7 @@ class Todo extends HiveObject { String? content, bool? isCompleted, DateTime? dueDate, + DateTime? scheduledTime, TodoPriority? priority, }) { return Todo( @@ -63,6 +69,7 @@ class Todo extends HiveObject { content: content ?? this.content, isCompleted: isCompleted ?? this.isCompleted, dueDate: dueDate ?? this.dueDate, + scheduledTime: scheduledTime ?? this.scheduledTime, priority: priority ?? this.priority, createTime: createTime, updateTime: DateTime.now(), @@ -72,10 +79,11 @@ class Todo extends HiveObject { static Todo getEmpty() { return Todo( id: DateTime.now().millisecondsSinceEpoch ~/ 1000, - title: '日常待办', + title: '', content: '', isCompleted: false, dueDate: null, + scheduledTime: null, priority: TodoPriority.medium, ); } diff --git a/lib/models/todo.g.dart b/lib/models/todo.g.dart index 426a7cd..37a7b2f 100644 --- a/lib/models/todo.g.dart +++ b/lib/models/todo.g.dart @@ -22,15 +22,16 @@ class TodoAdapter extends TypeAdapter { content: fields[2] as String, isCompleted: fields[3] as bool, dueDate: fields[4] as DateTime?, - createTime: fields[6] as DateTime?, - updateTime: fields[7] as DateTime?, - )..priorityIndex = fields[5] as int; + scheduledTime: fields[5] as DateTime?, + createTime: fields[7] as DateTime?, + updateTime: fields[8] as DateTime?, + )..priorityIndex = fields[6] as int; } @override void write(BinaryWriter writer, Todo obj) { writer - ..writeByte(8) + ..writeByte(9) ..writeByte(0) ..write(obj.id) ..writeByte(1) @@ -42,10 +43,12 @@ class TodoAdapter extends TypeAdapter { ..writeByte(4) ..write(obj.dueDate) ..writeByte(5) - ..write(obj.priorityIndex) + ..write(obj.scheduledTime) ..writeByte(6) - ..write(obj.createTime) + ..write(obj.priorityIndex) ..writeByte(7) + ..write(obj.createTime) + ..writeByte(8) ..write(obj.updateTime); } diff --git a/lib/pages/reminders_page.dart b/lib/pages/reminders_page.dart deleted file mode 100644 index 47f46ea..0000000 --- a/lib/pages/reminders_page.dart +++ /dev/null @@ -1,171 +0,0 @@ -import 'package:flisp_app/models/reminder.dart'; -import 'package:flisp_app/provider/reminder_provider.dart'; -import 'package:flisp_app/service/reminder_service.dart'; -import 'package:flisp_app/utils/notify_utils.dart'; -import 'package:flisp_app/widgets/awesome_dialog.dart'; -import 'package:flisp_app/widgets/reminder_form.dart'; -import 'package:flisp_app/widgets/reminder_widget.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_form_builder/flutter_form_builder.dart'; -import 'package:provider/provider.dart'; -import 'package:flisp_app/widgets/common.dart'; - -class RemindersPage extends StatefulWidget { - final VoidCallback? onAddPressed; - - const RemindersPage({super.key, this.onAddPressed}); - - @override - State createState() => ReminderPageState(); -} - -class ReminderPageState extends State { - final ReminderService reminderService = ReminderService(); - final NotifyService notifyService = NotifyService(); - - List _reminders = []; - - void showAddDialog() { - _showDialog(false, null); - } - - @override - void initState() { - super.initState(); - _loadReminders(); - } - - Future _loadReminders() async { - final reminders = await updateReminders(); - if (mounted) { - setState(() { - _reminders = reminders; - }); - } - } - - Future> updateReminders() async { - // 获取flutter_local_notifications中所有的通知ID - final pendingNotifications = await notifyService.getPendingNotifications(); - print("notifyService: ${pendingNotifications.length}"); - final notificationIds = pendingNotifications.map((n) => n.id).toSet(); - - // 获取Hive中所有的提醒 - final hiveReminders = reminderService.getAllReminders(); - print("hiveReminders: ${hiveReminders.length}"); - final hiveReminderIds = hiveReminders.map((r) => r.id).toSet(); - - // 情况1:删除Hive中存在但通知中不存在的提醒(已触发或已取消) - final remindersToDeleteFromHive = - hiveReminders.where((reminder) { - return !notificationIds.contains(reminder.id); - }).toList(); - - for (final reminder in remindersToDeleteFromHive) { - await reminderService.deleteReminder(reminder); - } - - // 情况2:对于通知中存在但Hive中不存在的提醒,重新创建(保留时间等完整信息) - final remindersToRecreate = - pendingNotifications.where((notification) { - return !hiveReminderIds.contains(notification.id); - }).toList(); - - for (final notification in remindersToRecreate) { - final newReminder = Reminder( - id: notification.id, - title: notification.title ?? '新提醒', - content: notification.body ?? '', - scheduledTime: DateTime.now().add(const Duration(days: 1)), - ); - await reminderService.addReminder(newReminder); - } - - // 更新本地列表 - _reminders = reminderService.getAllReminders(); - - return _reminders; - } - - @override - Widget build(BuildContext context) { - return buildBody( - child: Column( - children: [ - Expanded( - child: buildReminderList( - reminders: _reminders, - onEditReminder: (reminder) { - _showDialog(true, reminder); - }, - ), - ), - ], - ), - ); - } - - // 显示对话框 - void _showDialog(bool isEditing, Reminder? reminder) { - final reminderProvider = Provider.of( - context, - listen: false, - ); - - if (isEditing) { - reminderProvider.initForm(reminder!); - } else { - reminderProvider.resetForm(); - } - - final formKey = GlobalKey(); - - showAwesomeDialog( - context: context, - body: ReminderForm( - formKey: formKey, - isEditing: isEditing, - initialReminder: reminder, - ), - onOk: () { - if (formKey.currentState!.saveAndValidate()) { - Navigator.of(context).pop(); - _saveOrUpdateReminder(isEditing, reminderProvider.formItem); - } - }, - onCancel: () { - reminderProvider.resetForm(); - }, - ); - } - - // 保存提醒事项 - void _saveOrUpdateReminder(bool isEditing, Reminder reminder) async { - late bool isSuccess; - - if (isEditing) { - // 先删除 - await notifyService.cancelNotification(reminder.id); - await reminderService.deleteReminder(reminder); - // 在重新新建 - reminder.id = DateTime.now().millisecondsSinceEpoch ~/ 1000; - } - - isSuccess = await reminderService.addReminder(reminder); - - await notifyService.scheduleNotification( - id: reminder.id, - title: reminder.title, - body: reminder.content, - scheduledTime: reminder.scheduledTime, - ); - - await _loadReminders(); - - if (isSuccess) { - showSuccessDialog(context, isEditing ? '更新成功' : '添加成功'); - } else { - showErrorDialog(context, isEditing ? '更新失败' : '添加失败'); - } - } -} diff --git a/lib/pages/todo_page.dart b/lib/pages/todo_page.dart index d3e559e..1ae064b 100644 --- a/lib/pages/todo_page.dart +++ b/lib/pages/todo_page.dart @@ -1,5 +1,6 @@ import 'package:flisp_app/provider/todo_provider.dart'; import 'package:flisp_app/service/todo_service.dart'; +import 'package:flisp_app/utils/notify_utils.dart'; import 'package:flisp_app/widgets/awesome_dialog.dart'; import 'package:flisp_app/widgets/todo_widget.dart'; import 'package:flutter/material.dart'; @@ -21,6 +22,7 @@ class TodoPage extends StatefulWidget { class TodoPageState extends State { final TodoService todoService = TodoService(); + final NotifyService notifyService = NotifyService(); late List _todos; TodoTab _currentTab = TodoTab.active; @@ -78,9 +80,13 @@ class TodoPageState extends State { } // 切换待办事项完成状态 - void _toggleTodo(Todo todo) { + void _toggleTodo(Todo todo) async { + await notifyService.cancelNotification(todo.id); + setState(() { - todoService.updateTodoCompletion(todo, !todo.isCompleted); + todo.isCompleted = !todo.isCompleted; + todo.scheduledTime = null; + todoService.updateTodo(todo); }); } @@ -115,9 +121,22 @@ class TodoPageState extends State { void _saveTodo(bool isEditing, Todo todo) async { late bool isSuccess; if (isEditing) { - isSuccess = await todoService.updateTodo(todo); - } else { - isSuccess = await todoService.addTodo(todo); + // 先删除 + await notifyService.cancelNotification(todo.id); + await todoService.deleteTodo(todo); + // 在重新新建 + todo.id = DateTime.now().millisecondsSinceEpoch ~/ 1000; + } + + isSuccess = await todoService.addTodo(todo); + + if (todo.scheduledTime != null) { + await notifyService.scheduleNotification( + id: todo.id, + title: todo.title, + body: todo.content, + scheduledTime: todo.scheduledTime!, + ); } setState(() { diff --git a/lib/provider/reminder_provider.dart b/lib/provider/reminder_provider.dart deleted file mode 100644 index 5af5722..0000000 --- a/lib/provider/reminder_provider.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flisp_app/models/reminder.dart'; -import 'package:flutter/material.dart'; - -class ReminderProvider with ChangeNotifier { - late Reminder _formItem; - - Reminder get formItem => _formItem; - - void resetForm() { - _formItem = Reminder.getEmpty(); - notifyListeners(); - } - - void initForm(Reminder reminder) { - _formItem = reminder; - notifyListeners(); - } -} diff --git a/lib/service/reminder_service.dart b/lib/service/reminder_service.dart deleted file mode 100644 index d48b3d1..0000000 --- a/lib/service/reminder_service.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:flisp_app/models/reminder.dart'; -import 'package:hive_flutter/hive_flutter.dart'; - -class ReminderService { - static const String boxName = 'reminders'; - - Box get box => Hive.box(boxName); - - Future addReminder(Reminder reminder) async { - try { - await box.add(reminder); - return true; - } catch (e) { - return false; - } - } - - List getAllReminders() { - return box.values.toList(); - } - - Future updateReminder(Reminder reminder) async { - try { - await reminder.save(); - return true; - } catch (e) { - return false; - } - } - - Future deleteReminder(Reminder reminder) async { - try { - await reminder.delete(); - return true; - } catch (e) { - return false; - } - } -} diff --git a/lib/utils/todo_utils.dart b/lib/utils/todo_utils.dart index 6137ea0..934ba2d 100644 --- a/lib/utils/todo_utils.dart +++ b/lib/utils/todo_utils.dart @@ -1,4 +1,5 @@ import 'package:flisp_app/models/todo.dart'; +import 'package:intl/intl.dart'; List getActiveTodos(TodoTab currentTab, List todos) { switch (currentTab) { @@ -20,4 +21,25 @@ List getActiveTodos(TodoTab currentTab, List todos) { default: return todos; } -} \ No newline at end of file +} + +// 格式化截止日期 +String formatDueDate(DateTime dueDate, bool isToday, bool isTomorrow) { + if (isToday) return '今天截止'; + if (isTomorrow) return '明天截止'; + + final now = DateTime.now(); + final difference = dueDate.difference(DateTime(now.year, now.month, now.day)); + + if (difference.inDays < 7) { + return '${difference.inDays}天后截止'; + } + + return DateFormat("MM-dd").format(dueDate); +} + +bool isSameDay(DateTime date1, DateTime date2) { + return date1.year == date2.year && + date1.month == date2.month && + date1.day == date2.day; +} diff --git a/lib/widgets/reminder_form.dart b/lib/widgets/reminder_form.dart deleted file mode 100644 index 4163f36..0000000 --- a/lib/widgets/reminder_form.dart +++ /dev/null @@ -1,208 +0,0 @@ -import 'package:flisp_app/models/reminder.dart'; -import 'package:flisp_app/provider/reminder_provider.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_form_builder/flutter_form_builder.dart'; -import 'package:intl/intl.dart'; -import 'package:provider/provider.dart'; -import 'package:flisp_app/utils/date_utils.dart'; - -class ReminderForm extends StatefulWidget { - final GlobalKey formKey; - final bool isEditing; - final Reminder? initialReminder; - - const ReminderForm({ - super.key, - required this.formKey, - required this.isEditing, - this.initialReminder, - }); - - @override - State createState() => _ReminderFormState(); -} - -class _ReminderFormState extends State { - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - final int maxTitleCount = 10; - final int maxContentCount = 20; - final provider = Provider.of(context); - - Widget buildTitle() { - return Row( - children: [ - Icon( - widget.isEditing ? Icons.edit_note : Icons.add_task, - color: Colors.orange, - size: 24, - ), - SizedBox(width: 8), - Text( - widget.isEditing ? '编辑提醒事项' : '添加提醒事项', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.orange, - ), - ), - ], - ); - } - - FormBuilderTextField buildTitleField() { - return FormBuilderTextField( - name: 'title', - initialValue: provider.formItem.title, - onChanged: (value) { - setState(() { - provider.formItem.title = value ?? ''; - }); - }, - decoration: InputDecoration( - labelText: '标题', - hintText: '请输入提醒事项标题...', - counterText: '', - suffixText: '${provider.formItem.title.length}/$maxTitleCount', - 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), - ), - filled: true, - fillColor: Colors.white, - prefixIcon: Icon(Icons.title, color: Colors.blue), - contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), - ), - maxLength: maxTitleCount, - validator: (value) { - if (value == null || value.isEmpty) { - return '请输入标题'; - } - if (value.length > maxTitleCount) { - return '标题不能超过$maxTitleCount个字符'; - } - return null; - }, - ); - } - - FormBuilderTextField buildContentField() { - return FormBuilderTextField( - name: 'content', - initialValue: provider.formItem.content, - onChanged: (value) { - setState(() { - provider.formItem.content = value ?? ''; - }); - }, - decoration: InputDecoration( - labelText: '内容', - hintText: '请输入提醒事项内容...', - counterText: '', - suffixText: '${provider.formItem.content.length}/$maxContentCount', - 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), - ), - filled: true, - fillColor: Colors.white, - prefixIcon: Icon(Icons.description, color: Colors.green), - contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), - ), - maxLines: 2, - maxLength: maxContentCount, - validator: (value) { - if (value != null && value.length > maxContentCount) { - return '内容不能超过$maxContentCount个字符'; - } - return null; - }, - ); - } - - FormBuilderDateTimePicker buildDateField() { - return FormBuilderDateTimePicker( - name: 'dueDate', - format: DateFormat('yyyy-MM-dd HH:mm:ss'), - initialValue: provider.formItem.scheduledTime, - inputType: InputType.both, - onChanged: (value) { - setState(() { - provider.formItem.scheduledTime = value!; - }); - }, - decoration: InputDecoration( - labelText: '提醒时间', - labelStyle: TextStyle(color: Colors.black87), - prefixIcon: Icon(Icons.calendar_today, color: Colors.purple), - 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), - ), - filled: true, - fillColor: Colors.white, - contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), - ), - validator: (value) { - if (value != null && value.isBefore(DateTime.now())) { - return '不能选择过去的时间'; - } - return null; - }, - ); - } - - FormBuilder buildForm() { - return FormBuilder( - key: widget.formKey, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - buildTitleField(), - SizedBox(height: 12), - buildContentField(), - SizedBox(height: 12), - buildDateField(), - ], - ), - ); - } - - return Padding( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [buildTitle(), SizedBox(height: 20), buildForm()], - ), - ); - } -} diff --git a/lib/widgets/reminder_widget.dart b/lib/widgets/reminder_widget.dart deleted file mode 100644 index ed7c61e..0000000 --- a/lib/widgets/reminder_widget.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'package:flisp_app/models/reminder.dart'; -import 'package:flisp_app/widgets/common.dart'; -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; - -Widget buildReminderList({ - required List reminders, - required ValueChanged onEditReminder, -}) { - return ListView.separated( - itemCount: reminders.length, - separatorBuilder: (context, index) => SizedBox(height: 8), - itemBuilder: (context, index) { - final reminder = reminders[index]; - return _buildReminderItem(reminder: reminder, onEdit: onEditReminder); - }, - ); -} - -Widget _buildReminderItem({ - required Reminder reminder, - required ValueChanged onEdit, -}) { - return buildCard( - child: ListTile( - contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 0), - leading: Icon(Icons.notifications, color: Colors.blue), - title: _buildReminderTitle(reminder), - subtitle: _buildReminderSubtitle(reminder), - onTap: () => onEdit(reminder), - ), - ); -} - -Widget _buildReminderTitle(Reminder reminder) { - return Text( - reminder.title, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ); -} - -Widget _buildReminderSubtitle(Reminder reminder) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 4), - Text( - reminder.content, - style: TextStyle( - fontSize: 14, - color: Colors.grey[700], - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - SizedBox(height: 8), - Container( - padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: Colors.blue[50], - borderRadius: BorderRadius.circular(4), - ), - child: Text( - DateFormat('MM月dd日 HH:mm').format(reminder.scheduledTime), - style: TextStyle( - fontSize: 12, - color: Colors.blue[700], - fontWeight: FontWeight.w500, - ), - ), - ), - ], - ); -} - -// 空状态 -Widget buildEmptyState() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.task_alt, size: 80, color: Colors.orange.shade600), - Text( - '📋 还没有任何待办事项\n点击下方+号开始规划你的任务吧!', - textAlign: TextAlign.center, - style: TextStyle(color: Colors.orange.shade600), - ), - ], - ), - ); -} diff --git a/lib/widgets/todo_form.dart b/lib/widgets/todo_form.dart index 7e2e96a..e2bb304 100644 --- a/lib/widgets/todo_form.dart +++ b/lib/widgets/todo_form.dart @@ -140,7 +140,7 @@ class _TodoFormState extends State { ); } - IconButton buildClearDateSuffixIcon() { + IconButton buildClearDueDateSuffixIcon() { return IconButton( icon: Icon(Icons.clear, size: 18), onPressed: () { @@ -152,7 +152,7 @@ class _TodoFormState extends State { ); } - FormBuilderDateTimePicker buildDateField() { + FormBuilderDateTimePicker buildDueDateField() { return FormBuilderDateTimePicker( name: 'dueDate', format: DateFormat('yyyy-MM-dd'), @@ -192,7 +192,7 @@ class _TodoFormState extends State { contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), suffixIcon: provider.formItem.dueDate != null - ? buildClearDateSuffixIcon() + ? buildClearDueDateSuffixIcon() : null, ), validator: (value) { @@ -205,6 +205,70 @@ class _TodoFormState extends State { ); } + IconButton buildClearScheduledTimeSuffixIcon() { + return IconButton( + icon: Icon(Icons.clear, size: 18), + onPressed: () { + setState(() { + provider.formItem.scheduledTime = null; + }); + widget.formKey.currentState?.fields['scheduledTime']?.didChange(null); + }, + ); + } + + FormBuilderDateTimePicker buildScheduledTimeField() { + return FormBuilderDateTimePicker( + name: 'scheduledTime', + format: DateFormat('yyyy-MM-dd HH:mm:ss'), + initialValue: provider.formItem.scheduledTime, + inputType: InputType.both, + onChanged: (value) { + setState(() { + provider.formItem.scheduledTime = value; + }); + }, + decoration: InputDecoration( + labelText: + provider.formItem.scheduledTime == null + ? '选择提醒日期' + : '提醒: ${DateFormat('yyyy-MM-dd HH:mm:ss').format(provider.formItem.scheduledTime!)}', + labelStyle: TextStyle( + color: + provider.formItem.scheduledTime == null + ? Colors.grey + : Colors.black87, + ), + prefixIcon: Icon(Icons.calendar_today, color: Colors.purple), + 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), + ), + filled: true, + fillColor: Colors.white, + contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), + suffixIcon: + provider.formItem.scheduledTime != null + ? buildClearScheduledTimeSuffixIcon() + : null, + ), + validator: (value) { + if (value != null && value.isBefore(DateTime.now())) { + return '不能选择过去的日期'; + } + return null; + }, + ); + } + FormBuilderRadioGroup builderRadioGroup() { return FormBuilderRadioGroup( name: 'priority', @@ -269,7 +333,9 @@ class _TodoFormState extends State { SizedBox(height: 12), buildContentField(), SizedBox(height: 12), - buildDateField(), + buildDueDateField(), + SizedBox(height: 12), + buildScheduledTimeField(), SizedBox(height: 12), buildPriorityField(), ], diff --git a/lib/widgets/todo_widget.dart b/lib/widgets/todo_widget.dart index b761588..ef9b9b4 100644 --- a/lib/widgets/todo_widget.dart +++ b/lib/widgets/todo_widget.dart @@ -1,4 +1,5 @@ import 'package:flisp_app/models/todo.dart'; +import 'package:flisp_app/utils/todo_utils.dart'; import 'package:flisp_app/widgets/common.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -125,52 +126,128 @@ Widget _buildTodoTitle(Todo todo) { ); } -// 构建待办事项副标题 -Widget? _buildTodoSubtitle(Todo todo) { +Widget _buildContent(Todo todo) { + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Text( + todo.content, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 13, color: Colors.grey[600], height: 1.3), + ), + ); +} + +Widget _buildDueDateChip(Todo todo) { final isOverdue = todo.dueDate != null && - todo.dueDate!.isBefore(DateTime.now()) && - !todo.isCompleted; + todo.dueDate!.isBefore(DateTime.now()) && + !todo.isCompleted; - final hasContent = todo.content.isNotEmpty == true || todo.dueDate != null; + final isDueToday = + todo.dueDate != null && isSameDay(todo.dueDate!, DateTime.now()); + + final isDueTomorrow = + todo.dueDate != null && + isSameDay(todo.dueDate!, DateTime.now().add(Duration(days: 1))); + + return _buildInfoChip( + icon: Icons.access_time_rounded, + text: formatDueDate(todo.dueDate!, isDueToday, isDueTomorrow), + backgroundColor: + isOverdue + ? Colors.red[50]! + : (isDueToday ? Colors.orange[50]! : Colors.grey[50]!), + borderColor: + isOverdue + ? Colors.red[200]! + : (isDueToday ? Colors.orange[200]! : Colors.grey[300]!), + textColor: + isOverdue + ? Colors.red[700]! + : (isDueToday ? Colors.orange[700]! : Colors.grey[700]!), + iconColor: + isOverdue + ? Colors.red[500]! + : (isDueToday ? Colors.orange[500]! : Colors.grey[500]!), + ); +} + +Widget _buildScheduledTimeChip(Todo todo) { + return _buildInfoChip( + icon: Icons.notifications_active_rounded, + text: DateFormat("MM-dd HH:mm").format(todo.scheduledTime!), + backgroundColor: Colors.blue[50]!, + borderColor: Colors.blue[200]!, + textColor: Colors.blue[700]!, + iconColor: Colors.blue[500]!, + ); +} + +// 构建待办事项副标题 +Widget? _buildTodoSubtitle(Todo todo) { + final hasContent = + todo.content.isNotEmpty == true || + todo.dueDate != null || + todo.scheduledTime != null; if (!hasContent) return null; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 4), - if (todo.content.isNotEmpty == true) + return Padding( + padding: const EdgeInsets.only(top: 6), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 内容文本 + if (todo.content.isNotEmpty == true) _buildContent(todo), + // 标签容器 + Wrap( + spacing: 6, + runSpacing: 6, + children: [ + // 截止日期标签 + if (todo.dueDate != null) _buildDueDateChip(todo), + // 提醒时间标签 + if (todo.scheduledTime != null) _buildScheduledTimeChip(todo), + ], + ), + ], + ), + ); +} + +// 构建信息标签 +Widget _buildInfoChip({ + required IconData icon, + required String text, + required Color backgroundColor, + required Color borderColor, + required Color textColor, + required Color iconColor, +}) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: borderColor, width: 1), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 12, color: iconColor), + const SizedBox(width: 4), Text( - todo.content, - maxLines: 1, - overflow: TextOverflow.ellipsis, + text, style: TextStyle( - fontSize: 14, - color: Colors.grey[700], + fontSize: 10, + color: textColor, + fontWeight: FontWeight.w500, + height: 1.2, ), ), - SizedBox(height: 8), - if (todo.dueDate != null) - Container( - padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: isOverdue ? Colors.red[50] : Colors.grey[50], - borderRadius: BorderRadius.circular(4), - border: Border.all( - color: isOverdue ? Colors.red[100]! : Colors.grey[300]!, - ), - ), - child: Text( - '截止: ${DateFormat("yyyy-MM-dd").format(todo.dueDate!)}', - style: TextStyle( - fontSize: 11, - color: isOverdue ? Colors.red[600] : Colors.grey[600], - fontWeight: isOverdue ? FontWeight.w600 : FontWeight.normal, - ), - ), - ), - ], + ], + ), ); }