BackgroundAlertManager.swift 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. //
  2. // BackgroundAlertManager.swift
  3. // LoopFollow
  4. //
  5. // Created by Jonas Björkert on 2024-06-22.
  6. // Copyright © 2024 Jon Fawcett. All rights reserved.
  7. //
  8. import Foundation
  9. import UserNotifications
  10. /// Enum representing different background alert durations.
  11. enum BackgroundAlertDuration: TimeInterval, CaseIterable {
  12. case sixMinutes = 360 // 6 minutes in seconds
  13. case twelveMinutes = 720 // 12 minutes in seconds
  14. case eighteenMinutes = 1080 // 18 minutes in seconds
  15. }
  16. /// Enum representing unique identifiers for each background alert.
  17. enum BackgroundAlertIdentifier: String, CaseIterable {
  18. case sixMin = "loopfollow.background.alert.6min"
  19. case twelveMin = "loopfollow.background.alert.12min"
  20. case eighteenMin = "loopfollow.background.alert.18min"
  21. }
  22. class BackgroundAlertManager {
  23. static let shared = BackgroundAlertManager()
  24. private init() {}
  25. /// Flag indicating whether background alerts are currently scheduled.
  26. private var isAlertScheduled: Bool = false
  27. /// Title prefix for all background refresh notifications.
  28. private let notificationTitlePrefix = "LoopFollow Background Refresh"
  29. /// Start scheduling background alerts.
  30. func startBackgroundAlert() {
  31. isAlertScheduled = true
  32. scheduleBackgroundAlert()
  33. }
  34. /// Stop all scheduled background alerts.
  35. func stopBackgroundAlert() {
  36. isAlertScheduled = false
  37. removeDeliveredNotifications()
  38. cancelBackgroundAlerts()
  39. }
  40. /// (Re)schedule all background alerts based on predefined durations.
  41. func scheduleBackgroundAlert() {
  42. removeDeliveredNotifications()
  43. guard isAlertScheduled, Storage.shared.backgroundRefreshType.value != .none else { return }
  44. let isBluetoothActive = Storage.shared.backgroundRefreshType.value.isBluetooth
  45. // Define alerts
  46. let alerts: [BackgroundAlert] = [
  47. BackgroundAlert(
  48. identifier: BackgroundAlertIdentifier.sixMin.rawValue,
  49. timeInterval: BackgroundAlertDuration.sixMinutes.rawValue,
  50. body: isBluetoothActive
  51. ? "App inactive for 6 minutes. Verify Bluetooth connectivity."
  52. : "App inactive for 6 minutes. Open to resume."
  53. ),
  54. BackgroundAlert(
  55. identifier: BackgroundAlertIdentifier.twelveMin.rawValue,
  56. timeInterval: BackgroundAlertDuration.twelveMinutes.rawValue,
  57. body: isBluetoothActive
  58. ? "App inactive for 12 minutes. Verify Bluetooth connectivity."
  59. : "App inactive for 12 minutes. Open to resume."
  60. ),
  61. BackgroundAlert(
  62. identifier: BackgroundAlertIdentifier.eighteenMin.rawValue,
  63. timeInterval: BackgroundAlertDuration.eighteenMinutes.rawValue,
  64. body: isBluetoothActive
  65. ? "App inactive for 18 minutes. Verify Bluetooth connectivity."
  66. : "App inactive for 18 minutes. Open to resume."
  67. )
  68. ]
  69. // Schedule each alert.
  70. for alert in alerts {
  71. let content = createNotificationContent(for: notificationTitlePrefix, body: alert.body)
  72. let trigger = UNTimeIntervalNotificationTrigger(timeInterval: alert.timeInterval, repeats: false)
  73. let request = UNNotificationRequest(identifier: alert.identifier, content: content, trigger: trigger)
  74. UNUserNotificationCenter.current().add(request) { error in
  75. if let error = error {
  76. LogManager.shared.log(category: .general, message: "Error scheduling \(alert.timeInterval / 60)-minute background alert: \(error)")
  77. }
  78. }
  79. }
  80. }
  81. /// Create notification content with a given title and body.
  82. /// - Parameters:
  83. /// - title: The title of the notification.
  84. /// - body: The body text of the notification.
  85. /// - Returns: Configured `UNMutableNotificationContent` object.
  86. private func createNotificationContent(for title: String, body: String) -> UNMutableNotificationContent {
  87. let content = UNMutableNotificationContent()
  88. content.title = title
  89. content.body = body
  90. content.sound = .defaultCritical
  91. content.categoryIdentifier = "loopfollow.background.alert"
  92. return content
  93. }
  94. /// Cancel all scheduled background alerts.
  95. private func cancelBackgroundAlerts() {
  96. let identifiers = BackgroundAlertIdentifier.allCases.map { $0.rawValue }
  97. UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: identifiers)
  98. }
  99. /// Remove all delivered notifications
  100. private func removeDeliveredNotifications() {
  101. let identifiers = BackgroundAlertIdentifier.allCases.map { $0.rawValue }
  102. UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: identifiers)
  103. }
  104. }
  105. /// Struct representing a single background alert.
  106. struct BackgroundAlert {
  107. let identifier: String
  108. let timeInterval: TimeInterval
  109. let body: String
  110. }