Skip to content

Commit 84b9b96

Browse files
authored
Expand message long-press hit area to entire cell (#6405)
* DS-031: Expand message long-press hit area to entire cell Move the click modifier from the inner content Row (wrap-content) to the outer Box (full-width) so long-press can be triggered anywhere across the row, not only on the avatar+bubble area. Aligns the long-press surface with the swipe surface and matches iOS behaviour. Adds a ripple indication so the gesture target is visible to the user. * Extract message click predicates into named locals Replace inline boolean expressions inside the click and long-click lambdas with `canOpenThread` and `canOpenActions`, so the gesture handlers read as intent rather than condition bookkeeping. * Collapse message item Box+Row wrapper into a single Row Replace the outer `Box(fillMaxWidth, contentAlignment)` + inner `Row(wrapContentWidth)` pair with a single `Row(fillMaxWidth, horizontalArrangement)`. The Box was only providing edge alignment, which a Row achieves directly via `horizontalArrangement` mapped from `MessageAlignment`. Drops the now-redundant `Stream_MessageItem` test tag (zero references outside this file) and keeps `Stream_MessageCell` on the merged Row, where it accurately labels the message cell that the gesture surface lives on. * Enable message clicks only when threads or actions are available
1 parent 890e6a5 commit 84b9b96

1 file changed

Lines changed: 79 additions & 76 deletions

File tree

  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list

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

Lines changed: 79 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import androidx.compose.foundation.background
2424
import androidx.compose.foundation.combinedClickable
2525
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
2626
import androidx.compose.foundation.interaction.MutableInteractionSource
27+
import androidx.compose.foundation.layout.Arrangement
2728
import androidx.compose.foundation.layout.Box
2829
import androidx.compose.foundation.layout.Column
2930
import androidx.compose.foundation.layout.ColumnScope
@@ -38,7 +39,7 @@ import androidx.compose.foundation.layout.size
3839
import androidx.compose.foundation.layout.width
3940
import androidx.compose.foundation.layout.widthIn
4041
import androidx.compose.foundation.layout.wrapContentHeight
41-
import androidx.compose.foundation.layout.wrapContentWidth
42+
import androidx.compose.material3.ripple
4243
import androidx.compose.runtime.Composable
4344
import androidx.compose.runtime.LaunchedEffect
4445
import androidx.compose.runtime.getValue
@@ -174,12 +175,16 @@ public fun MessageContainer(
174175
val focusState = messageItem.focusState
175176
val haptic = LocalHapticFeedback.current
176177

178+
val canOpenThread = message.isThreadStart() && !messageItem.isInThread
179+
val canOpenActions = !message.isDeleted() && !message.isUploading()
180+
177181
val clickModifier = Modifier.combinedClickable(
178182
interactionSource = remember { MutableInteractionSource() },
179-
indication = null,
180-
onClick = { if (message.isThreadStart() && !messageItem.isInThread) onThreadClick(message) },
183+
indication = ripple(),
184+
enabled = canOpenThread || canOpenActions,
185+
onClick = { if (canOpenThread) onThreadClick(message) },
181186
onLongClick = {
182-
if (!message.isDeleted() && !message.isUploading()) {
187+
if (canOpenActions) {
183188
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
184189
onLongItemClick(message)
185190
}
@@ -218,89 +223,87 @@ public fun MessageContainer(
218223
onReply = { onReply(replyMessage) },
219224
isSwipeable = isSwipeable,
220225
) {
221-
Box(
226+
Row(
222227
modifier = Modifier
223-
.testTag("Stream_MessageItem")
228+
.testTag("Stream_MessageCell")
224229
.fillMaxWidth()
225230
.wrapContentHeight()
226231
.background(color = color)
232+
.then(clickModifier)
227233
.semantics { contentDescription = description },
228-
contentAlignment = messageAlignment.itemAlignment,
234+
horizontalArrangement = if (messageAlignment == MessageAlignment.Start) {
235+
Arrangement.Start
236+
} else {
237+
Arrangement.End
238+
},
229239
) {
230-
Row(
231-
Modifier
232-
.wrapContentWidth()
233-
.then(clickModifier)
234-
.testTag("Stream_MessageCell"),
235-
) {
236-
with(ChatTheme.componentFactory) {
237-
when (messageAlignment) {
238-
MessageAlignment.Start -> MessageAuthor(
239-
params = MessageAuthorParams(
240-
messageItem = messageItem,
241-
onUserAvatarClick = onUserAvatarClick,
242-
),
243-
)
240+
with(ChatTheme.componentFactory) {
241+
when (messageAlignment) {
242+
MessageAlignment.Start -> MessageAuthor(
243+
params = MessageAuthorParams(
244+
messageItem = messageItem,
245+
onUserAvatarClick = onUserAvatarClick,
246+
),
247+
)
244248

245-
MessageAlignment.End -> MessageSpacer(params = MessageSpacerParams(messageItem))
246-
}
249+
MessageAlignment.End -> MessageSpacer(params = MessageSpacerParams(messageItem))
250+
}
247251

248-
Column(
249-
modifier = Modifier.weight(1f, fill = false),
250-
horizontalAlignment = messageAlignment.contentAlignment,
251-
) {
252-
MessageTop(
253-
params = MessageTopParams(
254-
messageItem = messageItem,
255-
onThreadClick = onThreadClick,
256-
),
257-
)
258-
MessageContentWithReactions(
259-
messageAlignment = messageAlignment,
260-
reactions = rememberMessageReactions(message)?.let { reactions ->
261-
{
262-
MessageReactions(
263-
params = MessageReactionsParams(
264-
message = message,
265-
reactions = reactions,
266-
onClick = onReactionsClick,
267-
),
268-
)
269-
}
270-
},
271-
content = {
272-
MessageContent(
273-
params = MessageContentParams(
274-
messageItem = messageItem,
275-
onLongItemClick = onLongItemClick,
276-
onMediaGalleryPreviewResult = onMediaGalleryPreviewResult,
277-
onGiphyActionClick = onGiphyActionClick,
278-
onQuotedMessageClick = onQuotedMessageClick,
279-
onLinkClick = onLinkClick,
280-
onUserMentionClick = onUserMentionClick,
281-
onPollUpdated = onPollUpdated,
282-
onCastVote = onCastVote,
283-
onRemoveVote = onRemoveVote,
284-
selectPoll = selectPoll,
285-
onAddAnswer = onAddAnswer,
286-
onClosePoll = onClosePoll,
287-
onAddPollOption = onAddPollOption,
252+
Column(
253+
modifier = Modifier.weight(1f, fill = false),
254+
horizontalAlignment = messageAlignment.contentAlignment,
255+
) {
256+
MessageTop(
257+
params = MessageTopParams(
258+
messageItem = messageItem,
259+
onThreadClick = onThreadClick,
260+
),
261+
)
262+
MessageContentWithReactions(
263+
messageAlignment = messageAlignment,
264+
reactions = rememberMessageReactions(message)?.let { reactions ->
265+
{
266+
MessageReactions(
267+
params = MessageReactionsParams(
268+
message = message,
269+
reactions = reactions,
270+
onClick = onReactionsClick,
288271
),
289272
)
290-
},
291-
)
292-
MessageBottom(params = MessageBottomParams(messageItem = messageItem))
293-
}
273+
}
274+
},
275+
content = {
276+
MessageContent(
277+
params = MessageContentParams(
278+
messageItem = messageItem,
279+
onLongItemClick = onLongItemClick,
280+
onMediaGalleryPreviewResult = onMediaGalleryPreviewResult,
281+
onGiphyActionClick = onGiphyActionClick,
282+
onQuotedMessageClick = onQuotedMessageClick,
283+
onLinkClick = onLinkClick,
284+
onUserMentionClick = onUserMentionClick,
285+
onPollUpdated = onPollUpdated,
286+
onCastVote = onCastVote,
287+
onRemoveVote = onRemoveVote,
288+
selectPoll = selectPoll,
289+
onAddAnswer = onAddAnswer,
290+
onClosePoll = onClosePoll,
291+
onAddPollOption = onAddPollOption,
292+
),
293+
)
294+
},
295+
)
296+
MessageBottom(params = MessageBottomParams(messageItem = messageItem))
297+
}
294298

295-
when (messageAlignment) {
296-
MessageAlignment.Start -> MessageSpacer(params = MessageSpacerParams(messageItem))
297-
MessageAlignment.End -> MessageAuthor(
298-
params = MessageAuthorParams(
299-
messageItem = messageItem,
300-
onUserAvatarClick = onUserAvatarClick,
301-
),
302-
)
303-
}
299+
when (messageAlignment) {
300+
MessageAlignment.Start -> MessageSpacer(params = MessageSpacerParams(messageItem))
301+
MessageAlignment.End -> MessageAuthor(
302+
params = MessageAuthorParams(
303+
messageItem = messageItem,
304+
onUserAvatarClick = onUserAvatarClick,
305+
),
306+
)
304307
}
305308
}
306309
}

0 commit comments

Comments
 (0)