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 b933b8bd871..d1237c94d04 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 @@ -68,7 +68,7 @@ public StackController(Activity activity, List> children, Chil @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - presenter.onConfigurationChanged(resolveCurrentOptions()); + presenter.onConfigurationChanged(resolveCurrentOptions(), getCurrentChild()); fabPresenter.onConfigurationChanged(resolveCurrentOptions()); } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenter.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenter.java index 313a9c3912c..4fc0950ef75 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenter.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenter.java @@ -1,9 +1,17 @@ package com.reactnativenavigation.viewcontrollers.stack; +import static com.reactnativenavigation.utils.CollectionUtils.filter; +import static com.reactnativenavigation.utils.CollectionUtils.forEach; +import static com.reactnativenavigation.utils.CollectionUtils.isNullOrEmpty; +import static com.reactnativenavigation.utils.CollectionUtils.merge; +import static com.reactnativenavigation.utils.ObjectUtils.perform; +import static com.reactnativenavigation.viewcontrollers.stack.topbar.TopBarControllerKt.DEFAULT_BORDER_COLOR; + import android.animation.Animator; import android.app.Activity; import android.graphics.Color; import android.view.View; +import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; import android.widget.FrameLayout; @@ -47,24 +55,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import static com.reactnativenavigation.utils.CollectionUtils.difference; -import static com.reactnativenavigation.utils.CollectionUtils.filter; -import static com.reactnativenavigation.utils.CollectionUtils.first; -import static com.reactnativenavigation.utils.CollectionUtils.forEach; -import static com.reactnativenavigation.utils.CollectionUtils.getOrDefault; -import static com.reactnativenavigation.utils.CollectionUtils.isNullOrEmpty; -import static com.reactnativenavigation.utils.CollectionUtils.keyBy; -import static com.reactnativenavigation.utils.CollectionUtils.merge; -import static com.reactnativenavigation.utils.ObjectUtils.perform; -import static com.reactnativenavigation.utils.ObjectUtils.take; - public class StackPresenter { - private static final int DEFAULT_BORDER_COLOR = Color.BLACK; private static final double DEFAULT_ELEVATION = 4d; private final Activity activity; @@ -79,12 +74,10 @@ public class StackPresenter { private final TitleBarButtonCreator buttonCreator; private Options defaultOptions; - private List currentRightButtons = new ArrayList<>(); - private List currentLeftButtons = new ArrayList<>(); private final Map titleControllers = new HashMap(); private final Map backgroundControllers = new HashMap(); - private final Map> componentRightButtons = new HashMap(); - private final Map> componentLeftButtons = new HashMap(); + private final Map> rightButtonControllers = new HashMap(); + private final Map> leftButtonControllers = new HashMap(); private final IconResolver iconResolver; private final TypefaceLoader typefaceLoader; @@ -126,8 +119,8 @@ public void bindView(TopBarController topBarController, @Nullable BottomTabsCont public boolean isRendered(View component) { ArrayList> controllers = new ArrayList<>(); - controllers.addAll(perform(componentRightButtons.get(component), new ArrayList<>(), Map::values)); - controllers.addAll(perform(componentLeftButtons.get(component), new ArrayList<>(), Map::values)); + controllers.addAll(perform(rightButtonControllers.get(component), new ArrayList<>(), Map::values)); + controllers.addAll(perform(leftButtonControllers.get(component), new ArrayList<>(), Map::values)); controllers.add(backgroundControllers.get(component)); controllers.add(titleControllers.get(component)); return renderChecker.areRendered(filter(controllers, ObjectUtils::notNull)); @@ -136,29 +129,21 @@ public boolean isRendered(View component) { public void mergeOptions(Options options, StackController stack, ViewController currentChild) { TopBarOptions resolvedTopBarOptions = options.topBar.copy().mergeWithDefault(stack.resolveChildOptions(currentChild).topBar).mergeWithDefault(defaultOptions.topBar); mergeOrientation(options.layout.orientation); - // mergeButtons(topBar, withDefault.topBar.buttons, child); mergeTopBarOptions(resolvedTopBarOptions, options, stack, currentChild); mergeTopTabsOptions(options.topTabs); mergeTopTabOptions(options.topTabOptions); } - public void onConfigurationChanged(Options options) { + public void onConfigurationChanged(Options options, ViewController currentChild) { if (topBar == null) return; Options withDefault = options.copy().withDefaultOptions(defaultOptions); - if (currentRightButtons != null && !currentRightButtons.isEmpty()) - topBarController.applyRightButtons(currentRightButtons); - if (currentLeftButtons != null && !currentLeftButtons.isEmpty()) - topBarController.applyLeftButtons(currentLeftButtons); if (withDefault.topBar.buttons.back.visible.isTrue()) { - topBar.setBackButton(createButtonController(withDefault.topBar.buttons.back)); + topBarController.setBackButton(createButtonController(withDefault.topBar.buttons.back)); } - topBar.setOverflowButtonColor(withDefault.topBar.rightButtonColor.get(Color.BLACK)); - topBar.applyTopTabsColors(withDefault.topTabs.selectedTabColor, - withDefault.topTabs.unselectedTabColor); - topBar.setBorderColor(withDefault.topBar.borderColor.get(DEFAULT_BORDER_COLOR)); - topBar.setBackgroundColor(withDefault.topBar.background.color.get(Color.WHITE)); - topBar.setTitleTextColor(withDefault.topBar.title.color.get(TopBar.DEFAULT_TITLE_COLOR)); - topBar.setSubtitleColor(withDefault.topBar.subtitle.color.get(TopBar.DEFAULT_TITLE_COLOR)); + topBarController.onConfigurationChanged(withDefault, + leftButtonControllers.get(currentChild.getView()), + rightButtonControllers.get(currentChild.getView())); + } public void applyInitialChildLayoutOptions(Options options) { @@ -183,10 +168,10 @@ public void applyOrientation(OrientationOptions options) { public void onChildDestroyed(ViewController child) { perform(titleControllers.remove(child.getView()), TitleBarReactViewController::destroy); perform(backgroundControllers.remove(child.getView()), TopBarBackgroundViewController::destroy); - destroyButtons(componentRightButtons.get(child.getView())); - destroyButtons(componentLeftButtons.get(child.getView())); - componentRightButtons.remove(child.getView()); - componentLeftButtons.remove(child.getView()); + destroyButtons(rightButtonControllers.get(child.getView())); + destroyButtons(leftButtonControllers.get(child.getView())); + rightButtonControllers.remove(child.getView()); + leftButtonControllers.remove(child.getView()); } private void destroyButtons(@Nullable Map buttons) { @@ -258,7 +243,7 @@ private void applyTopBarOptions(Options options, StackController stack, ViewCont } private void applyStatusBarDrawBehindOptions(TopBarOptions topBarOptions, Options withDefault) { - if(withDefault.statusBar.visible.isTrueOrUndefined() && withDefault.statusBar.drawBehind.isTrue()){ + if (withDefault.statusBar.visible.isTrueOrUndefined() && withDefault.statusBar.drawBehind.isTrue()) { topBar.setTopPadding(StatusBarUtils.getStatusBarHeight(activity)); topBar.setHeight(topBarOptions.height.get(UiUtils.getTopBarHeightDp(activity)) + StatusBarUtils.getStatusBarHeightDp(activity)); } else { @@ -268,8 +253,8 @@ private void applyStatusBarDrawBehindOptions(TopBarOptions topBarOptions, Option } private void mergeStatusBarDrawBehindOptions(TopBarOptions topBarOptions, Options toMerge) { - if(toMerge.statusBar.drawBehind.hasValue()){ - if(toMerge.statusBar.visible.isTrueOrUndefined() && toMerge.statusBar.drawBehind.isTrue()){ + if (toMerge.statusBar.drawBehind.hasValue()) { + if (toMerge.statusBar.visible.isTrueOrUndefined() && toMerge.statusBar.drawBehind.isTrue()) { topBar.setTopPadding(StatusBarUtils.getStatusBarHeight(activity)); topBar.setHeight(topBarOptions.height.get(UiUtils.getTopBarHeightDp(activity)) + StatusBarUtils.getStatusBarHeightDp(activity)); } else { @@ -304,59 +289,21 @@ private void applyTopBarVisibility(TopBarOptions options) { } private void applyButtons(TopBarOptions options, ViewController child) { - if (options.buttons.right != null) { - List rightButtons = mergeButtonsWithColor(options.buttons.right, - options.rightButtonColor - , options.rightButtonDisabledColor); - List rightButtonControllers = getOrCreateButtonControllersByInstanceId(componentRightButtons.get(child.getView()), rightButtons); - componentRightButtons.put(child.getView(), keyBy(rightButtonControllers, ButtonController::getButtonInstanceId)); - if (!CollectionUtils.equals(currentRightButtons, rightButtonControllers)) { - currentRightButtons = rightButtonControllers; - topBarController.applyRightButtons(currentRightButtons); - } - } else { - currentRightButtons = null; - topBar.clearRightButtons(); - } - - if (options.buttons.left != null) { - List leftButtons = mergeButtonsWithColor(options.buttons.left, - options.leftButtonColor, - options.leftButtonDisabledColor); - List leftButtonControllers = getOrCreateButtonControllersByInstanceId(componentLeftButtons.get(child.getView()), leftButtons); - componentLeftButtons.put(child.getView(), keyBy(leftButtonControllers, ButtonController::getButtonInstanceId)); - if (!CollectionUtils.equals(currentLeftButtons, leftButtonControllers)) { - currentLeftButtons = leftButtonControllers; - topBarController.applyLeftButtons(currentLeftButtons); - } - } else { - currentLeftButtons = null; - topBar.clearLeftButtons(); - } - - if (options.buttons.back.visible.isTrue() && !options.buttons.hasLeftButtons()) { - topBar.setBackButton(createButtonController(options.buttons.back)); - } + //should be at first in order for next actions to be animated if (options.animateRightButtons.hasValue()) - topBar.animateRightButtons(options.animateRightButtons.isTrue()); + topBarController.animateRightButtons(options.animateRightButtons.isTrue()); if (options.animateLeftButtons.hasValue()) - topBar.animateLeftButtons(options.animateLeftButtons.isTrue()); - topBar.setOverflowButtonColor(options.rightButtonColor.get(Color.BLACK)); - } + topBarController.animateLeftButtons(options.animateLeftButtons.isTrue()); - private List getOrCreateButtonControllersByInstanceId(@Nullable Map currentButtons, @Nullable List buttons) { - if (buttons == null) return null; - Map result = new LinkedHashMap<>(); - forEach(buttons, b -> result.put(b.instanceId, getOrDefault(currentButtons, b.instanceId, () -> createButtonController(b)))); - return new ArrayList<>(result.values()); - } + applyRightButtonsOptions(options, child); - private List getOrCreateButtonControllers(@Nullable Map currentButtons, @NonNull List buttons) { - ArrayList result = new ArrayList<>(); - for (ButtonOptions b : buttons) { - result.add(take(first(perform(currentButtons, null, Map::values), button -> button.getButton().equals(b)), createButtonController(b))); + applyLeftButtonsOptions(options, child); + + if (options.buttons.back.visible.isTrue() && !options.buttons.hasLeftButtons()) { + topBarController.setBackButton(createButtonController(options.buttons.back)); } - return result; + + topBar.setOverflowButtonColor(options.rightButtonColor.get(Color.BLACK)); } private ButtonController createButtonController(ButtonOptions button) { @@ -419,8 +366,13 @@ private void mergeOrientation(OrientationOptions orientationOptions) { } private void mergeButtons(TopBarOptions options, TopBarOptions optionsToMerge, View child, StackController stack) { - mergeRightButtons(options, optionsToMerge.buttons, child); - mergeLeftButton(options, optionsToMerge.buttons, child); + if (optionsToMerge.animateRightButtons.hasValue()) + topBarController.animateRightButtons(optionsToMerge.animateRightButtons.isTrue()); + if (optionsToMerge.animateLeftButtons.hasValue()) + topBarController.animateLeftButtons(optionsToMerge.animateLeftButtons.isTrue()); + + mergeRightButtonsOptions(options, optionsToMerge.buttons, child); + mergeLeftButtonsOptions(options, optionsToMerge.buttons, child); mergeLeftButtonsColor(child, optionsToMerge.leftButtonColor, optionsToMerge.leftButtonDisabledColor); mergeRightButtonsColor(child, optionsToMerge.rightButtonColor, optionsToMerge.rightButtonDisabledColor); mergeBackButton(optionsToMerge.buttons, stack); @@ -428,7 +380,7 @@ private void mergeButtons(TopBarOptions options, TopBarOptions optionsToMerge, V private void mergeLeftButtonsColor(View child, ThemeColour color, ThemeColour disabledColor) { if (color.hasValue() || disabledColor.hasValue()) { - Map stringButtonControllerMap = componentLeftButtons.get(child); + Map stringButtonControllerMap = leftButtonControllers.get(child); if (stringButtonControllerMap != null) { forEach(stringButtonControllerMap.values(), (btnController) -> { if (color.hasValue()) { @@ -444,7 +396,7 @@ private void mergeLeftButtonsColor(View child, ThemeColour color, ThemeColour di private void mergeRightButtonsColor(View child, ThemeColour color, ThemeColour disabledColor) { if (color.hasValue() || disabledColor.hasValue()) { - Map stringButtonControllerMap = componentRightButtons.get(child); + Map stringButtonControllerMap = rightButtonControllers.get(child); if (stringButtonControllerMap != null) { forEach(stringButtonControllerMap.values(), (btnController) -> { if (color.hasValue()) { @@ -458,40 +410,65 @@ private void mergeRightButtonsColor(View child, ThemeColour color, ThemeColour d } } - private void mergeRightButtons(TopBarOptions options, TopBarButtons buttons, View child) { - if (buttons.right == null) return; - List rightButtons = mergeButtonsWithColor(buttons.right, options.rightButtonColor, options.rightButtonDisabledColor); - List toMerge = getOrCreateButtonControllers(componentRightButtons.get(child), rightButtons); - List toRemove = difference(currentRightButtons, toMerge, ButtonController::areButtonsEqual); - forEach(toRemove, ButtonController::destroy); + private void applyLeftButtonsOptions(TopBarOptions options, ViewController child) { + if (options.buttons.left != null) { + List leftButtons = mergeButtonsWithColor(options.buttons.left, + options.leftButtonColor + , options.leftButtonDisabledColor); + final ViewGroup childView = child.getView(); + final Map btnControllers = getOrCreateButtonControllerMap(childView, leftButtonControllers); + topBarController.applyLeftButtonsOptions(btnControllers, leftButtons, this::createButtonController); + } else { + topBarController.clearLeftButtons(); + } + } - if (!CollectionUtils.equals(currentRightButtons, toMerge)) { - componentRightButtons.put(child, keyBy(toMerge, ButtonController::getButtonInstanceId)); - topBarController.mergeRightButtons(toMerge, toRemove); - currentRightButtons = toMerge; + private void applyRightButtonsOptions(TopBarOptions options, ViewController child) { + if (options.buttons.right != null) { + List rightButtons = mergeButtonsWithColor(options.buttons.right, + options.rightButtonColor + , options.rightButtonDisabledColor); + final ViewGroup childView = child.getView(); + final Map btnControllers = getOrCreateButtonControllerMap(childView, rightButtonControllers); + topBarController.applyRightButtonsOptions(btnControllers, rightButtons, this::createButtonController); + } else { + topBarController.clearRightButtons(); } + } + + private void mergeRightButtonsOptions(TopBarOptions options, TopBarButtons buttons, View child) { + if (buttons.right == null) return; + List rightButtons = mergeButtonsWithColor(buttons.right, options.rightButtonColor, + options.rightButtonDisabledColor); + final Map btnControllers = getOrCreateButtonControllerMap(child, rightButtonControllers); + topBarController.mergeRightButtonsOptions(btnControllers, rightButtons, this::createButtonController); if (options.rightButtonColor.hasValue()) topBar.setOverflowButtonColor(options.rightButtonColor.get()); } - private void mergeLeftButton(TopBarOptions options, TopBarButtons buttons, View child) { + private void mergeLeftButtonsOptions(TopBarOptions options, TopBarButtons buttons, View child) { if (buttons.left == null) return; - List leftButtons = mergeButtonsWithColor(buttons.left, options.leftButtonColor, options.leftButtonDisabledColor); - List toMerge = getOrCreateButtonControllers(componentLeftButtons.get(child), leftButtons); - List toRemove = difference(currentLeftButtons, toMerge, ButtonController::areButtonsEqual); - forEach(toRemove, ButtonController::destroy); - if (!CollectionUtils.equals(currentLeftButtons, toMerge)) { - componentLeftButtons.put(child, keyBy(toMerge, ButtonController::getButtonInstanceId)); - topBarController.mergeLeftButtons(toMerge, toRemove); - currentLeftButtons = toMerge; - } + List leftButtons = mergeButtonsWithColor(buttons.left, options.leftButtonColor, + options.leftButtonDisabledColor); + final Map btnControllers = getOrCreateButtonControllerMap(child, leftButtonControllers); + topBarController.mergeLeftButtonsOptions(btnControllers, leftButtons, this::createButtonController); + if (options.leftButtonColor.hasValue()) topBar.setOverflowButtonColor(options.leftButtonColor.get()); + } + + @NonNull + private Map getOrCreateButtonControllerMap(View child, Map> buttonControllers) { + final Map controllerMap = buttonControllers.get(child); + final Map btnControllers = controllerMap != null ? controllerMap : new HashMap<>(); + if (controllerMap == null) + buttonControllers.put(child, btnControllers); + return btnControllers; } private void mergeBackButton(TopBarButtons buttons, StackController stack) { if (buttons.back.hasValue() && isNullOrEmpty(buttons.left)) { if (buttons.back.visible.isFalse()) { - topBar.clearBackButton(); + topBarController.clearBackButton(); } else if (stack.size() > 1) { - topBar.setBackButton(createButtonController(buttons.back)); + topBarController.setBackButton(createButtonController(buttons.back)); } } } @@ -517,13 +494,10 @@ private void mergeTopBarOptions(TopBarOptions resolveOptions, Options options, S if (topBarOptions.topMargin.hasValue() && topBar.getLayoutParams() instanceof MarginLayoutParams) { ((MarginLayoutParams) topBar.getLayoutParams()).topMargin = UiUtils.dpToPx(activity, topBarOptions.topMargin.get()); } - mergeStatusBarDrawBehindOptions(resolveOptions,options); + mergeStatusBarDrawBehindOptions(resolveOptions, options); if (topBarOptions.title.height.hasValue()) topBar.setTitleHeight(topBarOptions.title.height.get()); if (topBarOptions.title.topMargin.hasValue()) topBar.setTitleTopMargin(topBarOptions.title.topMargin.get()); - if (topBarOptions.animateLeftButtons.hasValue()) - topBar.animateLeftButtons(topBarOptions.animateLeftButtons.isTrue()); - if (topBarOptions.animateRightButtons.hasValue()) - topBar.animateRightButtons(topBarOptions.animateRightButtons.isTrue()); + if (topBarOptions.title.component.hasValue()) { TitleBarReactViewController controller = findTitleComponent(topBarOptions.title.component); if (controller == null) { @@ -646,8 +620,8 @@ public List getComponentButtons(View child, List componentLeftButtons.get(child).put(key, leftController)); - forEach(componentRightButtons.get(child).keySet(), (key) -> componentRightButtons.get(child).put(key, rightController)); + forEach(leftButtonControllers.get(child).keySet(), (key) -> leftButtonControllers.get(child).put(key, leftController)); + forEach(rightButtonControllers.get(child).keySet(), (key) -> rightButtonControllers.get(child).put(key, rightController)); } @@ -657,11 +631,11 @@ public void applyTopInsets(StackController stack, ViewController child) { } private List getRightButtons(View child) { - return componentRightButtons.containsKey(child) ? new ArrayList<>(componentRightButtons.get(child).values()) : null; + return rightButtonControllers.containsKey(child) ? new ArrayList<>(rightButtonControllers.get(child).values()) : null; } private List getLeftButtons(View child) { - return componentLeftButtons.containsKey(child) ? new ArrayList<>(componentLeftButtons.get(child).values()) : null; + return leftButtonControllers.containsKey(child) ? new ArrayList<>(leftButtonControllers.get(child).values()) : null; } private void applyStatusBarInsets(StackController stack, ViewController child) { diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/TopBarController.kt b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/TopBarController.kt index f0ac09d20b7..5fa4048a61d 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/TopBarController.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/TopBarController.kt @@ -2,13 +2,16 @@ package com.reactnativenavigation.viewcontrollers.stack.topbar import android.animation.Animator import android.content.Context +import android.graphics.Color import android.view.MenuItem import android.view.View +import androidx.transition.AutoTransition +import androidx.transition.TransitionManager import androidx.viewpager.widget.ViewPager import com.reactnativenavigation.options.Alignment import com.reactnativenavigation.options.AnimationOptions +import com.reactnativenavigation.options.ButtonOptions import com.reactnativenavigation.options.Options -import com.reactnativenavigation.utils.CollectionUtils.forEachIndexed import com.reactnativenavigation.utils.ViewUtils import com.reactnativenavigation.utils.resetViewProperties import com.reactnativenavigation.viewcontrollers.stack.topbar.button.ButtonController @@ -17,12 +20,13 @@ import com.reactnativenavigation.views.stack.StackLayout import com.reactnativenavigation.views.stack.topbar.TopBar import com.reactnativenavigation.views.stack.topbar.titlebar.ButtonBar +const val DEFAULT_BORDER_COLOR = Color.BLACK open class TopBarController(private val animator: TopBarAnimator = TopBarAnimator()) { lateinit var view: TopBar private lateinit var leftButtonBar: ButtonBar private lateinit var rightButtonBar: ButtonBar - + private val buttonsTransition = AutoTransition() val height: Int get() = view.height @@ -54,26 +58,26 @@ open class TopBarController(private val animator: TopBarAnimator = TopBarAnimato fun getPushAnimation(appearingOptions: Options, additionalDy: Float = 0f): Animator? { if (appearingOptions.topBar.animate.isFalse) return null return animator.getPushAnimation( - appearingOptions.animations.push.topBar, - appearingOptions.topBar.visible, - additionalDy + appearingOptions.animations.push.topBar, + appearingOptions.topBar.visible, + additionalDy ) } fun getPopAnimation(appearingOptions: Options, disappearingOptions: Options): Animator? { if (appearingOptions.topBar.animate.isFalse) return null return animator.getPopAnimation( - disappearingOptions.animations.pop.topBar, - appearingOptions.topBar.visible + disappearingOptions.animations.pop.topBar, + appearingOptions.topBar.visible ) } fun getSetStackRootAnimation(appearingOptions: Options, additionalDy: Float = 0f): Animator? { if (appearingOptions.topBar.animate.isFalse) return null return animator.getSetStackRootAnimation( - appearingOptions.animations.setStackRoot.topBar, - appearingOptions.topBar.visible, - additionalDy + appearingOptions.animations.setStackRoot.topBar, + appearingOptions.topBar.visible, + additionalDy ) } @@ -105,25 +109,182 @@ open class TopBarController(private val animator: TopBarAnimator = TopBarAnimato view.alignTitleComponent(alignment) } - fun applyRightButtons(toAdd: List) { + fun clearRightButtons() { view.clearRightButtons() - toAdd.reversed().forEachIndexed { i, b -> b.addToMenu(rightButtonBar, i * 10) } } - fun mergeRightButtons(toAdd: List, toRemove: List) { - toRemove.forEach { view.removeRightButton(it) } - toAdd.reversed().forEachIndexed { i, b -> b.addToMenu(rightButtonBar, i * 10) } + fun clearLeftButtons() { + view.clearLeftButtons() } - open fun applyLeftButtons(toAdd: List) { + fun clearBackButton() { view.clearBackButton() - view.clearLeftButtons() - forEachIndexed(toAdd) { b: ButtonController, i: Int -> b.addToMenu(leftButtonBar, i * 10) } } - open fun mergeLeftButtons(toAdd: List, toRemove: List) { - view.clearBackButton() - toRemove.forEach { view.removeLeftButton(it) } - forEachIndexed(toAdd) { b: ButtonController, i: Int -> b.addToMenu(leftButtonBar, i * 10) } + fun setBackButton(backButton: ButtonController?) { + backButton?.let { view.setBackButton(it) } + } + + fun animateRightButtons(shouldAnimate: Boolean) { + view.animateRightButtons(shouldAnimate) + } + + fun animateLeftButtons(shouldAnimate: Boolean) { + view.animateLeftButtons(shouldAnimate) + } + + fun mergeRightButtonsOptions( + btnControllers: MutableMap, + rightButtons: List, + controllerCreator: (ButtonOptions) -> ButtonController + ) { + mergeButtonOptions(btnControllers, rightButtons.reversed(), controllerCreator, rightButtonBar) + } + + fun mergeLeftButtonsOptions( + btnControllers: MutableMap, + leftButtons: List, + controllerCreator: (ButtonOptions) -> ButtonController + ) { + clearBackButton() + mergeButtonOptions(btnControllers, leftButtons, controllerCreator, leftButtonBar) + } + + fun applyRightButtonsOptions( + btnControllers: MutableMap, + rightButtons: List, + controllerCreator: (ButtonOptions) -> ButtonController + ) { + applyButtonsOptions( + btnControllers, + rightButtons.reversed(), + controllerCreator, + rightButtonBar + ) + } + + fun applyLeftButtonsOptions( + btnControllers: MutableMap, + leftButtons: List, + controllerCreator: (ButtonOptions) -> ButtonController + ) { + applyButtonsOptions(btnControllers, leftButtons, controllerCreator, leftButtonBar) + } + + private fun applyButtonsOptions( + btnControllers: MutableMap, + buttons: List, + controllerCreator: (ButtonOptions) -> ButtonController, + buttonBar: ButtonBar + ) { + if (buttonBar.shouldAnimate) + TransitionManager.beginDelayedTransition(buttonBar, buttonsTransition) + + buttonBar.clearButtons() + buttons.forEachIndexed { index, it -> + val order = index * 10 + val newController = if (btnControllers.containsKey(it.id)) { + btnControllers.remove(it.id) + } else { + controllerCreator(it) + }!! + + newController.addToMenu(buttonBar, order) + btnControllers[it.id] = newController + } + } + + + private fun mergeButtonOptions( + btnControllers: MutableMap, + buttons: List, + controllerCreator: (ButtonOptions) -> ButtonController, + buttonBar: ButtonBar + ) { + fun hasChangedOrder(): Boolean { + val values = btnControllers.values + return buttons.filterIndexed { index, buttonOptions -> + val buttonController = btnControllers[buttonOptions.id] + values.indexOf(buttonController) == index + }.size != buttons.size + } + + fun sameIdDifferentCompId( + toUpdate: MutableMap, + ctrl: Map.Entry, + buttons: List + ) = if (toUpdate.containsKey(ctrl.key) + && ctrl.value.button.hasComponent() + && buttons[toUpdate[ctrl.key]!!].component.componentId != ctrl.value.button.component.componentId + ) { + toUpdate.remove(ctrl.key) + true + } else false + + val requestedButtons = buttons.mapIndexed { index, buttonOptions -> buttonOptions.id to index }.toMap() + var toUpdate = requestedButtons.filter { + btnControllers[it.key]?.areButtonOptionsChanged(buttons[it.value]) ?: false + }.toMutableMap() + var toAdd = requestedButtons.filter { !btnControllers.containsKey(it.key) } + var toRemove = btnControllers.filter { ctrl -> !requestedButtons.containsKey(ctrl.key) } + val toDestroy = btnControllers.filter { ctrl -> sameIdDifferentCompId(toUpdate, ctrl, buttons) } + .toMutableMap().apply { this.putAll(toRemove) } + + fun needsRebuild(): Boolean { + return if (toUpdate.size == buttons.size) { + hasChangedOrder() + } else toAdd.isNotEmpty() || toRemove.isNotEmpty() + } + + if (needsRebuild()) { + toUpdate = mutableMapOf() + toAdd = requestedButtons + toRemove = btnControllers.toMap() + if (buttonBar.shouldAnimate) + TransitionManager.beginDelayedTransition(buttonBar, buttonsTransition) + } + + toUpdate.forEach { + val button = buttons[it.value] + btnControllers[button.id]?.mergeButtonOptions(button, buttonBar) + } + toRemove.forEach { + buttonBar.removeButton(it.value.buttonIntId) + btnControllers.remove(it.key) + } + toDestroy.values.forEach { + it.destroy() + } + toAdd.forEach { + val button = buttons[it.value] + val order = it.value * 10 + val newController = btnControllers[button.id] ?: controllerCreator(button) + newController.addToMenu(buttonBar, order) + btnControllers[button.id] = newController + } + } + + + fun onConfigurationChanged( + options: Options, + leftBtnControllers: MutableMap?, + rightBtnControllers: MutableMap? + ) { + leftBtnControllers?.values?.forEach { + it.onConfigurationChanged(leftButtonBar) + } + rightBtnControllers?.values?.forEach { + it.onConfigurationChanged(rightButtonBar) + } + + view.setOverflowButtonColor(options.topBar.rightButtonColor.get(Color.BLACK)!!) + view.applyTopTabsColors( + options.topTabs.selectedTabColor, + options.topTabs.unselectedTabColor + ) + view.setBorderColor(options.topBar.borderColor.get(DEFAULT_BORDER_COLOR)!!) + view.setBackgroundColor(options.topBar.background.color.get(Color.WHITE)!!) + view.setTitleTextColor(options.topBar.title.color.get(TopBar.DEFAULT_TITLE_COLOR)!!) + view.setSubtitleColor(options.topBar.subtitle.color.get(TopBar.DEFAULT_TITLE_COLOR)!!) } } \ No newline at end of file diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/button/ButtonController.kt b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/button/ButtonController.kt index b18cb11b987..22ce11f031e 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/button/ButtonController.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/button/ButtonController.kt @@ -71,6 +71,10 @@ open class ButtonController(activity: Activity, return if (other.id != id) false else button.equals(other.button) } + fun areButtonOptionsChanged(otherOptions:ButtonOptions):Boolean{ + return otherOptions.id == id && !button.equals(otherOptions) + } + fun applyNavigationIcon(toolbar: Toolbar) { presenter.applyNavigationIcon(toolbar) { onPressListener.onPress(it) @@ -83,13 +87,30 @@ open class ButtonController(activity: Activity, fun addToMenu(buttonBar: ButtonBar, order: Int) { if (button.component.hasValue() && buttonBar.containsButton(menuItem, order)) return - buttonBar.menu.removeItem(button.intId) - menuItem = buttonBar.addButton(Menu.NONE, + buttonBar.menu.removeItem(button.intId) + menuItem = buttonBar.addButton(Menu.NONE, button.intId, order, presenter.styledText)?.also { menuItem -> - menuItem.setOnMenuItemClickListener(this@ButtonController) - presenter.applyOptions(buttonBar, menuItem, this@ButtonController::getView) + menuItem.setOnMenuItemClickListener(this@ButtonController) + presenter.applyOptions(buttonBar, menuItem, this@ButtonController::getView) + } + } + + fun mergeButtonOptions(optionsToMerge: ButtonOptions,buttonBar: ButtonBar) { + button.mergeWith(optionsToMerge) + presenter.button = this.button + buttonBar.getButtonById(button.intId)?.let { + menuItem-> + presenter.applyOptions(buttonBar,menuItem,this::getView) } } + + fun onConfigurationChanged(buttonBar: ButtonBar) { + buttonBar.getButtonById(button.intId)?.let { + menuItem-> + presenter.applyOptions(buttonBar,menuItem,this::getView) + } + } + } \ No newline at end of file diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/button/ButtonPresenter.kt b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/button/ButtonPresenter.kt index e1520a4cceb..5be22e2047e 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/button/ButtonPresenter.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/button/ButtonPresenter.kt @@ -25,7 +25,8 @@ import com.reactnativenavigation.utils.ViewUtils import com.reactnativenavigation.views.stack.topbar.titlebar.IconBackgroundDrawable import kotlin.math.max -open class ButtonPresenter(private val context: Context, private val button: ButtonOptions, private val iconResolver: IconResolver) { +open class ButtonPresenter(private val context: Context,var button: ButtonOptions, private val iconResolver: +IconResolver) { companion object { const val DISABLED_COLOR = Color.LTGRAY } @@ -47,6 +48,8 @@ open class ButtonPresenter(private val context: Context, private val button: But applyComponent(menuItem, viewCreator) applyAccessibilityLabel(menuItem) applyIcon(menuItem) + applyText(menuItem) + applyOptionsDirectlyOnView(toolbar, menuItem) { applyTestId(it) @@ -55,6 +58,11 @@ open class ButtonPresenter(private val context: Context, private val button: But } } + private fun applyText(menuItem: MenuItem) { + if (button.text.hasValue()) + menuItem.title = button.text.get() + } + fun applyColor(toolbar: Toolbar, menuItem: MenuItem, color: ThemeColour) { button.color = color applyIcon(menuItem) diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/views/stack/topbar/titlebar/ButtonBar.kt b/lib/android/app/src/main/java/com/reactnativenavigation/views/stack/topbar/titlebar/ButtonBar.kt index c17afc67757..32c8cd23486 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/views/stack/topbar/titlebar/ButtonBar.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/views/stack/topbar/titlebar/ButtonBar.kt @@ -4,17 +4,15 @@ import android.content.Context import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter import android.text.SpannableString -import android.transition.AutoTransition -import android.transition.TransitionManager import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.ActionMenuView import androidx.appcompat.widget.Toolbar +import androidx.core.view.children import com.reactnativenavigation.utils.ObjectUtils import com.reactnativenavigation.utils.ViewUtils import com.reactnativenavigation.viewcontrollers.stack.topbar.button.ButtonController - open class ButtonBar internal constructor(context: Context) : Toolbar(context) { var shouldAnimate: Boolean=false @@ -43,23 +41,17 @@ open class ButtonBar internal constructor(context: Context) : Toolbar(context) { get() = menu.size() fun addButton(menuItem: Int, intId: Int, order: Int, styledText: SpannableString): MenuItem? { - if(shouldAnimate) - TransitionManager.beginDelayedTransition(this,AutoTransition()) return this.menu?.add(menuItem, - intId, - order, - styledText) + intId, + order, + styledText) } fun removeButton(buttonId: Int) { - if(shouldAnimate) - TransitionManager.beginDelayedTransition(this,AutoTransition()) menu.removeItem(buttonId) } open fun clearButtons() { - if(shouldAnimate) - TransitionManager.beginDelayedTransition(this,AutoTransition()) clearBackButton() if (menu.size() > 0) menu.clear() } @@ -68,6 +60,9 @@ open class ButtonBar internal constructor(context: Context) : Toolbar(context) { return menu.getItem(index) } + fun getButtonById(id: Int): MenuItem? { + return menu.children.firstOrNull { it.itemId == id } + } fun containsButton(menuItem: MenuItem?, order: Int): Boolean { return menuItem != null && menu.findItem(menuItem.itemId) != null && menuItem.order == order } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/views/stack/topbar/titlebar/TitleAndButtonsContainer.kt b/lib/android/app/src/main/java/com/reactnativenavigation/views/stack/topbar/titlebar/TitleAndButtonsContainer.kt index 196e580b5af..52d687e853e 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/views/stack/topbar/titlebar/TitleAndButtonsContainer.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/views/stack/topbar/titlebar/TitleAndButtonsContainer.kt @@ -76,8 +76,6 @@ class TitleAndButtonsContainer(context: Context) : ViewGroup(context) { fun setSubTitleTextAlignment(alignment: Alignment) = titleSubTitleBar.setSubTitleAlignment(alignment) - fun setTitleTextAlignment(alignment: Alignment) = titleSubTitleBar.setTitleAlignment(alignment) - fun setBackgroundColor(color: ThemeColour) = if (color.hasValue()) setBackgroundColor(color.get()) else Unit fun setTitleFontSize(size: Float) = titleSubTitleBar.setTitleFontSize(size) diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/views/stack/topbar/titlebar/TitleSubTitleLayout.kt b/lib/android/app/src/main/java/com/reactnativenavigation/views/stack/topbar/titlebar/TitleSubTitleLayout.kt index 62c46250f64..3af4d77b903 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/views/stack/topbar/titlebar/TitleSubTitleLayout.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/views/stack/topbar/titlebar/TitleSubTitleLayout.kt @@ -38,13 +38,6 @@ class TitleSubTitleLayout(context: Context) : LinearLayout(context) { } } - fun setTitleAlignment(alignment: Alignment) { - if (alignment == Alignment.Center) { - (this.titleTextView.layoutParams as LayoutParams).gravity = Gravity.CENTER - } else { - (this.titleTextView.layoutParams as LayoutParams).gravity = Gravity.START or Gravity.CENTER_VERTICAL - } - } fun setTitleFontSize(size: Float) = titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, size) diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/utils/TitleAndButtonsMeasurer.kt b/lib/android/app/src/test/java/com/reactnativenavigation/utils/TitleAndButtonsMeasurerTest.kt similarity index 99% rename from lib/android/app/src/test/java/com/reactnativenavigation/utils/TitleAndButtonsMeasurer.kt rename to lib/android/app/src/test/java/com/reactnativenavigation/utils/TitleAndButtonsMeasurerTest.kt index d8509ee5089..3908a776979 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/utils/TitleAndButtonsMeasurer.kt +++ b/lib/android/app/src/test/java/com/reactnativenavigation/utils/TitleAndButtonsMeasurerTest.kt @@ -2,13 +2,13 @@ package com.reactnativenavigation.utils import com.reactnativenavigation.BaseTest import com.reactnativenavigation.views.stack.topbar.titlebar.DEFAULT_LEFT_MARGIN_PX +import com.reactnativenavigation.views.stack.topbar.titlebar.resolveHorizontalTitleBoundsLimit import com.reactnativenavigation.views.stack.topbar.titlebar.resolveLeftButtonsBounds import com.reactnativenavigation.views.stack.topbar.titlebar.resolveRightButtonsBounds -import com.reactnativenavigation.views.stack.topbar.titlebar.resolveHorizontalTitleBoundsLimit import org.junit.Test import kotlin.test.assertEquals -class TitleAndButtonsMeasurer : BaseTest() { +class TitleAndButtonsMeasurerTest : BaseTest() { private val parentWidth = 1080 @Test diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenterTest.kt b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenterTest.kt index 779d9b3b621..333b3cd2777 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenterTest.kt +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenterTest.kt @@ -17,7 +17,10 @@ import com.reactnativenavigation.options.params.* import com.reactnativenavigation.options.params.Number import com.reactnativenavigation.options.parsers.TypefaceLoader import com.reactnativenavigation.react.CommandListenerAdapter -import com.reactnativenavigation.utils.* +import com.reactnativenavigation.utils.CollectionUtils +import com.reactnativenavigation.utils.RenderChecker +import com.reactnativenavigation.utils.TitleBarHelper +import com.reactnativenavigation.utils.UiUtils import com.reactnativenavigation.viewcontrollers.child.ChildControllersRegistry import com.reactnativenavigation.viewcontrollers.stack.topbar.TopBarController import com.reactnativenavigation.viewcontrollers.stack.topbar.button.ButtonController @@ -99,6 +102,7 @@ class StackPresenterTest : BaseTest() { @Test fun onConfigurationChange_shouldApplyColors() { + parent.setRoot(listOf(child), CommandListenerAdapter()) val options = Options.EMPTY.copy() options.topBar.borderColor = ThemeColour.of(Color.BLACK, Color.RED) options.topBar.background = TopBarBackgroundOptions().apply { @@ -122,7 +126,7 @@ class StackPresenterTest : BaseTest() { options.topTabs.unselectedTabColor = ThemeColour.of(Color.BLACK, Color.RED) mockConfiguration.uiMode = Configuration.UI_MODE_NIGHT_NO - uut.onConfigurationChanged(options) + uut.onConfigurationChanged(options, getCurrentChild()) verify(topBar).setTitleTextColor(Color.BLACK) verify(topBar).setSubtitleColor(Color.BLACK) @@ -133,7 +137,7 @@ class StackPresenterTest : BaseTest() { verify(topBar).setBackButton(any()) mockConfiguration.uiMode = Configuration.UI_MODE_NIGHT_YES - uut.onConfigurationChanged(options) + uut.onConfigurationChanged(options, getCurrentChild()) verify(topBar).setTitleTextColor(Color.RED) verify(topBar).setSubtitleColor(Color.RED) @@ -147,15 +151,18 @@ class StackPresenterTest : BaseTest() { @Test fun onConfigurationChange_shouldApplyColorsOnTopBarButtons() { + parent.setRoot(listOf(child), CommandListenerAdapter()) + val options = Options.EMPTY.copy() options.topBar.buttons.left = arrayListOf(ButtonOptions()) options.topBar.buttons.right = arrayListOf(ButtonOptions()) - uut.applyChildOptions(options,parent,child) - uut.onConfigurationChanged(options) + uut.applyChildOptions(options,parent,child) + verify(topBarController, times(1)).applyRightButtonsOptions(any(),any(),any()) + verify(topBarController, times(1)).applyLeftButtonsOptions(any(),any(),any()) - verify(topBarController, times(2)).applyRightButtons(any()) - verify(topBarController, times(2)).applyLeftButtons(any()) + uut.onConfigurationChanged(options, getCurrentChild()) + verify(topBarController, times(1)).onConfigurationChanged(any(), any(), any()) } @Test @@ -252,19 +259,19 @@ class StackPresenterTest : BaseTest() { @Test fun mergeButtons() { uut.mergeChildOptions(EMPTY_OPTIONS, EMPTY_OPTIONS, parent, child) - verify(topBarController, never()).applyRightButtons(any()) - verify(topBarController, never()).applyLeftButtons(any()) + verify(topBarController, never()).mergeLeftButtonsOptions(any(),any(),any()) + verify(topBarController, never()).mergeRightButtonsOptions(any(),any(),any()) val options = Options() val button = ButtonOptions() button.text = Text("btn") options.topBar.buttons.right = ArrayList(setOf(button)) uut.mergeChildOptions(options, EMPTY_OPTIONS, parent, child) - verify(topBarController).mergeRightButtons(any(), any()) + verify(topBarController).mergeRightButtonsOptions(any(), any(),any()) options.topBar.buttons.left = ArrayList(setOf(button)) uut.mergeChildOptions(options, EMPTY_OPTIONS, parent, child) - verify(topBarController).mergeLeftButtons(any(), any()) + verify(topBarController).mergeLeftButtonsOptions(any(), any(), any()) } @Test @@ -313,8 +320,8 @@ class StackPresenterTest : BaseTest() { toApply.topBar.buttons.right = arrayListOf(textBtn1, componentBtn1) uut.applyChildOptions(toApply, parent, child) - val captor1 = argumentCaptor>() - verify(topBarController).applyRightButtons(captor1.capture()) + val captor1 = argumentCaptor>() + verify(topBarController).applyRightButtonsOptions(any(),captor1.capture(), any()) assertThat(topBar.rightButtonBar.menu.size()).isEqualTo(2) val appliedButtons = captor1.firstValue @@ -325,12 +332,12 @@ class StackPresenterTest : BaseTest() { uut.mergeChildOptions(toMerge, Options.EMPTY, parent, child) assertThat(topBar.rightButtonBar.menu.size()).isEqualTo(3) - val captor2 = argumentCaptor>() - verify(topBarController).mergeRightButtons(captor2.capture(), any()) + val captor2 = argumentCaptor>() + verify(topBarController).mergeRightButtonsOptions(any(),captor2.capture(), any()) val mergedButtons = captor2.firstValue assertThat(mergedButtons).hasSize(3) - assertThat(appliedButtons[0]).isNotEqualTo(mergedButtons[0]) - assertThat(appliedButtons[1]).isEqualTo(mergedButtons[2]) + assertThat(appliedButtons[0].id).isNotEqualTo(mergedButtons[1].id) + assertThat(appliedButtons[1].id).isEqualTo(mergedButtons[2].id) } @Test @@ -357,15 +364,14 @@ class StackPresenterTest : BaseTest() { options.topBar.buttons.left = ArrayList(listOf(textBtn2)) uut.applyChildOptions(options, parent, child) ShadowLooper.idleMainLooper() - verify(topBar, times(1)).clearLeftButtons() - verify(topBar, times(1)).clearBackButton() + verify(topBarController, times(1)).applyLeftButtonsOptions(any(), any(), any()) + verify(topBar, never()).setBackButton(any()) val backButtonHidden = Options() backButtonHidden.topBar.buttons.back.setHidden() uut.mergeChildOptions(backButtonHidden, options, parent, child) ShadowLooper.idleMainLooper() - verify(topBar, times(1)).clearLeftButtons() - verify(topBar, times(2)).clearBackButton() + verify(topBar, times(1)).clearBackButton() } @Test @@ -400,7 +406,7 @@ class StackPresenterTest : BaseTest() { assertThat(toMerge.topBar.buttons.back.hasValue()).isTrue() uut.mergeChildOptions(toMerge, Options.EMPTY, parent, child) - verify(topBarController).mergeLeftButtons(any(), any()) + verify(topBarController).mergeLeftButtonsOptions(any(), any(),any()) verify(topBar, never()).clearLeftButtons() } @@ -662,15 +668,15 @@ class StackPresenterTest : BaseTest() { options.topBar.buttons.left = ArrayList() options.topBar.buttons.left!!.add(leftButton) uut.applyChildOptions(options, parent, child) - val rightCaptor = argumentCaptor>() - verify(topBarController).applyRightButtons(rightCaptor.capture()) - assertThat(rightCaptor.firstValue[0].button.color.get()).isEqualTo(options.topBar.rightButtonColor.get()) - assertThat(rightCaptor.firstValue[1].button.color.get()).isEqualTo(options.topBar.rightButtonColor.get()) + val rightCaptor = argumentCaptor>() + verify(topBarController).applyRightButtonsOptions(any(),rightCaptor.capture(), any()) + assertThat(rightCaptor.firstValue[0].color.get()).isEqualTo(options.topBar.rightButtonColor.get()) + assertThat(rightCaptor.firstValue[1].color.get()).isEqualTo(options.topBar.rightButtonColor.get()) assertThat(rightCaptor.firstValue[0]).isNotEqualTo(rightButton1) assertThat(rightCaptor.firstValue[1]).isNotEqualTo(rightButton2) - val leftCaptor = argumentCaptor>() - verify(topBarController).applyLeftButtons(leftCaptor.capture()) - assertThat(leftCaptor.firstValue[0].button.color).isEqualTo(options.topBar.leftButtonColor) + val leftCaptor = argumentCaptor>() + verify(topBarController).applyLeftButtonsOptions(any(),leftCaptor.capture(),any()) + assertThat(leftCaptor.firstValue[0].color).isEqualTo(options.topBar.leftButtonColor) assertThat(leftCaptor.firstValue[0]).isNotEqualTo(leftButton) } @@ -790,15 +796,15 @@ class StackPresenterTest : BaseTest() { options2.topBar.buttons.left = ArrayList(listOf(leftButton)) uut.mergeChildOptions(options2, appliedOptions, parent, child) - val rightCaptor = argumentCaptor>() - verify(topBarController).mergeRightButtons(rightCaptor.capture(), any()) - assertThat(rightCaptor.firstValue[0].button.color.get()).isEqualTo(appliedOptions.topBar.rightButtonColor.get()) - assertThat(rightCaptor.firstValue[1].button.color.get()).isEqualTo(appliedOptions.topBar.rightButtonColor.get()) + val rightCaptor = argumentCaptor>() + verify(topBarController).mergeRightButtonsOptions(any(),rightCaptor.capture(), any()) + assertThat(rightCaptor.firstValue[0].color.get()).isEqualTo(appliedOptions.topBar.rightButtonColor.get()) + assertThat(rightCaptor.firstValue[1].color.get()).isEqualTo(appliedOptions.topBar.rightButtonColor.get()) assertThat(rightCaptor.firstValue[0]).isNotEqualTo(rightButton1) assertThat(rightCaptor.firstValue[1]).isNotEqualTo(rightButton2) - val leftCaptor = argumentCaptor>() - verify(topBarController).mergeLeftButtons(leftCaptor.capture(), any()) - assertThat(leftCaptor.firstValue[0].button.color.get()).isEqualTo(appliedOptions.topBar.leftButtonColor.get()) + val leftCaptor = argumentCaptor>() + verify(topBarController).mergeLeftButtonsOptions(any(),leftCaptor.capture(), any()) + assertThat(leftCaptor.firstValue[0].color.get()).isEqualTo(appliedOptions.topBar.leftButtonColor.get()) assertThat(leftCaptor.firstValue[0]).isNotEqualTo(leftButton) } @@ -816,15 +822,15 @@ class StackPresenterTest : BaseTest() { options2.topBar.buttons.left = ArrayList(listOf(leftButton)) uut.mergeChildOptions(options2, resolvedOptions, parent, child) - val rightCaptor = argumentCaptor>() - verify(topBarController).mergeRightButtons(rightCaptor.capture(), any()) - assertThat(rightCaptor.firstValue[0].button.color.get()).isEqualTo(resolvedOptions.topBar.rightButtonColor.get()) - assertThat(rightCaptor.firstValue[1].button.color.get()).isEqualTo(resolvedOptions.topBar.rightButtonColor.get()) + val rightCaptor = argumentCaptor>() + verify(topBarController).mergeRightButtonsOptions(any(),rightCaptor.capture(), any()) + assertThat(rightCaptor.firstValue[0].color.get()).isEqualTo(resolvedOptions.topBar.rightButtonColor.get()) + assertThat(rightCaptor.firstValue[1].color.get()).isEqualTo(resolvedOptions.topBar.rightButtonColor.get()) assertThat(rightCaptor.firstValue[0]).isNotEqualTo(rightButton1) assertThat(rightCaptor.firstValue[1]).isNotEqualTo(rightButton2) - val leftCaptor = argumentCaptor>() - verify(topBarController).mergeLeftButtons(leftCaptor.capture(), any()) - assertThat(leftCaptor.firstValue[0].button.color.get()).isEqualTo(resolvedOptions.topBar.leftButtonColor.get()) + val leftCaptor = argumentCaptor>() + verify(topBarController).mergeLeftButtonsOptions(any(),leftCaptor.capture(), any()) + assertThat(leftCaptor.firstValue[0].color.get()).isEqualTo(resolvedOptions.topBar.leftButtonColor.get()) assertThat(leftCaptor.firstValue[0]).isNotEqualTo(leftButton) } @@ -834,10 +840,10 @@ class StackPresenterTest : BaseTest() { options.topBar.buttons.right = ArrayList(listOf(textBtn1)) options.topBar.buttons.left = ArrayList(listOf(textBtn1)) uut.applyChildOptions(options, parent, child) - val rightCaptor = argumentCaptor>() - val leftCaptor = argumentCaptor>() - verify(topBarController).applyRightButtons(rightCaptor.capture()) - verify(topBarController).applyLeftButtons(leftCaptor.capture()) + val rightCaptor = argumentCaptor>() + val leftCaptor = argumentCaptor>() + verify(topBarController).applyRightButtonsOptions(any(),rightCaptor.capture(),any()) + verify(topBarController).applyLeftButtonsOptions(any(),leftCaptor.capture(),any()) assertThat(rightCaptor.firstValue.size).isOne() assertThat(leftCaptor.firstValue.size).isOne() } @@ -1022,6 +1028,7 @@ class StackPresenterTest : BaseTest() { assertThat((topBar.layoutParams as ViewGroup.MarginLayoutParams).topMargin).isEqualTo(10) } + private fun getCurrentChild()=parent.currentChild private fun assertTopBarOptions(options: Options, t: Int) { if (options.topBar.title.component.hasValue()) { verify(topBar, never()).title = any() diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/TopBarControllerTest.kt b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/TopBarControllerTest.kt index fc0baeb6fa4..00c337608fd 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/TopBarControllerTest.kt +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/TopBarControllerTest.kt @@ -6,24 +6,26 @@ import android.content.Context import android.view.View import com.nhaarman.mockitokotlin2.* import com.reactnativenavigation.BaseTest +import com.reactnativenavigation.fakes.IconResolverFake +import com.reactnativenavigation.mocks.TitleBarButtonCreatorMock import com.reactnativenavigation.options.BackButton import com.reactnativenavigation.options.ButtonOptions +import com.reactnativenavigation.options.ComponentOptions import com.reactnativenavigation.options.Options import com.reactnativenavigation.options.params.Bool import com.reactnativenavigation.options.params.Text import com.reactnativenavigation.react.Constants import com.reactnativenavigation.react.ReactView -import com.reactnativenavigation.utils.CollectionUtils import com.reactnativenavigation.utils.TitleBarHelper import com.reactnativenavigation.utils.resetViewProperties import com.reactnativenavigation.viewcontrollers.stack.topbar.TopBarAnimator import com.reactnativenavigation.viewcontrollers.stack.topbar.TopBarController import com.reactnativenavigation.viewcontrollers.stack.topbar.button.ButtonController +import com.reactnativenavigation.viewcontrollers.stack.topbar.button.ButtonPresenter import com.reactnativenavigation.views.stack.StackLayout import com.reactnativenavigation.views.stack.topbar.TopBar import org.assertj.core.api.Java6Assertions.assertThat import org.junit.Test -import java.util.* class TopBarControllerTest : BaseTest() { private lateinit var uut: TopBarController @@ -34,10 +36,16 @@ class TopBarControllerTest : BaseTest() { private lateinit var textButton2: ButtonOptions private lateinit var componentButton: ButtonOptions private lateinit var animator: TopBarAnimator + private lateinit var leftButtonControllers: MutableMap + private lateinit var rightButtonControllers: MutableMap + + private val topBar: View get() = uut.view override fun beforeEach() { + leftButtonControllers= mutableMapOf() + rightButtonControllers= mutableMapOf() activity = newActivity() animator = spy(TopBarAnimator()) uut = createTopBarController() @@ -48,68 +56,111 @@ class TopBarControllerTest : BaseTest() { @Test fun setButton_setsTextButton() { - uut.applyRightButtons(rightButtons(textButton1)!!) - uut.applyLeftButtons(leftButton(leftButton)) + uut.applyRightButtonsOptions(rightButtonControllers, listOf(textButton1)){ + createButtonController(it) + } + uut.applyLeftButtonsOptions(leftButtonControllers, listOf(leftButton)){ + createButtonController(it) + } assertThat(uut.getRightButton(0).title.toString()).isEqualTo(textButton1.text.get()) } @Test fun setButton_setsCustomButton() { - uut.applyLeftButtons(leftButton(leftButton)) - uut.applyRightButtons(rightButtons(componentButton)!!) + uut.applyLeftButtonsOptions(leftButtonControllers, listOf(leftButton)){ + createButtonController(it) + } + uut.applyRightButtonsOptions(rightButtonControllers, listOf(componentButton)){ + createButtonController(it) + } val btnView = uut.getRightButton(0).actionView as ReactView assertThat(btnView.componentName).isEqualTo(componentButton.component.name.get()) } @Test fun applyRightButtons_emptyButtonsListClearsRightButtons() { - uut.applyLeftButtons(leftButton(leftButton)) - uut.applyRightButtons(rightButtons(componentButton, textButton1)!!) - uut.applyLeftButtons(leftButton(leftButton)) - uut.applyRightButtons(ArrayList()) + uut.applyLeftButtonsOptions(leftButtonControllers, listOf(leftButton)){ + createButtonController(it) + } + uut.applyRightButtonsOptions(rightButtonControllers, listOf(componentButton, textButton1)){ + createButtonController(it) + } + uut.applyLeftButtonsOptions(leftButtonControllers, listOf(leftButton)){ + createButtonController(it) + } + uut.applyRightButtonsOptions(rightButtonControllers, listOf()){ + createButtonController(it) + } assertThat(uut.rightButtonCount).isEqualTo(0) } @Test fun applyRightButtons_previousButtonsAreCleared() { - uut.applyRightButtons(rightButtons(textButton1, componentButton)!!) + uut.applyRightButtonsOptions(rightButtonControllers, listOf(textButton1, componentButton)){ + createButtonController(it) + } assertThat(uut.rightButtonCount).isEqualTo(2) - uut.applyRightButtons(rightButtons(textButton2)!!) + uut.applyRightButtonsOptions(rightButtonControllers, listOf(textButton2)){ + createButtonController(it) + } assertThat(uut.rightButtonCount).isEqualTo(1) } @Test fun applyRightButtons_buttonsAreAddedInReversedOrderToMatchOrderOnIOs() { - uut.applyLeftButtons(leftButton(leftButton)) - uut.applyRightButtons(rightButtons(textButton1, componentButton)!!) + uut.applyLeftButtonsOptions(leftButtonControllers, listOf(leftButton)){ + createButtonController(it) + } + uut.applyRightButtonsOptions(rightButtonControllers, listOf(textButton1, componentButton)){ + createButtonController(it) + } assertThat(uut.getRightButton(1).title.toString()).isEqualTo(textButton1.text.get()) } @Test fun applyRightButtons_componentButtonIsReapplied() { - val initialButtons = rightButtons(componentButton) - uut.applyRightButtons(initialButtons!!) + uut.applyRightButtonsOptions(rightButtonControllers, listOf( componentButton)){ + createButtonController(it) + } assertThat(uut.getRightButton(0).itemId).isEqualTo(componentButton.intId) - uut.applyRightButtons(rightButtons(textButton1)!!) + uut.applyRightButtonsOptions(rightButtonControllers, listOf( textButton1)){ + createButtonController(it) + } assertThat(uut.getRightButton(0).itemId).isEqualTo(textButton1.intId) - uut.applyRightButtons(initialButtons) + uut.applyRightButtonsOptions(rightButtonControllers, listOf( componentButton)){ + createButtonController(it) + } assertThat(uut.getRightButton(0).itemId).isEqualTo(componentButton.intId) } @Test - fun mergeRightButtons_componentButtonIsNotAddedIfAlreadyAddedToMenu() { - val initialButtons = rightButtons(componentButton) - uut.applyRightButtons(initialButtons!!) - uut.mergeRightButtons(initialButtons, emptyList()) + fun mergeRightButtonsOptions_componentButtonIsNotAddedIfAlreadyAddedToMenu() { + val controllers = mutableMapOf() + uut.applyRightButtonsOptions(controllers, listOf(componentButton)){ + createButtonController(it) + } + verify(controllers[componentButton.id]!!, times(1)).addToMenu(any(), any()) + uut.mergeRightButtonsOptions(controllers, listOf(componentButton.copy())){ + createButtonController(it) + } + verify(controllers[componentButton.id]!!, times(1)).addToMenu(any(), any()) } @Test fun setLeftButtons_emptyButtonsListClearsLeftButton() { - uut.applyLeftButtons(leftButton(leftButton)) - uut.applyRightButtons(rightButtons(componentButton)!!) + uut.applyLeftButtonsOptions(leftButtonControllers, listOf(leftButton)){ + createButtonController(it) + } + uut.applyRightButtonsOptions(rightButtonControllers, listOf( componentButton)){ + createButtonController(it) + } assertThat(uut.leftButtonCount).isNotZero() - uut.applyLeftButtons(emptyList()) - uut.applyRightButtons(rightButtons(textButton1)!!) + uut.applyLeftButtonsOptions(leftButtonControllers, listOf()){ + createButtonController(it) + } + uut.applyRightButtonsOptions(rightButtonControllers, listOf( textButton1)){ + createButtonController(it) + } assertThat(uut.leftButtonCount).isZero() } @@ -117,7 +168,9 @@ class TopBarControllerTest : BaseTest() { fun setLeftButtons_clearsBackButton() { uut.view.setBackButton(TitleBarHelper.createButtonController(activity, backButton)) assertThat(uut.view.navigationIcon).isNotNull() - uut.applyLeftButtons(leftButton(leftButton)) + uut.applyLeftButtonsOptions(leftButtonControllers, listOf(leftButton)){ + createButtonController(it) + } assertThat(uut.view.navigationIcon).isNull() } @@ -125,25 +178,35 @@ class TopBarControllerTest : BaseTest() { fun setLeftButtons_emptyButtonsListClearsBackButton() { uut.view.setBackButton(TitleBarHelper.createButtonController(activity, backButton)) assertThat(uut.view.navigationIcon).isNotNull() - uut.applyLeftButtons(emptyList()) + uut.applyLeftButtonsOptions(leftButtonControllers, listOf()){ + createButtonController(it) + } assertThat(uut.view.navigationIcon).isNull() } @Test fun mergeLeftButtons_clearsBackButton() { + val controllers = mutableMapOf() uut.view.setBackButton(TitleBarHelper.createButtonController(activity, backButton)) assertThat(uut.view.navigationIcon).isNotNull() - uut.mergeLeftButtons(emptyList(), leftButton(leftButton)) + uut.mergeLeftButtonsOptions(controllers, listOf(leftButton)){ + createButtonController(it) + } assertThat(uut.view.navigationIcon).isNull() } @Test fun mergeLeftButtons_emptyButtonsListClearsBackButton() { + val controllers = mutableMapOf() + uut.view.setBackButton(TitleBarHelper.createButtonController(activity, backButton)) assertThat(uut.view.navigationIcon).isNotNull() - val initialButtons = leftButton(leftButton) - uut.applyLeftButtons(initialButtons) - uut.mergeLeftButtons(initialButtons, emptyList()) + uut.applyLeftButtonsOptions(controllers, listOf(leftButton)){ + createButtonController(it) + } + uut.mergeLeftButtonsOptions(controllers, emptyList()){ + createButtonController(it) + } assertThat(uut.view.navigationIcon).isNull() } @@ -219,6 +282,127 @@ class TopBarControllerTest : BaseTest() { assertThat(result).isEqualTo(someAnimator) } + @Test + fun `mergeRightButtons - should add buttons`(){ + val controllers = spy(LinkedHashMap()) + val controller = spy(ButtonController(activity, ButtonPresenter(activity, textButton1, IconResolverFake(activity)), + textButton1, TitleBarButtonCreatorMock(), object : ButtonController.OnClickListener { + override fun onPress(button: ButtonOptions) { + + } + + })) + uut.mergeRightButtonsOptions(controllers, listOf(textButton1)) { + controller + } + assertThat(uut.rightButtonCount).isEqualTo(1) + verify(controllers, never()).remove(any()) + assertThat(controllers[textButton1.id]).isEqualTo(controller) + } + @Test + fun `mergeRightOptions - should destroy all buttons that was removed`(){ + val componentButton2 = componentButton.copy() + componentButton2.component = ComponentOptions().apply { + this.name = componentButton.component.name + this.componentId = Text("CustomNewComponent") + } + uut.mergeRightButtonsOptions(rightButtonControllers, listOf(textButton1, textButton2, componentButton)) { + createButtonController(it) + } + val removedControllers = mutableMapOf().apply { + putAll(rightButtonControllers) + } + uut.mergeRightButtonsOptions(rightButtonControllers, listOf(componentButton2)) { + createButtonController(it) + } + verify(removedControllers[textButton1.id]!!, times(1)).destroy() + verify(removedControllers[textButton2.id]!!, times(1)).destroy() + verify(removedControllers[componentButton.id]!!, times(1)).destroy() + } + @Test + fun `mergeRightButtons - should remove all and re-add buttons in case of reorder, without destroy`(){ + uut.mergeRightButtonsOptions(rightButtonControllers, listOf(textButton1, textButton2)) { + createButtonController(it) + } + assertThat(uut.getRightButton(1).itemId ).isEqualTo(textButton1.intId) + assertThat(uut.getRightButton(0).itemId ).isEqualTo(textButton2.intId) + val removedControllers = mutableMapOf().apply { putAll(rightButtonControllers) } + uut.mergeRightButtonsOptions(rightButtonControllers, listOf(textButton2.copy(), textButton1.copy())) { + createButtonController(it) + } + assertThat(uut.getRightButton(1).itemId ).isEqualTo(textButton2.intId) + assertThat(uut.getRightButton(0).itemId ).isEqualTo(textButton1.intId) + + verify(removedControllers[textButton1.id]!!, never()).destroy() + verify(removedControllers[textButton2.id]!!, never()).destroy() + + verify(rightButtonControllers[textButton1.id]!!, times(1)).addToMenu(any(), any()) + verify(rightButtonControllers[textButton2.id]!!, times(1)).addToMenu(any(), any()) + } + @Test + fun `mergeRightButtons - should rebuild menu when adding menu items`(){ + val controllers = spy(LinkedHashMap()) + uut.mergeRightButtonsOptions(controllers, listOf(textButton1)) { + createButtonController(it) + } + assertThat(uut.rightButtonCount).isEqualTo(1) + + uut.mergeRightButtonsOptions(controllers, listOf(textButton1, textButton2)) { + createButtonController(it) + } + assertThat(uut.rightButtonCount).isEqualTo(2) + verify(controllers, times(1)).remove(any()) + verify(controllers[textButton1.id]!!, times(1)).addToMenu(any(), any()) + } + @Test + fun `mergeRightButtons - should modify changed buttons`(){ + val controllers = spy(LinkedHashMap()) + uut.mergeRightButtonsOptions(controllers, listOf(textButton1.apply { + this.enabled = Bool(true) + })) { + createButtonController(it) + } + assertThat(uut.rightButtonCount).isEqualTo(1) + verify(controllers[textButton1.id]!!, times(1)).addToMenu(any(), any()) + + uut.mergeRightButtonsOptions(controllers, listOf(textButton1.copy().apply { this.enabled= Bool(false) })) { + createButtonController(it) + } + verify(controllers, never()).remove(any()) + verify(controllers[textButton1.id]!!, times(1)).mergeButtonOptions(any(), any()) + verify(controllers[textButton1.id]!!, times(1)).addToMenu(any(), any()) + verify(controllers[textButton1.id]!!, never()).destroy() + } + + fun `mergeRightButtons - reorder of same menu items should rebuild menu`(){ + val controllers = spy(LinkedHashMap()) + uut.mergeRightButtonsOptions(controllers, listOf(textButton1, textButton2)) { + createButtonController(it) + } + assertThat(uut.rightButtonCount).isEqualTo(1) + verify(controllers[textButton1.id]!!, times(1)).addToMenu(any(), any()) + verify(controllers[textButton2.id]!!, times(1)).addToMenu(any(), any()) + + uut.mergeRightButtonsOptions(controllers, listOf(textButton2.copy(), textButton1.copy())) { + createButtonController(it) + } + verify(controllers[textButton1.id]!!, never()).mergeButtonOptions(any(), any()) + verify(controllers[textButton2.id]!!, never()).mergeButtonOptions(any(), any()) + verify(controllers[textButton1.id]!!, times(2)).addToMenu(any(), any()) + verify(controllers[textButton2.id]!!, times(2)).addToMenu(any(), any()) + verify(controllers[textButton1.id]!!, times(1)).destroy() + verify(controllers[textButton2.id]!!, times(1)).destroy() + } + + private fun createButtonController(it: ButtonOptions) = + spy(ButtonController(activity, ButtonPresenter(activity, it, IconResolverFake(activity)), + it, TitleBarButtonCreatorMock(), object : ButtonController.OnClickListener { + override fun onPress(button: ButtonOptions) { + + } + + })) + private fun createButtons() { leftButton = ButtonOptions() leftButton.id = Constants.BACK_BUTTON_ID @@ -238,17 +422,10 @@ class TopBarControllerTest : BaseTest() { return button } - private fun leftButton(leftButton: ButtonOptions): List { - return listOf(TitleBarHelper.createButtonController(activity, leftButton)) - } - - private fun rightButtons(vararg buttons: ButtonOptions): List? { - return CollectionUtils.map(listOf(*buttons)) { button: ButtonOptions? -> TitleBarHelper.createButtonController(activity, button) } - } - private fun createTopBarController() = spy(object : TopBarController(animator) { override fun createTopBar(context: Context, stackLayout: StackLayout): TopBar { return spy(super.createTopBar(context, stackLayout)) } }) + } \ No newline at end of file