Fix context menu in chat

Signed-off-by: denis0001-dev <denis0001.dev@ya.ru>
This commit is contained in:
2026-07-10 16:44:01 +03:00
Unverified
parent 243d41385d
commit e4aba8d6e5
@@ -10,11 +10,8 @@ import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraints
@@ -177,10 +174,11 @@ fun MessageItem(
val isDeletedSender = messageSenderIsDeleted(message, currentUserId) val isDeletedSender = messageSenderIsDeleted(message, currentUserId)
val replyRef = message.reply_to val replyRef = message.reply_to
var isPressed by remember { mutableStateOf(false) } var isPressed by remember(message.id) { mutableStateOf(false) }
var avatarPressed by remember(message.id) { mutableStateOf(false) } var avatarPressed by remember(message.id) { mutableStateOf(false) }
var replyPressed by remember(message.id) { mutableStateOf(false) } var replyPressed by remember(message.id) { mutableStateOf(false) }
var rowLayoutCoords by remember(message.id) { mutableStateOf<LayoutCoordinates?>(null) } var rowLayoutCoords by remember(message.id) { mutableStateOf<LayoutCoordinates?>(null) }
var bubbleContentCoords by remember(message.id) { mutableStateOf<LayoutCoordinates?>(null) }
val scaleTarget = if (isPressed && !isContextMenuForThisMessage && !isContextMenuOpen) 0.96f else 1f val scaleTarget = if (isPressed && !isContextMenuForThisMessage && !isContextMenuOpen) 0.96f else 1f
val avatarScaleTarget = if (avatarPressed && !isContextMenuOpen) 0.96f else 1f val avatarScaleTarget = if (avatarPressed && !isContextMenuOpen) 0.96f else 1f
val replyScaleTarget = if (replyPressed && !isContextMenuOpen) 0.96f else 1f val replyScaleTarget = if (replyPressed && !isContextMenuOpen) 0.96f else 1f
@@ -376,7 +374,7 @@ fun MessageItem(
val rowLongPress = val rowLongPress =
if (isContextMenuOpen) Modifier if (isContextMenuOpen) Modifier
else Modifier.pointerInput(isContextMenuOpen, message.id) { else Modifier.pointerInput(message.id, onLongPress) {
detectTapGestures( detectTapGestures(
onPress = { onPress = {
isPressed = true isPressed = true
@@ -490,21 +488,30 @@ fun MessageItem(
message.files?.firstOrNull()?.let { isImageFilename(it.name) } == true message.files?.firstOrNull()?.let { isImageFilename(it.name) } == true
) )
val bubbleTap = val bubbleBodyGestures =
if (isContextMenuOpen || onBubbleTap == null) Modifier if (isContextMenuOpen) Modifier
else Modifier.pointerInput(message.id, onBubbleTap) { else Modifier
detectTapGestures( .onGloballyPositioned { bubbleContentCoords = it }
onPress = { .pointerInput(message.id, onBubbleTap, onLongPress) {
isPressed = true detectTapGestures(
try { onPress = {
awaitRelease() isPressed = true
} finally { try {
isPressed = false awaitRelease()
} } finally {
}, isPressed = false
onTap = { onBubbleTap.invoke() }, }
) },
} onTap = { onBubbleTap?.invoke() },
onLongPress = { localOffset ->
val coords = bubbleContentCoords
if (coords != null && coords.isAttached) {
onTapPosition(coords.localToRoot(localOffset))
}
onLongPress()
},
)
}
Box { Box {
Column( Column(
@@ -550,16 +557,38 @@ fun MessageItem(
val usernameInset = val usernameInset =
Modifier.padding(horizontal = 4.dp, vertical = 2.dp) Modifier.padding(horizontal = 4.dp, vertical = 2.dp)
if (onUsernameClick != null) { if (onUsernameClick != null) {
val usernameInteraction =
remember(message.id) { MutableInteractionSource() }
Box( Box(
modifier = usernameOutset modifier = usernameOutset
.clip(usernameShape) .clip(usernameShape)
.clickable( .pointerInput(message.id, onUsernameClick) {
interactionSource = usernameInteraction, detectTapGestures(
indication = LocalIndication.current, onPress = {
onClick = onUsernameClick isPressed = true
) try {
awaitRelease()
} finally {
isPressed = false
}
},
onTap = { onUsernameClick.invoke() },
onLongPress = {
val coords = bubbleContentCoords
if (
coords != null &&
coords.isAttached
) {
val center = Offset(
coords.size.width / 2f,
coords.size.height / 2f,
)
onTapPosition(
coords.localToRoot(center),
)
}
onLongPress()
},
)
}
) { ) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -579,7 +608,38 @@ fun MessageItem(
} }
} }
} else { } else {
Box(modifier = usernameOutset) { Box(
modifier = usernameOutset
.pointerInput(message.id, onBubbleTap) {
detectTapGestures(
onPress = {
isPressed = true
try {
awaitRelease()
} finally {
isPressed = false
}
},
onTap = { onBubbleTap?.invoke() },
onLongPress = {
val coords = bubbleContentCoords
if (
coords != null &&
coords.isAttached
) {
val center = Offset(
coords.size.width / 2f,
coords.size.height / 2f,
)
onTapPosition(
coords.localToRoot(center),
)
}
onLongPress()
},
)
},
) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp), horizontalArrangement = Arrangement.spacedBy(4.dp),
@@ -600,11 +660,7 @@ fun MessageItem(
} }
} }
Box( Box(modifier = Modifier.fillMaxWidth()) {
modifier = Modifier
.fillMaxWidth()
.then(bubbleTap)
) {
Column { Column {
replyRef?.let { replyToMsg -> replyRef?.let { replyToMsg ->
MessageReplyQuote( MessageReplyQuote(
@@ -620,6 +676,12 @@ fun MessageItem(
) )
} }
Box(
modifier = Modifier
.fillMaxWidth()
.then(bubbleBodyGestures),
) {
Column {
if (isCorrupted) { if (isCorrupted) {
Text( Text(
text = corruptedBody, text = corruptedBody,
@@ -850,6 +912,8 @@ fun MessageItem(
modifier = Modifier.padding(horizontal = 14.dp) modifier = Modifier.padding(horizontal = 14.dp)
) )
} }
}
}
} }
} }
} }
@@ -981,7 +1045,7 @@ private fun MessageReplyQuote(
val replyPressModifier = val replyPressModifier =
if (replyTapEnabled && !isContextMenuOpen) { if (replyTapEnabled && !isContextMenuOpen) {
Modifier.pointerInput(replyTo.id, isContextMenuOpen) { Modifier.pointerInput(replyTo.id) {
detectTapGestures( detectTapGestures(
onPress = { onPress = {
onReplyPressedChange(true) onReplyPressedChange(true)