NotificationManager.swift 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import Foundation
  2. import UserNotifications
  3. import DcCore
  4. import UIKit
  5. public class NotificationManager {
  6. var incomingMsgObserver: NSObjectProtocol?
  7. var msgsNoticedObserver: NSObjectProtocol?
  8. private var notificationBackgroundTasks = [Int: UIBackgroundTaskIdentifier]()
  9. init() {
  10. initIncomingMsgsObserver()
  11. initMsgsNoticedObserver()
  12. }
  13. public static func updateApplicationIconBadge(reset: Bool) {
  14. var unreadMessages = 0
  15. if !reset {
  16. unreadMessages = DcContext.shared.getFreshMessages().count
  17. }
  18. DispatchQueue.main.async {
  19. UIApplication.shared.applicationIconBadgeNumber = unreadMessages
  20. }
  21. }
  22. public static func removeAllNotifications() {
  23. let nc = UNUserNotificationCenter.current()
  24. nc.removeAllDeliveredNotifications()
  25. nc.removeAllPendingNotificationRequests()
  26. }
  27. public static func removeNotificationsForChat(chatId: Int) {
  28. DispatchQueue.global(qos: .background).async {
  29. NotificationManager.removePendingNotificationsFor(chatId: chatId)
  30. NotificationManager.removeDeliveredNotificationsFor(chatId: chatId)
  31. NotificationManager.updateApplicationIconBadge(reset: false)
  32. }
  33. }
  34. private func unregisterNotificationBackgroundTask(msgId: Int) {
  35. let backgroundTask = notificationBackgroundTasks[msgId]
  36. if let backgroundTask = backgroundTask, backgroundTask != .invalid {
  37. UIApplication.shared.endBackgroundTask(backgroundTask)
  38. notificationBackgroundTasks.removeValue(forKey: msgId)
  39. logger.info("⬅️ notification background task ended and removed: \(msgId)")
  40. }
  41. }
  42. private func registerNotificationBackgroundTask(msgId: Int) {
  43. logger.info("⬅️ notification notification background task: \(msgId)")
  44. let backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
  45. logger.info("⬅️ notification background expirationHandler called")
  46. self?.unregisterNotificationBackgroundTask(msgId: msgId)
  47. }
  48. notificationBackgroundTasks[msgId] = backgroundTask
  49. }
  50. private func initIncomingMsgsObserver() {
  51. incomingMsgObserver = NotificationCenter.default.addObserver(
  52. forName: dcNotificationIncoming,
  53. object: nil, queue: OperationQueue.main
  54. ) { notification in
  55. DispatchQueue.global(qos: .background).async { [weak self] in
  56. if let ui = notification.userInfo,
  57. let chatId = ui["chat_id"] as? Int,
  58. let messageId = ui["message_id"] as? Int,
  59. !UserDefaults.standard.bool(forKey: "notifications_disabled") {
  60. NotificationManager.updateApplicationIconBadge(reset: false)
  61. let chat = DcContext.shared.getChat(chatId: chatId)
  62. if chat.isMuted {
  63. return
  64. }
  65. self?.registerNotificationBackgroundTask(msgId: messageId)
  66. let content = UNMutableNotificationContent()
  67. let msg = DcMsg(id: messageId)
  68. content.title = chat.isGroup ? chat.name : msg.getSenderName(msg.fromContact)
  69. content.body = msg.summary(chars: 80) ?? ""
  70. content.subtitle = chat.isGroup ? msg.getSenderName(msg.fromContact) : ""
  71. content.userInfo = ui
  72. content.sound = .default
  73. if msg.type == DC_MSG_IMAGE || msg.type == DC_MSG_GIF,
  74. let url = msg.fileURL {
  75. do {
  76. // make a copy of the file first since UNNotificationAttachment will move attached files into the attachment data store
  77. // so that they can be accessed by all of the appropriate processes
  78. let tempUrl = url.deletingLastPathComponent()
  79. .appendingPathComponent("notification_tmp")
  80. .appendingPathExtension(url.pathExtension)
  81. try FileManager.default.copyItem(at: url, to: tempUrl)
  82. if let attachment = try? UNNotificationAttachment(identifier: Constants.notificationIdentifier, url: tempUrl, options: nil) {
  83. content.attachments = [attachment]
  84. }
  85. } catch let error {
  86. logger.error("Failed to copy file \(url) for notification preview generation: \(error)")
  87. }
  88. }
  89. let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
  90. let accountEmail = DcContact(id: Int(DC_CONTACT_ID_SELF)).email
  91. if #available(iOS 12.0, *) {
  92. content.threadIdentifier = "\(accountEmail)\(chatId)"
  93. }
  94. let request = UNNotificationRequest(identifier: "\(Constants.notificationIdentifier).\(accountEmail).\(chatId).\(msg.messageId)",
  95. content: content,
  96. trigger: trigger)
  97. UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
  98. logger.info("notifications: added \(content.title) \(content.body) \(content.userInfo)")
  99. self?.unregisterNotificationBackgroundTask(msgId: messageId)
  100. }
  101. }
  102. }
  103. }
  104. private func initMsgsNoticedObserver() {
  105. msgsNoticedObserver = NotificationCenter.default.addObserver(
  106. forName: dcMsgsNoticed,
  107. object: nil, queue: OperationQueue.main
  108. ) { notification in
  109. if !UserDefaults.standard.bool(forKey: "notifications_disabled"),
  110. let ui = notification.userInfo,
  111. let chatId = ui["chat_id"] as? Int {
  112. NotificationManager.removeNotificationsForChat(chatId: chatId)
  113. }
  114. }
  115. }
  116. private static func removeDeliveredNotificationsFor(chatId: Int) {
  117. var identifiers = [String]()
  118. let nc = UNUserNotificationCenter.current()
  119. nc.getDeliveredNotifications { notifications in
  120. let accountEmail = DcContact(id: Int(DC_CONTACT_ID_SELF)).email
  121. for notification in notifications {
  122. if !notification.request.identifier.containsExact(subSequence: "\(Constants.notificationIdentifier).\(accountEmail).\(chatId)").isEmpty {
  123. identifiers.append(notification.request.identifier)
  124. }
  125. }
  126. nc.removeDeliveredNotifications(withIdentifiers: identifiers)
  127. }
  128. }
  129. private static func removePendingNotificationsFor(chatId: Int) {
  130. var identifiers = [String]()
  131. let nc = UNUserNotificationCenter.current()
  132. nc.getPendingNotificationRequests { notificationRequests in
  133. let accountEmail = DcContact(id: Int(DC_CONTACT_ID_SELF)).email
  134. for request in notificationRequests {
  135. if !request.identifier.containsExact(subSequence: "\(Constants.notificationIdentifier).\(accountEmail).\(chatId)").isEmpty {
  136. identifiers.append(request.identifier)
  137. }
  138. }
  139. nc.removePendingNotificationRequests(withIdentifiers: identifiers)
  140. }
  141. }
  142. deinit {
  143. NotificationCenter.default.removeObserver(self)
  144. }
  145. }