Notification system audit: fix 6 defects, close 5 gaps, add rich notifications (v1.1.0)

Defects fixed:
- D1: Fix notification ID range collision (report reminders 700k→2M+)
- D2: Streak risk now checks both dzikir pagi & petang
- D3: _cancelPrayerPending no longer kills non-prayer notifications
- D4: Push notifications carry deeplink in payload for proper routing
- D5: Add reconfigureTimeZoneIfNeeded() for TZ change detection
- D6: Defer launch notification routing until widget tree is ready

Gaps closed:
- G1: Add streak risk + weekly summary toggles to settings UI
- G2: Verified boot reschedule already in place (flutter_local_notifications v21)
- G3: Remove unused mirrorAdzanToInbox field and legacy cleanup calls
- G4: Add notif_push_opened analytics tracking
- G5: Add notif_settings_changed analytics tracking

Enhancements:
- O1: Rich notification with Sudah Sholat action button on report reminders
- O2: Permission check on app resume via WidgetsBindingObserver (30s throttle)
- O2b: Fix stretched notification icon (white crescent moon vector drawable)
- O3: Expired inbox cleanup in background sync
- O4: Haptic feedback on notification bell quick actions

Bump version 1.0.8+9 → 1.1.0+10
This commit is contained in:
Dwindi Ramadhana
2026-06-06 22:38:02 +07:00
parent 2bd8e3666a
commit 4badfb6521
13 changed files with 420 additions and 37 deletions

View File

