ios: show accept/reject dialog when tapping a chat-invitation notification

A plain tap on a contact-request notification carries the legacy contact
request chatId, which has no openable chat view, so unlike a message it
could not be opened - previously the tap only switched profile and left a
blank screen. Now the tap surfaces the same accept/reject dialog as the
chat-list row: NtfManager sets chatModel.showingContactRequest (only when
the request is loaded, which changeActiveUser does synchronously) and drops
any open chat, and ChatListView presents a model-driven confirmationDialog.
The dialog buttons are extracted into a shared contactRequestDialogButtons
view builder reused by the chat-list row.
This commit is contained in:
Narasimha-sc
2026-07-21 19:18:24 +00:00
parent d89010fbea
commit 1b97770a3b
4 changed files with 38 additions and 10 deletions
+2
View File
@@ -413,6 +413,8 @@ final class ChatModel: ObservableObject {
@Published var notificationPreview: NotificationPreviewMode = ntfPreviewModeGroupDefault.get()
// pending notification actions
@Published var ntfContactRequest: NTFContactRequest?
// contact request whose accept/reject dialog should be surfaced after opening from a notification tap
@Published var showingContactRequest: NTFContactRequest?
@Published var ntfCallInvitationAction: (ChatId, NtfCallAction)?
// current WebRTC call
@Published var callInvitations: Dictionary<ChatId, RcvCallInvitation> = [:]
+15 -5
View File
@@ -64,12 +64,22 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
// switched profile's chats, so drop it to land on the chat list instead of a blank chat view
chatModel.chatId = nil
}
if content.categoryIdentifier == ntfCategoryContactRequest && action == ntfActionAcceptContact,
if content.categoryIdentifier == ntfCategoryContactRequest,
let chatId = content.userInfo["chatId"] as? String {
if case let .contactRequest(contactRequest) = chatModel.getChat(chatId)?.chatInfo {
Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) }
} else {
chatModel.ntfContactRequest = NTFContactRequest(chatId: chatId)
if action == ntfActionAcceptContact {
if case let .contactRequest(contactRequest) = chatModel.getChat(chatId)?.chatInfo {
Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) }
} else {
chatModel.ntfContactRequest = NTFContactRequest(chatId: chatId)
}
} else if action == UNNotificationDefaultActionIdentifier,
case .contactRequest = chatModel.getChat(chatId)?.chatInfo {
// a plain tap (not the Accept button) surfaces the accept/reject dialog for the request;
// a contact request has no openable chat view, so it cannot be opened like a message.
// require the request to be loaded (changeActiveUser above loads chats synchronously)
// so the dialog always has buttons, and drop any open chat so it presents over the list
chatModel.chatId = nil
chatModel.showingContactRequest = NTFContactRequest(chatId: chatId)
}
} else if let (chatId, ntfAction) = ntfCallAction(content, action) {
if let invitation = chatModel.callInvitations.removeValue(forKey: chatId) {
@@ -518,11 +518,7 @@ struct ChatListNavLink: View {
.contentShape(Rectangle())
.onTapGesture { showContactRequestDialog = true }
.confirmationDialog("Accept connection request?", isPresented: $showContactRequestDialog, titleVisibility: .visible) {
Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) } }
if !ChatModel.shared.addressShortLinkDataSet {
Button("Accept incognito") { Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) } }
}
Button("Reject (sender NOT notified)", role: .destructive) { Task { await rejectContactRequest(contactRequest.apiId) } }
contactRequestDialogButtons(contactRequest)
}
}
@@ -685,6 +681,17 @@ extension View {
}
}
// Buttons for the "Accept connection request?" dialog, shared by the chat-list row and the
// dialog surfaced when a contact-request notification is tapped (ChatListView).
@ViewBuilder
func contactRequestDialogButtons(_ contactRequest: UserContactRequest) -> some View {
Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) } }
if !ChatModel.shared.addressShortLinkDataSet {
Button("Accept incognito") { Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) } }
}
Button("Reject (sender NOT notified)", role: .destructive) { Task { await rejectContactRequest(contactRequest.apiId) } }
}
func rejectContactRequestAlert(_ contactRequestId: Int64) -> Alert {
Alert(
title: Text("Reject contact request"),
@@ -218,6 +218,15 @@ struct ChatListView: View {
}
}
}
.confirmationDialog("Accept connection request?", isPresented: Binding(
get: { chatModel.showingContactRequest != nil },
set: { if !$0 { chatModel.showingContactRequest = nil } }
), titleVisibility: .visible) {
if let ncr = chatModel.showingContactRequest,
case let .contactRequest(contactRequest) = chatModel.getChat(ncr.chatId)?.chatInfo {
contactRequestDialogButtons(contactRequest)
}
}
.environmentObject(chatTagsModel)
}