From 8033d53aa2f8b92cc0b091dad4851a192aaa9344 Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Fri, 11 Sep 2026 16:42:39 +0300 Subject: [PATCH] Fix drag & drop --- .../ui/chat/utils/AttachmentDragDrop.kt | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/utils/AttachmentDragDrop.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/utils/AttachmentDragDrop.kt index 9dc437b..9b1b6e3 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/utils/AttachmentDragDrop.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/utils/AttachmentDragDrop.kt @@ -69,23 +69,39 @@ object AttachmentDropHighlight { var activeBridge by mutableStateOf(null) private set - private val boundsByBridge = mutableMapOf() + private val boundsByKey = mutableMapOf>() + private var lastPointer by mutableStateOf(null) - fun register(bridge: AttachmentDropBridge, bounds: Rect) { - boundsByBridge[bridge] = bounds + fun register(key: Any, bridge: AttachmentDropBridge, bounds: Rect) { + boundsByKey[key] = bridge to bounds } - fun unregister(bridge: AttachmentDropBridge) { - boundsByBridge.remove(bridge) - if (activeBridge === bridge) activeBridge = null + fun unregister(key: Any) { + boundsByKey.remove(key) + if (activeBridge != null && boundsByKey.values.none { it.first === activeBridge }) { + activeBridge = hitTest(lastPointer) + } } fun syncFromPointer(windowPoint: Offset) { + lastPointer = windowPoint activeBridge = hitTest(windowPoint) } fun clear() { activeBridge = null + lastPointer = null + } + + fun isHighlightActive(bridge: AttachmentDropBridge): Boolean { + if (!AttachmentDragSession.isActive) return false + val point = lastPointer + if (point != null) { + return boundsByKey.values.any { (owner, bounds) -> + owner === bridge && bounds.contains(point) + } + } + return activeBridge === bridge } /** Delivers to the currently hovered target. Returns true if handled. */ @@ -97,10 +113,11 @@ object AttachmentDropHighlight { return true } - private fun hitTest(windowPoint: Offset): AttachmentDropBridge? { + private fun hitTest(windowPoint: Offset?): AttachmentDropBridge? { + if (windowPoint == null) return null var best: AttachmentDropBridge? = null var bestArea = Float.POSITIVE_INFINITY - boundsByBridge.forEach { (bridge, bounds) -> + boundsByKey.values.forEach { (bridge, bounds) -> if (!bounds.contains(windowPoint)) return@forEach val area = bounds.width * bounds.height if (area > 0f && area < bestArea) { @@ -169,7 +186,7 @@ fun rememberAttachmentDropBridge(): AttachmentDropBridge = remember { Attachment /** Whether [bridge] currently owns the exclusive drop highlight. */ @Composable fun AttachmentDropBridge.isDropHighlightActive(): Boolean = - AttachmentDropHighlight.activeBridge === this + AttachmentDropHighlight.isHighlightActive(this) fun urisToSelectedAttachments( uris: List, @@ -223,8 +240,9 @@ fun Modifier.chatAttachmentDropTarget( ): Modifier = composed { if (!enabled) return@composed Modifier val permissionsHost = rememberAttachmentDropPermissionsHost() - DisposableEffect(bridge) { - onDispose { AttachmentDropHighlight.unregister(bridge) } + val registrationKey = remember { Any() } + DisposableEffect(registrationKey) { + onDispose { AttachmentDropHighlight.unregister(registrationKey) } } val target = remember(bridge, permissionsHost) { object : DragAndDropTarget { @@ -260,7 +278,7 @@ fun Modifier.chatAttachmentDropTarget( } } onGloballyPositioned { coords -> - AttachmentDropHighlight.register(bridge, coords.boundsInRoot()) + AttachmentDropHighlight.register(registrationKey, bridge, coords.boundsInRoot()) }.dragAndDropTarget( shouldStartDragAndDrop = { acceptsAttachmentDrop(it) }, target = target,