|
@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() |
|
) |
|
} |
|
) |
|
|