Skip to content

Commit

Permalink
Merge pull request #169 from MohamedRejeb/1.x
Browse files Browse the repository at this point in the history
Fix RichText intercepts click events
  • Loading branch information
MohamedRejeb authored Dec 31, 2023
2 parents 1a87d96 + 4f88c73 commit d03fe3c
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.mohamedrejeb.richeditor.gesture

import androidx.compose.foundation.gestures.*
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.AwaitPointerEventScope
import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
import androidx.compose.ui.platform.ViewConfiguration
import com.mohamedrejeb.richeditor.utils.fastAny
import com.mohamedrejeb.richeditor.utils.fastForEach
import kotlinx.coroutines.coroutineScope

/**
* Consumes all pointer events until nothing is pressed and then returns. This method assumes
* that something is currently pressed.
*/
private suspend fun AwaitPointerEventScope.consumeUntilUp() {
do {
val event = awaitPointerEvent()
event.changes.fastForEach { it.consume() }
} while (event.changes.fastAny { it.pressed })
}

/**
* Waits for [ViewConfiguration.doubleTapTimeoutMillis] for a second press event. If a
* second press event is received before the time out, it is returned or `null` is returned
* if no second press is received.
*/
private suspend fun AwaitPointerEventScope.awaitSecondDown(
firstUp: PointerInputChange
): PointerInputChange? = withTimeoutOrNull(viewConfiguration.doubleTapTimeoutMillis) {
val minUptime = firstUp.uptimeMillis + viewConfiguration.doubleTapMinTimeMillis
var change: PointerInputChange
// The second tap doesn't count if it happens before DoubleTapMinTime of the first tap
do {
change = awaitFirstDown()
} while (change.uptimeMillis < minUptime)
change
}

internal suspend fun PointerInputScope.detectTapGestures(
onDoubleTap: ((Offset) -> Unit)? = null,
onLongPress: ((Offset) -> Unit)? = null,
onTap: ((Offset) -> Unit)? = null,
consumeDown: (Offset) -> Boolean,
) = coroutineScope {
awaitEachGesture {
val down = awaitFirstDown()
if (!consumeDown(down.position)) return@awaitEachGesture
down.consume()
val longPressTimeout = onLongPress?.let {
viewConfiguration.longPressTimeoutMillis
} ?: (Long.MAX_VALUE / 2)
var upOrCancel: PointerInputChange? = null
try {
// wait for first tap up or long press
upOrCancel = withTimeout(longPressTimeout) {
waitForUpOrCancellation()
}
upOrCancel?.consume()
} catch (_: PointerEventTimeoutCancellationException) {
onLongPress?.invoke(down.position)
consumeUntilUp()
}

if (upOrCancel != null) {
// tap was successful.
if (onDoubleTap == null) {
onTap?.invoke(upOrCancel.position) // no need to check for double-tap.
} else {
// check for second tap
val secondDown = awaitSecondDown(upOrCancel)

if (secondDown == null) {
onTap?.invoke(upOrCancel.position) // no valid second tap started
} else {
try {
// Might have a long second press as the second tap
withTimeout(longPressTimeout) {
val secondUp = waitForUpOrCancellation()
if (secondUp != null) {
secondUp.consume()
onDoubleTap(secondUp.position)
} else {
onTap?.invoke(upOrCancel.position)
}
}
} catch (e: PointerEventTimeoutCancellationException) {
// The first tap was valid, but the second tap is a long press.
// notify for the first tap
onTap?.invoke(upOrCancel.position)

// notify for the long press
onLongPress?.invoke(secondDown.position)
consumeUntilUp()
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.mohamedrejeb.richeditor.ui

import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.runtime.Composable
Expand All @@ -16,6 +15,7 @@ import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import com.mohamedrejeb.richeditor.gesture.detectTapGestures
import com.mohamedrejeb.richeditor.model.RichTextState

@Composable
Expand Down Expand Up @@ -63,7 +63,10 @@ fun BasicRichText(
e.printStackTrace()
}
}
}
},
consumeDown = { offset ->
state.isLink(offset)
},
)
},
style = style,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,18 @@ internal inline fun <T, R> List<T>.fastMap(
}

/**
* Copied from [androidx](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TempListUtils.kt;l=107;drc=ceaa7640c065146360515e598a3d09f6f66553dd).
* Returns `true` if at least one element matches the given [predicate].
*
* **Do not use for collections that come from public APIs**, since they may not support random
* access in an efficient way, and this method may actually be a lot slower. Only use for
* collections that are created by code we control and are known to support random access.
*/
@Suppress("BanInlineOptIn") // Treat Kotlin Contracts as non-experimental.
@Suppress("BanInlineOptIn")
@OptIn(ExperimentalContracts::class)
internal inline fun <T, R> List<T>.fastFold(initial: R, operation: (acc: R, T) -> R): R {
contract { callsInPlace(operation) }
var accumulator = initial
fastForEach { e ->
accumulator = operation(accumulator, e)
}
return accumulator
internal inline fun <T> List<T>.fastAny(predicate: (T) -> Boolean): Boolean {
contract { callsInPlace(predicate) }
fastForEach { if (predicate(it)) return true }
return false
}

@OptIn(ExperimentalContracts::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ fun SlackDemoContent() {
topBar = {
Column(
modifier = Modifier

) {
TopAppBar(
title = { Text("Slack Demo") },
Expand Down

0 comments on commit d03fe3c

Please sign in to comment.