# New Activity Notification (API-Driven Daily Message) **Purpose:** Integrate the daily-notification-plugin’s second feature—the **daily, API-driven message**—into the crowd-funder (TimeSafari) app. The first feature (daily static reminder) is already integrated; this document covers the plan, completed work, and remaining tasks for the API-driven flow. **References:** - Plugin: `daily-notification-plugin` (INTEGRATION_GUIDE.md, definitions.ts) - Alignment outline: `doc/daily-notification-alignment-outline.md` - Help copy: `HelpNotificationTypesView.vue` (“New Activity Notifications”) --- ## Plan Summary The API-driven flow: 1. **Prefetch** – Shortly before the user’s chosen time, the plugin runs a background job that calls the Endorser.ch API (e.g. `plansLastUpdatedBetween`, and optionally offers endpoints) using credentials supplied by the app. 2. **Cache** – Fetched content is stored in the plugin’s cache. 3. **Notify** – At the chosen time, the user sees a notification whose title/body come from that content (or a fallback). The app must: - **Configure the native fetcher** with `apiBaseUrl`, `activeDid`, and a JWT so the plugin’s background workers can call the API. - **Implement the native fetcher** (or register an implementation) so the plugin can perform the actual HTTP requests and parse responses into notification content. - **Sync starred plan IDs** to the plugin via `updateStarredPlans` so the fetcher knows which plans to query. - **Expose UI** to enable/disable the “New Activity” notification and choose a time, and call `scheduleDualNotification` / `cancelDualSchedule` accordingly. --- ## Tasks Finished - **Configure native fetcher on startup and identity** - Added `configureNativeFetcherIfReady()` in `src/services/notifications/nativeFetcherConfig.ts` (reads `activeDid` and `apiServer` from DB, gets JWT via `getHeaders(did)`, calls `DailyNotification.configureNativeFetcher()`). - Called from `main.capacitor.ts` after the 2s delay (with deep link registration). - Called from `AccountViewView.initializeState()` when on native and `activeDid` is set; when New Activity is enabled, also calls `updateStarredPlans(settings.starredPlanHandleIds)`. - **Implement real API calls in Android native fetcher** - `android/app/src/main/java/app/timesafari/TimeSafariNativeFetcher.java` implements `NativeNotificationContentFetcher`: POST to `/api/v2/report/plansLastUpdatedBetween` with `planIds` (from SharedPreferences `daily_notification_timesafari` / `starredPlanIds`) and `afterId`; when `data` is non-empty, builds **one** aggregated `NotificationContent` (title **Starred Project Update** or **Starred Project Updates**, body from `plan.name` with typographic quotes, then `has been updated.` or `+ N more have been updated.`); when `data` is empty, returns an empty list (no “no updates” notification); updates `last_acked_jwt_id` for pagination when content is returned. - Registered in `MainActivity.onCreate()` via `DailyNotificationPlugin.setNativeFetcher(new TimeSafariNativeFetcher(this))`. - **Sync starred plan IDs** - Shared helper `syncStarredPlansToNativePlugin(planIds)` in `src/services/notifications/syncStarredPlansToNativePlugin.ts` (exported from `src/services/notifications/index.ts`) calls `DailyNotification.updateStarredPlans` on native only; ignores `UNIMPLEMENTED`. - When user enables New Activity, `scheduleNewActivityDualNotification()` uses the helper with `settings.starredPlanHandleIds ?? []`. - When Account view loads and New Activity is on, `initializeState()` uses the helper with the same list. - When the user stars or unstars on a project (`ProjectViewView.toggleStar`), after a successful settings save, the helper runs if `notifyingNewActivityTime` is set so prefetch sees the current list without reopening Account. - **Dual schedule config and scheduling** - Added `src/services/notifications/dualScheduleConfig.ts`: `timeToCron()`, `timeToCronFiveMinutesBefore()`, `buildDualScheduleConfig({ notifyTime, title?, body? })` (contentFetch 5 min before, userNotification at chosen time). - When user enables New Activity and picks a time, app calls `DailyNotification.scheduleDualNotification({ config })` with this config. - When user disables New Activity, app calls `DailyNotification.cancelDualSchedule()`. - **UI for New Activity notification** - Unhid the “New Activity Notification” block in `AccountViewView.vue` (toggle + accessibility). - Enable flow: time dialog → save settings → on native, `scheduleNewActivityDualNotification(timeText)` (configure fetcher, updateStarredPlans, scheduleDualNotification). - Disable flow: on native, `cancelDualSchedule()` then save and clear settings. - Added `starredPlanHandleIds` to `AccountSettings` in `interfaces/accountView.ts`. - **Exports** - `src/services/notifications/index.ts` exports `configureNativeFetcherIfReady`, `syncStarredPlansToNativePlugin`, `buildDualScheduleConfig`, `timeToCron`, `timeToCronFiveMinutesBefore`, and `DualScheduleConfigInput`. --- ## Checklist of Remaining Tasks ### iOS **Parity outline (API, starred plans, plugin vs app work):** See **`doc/new-activity-notifications-ios-android-parity.md`**. - **Confirm iOS native fetcher / dual schedule** Plugin exposes `configureNativeFetcher` on iOS. Confirm whether the plugin expects an iOS-specific native fetcher registration (similar to Android’s `setNativeFetcher`) and, if so, register a TimeSafari fetcher implementation for iOS so API-driven notifications work on iPhone. - **Verify dual schedule on iOS** Test `scheduleDualNotification` and `cancelDualSchedule` on iOS; ensure content fetch and user notification fire at the expected times and that foreground/background behavior matches expectations. ### Testing and hardening - **Test full flow on Android** Enable New Activity, set time, wait for prefetch and notification (or use a short rollover for testing). Confirm notification shows with API-derived or fallback content. - **Test full flow on iOS** Same as Android: enable, set time, verify prefetch and notification delivery and content. - **Test with no starred plans** Enable New Activity with empty `starredPlanHandleIds`; confirm no crash; the native fetcher returns no Endorser-derived items when there is nothing to query or no new rows (see `TimeSafariNativeFetcher`). - **Test JWT expiry** Ensure behavior when the token passed to `configureNativeFetcher` has expired (e.g. app in background for a long time); document or implement refresh (e.g. re-call `configureNativeFetcherIfReady` on foreground or when opening Account). ### Optional enhancements - **Offers endpoints** Extend `TimeSafariNativeFetcher` (and any iOS fetcher) to call offers endpoints (e.g. `offers`, `offersToPlansOwnedByMe`) and merge with project-update content for richer notifications. - **Documentation** Add a short “New Activity notifications” section to BUILDING.md or a user-facing help page describing how the feature works and how to troubleshoot (e.g. no notification, wrong content, JWT/API errors). --- ## File Reference | Area | Files | | ---------------------- | ----------------------------------------------------------------------- | | Fetcher config | `src/services/notifications/nativeFetcherConfig.ts` | | Starred list → plugin | `src/services/notifications/syncStarredPlansToNativePlugin.ts` | | Dual schedule config | `src/services/notifications/dualScheduleConfig.ts` | | Notification exports | `src/services/notifications/index.ts` | | Startup | `src/main.capacitor.ts` | | Account UI and flow | `src/views/AccountViewView.vue` | | Project star / unstar | `src/views/ProjectViewView.vue` (`toggleStar`) | | Settings type | `src/interfaces/accountView.ts` | | Android native fetcher | `android/app/src/main/java/app/timesafari/TimeSafariNativeFetcher.java` | | Android registration | `android/app/src/main/java/app/timesafari/MainActivity.java` | | iOS native fetcher | `ios/App/App/TimeSafariNativeFetcher.swift` | | iOS registration | `ios/App/App/AppDelegate.swift` (`DailyNotificationPlugin.registerNativeFetcher`) | | Plugin 3.x handoff | `doc/consuming-app-handoff-ios-native-fetcher-chained-dual.md` |