import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:timetrack/features/projects/domain/project.dart'; import 'package:timetrack/features/projects/domain/projects_provider.dart'; import 'package:timetrack/features/projects/presentation/widgets/color_picker_row.dart'; class ProjectFormSheet extends ConsumerStatefulWidget { const ProjectFormSheet({super.key, this.existing}); final Project? existing; static Future show(BuildContext context, {Project? existing}) { return showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (_) => ProjectFormSheet(existing: existing), ); } @override ConsumerState createState() => _ProjectFormSheetState(); } class _ProjectFormSheetState extends ConsumerState { late int _color; final _nameController = TextEditingController(); final _descController = TextEditingController(); String? _error; @override void initState() { super.initState(); _color = widget.existing?.colorValue ?? ColorPickerRow.presetColors.first; _nameController.text = widget.existing?.name ?? ''; _descController.text = widget.existing?.description ?? ''; } @override void dispose() { _nameController.dispose(); _descController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom + 16, top: 16, left: 16, right: 16, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.existing == null ? 'New Project' : 'Edit Project', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 16), TextField( key: const Key('project_name_field'), controller: _nameController, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Name', ), textCapitalization: TextCapitalization.words, ), const SizedBox(height: 12), TextField( controller: _descController, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Description (optional)', ), ), const SizedBox(height: 16), Text('Color', style: Theme.of(context).textTheme.labelLarge), const SizedBox(height: 8), ColorPickerRow( selected: _color, onChanged: (c) => setState(() => _color = c), ), if (_error != null) ...[ const SizedBox(height: 8), Text(_error!, style: TextStyle(color: Theme.of(context).colorScheme.error)), ], const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancel'), ), const SizedBox(width: 8), FilledButton( key: const Key('project_save_button'), onPressed: _save, child: const Text('Save'), ), ], ), ], ), ); } Future _save() async { final name = _nameController.text.trim(); if (name.isEmpty) { setState(() => _error = 'Name is required.'); return; } final notifier = ref.read(projectsNotifierProvider.notifier); if (widget.existing == null) { await notifier.create( name: name, colorValue: _color, description: _descController.text.trim().isEmpty ? null : _descController.text.trim(), ); } else { await notifier.updateProject(widget.existing!.copyWith( name: name, colorValue: _color, description: _descController.text.trim().isEmpty ? null : _descController.text.trim(), )); } if (mounted) Navigator.pop(context); } }