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)
239 lines
8.0 KiB
Markdown
239 lines
8.0 KiB
Markdown
# 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<boolean> {
|
|
const result = await DailyNotification.requestPermissions();
|
|
return result.allPermissionsGranted;
|
|
}
|
|
```
|
|
|
|
**3. Plugin Registration** (`src/plugins/DailyNotificationPlugin.ts`):
|
|
```typescript
|
|
// Line 30-36
|
|
const DailyNotification = registerPlugin<DailyNotificationPluginType>(
|
|
"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
|