Browse Source

Android: Add bottom-tabs support (WIP)

Amit Davidi 7 years ago
parent
commit
55d23979bc

+ 1
- 0
android/app/build.gradle View File

@@ -48,6 +48,7 @@ dependencies {
48 48
     compile fileTree(include: ['*.jar'], dir: 'libs')
49 49
     compile 'com.android.support:design:25.1.1'
50 50
     compile 'com.android.support:appcompat-v7:25.1.1'
51
+    compile "com.android.support:support-v4:25.1.1"
51 52
 
52 53
     // node_modules
53 54
     compile 'com.facebook.react:react-native:+'

+ 0
- 2
android/app/src/main/AndroidManifest.xml View File

@@ -2,8 +2,6 @@
2 2
     package="com.reactnativenavigation">
3 3
 
4 4
     <application>
5
-        <activity android:name=".controllers.NavigationActivity" />
6
-
7 5
         <activity
8 6
             android:name="com.facebook.react.devsupport.DevSettingsActivity"
9 7
             android:exported="false" />

+ 21
- 12
android/app/src/main/java/com/reactnativenavigation/layout/LayoutFactory.java View File

@@ -1,9 +1,11 @@
1 1
 package com.reactnativenavigation.layout;
2 2
 
3 3
 import android.app.Activity;
4
-import android.support.annotation.NonNull;
5 4
 import android.view.View;
6 5
 
6
+import com.reactnativenavigation.layout.bottomtabs.BottomTabsContainer;
7
+import com.reactnativenavigation.layout.bottomtabs.BottomTabsCreator;
8
+
7 9
 import java.util.List;
8 10
 
9 11
 public class LayoutFactory {
@@ -11,12 +13,14 @@ public class LayoutFactory {
11 13
         View createRootView(String id, String name);
12 14
     }
13 15
 
14
-    private Activity activity;
15
-    private RootViewCreator rootViewCreator;
16
+    private final Activity activity;
17
+    private final RootViewCreator rootViewCreator;
18
+    private final BottomTabsCreator bottomTabsCreator; // TODO: revisit this, may not be needed
16 19
 
17
-    public LayoutFactory(Activity activity, RootViewCreator rootViewCreator) {
20
+    public LayoutFactory(Activity activity, RootViewCreator rootViewCreator, BottomTabsCreator bottomTabsCreator) {
18 21
         this.activity = activity;
19 22
         this.rootViewCreator = rootViewCreator;
23
+        this.bottomTabsCreator = bottomTabsCreator;
20 24
     }
21 25
 
22 26
     public View create(LayoutNode node) {
@@ -25,23 +29,28 @@ public class LayoutFactory {
25 29
                 return createContainerView(node);
26 30
             case "ContainerStack":
27 31
                 return createContainerStackView(node);
32
+            case "BottomTabs":
33
+                return createBottomTabs(node);
28 34
             default:
29 35
                 throw new IllegalArgumentException("Invalid node type: "+node.type);
30 36
         }
31 37
     }
32 38
 
33
-    @NonNull
39
+    private View createContainerView(LayoutNode node) {
40
+        final String name = (String) node.data.get("name");
41
+        return new Container(activity, rootViewCreator, node.id, name);
42
+    }
43
+
34 44
     private View createContainerStackView(LayoutNode node) {
35
-        ContainerStack containerStack = new ContainerStack(activity);
36
-        List<LayoutNode> children = node.children;
37
-        addChildrenNodes(containerStack, children);
45
+        final ContainerStack containerStack = new ContainerStack(activity);
46
+        addChildrenNodes(containerStack, node.children);
38 47
         return containerStack;
39 48
     }
40 49
 
41
-    @NonNull
42
-    private View createContainerView(LayoutNode node) {
43
-        String name = (String) node.data.get("name");
44
-        return new Container(activity, rootViewCreator, node.id, name);
50
+    private View createBottomTabs(LayoutNode node) {
51
+        final BottomTabsContainer tabsContainer = new BottomTabsContainer(activity, bottomTabsCreator);
52
+        tabsContainer.setTabContent(create(node.children.get(0)));
53
+        return tabsContainer;
45 54
     }
46 55
 
47 56
     private void addChildrenNodes(ContainerStack containerStack, List<LayoutNode> children) {

+ 25
- 0
android/app/src/main/java/com/reactnativenavigation/layout/bottomtabs/BottomTabs.java View File

@@ -0,0 +1,25 @@
1
+package com.reactnativenavigation.layout.bottomtabs;
2
+
3
+import android.support.design.widget.BottomNavigationView;
4
+import android.widget.RelativeLayout;
5
+
6
+import static android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM;
7
+
8
+public class BottomTabs {
9
+
10
+    private BottomNavigationView bottomNavigationView;
11
+
12
+    public void attach(RelativeLayout parentLayout) {
13
+        bottomNavigationView = new BottomNavigationView(parentLayout.getContext());
14
+
15
+        RelativeLayout.LayoutParams lp =
16
+                new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
17
+        lp.addRule(ALIGN_PARENT_BOTTOM);
18
+        bottomNavigationView.setLayoutParams(lp);
19
+        parentLayout.addView(bottomNavigationView, lp);
20
+    }
21
+
22
+    public void addTab(String label) {
23
+        bottomNavigationView.getMenu().add(label);
24
+    }
25
+}

+ 27
- 0
android/app/src/main/java/com/reactnativenavigation/layout/bottomtabs/BottomTabsContainer.java View File

@@ -0,0 +1,27 @@
1
+package com.reactnativenavigation.layout.bottomtabs;
2
+
3
+import android.app.Activity;
4
+import android.view.View;
5
+import android.widget.RelativeLayout;
6
+
7
+public class BottomTabsContainer extends RelativeLayout {
8
+
9
+    private BottomTabs bottomTabs;
10
+
11
+    public BottomTabsContainer(Activity activity, BottomTabsCreator bottomTabsCreator) {
12
+        super(activity);
13
+        createBottomTabs(bottomTabsCreator);
14
+    }
15
+
16
+    public void setTabContent(View tab) {
17
+        bottomTabs.addTab("#0");
18
+
19
+        LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
20
+        addView(tab, lp);
21
+    }
22
+
23
+    private void createBottomTabs(BottomTabsCreator bottomTabsCreator) {
24
+        bottomTabs = bottomTabsCreator.create();
25
+        bottomTabs.attach(this);
26
+    }
27
+}

+ 8
- 0
android/app/src/main/java/com/reactnativenavigation/layout/bottomtabs/BottomTabsCreator.java View File

@@ -0,0 +1,8 @@
1
+package com.reactnativenavigation.layout.bottomtabs;
2
+
3
+public class BottomTabsCreator {
4
+
5
+    public BottomTabs create() {
6
+        return new BottomTabs();
7
+    }
8
+}

+ 2
- 1
android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java View File

@@ -13,6 +13,7 @@ import com.facebook.react.bridge.ReadableMapKeySetIterator;
13 13
 import com.reactnativenavigation.controllers.NavigationActivity;
14 14
 import com.reactnativenavigation.layout.LayoutFactory;
15 15
 import com.reactnativenavigation.layout.LayoutNode;
16
+import com.reactnativenavigation.layout.bottomtabs.BottomTabsCreator;
16 17
 
17 18
 import java.util.ArrayList;
18 19
 import java.util.HashMap;
@@ -44,7 +45,7 @@ public class NavigationModule extends ReactContextBaseJavaModule {
44 45
                         rootView.startReactApplication(NavigationActivity.instance.getHost().getReactInstanceManager(), name, opts);
45 46
                         return rootView;
46 47
                     }
47
-                });
48
+                }, new BottomTabsCreator());
48 49
 
49 50
                 final LayoutNode layoutTreeRoot = readableMapToLayoutNode(layoutTree);
50 51
                 final View rootView = factory.create(layoutTreeRoot);

+ 2
- 1
android/app/src/main/res/values/styles.xml View File

@@ -1,5 +1,6 @@
1 1
 <?xml version="1.0" encoding="utf-8"?>
2 2
 <resources>
3
+
3 4
     <style name="Modal" parent="@android:style/Theme.Translucent.NoTitleBar">
4 5
         <item name="android:windowAnimationStyle">@style/modalAnimations</item>
5 6
     </style>
@@ -7,4 +8,4 @@
7 8
     <style name="modalAnimations">
8 9
         <item name="android:windowExitAnimation">@anim/slide_down</item>
9 10
     </style>
10
-</resources>
11
+</resources>

+ 6
- 0
android/app/src/test/RoboManifest.xml View File

@@ -0,0 +1,6 @@
1
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+          package="com.reactnativenavigation">
3
+    <application android:theme="@style/Theme.AppCompat.Light">
4
+        <activity android:name=".RoboActivity" android:theme="@style/Theme.AppCompat.Light"/>
5
+    </application>
6
+</manifest>

+ 47
- 18
android/app/src/test/java/com/reactnativenavigation/LayoutFactoryTest.java View File

@@ -1,13 +1,17 @@
1 1
 package com.reactnativenavigation;
2 2
 
3 3
 import android.app.Activity;
4
+import android.support.v7.app.AppCompatActivity;
4 5
 import android.view.View;
5 6
 import android.view.ViewGroup;
6 7
 
8
+import com.reactnativenavigation.layout.bottomtabs.BottomTabs;
9
+import com.reactnativenavigation.layout.bottomtabs.BottomTabsContainer;
7 10
 import com.reactnativenavigation.layout.Container;
8 11
 import com.reactnativenavigation.layout.ContainerStack;
9 12
 import com.reactnativenavigation.layout.LayoutFactory;
10 13
 import com.reactnativenavigation.layout.LayoutNode;
14
+import com.reactnativenavigation.layout.bottomtabs.BottomTabsCreator;
11 15
 
12 16
 import org.junit.Before;
13 17
 import org.junit.Test;
@@ -26,7 +30,6 @@ import static org.mockito.ArgumentMatchers.eq;
26 30
 import static org.mockito.Mockito.mock;
27 31
 import static org.mockito.Mockito.when;
28 32
 
29
-
30 33
 @RunWith(RobolectricTestRunner.class)
31 34
 public class LayoutFactoryTest {
32 35
 
@@ -36,17 +39,19 @@ public class LayoutFactoryTest {
36 39
     private final static String OTHER_VIEW_ID = "anotherUniqueId";
37 40
     private final static String OTHER_VIEW_NAME = "anotherName";
38 41
 
42
+    private Activity activity;
39 43
     private View mockView;
40 44
     private LayoutFactory.RootViewCreator rootViewCreator;
41 45
 
42 46
     @Before
43 47
     public void setUp() {
44
-        mockView = new View(Robolectric.setupActivity(Activity.class));
48
+        activity = Robolectric.buildActivity(AppCompatActivity.class).get();
49
+        mockView = new View(activity);
45 50
         rootViewCreator = mock(LayoutFactory.RootViewCreator.class);
46 51
     }
47 52
 
48 53
     @Test
49
-    public void returnsContainerThatHoldsTheRootView() {
54
+    public void returnsContainerThatHoldsTheRootView() throws Exception {
50 55
         when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView);
51 56
         final LayoutNode node = createContainerNode();
52 57
 
@@ -57,12 +62,12 @@ public class LayoutFactoryTest {
57 62
     }
58 63
 
59 64
     @Test
60
-    public void returnsContainerStack() {
65
+    public void returnsContainerStack() throws Exception  {
61 66
         when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView);
62
-        final LayoutNode node = createContainerNode();
63
-        final LayoutNode outerNode = getContainerStackNode(node);
67
+        final LayoutNode containerNode = createContainerNode();
68
+        final LayoutNode stackNode = getContainerStackNode(containerNode);
64 69
 
65
-        final ViewGroup result = (ViewGroup) createLayoutFactory().create(outerNode);
70
+        final ViewGroup result = (ViewGroup) createLayoutFactory().create(stackNode);
66 71
 
67 72
         assertThat(result).isInstanceOf(ContainerStack.class);
68 73
         ViewGroup container = (ViewGroup) assertViewChildrenCount(result, 1).get(0);
@@ -70,17 +75,17 @@ public class LayoutFactoryTest {
70 75
     }
71 76
 
72 77
     @Test
73
-    public void returnsContainerStackWithMultipleViews() {
78
+    public void returnsContainerStackWithMultipleViews() throws Exception  {
74 79
         final View mockView1 = mock(View.class);
75 80
         final View mockView2 = mock(View.class);
76 81
         when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView1);
77 82
         when(rootViewCreator.createRootView(eq(OTHER_VIEW_ID), eq(OTHER_VIEW_NAME))).thenReturn(mockView2);
78 83
 
79
-        final LayoutNode node1 = createContainerNode(VIEW_ID, VIEW_NAME);
80
-        final LayoutNode node2 = createContainerNode(OTHER_VIEW_ID, OTHER_VIEW_NAME);
81
-        final LayoutNode outerNode = getContainerStackNode(Arrays.asList(node1, node2));
84
+        final LayoutNode containerNode1 = createContainerNode(VIEW_ID, VIEW_NAME);
85
+        final LayoutNode containerNode2 = createContainerNode(OTHER_VIEW_ID, OTHER_VIEW_NAME);
86
+        final LayoutNode stackNode = getContainerStackNode(Arrays.asList(containerNode1, containerNode2));
82 87
 
83
-        final ViewGroup result = (ViewGroup) createLayoutFactory().create(outerNode);
88
+        final ViewGroup result = (ViewGroup) createLayoutFactory().create(stackNode);
84 89
 
85 90
         assertThat(result).isInstanceOf(ContainerStack.class);
86 91
         List<View> containers = assertViewChildrenCount(result, 2);
@@ -90,8 +95,22 @@ public class LayoutFactoryTest {
90 95
         assertViewChildren(container2, mockView2);
91 96
     }
92 97
 
98
+    @Test
99
+    public void returnsSingleTabContent() throws Exception {
100
+        when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView);
101
+        final LayoutNode containerNode = createContainerNode();
102
+        final LayoutNode tabNode = createTabNode(containerNode);
103
+
104
+        final View result = createLayoutFactory().create(tabNode);
105
+
106
+        assertThat(result).isInstanceOf(BottomTabsContainer.class);
107
+        View containerView = assertViewChildrenCount((BottomTabsContainer) result, 1).get(0);
108
+        assertThat(containerView).isInstanceOf(Container.class);
109
+        assertViewChildren((Container) containerView, mockView);
110
+    }
111
+
93 112
     @Test(expected = IllegalArgumentException.class)
94
-    public void throwsExceptionForUnknownType() {
113
+    public void throwsExceptionForUnknownType() throws Exception  {
95 114
         when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView);
96 115
         final LayoutNode node = new LayoutNode(VIEW_ID, "***unknownType***", Collections.<String, Object>emptyMap());
97 116
 
@@ -99,7 +118,10 @@ public class LayoutFactoryTest {
99 118
     }
100 119
 
101 120
     private LayoutFactory createLayoutFactory() {
102
-        return new LayoutFactory(Robolectric.buildActivity(Activity.class).get(), rootViewCreator);
121
+        BottomTabs bottomTabs = mock(BottomTabs.class);
122
+        BottomTabsCreator bottomTabsCreator = mock(BottomTabsCreator.class);
123
+        when(bottomTabsCreator.create()).thenReturn(bottomTabs);
124
+        return new LayoutFactory(activity, rootViewCreator, bottomTabsCreator);
103 125
     }
104 126
 
105 127
     private LayoutNode createContainerNode() {
@@ -115,10 +137,17 @@ public class LayoutFactoryTest {
115 137
     }
116 138
 
117 139
     private LayoutNode getContainerStackNode(List<LayoutNode> children) {
118
-        LayoutNode outerNode = new LayoutNode();
119
-        outerNode.type = "ContainerStack";
120
-        outerNode.children = children;
121
-        return outerNode;
140
+        LayoutNode node = new LayoutNode();
141
+        node.type = "ContainerStack";
142
+        node.children = children;
143
+        return node;
144
+    }
145
+
146
+    private LayoutNode createTabNode(LayoutNode children) {
147
+        LayoutNode node = new LayoutNode();
148
+        node.type = "BottomTabs";
149
+        node.children = Arrays.asList(children);
150
+        return node;
122 151
     }
123 152
 
124 153
     private List<View> assertViewChildrenCount(ViewGroup view, int count) {

+ 2
- 1
playground/android/app/src/main/AndroidManifest.xml View File

@@ -8,7 +8,8 @@
8 8
         android:allowBackup="false"
9 9
         android:icon="@mipmap/ic_launcher"
10 10
         android:label="@string/app_name"
11
-        android:theme="@style/Theme.AppCompat.Light.NoActionBar">
11
+        android:theme="@style/AppTheme"
12
+        >
12 13
         <activity
13 14
             android:name=".MainActivity"
14 15
             android:label="@string/app_name">

+ 4
- 0
playground/android/app/src/main/res/values/styles.xml View File

@@ -0,0 +1,4 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<resources>
3
+    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"/>
4
+</resources>