@@ -3,54 +3,109 @@ package com.ninecraft.booket.core.common.extensions
33import androidx.compose.foundation.clickable
44import androidx.compose.foundation.gestures.awaitEachGesture
55import androidx.compose.foundation.gestures.awaitFirstDown
6+ import androidx.compose.foundation.gestures.detectTapGestures
67import androidx.compose.foundation.interaction.MutableInteractionSource
8+ import androidx.compose.foundation.interaction.PressInteraction
79import androidx.compose.material3.ripple
8- import androidx.compose.runtime.remember
910import androidx.compose.ui.Modifier
10- import androidx.compose.ui.composed
1111import androidx.compose.ui.draw.drawWithContent
1212import androidx.compose.ui.graphics.layer.GraphicsLayer
1313import androidx.compose.ui.graphics.layer.drawLayer
1414import androidx.compose.ui.input.pointer.PointerEventPass
15+ import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
1516import androidx.compose.ui.input.pointer.pointerInput
16- import androidx.compose.ui.platform.debugInspectorInfo
17+ import androidx.compose.ui.node.DelegatingNode
18+ import androidx.compose.ui.node.ModifierNodeElement
19+ import androidx.compose.ui.node.SemanticsModifierNode
1720import androidx.compose.ui.semantics.Role
21+ import androidx.compose.ui.semantics.SemanticsPropertyReceiver
22+ import androidx.compose.ui.semantics.disabled
23+ import androidx.compose.ui.semantics.onClick
24+ import androidx.compose.ui.semantics.role
1825import com.ninecraft.booket.core.common.utils.MultipleEventsCutter
1926import com.ninecraft.booket.core.common.utils.get
2027
2128// https://stackoverflow.com/questions/66703448/how-to-disable-ripple-effect-when-clicking-in-jetpack-compose
22- inline fun Modifier.noRippleClickable (crossinline onClick : () -> Unit ): Modifier = composed {
23- clickable(
29+ fun Modifier.noRippleClickable (onClick : () -> Unit ): Modifier =
30+ this .clickable(
31+ interactionSource = null ,
2432 indication = null ,
25- interactionSource = remember { MutableInteractionSource () },
26- ) {
27- onClick()
28- }
29- }
33+ onClick = onClick,
34+ )
3035
3136fun Modifier.clickableSingle (
3237 enabled : Boolean = true,
3338 onClickLabel : String? = null,
3439 role : Role ? = null,
3540 onClick : () -> Unit ,
36- ) = composed(
37- inspectorInfo = debugInspectorInfo {
38- name = " clickable"
39- properties[" enabled" ] = enabled
40- properties[" onClickLabel" ] = onClickLabel
41- properties[" role" ] = role
42- properties[" onClick" ] = onClick
43- },
44- ) {
45- val multipleEventsCutter = remember { MultipleEventsCutter .get() }
46- Modifier .clickable(
47- enabled = enabled,
48- onClickLabel = onClickLabel,
49- onClick = { multipleEventsCutter.processEvent { onClick() } },
50- role = role,
51- indication = ripple(),
52- interactionSource = remember { MutableInteractionSource () },
41+ ): Modifier = this then ClickableSingleElement (enabled, onClickLabel, role, onClick)
42+
43+ private data class ClickableSingleElement (
44+ private val enabled : Boolean ,
45+ private val onClickLabel : String? ,
46+ private val role : Role ? ,
47+ private val onClick : () -> Unit ,
48+ ) : ModifierNodeElement<ClickableSingleNode>() {
49+ override fun create () = ClickableSingleNode (enabled, onClickLabel, role, onClick)
50+ override fun update (node : ClickableSingleNode ) {
51+ node.update(enabled, onClickLabel, role, onClick)
52+ }
53+ }
54+
55+ private class ClickableSingleNode (
56+ private var enabled : Boolean ,
57+ private var onClickLabel : String? ,
58+ private var role : Role ? ,
59+ private var onClick : () -> Unit ,
60+ ) : DelegatingNode(), SemanticsModifierNode {
61+ private val multipleEventsCutter = MultipleEventsCutter .get()
62+ private val interactionSource = MutableInteractionSource ()
63+
64+ @Suppress(" unused" )
65+ private val indicationNode = delegate(
66+ ripple().create(interactionSource),
5367 )
68+
69+ @Suppress(" unused" )
70+ private val pointerInputNode = delegate(
71+ SuspendingPointerInputModifierNode {
72+ if (! enabled) return @SuspendingPointerInputModifierNode
73+ detectTapGestures(
74+ onPress = { offset ->
75+ val press = PressInteraction .Press (offset)
76+ interactionSource.emit(press)
77+ val released = tryAwaitRelease()
78+ if (released) {
79+ interactionSource.emit(PressInteraction .Release (press))
80+ } else {
81+ interactionSource.emit(PressInteraction .Cancel (press))
82+ }
83+ },
84+ onTap = {
85+ multipleEventsCutter.processEvent { onClick() }
86+ },
87+ )
88+ },
89+ )
90+
91+ override fun SemanticsPropertyReceiver.applySemantics () {
92+ onClick(label = onClickLabel) {
93+ if (! enabled) return @onClick false
94+ multipleEventsCutter.processEvent { this @ClickableSingleNode.onClick() }
95+ true
96+ }
97+ this @ClickableSingleNode.role?.let { this .role = it }
98+ if (! enabled) {
99+ disabled()
100+ }
101+ }
102+
103+ fun update (enabled : Boolean , onClickLabel : String? , role : Role ? , onClick : () -> Unit ) {
104+ this .enabled = enabled
105+ this .onClickLabel = onClickLabel
106+ this .role = role
107+ this .onClick = onClick
108+ }
54109}
55110
56111fun Modifier.captureToGraphicsLayer (graphicsLayer : GraphicsLayer ) =
0 commit comments