Files
HouseHoldKeaper/.planning/milestones/v1.0-phases/02-rooms-and-tasks/02-03-PLAN.md
2026-03-16 20:12:01 +01:00

306 lines
14 KiB
Markdown

---
phase: 02-rooms-and-tasks
plan: 03
type: execute
wave: 2
depends_on: ["02-01"]
files_modified:
- lib/features/tasks/presentation/task_list_screen.dart
- lib/features/tasks/presentation/task_row.dart
- lib/features/tasks/presentation/task_form_screen.dart
- lib/features/tasks/presentation/task_providers.dart
- lib/l10n/app_de.arb
autonomous: true
requirements:
- TASK-01
- TASK-02
- TASK-03
- TASK-04
- TASK-05
- TASK-06
- TASK-07
- TASK-08
must_haves:
truths:
- "User can see all tasks in a room sorted by due date"
- "User can create a task with name, optional description, frequency interval, and effort level"
- "User can edit a task's name, description, frequency interval, and effort level"
- "User can delete a task with a confirmation dialog"
- "User can mark a task done via leading checkbox, which records completion and auto-calculates next due"
- "Overdue tasks have their due date text displayed in warm coral/red color"
- "Each task row shows: task name, relative due date in German, frequency label"
artifacts:
- path: "lib/features/tasks/presentation/task_list_screen.dart"
provides: "Scaffold showing task list for a room with sorted tasks and FAB for new task"
min_lines: 50
- path: "lib/features/tasks/presentation/task_row.dart"
provides: "Task row widget with leading checkbox, name, relative due date, frequency label"
min_lines: 40
- path: "lib/features/tasks/presentation/task_form_screen.dart"
provides: "Full-screen form for task creation and editing with frequency picker and effort selector"
min_lines: 80
- path: "lib/features/tasks/presentation/task_providers.dart"
provides: "Riverpod providers wrapping TasksDao stream queries and mutations"
exports: ["tasksInRoomProvider", "taskActionsProvider"]
key_links:
- from: "lib/features/tasks/presentation/task_providers.dart"
to: "lib/features/tasks/data/tasks_dao.dart"
via: "providers watch appDatabaseProvider then access tasksDao"
pattern: "ref\\.watch\\(appDatabaseProvider\\)"
- from: "lib/features/tasks/presentation/task_list_screen.dart"
to: "lib/features/tasks/presentation/task_providers.dart"
via: "ConsumerWidget watches tasksInRoomProvider(roomId)"
pattern: "ref\\.watch\\(tasksInRoom"
- from: "lib/features/tasks/presentation/task_row.dart"
to: "lib/features/tasks/domain/relative_date.dart"
via: "formatRelativeDate for German due date labels"
pattern: "formatRelativeDate"
- from: "lib/features/tasks/presentation/task_row.dart"
to: "lib/features/tasks/presentation/task_providers.dart"
via: "checkbox onChanged calls taskActions.completeTask"
pattern: "completeTask"
---
<objective>
Build the complete task management UI: task list screen (sorted by due date), task row with checkbox completion and overdue highlighting, task creation/edit form with frequency and effort selectors, and Riverpod providers.
Purpose: Delivers TASK-01 through TASK-08 as a working user-facing feature. After this plan, users can create, view, edit, delete, and complete tasks with automatic rescheduling.
Output: Task list screen, task row component, task form, providers, and localization keys.
</objective>
<execution_context>
@/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md
@/home/jlmak/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/02-rooms-and-tasks/2-CONTEXT.md
@.planning/phases/02-rooms-and-tasks/02-RESEARCH.md
@.planning/phases/02-rooms-and-tasks/02-01-SUMMARY.md
<interfaces>
<!-- From Plan 01 (data layer) -->
From lib/features/tasks/data/tasks_dao.dart:
```dart
@DriftAccessor(tables: [Tasks, TaskCompletions])
class TasksDao extends DatabaseAccessor<AppDatabase> with _$TasksDaoMixin {
Stream<List<Task>> watchTasksInRoom(int roomId); // ordered by nextDueDate ASC
Future<int> insertTask(TasksCompanion task);
Future<bool> updateTask(Task task);
Future<void> deleteTask(int taskId);
Future<void> completeTask(int taskId, {DateTime? now});
}
```
From lib/features/tasks/domain/frequency.dart:
```dart
enum IntervalType { daily, everyNDays, weekly, biweekly, monthly, everyNMonths, quarterly, yearly }
class FrequencyInterval {
final IntervalType type;
final int days;
String label(); // German display string
static const List<FrequencyInterval> presets = [...]; // 10 preset intervals
}
```
From lib/features/tasks/domain/effort_level.dart:
```dart
enum EffortLevel { low, medium, high }
// Extension with label() -> "Gering", "Mittel", "Hoch"
```
From lib/features/tasks/domain/relative_date.dart:
```dart
String formatRelativeDate(DateTime dueDate, DateTime today);
// Returns "Heute", "Morgen", "in X Tagen", "Uberfaellig seit X Tagen"
```
From lib/core/database/database.dart (Tasks table generates):
```dart
class Task {
final int id;
final int roomId;
final String name;
final String? description;
final IntervalType intervalType;
final int intervalDays;
final int? anchorDay;
final EffortLevel effortLevel;
final DateTime nextDueDate;
final DateTime createdAt;
}
```
From lib/core/router/router.dart (updated in Plan 02):
```dart
// Routes already defined:
// /rooms/:roomId -> TaskListScreen(roomId: roomId)
// /rooms/:roomId/tasks/new -> TaskFormScreen(roomId: roomId)
// /rooms/:roomId/tasks/:taskId -> TaskFormScreen(taskId: taskId)
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Create task providers, task form screen with frequency and effort selectors</name>
<files>
lib/features/tasks/presentation/task_providers.dart,
lib/features/tasks/presentation/task_form_screen.dart,
lib/l10n/app_de.arb
</files>
<action>
1. **lib/features/tasks/presentation/task_providers.dart**: Create Riverpod providers:
```dart
@riverpod
Stream<List<Task>> tasksInRoom(Ref ref, int roomId) {
final db = ref.watch(appDatabaseProvider);
return db.tasksDao.watchTasksInRoom(roomId);
}
@riverpod
class TaskActions extends _$TaskActions {
@override
FutureOr<void> build() {}
Future<int> createTask({
required int roomId,
required String name,
String? description,
required IntervalType intervalType,
required int intervalDays,
int? anchorDay,
required EffortLevel effortLevel,
required DateTime nextDueDate,
}) async { ... }
Future<void> updateTask(Task task) async { ... }
Future<void> deleteTask(int taskId) async { ... }
Future<void> completeTask(int taskId) async { ... }
}
```
2. **lib/features/tasks/presentation/task_form_screen.dart**: Full-screen `ConsumerStatefulWidget` form for creating and editing tasks. Constructor takes either `roomId` (for create, required) or `taskId` (for edit, loads existing task).
Form fields in this order (per RESEARCH.md Open Question 3 recommendation):
- **Name** (required): `TextFormField` with autofocus, validator non-empty, max 200 chars. Label: "Aufgabenname"
- **Frequency** (required): A dropdown or selector showing preset intervals from `FrequencyInterval.presets` with their German labels. Display as a `DropdownButtonFormField<FrequencyInterval>` or a custom tappable field that opens a bottom sheet with the preset list. Include "Benutzerdefiniert" option at the end that expands to show a number field + unit picker (Tage/Wochen/Monate). For custom: two fields — a `TextFormField` for the number and a `SegmentedButton` for the unit.
- **Effort** (required): `SegmentedButton<EffortLevel>` with 3 segments showing German labels ("Gering", "Mittel", "Hoch"). Default to `medium`.
- **Description** (optional): `TextFormField` with maxLines: 3, label "Beschreibung (optional)"
- **Initial due date**: For new tasks, default to today. Show as a tappable field that opens `showDatePicker`. Label: "Erstes Faelligkeitsdatum". Format as German date (DD.MM.YYYY).
For calendar-anchored intervals (monthly, everyNMonths, quarterly, yearly), automatically set `anchorDay` to the selected due date's day-of-month.
AppBar title: "Aufgabe erstellen" (create) or "Aufgabe bearbeiten" (edit). Save button (check icon) in AppBar.
On save: validate, build `TasksCompanion` or updated `Task`, call provider method, `context.pop()`.
For edit mode: load task data in `initState` via `ref.read(appDatabaseProvider).tasksDao` and pre-fill all fields.
3. **lib/l10n/app_de.arb**: Add task-related localization keys:
- "taskFormCreateTitle": "Aufgabe erstellen"
- "taskFormEditTitle": "Aufgabe bearbeiten"
- "taskFormNameLabel": "Aufgabenname"
- "taskFormNameHint": "z.B. Staubsaugen, Fenster putzen..."
- "taskFormNameRequired": "Bitte einen Namen eingeben"
- "taskFormFrequencyLabel": "Wiederholung"
- "taskFormFrequencyCustom": "Benutzerdefiniert"
- "taskFormFrequencyEvery": "Alle"
- "taskFormFrequencyUnitDays": "Tage"
- "taskFormFrequencyUnitWeeks": "Wochen"
- "taskFormFrequencyUnitMonths": "Monate"
- "taskFormEffortLabel": "Aufwand"
- "taskFormDescriptionLabel": "Beschreibung (optional)"
- "taskFormDueDateLabel": "Erstes F\u00e4lligkeitsdatum"
- "taskDeleteConfirmTitle": "Aufgabe l\u00f6schen?"
- "taskDeleteConfirmMessage": "Die Aufgabe wird unwiderruflich gel\u00f6scht."
- "taskDeleteConfirmAction": "L\u00f6schen"
- "taskEmptyTitle": "Noch keine Aufgaben"
- "taskEmptyMessage": "Erstelle die erste Aufgabe f\u00fcr diesen Raum."
- "taskEmptyAction": "Aufgabe erstellen"
Use proper Unicode escapes for umlauts in ARB file.
4. Run `dart run build_runner build --delete-conflicting-outputs` to generate provider .g.dart files.
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && dart analyze lib/features/tasks/presentation/ && flutter test</automated>
</verify>
<done>
Task providers connect to DAO layer. Task form handles create and edit with all fields (name, frequency with presets + custom, effort segmented button, description, initial due date). ARB keys added. All code analyzes clean.
</done>
</task>
<task type="auto">
<name>Task 2: Build task list screen with task row component, completion, and overdue highlighting</name>
<files>
lib/features/tasks/presentation/task_list_screen.dart,
lib/features/tasks/presentation/task_row.dart
</files>
<action>
1. **lib/features/tasks/presentation/task_row.dart**: Create `TaskRow` StatelessWidget accepting a `Task` object and callbacks. Per user decision (2-CONTEXT.md):
- **Leading checkbox**: `Checkbox` widget. When checked, calls `taskActions.completeTask(task.id)`. Per user decision: "No undo on completion -- immediate and final." The checkbox should feel instant (optimistic UI).
- **Task name**: `Text` with `titleMedium` style
- **Relative due date**: Call `formatRelativeDate(task.nextDueDate, DateTime.now())` for German label. Per user decision: "Overdue visual: due date text turns warm red/coral color. Rest of row stays normal." Use `Color(0xFFE07A5F)` (warm coral/terracotta from the palette) for overdue due date text color. Normal dates use `onSurfaceVariant`.
- **Frequency label**: Show interval label from `FrequencyInterval` helper (e.g. "Woechentlich", "Alle 3 Tage"). Use `bodySmall` style with `onSurfaceVariant` color.
- Layout: `ListTile` with leading `Checkbox`, title = task name, subtitle = Row of relative due date + frequency label separated by a dot or dash.
- **Tap row (not checkbox) opens edit**: `onTap` navigates to `/rooms/${task.roomId}/tasks/${task.id}` per user decision.
- No swipe gesture per user decision: "Leading checkbox on each task row to mark done -- tap to toggle. No swipe gesture."
- Per user decision: "No effort indicator or description preview on list view"
2. **lib/features/tasks/presentation/task_list_screen.dart**: Replace the placeholder (created in Plan 02) with a `ConsumerWidget` that:
- Takes `roomId` as constructor parameter
- Watches `tasksInRoomProvider(roomId)` for reactive task list (already sorted by due date from DAO)
- Shows task empty state when no tasks: icon + "Noch keine Aufgaben" text + "Aufgabe erstellen" button (from ARB keys)
- When tasks exist: `ListView.builder` of `TaskRow` widgets
- Scaffold with AppBar showing room name (load from `appDatabaseProvider.roomsDao.getRoomById(roomId)`)
- AppBar actions: edit room icon (navigates to `/rooms/$roomId/edit`), delete room with confirmation dialog (same pattern as room delete)
- FAB with `Icons.add` to navigate to `/rooms/$roomId/tasks/new`
- Uses `AsyncValue.when()` for loading/error/data states
- Task deletion: long-press on task row shows confirmation dialog. On confirm, calls `taskActions.deleteTask(taskId)`.
Note on overdue detection: A task is overdue if `task.nextDueDate` is before today (compare date-only: `DateTime(now.year, now.month, now.day)`). The `formatRelativeDate` function already handles this labeling. The `TaskRow` widget checks if `dueDate.isBefore(today)` to apply coral color.
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && dart analyze lib/features/tasks/presentation/ && flutter test</automated>
</verify>
<done>
Task list screen shows tasks sorted by due date with empty state. Task rows display checkbox, name, relative due date (German), and frequency label. Overdue dates highlighted in warm coral. Checkbox marks task done immediately (optimistic UI, no undo). Row tap opens edit form. Long-press deletes with confirmation. FAB creates new task. AppBar has room edit and delete actions. All existing tests pass.
</done>
</task>
</tasks>
<verification>
```bash
cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && dart analyze && flutter test
```
All code analyzes clean. All tests pass. Task creation, viewing, editing, deletion, and completion are functional at the UI level.
</verification>
<success_criteria>
- Task list screen shows tasks in a room sorted by due date
- Task rows: leading checkbox, name, German relative due date, frequency label
- Overdue dates displayed in warm coral (0xFFE07A5F)
- Checkbox marks task done instantly, records completion, auto-schedules next due
- Task form: create/edit with name, frequency (presets + custom), effort (3-way segmented), description, initial due date
- Custom frequency: number + unit picker (Tage/Wochen/Monate)
- Delete task with confirmation dialog
- Empty state when room has no tasks
- All strings from ARB localization
</success_criteria>
<output>
After completion, create `.planning/phases/02-rooms-and-tasks/02-03-SUMMARY.md`
</output>