import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/exceptions.dart'; import '../../data/models/task.dart'; import '../../providers/api_client_provider.dart'; import '../../providers/tasks_provider.dart'; class TaskEditScreen extends ConsumerStatefulWidget { final int? taskId; const TaskEditScreen({super.key, this.taskId}); @override ConsumerState createState() => _TaskEditScreenState(); } class _TaskEditScreenState extends ConsumerState { final _formKey = GlobalKey(); final _titleController = TextEditingController(); final _descController = TextEditingController(); TaskStatus _status = TaskStatus.todo; TaskPriority _priority = TaskPriority.medium; DateTime? _dueDate; bool _saving = false; bool _loaded = false; @override void dispose() { _titleController.dispose(); _descController.dispose(); super.dispose(); } Future _loadExisting() async { if (_loaded || widget.taskId == null) { _loaded = true; return; } final task = await ref.read(tasksRepositoryProvider).getOne(widget.taskId!); _titleController.text = task.title; _descController.text = task.description ?? ''; _status = task.status; _priority = task.priority; _dueDate = task.dueDate; _loaded = true; } Future _save() async { if (!_formKey.currentState!.validate()) return; setState(() => _saving = true); try { if (widget.taskId == null) { await ref.read(tasksProvider.notifier).create( title: _titleController.text.trim(), description: _descController.text.trim().isEmpty ? null : _descController.text.trim(), status: _status, priority: _priority, dueDate: _dueDate, ); } else { await ref.read(tasksProvider.notifier).updateTask(widget.taskId!, { 'title': _titleController.text.trim(), 'description': _descController.text.trim().isEmpty ? null : _descController.text.trim(), 'status': _status.value, 'priority': _priority.value, 'due_date': _dueDate?.toIso8601String(), }); } if (mounted) context.pop(); } on AppException catch (e) { if (mounted) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(e.message))); } } finally { if (mounted) setState(() => _saving = false); } } Future _delete() async { final confirm = await showDialog( context: context, builder: (_) => AlertDialog( title: const Text('Delete task?'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Cancel')), TextButton( onPressed: () => Navigator.pop(context, true), child: const Text('Delete')), ], ), ); if (confirm == true) { await ref.read(tasksProvider.notifier).delete(widget.taskId!); if (mounted) context.pop(); } } Future _pickDate() async { final date = await showDatePicker( context: context, initialDate: _dueDate ?? DateTime.now(), firstDate: DateTime(2020), lastDate: DateTime(2100), ); if (date != null) setState(() => _dueDate = date); } @override Widget build(BuildContext context) { return FutureBuilder( future: _loadExisting(), builder: (context, snapshot) { return Scaffold( appBar: AppBar( title: Text(widget.taskId == null ? 'New Task' : 'Edit Task'), actions: [ if (widget.taskId != null) IconButton( icon: const Icon(Icons.delete), onPressed: _delete, ), IconButton( icon: _saving ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.check), onPressed: _saving ? null : _save, ), ], ), body: snapshot.connectionState == ConnectionState.waiting ? const Center(child: CircularProgressIndicator()) : Form( key: _formKey, child: ListView( padding: const EdgeInsets.all(16), children: [ TextFormField( controller: _titleController, decoration: const InputDecoration( labelText: 'Title', border: OutlineInputBorder(), ), validator: (v) => (v == null || v.trim().isEmpty) ? 'Required' : null, ), const SizedBox(height: 16), TextFormField( controller: _descController, decoration: const InputDecoration( labelText: 'Description (optional)', border: OutlineInputBorder(), ), maxLines: 3, ), const SizedBox(height: 16), DropdownButtonFormField( initialValue: _status, decoration: const InputDecoration( labelText: 'Status', border: OutlineInputBorder(), ), items: TaskStatus.values .map((s) => DropdownMenuItem( value: s, child: Text(s.label))) .toList(), onChanged: (v) => setState(() => _status = v!), ), const SizedBox(height: 16), DropdownButtonFormField( initialValue: _priority, decoration: const InputDecoration( labelText: 'Priority', border: OutlineInputBorder(), ), items: TaskPriority.values .map((p) => DropdownMenuItem( value: p, child: Text(p.label))) .toList(), onChanged: (v) => setState(() => _priority = v!), ), const SizedBox(height: 16), ListTile( contentPadding: EdgeInsets.zero, title: Text(_dueDate == null ? 'No due date' : 'Due: ${_dueDate!.toLocal().toString().substring(0, 10)}'), leading: const Icon(Icons.calendar_today), trailing: _dueDate != null ? IconButton( icon: const Icon(Icons.clear), onPressed: () => setState(() => _dueDate = null), ) : null, onTap: _pickDate, ), ], ), ), ); }, ); } }