diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 9a5ebc5..eb36f51 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -46,8 +46,8 @@ Requirements for initial release. Each maps to roadmap phases. ### Notifications -- [ ] **NOTF-01**: User receives a daily summary notification showing today's task count at a configurable time -- [ ] **NOTF-02**: User can enable/disable notifications in settings +- [x] **NOTF-01**: User receives a daily summary notification showing today's task count at a configurable time +- [x] **NOTF-02**: User can enable/disable notifications in settings ### Theme & UI @@ -147,8 +147,8 @@ Which phases cover which requirements. Updated during roadmap creation. | PLAN-05 | Phase 3: Daily Plan and Cleanliness | Complete | | PLAN-06 | Phase 3: Daily Plan and Cleanliness | Complete | | CLEAN-01 | Phase 3: Daily Plan and Cleanliness | Complete | -| NOTF-01 | Phase 4: Notifications | Pending | -| NOTF-02 | Phase 4: Notifications | Pending | +| NOTF-01 | Phase 4: Notifications | Complete | +| NOTF-02 | Phase 4: Notifications | Complete | **Coverage:** - v1 requirements: 30 total diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 0f7b186..2b616b4 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -97,4 +97,4 @@ Note: Phase 4 depends on Phase 2 (needs scheduling data) but can be developed in | 1. Foundation | 2/2 | Complete | 2026-03-15 | | 2. Rooms and Tasks | 5/5 | Complete | 2026-03-15 | | 3. Daily Plan and Cleanliness | 3/3 | Complete | 2026-03-16 | -| 4. Notifications | 0/3 | In progress | - | +| 4. Notifications | 1/3 | In Progress| | diff --git a/.planning/STATE.md b/.planning/STATE.md index 6a20549..9d0ae04 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -2,15 +2,15 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone -status: verifying -stopped_at: Phase 4 context gathered -last_updated: "2026-03-16T13:26:18.551Z" -last_activity: 2026-03-16 — Completed 03-03-PLAN.md (Phase 3 verification gate) +status: executing +stopped_at: Completed 04-01-PLAN.md (Notification infrastructure) +last_updated: "2026-03-16T14:00:13.196Z" +last_activity: 2026-03-16 — Completed 04-01-PLAN.md (Notification infrastructure) progress: total_phases: 4 completed_phases: 3 - total_plans: 10 - completed_plans: 10 + total_plans: 13 + completed_plans: 11 percent: 100 --- @@ -25,10 +25,10 @@ See: .planning/PROJECT.md (updated 2026-03-15) ## Current Position -Phase: 3 of 4 (Daily Plan and Cleanliness) -- COMPLETE -Plan: 3 of 3 in current phase -- COMPLETE -Status: Phase 3 complete -- all requirements verified, ready for Phase 4 -Last activity: 2026-03-16 — Completed 03-03-PLAN.md (Phase 3 verification gate) +Phase: 4 of 4 (Notifications) +Plan: 1 of 2 in current phase -- COMPLETE +Status: Phase 4 in progress — plan 1 complete, plan 2 (Settings UI) pending +Last activity: 2026-03-16 — Completed 04-01-PLAN.md (Notification infrastructure) Progress: [██████████] 100% @@ -60,6 +60,7 @@ Progress: [██████████] 100% | Phase 03 P01 | 5 | 2 tasks | 10 files | | Phase 03 P02 | 4 | 2 tasks | 5 files | | Phase 03 P03 | 2 | 2 tasks | 0 files | +| Phase 04-notifications P01 | 9 | 2 tasks | 11 files | ## Accumulated Context @@ -102,6 +103,10 @@ Recent decisions affecting current work: - [03-02]: DailyPlanTaskRow is StatelessWidget (not ConsumerWidget) -- completion callback passed in from parent - [03-02]: No-tasks empty state uses dailyPlanNoTasks key for clearer daily plan context messaging - [03-03]: Phase 3 verification gate passed: dart analyze clean, 72/72 tests, all 7 requirements confirmed functional +- [Phase 04-01]: timezone constraint upgraded to ^0.11.0 — flutter_local_notifications v21 requires ^0.11.0, plan specified ^0.9.4 +- [Phase 04-01]: flutter_local_notifications v21 uses named parameters in initialize() and zonedSchedule() — positional API removed in v20+ +- [Phase 04-01]: Generated Riverpod 3 provider named notificationSettingsProvider (not notificationSettingsNotifierProvider) — consistent with themeProvider naming convention +- [Phase 04-01]: nextInstanceOf exposed as @visibleForTesting public method to enable TZ logic unit testing without native dispatch mocking ### Pending Todos @@ -115,6 +120,6 @@ None yet. ## Session Continuity -Last session: 2026-03-16T13:26:18.548Z -Stopped at: Phase 4 context gathered -Resume file: .planning/phases/04-notifications/04-CONTEXT.md +Last session: 2026-03-16T14:00:13.194Z +Stopped at: Completed 04-01-PLAN.md (Notification infrastructure) +Resume file: None diff --git a/.planning/phases/04-notifications/04-01-SUMMARY.md b/.planning/phases/04-notifications/04-01-SUMMARY.md new file mode 100644 index 0000000..1f83655 --- /dev/null +++ b/.planning/phases/04-notifications/04-01-SUMMARY.md @@ -0,0 +1,184 @@ +--- +phase: 04-notifications +plan: 01 +subsystem: notifications +tags: [flutter_local_notifications, timezone, flutter_timezone, shared_preferences, riverpod, android, drift] + +# Dependency graph +requires: + - phase: 03-daily-plan + provides: DailyPlanDao with tasks/rooms database access + - phase: 01-foundation + provides: SharedPreferences pattern via ThemeNotifier + +provides: + - NotificationService singleton wrapping FlutterLocalNotificationsPlugin + - NotificationSettingsNotifier persisting enabled + TimeOfDay to SharedPreferences + - DailyPlanDao one-shot queries for overdue and today task counts + - Android build configured for flutter_local_notifications v21 + - Timezone initialization in main.dart + - 7 notification ARB strings for German locale +affects: [04-02-settings-ui, future notification scheduling] + +# Tech tracking +tech-stack: + added: [flutter_local_notifications ^21.0.0, timezone ^0.11.0, flutter_timezone ^1.0.8] + patterns: [singleton service for native plugin wrapper, @Riverpod(keepAlive) notifier with sync default + async load override] + +key-files: + created: + - lib/core/notifications/notification_service.dart + - lib/core/notifications/notification_settings_notifier.dart + - lib/core/notifications/notification_settings_notifier.g.dart + - test/core/notifications/notification_service_test.dart + - test/core/notifications/notification_settings_notifier_test.dart + modified: + - pubspec.yaml + - android/app/build.gradle.kts + - android/app/src/main/AndroidManifest.xml + - lib/main.dart + - lib/features/home/data/daily_plan_dao.dart + - lib/features/home/data/daily_plan_dao.g.dart + - lib/l10n/app_de.arb + +key-decisions: + - "timezone constraint upgraded to ^0.11.0 — flutter_local_notifications v21 requires ^0.11.0, plan specified ^0.9.4" + - "flutter_local_notifications v21 uses named parameters in initialize() and zonedSchedule() — updated from positional parameter style in RESEARCH.md examples" + - "Generated Riverpod 3 provider named notificationSettingsProvider (not notificationSettingsNotifierProvider) — consistent with existing themeProvider naming convention" + - "nextInstanceOf exposed as @visibleForTesting public method (not private _nextInstanceOf) to enable unit testing without mocking" + - "Test helper makeContainer() awaits Future.delayed(Duration.zero) to let initial async _load() complete before mutating state assertions" + +patterns-established: + - "Plain Dart singleton for native plugin wrapper: NotificationService uses factory constructor + static _instance, initialized once at app startup outside Riverpod" + - "Sync default + async load pattern: @Riverpod(keepAlive: true) returns const default synchronously in build(), async _load() overrides state after SharedPreferences hydration" + - "TDD with async state: test helper function awaits initial async load before running mutation tests to avoid race conditions" + +requirements-completed: [NOTF-01, NOTF-02] + +# Metrics +duration: 9min +completed: 2026-03-16 +--- + +# Phase 4 Plan 1: Notification Infrastructure Summary + +**flutter_local_notifications v21 singleton service with TZ-aware scheduling, Riverpod keepAlive settings notifier persisting to SharedPreferences, Android desugaring config, and DailyPlanDao one-shot task count queries** + +## Performance + +- **Duration:** 9 min +- **Started:** 2026-03-16T13:48:28Z +- **Completed:** 2026-03-16T13:57:42Z +- **Tasks:** 2 +- **Files modified:** 11 + +## Accomplishments +- Android build fully configured for flutter_local_notifications v21: compileSdk=35, core library desugaring enabled, permissions and receivers in AndroidManifest +- NotificationService singleton wrapping FlutterLocalNotificationsPlugin with initialize, requestPermission, scheduleDailyNotification, cancelAll, and @visibleForTesting nextInstanceOf +- NotificationSettingsNotifier with @Riverpod(keepAlive: true) persisting enabled/time to SharedPreferences, following ThemeNotifier pattern +- DailyPlanDao extended with getOverdueAndTodayTaskCount and getOverdueTaskCount one-shot Future queries +- Timezone initialization chain in main.dart: initializeTimeZones → getLocalTimezone → setLocalLocation → NotificationService.initialize +- 7 German ARB strings for notification UI and content +- 12 new unit tests (5 service, 7 notifier) plus all 72 existing tests passing (84 total) + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Android config, packages, NotificationService, timezone init, DAO query, ARB strings** - `8787671` (feat) +2. **Task 2 RED: Failing tests for NotificationSettingsNotifier and NotificationService** - `0f6789b` (test) +3. **Task 2 GREEN: NotificationSettingsNotifier implementation + fixed tests** - `4f72eac` (feat) + +**Plan metadata:** (docs commit — see final commit hash below) + +_Note: TDD task 2 has separate test (RED) and implementation (GREEN) commits per TDD protocol_ + +## Files Created/Modified +- `lib/core/notifications/notification_service.dart` - Singleton wrapping FlutterLocalNotificationsPlugin; scheduleDailyNotification uses zonedSchedule with TZDateTime +- `lib/core/notifications/notification_settings_notifier.dart` - @Riverpod(keepAlive: true) notifier; NotificationSettings data class with enabled + time +- `lib/core/notifications/notification_settings_notifier.g.dart` - Riverpod code gen; provider named notificationSettingsProvider +- `test/core/notifications/notification_service_test.dart` - Unit tests for singleton pattern and nextInstanceOf TZ logic +- `test/core/notifications/notification_settings_notifier_test.dart` - Unit tests for default state, setEnabled, setTime, and persistence loading +- `pubspec.yaml` - Added flutter_local_notifications ^21.0.0, timezone ^0.11.0, flutter_timezone ^1.0.8 +- `android/app/build.gradle.kts` - compileSdk=35, isCoreLibraryDesugaringEnabled=true, desugar_jdk_libs:2.1.4 dependency +- `android/app/src/main/AndroidManifest.xml` - POST_NOTIFICATIONS + RECEIVE_BOOT_COMPLETED permissions, ScheduledNotificationReceiver + ScheduledNotificationBootReceiver +- `lib/main.dart` - async main with timezone init chain and NotificationService.initialize() +- `lib/features/home/data/daily_plan_dao.dart` - Added getOverdueAndTodayTaskCount and getOverdueTaskCount one-shot queries +- `lib/l10n/app_de.arb` - 7 new keys: settingsSectionNotifications, notificationsEnabledLabel, notificationsTimeLabel, notificationsPermissionDeniedHint, notificationTitle, notificationBody, notificationBodyWithOverdue + +## Decisions Made +- **timezone version upgraded to ^0.11.0**: Plan specified ^0.9.4, but flutter_local_notifications v21 requires ^0.11.0. Auto-fixed (Rule 3 — blocking). +- **v21 named parameter API**: RESEARCH.md examples used old positional parameter style. v21 uses `settings:`, `id:`, `scheduledDate:`, `notificationDetails:` named params. Fixed to match actual API. +- **Riverpod 3 naming convention**: Generated provider is `notificationSettingsProvider` not `notificationSettingsNotifierProvider`, consistent with existing `themeProvider` decision from Phase 1. +- **nextInstanceOf public @visibleForTesting**: Made public with annotation instead of private `_nextInstanceOf` to enable unit testing without native dispatch mocking. +- **makeContainer() async helper**: Test helper awaits `Future.delayed(Duration.zero)` after first read to let the async `_load()` from `build()` complete before mutation tests run, preventing race conditions. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 3 - Blocking] timezone package version constraint incompatible** +- **Found during:** Task 1 (flutter pub get) +- **Issue:** Plan specified `timezone: ^0.9.4` but flutter_local_notifications v21 depends on `timezone: ^0.11.0` — pub solve failed immediately +- **Fix:** Updated constraint to `^0.11.0` in pubspec.yaml +- **Files modified:** pubspec.yaml +- **Verification:** `flutter pub get` resolved successfully +- **Committed in:** 8787671 + +**2. [Rule 1 - Bug] flutter_local_notifications v21 uses named parameters** +- **Found during:** Task 2 (first test run against NotificationService) +- **Issue:** RESEARCH.md pattern and plan used positional parameters for `_plugin.initialize()` and `_plugin.zonedSchedule()`. flutter_local_notifications v21 changed to named parameters — compile error "Too many positional arguments" +- **Fix:** Updated NotificationService to use `settings:`, `id:`, `scheduledDate:`, `notificationDetails:`, `androidScheduleMode:` named params +- **Files modified:** lib/core/notifications/notification_service.dart +- **Verification:** `dart analyze` clean, tests pass +- **Committed in:** 4f72eac + +**3. [Rule 1 - Bug] Riverpod 3 generated provider name is notificationSettingsProvider** +- **Found during:** Task 2 (test compilation) +- **Issue:** Tests referenced `notificationSettingsNotifierProvider` but Riverpod 3 code gen for `NotificationSettingsNotifier` produces `notificationSettingsProvider` — consistent with existing pattern +- **Fix:** Updated all test references to use `notificationSettingsProvider` +- **Files modified:** test/core/notifications/notification_settings_notifier_test.dart +- **Verification:** Tests compile and pass +- **Committed in:** 4f72eac + +**4. [Rule 1 - Bug] Async _load() race condition in tests** +- **Found during:** Task 2 (setTime test failure) +- **Issue:** `setTime(9:30)` persisted correctly but state read back as `(9:00)` because the async `_load()` from `build()` ran after `setTime`, resetting state to SharedPreferences defaults (hour=7, minute=0 since prefs were empty) +- **Fix:** Added `makeContainer()` async helper that awaits `Future.delayed(Duration.zero)` to let initial `_load()` complete before mutations +- **Files modified:** test/core/notifications/notification_settings_notifier_test.dart +- **Verification:** All 7 notifier tests pass consistently +- **Committed in:** 4f72eac + +--- + +**Total deviations:** 4 auto-fixed (1 blocking dependency, 3 bugs from API mismatch/race condition) +**Impact on plan:** All auto-fixes were necessary for correctness. No scope creep. + +## Issues Encountered +- flutter_local_notifications v21 breaking changes (named params, compileSdk requirement) were not fully reflected in RESEARCH.md examples — all caught and fixed during compilation/test runs. + +## User Setup Required +None — no external service configuration required. + +## Next Phase Readiness +- NotificationService and NotificationSettingsNotifier fully implemented and tested +- Plan 02 can immediately wire notificationSettingsProvider into SettingsScreen +- notificationSettingsProvider (notificationSettings.dart) exports are ready for import +- ScheduledNotificationBootReceiver is registered and exported=true for Android 12+ +- Timezone is initialized at app start — no further setup needed for Plan 02 + +--- +*Phase: 04-notifications* +*Completed: 2026-03-16* + +## Self-Check: PASSED + +- lib/core/notifications/notification_service.dart: FOUND +- lib/core/notifications/notification_settings_notifier.dart: FOUND +- lib/core/notifications/notification_settings_notifier.g.dart: FOUND +- test/core/notifications/notification_service_test.dart: FOUND +- test/core/notifications/notification_settings_notifier_test.dart: FOUND +- .planning/phases/04-notifications/04-01-SUMMARY.md: FOUND +- commit 8787671: FOUND +- commit 0f6789b: FOUND +- commit 4f72eac: FOUND