Repository: android/compose-samples
File: Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/RecordButton.kt (Kotlin)

Code before:
        RichTooltipBox(
            text = { Text("Touch and hold to record") },
            tooltipState = tooltipState
        ) {
            Icon(
                Icons.Default.Mic,
                contentDescription = "Record voice message",
                tint = iconColor.value,
                modifier = modifier
                    .sizeIn(minWidth = 56.dp, minHeight = 6.dp)
                    .padding(18.dp)
                    .recordGesture(
                        swipeOffset,
                        swipeToCancelThreshold(),
                        verticalThreshold(),
                        { scope.launch { tooltipState.show() } },
                        onStartRecording,
                        onFinishRecording,
                        onCancelRecording,
                    )
            )
        }
    }
}

private fun Modifier.recordGesture(
    horizontalSwipeProgress: MutableState<Float>,
    swipeToCancelThreshold: Float,
    verticalThreshold: Float,
    onClick: () -> Unit,
    onStartRecording: () -> Boolean,
    onFinishRecording: () -> Unit,
    onCancelRecording: () -> Unit,
): Modifier {
    var offsetY = 0f
    return this
        .pointerInput(Unit) { detectTapGestures { onClick() } }
        .pointerInput(Unit) {
            detectDragGesturesAfterLongPress(
                onDragStart = {
                    horizontalSwipeProgress.value = 0f
                    offsetY = 0f
                    onStartRecording()
                },
                onDragCancel = { onCancelRecording() },
                onDragEnd = { onFinishRecording() },
                onDrag = { _, dragAmount ->
                    horizontalSwipeProgress.value += dragAmount.x
                    offsetY += dragAmount.y
                    val offsetX = horizontalSwipeProgress.value
                    if ((offsetX < 0) &&

Review comment (suggestion):
nit: maybe should be named `voiceRecordingGesture`, or something similar.. Reading this initially I thought it meant recording the gesture. 

Code after:
            text = { Text(stringResource(R.string.touch_and_hold_to_record)) },
            tooltipState = tooltipState
        ) {
            Icon(
                Icons.Default.Mic,
                contentDescription = stringResource(R.string.record_message),
                tint = iconColor.value,
                modifier = modifier
                    .sizeIn(minWidth = 56.dp, minHeight = 6.dp)
                    .padding(18.dp)
                    .clickable { }
                    .voiceRecordingGesture(
                        horizontalSwipeProgress = swipeOffset,
                        onSwipeProgressChanged = onSwipeOffsetChange,
                        onClick = { scope.launch { tooltipState.show() } },
                        onStartRecording = onStartRecording,
                        onFinishRecording = onFinishRecording,
                        onCancelRecording = onCancelRecording,
                    )
            )
        }
    }
}

private fun Modifier.voiceRecordingGesture(
    horizontalSwipeProgress: () -> Float,
    onSwipeProgressChanged: (Float) -> Unit,
    onClick: () -> Unit = {},
    onStartRecording: () -> Boolean = { false },
    onFinishRecording: () -> Unit = {},
    onCancelRecording: () -> Unit = {},
    swipeToCancelThreshold: Dp = 200.dp,
    verticalThreshold: Dp = 80.dp,
): Modifier = composed {
    val density = LocalDensity.current
    val swipeToCancelThresholdPx = with(density) { swipeToCancelThreshold.toPx() }
    val verticalThresholdPx = with(density) { verticalThreshold.toPx() }
    var offsetY = 0f
    this
        .pointerInput(Unit) { detectTapGestures { onClick() } }
        .pointerInput(Unit) {
            detectDragGesturesAfterLongPress(
                onDragStart = {
                    onSwipeProgressChanged(0f)
                    offsetY = 0f
                    onStartRecording()
                },
                onDragCancel = { onCancelRecording() },
                onDragEnd = { onFinishRecording() },
                onDrag = { _, dragAmount ->
                    onSwipeProgressChanged(horizontalSwipeProgress() + dragAmount.x)