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: '1'); _ShortcutFrequency? _activeShortcut; _CustomUnit _customUnit = _CustomUnit.weeks; EffortLevel _effortLevel = EffortLevel.medium; DateTime _dueDate = DateTime.now(); Task? _existingTask; bool _isLoading = false; @override void initState() { super.initState(); _activeShortcut = _ShortcutFrequency.weekly; _customIntervalController.text = '1'; _customUnit = _CustomUnit.weeks; _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; // Populate picker from stored interval switch (task.intervalType) { case IntervalType.daily: _customUnit = _CustomUnit.days; _customIntervalController.text = '1'; case IntervalType.everyNDays: // Check if it's a clean week multiple if (task.intervalDays % 7 == 0) { _customUnit = _CustomUnit.weeks; _customIntervalController.text = (task.intervalDays ~/ 7).toString(); } else { _customUnit = _CustomUnit.days; _customIntervalController.text = task.intervalDays.toString(); } case IntervalType.weekly: _customUnit = _CustomUnit.weeks; _customIntervalController.text = '1'; case IntervalType.biweekly: _customUnit = _CustomUnit.weeks; _customIntervalController.text = '2'; case IntervalType.monthly: _customUnit = _CustomUnit.months; _customIntervalController.text = '1'; case IntervalType.everyNMonths: _customUnit = _CustomUnit.months; _customIntervalController.text = task.intervalDays.toString(); case IntervalType.quarterly: _customUnit = _CustomUnit.months; _customIntervalController.text = '3'; case IntervalType.yearly: _customUnit = _CustomUnit.months; _customIntervalController.text = '12'; } // Detect matching shortcut chip _activeShortcut = _ShortcutFrequency.fromPickerValues( int.tryParse(_customIntervalController.text) ?? 1, _customUnit, ); }); } @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!, ), ), // DELETE BUTTON const Divider(), const SizedBox(height: 8), SizedBox( width: double.infinity, child: FilledButton.icon( style: FilledButton.styleFrom( backgroundColor: theme.colorScheme.error, foregroundColor: theme.colorScheme.onError, ), onPressed: _isLoading ? null : _onDelete, icon: const Icon(Icons.delete_outline), label: Text(l10n.taskDeleteConfirmAction), ), ), ], ], ), ), ); } Widget _buildFrequencySelector(AppLocalizations l10n, ThemeData theme) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Shortcut chips row (always visible) Wrap( spacing: 8, runSpacing: 4, children: [ for (final shortcut in _ShortcutFrequency.values) ChoiceChip( label: Text(_shortcutLabel(shortcut, l10n)), selected: _activeShortcut == shortcut, onSelected: (selected) { if (selected) { final values = shortcut.toPickerValues(); setState(() { _activeShortcut = shortcut; _customIntervalController.text = values.number.toString(); _customUnit = values.unit; }); } }, ), ], ), const SizedBox(height: 12), // Freeform picker row (ALWAYS visible — not conditional) _buildFrequencyPickerRow(l10n, theme), ], ); } String _shortcutLabel(_ShortcutFrequency shortcut, AppLocalizations l10n) { switch (shortcut) { case _ShortcutFrequency.daily: return l10n.frequencyShortcutDaily; case _ShortcutFrequency.weekly: return l10n.frequencyShortcutWeekly; case _ShortcutFrequency.biweekly: return l10n.frequencyShortcutBiweekly; case _ShortcutFrequency.monthly: return l10n.frequencyShortcutMonthly; } } Widget _buildFrequencyPickerRow(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), ), onChanged: (value) { setState(() { _activeShortcut = _ShortcutFrequency.fromPickerValues( int.tryParse(value) ?? 1, _customUnit, ); }); }, ), ), 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) { final newUnit = newSelection.first; setState(() { _customUnit = newUnit; _activeShortcut = _ShortcutFrequency.fromPickerValues( int.tryParse(_customIntervalController.text) ?? 1, newUnit, ); }); }, ), ), ], ); } 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 the freeform picker (single source of truth). /// The picker is always the source of truth; shortcut chips just populate it. ({IntervalType type, int days, int? anchorDay}) _resolveFrequency() { final number = int.tryParse(_customIntervalController.text) ?? 1; switch (_customUnit) { case _CustomUnit.days: if (number == 1) { return (type: IntervalType.daily, days: 1, anchorDay: null); } return (type: IntervalType.everyNDays, days: number, anchorDay: null); case _CustomUnit.weeks: if (number == 1) { return (type: IntervalType.weekly, days: 1, anchorDay: null); } if (number == 2) { return (type: IntervalType.biweekly, days: 14, anchorDay: null); } return (type: IntervalType.everyNDays, days: number * 7, anchorDay: null); case _CustomUnit.months: if (number == 1) { return (type: IntervalType.monthly, days: 1, anchorDay: _dueDate.day); } return (type: IntervalType.everyNMonths, days: number, anchorDay: _dueDate.day); } } 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); } } } Future _onDelete() async { final l10n = AppLocalizations.of(context); final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: Text(l10n.taskDeleteConfirmTitle), content: Text(l10n.taskDeleteConfirmMessage), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: Text(l10n.cancel), ), FilledButton( style: FilledButton.styleFrom( backgroundColor: Theme.of(ctx).colorScheme.error, ), onPressed: () => Navigator.pop(ctx, true), child: Text(l10n.taskDeleteConfirmAction), ), ], ), ); if (confirmed != true || !mounted) return; setState(() => _isLoading = true); try { await ref.read(taskActionsProvider.notifier).smartDeleteTask(widget.taskId!); if (mounted) { context.pop(); } } finally { if (mounted) { setState(() => _isLoading = false); } } } } /// Shortcut frequency options for quick selection chips. enum _ShortcutFrequency { daily, weekly, biweekly, monthly; /// Returns the picker values (number + unit) that this shortcut represents. ({int number, _CustomUnit unit}) toPickerValues() { switch (this) { case _ShortcutFrequency.daily: return (number: 1, unit: _CustomUnit.days); case _ShortcutFrequency.weekly: return (number: 1, unit: _CustomUnit.weeks); case _ShortcutFrequency.biweekly: return (number: 2, unit: _CustomUnit.weeks); case _ShortcutFrequency.monthly: return (number: 1, unit: _CustomUnit.months); } } /// Returns the matching shortcut for given picker values, or null if no match. static _ShortcutFrequency? fromPickerValues(int number, _CustomUnit unit) { if (number == 1 && unit == _CustomUnit.days) return _ShortcutFrequency.daily; if (number == 1 && unit == _CustomUnit.weeks) return _ShortcutFrequency.weekly; if (number == 2 && unit == _CustomUnit.weeks) return _ShortcutFrequency.biweekly; if (number == 1 && unit == _CustomUnit.months) return _ShortcutFrequency.monthly; return null; } } /// Unit options for freeform frequency picker. enum _CustomUnit { days, weeks, months }