Properly support Dialog.dismissOnClickOutside
Fixes: 364508685
The dialog should support dismissing when clicking outside.
This fixes the support when decorFitsSystemWindows = false.
Test: manual testing, new tests
Change-Id: If49301c4fbe826dceaa5680a523c05fc1e95c816
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/DialogTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/DialogTest.kt
index ce12b6d..9885522 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/DialogTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/DialogTest.kt
@@ -106,6 +106,20 @@
}
@Test
+ fun dialogTest_isNotDismissed_whenClicked_noClickableContent() {
+ setupDialogTest { DefaultDialogContent() }
+
+ val interaction = rule.onNodeWithTag(testTag)
+ interaction.assertIsDisplayed()
+
+ // Click inside the dialog
+ interaction.performClick()
+
+ // Check that the Clickable was pressed and the Dialog is still visible.
+ interaction.assertIsDisplayed()
+ }
+
+ @Test
fun dialogTest_isDismissed_whenSpecified() {
setupDialogTest()
val textInteraction = rule.onNodeWithTag(testTag)
@@ -116,6 +130,16 @@
}
@Test
+ fun dialogTest_isDismissed_whenSpecified_decorFitsFalse() {
+ setupDialogTest(dialogProperties = DialogProperties(decorFitsSystemWindows = false))
+ val textInteraction = rule.onNodeWithTag(testTag)
+ textInteraction.assertIsDisplayed()
+
+ clickOutsideDialog()
+ textInteraction.assertDoesNotExist()
+ }
+
+ @Test
fun dialogTest_isNotDismissed_whenNotSpecified() {
setupDialogTest(closeDialogOnDismiss = false)
val textInteraction = rule.onNodeWithTag(testTag)
@@ -138,6 +162,20 @@
}
@Test
+ fun dialogTest_isNotDismissed_whenDismissOnClickOutsideIsFalse_decorFitsFalse() {
+ setupDialogTest(
+ dialogProperties =
+ DialogProperties(dismissOnClickOutside = false, decorFitsSystemWindows = false)
+ )
+ val textInteraction = rule.onNodeWithTag(testTag)
+ textInteraction.assertIsDisplayed()
+
+ clickOutsideDialog()
+ // The Dialog should still be visible
+ textInteraction.assertIsDisplayed()
+ }
+
+ @Test
fun dialogTest_isDismissed_whenSpecified_backButtonPressed() {
setupDialogTest()
val textInteraction = rule.onNodeWithTag(testTag)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
index 8e4f291..cdf731a 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
@@ -25,6 +25,7 @@
import android.view.MotionEvent
import android.view.View
import android.view.View.OnLayoutChangeListener
+import android.view.View.OnTouchListener
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import android.view.Window
@@ -73,6 +74,7 @@
import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import java.util.UUID
+import kotlin.math.roundToInt
/**
* Properties used to customize the behavior of a [Dialog].
@@ -274,7 +276,8 @@
),
ViewRootForInspector,
OnApplyWindowInsetsListener,
- OnLayoutChangeListener {
+ OnLayoutChangeListener,
+ OnTouchListener {
private val dialogLayout: DialogLayout
@@ -341,7 +344,6 @@
)
.also { it.gravity = Gravity.CENTER }
)
- frameLayout.setOnClickListener { onDismissRequest() }
ViewCompat.setOnApplyWindowInsetsListener(frameLayout, this)
ViewCompat.setWindowInsetsAnimationCallback(
frameLayout,
@@ -454,6 +456,9 @@
dialogLayout.usePlatformDefaultWidth = properties.usePlatformDefaultWidth
val decorFitsSystemWindows = adjustedDecorFitsSystemWindows(properties, context)
dialogLayout.decorFitsSystemWindows = decorFitsSystemWindows
+ setCanceledOnTouchOutside(properties.dismissOnClickOutside)
+ val frameLayout = dialogLayout.parent as View
+ frameLayout.setOnTouchListener(if (properties.dismissOnClickOutside) this else null)
val window = window
if (window != null) {
val softInput =
@@ -533,6 +538,24 @@
) {
v.requestApplyInsets()
}
+
+ override fun onTouch(v: View, event: MotionEvent): Boolean {
+ // This handler only set when properties.dismissOnClickOutside is true
+ if (event.actionMasked == MotionEvent.ACTION_UP) {
+ val x = event.x.roundToInt()
+ val y = event.y.roundToInt()
+ val insideContent =
+ x in dialogLayout.left..dialogLayout.right &&
+ y in dialogLayout.top..dialogLayout.bottom
+ if (!insideContent) {
+ onDismissRequest()
+ return true
+ }
+ }
+ // We must always accept the ACTION_DOWN or else we don't receive the rest of the
+ // event stream.
+ return event.actionMasked == MotionEvent.ACTION_DOWN
+ }
}
@Composable