@@ -0,0 +1,111 @@
# Notification Feature Audit — Tasklist
**Source:** Full codebase trace of notification system
**Date:** June 2026
**Status legend:** `[ ]` Not started · `[~]` In progress · `[x]` Done · `[-]` Skipped
---
## Defects (Bugs)
### D1. Notification ID Range Collision — Adhan vs Report Reminders `[SEVERITY: High]`
- [x] **D1.1** Move report reminder ID range from `700000+` to `2000000+` in `_reportReminderId()`
- [x] **D1.2** Update `_cancelPrayerPending()` range guard to exclude the new report range explicitly
- [x] **D1.3** Verify adhan IDs (100k800k), iqamah IDs (800k1.5M), report IDs (2M+), non-prayer IDs (900k980k) are all disjoint
### D2. Streak Risk Only Checks Dzikir Petang, Not Pagi `[SEVERITY: Medium]`
- [x] **D2.1** Fix `emitStreakRiskIfNeeded` to check both `!pagi` and `!petang` in dzikir risk logic
- [x] **D2.2** Emit separate inbox items for pagi vs petang dzikir risk with correct deeplinks
### D3. `_cancelPrayerPending` Cancels Non-Prayer Notifications Too `[SEVERITY: Medium]`
- [x] **D3.1** Narrow the ID range filter to only cancel adhan (100k799k), iqamah (800k1.5M), and report (2M+) IDs
- [x] **D3.2** Exclude non-prayer range (900k980k) from cancellation
### D4. Notification Tap Routes All Non-Prayer to `/notifications` Instead of Deep Link `[SEVERITY: Medium]`
- [x] **D4.1** Update `routeForNotificationPayload` to parse deeplink from payload for `streak_risk` type
- [x] **D4.2** Include deeplink in notification payload through `_pushNonPrayer``showNonPrayerAlert` chain
### D5. Timezone Not Updated on Device TZ Change `[SEVERITY: Medium]`
- [x] **D5.1** Add `reconfigureTimeZoneIfNeeded()` method to detect and apply timezone changes
- [x] **D5.2** Reset `_lastSyncSignature` on TZ change to force prayer notification resync
### D6. `_handleLaunchNotification` May Fire Before Router is Ready `[SEVERITY: Low]`
- [x] **D6.1** Defer launch notification routing — store pending route, consume from `AppState.initState` with 800ms delay
---
## Gaps (Missing or Incomplete)
### G1. No Settings UI for Notification Preferences `[SEVERITY: High]`
- [x] **G1.1** Settings UI already existed — added missing `streakRiskEnabled` toggle to notification group
- [x] **G1.2** Added `weeklySummaryEnabled` toggle to notification group
- [x] **G1.3** All other notification settings (alerts, inbox, checklist reminder, quiet hours, push cap) were already present
### G2. No Device Reboot Reschedule `[SEVERITY: High]`
- [x] **G2.1** Verified `RECEIVE_BOOT_COMPLETED` permission in AndroidManifest.xml — already present
- [x] **G2.2** Verified `ScheduledNotificationReceiver` and `ScheduledNotificationBootReceiver` — already declared
- [x] **G2.3** `flutter_local_notifications` v21 handles reboot natively; `workmanager` periodic task resumes via `ExistingPeriodicWorkPolicy.update`
### G3. `mirrorAdzanToInbox` Setting Exists But Never Used `[SEVERITY: Medium]`
- [x] **G3.1** Removed unused `mirrorAdzanToInbox` field from `AppSettings` and generated adapter
- [x] **G3.2** Removed legacy `removeByType('prayer')` calls from `main.dart` and `notification_center_screen.dart`
### G4. No Analytics for `notif_push_opened` `[SEVERITY: Low]`
- [x] **G4.1** Added `notif_push_opened` tracking in `_handleNotificationResponse` (foreground) and `consumePendingLaunchRoute` (launch)
### G5. No Analytics for `notif_settings_changed` `[SEVERITY: Low]`
- [x] **G5.1** Added `notif_settings_changed` tracking in notification bell quick actions toggle
---
## Opportunities (Enhancements)
### O1. Rich Notification Actions — "Sudah Sholat" Button on Report Reminders
- [x] **O1.1** Added `AndroidNotificationAction` with `action_prayed` / "Sudah Sholat" button to `_scheduleShalatReportReminder`
- [x] **O1.2** Background handler (`notificationTapBackgroundHandler`) opens Hive and logs `ShalatLog(completed: true)` via `_markPrayedFromBackground`
- [x] **O1.3** Foreground handler (`_handleNotificationResponse`) logs prayer via `_markPrayedFromForeground`
- [x] **O1.4** Added `_resolvePrayerKeyFromName` to map display names back to canonical keys in background isolate
### O2. Notification Permission Check on App Resume via WidgetsBindingObserver
- [x] **O2.1** Added `_checkNotificationPermissionOnResume()` with 30-second throttle to `_AppState.didChangeAppLifecycleState`
- [x] **O2.2** Re-checks notification permissions and emits warnings via `emitPermissionWarningsIfNeeded` on resume
### O2b. Fix Stretched Notification Icon
- [x] **O2b.1** Created `@drawable/ic_notification` — white crescent moon vector drawable (Android notification-safe)
- [x] **O2b.2** Changed `AndroidInitializationSettings` from `@mipmap/ic_launcher` to `@drawable/ic_notification`
- [x] **O2b.3** Added `icon: '@drawable/ic_notification'` to all 4 notification channels
### O3. Add Expired Item Cleanup to Background Sync
- [x] **O3.1** Added `removeExpired()` call in `BackgroundSyncService.runSyncPass()`
### O4. Haptic Feedback on Quick Actions
- [x] **O4.1** Added `HapticFeedback.selectionClick()` to all three notification bell quick action taps
---
## Progress Tracker
| Category | Total | Done | Skipped | Remaining |
|----------|-------|------|---------|------------|
| Defects (D1D6) | 11 | 11 | 0 | 0 |
| Gaps (G1G5) | 10 | 10 | 0 | 0 |
| Opportunities (O1O4) | 9 | 9 | 0 | 0 |
| **TOTAL** | **30** | **30** | **0** | **0** |
---
## Files Changed
| File | Changes |
|------|---------|
| `lib/data/services/notification_service.dart` | D1: ID range fix, D3: cancel range fix, D4: payload routing with deeplink, D5: TZ reconfig, D6: deferred launch routing, O1: rich notification action + background handler, O2b: icon fix |
| `lib/data/services/notification_event_producer_service.dart` | D2: pagi+petang dzikir streak risk, D4: deeplink threading |
| `lib/core/widgets/notification_bell_button.dart` | G5: analytics tracking, O4: haptic feedback |
| `lib/data/services/background_sync_service.dart` | O3: expired inbox cleanup |
| `lib/features/settings/presentation/settings_screen.dart` | G1: streak risk + weekly summary toggles |
| `lib/data/local/models/app_settings.dart` | G3: removed `mirrorAdzanToInbox` field |
| `lib/data/local/models/app_settings.g.dart` | G3: removed field 32 from adapter |
| `lib/main.dart` | G3: removed legacy `removeByType('prayer')` |
| `lib/features/notifications/presentation/notification_center_screen.dart` | G3: removed legacy cleanup |
| `lib/app/app.dart` | D6: consume pending launch route on init, O2: permission check on resume |
| `android/app/src/main/res/drawable/ic_notification.xml` | O2b: white crescent moon vector drawable for notification icon |