Skip to content

Commit ba73f80

Browse files
authored
Merge pull request #263 from YAPP-Github/BOOK-501-refactor/#262
refactor: composed {} 를 이용한 Custom Modifier 구현 Modifier.Node API 사용 방식으로 변경
2 parents 18354ac + ec8676f commit ba73f80

1 file changed

Lines changed: 82 additions & 27 deletions

File tree

  • core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions

core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,54 +3,109 @@ package com.ninecraft.booket.core.common.extensions
33
import androidx.compose.foundation.clickable
44
import androidx.compose.foundation.gestures.awaitEachGesture
55
import androidx.compose.foundation.gestures.awaitFirstDown
6+
import androidx.compose.foundation.gestures.detectTapGestures
67
import androidx.compose.foundation.interaction.MutableInteractionSource
8+
import androidx.compose.foundation.interaction.PressInteraction
79
import androidx.compose.material3.ripple
8-
import androidx.compose.runtime.remember
910
import androidx.compose.ui.Modifier
10-
import androidx.compose.ui.composed
1111
import androidx.compose.ui.draw.drawWithContent
1212
import androidx.compose.ui.graphics.layer.GraphicsLayer
1313
import androidx.compose.ui.graphics.layer.drawLayer
1414
import androidx.compose.ui.input.pointer.PointerEventPass
15+
import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
1516
import 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
1720
import 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
1825
import com.ninecraft.booket.core.common.utils.MultipleEventsCutter
1926
import 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

3136
fun 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

56111
fun Modifier.captureToGraphicsLayer(graphicsLayer: GraphicsLayer) =

0 commit comments

Comments
 (0)