package com.reactnativenavigation.screens; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.RelativeLayout; import android.widget.RelativeLayout.LayoutParams; import com.facebook.react.bridge.Callback; import com.reactnativenavigation.NavigationApplication; import com.reactnativenavigation.params.ContextualMenuParams; import com.reactnativenavigation.params.FabParams; import com.reactnativenavigation.params.ScreenParams; import com.reactnativenavigation.params.StyleParams; import com.reactnativenavigation.params.TitleBarButtonParams; import com.reactnativenavigation.params.TitleBarLeftButtonParams; import com.reactnativenavigation.utils.KeyboardVisibilityDetector; import com.reactnativenavigation.utils.Task; import com.reactnativenavigation.views.LeftButtonOnClickListener; import java.util.List; import java.util.Stack; public class ScreenStack { private static final String TAG = "ScreenStack"; public interface OnScreenPop { void onScreenPopAnimationEnd(); } private final AppCompatActivity activity; private RelativeLayout parent; private LeftButtonOnClickListener leftButtonOnClickListener; private Stack<Screen> stack = new Stack<>(); private final KeyboardVisibilityDetector keyboardVisibilityDetector; private boolean isStackVisible = false; private final String navigatorId; public String getNavigatorId() { return navigatorId; } public ScreenStack(AppCompatActivity activity, RelativeLayout parent, String navigatorId, LeftButtonOnClickListener leftButtonOnClickListener) { this.activity = activity; this.parent = parent; this.navigatorId = navigatorId; this.leftButtonOnClickListener = leftButtonOnClickListener; keyboardVisibilityDetector = new KeyboardVisibilityDetector(parent); } public void newStack(final ScreenParams params, LayoutParams layoutParams) { final Screen nextScreen = ScreenFactory.create(activity, params, leftButtonOnClickListener); final Screen previousScreen = stack.peek(); if (isStackVisible) { pushScreenToVisibleStack(layoutParams, nextScreen, previousScreen, new Screen.OnDisplayListener() { @Override public void onDisplay() { removeElementsBelowTop(); } }); } else { pushScreenToInvisibleStack(layoutParams, nextScreen, previousScreen); removeElementsBelowTop(); } } private void removeElementsBelowTop() { while (stack.size() > 1) { Screen screen = stack.get(0); parent.removeView(screen); screen.destroy(); stack.remove(0); } } public void pushInitialScreenWithAnimation(final ScreenParams initialScreenParams, LayoutParams params) { isStackVisible = true; pushInitialScreen(initialScreenParams, params); final Screen screen = stack.peek(); screen.setOnDisplayListener(new Screen.OnDisplayListener() { @Override public void onDisplay() { screen.show(initialScreenParams.animateScreenTransitions); screen.setStyle(); } }); } public void pushInitialScreen(ScreenParams initialScreenParams, LayoutParams params) { Screen initialScreen = ScreenFactory.create(activity, initialScreenParams, leftButtonOnClickListener); initialScreen.setVisibility(View.INVISIBLE); initialScreen.setOnDisplayListener(new Screen.OnDisplayListener() { @Override public void onDisplay() { if (isStackVisible) { NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willAppear", stack.peek().getNavigatorEventId()); NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didAppear", stack.peek().getNavigatorEventId()); } } }); addScreen(initialScreen, params); } public void push(final ScreenParams params, LayoutParams layoutParams) { Screen nextScreen = ScreenFactory.create(activity, params, leftButtonOnClickListener); final Screen previousScreen = stack.peek(); if (isStackVisible) { if (nextScreen.screenParams.sharedElementsTransitions.isEmpty()) { pushScreenToVisibleStack(layoutParams, nextScreen, previousScreen); } else { pushScreenToVisibleStackWithSharedElementTransition(layoutParams, nextScreen, previousScreen); } } else { pushScreenToInvisibleStack(layoutParams, nextScreen, previousScreen); } } private void pushScreenToVisibleStack(LayoutParams layoutParams, final Screen nextScreen, final Screen previousScreen) { pushScreenToVisibleStack(layoutParams, nextScreen, previousScreen, null); } private void pushScreenToVisibleStack(LayoutParams layoutParams, final Screen nextScreen, final Screen previousScreen, @Nullable final Screen.OnDisplayListener onDisplay) { nextScreen.setVisibility(View.INVISIBLE); addScreen(nextScreen, layoutParams); NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willDisappear", previousScreen.getNavigatorEventId()); nextScreen.setOnDisplayListener(new Screen.OnDisplayListener() { @Override public void onDisplay() { nextScreen.show(nextScreen.screenParams.animateScreenTransitions, new Runnable() { @Override public void run() { if (onDisplay != null) onDisplay.onDisplay(); NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didDisappear", previousScreen.getNavigatorEventId()); parent.removeView(previousScreen); } }); } }); } private void pushScreenToVisibleStackWithSharedElementTransition(LayoutParams layoutParams, final Screen nextScreen, final Screen previousScreen) { nextScreen.setVisibility(View.INVISIBLE); nextScreen.setOnDisplayListener(new Screen.OnDisplayListener() { @Override public void onDisplay() { nextScreen.showWithSharedElementsTransitions(previousScreen.sharedElements.getToElements(), new Runnable() { @Override public void run() { parent.removeView(previousScreen); } }); } }); addScreen(nextScreen, layoutParams); } private void pushScreenToInvisibleStack(LayoutParams layoutParams, Screen nextScreen, Screen previousScreen) { nextScreen.setVisibility(View.INVISIBLE); addScreen(nextScreen, layoutParams); parent.removeView(previousScreen); } private void addScreen(Screen screen, LayoutParams layoutParams) { addScreenBeforeSnackbarAndFabLayout(screen, layoutParams); stack.push(screen); } private void addScreenBeforeSnackbarAndFabLayout(Screen screen, LayoutParams layoutParams) { parent.addView(screen, parent.getChildCount() - 1, layoutParams); } public void pop(boolean animated) { pop(animated, null); } public void pop(final boolean animated, @Nullable final OnScreenPop onScreenPop) { if (!canPop()) { return; } if (keyboardVisibilityDetector.isKeyboardVisible()) { keyboardVisibilityDetector.setKeyboardCloseListener(new Runnable() { @Override public void run() { keyboardVisibilityDetector.setKeyboardCloseListener(null); popInternal(animated, onScreenPop); } }); keyboardVisibilityDetector.closeKeyboard(); } else { popInternal(animated, onScreenPop); } } private void popInternal(final boolean animated, @Nullable final OnScreenPop onScreenPop) { final Screen toRemove = stack.pop(); final Screen previous = stack.peek(); swapScreens(animated, toRemove, previous, onScreenPop); } private void swapScreens(boolean animated, final Screen toRemove, Screen previous, OnScreenPop onScreenPop) { readdPrevious(previous); previous.setStyle(); hideScreen(animated, toRemove, previous); if (onScreenPop != null) { onScreenPop.onScreenPopAnimationEnd(); } } private void hideScreen(boolean animated, final Screen toRemove, Screen previous) { Runnable onAnimationEnd = new Runnable() { @Override public void run() { toRemove.destroy(); parent.removeView(toRemove); } }; if (animated) { toRemove.animateHide(previous.sharedElements.getToElements(), onAnimationEnd); } else { toRemove.hide(previous.sharedElements.getToElements(), onAnimationEnd); } } public Screen peek() { return stack.peek(); } private void readdPrevious(Screen previous) { previous.setVisibility(View.VISIBLE); NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willAppear", previous.getNavigatorEventId()); NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didAppear", previous.getNavigatorEventId()); parent.addView(previous, 0); } public void popToRoot(final boolean animated, @Nullable final OnScreenPop onScreenPop) { if (keyboardVisibilityDetector.isKeyboardVisible()) { keyboardVisibilityDetector.setKeyboardCloseListener(new Runnable() { @Override public void run() { keyboardVisibilityDetector.setKeyboardCloseListener(null); popToRootInternal(animated, onScreenPop); } }); keyboardVisibilityDetector.closeKeyboard(); } else { popToRootInternal(animated, onScreenPop); } } private void popToRootInternal(final boolean animated, @Nullable final OnScreenPop onScreenPop) { while (canPop()) { if (stack.size() == 2) { popInternal(animated, onScreenPop); } else { popInternal(animated, null); } } } public void destroy() { for (Screen screen : stack) { screen.destroy(); parent.removeView(screen); } stack.clear(); } public boolean canPop() { return stack.size() > 1 && !isPreviousScreenAttachedToWindow(); } private boolean isPreviousScreenAttachedToWindow() { Screen previousScreen = stack.get(stack.size() - 2); if (previousScreen.getParent() != null) { Log.w(TAG, "Can't pop stack. reason: previous screen is already attached"); return true; } return false; } public void setScreenTopBarVisible(String screenInstanceId, final boolean visible, final boolean animate) { performOnScreen(screenInstanceId, new Task<Screen>() { @Override public void run(Screen param) { param.setTopBarVisible(visible, animate); } }); } public void setScreenTitleBarTitle(String screenInstanceId, final String title) { performOnScreen(screenInstanceId, new Task<Screen>() { @Override public void run(Screen param) { param.setTitleBarTitle(title); } }); } public void setScreenTitleBarSubtitle(String screenInstanceId, final String subtitle) { performOnScreen(screenInstanceId, new Task<Screen>() { @Override public void run(Screen param) { param.setTitleBarSubtitle(subtitle); } }); } public void setScreenTitleBarRightButtons(String screenInstanceId, final String navigatorEventId, final List<TitleBarButtonParams> titleBarButtons) { performOnScreen(screenInstanceId, new Task<Screen>() { @Override public void run(Screen param) { param.setTitleBarRightButtons(navigatorEventId, titleBarButtons); } }); } public void setScreenTitleBarLeftButton(String screenInstanceId, final String navigatorEventId, final TitleBarLeftButtonParams titleBarLeftButtonParams) { performOnScreen(screenInstanceId, new Task<Screen>() { @Override public void run(Screen screen) { screen.setTitleBarLeftButton(navigatorEventId, leftButtonOnClickListener, titleBarLeftButtonParams); } }); } public void setFab(String screenInstanceId, final FabParams fabParams) { performOnScreen(screenInstanceId, new Task<Screen>() { @Override public void run(Screen screen) { screen.setFab(fabParams); } }); } public void updateScreenStyle(String screenInstanceId, final Bundle styleParams) { performOnScreen(screenInstanceId, new Task<Screen>() { @Override public void run(Screen screen) { if (isScreenVisible(screen)) { screen.updateVisibleScreenStyle(styleParams); } else { screen.updateInvisibleScreenStyle(styleParams); } } }); } private boolean isScreenVisible(Screen screen) { return isStackVisible && peek() == screen; } public void showContextualMenu(String screenInstanceId, final ContextualMenuParams params, final Callback onButtonClicked) { performOnScreen(screenInstanceId, new Task<Screen>() { @Override public void run(Screen screen) { screen.showContextualMenu(params, onButtonClicked); } }); } public void dismissContextualMenu(String screenInstanceId) { performOnScreen(screenInstanceId, new Task<Screen>() { @Override public void run(Screen screen) { screen.dismissContextualMenu(); } }); } public void selectTopTabByTabIndex(String screenInstanceId, final int index) { performOnScreen(screenInstanceId, new Task<Screen>() { @Override public void run(Screen screen) { if (screen.screenParams.hasTopTabs()) { ((ViewPagerScreen) screen).selectTopTabByTabIndex(index); } } }); } public void selectTopTabByScreen(final String screenInstanceId) { performOnScreen(screenInstanceId, new Task<Screen>() { @Override public void run(Screen screen) { ((ViewPagerScreen) screen).selectTopTabByTabByScreen(screenInstanceId); } }); } public StyleParams getCurrentScreenStyleParams() { return stack.peek().getStyleParams(); } public boolean handleBackPressInJs() { ScreenParams currentScreen = stack.peek().screenParams; if (currentScreen.overrideBackPressInJs) { NavigationApplication.instance.getEventEmitter().sendNavigatorEvent("backPress", currentScreen.getNavigatorEventId()); return true; } return false; } private void performOnScreen(String screenInstanceId, Task<Screen> task) { if (stack.isEmpty()) { return; } for (Screen screen : stack) { if (screen.hasScreenInstance(screenInstanceId)) { task.run(screen); return; } } } public void show() { isStackVisible = true; stack.peek().setStyle(); stack.peek().setVisibility(View.VISIBLE); NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willAppear", stack.peek().getNavigatorEventId()); NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didAppear", stack.peek().getNavigatorEventId()); } public void hide() { NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("willDisappear", stack.peek().getNavigatorEventId()); NavigationApplication.instance.getEventEmitter().sendScreenChangedEvent("didDisappear", stack.peek().getNavigatorEventId()); isStackVisible = false; stack.peek().setVisibility(View.INVISIBLE); } }