Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bottom tabs selection stack (HW button Android) #7380

Merged
merged 16 commits into from Dec 1, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 25 additions & 0 deletions e2e/BottomTabs.test.js
@@ -1,5 +1,6 @@
import Utils from './Utils';
import TestIDs from '../playground/src/testIDs';
import Android from './AndroidUtils';

const { elementByLabel, elementById } = Utils;

Expand Down Expand Up @@ -113,4 +114,28 @@ describe('BottomTabs', () => {
await elementByLabel('OK').tap();
await expect(elementByLabel('First Tab')).toBeVisible();
});

it(':android: hardware back tab selection history', async () => {
await elementById(TestIDs.SECOND_TAB_BAR_BTN).tap();
await elementById(TestIDs.FIRST_TAB_BAR_BUTTON).tap();
await elementById(TestIDs.SECOND_TAB_BAR_BTN).tap();
await elementById(TestIDs.SECOND_TAB_BAR_BTN).tap();
await elementById(TestIDs.FIRST_TAB_BAR_BUTTON).tap();

Android.pressBack();
await expect(elementByLabel('Second Tab')).toBeVisible();

Android.pressBack();
await expect(elementByLabel('First Tab')).toBeVisible();

Android.pressBack();
await expect(elementByLabel('Second Tab')).toBeVisible();

Android.pressBack();
await expect(elementByLabel('First Tab')).toBeVisible();

Android.pressBack();
await expect(elementByLabel('First Tab')).toBeNotVisible();
await expect(elementByLabel('Second Tab')).toBeNotVisible();
});
});
Expand Up @@ -6,10 +6,40 @@ import com.reactnativenavigation.options.parsers.BoolParser
import org.json.JSONObject


sealed class HwBackBottomTabsBehaviour {
object Undefined : HwBackBottomTabsBehaviour() {
override fun hasValue(): Boolean = false
}

object Exit : HwBackBottomTabsBehaviour()
object PrevSelection : HwBackBottomTabsBehaviour()
object JumpToFirst : HwBackBottomTabsBehaviour()

open fun hasValue(): Boolean = true

companion object {
private const val BEHAVIOUR_EXIT = "exit"
private const val BEHAVIOUR_PREV = "previous"
private const val BEHAVIOUR_FIRST = "first"
fun fromString(behaviour: String?): HwBackBottomTabsBehaviour {
return when (behaviour) {
BEHAVIOUR_PREV -> PrevSelection
BEHAVIOUR_FIRST -> JumpToFirst
BEHAVIOUR_EXIT -> Exit
else -> Undefined
}
}
}
}

