From 18b3ede56c822bbe3c655450edbaff999d476dd0 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sun, 20 Oct 2024 12:50:31 +0200 Subject: [PATCH 1/4] notify reactions from 'Notification Service Extension' --- .../NotificationService.swift | 24 ++++++++++++++++++- .../Helper/NotificationManager.swift | 2 ++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index db0b94443..4f90fc95f 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -55,6 +55,28 @@ class NotificationService: UNNotificationServiceExtension { uniqueChats["\(dcContext.id)-\(chat.id)"] = bestAttemptContent.title messageCount += 1 } + } else if event.id == DC_EVENT_INCOMING_REACTION { + let dcContext = dcAccounts.get(id: event.accountId) + if !dcContext.isMuted() { + let msg = dcContext.getMessage(id: event.data2Int) + let chat = dcContext.getChat(chatId: msg.chatId) + if !chat.isMuted { + let sender = msg.getSenderName(dcContext.getContact(id: msg.fromContactId)) + let summary = (msg.summary(chars: 80) ?? "") + if chat.isGroup { + bestAttemptContent.title = chat.name + } else { + bestAttemptContent.title = sender + } + bestAttemptContent.body = String.localized(stringID: "reaction_by_other", parameter: sender, event.data2String, summary) + bestAttemptContent.userInfo["account_id"] = dcContext.id + bestAttemptContent.userInfo["chat_id"] = chat.id + bestAttemptContent.userInfo["message_id"] = msg.id + + uniqueChats["\(dcContext.id)-\(chat.id)"] = bestAttemptContent.title + messageCount += 1 + } + } } } @@ -68,7 +90,7 @@ class NotificationService: UNNotificationServiceExtension { if messageCount > 1 { bestAttemptContent.userInfo["message_id"] = nil if uniqueChats.count == 1 { - bestAttemptContent.body = String.localized(stringID: "n_messages", parameter: messageCount) + bestAttemptContent.body = String.localized(stringID: "n_messages", parameter: messageCount) // TODO: consider improving summary if there are messages+reactions } else { bestAttemptContent.userInfo["open_as_overview"] = true // leaving chat_id as is removes the notification when one of the chats is opened (does not matter which) bestAttemptContent.title = uniqueChats.values.joined(separator: ", ") diff --git a/deltachat-ios/Helper/NotificationManager.swift b/deltachat-ios/Helper/NotificationManager.swift index 9691f9070..c76e9d68b 100644 --- a/deltachat-ios/Helper/NotificationManager.swift +++ b/deltachat-ios/Helper/NotificationManager.swift @@ -86,6 +86,8 @@ public class NotificationManager { } @objc private func handleIncomingMessage(_ notification: Notification) { + // TODO: handle DC_EVENT_INCOMING_REACTION + // make sure to balance each call to `beginBackgroundTask` with `endBackgroundTask` let backgroundTask = UIApplication.shared.beginBackgroundTask { // we cannot easily stop the task, From 8b227064afe9d029d47585a4fc48461703168a42 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sun, 20 Oct 2024 13:01:32 +0200 Subject: [PATCH 2/4] fix sender --- DcNotificationService/NotificationService.swift | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index 4f90fc95f..76065bf06 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -61,13 +61,9 @@ class NotificationService: UNNotificationServiceExtension { let msg = dcContext.getMessage(id: event.data2Int) let chat = dcContext.getChat(chatId: msg.chatId) if !chat.isMuted { - let sender = msg.getSenderName(dcContext.getContact(id: msg.fromContactId)) + let sender = msg.getSenderName(dcContext.getContact(id: event.data1Int)) let summary = (msg.summary(chars: 80) ?? "") - if chat.isGroup { - bestAttemptContent.title = chat.name - } else { - bestAttemptContent.title = sender - } + bestAttemptContent.title = chat.name bestAttemptContent.body = String.localized(stringID: "reaction_by_other", parameter: sender, event.data2String, summary) bestAttemptContent.userInfo["account_id"] = dcContext.id bestAttemptContent.userInfo["chat_id"] = chat.id From 92523751412db247cc9fd77be577d0b04b221dd6 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sun, 20 Oct 2024 17:13:05 +0200 Subject: [PATCH 3/4] notify reactions while app is running --- DcCore/DcCore/DC/events.swift | 10 ++++++ .../NotificationService.swift | 2 +- .../Helper/NotificationManager.swift | 35 +++++++++++++++++-- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/DcCore/DcCore/DC/events.swift b/DcCore/DcCore/DC/events.swift index a4c5247a2..3b24fc88d 100644 --- a/DcCore/DcCore/DC/events.swift +++ b/DcCore/DcCore/DC/events.swift @@ -7,6 +7,7 @@ public enum Event { public static let messageReadDeliveredFailedReaction = Notification.Name(rawValue: "messageReadDeliveredFailedReaction") public static let incomingMessage = Notification.Name(rawValue: "incomingMessage") public static let incomingMessageOnAnyAccount = Notification.Name(rawValue: "incomingMessageOnAnyAccount") + public static let incomingReaction = Notification.Name(rawValue: "incomingReaction") public static let messagesNoticed = Notification.Name(rawValue: "messagesNoticed") // Chats @@ -140,6 +141,15 @@ public class DcEventHandler { "chat_id": Int(data1), ]) + case DC_EVENT_INCOMING_REACTION: + logger.info("📡[\(accountId)] incoming reaction") + NotificationCenter.default.post(name: Event.incomingReaction, object: nil, userInfo: [ + "account_id": Int(accountId), + "contact_id": Int(data1), + "msg_id": Int(data2), + "reaction": event.data2String + ]) + case DC_EVENT_CONTACTS_CHANGED: if accountId != dcAccounts.getSelected().id { return diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index 76065bf06..c153a22f8 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -61,7 +61,7 @@ class NotificationService: UNNotificationServiceExtension { let msg = dcContext.getMessage(id: event.data2Int) let chat = dcContext.getChat(chatId: msg.chatId) if !chat.isMuted { - let sender = msg.getSenderName(dcContext.getContact(id: event.data1Int)) + let sender = dcContext.getContact(id: event.data1Int).displayName let summary = (msg.summary(chars: 80) ?? "") bestAttemptContent.title = chat.name bestAttemptContent.body = String.localized(stringID: "reaction_by_other", parameter: sender, event.data2String, summary) diff --git a/deltachat-ios/Helper/NotificationManager.swift b/deltachat-ios/Helper/NotificationManager.swift index c76e9d68b..1f6e260c0 100644 --- a/deltachat-ios/Helper/NotificationManager.swift +++ b/deltachat-ios/Helper/NotificationManager.swift @@ -14,6 +14,7 @@ public class NotificationManager { NotificationCenter.default.addObserver(self, selector: #selector(NotificationManager.handleIncomingMessageOnAnyAccount(_:)), name: Event.incomingMessageOnAnyAccount, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(NotificationManager.handleIncomingMessage(_:)), name: Event.incomingMessage, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(NotificationManager.handleIncomingReaction(_:)), name: Event.incomingReaction, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(NotificationManager.handleMessagesNoticed(_:)), name: Event.messagesNoticed, object: nil) } @@ -86,8 +87,6 @@ public class NotificationManager { } @objc private func handleIncomingMessage(_ notification: Notification) { - // TODO: handle DC_EVENT_INCOMING_REACTION - // make sure to balance each call to `beginBackgroundTask` with `endBackgroundTask` let backgroundTask = UIApplication.shared.beginBackgroundTask { // we cannot easily stop the task, @@ -127,4 +126,36 @@ public class NotificationManager { UIApplication.shared.endBackgroundTask(backgroundTask) } } + + @objc private func handleIncomingReaction(_ notification: Notification) { + let backgroundTask = UIApplication.shared.beginBackgroundTask { + logger.info("incoming-reaction-task will end soon") + } + + DispatchQueue.global().async { [weak self] in + guard let self, let ui = notification.userInfo else { return } + let eventContext = dcAccounts.get(id: ui["account_id"] as? Int ?? 0) + if !eventContext.isMuted() { + let msg = eventContext.getMessage(id: ui["msg_id"] as? Int ?? 0) + let chat = eventContext.getChat(chatId: msg.chatId) + if !chat.isMuted { + let contact = eventContext.getContact(id: ui["contact_id"] as? Int ?? 0) + let summary = (msg.summary(chars: 80) ?? "") + let reaction = ui["reaction"] as? String ?? "" + + let content = UNMutableNotificationContent() + content.title = chat.name + content.body = String.localized(stringID: "reaction_by_other", parameter: contact.displayName, reaction, summary) + content.userInfo["account_id"] = eventContext.id + content.userInfo["chat_id"] = chat.id + content.userInfo["message_id"] = msg.id + + let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil) + UNUserNotificationCenter.current().add(request, withCompletionHandler: nil) + } + } + + UIApplication.shared.endBackgroundTask(backgroundTask) // this line mist be reached to balance call to `beginBackgroundTask` above + } + } } From fdc0ac705b3d03991525f1b992e2fd9bbbba5d51 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sun, 20 Oct 2024 20:50:27 +0200 Subject: [PATCH 4/4] better summaries, when messages and reactions are combined --- .../NotificationService.swift | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/DcNotificationService/NotificationService.swift b/DcNotificationService/NotificationService.swift index c153a22f8..7c7ab057e 100644 --- a/DcNotificationService/NotificationService.swift +++ b/DcNotificationService/NotificationService.swift @@ -31,6 +31,7 @@ class NotificationService: UNNotificationServiceExtension { UserDefaults.setNseFetching(false) var messageCount = 0 + var reactionCount = 0 var uniqueChats: [String: String] = [:] while true { guard let event = eventEmitter.getNextEvent() else { break } @@ -70,27 +71,34 @@ class NotificationService: UNNotificationServiceExtension { bestAttemptContent.userInfo["message_id"] = msg.id uniqueChats["\(dcContext.id)-\(chat.id)"] = bestAttemptContent.title - messageCount += 1 + reactionCount += 1 } } } } - if messageCount == 0 { + if (messageCount + reactionCount) == 0 { dcAccounts.closeDatabase() UserDefaults.pushToDebugArray(String(format: "OK0 %.3fs", Double(Date().timeIntervalSince1970) - nowTimestamp)) contentHandler(silenceNotification()) } else { bestAttemptContent.badge = dcAccounts.getFreshMessageCount() as NSNumber dcAccounts.closeDatabase() - if messageCount > 1 { + if (messageCount + reactionCount) > 1 { bestAttemptContent.userInfo["message_id"] = nil - if uniqueChats.count == 1 { - bestAttemptContent.body = String.localized(stringID: "n_messages", parameter: messageCount) // TODO: consider improving summary if there are messages+reactions + + if messageCount > 0 && reactionCount > 0 { + bestAttemptContent.body = String.localized(stringID: "n_messages", parameter: messageCount) + + ", " + String.localized(stringID: "n_reactions", parameter: reactionCount) + } else if messageCount > 0 { + bestAttemptContent.body = String.localized(stringID: "n_messages", parameter: messageCount) } else { - bestAttemptContent.userInfo["open_as_overview"] = true // leaving chat_id as is removes the notification when one of the chats is opened (does not matter which) + bestAttemptContent.body = String.localized(stringID: "n_reactions", parameter: reactionCount) + } + + if uniqueChats.count > 1 { + bestAttemptContent.userInfo["open_as_overview"] = true // leaving chat_id removes the notification when one of the chats is opened bestAttemptContent.title = uniqueChats.values.joined(separator: ", ") - bestAttemptContent.body = String.localized(stringID: "n_messages_in_m_chats", parameter: messageCount, uniqueChats.count) } } if #available(iOS 15.0, *) {