diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 04b42fb..a7796cc 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -58,6 +58,9 @@ Plans: **Goal**: Users can set any recurring frequency intuitively without hunting through a grid of preset chips — common frequencies are one tap away, custom intervals are freeform **Depends on**: Phase 8 **Requirements**: TCX-01, TCX-02, TCX-03, TCX-04 +**Plans:** 1 plan +Plans: +- [ ] 09-01-PLAN.md — Rework frequency picker: 4 shortcut chips + freeform "Every N units" picker **Success Criteria** (what must be TRUE): 1. The frequency section presents a primary "Every [N] [unit]" picker where users can type a number and select days/weeks/months 2. Common frequencies (daily, weekly, biweekly, monthly) are available as quick-select shortcuts that populate the picker @@ -87,5 +90,5 @@ Plans: | 6. Task History | v1.1 | 1/1 | Complete | 2026-03-16 | | 7. Task Sorting | v1.1 | 2/2 | Complete | 2026-03-16 | | 8. Task Delete | 2/2 | Complete | 2026-03-18 | - | -| 9. Task Creation UX | v1.2 | - | Planned | - | +| 9. Task Creation UX | v1.2 | 0/1 | Planned | - | | 10. Dead Code Cleanup | v1.2 | - | Planned | - | diff --git a/.planning/phases/09-task-creation-ux/09-01-PLAN.md b/.planning/phases/09-task-creation-ux/09-01-PLAN.md new file mode 100644 index 0000000..2472dc5 --- /dev/null +++ b/.planning/phases/09-task-creation-ux/09-01-PLAN.md @@ -0,0 +1,330 @@ +--- +phase: 09-task-creation-ux +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - lib/features/tasks/presentation/task_form_screen.dart + - lib/l10n/app_de.arb + - lib/l10n/app_localizations.dart + - lib/l10n/app_localizations_de.dart +autonomous: false +requirements: [TCX-01, TCX-02, TCX-03, TCX-04] + +must_haves: + truths: + - "Frequency section shows 4 shortcut chips (Taeglich, Woechentlich, Alle 2 Wochen, Monatlich) above the freeform picker" + - "The freeform 'Every [N] [unit]' picker row is always visible — not hidden behind a Custom toggle" + - "Tapping a shortcut chip highlights it AND populates the picker with the corresponding values" + - "Editing the picker number or unit manually deselects any highlighted chip" + - "Any arbitrary interval (e.g., every 5 days, every 3 weeks, every 2 months) can be entered directly in the freeform picker" + - "Editing an existing daily task shows 'Taeglich' chip highlighted and picker showing 1/Tage" + - "Editing an existing quarterly task (3 months) shows no chip highlighted and picker showing 3/Monate" + - "Saving a task from the new picker produces the correct IntervalType and intervalDays values" + artifacts: + - path: "lib/features/tasks/presentation/task_form_screen.dart" + provides: "Reworked frequency picker with shortcut chips + freeform picker" + contains: "_ShortcutFrequency" + - path: "lib/l10n/app_de.arb" + provides: "German l10n strings for shortcut chip labels" + contains: "frequencyShortcutDaily" + key_links: + - from: "lib/features/tasks/presentation/task_form_screen.dart" + to: "lib/features/tasks/domain/frequency.dart" + via: "IntervalType enum and FrequencyInterval for _resolveFrequency mapping" + pattern: "IntervalType\\." + - from: "lib/features/tasks/presentation/task_form_screen.dart" + to: "lib/l10n/app_de.arb" + via: "AppLocalizations for chip labels and picker labels" + pattern: "l10n\\.frequencyShortcut" +--- + + +Rework the frequency picker in TaskFormScreen from a flat grid of 10 preset chips + hidden "Custom" mode into an intuitive 4 shortcut chips + always-visible freeform "Every [N] [unit]" picker. + +Purpose: Users should be able to set any recurring frequency intuitively — common frequencies are one tap away, custom intervals are freeform without mode switching. + +Output: Reworked `task_form_screen.dart` with simplified state management, bidirectional chip/picker sync, correct edit-mode loading, and all existing scheduling behavior preserved. + + + +@/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md +@/home/jlmak/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/09-task-creation-ux/09-CONTEXT.md + + + + +From lib/features/tasks/domain/frequency.dart: +```dart +enum IntervalType { + daily, // 0 + everyNDays, // 1 + weekly, // 2 + biweekly, // 3 + monthly, // 4 + everyNMonths,// 5 + quarterly, // 6 + yearly, // 7 +} + +class FrequencyInterval { + final IntervalType intervalType; + final int days; + const FrequencyInterval({required this.intervalType, this.days = 1}); + String label() { /* German label logic */ } + static const List presets = [ /* 10 presets */ ]; +} +``` + +From lib/features/tasks/presentation/task_form_screen.dart (current state to rework): +```dart +// Current state variables (lines 37-39): +FrequencyInterval? _selectedPreset; +bool _isCustomFrequency = false; +_CustomUnit _customUnit = _CustomUnit.days; + +// Current methods to rework: +_buildFrequencySelector() // lines 226-277 — chip grid + conditional custom input +_buildCustomFrequencyInput() // lines 279-326 — the "Alle [N] [unit]" row (KEEP, promote to primary) +_loadExistingTask() // lines 55-101 — edit mode preset matching (rework for new chips) +_resolveFrequency() // lines 378-415 — maps to IntervalType (KEEP, simplify condition) +``` + +From lib/l10n/app_de.arb (existing frequency strings): +```json +"taskFormFrequencyLabel": "Wiederholung", +"taskFormFrequencyCustom": "Benutzerdefiniert", // will be unused +"taskFormFrequencyEvery": "Alle", +"taskFormFrequencyUnitDays": "Tage", +"taskFormFrequencyUnitWeeks": "Wochen", +"taskFormFrequencyUnitMonths": "Monate" +``` + +IMPORTANT: FrequencyInterval.presets is NOT used outside of task_form_screen.dart for selection purposes. +template_picker_sheet.dart and task_row.dart only use FrequencyInterval constructor + .label() — they do NOT reference .presets. +The .presets list can safely stop being used in the UI without breaking anything. + + + + + + + Task 1: Rework frequency picker — shortcut chips + freeform picker + lib/features/tasks/presentation/task_form_screen.dart, lib/l10n/app_de.arb, lib/l10n/app_localizations.dart, lib/l10n/app_localizations_de.dart + +Rework the frequency picker in `task_form_screen.dart` following the locked user decisions in 09-CONTEXT.md. + +**Step 1 — Add l10n strings** to `app_de.arb`: +Add 4 new keys for shortcut chip labels: +- `"frequencyShortcutDaily": "Täglich"` +- `"frequencyShortcutWeekly": "Wöchentlich"` +- `"frequencyShortcutBiweekly": "Alle 2 Wochen"` +- `"frequencyShortcutMonthly": "Monatlich"` + +Then run `flutter gen-l10n` to regenerate `app_localizations.dart` and `app_localizations_de.dart`. + +**Step 2 — Define shortcut enum** in `task_form_screen.dart`: +Create a private enum `_ShortcutFrequency` with values: `daily, weekly, biweekly, monthly`. +Add a method `toPickerValues()` returning `({int number, _CustomUnit unit})`: +- daily → (1, days) +- weekly → (1, weeks) +- biweekly → (2, weeks) +- monthly → (1, months) + +Add a static method `fromPickerValues(int number, _CustomUnit unit)` returning `_ShortcutFrequency?`: +- (1, days) → daily +- (1, weeks) → weekly +- (2, weeks) → biweekly +- (1, months) → monthly +- anything else → null + +**Step 3 — Simplify state variables:** +Remove `_selectedPreset` (FrequencyInterval?) and `_isCustomFrequency` (bool). +Add `_activeShortcut` (_ShortcutFrequency?) — nullable, null means no chip highlighted. + +Change `initState` default: instead of `_selectedPreset = FrequencyInterval.presets[3]`, set: +- `_activeShortcut = _ShortcutFrequency.weekly` +- `_customIntervalController.text = '1'` (already defaults to '2', change to '1') +- `_customUnit = _CustomUnit.weeks` + +**Step 4 — Rework `_buildFrequencySelector()`:** +Replace the entire method. New structure: +``` +Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Shortcut chips row (always visible) + Wrap( + spacing: 8, + runSpacing: 4, + children: [ + for each _ShortcutFrequency shortcut: + 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), + ], +) +``` + +Add helper `_shortcutLabel(_ShortcutFrequency shortcut, AppLocalizations l10n)` returning the l10n string for each shortcut. + +**Step 5 — Rename `_buildCustomFrequencyInput` to `_buildFrequencyPickerRow`:** +The method body stays almost identical. One change: when the user edits the number field or changes the unit, recalculate `_activeShortcut`: +- In the TextFormField's `onChanged` callback: `setState(() { _activeShortcut = _ShortcutFrequency.fromPickerValues(int.tryParse(_customIntervalController.text) ?? 1, _customUnit); })` +- In the SegmentedButton's `onSelectionChanged`: after setting `_customUnit`, also recalculate: `_activeShortcut = _ShortcutFrequency.fromPickerValues(int.tryParse(_customIntervalController.text) ?? 1, newUnit);` + +This ensures bidirectional sync: chip → picker and picker → chip. + +**Step 6 — Simplify `_resolveFrequency()`:** +Remove the `if (!_isCustomFrequency && _selectedPreset != null)` branch entirely. +The method now ALWAYS reads from the picker values (`_customIntervalController` + `_customUnit`), since the picker is always the source of truth (shortcuts just populate it). Use the SMART mapping that matches existing DB behavior for named types: + - 1 day → IntervalType.daily, days=1 + - N days (N>1) → IntervalType.everyNDays, days=N + - 1 week → IntervalType.weekly, days=1 + - 2 weeks → IntervalType.biweekly, days=14 + - N weeks (N>2) → IntervalType.everyNDays, days=N*7 + - 1 month → IntervalType.monthly, days=1, anchorDay=dueDate.day + - N months (N>1) → IntervalType.everyNMonths, days=N, anchorDay=dueDate.day + +CRITICAL correctness note: The existing weekly preset has `days=1` (it's a named type where `intervalDays` stores 1). The old custom weeks path returns `everyNDays` with `days=N*7`. The new unified `_resolveFrequency` MUST use the named types (daily/weekly/biweekly/monthly) for their canonical values to match existing DB records. Only use everyNDays for non-canonical week counts (3+ weeks). Similarly, monthly uses `days=1` (not days=30) since it's a named type. + +**Step 7 — Rework `_loadExistingTask()` for edit mode:** +Replace the preset-matching loop (lines 69-78) and custom-detection logic (lines 80-98) with unified picker population: + +```dart +// 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, +); +``` + +This handles ALL 8 IntervalType values correctly, including quarterly (3 months) and yearly (12 months) which have no shortcut chip but display correctly in the picker. + +**Step 8 — Clean up unused references:** +- Remove `_selectedPreset` field +- Remove `_isCustomFrequency` field +- Remove the import or reference to `FrequencyInterval.presets` in the chip-building code (the `for (final preset in FrequencyInterval.presets)` loop) +- Keep the `taskFormFrequencyCustom` l10n key in the ARB file (do NOT delete l10n keys — they're harmless and removing requires regen) +- Do NOT modify `frequency.dart` — the `presets` list stays for backward compatibility even though the UI no longer iterates it + +**Verification notes for _resolveFrequency:** +The key correctness requirement is that saving a task from the new picker produces EXACTLY the same IntervalType + intervalDays + anchorDay as the old preset path did for equivalent selections. Verify by mentally tracing: +- Chip "Taeglich" → picker (1, days) → resolves to (daily, 1, null) -- matches old preset[0] +- Chip "Woechentlich" → picker (1, weeks) → resolves to (weekly, 1, null) -- matches old preset[3] +- Chip "Alle 2 Wochen" → picker (2, weeks) → resolves to (biweekly, 14, null) -- matches old preset[4] +- Chip "Monatlich" → picker (1, months) → resolves to (monthly, 1, anchorDay) -- matches old preset[5] +- Freeform "5 days" → picker (5, days) → resolves to (everyNDays, 5, null) -- matches old custom path +- Freeform "3 months" → picker (3, months) → resolves to (everyNMonths, 3, anchorDay) -- matches old custom path + + + cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter analyze --no-fatal-infos 2>&1 | tail -5 && flutter test 2>&1 | tail -10 + + +- The 10-chip Wrap grid is fully replaced by 4 shortcut chips + always-visible freeform picker +- The "Benutzerdefiniert" (Custom) chip is removed — the picker is inherently freeform +- Bidirectional sync: tapping a chip populates the picker; editing the picker recalculates chip highlight +- `_resolveFrequency()` reads exclusively from the picker (single source of truth) +- Edit mode correctly loads all 8 IntervalType values into the picker and highlights matching shortcut chip +- All existing tests pass, dart analyze is clean +- No changes to frequency.dart, no DB migration, no new screens + + + + + Task 2: Verify frequency picker UX + Reworked frequency picker with 4 shortcut chips and freeform "Every [N] [unit]" picker, replacing the old 10-chip grid + hidden Custom mode + + 1. Launch the app: `flutter run` + 2. Navigate to any room and tap "+" to create a new task + 3. Verify the frequency section shows: + - Row of 4 shortcut chips: Taeglich, Woechentlich, Alle 2 Wochen, Monatlich + - Below: always-visible freeform picker row with number field + Tage/Wochen/Monate segments + - "Woechentlich" chip highlighted by default, picker showing "1" with "Wochen" selected + 4. Tap "Taeglich" chip — verify chip highlights and picker updates to "1" / "Tage" + 5. Tap "Monatlich" chip — verify chip highlights and picker updates to "1" / "Monate" + 6. Manually type "5" in the number field — verify all chips deselect (no shortcut matches 5 weeks) + 7. Change unit to "Tage" — verify still no chip selected (5 days is not a shortcut) + 8. Type "1" in the number field with "Tage" selected — verify "Taeglich" chip auto-highlights + 9. Save a task with "Alle 2 Wochen" shortcut, then re-open in edit mode — verify "Alle 2 Wochen" chip is highlighted and picker shows "2" / "Wochen" + 10. If you have an existing quarterly or yearly task, open it in edit mode — verify no chip highlighted, picker shows "3" / "Monate" (quarterly) or "12" / "Monate" (yearly) + + Type "approved" or describe issues + + + + + +- `flutter analyze --no-fatal-infos` reports zero issues +- `flutter test` — all existing tests pass (108+) +- Manual verification: create task with each shortcut, create task with arbitrary interval, edit existing tasks of all interval types + + + +1. The frequency section presents 4 shortcut chips above an always-visible "Every [N] [unit]" picker (TCX-01, TCX-02) +2. Any arbitrary interval is settable directly in the picker without a "Custom" mode (TCX-03) +3. All 8 IntervalType values save and load correctly, including calendar-anchored monthly/quarterly/yearly with anchor memory (TCX-04) +4. Existing tests pass without modification, dart analyze is clean + + + +After completion, create `.planning/phases/09-task-creation-ux/09-01-SUMMARY.md` +