// // BackgroundAlertManager.swift // LoopFollow // // Created by Jonas Björkert on 2024-06-22. // Copyright © 2024 Jon Fawcett. All rights reserved. // import Foundation import UserNotifications /// Enum representing different background alert durations. enum BackgroundAlertDuration: TimeInterval, CaseIterable { case sixMinutes = 360 // 6 minutes in seconds case twelveMinutes = 720 // 12 minutes in seconds case eighteenMinutes = 1080 // 18 minutes in seconds } /// Enum representing unique identifiers for each background alert. enum BackgroundAlertIdentifier: String, CaseIterable { case sixMin = "loopfollow.background.alert.6min" case twelveMin = "loopfollow.background.alert.12min" case eighteenMin = "loopfollow.background.alert.18min" } class BackgroundAlertManager { static let shared = BackgroundAlertManager() private init() {} /// Flag indicating whether background alerts are currently scheduled. private var isAlertScheduled: Bool = false /// Title prefix for all background refresh notifications. private let notificationTitlePrefix = "LoopFollow Background Refresh" /// Start scheduling background alerts. func startBackgroundAlert() { isAlertScheduled = true scheduleBackgroundAlert() } /// Stop all scheduled background alerts. func stopBackgroundAlert() { isAlertScheduled = false cancelBackgroundAlerts() } /// Schedule all background alerts based on predefined durations. func scheduleBackgroundAlert() { guard isAlertScheduled, Storage.shared.backgroundRefreshType.value != .none else { return } let isBluetoothActive = Storage.shared.backgroundRefreshType.value.isBluetooth // Define all alerts in an array for scalability. let alerts: [BackgroundAlert] = [ BackgroundAlert( identifier: BackgroundAlertIdentifier.sixMin.rawValue, timeInterval: BackgroundAlertDuration.sixMinutes.rawValue, body: isBluetoothActive ? "App inactive for 6 minutes. Verify Bluetooth connectivity." : "App inactive for 6 minutes. Open to resume." ), BackgroundAlert( identifier: BackgroundAlertIdentifier.twelveMin.rawValue, timeInterval: BackgroundAlertDuration.twelveMinutes.rawValue, body: isBluetoothActive ? "App inactive for 12 minutes. Verify Bluetooth connectivity." : "App inactive for 12 minutes. Open to resume." ), BackgroundAlert( identifier: BackgroundAlertIdentifier.eighteenMin.rawValue, timeInterval: BackgroundAlertDuration.eighteenMinutes.rawValue, body: isBluetoothActive ? "App inactive for 18 minutes. Verify Bluetooth connectivity." : "App inactive for 18 minutes. Open to resume." ) ] // Schedule each alert. for alert in alerts { let content = createNotificationContent(for: notificationTitlePrefix, body: alert.body) let trigger = UNTimeIntervalNotificationTrigger(timeInterval: alert.timeInterval, repeats: false) let request = UNNotificationRequest(identifier: alert.identifier, content: content, trigger: trigger) UNUserNotificationCenter.current().add(request) { error in if let error = error { print("Error scheduling \(alert.timeInterval / 60)-minute background alert: \(error)") } } } } /// Create notification content with a given title and body. /// - Parameters: /// - title: The title of the notification. /// - body: The body text of the notification. /// - Returns: Configured `UNMutableNotificationContent` object. private func createNotificationContent(for title: String, body: String) -> UNMutableNotificationContent { let content = UNMutableNotificationContent() content.title = title content.body = body content.sound = .defaultCritical content.categoryIdentifier = "loopfollow.background.alert" return content } /// Cancel all scheduled background alerts. private func cancelBackgroundAlerts() { let identifiers = BackgroundAlertIdentifier.allCases.map { $0.rawValue } UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: identifiers) } } /// Struct representing a single background alert. struct BackgroundAlert { let identifier: String let timeInterval: TimeInterval let body: String }