From 376fe2073760614011be572aee3205a0dff70012 Mon Sep 17 00:00:00 2001 From: easyhooon Date: Tue, 3 Feb 2026 15:07:05 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[BOOK-501]=20refactor:=20composed=20{}=20?= =?UTF-8?q?=EB=A5=BC=20=EC=9D=B4=EC=9A=A9=ED=95=9C=20Custom=20Modifier=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20Modifier.Node=20API=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../booket/core/common/extensions/Modifier.kt | 112 +++++++++++++----- 1 file changed, 85 insertions(+), 27 deletions(-) diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt index 4c5695cb..b839bbba 100644 --- a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt +++ b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt @@ -3,54 +3,112 @@ package com.ninecraft.booket.core.common.extensions import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.material3.ripple -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.composed import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.compose.ui.graphics.layer.drawLayer import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.debugInspectorInfo +import androidx.compose.ui.node.DelegatingNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.SemanticsModifierNode import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.SemanticsPropertyReceiver +import androidx.compose.ui.semantics.disabled +import androidx.compose.ui.semantics.onClick +import androidx.compose.ui.semantics.role import com.ninecraft.booket.core.common.utils.MultipleEventsCutter import com.ninecraft.booket.core.common.utils.get // https://stackoverflow.com/questions/66703448/how-to-disable-ripple-effect-when-clicking-in-jetpack-compose -inline fun Modifier.noRippleClickable(crossinline onClick: () -> Unit): Modifier = composed { - clickable( +fun Modifier.noRippleClickable(onClick: () -> Unit): Modifier = + this.clickable( + interactionSource = null, indication = null, - interactionSource = remember { MutableInteractionSource() }, - ) { - onClick() - } -} + onClick = onClick, + ) fun Modifier.clickableSingle( enabled: Boolean = true, onClickLabel: String? = null, role: Role? = null, onClick: () -> Unit, -) = composed( - inspectorInfo = debugInspectorInfo { - name = "clickable" - properties["enabled"] = enabled - properties["onClickLabel"] = onClickLabel - properties["role"] = role - properties["onClick"] = onClick - }, -) { - val multipleEventsCutter = remember { MultipleEventsCutter.get() } - Modifier.clickable( - enabled = enabled, - onClickLabel = onClickLabel, - onClick = { multipleEventsCutter.processEvent { onClick() } }, - role = role, - indication = ripple(), - interactionSource = remember { MutableInteractionSource() }, +): Modifier = this then ClickableSingleElement(enabled, onClickLabel, role, onClick) + +private data class ClickableSingleElement( + private val enabled: Boolean, + private val onClickLabel: String?, + private val role: Role?, + private val onClick: () -> Unit, +) : ModifierNodeElement() { + override fun create() = ClickableSingleNode(enabled, onClickLabel, role, onClick) + override fun update(node: ClickableSingleNode) { + node.update(enabled, onClickLabel, role, onClick) + } +} + +private class ClickableSingleNode( + private var enabled: Boolean, + private var onClickLabel: String?, + private var role: Role?, + private var onClick: () -> Unit, +) : DelegatingNode(), SemanticsModifierNode { + private val multipleEventsCutter = MultipleEventsCutter.get() + private val interactionSource = MutableInteractionSource() + + @Suppress("unused") + private val indicationNode = delegate( + ripple().create(interactionSource), + ) + + @Suppress("unused") + private val pointerInputNode = delegate( + SuspendingPointerInputModifierNode { + if (!enabled) return@SuspendingPointerInputModifierNode + detectTapGestures( + onPress = { offset -> + val press = PressInteraction.Press(offset) + interactionSource.emit(press) + val released = tryAwaitRelease() + if (released) { + interactionSource.emit(PressInteraction.Release(press)) + } else { + interactionSource.emit(PressInteraction.Cancel(press)) + } + }, + onTap = { + multipleEventsCutter.processEvent { onClick() } + }, + ) + }, ) + + override fun SemanticsPropertyReceiver.applySemantics() { + onClick(label = onClickLabel) { + if (enabled) { + multipleEventsCutter.processEvent { this@ClickableSingleNode.onClick() } + } + true + } + this@ClickableSingleNode.role?.let { this.role = it } + if (!enabled) { + disabled() + } + } + + override val shouldAutoInvalidate: Boolean = false + + fun update(enabled: Boolean, onClickLabel: String?, role: Role?, onClick: () -> Unit) { + this.enabled = enabled + this.onClickLabel = onClickLabel + this.role = role + this.onClick = onClick + } } fun Modifier.captureToGraphicsLayer(graphicsLayer: GraphicsLayer) = From ec8676f2e35251ec4941d136ecf61b02345b8d0f Mon Sep 17 00:00:00 2001 From: easyhooon Date: Tue, 3 Feb 2026 15:31:47 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[BOOK-501]=20refactor:=20=ED=86=A0=EB=81=BC?= =?UTF-8?q?=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ninecraft/booket/core/common/extensions/Modifier.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt index b839bbba..3df531ae 100644 --- a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt +++ b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt @@ -90,9 +90,8 @@ private class ClickableSingleNode( override fun SemanticsPropertyReceiver.applySemantics() { onClick(label = onClickLabel) { - if (enabled) { - multipleEventsCutter.processEvent { this@ClickableSingleNode.onClick() } - } + if (!enabled) return@onClick false + multipleEventsCutter.processEvent { this@ClickableSingleNode.onClick() } true } this@ClickableSingleNode.role?.let { this.role = it } @@ -101,8 +100,6 @@ private class ClickableSingleNode( } } - override val shouldAutoInvalidate: Boolean = false - fun update(enabled: Boolean, onClickLabel: String?, role: Role?, onClick: () -> Unit) { this.enabled = enabled this.onClickLabel = onClickLabel