import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import '../../core/theme/app_theme.dart'; import '../../core/widgets/status_badge.dart'; import '../../core/widgets/error_view.dart'; import 'tasks_provider.dart'; class TaskDetailScreen extends ConsumerStatefulWidget { final int id; const TaskDetailScreen({super.key, required this.id}); @override ConsumerState createState() => _TaskDetailScreenState(); } class _TaskDetailScreenState extends ConsumerState { final _commentCtrl = TextEditingController(); bool _sendingComment = false; @override void dispose() { _commentCtrl.dispose(); super.dispose(); } Future _sendComment() async { if (_commentCtrl.text.trim().isEmpty) return; setState(() => _sendingComment = true); try { await addComment(widget.id, _commentCtrl.text.trim()); _commentCtrl.clear(); ref.invalidate(taskDetailProvider(widget.id)); } catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.toString()), backgroundColor: AppColors.danger)); } finally { if (mounted) setState(() => _sendingComment = false); } } Future _updateStatus(String newStatus) async { try { await updateTask(widget.id, {'status': newStatus}); ref.invalidate(taskDetailProvider(widget.id)); ref.read(tasksProvider.notifier).load(); } catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.toString()), backgroundColor: AppColors.danger)); } } Future _delete() async { final confirm = await showDialog(context: context, builder: (_) => AlertDialog( title: const Text('Delete Task'), content: const Text('This task will be permanently deleted.'), actions: [ TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Cancel')), TextButton(onPressed: () => Navigator.pop(context, true), child: const Text('Delete', style: TextStyle(color: AppColors.danger))), ], )); if (confirm != true) return; try { await deleteTask(widget.id); if (mounted) { context.pop(); ref.read(tasksProvider.notifier).load(); } } catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.toString()), backgroundColor: AppColors.danger)); } } @override Widget build(BuildContext context) { final async = ref.watch(taskDetailProvider(widget.id)); return async.when( loading: () => const Scaffold(body: Center(child: CircularProgressIndicator())), error: (e, _) => Scaffold( appBar: AppBar(), body: ErrorView(message: e.toString(), onRetry: () => ref.invalidate(taskDetailProvider(widget.id))), ), data: (task) => Scaffold( appBar: AppBar( title: const Text('Task Detail'), actions: [ IconButton( icon: const Icon(Icons.edit_outlined), onPressed: () async { await context.push('/tasks/${task.id}/edit'); ref.invalidate(taskDetailProvider(widget.id)); }, ), IconButton( icon: const Icon(Icons.delete_outline, color: AppColors.danger), onPressed: _delete, ), ], ), body: Column( children: [ Expanded( child: ListView( padding: const EdgeInsets.all(16), children: [ // Header card _card(Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Expanded(child: Text(task.title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: AppColors.text))), const SizedBox(width: 8), StatusBadge(task.status), ]), const SizedBox(height: 16), _infoRow(Icons.person_outline, 'Assignee', task.assignedUsername ?? '-'), _infoRow(Icons.person_outlined, 'Created by', task.createdByUsername), if (task.dueDate != null) _infoRow(Icons.calendar_today_outlined, 'Deadline', DateFormat('d MMMM yyyy').format(task.dueDate!), color: task.isOverdue ? AppColors.danger : null), if (task.completedAt != null) _infoRow(Icons.check_circle_outline, 'Completed', DateFormat('d MMM yyyy HH:mm').format(task.completedAt!)), ], )), const SizedBox(height: 12), // Status update _card(Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Update Status', style: TextStyle(fontWeight: FontWeight.w700, fontSize: 13)), const SizedBox(height: 10), Row( children: [ for (final s in ['todo', 'in_progress', 'done']) Expanded(child: Padding( padding: const EdgeInsets.only(right: 6), child: OutlinedButton( style: OutlinedButton.styleFrom( backgroundColor: task.status == s ? AppColors.primary : null, foregroundColor: task.status == s ? Colors.white : AppColors.text, side: BorderSide(color: task.status == s ? AppColors.primary : AppColors.border), minimumSize: const Size(0, 36), textStyle: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600), padding: EdgeInsets.zero, ), onPressed: task.status == s ? null : () => _updateStatus(s), child: Text(s == 'in_progress' ? 'Progress' : s.capitalize()), ), )), ], ), ], )), const SizedBox(height: 12), // Subtasks if (task.subtasks.isNotEmpty) ...[ _card(Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Subtasks (${task.subtasks.length})', style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 13)), const SizedBox(height: 10), ...task.subtasks.map((s) => ListTile( contentPadding: EdgeInsets.zero, dense: true, leading: StatusBadge(s.status, small: true), title: Text(s.title, style: const TextStyle(fontSize: 13)), onTap: () async { await context.push('/tasks/${task.id}/subtasks/${s.id}'); ref.invalidate(taskDetailProvider(widget.id)); }, )), ], )), const SizedBox(height: 12), ], // Comments _card(Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Comments (${task.comments.length})', style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 13)), if (task.comments.isEmpty) const Padding( padding: EdgeInsets.symmetric(vertical: 12), child: Text('No comments yet', style: TextStyle(color: AppColors.textSub, fontSize: 13)), ), ...task.comments.map((c) => Padding( padding: const EdgeInsets.only(top: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ CircleAvatar( radius: 16, backgroundColor: AppColors.primaryLight, child: Text(c.username.isNotEmpty ? c.username[0].toUpperCase() : '?', style: const TextStyle(color: AppColors.primary, fontSize: 12, fontWeight: FontWeight.w700)), ), const SizedBox(width: 10), Expanded(child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Text(c.username, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 13)), const SizedBox(width: 8), Text(DateFormat('d MMM HH:mm').format(c.createdAt), style: const TextStyle(fontSize: 11, color: AppColors.textSub)), ]), const SizedBox(height: 4), Text(c.body, style: const TextStyle(fontSize: 13, color: AppColors.text)), ], )), ], ), )), ], )), const SizedBox(height: 16), ], ), ), // Sticky comment bar Container( padding: EdgeInsets.only( left: 16, right: 16, top: 12, bottom: MediaQuery.of(context).viewInsets.bottom + 12, ), decoration: const BoxDecoration( color: AppColors.surface, border: Border(top: BorderSide(color: AppColors.border)), ), child: Row( children: [ Expanded( child: TextField( controller: _commentCtrl, decoration: const InputDecoration(hintText: 'Write a comment...', isDense: true), textInputAction: TextInputAction.send, onSubmitted: (_) => _sendComment(), ), ), const SizedBox(width: 8), IconButton( onPressed: _sendingComment ? null : _sendComment, icon: _sendingComment ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2)) : const Icon(Icons.send, color: AppColors.primary), ), ], ), ), ], ), ), ); } Widget _card(Widget child) => Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.border), ), child: child, ); Widget _infoRow(IconData icon, String label, String value, {Color? color}) => Padding( padding: const EdgeInsets.only(bottom: 8), child: Row(children: [ Icon(icon, size: 15, color: AppColors.textSub), const SizedBox(width: 8), Text('$label: ', style: const TextStyle(fontSize: 13, color: AppColors.textSub)), Expanded(child: Text(value, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: color ?? AppColors.text))), ]), ); } extension StringExt on String { String capitalize() => isEmpty ? this : this[0].toUpperCase() + substring(1); }