// todo_form.dart (適配 Todo Model) import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import './todo_model.dart'; import './todo_api.dart'; class TodoForm extends StatefulWidget { final String userId; const TodoForm({required this.userId, super.key}); @override State createState() => _TodoFormState(); } class _TodoFormState extends State { final _formKey = GlobalKey(); late TodoApiService _apiService; // 控制器與狀態 final TextEditingController _nameController = TextEditingController(); final TextEditingController _descController = TextEditingController(); String _selectedClass = 'Work'; String _selectedPriority = 'Medium'; DateTime _endDate = DateTime.now().add(const Duration(days: 7)); final Color primaryPurple = const Color(0xFF6542D0); final Color bgLight = const Color(0xFFF8F9FA); @override void initState() { super.initState(); _apiService = TodoApiService(currentUserId: widget.userId); } Future _selectEndDate() async { final picked = await showDatePicker( context: context, initialDate: _endDate, firstDate: DateTime.now(), lastDate: DateTime(2030), ); if (picked != null) setState(() => _endDate = picked); } void _handleSubmit() async { if (_formKey.currentState!.validate()) { // 構建 Todo 物件 final newTodo = Todo( id: 0, // 由後端生成 taskName: _nameController.text, className: _selectedClass, description: _descController.text, priority: _selectedPriority, status: 'WIP', endDate: _endDate, createdBy: widget.userId, ); bool success = await _apiService.createTodo(newTodo); if (mounted) { if (success) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('任務新增成功!'))); Navigator.pop(context, true); // 回傳 true 告知列表頁刷新 } else { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('新增失敗,請檢查網路連線。'))); } } } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: bgLight, appBar: AppBar( backgroundColor: bgLight, elevation: 0, leading: IconButton(icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black87), onPressed: () => Navigator.pop(context)), title: const Text('New Task', style: TextStyle(color: Colors.black87, fontWeight: FontWeight.bold)), centerTitle: true, ), body: SingleChildScrollView( padding: const EdgeInsets.all(24.0), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildDropdownField('Task Category', _selectedClass, ['Work', 'Personal', 'Urgent'], (val) => setState(() => _selectedClass = val!)), const SizedBox(height: 20), _buildTextField('Task Name', _nameController, 'e.g. Design UI Mockup', Icons.edit_note), const SizedBox(height: 20), _buildTextField('Description', _descController, 'Enter details here...', Icons.description, isMultiline: true), const SizedBox(height: 20), _buildDropdownField('Priority', _selectedPriority, ['High', 'Medium', 'Low'], (val) => setState(() => _selectedPriority = val!)), const SizedBox(height: 20), _buildDatePicker('Due Date', _endDate), const SizedBox(height: 40), _buildSubmitButton(), ], ), ), ), ); } // --- 重構後的 UI 元件 --- Widget _buildTextField(String label, TextEditingController controller, String hint, IconData icon, {bool isMultiline = false}) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 5))]), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: const TextStyle(fontSize: 12, color: Colors.grey)), TextFormField( controller: controller, maxLines: isMultiline ? 3 : 1, decoration: InputDecoration(hintText: hint, border: InputBorder.none, isDense: true, contentPadding: const EdgeInsets.only(top: 8)), validator: (v) => v == null || v.isEmpty ? 'Cannot be empty' : null, ) ], ), ); } Widget _buildDropdownField(String label, String value, List items, ValueChanged onChanged) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 5))]), child: DropdownButtonHideUnderline( child: DropdownButtonFormField( value: value, decoration: InputDecoration(labelText: label, labelStyle: const TextStyle(fontSize: 14, color: Colors.grey), border: InputBorder.none), items: items.map((s) => DropdownMenuItem(value: s, child: Text(s, style: const TextStyle(fontWeight: FontWeight.bold)))).toList(), onChanged: onChanged, ), ), ); } Widget _buildDatePicker(String label, DateTime date) { return InkWell( onTap: _selectEndDate, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 5))]), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: const TextStyle(fontSize: 12, color: Colors.grey)), const SizedBox(height: 4), Text(DateFormat('dd MMM, yyyy').format(date), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), ], ), Icon(Icons.calendar_today, color: primaryPurple, size: 20), ], ), ), ); } Widget _buildSubmitButton() { return SizedBox( width: double.infinity, height: 56, child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: primaryPurple, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), elevation: 5), onPressed: _handleSubmit, child: const Text('Create Task', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white)), ), ); } }