diff --git a/packages/expo-ui/CHANGELOG.md b/packages/expo-ui/CHANGELOG.md index 91c5717461aef0..4862074f544b90 100644 --- a/packages/expo-ui/CHANGELOG.md +++ b/packages/expo-ui/CHANGELOG.md @@ -28,6 +28,7 @@ ### 🐛 Bug fixes +- [Android] Fix a drag that starts on a hosted `TextInput` not scrolling the `ScrollView` around it. React Native's text input asks its ancestors not to intercept the gesture, then releases them one move later, and Jetpack Compose read that release as "Compose claimed the gesture" and cancelled the hosted subtree. `RNHostView` no longer passes such a release on to Compose. - [Android] Fix the system status bar and navigation bar turning light while a `BottomSheet` or `ModalBottomSheet` with a custom dark background is open. A custom container color matches no color-scheme role, so the default content color fell back to black and Material3 themed the sheet window's system bars from it. The default content color is now derived from the container color's luminance, so it also contrasts with a custom background. ([#49394](https://github.com/expo/expo/pull/49394) by [@expo-bot](https://github.com/expo-bot)) - [iOS] Fixed a crash when a focused `TextField` or `SecureField` is unmounted inside a list row that is being removed, for example closing a modal with the keyboard up on a field nested in `SwipeActions`. Hosted text inputs now blur before React removes their native views. ([#49348](https://github.com/expo/expo/issues/49348) by [@nishan](https://github.com/intergalacticspacehighway)) ([#49357](https://github.com/expo/expo/pull/49357) by [@expo-tuft[bot]](https://github.com/apps/expo-tuft)) - Fixed the keyboard staying open when tapping outside a text field hosted by ``. A parent `ScrollView`'s tap-to-dismiss, its `keyboardShouldPersistTaps` setting, and `Keyboard.dismiss()` now reach hosted text fields. React Native treats the whole `` as the input, so a tap on other content inside the same host still keeps the keyboard open. ([#48788](https://github.com/expo/expo/pull/48788) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway)) diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/RNHostView.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/RNHostView.kt index 4985363753510f..299a25c6578e1e 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/RNHostView.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/RNHostView.kt @@ -270,6 +270,10 @@ private class TouchDispatchingRootViewGroup( // True if the sheet consumed scroll on the most recent drag frame; drives the settle decision. private var sheetMovingOnLastDragFrame = false + // True once a descendant asked us to stop ancestors from intercepting this gesture, so a later + // release from a different descendant doesn't reach Compose. See requestDisallowInterceptTouchEvent. + private var forwardedDisallowIntercept = false + // True once a fling was dispatched this gesture, so the gentle-release settle doesn't double-fire. private var flingHandledThisGesture = false @@ -336,6 +340,7 @@ private class TouchDispatchingRootViewGroup( trackingGestureOffset = true sheetMovingOnLastDragFrame = false flingHandledThisGesture = false + forwardedDisallowIntercept = false } // While a nested scroll is in flight the sheet may be sliding this whole view up/down. Re-express @@ -440,6 +445,19 @@ private class TouchDispatchingRootViewGroup( // yields. But don't call super: setting our own FLAG_DISALLOW_INTERCEPT would skip // onInterceptTouchEvent, which must keep firing to dispatch touches to JS (the reason #43716 // added this override). + // + // Never forward a release after a claim in the same gesture. Compose's `AndroidView` interop + // cancels this subtree when the flag goes true then false inside one move event: it dispatches + // the move and consumes it on the initial pass, then reads that same consumption on the final + // pass as "Compose claimed the gesture" and sends ACTION_CANCEL down here. `ReactEditText` makes + // exactly that flip — it claims on ACTION_DOWN and releases on the first ACTION_MOVE — so a drag + // that starts on a TextInput killed the hosted ScrollView. The release is meant for the React + // Native ancestors inside this wrapper, which still get it; Compose clears its own flag when the + // gesture ends. + if (!disallowIntercept && forwardedDisallowIntercept) { + return + } + forwardedDisallowIntercept = disallowIntercept parent?.requestDisallowInterceptTouchEvent(disallowIntercept) }