Skip to content

Commit 56a1073

Browse files
VelikovPetarclaude
andauthored
Disable swipe-to-reply for deleted messages (#6226)
* Disable swipe-to-reply for deleted messages Co-Authored-By: Claude <noreply@anthropic.com> * PR remarks. --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3b5cd86 commit 56a1073

5 files changed

Lines changed: 63 additions & 20 deletions

File tree

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageItem.kt

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -267,18 +267,17 @@ public fun MessageItem(
267267

268268
val messageAlignment = ChatTheme.messageAlignmentProvider.provideMessageAlignment(messageItem)
269269
val description = stringResource(id = R.string.stream_compose_cd_message_item)
270-
val isSwipable = ChatTheme.messageOptionsTheme.optionVisibility
271-
.canReplyToMessage(
272-
message = message,
273-
ownCapabilities = messageItem.ownCapabilities,
274-
)
270+
val optionVisibility = ChatTheme.messageOptionsTheme.optionVisibility
271+
val isSwipeable = remember(message, messageItem.ownCapabilities, optionVisibility) {
272+
optionVisibility.canReplyToMessage(message, messageItem.ownCapabilities)
273+
}
275274

276275
// Remember the message to ensure updated values are captured in the onReply lambda
277276
val replyMessage by rememberUpdatedState(message)
278277
SwipeToReply(
279278
modifier = modifier,
280279
onReply = { onReply(replyMessage) },
281-
isSwipeable = { isSwipable },
280+
isSwipeable = isSwipeable,
282281
swipeToReplyContent = swipeToReplyContent,
283282
) {
284283
Box(
@@ -735,7 +734,7 @@ private fun getMessageBubbleShape(position: List<MessagePosition>, ownsMessage:
735734
*
736735
* @param modifier Modifier for styling.
737736
* @param onReply Handler when the user swipes to reply.
738-
* @param isSwipeable Handler to determine if the message is swipeable.
737+
* @param isSwipeable Indicator if swipe-to-reply is enabled.
739738
* @param swipeToReplyContent The content to show when swiping to reply.
740739
* @param content The swipeable content to show when not swiping to reply.
741740
*/
@@ -744,7 +743,7 @@ private fun getMessageBubbleShape(position: List<MessagePosition>, ownsMessage:
744743
private fun SwipeToReply(
745744
modifier: Modifier = Modifier,
746745
onReply: () -> Unit = {},
747-
isSwipeable: () -> Boolean = { true },
746+
isSwipeable: Boolean = true,
748747
swipeToReplyContent: @Composable RowScope.() -> Unit,
749748
content: @Composable () -> Unit,
750749
) {
@@ -779,8 +778,8 @@ private fun SwipeToReply(
779778
.fillMaxWidth()
780779
.onSizeChanged { rowWidth = it.width.toFloat() }
781780
.offset { IntOffset(x = offset.value.roundToInt(), y = 0) }
782-
.pointerInput(swipeToReplyWidth) {
783-
if (isSwipeable()) {
781+
.pointerInput(swipeToReplyWidth, isSwipeable) {
782+
if (isSwipeable) {
784783
detectHorizontalDragGestures(
785784
onHorizontalDrag = { change, dragAmount ->
786785
// Only consume if horizontal drag dominates vertical

stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/util/extensions/MessageOptionItemVisibilityTest.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import org.amshove.kluent.`should be`
3232
import org.junit.jupiter.params.ParameterizedTest
3333
import org.junit.jupiter.params.provider.Arguments
3434
import org.junit.jupiter.params.provider.MethodSource
35+
import java.util.Date
3536

3637
internal class MessageOptionItemVisibilityTest {
3738

@@ -157,27 +158,38 @@ internal class MessageOptionItemVisibilityTest {
157158

158159
@JvmStatic
159160
fun canReplyToMessageArguments() = listOf(
161+
// case: reply disabled
160162
Arguments.of(
161163
MessageOptionItemVisibility(isReplyVisible = false),
162-
randomMessage(),
164+
randomMessage(deletedAt = null, deletedForMe = false),
163165
randomChannelCapabilities(),
164166
false,
165167
),
168+
// case: message not synced
166169
Arguments.of(
167170
MessageOptionItemVisibility(),
168171
randomMessage(syncStatus = randomSyncStatus(exclude = listOf(SyncStatus.COMPLETED))),
169172
randomChannelCapabilities(),
170173
false,
171174
),
175+
// case: no QUOTE_MESSAGE capability
172176
Arguments.of(
173177
MessageOptionItemVisibility(),
174-
randomMessage(),
178+
randomMessage(deletedAt = null, deletedForMe = false),
175179
randomChannelCapabilities(exclude = setOf(ChannelCapabilities.QUOTE_MESSAGE)),
176180
false,
177181
),
182+
// case: message is deleted
178183
Arguments.of(
179184
MessageOptionItemVisibility(isReplyVisible = true),
180-
randomMessage(syncStatus = SyncStatus.COMPLETED),
185+
randomMessage(syncStatus = SyncStatus.COMPLETED, deletedAt = Date(), deletedForMe = false),
186+
randomChannelCapabilities(include = setOf(ChannelCapabilities.QUOTE_MESSAGE)),
187+
false,
188+
),
189+
// case: all conditions met
190+
Arguments.of(
191+
MessageOptionItemVisibility(isReplyVisible = true),
192+
randomMessage(syncStatus = SyncStatus.COMPLETED, deletedAt = null, deletedForMe = false),
181193
randomChannelCapabilities(include = setOf(ChannelCapabilities.QUOTE_MESSAGE)),
182194
true,
183195
),

stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/utils/CapabilitiesHelper.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package io.getstream.chat.android.ui.common.utils
2020

2121
import io.getstream.chat.android.client.utils.attachment.isGiphy
2222
import io.getstream.chat.android.client.utils.message.hasSharedLocation
23+
import io.getstream.chat.android.client.utils.message.isDeleted
2324
import io.getstream.chat.android.client.utils.message.isThreadReply
2425
import io.getstream.chat.android.models.AttachmentType
2526
import io.getstream.chat.android.models.ChannelCapabilities
@@ -59,7 +60,10 @@ public fun canReplyToMessage(
5960
replyEnabled: Boolean,
6061
message: Message,
6162
ownCapabilities: Set<String>,
62-
): Boolean = replyEnabled && message.isSynced() && ownCapabilities.contains(ChannelCapabilities.QUOTE_MESSAGE)
63+
): Boolean = replyEnabled &&
64+
message.isSynced() &&
65+
!message.isDeleted() &&
66+
ownCapabilities.contains(ChannelCapabilities.QUOTE_MESSAGE)
6367

6468
/**
6569
* Determines whether a thread reply can be made to the given message.

stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/utils/CapabilitiesHelperTest.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import org.amshove.kluent.`should be`
3131
import org.junit.jupiter.params.ParameterizedTest
3232
import org.junit.jupiter.params.provider.Arguments
3333
import org.junit.jupiter.params.provider.MethodSource
34+
import java.util.Date
3435

3536
internal class CapabilitiesHelperTest {
3637

@@ -152,27 +153,38 @@ internal class CapabilitiesHelperTest {
152153

153154
@JvmStatic
154155
fun canReplyToMessageArguments() = listOf(
156+
// case: reply disabled
155157
Arguments.of(
156158
false,
157159
randomMessage(),
158160
randomChannelCapabilities(),
159161
false,
160162
),
163+
// case: message not synced
161164
Arguments.of(
162165
randomBoolean(),
163166
randomMessage(syncStatus = randomSyncStatus(exclude = listOf(SyncStatus.COMPLETED))),
164167
randomChannelCapabilities(),
165168
false,
166169
),
170+
// case: no QUOTE_MESSAGE capability
167171
Arguments.of(
168172
randomBoolean(),
169-
randomMessage(),
173+
randomMessage(deletedAt = null, deletedForMe = false),
170174
randomChannelCapabilities(exclude = setOf(ChannelCapabilities.QUOTE_MESSAGE)),
171175
false,
172176
),
177+
// case: message is deleted
173178
Arguments.of(
174179
true,
175-
randomMessage(syncStatus = SyncStatus.COMPLETED),
180+
randomMessage(syncStatus = SyncStatus.COMPLETED, deletedAt = Date(), deletedForMe = false),
181+
randomChannelCapabilities(include = setOf(ChannelCapabilities.QUOTE_MESSAGE)),
182+
false,
183+
),
184+
// case: all conditions met
185+
Arguments.of(
186+
true,
187+
randomMessage(syncStatus = SyncStatus.COMPLETED, deletedAt = null, deletedForMe = false),
176188
randomChannelCapabilities(include = setOf(ChannelCapabilities.QUOTE_MESSAGE)),
177189
true,
178190
),

stream-chat-android-ui-components/src/test/kotlin/io/getstream/chat/android/ui/feature/messages/list/internal/MessageListViewExtensionsKtTest.kt

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import org.amshove.kluent.`should be`
3333
import org.junit.jupiter.params.ParameterizedTest
3434
import org.junit.jupiter.params.provider.Arguments
3535
import org.junit.jupiter.params.provider.MethodSource
36+
import java.util.Date
3637

3738
internal class MessageListViewExtensionsKtTest {
3839

@@ -154,27 +155,42 @@ internal class MessageListViewExtensionsKtTest {
154155

155156
@JvmStatic
156157
fun canReplyToMessageArguments() = listOf(
158+
// case: reply disabled
157159
Arguments.of(
158160
randomMessageListViewStyle(replyEnabled = false),
159-
randomMessage(),
161+
randomMessage(deletedAt = null, deletedForMe = false),
160162
randomChannelCapabilities(),
161163
false,
162164
),
165+
// case: message not synced
163166
Arguments.of(
164167
randomMessageListViewStyle(),
165-
randomMessage(syncStatus = randomSyncStatus(exclude = listOf(SyncStatus.COMPLETED))),
168+
randomMessage(
169+
syncStatus = randomSyncStatus(exclude = listOf(SyncStatus.COMPLETED)),
170+
deletedAt = null,
171+
deletedForMe = false,
172+
),
166173
randomChannelCapabilities(),
167174
false,
168175
),
176+
// case: no QUOTE_MESSAGE capability
169177
Arguments.of(
170178
randomMessageListViewStyle(),
171-
randomMessage(),
179+
randomMessage(deletedAt = null, deletedForMe = false),
172180
randomChannelCapabilities(exclude = setOf(ChannelCapabilities.QUOTE_MESSAGE)),
173181
false,
174182
),
183+
// case: message is deleted
175184
Arguments.of(
176185
randomMessageListViewStyle(replyEnabled = true),
177-
randomMessage(syncStatus = SyncStatus.COMPLETED),
186+
randomMessage(syncStatus = SyncStatus.COMPLETED, deletedAt = Date(), deletedForMe = false),
187+
randomChannelCapabilities(include = setOf(ChannelCapabilities.QUOTE_MESSAGE)),
188+
false,
189+
),
190+
// case: all conditions met
191+
Arguments.of(
192+
randomMessageListViewStyle(replyEnabled = true),
193+
randomMessage(syncStatus = SyncStatus.COMPLETED, deletedAt = null, deletedForMe = false),
178194
randomChannelCapabilities(include = setOf(ChannelCapabilities.QUOTE_MESSAGE)),
179195
true,
180196
),

0 commit comments

Comments
 (0)