From 4aac2febc5ee2bb5f3502f51d5cb4710497601f5 Mon Sep 17 00:00:00 2001 From: miguelsm Date: Fri, 16 Mar 2018 16:59:49 -0700 Subject: [PATCH] Add a way to dismiss PopupMenu elements Summary: In native Android apps, like the YouTube app, context menus are closed when the device orientation changes. In React Native apps instead, when having a [PopupMenu](https://developer.android.com/reference/android/widget/PopupMenu.html) open and rotating the device, the PopupMenu is not dismissed and appears in a wrong position on the screen. This PR exposes a `dismissPopupMenu` method to allow the application to dismiss any open PopupMenu: ```(javascript) UIManager.dismissPopupMenu() ``` Closes https://github.com/facebook/react-native/pull/15636 Differential Revision: D6837663 Pulled By: hramos fbshipit-source-id: 7b0f4f04341129ad45c703a50897e17d93651974 --- .../uimanager/NativeViewHierarchyManager.java | 20 ++++++++++++----- .../react/uimanager/UIImplementation.java | 4 ++++ .../react/uimanager/UIManagerModule.java | 5 +++++ .../react/uimanager/UIViewOperationQueue.java | 11 ++++++++++ .../views/toolbar/ReactToolbarManager.java | 22 +++++++++++++++++++ 5 files changed, 57 insertions(+), 5 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index 2b259c6806ff6c..a57205b861ebe2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -74,6 +74,7 @@ public class NativeViewHierarchyManager { private final LayoutAnimationController mLayoutAnimator = new LayoutAnimationController(); private boolean mLayoutAnimationEnabled; + private PopupMenu mPopupMenu; public NativeViewHierarchyManager(ViewManagerRegistry viewManagers) { this(viewManagers, new RootViewManager()); @@ -731,18 +732,27 @@ public synchronized void showPopupMenu(int reactTag, ReadableArray items, Callba error.invoke("Can't display popup. Could not find view with tag " + reactTag); return; } - PopupMenu popupMenu = new PopupMenu(getReactContextForView(reactTag), anchor); + mPopupMenu = new PopupMenu(getReactContextForView(reactTag), anchor); - Menu menu = popupMenu.getMenu(); + Menu menu = mPopupMenu.getMenu(); for (int i = 0; i < items.size(); i++) { menu.add(Menu.NONE, Menu.NONE, i, items.getString(i)); } PopupMenuCallbackHandler handler = new PopupMenuCallbackHandler(success); - popupMenu.setOnMenuItemClickListener(handler); - popupMenu.setOnDismissListener(handler); + mPopupMenu.setOnMenuItemClickListener(handler); + mPopupMenu.setOnDismissListener(handler); - popupMenu.show(); + mPopupMenu.show(); + } + + /** + * Dismiss the last opened PopupMenu {@link PopupMenu}. + */ + public void dismissPopupMenu() { + if (mPopupMenu != null) { + mPopupMenu.dismiss(); + } } private static class PopupMenuCallbackHandler implements PopupMenu.OnMenuItemClickListener, diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java index c312ff763b0691..46353bc28fc390 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -802,6 +802,10 @@ public void showPopupMenu(int reactTag, ReadableArray items, Callback error, Cal mOperationsQueue.enqueueShowPopupMenu(reactTag, items, error, success); } + public void dismissPopupMenu() { + mOperationsQueue.enqueueDismissPopupMenu(); + } + public void sendAccessibilityEvent(int tag, int eventType) { mOperationsQueue.enqueueSendAccessibilityEvent(tag, eventType); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index 282c92ca3814a8..57430f3d07e7dd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -600,6 +600,11 @@ public void showPopupMenu(int reactTag, ReadableArray items, Callback error, Cal mUIImplementation.showPopupMenu(reactTag, items, error, success); } + @ReactMethod + public void dismissPopupMenu() { + mUIImplementation.dismissPopupMenu(); + } + /** * LayoutAnimation API on Android is currently experimental. Therefore, it needs to be enabled * explicitly in order to avoid regression in existing application written for iOS using this API. diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index 1f2135db86523e..832b94ce2cdcd9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -285,6 +285,13 @@ public void execute() { } } + private final class DismissPopupMenuOperation implements UIOperation { + @Override + public void execute() { + mNativeViewHierarchyManager.dismissPopupMenu(); + } + } + /** * A spec for animation operations (add/remove) */ @@ -656,6 +663,10 @@ public void enqueueShowPopupMenu( mOperations.add(new ShowPopupMenuOperation(reactTag, items, error, success)); } + public void enqueueDismissPopupMenu() { + mOperations.add(new DismissPopupMenuOperation()); + } + public void enqueueCreateView( ThemedReactContext themedContext, int viewReactTag, diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java index be9377047a90c8..0de356f1b95ed1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java @@ -33,6 +33,7 @@ public class ReactToolbarManager extends ViewGroupManager { private static final String REACT_CLASS = "ToolbarAndroid"; + private static final int COMMAND_DISMISS_POPUP_MENUS = 1; @Override public String getName() { @@ -157,6 +158,27 @@ public boolean needsCustomLayoutForChildren() { return true; } + @Nullable + @Override + public Map getCommandsMap() { + return MapBuilder.of("dismissPopupMenus", COMMAND_DISMISS_POPUP_MENUS); + } + + @Override + public void receiveCommand(ReactToolbar view, int commandType, @Nullable ReadableArray args) { + switch (commandType) { + case COMMAND_DISMISS_POPUP_MENUS: { + view.dismissPopupMenus(); + return; + } + default: + throw new IllegalArgumentException(String.format( + "Unsupported command %d received by %s.", + commandType, + getClass().getSimpleName())); + } + } + private int[] getDefaultContentInsets(Context context) { Resources.Theme theme = context.getTheme(); TypedArray toolbarStyle = null;