123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- import Foundation
- import UserNotifications
- import DcCore
- import UIKit
- public class NotificationManager {
-
- var incomingMsgObserver: NSObjectProtocol?
- var msgsNoticedObserver: NSObjectProtocol?
- private let dcAccounts: DcAccounts
- private var dcContext: DcContext
- init(dcAccounts: DcAccounts) {
- self.dcAccounts = dcAccounts
- self.dcContext = dcAccounts.getSelected()
- initIncomingMsgsObserver()
- initMsgsNoticedObserver()
- }
- public func reloadDcContext() {
- NotificationManager.removeAllNotifications()
- dcContext = dcAccounts.getSelected()
- NotificationManager.updateApplicationIconBadge(dcContext: dcContext, reset: false)
- }
- public static func updateApplicationIconBadge(dcContext: DcContext, reset: Bool) {
- var unreadMessages = 0
- if !reset {
- unreadMessages = dcContext.getFreshMessages().count
- }
- DispatchQueue.main.async {
- UIApplication.shared.applicationIconBadgeNumber = unreadMessages
- }
- }
- public static func removeAllNotifications() {
- let nc = UNUserNotificationCenter.current()
- nc.removeAllDeliveredNotifications()
- nc.removeAllPendingNotificationRequests()
- }
-
- public static func removeNotificationsForChat(dcContext: DcContext, chatId: Int) {
- DispatchQueue.global(qos: .background).async {
- NotificationManager.removePendingNotificationsFor(dcContext: dcContext, chatId: chatId)
- NotificationManager.removeDeliveredNotificationsFor(dcContext: dcContext, chatId: chatId)
- NotificationManager.updateApplicationIconBadge(dcContext: dcContext, reset: false)
- }
- }
- private func initIncomingMsgsObserver() {
- incomingMsgObserver = NotificationCenter.default.addObserver(
- forName: dcNotificationIncoming,
- object: nil, queue: OperationQueue.main
- ) { notification in
- // make sure to balance each call to `beginBackgroundTask` with `endBackgroundTask`
- let backgroundTask = UIApplication.shared.beginBackgroundTask {
- // we cannot easily stop the task,
- // however, this handler should not be called as adding the notification should not take 30 seconds.
- logger.info("notification background task will end soon")
- }
- DispatchQueue.global(qos: .background).async { [weak self] in
- guard let self = self else { return }
- if let ui = notification.userInfo,
- let chatId = ui["chat_id"] as? Int,
- let messageId = ui["message_id"] as? Int,
- !UserDefaults.standard.bool(forKey: "notifications_disabled") {
- NotificationManager.updateApplicationIconBadge(dcContext: self.dcContext, reset: false)
- let chat = self.dcContext.getChat(chatId: chatId)
- if !chat.isMuted {
- let msg = self.dcContext.getMessage(id: messageId)
- let fromContact = self.dcContext.getContact(id: msg.fromContactId)
- let accountEmail = self.dcContext.getContact(id: Int(DC_CONTACT_ID_SELF)).email
- let content = UNMutableNotificationContent()
- content.title = chat.isGroup ? chat.name : msg.getSenderName(fromContact)
- content.body = msg.summary(chars: 80) ?? ""
- content.subtitle = chat.isGroup ? msg.getSenderName(fromContact) : ""
- content.userInfo = ui
- content.sound = .default
- if msg.type == DC_MSG_IMAGE || msg.type == DC_MSG_GIF,
- let url = msg.fileURL {
- do {
- // make a copy of the file first since UNNotificationAttachment will move attached files into the attachment data store
- // so that they can be accessed by all of the appropriate processes
- let tempUrl = url.deletingLastPathComponent()
- .appendingPathComponent("notification_tmp")
- .appendingPathExtension(url.pathExtension)
- try FileManager.default.copyItem(at: url, to: tempUrl)
- if let attachment = try? UNNotificationAttachment(identifier: Constants.notificationIdentifier, url: tempUrl, options: nil) {
- content.attachments = [attachment]
- }
- } catch let error {
- logger.error("Failed to copy file \(url) for notification preview generation: \(error)")
- }
- }
- let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
- if #available(iOS 12.0, *) {
- content.threadIdentifier = "\(accountEmail)\(chatId)"
- }
- let request = UNNotificationRequest(identifier: "\(Constants.notificationIdentifier).\(accountEmail).\(chatId).\(msg.messageId)",
- content: content,
- trigger: trigger)
- UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
- logger.info("notifications: added \(content.title) \(content.body) \(content.userInfo)")
- }
- }
- // this line should always be reached
- // and balances the call to `beginBackgroundTask` above.
- UIApplication.shared.endBackgroundTask(backgroundTask)
- }
- }
- }
- private func initMsgsNoticedObserver() {
- msgsNoticedObserver = NotificationCenter.default.addObserver(
- forName: dcMsgsNoticed,
- object: nil, queue: OperationQueue.main
- ) { [weak self] notification in
- guard let self = self else { return }
- if !UserDefaults.standard.bool(forKey: "notifications_disabled"),
- let ui = notification.userInfo,
- let chatId = ui["chat_id"] as? Int {
- NotificationManager.removeNotificationsForChat(dcContext: self.dcContext, chatId: chatId)
- }
- }
- }
- private static func removeDeliveredNotificationsFor(dcContext: DcContext, chatId: Int) {
- var identifiers = [String]()
- let nc = UNUserNotificationCenter.current()
- nc.getDeliveredNotifications { notifications in
- let accountEmail = dcContext.getContact(id: Int(DC_CONTACT_ID_SELF)).email
- for notification in notifications {
- if !notification.request.identifier.containsExact(subSequence: "\(Constants.notificationIdentifier).\(accountEmail).\(chatId)").isEmpty {
- identifiers.append(notification.request.identifier)
- }
- }
- nc.removeDeliveredNotifications(withIdentifiers: identifiers)
- }
- }
- private static func removePendingNotificationsFor(dcContext: DcContext, chatId: Int) {
- var identifiers = [String]()
- let nc = UNUserNotificationCenter.current()
- nc.getPendingNotificationRequests { notificationRequests in
- let accountEmail = dcContext.getContact(id: Int(DC_CONTACT_ID_SELF)).email
- for request in notificationRequests {
- if !request.identifier.containsExact(subSequence: "\(Constants.notificationIdentifier).\(accountEmail).\(chatId)").isEmpty {
- identifiers.append(request.identifier)
- }
- }
- nc.removePendingNotificationRequests(withIdentifiers: identifiers)
- }
- }
-
- deinit {
- NotificationCenter.default.removeObserver(self)
- }
-
- }
|