# 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
