From 9d289c8c96b99be2f0710140e2ee016ef727f233 Mon Sep 17 00:00:00 2001 From: "Evgeny @ SimpleX Chat" <259188159+evgeny-simplex@users.noreply.github.com> Date: Thu, 2 Apr 2026 06:42:33 +0000 Subject: [PATCH] refactor 2 --- .../simplex/common/views/chat/ChatView.kt | 15 +------ .../common/views/chat/TextSelection.kt | 42 +++++++++++++++++-- .../common/views/chat/item/EmojiItemView.kt | 27 +----------- 3 files changed, 41 insertions(+), 43 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 7e1403e17f..b22903a1d0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -14,7 +14,6 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.* import androidx.compose.ui.draw.* import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.compose.ui.layout.layoutId @@ -1137,19 +1136,7 @@ fun ChatLayout( } // Desktop selection copy button — last child of outer Box, on top of everything if (appPlatform.isDesktop) { - val manager = LocalSelectionManager.current - val range = manager?.range - if (manager != null && manager.selectionState == SelectionState.Selected && range != null && manager.focusCharRect != Rect.Zero) { - val draggingDown = range.startIndex > range.endIndex || (range.startIndex == range.endIndex && range.startOffset < range.endOffset) - val gap = with(LocalDensity.current) { 4.dp.toPx() } - var buttonSize by remember { mutableStateOf(IntSize.Zero) } - SelectionCopyButton( - modifier = Modifier - .offset { manager.copyButtonOffset(draggingDown, gap, buttonSize) } - .onSizeChanged { buttonSize = it }, - onCopy = { manager.onCopySelection?.invoke() } - ) - } + SelectionCopyButton() } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/TextSelection.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/TextSelection.kt index 2a84aca78c..d3f5672420 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/TextSelection.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/TextSelection.kt @@ -23,8 +23,10 @@ import androidx.compose.ui.input.key.* import androidx.compose.ui.input.pointer.* import androidx.compose.ui.layout.boundsInWindow import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInWindow import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextLayoutResult @@ -404,14 +406,48 @@ fun setupItemSelection(selectionManager: SelectionManager?, selectionIndex: Int, return ItemSelection(highlightRange, positionModifier, onTextLayoutResult) } +// Sets up full-item selection for emoji items (no character-level tracking). @Composable -fun SelectionCopyButton(modifier: Modifier = Modifier, onCopy: () -> Unit) { +fun setupEmojiSelection(selectionManager: SelectionManager?, selectionIndex: Int, textLength: Int): Boolean { + if (selectionManager == null || selectionIndex < 0) return false + + val isAnchor = remember(selectionIndex) { + derivedStateOf { selectionManager.range?.startIndex == selectionIndex && selectionManager.selectionState == SelectionState.Selecting } + } + LaunchedEffect(isAnchor.value) { + if (!isAnchor.value) return@LaunchedEffect + selectionManager.setAnchorOffset(0) + } + + val isFocus = remember(selectionIndex) { + derivedStateOf { selectionManager.range?.endIndex == selectionIndex && selectionManager.selectionState == SelectionState.Selecting } + } + if (isFocus.value) { + LaunchedEffect(Unit) { + snapshotFlow { selectionManager.focusWindowY } + .collect { selectionManager.updateFocusOffset(textLength) } + } + } + + return remember(selectionIndex) { derivedStateOf { selectedRange(selectionManager.range, selectionIndex) != null } }.value +} + +@Composable +fun SelectionCopyButton() { + val manager = LocalSelectionManager.current ?: return + val range = manager.range ?: return + if (manager.selectionState != SelectionState.Selected || manager.focusCharRect == Rect.Zero) return + val draggingDown = range.startIndex > range.endIndex || (range.startIndex == range.endIndex && range.startOffset < range.endOffset) + val gap = with(LocalDensity.current) { 4.dp.toPx() } + var buttonSize by remember { mutableStateOf(IntSize.Zero) } Row( - modifier + Modifier + .offset { manager.copyButtonOffset(draggingDown, gap, buttonSize) } + .onSizeChanged { buttonSize = it } .background(MaterialTheme.colors.surface, RoundedCornerShape(20.dp)) .border(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.12f), RoundedCornerShape(20.dp)) .clip(RoundedCornerShape(20.dp)) - .clickable { onCopy() } + .clickable { manager.onCopySelection?.invoke() } .padding(horizontal = 16.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/EmojiItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/EmojiItemView.kt index 17b2a53eac..3bcd02411f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/EmojiItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/EmojiItemView.kt @@ -22,33 +22,8 @@ val mediumEmojiFont: TextStyle = TextStyle(fontSize = 36.sp, fontFamily = EmojiF @Composable fun EmojiItemView(chatItem: ChatItem, timedMessagesTTL: Int?, showViaProxy: Boolean, showTimestamp: Boolean) { - val selectionManager = LocalSelectionManager.current - val selectionIndex = LocalItemContext.current.selectionIndex val emojiText = chatItem.content.text.trim() - - if (selectionManager != null && selectionIndex >= 0) { - val isAnchor = remember(selectionIndex) { - derivedStateOf { selectionManager.range?.startIndex == selectionIndex && selectionManager.selectionState == SelectionState.Selecting } - } - LaunchedEffect(isAnchor.value) { - if (!isAnchor.value) return@LaunchedEffect - selectionManager.setAnchorOffset(0) - } - - val isFocus = remember(selectionIndex) { - derivedStateOf { selectionManager.range?.endIndex == selectionIndex && selectionManager.selectionState == SelectionState.Selecting } - } - if (isFocus.value) { - LaunchedEffect(Unit) { - snapshotFlow { selectionManager.focusWindowY } - .collect { selectionManager.updateFocusOffset(emojiText.length) } - } - } - } - - val isSelected = if (selectionManager != null && selectionIndex >= 0) { - remember(selectionIndex) { derivedStateOf { selectedRange(selectionManager.range, selectionIndex) != null } }.value - } else false + val isSelected = setupEmojiSelection(LocalSelectionManager.current, LocalItemContext.current.selectionIndex, emojiText.length) Column( Modifier.padding(vertical = 8.dp, horizontal = 12.dp),