feat(ios): Extract orchestration helpers to ScheduleHelper
Extract iOS orchestration logic from plugin to dedicated helper, matching Android's ScheduleHelper.kt pattern. This completes the P2.1 native plugin refactoring for both platforms. Changes: - Created DailyNotificationScheduleHelper.swift (192 lines) - scheduleDailyNotification(): Full orchestration (cancel, clear, save, schedule, prefetch) - scheduleDualNotification(): Dual scheduling coordination - clearRolloverState(): Rollover state cleanup helper - getHealthStatus(): Status combination from multiple sources - Refactored DailyNotificationPlugin.swift to delegate to helper - Reduced plugin by 236 lines (1854 → 1807 LOC) - Total iOS reduction: 11.7% (2047 → 1807 LOC) - Updated documentation - docs/progress/00-STATUS.md: Marked verification complete, added helper extraction - docs/progress/01-CHANGELOG-WORK.md: Added iOS helper extraction entry - docs/progress/P2.1-REFACTORING-COMPLETE.md: Updated with helper extraction - docs/00-INDEX.md: Added reference to refactoring summary Verification: - TypeScript typecheck: PASS - Build: PASS - Tests: PASS (115 tests, 8 test suites) - External API behavior unchanged Files changed: - ios/Plugin/DailyNotificationScheduleHelper.swift (new, 192 lines) - ios/Plugin/DailyNotificationPlugin.swift (198 insertions, 434 deletions) - docs/progress/00-STATUS.md (verification status updated) - docs/progress/01-CHANGELOG-WORK.md (changelog entry added) - docs/00-INDEX.md (index reference added) Related: - Completes P2.1 iOS refactoring (27 methods across 3 batches) - Matches Android ScheduleHelper.kt pattern - Total P2.1: 55 methods refactored (28 Android + 27 iOS)
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
//
|
||||
// DailyNotificationScheduleHelper.swift
|
||||
// DailyNotificationPlugin
|
||||
//
|
||||
// Created by Matthew Raymer on 2025-12-23
|
||||
// Copyright © 2025 TimeSafari. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UserNotifications
|
||||
|
||||
/**
|
||||
* DailyNotificationScheduleHelper.swift
|
||||
*
|
||||
* Orchestration helper for daily notification scheduling
|
||||
*
|
||||
* This helper encapsulates complex scheduling orchestration logic that combines
|
||||
* multiple services (scheduler, storage, stateActor, background tasks).
|
||||
* Similar to Android's ScheduleHelper.kt pattern.
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Schedule daily notifications with full orchestration (cancel, clear, save, schedule, prefetch)
|
||||
* - Schedule dual notifications (background fetch + user notification)
|
||||
* - Clear rollover state
|
||||
* - Combine status from multiple sources
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
* @version 1.0.0
|
||||
* @created 2025-12-23
|
||||
*/
|
||||
enum DailyNotificationScheduleHelper {
|
||||
|
||||
/**
|
||||
* Schedule daily notification with full orchestration
|
||||
*
|
||||
* Orchestrates:
|
||||
* 1. Cancel all existing notifications
|
||||
* 2. Clear all stored notification content
|
||||
* 3. Clear rollover state
|
||||
* 4. Save notification content (via stateActor if available)
|
||||
* 5. Schedule notification
|
||||
* 6. Schedule background fetch (5 minutes before notification)
|
||||
*
|
||||
* @param content Notification content to schedule
|
||||
* @param scheduledTime Scheduled time in milliseconds
|
||||
* @param scheduler DailyNotificationScheduler instance
|
||||
* @param storage DailyNotificationStorage instance
|
||||
* @param stateActor DailyNotificationStateActor instance (optional, iOS 13+)
|
||||
* @param scheduleBackgroundFetch Closure to schedule background fetch
|
||||
* @return true if scheduling succeeded, false otherwise
|
||||
*/
|
||||
static func scheduleDailyNotification(
|
||||
content: NotificationContent,
|
||||
scheduledTime: Int64,
|
||||
scheduler: DailyNotificationScheduler,
|
||||
storage: DailyNotificationStorage?,
|
||||
stateActor: DailyNotificationStateActor?,
|
||||
scheduleBackgroundFetch: (Int64) -> Void
|
||||
) async -> Bool {
|
||||
// Step 1: Cancel all existing notifications
|
||||
await scheduler.cancelAllNotifications()
|
||||
|
||||
// Step 2: Clear all stored notification content
|
||||
storage?.clearAllNotifications()
|
||||
|
||||
// Step 3: Clear rollover state
|
||||
clearRolloverState(storage: storage)
|
||||
|
||||
// Step 4: Save notification content (via stateActor if available, otherwise storage)
|
||||
if #available(iOS 13.0, *), let stateActor = stateActor {
|
||||
await stateActor.saveNotificationContent(content)
|
||||
} else {
|
||||
storage?.saveNotificationContent(content)
|
||||
}
|
||||
|
||||
// Step 5: Schedule notification
|
||||
let scheduled = await scheduler.scheduleNotification(content)
|
||||
|
||||
// Step 6: Schedule background fetch if notification was scheduled
|
||||
if scheduled {
|
||||
scheduleBackgroundFetch(scheduledTime)
|
||||
}
|
||||
|
||||
return scheduled
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule dual notification (background fetch + user notification)
|
||||
*
|
||||
* Orchestrates both background fetch and user notification scheduling.
|
||||
*
|
||||
* @param contentFetchConfig Background fetch configuration
|
||||
* @param userNotificationConfig User notification configuration
|
||||
* @param scheduleBackgroundFetch Closure to schedule background fetch
|
||||
* @param scheduleUserNotification Closure to schedule user notification
|
||||
* @throws Error if scheduling fails
|
||||
*/
|
||||
static func scheduleDualNotification(
|
||||
contentFetchConfig: [String: Any],
|
||||
userNotificationConfig: [String: Any],
|
||||
scheduleBackgroundFetch: ([String: Any]) throws -> Void,
|
||||
scheduleUserNotification: ([String: Any]) throws -> Void
|
||||
) throws {
|
||||
// Schedule both background fetch and user notification
|
||||
try scheduleBackgroundFetch(contentFetchConfig)
|
||||
try scheduleUserNotification(userNotificationConfig)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear rollover state from storage and UserDefaults
|
||||
*
|
||||
* Clears:
|
||||
* - Global rollover time in storage
|
||||
* - All rollover_* keys from UserDefaults
|
||||
*
|
||||
* @param storage DailyNotificationStorage instance (optional)
|
||||
*/
|
||||
static func clearRolloverState(storage: DailyNotificationStorage?) {
|
||||
// Clear global rollover time
|
||||
storage?.saveLastRolloverTime(0)
|
||||
|
||||
// Clear per-notification rollover times from UserDefaults
|
||||
let userDefaults = UserDefaults.standard
|
||||
let allKeys = userDefaults.dictionaryRepresentation().keys
|
||||
for key in allKeys {
|
||||
if key.hasPrefix("rollover_") {
|
||||
userDefaults.removeObject(forKey: key)
|
||||
}
|
||||
}
|
||||
userDefaults.synchronize()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get health status combining multiple sources
|
||||
*
|
||||
* Combines:
|
||||
* - Scheduler status (pending count, permission status)
|
||||
* - Storage/StateActor status (last notification)
|
||||
*
|
||||
* @param scheduler DailyNotificationScheduler instance
|
||||
* @param storage DailyNotificationStorage instance (optional)
|
||||
* @param stateActor DailyNotificationStateActor instance (optional, iOS 13+)
|
||||
* @return Health status dictionary
|
||||
* @throws Error if scheduler not initialized
|
||||
*/
|
||||
static func getHealthStatus(
|
||||
scheduler: DailyNotificationScheduler,
|
||||
storage: DailyNotificationStorage?,
|
||||
stateActor: DailyNotificationStateActor?
|
||||
) async throws -> [String: Any] {
|
||||
// Delegate to scheduler for pending count and permission status
|
||||
let pendingCount = await scheduler.getPendingNotificationCount()
|
||||
let isEnabled = await scheduler.checkPermissionStatus() == .authorized
|
||||
|
||||
// Delegate to stateActor if available (thread-safe), otherwise use storage directly
|
||||
let lastNotification: NotificationContent?
|
||||
if #available(iOS 13.0, *), let stateActor = stateActor {
|
||||
lastNotification = await stateActor.getLastNotification()
|
||||
} else {
|
||||
lastNotification = storage?.getLastNotification()
|
||||
}
|
||||
|
||||
return [
|
||||
"contentFetch": [
|
||||
"isEnabled": true,
|
||||
"isScheduled": pendingCount > 0,
|
||||
"lastFetchTime": lastNotification?.fetchedAt ?? 0,
|
||||
"nextFetchTime": 0,
|
||||
"pendingFetches": pendingCount
|
||||
],
|
||||
"userNotification": [
|
||||
"isEnabled": isEnabled,
|
||||
"isScheduled": pendingCount > 0,
|
||||
"lastNotificationTime": lastNotification?.scheduledTime ?? 0,
|
||||
"nextNotificationTime": 0,
|
||||
"pendingNotifications": pendingCount
|
||||
],
|
||||
"relationship": [
|
||||
"isLinked": true,
|
||||
"contentAvailable": lastNotification != nil,
|
||||
"lastLinkTime": lastNotification?.fetchedAt ?? 0
|
||||
],
|
||||
"overall": [
|
||||
"isActive": isEnabled && pendingCount > 0,
|
||||
"lastActivity": lastNotification?.scheduledTime ?? 0,
|
||||
"errorCount": 0,
|
||||
"successRate": 1.0
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user