ios: notification actions for calls and contact requests with NSE (#829)

* ios: notification actions for calls and contact requests with NSE

* update contact request if already in the list
This commit is contained in:
Evgeny Poberezkin
2022-07-22 08:10:37 +01:00
committed by GitHub
parent e538a9e057
commit 8469f921b7
6 changed files with 68 additions and 22 deletions

View File

@@ -36,6 +36,9 @@ final class ChatModel: ObservableObject {
@Published var tokenStatus: NtfTknStatus?
@Published var notificationMode = NotificationsMode.off
@Published var notificationPreview: NotificationPreviewMode? = ntfPreviewModeGroupDefault.get()
// pending notification actions
@Published var ntfContactRequest: ChatId?
@Published var ntfCallInvitationAction: (ChatId, NtfCallAction)?
// current WebRTC call
@Published var callInvitations: Dictionary<ChatId, RcvCallInvitation> = [:]
@Published var activeCall: Call?

View File

@@ -17,6 +17,11 @@ let ntfActionRejectCall = "NTF_ACT_REJECT_CALL"
private let ntfTimeInterval: TimeInterval = 1
enum NtfCallAction {
case accept
case reject
}
class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
static let shared = NtfManager()
@@ -32,18 +37,19 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
let content = response.notification.request.content
let chatModel = ChatModel.shared
let action = response.actionIdentifier
logger.debug("NtfManager.userNotificationCenter: didReceive: action \(action), categoryIdentifier \(content.categoryIdentifier)")
if content.categoryIdentifier == ntfCategoryContactRequest && action == ntfActionAcceptContact,
let chatId = content.userInfo["chatId"] as? String,
case let .contactRequest(contactRequest) = chatModel.getChat(chatId)?.chatInfo {
Task { await acceptContactRequest(contactRequest) }
} else if content.categoryIdentifier == ntfCategoryCallInvitation && (action == ntfActionAcceptCall || action == ntfActionRejectCall),
let chatId = content.userInfo["chatId"] as? String,
let invitation = chatModel.callInvitations.removeValue(forKey: chatId) {
let cc = CallController.shared
if action == ntfActionAcceptCall {
cc.answerCall(invitation: invitation)
let chatId = content.userInfo["chatId"] as? String {
if case let .contactRequest(contactRequest) = chatModel.getChat(chatId)?.chatInfo {
Task { await acceptContactRequest(contactRequest) }
} else {
cc.endCall(invitation: invitation)
chatModel.ntfContactRequest = chatId
}
} else if let (chatId, ntfAction) = ntfCallAction(content, action) {
if let invitation = chatModel.callInvitations.removeValue(forKey: chatId) {
CallController.shared.callAction(invitation: invitation, action: ntfAction)
} else {
chatModel.ntfCallInvitationAction = (chatId, ntfAction)
}
} else {
chatModel.chatId = content.targetContentIdentifier
@@ -51,6 +57,19 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
handler()
}
private func ntfCallAction(_ content: UNNotificationContent, _ action: String) -> (ChatId, NtfCallAction)? {
if content.categoryIdentifier == ntfCategoryCallInvitation,
let chatId = content.userInfo["chatId"] as? String {
if action == ntfActionAcceptCall {
return (chatId, .accept)
} else if action == ntfActionRejectCall {
return (chatId, .reject)
}
}
return nil
}
// Handle notification when the app is in foreground
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
@@ -103,7 +122,8 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
identifier: ntfCategoryContactRequest,
actions: [UNNotificationAction(
identifier: ntfActionAcceptContact,
title: NSLocalizedString("Accept", comment: "accept contact request via notification")
title: NSLocalizedString("Accept", comment: "accept contact request via notification"),
options: .foreground
)],
intentIdentifiers: [],
hiddenPreviewsBodyPlaceholder: NSLocalizedString("New contact request", comment: "notification")

View File

@@ -669,11 +669,16 @@ func processReceivedMsg(_ res: ChatResponse) async {
m.updateContact(contact)
m.removeChat(contact.activeConn.id)
case let .receivedContactRequest(contactRequest):
m.addChat(Chat(
chatInfo: ChatInfo.contactRequest(contactRequest: contactRequest),
chatItems: []
))
NtfManager.shared.notifyContactRequest(contactRequest)
let cInfo = ChatInfo.contactRequest(contactRequest: contactRequest)
if m.hasChat(contactRequest.id) {
m.updateChatInfo(cInfo)
} else {
m.addChat(Chat(
chatInfo: cInfo,
chatItems: []
))
NtfManager.shared.notifyContactRequest(contactRequest)
}
case let .contactUpdated(toContact):
let cInfo = ChatInfo.direct(contact: toContact)
if m.hasChat(toContact.id) {
@@ -850,8 +855,12 @@ func refreshCallInvitations() throws {
let m = ChatModel.shared
let callInvitations = try apiGetCallInvitations()
m.callInvitations = callInvitations.reduce(into: [ChatId: RcvCallInvitation]()) { result, inv in result[inv.contact.id] = inv }
if let inv = callInvitations.last {
activateCall(inv)
if let (chatId, ntfAction) = m.ntfCallInvitationAction,
let invitation = m.callInvitations.removeValue(forKey: chatId) {
m.ntfCallInvitationAction = nil
CallController.shared.callAction(invitation: invitation, action: ntfAction)
} else if let invitation = callInvitations.last {
activateCall(invitation)
}
}

View File

@@ -120,6 +120,12 @@ struct SimpleXApp: App {
}
}
}
if let chatId = chatModel.ntfContactRequest {
chatModel.ntfContactRequest = nil
if case let .contactRequest(contactRequest) = chatModel.getChat(chatId)?.chatInfo {
Task { await acceptContactRequest(contactRequest) }
}
}
} catch let error {
logger.error("apiGetChats: cannot update chats \(responseError(error))")
}
@@ -128,8 +134,7 @@ struct SimpleXApp: App {
private func updateCallInvitations() {
do {
try refreshCallInvitations()
}
catch let error {
} catch let error {
logger.error("apiGetCallInvitations: cannot update call invitations \(responseError(error))")
}
}

View File

@@ -206,6 +206,13 @@ class CallController: NSObject, ObservableObject {
callManager.endCall(call: call, completed: completed)
}
func callAction(invitation: RcvCallInvitation, action: NtfCallAction) {
switch action {
case .accept: answerCall(invitation: invitation)
case .reject: endCall(invitation: invitation)
}
}
// private func requestTransaction(with action: CXAction) {
// let t = CXTransaction()
// t.addAction(action)

View File

@@ -39,7 +39,7 @@ actor PendingNtfs {
for await ntf in s {
nse.setBestAttemptNtf(ntf)
rcvCount -= 1
if rcvCount == 0 { break }
if rcvCount == 0 || ntf.categoryIdentifier == ntfCategoryCallInvitation { break }
}
logger.debug("PendingNtfs.readStream: exiting")
}
@@ -204,7 +204,9 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, UNMutableNotification
cItem = apiReceiveFile(fileId: file.fileId)?.chatItem ?? cItem
}
}
return (aChatItem.chatId, createMessageReceivedNtf(cInfo, cItem))
return cItem.isCall() ? nil : (aChatItem.chatId, createMessageReceivedNtf(cInfo, cItem))
case let .callInvitation(invitation):
return (invitation.contact.id, createCallInvitationNtf(invitation))
default:
logger.debug("NotificationService processReceivedMsg ignored event: \(res.responseType)")
return nil