From ffbd2882b24109ff8f2b51ca3c8c88822cc9afb7 Mon Sep 17 00:00:00 2001 From: Guy Carmeli Date: Wed, 5 Feb 2020 17:50:54 +0200 Subject: [PATCH] Support react-native-youtube (#5903) react-native-youtubte is a popular library which wraps the native youtube library. The native lib attempts to detect if the player is hidden behind other views in order to prevent developers from playing videos in the background. Since the overlay container was always attached to hierarchy, the library stopped playback as it mistakingly detected the player was used in the background. This commit simply attaches the overlay container only when needed so as long as no overlays are displayed, the lib can be used. --- .../presentation/OverlayManager.java | 29 +++++++++++--- .../viewcontrollers/navigator/Navigator.java | 2 +- .../navigator/NavigatorTest.java | 13 ++++++- .../overlay/OverlayManagerTest.java | 39 ++++++++++++++----- 4 files changed, 65 insertions(+), 18 deletions(-) diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/presentation/OverlayManager.java b/lib/android/app/src/main/java/com/reactnativenavigation/presentation/OverlayManager.java index 20ed1f2280f..bf0d8ec2c38 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/presentation/OverlayManager.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/presentation/OverlayManager.java @@ -1,19 +1,28 @@ package com.reactnativenavigation.presentation; +import android.view.View; import android.view.ViewGroup; import com.reactnativenavigation.utils.CommandListener; +import com.reactnativenavigation.utils.ViewUtils; import com.reactnativenavigation.viewcontrollers.ViewController; import com.reactnativenavigation.views.BehaviourDelegate; import java.util.HashMap; +import static com.reactnativenavigation.utils.CollectionUtils.*; import static com.reactnativenavigation.utils.CoordinatorLayoutUtils.matchParentWithBehaviour; public class OverlayManager { private final HashMap overlayRegistry = new HashMap<>(); + private ViewGroup contentLayout; + + public void setContentLayout(ViewGroup contentLayout) { + this.contentLayout = contentLayout; + } public void show(ViewGroup overlaysContainer, ViewController overlay, CommandListener listener) { + if (overlaysContainer.getParent() == null) contentLayout.addView(overlaysContainer); overlayRegistry.put(overlay.getId(), overlay); overlay.addOnAppearedListener(() -> listener.onSuccess(overlay.getId())); overlaysContainer.addView(overlay.getView(), matchParentWithBehaviour(new BehaviourDelegate(overlay))); @@ -24,17 +33,14 @@ public void dismiss(String componentId, CommandListener listener) { if (overlay == null) { listener.onError("Could not dismiss Overlay. Overlay with id " + componentId + " was not found."); } else { - overlay.destroy(); - overlayRegistry.remove(componentId); + destroyOverlay(overlay); listener.onSuccess(componentId); } } public void destroy() { - for (ViewController view : overlayRegistry.values()) { - view.destroy(); - } - overlayRegistry.clear(); + forEach(overlayRegistry.values(), this::destroyOverlay); + contentLayout = null; } public int size() { @@ -44,4 +50,15 @@ public int size() { public ViewController findControllerById(String id) { return overlayRegistry.get(id); } + + private void destroyOverlay(ViewController overlay) { + View parent = (View) overlay.getView().getParent(); + overlay.destroy(); + overlayRegistry.remove(overlay.getId()); + if (isEmpty()) ViewUtils.removeFromParent(parent); + } + + private boolean isEmpty() { + return size() == 0; + } } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/navigator/Navigator.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/navigator/Navigator.java index 696bd4159b6..737a5eec1d1 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/navigator/Navigator.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/navigator/Navigator.java @@ -64,9 +64,9 @@ public void setEventEmitter(EventEmitter eventEmitter) { public void setContentLayout(ViewGroup contentLayout) { this.contentLayout = contentLayout; + overlayManager.setContentLayout(contentLayout); contentLayout.addView(rootLayout); contentLayout.addView(modalsLayout); - contentLayout.addView(overlaysLayout); } public Navigator(final Activity activity, ChildControllersRegistry childRegistry, ModalStack modalStack, OverlayManager overlayManager, RootPresenter rootPresenter) { diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/NavigatorTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/NavigatorTest.java index 60379da9371..faf73f2f708 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/NavigatorTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/NavigatorTest.java @@ -2,6 +2,7 @@ import android.os.Bundle; import android.view.View; +import android.view.ViewGroup; import android.widget.FrameLayout; import com.facebook.react.ReactInstanceManager; @@ -118,6 +119,14 @@ public boolean onViewDisappear(View view) { activityController.postCreate(Bundle.EMPTY); } + @Test + public void setContentLayout() { + ViewGroup contentLayout = Mockito.mock(ViewGroup.class); + uut.setContentLayout(contentLayout); + + verify(overlayManager).setContentLayout(contentLayout); + } + @Test public void bindViews() { verify(rootPresenter).setRootContainer(uut.getRootLayout()); @@ -149,11 +158,11 @@ public void setRoot_delegatesToRootPresenter() { @Test public void setRoot_clearsSplashLayout() { FrameLayout content = activity.findViewById(android.R.id.content); - assertThat(content.getChildCount()).isEqualTo(4); // 3 frame layouts and the default splash layout + assertThat(content.getChildCount()).isEqualTo(3); // 2 frame layouts (root and modal containers) and the default splash layout uut.setRoot(child2, new CommandListenerAdapter(), reactInstanceManager); - assertThat(content.getChildCount()).isEqualTo(3); + assertThat(content.getChildCount()).isEqualTo(2); } @Test diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/overlay/OverlayManagerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/overlay/OverlayManagerTest.java index ca442d6c526..b52b4b7deb2 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/overlay/OverlayManagerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/overlay/OverlayManagerTest.java @@ -26,33 +26,43 @@ public class OverlayManagerTest extends BaseTest { private OverlayManager uut; private SimpleViewController overlay1; private SimpleViewController overlay2; - private FrameLayout root; + private FrameLayout contentLayout; + private FrameLayout overlayContainer; @Override public void beforeEach() { Activity activity = newActivity(); - root = new FrameLayout(activity); - root.layout(0, 0, 1000, 1000); - activity.setContentView(root); + contentLayout = new FrameLayout(activity); + contentLayout.layout(0, 0, 1000, 1000); + activity.setContentView(contentLayout); + overlayContainer = new FrameLayout(activity); ChildControllersRegistry childRegistry = new ChildControllersRegistry(); overlay1 = spy(new SimpleViewController(activity, childRegistry, OVERLAY_ID_1, new Options())); overlay2 = spy(new SimpleViewController(activity, childRegistry, OVERLAY_ID_2, new Options())); uut = new OverlayManager(); + uut.setContentLayout(contentLayout); + } + + @Test + public void show_attachesOverlayContainerToContentLayout() { + uut.show(overlayContainer, overlay1, new CommandListenerAdapter()); + assertThat(overlayContainer.getParent()).isEqualTo(contentLayout); + uut.show(overlayContainer, overlay2, new CommandListenerAdapter()); } @Test public void show() { CommandListenerAdapter listener = spy(new CommandListenerAdapter()); - uut.show(root, overlay1, listener); + uut.show(overlayContainer, overlay1, listener); verify(listener, times(1)).onSuccess(OVERLAY_ID_1); - assertThat(overlay1.getView().getParent()).isEqualTo(root); + assertThat(overlay1.getView().getParent()).isEqualTo(overlayContainer); assertMatchParent(overlay1.getView()); } @Test public void dismiss() { - uut.show(root, overlay1, new CommandListenerAdapter()); + uut.show(overlayContainer, overlay1, new CommandListenerAdapter()); assertThat(uut.size()).isOne(); CommandListener listener = spy(new CommandListenerAdapter()); uut.dismiss(overlay1.getId(), listener); @@ -70,11 +80,22 @@ public void dismiss_rejectIfOverlayNotFound() { @Test public void dismiss_onViewReturnedToFront() { - uut.show(root, overlay1, new CommandListenerAdapter()); - uut.show(root, overlay2, new CommandListenerAdapter()); + uut.show(overlayContainer, overlay1, new CommandListenerAdapter()); + uut.show(overlayContainer, overlay2, new CommandListenerAdapter()); verify(overlay1, times(0)).onViewBroughtToFront(); uut.dismiss(OVERLAY_ID_2, new CommandListenerAdapter()); verify(overlay1, times(1)).onViewBroughtToFront(); } + + @Test + public void dismiss_overlayContainerIsRemovedIfAllOverlaysAreDismissed() { + uut.show(overlayContainer, overlay1, new CommandListenerAdapter()); + uut.show(overlayContainer, overlay2, new CommandListenerAdapter()); + + uut.dismiss(OVERLAY_ID_2, new CommandListenerAdapter()); + assertThat(overlayContainer.getParent()).isEqualTo(contentLayout); + uut.dismiss(OVERLAY_ID_1, new CommandListenerAdapter()); + assertThat(overlayContainer.getParent()).isNull(); + } }