Files
simplex-chat/apps/ios/Shared/Views/Chat/ComposeMessage/ContextContactRequestActionsView.swift
T
Narasimha-sc 58aee54ad6 android, desktop, ios: do not offer to accept or join twice
An invitation being accepted is a fact about the invitation, not about the
view that shows it, but each view kept its own flag. The chat list row, the
long-press menu, the accept alert, the composer banner, the chat item
"tap to join" and the notification action therefore disagreed: accepting
from one left the others fully enabled, and the second accept reached the
core with an invitation the first one had already used.

ChatModel now records which contact requests are being accepted and which
groups are being joined. The shared acceptContactRequest and the new
joinGroup maintain it, so every caller participates without changing its
call site, and every view derives from it instead of owning a flag. The
record is taken before the request is sent, so a second tap in the same
view is blocked as well.

The connect composer no longer renders in a group's support scope: on
desktop that is a second view of the same group next to the main one, and
it offered its own Join button.
2026-08-20 11:43:11 +00:00

92 lines
3.1 KiB
Swift

//
// ContextContactRequestActionsView.swift
// SimpleX (iOS)
//
// Created by spaced4ndy on 02.05.2025.
// Copyright © 2025 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct ContextContactRequestActionsView: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
var contactRequestId: Int64
@UserDefault(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial
@State private var progressByTimeout = false
private var inProgress: Bool { chatModel.acceptingContactRequests.contains(contactRequestId) }
var body: some View {
HStack(spacing: 0) {
Button(role: .destructive, action: showRejectRequestAlert) {
Label("Reject", systemImage: "multiply")
}
.frame(maxWidth: .infinity, minHeight: 60)
Button {
if ChatModel.shared.addressShortLinkDataSet {
acceptRequest()
} else {
showAcceptRequestAlert()
}
} label: {
Label("Accept", systemImage: "checkmark")
}
.frame(maxWidth: .infinity, minHeight: 60)
}
.disabled(inProgress)
.frame(maxWidth: .infinity)
.background(ToolbarMaterial.material(toolbarMaterial))
.opacity(progressByTimeout ? 0.4 : 1)
.overlay {
if progressByTimeout {
ProgressView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
.progressByTimeout(inProgress, $progressByTimeout)
}
private func showRejectRequestAlert() {
showAlert(
NSLocalizedString("Reject contact request", comment: "alert title"),
message: NSLocalizedString("The sender will NOT be notified", comment: "alert message"),
actions: {[
UIAlertAction(title: NSLocalizedString("Reject", comment: "alert action"), style: .destructive) { _ in
Task { await rejectContactRequest(contactRequestId, dismissToChatList: true) }
},
cancelAlertAction
]}
)
}
private func showAcceptRequestAlert() {
showAlert(
NSLocalizedString("Accept contact request", comment: "alert title"),
actions: {[
UIAlertAction(title: NSLocalizedString("Accept", comment: "alert action"), style: .default) { _ in
acceptRequest()
},
UIAlertAction(title: NSLocalizedString("Accept incognito", comment: "alert action"), style: .default) { _ in
acceptRequest(incognito: true)
},
cancelAlertAction
]}
)
}
private func acceptRequest(incognito: Bool = false) {
Task {
await acceptContactRequest(incognito: incognito, contactRequestId: contactRequestId)
}
}
}
#Preview {
ContextContactRequestActionsView(
contactRequestId: 1
)
}