diff --git a/core/ui/src/main/java/com/record/ui/extension/Modifier.kt b/core/ui/src/main/java/com/record/ui/extension/Modifier.kt new file mode 100644 index 00000000..2066e528 --- /dev/null +++ b/core/ui/src/main/java/com/record/ui/extension/Modifier.kt @@ -0,0 +1,55 @@ +package com.record.ui.extension + +import android.annotation.SuppressLint +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material.ripple.rememberRipple +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.graphics.Color + +/** + * @param rippleEnabled + * @param rippleColor + * @param runIf clickable 조건 + * @param singleClick 중복 클릭 + * @param onClick + * @return clickable 이 적용된 [Modifier] + */ +@SuppressLint("ModifierFactoryUnreferencedReceiver") +@OptIn(ExperimentalFoundationApi::class) +fun Modifier.customClickable( + rippleEnabled: Boolean = true, + rippleColor: Color? = null, + runIf: Boolean = true, + singleClick: Boolean = true, + onLongClick: (() -> Unit)? = null, + onClick: (() -> Unit)?, +) = runIf(runIf) { + composed { + val multipleEventsCutter = remember { MultipleEventsCutter.get() } + + combinedClickable( + onClick = { + onClick?.let { + if (singleClick) { + multipleEventsCutter.processEvent { + it() + } + } else { + it() + } + } + }, + onLongClick = onLongClick, + indication = rememberRipple( + color = rippleColor ?: Color.Unspecified, + ).takeIf { + rippleEnabled + }, + interactionSource = remember { MutableInteractionSource() }, + ) + } +} diff --git a/core/ui/src/main/java/com/record/ui/extension/MultipleEventsCutter.kt b/core/ui/src/main/java/com/record/ui/extension/MultipleEventsCutter.kt new file mode 100644 index 00000000..840ae3f5 --- /dev/null +++ b/core/ui/src/main/java/com/record/ui/extension/MultipleEventsCutter.kt @@ -0,0 +1,32 @@ +package com.record.ui.extension + +internal inline fun T.runIf(condition: Boolean, run: T.() -> T) = if (condition) { + run() +} else { + this +} + +private const val CLICK_EVENT_DELAY_TIME: Long = 300L + +internal interface MultipleEventsCutter { + fun processEvent(event: () -> Unit) + + companion object +} + +internal fun MultipleEventsCutter.Companion.get(): MultipleEventsCutter = + MultipleEventsCutterImpl() + +private class MultipleEventsCutterImpl : MultipleEventsCutter { + private val now: Long + get() = System.currentTimeMillis() + + private var lastEventTimeMs: Long = 0 + + override fun processEvent(event: () -> Unit) { + if (now - lastEventTimeMs >= CLICK_EVENT_DELAY_TIME) { + event.invoke() + } + lastEventTimeMs = now + } +}