feat: integrate daily notification plugin for native iOS/Android
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
This commit is contained in:
@@ -101,6 +101,13 @@ dependencies {
|
|||||||
implementation project(':capacitor-android')
|
implementation project(':capacitor-android')
|
||||||
implementation project(':capacitor-community-sqlite')
|
implementation project(':capacitor-community-sqlite')
|
||||||
implementation "androidx.biometric:biometric:1.2.0-alpha05"
|
implementation "androidx.biometric:biometric:1.2.0-alpha05"
|
||||||
|
|
||||||
|
// Daily Notification Plugin dependencies
|
||||||
|
implementation "androidx.room:room-runtime:2.6.1"
|
||||||
|
implementation "androidx.work:work-runtime-ktx:2.9.0"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
|
||||||
|
annotationProcessor "androidx.room:room-compiler:2.6.1"
|
||||||
|
|
||||||
testImplementation "junit:junit:$junitVersion"
|
testImplementation "junit:junit:$junitVersion"
|
||||||
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||||
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
||||||
|
|||||||
@@ -52,6 +52,25 @@
|
|||||||
</provider>
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
<!-- Daily Notification Plugin Receivers -->
|
||||||
|
<!-- ⚠️ CRITICAL: NotifyReceiver is required for alarm-based notifications -->
|
||||||
|
<receiver
|
||||||
|
android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false">
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<!-- Boot receiver to restore notification schedules after device restart -->
|
||||||
|
<receiver
|
||||||
|
android:name="com.timesafari.dailynotification.BootReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
</application>
|
||||||
|
|
||||||
<!-- Permissions -->
|
<!-- Permissions -->
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
@@ -59,4 +78,11 @@
|
|||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-feature android:name="android.hardware.camera" android:required="true" />
|
<uses-feature android:name="android.hardware.camera" android:required="true" />
|
||||||
|
|
||||||
|
<!-- Daily Notification Plugin Permissions -->
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
|
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ public class MainActivity extends BridgeActivity {
|
|||||||
// Register SharedImage plugin
|
// Register SharedImage plugin
|
||||||
registerPlugin(SharedImagePlugin.class);
|
registerPlugin(SharedImagePlugin.class);
|
||||||
|
|
||||||
|
// Register DailyNotification plugin
|
||||||
|
registerPlugin(com.timesafari.dailynotification.DailyNotificationPlugin.class);
|
||||||
|
|
||||||
// Initialize SQLite
|
// Initialize SQLite
|
||||||
//registerPlugin(SQLite.class);
|
//registerPlugin(SQLite.class);
|
||||||
|
|
||||||
|
|||||||
312
doc/daily-notification-plugin-architecture.md
Normal file
312
doc/daily-notification-plugin-architecture.md
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
# 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 `NotificationServiceInterface` for 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
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 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.
|
||||||
341
doc/daily-notification-plugin-checklist.md
Normal file
341
doc/daily-notification-plugin-checklist.md
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
# Daily Notification Plugin - Integration Checklist
|
||||||
|
|
||||||
|
**Integration Date**: 2026-01-21
|
||||||
|
**Plugin Version**: 1.0.11
|
||||||
|
**Status**: Phase 1 Complete ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Infrastructure Setup ✅ COMPLETE
|
||||||
|
|
||||||
|
### Code Files
|
||||||
|
- [x] Created `src/plugins/DailyNotificationPlugin.ts`
|
||||||
|
- [x] Created `src/services/notifications/NotificationService.ts`
|
||||||
|
- [x] Created `src/services/notifications/NativeNotificationService.ts`
|
||||||
|
- [x] Created `src/services/notifications/WebPushNotificationService.ts`
|
||||||
|
- [x] Created `src/services/notifications/index.ts`
|
||||||
|
|
||||||
|
### Android Configuration
|
||||||
|
- [x] Added permissions to `AndroidManifest.xml`:
|
||||||
|
- [x] `POST_NOTIFICATIONS`
|
||||||
|
- [x] `SCHEDULE_EXACT_ALARM`
|
||||||
|
- [x] `USE_EXACT_ALARM`
|
||||||
|
- [x] `RECEIVE_BOOT_COMPLETED`
|
||||||
|
- [x] `WAKE_LOCK`
|
||||||
|
- [x] Registered receivers in `AndroidManifest.xml`:
|
||||||
|
- [x] `DailyNotificationReceiver`
|
||||||
|
- [x] `BootReceiver`
|
||||||
|
- [x] Added dependencies to `build.gradle`:
|
||||||
|
- [x] Room (`androidx.room:room-runtime:2.6.1`)
|
||||||
|
- [x] WorkManager (`androidx.work:work-runtime-ktx:2.9.0`)
|
||||||
|
- [x] Coroutines (`org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3`)
|
||||||
|
- [x] Room Compiler (`androidx.room:room-compiler:2.6.1`)
|
||||||
|
- [x] Registered plugin in `MainActivity.java`
|
||||||
|
|
||||||
|
### iOS Configuration
|
||||||
|
- [x] Added to `Info.plist`:
|
||||||
|
- [x] `UIBackgroundModes` (fetch, processing)
|
||||||
|
- [x] `BGTaskSchedulerPermittedIdentifiers`
|
||||||
|
- [x] `NSUserNotificationAlertStyle`
|
||||||
|
- [ ] ⚠️ **MANUAL STEP**: Xcode capabilities (see Phase 5)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- [x] Created `doc/daily-notification-plugin-integration.md`
|
||||||
|
- [x] Created `doc/daily-notification-plugin-integration-summary.md`
|
||||||
|
- [x] Created `doc/daily-notification-plugin-architecture.md`
|
||||||
|
- [x] Created this checklist
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: UI Integration ⏳ TODO
|
||||||
|
|
||||||
|
### Update Components
|
||||||
|
- [ ] Modify `PushNotificationPermission.vue`:
|
||||||
|
- [ ] Import `NotificationService`
|
||||||
|
- [ ] Replace direct web push calls with service methods
|
||||||
|
- [ ] Add platform-aware messaging
|
||||||
|
- [ ] Test permission flow
|
||||||
|
- [ ] Test notification scheduling
|
||||||
|
|
||||||
|
### Update Views
|
||||||
|
- [ ] Update `AccountViewView.vue`:
|
||||||
|
- [ ] Use `NotificationService` for status checks
|
||||||
|
- [ ] Add platform indicator
|
||||||
|
- [ ] Test settings display
|
||||||
|
|
||||||
|
### Settings Integration
|
||||||
|
- [ ] Verify settings save/load correctly:
|
||||||
|
- [ ] `notifyingNewActivityTime` for native
|
||||||
|
- [ ] `notifyingReminderMessage` for native
|
||||||
|
- [ ] `notifyingReminderTime` for native
|
||||||
|
- [ ] Existing web push settings preserved
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Web Push Integration ⏳ TODO
|
||||||
|
|
||||||
|
### Wire WebPushNotificationService
|
||||||
|
- [ ] Extract subscription logic from `PushNotificationPermission.vue`
|
||||||
|
- [ ] Implement `scheduleDailyNotification()` method
|
||||||
|
- [ ] Implement `cancelDailyNotification()` method
|
||||||
|
- [ ] Implement `getStatus()` method
|
||||||
|
- [ ] Test web platform notification flow
|
||||||
|
|
||||||
|
### Server Integration
|
||||||
|
- [ ] Verify web push server endpoints still work
|
||||||
|
- [ ] Test subscription/unsubscription
|
||||||
|
- [ ] Test scheduled message delivery
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Testing ⏳ TODO
|
||||||
|
|
||||||
|
### Desktop Development
|
||||||
|
- [ ] Code compiles without errors
|
||||||
|
- [ ] ESLint passes
|
||||||
|
- [ ] TypeScript types are correct
|
||||||
|
- [ ] Platform detection works in browser console
|
||||||
|
|
||||||
|
### Android Emulator
|
||||||
|
- [ ] App builds successfully
|
||||||
|
- [ ] Plugin loads without errors
|
||||||
|
- [ ] Can open app and navigate
|
||||||
|
- [ ] No JavaScript console errors
|
||||||
|
|
||||||
|
### Android Device (Real)
|
||||||
|
- [ ] Request permissions dialog appears
|
||||||
|
- [ ] Permissions can be granted
|
||||||
|
- [ ] Schedule notification succeeds
|
||||||
|
- [ ] Notification appears at scheduled time
|
||||||
|
- [ ] Notification survives app close
|
||||||
|
- [ ] Notification survives device reboot
|
||||||
|
- [ ] Notification can be cancelled
|
||||||
|
|
||||||
|
### iOS Simulator
|
||||||
|
- [ ] App builds successfully
|
||||||
|
- [ ] Plugin loads without errors
|
||||||
|
- [ ] Can open app and navigate
|
||||||
|
- [ ] No JavaScript console errors
|
||||||
|
|
||||||
|
### iOS Device (Real)
|
||||||
|
- [ ] Request permissions dialog appears
|
||||||
|
- [ ] Permissions can be granted
|
||||||
|
- [ ] Schedule notification succeeds
|
||||||
|
- [ ] Notification appears at scheduled time
|
||||||
|
- [ ] Background fetch works
|
||||||
|
- [ ] Notification survives app close
|
||||||
|
- [ ] Notification can be cancelled
|
||||||
|
|
||||||
|
### Web Browser
|
||||||
|
- [ ] Existing web push still works
|
||||||
|
- [ ] No JavaScript errors
|
||||||
|
- [ ] Platform detection selects web service
|
||||||
|
- [ ] Permission flow works
|
||||||
|
- [ ] Subscription works
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: iOS Xcode Setup ⚠️ MANUAL REQUIRED
|
||||||
|
|
||||||
|
### Open Xcode Project
|
||||||
|
```bash
|
||||||
|
cd ios
|
||||||
|
open App/App.xcodeproj
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configure Capabilities
|
||||||
|
- [ ] Select "App" target in project navigator
|
||||||
|
- [ ] Go to "Signing & Capabilities" tab
|
||||||
|
- [ ] Click "+ Capability" button
|
||||||
|
- [ ] Add "Background Modes":
|
||||||
|
- [ ] Enable "Background fetch"
|
||||||
|
- [ ] Enable "Background processing"
|
||||||
|
- [ ] Click "+ Capability" button again
|
||||||
|
- [ ] Add "Push Notifications" (if using remote notifications)
|
||||||
|
|
||||||
|
### Install CocoaPods
|
||||||
|
```bash
|
||||||
|
cd ios
|
||||||
|
pod install
|
||||||
|
cd ..
|
||||||
|
```
|
||||||
|
- [ ] Run `pod install` successfully
|
||||||
|
- [ ] Verify `CapacitorDailyNotification` pod is installed
|
||||||
|
|
||||||
|
### Verify Configuration
|
||||||
|
- [ ] Build succeeds in Xcode
|
||||||
|
- [ ] No capability warnings
|
||||||
|
- [ ] No pod errors
|
||||||
|
- [ ] Can run on simulator
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: Build & Deploy ⏳ TODO
|
||||||
|
|
||||||
|
### Sync Capacitor
|
||||||
|
```bash
|
||||||
|
npx cap sync
|
||||||
|
```
|
||||||
|
- [ ] Sync completes without errors
|
||||||
|
- [ ] Plugin files copied to native projects
|
||||||
|
|
||||||
|
### Build Android
|
||||||
|
```bash
|
||||||
|
npm run build:android:debug
|
||||||
|
```
|
||||||
|
- [ ] Build succeeds
|
||||||
|
- [ ] APK/AAB generated
|
||||||
|
- [ ] Can install on device/emulator
|
||||||
|
|
||||||
|
### Build iOS
|
||||||
|
```bash
|
||||||
|
npm run build:ios:debug
|
||||||
|
```
|
||||||
|
- [ ] Build succeeds
|
||||||
|
- [ ] IPA generated (if release)
|
||||||
|
- [ ] Can install on device/simulator
|
||||||
|
|
||||||
|
### Test Production Builds
|
||||||
|
- [ ] Android release build works
|
||||||
|
- [ ] iOS release build works
|
||||||
|
- [ ] Notifications work in production
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting Checklist
|
||||||
|
|
||||||
|
### Android Issues
|
||||||
|
|
||||||
|
#### Notifications Not Appearing
|
||||||
|
- [ ] Verified `DailyNotificationReceiver` is in AndroidManifest.xml
|
||||||
|
- [ ] Checked logcat for errors: `adb logcat | grep DailyNotification`
|
||||||
|
- [ ] Verified permissions granted in app settings
|
||||||
|
- [ ] Checked "Exact alarms" permission (Android 12+)
|
||||||
|
- [ ] Verified notification channel is created
|
||||||
|
|
||||||
|
#### Build Errors
|
||||||
|
- [ ] Verified all dependencies in build.gradle
|
||||||
|
- [ ] Ran `./gradlew clean` and rebuilt
|
||||||
|
- [ ] Verified Kotlin version compatibility
|
||||||
|
- [ ] Checked for conflicting dependencies
|
||||||
|
|
||||||
|
### iOS Issues
|
||||||
|
|
||||||
|
#### Notifications Not Appearing
|
||||||
|
- [ ] Verified Background Modes enabled in Xcode
|
||||||
|
- [ ] Checked Xcode console for errors
|
||||||
|
- [ ] Verified permissions granted in Settings app
|
||||||
|
- [ ] Tested on real device (not just simulator)
|
||||||
|
- [ ] Checked BGTaskScheduler identifiers match Info.plist
|
||||||
|
|
||||||
|
#### Build Errors
|
||||||
|
- [ ] Ran `pod install` successfully
|
||||||
|
- [ ] Verified deployment target is iOS 13.0+
|
||||||
|
- [ ] Checked for pod conflicts
|
||||||
|
- [ ] Cleaned build folder (Xcode → Product → Clean Build Folder)
|
||||||
|
|
||||||
|
### Web Issues
|
||||||
|
|
||||||
|
#### Web Push Not Working
|
||||||
|
- [ ] Verified service worker is registered
|
||||||
|
- [ ] Checked browser console for errors
|
||||||
|
- [ ] Verified VAPID keys are correct
|
||||||
|
- [ ] Tested in supported browser (Chrome 42+, Firefox)
|
||||||
|
- [ ] Checked push server is running
|
||||||
|
|
||||||
|
#### Permission Issues
|
||||||
|
- [ ] Verified permissions not blocked in browser
|
||||||
|
- [ ] Checked site settings in browser
|
||||||
|
- [ ] Verified HTTPS connection (required for web push)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification Commands
|
||||||
|
|
||||||
|
### Check Plugin is Installed
|
||||||
|
```bash
|
||||||
|
npm list @timesafari/daily-notification-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Capacitor Sync
|
||||||
|
```bash
|
||||||
|
npx cap ls
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Android Build
|
||||||
|
```bash
|
||||||
|
cd android
|
||||||
|
./gradlew clean
|
||||||
|
./gradlew assembleDebug
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check iOS Build
|
||||||
|
```bash
|
||||||
|
cd ios
|
||||||
|
pod install
|
||||||
|
xcodebuild -workspace App/App.xcworkspace -scheme App -configuration Debug build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check TypeScript
|
||||||
|
```bash
|
||||||
|
npm run type-check
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Linting
|
||||||
|
```bash
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Immediate Actions
|
||||||
|
|
||||||
|
1. **Run Capacitor Sync**:
|
||||||
|
```bash
|
||||||
|
npx cap sync
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **For iOS Development**:
|
||||||
|
```bash
|
||||||
|
cd ios
|
||||||
|
open App/App.xcodeproj
|
||||||
|
# Enable Background Modes capability
|
||||||
|
pod install
|
||||||
|
cd ..
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Test on Emulator/Simulator**:
|
||||||
|
```bash
|
||||||
|
npm run build:android:debug # For Android
|
||||||
|
npm run build:ios:debug # For iOS
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Update UI Components**:
|
||||||
|
- Start with `PushNotificationPermission.vue`
|
||||||
|
- Import and use `NotificationService`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
- [x] **Phase 1**: All files created and configurations applied
|
||||||
|
- [ ] **Phase 2**: Components use NotificationService
|
||||||
|
- [ ] **Phase 3**: Web push integrated with service
|
||||||
|
- [ ] **Phase 4**: All tests pass on all platforms
|
||||||
|
- [ ] **Phase 5**: iOS capabilities configured in Xcode
|
||||||
|
- [ ] **Phase 6**: Production builds work on real devices
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Questions or Issues?
|
||||||
|
|
||||||
|
See documentation:
|
||||||
|
- Full guide: `doc/daily-notification-plugin-integration.md`
|
||||||
|
- Architecture: `doc/daily-notification-plugin-architecture.md`
|
||||||
|
- Summary: `doc/daily-notification-plugin-integration-summary.md`
|
||||||
|
|
||||||
|
Plugin docs: `node_modules/@timesafari/daily-notification-plugin/README.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Current Status**: Ready for Phase 2 (UI Integration) 🚀
|
||||||
193
doc/daily-notification-plugin-integration-summary.md
Normal file
193
doc/daily-notification-plugin-integration-summary.md
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
# Daily Notification Plugin Integration - Summary
|
||||||
|
|
||||||
|
**Date**: 2026-01-21
|
||||||
|
**Status**: ✅ Phase 1 Complete
|
||||||
|
**Next Phase**: UI Integration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Was Completed
|
||||||
|
|
||||||
|
### ✅ Plugin Infrastructure
|
||||||
|
1. **Plugin Registration**: `src/plugins/DailyNotificationPlugin.ts`
|
||||||
|
- Capacitor plugin registered with full TypeScript types
|
||||||
|
- Native-only (iOS/Android)
|
||||||
|
|
||||||
|
2. **Service Abstraction**: `src/services/notifications/`
|
||||||
|
- `NotificationService.ts` - Platform detection & factory
|
||||||
|
- `NativeNotificationService.ts` - Native implementation
|
||||||
|
- `WebPushNotificationService.ts` - Web stub (for future)
|
||||||
|
- `index.ts` - Barrel export
|
||||||
|
|
||||||
|
3. **Android Configuration**:
|
||||||
|
- ✅ Permissions added to `AndroidManifest.xml`
|
||||||
|
- ✅ Receivers registered (DailyNotificationReceiver, BootReceiver)
|
||||||
|
- ✅ Dependencies added to `build.gradle` (Room, WorkManager, Coroutines)
|
||||||
|
- ✅ Plugin registered in `MainActivity.java`
|
||||||
|
|
||||||
|
4. **iOS Configuration**:
|
||||||
|
- ✅ Background modes added to `Info.plist`
|
||||||
|
- ✅ BGTaskScheduler identifiers configured
|
||||||
|
- ⚠️ **Requires manual Xcode setup** (capabilities)
|
||||||
|
|
||||||
|
5. **Documentation**: `doc/daily-notification-plugin-integration.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Platform Support
|
||||||
|
|
||||||
|
| Platform | Notification System | Status |
|
||||||
|
|----------|---------------------|--------|
|
||||||
|
| **iOS** | Native (UNUserNotificationCenter) | ✅ Configured |
|
||||||
|
| **Android** | Native (NotificationManager + AlarmManager) | ✅ Configured |
|
||||||
|
| **Web/PWA** | Web Push (existing) | 🔄 Coexists, not yet wired |
|
||||||
|
| **Electron** | Native (via Capacitor) | ✅ Ready |
|
||||||
|
|
||||||
|
**Key Feature**: Both systems coexist using runtime platform detection.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start Usage
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { NotificationService } from '@/services/notifications';
|
||||||
|
|
||||||
|
// Automatically uses native on iOS/Android, web push on web
|
||||||
|
const service = NotificationService.getInstance();
|
||||||
|
|
||||||
|
// Request permissions
|
||||||
|
const granted = await service.requestPermissions();
|
||||||
|
|
||||||
|
if (granted) {
|
||||||
|
// Schedule daily notification at 9 AM
|
||||||
|
await service.scheduleDailyNotification({
|
||||||
|
time: '09:00',
|
||||||
|
title: 'Daily Check-In',
|
||||||
|
body: 'Time to check your TimeSafari activity'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check status
|
||||||
|
const status = await service.getStatus();
|
||||||
|
console.log('Notifications enabled:', status.enabled);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
### Immediate (Phase 2)
|
||||||
|
1. **Update UI Components**:
|
||||||
|
- Modify `PushNotificationPermission.vue` to use `NotificationService`
|
||||||
|
- Add platform-aware messaging
|
||||||
|
- Test on simulator/emulator
|
||||||
|
|
||||||
|
2. **iOS Xcode Setup** (Required):
|
||||||
|
```bash
|
||||||
|
cd ios
|
||||||
|
open App/App.xcodeproj
|
||||||
|
```
|
||||||
|
- Enable "Background Modes" capability
|
||||||
|
- Enable "Push Notifications" capability
|
||||||
|
- Run `pod install`
|
||||||
|
|
||||||
|
### Short-term (Phase 3)
|
||||||
|
3. **Wire Web Push**: Connect `WebPushNotificationService` to existing web push logic
|
||||||
|
4. **Test on Devices**: Real iOS and Android devices
|
||||||
|
5. **Update Settings**: Ensure notification preferences save correctly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build & Sync
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Sync native projects with web code
|
||||||
|
npx cap sync
|
||||||
|
|
||||||
|
# Build for Android
|
||||||
|
npm run build:android:debug
|
||||||
|
|
||||||
|
# Build for iOS (after Xcode setup)
|
||||||
|
cd ios && pod install && cd ..
|
||||||
|
npm run build:ios:debug
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
### ⚠️ Critical Requirements
|
||||||
|
|
||||||
|
**Android**:
|
||||||
|
- `DailyNotificationReceiver` must be in AndroidManifest.xml (✅ done)
|
||||||
|
- Runtime permissions needed for Android 13+ (API 33+)
|
||||||
|
- Exact alarm permission for Android 12+ (API 31+)
|
||||||
|
|
||||||
|
**iOS**:
|
||||||
|
- Background Modes capability must be enabled in Xcode (⚠️ manual)
|
||||||
|
- BGTaskScheduler identifiers must match Info.plist (✅ done)
|
||||||
|
- Test on real device (simulators have limitations)
|
||||||
|
|
||||||
|
**Web**:
|
||||||
|
- Existing Web Push continues to work unchanged
|
||||||
|
- No conflicts - platform detection ensures correct system
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
### Created (8 files)
|
||||||
|
- `src/plugins/DailyNotificationPlugin.ts`
|
||||||
|
- `src/services/notifications/NotificationService.ts`
|
||||||
|
- `src/services/notifications/NativeNotificationService.ts`
|
||||||
|
- `src/services/notifications/WebPushNotificationService.ts`
|
||||||
|
- `src/services/notifications/index.ts`
|
||||||
|
- `doc/daily-notification-plugin-integration.md`
|
||||||
|
- `doc/daily-notification-plugin-integration-summary.md`
|
||||||
|
|
||||||
|
### Modified (4 files)
|
||||||
|
- `android/app/src/main/AndroidManifest.xml` - Permissions + Receivers
|
||||||
|
- `android/app/build.gradle` - Dependencies
|
||||||
|
- `android/app/src/main/java/app/timesafari/MainActivity.java` - Plugin registration
|
||||||
|
- `ios/App/App/Info.plist` - Background modes + BGTaskScheduler
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
### Before Device Testing
|
||||||
|
- [ ] Code compiles without errors
|
||||||
|
- [ ] Platform detection logic verified
|
||||||
|
- [ ] Service factory creates correct implementation
|
||||||
|
|
||||||
|
### Android Device
|
||||||
|
- [ ] Request permissions (Android 13+)
|
||||||
|
- [ ] Schedule notification
|
||||||
|
- [ ] Notification appears at scheduled time
|
||||||
|
- [ ] Notification survives app close
|
||||||
|
- [ ] Notification survives device reboot
|
||||||
|
|
||||||
|
### iOS Device
|
||||||
|
- [ ] Xcode capabilities enabled
|
||||||
|
- [ ] Request permissions
|
||||||
|
- [ ] Schedule notification
|
||||||
|
- [ ] Notification appears at scheduled time
|
||||||
|
- [ ] Background fetch works
|
||||||
|
- [ ] Notification survives app close
|
||||||
|
|
||||||
|
### Web/PWA
|
||||||
|
- [ ] Existing web push still works
|
||||||
|
- [ ] No errors in console
|
||||||
|
- [ ] Platform detection selects web implementation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
See full documentation: `doc/daily-notification-plugin-integration.md`
|
||||||
|
|
||||||
|
Plugin README: `node_modules/@timesafari/daily-notification-plugin/README.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Status**: Ready for Phase 2 (UI Integration) 🚀
|
||||||
237
doc/daily-notification-plugin-integration.md
Normal file
237
doc/daily-notification-plugin-integration.md
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
# Daily Notification Plugin Integration
|
||||||
|
|
||||||
|
**Date**: 2026-01-21
|
||||||
|
**Status**: ✅ Phase 1 Complete - Native Infrastructure
|
||||||
|
**Integration Type**: Native + Web Coexistence
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Daily Notification Plugin has been integrated to provide native notification functionality for iOS and Android while maintaining existing Web Push for web/PWA builds. The integration uses platform detection to automatically select the appropriate notification system at runtime.
|
||||||
|
|
||||||
|
## What Was Implemented
|
||||||
|
|
||||||
|
### 1. **Plugin Registration** ✅
|
||||||
|
- **File**: `src/plugins/DailyNotificationPlugin.ts`
|
||||||
|
- Registered Capacitor plugin with proper TypeScript types
|
||||||
|
- Native-only (no web implementation)
|
||||||
|
|
||||||
|
### 2. **Service Abstraction Layer** ✅
|
||||||
|
Created unified notification service with platform-specific implementations:
|
||||||
|
|
||||||
|
- **`NotificationService.ts`**: Factory that selects implementation based on platform
|
||||||
|
- **`NativeNotificationService.ts`**: Wraps DailyNotificationPlugin for iOS/Android
|
||||||
|
- **`WebPushNotificationService.ts`**: Stub for future Web Push integration
|
||||||
|
|
||||||
|
**Location**: `src/services/notifications/`
|
||||||
|
|
||||||
|
**Key Features**:
|
||||||
|
- Unified interface (`NotificationServiceInterface`)
|
||||||
|
- Automatic platform detection via `Capacitor.isNativePlatform()`
|
||||||
|
- Type-safe implementation
|
||||||
|
- Singleton pattern for efficiency
|
||||||
|
|
||||||
|
### 3. **Android Configuration** ✅
|
||||||
|
|
||||||
|
**Modified Files**:
|
||||||
|
- `android/app/src/main/AndroidManifest.xml`
|
||||||
|
- `android/app/build.gradle`
|
||||||
|
- `android/app/src/main/java/app/timesafari/MainActivity.java`
|
||||||
|
|
||||||
|
**Changes**:
|
||||||
|
- ✅ Added notification permissions (POST_NOTIFICATIONS, SCHEDULE_EXACT_ALARM, etc.)
|
||||||
|
- ✅ Registered `DailyNotificationReceiver` (critical for alarm delivery)
|
||||||
|
- ✅ Registered `BootReceiver` (restores schedules after device restart)
|
||||||
|
- ✅ Added Room, WorkManager, and Coroutines dependencies
|
||||||
|
- ✅ Registered plugin in MainActivity
|
||||||
|
|
||||||
|
### 4. **iOS Configuration** ✅
|
||||||
|
|
||||||
|
**Modified Files**:
|
||||||
|
- `ios/App/App/Info.plist`
|
||||||
|
|
||||||
|
**Changes**:
|
||||||
|
- ✅ Added `UIBackgroundModes` (fetch, processing)
|
||||||
|
- ✅ Added `BGTaskSchedulerPermittedIdentifiers` for background tasks
|
||||||
|
- ✅ Added `NSUserNotificationAlertStyle` for alert-style notifications
|
||||||
|
|
||||||
|
**Still Required** (Manual in Xcode):
|
||||||
|
- ⚠️ Enable "Background Modes" capability in Xcode
|
||||||
|
- Background fetch
|
||||||
|
- Background processing
|
||||||
|
- ⚠️ Enable "Push Notifications" capability (if using remote notifications)
|
||||||
|
|
||||||
|
## Platform Behavior
|
||||||
|
|
||||||
|
| Platform | Implementation | Status |
|
||||||
|
|----------|---------------|--------|
|
||||||
|
| **iOS** | DailyNotificationPlugin (native) | ✅ Configured |
|
||||||
|
| **Android** | DailyNotificationPlugin (native) | ✅ Configured |
|
||||||
|
| **Web/PWA** | Web Push (existing) | 🔄 Not yet wired up |
|
||||||
|
| **Electron** | Would use native | ✅ Ready |
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { NotificationService } from '@/services/notifications/NotificationService';
|
||||||
|
|
||||||
|
// Get the appropriate service for current platform
|
||||||
|
const notificationService = NotificationService.getInstance();
|
||||||
|
|
||||||
|
// Check platform
|
||||||
|
console.log('Platform:', NotificationService.getPlatform());
|
||||||
|
console.log('Is native:', NotificationService.isNative());
|
||||||
|
|
||||||
|
// Request permissions
|
||||||
|
const granted = await notificationService.requestPermissions();
|
||||||
|
|
||||||
|
if (granted) {
|
||||||
|
// Schedule daily notification
|
||||||
|
await notificationService.scheduleDailyNotification({
|
||||||
|
time: '09:00',
|
||||||
|
title: 'Daily Check-In',
|
||||||
|
body: 'Time to check your TimeSafari activity',
|
||||||
|
priority: 'normal'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check status
|
||||||
|
const status = await notificationService.getStatus();
|
||||||
|
console.log('Enabled:', status.enabled);
|
||||||
|
console.log('Time:', status.scheduledTime);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
### Phase 2: UI Integration
|
||||||
|
- [ ] Update `PushNotificationPermission.vue` to use `NotificationService`
|
||||||
|
- [ ] Add platform-aware UI messaging
|
||||||
|
- [ ] Update settings storage to work with both systems
|
||||||
|
- [ ] Test notification scheduling UI
|
||||||
|
|
||||||
|
### Phase 3: Web Push Integration
|
||||||
|
- [ ] Wire `WebPushNotificationService` to existing PushNotificationPermission logic
|
||||||
|
- [ ] Extract web push subscription code into service methods
|
||||||
|
- [ ] Test web platform notification flow
|
||||||
|
|
||||||
|
### Phase 4: Testing & Polish
|
||||||
|
- [ ] Test on real iOS device
|
||||||
|
- [ ] Test on real Android device (API 23+, API 33+)
|
||||||
|
- [ ] Test permission flows
|
||||||
|
- [ ] Test notification delivery
|
||||||
|
- [ ] Test app restart/reboot scenarios
|
||||||
|
- [ ] Verify background notification delivery
|
||||||
|
|
||||||
|
### Phase 5: Xcode Configuration (iOS Only)
|
||||||
|
- [ ] Open `ios/App/App.xcodeproj` in Xcode
|
||||||
|
- [ ] Select App target → Signing & Capabilities
|
||||||
|
- [ ] Click "+ Capability" → Add "Background Modes"
|
||||||
|
- Enable "Background fetch"
|
||||||
|
- Enable "Background processing"
|
||||||
|
- [ ] Click "+ Capability" → Add "Push Notifications" (if using remote)
|
||||||
|
- [ ] Run `pod install` in `ios/` directory
|
||||||
|
- [ ] Build and test on device
|
||||||
|
|
||||||
|
## Build Commands
|
||||||
|
|
||||||
|
### Sync Capacitor
|
||||||
|
```bash
|
||||||
|
npx cap sync
|
||||||
|
# or
|
||||||
|
npx cap sync android
|
||||||
|
npx cap sync ios
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Android
|
||||||
|
```bash
|
||||||
|
npm run build:android
|
||||||
|
# or
|
||||||
|
npm run build:android:debug
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build iOS
|
||||||
|
```bash
|
||||||
|
npm run build:ios
|
||||||
|
# or after Xcode setup:
|
||||||
|
cd ios && pod install && cd ..
|
||||||
|
npm run build:ios:debug
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
### Android
|
||||||
|
- **Critical**: `DailyNotificationReceiver` must be in AndroidManifest.xml
|
||||||
|
- Android 12+ (API 31+) requires `SCHEDULE_EXACT_ALARM` permission
|
||||||
|
- Android 13+ (API 33+) requires runtime `POST_NOTIFICATIONS` permission
|
||||||
|
- BootReceiver restores schedules after device restart
|
||||||
|
|
||||||
|
### iOS
|
||||||
|
- **Critical**: Background modes must be enabled in Xcode capabilities
|
||||||
|
- iOS 13.0+ supported (already compatible with your deployment target)
|
||||||
|
- Background tasks use `BGTaskScheduler`
|
||||||
|
- User must grant notification permissions in Settings
|
||||||
|
|
||||||
|
### Web
|
||||||
|
- Existing Web Push continues to work
|
||||||
|
- No conflicts with native implementation
|
||||||
|
- Platform detection ensures correct system is used
|
||||||
|
|
||||||
|
## Files Modified
|
||||||
|
|
||||||
|
### Created
|
||||||
|
- `src/plugins/DailyNotificationPlugin.ts`
|
||||||
|
- `src/services/notifications/NotificationService.ts`
|
||||||
|
- `src/services/notifications/NativeNotificationService.ts`
|
||||||
|
- `src/services/notifications/WebPushNotificationService.ts`
|
||||||
|
|
||||||
|
### Modified
|
||||||
|
- `android/app/src/main/AndroidManifest.xml`
|
||||||
|
- `android/app/build.gradle`
|
||||||
|
- `android/app/src/main/java/app/timesafari/MainActivity.java`
|
||||||
|
- `ios/App/App/Info.plist`
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Android: Notifications Not Appearing
|
||||||
|
1. Check that `DailyNotificationReceiver` is registered in AndroidManifest.xml
|
||||||
|
2. Verify permissions are requested at runtime (Android 13+)
|
||||||
|
3. Check that notification channel is created
|
||||||
|
4. Enable "Exact alarms" in app settings (Android 12+)
|
||||||
|
|
||||||
|
### iOS: Background Tasks Not Running
|
||||||
|
1. Ensure Background Modes capability is enabled in Xcode
|
||||||
|
2. Check that BGTaskScheduler identifiers match Info.plist
|
||||||
|
3. Test on real device (simulator has limitations)
|
||||||
|
4. Check iOS Settings → Notifications → TimeSafari
|
||||||
|
|
||||||
|
### Permission Issues
|
||||||
|
1. Request permissions before scheduling: `requestPermissions()`
|
||||||
|
2. Check permission status: `checkPermissions()`
|
||||||
|
3. Guide users to system settings if denied
|
||||||
|
|
||||||
|
## Plugin Documentation
|
||||||
|
|
||||||
|
For complete plugin documentation, see:
|
||||||
|
- Plugin README: `node_modules/@timesafari/daily-notification-plugin/README.md`
|
||||||
|
- Plugin version: 1.0.11
|
||||||
|
- Repository: https://gitea.anomalistdesign.com/trent_larson/daily-notification-plugin
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
- [ ] Android: Notification appears at scheduled time
|
||||||
|
- [ ] Android: Notification survives app close
|
||||||
|
- [ ] Android: Notification survives device reboot
|
||||||
|
- [ ] iOS: Notification appears at scheduled time
|
||||||
|
- [ ] iOS: Background fetch works
|
||||||
|
- [ ] iOS: Notification survives app close
|
||||||
|
- [ ] Web: Existing web push still works
|
||||||
|
- [ ] Platform detection works correctly
|
||||||
|
- [ ] Permission requests work on all platforms
|
||||||
|
- [ ] Status retrieval works correctly
|
||||||
|
|
||||||
|
## Current Status
|
||||||
|
|
||||||
|
✅ **Phase 1 Complete**: Native infrastructure configured
|
||||||
|
🔄 **Phase 2 In Progress**: Ready for UI integration
|
||||||
|
⏳ **Phase 3 Pending**: Web Push service integration
|
||||||
|
⏳ **Phase 4 Pending**: Testing and validation
|
||||||
|
⏳ **Phase 5 Pending**: Xcode capabilities setup
|
||||||
@@ -58,5 +58,17 @@
|
|||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
|
<key>UIBackgroundModes</key>
|
||||||
|
<array>
|
||||||
|
<string>fetch</string>
|
||||||
|
<string>processing</string>
|
||||||
|
</array>
|
||||||
|
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||||
|
<array>
|
||||||
|
<string>com.timesafari.dailynotification.content-fetch</string>
|
||||||
|
<string>com.timesafari.dailynotification.notification-delivery</string>
|
||||||
|
</array>
|
||||||
|
<key>NSUserNotificationAlertStyle</key>
|
||||||
|
<string>alert</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
38
src/plugins/DailyNotificationPlugin.ts
Normal file
38
src/plugins/DailyNotificationPlugin.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Daily Notification Capacitor Plugin
|
||||||
|
* Provides native notification functionality for iOS and Android
|
||||||
|
*
|
||||||
|
* @see https://gitea.anomalistdesign.com/trent_larson/daily-notification-plugin
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { registerPlugin } from "@capacitor/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type definitions for the Daily Notification Plugin
|
||||||
|
* Re-exported from the plugin package
|
||||||
|
*/
|
||||||
|
export type {
|
||||||
|
DailyNotificationPlugin,
|
||||||
|
DailyReminderOptions,
|
||||||
|
DailyReminderInfo,
|
||||||
|
PermissionStatusResult,
|
||||||
|
NotificationStatus,
|
||||||
|
} from "@timesafari/daily-notification-plugin";
|
||||||
|
|
||||||
|
// Import the plugin type
|
||||||
|
import type { DailyNotificationPlugin as DailyNotificationPluginType } from "@timesafari/daily-notification-plugin";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the Daily Notification plugin
|
||||||
|
* This plugin is only available on native platforms (iOS/Android)
|
||||||
|
* No web implementation - use Web Push for web/PWA builds
|
||||||
|
*/
|
||||||
|
const DailyNotification = registerPlugin<DailyNotificationPluginType>(
|
||||||
|
"DailyNotification",
|
||||||
|
{
|
||||||
|
// No web implementation - native platforms only
|
||||||
|
// Web builds will use the existing Web Push system
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export { DailyNotification };
|
||||||
197
src/services/notifications/NativeNotificationService.ts
Normal file
197
src/services/notifications/NativeNotificationService.ts
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
/**
|
||||||
|
* Native Notification Service
|
||||||
|
*
|
||||||
|
* Implementation of notification service using the DailyNotificationPlugin
|
||||||
|
* for native iOS and Android platforms. Provides full native notification
|
||||||
|
* capabilities including background delivery, exact alarm scheduling, and
|
||||||
|
* offline operation.
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 2026-01-21
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DailyNotification } from "@/plugins/DailyNotificationPlugin";
|
||||||
|
import type {
|
||||||
|
NotificationServiceInterface,
|
||||||
|
DailyNotificationOptions,
|
||||||
|
NotificationStatus,
|
||||||
|
PermissionStatus,
|
||||||
|
} from "./NotificationService";
|
||||||
|
import { logger } from "@/utils/logger";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native notification implementation using DailyNotificationPlugin
|
||||||
|
* Used for iOS and Android builds
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Full background notification support
|
||||||
|
* - Survives app restarts and device reboots
|
||||||
|
* - Exact alarm scheduling (Android 12+)
|
||||||
|
* - No network dependency for delivery
|
||||||
|
* - Native OS notification UI
|
||||||
|
*/
|
||||||
|
export class NativeNotificationService implements NotificationServiceInterface {
|
||||||
|
private readonly reminderId = "timesafari_daily_reminder";
|
||||||
|
private readonly platformName = "native";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native notifications are always supported on iOS/Android
|
||||||
|
*/
|
||||||
|
isSupported(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request notification permissions from the OS
|
||||||
|
* Shows native permission dialog on first call
|
||||||
|
*/
|
||||||
|
async requestPermissions(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
logger.debug("[NativeNotificationService] Requesting permissions");
|
||||||
|
|
||||||
|
const result = await DailyNotification.requestPermissions();
|
||||||
|
|
||||||
|
logger.debug("[NativeNotificationService] Permission result:", {
|
||||||
|
notificationsEnabled: result.notificationsEnabled,
|
||||||
|
allPermissionsGranted: result.allPermissionsGranted,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.allPermissionsGranted;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
"[NativeNotificationService] Permission request failed:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check current permission status without prompting
|
||||||
|
*/
|
||||||
|
async checkPermissions(): Promise<PermissionStatus> {
|
||||||
|
try {
|
||||||
|
const status = await DailyNotification.checkPermissionStatus();
|
||||||
|
|
||||||
|
return {
|
||||||
|
granted: status.allPermissionsGranted,
|
||||||
|
details: {
|
||||||
|
notifications: status.notificationsEnabled,
|
||||||
|
exactAlarm: status.exactAlarmEnabled,
|
||||||
|
backgroundRefresh: true, // Native always has background capability
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
"[NativeNotificationService] Permission check failed:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
granted: false,
|
||||||
|
details: {
|
||||||
|
notifications: false,
|
||||||
|
exactAlarm: false,
|
||||||
|
backgroundRefresh: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule a daily notification using native alarms
|
||||||
|
*/
|
||||||
|
async scheduleDailyNotification(
|
||||||
|
options: DailyNotificationOptions,
|
||||||
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
logger.info(
|
||||||
|
"[NativeNotificationService] Scheduling daily notification:",
|
||||||
|
{
|
||||||
|
time: options.time,
|
||||||
|
title: options.title,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await DailyNotification.scheduleDailyReminder({
|
||||||
|
id: this.reminderId,
|
||||||
|
title: options.title,
|
||||||
|
body: options.body,
|
||||||
|
time: options.time, // HH:mm format
|
||||||
|
repeatDaily: true,
|
||||||
|
sound: true,
|
||||||
|
vibration: true,
|
||||||
|
priority: options.priority || "normal",
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"[NativeNotificationService] Daily notification scheduled successfully",
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[NativeNotificationService] Schedule failed:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the daily notification
|
||||||
|
*/
|
||||||
|
async cancelDailyNotification(): Promise<void> {
|
||||||
|
try {
|
||||||
|
logger.info("[NativeNotificationService] Cancelling daily notification");
|
||||||
|
await DailyNotification.cancelDailyReminder(this.reminderId);
|
||||||
|
logger.info(
|
||||||
|
"[NativeNotificationService] Daily notification cancelled successfully",
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[NativeNotificationService] Cancel failed:", error);
|
||||||
|
// Don't throw - cancellation failures are non-critical
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current notification status from the plugin
|
||||||
|
*/
|
||||||
|
async getStatus(): Promise<NotificationStatus> {
|
||||||
|
try {
|
||||||
|
const reminders = await DailyNotification.getScheduledReminders();
|
||||||
|
const reminder = reminders.reminders.find(
|
||||||
|
(r) => r.id === this.reminderId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (reminder) {
|
||||||
|
logger.debug("[NativeNotificationService] Found active reminder:", {
|
||||||
|
time: reminder.time,
|
||||||
|
isScheduled: reminder.isScheduled,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
enabled: reminder.isScheduled,
|
||||||
|
scheduledTime: reminder.time,
|
||||||
|
message: reminder.body,
|
||||||
|
notificationType: "native",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("[NativeNotificationService] No active reminder found");
|
||||||
|
return {
|
||||||
|
enabled: false,
|
||||||
|
notificationType: "native",
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[NativeNotificationService] Get status failed:", error);
|
||||||
|
return {
|
||||||
|
enabled: false,
|
||||||
|
notificationType: "native",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get platform identifier
|
||||||
|
*/
|
||||||
|
getPlatformName(): string {
|
||||||
|
return this.platformName;
|
||||||
|
}
|
||||||
|
}
|
||||||
220
src/services/notifications/NotificationService.ts
Normal file
220
src/services/notifications/NotificationService.ts
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
/**
|
||||||
|
* Unified Notification Service
|
||||||
|
*
|
||||||
|
* Provides a platform-agnostic interface for managing notifications across
|
||||||
|
* web and native platforms. Automatically delegates to the appropriate
|
||||||
|
* implementation based on the runtime platform:
|
||||||
|
*
|
||||||
|
* - Native platforms (iOS/Android): Uses DailyNotificationPlugin
|
||||||
|
* - Web/PWA: Uses Web Push with service workers and VAPID
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 2026-01-21
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Capacitor } from "@capacitor/core";
|
||||||
|
import { NativeNotificationService } from "./NativeNotificationService";
|
||||||
|
import { WebPushNotificationService } from "./WebPushNotificationService";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for scheduling a daily notification
|
||||||
|
*/
|
||||||
|
export interface DailyNotificationOptions {
|
||||||
|
/**
|
||||||
|
* Time to send notification in HH:mm format (24-hour)
|
||||||
|
* Example: "09:00" for 9 AM, "17:30" for 5:30 PM
|
||||||
|
*/
|
||||||
|
time: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notification title
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notification body/message
|
||||||
|
*/
|
||||||
|
body: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional notification priority
|
||||||
|
* @default 'normal'
|
||||||
|
*/
|
||||||
|
priority?: "low" | "normal" | "high";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current notification status
|
||||||
|
*/
|
||||||
|
export interface NotificationStatus {
|
||||||
|
/**
|
||||||
|
* Whether notifications are currently enabled
|
||||||
|
*/
|
||||||
|
enabled: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scheduled time in HH:mm format, if any
|
||||||
|
*/
|
||||||
|
scheduledTime?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current notification message, if any
|
||||||
|
*/
|
||||||
|
message?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform-specific notification type identifier
|
||||||
|
*/
|
||||||
|
notificationType?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permission status result
|
||||||
|
*/
|
||||||
|
export interface PermissionStatus {
|
||||||
|
/**
|
||||||
|
* Whether notification permissions are granted
|
||||||
|
*/
|
||||||
|
granted: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional platform-specific permission details
|
||||||
|
*/
|
||||||
|
details?: {
|
||||||
|
notifications?: boolean;
|
||||||
|
exactAlarm?: boolean;
|
||||||
|
backgroundRefresh?: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unified notification service interface
|
||||||
|
* All platform-specific implementations must conform to this interface
|
||||||
|
*/
|
||||||
|
export interface NotificationServiceInterface {
|
||||||
|
/**
|
||||||
|
* Check if notifications are supported on current platform
|
||||||
|
* @returns true if notifications are supported
|
||||||
|
*/
|
||||||
|
isSupported(): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request notification permissions from the user
|
||||||
|
* Shows system permission dialog on first call
|
||||||
|
* @returns Promise that resolves to true if permissions granted
|
||||||
|
*/
|
||||||
|
requestPermissions(): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check current notification permission status
|
||||||
|
* @returns Promise with permission status
|
||||||
|
*/
|
||||||
|
checkPermissions(): Promise<PermissionStatus>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule a daily notification/reminder
|
||||||
|
* @param options Notification configuration
|
||||||
|
* @returns Promise that resolves to true if scheduling succeeded
|
||||||
|
*/
|
||||||
|
scheduleDailyNotification(
|
||||||
|
options: DailyNotificationOptions,
|
||||||
|
): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel all daily notifications
|
||||||
|
* @returns Promise that resolves when cancellation is complete
|
||||||
|
*/
|
||||||
|
cancelDailyNotification(): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current notification status
|
||||||
|
* @returns Promise with current notification state
|
||||||
|
*/
|
||||||
|
getStatus(): Promise<NotificationStatus>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the platform name for this service
|
||||||
|
* @returns Platform identifier (e.g., 'native', 'web')
|
||||||
|
*/
|
||||||
|
getPlatformName(): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notification Service Factory
|
||||||
|
*
|
||||||
|
* Singleton factory that creates and manages the appropriate notification
|
||||||
|
* service implementation based on the current platform.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const notificationService = NotificationService.getInstance();
|
||||||
|
* const granted = await notificationService.requestPermissions();
|
||||||
|
* if (granted) {
|
||||||
|
* await notificationService.scheduleDailyNotification({
|
||||||
|
* time: '09:00',
|
||||||
|
* title: 'Daily Check-In',
|
||||||
|
* body: 'Time to check your TimeSafari activity'
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export class NotificationService {
|
||||||
|
private static instance: NotificationServiceInterface | null = null;
|
||||||
|
private static instanceCreated = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the singleton notification service instance
|
||||||
|
* Creates the appropriate platform-specific implementation on first call
|
||||||
|
*
|
||||||
|
* @returns NotificationServiceInterface implementation for current platform
|
||||||
|
*/
|
||||||
|
static getInstance(): NotificationServiceInterface {
|
||||||
|
if (!this.instance) {
|
||||||
|
const platform = Capacitor.getPlatform();
|
||||||
|
|
||||||
|
if (!this.instanceCreated) {
|
||||||
|
// Log initialization for debugging (only happens once per app lifecycle)
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
`[NotificationService] Creating ${this.isNative() ? "native" : "web"} notification service for platform: ${platform}`,
|
||||||
|
);
|
||||||
|
this.instanceCreated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isNative()) {
|
||||||
|
// iOS/Android: Use native plugin
|
||||||
|
this.instance = new NativeNotificationService();
|
||||||
|
} else {
|
||||||
|
// Web/PWA: Use web push
|
||||||
|
this.instance = new WebPushNotificationService();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if running on a native platform (iOS/Android)
|
||||||
|
* @returns true if running on iOS or Android
|
||||||
|
*/
|
||||||
|
static isNative(): boolean {
|
||||||
|
return Capacitor.isNativePlatform();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current platform name
|
||||||
|
* @returns Platform identifier: 'ios', 'android', 'web', 'electron'
|
||||||
|
*/
|
||||||
|
static getPlatform(): string {
|
||||||
|
return Capacitor.getPlatform();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the singleton instance (for testing purposes)
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
static reset(): void {
|
||||||
|
this.instance = null;
|
||||||
|
this.instanceCreated = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
212
src/services/notifications/WebPushNotificationService.ts
Normal file
212
src/services/notifications/WebPushNotificationService.ts
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
/**
|
||||||
|
* Web Push Notification Service
|
||||||
|
*
|
||||||
|
* Implementation of notification service using Web Push API with service workers
|
||||||
|
* for web/PWA builds. This is a wrapper around the existing PushNotificationPermission
|
||||||
|
* component logic, providing a consistent interface with the native notification service.
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 2026-01-21
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {
|
||||||
|
NotificationServiceInterface,
|
||||||
|
DailyNotificationOptions,
|
||||||
|
NotificationStatus,
|
||||||
|
PermissionStatus,
|
||||||
|
} from "./NotificationService";
|
||||||
|
import { logger } from "@/utils/logger";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Push notification implementation for web/PWA builds
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Uses Web Push API with VAPID authentication
|
||||||
|
* - Service worker-based message delivery
|
||||||
|
* - Requires server-side push infrastructure
|
||||||
|
* - Limited background support (browser-dependent)
|
||||||
|
*
|
||||||
|
* Limitations:
|
||||||
|
* - Requires active service worker
|
||||||
|
* - iOS: Limited to Home Screen PWAs on iOS 16.4+
|
||||||
|
* - Android: Works in Chrome 42+
|
||||||
|
* - Requires network connection for delivery
|
||||||
|
*
|
||||||
|
* @note This is currently a stub implementation that will be fully integrated
|
||||||
|
* with the existing PushNotificationPermission.vue component logic in a
|
||||||
|
* future update. For now, it provides the interface structure.
|
||||||
|
*/
|
||||||
|
export class WebPushNotificationService
|
||||||
|
implements NotificationServiceInterface
|
||||||
|
{
|
||||||
|
private readonly platformName = "web";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Web Push is supported in the current browser
|
||||||
|
*/
|
||||||
|
isSupported(): boolean {
|
||||||
|
const supported =
|
||||||
|
"serviceWorker" in navigator &&
|
||||||
|
"PushManager" in window &&
|
||||||
|
"Notification" in window;
|
||||||
|
|
||||||
|
if (!supported) {
|
||||||
|
logger.warn(
|
||||||
|
"[WebPushNotificationService] Web Push is not supported in this browser",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request notification permissions via browser API
|
||||||
|
*/
|
||||||
|
async requestPermissions(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
if (!this.isSupported()) {
|
||||||
|
logger.error("[WebPushNotificationService] Web Push not supported");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"[WebPushNotificationService] Requesting browser notification permissions",
|
||||||
|
);
|
||||||
|
|
||||||
|
const permission = await Notification.requestPermission();
|
||||||
|
const granted = permission === "granted";
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"[WebPushNotificationService] Permission result:",
|
||||||
|
permission,
|
||||||
|
);
|
||||||
|
|
||||||
|
return granted;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
"[WebPushNotificationService] Permission request failed:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check current browser notification permission status
|
||||||
|
*/
|
||||||
|
async checkPermissions(): Promise<PermissionStatus> {
|
||||||
|
if (!this.isSupported()) {
|
||||||
|
return {
|
||||||
|
granted: false,
|
||||||
|
details: {
|
||||||
|
notifications: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const granted = window.Notification?.permission === "granted";
|
||||||
|
|
||||||
|
return {
|
||||||
|
granted,
|
||||||
|
details: {
|
||||||
|
notifications: granted,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule a daily notification via Web Push
|
||||||
|
*
|
||||||
|
* @note This is a stub implementation. The actual scheduling will be
|
||||||
|
* integrated with the existing PushNotificationPermission.vue component
|
||||||
|
* and web push server infrastructure.
|
||||||
|
*
|
||||||
|
* @param options Notification configuration
|
||||||
|
* @returns Promise that resolves to true if scheduling succeeded
|
||||||
|
*/
|
||||||
|
async scheduleDailyNotification(
|
||||||
|
options: DailyNotificationOptions,
|
||||||
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
logger.info("[WebPushNotificationService] Schedule requested:", {
|
||||||
|
time: options.time,
|
||||||
|
title: options.title,
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Integrate with existing PushNotificationPermission.vue logic
|
||||||
|
// This will involve:
|
||||||
|
// 1. Subscribing to push service with VAPID key
|
||||||
|
// 2. Sending subscription to server with schedule time
|
||||||
|
// 3. Server will send push messages at scheduled time
|
||||||
|
|
||||||
|
logger.warn(
|
||||||
|
"[WebPushNotificationService] Scheduling not yet implemented - use PushNotificationPermission.vue component directly",
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[WebPushNotificationService] Schedule failed:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel daily web push notifications
|
||||||
|
*
|
||||||
|
* @note This is a stub implementation. Will be integrated with existing
|
||||||
|
* web push unsubscribe logic.
|
||||||
|
*/
|
||||||
|
async cancelDailyNotification(): Promise<void> {
|
||||||
|
try {
|
||||||
|
logger.info("[WebPushNotificationService] Cancel requested");
|
||||||
|
|
||||||
|
// TODO: Integrate with existing unsubscribe logic from App.vue
|
||||||
|
// This will involve:
|
||||||
|
// 1. Getting current service worker subscription
|
||||||
|
// 2. Calling subscription.unsubscribe()
|
||||||
|
// 3. Notifying server to stop sending push messages
|
||||||
|
|
||||||
|
logger.warn(
|
||||||
|
"[WebPushNotificationService] Cancellation not yet implemented - use existing App.vue logic",
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[WebPushNotificationService] Cancel failed:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current web push notification status
|
||||||
|
*
|
||||||
|
* @note This is a stub implementation. Will be integrated with existing
|
||||||
|
* settings retrieval logic.
|
||||||
|
*/
|
||||||
|
async getStatus(): Promise<NotificationStatus> {
|
||||||
|
try {
|
||||||
|
// TODO: Integrate with existing settings from database
|
||||||
|
// Check settings.notifyingNewActivityTime and settings.notifyingReminderTime
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"[WebPushNotificationService] Status check - stub implementation",
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
enabled: false,
|
||||||
|
notificationType: "web-push",
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[WebPushNotificationService] Get status failed:", error);
|
||||||
|
return {
|
||||||
|
enabled: false,
|
||||||
|
notificationType: "web-push",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get platform identifier
|
||||||
|
*/
|
||||||
|
getPlatformName(): string {
|
||||||
|
return this.platformName;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/services/notifications/index.ts
Normal file
25
src/services/notifications/index.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Notification Services Barrel Export
|
||||||
|
*
|
||||||
|
* Central export point for all notification-related services.
|
||||||
|
* Use this instead of importing from individual files.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* import { NotificationService } from '@/services/notifications';
|
||||||
|
*
|
||||||
|
* const service = NotificationService.getInstance();
|
||||||
|
* await service.scheduleDailyNotification({ ... });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { NotificationService } from "./NotificationService";
|
||||||
|
export { NativeNotificationService } from "./NativeNotificationService";
|
||||||
|
export { WebPushNotificationService } from "./WebPushNotificationService";
|
||||||
|
|
||||||
|
export type {
|
||||||
|
NotificationServiceInterface,
|
||||||
|
DailyNotificationOptions,
|
||||||
|
NotificationStatus,
|
||||||
|
PermissionStatus,
|
||||||
|
} from "./NotificationService";
|
||||||
Reference in New Issue
Block a user