diff --git a/Libraries/Modal/Modal.js b/Libraries/Modal/Modal.js index 9df6208d7860..dfbfdb685542 100644 --- a/Libraries/Modal/Modal.js +++ b/Libraries/Modal/Modal.js @@ -31,8 +31,6 @@ var RCTModalHostView = requireNativeComponent('RCTModalHostView', null); * Navigator instead of Modal. With a top-level Navigator, you have more control * over how to present the modal scene over the rest of your app by using the * configureScene property. - * - * This component is only available in iOS at this time. */ class Modal extends React.Component { render(): ?ReactElement { diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java index 5f0d2d4ead99..aac92767cb20 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -18,6 +18,7 @@ import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.modules.camera.CameraRollManager; +import com.facebook.react.modules.clipboard.ClipboardModule; import com.facebook.react.modules.dialog.DialogModule; import com.facebook.react.modules.fresco.FrescoModule; import com.facebook.react.modules.intent.IntentModule; @@ -32,24 +33,24 @@ import com.facebook.react.views.art.ARTSurfaceViewManager; import com.facebook.react.views.drawer.ReactDrawerLayoutManager; import com.facebook.react.views.image.ReactImageManager; +import com.facebook.react.views.modalhost.ReactModalHostManager; import com.facebook.react.views.picker.ReactDialogPickerManager; import com.facebook.react.views.picker.ReactDropdownPickerManager; import com.facebook.react.views.progressbar.ReactProgressBarViewManager; import com.facebook.react.views.recyclerview.RecyclerViewBackedScrollViewManager; import com.facebook.react.views.scroll.ReactHorizontalScrollViewManager; import com.facebook.react.views.scroll.ReactScrollViewManager; +import com.facebook.react.views.swiperefresh.SwipeRefreshLayoutManager; import com.facebook.react.views.switchview.ReactSwitchManager; import com.facebook.react.views.text.ReactRawTextManager; -import com.facebook.react.views.text.ReactTextViewManager; import com.facebook.react.views.text.ReactTextInlineImageViewManager; +import com.facebook.react.views.text.ReactTextViewManager; import com.facebook.react.views.text.ReactVirtualTextViewManager; import com.facebook.react.views.textinput.ReactTextInputManager; import com.facebook.react.views.toolbar.ReactToolbarManager; import com.facebook.react.views.view.ReactViewManager; import com.facebook.react.views.viewpager.ReactViewPagerManager; -import com.facebook.react.views.swiperefresh.SwipeRefreshLayoutManager; import com.facebook.react.views.webview.ReactWebViewManager; -import com.facebook.react.modules.clipboard.ClipboardModule; /** * Package defining basic modules and view managers. @@ -102,6 +103,7 @@ public List createViewManagers(ReactApplicationContext reactContext new ReactTextInlineImageViewManager(), new ReactVirtualTextViewManager(), new SwipeRefreshLayoutManager(), - new ReactWebViewManager()); + new ReactWebViewManager(), + new ReactModalHostManager()); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/modalhost/DismissEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/modalhost/DismissEvent.java new file mode 100644 index 000000000000..b7111a3d58d2 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/modalhost/DismissEvent.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.views.modalhost; + +import com.facebook.react.uimanager.events.Event; +import com.facebook.react.uimanager.events.RCTEventEmitter; + +/** + * {@link Event} for dismissing a Dialog. + */ +public class DismissEvent extends Event { + + public static final String EVENT_NAME = "topDismiss"; + + protected DismissEvent(int viewTag, long timestampMs) { + super(viewTag, timestampMs); + } + + @Override + public String getEventName() { + return EVENT_NAME; + } + + @Override + public void dispatch(RCTEventEmitter rctEventEmitter) { + rctEventEmitter.receiveEvent(getViewTag(), getEventName(), null); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/modalhost/ReactModalHostManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/modalhost/ReactModalHostManager.java new file mode 100644 index 000000000000..924f2b0bdc7f --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/modalhost/ReactModalHostManager.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.views.modalhost; + +import android.content.DialogInterface; +import android.os.SystemClock; + +import com.facebook.react.common.MapBuilder; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.ViewGroupManager; +import com.facebook.react.uimanager.annotations.ReactProp; + +import java.util.Map; + +/** + * View manager for {@link ReactModalHostView} components. + * + * Emits an {@code onDismiss} event when the Dialog host is dismissed. + */ +public class ReactModalHostManager extends ViewGroupManager { + + private static final String REACT_CLASS = "RCTModalHostView"; + + @ReactProp(name = "animated") + public void setAnimated(ReactModalHostView view, boolean animated) { + // TODO(8776300): Implement this + } + + @ReactProp(name = "transparent") + public void setTransparent(ReactModalHostView view, boolean transparent) { + view.setTransparent(transparent); + } + + @Override + public String getName() { + return REACT_CLASS; + } + + @Override + protected ReactModalHostView createViewInstance(ThemedReactContext reactContext) { + return new ReactModalHostView(reactContext); + } + + @Override + public void onDropViewInstance(ReactModalHostView view) { + super.onDropViewInstance(view); + view.dismissModal(); + } + + @Override + protected void addEventEmitters( + final ThemedReactContext reactContext, + final ReactModalHostView view) { + view.setOnDismissListener( + new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + reactContext.getNativeModule(UIManagerModule.class) + .getEventDispatcher() + .dispatchEvent(new DismissEvent(view.getId(), SystemClock.uptimeMillis())); + } + }); + } + + @Override + public Map getExportedCustomDirectEventTypeConstants() { + return MapBuilder.builder() + .put(DismissEvent.EVENT_NAME, MapBuilder.of("registrationName", "onDismiss")) + .build(); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/modalhost/ReactModalHostView.java b/ReactAndroid/src/main/java/com/facebook/react/views/modalhost/ReactModalHostView.java new file mode 100644 index 000000000000..2150ee35a1a2 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/modalhost/ReactModalHostView.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.views.modalhost; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; + +import com.facebook.react.common.annotations.VisibleForTesting; +import com.facebook.react.views.view.ReactViewGroup; + +/** + * A Modal view that works as a base ViewGroup to host other views. + */ +public class ReactModalHostView extends ViewGroup { + + private ReactViewGroup mHostView; + private Dialog mDialog; + + public ReactModalHostView(Context context) { + super(context); + + mHostView = new ReactViewGroup(context); + mHostView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + + mDialog = new Dialog(context, android.R.style.Theme_DeviceDefault_Dialog_NoActionBar); + mDialog.setContentView(mHostView); + mDialog.show(); + mDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + mDialog.getWindow().setLayout( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT); + } + + public void setTransparent(boolean transparent) { + if (transparent) { + mDialog.getWindow().clearFlags( + WindowManager.LayoutParams.FLAG_DIM_BEHIND); + } else { + mDialog.getWindow().setFlags( + WindowManager.LayoutParams.FLAG_DIM_BEHIND, + WindowManager.LayoutParams.FLAG_DIM_BEHIND); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + } + + @Override + public void addView(View child, int index) { + mHostView.addView(child, index); + } + + @Override + public int getChildCount() { + return mHostView.getChildCount(); + } + + @Override + public View getChildAt(int index) { + return mHostView.getChildAt(index); + } + + @Override + public void removeView(View child) { + mHostView.removeView(child); + } + + @Override + public void removeViewAt(int index) { + mHostView.removeViewAt(index); + } + + @Override + public void removeAllViews() { + mHostView.removeAllViews(); + } + + public void dismissModal() { + mDialog.dismiss(); + } + + /*package*/ void setOnDismissListener(DialogInterface.OnDismissListener listener) { + mDialog.setOnDismissListener(listener); + } + + @VisibleForTesting + public Dialog getDialog() { + return mDialog; + } +}