From d947d2c23b465baa5c7eda17e3c22963dea5eb3f Mon Sep 17 00:00:00 2001 From: Anian Schleyer Date: Sat, 22 Feb 2025 01:16:53 +0100 Subject: [PATCH 1/4] Add handling for notification actions --- .../Sources/ArtemisKit/AppDelegate.swift | 33 +++++++++++++++++ .../NotificationMessageResponseHandler.swift | 37 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 ArtemisKit/Sources/Messages/Services/NotificationMessageResponseHandler.swift diff --git a/ArtemisKit/Sources/ArtemisKit/AppDelegate.swift b/ArtemisKit/Sources/ArtemisKit/AppDelegate.swift index 77b2028a..af42d2bd 100644 --- a/ArtemisKit/Sources/ArtemisKit/AppDelegate.swift +++ b/ArtemisKit/Sources/ArtemisKit/AppDelegate.swift @@ -11,6 +11,7 @@ import UIKit import UserNotifications import UserStore import PushNotifications +import Messages import Navigation import Common @@ -30,6 +31,7 @@ public class AppDelegate: UIResponder, UIApplicationDelegate { private func registerForPushNotifications() { UNUserNotificationCenter.current().delegate = self + PushNotificationHandler.registerNotificationCategories() } } @@ -88,6 +90,13 @@ extension AppDelegate: UNUserNotificationCenterDelegate { didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void ) { + guard handleResponse(response) else { + // If handleResponse returns false, we don't need to perform additional work + log.info("Handled notification action. Not opening deep link.") + completionHandler() + return + } + let userInfo = response.notification.request.content.userInfo guard let targetURL = PushNotificationResponseHandler.getTarget(userInfo: userInfo) else { log.error("Could not handle click on push notification!") @@ -103,6 +112,30 @@ extension AppDelegate: UNUserNotificationCenterDelegate { // maybe add as param in handle above completionHandler() } + + /// Handles actions triggered from a user interacting with a notification. + /// Returns whether the corresponding deep link should be opened. + private func handleResponse(_ response: UNNotificationResponse) -> Bool { + if response.actionIdentifier == PushNotificationActionIdentifiers.reply { + guard + let communicationInfoData = + response + .notification + .request + .content + .userInfo[PushNotificationUserInfoKeys.communicationInfo] as? Data, + let communicationInfo = try? PushNotificationCommunicationInfo(with: communicationInfoData), + let textResponse = response as? UNTextInputNotificationResponse else { + return true + } + + NotificationMessageResponseHandler.handle(responseText: textResponse.userText, + info: communicationInfo) + + return false + } + return true + } } // Define initializer diff --git a/ArtemisKit/Sources/Messages/Services/NotificationMessageResponseHandler.swift b/ArtemisKit/Sources/Messages/Services/NotificationMessageResponseHandler.swift new file mode 100644 index 00000000..d2108463 --- /dev/null +++ b/ArtemisKit/Sources/Messages/Services/NotificationMessageResponseHandler.swift @@ -0,0 +1,37 @@ +// +// NotificationMessageResponseHandler.swift +// ArtemisKit +// +// Created by Anian Schleyer on 22.02.25. +// + +import PushNotifications +import SharedModels +import UserStore + +public struct NotificationMessageResponseHandler { + public static func handle(responseText: String, info: PushNotificationCommunicationInfo) { + let courseId = info.courseId + let channelId = Int64(info.channelId) + let messageId = Int64(info.messageId) ?? 0 + Task { + var message = Message(id: messageId) + message.conversation = .channel(conversation: .init(id: channelId)) + let result = await MessagesServiceFactory.shared.sendAnswerMessage(for: courseId, + message: message, + content: responseText) + switch result { + case .failure: + // Save message to try again later in case of failure + let host = UserSessionFactory.shared.institution?.baseURL?.host() ?? "" + _ = try? await MessagesRepository.shared.insertMessage(host: host, + courseId: courseId, + conversationId: Int(channelId), + messageId: Int(messageId), + answerMessageDraft: responseText) + default: + break + } + } + } +} From 9196d844779b02570d7f7f25f36805d490a30392 Mon Sep 17 00:00:00 2001 From: Anian Schleyer Date: Sat, 22 Feb 2025 01:27:05 +0100 Subject: [PATCH 2/4] Rename handleNotificationResponse --- ArtemisKit/Sources/ArtemisKit/AppDelegate.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ArtemisKit/Sources/ArtemisKit/AppDelegate.swift b/ArtemisKit/Sources/ArtemisKit/AppDelegate.swift index af42d2bd..c94976da 100644 --- a/ArtemisKit/Sources/ArtemisKit/AppDelegate.swift +++ b/ArtemisKit/Sources/ArtemisKit/AppDelegate.swift @@ -90,8 +90,8 @@ extension AppDelegate: UNUserNotificationCenterDelegate { didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void ) { - guard handleResponse(response) else { - // If handleResponse returns false, we don't need to perform additional work + guard handleNotificationResponse(response) else { + // If handleNotificationResponse returns false, we don't need to perform additional work log.info("Handled notification action. Not opening deep link.") completionHandler() return @@ -115,7 +115,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate { /// Handles actions triggered from a user interacting with a notification. /// Returns whether the corresponding deep link should be opened. - private func handleResponse(_ response: UNNotificationResponse) -> Bool { + private func handleNotificationResponse(_ response: UNNotificationResponse) -> Bool { if response.actionIdentifier == PushNotificationActionIdentifiers.reply { guard let communicationInfoData = From 34ec5a9047ee56742f213e104c1e827caa5d6371 Mon Sep 17 00:00:00 2001 From: Anian Schleyer Date: Sat, 22 Feb 2025 01:31:50 +0100 Subject: [PATCH 3/4] Refactor handle method --- ArtemisKit/Sources/ArtemisKit/AppDelegate.swift | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/ArtemisKit/Sources/ArtemisKit/AppDelegate.swift b/ArtemisKit/Sources/ArtemisKit/AppDelegate.swift index c94976da..73e984b7 100644 --- a/ArtemisKit/Sources/ArtemisKit/AppDelegate.swift +++ b/ArtemisKit/Sources/ArtemisKit/AppDelegate.swift @@ -118,13 +118,10 @@ extension AppDelegate: UNUserNotificationCenterDelegate { private func handleNotificationResponse(_ response: UNNotificationResponse) -> Bool { if response.actionIdentifier == PushNotificationActionIdentifiers.reply { guard - let communicationInfoData = - response - .notification - .request - .content - .userInfo[PushNotificationUserInfoKeys.communicationInfo] as? Data, - let communicationInfo = try? PushNotificationCommunicationInfo(with: communicationInfoData), + let infoData = response + .notification.request.content + .userInfo[PushNotificationUserInfoKeys.communicationInfo] as? Data, + let communicationInfo = try? PushNotificationCommunicationInfo(with: infoData), let textResponse = response as? UNTextInputNotificationResponse else { return true } From aa3e294afae63a0a1ba8176a96d362e801b3227a Mon Sep 17 00:00:00 2001 From: Anian Schleyer Date: Sat, 22 Feb 2025 01:35:00 +0100 Subject: [PATCH 4/4] Call save explicitly in case of sending failure --- .../NotificationMessageResponseHandler.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ArtemisKit/Sources/Messages/Services/NotificationMessageResponseHandler.swift b/ArtemisKit/Sources/Messages/Services/NotificationMessageResponseHandler.swift index d2108463..25e0b930 100644 --- a/ArtemisKit/Sources/Messages/Services/NotificationMessageResponseHandler.swift +++ b/ArtemisKit/Sources/Messages/Services/NotificationMessageResponseHandler.swift @@ -24,11 +24,13 @@ public struct NotificationMessageResponseHandler { case .failure: // Save message to try again later in case of failure let host = UserSessionFactory.shared.institution?.baseURL?.host() ?? "" - _ = try? await MessagesRepository.shared.insertMessage(host: host, - courseId: courseId, - conversationId: Int(channelId), - messageId: Int(messageId), - answerMessageDraft: responseText) + let repository = await MessagesRepository.shared + _ = try? await repository.insertMessage(host: host, + courseId: courseId, + conversationId: Int(channelId), + messageId: Int(messageId), + answerMessageDraft: responseText) + await repository.save() default: break }