Skip to content

Commit d42a811

Browse files
author
贾太滨
committed
fix(android): intercept ESCAPE key ACTION_DOWN in Modal to prevent premature dismiss
The Modal's OnKeyListener previously only handled BACK/ESCAPE on ACTION_UP, but Dialog.onKeyDown() directly calls cancel() for KEYCODE_ESCAPE on ACTION_DOWN (unlike KEYCODE_BACK, which defers to onBackPressed on ACTION_UP). As a result, ESCAPE closed the dialog before JS was ever notified. Restructure the listener to branch on keyCode first: - For BACK/ESCAPE: consume ACTION_DOWN to block Dialog's default handling, then call handleCloseAction() on ACTION_UP so JS can decide whether to close. - For all other keys: keep the existing behavior of forwarding ACTION_UP to the current Activity (needed by the dev menu, etc.).
1 parent 68debb2 commit d42a811

1 file changed

Lines changed: 25 additions & 14 deletions

File tree

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -305,23 +305,34 @@ public class ReactModalHostView(context: ThemedReactContext) :
305305
newDialog.setOnKeyListener(
306306
object : DialogInterface.OnKeyListener {
307307
override fun onKey(dialog: DialogInterface, keyCode: Int, event: KeyEvent): Boolean {
308-
if (event.action == KeyEvent.ACTION_UP) {
308+
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
309309
// We need to stop the BACK button and ESCAPE key from closing the dialog by default
310-
// so we capture that event and instead inform JS so that it can make the decision as
311-
// to whether or not to allow the back/escape key to close the dialog. If it chooses
312-
// to, it can just set visible to false on the Modal and the Modal will go away
313-
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
310+
// so we capture those events and instead inform JS so that it can make the decision
311+
// as to whether or not to allow the back/escape key to close the dialog. If it
312+
// chooses to, it can just set visible to false on the Modal and the Modal will go
313+
// away.
314+
// We must intercept both ACTION_DOWN and ACTION_UP for ESCAPE because
315+
// Dialog.onKeyDown() will directly call cancel() on ACTION_DOWN for
316+
// KEYCODE_ESCAPE (unlike KEYCODE_BACK which only starts tracking on ACTION_DOWN
317+
// and defers to onBackPressed() on ACTION_UP). If we don't intercept
318+
// ACTION_DOWN, the dialog is dismissed before our ACTION_UP handler runs.
319+
if (event.action == KeyEvent.ACTION_DOWN) {
320+
// Consume ACTION_DOWN to prevent Dialog.onKeyDown() from closing the dialog.
321+
// For BACK this prevents the default key tracking; for ESCAPE this prevents
322+
// Dialog.cancel() from being called directly.
323+
return true
324+
} else if (event.action == KeyEvent.ACTION_UP) {
314325
handleCloseAction()
315326
return true
316-
} else {
317-
// We redirect the rest of the key events to the current activity, since the
318-
// activity expects to receive those events and react to them, ie. in the case of
319-
// the dev menu
320-
val innerCurrentActivity =
321-
(this@ReactModalHostView.context as ReactContext).currentActivity
322-
if (innerCurrentActivity != null) {
323-
return innerCurrentActivity.onKeyUp(keyCode, event)
324-
}
327+
}
328+
} else if (event.action == KeyEvent.ACTION_UP) {
329+
// We redirect the rest of the key events to the current activity, since the
330+
// activity expects to receive those events and react to them, ie. in the case of
331+
// the dev menu
332+
val innerCurrentActivity =
333+
(this@ReactModalHostView.context as ReactContext).currentActivity
334+
if (innerCurrentActivity != null) {
335+
return innerCurrentActivity.onKeyUp(keyCode, event)
325336
}
326337
}
327338
return false

0 commit comments

Comments
 (0)