NotificationManager.swift 7.9 KB

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