position copy button

This commit is contained in:
Evgeny @ SimpleX Chat
2026-04-01 17:52:51 +00:00
parent 4c3740da66
commit 76f963a2d0
2 changed files with 20 additions and 14 deletions
@@ -148,6 +148,7 @@ fun ChatView(
val showCommandsMenu = rememberSaveable { mutableStateOf(false) }
val contentFilter = rememberSaveable { mutableStateOf<ContentFilter?>(null) }
val availableContent = remember { mutableStateOf<List<ContentFilter>>(ContentFilter.initialList) }
val selectionManager = if (appPlatform.isDesktop) remember { SelectionManager() } else null
if (appPlatform.isAndroid) {
DisposableEffect(Unit) {
@@ -178,6 +179,7 @@ fun ChatView(
contentFilter.value = null
availableContent.value = ContentFilter.initialList
selectedChatItems.value = null
selectionManager?.clearSelection()
val cInfo = activeChat.value?.chatInfo
if (chatsCtx.secondaryContextFilter == null && (cInfo is ChatInfo.Direct || cInfo is ChatInfo.Group || cInfo is ChatInfo.Local)) {
updateAvailableContent(chatRh, activeChat, availableContent)
@@ -227,6 +229,7 @@ fun ChatView(
val clipboard = LocalClipboardManager.current
CompositionLocalProvider(
LocalAppBarHandler provides rememberAppBarHandler(chatInfo.id, keyboardCoversBar = false),
LocalSelectionManager provides selectionManager,
) {
when (chatInfo) {
is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> {
@@ -962,7 +965,7 @@ fun ChatLayout(
val composeViewFocusRequester = remember { if (appPlatform.isDesktop) FocusRequester() else null }
AdaptingBottomPaddingLayout(Modifier, CHAT_COMPOSE_LAYOUT_ID, composeViewHeight) {
if (chat != null) {
val selectionManager = if (appPlatform.isDesktop) remember { SelectionManager() } else null
val selectionManager = LocalSelectionManager.current
if (selectionManager != null) {
LaunchedEffect(selectionManager) {
snapshotFlow { selectionManager.selectionState != SelectionState.Idle }
@@ -972,7 +975,6 @@ fun ChatLayout(
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) {
// disables scrolling to top of chat item on click inside the bubble
CompositionLocalProvider(
LocalSelectionManager provides selectionManager,
LocalBringIntoViewSpec provides object : BringIntoViewSpec {
override fun calculateScrollDistance(offset: Float, size: Float, containerSize: Float): Float = 0f
}
@@ -1132,6 +1134,16 @@ fun ChatLayout(
}
}
}
// Desktop selection copy button — last child of outer Box, on top of everything
if (appPlatform.isDesktop) {
val manager = LocalSelectionManager.current
if (manager != null && manager.selectionState == SelectionState.Selected && manager.onCopySelection != null) {
SelectionCopyButton(
modifier = Modifier.align(Alignment.BottomCenter).padding(bottom = composeViewHeight.value + 8.dp),
onCopy = { manager.onCopySelection?.invoke() }
)
}
}
}
}
}
@@ -64,6 +64,7 @@ class SelectionManager {
private set
var focusWindowY by mutableStateOf(0f)
var focusWindowX by mutableStateOf(0f)
var onCopySelection: (() -> Unit)? = null
fun startSelection(startIndex: Int, anchorY: Float, anchorX: Float) {
range = SelectionRange(startIndex, -1, startIndex, -1)
@@ -168,13 +169,8 @@ fun BoxScope.SelectionHandler(
}
}
// Copy button
if (manager.selectionState == SelectionState.Selected) {
SelectionCopyButton(
onCopy = {
clipboard.setText(AnnotatedString(manager.getSelectedText(mergedItems.value.items, linkMode)))
}
)
manager.onCopySelection = {
clipboard.setText(AnnotatedString(manager.getSelectedText(mergedItems.value.items, linkMode)))
}
return Modifier
@@ -186,7 +182,7 @@ fun BoxScope.SelectionHandler(
&& event.key == Key.C
&& event.type == KeyEventType.KeyDown
) {
clipboard.setText(AnnotatedString(manager.getSelectedText(mergedItems.value.items, linkMode)))
manager.onCopySelection?.invoke()
true
} else false
}
@@ -298,11 +294,9 @@ private fun resolveIndexAtY(listState: LazyListState, localY: Float): Int? {
}
@Composable
private fun BoxScope.SelectionCopyButton(onCopy: () -> Unit) {
fun SelectionCopyButton(modifier: Modifier = Modifier, onCopy: () -> Unit) {
Row(
Modifier
.align(Alignment.BottomCenter)
.padding(bottom = 8.dp)
modifier
.background(MaterialTheme.colors.surface, RoundedCornerShape(20.dp))
.border(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.12f), RoundedCornerShape(20.dp))
.clickable { onCopy() }