From 404b7cc069471cc8e0277d398751305665f0d3e1 Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Wed, 28 Sep 2016 02:53:36 -0700 Subject: [PATCH] BREAKING: Fix modal resizing on keyboard show Summary: This changes modal behavior to resize when the keyboard appears/disappears. Previously, the modal would not react in any way, or it would pan above to bring the TextInput into view. Resizing is the correct behavior for android. This is not trivial, as in, setting the flag, because of the combination of react native laying out all views and the system reacting to the keyboard appearance in a weird way. Namely: - if `windowTranslucentStatus` is not set, the system will just call `onSizeChanged` on the dialog's content view, and everything works nicely - with `windowTranslucentStatus` set, the system will consider the dialog as a full screen view that doesn't resize. In order for it to resize, the base view of the layout needs to have `setFitsSystemWindows(true)` called on it. This is needed, so that the system can call layout on that base view with the new value of `paddingBottom` that coincides with the height of the keyboard. Neat. We fix this by wrapping our existing content view (mHostView) in a simple FrameLayout that has `setFitsSystemWindows` set. That way, `mHostView` will have `onSizeChanged` called on itself with the correct new size of the dialog. This has the fortunate consequence of our layout now also getting `paddingTop` as the size of the status bar, which means that we can remove the JS `top` hack in Modal, which was necessary for no view getting drawn under the status bar. This behavior is set as default, since that is the default correct Android behavior. Reviewed By: astreet Differential Revision: D3913784 fbshipit-source-id: 4378ada21f466dc7ac6e357abeca10b88009ca3f --- Libraries/Modal/Modal.js | 4 +--- .../react/views/modal/ReactModalHostView.java | 21 +++++++++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Libraries/Modal/Modal.js b/Libraries/Modal/Modal.js index 43c1b31135..2813436804 100644 --- a/Libraries/Modal/Modal.js +++ b/Libraries/Modal/Modal.js @@ -16,10 +16,9 @@ const Platform = require('Platform'); const PropTypes = require('react/lib/ReactPropTypes'); const React = require('React'); const StyleSheet = require('StyleSheet'); -const UIManager = require('UIManager'); const View = require('View'); -const deprecatedPropType = require('deprecatedPropType'); +const deprecatedPropType = require('deprecatedPropType'); const requireNativeComponent = require('requireNativeComponent'); const RCTModalHostView = requireNativeComponent('RCTModalHostView', null); @@ -136,7 +135,6 @@ class Modal extends React.Component { const containerStyles = { backgroundColor: this.props.transparent ? 'transparent' : 'white', - top: Platform.OS === 'android' && Platform.Version >= 19 ? UIManager.RCTModalHostView.Constants.StatusBarHeight : 0, }; let animationType = this.props.animationType; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.java b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.java index af6c7449e2..c2a5c933db 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.java @@ -17,13 +17,13 @@ import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; -import android.graphics.Point; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; +import android.widget.FrameLayout; import com.facebook.infer.annotation.Assertions; import com.facebook.react.R; @@ -204,7 +204,7 @@ public class ReactModalHostView extends ViewGroup implements LifecycleEventListe } mDialog = new Dialog(getContext(), theme); - mDialog.setContentView(mHostView); + mDialog.setContentView(getContentView()); updateProperties(); mDialog.setOnShowListener(mOnShowListener); @@ -236,9 +236,23 @@ public class ReactModalHostView extends ViewGroup implements LifecycleEventListe } }); + mDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); mDialog.show(); } + /** + * Returns the view that will be the root view of the dialog. We are wrapping this in a + * FrameLayout because this is the system's way of notifying us that the dialog size has changed. + * This has the pleasant side-effect of us not having to preface all Modals with + * "top: statusBarHeight", since that margin will be included in the FrameLayout. + */ + private View getContentView() { + FrameLayout frameLayout = new FrameLayout(getContext()); + frameLayout.addView(mHostView); + frameLayout.setFitsSystemWindows(true); + return frameLayout; + } + /** * updateProperties will update the properties that do not require us to recreate the dialog * Properties that do require us to recreate the dialog should set mPropertyRequiresNewDialog to @@ -284,9 +298,8 @@ public class ReactModalHostView extends ViewGroup implements LifecycleEventListe new Runnable() { @Override public void run() { - Point modalSize = ModalHostHelper.getModalHostSize(getContext()); ((ReactContext) getContext()).getNativeModule(UIManagerModule.class) - .updateNodeSize(getChildAt(0).getId(), modalSize.x, modalSize.y); + .updateNodeSize(getChildAt(0).getId(), w, h); } }); }