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
     compile fileTree(include: ['*.jar'], dir: 'libs')
48
     compile fileTree(include: ['*.jar'], dir: 'libs')
49
     compile 'com.android.support:design:25.1.1'
49
     compile 'com.android.support:design:25.1.1'
50
     compile 'com.android.support:appcompat-v7:25.1.1'
50
     compile 'com.android.support:appcompat-v7:25.1.1'
51
+    compile "com.android.support:support-v4:25.1.1"
51
 
52
 
52
     // node_modules
53
     // node_modules
53
     compile 'com.facebook.react:react-native:+'
54
     compile 'com.facebook.react:react-native:+'

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

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

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

1
 package com.reactnativenavigation.layout;
1
 package com.reactnativenavigation.layout;
2
 
2
 
3
 import android.app.Activity;
3
 import android.app.Activity;
4
-import android.support.annotation.NonNull;
5
 import android.view.View;
4
 import android.view.View;
6
 
5
 
6
+import com.reactnativenavigation.layout.bottomtabs.BottomTabsContainer;
7
+import com.reactnativenavigation.layout.bottomtabs.BottomTabsCreator;
8
+
7
 import java.util.List;
9
 import java.util.List;
8
 
10
 
9
 public class LayoutFactory {
11
 public class LayoutFactory {
11
         View createRootView(String id, String name);
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
         this.activity = activity;
21
         this.activity = activity;
19
         this.rootViewCreator = rootViewCreator;
22
         this.rootViewCreator = rootViewCreator;
23
+        this.bottomTabsCreator = bottomTabsCreator;
20
     }
24
     }
21
 
25
 
22
     public View create(LayoutNode node) {
26
     public View create(LayoutNode node) {
25
                 return createContainerView(node);
29
                 return createContainerView(node);
26
             case "ContainerStack":
30
             case "ContainerStack":
27
                 return createContainerStackView(node);
31
                 return createContainerStackView(node);
32
+            case "BottomTabs":
33
+                return createBottomTabs(node);
28
             default:
34
             default:
29
                 throw new IllegalArgumentException("Invalid node type: "+node.type);
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
     private View createContainerStackView(LayoutNode node) {
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
         return containerStack;
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
     private void addChildrenNodes(ContainerStack containerStack, List<LayoutNode> children) {
56
     private void addChildrenNodes(ContainerStack containerStack, List<LayoutNode> children) {

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

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

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

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
 import com.reactnativenavigation.controllers.NavigationActivity;
13
 import com.reactnativenavigation.controllers.NavigationActivity;
14
 import com.reactnativenavigation.layout.LayoutFactory;
14
 import com.reactnativenavigation.layout.LayoutFactory;
15
 import com.reactnativenavigation.layout.LayoutNode;
15
 import com.reactnativenavigation.layout.LayoutNode;
16
+import com.reactnativenavigation.layout.bottomtabs.BottomTabsCreator;
16
 
17
 
17
 import java.util.ArrayList;
18
 import java.util.ArrayList;
18
 import java.util.HashMap;
19
 import java.util.HashMap;
44
                         rootView.startReactApplication(NavigationActivity.instance.getHost().getReactInstanceManager(), name, opts);
45
                         rootView.startReactApplication(NavigationActivity.instance.getHost().getReactInstanceManager(), name, opts);
45
                         return rootView;
46
                         return rootView;
46
                     }
47
                     }
47
-                });
48
+                }, new BottomTabsCreator());
48
 
49
 
49
                 final LayoutNode layoutTreeRoot = readableMapToLayoutNode(layoutTree);
50
                 final LayoutNode layoutTreeRoot = readableMapToLayoutNode(layoutTree);
50
                 final View rootView = factory.create(layoutTreeRoot);
51
                 final View rootView = factory.create(layoutTreeRoot);

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

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

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

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
 package com.reactnativenavigation;
1
 package com.reactnativenavigation;
2
 
2
 
3
 import android.app.Activity;
3
 import android.app.Activity;
4
+import android.support.v7.app.AppCompatActivity;
4
 import android.view.View;
5
 import android.view.View;
5
 import android.view.ViewGroup;
6
 import android.view.ViewGroup;
6
 
7
 
8
+import com.reactnativenavigation.layout.bottomtabs.BottomTabs;
9
+import com.reactnativenavigation.layout.bottomtabs.BottomTabsContainer;
7
 import com.reactnativenavigation.layout.Container;
10
 import com.reactnativenavigation.layout.Container;
8
 import com.reactnativenavigation.layout.ContainerStack;
11
 import com.reactnativenavigation.layout.ContainerStack;
9
 import com.reactnativenavigation.layout.LayoutFactory;
