From 1b97770a3b48558cbd1bd86c01f685b885eed65e Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Tue, 21 Jul 2026 19:18:24 +0000 Subject: [PATCH] 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. --- apps/ios/Shared/Model/ChatModel.swift | 2 ++ apps/ios/Shared/Model/NtfManager.swift | 20 ++++++++++++++----- .../Views/ChatList/ChatListNavLink.swift | 17 +++++++++++----- .../Shared/Views/ChatList/ChatListView.swift | 9 +++++++++ 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 111dff382a..72ead50dc8 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -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 = [:] diff --git a/apps/ios/Shared/Model/NtfManager.swift b/apps/ios/Shared/Model/NtfManager.swift index 4d2fb6d303..f4ac8e5aca 100644 --- a/apps/ios/Shared/Model/NtfManager.swift +++ b/apps/ios/Shared/Model/NtfManager.swift @@ -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) { diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index 0ed78401b0..8338a8f2cc 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -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"), diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 238ea89e90..1210c387ce 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -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) }