Scope message cell ripple to the bubble shape#6425
Scope message cell ripple to the bubble shape#6425
Conversation
Image attachments already render a ripple on tap via their combinedClickable. Giphy, file rows, link previews and the quoted- message preview block had indication = null, leaving them without visual feedback. Match the image-attachment pattern across all four so every interactive surface inside a message bubble ripples consistently on press.
Wrap the message-content Column inside DefaultMessageRegularContent with combinedClickable + ripple(). The Column owns the click + ripple for the entire bubble interior (text, spacer, and any space around inner attachments). Inner attachment clickables (image, file, giphy, link, quoted) still consume their own taps and ripple in their own bounds. This replaces the earlier params-based bubble ripple (which had a position-translation issue between the cell's interaction source and the bubble's local coords). With the click and the ripple at the same layout node, press positions are captured in Column-local coords and the ripple renders correctly regardless of message alignment or bubble width.
Restore the ripple feedback on the bubble when the user long-presses in the avatar gap (or any cell area outside the bubble). The column-level clickable inside DefaultMessageRegularContent only fires for taps inside the bubble; cell-area presses go to MessageContainer's combinedClickable, which has indication = null. Without forwarding the cell's interaction source, those presses had no visual feedback. Add interactionSource to MessageBubbleParams and MessageContentParams. The cell hoists its MutableInteractionSource and threads it via MessageContainer -> factory.MessageContent -> DefaultMessageContent -> RegularMessageContent / PollMessageContent -> factory.MessageBubble. The MessageBubble factory default applies clip(shape).indication( source, ripple(bounded = false)) when the source is non-null, synchronised with the cell's press state. Two ripple paths now coexist by design: - Tap inside the bubble: column's combinedClickable consumes, column-level bounded ripple fires (position-aware). - Tap in avatar gap: cell's combinedClickable handles, the cell's source emits, bubble's params indication renders an unbounded ripple from the bubble centre. Both fire on the correct trigger; no double rippling thanks to gesture consumption rules.
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
PR checklist ✅All required conditions are satisfied:
🎉 Great job! This PR is ready for review. |
SDK Size Comparison 📏
|
WalkthroughThis PR threads an ChangesMessage UI InteractionSource Support
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/MessageContent.kt`:
- Around line 183-189: The Column's combinedClickable currently uses a no-op
onClick which swallows taps; replace that no-op by invoking the parent click
intent so bubble taps still trigger the MessageContainer thread-open behavior.
In MessageContent.kt update the combinedClickable onClick to call the parent
handler that's passed into this composable (e.g., invoke the onMessageClick /
onThreadOpen callback or forward the existing click lambda used by
MessageContainer) instead of {}, keeping onLongClick as
onLongItemClick(message).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: d453df3f-1b17-4f76-9aaa-fe5ec0fdb5bc
📒 Files selected for processing (9)
stream-chat-android-compose/api/stream-chat-android-compose.apistream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/FileAttachmentContent.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/GiphyAttachmentContent.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/LinkAttachmentContent.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/MessageContent.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/PollMessageContent.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageContainer.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactoryParams.kt
| Column( | ||
| modifier = Modifier.combinedClickable( | ||
| interactionSource = remember(::MutableInteractionSource), | ||
| indication = ripple(), | ||
| onClick = {}, | ||
| onLongClick = { onLongItemClick(message) }, | ||
| ), |
There was a problem hiding this comment.
No-op child click is swallowing parent thread-open taps.
The new combinedClickable consumes taps inside the bubble (onClick = {}), so the parent click handler in MessageContainer no longer receives those events. For thread-start messages, tapping the bubble can stop opening the thread.
💡 Proposed direction
- internal fun DefaultMessageRegularContent(
+ internal fun DefaultMessageRegularContent(
message: Message,
currentUser: User?,
messageAlignment: MessageAlignment = MessageAlignment.Start,
onLongItemClick: (Message) -> Unit,
+ onItemClick: () -> Unit = {},
onMediaGalleryPreviewResult: (MediaGalleryPreviewResult?) -> Unit = {},
onQuotedMessageClick: (Message) -> Unit,
onUserMentionClick: (User) -> Unit = {},
onLinkClick: ((Message, String) -> Unit)? = null,
) {
@@
- modifier = Modifier.combinedClickable(
+ modifier = Modifier.combinedClickable(
interactionSource = remember(::MutableInteractionSource),
indication = ripple(),
- onClick = {},
+ onClick = onItemClick,
onLongClick = { onLongItemClick(message) },
),Then pass the existing parent click intent (thread-open logic) into this callback path so bubble taps keep expected behavior.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/MessageContent.kt`
around lines 183 - 189, The Column's combinedClickable currently uses a no-op
onClick which swallows taps; replace that no-op by invoking the parent click
intent so bubble taps still trigger the MessageContainer thread-open behavior.
In MessageContent.kt update the combinedClickable onClick to call the parent
handler that's passed into this composable (e.g., invoke the onMessageClick /
onThreadOpen callback or forward the existing click lambda used by
MessageContainer) instead of {}, keeping onLongClick as
onLongItemClick(message).
|



Goal
Move the ripple from the message cell to the bubble, while keeping the cell-wide hit area.
Implementation
Two ripple paths, one per click region:
Column(inDefaultMessageRegularContent) in acombinedClickablewith a bounded ripple. Position-aware ripple, clipped to the bubble shape via the surroundingSurface.combinedClickablekeepsindication = nulland forwards itsMutableInteractionSourceto the bubble throughMessageBubbleParams.interactionSourceandMessageContentParams.interactionSource. The bubble's factory default applies an unbounded ripple driven by that source. Press positions stay in cell-local coords, so we usebounded = falseto avoid a clipping mismatch — the ripple emanates from the bubble center.Attachment ripple fixes. Giphy, file, link and quoted-message clickables had
indication = null. Addedripple()to align them with the image attachment, which already had a bounded ripple.Important
Decisions worth flagging for review
interactionSourcetoMessageBubbleParamsandMessageContentParamsmakes the override path discoverable: integrators overridingChatComponentFactory.MessageBubblecan readparams.interactionSourceand decide what to do with it. The CompositionLocal approach we tried earlier hides the dependency.Known limitation
Text messages whose body contains a URL, email, or
@mentiondon't ripple on text-character taps. The internalClickableText(inMessageText.kt) usespointerInput { detectTapGestures(...) }to route tap-on-link to the correct character — that consumer blocks the parent Column from receiving the press. Link clicks still work; long-press still opens actions with haptic feedback.The proper fix is to migrate the link/mention handling to Compose Foundation's
LinkAnnotationAPI (AnnotatedStringwithLinkAnnotation.Url/LinkAnnotation.Clickable). That API lets non-link taps propagate to the parent and pick up the bubble ripple. Tracked as a follow-up.🎨 UI Changes
Screen_recording_20260507_165648.webm
Screen_recording_20260507_165559.webm
Testing
Summary by CodeRabbit