Skip to content

Commit

Permalink
Root layouts refactor (#5999)
Browse files Browse the repository at this point in the history
* Hide modal and overlay containers

This commit improves support for react-native-youtube library. This library uses the native Youtube player which requires that no visible views will be laid out on top of the player. This commit set visibility of Modal and Overlay containers to GONE when they are empty.

* fix tests
  • Loading branch information
guyca authored Mar 4, 2020
1 parent 5210848 commit 2793a02
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,36 @@
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 androidx.annotation.Nullable;

import static com.reactnativenavigation.utils.CollectionUtils.*;
import static com.reactnativenavigation.utils.CoordinatorLayoutUtils.matchParentWithBehaviour;

public class OverlayManager {
private final HashMap<String, ViewController> overlayRegistry = new HashMap<>();

public void show(@Nullable ViewGroup contentLayout, ViewGroup overlaysContainer, ViewController overlay, CommandListener listener) {
if (contentLayout == null) return;
if (overlaysContainer.getParent() == null) contentLayout.addView(overlaysContainer);
public void show(ViewGroup overlaysContainer, ViewController overlay, CommandListener listener) {
overlaysContainer.setVisibility(View.VISIBLE);
overlayRegistry.put(overlay.getId(), overlay);
overlay.addOnAppearedListener(() -> listener.onSuccess(overlay.getId()));
overlaysContainer.addView(overlay.getView(), matchParentWithBehaviour(new BehaviourDelegate(overlay)));
}

public void dismiss(String componentId, CommandListener listener) {
public void dismiss(ViewGroup overlaysContainer, String componentId, CommandListener listener) {
ViewController overlay = overlayRegistry.get(componentId);
if (overlay == null) {
listener.onError("Could not dismiss Overlay. Overlay with id " + componentId + " was not found.");
} else {
destroyOverlay(overlay);
destroyOverlay(overlaysContainer, overlay);
listener.onSuccess(componentId);
}
}

public void destroy() {
forEach(overlayRegistry.values(), this::destroyOverlay);
public void destroy(ViewGroup overlaysContainer) {
forEach(overlayRegistry.values(), overlay -> destroyOverlay(overlaysContainer, overlay));
}

public int size() {
Expand All @@ -48,11 +44,10 @@ public ViewController findControllerById(String id) {
return overlayRegistry.get(id);
}

private void destroyOverlay(ViewController overlay) {
View parent = (View) overlay.getView().getParent();
private void destroyOverlay(ViewGroup overlaysContainer, ViewController overlay) {
overlay.destroy();
overlayRegistry.remove(overlay.getId());
if (isEmpty()) ViewUtils.removeFromParent(parent);
if (isEmpty()) overlaysContainer.setVisibility(View.GONE);
}

private boolean isEmpty() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.view.View;
import android.view.ViewGroup;

import com.reactnativenavigation.anim.ModalAnimator;
Expand Down Expand Up @@ -46,6 +47,7 @@ void showModal(ViewController toAdd, ViewController toRemove, CommandListener li

Options options = toAdd.resolveCurrentOptions(defaultOptions);
toAdd.setWaitForRender(options.animations.showModal.waitForRender);
modalsLayout.setVisibility(View.VISIBLE);
modalsLayout.addView(toAdd.getView(), matchParentLP());

if (options.animations.showModal.enabled.isTrueOrUndefined()) {
Expand Down Expand Up @@ -107,5 +109,10 @@ public void onAnimationEnd(Animator animation) {
private void onDismissEnd(ViewController toDismiss, CommandListener listener) {
listener.onSuccess(toDismiss.getId());
toDismiss.destroy();
if (isEmpty()) modalsLayout.setVisibility(View.GONE);
}

private boolean isEmpty() {
return modalsLayout.getChildCount() == 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public void setContentLayout(ViewGroup contentLayout) {
this.contentLayout = 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 Expand Up @@ -114,7 +115,7 @@ public void destroy() {

public void destroyViews() {
modalStack.destroy();
overlayManager.destroy();
overlayManager.destroy(overlaysLayout);
destroyRoot();
}

Expand Down Expand Up @@ -198,11 +199,11 @@ public void dismissAllModals(Options mergeOptions, CommandListener listener) {
}

public void showOverlay(ViewController overlay, CommandListener listener) {
overlayManager.show(contentLayout, overlaysLayout, overlay, listener);
overlayManager.show(overlaysLayout, overlay, listener);
}

public void dismissOverlay(final String componentId, CommandListener listener) {
overlayManager.dismiss(componentId, listener);
overlayManager.dismiss(overlaysLayout, componentId, listener);
}

@Nullable
Expand Down Expand Up @@ -239,4 +240,9 @@ private boolean isRootNotCreated() {
CoordinatorLayout getModalsLayout() {
return modalsLayout;
}

@RestrictTo(RestrictTo.Scope.TESTS)
CoordinatorLayout getOverlaysLayout() {
return overlaysLayout;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,12 @@ protected View mockView(Activity activity) {
when(mock.getContext()).thenReturn(activity);
return mock;
}

protected void assertVisible(View view) {
assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
}

protected void assertGone(View view) {
assertThat(view.getVisibility()).isEqualTo(View.GONE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,20 @@ public void dismissModal_successIsReportedBeforeViewIsDestroyed() {
inOrder.verify(listener).onSuccess(modal.getId());
inOrder.verify(modal).destroy();
}

@Test
public void dismissModal_modalsLayoutIfHiddenIsAllModalsAreDismissed() {
disableShowModalAnimation(modal1, modal2);
disableDismissModalAnimation(modal1, modal2);

uut.showModal(modal1, root, new CommandListenerAdapter());
assertVisible(modalsLayout);
uut.showModal(modal2, modal1, new CommandListenerAdapter());
assertVisible(modalsLayout);

uut.dismissModal(modal2, modal1, root, new CommandListenerAdapter());
assertVisible(modalsLayout);
uut.dismissModal(modal1, root, root, new CommandListenerAdapter());
assertGone(modalsLayout);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,11 @@ public void setRoot_delegatesToRootPresenter() {
@Test
public void setRoot_clearsSplashLayout() {
FrameLayout content = activity.findViewById(android.R.id.content);
assertThat(content.getChildCount()).isEqualTo(3); // 2 frame layouts (root and modal containers) and the default splash layout
assertThat(content.getChildCount()).isEqualTo(4); // 3 frame layouts (root, modal and overlay containers) and the default splash layout

uut.setRoot(child2, new CommandListenerAdapter(), reactInstanceManager);

assertThat(content.getChildCount()).isEqualTo(2);
assertThat(content.getChildCount()).isEqualTo(3);
}

@Test
Expand Down Expand Up @@ -658,7 +658,7 @@ public void destroy_destroyedRoot() {
public void destroy_destroyOverlayManager() {
uut.setRoot(parentController, new CommandListenerAdapter(), reactInstanceManager);
activityController.destroy();
verify(overlayManager).destroy();
verify(overlayManager).destroy(uut.getOverlaysLayout());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.reactnativenavigation.viewcontrollers.overlay;

import android.app.Activity;
import android.view.View;
import android.widget.FrameLayout;

import com.reactnativenavigation.BaseTest;
Expand Down Expand Up @@ -36,6 +37,7 @@ public void beforeEach() {
contentLayout.layout(0, 0, 1000, 1000);
activity.setContentView(contentLayout);
overlayContainer = new FrameLayout(activity);
contentLayout.addView(overlayContainer);

ChildControllersRegistry childRegistry = new ChildControllersRegistry();
overlay1 = spy(new SimpleViewController(activity, childRegistry, OVERLAY_ID_1, new Options()));
Expand All @@ -45,26 +47,26 @@ public void beforeEach() {

@Test
public void show_attachesOverlayContainerToContentLayout() {
uut.show(contentLayout, overlayContainer, overlay1, new CommandListenerAdapter());
uut.show(overlayContainer, overlay1, new CommandListenerAdapter());
assertThat(overlayContainer.getParent()).isEqualTo(contentLayout);
uut.show(contentLayout, overlayContainer, overlay2, new CommandListenerAdapter());
uut.show(overlayContainer, overlay2, new CommandListenerAdapter());
}

@Test
public void show() {
CommandListenerAdapter listener = spy(new CommandListenerAdapter());
uut.show(contentLayout, overlayContainer, overlay1, listener);
uut.show(overlayContainer, overlay1, listener);
verify(listener, times(1)).onSuccess(OVERLAY_ID_1);
assertThat(overlay1.getView().getParent()).isEqualTo(overlayContainer);
assertMatchParent(overlay1.getView());
}

@Test
public void dismiss() {
uut.show(contentLayout, overlayContainer, overlay1, new CommandListenerAdapter());
uut.show(overlayContainer, overlay1, new CommandListenerAdapter());
assertThat(uut.size()).isOne();
CommandListener listener = spy(new CommandListenerAdapter());
uut.dismiss(overlay1.getId(), listener);
uut.dismiss(overlayContainer, overlay1.getId(), listener);
assertThat(uut.size()).isZero();
verify(listener, times(1)).onSuccess(OVERLAY_ID_1);
verify(overlay1, times(1)).destroy();
Expand All @@ -73,28 +75,29 @@ public void dismiss() {
@Test
public void dismiss_rejectIfOverlayNotFound() {
CommandListener listener = spy(new CommandListenerAdapter());
uut.dismiss(overlay1.getId(), listener);
uut.dismiss(overlayContainer, overlay1.getId(), listener);
verify(listener, times(1)).onError(any());
}

@Test
public void dismiss_onViewReturnedToFront() {
uut.show(contentLayout, overlayContainer, overlay1, new CommandListenerAdapter());
uut.show(contentLayout, overlayContainer, 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());
uut.dismiss(overlayContainer, OVERLAY_ID_2, new CommandListenerAdapter());
verify(overlay1, times(1)).onViewBroughtToFront();
}

@Test
public void dismiss_overlayContainerIsRemovedIfAllOverlaysAreDismissed() {
uut.show(contentLayout, overlayContainer, overlay1, new CommandListenerAdapter());
uut.show(contentLayout, overlayContainer, overlay2, new CommandListenerAdapter());
public void dismiss_overlayContainerIsHiddenIfAllOverlaysAreDismissed() {
uut.show(overlayContainer, overlay1, new CommandListenerAdapter());
uut.show(overlayContainer, overlay2, new CommandListenerAdapter());

uut.dismiss(OVERLAY_ID_2, new CommandListenerAdapter());
uut.dismiss(overlayContainer, OVERLAY_ID_2, new CommandListenerAdapter());
assertThat(overlayContainer.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(overlayContainer.getParent()).isEqualTo(contentLayout);
uut.dismiss(OVERLAY_ID_1, new CommandListenerAdapter());
assertThat(overlayContainer.getParent()).isNull();
uut.dismiss(overlayContainer, OVERLAY_ID_1, new CommandListenerAdapter());
assertThat(overlayContainer.getVisibility()).isEqualTo(View.GONE);
}
}

0 comments on commit 2793a02

Please sign in to comment.