import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import '../../../core/database/database.dart'; import '../../../core/providers/database_provider.dart'; import '../../../l10n/app_localizations.dart'; import '../domain/effort_level.dart'; import '../domain/frequency.dart'; import 'task_history_sheet.dart'; import 'task_providers.dart'; /// Full-screen form for task creation and editing. /// /// Pass [roomId] for create mode, or [taskId] for edit mode. class TaskFormScreen extends ConsumerStatefulWidget { final int? roomId; final int? taskId; const TaskFormScreen({super.key, this.roomId, this.taskId}); bool get isEditing => taskId != null; @override ConsumerState createState() => _TaskFormScreenState(); } class _TaskFormScreenState extends ConsumerState { final _formKey = GlobalKey(); final _nameController = TextEditingController(); final _descriptionController = TextEditingController(); final _customIntervalController = TextEditingController(text: '2'); FrequencyInterval? _selectedPreset; bool _isCustomFrequency = false; _CustomUnit _customUnit = _CustomUnit.days; EffortLevel _effortLevel = EffortLevel.medium; DateTime _dueDate = DateTime.now(); Task? _existingTask; bool _isLoading = false; @override void initState() { super.initState(); _selectedPreset = FrequencyInterval.presets[3]; // Default: weekly _dueDate = _dateOnly(DateTime.now()); if (widget.isEditing) { _loadExistingTask(); } } Future _loadExistingTask() async { final db = ref.read(appDatabaseProvider); final task = await (db.select(db.tasks) ..where((t) => t.id.equals(widget.taskId!))) .getSingle(); setState(() { _existingTask = task; _nameController.text = task.name; _descriptionController.text = task.description ?? ''; _effortLevel = task.effortLevel; _dueDate = task.nextDueDate; // Find matching preset _selectedPreset = null; _isCustomFrequency = true; for (final preset in FrequencyInterval.presets) { if (preset.intervalType == task.intervalType && preset.days == task.intervalDays) { _selectedPreset = preset; _isCustomFrequency = false; break; } } if (_isCustomFrequency) { // Determine custom unit from stored interval switch (task.intervalType) { case IntervalType.everyNMonths: _customUnit = _CustomUnit.months; _customIntervalController.text = task.intervalDays.toString(); case IntervalType.monthly: _customUnit = _CustomUnit.months; _customIntervalController.text = '1'; case IntervalType.weekly: _customUnit = _CustomUnit.weeks; _customIntervalController.text = '1'; case IntervalType.biweekly: _customUnit = _CustomUnit.weeks; _customIntervalController.text = '2'; default: _customUnit = _CustomUnit.days; _customIntervalController.text = task.intervalDays.toString(); } } }); } @override void dispose() { _nameController.dispose(); _descriptionController.dispose(); _customIntervalController.dispose(); super.dispose(); } int get _roomId => widget.roomId ?? _existingTask!.roomId; static DateTime _dateOnly(DateTime dt) => DateTime(dt.year, dt.month, dt.day); @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); final theme = Theme.of(context); return Scaffold( appBar: AppBar( title: Text( widget.isEditing ? l10n.taskFormEditTitle : l10n.taskFormCreateTitle, ), actions: [ IconButton( onPressed: _isLoading ? null : _onSave, icon: const Icon(Icons.check), ), ], ), body: Form( key: _formKey, child: ListView( padding: const EdgeInsets.all(16), children: [ // Name field (required, autofocus) TextFormField( controller: _nameController, autofocus: !widget.isEditing, maxLength: 200, decoration: InputDecoration( labelText: l10n.taskFormNameLabel, hintText: l10n.taskFormNameHint, ), validator: (value) { if (value == null || value.trim().isEmpty) { return l10n.taskFormNameRequired; } return null; }, ), const SizedBox(height: 16), // Frequency selector Text( l10n.taskFormFrequencyLabel, style: theme.textTheme.titleSmall, ), const SizedBox(height: 8), _buildFrequencySelector(l10n, theme), const SizedBox(height: 16), // Effort selector Text( l10n.taskFormEffortLabel, style: theme.textTheme.titleSmall, ), const SizedBox(height: 8), _buildEffortSelector(), const SizedBox(height: 16), // Description (optional) TextFormField( controller: _descriptionController, maxLines: 3, decoration: InputDecoration( labelText: l10n.taskFormDescriptionLabel, ), ), const SizedBox(height: 16), // Initial due date Text( l10n.taskFormDueDateLabel, style: theme.textTheme.titleSmall, ), const SizedBox(height: 8), _buildDueDatePicker(theme), // History section (edit mode only) if (widget.isEditing) ...[ const SizedBox(height: 24), const Divider(), ListTile( leading: const Icon(Icons.history), title: Text(l10n.taskHistoryTitle), trailing: const Icon(Icons.chevron_right), onTap: () => showTaskHistorySheet( context: context, taskId: widget.taskId!, ), ), ], ], ), ), ); } Widget _buildFrequencySelector(AppLocalizations l10n, ThemeData theme) { final items = []; // Preset intervals for (final preset in FrequencyInterval.presets) { items.add( ChoiceChip( label: Text(preset.label()), selected: !_isCustomFrequency && _selectedPreset == preset, onSelected: (selected) { if (selected) { setState(() { _selectedPreset = preset; _isCustomFrequency = false; }); } }, ), ); } // Custom option items.add( ChoiceChip( label: Text(l10n.taskFormFrequencyCustom), selected: _isCustomFrequency, onSelected: (selected) { if (selected) { setState(() { _isCustomFrequency = true; _selectedPreset = null; }); } }, ), ); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Wrap( spacing: 8, runSpacing: 4, children: items, ), if (_isCustomFrequency) ...[ const SizedBox(height: 12), _buildCustomFrequencyInput(l10n, theme), ], ], ); } Widget _buildCustomFrequencyInput(AppLocalizations l10n, ThemeData theme) { return Row( children: [ Text( l10n.taskFormFrequencyEvery, style: theme.textTheme.bodyLarge, ), const SizedBox(width: 8), SizedBox( width: 60, child: TextFormField( controller: _customIntervalController, keyboardType: TextInputType.number, inputFormatters: [FilteringTextInputFormatter.digitsOnly], textAlign: TextAlign.center, decoration: const InputDecoration( contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 8), ), ), ), const SizedBox(width: 8), Expanded( child: SegmentedButton<_CustomUnit>( segments: [ ButtonSegment( value: _CustomUnit.days, label: Text(l10n.taskFormFrequencyUnitDays), ), ButtonSegment( value: _CustomUnit.weeks, label: Text(l10n.taskFormFrequencyUnitWeeks), ), ButtonSegment( value: _CustomUnit.months, label: Text(l10n.taskFormFrequencyUnitMonths), ), ], selected: {_customUnit}, onSelectionChanged: (newSelection) { setState(() { _customUnit = newSelection.first; }); }, ), ), ], ); } Widget _buildEffortSelector() { return SegmentedButton( segments: EffortLevel.values .map((e) => ButtonSegment( value: e, label: Text(e.label()), )) .toList(), selected: {_effortLevel}, onSelectionChanged: (newSelection) { setState(() { _effortLevel = newSelection.first; }); }, ); } Widget _buildDueDatePicker(ThemeData theme) { final dateFormat = DateFormat('dd.MM.yyyy'); return InkWell( onTap: _pickDueDate, borderRadius: BorderRadius.circular(8), child: InputDecorator( decoration: const InputDecoration( suffixIcon: Icon(Icons.calendar_today), ), child: Text( dateFormat.format(_dueDate), style: theme.textTheme.bodyLarge, ), ), ); } Future _pickDueDate() async { final picked = await showDatePicker( context: context, initialDate: _dueDate, firstDate: DateTime(2020), lastDate: DateTime(2100), locale: const Locale('de'), ); if (picked != null) { setState(() { _dueDate = _dateOnly(picked); }); } } /// Resolve the frequency from either selected preset or custom input. ({IntervalType type, int days, int? anchorDay}) _resolveFrequency() { if (!_isCustomFrequency && _selectedPreset != null) { final preset = _selectedPreset!; // For calendar-anchored intervals, set anchorDay to due date's day int? anchorDay; if (_isCalendarAnchored(preset.intervalType)) { anchorDay = _dueDate.day; } return ( type: preset.intervalType, days: preset.days, anchorDay: anchorDay, ); } // Custom frequency final number = int.tryParse(_customIntervalController.text) ?? 1; switch (_customUnit) { case _CustomUnit.days: return ( type: IntervalType.everyNDays, days: number, anchorDay: null, ); case _CustomUnit.weeks: return ( type: IntervalType.everyNDays, days: number * 7, anchorDay: null, ); case _CustomUnit.months: return ( type: IntervalType.everyNMonths, days: number, anchorDay: _dueDate.day, ); } } bool _isCalendarAnchored(IntervalType type) { return type == IntervalType.monthly || type == IntervalType.everyNMonths || type == IntervalType.quarterly || type == IntervalType.yearly; } Future _onSave() async { if (!_formKey.currentState!.validate()) return; setState(() => _isLoading = true); try { final freq = _resolveFrequency(); final actions = ref.read(taskActionsProvider.notifier); if (widget.isEditing && _existingTask != null) { final updatedTask = _existingTask!.copyWith( name: _nameController.text.trim(), description: Value(_descriptionController.text.trim().isEmpty ? null : _descriptionController.text.trim()), intervalType: freq.type, intervalDays: freq.days, anchorDay: Value(freq.anchorDay), effortLevel: _effortLevel, nextDueDate: _dueDate, ); await actions.updateTask(updatedTask); } else { await actions.createTask( roomId: _roomId, name: _nameController.text.trim(), description: _descriptionController.text.trim().isEmpty ? null : _descriptionController.text.trim(), intervalType: freq.type, intervalDays: freq.days, anchorDay: freq.anchorDay, effortLevel: _effortLevel, nextDueDate: _dueDate, ); } if (mounted) { context.pop(); } } finally { if (mounted) { setState(() => _isLoading = false); } } } } /// Unit options for custom frequency input. enum _CustomUnit { days, weeks, months }