feat(notifications): enable foreground notifications and rollover recovery

- iOS: set UNUserNotificationCenter delegate and implement willPresent
  so notifications show in foreground and DailyNotificationDelivered is
  posted for rollover; implement didReceive for tap handling; re-set
  delegate in applicationDidBecomeActive
- Android: move DailyNotificationReceiver and BootReceiver inside
  <application>; add NotifyReceiver; extend BootReceiver with
  LOCKED_BOOT_COMPLETED, MY_PACKAGE_REPLACED, directBootAware
- main.capacitor: import daily-notification-plugin at startup so
  plugin (and recovery) load on launch
- doc: add daily-notification-alignment-outline.md

Fixes foreground notifications not showing and rollover recovery;
Android receivers were previously declared outside <application>.
This commit is contained in:
Jose Olarte III
2026-01-29 21:10:34 +08:00
parent fb4ea08f3c
commit 22c3ac80c2
4 changed files with 172 additions and 20 deletions

View File

@@ -0,0 +1,106 @@
# Daily Notification Plugin: Alignment Outline
**Purpose:** Checklist of changes/additions needed in this app to align with the test app (`daily-notification-plugin/test-apps/daily-notification-test`) so that:
1. **Rollover recovery** (and rollover itself) works.
2. **Notifications show when the app is in the foreground** (not only background/closed).
3. **Plugin loads at app launch** so recovery runs after reboot without the user opening notification UI.
**Reference:** Test app at
`/Users/aardimus/Sites/trentlarson/daily-notification-plugin_test/daily-notification-plugin/test-apps/daily-notification-test`
---
## 1. iOS AppDelegate
**File:** `ios/App/App/AppDelegate.swift`
### 1.1 Add imports
- [ ] `import UserNotifications`
- [ ] Import the Daily Notification plugin framework (Swift module name: **TimesafariDailyNotificationPlugin** per this apps Podfile; test app uses **DailyNotificationPlugin**)
### 1.2 Conform to `UNUserNotificationCenterDelegate`
- [ ] Add `, UNUserNotificationCenterDelegate` to the class declaration:
`class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate`
### 1.3 Force-load plugin at launch
- [ ] In `application(_:didFinishLaunchingWithOptions:)`, **before** other setup, add logic to force-load the plugin class (e.g. `_ = DailyNotificationPlugin.self` or the class exposed by the TimesafariDailyNotificationPlugin pod) so that the plugins `load()` (and thus `performRecovery()`) runs at app launch, not only when JS first calls the plugin.
### 1.4 Set notification center delegate
- [ ] In `didFinishLaunchingWithOptions`, set:
`UNUserNotificationCenter.current().delegate = self`
- [ ] In `applicationDidBecomeActive`, **re-set** the same delegate (in case Capacitor or another component clears it).
### 1.5 Implement `userNotificationCenter(_:willPresent:withCompletionHandler:)`
- [ ] When a notification is delivered (including in foreground), read `notification_id` and `scheduled_time` from `notification.request.content.userInfo`.
- [ ] Post rollover event:
`NotificationCenter.default.post(name: NSNotification.Name("DailyNotificationDelivered"), object: nil, userInfo: ["notification_id": id, "scheduled_time": scheduledTime])`
- [ ] Call completion handler with presentation options so the notification is shown in foreground, e.g.
`completionHandler([.banner, .sound, .badge])` (use `.alert` on iOS 13 if needed).
### 1.6 Implement `userNotificationCenter(_:didReceive:withCompletionHandler:)`
- [ ] Handle notification tap/interaction; call `completionHandler()` when done.
---
## 2. Android Manifest
**File:** `android/app/src/main/AndroidManifest.xml`
### 2.1 Fix receiver placement
- [ ] Move the two `<receiver>` elements (**DailyNotificationReceiver** and **BootReceiver**) **inside** the `<application>` block (e.g. after `<activity>...</activity>` and before `<provider>...</provider>`).
- [ ] Remove the stray second `</application>` so there is a single `<application>...</application>` containing activity, receivers, and provider.
### 2.2 (Optional) Add NotifyReceiver
- [ ] If the plugins Android integration expects **NotifyReceiver** for alarm-based delivery, add a `<receiver>` for `com.timesafari.dailynotification.NotifyReceiver` inside `<application>` (see test app manifest for exact declaration).
### 2.3 (Optional) BootReceiver options
- [ ] Consider aligning with test app: add `android:directBootAware="true"`, `android:exported="true"`, and intent-filter actions `LOCKED_BOOT_COMPLETED`, `MY_PACKAGE_REPLACED`, `PACKAGE_REPLACED` if you need the same boot/update behavior.
---
## 3. Capacitor / JS startup (optional but recommended)
**File:** `src/main.capacitor.ts` (or the main entry used for native builds)
### 3.1 Load plugin at startup
- [ ] Add a top-level import or an early call that touches the Daily Notification plugin so the JS side loads it at app startup (e.g. `import "@timesafari/daily-notification-plugin"` or a small init that calls `getRebootRecoveryStatus()` or `configure()`).
This ensures the plugin is loaded as soon as the app runs; together with the iOS force-load in AppDelegate, recovery runs at launch.
---
## 4. Plugin configuration (optional)
- [ ] If you use the native fetcher or need plugin config (db path, storage, etc.), call `DailyNotification.configure()` and/or `configureNativeFetcher()` when appropriate (e.g. after login or when notification UI is first used), similar to the test apps `configureNativeFetcher()` in HomeView.
---
## 5. Summary table
| Area | Change / addition |
|-------------------------|------------------------------------------------------------------------------------|
| **iOS AppDelegate** | Conform to `UNUserNotificationCenterDelegate`; set delegate; force-load plugin; implement `willPresent` (post `DailyNotificationDelivered` + show in foreground) and `didReceive`. |
| **Android manifest** | Move DailyNotificationReceiver and BootReceiver inside `<application>`; remove duplicate `</application>`; optionally add NotifyReceiver and BootReceiver options. |
| **main.capacitor.ts** | Optionally import or call plugin at startup so it (and recovery) load at launch. |
| **Plugin config** | Optionally call `configure()` / `configureNativeFetcher()` where appropriate. |
---
## 6. Verification
After making the changes:
- [ ] **iOS:** Build and run; trigger a daily notification and confirm it appears when the app is in the foreground.
- [ ] **iOS:** Confirm rollover (next days schedule) still occurs after a notification fires (check logs for `DNP-ROLLOVER` / `DailyNotificationDelivered`).
- [ ] **iOS:** Restart the app (or reboot) and confirm recovery runs without opening the notification settings screen (e.g. logs show plugin load and recovery).
- [ ] **Android:** Build and run; confirm receivers are registered (no manifest errors) and that notifications and boot recovery behave as expected.