Add native notification support via @timesafari/daily-notification-plugin while maintaining existing Web Push for web/PWA builds. Platform detection automatically selects the appropriate notification system at runtime. Key Changes: - Created NotificationService abstraction layer with unified API - Implemented NativeNotificationService for iOS/Android - Stubbed WebPushNotificationService for future web integration - Registered DailyNotificationPlugin in Capacitor plugin system Android Configuration: - Added notification permissions (POST_NOTIFICATIONS, SCHEDULE_EXACT_ALARM, etc.) - Registered DailyNotificationReceiver for alarm-based notifications - Registered BootReceiver to restore schedules after device restart - Added Room, WorkManager, and Coroutines dependencies - Registered plugin in MainActivity.java iOS Configuration: - Added UIBackgroundModes (fetch, processing) to Info.plist - Configured BGTaskSchedulerPermittedIdentifiers - Added NSUserNotificationAlertStyle Documentation: - Created comprehensive integration guide - Added architecture overview with diagrams - Created implementation checklist - Documented platform-specific behavior Manual Steps Required: - iOS: Enable Background Modes capability in Xcode - iOS: Run `pod install` to install CapacitorDailyNotification pod - Run `npx cap sync` to sync native projects Platform Support: - iOS: Native UNUserNotificationCenter (requires Xcode setup) - Android: Native NotificationManager with AlarmManager - Web/PWA: Existing Web Push (coexists, not yet wired to service) - Electron: Ready (uses native implementation) Status: Phase 1 complete - infrastructure ready for UI integration Next: Update PushNotificationPermission.vue to use NotificationService
9.8 KiB
9.8 KiB
Daily Notification Plugin - Architecture Overview
System Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Vue Components │
│ (PushNotificationPermission.vue, AccountViewView.vue, etc.) │
└───────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ NotificationService (Factory) │
│ - Platform detection via Capacitor API │
│ - Singleton pattern │
│ - Returns appropriate implementation │
└───────────────────────────┬─────────────────────────────────────┘
│
┌───────────┴────────────┐
▼ ▼
┌───────────────────────────┐ ┌────────────────────────────┐
│ NativeNotificationService │ │ WebPushNotificationService │
│ │ │ │
│ iOS/Android │ │ Web/PWA │
│ - UNUserNotificationCenter│ │ - Web Push API │
│ - NotificationManager │ │ - Service Workers │
│ - AlarmManager │ │ - VAPID keys │
│ - Background tasks │ │ - Push server │
└─────────────┬─────────────┘ └────────────┬───────────────┘
│ │
▼ ▼
┌─────────────────────────┐ ┌──────────────────────────────┐
│ DailyNotificationPlugin│ │ Existing Web Push Logic │
│ (Capacitor Plugin) │ │ (PushNotificationPermission)│
│ │ │ │
│ - Native iOS code │ │ - Service worker │
│ - Native Android code │ │ - VAPID subscription │
│ - SQLite storage │ │ - Push server integration │
└─────────────────────────┘ └──────────────────────────────┘
Platform Decision Flow
User Action: Schedule Notification
│
▼
NotificationService.getInstance()
│
├──> Check: Capacitor.isNativePlatform()
│
┌────┴─────┐
│ │
YES NO
│ │
▼ ▼
Native Web/PWA
Service Service
│ │
▼ ▼
Plugin Web Push
Data Flow Example: Scheduling a Notification
Native Platform (iOS/Android)
1. User clicks "Enable Notifications"
│
2. PushNotificationPermission.vue
│
└─> NotificationService.getInstance()
│
└─> Returns NativeNotificationService (detected iOS/Android)
│
└─> nativeService.requestPermissions()
│
└─> DailyNotification.requestPermissions() [Capacitor Plugin]
│
└─> Native code requests OS permissions
│
└─> Returns: { granted: true/false }
3. User sets time & message
│
4. nativeService.scheduleDailyNotification({ time: '09:00', ... })
│
└─> DailyNotification.scheduleDailyReminder({ ... })
│
└─> Native code:
- Stores in SQLite
- Schedules AlarmManager (Android) or UNNotificationRequest (iOS)
- Returns: success/failure
5. At 9:00 AM:
- Android: AlarmManager triggers → DailyNotificationReceiver
- iOS: UNUserNotificationCenter triggers notification
- Notification appears even if app is closed
Web Platform
1. User clicks "Enable Notifications"
│
2. PushNotificationPermission.vue
│
└─> NotificationService.getInstance()
│
└─> Returns WebPushNotificationService (detected web)
│
└─> webService.requestPermissions()
│
└─> Notification.requestPermission() [Browser API]
│
└─> Returns: 'granted'/'denied'/'default'
3. User sets time & message
│
4. webService.scheduleDailyNotification({ ... })
│
└─> [TODO] Subscribe to push service with VAPID
│
└─> Send subscription to server with schedule time
│
└─> Server sends push at scheduled time
│
└─> Service worker receives → shows notification
File Organization
src/
├── plugins/
│ └── DailyNotificationPlugin.ts [Plugin registration]
│
├── services/
│ └── notifications/
│ ├── index.ts [Barrel export]
│ ├── NotificationService.ts [Factory + Interface]
│ ├── NativeNotificationService.ts [iOS/Android impl]
│ └── WebPushNotificationService.ts [Web impl stub]
│
├── components/
│ └── PushNotificationPermission.vue [UI - to be updated]
│
└── views/
└── AccountViewView.vue [Settings UI]
Key Design Decisions
1. Unified Interface
- Single
NotificationServiceInterfacefor all platforms - Consistent API regardless of underlying implementation
- Type-safe across TypeScript codebase
2. Runtime Platform Detection
- No build-time configuration needed
- Same code bundle for all platforms
- Factory pattern selects implementation automatically
3. Coexistence Strategy
- Web Push and Native run on different platforms
- No conflicts - mutually exclusive at runtime
- Allows gradual migration and testing
4. Singleton Pattern
- One service instance per app lifecycle
- Efficient resource usage
- Consistent state management
Permission Flow
Android
App Launch
↓
Check if POST_NOTIFICATIONS granted (API 33+)
│
├─> YES: Ready to schedule
│
└─> NO: Request runtime permission
↓
Show system dialog
↓
User grants/denies
↓
Schedule notifications (if granted)
iOS
App Launch
↓
Check notification authorization status
│
├─> authorized: Ready to schedule
│
├─> notDetermined: Request permission
│ ↓
│ Show system dialog
│ ↓
│ User grants/denies
│
└─> denied: Guide user to Settings
Web
App Load
↓
Check Notification.permission
│
├─> "granted": Ready to subscribe
│
├─> "default": Request permission
│ ↓
│ Show browser prompt
│ ↓
│ User grants/denies
│
└─> "denied": Cannot show notifications
Error Handling Strategy
// All methods return promises with success/failure
try {
const granted = await service.requestPermissions();
if (granted) {
const success = await service.scheduleDailyNotification({...});
if (success) {
// Show success message
} else {
// Show scheduling error
}
} else {
// Show permission denied message
}
} catch (error) {
// Log error and show generic error message
logger.error('Notification error:', error);
}
Background Execution
Native (iOS/Android)
- ✅ Full background support
- ✅ Survives app termination
- ✅ Survives device reboot (with BootReceiver)
- ✅ Exact alarm scheduling
- ✅ Works offline
Web/PWA
- ⚠️ Limited background support
- ⚠️ Requires active service worker
- ⚠️ Browser/OS dependent
- ❌ Needs network for delivery
- ⚠️ iOS: Only on Home Screen PWAs (16.4+)
Storage
Native
DailyNotificationPlugin
↓
SQLite Database (Room/Core Data)
↓
Stores:
- Schedule configurations
- Content cache
- Delivery history
- Callback registrations
Web
Web Push
↓
IndexedDB (via Dexie)
↓
Stores:
- Settings (notifyingNewActivityTime, etc.)
- Push subscription info
- VAPID keys
Testing Strategy
Unit Testing
- Mock
Capacitor.isNativePlatform()to test both paths - Test factory returns correct implementation
- Test each service implementation independently
Integration Testing
- Test on actual devices (iOS/Android)
- Test in browsers (Chrome, Safari, Firefox)
- Verify notification delivery
- Test permission flows
E2E Testing
- Schedule notification → Wait → Verify delivery
- Test app restart scenarios
- Test device reboot scenarios
- Test permission denial recovery
Key Takeaway: The architecture provides a clean separation between platforms while maintaining a unified API for Vue components. Platform detection happens automatically at runtime, and the appropriate notification system is used transparently.