open class HardwareBackButtonOptions(json: JSONObject? = null) {

@JvmField var dismissModalOnPress: Bool = NullBool()
@JvmField var popStackOnPress: Bool = NullBool()
@JvmField
var dismissModalOnPress: Bool = NullBool()

@JvmField
var popStackOnPress: Bool = NullBool()
var bottomTabOnPress: HwBackBottomTabsBehaviour = HwBackBottomTabsBehaviour.Undefined

init {
parse(json)
Expand All @@ -18,16 +48,19 @@ open class HardwareBackButtonOptions(json: JSONObject? = null) {
fun mergeWith(other: HardwareBackButtonOptions) {
if (other.dismissModalOnPress.hasValue()) dismissModalOnPress = other.dismissModalOnPress
if (other.popStackOnPress.hasValue()) popStackOnPress = other.popStackOnPress
if (other.bottomTabOnPress.hasValue()) bottomTabOnPress = other.bottomTabOnPress
}

fun mergeWithDefault(defaultOptions: HardwareBackButtonOptions) {
if (!dismissModalOnPress.hasValue()) dismissModalOnPress = defaultOptions.dismissModalOnPress
if (!popStackOnPress.hasValue()) popStackOnPress = defaultOptions.popStackOnPress
if (!bottomTabOnPress.hasValue()) bottomTabOnPress = defaultOptions.bottomTabOnPress
}

private fun parse(json: JSONObject?) {
json ?: return
dismissModalOnPress = BoolParser.parse(json, "dismissModalOnPress")
popStackOnPress = BoolParser.parse(json, "popStackOnPress")
bottomTabOnPress = HwBackBottomTabsBehaviour.fromString(json.optString("bottomTabsOnPress"))
}
}
Expand Up @@ -13,6 +13,7 @@
import com.aurelhubert.ahbottomnavigation.AHBottomNavigation;
import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem;
import com.reactnativenavigation.options.BottomTabOptions;
import com.reactnativenavigation.options.HwBackBottomTabsBehaviour;
import com.reactnativenavigation.options.Options;
import com.reactnativenavigation.react.CommandListener;
import com.reactnativenavigation.react.CommandListenerAdapter;
Expand All @@ -29,6 +30,8 @@
import com.reactnativenavigation.views.bottomtabs.BottomTabsLayout;

import java.util.Collection;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;

import static com.reactnativenavigation.utils.CollectionUtils.forEach;
Expand All @@ -39,6 +42,7 @@ public class BottomTabsController extends ParentController<BottomTabsLayout> imp

private BottomTabsContainer bottomTabsContainer;
private BottomTabs bottomTabs;
private final Deque<Integer> selectionStack;
private final List<ViewController<?>> tabs;
private final EventEmitter eventEmitter;
private final ImageLoader imageLoader;
Expand Down Expand Up @@ -66,6 +70,7 @@ public BottomTabsController(Activity activity, List<ViewController<?>> tabs, Chi
this.presenter = bottomTabsPresenter;
this.tabPresenter = bottomTabPresenter;
forEach(tabs, tab -> tab.setParentController(this));
selectionStack = new LinkedList<>();
}

@Override
Expand Down Expand Up @@ -156,7 +161,27 @@ public void mergeChildOptions(Options options, ViewController<?> child) {

@Override
public boolean handleBack(CommandListener listener) {
return !tabs.isEmpty() && tabs.get(bottomTabs.getCurrentItem()).handleBack(listener);
final boolean childBack = !tabs.isEmpty() && tabs.get(bottomTabs.getCurrentItem()).handleBack(listener);
final Options options = resolveCurrentOptions();
if (!childBack) {
if (options.hardwareBack.getBottomTabOnPress() instanceof HwBackBottomTabsBehaviour.PrevSelection) {
if (!selectionStack.isEmpty()) {
final int prevSelectedTabIndex = selectionStack.poll();
selectTab(prevSelectedTabIndex, false);
return true;
}
} else if (options.hardwareBack.getBottomTabOnPress() instanceof HwBackBottomTabsBehaviour.JumpToFirst) {
if(getSelectedIndex() != 0){
selectTab(0, false);
return true;
}else{
return false;
}
}else{
return false;
}
swabbass marked this conversation as resolved.
Show resolved Hide resolved
}
return childBack;
}

@Override
Expand Down Expand Up @@ -203,7 +228,7 @@ private List<AHBottomNavigationItem> createTabs() {
});
}

int getSelectedIndex() {
public int getSelectedIndex() {
return bottomTabs.getCurrentItem();
}

Expand Down Expand Up @@ -239,13 +264,28 @@ public void destroy() {

@Override
public void selectTab(final int newIndex) {
final boolean enableSelectionHistory = resolveCurrentOptions().hardwareBack.getBottomTabOnPress() instanceof HwBackBottomTabsBehaviour.PrevSelection;
selectTab(newIndex, enableSelectionHistory);
}

private void selectTab(int newIndex, boolean enableSelectionHistory) {
saveTabSelection(newIndex, enableSelectionHistory);
tabsAttacher.onTabSelected(tabs.get(newIndex));
getCurrentView().setVisibility(View.INVISIBLE);
bottomTabs.setCurrentItem(newIndex, false);
getCurrentView().setVisibility(View.VISIBLE);
getCurrentChild().onViewDidAppear();
}

private void saveTabSelection(int newIndex, boolean enableSelectionHistory) {
if (enableSelectionHistory) {
if (selectionStack.isEmpty()
|| selectionStack.peek() != newIndex
|| bottomTabs.getCurrentItem() != newIndex)
selectionStack.offerFirst(bottomTabs.getCurrentItem());
}
}

@NonNull
private ViewGroup getCurrentView() {
return tabs.get(bottomTabs.getCurrentItem()).getView();
Expand Down