From 1462b8e012170843c5a6fe96593ccf481c2a81f0 Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Thu, 20 Aug 2026 15:37:29 +0000 Subject: [PATCH] android, desktop, ios: change only the progress indicator timeout Reverts the shared helper the timeout change was extracted into, leaving the effect where each view already had it. NewChatView and ViewModifiers are no longer touched by this branch at all - the first was already at 0.5s. --- .../Chat/ChatItem/CIGroupInvitationView.swift | 10 +++++++- .../Chat/ComposeMessage/ComposeView.swift | 10 +++++++- .../ContextContactRequestActionsView.swift | 10 +++++++- .../Views/ChatList/ChatListNavLink.swift | 10 +++++++- .../Shared/Views/Helpers/ViewModifiers.swift | 15 ----------- ...ComposeContextContactRequestActionsView.kt | 12 ++++++++- ...ContextGroupDirectInvitationActionsView.kt | 11 +++++++- .../simplex/common/views/chat/ComposeView.kt | 10 ++++++-- .../views/chat/item/CIGroupInvitationView.kt | 12 ++++++++- .../views/chatlist/ChatListNavLinkView.kt | 10 +++++++- .../common/views/helpers/ProgressByTimeout.kt | 25 ------------------- .../common/views/newchat/NewChatView.kt | 11 +++++++- .../2026-08-20-do-not-accept-or-join-twice.md | 8 +++--- 13 files changed, 99 insertions(+), 55 deletions(-) delete mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ProgressByTimeout.kt diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift index af9b26317a..5bb957afe0 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift @@ -76,7 +76,15 @@ struct CIGroupInvitationView: View { .background { chatItemFrameColor(chatItem, theme).modifier(ChatTailPadding()) } .textSelection(.disabled) .onPreferenceChange(DetermineWidth.Key.self) { frameWidth = $0 } - .progressByTimeout(inProgress, $progressByTimeout) + .onChange(of: inProgress) { inPrgrs in + if inPrgrs { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + progressByTimeout = inProgress + } + } else { + progressByTimeout = false + } + } if action { v.simultaneousGesture(TapGesture().onEnded { joinGroup(groupInvitation.groupId) }) diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index ce809dedf6..3a0769b71e 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -584,7 +584,15 @@ struct ComposeView: View { clearState() } } - .progressByTimeout(composeState.inProgress, $composeState.progressByTimeout) + .onChange(of: composeState.inProgress) { inProgress in + if inProgress { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + composeState.progressByTimeout = composeState.inProgress + } + } else { + composeState.progressByTimeout = false + } + } .confirmationDialog("Attach", isPresented: $showChooseSource, titleVisibility: .visible) { Button("Take picture") { showTakePhoto = true diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextContactRequestActionsView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextContactRequestActionsView.swift index b3a2c71fdb..ec3173028a 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextContactRequestActionsView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextContactRequestActionsView.swift @@ -46,7 +46,15 @@ struct ContextContactRequestActionsView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) } } - .progressByTimeout(inProgress, $progressByTimeout) + .onChange(of: inProgress) { inPrgrs in + if inPrgrs { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + progressByTimeout = inProgress + } + } else { + progressByTimeout = false + } + } } private func showRejectRequestAlert() { diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index 296dba8857..1cf9f9b786 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -94,7 +94,15 @@ struct ChatListNavLink: View { invalidJSONPreview(json) } } - .progressByTimeout(inProgress, $progressByTimeout) + .onChange(of: inProgress) { inPrgrs in + if inPrgrs { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + progressByTimeout = inProgress + } + } else { + progressByTimeout = false + } + } .actionSheet(item: $actionSheet) { $0.actionSheet } } diff --git a/apps/ios/Shared/Views/Helpers/ViewModifiers.swift b/apps/ios/Shared/Views/Helpers/ViewModifiers.swift index cfcf0353ee..902a3f95d7 100644 --- a/apps/ios/Shared/Views/Helpers/ViewModifiers.swift +++ b/apps/ios/Shared/Views/Helpers/ViewModifiers.swift @@ -26,23 +26,8 @@ extension View { self } } - - func progressByTimeout(_ inProgress: Bool, _ progressByTimeout: Binding) -> some View { - task(id: inProgress) { - if inProgress { - try? await Task.sleep(nanoseconds: progressTimeout) - if !Task.isCancelled { - progressByTimeout.wrappedValue = true - } - } else { - progressByTimeout.wrappedValue = false - } - } - } } -private let progressTimeout: UInt64 = 500_000000 - extension Notification.Name { static let chatViewWillBeginScrolling = Notification.Name("chatWillBeginScrolling") } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextContactRequestActionsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextContactRequestActionsView.kt index 40ac86add6..1514f32323 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextContactRequestActionsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextContactRequestActionsView.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -21,6 +22,7 @@ import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.delay @Composable fun ComposeContextContactRequestActionsView( @@ -28,7 +30,15 @@ fun ComposeContextContactRequestActionsView( contactRequestId: Long ) { val inProgress = rememberAcceptingContactRequest(contactRequestId) - val progressByTimeout by rememberProgressByTimeout(inProgress) + var progressByTimeout by rememberSaveable { mutableStateOf(false) } + LaunchedEffect(inProgress.value) { + progressByTimeout = if (inProgress.value) { + delay(500) + inProgress.value + } else { + false + } + } Box( Modifier.height(60.dp), 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 d6092bfa69..6a2d0f367b 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 @@ -28,11 +28,20 @@ fun ComposeContextMemberContactActionsView( groupDirectInv: GroupDirectInvitation ) { val inProgress = rememberSaveable { mutableStateOf(false) } - val progressByTimeout by rememberProgressByTimeout(inProgress) + var progressByTimeout by rememberSaveable { mutableStateOf(false) } + LaunchedEffect(inProgress.value) { + progressByTimeout = if (inProgress.value) { + delay(500) + inProgress.value + } else { + false + } + } KeyChangeEffect(chatModel.chatId.value) { if (inProgress.value) { inProgress.value = false + progressByTimeout = false } } 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 0b79b42185..157d10da36 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 @@ -1573,8 +1573,14 @@ fun ComposeView( chatModel.sharedContent.value = null } - ProgressByTimeoutEffect(composeState.value.inProgress) { - composeState.value = composeState.value.copy(progressByTimeout = it) + LaunchedEffect(composeState.value.inProgress) { + val newProgressByTimeout = if (composeState.value.inProgress) { + delay(500) + composeState.value.inProgress + } else { + false + } + composeState.value = composeState.value.copy(progressByTimeout = newProgressByTimeout) } val relayListExpanded = remember { mutableStateOf(false) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt index 0ff8218e9e..f873f2d14f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.buildAnnotatedString @@ -20,6 +21,7 @@ import chat.simplex.common.views.chatlist.rememberJoiningGroup import chat.simplex.common.views.helpers.* import chat.simplex.common.model.* import chat.simplex.res.MR +import kotlinx.coroutines.delay @Composable fun CIGroupInvitationView( @@ -34,7 +36,15 @@ fun CIGroupInvitationView( val sent = ci.chatDir.sent val action = !sent && groupInvitation.status == CIGroupInvitationStatus.Pending val inProgress = rememberJoiningGroup(groupInvitation.groupId) - val progressByTimeout by rememberProgressByTimeout(inProgress) + var progressByTimeout by rememberSaveable { mutableStateOf(false) } + LaunchedEffect(inProgress.value) { + progressByTimeout = if (inProgress.value) { + delay(500) + inProgress.value + } else { + false + } + } @Composable fun groupInfoView() { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index 92f158f362..25e3c16ddc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -73,7 +73,15 @@ fun ChatListNavLinkView(chat: Chat, nextChatSelected: State) { } is ChatInfo.Group -> { val inProgress = rememberJoiningGroup(chat.chatInfo.groupInfo.groupId) - val progressByTimeout by rememberProgressByTimeout(inProgress) + var progressByTimeout by rememberSaveable { mutableStateOf(false) } + LaunchedEffect(inProgress.value) { + progressByTimeout = if (inProgress.value) { + delay(500) + inProgress.value + } else { + false + } + } val defaultClickAction = { if (!inProgress.value && chatModel.chatId.value != chat.id) scope.launch { groupChatAction(chat.remoteHostId, chat.chatInfo.groupInfo, chatModel) } } ChatListNavLinkLayout( chatLinkPreview = { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ProgressByTimeout.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ProgressByTimeout.kt deleted file mode 100644 index 4d6b97ef05..0000000000 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ProgressByTimeout.kt +++ /dev/null @@ -1,25 +0,0 @@ -package chat.simplex.common.views.helpers - -import androidx.compose.runtime.* -import kotlinx.coroutines.delay - -private const val PROGRESS_TIMEOUT_MS = 500L - -@Composable -fun ProgressByTimeoutEffect(inProgress: Boolean, setProgressByTimeout: (Boolean) -> Unit) { - LaunchedEffect(inProgress) { - if (inProgress) { - delay(PROGRESS_TIMEOUT_MS) - setProgressByTimeout(true) - } else { - setProgressByTimeout(false) - } - } -} - -@Composable -fun rememberProgressByTimeout(inProgress: State): State { - val progressByTimeout = remember { mutableStateOf(false) } - ProgressByTimeoutEffect(inProgress.value) { progressByTimeout.value = it } - return progressByTimeout -} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 3c67cd1aea..d3bca178aa 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -303,7 +303,16 @@ fun ActiveProfilePicker( filteredProfiles(chatModel.users.map { it.user }.sortedBy { !it.activeUser }, searchTextOrPassword.value) } - val progressByTimeout by rememberProgressByTimeout(switchingProfile) + var progressByTimeout by rememberSaveable { mutableStateOf(false) } + + LaunchedEffect(switchingProfile.value) { + progressByTimeout = if (switchingProfile.value) { + delay(500) + switchingProfile.value + } else { + false + } + } @Composable fun ProfilePickerUserOption(user: User) { 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 d843567ff7..f44f66fdf4 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 @@ -158,10 +158,10 @@ Join buttons. The connect composer belongs to the chat itself, not to a scope wi **5. Client: the progress indicator appears after 0.5s instead of 1s** (android, desktop, ios). -The effect behind it was copied into 5 Kotlin and 4 Swift views; it is now one helper per platform, -which is also what makes the threshold a single constant. Two of the Swift copies had drifted — the -deferred write captured the value that started the delay rather than reading the current one, so an -operation that finished before the delay elapsed left the indicator on with nothing to turn it off. +Accepting a request or joining a group usually takes longer than 1s, so the button looked inert for +a second before anything acknowledged the tap - which is what prompts a second tap. The threshold is +per view; extracting the effect the views share was considered and left out, to keep the change to +the timing. ## Alternatives considered and rejected