317 lines
16 KiB
Markdown
317 lines
16 KiB
Markdown
---
|
|
phase: 05-calendar-strip
|
|
plan: 02
|
|
type: execute
|
|
wave: 2
|
|
depends_on: ["05-01"]
|
|
files_modified:
|
|
- lib/features/home/presentation/home_screen.dart
|
|
- lib/features/home/presentation/calendar_strip.dart
|
|
- lib/features/home/presentation/calendar_task_row.dart
|
|
- lib/features/home/presentation/calendar_day_list.dart
|
|
autonomous: false
|
|
requirements:
|
|
- CAL-01
|
|
- CAL-03
|
|
- CAL-04
|
|
- CAL-05
|
|
|
|
must_haves:
|
|
truths:
|
|
- "Home screen shows a horizontal scrollable strip of day cards with German abbreviation (Mo, Di, Mi...) and date number"
|
|
- "Tapping a day card updates the task list below to show that day's tasks"
|
|
- "On app launch the strip auto-scrolls so today's card is centered"
|
|
- "A subtle wider gap and month label appears at month boundaries"
|
|
- "Overdue tasks appear in a separate coral-accented section when viewing today"
|
|
- "Overdue tasks do NOT appear when viewing past or future days"
|
|
- "Completing a task via checkbox triggers slide-out animation"
|
|
- "Floating Today button appears when scrolled away from today, hidden when today is visible"
|
|
- "First-run empty state (no rooms/tasks) still shows the create-room prompt"
|
|
- "Celebration state shows when all tasks for the selected day are done"
|
|
artifacts:
|
|
- path: "lib/features/home/presentation/calendar_strip.dart"
|
|
provides: "Horizontal scrollable date strip widget"
|
|
min_lines: 100
|
|
- path: "lib/features/home/presentation/calendar_day_list.dart"
|
|
provides: "Day task list with overdue section, empty, and celebration states"
|
|
min_lines: 80
|
|
- path: "lib/features/home/presentation/calendar_task_row.dart"
|
|
provides: "Task row adapted for calendar (no relative date, has room tag + checkbox)"
|
|
min_lines: 30
|
|
- path: "lib/features/home/presentation/home_screen.dart"
|
|
provides: "Rewritten HomeScreen composing strip + day list"
|
|
min_lines: 40
|
|
key_links:
|
|
- from: "lib/features/home/presentation/home_screen.dart"
|
|
to: "lib/features/home/presentation/calendar_strip.dart"
|
|
via: "HomeScreen composes CalendarStrip widget"
|
|
pattern: "CalendarStrip"
|
|
- from: "lib/features/home/presentation/home_screen.dart"
|
|
to: "lib/features/home/presentation/calendar_day_list.dart"
|
|
via: "HomeScreen composes CalendarDayList widget"
|
|
pattern: "CalendarDayList"
|
|
- from: "lib/features/home/presentation/calendar_strip.dart"
|
|
to: "lib/features/home/presentation/calendar_providers.dart"
|
|
via: "Strip reads and writes selectedDateProvider"
|
|
pattern: "selectedDateProvider"
|
|
- from: "lib/features/home/presentation/calendar_day_list.dart"
|
|
to: "lib/features/home/presentation/calendar_providers.dart"
|
|
via: "Day list watches calendarDayProvider for reactive task data"
|
|
pattern: "calendarDayProvider"
|
|
- from: "lib/features/home/presentation/calendar_day_list.dart"
|
|
to: "lib/features/tasks/presentation/task_providers.dart"
|
|
via: "Task completion uses taskActionsProvider.completeTask()"
|
|
pattern: "taskActionsProvider"
|
|
---
|
|
|
|
<objective>
|
|
Build the complete calendar strip UI and replace the old HomeScreen with it.
|
|
|
|
Purpose: This is the user-facing deliverable of Phase 5 -- the horizontal date strip with day-task list that replaces the stacked overdue/today/tomorrow daily plan.
|
|
|
|
Output: CalendarStrip widget, CalendarDayList widget, CalendarTaskRow widget, rewritten HomeScreen that composes them.
|
|
</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/05-calendar-strip/5-CONTEXT.md
|
|
@.planning/phases/05-calendar-strip/05-01-SUMMARY.md
|
|
|
|
<interfaces>
|
|
<!-- From Plan 01 outputs (CalendarDao, models, providers) -->
|
|
|
|
From lib/features/home/domain/calendar_models.dart:
|
|
```dart
|
|
class CalendarDayState {
|
|
final DateTime selectedDate;
|
|
final List<TaskWithRoom> dayTasks;
|
|
final List<TaskWithRoom> overdueTasks;
|
|
const CalendarDayState({required this.selectedDate, required this.dayTasks, required this.overdueTasks});
|
|
bool get isEmpty => dayTasks.isEmpty && overdueTasks.isEmpty;
|
|
}
|
|
```
|
|
|
|
From lib/features/home/presentation/calendar_providers.dart:
|
|
```dart
|
|
final selectedDateProvider = StateProvider<DateTime>(...); // read/write selected date
|
|
final calendarDayProvider = StreamProvider.autoDispose<CalendarDayState>(...); // reactive day data
|
|
```
|
|
|
|
From lib/features/home/domain/daily_plan_models.dart:
|
|
```dart
|
|
class TaskWithRoom {
|
|
final Task task;
|
|
final String roomName;
|
|
final int roomId;
|
|
}
|
|
```
|
|
|
|
From lib/features/tasks/presentation/task_providers.dart:
|
|
```dart
|
|
// Use to complete tasks:
|
|
ref.read(taskActionsProvider.notifier).completeTask(taskId);
|
|
```
|
|
|
|
From lib/core/theme/app_theme.dart:
|
|
```dart
|
|
// Seed color: Color(0xFF7A9A6D) -- sage green
|
|
// The "light sage/green tint" for day cards should derive from the theme's primary/seed
|
|
```
|
|
|
|
Existing reusable constants:
|
|
```dart
|
|
const _overdueColor = Color(0xFFE07A5F); // warm coral for overdue
|
|
```
|
|
|
|
Existing l10n strings to reuse:
|
|
```dart
|
|
l10n.dailyPlanSectionOverdue // "Uberfaellig"
|
|
l10n.dailyPlanNoTasks // "Noch keine Aufgaben angelegt"
|
|
l10n.dailyPlanAllClearTitle // "Alles erledigt!"
|
|
l10n.dailyPlanAllClearMessage // "Keine Aufgaben fuer heute..."
|
|
l10n.homeEmptyMessage // "Lege zuerst einen Raum an..."
|
|
l10n.homeEmptyAction // "Raum erstellen"
|
|
l10n.calendarTodayButton // "Heute" (added in Plan 01)
|
|
```
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Build CalendarStrip, CalendarTaskRow, CalendarDayList widgets</name>
|
|
<files>
|
|
lib/features/home/presentation/calendar_strip.dart,
|
|
lib/features/home/presentation/calendar_task_row.dart,
|
|
lib/features/home/presentation/calendar_day_list.dart
|
|
</files>
|
|
<action>
|
|
**CalendarStrip** (`lib/features/home/presentation/calendar_strip.dart`):
|
|
|
|
A ConsumerStatefulWidget that renders a horizontal scrollable row of day cards.
|
|
|
|
Scroll range: 90 days in the past and 90 days in the future (181 total items). This gives enough past for review and future for planning without performance concerns.
|
|
|
|
Layout:
|
|
- Uses a `ScrollController` with `initialScrollOffset` calculated to center today's card on first build.
|
|
- Each day card is a fixed-width container (~56px wide, ~72px tall). Cards show:
|
|
- Top: German day abbreviation using `DateFormat('E', 'de').format(date)` which gives "Mo", "Di", "Mi", "Do", "Fr", "Sa", "So". Import `package:intl/intl.dart`.
|
|
- Bottom: Date number (day of month) as text.
|
|
- Card styling per user decisions:
|
|
- All cards: light sage/green tint background. Use `theme.colorScheme.primaryContainer.withValues(alpha: 0.3)` or similar to get a subtle green wash.
|
|
- Selected card: stronger green (`theme.colorScheme.primaryContainer`) and border with `theme.colorScheme.primary`. The strip scrolls to center the selected card using `animateTo()`.
|
|
- Today's card (when not selected): bold text + a small accent underline bar below the date number (2px, primary color).
|
|
- Today + selected: both treatments combined.
|
|
- Spacing: cards have 4px horizontal margin by default. At month boundaries (where card N is the last day of a month and card N+1 is the first of the next month), the gap is 16px, and a small Text widget showing the new month abbreviation (e.g., "Apr") in `theme.textTheme.labelSmall` is inserted between them.
|
|
- On tap: update `ref.read(selectedDateProvider.notifier).state = tappedDate` and animate the scroll to center the tapped card.
|
|
- Auto-scroll on init: In `initState`, after the first frame (using `WidgetsBinding.instance.addPostFrameCallback`), animate to center today's card with a 200ms duration using `Curves.easeOut`.
|
|
|
|
Controller pattern for scroll-to-today:
|
|
```dart
|
|
class CalendarStripController {
|
|
VoidCallback? _scrollToToday;
|
|
void scrollToToday() => _scrollToToday?.call();
|
|
}
|
|
```
|
|
CalendarStrip takes `CalendarStripController controller` parameter and sets `controller._scrollToToday` in initState. Parent calls `controller.scrollToToday()` from the Today button.
|
|
|
|
Today visibility callback: Expose `onTodayVisibilityChanged(bool isVisible)`. Determine visibility by checking if today's card offset is within the viewport bounds during scroll events.
|
|
|
|
**CalendarTaskRow** (`lib/features/home/presentation/calendar_task_row.dart`):
|
|
|
|
Adapted from `DailyPlanTaskRow` but simplified per user decisions:
|
|
- Shows: task name, tappable room tag (navigates to room via `context.go('/rooms/$roomId')`), checkbox
|
|
- Does NOT show relative date (strip already communicates which day)
|
|
- Same room tag styling as DailyPlanTaskRow (secondaryContainer chip with borderRadius 4)
|
|
- Checkbox visible, onChanged triggers `onCompleted` callback
|
|
- Overdue variant: if `isOverdue` flag is true, task name text color uses `_overdueColor` for visual distinction
|
|
|
|
**CalendarDayList** (`lib/features/home/presentation/calendar_day_list.dart`):
|
|
|
|
A ConsumerStatefulWidget that shows the task list for the selected day.
|
|
|
|
Watches `calendarDayProvider`. Manages `Set<int> _completingTaskIds` for animation state.
|
|
|
|
Handles these states:
|
|
|
|
a) **Loading**: `CircularProgressIndicator` centered.
|
|
|
|
b) **Error**: Error text centered.
|
|
|
|
c) **First-run empty** (no rooms/tasks at all): Same pattern as current `_buildNoTasksState` -- checklist icon, "Noch keine Aufgaben angelegt" message, "Lege zuerst einen Raum an" subtitle, "Raum erstellen" FilledButton.tonal navigating to `/rooms`. Detect by checking if `state.isEmpty && state.totalTaskCount == 0` (requires adding `totalTaskCount` field to CalendarDayState and computing it in the provider -- see NOTE below).
|
|
|
|
d) **Empty day** (tasks exist elsewhere but not this day, and not today): show centered subtle icon (Icons.event_available) + "Keine Aufgaben" text.
|
|
|
|
e) **Celebration** (today is selected, tasks exist elsewhere, but today's tasks are all done): show celebration icon + "Alles erledigt!" title + "Keine Aufgaben fuer heute. Geniesse den Moment!" message. Compact layout (no ProgressCard).
|
|
|
|
f) **Has tasks**: Render a ListView with:
|
|
- If overdue tasks exist (only present when viewing today): Section header "Uberfaellig" in coral color (`_overdueColor`), followed by overdue CalendarTaskRow items with `isOverdue: true` and interactive checkboxes.
|
|
- Day tasks: CalendarTaskRow items with interactive checkboxes.
|
|
- Task completion: on checkbox tap, add taskId to `_completingTaskIds`, call `ref.read(taskActionsProvider.notifier).completeTask(taskId)`. Render completing tasks with the `_CompletingTaskRow` animation (SizeTransition + SlideTransition, 300ms, Curves.easeInOut) -- recreate this private widget in calendar_day_list.dart.
|
|
|
|
NOTE for executor: Plan 01 creates CalendarDayState with selectedDate, dayTasks, overdueTasks. This task needs a `totalTaskCount` int field on CalendarDayState to distinguish first-run from celebration. When implementing, add `final int totalTaskCount` to CalendarDayState in calendar_models.dart and compute it in the calendarDayProvider via a simple `SELECT COUNT(*) FROM tasks` query (one line in CalendarDao: `Future<int> getTaskCount() async { final r = await (selectOnly(tasks)..addColumns([tasks.id.count()])).getSingle(); return r.read(tasks.id.count()) ?? 0; }`).
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter analyze --no-fatal-infos</automated>
|
|
</verify>
|
|
<done>CalendarStrip renders 181 day cards with German abbreviations, highlights selected/today cards, shows month boundary labels. CalendarTaskRow shows name + room tag + checkbox without relative date. CalendarDayList shows overdue section (today only), day tasks, empty states, and celebration state. All compile without analysis errors.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Replace HomeScreen with calendar composition and floating Today button</name>
|
|
<files>
|
|
lib/features/home/presentation/home_screen.dart
|
|
</files>
|
|
<action>
|
|
Rewrite `lib/features/home/presentation/home_screen.dart` entirely. The old content (DailyPlanState, overdue/today/tomorrow sections, ProgressCard) is fully replaced.
|
|
|
|
New HomeScreen is a `ConsumerStatefulWidget`:
|
|
|
|
State fields:
|
|
- `late final CalendarStripController _stripController = CalendarStripController();`
|
|
- `bool _showTodayButton = false;`
|
|
|
|
Build method returns a Stack with:
|
|
1. A Column containing:
|
|
- `CalendarStrip(controller: _stripController, onTodayVisibilityChanged: (visible) { setState(() => _showTodayButton = !visible); })`
|
|
- `Expanded(child: CalendarDayList())`
|
|
2. Conditionally, a Positioned floating "Heute" button at bottom-center:
|
|
- `FloatingActionButton.extended` with `Icons.today` icon and `l10n.calendarTodayButton` label
|
|
- onPressed: set `selectedDateProvider` to today's date-only DateTime, call `_stripController.scrollToToday()`
|
|
|
|
Imports needed:
|
|
- `flutter/material.dart`
|
|
- `flutter_riverpod/flutter_riverpod.dart`
|
|
- `calendar_strip.dart`
|
|
- `calendar_day_list.dart`
|
|
- `calendar_providers.dart` (for selectedDateProvider)
|
|
- `app_localizations.dart`
|
|
|
|
Do NOT delete old files (`daily_plan_providers.dart`, `daily_plan_task_row.dart`, `progress_card.dart`, `daily_plan_dao.dart`). DailyPlanDao is still used by the notification service. Old presentation files become dead code -- safe to clean up in a future phase.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter analyze --no-fatal-infos && flutter test</automated>
|
|
</verify>
|
|
<done>HomeScreen renders CalendarStrip at top and CalendarDayList below. Floating Today button appears when scrolled away from today. Old overdue/today/tomorrow sections are gone. Full test suite passes. No analysis errors.</done>
|
|
</task>
|
|
|
|
<task type="checkpoint:human-verify" gate="blocking">
|
|
<name>Task 3: Verify calendar strip home screen visually and functionally</name>
|
|
<files>lib/features/home/presentation/home_screen.dart</files>
|
|
<action>
|
|
Human verifies the complete calendar strip experience on a running device/emulator.
|
|
|
|
Launch the app with `flutter run` (or hot-restart). Walk through all key behaviors:
|
|
1. Strip appearance: day cards with German abbreviations and date numbers
|
|
2. Today highlighting: centered, stronger green, bold + underline
|
|
3. Day selection: tap a card, task list updates
|
|
4. Month boundaries: wider gap with month label
|
|
5. Today button: appears when scrolled away, snaps back on tap
|
|
6. Overdue section: coral header on today only
|
|
7. Task completion: checkbox triggers slide-out animation
|
|
8. Empty/celebration states
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/jlmak/Projects/jlmak/HouseHoldKeaper && flutter analyze --no-fatal-infos</automated>
|
|
</verify>
|
|
<done>User has confirmed the calendar strip looks correct, day selection works, overdue behavior is right, and all states render properly.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `flutter analyze --no-fatal-infos` -- zero errors
|
|
- `flutter test` -- full test suite passes (existing + new DAO tests)
|
|
- Visual: calendar strip is horizontally scrollable with day cards
|
|
- Visual: selected day highlighted, today has bold + underline treatment
|
|
- Visual: month boundaries have wider gaps and month name labels
|
|
- Functional: tapping a day card updates the task list below
|
|
- Functional: overdue tasks appear only when viewing today
|
|
- Functional: floating Today button appears/disappears correctly
|
|
- Functional: task completion animation works
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- Home screen replaced: no more stacked overdue/today/tomorrow sections
|
|
- Horizontal date strip scrolls smoothly with 181 day range
|
|
- Day cards show German abbreviations and date numbers
|
|
- Tapping a card selects it and shows that day's tasks
|
|
- Today auto-centers on launch with smooth animation
|
|
- Month boundaries visually distinct with labels
|
|
- Overdue carry-over only on today's view with coral accent
|
|
- Floating Today button for quick navigation
|
|
- Empty and celebration states work correctly
|
|
- All existing tests pass, no regressions
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/05-calendar-strip/05-02-SUMMARY.md`
|
|
</output>
|