Remove the tap detector used to clear focus
This makes compose similar to the classic view system where
clicking on white space does not clear focus. It also solves
these immediate issues:
1. The tap detector in FocusManager was preventing Android views
behind a Compose View from getting any clicks.
2. We wouldn't clear focus when the user clicked on whitespace
if the container consumed the touch events.
The selection container does not intercept clicks, and instead
of dismissing the selection when the user clicks the selection
container, it relies on focus being cleared as a means to
clear selection. Since this CL removes this feature. It adds a
detector to selection container to clear selection. The detector
is added only when the selection popup is shown and is removed
when the selection popup is not visible.
Bug: 189160953
Bug: 183631681
Bug: 179602539
Test: ./gradlew compose:ui:ui:connectedCheck -P android.testInstrumentationRunnerArguments.class=androidx.compose.ui.focus.ClearFocusTest
Test: ./gradlew compose:foundation:foundation:connectedCheck -P android.testInstrumentationRunnerArguments.class=androidx.compose.foundation.text.selection.SelectionContainerTest
Change-Id: Ic2636589623a048b1d4431c9727d4fb2922fa3e4
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
index 1774fd4..8d516e3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
@@ -20,6 +20,8 @@
import androidx.compose.foundation.fastFold
import androidx.compose.foundation.focusable
+import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.waitForUpOrCancellation
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -36,6 +38,8 @@
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
@@ -44,6 +48,7 @@
import androidx.compose.ui.platform.TextToolbar
import androidx.compose.ui.platform.TextToolbarStatus
import androidx.compose.ui.text.AnnotatedString
+import kotlinx.coroutines.coroutineScope
import kotlin.math.max
import kotlin.math.min
@@ -103,6 +108,7 @@
* Modifier for selection container.
*/
val modifier get() = Modifier
+ .onClearSelectionRequested { onRelease() }
.onGloballyPositioned { containerLayoutCoordinates = it }
.focusRequester(focusRequester)
.onFocusChanged { focusState ->
@@ -625,6 +631,25 @@
}
}
+ /**
+ * Detect tap without consuming the up event.
+ */
+ private suspend fun PointerInputScope.detectNonConsumingTap(onTap: (Offset) -> Unit) {
+ forEachGesture {
+ coroutineScope {
+ awaitPointerEventScope {
+ waitForUpOrCancellation()?.let {
+ onTap(it.position)
+ }
+ }
+ }
+ }
+ }
+
+ private fun Modifier.onClearSelectionRequested(block: () -> Unit): Modifier {
+ return if (hasFocus) pointerInput(Unit) { detectNonConsumingTap { block() } } else this
+ }
+
private fun convertToContainerCoordinates(
layoutCoordinates: LayoutCoordinates,
offset: Offset