-
Notifications
You must be signed in to change notification settings - Fork 0
refactor: composed {} 를 이용한 Custom Modifier 구현 Modifier.Node API 사용 방식으로 변경 #263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,54 +3,109 @@ 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<ClickableSingleNode>() { | ||
| 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() } | ||
| }, | ||
| ) | ||
| }, | ||
| ) | ||
|
Comment on lines
+69
to
+89
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
🩹 수정 제안- `@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() }
},
)
},
)
fun update(enabled: Boolean, onClickLabel: String?, role: Role?, onClick: () -> Unit) {
+ val enabledChanged = this.enabled != enabled
this.enabled = enabled
this.onClickLabel = onClickLabel
this.role = role
this.onClick = onClick
+ if (enabledChanged) {
+ pointerInputNode.resetPointerInputHandler()
+ }
}Also applies to: 103-108 🤖 Prompt for AI Agents |
||
|
|
||
| override fun SemanticsPropertyReceiver.applySemantics() { | ||
| onClick(label = onClickLabel) { | ||
| if (!enabled) return@onClick false | ||
| multipleEventsCutter.processEvent { this@ClickableSingleNode.onClick() } | ||
| true | ||
| } | ||
| this@ClickableSingleNode.role?.let { this.role = it } | ||
| if (!enabled) { | ||
| disabled() | ||
| } | ||
| } | ||
|
|
||
| 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) = | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.