Fix long messages not being clickable (#6356)

* Fix long messages not being clickable

As @bmarty found out, `clip = true` causes the click event to be ignored in some cases. Since we have the shape we want to draw and we're using a custom `onDraw` modifier anyway to cut-out part of the path, we can just draw everything using the modifier and avoid using `clip = true`.

This seems to fix the issue.

* Fix clipping of images or other items that cover the bubble

* Fix borders being displayed for contents

* Extract the layer drawing logic into `drawInLayer` to simplify the inlined code. Remove redundant code, those changes are now in the `drawInLayer` block

* Workaround for lint issue: it seems like detekt can't properly detect usages in content receivers

* Update screenshots

---------

Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
Jorge Martin Espinosa
2026-03-23 18:11:55 +01:00
committed by GitHub
parent 13bbd24df1
commit a2d9f241dd
2 changed files with 70 additions and 23 deletions

View File

@@ -22,14 +22,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.graphics.layer.CompositingStrategy
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
@@ -49,6 +47,7 @@ import io.element.android.libraries.designsystem.theme.messageFromMeBackground
import io.element.android.libraries.designsystem.theme.messageFromOtherBackground
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.utils.graphics.drawInLayer
import io.element.android.libraries.ui.utils.time.isTalkbackActive
private val BUBBLE_RADIUS = 12.dp
@@ -78,32 +77,45 @@ fun MessageEventBubble(
.onKeyboardContextMenuAction(onLongClick)
}
val cutTopStart = state.cutTopStart
// Ignore state.isHighlighted for now, we need a design decision on it.
val backgroundBubbleColor = MessageEventBubbleDefaults.backgroundBubbleColor(state.isMine)
val bubbleShape = remember(state) { MessageEventBubbleDefaults.shape(state.cutTopStart, state.groupPosition, state.isMine) }
val radiusPx = (avatarRadius + SENDER_AVATAR_BORDER_WIDTH).toPx()
val yOffsetPx = -(NEGATIVE_MARGIN_FOR_BUBBLE + avatarRadius).toPx()
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
BoxWithConstraints(
modifier = modifier
.graphicsLayer {
shape = bubbleShape
clip = true
compositingStrategy = CompositingStrategy.Offscreen
}
.drawWithContent {
drawRect(backgroundBubbleColor)
drawContent()
if (state.cutTopStart) {
drawCircle(
color = Color.Black,
center = Offset(
x = if (isRtl) size.width else 0f,
y = yOffsetPx,
),
radius = radiusPx,
blendMode = BlendMode.Clear,
)
.drawWithCache {
// Calculate the outline of the background and cache it
val outline = bubbleShape.createOutline(size, layoutDirection, this)
onDrawWithContent {
// Draw the contents in a layer to be able to clip them with the same outline
// For some reason, doing this clipping outside a layer messes up with the touch events
drawInLayer(
composingStrategy = CompositingStrategy.Offscreen,
outline = outline,
clip = true,
) {
// Draw the background first, so that it's behind the content
drawRect(backgroundBubbleColor)
// Then draw the content on top of it
drawContent()
// And then clip the top start corner if needed to make room for the avatar
if (cutTopStart) {
drawCircle(
color = Color.Black,
center = Offset(
x = if (layoutDirection == LayoutDirection.Rtl) size.width else 0f,
y = yOffsetPx,
),
radius = radiusPx,
blendMode = BlendMode.Clear,
)
}
}
}
},
// Need to set the contentAlignment again (it's already set in TimelineItemEventRow), for the case