diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/anim/NavigationAnimator.java b/lib/android/app/src/main/java/com/reactnativenavigation/anim/StackAnimator.java similarity index 97% rename from lib/android/app/src/main/java/com/reactnativenavigation/anim/NavigationAnimator.java rename to lib/android/app/src/main/java/com/reactnativenavigation/anim/StackAnimator.java index c3e55cacabb..e85154a3b96 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/anim/NavigationAnimator.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/anim/StackAnimator.java @@ -22,12 +22,12 @@ import static com.reactnativenavigation.utils.CollectionUtils.*; @SuppressWarnings("ResourceType") -public class NavigationAnimator extends BaseAnimator { +public class StackAnimator extends BaseAnimator { private final ElementTransitionManager transitionManager; private Map runningPushAnimations = new HashMap<>(); - public NavigationAnimator(Context context, ElementTransitionManager transitionManager) { + public StackAnimator(Context context, ElementTransitionManager transitionManager) { super(context); this.transitionManager = transitionManager; } 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 2d69518d531..e679f35dce5 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 @@ -18,7 +18,10 @@ public class OverlayManager { public void show(ViewGroup overlaysContainer, ViewController overlay, CommandListener listener) { overlaysContainer.setVisibility(View.VISIBLE); overlayRegistry.put(overlay.getId(), overlay); - overlay.addOnAppearedListener(() -> listener.onSuccess(overlay.getId())); + overlay.addOnAppearedListener(() -> { + overlay.onViewDidAppear(); + listener.onSuccess(overlay.getId()); + }); overlaysContainer.addView(overlay.getView(), matchParentWithBehaviour(new BehaviourDelegate(overlay))); } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/presentation/RootPresenter.java b/lib/android/app/src/main/java/com/reactnativenavigation/presentation/RootPresenter.java index 7ef189c7e80..93a1b47d621 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/presentation/RootPresenter.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/presentation/RootPresenter.java @@ -3,7 +3,7 @@ import android.content.Context; import com.facebook.react.ReactInstanceManager; -import com.reactnativenavigation.anim.NavigationAnimator; +import com.reactnativenavigation.anim.StackAnimator; import com.reactnativenavigation.parse.Options; import com.reactnativenavigation.utils.CommandListener; import com.reactnativenavigation.viewcontrollers.ViewController; @@ -16,7 +16,7 @@ import static com.reactnativenavigation.utils.CoordinatorLayoutUtils.matchParentWithBehaviour; public class RootPresenter { - private NavigationAnimator animator; + private StackAnimator animator; private CoordinatorLayout rootLayout; private LayoutDirectionApplier layoutDirectionApplier; @@ -25,11 +25,11 @@ public void setRootContainer(CoordinatorLayout rootLayout) { } public RootPresenter(Context context) { - this(new NavigationAnimator(context, new ElementTransitionManager()), new LayoutDirectionApplier()); + this(new StackAnimator(context, new ElementTransitionManager()), new LayoutDirectionApplier()); } @VisibleForTesting - public RootPresenter(NavigationAnimator animator, LayoutDirectionApplier layoutDirectionApplier) { + public RootPresenter(StackAnimator animator, LayoutDirectionApplier layoutDirectionApplier) { this.animator = animator; this.layoutDirectionApplier = layoutDirectionApplier; } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/utils/CollectionUtils.java b/lib/android/app/src/main/java/com/reactnativenavigation/utils/CollectionUtils.java index fb0d21126e1..61abb12fde2 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/utils/CollectionUtils.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/utils/CollectionUtils.java @@ -12,6 +12,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.Size; import androidx.core.util.Pair; @SuppressWarnings("WeakerAccess") @@ -153,6 +154,10 @@ public static T last(@Nullable List items) { return CollectionUtils.isNullOrEmpty(items) ? null : items.get(items.size() - 1); } + public static T requireLast(@Size(min = 1) List items) { + return items.get(items.size() - 1); + } + public static T removeLast(@NonNull List items) { return items.remove(items.size() - 1); } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ChildController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ChildController.java index 16a378d5bfa..0e789e534a2 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ChildController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ChildController.java @@ -46,8 +46,8 @@ public void setDefaultOptions(Options defaultOptions) { } @Override - public void onViewAppeared() { - super.onViewAppeared(); + public void onViewWillAppear() { + super.onViewWillAppear(); childRegistry.onViewAppeared(this); } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ComponentViewController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ComponentViewController.java index 3519ebced95..bcbb5bb5b5e 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ComponentViewController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ComponentViewController.java @@ -20,6 +20,8 @@ public class ComponentViewController extends ChildController { private final String componentName; private ComponentPresenter presenter; private final ReactViewCreator viewCreator; + private enum VisibilityState { Appear, Disappear } + private VisibilityState lastVisibilityState = VisibilityState.Disappear; public ComponentViewController(final Activity activity, final ChildControllersRegistry childRegistry, @@ -57,13 +59,15 @@ public ScrollEventListener getScrollEventListener() { } @Override - public void onViewAppeared() { - super.onViewAppeared(); - if (view != null) view.sendComponentStart(); + public void onViewDidAppear() { + super.onViewDidAppear(); + if (view != null && lastVisibilityState == VisibilityState.Disappear) view.sendComponentStart(); + lastVisibilityState = VisibilityState.Appear; } @Override public void onViewDisappear() { + lastVisibilityState = VisibilityState.Disappear; if (view != null) view.sendComponentStop(); super.onViewDisappear(); } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ParentController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ParentController.java index 435a0611ba0..9fffab26570 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ParentController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ParentController.java @@ -39,6 +39,11 @@ public void setDefaultOptions(Options defaultOptions) { forEach(getChildControllers(), child -> child.setDefaultOptions(defaultOptions)); } + @Override + public void onViewDidAppear() { + getCurrentChild().onViewDidAppear(); + } + @Override @CheckResult public Options resolveCurrentOptions() { diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/TitleBarButtonController.kt b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/TitleBarButtonController.kt index 1e9425eaa49..a5345ea3dcf 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/TitleBarButtonController.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/TitleBarButtonController.kt @@ -31,7 +31,7 @@ class TitleBarButtonController(activity: Activity, get() = button.intId @SuppressLint("MissingSuperCall") - override fun onViewAppeared() { + override fun onViewWillAppear() { getView()!!.sendComponentStart(ComponentType.Button) } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/TitleBarReactViewController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/TitleBarReactViewController.java index 5b9965c124e..ed8e66ef6cd 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/TitleBarReactViewController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/TitleBarReactViewController.java @@ -26,8 +26,8 @@ public TitleBarReactViewController(Activity activity, TitleBarReactViewCreator r } @Override - public void onViewAppeared() { - super.onViewAppeared(); + public void onViewWillAppear() { + super.onViewWillAppear(); if (!isDestroyed()) { runOnPreDraw(view -> view.setLayoutParams(view.getLayoutParams())); getView().sendComponentStart(ComponentType.Title); diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java index f730330f90f..534554beda2 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java @@ -231,12 +231,8 @@ public boolean containsComponent(Component component) { return getView().equals(component); } - public void onViewWillAppear() { - - } - @CallSuper - public void onViewAppeared() { + public void onViewWillAppear() { isShown = true; applyOptions(options); performOnParentController(parentController -> { @@ -252,6 +248,10 @@ public void onViewAppeared() { } } + public void onViewDidAppear() { + + } + public void onViewWillDisappear() { } @@ -291,7 +291,7 @@ public void onGlobalLayout() { if (!isShown && isViewShown()) { if (!viewVisibilityListener.onViewAppeared(view)) { isShown = true; - onViewAppeared(); + onViewWillAppear(); } } else if (isShown && !isViewShown()) { if (!viewVisibilityListener.onViewDisappear(view)) { diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java index 781039587c1..13487872ec9 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java @@ -210,6 +210,7 @@ public void selectTab(final int newIndex) { getCurrentView().setVisibility(View.INVISIBLE); bottomTabs.setCurrentItem(newIndex, false); getCurrentView().setVisibility(View.VISIBLE); + getCurrentChild().onViewDidAppear(); } @NonNull diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/externalcomponent/ExternalComponentViewController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/externalcomponent/ExternalComponentViewController.java index 78707169c70..c858ce0e7b7 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/externalcomponent/ExternalComponentViewController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/externalcomponent/ExternalComponentViewController.java @@ -54,8 +54,8 @@ public void sendOnNavigationButtonPressed(String buttonId) { } @Override - public void onViewAppeared() { - super.onViewAppeared(); + public void onViewWillAppear() { + super.onViewWillAppear(); emitter.emitComponentDidAppear(getId(), externalComponent.name.get(), ComponentType.Component); } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenter.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenter.java index fbe5c64d367..e92fd8dff47 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenter.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenter.java @@ -81,7 +81,8 @@ public void onAnimationEnd(Animator animation) { } private void onShowModalEnd(ViewController toAdd, @Nullable ViewController toRemove, CommandListener listener) { - if (toRemove != null && toAdd.options.modal.presentationStyle != ModalPresentationStyle.OverCurrentContext) { + toAdd.onViewDidAppear(); + if (toRemove != null && toAdd.resolveCurrentOptions(defaultOptions).modal.presentationStyle != ModalPresentationStyle.OverCurrentContext) { toRemove.detachView(); } listener.onSuccess(toAdd.getId()); @@ -92,7 +93,10 @@ void dismissModal(ViewController toDismiss, @Nullable ViewController toAdd, View listener.onError("Can not dismiss modal before activity is created"); return; } - if (toAdd != null) toAdd.attachView(toAdd == root ? rootLayout : modalsLayout, 0); + if (toAdd != null) { + toAdd.attachView(toAdd == root ? rootLayout : modalsLayout, 0); + toAdd.onViewDidAppear(); + } Options options = toDismiss.resolveCurrentOptions(defaultOptions); if (options.animations.dismissModal.enabled.isTrueOrUndefined()) { animator.dismiss(toDismiss.getView(), options.animations.dismissModal, new AnimatorListenerAdapter() { diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalStack.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalStack.java index 6c81cf1c777..0833bad3883 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalStack.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalStack.java @@ -62,20 +62,19 @@ public boolean dismissModal(String componentId, @Nullable ViewController root, C boolean isDismissingTopModal = isTop(toDismiss); modals.remove(toDismiss); @Nullable ViewController toAdd = isEmpty() ? root : isDismissingTopModal ? get(size() - 1) : null; - CommandListenerAdapter onDismiss = new CommandListenerAdapter(listener) { - @Override - public void onSuccess(String childId) { - eventEmitter.emitModalDismissed(componentId, toDismiss.getCurrentComponentName(), 1); - super.onSuccess(componentId); - } - }; if (isDismissingTopModal) { if (toAdd == null) { listener.onError("Could not dismiss modal"); return false; } } - presenter.dismissModal(toDismiss, toAdd, root, onDismiss); + presenter.dismissModal(toDismiss, toAdd, root, new CommandListenerAdapter(listener) { + @Override + public void onSuccess(String childId) { + eventEmitter.emitModalDismissed(componentId, toDismiss.getCurrentComponentName(), 1); + super.onSuccess(componentId); + } + }); return true; } else { listener.onError("Nothing to dismiss"); 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 07954123c2d..f39f440dd9e 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 @@ -144,6 +144,7 @@ public void setRoot(final ViewController viewController, CommandListener command rootPresenter.setRoot(root, defaultOptions, new CommandListenerAdapter(commandListener) { @Override public void onSuccess(String childId) { + root.onViewDidAppear(); if (removeSplashView) contentLayout.removeViewAt(0); destroyPreviousRoot(); super.onSuccess(childId); diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/sidemenu/SideMenuController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/sidemenu/SideMenuController.java index f493b1f7776..25fe72a8973 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/sidemenu/SideMenuController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/sidemenu/SideMenuController.java @@ -102,8 +102,8 @@ public void mergeChildOptions(Options options, ViewController child) { } @Override - public void onViewAppeared() { - super.onViewAppeared(); + public void onViewWillAppear() { + super.onViewWillAppear(); if (left != null) left.performOnView(view -> ((View) view).requestLayout()); if (right != null) right.performOnView(view -> ((View) view).requestLayout()); } @@ -205,8 +205,10 @@ private Options getOptionsWithVisibility(boolean isLeft, boolean visible ) { } private void dispatchSideMenuVisibilityEvents(ViewController drawer, float prevOffset, float offset) { - if (prevOffset == 0 && offset > 0) { - drawer.onViewAppeared(); + if (prevOffset < 1 && offset == 1) { + drawer.onViewDidAppear(); + } else if (prevOffset == 0 && offset > 0) { + drawer.onViewWillAppear(); } else if (prevOffset > 0 && offset == 0) { drawer.onViewDisappear(); } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java index a5f749e3c00..1218257da5c 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java @@ -4,7 +4,7 @@ import android.view.View; import android.view.ViewGroup; -import com.reactnativenavigation.anim.NavigationAnimator; +import com.reactnativenavigation.anim.StackAnimator; import com.reactnativenavigation.parse.NestedAnimationsOptions; import com.reactnativenavigation.parse.Options; import com.reactnativenavigation.presentation.FabPresenter; @@ -34,6 +34,7 @@ import androidx.annotation.NonNull; import androidx.annotation.RestrictTo; +import androidx.annotation.Size; import androidx.annotation.VisibleForTesting; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.viewpager.widget.ViewPager; @@ -46,14 +47,14 @@ public class StackController extends ParentController { private IdStack stack = new IdStack<>(); - private final NavigationAnimator animator; + private final StackAnimator animator; private final EventEmitter eventEmitter; private TopBarController topBarController; private BackButtonHelper backButtonHelper; private final StackPresenter presenter; private final FabPresenter fabPresenter; - public StackController(Activity activity, List children, ChildControllersRegistry childRegistry, EventEmitter eventEmitter, TopBarController topBarController, NavigationAnimator animator, String id, Options initialOptions, BackButtonHelper backButtonHelper, StackPresenter stackPresenter, Presenter presenter, FabPresenter fabPresenter) { + public StackController(Activity activity, List children, ChildControllersRegistry childRegistry, EventEmitter eventEmitter, TopBarController topBarController, StackAnimator animator, String id, Options initialOptions, BackButtonHelper backButtonHelper, StackPresenter stackPresenter, Presenter presenter, FabPresenter fabPresenter) { super(activity, childRegistry, id, presenter, initialOptions); this.eventEmitter = eventEmitter; this.topBarController = topBarController; @@ -167,6 +168,7 @@ public void push(ViewController child, CommandListener listener) { if (animation.enabled.isTrueOrUndefined()) { animator.push(child, toRemove, resolvedOptions, () -> onPushAnimationComplete(child, toRemove, listener)); } else { + child.onViewDidAppear(); getView().removeView(toRemove.getView()); listener.onSuccess(child.getId()); } @@ -176,6 +178,7 @@ public void push(ViewController child, CommandListener listener) { } private void onPushAnimationComplete(ViewController toAdd, ViewController toRemove, CommandListener listener) { + toAdd.onViewDidAppear(); if (!peek().equals(toRemove)) getView().removeView(toRemove.getView()); listener.onSuccess(toAdd.getId()); } @@ -186,13 +189,13 @@ private void addChildToStack(ViewController child, Options resolvedOptions) { getView().addView(child.getView(), getView().getChildCount() - 1, matchParentWithBehaviour(new StackBehaviour(this))); } - public void setRoot(List children, CommandListener listener) { + public void setRoot(@Size(min = 1) List children, CommandListener listener) { animator.cancelPushAnimations(); final ViewController toRemove = stack.peek(); IdStack stackToDestroy = stack; stack = new IdStack<>(); - ViewController child = last(children); + ViewController child = requireLast(children); if (children.size() == 1) { backButtonHelper.clear(child); } else { @@ -207,6 +210,7 @@ public void setRoot(List children, CommandListener listener) { CommandListener listenerAdapter = new CommandListenerAdapter() { @Override public void onSuccess(String childId) { + child.onViewDidAppear(); destroyStack(stackToDestroy); if (children.size() > 1) { for (int i = 0; i < children.size() - 1; i++) { @@ -261,7 +265,6 @@ public void pop(Options mergeOptions, CommandListener listener) { final ViewController appearing = stack.peek(); disappearing.onViewWillDisappear(); - appearing.onViewWillAppear(); ViewGroup appearingView = appearing.getView(); if (appearingView.getLayoutParams() == null) { @@ -272,13 +275,14 @@ public void pop(Options mergeOptions, CommandListener listener) { } presenter.onChildWillAppear(this, appearing, disappearing); if (disappearingOptions.animations.pop.enabled.isTrueOrUndefined()) { - animator.pop(disappearing.getView(), disappearingOptions.animations.pop, () -> finishPopping(disappearing, listener)); + animator.pop(disappearing.getView(), disappearingOptions.animations.pop, () -> finishPopping(appearing, disappearing, listener)); } else { - finishPopping(disappearing, listener); + finishPopping(appearing, disappearing, listener); } } - private void finishPopping(ViewController disappearing, CommandListener listener) { + private void finishPopping(ViewController appearing, ViewController disappearing, CommandListener listener) { + appearing.onViewDidAppear(); disappearing.destroy(); listener.onSuccess(disappearing.getId()); eventEmitter.emitScreenPoppedEvent(disappearing.getId()); diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerBuilder.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerBuilder.java index 955a3f89ece..c2858a5e771 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerBuilder.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerBuilder.java @@ -2,7 +2,7 @@ import android.app.Activity; -import com.reactnativenavigation.anim.NavigationAnimator; +import com.reactnativenavigation.anim.StackAnimator; import com.reactnativenavigation.parse.Options; import com.reactnativenavigation.presentation.FabPresenter; import com.reactnativenavigation.presentation.Presenter; @@ -23,7 +23,7 @@ public class StackControllerBuilder { private TopBarController topBarController; private String id; private Options initialOptions = new Options(); - private NavigationAnimator animator; + private StackAnimator animator; private BackButtonHelper backButtonHelper = new BackButtonHelper(); private Presenter presenter; private StackPresenter stackPresenter; @@ -35,7 +35,7 @@ public StackControllerBuilder(Activity activity, EventEmitter eventEmitter) { this.activity = activity; this.eventEmitter = eventEmitter; presenter = new Presenter(activity, new Options()); - animator = new NavigationAnimator(activity, new ElementTransitionManager()); + animator = new StackAnimator(activity, new ElementTransitionManager()); } public StackControllerBuilder setEventEmitter(EventEmitter eventEmitter) { @@ -82,7 +82,7 @@ public StackControllerBuilder setInitialOptions(Options initialOptions) { return this; } - public StackControllerBuilder setAnimator(NavigationAnimator animator) { + public StackControllerBuilder setAnimator(StackAnimator animator) { this.animator = animator; return this; } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/topbar/TopBarBackgroundViewController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/topbar/TopBarBackgroundViewController.java index cd3aaef0adc..0e2cea20b82 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/topbar/TopBarBackgroundViewController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/topbar/TopBarBackgroundViewController.java @@ -28,8 +28,8 @@ protected TopBarBackgroundView createView() { } @Override - public void onViewAppeared() { - super.onViewAppeared(); + public void onViewWillAppear() { + super.onViewWillAppear(); getView().sendComponentStart(ComponentType.Background); } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsAdapter.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsAdapter.java index a75a0bb464c..4b695f833b9 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsAdapter.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsAdapter.java @@ -46,7 +46,7 @@ public void onPageScrolled(int position, float positionOffset, int positionOffse @Override public void onPageSelected(int position) { tabs.get(currentPage).onViewDisappear(); - tabs.get(position).onViewAppeared(); + tabs.get(position).onViewWillAppear(); currentPage = position; } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsController.java index 7c6362e72e8..73df11ab962 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsController.java @@ -1,8 +1,6 @@ package com.reactnativenavigation.viewcontrollers.toptabs; import android.app.Activity; -import androidx.annotation.CallSuper; -import androidx.annotation.NonNull; import android.view.View; import com.reactnativenavigation.parse.Options; @@ -18,6 +16,9 @@ import java.util.Collection; import java.util.List; +import androidx.annotation.CallSuper; +import androidx.annotation.NonNull; + public class TopTabsController extends ParentController { private List tabs; @@ -47,7 +48,7 @@ protected ViewController getCurrentChild() { @Override protected TopTabsViewPager createView() { view = viewCreator.create(); - return (TopTabsViewPager) view; + return view; } @NonNull @@ -57,10 +58,10 @@ public Collection getChildControllers() { } @Override - public void onViewAppeared() { - super.onViewAppeared(); - performOnParentController(parentController -> ((ParentController) parentController).setupTopTabsWithViewPager(getView())); - performOnCurrentTab(ViewController::onViewAppeared); + public void onViewWillAppear() { + super.onViewWillAppear(); + performOnParentController(parentController -> parentController.setupTopTabsWithViewPager(getView())); + performOnCurrentTab(ViewController::onViewWillAppear); } @Override @@ -72,7 +73,7 @@ public void onViewWillDisappear() { public void onViewDisappear() { super.onViewDisappear(); performOnCurrentTab(ViewController::onViewDisappear); - performOnParentController(parentController -> ((ParentController) parentController).clearTopTabs()); + performOnParentController(ParentController::clearTopTabs); } @Override @@ -89,17 +90,18 @@ public void applyOptions(Options options) { @Override public void applyChildOptions(Options options, ViewController child) { super.applyChildOptions(options, child); - performOnParentController(parentController -> ((ParentController) parentController).applyChildOptions(this.options.copy(), child)); + performOnParentController(parentController -> parentController.applyChildOptions(this.options.copy(), child)); } @CallSuper public void mergeChildOptions(Options options, ViewController child) { super.mergeChildOptions(options, child); - performOnParentController(parentController -> ((ParentController) parentController).applyChildOptions(options.copy(), child)); + performOnParentController(parentController -> parentController.applyChildOptions(options.copy(), child)); } public void switchToTab(int index) { getView().switchToTab(index); + getCurrentChild().onViewDidAppear(); } private void performOnCurrentTab(Func1 task) { diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/TestUtils.java b/lib/android/app/src/test/java/com/reactnativenavigation/TestUtils.java index 2d0fd4a8b3e..ad149515a49 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/TestUtils.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/TestUtils.java @@ -13,6 +13,7 @@ import com.reactnativenavigation.presentation.RenderChecker; import com.reactnativenavigation.presentation.StackPresenter; import com.reactnativenavigation.react.events.EventEmitter; +import com.reactnativenavigation.utils.CompatUtils; import com.reactnativenavigation.utils.ImageLoader; import com.reactnativenavigation.utils.UiUtils; import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry; @@ -36,7 +37,7 @@ protected TopBar createTopBar(Context context, StackLayout stackLayout) { } }; return new StackControllerBuilder(activity, Mockito.mock(EventEmitter.class)) - .setId("stack") + .setId("stack" + CompatUtils.generateViewId()) .setChildRegistry(new ChildControllersRegistry()) .setTopBarController(topBarController) .setStackPresenter(new StackPresenter(activity, new TitleBarReactViewCreatorMock(), new TopBarBackgroundViewCreatorMock(), new TitleBarButtonCreatorMock(), new IconResolver(activity, new ImageLoader()), new RenderChecker(), new Options())) diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleViewController.java b/lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleViewController.java index 521c1059047..379e168e3a4 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleViewController.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleViewController.java @@ -55,7 +55,7 @@ public String toString() { @Override public int getTopInset() { - int statusBarInset = resolveCurrentOptions().statusBar.drawBehind.isTrue() ? 0 : 63; + int statusBarInset = resolveCurrentOptions().statusBar.isHiddenOrDrawBehind() ? 0 : 63; return statusBarInset + perform(getParentController(), 0, p -> p.getTopInset(this)); } diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ComponentViewControllerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ComponentViewControllerTest.java index b01c85a6f6e..82498368762 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ComponentViewControllerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ComponentViewControllerTest.java @@ -75,7 +75,7 @@ public void createsViewFromComponentViewCreator() { public void componentViewDestroyedOnDestroy() { uut.ensureViewIsCreated(); verify(view, times(0)).destroy(); - uut.onViewAppeared(); + uut.onViewWillAppear(); uut.destroy(); verify(view, times(1)).destroy(); } @@ -85,7 +85,10 @@ public void lifecycleMethodsSentToComponentView() { uut.ensureViewIsCreated(); verify(view, times(0)).sendComponentStart(); verify(view, times(0)).sendComponentStop(); - uut.onViewAppeared(); + uut.onViewWillAppear(); + verify(view, times(0)).sendComponentStart(); + verify(view, times(0)).sendComponentStop(); + uut.onViewDidAppear(); verify(view, times(1)).sendComponentStart(); verify(view, times(0)).sendComponentStop(); uut.onViewDisappear(); @@ -93,6 +96,21 @@ public void lifecycleMethodsSentToComponentView() { verify(view, times(1)).sendComponentStop(); } + @Test + public void onViewDidAppear_componentStartIsEmittedOnlyIfComponentIsNotAppeared() { + uut.ensureViewIsCreated(); + + uut.onViewDidAppear(); + verify(view).sendComponentStart(); + + uut.onViewDidAppear(); + verify(view).sendComponentStart(); + + uut.onViewDisappear(); + uut.onViewDidAppear(); + verify(view, times(2)).sendComponentStart(); + } + @Test public void isViewShownOnlyIfComponentViewIsReady() { assertThat(uut.isViewShown()).isFalse(); diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ExternalComponentViewControllerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ExternalComponentViewControllerTest.java index 119bbb02135..f00f3df4aff 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ExternalComponentViewControllerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ExternalComponentViewControllerTest.java @@ -72,7 +72,7 @@ public void createView_createsExternalComponent() { @Test public void onViewAppeared_appearEventIsEmitted() { - uut.onViewAppeared(); + uut.onViewWillAppear(); verify(emitter).emitComponentDidAppear(uut.getId(), ec.name.get(), ComponentType.Component); } @@ -84,7 +84,7 @@ public void onViewDisappear_disappearEventIsEmitted() { @Test public void registersInChildRegister() { - uut.onViewAppeared(); + uut.onViewWillAppear(); assertThat(childRegistry.size()).isOne(); uut.onViewDisappear(); assertThat(childRegistry.size()).isZero(); diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/FloatingActionButtonTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/FloatingActionButtonTest.java index 057a8055718..ef2fe2f9892 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/FloatingActionButtonTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/FloatingActionButtonTest.java @@ -86,17 +86,17 @@ private boolean hasFab() { @Test public void showOnPush() { stackController.push(childFab, new CommandListenerAdapter()); - childFab.onViewAppeared(); + childFab.onViewWillAppear(); assertThat(hasFab()).isTrue(); } @Test public void hideOnPush() { stackController.push(childFab, new CommandListenerAdapter()); - childFab.onViewAppeared(); + childFab.onViewWillAppear(); assertThat(hasFab()).isTrue(); stackController.push(childNoFab, new CommandListenerAdapter()); - childNoFab.onViewAppeared(); + childNoFab.onViewWillAppear(); assertThat(hasFab()).isFalse(); } @@ -105,10 +105,10 @@ public void hideOnPop() { disablePushAnimation(childNoFab, childFab); stackController.push(childNoFab, new CommandListenerAdapter()); stackController.push(childFab, new CommandListenerAdapter()); - childFab.onViewAppeared(); + childFab.onViewWillAppear(); assertThat(hasFab()).isTrue(); stackController.pop(Options.EMPTY, new CommandListenerAdapter()); - childNoFab.onViewAppeared(); + childNoFab.onViewWillAppear(); assertThat(hasFab()).isFalse(); } @@ -117,10 +117,10 @@ public void showOnPop() { disablePushAnimation(childFab, childNoFab); stackController.push(childFab, new CommandListenerAdapter()); stackController.push(childNoFab, new CommandListenerAdapter()); - childNoFab.onViewAppeared(); + childNoFab.onViewWillAppear(); assertThat(hasFab()).isFalse(); stackController.pop(Options.EMPTY, new CommandListenerAdapter()); - childFab.onViewAppeared(); + childFab.onViewWillAppear(); assertThat(hasFab()).isTrue(); } @@ -128,7 +128,7 @@ public void showOnPop() { public void hasChildren() { childFab = new SimpleViewController(activity, childRegistry, "child1", getOptionsWithFabActions()); stackController.push(childFab, new CommandListenerAdapter()); - childFab.onViewAppeared(); + childFab.onViewWillAppear(); assertThat(hasFab()).isTrue(); assertThat(containsActions()).isTrue(); } diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/OptionsApplyingTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/OptionsApplyingTest.java index 882e268869d..2373b14df7c 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/OptionsApplyingTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/OptionsApplyingTest.java @@ -81,7 +81,7 @@ public void applyNavigationOptionsHandlesNoParentStack() { uut.setParentController(null); assertThat(uut.getParentController()).isNull(); uut.ensureViewIsCreated(); - uut.onViewAppeared(); + uut.onViewWillAppear(); assertThat(uut.getParentController()).isNull(); } @@ -93,7 +93,7 @@ public void initialOptionsAppliedOnAppear() { stackController.push(uut, new CommandListenerAdapter()); assertThat(stackController.getTopBar().getTitle()).isEmpty(); - uut.onViewAppeared(); + uut.onViewWillAppear(); assertThat(stackController.getTopBar().getTitle()).isEqualTo("the title"); } diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ParentControllerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ParentControllerTest.java index e2bffcd16e9..ea4ed3598df 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ParentControllerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ParentControllerTest.java @@ -131,7 +131,7 @@ public void optionsAreClearedWhenChildIsAppeared() { SimpleViewController child1 = new SimpleViewController(activity, childRegistry, "child1", new Options()); stackController.push(child1, new CommandListenerAdapter()); - child1.onViewAppeared(); + child1.onViewWillAppear(); verify(stackController, times(1)).clearOptions(); } @@ -144,7 +144,7 @@ public void mergeOptions_optionsAreMergedWhenChildAppears() { uut.ensureViewIsCreated(); child1.ensureViewIsCreated(); - child1.onViewAppeared(); + child1.onViewWillAppear(); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(Options.class); verify(uut, times(1)).clearOptions(); verify(uut, times(1)).applyChildOptions(optionsCaptor.capture(), eq(child1)); @@ -161,7 +161,7 @@ public void mergeOptions_initialParentOptionsAreNotMutatedWhenChildAppears() { uut.ensureViewIsCreated(); child1.ensureViewIsCreated(); - child1.onViewAppeared(); + child1.onViewWillAppear(); assertThat(uut.initialOptions.topBar.title.text.get()).isEqualTo(INITIAL_TITLE); } diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackPresenterTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackPresenterTest.java index de3705ea249..aeefd94bb92 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackPresenterTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackPresenterTest.java @@ -294,7 +294,7 @@ public void mergeButtons_backButtonIsRemovedIfVisibleFalse() { parent.push(pushedChild, new CommandListenerAdapter()); ShadowLooper.idleMainLooper(); - verify(pushedChild).onViewAppeared(); + verify(pushedChild).onViewWillAppear(); assertThat(topBar.getTitleBar().getNavigationIcon()).isInstanceOf(BackDrawable.class); Options backButtonHidden = new Options(); diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopBarButtonControllerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopBarButtonControllerTest.java index 3f976fd70ec..72986631ea0 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopBarButtonControllerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopBarButtonControllerTest.java @@ -57,7 +57,7 @@ public void beforeEach() { public void buttonDoesNotClearStackOptionsOnAppear() { setReactComponentButton(); uut.ensureViewIsCreated(); - uut.onViewAppeared(); + uut.onViewWillAppear(); verify(stackController, times(0)).clearOptions(); } diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopTabsViewControllerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopTabsViewControllerTest.java index 57fdbf2d747..40ec91f77c5 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopTabsViewControllerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopTabsViewControllerTest.java @@ -127,10 +127,6 @@ public void lifecycleMethodsSentWhenSelectedTabChanges() { tabControllers.get(0).ensureViewIsCreated(); tabControllers.get(1).ensureViewIsCreated(); - tabControllers.get(0).onViewAppeared(); - - uut.onViewAppeared(); - TestReactView initialTab = getActualTabView(0); TestReactView selectedTab = getActualTabView(1); @@ -144,7 +140,7 @@ public void lifecycleMethodsSentWhenSelectedTabChanges() { public void lifecycleMethodsSentWhenSelectedPreviouslySelectedTab() { stack.ensureViewIsCreated(); uut.ensureViewIsCreated(); - uut.onViewAppeared(); + uut.onViewDidAppear(); uut.switchToTab(1); uut.switchToTab(0); @@ -158,9 +154,9 @@ public void lifecycleMethodsSentWhenSelectedPreviouslySelectedTab() { public void setOptionsOfInitialTab() { stack.ensureViewIsCreated(); uut.ensureViewIsCreated(); - uut.onViewAppeared(); - verify(tabControllers.get(0), times(1)).onViewAppeared(); - verify(tabControllers.get(1), times(0)).onViewAppeared(); + uut.onViewWillAppear(); + verify(tabControllers.get(0), times(1)).onViewWillAppear(); + verify(tabControllers.get(1), times(0)).onViewWillAppear(); ViewController comp = tabControllers.get(0); verify(uut, times(1)).applyChildOptions(any(Options.class), eq(comp)); @@ -173,7 +169,7 @@ public void setOptionsWhenTabChanges() { tabControllers.get(0).ensureViewIsCreated(); tabControllers.get(1).ensureViewIsCreated(); - uut.onViewAppeared(); + uut.onViewWillAppear(); ViewController currentTab = tab(0); verify(uut, times(1)).applyChildOptions(any(Options.class), eq(currentTab)); assertThat(uut.options.topBar.title.text.get()).isEqualTo(createTabTopBarTitle(0)); @@ -199,7 +195,7 @@ public void appliesOptionsOnLayoutWhenVisible() { stack.ensureViewIsCreated(); uut.ensureViewIsCreated(); - uut.onViewAppeared(); + uut.onViewWillAppear(); verify(topTabsLayout, times(1)).applyOptions(any(Options.class)); } @@ -213,13 +209,13 @@ public void applyOptions_tabsAreRemovedAfterViewDisappears() { stackController.push(first, new CommandListenerAdapter()); stackController.push(uut, new CommandListenerAdapter()); - uut.onViewAppeared(); + uut.onViewWillAppear(); assertThat(ViewHelper.isVisible(stackController.getTopBar().getTopTabs())).isTrue(); disablePopAnimation(uut); stackController.pop(Options.EMPTY, new CommandListenerAdapter()); - first.onViewAppeared(); + first.onViewWillAppear(); assertThat(ViewHelper.isVisible(stackController.getTopBar().getTopTabs())).isFalse(); } @@ -227,7 +223,7 @@ public void applyOptions_tabsAreRemovedAfterViewDisappears() { @Test public void onNavigationButtonPressInvokedOnCurrentTab() { uut.ensureViewIsCreated(); - uut.onViewAppeared(); + uut.onViewWillAppear(); uut.switchToTab(1); uut.sendOnNavigationButtonPressed("btn1"); verify(tabControllers.get(1), times(1)).sendOnNavigationButtonPressed("btn1"); diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ViewControllerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ViewControllerTest.java index 599aac1c348..d831de80bee 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ViewControllerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ViewControllerTest.java @@ -163,13 +163,13 @@ public void onAppear_WhenShown() { ViewController spy = spy(uut); spy.getView().getViewTreeObserver().dispatchOnGlobalLayout(); Assertions.assertThat(spy.getView()).isNotShown(); - verify(spy, times(0)).onViewAppeared(); + verify(spy, times(0)).onViewWillAppear(); Shadows.shadowOf(spy.getView()).setMyParent(mock(ViewParent.class)); spy.getView().getViewTreeObserver().dispatchOnGlobalLayout(); Assertions.assertThat(spy.getView()).isShown(); - verify(spy, times(1)).onViewAppeared(); + verify(spy, times(1)).onViewWillAppear(); } @Test @@ -181,7 +181,7 @@ public void onAppear_CalledAtMostOnce() { spy.getView().getViewTreeObserver().dispatchOnGlobalLayout(); spy.getView().getViewTreeObserver().dispatchOnGlobalLayout(); - verify(spy, times(1)).onViewAppeared(); + verify(spy, times(1)).onViewWillAppear(); } @Test @@ -196,7 +196,7 @@ public void onDisappear_WhenNotShown_AfterOnAppearWasCalled() { Shadows.shadowOf(spy.getView()).setMyParent(mock(ViewParent.class)); Assertions.assertThat(spy.getView()).isShown(); spy.getView().getViewTreeObserver().dispatchOnGlobalLayout(); - verify(spy, times(1)).onViewAppeared(); + verify(spy, times(1)).onViewWillAppear(); verify(spy, times(0)).onViewDisappear(); spy.getView().setVisibility(View.GONE); @@ -230,7 +230,7 @@ public void onDestroy_RemovesGlobalLayoutListener() throws Exception { Assertions.assertThat(view).isShown(); view.getViewTreeObserver().dispatchOnGlobalLayout(); - verify(spy, times(0)).onViewAppeared(); + verify(spy, times(0)).onViewWillAppear(); verify(spy, times(0)).onViewDisappear(); Field field = ViewController.class.getDeclaredField("view"); @@ -244,7 +244,7 @@ public void onDestroy_CallsOnDisappearIfNeeded() { Shadows.shadowOf(spy.getView()).setMyParent(mock(ViewParent.class)); Assertions.assertThat(spy.getView()).isShown(); spy.getView().getViewTreeObserver().dispatchOnGlobalLayout(); - verify(spy, times(1)).onViewAppeared(); + verify(spy, times(1)).onViewWillAppear(); spy.destroy(); diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.java index e5b9bad3ed4..decd3972425 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.java @@ -28,6 +28,7 @@ import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry; import com.reactnativenavigation.viewcontrollers.ParentController; import com.reactnativenavigation.viewcontrollers.ViewController; +import com.reactnativenavigation.viewcontrollers.fakes.FakeParentController; import com.reactnativenavigation.viewcontrollers.stack.StackController; import com.reactnativenavigation.views.BottomTabs; import com.reactnativenavigation.views.bottomtabs.BottomTabsLayout; @@ -63,6 +64,7 @@ public class BottomTabsControllerTest extends BaseTest { private ViewController child1; private ViewController child2; private ViewController child3; + private ViewController stackChild; private StackController child4; private ViewController child5; private Options tabOptions = OptionHelper.createBottomTabOptions(); @@ -122,7 +124,7 @@ public void parentControllerIsSet() { @Test public void setTabs_allChildViewsAreAttachedToHierarchy() { - uut.onViewAppeared(); + uut.onViewWillAppear(); assertThat(uut.getView().getChildCount()).isEqualTo(6); for (ViewController child : uut.getChildControllers()) { assertThat(child.getView().getParent()).isNotNull(); @@ -131,7 +133,7 @@ public void setTabs_allChildViewsAreAttachedToHierarchy() { @Test public void setTabs_firstChildIsVisibleOtherAreGone() { - uut.onViewAppeared(); + uut.onViewWillAppear(); for (int i = 0; i < uut.getChildControllers().size(); i++) { assertThat(uut.getView().getChildAt(i + 1)).isEqualTo(tabs.get(i).getView()); assertThat(uut.getView().getChildAt(i + 1).getVisibility()).isEqualTo(i == 0 ? View.VISIBLE : View.INVISIBLE); @@ -179,7 +181,7 @@ public void applyChildOptions_bottomTabsOptionsAreClearedAfterApply() { uut.setParentController(parent); child1.options.bottomTabsOptions.backgroundColor = new Colour(Color.RED); - child1.onViewAppeared(); + child1.onViewWillAppear(); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(Options.class); verify(parent).applyChildOptions(optionsCaptor.capture(), any()); @@ -241,12 +243,9 @@ public void mergeOptions_drawBehind() { @Test public void mergeOptions_drawBehind_stack() { + uut.ensureViewIsCreated(); uut.selectTab(3); - SimpleViewController stackChild = new SimpleViewController(activity, childRegistry, "stackChild", new Options()); - disablePushAnimation(stackChild); - child4.push(stackChild, new CommandListenerAdapter()); - assertThat(((MarginLayoutParams) stackChild.getView().getLayoutParams()).bottomMargin).isEqualTo(bottomTabs.getHeight()); Options o1 = new Options(); @@ -266,9 +265,9 @@ public void mergeOptions_mergesBottomTabOptions() { @Test public void applyChildOptions_resolvedOptionsAreUsed() { Options childOptions = new Options(); - SimpleViewController pushedScreen = new SimpleViewController(activity, childRegistry, "child4.1", childOptions); + SimpleViewController pushedScreen = new SimpleViewController(activity , childRegistry, "child4.1", childOptions); disablePushAnimation(pushedScreen); - child4 = createStack(pushedScreen); + child4 = spyOnStack(pushedScreen); tabs = new ArrayList<>(Collections.singletonList(child4)); tabsAttacher = new BottomTabsAttacher(tabs, presenter, Options.EMPTY); @@ -282,10 +281,10 @@ public void applyChildOptions_resolvedOptionsAreUsed() { imageLoaderMock, "uut", initialOptions, - new Presenter(activity, new Options()), + new Presenter(activity , new Options()), tabsAttacher, presenter, - new BottomTabPresenter(activity, tabs, ImageLoaderMock.mock(), new Options())) { + new BottomTabPresenter(activity , tabs, ImageLoaderMock.mock(), new Options())) { @Override public Options resolveCurrentOptions() { return resolvedOptions; @@ -294,7 +293,7 @@ public Options resolveCurrentOptions() { @NonNull @Override protected BottomTabs createBottomTabs() { - return new BottomTabs(activity) { + return new BottomTabs(activity ) { @Override protected void createItems() { @@ -337,17 +336,13 @@ public void buttonPressInvokedOnCurrentTab() { @Test public void push() { - uut.ensureViewIsCreated(); uut.selectTab(3); - SimpleViewController stackChild = new SimpleViewController(activity, childRegistry, "stackChild", new Options()); SimpleViewController stackChild2 = new SimpleViewController(activity, childRegistry, "stackChild2", new Options()); - - disablePushAnimation(stackChild, stackChild2); + disablePushAnimation(stackChild2); hideBackButton(stackChild2); - child4.push(stackChild, new CommandListenerAdapter()); - assertThat(child4.size()).isOne(); + assertThat(child4.size()).isEqualTo(1); child4.push(stackChild2, new CommandListenerAdapter()); assertThat(child4.size()).isEqualTo(2); } @@ -370,27 +365,22 @@ public void selectTab() { verify(tabsAttacher).onTabSelected(tabs.get(1)); } + @Test + public void selectTab_onViewDidAppearIsInvokedAfterSelection() { + uut.selectTab(1); + verify(child2).onViewDidAppear(); + } + @Test public void getTopInset() { - assertThat(child1.getTopInset()).isEqualTo(StatusBarUtils.getStatusBarHeight(activity)); - assertThat(child2.getTopInset()).isEqualTo(StatusBarUtils.getStatusBarHeight(activity)); + assertThat(child1.getTopInset()).isEqualTo(getStatusBarHeight()); + assertThat(child2.getTopInset()).isEqualTo(getStatusBarHeight()); child1.options.statusBar.drawBehind = new Bool(true); assertThat(child1.getTopInset()).isEqualTo(0); - assertThat(child2.getTopInset()).isEqualTo(StatusBarUtils.getStatusBarHeight(activity)); - - SimpleViewController stackChild = new SimpleViewController(activity, childRegistry, "stackChild", new Options()); - disablePushAnimation(stackChild); - child4.push(stackChild, new CommandListenerAdapter()); + assertThat(child2.getTopInset()).isEqualTo(getStatusBarHeight()); - assertThat(stackChild.getTopInset()).isEqualTo(StatusBarUtils.getStatusBarHeight(activity) + child4.getTopBar().getHeight()); - - uut.options.statusBar.drawBehind = new Bool(true); - stackChild.options.topBar.drawBehind = new Bool(true); - assertThat(uut.getTopInset()).isEqualTo(0); - assertThat(child4.getTopInset()).isEqualTo(0); - assertThat(child4.getTopBar().getY()).isEqualTo(StatusBarUtils.getStatusBarHeight(activity)); - assertThat(stackChild.getTopInset()).isEqualTo(StatusBarUtils.getStatusBarHeight(activity)); + assertThat(stackChild.getTopInset()).isEqualTo(getStatusBarHeight() + child4.getTopBar().getHeight()); } @Test @@ -418,44 +408,34 @@ public void superCreateItems() { } }); createChildren(); - tabs = createTabs(); - presenter = spy(new BottomTabsPresenter(tabs, new Options())); - bottomTabPresenter = spy(new BottomTabPresenter(activity, tabs, ImageLoaderMock.mock(), new Options())); + tabs = Arrays.asList(child1, child2, child3, child4, child5); + presenter = spy(new BottomTabsPresenter(tabs, Options.EMPTY)); + bottomTabPresenter = spy(new BottomTabPresenter(activity, tabs, ImageLoaderMock.mock(), Options.EMPTY)); tabsAttacher = spy(new BottomTabsAttacher(tabs, presenter, Options.EMPTY)); uut = createBottomTabs(); - uut.setParentController(Mockito.mock(ParentController.class)); - CoordinatorLayout parent = new CoordinatorLayout(activity); - parent.addView(uut.getView()); - activity.setContentView(parent); + activity.setContentView(new FakeParentController(activity, childRegistry, uut).getView()); } private void createChildren() { child1 = spy(new SimpleViewController(activity, childRegistry, "child1", tabOptions)); child2 = spy(new SimpleViewController(activity, childRegistry, "child2", tabOptions)); child3 = spy(new SimpleViewController(activity, childRegistry, "child3", tabOptions)); - child4 = spy(createStack()); + stackChild = spy(new SimpleViewController(activity, childRegistry, "stackChild", tabOptions)); + child4 = spyOnStack(stackChild); child5 = spy(new SimpleViewController(activity, childRegistry, "child5", tabOptions)); when(child5.handleBack(any())).thenReturn(true); } - @NonNull - private List createTabs() { - return Arrays.asList(child1, child2, child3, child4, child5); - } - - private StackController createStack() { - return TestUtils.newStackController(activity) - .setId("someStack") + private StackController spyOnStack(ViewController initialChild) { + StackController build = TestUtils.newStackController(activity) .setInitialOptions(tabOptions) .build(); - } - - private StackController createStack(ViewController initialChild) { - return TestUtils.newStackController(activity) - .setInitialOptions(tabOptions) - .setChildren(initialChild) - .build(); + StackController stack = spy(build); + disablePushAnimation(initialChild); + stack.ensureViewIsCreated(); + stack.push(initialChild, new CommandListenerAdapter()); + return stack; } private BottomTabsController createBottomTabs() { @@ -491,4 +471,8 @@ protected BottomTabs createBottomTabs() { } }; } + + private int getStatusBarHeight() { + return StatusBarUtils.getStatusBarHeight(activity); + } } diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/attachmode/AfterInitialTabTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/attachmode/AfterInitialTabTest.java index 2f4f1dc7745..773333455d1 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/attachmode/AfterInitialTabTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/attachmode/AfterInitialTabTest.java @@ -27,7 +27,7 @@ public void attach_otherTabsAreAttachedAfterInitialTab() { uut.attach(); assertNotChildOf(parent, otherTabs()); - initialTab().onViewAppeared(); + initialTab().onViewWillAppear(); assertIsChild(parent, otherTabs()); } diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/child/ChildControllerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/child/ChildControllerTest.java index c98f371e0f9..2e6a19ca612 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/child/ChildControllerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/child/ChildControllerTest.java @@ -40,13 +40,13 @@ public Options resolveCurrentOptions() { @Test public void onViewAppeared() { - uut.onViewAppeared(); + uut.onViewWillAppear(); verify(childRegistry, times(1)).onViewAppeared(uut); } @Test public void onViewDisappear() { - uut.onViewAppeared(); + uut.onViewWillAppear(); uut.onViewDisappear(); verify(childRegistry, times(1)).onViewDisappear(uut); diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/child/ChildControllersRegistryTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/child/ChildControllersRegistryTest.java index 9c49b3de503..4ac7f1b42f1 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/child/ChildControllersRegistryTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/child/ChildControllersRegistryTest.java @@ -30,15 +30,15 @@ public void beforeEach() { @Test public void onViewAppeared() { - child1.onViewAppeared(); + child1.onViewWillAppear(); verify(child1, times(0)).onViewBroughtToFront(); assertThat(uut.size()).isOne(); } @Test public void onViewDisappear() { - child1.onViewAppeared(); - child2.onViewAppeared(); + child1.onViewWillAppear(); + child2.onViewWillAppear(); assertThat(uut.size()).isEqualTo(2); child2.onViewDisappear(); verify(child1, times(1)).onViewBroughtToFront(); diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/fakes/FakeParentController.kt b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/fakes/FakeParentController.kt new file mode 100644 index 00000000000..8e73224c64a --- /dev/null +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/fakes/FakeParentController.kt @@ -0,0 +1,35 @@ +package com.reactnativenavigation.viewcontrollers.fakes + +import android.app.Activity +import android.view.ViewGroup +import androidx.coordinatorlayout.widget.CoordinatorLayout +import com.reactnativenavigation.parse.Options +import com.reactnativenavigation.presentation.Presenter +import com.reactnativenavigation.utils.CompatUtils +import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry +import com.reactnativenavigation.viewcontrollers.ParentController +import com.reactnativenavigation.viewcontrollers.ViewController +import com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabsController +import org.mockito.Mockito.mock + +class FakeParentController @JvmOverloads constructor( + activity: Activity, + childRegistry: ChildControllersRegistry, + private val child: ViewController<*>, + id: String = "Parent" + CompatUtils.generateViewId(), + presenter: Presenter = mock(Presenter::class.java), + initialOptions: Options = Options.EMPTY +) : ParentController(activity, childRegistry, id, presenter, initialOptions) { + init { + child.parentController = this + } + override fun getCurrentChild(): ViewController<*> = child + + override fun createView() = CoordinatorLayout(activity).apply { + addView(child.view) + } + + override fun getChildControllers() = listOf(child) + + override fun sendOnNavigationButtonPressed(buttonId: String?) = child.sendOnNavigationButtonPressed(buttonId) +} \ No newline at end of file diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenterTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenterTest.java index 691aecc5ca4..3bcae8b469e 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenterTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenterTest.java @@ -73,7 +73,6 @@ public void showModal() { disableShowModalAnimation(modal1); uut.showModal(modal1, root, new CommandListenerAdapter()); verify(modal1).setWaitForRender(any()); - verify(modal1).resolveCurrentOptions(defaultOptions); assertThat(modal1.getView().getFitsSystemWindows()).isTrue(); } @@ -84,7 +83,7 @@ public void showModal_noAnimation() { @Override public void onSuccess(String childId) { assertThat(modal1.getView().getParent()).isEqualTo(modalsLayout); - verify(modal1, times(1)).onViewAppeared(); + verify(modal1, times(1)).onViewWillAppear(); } }); uut.showModal(modal1, root, listener); @@ -124,6 +123,17 @@ public void onSuccess(String childId) { }); } + @Test + public void showModal_overCurrentContext_previousModalIsNotRemovedFromHierarchy() { + Options options = new Options(); + options.modal.presentationStyle = ModalPresentationStyle.OverCurrentContext; + uut.setDefaultOptions(options); + disableShowModalAnimation(modal1); + uut.showModal(modal1, root, new CommandListenerAdapter()); + verify(root, times(0)).detachView(); + verify(root, times(0)).onViewDisappear(); + } + @Test public void showModal_animatesByDefault() { uut.showModal(modal1, null, new CommandListenerAdapter() { @@ -155,6 +165,15 @@ public void showModal_rejectIfContentIsNull() { verify(listener).onError(any()); } + @Test + public void showModal_onViewDidAppearIsInvokedBeforeViewDisappear() { + disableShowModalAnimation(modal1); + uut.showModal(modal1, root, new CommandListenerAdapter()); + InOrder inOrder = inOrder(modal1, root); + inOrder.verify(modal1).onViewDidAppear(); + inOrder.verify(root).onViewDisappear(); + } + @Test public void dismissModal_animatesByDefault() { disableShowModalAnimation(modal1); @@ -200,12 +219,12 @@ public void dismissModal_previousModalIsAddedBackToHierarchy() { disableShowModalAnimation(modal1, modal2); uut.showModal(modal1, root, new CommandListenerAdapter()); - verify(modal1).onViewAppeared(); + verify(modal1).onViewWillAppear(); uut.showModal(modal2, modal1, new CommandListenerAdapter()); assertThat(modal1.getView().getParent()).isNull(); uut.dismissModal(modal2, modal1, root, new CommandListenerAdapter()); assertThat(modal1.getView().getParent()).isNotNull(); - verify(modal1, times(2)).onViewAppeared(); + verify(modal1, times(2)).onViewWillAppear(); } @Test diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalStackTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalStackTest.java index ff04be23fe5..001bce3ed44 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalStackTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalStackTest.java @@ -95,20 +95,20 @@ public void modalRefIsSaved() { public void showModal() { CommandListener listener = spy(new CommandListenerAdapter()); uut.showModal(modal1, root, listener); - verify(listener, times(1)).onSuccess(modal1.getId()); + verify(listener).onSuccess(modal1.getId()); + verify(modal1).onViewDidAppear(); assertThat(uut.size()).isOne(); - verify(presenter, times(1)).showModal(modal1, root, listener); + verify(presenter).showModal(eq(modal1), eq(root), any()); assertThat(findModal(MODAL_ID_1)).isNotNull(); } - @SuppressWarnings("Convert2Lambda") @Test public void dismissModal() { uut.showModal(modal1, root, new CommandListenerAdapter()); CommandListener listener = spy(new CommandListenerAdapter()); uut.dismissModal(modal1.getId(), root, listener); assertThat(findModal(modal1.getId())).isNull(); - verify(presenter, times(1)).dismissModal(eq(modal1), eq(root), eq(root), any()); + verify(presenter).dismissModal(eq(modal1), eq(root), eq(root), any()); verify(listener).onSuccess(modal1.getId()); } @@ -200,7 +200,6 @@ public void dismissAllModals_optionsAreMergedOnTopModal() { verify(modal2, times(0)).mergeOptions(mergeOptions); } - @SuppressWarnings("Convert2Lambda") @Test public void dismissAllModals_onlyTopModalIsAnimated() { modal2 = spy(modal2); @@ -262,7 +261,7 @@ public void onDismiss_onViewAppearedInvokedOnPreviousModal() { uut.showModal(modal1, root, new CommandListenerAdapter()); uut.showModal(modal2, root, new CommandListenerAdapter()); uut.dismissModal(modal2.getId(), root, new CommandListenerAdapter()); - verify(modal1, times(2)).onViewAppeared(); + verify(modal1, times(2)).onViewWillAppear(); } @Test 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 c359c55b36f..0d2eae7abc7 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 @@ -64,6 +64,7 @@ public class NavigatorTest extends BaseTest { private Navigator uut; private RootPresenter rootPresenter; private StackController parentController; + private ViewController initialChild; private SimpleViewController child1; private ViewController child2; private ViewController child3; @@ -93,7 +94,8 @@ public void beforeEach() { uut = new Navigator(activity, childRegistry, modalStack, overlayManager, rootPresenter); activity.setNavigator(uut); - parentController = newStack(); + initialChild = new SimpleViewController(activity, childRegistry, "initialChild", Options.EMPTY); + parentController = newStack(initialChild); parentVisibilityListener = spy(new ViewController.ViewVisibilityListener() { @Override public boolean onViewAppeared(View view) { @@ -268,12 +270,10 @@ public void pop_InvalidDoesNothing() { @Test public void pop_FromCorrectStackByFindingChildId() { - StackController stack1 = newStack(); - StackController stack2 = newStack(); + StackController stack1 = newStack(child1); + StackController stack2 = newStack(child2); BottomTabsController bottomTabsController = newTabs(Arrays.asList(stack1, stack2)); uut.setRoot(bottomTabsController, new CommandListenerAdapter(), reactInstanceManager); - stack1.push(child1, new CommandListenerAdapter()); - stack2.push(child2, new CommandListenerAdapter()); stack2.push(child3, new CommandListenerAdapter() { @Override public void onSuccess(String childId) { @@ -293,10 +293,8 @@ public void onSuccess(String childId) { public void pop_byStackId() { disablePushAnimation(child1, child2); disablePopAnimation(child2, child1); - StackController stack = newStack(); stack.ensureViewIsCreated(); + StackController stack = newStack(child1, child2); stack.ensureViewIsCreated(); uut.setRoot(stack, new CommandListenerAdapter(), reactInstanceManager); - stack.push(child1, new CommandListenerAdapter()); - stack.push(child2, new CommandListenerAdapter()); uut.pop(stack.getId(), Options.EMPTY, new CommandListenerAdapter()); assertThat(stack.getChildControllers()).containsOnly(child1); @@ -304,35 +302,31 @@ public void pop_byStackId() { @Test public void popTo_FromCorrectStackUpToChild() { - StackController stack1 = newStack(); - StackController stack2 = newStack(); + disablePushAnimation(child5); + + StackController stack1 = newStack(child1); + StackController stack2 = newStack(child2, child3, child4); BottomTabsController bottomTabsController = newTabs(Arrays.asList(stack1, stack2)); uut.setRoot(bottomTabsController, new CommandListenerAdapter(), reactInstanceManager); - stack1.push(child1, new CommandListenerAdapter()); - stack2.push(child2, new CommandListenerAdapter()); - stack2.push(child3, new CommandListenerAdapter()); - stack2.push(child4, new CommandListenerAdapter()); - stack2.push(child5, new CommandListenerAdapter() { + CommandListenerAdapter listener = spy(new CommandListenerAdapter() { @Override public void onSuccess(String childId) { uut.popTo(child2.getId(), Options.EMPTY, new CommandListenerAdapter()); assertThat(stack2.getChildControllers()).containsOnly(child2); } }); + stack2.push(child5, listener); + verify(listener).onSuccess(child5.getId()); } @Test public void popToRoot() { - StackController stack1 = newStack(); - StackController stack2 = newStack(); + StackController stack1 = newStack(child1); + StackController stack2 = newStack(child2, child3, child4); BottomTabsController bottomTabsController = newTabs(Arrays.asList(stack1, stack2)); uut.setRoot(bottomTabsController, new CommandListenerAdapter(), reactInstanceManager); - stack1.push(child1, new CommandListenerAdapter()); - stack2.push(child2, new CommandListenerAdapter()); - stack2.push(child3, new CommandListenerAdapter()); - stack2.push(child4, new CommandListenerAdapter()); stack2.push(child5, new CommandListenerAdapter() { @Override public void onSuccess(String childId) { @@ -346,11 +340,9 @@ public void onSuccess(String childId) { public void setStackRoot() { disablePushAnimation(child1, child2, child3); - StackController stack = newStack(); + StackController stack = newStack(child1, child2); uut.setRoot(stack, new CommandListenerAdapter(), reactInstanceManager); - stack.push(child1, new CommandListenerAdapter()); - stack.push(child2, new CommandListenerAdapter()); stack.setRoot(Collections.singletonList(child3), new CommandListenerAdapter()); assertThat(stack.getChildControllers()).containsOnly(child3); @@ -429,17 +421,6 @@ public void findController_modal() { assertThat(uut.findController(child1.getId())).isEqualTo(child1); } - @NonNull - private StackController newStack() { - StackController stack = TestUtils.newStackController(activity) - .setChildRegistry(childRegistry) - .setId("stack" + CompatUtils.generateViewId()) - .setInitialOptions(tabOptions) - .build(); - stack.ensureViewIsCreated(); - return stack; - } - @Test public void push_promise() { final StackController stackController = newStack(); @@ -484,21 +465,22 @@ public void onError(String reason) { @Test public void pop_FromCorrectStackByFindingChildId_Promise() { - StackController stack1 = newStack(); - final StackController stack2 = newStack(); + disablePushAnimation(child4); + + StackController stack1 = newStack(child1); + final StackController stack2 = newStack(child2, child3); BottomTabsController bottomTabsController = newTabs(Arrays.asList(stack1, stack2)); uut.setRoot(bottomTabsController, new CommandListenerAdapter(), reactInstanceManager); - stack1.push(child1, new CommandListenerAdapter()); - stack2.push(child2, new CommandListenerAdapter()); - stack2.push(child3, new CommandListenerAdapter()); - stack2.push(child4, new CommandListenerAdapter() { + CommandListenerAdapter listener = spy(new CommandListenerAdapter() { @Override public void onSuccess(String childId) { uut.pop("child4", Options.EMPTY, new CommandListenerAdapter()); assertThat(stack2.getChildControllers()).containsOnly(child2, child3); } }); + stack2.push(child4, listener); + verify(listener).onSuccess(child4.getId()); } @Test @@ -514,12 +496,10 @@ public void pushIntoModal() { @Test public void pushedStackCanBePopped() { StackController spy = spy(parentController); - disablePushAnimation(spy, child1, child2); - spy.push(child1, new CommandListenerAdapter()); + disablePushAnimation(spy, child2); spy.push(child2, new CommandListenerAdapter()); - StackController parent = newStack(); - parent.push(spy, new CommandListenerAdapter()); + StackController parent = newStack(spy); parent.options.animations.setRoot.enabled = new Bool(false); uut.setRoot(parent, new CommandListenerAdapter(), reactInstanceManager); @@ -636,7 +616,7 @@ public void handleBack_onViewAppearedInvokedOnRoot() { @Override public void onSuccess(String childId) { assertThat(spy.getView().getParent()).isNotNull(); - verify(spy, times(2)).onViewAppeared(); + verify(spy, times(2)).onViewWillAppear(); } }); } @@ -678,4 +658,16 @@ public void destroyViews() { uut.destroy(); assertThat(childRegistry.size()).isZero(); } + + @NonNull + private StackController newStack(ViewController... children) { + StackController stack = TestUtils.newStackController(activity) + .setChildren(children) + .setChildRegistry(childRegistry) + .setId("stack" + CompatUtils.generateViewId()) + .setInitialOptions(tabOptions) + .build(); + stack.ensureViewIsCreated(); + return stack; + } } diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/RootPresenterTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/RootPresenterTest.java index 123a9f6a945..d1976ff9d27 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/RootPresenterTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/RootPresenterTest.java @@ -5,7 +5,7 @@ import com.facebook.react.ReactInstanceManager; import com.reactnativenavigation.BaseTest; -import com.reactnativenavigation.anim.NavigationAnimator; +import com.reactnativenavigation.anim.StackAnimator; import com.reactnativenavigation.mocks.SimpleViewController; import com.reactnativenavigation.parse.AnimationOptions; import com.reactnativenavigation.parse.Options; @@ -38,7 +38,7 @@ public class RootPresenterTest extends BaseTest { private RootPresenter uut; private CoordinatorLayout rootContainer; private ViewController root; - private NavigationAnimator animator; + private StackAnimator animator; private LayoutDirectionApplier layoutDirectionApplier; private Options defaultOptions; private ReactInstanceManager reactInstanceManager; @@ -120,7 +120,7 @@ public void setRoot_waitForRender() { assertThat(spy.getView().getAlpha()).isZero(); verifyZeroInteractions(listener); - spy.onViewAppeared(); + spy.onViewWillAppear(); assertThat(spy.getView().getAlpha()).isOne(); verify(listener).onSuccess(spy.getId()); } @@ -133,8 +133,8 @@ public void setRoot_appliesLayoutDirection() { } @NonNull - private NavigationAnimator createAnimator(Activity activity) { - return new NavigationAnimator(activity, mock(ElementTransitionManager.class)) { + private StackAnimator createAnimator(Activity activity) { + return new StackAnimator(activity, mock(ElementTransitionManager.class)) { @Override public void setRoot(View root, AnimationOptions setRoot, Runnable onAnimationEnd) { onAnimationEnd.run(); 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 9fbefc22533..2ad85ea3abf 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 @@ -56,7 +56,8 @@ public void show_attachesOverlayContainerToContentLayout() { public void show() { CommandListenerAdapter listener = spy(new CommandListenerAdapter()); uut.show(overlayContainer, overlay1, listener); - verify(listener, times(1)).onSuccess(OVERLAY_ID_1); + verify(listener).onSuccess(OVERLAY_ID_1); + verify(overlay1).onViewDidAppear(); assertThat(overlay1.getView().getParent()).isEqualTo(overlayContainer); assertMatchParent(overlay1.getView()); } diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/sidemenu/SideMenuControllerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/sidemenu/SideMenuControllerTest.java index da8375d755d..a4b8e92a1e3 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/sidemenu/SideMenuControllerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/sidemenu/SideMenuControllerTest.java @@ -25,6 +25,7 @@ import com.reactnativenavigation.viewcontrollers.ViewController; import com.reactnativenavigation.views.SideMenu; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import org.mockito.Mockito; @@ -59,9 +60,9 @@ public void beforeEach() { childRegistry = new ChildControllersRegistry(); presenter = spy(new SideMenuPresenter()); child = new SimpleComponentViewController(activity, childRegistry, "child", new Options()); - left = new SimpleComponentViewController(activity, childRegistry, "left", new Options()); - right = new SimpleComponentViewController(activity, childRegistry, "right", new Options()); - center = spy(new SimpleComponentViewController(activity, childRegistry, "center", new Options())); + left = spy(new SimpleComponentViewController(activity, childRegistry, "left", new Options())); + right = spy(new SimpleComponentViewController(activity, childRegistry, "right", createSideMenuOptions())); + center = spy(new SimpleComponentViewController(activity, childRegistry, "center", createSideMenuOptions())); uut = new SideMenuController(activity, childRegistry, "sideMenu", new Options(), presenter, new Presenter(activity, new Options())) { @Override public Options resolveCurrentOptions() { @@ -74,6 +75,14 @@ public Options resolveCurrentOptions() { uut.setParentController(parent); } + @NotNull + private Options createSideMenuOptions() { + Options options = new Options(); + options.sideMenuRootOptions.left.animate = new Bool(false); + options.sideMenuRootOptions.right.animate = new Bool(false); + return options; + } + @Test public void createView_bindView() { uut.ensureViewIsCreated(); @@ -119,7 +128,7 @@ public void onViewAppeared() { setLeftRight(left, right); - uut.onViewAppeared(); + uut.onViewWillAppear(); verify(leftView).requestLayout(); verify(rightView).requestLayout(); } @@ -289,11 +298,11 @@ public void leftMenuOpen_visibilityEventsAreEmitted() { activity.setContentView(uut.getView()); assertThat(uut.getView().isDrawerOpen(Gravity.LEFT)).isFalse(); - verify(spy, times(0)).onViewAppeared(); + verify(spy, times(0)).onViewWillAppear(); openLeftMenu(); assertThat(uut.getView().isDrawerOpen(Gravity.LEFT)).isTrue(); - verify(spy).onViewAppeared(); + verify(spy).onViewDidAppear(); closeLeftMenu(); assertThat(uut.getView().isDrawerOpen(Gravity.LEFT)).isFalse(); @@ -307,11 +316,11 @@ public void rightMenuOpen_visibilityEventsAreEmitted() { activity.setContentView(uut.getView()); assertThat(uut.getView().isDrawerOpen(Gravity.RIGHT)).isFalse(); - verify(spy, times(0)).onViewAppeared(); + verify(spy, times(0)).onViewWillAppear(); openRightMenu(); assertThat(uut.getView().isDrawerOpen(Gravity.RIGHT)).isTrue(); - verify(spy).onViewAppeared(); + verify(spy).onViewDidAppear(); closeRightMenu(); assertThat(uut.getView().isDrawerOpen(Gravity.RIGHT)).isFalse(); @@ -331,6 +340,25 @@ public void onDrawerOpened_drawerOpenedWIthSwipe_visibilityIsUpdated() { closeDrawerAndAssertVisibility(left, (side) -> side.resolveCurrentOptions().sideMenuRootOptions.left); } + @Test + public void onDrawerSlide_componentDidAppearIsEmittedWhenDrawerIsFullyOpened() { + uut.setLeftController(left); + uut.setRightController(right); + uut.ensureViewIsCreated(); + + uut.onDrawerSlide(left.getView(),0.1f); + uut.onDrawerSlide(right.getView(),0.1f); + + verify(left, times(0)).onViewDidAppear(); + verify(right, times(0)).onViewDidAppear(); + + uut.onDrawerSlide(left.getView(),1); + uut.onDrawerSlide(right.getView(),1); + + verify(left).onViewDidAppear(); + verify(right).onViewDidAppear(); + } + @Test public void applyTopInsets_delegatesToChildren() { setLeftRight(spy(left), spy(right)); diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.java index dd3365e63f9..c5753529975 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.java @@ -9,7 +9,7 @@ import com.reactnativenavigation.BaseTest; import com.reactnativenavigation.TestUtils; -import com.reactnativenavigation.anim.NavigationAnimator; +import com.reactnativenavigation.anim.StackAnimator; import com.reactnativenavigation.mocks.ImageLoaderMock; import com.reactnativenavigation.mocks.SimpleViewController; import com.reactnativenavigation.mocks.TitleBarButtonCreatorMock; @@ -85,7 +85,7 @@ public class StackControllerTest extends BaseTest { private ViewController child3; private SimpleViewController.SimpleView child3View; private ViewController child4; - private NavigationAnimator animator; + private StackAnimator animator; private TopBarController topBarController; private StackPresenter presenter; private BackButtonHelper backButtonHelper; @@ -98,7 +98,7 @@ public void beforeEach() { backButtonHelper = spy(new BackButtonHelper()); activity = newActivity(); StatusBarUtils.saveStatusBarHeight(63); - animator = spy(new NavigationAnimator(activity, new ElementTransitionManager())); + animator = spy(new StackAnimator(activity, new ElementTransitionManager())); childRegistry = new ChildControllersRegistry(); presenter = spy(new StackPresenter( activity, @@ -250,11 +250,11 @@ public void push_waitForRender() { assertThat(child1.getView().getParent()).isEqualTo(uut.getView()); assertThat(child2.getView().getParent()).isEqualTo(uut.getView()); assertThat(child2.isViewShown()).isFalse(); - verify(child2, times(0)).onViewAppeared(); + verify(child2, times(0)).onViewWillAppear(); child2.getView().addView(new View(activity)); ShadowLooper.idleMainLooper(); - verify(child2).onViewAppeared(); + verify(child2).onViewWillAppear(); assertThat(child2.isViewShown()).isTrue(); animator.endPushAnimation(child2.getView()); assertThat(child1.getView().getParent()).isNull(); @@ -303,6 +303,15 @@ public void push_rejectIfStackContainsChildWithId() { assertThat(uut.size()).isEqualTo(1); } + @Test + public void push_onViewDidAppearInvokedOnPushedScreen() { + disablePushAnimation(child1, child2); + uut.push(child1, new CommandListenerAdapter()); // Initialize stack with a child + + uut.push(child2, new CommandListenerAdapter()); + verify(child2).onViewDidAppear(); + } + @Test public void animateSetRoot() { disablePushAnimation(child1, child2, child3); @@ -419,7 +428,29 @@ public void setRoot_topScreenIsStartedThenTheRest() { } @Test - public synchronized void pop() { + public void setRoot_onViewDidAppearIsInvokedOnAppearingChild() { + disablePushAnimation(child1); + uut.setRoot(Collections.singletonList(child1), new CommandListenerAdapter()); + + verify(child1).onViewDidAppear(); + } + + @Test + public void setRoot_inViewDidAppearIsInvokedBeforePreviousRootIsDestroyed() { + disablePushAnimation(child1, child2, child3); + uut.push(child1, new CommandListenerAdapter()); + + uut.setRoot(Arrays.asList(child2, child3), new CommandListenerAdapter()); + ShadowLooper.idleMainLooper(); + + InOrder inOrder = inOrder(child2, child3, child1); + inOrder.verify(child3).onViewDidAppear(); + inOrder.verify(child1).onViewDisappear(); + verify(child2, times(0)).onViewDidAppear(); + } + + @Test + public void pop() { disablePushAnimation(child1, child2); uut.push(child1, new CommandListenerAdapter()); uut.push(child2, new CommandListenerAdapter() { @@ -663,11 +694,11 @@ public void push_doesNotAnimateTopBarIfScreenIsPushedWithoutAnimation() { uut.push(child1, new CommandListenerAdapter() { @Override public void onSuccess(String childId) { - child1.onViewAppeared(); + child1.onViewWillAppear(); assertThat(uut.getTopBar().getVisibility()).isEqualTo(View.GONE); uut.push(child2, new CommandListenerAdapter()); - child2.onViewAppeared(); + child2.onViewWillAppear(); verify(topBarController, times(0)).showAnimate(child2.options.animations.push.topBar, 0); assertThat(uut.getTopBar().getVisibility()).isEqualTo(View.VISIBLE); verify(topBarController, times(2)).resetViewProperties(); @@ -918,16 +949,22 @@ public void onSuccess(String childId) { } @Test - public void pop_callWillAppearWillDisappear() { - child1.options.animations.push.enabled = new Bool(false); - child2.options.animations.push.enabled = new Bool(false); - child1 = spy(child1); - child2 = spy(child2); + public void pop_callWillDisappear() { + disablePushAnimation(child1, child2); + uut.push(child1, new CommandListenerAdapter()); + uut.push(child2, new CommandListenerAdapter()); + uut.pop(Options.EMPTY, new CommandListenerAdapter()); + verify(child2).onViewWillDisappear(); + } + + @Test + public void pop_callDidAppear() { + disablePushAnimation(child1, child2); + disablePopAnimation(child2); uut.push(child1, new CommandListenerAdapter()); uut.push(child2, new CommandListenerAdapter()); uut.pop(Options.EMPTY, new CommandListenerAdapter()); - verify(child1, times(1)).onViewWillAppear(); - verify(child2, times(1)).onViewWillDisappear(); + verify(child1).onViewDidAppear(); } @Test @@ -940,7 +977,7 @@ public void pop_animatesTopBar() { uut.push(child1, new CommandListenerAdapter() { @Override public void onSuccess(String childId) { - child1.onViewAppeared(); + child1.onViewWillAppear(); assertThat(uut.getTopBar().getVisibility()).isEqualTo(View.GONE); uut.push(child2, new CommandListenerAdapter() { @Override @@ -1012,7 +1049,7 @@ public void stackCanBePushed() { StackController parent = createStack("someStack"); parent.ensureViewIsCreated(); parent.push(uut, new CommandListenerAdapter()); - uut.onViewAppeared(); + uut.onViewWillAppear(); assertThat(parent.getView().getChildAt(0)).isEqualTo(uut.getView()); } @@ -1028,7 +1065,7 @@ public void applyOptions_applyOnlyOnFirstStack() { child1.options = childOptions; uut.push(child1, new CommandListenerAdapter()); child1.ensureViewIsCreated(); - child1.onViewAppeared(); + child1.onViewWillAppear(); ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(Options.class); ArgumentCaptor viewCaptor = ArgumentCaptor.forClass(ViewController.class); @@ -1041,7 +1078,7 @@ public void applyOptions_topTabsAreNotVisibleIfNoTabsAreDefined() { uut.ensureViewIsCreated(); uut.push(child1, new CommandListenerAdapter()); child1.ensureViewIsCreated(); - child1.onViewAppeared(); + child1.onViewWillAppear(); assertThat(ViewHelper.isVisible(uut.getTopBar().getTopTabs())).isFalse(); }