[go: nahoru, domu]

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