# 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