12
 import com.reactnativenavigation.layout.LayoutFactory;
10
 import com.reactnativenavigation.layout.LayoutNode;
13
 import com.reactnativenavigation.layout.LayoutNode;
14
+import com.reactnativenavigation.layout.bottomtabs.BottomTabsCreator;
11
 
15
 
12
 import org.junit.Before;
16
 import org.junit.Before;
13
 import org.junit.Test;
17
 import org.junit.Test;
26
 import static org.mockito.Mockito.mock;
30
 import static org.mockito.Mockito.mock;
27
 import static org.mockito.Mockito.when;
31
 import static org.mockito.Mockito.when;
28
 
32
 
29
-
30
 @RunWith(RobolectricTestRunner.class)
33
 @RunWith(RobolectricTestRunner.class)
31
 public class LayoutFactoryTest {
34
 public class LayoutFactoryTest {
32
 
35
 
36
     private final static String OTHER_VIEW_ID = "anotherUniqueId";
39
     private final static String OTHER_VIEW_ID = "anotherUniqueId";
37
     private final static String OTHER_VIEW_NAME = "anotherName";
40
     private final static String OTHER_VIEW_NAME = "anotherName";
38
 
41
 
42
+    private Activity activity;
39
     private View mockView;
43
     private View mockView;
40
     private LayoutFactory.RootViewCreator rootViewCreator;
44
     private LayoutFactory.RootViewCreator rootViewCreator;
41
 
45
 
42
     @Before
46
     @Before
43
     public void setUp() {
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
         rootViewCreator = mock(LayoutFactory.RootViewCreator.class);
50
         rootViewCreator = mock(LayoutFactory.RootViewCreator.class);
46
     }
51
     }
47
 
52
 
48
     @Test
53
     @Test
49
-    public void returnsContainerThatHoldsTheRootView() {
54
+    public void returnsContainerThatHoldsTheRootView() throws Exception {
50
         when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView);
55
         when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView);
51
         final LayoutNode node = createContainerNode();
56
         final LayoutNode node = createContainerNode();
52
 
57
 
57
     }
62
     }
58
 
63
 
59
     @Test
64
     @Test
60
-    public void returnsContainerStack() {
65
+    public void returnsContainerStack() throws Exception  {
61
         when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView);
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
         assertThat(result).isInstanceOf(ContainerStack.class);
72
         assertThat(result).isInstanceOf(ContainerStack.class);
68
         ViewGroup container = (ViewGroup) assertViewChildrenCount(result, 1).get(0);
73
         ViewGroup container = (ViewGroup) assertViewChildrenCount(result, 1).get(0);
70
     }
75
     }
71
 
76
 
72
     @Test
77
     @Test
73
-    public void returnsContainerStackWithMultipleViews() {
78
+    public void returnsContainerStackWithMultipleViews() throws Exception  {
74
         final View mockView1 = mock(View.class);
79
         final View mockView1 = mock(View.class);
75
         final View mockView2 = mock(View.class);
80
         final View mockView2 = mock(View.class);
76
         when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView1);
81
         when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView1);
77
         when(rootViewCreator.createRootView(eq(OTHER_VIEW_ID), eq(OTHER_VIEW_NAME))).thenReturn(mockView2);
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
         assertThat(result).isInstanceOf(ContainerStack.class);
90
         assertThat(result).isInstanceOf(ContainerStack.class);
86
         List<View> containers = assertViewChildrenCount(result, 2);
91
         List<View> containers = assertViewChildrenCount(result, 2);
90
         assertViewChildren(container2, mockView2);
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
     @Test(expected = IllegalArgumentException.class)
112
     @Test(expected = IllegalArgumentException.class)
94
-    public void throwsExceptionForUnknownType() {
113
+    public void throwsExceptionForUnknownType() throws Exception  {
95
         when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView);
114
         when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView);
96
         final LayoutNode node = new LayoutNode(VIEW_ID, "***unknownType***", Collections.<String, Object>emptyMap());
115
         final LayoutNode node = new LayoutNode(VIEW_ID, "***unknownType***", Collections.<String, Object>emptyMap());
97
 
116
 
99
     }
118
     }
100
 
119
 
101
     private LayoutFactory createLayoutFactory() {
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
     private LayoutNode createContainerNode() {
127
     private LayoutNode createContainerNode() {
115
     }
137
     }
116
 
138
 
117
     private LayoutNode getContainerStackNode(List<LayoutNode> children) {
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
     private List<View> assertViewChildrenCount(ViewGroup view, int count) {
153
     private List<View> assertViewChildrenCount(ViewGroup view, int count) {

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

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

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

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