diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextGroupDirectInvitationActionsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextGroupDirectInvitationActionsView.kt index 6a2d0f367b..f64965f240 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextGroupDirectInvitationActionsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextGroupDirectInvitationActionsView.kt @@ -29,6 +29,14 @@ fun ComposeContextMemberContactActionsView( ) { val inProgress = rememberSaveable { mutableStateOf(false) } var progressByTimeout by rememberSaveable { mutableStateOf(false) } + + KeyChangeEffect(chatModel.chatId.value) { + if (inProgress.value) { + inProgress.value = false + progressByTimeout = false + } + } + LaunchedEffect(inProgress.value) { progressByTimeout = if (inProgress.value) { delay(500) @@ -38,13 +46,6 @@ fun ComposeContextMemberContactActionsView( } } - KeyChangeEffect(chatModel.chatId.value) { - if (inProgress.value) { - inProgress.value = false - progressByTimeout = false - } - } - Box( Modifier.height(60.dp), contentAlignment = Alignment.Center diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index 157d10da36..8f3c72a261 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -654,17 +654,12 @@ fun ComposeView( val mc = checkLinkPreview() sending() val incognito = if (chat.chatInfo.profileChangeProhibited) chat.chatInfo.incognito else chatModel.controller.appPrefs.incognito.get() - chatModel.joiningGroups.add(chat.chatInfo.apiId) - val result = try { - chatModel.controller.apiConnectPreparedGroup( - rh = chat.remoteHostId, - groupId = chat.chatInfo.apiId, - incognito = incognito, - msg = mc - ) - } finally { - chatModel.joiningGroups.remove(chat.chatInfo.apiId) - } + val result = chatModel.controller.apiConnectPreparedGroup( + rh = chat.remoteHostId, + groupId = chat.chatInfo.apiId, + incognito = incognito, + msg = mc + ) if (result != null) { val (groupInfo, relayResults) = result withContext(Dispatchers.Main) { diff --git a/plans/2026-08-20-do-not-accept-or-join-twice.md b/plans/2026-08-20-do-not-accept-or-join-twice.md index f44f66fdf4..02175466b6 100644 --- a/plans/2026-08-20-do-not-accept-or-join-twice.md +++ b/plans/2026-08-20-do-not-accept-or-join-twice.md @@ -138,9 +138,9 @@ contact request id and group id — not by chat id, because the same request app `ChatInfo.ContactRequest` (`<@N`) in the chat list and under `ChatInfo.Direct` (`@N`) in the chat, and those two views must agree. They are maintained by the shared `acceptContactRequest` and by a new shared `joinGroup`, so every caller participates without changing its call site and a failed -accept re-enables the buttons. Every view derives from the record instead of owning a flag, which -deletes the parameter chain, the per-view flags and the `KeyChangeEffect` that existed only to reset -them on chat change. +accept re-enables the buttons. The views this change covers derive from the record instead of owning +a flag, which deletes the parameter chain, the per-view flags and the `KeyChangeEffect` that existed +only to reset them on chat change. The contacts list is not covered - see "Not covered". The record is taken before the request is sent rather than inside the background coroutine, so a second tap in the same view is blocked too — the previous per-view flags were set synchronously at @@ -204,6 +204,12 @@ change addresses it. **`acceptMemberContact` and `apiAcceptMember`** keep their per-view flags; they are the same shape but were left out of scope. +**The contacts list still offers Accept while an accept is in flight** - `ContactListNavView.kt` +opens `contactRequestAlertDialog` without checking, and `ContactListNavLink.swift` has its own +buttons rather than the ones this change gates. It is the same defect in one more view on each +platform, left out to keep the change small; the outcome is now a clear error from fix 1 rather than +the agent error, and the contacts list is a sheet, so the other views are not tappable behind it. + ## Testing - `connecting to channel twice fails` (`tests/ChatTests/Groups.hs`) and `prepare group and connect