import 'package:flisp_app/provider/todo_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/models/todo.dart'; class TodoForm extends StatefulWidget { final GlobalKey formKey; final bool isEditing; final Todo? initialTodo; const TodoForm({ super.key, required this.formKey, required this.isEditing, this.initialTodo, }); @override State createState() => _TodoFormState(); } class _TodoFormState extends State { final int maxTitleCount = 10; final int maxContentCount = 20; @override void initState() { super.initState(); } Widget buildTitle() { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( widget.isEditing ? Icons.edit_note : Icons.add_task, color: Theme.of(context).colorScheme.primary, size: 24, ), SizedBox(width: 8), Text( widget.isEditing ? '编辑待办事项' : '添加待办事项', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.primary, ), ), ], ); } FormBuilderTextField buildTitleField(TodoProvider provider) { final colors = Theme.of(context).colorScheme; return FormBuilderTextField( name: 'title', initialValue: provider.formItem.title, onChanged: (value) { setState(() { provider.formItem.title = value ?? ''; }); }, decoration: InputDecoration( label: RichText( text: TextSpan( text: '标题', style: TextStyle( color: Colors.grey, fontSize: 16, fontFamily: 'CustomFont', ), children: const [ TextSpan( text: '*', style: TextStyle( color: Colors.red, fontSize: 18, fontWeight: FontWeight.bold, ), ), ], ), ), hintText: '请输入待办事项标题...', hintStyle: TextStyle(color: Colors.grey), 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.primary), ), filled: true, fillColor: colors.surface, 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(TodoProvider provider) { final colors = Theme.of(context).colorScheme; return FormBuilderTextField( name: 'content', initialValue: provider.formItem.content, onChanged: (value) { setState(() { provider.formItem.content = value ?? ''; }); }, decoration: InputDecoration( labelText: '内容', labelStyle: TextStyle(color: Colors.grey), hintText: '请输入待办事项内容...', hintStyle: TextStyle(color: Colors.grey), 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.primary), ), filled: true, fillColor: colors.surface, 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; }, ); } IconButton buildClearDueDateSuffixIcon(TodoProvider provider) { return IconButton( icon: Icon(Icons.clear, size: 18), onPressed: () { setState(() { provider.formItem.dueDate = null; }); widget.formKey.currentState?.fields['dueDate']?.didChange(null); }, ); } FormBuilderDateTimePicker buildDueDateField(TodoProvider provider) { final colors = Theme.of(context).colorScheme; return FormBuilderDateTimePicker( name: 'dueDate', format: DateFormat('yyyy-MM-dd'), initialValue: provider.formItem.dueDate, inputType: InputType.date, onChanged: (value) { setState(() { provider.formItem.dueDate = value; }); }, decoration: InputDecoration( labelText: provider.formItem.dueDate == null ? '选择截止日期' : '截止日期: ${DateFormat('yyyy-MM-dd').format(provider.formItem.dueDate!)}', labelStyle: TextStyle(color: Colors.grey), prefixIcon: Icon(Icons.calendar_month, 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.primary), ), filled: true, fillColor: colors.surface, contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), suffixIcon: provider.formItem.dueDate != null ? buildClearDueDateSuffixIcon(provider) : null, ), validator: (value) { if (value != null && value.isBefore(DateTime.now().subtract(Duration(days: 1)))) { return '不能选择过去的日期'; } return null; }, ); } IconButton buildClearScheduledTimeSuffixIcon(TodoProvider provider) { return IconButton( icon: Icon(Icons.clear, size: 18), onPressed: () { setState(() { provider.formItem.scheduledTime = null; }); widget.formKey.currentState?.fields['scheduledTime']?.didChange(null); }, ); } FormBuilderDateTimePicker buildScheduledTimeField(TodoProvider provider) { final colors = Theme.of(context).colorScheme; 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: Colors.grey), prefixIcon: Icon(Icons.calendar_month, 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.primary), ), filled: true, fillColor: colors.surface, contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), suffixIcon: provider.formItem.scheduledTime != null ? buildClearScheduledTimeSuffixIcon(provider) : null, ), validator: (value) { if (value != null && value.isBefore(DateTime.now())) { return '不能选择过去的日期'; } return null; }, ); } FormBuilderRadioGroup builderRadioGroup(TodoProvider provider) { return FormBuilderRadioGroup( name: 'priority', initialValue: provider.formItem.priority, decoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.zero, ), orientation: OptionsOrientation.horizontal, wrapSpacing: 6, options: TodoPriority.values.map((priority) { return FormBuilderFieldOption( value: priority, child: Row( mainAxisSize: MainAxisSize.min, children: [Text(priority.label)], ), ); }).toList(), onChanged: (value) { if (value != null) { setState(() { provider.formItem.priority = value; }); } }, ); } Container buildPriorityField(TodoProvider provider) { return Container( decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), ), padding: EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.flag, color: Colors.orange), SizedBox(width: 6), Text('优先级'), ], ), builderRadioGroup(provider), ], ), ); } FormBuilder buildForm(TodoProvider provider) { return FormBuilder( key: widget.formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ buildTitleField(provider), SizedBox(height: 12), buildContentField(provider), SizedBox(height: 12), buildDueDateField(provider), SizedBox(height: 12), buildScheduledTimeField(provider), SizedBox(height: 12), buildPriorityField(provider), ], ), ); } @override Widget build(BuildContext context) { final provider = Provider.of(context); return Padding( padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 10), child: Column( mainAxisSize: MainAxisSize.min, children: [buildTitle(), SizedBox(height: 20), buildForm(provider)], ), ); } }