Browse Source

Merge branch 'navigator' into v2

Daniel Zlotin 7 years ago
parent
commit
2442a27a4e
25 changed files with 944 additions and 279 deletions
  1. 10
    17
      lib/android/app/src/main/java/com/reactnativenavigation/NavigationActivity.java
  2. 43
    6
      lib/android/app/src/main/java/com/reactnativenavigation/NavigationApplication.java
  3. 0
    18
      lib/android/app/src/main/java/com/reactnativenavigation/Store.java
  4. 19
    23
      lib/android/app/src/main/java/com/reactnativenavigation/layout/LayoutFactory.java
  5. 0
    83
      lib/android/app/src/main/java/com/reactnativenavigation/layout/impl/BottomTabsLayout.java
  6. 16
    5
      lib/android/app/src/main/java/com/reactnativenavigation/layout/impl/ReactRootViewController.java
  7. 14
    12
      lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java
  8. 2
    5
      lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationPackage.java
  9. 2
    5
      lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationReactNativeHost.java
  10. 12
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/StringUtils.java
  11. 87
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/BottomTabsController.java
  12. 80
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/IndexedStack.java
  13. 77
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/Navigator.java
  14. 40
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ParentController.java
  15. 25
    23
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/StackController.java
  16. 18
    21
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java
  17. 0
    31
      lib/android/app/src/test/java/com/reactnativenavigation/StoreTest.java
  18. 3
    6
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleViewController.java
  19. 19
    0
      lib/android/app/src/test/java/com/reactnativenavigation/utils/StringUtilsTest.java
  20. 99
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/BottomTabsControllerTest.java
  21. 114
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/IndexedStackTest.java
  22. 113
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/NavigatorTest.java
  23. 93
    0
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ParentControllerTest.java
  24. 43
    20
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackControllerTest.java
  25. 15
    4
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ViewControllerTest.java

+ 10
- 17
lib/android/app/src/main/java/com/reactnativenavigation/NavigationActivity.java View File

@@ -3,43 +3,36 @@ package com.reactnativenavigation;
3 3
 import android.os.Bundle;
4 4
 import android.support.annotation.Nullable;
5 5
 import android.support.v7.app.AppCompatActivity;
6
-import android.widget.FrameLayout;
7 6
 
8 7
 import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
9
-import com.reactnativenavigation.viewcontrollers.ViewController;
8
+import com.reactnativenavigation.viewcontrollers.Navigator;
10 9
 
