Skip to content

Instantly share code, notes, and snippets.

@carterhudson
Last active November 18, 2024 12:55
Show Gist options
  • Save carterhudson/a24086048201f5baf3f791bdd89017a1 to your computer and use it in GitHub Desktop.
Save carterhudson/a24086048201f5baf3f791bdd89017a1 to your computer and use it in GitHub Desktop.
@Composable
fun RepeatingButton(
modifier: Modifier = Modifier,
onClick: () -> Unit,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
elevation: ButtonElevation? = ButtonDefaults.elevation(),
shape: Shape = MaterialTheme.shapes.small,
border: BorderStroke? = null,
colors: ButtonColors = ButtonDefaults.buttonColors(),
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
maxDelayMillis: Long = 1000,
minDelayMillis: Long = 5,
delayDecayFactor: Float = .20f,
content: @Composable RowScope.() -> Unit
) {
Button(
modifier = modifier.repeatingClickable(
interactionSource = interactionSource,
enabled = enabled,
maxDelayMillis = maxDelayMillis,
minDelayMillis = minDelayMillis,
decayFactor = delayDecayFactor
) { onClick() },
onClick = {},
enabled = enabled,
interactionSource = interactionSource,
elevation = elevation,
shape = shape,
border = border,
colors = colors,
contentPadding = contentPadding,
content = content
)
}
fun Modifier.repeatingClickable(
interactionSource: MutableInteractionSource,
enabled: Boolean,
maxDelayMillis: Long = 1000,
minDelayMillis: Long = 5,
delayDecayFactor: Float = .20f,
onClick: () -> Unit
): Modifier = this.then(
composed {
val currentClickListener by rememberUpdatedState(onClick)
val scope = rememberCoroutineScope()
pointerInput(interactionSource, enabled) {
scope.launch {
awaitEachGesture {
val down = awaitFirstDown(requireUnconsumed = false)
// Create a down press interaction
val downPress = PressInteraction.Press(down.position)
val heldButtonJob = launch {
// Send the press through the interaction source
interactionSource.emit(downPress)
var currentDelayMillis = maxDelayMillis
while (enabled && down.pressed) {
currentClickListener()
delay(currentDelayMillis)
val nextMillis = currentDelayMillis - (currentDelayMillis * delayDecayFactor)
currentDelayMillis = nextMillis.toLong().coerceAtLeast(minDelayMillis)
}
}
val up = waitForUpOrCancellation()
heldButtonJob.cancel()
// Determine whether a cancel or release occurred, and create the interaction
val releaseOrCancel = when (up) {
null -> PressInteraction.Cancel(downPress)
else -> PressInteraction.Release(downPress)
}
launch {
// Send the result through the interaction source
interactionSource.emit(releaseOrCancel)
}
}
}
}.indication(
interactionSource = interactionSource,
indication = rememberRipple()
)
}
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment