From 5a4ab84bfe8d50157671ce59e9ee27994dd32f35 Mon Sep 17 00:00:00 2001 From: Jose Olarte III Date: Fri, 23 Jan 2026 19:06:16 +0800 Subject: [PATCH] feat(notifications): integrate DailyNotificationPlugin with UI for native platforms Integrate DailyNotificationPlugin with notification UI to enable native notifications on iOS/Android while maintaining web push for web/PWA. - Add platform detection to PushNotificationPermission component - Implement native notification flow via NotificationService - Hide push server setting on native platforms (not needed) - Add time conversion (AM/PM to 24-hour) for native plugin - Add comprehensive documentation Breaking Changes: None (backward compatible) --- ...otification-integration-changes-outline.md | 412 ++++++++++++++++++ doc/notification-permissions-and-rollovers.md | 238 ++++++++++ doc/notification-system-overview.md | 378 ++++++++++++++++ src/components/PushNotificationPermission.vue | 210 ++++++++- src/views/AccountViewView.vue | 96 ++-- 5 files changed, 1290 insertions(+), 44 deletions(-) create mode 100644 doc/notification-integration-changes-outline.md create mode 100644 doc/notification-permissions-and-rollovers.md create mode 100644 doc/notification-system-overview.md diff --git a/doc/notification-integration-changes-outline.md b/doc/notification-integration-changes-outline.md new file mode 100644 index 00000000..a68554d9 --- /dev/null +++ b/doc/notification-integration-changes-outline.md @@ -0,0 +1,412 @@ +# Notification Integration Changes - Implementation Outline + +**Date**: 2026-01-23 +**Purpose**: Detailed outline of changes needed to integrate DailyNotificationPlugin with UI + +--- + +## Overview + +This document outlines all changes required to integrate the DailyNotificationPlugin with the existing notification UI, making it work seamlessly on both native (iOS/Android) and web platforms. + +**Estimated Complexity**: Medium +**Estimated Files Changed**: 3-4 files +**Breaking Changes**: None (backward compatible) + +--- + +## Change Summary + +| File | Changes | Complexity | Risk | +|------|---------|------------|------| +| `PushNotificationPermission.vue` | Add platform detection, native flow | Medium | Low | +| `AccountViewView.vue` | Platform detection in toggles, hide push server on native | Low | Low | +| `WebPushNotificationService.ts` | Complete stub implementation (optional) | Medium | Low | + +--- + +## Detailed Changes + +### 1. PushNotificationPermission.vue + +**File**: `src/components/PushNotificationPermission.vue` +**Current Lines**: ~656 lines +**Estimated New Lines**: +50-80 lines +**Complexity**: Medium + +#### Changes Required + +**A. Add Imports** (Top of script section) +```typescript +import { Capacitor } from "@capacitor/core"; +import { NotificationService } from "@/services/notifications"; +``` + +**B. Add Platform Detection Property** +```typescript +// Add to class properties +private get isNativePlatform(): boolean { + return Capacitor.isNativePlatform(); +} +``` + +**C. Modify `open()` Method** (Lines 170-258) +- **Current**: Always initializes web push (VAPID key, service worker) +- **Change**: Add platform check at start + - If native: Skip VAPID/service worker, show UI immediately + - If web: Keep existing logic + +**D. Modify `turnOnNotifications()` Method** (Lines 393-499) +- **Current**: Web push subscription flow +- **Change**: Split into two paths: + - **Native path**: Use `NotificationService.getInstance()` → `requestPermissions()` → `scheduleDailyNotification()` + - **Web path**: Keep existing logic + +**E. Add New Method: `turnOnNativeNotifications()`** +- Request permissions via `NotificationService` +- Convert time input (AM/PM) to 24-hour format (HH:mm) +- Call `scheduleDailyNotification()` with proper options +- Save to settings +- Call callback with success/time/message + +**F. Update `handleTurnOnNotifications()` Method** (Line 643) +- Add platform check +- Route to `turnOnNativeNotifications()` or `turnOnNotifications()` based on platform + +**G. Update Computed Properties** +- `isSystemReady`: For native, return `true` immediately (no VAPID needed) +- `canShowNotificationForm`: For native, return `true` immediately + +**H. Update Template** (Optional - for better UX) +- Add platform-specific messaging if desired +- Native: "Notifications will be scheduled on your device" +- Web: Keep existing messaging + +#### Code Structure Preview + +```typescript +async open(pushType: string, callback?: ...) { + this.callback = callback || this.callback; + this.isVisible = true; + this.pushType = pushType; + + // Platform detection + if (this.isNativePlatform) { + // Native: No VAPID/service worker needed + this.serviceWorkerReady = true; // Fake it for UI + this.vapidKey = "native"; // Placeholder + return; // Skip web push initialization + } + + // Existing web push initialization... + // (keep all existing code) +} + +async turnOnNotifications() { + if (this.isNativePlatform) { + return this.turnOnNativeNotifications(); + } + // Existing web push logic... +} + +private async turnOnNativeNotifications(): Promise { + const service = NotificationService.getInstance(); + + // Request permissions + const granted = await service.requestPermissions(); + if (!granted) { + // Handle permission denial + return; + } + + // Convert time to 24-hour format + const time24h = this.convertTo24HourFormat(); + + // Determine title and body based on pushType + const title = this.pushType === this.DAILY_CHECK_TITLE + ? "Daily Check-In" + : "Daily Reminder"; + const body = this.pushType === this.DIRECT_PUSH_TITLE + ? this.messageInput + : "Time to check your TimeSafari activity"; + + // Schedule notification + const success = await service.scheduleDailyNotification({ + time: time24h, + title, + body, + priority: 'normal' + }); + + if (success) { + // Save to settings + const timeText = this.notificationTimeText; + await this.$saveSettings({ + [this.pushType === this.DAILY_CHECK_TITLE + ? 'notifyingNewActivityTime' + : 'notifyingReminderTime']: timeText, + ...(this.pushType === this.DIRECT_PUSH_TITLE && { + notifyingReminderMessage: this.messageInput + }) + }); + + // Call callback + this.callback(true, timeText, this.messageInput); + } +} + +private convertTo24HourFormat(): string { + const hour = parseInt(this.hourInput); + const minute = parseInt(this.minuteInput); + + let hour24 = hour; + if (!this.hourAm && hour !== 12) { + hour24 = hour + 12; + } else if (this.hourAm && hour === 12) { + hour24 = 0; + } + + return `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`; +} +``` + +#### Testing Considerations +- Test on iOS device +- Test on Android device +- Test on web (should still work as before) +- Test permission denial flow +- Test time conversion (AM/PM → 24-hour) + +--- + +### 2. AccountViewView.vue + +**File**: `src/views/AccountViewView.vue` +**Current Lines**: 2124 lines +**Estimated New Lines**: +20-30 lines +**Complexity**: Low + +#### Changes Required + +**A. Add Import** (Top of script section, around line 739) +```typescript +import { Capacitor } from "@capacitor/core"; +``` + +**B. Add Computed Property** (In class, around line 888) +```typescript +private get isNativePlatform(): boolean { + return Capacitor.isNativePlatform(); +} +``` + +**C. Modify Notification Toggle Methods** (Lines 1134-1202) + +**`showNewActivityNotificationChoice()`** (Lines 1134-1158) +- **Current**: Always uses `PushNotificationPermission` component +- **Change**: Add platform check + - If native: Use `NotificationService` directly (or still use component - it will handle platform) + - If web: Keep existing logic + - **Note**: Since we're updating `PushNotificationPermission` to handle both, this might not need changes, but we could add direct native path for cleaner code + +**`showReminderNotificationChoice()`** (Lines 1171-1202) +- Same as above + +**D. Conditionally Hide Push Server Setting** (Lines 506-549) +- Wrap the entire "Notification Push Server" section in `v-if="!isNativePlatform"` +- This hides it on iOS/Android where it's not needed + +**E. Update Status Display** (Optional) +- When showing notification status, could add platform indicator +- "Native notification scheduled" vs "Web push subscription active" + +#### Code Structure Preview + +```typescript +// Add computed property +private get isNativePlatform(): boolean { + return Capacitor.isNativePlatform(); +} + +// In template, wrap push server section: +
+

