TrioUserNotificationAlertScheduler.swift 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import Foundation
  2. import LoopKit
  3. import UserNotifications
  4. protocol TrioUserNotificationAlertResponder: AnyObject {
  5. func handleAcknowledgement(identifier: Alert.Identifier)
  6. }
  7. final class TrioUserNotificationAlertScheduler {
  8. weak var responder: TrioUserNotificationAlertResponder?
  9. private let notificationCenter: UNUserNotificationCenter
  10. private let soundsRoot: URL
  11. init(notificationCenter: UNUserNotificationCenter, soundsRoot: URL) {
  12. self.notificationCenter = notificationCenter
  13. self.soundsRoot = soundsRoot
  14. }
  15. func schedule(_ alert: Alert, muted: Bool, soundURL: URL?) {
  16. let request = makeRequest(alert: alert, muted: muted, soundURL: soundURL)
  17. notificationCenter.add(request) { error in
  18. if let error = error {
  19. debug(.service, "UserNotificationAlertScheduler failed: \(error.localizedDescription)")
  20. }
  21. }
  22. }
  23. func unschedule(identifier: Alert.Identifier) {
  24. notificationCenter.removePendingNotificationRequests(withIdentifiers: [identifier.value])
  25. notificationCenter.removeDeliveredNotifications(withIdentifiers: [identifier.value])
  26. }
  27. private func makeRequest(alert: Alert, muted: Bool, soundURL: URL?) -> UNNotificationRequest {
  28. let content = UNMutableNotificationContent()
  29. content.title = alert.backgroundContent.title
  30. content.body = alert.backgroundContent.body
  31. content.threadIdentifier = alert.identifier.managerIdentifier
  32. content.userInfo = [
  33. AlertUserInfoKey.managerIdentifier.rawValue: alert.identifier.managerIdentifier,
  34. AlertUserInfoKey.alertIdentifier.rawValue: alert.identifier.alertIdentifier
  35. ]
  36. content.interruptionLevel = alert.interruptionLevel.unNotificationLevel
  37. content.sound = sound(for: alert, muted: muted, soundURL: soundURL)
  38. // Surface the four quick-snooze actions (20 min / 1 h / 3 h / 6 h)
  39. // on both phone and watch lock-screen notifications. The category +
  40. // its actions are registered by `NotificationCategoryFactory` on
  41. // both the phone (`BaseUserNotificationsManager`) and watch
  42. // (`WatchNotificationHandler`) UN delegates.
  43. content.categoryIdentifier = NotificationCategoryIdentifier.trioAlert.rawValue
  44. return UNNotificationRequest(
  45. identifier: alert.identifier.value,
  46. content: content,
  47. trigger: alert.trigger.unTrigger
  48. )
  49. }
  50. private func sound(for alert: Alert, muted: Bool, soundURL: URL?) -> UNNotificationSound? {
  51. let isCritical = alert.interruptionLevel == .critical
  52. if muted {
  53. return isCritical ? .defaultCriticalSound(withAudioVolume: 0) : nil
  54. }
  55. switch alert.sound {
  56. case .none,
  57. .vibrate:
  58. return isCritical ? .defaultCriticalSound(withAudioVolume: 0) : nil
  59. case let .sound(name):
  60. if let filename = soundURL?.lastPathComponent {
  61. let unName = UNNotificationSoundName(rawValue: filename)
  62. return isCritical ? .criticalSoundNamed(unName) : UNNotificationSound(named: unName)
  63. }
  64. let unName = UNNotificationSoundName(name)
  65. return isCritical ? .criticalSoundNamed(unName) : UNNotificationSound(named: unName)
  66. }
  67. }
  68. }
  69. private extension Alert.InterruptionLevel {
  70. var unNotificationLevel: UNNotificationInterruptionLevel {
  71. switch self {
  72. case .active: return .active
  73. case .timeSensitive: return .timeSensitive
  74. case .critical: return .critical
  75. }
  76. }
  77. }
  78. private extension Alert.Trigger {
  79. var unTrigger: UNNotificationTrigger? {
  80. switch self {
  81. case .immediate:
  82. return nil
  83. case let .delayed(interval):
  84. return UNTimeIntervalNotificationTrigger(timeInterval: max(interval, 1), repeats: false)
  85. case let .repeating(interval):
  86. return UNTimeIntervalNotificationTrigger(timeInterval: max(interval, 60), repeats: true)
  87. }
  88. }
  89. }