import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/exceptions.dart'; import '../../data/models/project.dart'; import '../../providers/projects_provider.dart'; class ProjectListScreen extends ConsumerWidget { const ProjectListScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final projectsAsync = ref.watch(projectsProvider); return Scaffold( appBar: AppBar(title: const Text('Projects')), floatingActionButton: FloatingActionButton( onPressed: () => _showCreateDialog(context, ref), tooltip: 'New project', child: const Icon(Icons.add), ), body: projectsAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (e, _) => Center(child: Text('Error: $e')), data: (projects) { if (projects.isEmpty) { return const Center( child: Text( 'No projects yet.\nTap + to create one.', textAlign: TextAlign.center, ), ); } final active = projects.where((p) => p.status == 'active').toList(); final other = projects.where((p) => p.status != 'active').toList(); return ListView( padding: const EdgeInsets.symmetric(vertical: 8), children: [ if (active.isNotEmpty) ...[ _SectionHeader(title: 'Active (${active.length})'), ...active.map((p) => _ProjectTile(project: p)), ], if (other.isNotEmpty) ...[ _SectionHeader(title: 'Other'), ...other.map((p) => _ProjectTile(project: p)), ], ], ); }, ), ); } void _showCreateDialog(BuildContext context, WidgetRef ref) { showDialog( context: context, builder: (dialogContext) => _CreateProjectDialog( onCreate: (title, description, goal) async { try { await ref .read(projectsProvider.notifier) .create(title: title, description: description, goal: goal); } on AppException catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.message)), ); } } }, ), ); } } class _SectionHeader extends StatelessWidget { final String title; const _SectionHeader({required this.title}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 4), child: Text( title, style: Theme.of(context) .textTheme .labelMedium ?.copyWith(color: Theme.of(context).colorScheme.primary), ), ); } } class _ProjectTile extends ConsumerWidget { final Project project; const _ProjectTile({required this.project}); @override Widget build(BuildContext context, WidgetRef ref) { final statusColor = switch (project.status) { 'completed' => Colors.green, 'archived' => Colors.grey, _ => Theme.of(context).colorScheme.primary, }; return ListTile( leading: CircleAvatar( backgroundColor: statusColor.withValues(alpha: 0.15), child: Icon(Icons.folder_outlined, color: statusColor, size: 20), ), title: Text(project.title), subtitle: project.description != null && project.description!.isNotEmpty ? Text( project.description!, maxLines: 1, overflow: TextOverflow.ellipsis, ) : null, trailing: _StatusChip(status: project.status), onLongPress: () => _showOptions(context, ref), ); } void _showOptions(BuildContext context, WidgetRef ref) { showModalBottomSheet( context: context, builder: (_) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.check_circle_outline), title: const Text('Mark completed'), onTap: () async { Navigator.pop(context); await ref.read(projectsProvider.notifier).updateProject( project.id, {'status': 'completed'}, ); }, ), ListTile( leading: const Icon(Icons.archive_outlined), title: const Text('Archive'), onTap: () async { Navigator.pop(context); await ref.read(projectsProvider.notifier).updateProject( project.id, {'status': 'archived'}, ); }, ), ListTile( leading: Icon(Icons.delete_outline, color: Theme.of(context).colorScheme.error), title: Text('Delete', style: TextStyle( color: Theme.of(context).colorScheme.error)), onTap: () async { Navigator.pop(context); final confirm = await showDialog( context: context, builder: (dialogContext) => AlertDialog( title: const Text('Delete project?'), content: Text( 'Notes and tasks will be unlinked, not deleted.'), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext, false), child: const Text('Cancel'), ), TextButton( onPressed: () => Navigator.pop(dialogContext, true), child: const Text('Delete'), ), ], ), ); if (confirm == true && context.mounted) { await ref .read(projectsProvider.notifier) .delete(project.id); } }, ), ], ), ), ); } } class _StatusChip extends StatelessWidget { final String status; const _StatusChip({required this.status}); @override Widget build(BuildContext context) { final (label, color) = switch (status) { 'completed' => ('Done', Colors.green), 'archived' => ('Archived', Colors.grey), _ => ('Active', Theme.of(context).colorScheme.primary), }; return Chip( label: Text(label, style: const TextStyle(fontSize: 11)), padding: EdgeInsets.zero, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, side: BorderSide(color: color.withValues(alpha: 0.4)), backgroundColor: color.withValues(alpha: 0.1), labelStyle: TextStyle(color: color), ); } } class _CreateProjectDialog extends StatefulWidget { final Future Function(String title, String? description, String? goal) onCreate; const _CreateProjectDialog({required this.onCreate}); @override State<_CreateProjectDialog> createState() => _CreateProjectDialogState(); } class _CreateProjectDialogState extends State<_CreateProjectDialog> { final _titleController = TextEditingController(); final _descController = TextEditingController(); final _goalController = TextEditingController(); bool _saving = false; @override void dispose() { _titleController.dispose(); _descController.dispose(); _goalController.dispose(); super.dispose(); } Future _submit() async { final title = _titleController.text.trim(); if (title.isEmpty) return; setState(() => _saving = true); try { await widget.onCreate( title, _descController.text.trim().isEmpty ? null : _descController.text.trim(), _goalController.text.trim().isEmpty ? null : _goalController.text.trim(), ); if (mounted) Navigator.pop(context); } finally { if (mounted) setState(() => _saving = false); } } @override Widget build(BuildContext context) { return AlertDialog( title: const Text('New Project'), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: _titleController, decoration: const InputDecoration( labelText: 'Title', border: OutlineInputBorder(), ), autofocus: true, textInputAction: TextInputAction.next, ), const SizedBox(height: 12), TextField( controller: _descController, decoration: const InputDecoration( labelText: 'Description (optional)', border: OutlineInputBorder(), ), maxLines: 2, textInputAction: TextInputAction.next, ), const SizedBox(height: 12), TextField( controller: _goalController, decoration: const InputDecoration( labelText: 'Goal (optional)', border: OutlineInputBorder(), ), textInputAction: TextInputAction.done, onSubmitted: (_) => _submit(), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancel'), ), FilledButton( onPressed: _saving ? null : _submit, child: _saving ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Create'), ), ], ); } }