+ Notification Push Server +

+ +
+ +// Optional: Update notification choice methods +async showNewActivityNotificationChoice(): Promise { + if (!this.notifyingNewActivity) { + // Component now handles platform detection, so this can stay the same + // OR we could add direct native path here for cleaner separation + (this.$refs.pushNotificationPermission as PushNotificationPermission) + .open(DAILY_CHECK_TITLE, async (success: boolean, timeText: string) => { + // ... existing callback ... + }); + } else { + // ... existing turn-off logic ... + } +} +``` + +#### Testing Considerations +- Verify push server section hidden on iOS +- Verify push server section hidden on Android +- Verify push server section visible on web +- Test notification toggles work on all platforms + +--- + +### 3. WebPushNotificationService.ts (Optional Enhancement) + +**File**: `src/services/notifications/WebPushNotificationService.ts` +**Current Lines**: 213 lines +**Estimated New Lines**: +100-150 lines +**Complexity**: Medium +**Priority**: Low (can be done later) + +#### Changes Required + +**A. Complete `scheduleDailyNotification()` Implementation** +- Extract logic from `PushNotificationPermission.vue` +- Subscribe to push service +- Send subscription to server +- Return success status + +**B. Complete `cancelDailyNotification()` Implementation** +- Get current subscription +- Unsubscribe from push service +- Notify server to stop sending + +**C. Complete `getStatus()` Implementation** +- Check settings for `notifyingNewActivityTime` / `notifyingReminderTime` +- Check service worker subscription status +- Return combined status + +**Note**: This is optional because `PushNotificationPermission.vue` already handles web push. Completing this would allow using `NotificationService` directly for web too, but it's not required for the integration to work. + +--- + +## Implementation Order + +### Phase 1: Core Integration (Required) +1. ✅ Update `PushNotificationPermission.vue` with platform detection +2. ✅ Update `AccountViewView.vue` to hide push server on native +3. ✅ Test on native platforms + +### Phase 2: Polish (Optional) +4. Complete `WebPushNotificationService.ts` implementation +5. Add platform-specific UI messaging +6. Add status indicators + +--- + +## Risk Assessment + +### Low Risk Changes +- ✅ Adding platform detection (read-only check) +- ✅ Conditionally hiding UI elements +- ✅ Adding new code paths (not modifying existing) + +### Medium Risk Changes +- ⚠️ Modifying `turnOnNotifications()` flow (but we're adding, not replacing) +- ⚠️ Time format conversion (need to test edge cases) + +### Mitigation Strategies +1. **Backward Compatibility**: All changes are additive - existing web push flow remains unchanged +2. **Feature Flags**: Could add feature flag to enable/disable native notifications +3. **Gradual Rollout**: Test on one platform first (e.g., Android), then iOS +4. **Fallback**: If native service fails, could fall back to showing error message + +--- + +## Testing Checklist + +### Functional Testing +- [ ] Native iOS: Request permissions → Schedule notification → Verify scheduled +- [ ] Native Android: Request permissions → Schedule notification → Verify scheduled +- [ ] Web: Existing flow still works (no regression) +- [ ] Permission denial: Shows appropriate error message +- [ ] Time conversion: AM/PM correctly converts to 24-hour format +- [ ] Both notification types: Daily Check and Direct Push work on native +- [ ] Settings persistence: Times saved correctly to database + +### UI Testing +- [ ] Push server setting hidden on iOS +- [ ] Push server setting hidden on Android +- [ ] Push server setting visible on web +- [ ] Notification toggles work on all platforms +- [ ] Time picker UI works on native (same as web) + +### Edge Cases +- [ ] 12:00 AM conversion (should be 00:00) +- [ ] 12:00 PM conversion (should be 12:00) +- [ ] Invalid time input handling +- [ ] App restart: Notifications still scheduled +- [ ] Device reboot: Notifications still scheduled (Android) + +--- + +## Dependencies + +### Required +- ✅ `@capacitor/core` - Already in project +- ✅ `@timesafari/daily-notification-plugin` - Already installed +- ✅ `NotificationService` - Already created + +### No New Dependencies Needed + +--- + +## Estimated Effort + +| Task | Time Estimate | +|------|---------------| +| Update PushNotificationPermission.vue | 2-3 hours | +| Update AccountViewView.vue | 30 minutes - 1 hour | +| Testing on iOS | 1-2 hours | +| Testing on Android | 1-2 hours | +| Bug fixes & polish | 1-2 hours | +| **Total** | **5-10 hours** | + +--- + +## Rollback Plan + +If issues arise: + +1. **Quick Rollback**: Revert changes to `PushNotificationPermission.vue` and `AccountViewView.vue` +2. **Partial Rollback**: Keep platform detection but disable native path (feature flag) +3. **No Data Migration Needed**: Settings structure unchanged + +--- + +## Questions to Consider + +1. **Should we keep using `PushNotificationPermission` component for native, or create separate native flow?** + - **Recommendation**: Keep using component (simpler, less code duplication) + +2. **Should we show different UI messaging for native vs web?** + - **Recommendation**: Optional enhancement, not required for MVP + +3. **Should we complete `WebPushNotificationService` now or later?** + - **Recommendation**: Later (not blocking, existing component works) + +4. **How to handle notification cancellation on native?** + - **Recommendation**: Use `NotificationService.cancelDailyNotification()` in existing turn-off logic + +--- + +## Next Steps After Implementation + +1. Update documentation with platform-specific instructions +2. Add error handling for edge cases +3. Consider adding notification status display in UI +4. Test on real devices (critical for native notifications) +5. Monitor for any platform-specific issues + +--- + +**Last Updated**: 2026-01-23 diff --git a/doc/notification-permissions-and-rollovers.md b/doc/notification-permissions-and-rollovers.md new file mode 100644 index 00000000..143d4f05 --- /dev/null +++ b/doc/notification-permissions-and-rollovers.md @@ -0,0 +1,238 @@ +# Notification Permissions & Rollover Handling + +**Date**: 2026-01-23 +**Purpose**: Answers to questions about permission requests and rollover handling + +--- + +## Question 1: Where does the notification permission request happen? + +### Permission Request Flow + +The permission request flows through multiple layers: + +``` +User clicks "Turn on Daily Message" + ↓ +PushNotificationPermission.vue + ↓ (line 715) +service.requestPermissions() + ↓ +NotificationService.getInstance() + ↓ (platform detection) +NativeNotificationService.requestPermissions() + ↓ (line 53) +DailyNotification.requestPermissions() + ↓ +Plugin Native Code + ↓ +┌─────────────────────┬─────────────────────┐ +│ iOS Platform │ Android Platform │ +├─────────────────────┼─────────────────────┤ +│ UNUserNotification │ ActivityCompat │ +│ Center.current() │ .requestPermissions()│ +│ .requestAuthorization│ │ +│ (options: [.alert, │ (POST_NOTIFICATIONS) │ +│ .sound, .badge]) │ │ +└─────────────────────┴─────────────────────┘ + ↓ +Native OS Permission Dialog + ↓ +User grants/denies + ↓ +Result returned to app +``` + +### Code Locations + +**1. UI Entry Point** (`src/components/PushNotificationPermission.vue`): +```typescript +// Line 715 +const granted = await service.requestPermissions(); +``` + +**2. Service Layer** (`src/services/notifications/NativeNotificationService.ts`): +```typescript +// Lines 49-68 +async requestPermissions(): Promise { + const result = await DailyNotification.requestPermissions(); + return result.allPermissionsGranted; +} +``` + +**3. Plugin Registration** (`src/plugins/DailyNotificationPlugin.ts`): +```typescript +// Line 30-36 +const DailyNotification = registerPlugin( + "DailyNotification" +); +``` + +**4. iOS Native Implementation** (`node_modules/@timesafari/daily-notification-plugin/ios/Plugin/DailyNotificationScheduler.swift`): +```swift +// Lines 113-115 +func requestPermissions() async -> Bool { + let granted = try await notificationCenter.requestAuthorization( + options: [.alert, .sound, .badge] + ) + return granted +} +``` + +**5. Android Native Implementation** (`node_modules/@timesafari/daily-notification-plugin/android/src/main/java/com/timesafari/dailynotification/PermissionManager.java`): +```java +// Line 87 +ActivityCompat.requestPermissions( + activity, + new String[]{Manifest.permission.POST_NOTIFICATIONS}, + REQUEST_CODE +); +``` + +### Platform-Specific Details + +#### iOS +- **API Used**: `UNUserNotificationCenter.requestAuthorization()` +- **Options Requested**: `.alert`, `.sound`, `.badge` +- **Dialog**: System-native iOS permission dialog +- **Location**: First time user enables notifications +- **Result**: Returns `true` if granted, `false` if denied + +#### Android +- **API Used**: `ActivityCompat.requestPermissions()` +- **Permission**: `POST_NOTIFICATIONS` (Android 13+) +- **Dialog**: System-native Android permission dialog +- **Location**: First time user enables notifications +- **Result**: Returns `true` if granted, `false` if denied +- **Note**: Android 12 and below don't require runtime permission (declared in manifest) + +### When Permission Request Happens + +The permission request is triggered when: +1. User opens the notification setup dialog (`PushNotificationPermission.vue`) +2. User clicks "Turn on Daily Message" button +3. App detects native platform (`isNativePlatform === true`) +4. `turnOnNativeNotifications()` method is called +5. `service.requestPermissions()` is called (line 715) + +**Important**: The permission dialog only appears **once** per app installation. After that: +- If granted: Future calls to `requestPermissions()` return `true` immediately +- If denied: User must manually enable in system settings + +--- + +## Question 2: Does the plugin handle rollovers automatically? + +### ✅ Yes - Rollover Handling is Automatic + +The plugin **automatically handles rollovers** in multiple scenarios: + +### 1. Initial Scheduling (Time Has Passed Today) + +**Location**: `ios/Plugin/DailyNotificationScheduler.swift` (lines 326-329) + +```swift +// If time has passed today, schedule for tomorrow +if scheduledDate <= now { + scheduledDate = calendar.date(byAdding: .day, value: 1, to: scheduledDate) ?? scheduledDate +} +``` + +**Behavior**: +- If user schedules a notification for 9:00 AM but it's already 10:00 AM today +- Plugin automatically schedules it for 9:00 AM **tomorrow** +- No manual intervention needed + +### 2. Daily Rollover (After Notification Fires) + +**Location**: `ios/Plugin/DailyNotificationScheduler.swift` (lines 437-609) + +The plugin has a `scheduleNextNotification()` function that: +- Automatically schedules the next day's notification after current one fires +- Handles 24-hour rollovers with DST (Daylight Saving Time) awareness +- Prevents duplicate rollovers with state tracking + +**Key Function**: `calculateNextScheduledTime()` (lines 397-435) +```swift +// Add 24 hours (handles DST transitions automatically) +guard let nextDate = calendar.date(byAdding: .hour, value: 24, to: currentDate) else { + // Fallback to simple 24-hour addition + return currentScheduledTime + (24 * 60 * 60 * 1000) +} +``` + +**Features**: +- ✅ DST-safe: Uses Calendar API to handle daylight saving transitions +- ✅ Automatic: No manual scheduling needed +- ✅ Persistent: Survives app restarts and device reboots +- ✅ Duplicate prevention: Tracks rollover state to prevent duplicates + +### 3. Rollover State Tracking + +**Location**: `ios/Plugin/DailyNotificationStorage.swift` (lines 161-195) + +The plugin tracks rollover state to prevent duplicate scheduling: + +```swift +// Check if rollover was processed recently (< 1 hour ago) +if let lastTime = lastRolloverTime, + (currentTime - lastTime) < (60 * 60 * 1000) { + // Skip - already processed + return false +} +``` + +**Purpose**: Prevents multiple rollover attempts if notification fires multiple times + +### 4. Android Rollover Handling + +Android implementation also handles rollovers: +- Uses `AlarmManager` with `setRepeating()` or schedules next alarm after current fires +- Handles timezone changes and DST transitions +- Persists across device reboots via `BootReceiver` + +### Rollover Scenarios Handled + +| Scenario | Handled? | How | +|----------|----------|-----| +| Time passed today | ✅ Yes | Schedules for tomorrow automatically | +| Daily rollover | ✅ Yes | Schedules next day after notification fires | +| DST transitions | ✅ Yes | Uses Calendar API for DST-aware calculations | +| Device reboot | ✅ Yes | BootReceiver restores schedules | +| App restart | ✅ Yes | Schedules persist in database | +| Duplicate prevention | ✅ Yes | State tracking prevents duplicate rollovers | + +### Verification + +You can verify rollover handling by: + +1. **Check iOS logs** for rollover messages: + ``` + DNP-ROLLOVER: START id=... current_time=... scheduled_time=... + DNP-ROLLOVER: CALC_NEXT current=... next=... diff_hours=24.00 + ``` + +2. **Test scenario**: Schedule notification for a time that's already passed today + - Expected: Notification scheduled for tomorrow at same time + +3. **Test scenario**: Wait for notification to fire + - Expected: Next day's notification automatically scheduled + +### Summary + +✅ **Permission Request**: Happens in native plugin code via platform-specific APIs: +- iOS: `UNUserNotificationCenter.requestAuthorization()` +- Android: `ActivityCompat.requestPermissions()` + +✅ **Rollover Handling**: Fully automatic: +- Initial scheduling: If time passed, schedules for tomorrow +- Daily rollover: Automatically schedules next day after notification fires +- DST handling: Calendar-aware calculations +- Duplicate prevention: State tracking prevents issues +- Persistence: Survives app restarts and device reboots + +**No manual intervention needed** - the plugin handles all rollover scenarios automatically! + +--- + +**Last Updated**: 2026-01-23 diff --git a/doc/notification-system-overview.md b/doc/notification-system-overview.md new file mode 100644 index 00000000..a3865a09 --- /dev/null +++ b/doc/notification-system-overview.md @@ -0,0 +1,378 @@ +# Notification System Overview + +**Date**: 2026-01-23 +**Purpose**: Understanding notification architecture and implementation guide for daily-notification-plugin + +--- + +## Executive Summary + +Your app has **two separate notification systems** that coexist: + +1. **Web Push Notifications** (Web/PWA platforms) + - Uses service workers, VAPID keys, and a push server + - Requires the "Notification Push Server" setting + - Server-based delivery + +2. **Native Notifications** (iOS/Android via DailyNotificationPlugin) + - Uses native OS notification APIs + - On-device scheduling (no server needed) + - The "Notification Push Server" setting is **NOT used** for native + +The system automatically selects the correct implementation based on platform using `Capacitor.isNativePlatform()`. + +--- + +## Notification Push Server Setting + +### Location +- **File**: `src/views/AccountViewView.vue` (lines 506-549) +- **UI Section**: Advanced Settings → "Notification Push Server" +- **Database Field**: `settings.webPushServer` + +### Purpose +The "Notification Push Server" setting **ONLY applies to Web Push notifications** (web/PWA platforms). It configures: + +1. **VAPID Key Retrieval**: The server URL used to fetch VAPID (Voluntary Application Server Identification) keys +2. **Subscription Endpoint**: Where push subscriptions are sent +3. **Push Message Delivery**: The server that sends push messages to browsers + +### How It Works (Web Push Flow) + +``` +User enables notification + ↓ +PushNotificationPermission.vue opens + ↓ +Fetches VAPID key from: {webPushServer}/web-push/vapid + ↓ +Subscribes to browser push service + ↓ +Sends subscription + time + message to: {webPushServer}/web-push/subscribe + ↓ +Server stores subscription and schedules push messages + ↓ +Server sends push messages at scheduled time via browser push service +``` + +### Key Code Locations + +**AccountViewView.vue** (lines 1473-1479): +```typescript +async onClickSavePushServer(): Promise { + await this.$saveSettings({ + webPushServer: this.webPushServerInput, + }); + this.webPushServer = this.webPushServerInput; + this.notify.warning(ACCOUNT_VIEW_CONSTANTS.INFO.RELOAD_VAPID); +} +``` + +**PushNotificationPermission.vue** (lines 177-221): +- Retrieves `webPushServer` from settings +- Fetches VAPID key from `{webPushServer}/web-push/vapid` +- Uses VAPID key to subscribe to push notifications + +**PushNotificationPermission.vue** (lines 556-575): +- Sends subscription to `/web-push/subscribe` endpoint (relative URL, handled by service worker) + +### Important Notes + +- ⚠️ **This setting is NOT used for native iOS/Android notifications** +- The setting defaults to `DEFAULT_PUSH_SERVER` if not configured +- Changing the server requires reloading VAPID keys (hence the warning message) +- Local development (`http://localhost`) skips VAPID key retrieval + +--- + +## Daily Notification Plugin Integration + +### Current Status + +✅ **Infrastructure Complete**: +- Plugin registered (`src/plugins/DailyNotificationPlugin.ts`) +- Service abstraction layer created (`src/services/notifications/`) +- Platform detection working +- Native implementation ready (`NativeNotificationService.ts`) + +🔄 **UI Integration Needed**: +- `PushNotificationPermission.vue` still uses web push logic +- AccountViewView notification toggles need platform detection +- Settings storage needs to handle both systems + +### Architecture + +``` +NotificationService.getInstance() + ↓ +Platform Detection (Capacitor.isNativePlatform()) + ↓ +┌─────────────────────┬─────────────────────┐ +│ Native Platform │ Web Platform │ +│ (iOS/Android) │ (Web/PWA) │ +├─────────────────────┼─────────────────────┤ +│ NativeNotification │ WebPushNotification │ +│ Service │ Service │ +│ │ │ +│ Uses: │ Uses: │ +│ - DailyNotification │ - Service Workers │ +│ Plugin │ - VAPID Keys │ +│ - Native OS APIs │ - Push Server │ +│ - On-device alarms │ - Server scheduling │ +└─────────────────────┴─────────────────────┘ +``` + +### Key Differences + +| Feature | Native (Plugin) | Web Push | +|---------|----------------|----------| +| **Server Required** | ❌ No | ✅ Yes (Notification Push Server) | +| **Scheduling** | On-device | Server-side | +| **Offline Delivery** | ✅ Yes | ❌ No (requires network) | +| **Background Support** | ✅ Full | ⚠️ Limited (browser-dependent) | +| **Permission Model** | OS-level | Browser-level | +| **Settings Storage** | Local only | Local + server subscription | + +--- + +## Implementation Recommendations + +### 1. Update PushNotificationPermission Component + +**Current State**: Only handles web push + +**Recommended Changes**: + +```typescript +// In PushNotificationPermission.vue +import { NotificationService } from '@/services/notifications'; +import { Capacitor } from '@capacitor/core'; + +async open(pushType: string, callback?: ...) { + const isNative = Capacitor.isNativePlatform(); + + if (isNative) { + // Use native notification service + const service = NotificationService.getInstance(); + const granted = await service.requestPermissions(); + + if (granted) { + // Show time picker UI + // Then schedule via service.scheduleDailyNotification() + } + } else { + // Existing web push logic + // ... current implementation ... + } +} +``` + +### 2. Update AccountViewView Notification Toggles + +**Current State**: Always uses `PushNotificationPermission` component (web push) + +**Recommended Changes**: + +```typescript +// In AccountViewView.vue +import { NotificationService } from '@/services/notifications'; +import { Capacitor } from '@capacitor/core'; + +async showNewActivityNotificationChoice(): Promise { + const isNative = Capacitor.isNativePlatform(); + + if (isNative) { + // Use native service directly + const service = NotificationService.getInstance(); + // Show time picker, then schedule + } else { + // Use existing PushNotificationPermission component + (this.$refs.pushNotificationPermission as PushNotificationPermission) + .open(DAILY_CHECK_TITLE, ...); + } +} +``` + +### 3. Settings Storage Strategy + +**Current Settings Fields** (from `src/db/tables/settings.ts`): +- `notifyingNewActivityTime` - Time string for daily check +- `notifyingReminderTime` - Time string for reminder +- `notifyingReminderMessage` - Reminder message text +- `webPushServer` - Push server URL (web only) + +**Recommendation**: These settings work for both systems: +- ✅ `notifyingNewActivityTime` - Works for both (native stores locally, web sends to server) +- ✅ `notifyingReminderTime` - Works for both +- ✅ `notifyingReminderMessage` - Works for both +- ⚠️ `webPushServer` - Only used for web push (hide on native platforms) + +### 4. Platform-Aware UI + +**Recommendations**: + +1. **Hide "Notification Push Server" setting on native platforms**: + ```vue +