11 10
 public class NavigationActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {
12
-	private ViewController viewController;
11
+	private Navigator navigator;
13 12
 
14 13
 	@Override
15 14
 	protected void onCreate(@Nullable Bundle savedInstanceState) {
16 15
 		super.onCreate(savedInstanceState);
17
-		setContentView(new FrameLayout(this));
18
-		app().getInitializer().onActivityCreated(this);
16
+		navigator = new Navigator(this);
17
+		navigator.onActivityCreated();
19 18
 	}
20 19
 
21 20
 	@Override
22 21
 	protected void onResume() {
23 22
 		super.onResume();
24
-		app().getInitializer().onActivityResumed(this);
23
+		navigator.onActivityResumed();
25 24
 	}
26 25
 
27 26
 	@Override
28 27
 	protected void onPause() {
29 28
 		super.onPause();
30
-		app().getInitializer().onActivityPaused(this);
29
+		navigator.onActivityPaused();
31 30
 	}
32 31
 
33 32
 	@Override
34 33
 	protected void onDestroy() {
35 34
 		super.onDestroy();
36
-		app().getInitializer().onActivityDestroyed(this);
37
-	}
38
-
39
-
40
-	public void setViewController(ViewController viewController) {
41
-		this.viewController = viewController;
42
-		setContentView(viewController.getView());
35
+		navigator.onActivityDestroyed();
43 36
 	}
44 37
 
45 38
 	@Override
@@ -49,12 +42,12 @@ public class NavigationActivity extends AppCompatActivity implements DefaultHard
49 42
 
50 43
 	@Override
51 44
 	public void onBackPressed() {
52
-		if (!viewController.handleBack()) {
45
+		if (!navigator.handleBack()) {
53 46
 			super.onBackPressed();
54 47
 		}
55 48
 	}
56 49
 
57
-	private NavigationApplication app() {
58
-		return ((NavigationApplication) getApplication());
50
+	public Navigator getNavigator() {
51
+		return navigator;
59 52
 	}
60 53
 }

+ 43
- 6
lib/android/app/src/main/java/com/reactnativenavigation/NavigationApplication.java View File

@@ -1,24 +1,25 @@
1 1
 package com.reactnativenavigation;
2 2
 
3
+import android.app.Activity;
3 4
 import android.app.Application;
5
+import android.os.Bundle;
4 6
 
5 7
 import com.facebook.react.ReactApplication;
6 8
 import com.facebook.react.ReactNativeHost;
7 9
 import com.reactnativenavigation.react.NavigationReactInitializer;
8 10
 import com.reactnativenavigation.react.NavigationReactNativeHost;
9 11
 
10
-public abstract class NavigationApplication extends Application implements ReactApplication {
12
+public abstract class NavigationApplication extends Application implements ReactApplication, Application.ActivityLifecycleCallbacks {
11 13
 
12
-	private Store store;
13 14
 	private NavigationReactNativeHost reactNativeHost;
14 15
 	private NavigationReactInitializer initializer;
15 16
 
16 17
 	@Override
17 18
 	public void onCreate() {
18 19
 		super.onCreate();
19
-		store = new Store();
20
-		reactNativeHost = new NavigationReactNativeHost(this, isDebug(), store);
20
+		reactNativeHost = new NavigationReactNativeHost(this, isDebug());
21 21
 		initializer = new NavigationReactInitializer(reactNativeHost.getReactInstanceManager(), isDebug());
22
+		registerActivityLifecycleCallbacks(this);
22 23
 	}
23 24
 
24 25
 	@Override
@@ -28,7 +29,43 @@ public abstract class NavigationApplication extends Application implements React
28 29
 
29 30
 	public abstract boolean isDebug();
30 31
 
31
-	NavigationReactInitializer getInitializer() {
32
-		return initializer;
32
+	@Override
33
+	public void onActivityCreated(final Activity activity, final Bundle savedInstanceState) {
34
+		if (activity instanceof NavigationActivity) {
35
+			initializer.onActivityCreated((NavigationActivity) activity);
36
+		}
37
+	}
38
+
39
+	@Override
40
+	public void onActivityStarted(final Activity activity) {
41
+	}
42
+
43
+	@Override
44
+	public void onActivityResumed(final Activity activity) {
45
+		if (activity instanceof NavigationActivity) {
46
+			initializer.onActivityResumed((NavigationActivity) activity);
47
+		}
48
+	}
49
+
50
+	@Override
51
+	public void onActivityPaused(final Activity activity) {
52
+		if (activity instanceof NavigationActivity) {
53
+			initializer.onActivityPaused((NavigationActivity) activity);
54
+		}
55
+	}
56
+
57
+	@Override
58
+	public void onActivityStopped(final Activity activity) {
59
+	}
60
+
61
+	@Override
62
+	public void onActivitySaveInstanceState(final Activity activity, final Bundle outState) {
63
+	}
64
+
65
+	@Override
66
+	public void onActivityDestroyed(final Activity activity) {
67
+		if (activity instanceof NavigationActivity) {
68
+			initializer.onActivityDestroyed((NavigationActivity) activity);
69
+		}
33 70
 	}
34 71
 }

+ 0
- 18
lib/android/app/src/main/java/com/reactnativenavigation/Store.java View File

@@ -1,18 +0,0 @@
1
-package com.reactnativenavigation;
2
-
3
-import com.reactnativenavigation.viewcontrollers.ViewController;
4
-
5
-import java.util.HashMap;
6
-import java.util.Map;
7
-
8
-public class Store {
9
-	private Map<String, ViewController> viewControllersById = new HashMap<>();
10
-
11
-	public void setViewController(String id, ViewController viewController) {
12
-		viewControllersById.put(id, viewController);
13
-	}
14
-
15
-	public ViewController getViewController(String id) {
16
-		return viewControllersById.get(id);
17
-	}
18
-}

+ 19
- 23
lib/android/app/src/main/java/com/reactnativenavigation/layout/LayoutFactory.java View File

@@ -3,38 +3,33 @@ package com.reactnativenavigation.layout;
3 3
 import android.app.Activity;
4 4
 
5 5
 import com.facebook.react.ReactInstanceManager;
6
-import com.reactnativenavigation.Store;
7 6
 import com.reactnativenavigation.layout.impl.ReactRootViewController;
7
+import com.reactnativenavigation.viewcontrollers.BottomTabsController;
8 8
 import com.reactnativenavigation.viewcontrollers.StackController;
9 9
 import com.reactnativenavigation.viewcontrollers.ViewController;
10 10
 
11
+import java.util.ArrayList;
12
+import java.util.List;
13
+
11 14
 public class LayoutFactory {
12 15
 
13 16
 	private final Activity activity;
14 17
 	private final ReactInstanceManager reactInstanceManager;
15
-	private final Store store;
16 18
 
17
-	public LayoutFactory(Activity activity, final ReactInstanceManager reactInstanceManager, final Store store) {
19
+	public LayoutFactory(Activity activity, final ReactInstanceManager reactInstanceManager) {
18 20
 		this.activity = activity;
19 21
 		this.reactInstanceManager = reactInstanceManager;
20
-		this.store = store;
21
-	}
22
-
23
-	public ViewController createAndSaveToStore(LayoutNode node) {
24
-		ViewController viewController = create(node);
25
-		store.setViewController(node.id, viewController);
26
-		return viewController;
27 22
 	}
28 23
 
29
-	private ViewController create(final LayoutNode node) {
24
+	public ViewController create(final LayoutNode node) {
30 25
 		switch (node.type) {
31 26
 			case Container:
32 27
 				return createContainer(node);
33 28
 			case ContainerStack:
34 29
 			default:
35 30
 				return createContainerStack(node);
36
-//			case BottomTabs:
37
-//				return createBottomTabs(node);
31
+			case BottomTabs:
32
+				return createBottomTabs(node);
38 33
 //			case SideMenuRoot:
39 34
 //				return createSideMenuRoot(node);
40 35
 //			case SideMenuCenter:
@@ -86,19 +81,20 @@ public class LayoutFactory {
86 81
 	}
87 82
 
88 83
 	private ViewController createContainerStack(LayoutNode node) {
89
-		StackController stackController = new StackController(activity);
84
+		StackController stackController = new StackController(activity, node.id);
90 85
 		for (LayoutNode child : node.children) {
91
-			stackController.push(createAndSaveToStore(child));
86
+			stackController.push(create(child));
92 87
 		}
93 88
 		return stackController;
94 89
 	}
95 90
 
96
-//	private Layout createBottomTabs(LayoutNode node) {
97
-//		final BottomTabsLayout tabsContainer = new BottomTabsLayout(activity);
98
-//		for (int i = 0; i < node.children.size(); i++) {
99
-//			final Layout tabLayout = createAndSaveToStore(node.children.get(i));
100
-//			tabsContainer.addTab("#" + i, tabLayout);
101
-//		}
102
-//		return tabsContainer;
103
-//	}
91
+	private ViewController createBottomTabs(LayoutNode node) {
92
+		final BottomTabsController tabsContainer = new BottomTabsController(activity, node.id);
93
+		List<ViewController> tabs = new ArrayList<>();
94
+		for (int i = 0; i < node.children.size(); i++) {
95
+			tabs.add(create(node.children.get(i)));
96
+		}
97
+		tabsContainer.setTabs(tabs);
98
+		return tabsContainer;
99
+	}
104 100
 }

+ 0
- 83
lib/android/app/src/main/java/com/reactnativenavigation/layout/impl/BottomTabsLayout.java View File

@@ -1,83 +0,0 @@
1
-package com.reactnativenavigation.layout.impl;
2
-
3
-public class BottomTabsLayout {
4
-
5
-//public class BottomTabsLayout implements Layout, BottomNavigationView.OnNavigationItemSelectedListener {
6
-
7
-//	private StackLayout stackLayout;
8
-//
9
-//	public static class TooManyTabs extends RuntimeException {
10
-//		//
11
-//	}
12
-//
13
-//	private final RelativeLayout view;
14
-//	private final BottomNavigationView bottomNavigationView;
15
-//	private final List<Layout> tabs = new ArrayList<>();
16
-//	private int currentTab;
17
-//
18
-//	public BottomTabsLayout(Activity activity) {
19
-//		view = new RelativeLayout(activity);
20
-//		view.setId(CompatUtils.generateViewId());
21
-//
22
-//		bottomNavigationView = new BottomNavigationView(view.getContext());
23
-//		bottomNavigationView.setId(CompatUtils.generateViewId());
24
-//		bottomNavigationView.setBackgroundColor(Color.DKGRAY);
25
-//		bottomNavigationView.setOnNavigationItemSelectedListener(this);
26
-//		LayoutParams lp = new LayoutParams(MATCH_PARENT, WRAP_CONTENT);
27
-//		lp.addRule(ALIGN_PARENT_BOTTOM);
28
-//		bottomNavigationView.setLayoutParams(lp);
29
-//		view.addView(bottomNavigationView, lp);
30
-//	}
31
-//
32
-//	@Override
33
-//	public View getView() {
34
-//		return view;
35
-//	}
36
-//
37
-//	@Override
38
-//	public void destroy() {
39
-//		//
40
-//	}
41
-//
42
-//	@Override
43
-//	public boolean onNavigationItemSelected(@NonNull final MenuItem item) {
44
-//		hideTab(currentTab);
45
-//		currentTab = item.getItemId();
46
-//		showTab(currentTab);
47
-//		return true;
48
-//	}
49
-//
50
-//	public void addTab(String label, Layout tabLayout) {
51
-//		if (tabs.size() >= 5) {
52
-//			throw new TooManyTabs();
53
-//		}
54
-//		int tabId = bottomNavigationView.getMenu().size();
55
-//		bottomNavigationView.getMenu().add(0, tabId, Menu.NONE, label);
56
-//		LayoutParams tabParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT);
57
-//		tabParams.addRule(ABOVE, bottomNavigationView.getId());
58
-//		view.addView(tabLayout.getView(), tabParams);
59
-//		tabs.add(tabLayout);
60
-//
61
-//		if (tabs.size() > 1) {
62
-//			tabLayout.getView().setVisibility(View.GONE);
63
-//		}
64
-//	}
65
-//
66
-//	private void showTab(int tabId) {
67
-//		tabs.get(tabId).getView().setVisibility(View.VISIBLE);
68
-//	}
69
-//
70
-//	private void hideTab(int tabId) {
71
-//		tabs.get(tabId).getView().setVisibility(View.GONE);
72
-//	}
73
-//
74
-//	@Override
75
-//	public void setParentStackLayout(final StackLayout stackLayout) {
76
-//		this.stackLayout = stackLayout;
77
-//	}
78
-//
79
-//	@Override
80
-//	public StackLayout getParentStackLayout() {
81
-//		return stackLayout;
82
-//	}
83
-}

+ 16
- 5
lib/android/app/src/main/java/com/reactnativenavigation/layout/impl/ReactRootViewController.java View File

@@ -2,6 +2,7 @@ package com.reactnativenavigation.layout.impl;
2 2
 
3 3
 import android.app.Activity;
4 4
 import android.os.Bundle;
5
+import android.support.annotation.NonNull;
5 6
 import android.view.View;
6 7
 
7 8
 import com.facebook.react.ReactInstanceManager;
@@ -10,13 +11,12 @@ import com.reactnativenavigation.viewcontrollers.ViewController;
10 11
 
11 12
 public class ReactRootViewController extends ViewController {
12 13
 
13
-	private final String id;
14 14
 	private final String name;
15 15
 	private final ReactInstanceManager reactInstanceManager;
16
+	private boolean attachedToReactInstance = false;
16 17
 
17 18
 	public ReactRootViewController(final Activity activity, final String id, final String name, final ReactInstanceManager reactInstanceManager) {
18
-		super(activity);
19
-		this.id = id;
19
+		super(activity, id);
20 20
 		this.name = name;
21 21
 		this.reactInstanceManager = reactInstanceManager;
22 22
 	}
@@ -44,17 +44,28 @@ public class ReactRootViewController extends ViewController {
44 44
 //		new NavigationEvent(reactInstanceManager.getCurrentReactContext()).containerStop(id);
45 45
 //	}
46 46
 
47
+//	@Override
48
+//	public void onStart() {
49
+//		super.onStart();
50
+//		if (attachedToReactInstance) {
51
+//			new NavigationEvent(reactInstanceManager.getCurrentReactContext()).containerStart(id);
52
+//		} else {
53
+//			throw new RuntimeException("Not yet attached to react");
54
+//		}
55
+//	}
56
+
57
+	@NonNull
47 58
 	@Override
48 59
 	protected View createView() {
49 60
 		ReactRootView reactRootView = new ReactRootView(getActivity());
50 61
 		Bundle opts = new Bundle();
51
-		opts.putString("id", this.id);
62
+		opts.putString("id", getId());
52 63
 		reactRootView.startReactApplication(this.reactInstanceManager, this.name, opts);
53 64
 		reactRootView.setEventListener(new ReactRootView.ReactRootViewEventListener() {
54 65
 			@Override
55 66
 			public void onAttachedToReactInstance(final ReactRootView reactRootView) {
56 67
 				reactRootView.setEventListener(null);
57
-				onStart();
68
+				attachedToReactInstance = true;
58 69
 			}
59 70
 		});
60 71
 		return reactRootView;

+ 14
- 12
lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java View File

@@ -8,23 +8,21 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
8 8
 import com.facebook.react.bridge.ReactMethod;
9 9
 import com.facebook.react.bridge.ReadableMap;
10 10
 import com.reactnativenavigation.NavigationActivity;
11
-import com.reactnativenavigation.Store;
12 11
 import com.reactnativenavigation.layout.LayoutFactory;
13 12
 import com.reactnativenavigation.layout.LayoutNode;
14 13
 import com.reactnativenavigation.parse.JSONParser;
15 14
 import com.reactnativenavigation.parse.LayoutNodeParser;
16 15
 import com.reactnativenavigation.utils.UiThread;
16
+import com.reactnativenavigation.viewcontrollers.Navigator;
17 17
 import com.reactnativenavigation.viewcontrollers.ViewController;
18 18
 
19 19
 public class NavigationModule extends ReactContextBaseJavaModule {
20 20
 	private static final String NAME = "RNNBridgeModule";
21 21
 	private final ReactInstanceManager reactInstanceManager;
22
-	private final Store store;
23 22
 
24
-	public NavigationModule(final ReactApplicationContext reactContext, final ReactInstanceManager reactInstanceManager, final Store store) {
23
+	public NavigationModule(final ReactApplicationContext reactContext, final ReactInstanceManager reactInstanceManager) {
25 24
 		super(reactContext);
26 25
 		this.reactInstanceManager = reactInstanceManager;
27
-		this.store = store;
28 26
 	}
29 27
 
30 28
 	@Override
@@ -38,8 +36,8 @@ public class NavigationModule extends ReactContextBaseJavaModule {
38 36
 			@Override
39 37
 			public void run() {
40 38
 				final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree));
41
-				final ViewController viewController = newLayoutFactory().createAndSaveToStore(layoutTree);
42
-				activity().setViewController(viewController);
39
+				final ViewController viewController = newLayoutFactory().create(layoutTree);
40
+				navigator().setRoot(viewController);
43 41
 			}
44 42
 		});
45 43
 	}
@@ -50,8 +48,8 @@ public class NavigationModule extends ReactContextBaseJavaModule {
50 48
 			@Override
51 49
 			public void run() {
52 50
 				final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree));
53
-				final ViewController viewController = newLayoutFactory().createAndSaveToStore(layoutTree);
54
-				store.getViewController(onContainerId).getStackController().push(viewController);
51
+				final ViewController viewController = newLayoutFactory().create(layoutTree);
52
+//				store.getViewController(onContainerId).getStackController().push(viewController);
55 53
 			}
56 54
 		});
57 55
 	}
@@ -61,7 +59,7 @@ public class NavigationModule extends ReactContextBaseJavaModule {
61 59
 		handle(new Runnable() {
62 60
 			@Override
63 61
 			public void run() {
64
-				store.getViewController(onContainerId).getStackController().pop(store.getViewController(onContainerId));
62
+//				store.getViewController(onContainerId).getStackController().pop(store.getViewController(onContainerId));
65 63
 			}
66 64
 		});
67 65
 	}
@@ -71,7 +69,7 @@ public class NavigationModule extends ReactContextBaseJavaModule {
71 69
 		handle(new Runnable() {
72 70
 			@Override
73 71
 			public void run() {
74
-				store.getViewController(onContainerId).getStackController().popTo(store.getViewController(toContainerId));
72
+//				store.getViewController(onContainerId).getStackController().popTo(store.getViewController(toContainerId));
75 73
 			}
76 74
 		});
77 75
 	}
@@ -81,7 +79,7 @@ public class NavigationModule extends ReactContextBaseJavaModule {
81 79
 		handle(new Runnable() {
82 80
 			@Override
83 81
 			public void run() {
84
-				store.getViewController(onContainerId).getStackController().popToRoot();
82
+//				store.getViewController(onContainerId).getStackController().popToRoot();
85 83
 			}
86 84
 		});
87 85
 	}
@@ -90,9 +88,13 @@ public class NavigationModule extends ReactContextBaseJavaModule {
90 88
 		return (NavigationActivity) getCurrentActivity();
91 89
 	}
92 90
 
91
+	private Navigator navigator() {
92
+		return activity().getNavigator();
93
+	}
94
+
93 95
 	@NonNull
94 96
 	private LayoutFactory newLayoutFactory() {
95
-		return new LayoutFactory(activity(), reactInstanceManager, store);
97
+		return new LayoutFactory(activity(), reactInstanceManager);
96 98
 	}
97 99
 
98 100
 	private void handle(Runnable task) {

+ 2
- 5
lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationPackage.java View File

@@ -6,7 +6,6 @@ import com.facebook.react.bridge.JavaScriptModule;
6 6
 import com.facebook.react.bridge.NativeModule;
7 7
 import com.facebook.react.bridge.ReactApplicationContext;
8 8
 import com.facebook.react.uimanager.ViewManager;
9
-import com.reactnativenavigation.Store;
10 9
 
11 10
 import java.util.Arrays;
12 11
 import java.util.Collections;
@@ -15,17 +14,15 @@ import java.util.List;
15 14
 public class NavigationPackage implements ReactPackage {
16 15
 
17 16
 	private ReactNativeHost reactNativeHost;
18
-	private Store store;
19 17
 
20
-	public NavigationPackage(final ReactNativeHost reactNativeHost, final Store store) {
18
+	public NavigationPackage(final ReactNativeHost reactNativeHost) {
21 19
 		this.reactNativeHost = reactNativeHost;
22
-		this.store = store;
23 20
 	}
24 21
 
25 22
 	@Override
26 23
 	public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
27 24
 		return Arrays.<NativeModule>asList(
28
-				new NavigationModule(reactContext, reactNativeHost.getReactInstanceManager(), store)
25
+				new NavigationModule(reactContext, reactNativeHost.getReactInstanceManager())
29 26
 		);
30 27
 	}
31 28
 

+ 2
- 5
lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationReactNativeHost.java View File

@@ -5,7 +5,6 @@ import android.app.Application;
5 5
 import com.facebook.react.ReactNativeHost;
6 6
 import com.facebook.react.ReactPackage;
7 7
 import com.facebook.react.shell.MainReactPackage;
8
-import com.reactnativenavigation.Store;
9 8
 
10 9
 import java.util.Arrays;
11 10
 import java.util.List;
@@ -13,12 +12,10 @@ import java.util.List;
13 12
 public class NavigationReactNativeHost extends ReactNativeHost {
14 13
 
15 14
 	private final boolean isDebug;
16
-	private Store store;
17 15
 
18
-	public NavigationReactNativeHost(Application application, boolean isDebug, final Store store) {
16
+	public NavigationReactNativeHost(Application application, boolean isDebug) {
19 17
 		super(application);
20 18
 		this.isDebug = isDebug;
21
-		this.store = store;
22 19
 	}
23 20
 
24 21
 	@Override
@@ -30,7 +27,7 @@ public class NavigationReactNativeHost extends ReactNativeHost {
30 27
 	protected List<ReactPackage> getPackages() {
31 28
 		return Arrays.<ReactPackage>asList(
32 29
 				new MainReactPackage(),
33
-				new NavigationPackage(this, store)
30
+				new NavigationPackage(this)
34 31
 		);
35 32
 	}
36 33
 }

+ 12
- 0
lib/android/app/src/main/java/com/reactnativenavigation/utils/StringUtils.java View File

@@ -0,0 +1,12 @@
1
+package com.reactnativenavigation.utils;
2
+
3
+public class StringUtils {
4
+
5
+	@SuppressWarnings("StringEquality")
6
+	public static boolean isEqual(String s1, String s2) {
7
+		if (s1 == null || s2 == null) {
8
+			return s1 == s2;
9
+		}
10
+		return s1.equals(s2);
11
+	}
12
+}

+ 87
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/BottomTabsController.java View File

@@ -0,0 +1,87 @@
1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import android.app.Activity;
4
+import android.graphics.Color;
5
+import android.support.annotation.NonNull;
6
+import android.support.design.widget.BottomNavigationView;
7
+import android.view.Menu;
8
+import android.view.MenuItem;
9
+import android.view.View;
10
+import android.widget.RelativeLayout;
11
+
12
+import com.reactnativenavigation.utils.CompatUtils;
13
+
14
+import java.util.ArrayList;
15
+import java.util.Collection;
16
+import java.util.List;
17
+
18
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
19
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
20
+import static android.widget.RelativeLayout.ABOVE;
21
+import static android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM;
22
+
23
+public class BottomTabsController extends ParentController implements BottomNavigationView.OnNavigationItemSelectedListener {
24
+	private BottomNavigationView bottomNavigationView;
25
+	private List<ViewController> tabs = new ArrayList<>();
26
+	private int selectedIndex = 0;
27
+
28
+	public BottomTabsController(final Activity activity, final String id) {
29
+		super(activity, id);
30
+	}
31
+
32
+	@NonNull
33
+	@Override
34
+	protected RelativeLayout createView() {
35
+		RelativeLayout root = new RelativeLayout(getActivity());
36
+		bottomNavigationView = new BottomNavigationView(getActivity());
37
+		bottomNavigationView.setId(CompatUtils.generateViewId());
38
+		bottomNavigationView.setBackgroundColor(Color.DKGRAY);
39
+		bottomNavigationView.setOnNavigationItemSelectedListener(this);
40
+		RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT);
41
+		lp.addRule(ALIGN_PARENT_BOTTOM);
42
+		root.addView(bottomNavigationView, lp);
43
+		return root;
44
+	}
45
+
46
+	@Override
47
+	public boolean onNavigationItemSelected(@NonNull final MenuItem item) {
48
+		selectTabAtIndex(item.getItemId());
49
+		return true;
50
+	}
51
+
52
+	public void selectTabAtIndex(final int newIndex) {
53
+		tabs.get(selectedIndex).getView().setVisibility(View.GONE);
54
+		selectedIndex = newIndex;
55
+		tabs.get(selectedIndex).getView().setVisibility(View.VISIBLE);
56
+	}
57
+
58
+	public void setTabs(final List<ViewController> tabs) {
59
+		if (tabs.size() > 5) {
60
+			throw new RuntimeException("Too many tabs!");
61
+		}
62
+		this.tabs = tabs;
63
+		getView();
64
+		for (int i = 0; i < tabs.size(); i++) {
65
+			String title = String.valueOf(i);
66
+			createTab(tabs.get(i), i, title);
67
+		}
68
+		selectTabAtIndex(0);
69
+	}
70
+
71
+	private void createTab(ViewController tab, final int index, final String title) {
72
+		bottomNavigationView.getMenu().add(0, index, Menu.NONE, title);
73
+		RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
74
+		params.addRule(ABOVE, bottomNavigationView.getId());
75
+		tab.getView().setVisibility(View.GONE);
76
+		getView().addView(tab.getView(), params);
77
+	}
78
+
79
+	public int getSelectedIndex() {
80
+		return selectedIndex;
81
+	}
82
+
83
+	@Override
84
+	public Collection<ViewController> getChildControllers() {
85
+		return tabs;
86
+	}
87
+}

+ 80
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/IndexedStack.java View File

@@ -0,0 +1,80 @@
1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import com.reactnativenavigation.utils.StringUtils;
4
+
5
+import java.util.ArrayDeque;
6
+import java.util.Collection;
7
+import java.util.HashMap;
8
+import java.util.Iterator;
9
+
10
+public class IndexedStack<E> implements Iterable<String> {
11
+
12
+	private final ArrayDeque<String> deque = new ArrayDeque<>();
13
+	private final HashMap<String, E> map = new HashMap<>();
14
+
15
+	public void push(String id, E item) {
16
+		deque.push(id);
17
+		map.put(id, item);
18
+	}
19
+
20
+	public E peek() {
21
+		if (isEmpty()) {
22
+			return null;
23
+		}
24
+		return map.get(deque.peek());
25
+	}
26
+
27
+	public E pop() {
28
+		if (isEmpty()) {
29
+			return null;
30
+		}
31
+		return map.remove(deque.pop());
32
+	}
33
+
34
+	public boolean isEmpty() {
35
+		return deque.isEmpty();
36
+	}
37
+
38
+	public int size() {
39
+		return deque.size();
40
+	}
41
+
42
+	public String peekId() {
43
+		return deque.peek();
44
+	}
45
+
46
+	public void clear() {
47
+		deque.clear();
48
+		map.clear();
49
+	}
50
+
51
+	public E get(final String id) {
52
+		return map.get(id);
53
+	}
54
+
55
+	public boolean containsId(final String id) {
56
+		return deque.contains(id);
57
+	}
58
+
59
+	public E remove(final String id) {
60
+		if (!containsId(id)) {
61
+			return null;
62
+		}
63
+		deque.remove(id);
64
+		return map.remove(id);
65
+	}
66
+
67
+	public boolean isTop(final String id) {
68
+		return StringUtils.isEqual(id, peekId());
69
+	}
70
+
71
+	@Override
72
+	public Iterator<String> iterator() {
73
+		return deque.iterator();
74
+	}
75
+
76
+
77
+	public Collection<E> values() {
78
+		return map.values();
79
+	}
80
+}

+ 77
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/Navigator.java View File

@@ -0,0 +1,77 @@
1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import android.app.Activity;
4
+import android.support.annotation.NonNull;
5
+import android.view.View;
6
+import android.widget.FrameLayout;
7
+
8
+import com.reactnativenavigation.utils.CompatUtils;
9
+
10
+import java.util.Collection;
11
+import java.util.Collections;
12
+
13
+public class Navigator extends ParentController {
14
+
15
+	private ViewController root;
16
+	private boolean activityResumed = false;
17
+
18
+	public Navigator(final Activity activity) {
19
+		super(activity, "navigator" + CompatUtils.generateViewId());
20
+	}
21
+
22
+	@NonNull
23
+	@Override
24
+	protected View createView() {
25
+		return new FrameLayout(getActivity());
26
+	}
27
+
28
+	@Override
29
+	public Collection<ViewController> getChildControllers() {
30
+		return Collections.singletonList(root);
31
+	}
32
+
33
+	/*
34
+	 * Activity lifecycle
35
+	 */
36
+
37
+	public boolean isActivityResumed() {
38
+		return activityResumed;
39
+	}
40
+
41
+	public void onActivityCreated() {
42
+		getActivity().setContentView(getView());
43
+	}
44
+
45
+	public void onActivityResumed() {
46
+		activityResumed = true;
47
+	}
48
+
49
+	public void onActivityPaused() {
50
+		activityResumed = false;
51
+	}
52
+
53
+	public void onActivityDestroyed() {
54
+		//
55
+	}
56
+
57
+	/*
58
+	 * Navigation methods
59
+	 */
60
+
61
+	public void setRoot(final ViewController viewController) {
62
+		getView().removeAllViews();
63
+
64
+		root = viewController;
65
+		getView().addView(viewController.getView());
66
+	}
67
+
68
+	public void push(final String onId, final ViewController viewController) {
69
+		ViewController found = root.findControllerById(onId);
70
+		if (found == null) return;
71
+
72
+		StackController parentStackController = found.getParentStackController();
73
+		if (parentStackController == null) return;
74
+
75
+		parentStackController.push(viewController);
76
+	}
77
+}

+ 40
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ParentController.java View File

@@ -0,0 +1,40 @@
1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import android.app.Activity;
4
+import android.support.annotation.NonNull;
5
+import android.view.View;
6
+import android.view.ViewGroup;
7
+
8
+import java.util.Collection;
9
+
10
+public abstract class ParentController extends ViewController {
11
+	public ParentController(final Activity activity, final String id) {
12
+		super(activity, id);
13
+	}
14
+
15
+	@NonNull
16
+	@Override
17
+	public ViewGroup getView() {
18
+		return (ViewGroup) super.getView();
19
+	}
20
+
21
+	@NonNull
22
+	@Override
23
+	protected abstract View createView();
24
+
25
+	public abstract Collection<ViewController> getChildControllers();
26
+
27
+	public ViewController findControllerById(final String id) {
28
+		ViewController fromSuper = super.findControllerById(id);
29
+		if (fromSuper != null) {
30
+			return fromSuper;
31
+		}
32
+
33
+		for (ViewController child : getChildControllers()) {
34
+			ViewController found = child.findControllerById(id);
35
+			if (found != null) return found;
36
+		}
37
+
38
+		return null;
39
+	}
40
+}

+ 25
- 23
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/StackController.java View File

@@ -1,26 +1,27 @@
1 1
 package com.reactnativenavigation.viewcontrollers;
2 2
 
3 3
 import android.app.Activity;
4
+import android.support.annotation.NonNull;
4 5
 import android.support.annotation.Nullable;
5 6
 import android.view.ViewGroup;
6 7
 import android.widget.FrameLayout;
7 8
 
8
-import java.util.ArrayDeque;
9
+import java.util.Collection;
9 10
 
10
-public class StackController extends ViewController {
11
-	private final ArrayDeque<ViewController> stack = new ArrayDeque<>();
11
+public class StackController extends ParentController {
12
+	private final IndexedStack<ViewController> stack = new IndexedStack<>();
12 13
 
13
-	public StackController(final Activity activity) {
14
-		super(activity);
14
+	public StackController(final Activity activity, String id) {
15
+		super(activity, id);
15 16
 	}
16 17
 
17 18
 	public void push(final ViewController child) {
18
-		ViewController previousTop = peek();
19
+		final ViewController previousTop = peek();
19 20
 
20
-		child.setStackController(this);
21
-		stack.push(child);
22
-		getView().addView(child.getView());
21
+		child.setParentStackController(this);
22
+		stack.push(child.getId(), child);
23 23
 
24
+		getView().addView(child.getView());
24 25
 		if (previousTop != null) {
25 26
 			getView().removeView(previousTop.getView());
26 27
 		}
@@ -42,10 +43,10 @@ public class StackController extends ViewController {
42 43
 	}
43 44
 
44 45
 	public void pop(final ViewController childController) {
45
-		if (peek() == childController) {
46
+		if (stack.isTop(childController.getId())) {
46 47
 			pop();
47 48
 		} else {
48
-			stack.remove(childController);
49
+			stack.remove(childController.getId());
49 50
 		}
50 51
 	}
51 52
 
@@ -71,11 +72,7 @@ public class StackController extends ViewController {
71 72
 		}
72 73
 	}
73 74
 
74
-	@Override
75
-	public ViewGroup getView() {
76
-		return (ViewGroup) super.getView();
77
-	}
78
-
75
+	@NonNull
79 76
 	@Override
80 77
 	protected ViewGroup createView() {
81 78
 		return new FrameLayout(getActivity());
@@ -83,19 +80,15 @@ public class StackController extends ViewController {
83 80
 
84 81
 	@Nullable
85 82
 	@Override
86
-	public StackController getStackController() {
83
+	public StackController getParentStackController() {
87 84
 		return this;
88 85
 	}
89 86
 
90
-	ArrayDeque<ViewController> getStack() {
91
-		return stack;
92
-	}
93
-
94 87
 	public void popTo(final ViewController viewController) {
95
-		if (!stack.contains(viewController)) {
88
+		if (!stack.containsId(viewController.getId())) {
96 89
 			return;
97 90
 		}
98
-		while (peek() != viewController) {
91
+		while (!stack.isTop(viewController.getId())) {
99 92
 			pop();
100 93
 		}
101 94
 	}
@@ -105,4 +98,13 @@ public class StackController extends ViewController {
105 98
 			pop();
106 99
 		}
107 100
 	}
101
+
102
+	boolean containsId(String id) {
103
+		return stack.containsId(id);
104
+	}
105
+
106
+	@Override
107
+	public Collection<ViewController> getChildControllers() {
108
+		return stack.values();
109
+	}
108 110
 }

+ 18
- 21
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java View File

@@ -1,18 +1,24 @@
1 1
 package com.reactnativenavigation.viewcontrollers;
2 2
 
3 3
 import android.app.Activity;
4
+import android.support.annotation.NonNull;
4 5
 import android.support.annotation.Nullable;
5 6
 import android.view.View;
6 7
 
8
+import com.reactnativenavigation.utils.StringUtils;
9
+
7 10
 public abstract class ViewController {
8
-	private View view;
9 11
 	private final Activity activity;
10
-	private StackController stackController;
12
+	private final String id;
13
+	private View view;
14
+	private StackController parentStackController;
11 15
 
12
-	public ViewController(Activity activity) {
16
+	public ViewController(Activity activity, String id) {
13 17
 		this.activity = activity;
18
+		this.id = id;
14 19
 	}
15 20
 
21
+	@NonNull
16 22
 	protected abstract View createView();
17 23
 
18 24
 	public boolean handleBack() {
@@ -24,36 +30,27 @@ public abstract class ViewController {
24 30
 	}
25 31
 
26 32
 	@Nullable
27
-	public StackController getStackController() {
28
-		return stackController;
33
+	public StackController getParentStackController() {
34
+		return parentStackController;
29 35
 	}
30 36
 
31
-	void setStackController(final StackController stackController) {
32
-		this.stackController = stackController;
37
+	void setParentStackController(final StackController parentStackController) {
38
+		this.parentStackController = parentStackController;
33 39
 	}
34 40
 
41
+	@NonNull
35 42
 	public View getView() {
36 43
 		if (view == null) {
37 44
 			view = createView();
38
-			onCreate();
39 45
 		}
40 46
 		return view;
41 47
 	}
42 48
 
43
-	public void onCreate() {
44
-
45
-	}
46
-
47
-	public void onStart() {
48
-
49
-	}
50
-
51
-	public void onStop() {
52
-
49
+	public String getId() {
50
+		return id;
53 51
 	}
54 52
 
55
-	public void onDestroy() {
56
-
53
+	public ViewController findControllerById(final String id) {
54
+		return StringUtils.isEqual(this.id, id) ? this : null;
57 55
 	}
58
-
59 56
 }

+ 0
- 31
lib/android/app/src/test/java/com/reactnativenavigation/StoreTest.java View File

@@ -1,31 +0,0 @@
1
-package com.reactnativenavigation;
2
-
3
-import android.app.Activity;
4
-
5
-import com.reactnativenavigation.mocks.SimpleViewController;
6
-
7
-import org.junit.Test;
8
-
9
-import static org.assertj.core.api.Java6Assertions.assertThat;
10
-import static org.mockito.Mockito.mock;
11
-
12
-public class StoreTest extends BaseTest {
13
-
14
-	private Store uut;
15
-
16
-	@Override
17
-	public void beforeEach() {
18
-		super.beforeEach();
19
-		uut = new Store();
20
-	}
21
-
22
-	@Test
23
-	public void holdsViewControllersById() throws Exception {
24
-		SimpleViewController viewController = new SimpleViewController(mock(Activity.class), "my controller");
25
-
26
-		assertThat(uut.getViewController("the id")).isNull();
27
-
28
-		uut.setViewController("the id", viewController);
29
-		assertThat(uut.getViewController("the id")).isEqualTo(viewController);
30
-	}
31
-}

+ 3
- 6
lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleViewController.java View File

@@ -6,11 +6,8 @@ import android.view.View;
6 6
 import com.reactnativenavigation.viewcontrollers.ViewController;
7 7
 
8 8
 public class SimpleViewController extends ViewController {
9
-	private final String name;
10
-
11
-	public SimpleViewController(final Activity activity, String name) {
12
-		super(activity);
13
-		this.name = name;
9
+	public SimpleViewController(final Activity activity, String id) {
10
+		super(activity, id);
14 11
 	}
15 12
 
16 13
 	@Override
@@ -20,6 +17,6 @@ public class SimpleViewController extends ViewController {
20 17
 
21 18
 	@Override
22 19
 	public String toString() {
23
-		return "SimpleViewController " + name;
20
+		return "SimpleViewController " + getId();
24 21
 	}
25 22
 }

+ 19
- 0
lib/android/app/src/test/java/com/reactnativenavigation/utils/StringUtilsTest.java View File

@@ -0,0 +1,19 @@
1
+package com.reactnativenavigation.utils;
2
+
3
+import com.reactnativenavigation.BaseTest;
4
+
5
+import org.junit.Test;
6
+
7
+import static org.assertj.core.api.Java6Assertions.assertThat;
8
+
9
+public class StringUtilsTest extends BaseTest {
10
+	@Test
11
+	public void isEqual() throws Exception {
12
+		assertThat(StringUtils.isEqual(null, "a")).isFalse();
13
+		assertThat(StringUtils.isEqual("a", null)).isFalse();
14
+		assertThat(StringUtils.isEqual("a", "b")).isFalse();
15
+		assertThat(StringUtils.isEqual("a", "A")).isFalse();
16
+		assertThat(StringUtils.isEqual("a", "a")).isTrue();
17
+		assertThat(StringUtils.isEqual("", "")).isTrue();
18
+	}
19
+}

+ 99
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/BottomTabsControllerTest.java View File

@@ -0,0 +1,99 @@
1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import android.app.Activity;
4
+import android.support.annotation.NonNull;
5
+import android.support.design.widget.BottomNavigationView;
6
+import android.view.View;
7
+import android.widget.RelativeLayout;
8
+
9
+import com.reactnativenavigation.BaseTest;
10
+import com.reactnativenavigation.mocks.SimpleViewController;
11
+
12
+import org.assertj.core.api.iterable.Extractor;
13
+import org.junit.Test;
14
+
15
+import java.util.Arrays;
16
+import java.util.List;
17
+
18
+import static org.assertj.core.api.Java6Assertions.assertThat;
19
+
20
+public class BottomTabsControllerTest extends BaseTest {
21
+
22
+	private Activity activity;
23
+	private BottomTabsController uut;
24
+	private ViewController child1;
25
+	private ViewController child2;
26
+	private ViewController child3;
27
+	private ViewController child4;
28
+	private ViewController child5;
29
+
30
+	@Override
31
+	public void beforeEach() {
32
+		super.beforeEach();
33
+		activity = newActivity();
34
+		uut = new BottomTabsController(activity, "uut");
35
+		child1 = new SimpleViewController(activity, "child1");
36
+		child2 = new SimpleViewController(activity, "child2");
37
+		child3 = new SimpleViewController(activity, "child3");
38
+		child4 = new SimpleViewController(activity, "child4");
39
+		child5 = new SimpleViewController(activity, "child5");
40
+	}
41
+
42
+	@Test
43
+	public void containsRelativeLayoutView() throws Exception {
44
+		assertThat(uut.getView()).isInstanceOf(RelativeLayout.class);
45
+		assertThat(uut.getView().getChildAt(0)).isInstanceOf(BottomNavigationView.class);
46
+	}
47
+
48
+	@Test(expected = RuntimeException.class)
49
+	public void setTabs_ThrowWhenMoreThan5() throws Exception {
50
+		List<ViewController> tabs = createTabs();
51
+		tabs.add(new SimpleViewController(activity, "6"));
52
+		uut.setTabs(tabs);
53
+	}
54
+
55
+	@Test
56
+	public void setTabs_AddAllViewsAsGoneExceptFirst() throws Exception {
57
+		List<ViewController> tabs = createTabs();
58
+		uut.setTabs(tabs);
59
+		assertThat(uut.getView().getChildCount()).isEqualTo(6);
60
+		assertThat(uut.getChildControllers()).extracting(new Extractor<ViewController, Integer>() {
61
+			@Override
62
+			public Integer extract(final ViewController input) {
63
+				return input.getView().getVisibility();
64
+			}
65
+		}).containsExactly(View.VISIBLE, View.GONE, View.GONE, View.GONE, View.GONE);
66
+	}
67
+
68
+	@Test
69
+	public void selectTabAtIndex() throws Exception {
70
+		uut.setTabs(createTabs());
71
+		assertThat(uut.getSelectedIndex()).isZero();
72
+
73
+		uut.selectTabAtIndex(3);
74
+
75
+		assertThat(uut.getSelectedIndex()).isEqualTo(3);
76
+		assertThat(uut.getChildControllers()).extracting(new Extractor<ViewController, Integer>() {
77
+			@Override
78
+			public Integer extract(final ViewController input) {
79
+				return input.getView().getVisibility();
80
+			}
81
+		}).containsExactly(View.GONE, View.GONE, View.GONE, View.VISIBLE, View.GONE);
82
+	}
83
+
84
+	@Test
85
+	public void findControllerById_ReturnsSelfOrChildren() throws Exception {
86
+		assertThat(uut.findControllerById("123")).isNull();
87
+		assertThat(uut.findControllerById(uut.getId())).isEqualTo(uut);
88
+		StackController inner = new StackController(activity, "inner");
89
+		inner.push(child1);
90
+		assertThat(uut.findControllerById(child1.getId())).isNull();
91
+		uut.setTabs(Arrays.<ViewController>asList(inner));
92
+		assertThat(uut.findControllerById(child1.getId())).isEqualTo(child1);
93
+	}
94
+
95
+	@NonNull
96
+	private List<ViewController> createTabs() {
97
+		return Arrays.asList(child1, child2, child3, child4, child5);
98
+	}
99
+}

+ 114
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/IndexedStackTest.java View File

@@ -0,0 +1,114 @@
1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import com.reactnativenavigation.BaseTest;
4
+
5
+import org.junit.Test;
6
+
7
+import static org.assertj.core.api.Java6Assertions.assertThat;
8
+
9
+public class IndexedStackTest extends BaseTest {
10
+
11
+	private IndexedStack<Integer> uut;
12
+
13
+	@Override
14
+	public void beforeEach() {
15
+		super.beforeEach();
16
+		uut = new IndexedStack<>();
17
+	}
18
+
19
+	@Test
20
+	public void isEmpty() throws Exception {
21
+		assertThat(uut.isEmpty()).isTrue();
22
+		uut.push("123", 123);
23
+		assertThat(uut.isEmpty()).isFalse();
24
+	}
25
+
26
+	@Test
27
+	public void size() throws Exception {
28
+		assertThat(uut.size()).isEqualTo(0);
29
+		uut.push("123", 123);
30
+		assertThat(uut.size()).isEqualTo(1);
31
+	}
32
+
33
+	@Test
34
+	public void peek() throws Exception {
35
+		assertThat(uut.peek()).isNull();
36
+		uut.push("123", 123);
37
+		uut.push("456", 456);
38
+		assertThat(uut.peek()).isEqualTo(456);
39
+	}
40
+
41
+	@Test
42
+	public void pop() throws Exception {
43
+		assertThat(uut.pop()).isNull();
44
+		uut.push("123", 123);
45
+		uut.push("456", 456);
46
+		assertThat(uut.pop()).isEqualTo(456);
47
+	}
48
+
49
+	@Test
50
+	public void peekId() throws Exception {
51
+		assertThat(uut.peekId()).isNull();
52
+		uut.push("123", 123);
53
+		assertThat(uut.peekId()).isEqualTo("123");
54
+	}
55
+
56
+	@Test
57
+	public void clear() throws Exception {
58
+		uut.push("123", 123);
59
+		uut.push("456", 456);
60
+		uut.clear();
61
+		assertThat(uut.isEmpty()).isTrue();
62
+	}
63
+
64
+	@Test
65
+	public void getById() throws Exception {
66
+		assertThat(uut.get("123")).isNull();
67
+		uut.push("123", 123);
68
+		uut.push("456", 456);
69
+		assertThat(uut.get("123")).isEqualTo(123);
70
+	}
71
+
72
+	@Test
73
+	public void containsId() throws Exception {
74
+		assertThat(uut.containsId("123")).isFalse();
75
+		uut.push("123", 123);
76
+		assertThat(uut.containsId("123")).isTrue();
77
+	}
78
+
79
+	@Test
80
+	public void remove() throws Exception {
81
+		assertThat(uut.remove("123")).isNull();
82
+
83
+		uut.push("123", 123);
84
+		uut.push("456", 456);
85
+
86
+		assertThat(uut.remove("123")).isEqualTo(123);
87
+	}
88
+
89
+	@Test
90
+	public void iterableIds() throws Exception {
91
+		assertThat(uut).isInstanceOf(Iterable.class);
92
+		assertThat(uut).isEmpty();
93
+		uut.push("123", 123);
94
+		uut.push("456", 456);
95
+		assertThat(uut).containsExactly("456", "123");
96
+	}
97
+
98
+	@Test
99
+	public void isTop() throws Exception {
100
+		assertThat(uut.isTop("123")).isFalse();
101
+		uut.push("123", 123);
102
+		assertThat(uut.isTop("123")).isTrue();
103
+		uut.push("456", 456);
104
+		assertThat(uut.isTop("123")).isFalse();
105
+	}
106
+
107
+	@Test
108
+	public void values() throws Exception {
109
+		assertThat(uut.values()).isNotNull().isEmpty();
110
+		uut.push("123", 123);
111
+		uut.push("456", 456);
112
+		assertThat(uut.values()).isNotNull().containsExactlyInAnyOrder(123, 456);
113
+	}
114
+}

+ 113
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/NavigatorTest.java View File

@@ -0,0 +1,113 @@
1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import android.app.Activity;
4
+import android.view.ViewGroup;
5
+
6
+import com.reactnativenavigation.BaseTest;
7
+import com.reactnativenavigation.mocks.SimpleViewController;
8
+
9
+import org.junit.Test;
10
+import org.robolectric.Shadows;
11
+
12
+import java.util.Arrays;
13
+
14
+import static org.assertj.core.api.Java6Assertions.assertThat;
15
+
16
+public class NavigatorTest extends BaseTest {
17
+	private Activity activity;
18
+	private Navigator uut;
19
+	private ViewController child1;
20
+	private ViewController child2;
21
+
22
+	@Override
23
+	public void beforeEach() {
24
+		super.beforeEach();
25
+		activity = newActivity();
26
+		uut = new Navigator(activity);
27
+		child1 = new SimpleViewController(activity, "child1");
28
+		child2 = new SimpleViewController(activity, "child2");
29
+	}
30
+
31
+
32
+	@Test
33
+	public void isActivityResumed() throws Exception {
34
+		assertThat(uut.isActivityResumed()).isFalse();
35
+		uut.onActivityCreated();
36
+		assertThat(uut.isActivityResumed()).isFalse();
37
+		uut.onActivityResumed();
38
+		assertThat(uut.isActivityResumed()).isTrue();
39
+		uut.onActivityPaused();
40
+		assertThat(uut.isActivityResumed()).isFalse();
41
+	}
42
+
43
+	@Test
44
+	public void setsItselfAsContentView() throws Exception {
45
+		assertThat(Shadows.shadowOf(activity).getContentView()).isNull();
46
+		uut.onActivityCreated();
47
+		assertThat(Shadows.shadowOf(activity).getContentView()).isNotNull().isEqualTo(uut.getView());
48
+	}
49
+
50
+	@Test
51
+	public void setRoot_AddsChildControllerView() throws Exception {
52
+		assertThat(uut.getView().getChildCount()).isZero();
53
+		uut.setRoot(child1);
54
+		assertHasSingleChildViewOf(uut, child1);
55
+	}
56
+
57
+	@Test
58
+	public void setRoot_ReplacesExistingChildControllerViews() throws Exception {
59
+		uut.setRoot(child1);
60
+		uut.setRoot(child2);
61
+		assertHasSingleChildViewOf(uut, child2);
62
+	}
63
+
64
+	@Test
65
+	public void hasUniqueId() throws Exception {
66
+		assertThat(uut.getId()).startsWith("navigator");
67
+		assertThat(new Navigator(activity).getId()).isNotEqualTo(uut.getId());
68
+	}
69
+
70
+	@Test
71
+	public void push() throws Exception {
72
+		StackController stackController = new StackController(activity, "stack1");
73
+		stackController.push(child1);
74
+		uut.setRoot(stackController);
75
+
76
+		assertHasSingleChildViewOf(uut, stackController);
77
+		assertHasSingleChildViewOf(stackController, child1);
78
+
79
+		uut.push(child1.getId(), child2);
80
+
81
+		assertHasSingleChildViewOf(uut, stackController);
82
+		assertHasSingleChildViewOf(stackController, child2);
83
+	}
84
+
85
+	@Test
86
+	public void push_InvalidPushWithoutAStack_DoesNothing() throws Exception {
87
+		uut.setRoot(child1);
88
+		uut.push(child1.getId(), child2);
89
+		assertHasSingleChildViewOf(uut, child1);
90
+	}
91
+
92
+	@Test
93
+	public void push_OnCorrectStackByFindingChildId() throws Exception {
94
+		BottomTabsController bottomTabsController = new BottomTabsController(activity, "tabsController");
95
+		StackController stack1 = new StackController(activity, "stack1");
96
+		StackController stack2 = new StackController(activity, "stack2");
97
+		stack1.push(child1);
98
+		stack2.push(child2);
99
+		bottomTabsController.setTabs(Arrays.<ViewController>asList(stack1, stack2));
100
+
101
+		uut.setRoot(bottomTabsController);
102
+		SimpleViewController newChild = new SimpleViewController(activity, "new child");
103
+		uut.push(child2.getId(), newChild);
104
+
105
+		assertThat(stack1.getChildControllers()).doesNotContain(newChild);
106
+		assertThat(stack2.getChildControllers()).contains(newChild);
107
+	}
108
+
109
+	private void assertHasSingleChildViewOf(ViewController parent, ViewController child) {
110
+		assertThat(((ViewGroup) parent.getView()).getChildCount()).isEqualTo(1);
111
+		assertThat(((ViewGroup) parent.getView()).getChildAt(0)).isEqualTo(child.getView()).isNotNull();
112
+	}
113
+}

+ 93
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ParentControllerTest.java View File

@@ -0,0 +1,93 @@
1
+package com.reactnativenavigation.viewcontrollers;
2
+
3
+import android.app.Activity;
4
+import android.support.annotation.NonNull;
5
+import android.view.View;
6
+import android.view.ViewGroup;
7
+import android.widget.FrameLayout;
8
+
9
+import com.reactnativenavigation.BaseTest;
10
+import com.reactnativenavigation.mocks.SimpleViewController;
11
+
12
+import org.junit.Test;
13
+
14
+import java.util.Arrays;
15
+import java.util.Collection;
16
+import java.util.Collections;
17
+
18
+import static org.assertj.core.api.Java6Assertions.assertThat;
19
+
20
+public class ParentControllerTest extends BaseTest {
21
+
22
+	private Activity activity;
23
+
24
+	@Override
25
+	public void beforeEach() {
26
+		super.beforeEach();
27
+		activity = newActivity();
28
+	}
29
+
30
+	@Test
31
+	public void holdsViewGroup() throws Exception {
32
+		ParentController uut = new ParentController(activity, "uut") {
33
+			@Override
34
+			public Collection<ViewController> getChildControllers() {
35
+				return Collections.emptyList();
36
+			}
37
+
38
+			@NonNull
39
+			@Override
40
+			protected View createView() {
41
+				return new FrameLayout(activity);
42
+			}
43
+		};
44
+
45
+		assertThat(uut.getView()).isInstanceOf(ViewGroup.class);
46
+	}
47
+
48
+	@Test
49
+	public void findControllerById_ReturnsSelfIfSameId() throws Exception {
50
+		ParentController uut = new ParentController(activity, "uut") {
51
+			@Override
52
+			public Collection<ViewController> getChildControllers() {
53
+				return Collections.emptyList();
54
+			}
55
+
56
+			@NonNull
57
+			@Override
58
+			protected View createView() {
59
+				return new FrameLayout(activity);
60
+			}
61
+		};
62
+
63
+		assertThat(uut.findControllerById("123")).isNull();
64
+		assertThat(uut.findControllerById(uut.getId())).isEqualTo(uut);
65
+	}
66
+
67
+	@Test
68
+	public void findControllerById_DeeplyInOneOfTheChildren() throws Exception {
69
+		ViewController child1 = new SimpleViewController(activity, "child1");
70
+		ViewController child2 = new SimpleViewController(activity, "child2");
71
+
72
+		final StackController someInnerStack = new StackController(activity, "stack1");
73
+		someInnerStack.push(child1);
74
+		someInnerStack.push(child2);
75
+
76
+		ParentController uut = new ParentController(activity, "uut") {
77
+			@Override
78
+			public Collection<ViewController> getChildControllers() {
79
+				return Arrays.<ViewController>asList(someInnerStack);
80
+			}
81
+
82
+			@NonNull
83
+			@Override
84
+			protected View createView() {
85
+				return new FrameLayout(activity);
86
+			}
87
+		};
88
+
89
+		assertThat(uut.findControllerById("stack1")).isEqualTo(someInnerStack);
90
+		assertThat(uut.findControllerById("child1")).isEqualTo(child1);
91
+		assertThat(uut.findControllerById("child2")).isEqualTo(child2);
92
+	}
93
+}

+ 43
- 20
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackControllerTest.java View File

@@ -23,7 +23,7 @@ public class StackControllerTest extends BaseTest {
23 23
 	public void beforeEach() {
24 24
 		super.beforeEach();
25 25
 		activity = newActivity();
26
-		uut = new StackController(activity);
26
+		uut = new StackController(activity, "uut");
27 27
 		child1 = new SimpleViewController(activity, "child1");
28 28
 		child2 = new SimpleViewController(activity, "child2");
29 29
 		child3 = new SimpleViewController(activity, "child3");
@@ -36,28 +36,28 @@ public class StackControllerTest extends BaseTest {
36 36
 
37 37
 	@Test
38 38
 	public void holdsAStackOfViewControllers() throws Exception {
39
-		assertThat(uut.getStack()).isEmpty();
39
+		assertThat(uut.isEmpty()).isTrue();
40 40
 		uut.push(child1);
41 41
 		uut.push(child2);
42 42
 		uut.push(child3);
43
-		assertThat(uut.getStack()).containsOnly(child3, child2, child1);
44 43
 		assertThat(uut.peek()).isEqualTo(child3);
44
+		assertContainsOnlyId(child1.getId(), child2.getId(), child3.getId());
45 45
 	}
46 46
 
47 47
 	@Test
48 48
 	public void push() throws Exception {
49
-		assertThat(uut.getStack()).isEmpty();
49
+		assertThat(uut.isEmpty()).isTrue();
50 50
 		uut.push(child1);
51
-		assertThat(uut.getStack()).containsOnly(child1);
51
+		assertContainsOnlyId(child1.getId());
52 52
 	}
53 53
 
54 54
 	@Test
55 55
 	public void pop() throws Exception {
56 56
 		uut.push(child1);
57 57
 		uut.push(child2);
58
-		assertThat(uut.getStack()).containsOnly(child2, child1);
58
+		assertContainsOnlyId(child2.getId(), child1.getId());
59 59
 		uut.pop();
60
-		assertThat(uut.getStack()).containsOnly(child1);
60
+		assertContainsOnlyId(child1.getId());
61 61
 	}
62 62
 
63 63
 	@Test
@@ -73,13 +73,13 @@ public class StackControllerTest extends BaseTest {
73 73
 
74 74
 	@Test
75 75
 	public void pushAssignsRefToSelfOnPushedController() throws Exception {
76
-		assertThat(child1.getStackController()).isNull();
76
+		assertThat(child1.getParentStackController()).isNull();
77 77
 		uut.push(child1);
78
-		assertThat(child1.getStackController()).isEqualTo(uut);
78
+		assertThat(child1.getParentStackController()).isEqualTo(uut);
79 79
 
80
-		StackController anotherNavController = new StackController(activity);
80
+		StackController anotherNavController = new StackController(activity, "another");
81 81
 		anotherNavController.push(child2);
82
-		assertThat(child2.getStackController()).isEqualTo(anotherNavController);
82
+		assertThat(child2.getParentStackController()).isEqualTo(anotherNavController);
83 83
 	}
84 84
 
85 85
 	@Test
@@ -100,24 +100,24 @@ public class StackControllerTest extends BaseTest {
100 100
 
101 101
 	@Test
102 102
 	public void popDoesNothingWhenZeroOrOneChild() throws Exception {
103
-		assertThat(uut.getStack()).isEmpty();
103
+		assertThat(uut.isEmpty()).isTrue();
104 104
 		uut.pop();
105
-		assertThat(uut.getStack()).isEmpty();
105
+		assertThat(uut.isEmpty()).isTrue();
106 106
 
107 107
 		uut.push(child1);
108 108
 		uut.pop();
109
-		assertThat(uut.getStack()).containsOnly(child1);
109
+		assertContainsOnlyId(child1.getId());
110 110
 	}
111 111
 
112 112
 	@Test
113 113
 	public void canPopWhenSizeIsMoreThanOne() throws Exception {
114
-		assertThat(uut.getStack()).isEmpty();
114
+		assertThat(uut.isEmpty()).isTrue();
115 115
 		assertThat(uut.canPop()).isFalse();
116 116
 		uut.push(child1);
117
-		assertThat(uut.getStack()).containsOnly(child1);
117
+		assertContainsOnlyId(child1.getId());
118 118
 		assertThat(uut.canPop()).isFalse();
119 119
 		uut.push(child2);
120
-		assertThat(uut.getStack()).containsOnly(child1, child2);
120
+		assertContainsOnlyId(child1.getId(), child2.getId());
121 121
 		assertThat(uut.canPop()).isTrue();
122 122
 	}
123 123
 
@@ -159,7 +159,7 @@ public class StackControllerTest extends BaseTest {
159 159
 		uut.push(child1);
160 160
 		uut.push(child2);
161 161
 		uut.pop(child2);
162
-		assertThat(uut.getStack()).containsOnly(child1);
162
+		assertContainsOnlyId(child1.getId());
163 163
 		assertHasSingleChildViewOfController(child1);
164 164
 	}
165 165
 
@@ -170,13 +170,13 @@ public class StackControllerTest extends BaseTest {
170 170
 		assertHasSingleChildViewOfController(child2);
171 171
 
172 172
 		uut.pop(child1);
173
-		assertThat(uut.getStack()).containsOnly(child2);
173
+		assertContainsOnlyId(child2.getId());
174 174
 		assertHasSingleChildViewOfController(child2);
175 175
 	}
176 176
 
177 177
 	@Test
178 178
 	public void getStackControllerReturnsSelf() throws Exception {
179
-		assertThat(uut.getStackController()).isEqualTo(uut);
179
+		assertThat(uut.getParentStackController()).isEqualTo(uut);
180 180
 	}
181 181
 
182 182
 	@Test
@@ -225,8 +225,31 @@ public class StackControllerTest extends BaseTest {
225 225
 		assertThat(uut.isEmpty()).isTrue();
226 226
 	}
227 227
 
228
+	@Test
229
+	public void findControllerById_ReturnsSelfOrChildrenById() throws Exception {
230
+		assertThat(uut.findControllerById("123")).isNull();
231
+		assertThat(uut.findControllerById(uut.getId())).isEqualTo(uut);
232
+		uut.push(child1);
233
+		assertThat(uut.findControllerById(child1.getId())).isEqualTo(child1);
234
+	}
235
+
236
+	@Test
237
+	public void findControllerById_Deeply() throws Exception {
238
+		StackController stack = new StackController(activity, "stack2");
239
+		stack.push(child2);
240
+		uut.push(stack);
241
+		assertThat(uut.findControllerById(child2.getId())).isEqualTo(child2);
242
+	}
243
+
228 244
 	private void assertHasSingleChildViewOfController(ViewController childController) {
229 245
 		assertThat(uut.getView().getChildCount()).isEqualTo(1);
230 246
 		assertThat(uut.getView().getChildAt(0)).isEqualTo(childController.getView());
231 247
 	}
248
+
249
+	private void assertContainsOnlyId(String... ids) {
250
+		assertThat(uut.size()).isEqualTo(ids.length);
251
+		for (String id : ids) {
252
+			assertThat(uut.containsId(id));
253
+		}
254
+	}
232 255
 }

+ 15
- 4
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ViewControllerTest.java View File

@@ -35,7 +35,7 @@ public class ViewControllerTest extends BaseTest {
35 35
 	@Test
36 36
 	public void canOverrideViewCreation() throws Exception {
37 37
 		final View otherView = new View(activity);
38
-		ViewController myController = new ViewController(activity) {
38
+		ViewController myController = new ViewController(activity, "vc") {
39 39
 			@Override
40 40
 			protected View createView() {
41 41
 				return otherView;
@@ -46,14 +46,25 @@ public class ViewControllerTest extends BaseTest {
46 46
 
47 47
 	@Test
48 48
 	public void holdsAReferenceToStackControllerOrNull() throws Exception {
49
-		assertThat(uut.getStackController()).isNull();
50
-		StackController nav = new StackController(activity);
49
+		assertThat(uut.getParentStackController()).isNull();
50
+		StackController nav = new StackController(activity, "stack");
51 51
 		nav.push(uut);
52
-		assertThat(uut.getStackController()).isEqualTo(nav);
52
+		assertThat(uut.getParentStackController()).isEqualTo(nav);
53 53
 	}
54 54
 
55 55
 	@Test
56 56
 	public void handleBackDefaultFalse() throws Exception {
57 57
 		assertThat(uut.handleBack()).isFalse();
58 58
 	}
59
+
60
+	@Test
61
+	public void holdsId() throws Exception {
62
+		assertThat(uut.getId()).isEqualTo("uut");
63
+	}
64
+
65
+	@Test
66
+	public void findControllerById_ReturnsSelfIfSameId() throws Exception {
67
+		assertThat(uut.findControllerById("123")).isNull();
68
+		assertThat(uut.findControllerById(uut.getId())).isEqualTo(uut);
69
+	}
59 70
 }