From f0a3d163ddc12f00ddf789f95e271a3a3db6b63f Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 29 Mar 2026 17:33:18 +0100 Subject: [PATCH] android, desktop: icon for swipe to reply (#6723) * remove swipe icon * icon for swipe to reply * fix --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- .../simplex/common/views/chat/ChatView.kt | 24 +++++-------------- .../common/views/chat/item/ChatItemView.kt | 18 ++++++++++++-- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 51b4a7e6bc..aa1707cc7f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -1849,7 +1849,7 @@ fun BoxScope.ChatItemsList( } @Composable - fun ChatItemViewShortHand(cItem: ChatItem, itemSeparation: ItemSeparation, range: State, fillMaxWidth: Boolean = true) { + fun ChatItemViewShortHand(cItem: ChatItem, itemSeparation: ItemSeparation, range: State, fillMaxWidth: Boolean = true, swipeOffset: Float = 0f) { tryOrShowError("${cItem.id}ChatItem", error = { CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart) }) { @@ -1863,7 +1863,7 @@ fun BoxScope.ChatItemsList( highlightedItems.value = setOf() } } - ChatItemView(chatsCtx, remoteHostId, chat, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, hoveredItemId = hoveredItemId, range = range, searchIsNotBlank = searchValueIsNotBlank, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems, reversedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, archiveReports = archiveReports, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, scrollToItemId = scrollToItemId, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, showMemberInfo = showMemberInfo, showChatInfo = showChatInfo, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) + ChatItemView(chatsCtx, remoteHostId, chat, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, hoveredItemId = hoveredItemId, range = range, searchIsNotBlank = searchValueIsNotBlank, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems, reversedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, archiveReports = archiveReports, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, scrollToItemId = scrollToItemId, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, showMemberInfo = showMemberInfo, showChatInfo = showChatInfo, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp, swipeOffset = swipeOffset) } } @@ -1922,18 +1922,6 @@ fun BoxScope.ChatItemsList( val selectionVisible = selectedChatItems.value != null && cItem.canBeDeletedForSelf val selectionOffset by animateDpAsState(if (selectionVisible && !sent) 4.dp + 22.dp * fontSizeMultiplier else 0.dp) val swipeableOrSelectionModifier = (if (selectionVisible) Modifier else swipeableModifier).graphicsLayer { translationX = selectionOffset.toPx() } - // Reply icon revealed on swipe - val swipeOffset = dismissState.offset.value - val swipeThreshold = with(LocalDensity.current) { 30.dp.toPx() } - Icon( - painterResource(MR.images.ic_reply), - contentDescription = null, - tint = MaterialTheme.colors.secondary, - modifier = Modifier - .align(Alignment.CenterEnd) - .padding(end = 12.dp) - .alpha(((-swipeOffset) / swipeThreshold).coerceIn(0f, 1f)) - ) if (chatInfo is ChatInfo.Group) { if (cItem.chatDir is CIDirection.GroupRcv) { if (showAvatar) { @@ -1996,7 +1984,7 @@ fun BoxScope.ChatItemsList( MemberImage(member) } Box(modifier = Modifier.padding(top = 2.dp, start = 4.dp).chatItemOffset(cItem, itemSeparation.largeGap, revealed = revealed.value)) { - ChatItemViewShortHand(cItem, itemSeparation, range, false) + ChatItemViewShortHand(cItem, itemSeparation, range, false, dismissState.offset.value) } } } @@ -2021,7 +2009,7 @@ fun BoxScope.ChatItemsList( .chatItemOffset(cItem, itemSeparation.largeGap, revealed = revealed.value) .then(swipeableOrSelectionModifier) ) { - ChatItemViewShortHand(cItem, itemSeparation, range) + ChatItemViewShortHand(cItem, itemSeparation, range, swipeOffset = dismissState.offset.value) } } } @@ -2036,7 +2024,7 @@ fun BoxScope.ChatItemsList( .chatItemOffset(cItem, itemSeparation.largeGap, revealed = revealed.value) .then(if (selectionVisible) Modifier else swipeableModifier) ) { - ChatItemViewShortHand(cItem, itemSeparation, range) + ChatItemViewShortHand(cItem, itemSeparation, range, swipeOffset = dismissState.offset.value) } } } @@ -2054,7 +2042,7 @@ fun BoxScope.ChatItemsList( .chatItemOffset(cItem, itemSeparation.largeGap, revealed = revealed.value) .then(if (!selectionVisible || !sent) swipeableOrSelectionModifier else Modifier) ) { - ChatItemViewShortHand(cItem, itemSeparation, range) + ChatItemViewShortHand(cItem, itemSeparation, range, swipeOffset = dismissState.offset.value) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 633d6c454e..05c84db4c3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -10,6 +10,7 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.* import androidx.compose.ui.graphics.* @@ -109,6 +110,7 @@ fun ChatItemView( showTimestamp: Boolean, itemSeparation: ItemSeparation, preview: Boolean = false, + swipeOffset: Float = 0f, ) { val cInfo = chat.chatInfo val uriHandler = LocalUriHandler.current @@ -298,8 +300,11 @@ fun ChatItemView( } Column(horizontalAlignment = if (cItem.chatDir.sent) Alignment.End else Alignment.Start) { - Row(verticalAlignment = Alignment.CenterVertically) { - val bubbleInteractionSource = remember { MutableInteractionSource() } + val canReply = (cItem.content is CIContent.SndMsgContent || cItem.content is CIContent.RcvMsgContent) && + cInfo !is ChatInfo.Local && !cItem.isReport && !cItem.meta.isLive && cItem.meta.itemDeleted == null + Box { + Row(verticalAlignment = Alignment.CenterVertically) { + val bubbleInteractionSource = remember { MutableInteractionSource() } val bubbleHovered = bubbleInteractionSource.collectIsHoveredAsState() if (cItem.chatDir.sent) { GoToItemButton(true, bubbleHovered) @@ -800,6 +805,15 @@ fun ChatItemView( if (!cItem.chatDir.sent) { GoToItemButton(false, bubbleHovered) } + } + if (canReply && swipeOffset < 0) { + Icon( + painterResource(MR.images.ic_reply), + contentDescription = null, + modifier = Modifier.align(Alignment.CenterEnd).offset(x = 26.dp).size(18.dp).alpha(minOf(1f, -swipeOffset / 30f)), + tint = MaterialTheme.colors.secondary + ) + } } if (cItem.content.msgContent != null && (cItem.meta.itemDeleted == null || revealed.value) && cItem.reactions.isNotEmpty()) { ChatItemReactions()