+ Notification Push Server +

+ ``` + +2. **Update help text** to explain platform differences + +3. **Show different messaging** based on platform: + - Native: "Notifications are scheduled on your device" + - Web: "Notifications are sent via push server" + +--- + +## Notification Types + +Your app supports two notification types: + +### 1. Daily Check (`DAILY_CHECK_TITLE`) +- **Purpose**: Notify user of new activity/updates +- **Message**: Auto-generated by server (web) or app (native) +- **Settings Field**: `notifyingNewActivityTime` + +### 2. Direct Push (`DIRECT_PUSH_TITLE`) +- **Purpose**: Daily reminder with custom message +- **Message**: User-provided (max 100 characters) +- **Settings Fields**: `notifyingReminderTime`, `notifyingReminderMessage` + +Both types can be enabled simultaneously. + +--- + +## Code Flow Examples + +### Native Notification Flow (Recommended Implementation) + +```typescript +// 1. Get service instance +const service = NotificationService.getInstance(); + +// 2. Request permissions +const granted = await service.requestPermissions(); +if (!granted) { + // Show error, guide to settings + return; +} + +// 3. Schedule notification +await service.scheduleDailyNotification({ + time: '09:00', // HH:mm format (24-hour) + title: 'Daily Check-In', + body: 'Time to check your TimeSafari activity', + priority: 'normal' +}); + +// 4. Save to settings +await this.$saveSettings({ + notifyingNewActivityTime: '09:00' +}); + +// 5. Check status +const status = await service.getStatus(); +console.log('Enabled:', status.enabled); +console.log('Time:', status.scheduledTime); +``` + +### Web Push Flow (Current Implementation) + +```typescript +// 1. Open PushNotificationPermission component +(this.$refs.pushNotificationPermission as PushNotificationPermission) + .open(DAILY_CHECK_TITLE, async (success, timeText) => { + if (success) { + // Component handles: + // - VAPID key retrieval from webPushServer + // - Service worker subscription + // - Sending subscription to server + + // Just save the time + await this.$saveSettings({ + notifyingNewActivityTime: timeText + }); + } + }); +``` + +--- + +## Testing Checklist + +### Native (iOS/Android) +- [ ] Request permissions works +- [ ] Notification appears at scheduled time +- [ ] Notification survives app close +- [ ] Notification survives device reboot +- [ ] Both notification types can be enabled +- [ ] Cancellation works correctly + +### Web Push +- [ ] VAPID key retrieval works +- [ ] Service worker subscription works +- [ ] Subscription sent to server +- [ ] Push messages received at scheduled time +- [ ] Works with different push server URLs + +### Platform Detection +- [ ] Correct service selected on iOS +- [ ] Correct service selected on Android +- [ ] Correct service selected on web +- [ ] Settings UI shows/hides appropriately + +--- + +## Key Files Reference + +### Core Notification Services +- `src/services/notifications/NotificationService.ts` - Factory/selector +- `src/services/notifications/NativeNotificationService.ts` - Native implementation +- `src/services/notifications/WebPushNotificationService.ts` - Web implementation (stub) + +### UI Components +- `src/components/PushNotificationPermission.vue` - Web push UI (needs update) +- `src/views/AccountViewView.vue` - Settings UI (lines 506-549 for push server) + +### Settings & Constants +- `src/db/tables/settings.ts` - Settings schema +- `src/constants/app.ts` - `DEFAULT_PUSH_SERVER` constant +- `src/libs/util.ts` - `DAILY_CHECK_TITLE`, `DIRECT_PUSH_TITLE` + +### Plugin +- `src/plugins/DailyNotificationPlugin.ts` - Plugin registration + +--- + +## Next Steps + +1. **Update `PushNotificationPermission.vue`** to detect platform and use appropriate service +2. **Update `AccountViewView.vue`** notification toggles to use platform detection +3. **Hide "Notification Push Server" setting** on native platforms +4. **Test on real devices** (iOS and Android) +5. **Update documentation** with platform-specific instructions + +--- + +## Questions & Answers + +**Q: Do I need to configure the Notification Push Server for native apps?** +A: No. The setting is only for web push. Native notifications are scheduled on-device. + +**Q: Can both notification systems be active at the same time?** +A: No, they're mutually exclusive per platform. The app automatically selects the correct one. + +**Q: How do I test native notifications?** +A: Use `NotificationService.getInstance()` and test on a real device (simulators have limitations). + +**Q: What happens if I change the push server URL?** +A: Only affects web push. Users need to re-subscribe to push notifications with the new server. + +**Q: Can I use the same settings fields for both systems?** +A: Yes! The time and message fields work for both. Only `webPushServer` is web-specific. + +--- + +**Last Updated**: 2026-01-23 diff --git a/src/components/PushNotificationPermission.vue b/src/components/PushNotificationPermission.vue index 37b266a0..0865e95a 100644 --- a/src/components/PushNotificationPermission.vue +++ b/src/components/PushNotificationPermission.vue @@ -95,6 +95,7 @@ diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index 12ff331e..5438304f 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -503,50 +503,53 @@ -

- Notification Push Server -

-
- - - - - + +
+

+ Notification Push Server +

+
+ + + + + +
+ + When that setting is blank, this app will use the default web push + server URL: + {{ DEFAULT_PUSH_SERVER }} +
- - When that setting is blank, this app will use the default web push - server URL: - {{ DEFAULT_PUSH_SERVER }} -

Partner Server URL

@@ -887,6 +890,13 @@ export default class AccountViewView extends Vue { private notify!: ReturnType; + /** + * Check if running on native platform (iOS/Android) + */ + private get isNativePlatform(): boolean { + return Capacitor.isNativePlatform(); + } + created() { this.notify = createNotifyHelpers(this.$notify);