forked from trent_larson/crowd-funder-for-time-pwa
Aggregate API rows into one notification with Starred Project Update(s) titles, plan names in typographic quotes, and "+ N more have been updated." for multiples. Stop emitting the empty-data "No Project Updates" fallback. Sync internal docs.
104 lines
8.1 KiB
Markdown
104 lines
8.1 KiB
Markdown
# 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
|
||
|
||
- **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` |
|
||
|
||
|