import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../models/profile.model.dart'; import '../../models/programmer_project.model.dart'; import '../../models/programmer_task.model.dart'; import '../../providers/profile_provider.dart'; import '../../providers/programmer_projects_provider.dart'; import '../../providers/programmer_tasks_provider.dart'; import '../../widgets/quill_field.dart'; /// Shows the "New Programmer Task" dialog and, on success, navigates to the new /// task's detail. Shared by the tasks list FAB and the project detail screen /// (pass [initialProjectId] to pre-select a project). Future showCreateProgrammerTaskDialog( BuildContext context, WidgetRef ref, { String? initialProjectId, }) async { final userId = ref.read(currentUserIdProvider); final profiles = ref.read(profilesProvider).valueOrNull ?? const []; final eligible = profiles .where((p) => p.role == 'programmer' || p.role == 'admin') .toList() ..sort((a, b) => a.fullName.compareTo(b.fullName)); final projectOptions = List.from(ref.read(activeProgrammerProjectsProvider)); final titleCtrl = TextEditingController(); final descQuill = quillControllerFromDelta(null); var category = ProgrammerTaskCategory.softwareDevelopment; var assigneeId = userId; var projectId = initialProjectId; final created = await showDialog( context: context, builder: (context) { return StatefulBuilder( builder: (context, setDialogState) { final dialogWidth = math.min(480.0, MediaQuery.of(context).size.width - 48); return AlertDialog( semanticLabel: 'New programmer task', title: const Text('New Programmer Task'), content: SizedBox( width: dialogWidth, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextField( controller: titleCtrl, autofocus: true, textCapitalization: TextCapitalization.sentences, decoration: const InputDecoration( labelText: 'Title', hintText: 'What are you working on?', ), ), const SizedBox(height: 12), Align( alignment: Alignment.centerLeft, child: Text('Description (optional)', style: Theme.of(context).textTheme.bodySmall), ), const SizedBox(height: 4), QuillFieldEditor( controller: descQuill, placeholder: 'Describe the task…', height: 140, ), const SizedBox(height: 12), DropdownButtonFormField( initialValue: category, decoration: const InputDecoration(labelText: 'Category'), items: [ for (final c in ProgrammerTaskCategory.all) DropdownMenuItem(value: c, child: Text(c)), ], onChanged: (v) => setDialogState(() => category = v ?? category), ), if (eligible.isNotEmpty) ...[ const SizedBox(height: 12), DropdownButtonFormField( initialValue: assigneeId, decoration: const InputDecoration(labelText: 'Assignee'), items: [ for (final p in eligible) DropdownMenuItem( value: p.id, child: Text( p.id == userId ? '${p.fullName} (me)' : p.fullName, ), ), ], onChanged: (v) => setDialogState(() => assigneeId = v ?? assigneeId), ), ], const SizedBox(height: 12), DropdownButtonFormField( initialValue: projectOptions.any((p) => p.id == projectId) ? projectId : null, decoration: const InputDecoration(labelText: 'Project (optional)'), items: [ const DropdownMenuItem( value: null, child: Text('No project')), for (final p in projectOptions) DropdownMenuItem( value: p.id, child: Text(p.name)), ], onChanged: (v) => setDialogState(() => projectId = v), ), Align( alignment: Alignment.centerLeft, child: TextButton.icon( onPressed: () async { final proj = await _promptNewProject(context, ref); if (proj != null) { setDialogState(() { projectOptions.add(proj); projectId = proj.id; }); } }, icon: const Icon(Icons.add, size: 18), label: const Text('New project'), ), ), ], ), ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('Cancel'), ), FilledButton( onPressed: () => Navigator.of(context).pop(true), child: const Text('Create'), ), ], ); }, ); }, ); if (created != true) { titleCtrl.dispose(); descQuill.dispose(); return; } final title = titleCtrl.text.trim(); titleCtrl.dispose(); final description = quillPlainText(descQuill).isEmpty ? null : quillToDeltaJson(descQuill); descQuill.dispose(); if (title.isEmpty) return; if (!context.mounted) return; final messenger = ScaffoldMessenger.of(context); try { final id = await ref.read(programmerTasksControllerProvider).createTask( title: title, category: category, description: description, assigneeId: assigneeId, projectId: projectId, ); if (!context.mounted) return; context.go('/programmer-tasks/$id'); } catch (e) { messenger.showSnackBar(SnackBar(content: Text('Could not create task: $e'))); } } /// Prompts for a project name, creates it, and returns a lightweight model for /// the picker (the realtime insert with the same id syncs shortly after). Future _promptNewProject( BuildContext context, WidgetRef ref, ) async { final messenger = ScaffoldMessenger.of(context); final nameCtrl = TextEditingController(); final name = await showDialog( context: context, builder: (context) => AlertDialog( semanticLabel: 'New project', title: const Text('New project'), content: TextField( controller: nameCtrl, autofocus: true, textCapitalization: TextCapitalization.words, decoration: const InputDecoration(labelText: 'Project name'), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel'), ), FilledButton( onPressed: () => Navigator.of(context).pop(nameCtrl.text.trim()), child: const Text('Create'), ), ], ), ); nameCtrl.dispose(); if (name == null || name.isEmpty) return null; try { final id = await ref .read(programmerProjectsControllerProvider) .createProject(name: name); final now = DateTime.now().toUtc(); return ProgrammerProject( id: id, name: name, status: ProgrammerProjectStatus.active, createdAt: now, updatedAt: now, ); } catch (e) { messenger.showSnackBar( SnackBar(content: Text('Could not create project: $e')), ); return null; } }