Skip to content

Commit

Permalink
Support react-native-youtube (#5903)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
guyca authored Feb 5, 2020
1 parent 030d756 commit ffbd288
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -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<String, ViewController> 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)));
Expand All @@ -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() {
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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();
}
}

0 comments on commit ffbd288

Please sign in to comment.