From b9525704c900b4fb89e91b1a00f46a087f6d092b Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Fri, 7 Aug 2026 14:16:45 +0000 Subject: [PATCH] apply the busy gate to the whole iOS picker, not just one row The 500ms switchingProfileByTimeout delay left every profile and incognito row live while a change was already in flight, so a second tap could run apiChangeConnectionUser against a connection recreateConn was deleting. Gate hit testing on busy instead, which subsumes the per-row disabled. Also mirror the users.append fallback from the compose picker: a failed listUsersAsync left the new profile absent from the list it was selected in. --- .../ComposeMessage/ContextProfilePickerView.swift | 3 +++ apps/ios/Shared/Views/NewChat/NewChatView.swift | 14 ++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift index a290603add..27ff0573d2 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift @@ -352,6 +352,9 @@ struct ContextProfilePickerView: View { private func incognitoOption() -> some View { Button { + // As in profilerPickerUserOption: a failed changeProfile would leave the + // incognito default on while the picker still shows the chat profile. + if busy { return } if !chat.chatInfo.profileChangeProhibited { if incognitoDefault { listExpanded.toggle() diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index e40fb8a2da..2299ebc587 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -516,13 +516,15 @@ private struct ActiveProfilePicker: View { } - // The row must be dead from the moment work starts: switchingProfileByTimeout only - // latches half a second later, to keep the spinner from flickering. + // The picker must be dead from the moment work starts: switchingProfileByTimeout only + // latches half a second later, to keep the spinner from flickering, and in that window + // a second row tap would run apiChangeConnectionUser against a connection that + // recreateConn is already deleting. private var busy: Bool { creatingProfile || switchingProfileByTimeout || profileSwitchStatus != .idle } @ViewBuilder private func viewBody() -> some View { profilePicker() - .allowsHitTesting(!switchingProfileByTimeout) + .allowsHitTesting(!busy) .modifier(ThemedBackground(grouped: true)) .overlay { if switchingProfileByTimeout { @@ -586,7 +588,6 @@ private struct ActiveProfilePicker: View { Spacer() } } - .disabled(busy) } // Created without activating, then routed through the same selectedProfile path as @@ -638,6 +639,11 @@ private struct ActiveProfilePicker: View { await MainActor.run { if let updatedUsers = updatedUsers { chatModel.users = updatedUsers } profiles = chatModel.users.map { $0.user } + // listUsersAsync failed, so chatModel.users predates the creation: without this + // selectedProfile below points at a profile that has no row in the picker. + if !profiles.contains(where: { $0.userId == newUser.userId }) { + profiles.append(newUser) + } showAddProfile = false selectedProfile = newUser profileSwitchStatus = .switchingUser