Browse Source

Add support for bottom tabs in Android

Adds BottomTabActivity which replaces TabActivity
Adds a dependency on `com.aurelhubert:ahbottomnavigation:1.2.3`
Uses iOS tabsStyle, with additional parameter that allows toggling of whether to show the titles of inactive tabs
Yedidya Kennard 8 years ago
parent
commit
279a9b4a57

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

@@ -35,6 +35,7 @@ repositories {
35 35
 
36 36
 dependencies {
37 37
     compile fileTree(dir: "libs", include: ["*.jar"])
38
+    compile "com.aurelhubert:ahbottomnavigation:1.2.3"
38 39
     compile "com.android.support:appcompat-v7:23.0.1"
39 40
     compile 'com.android.support:design:23.1.1'
40 41
     compile "com.facebook.react:react-native:+"  // From node_modules

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

@@ -3,6 +3,7 @@
3 3
 
4 4
     <application>
5 5
         <activity android:name="com.reactnativenavigation.activities.TabActivity" />
6
+        <activity android:name="com.reactnativenavigation.activities.BottomTabActivity" />
6 7
         <activity android:name="com.reactnativenavigation.activities.SingleScreenActivity" />
7 8
     </application>
8 9
 

+ 1
- 0
android/app/src/main/java/com/reactnativenavigation/activities/BaseReactActivity.java View File

@@ -135,6 +135,7 @@ public abstract class BaseReactActivity extends AppCompatActivity implements Def
135 135
     @Override
136 136
     protected void onCreate(Bundle savedInstanceState) {
137 137
         super.onCreate(savedInstanceState);
138
+        ContextProvider.setActivityContext(this);
138 139
         mReactInstanceManager = createReactInstanceManager();
139 140
         handleOnCreate();
140 141
     }

+ 185
- 0
android/app/src/main/java/com/reactnativenavigation/activities/BottomTabActivity.java View File

@@ -0,0 +1,185 @@
1
+package com.reactnativenavigation.activities;
2
+
3
+import android.graphics.Color;
4
+import android.graphics.drawable.Drawable;
5
+import android.os.AsyncTask;
6
+import android.os.Bundle;
7
+import android.view.Menu;
8
+import android.widget.FrameLayout;
9
+
10
+import com.aurelhubert.ahbottomnavigation.AHBottomNavigation;
11
+import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem;
12
+import com.reactnativenavigation.R;
13
+import com.reactnativenavigation.core.RctManager;
14
+import com.reactnativenavigation.core.objects.Screen;
15
+import com.reactnativenavigation.views.RnnToolBar;
16
+import com.reactnativenavigation.views.ScreenStack;
17
+
18
+import java.util.ArrayList;
19
+import java.util.HashMap;
20
+import java.util.Map;
21
+
22
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
23
+
24
+/**
25
+ * Created by guyc on 02/04/16.
26
+ */
27
+public class BottomTabActivity extends BaseReactActivity implements AHBottomNavigation.OnTabSelectedListener {
28
+    public static final String EXTRA_SCREENS = "extraScreens";
29
+
30
+    private static final String TAB_STYLE_BUTTON_COLOR = "tabBarButtonColor";
31
+    private static final String TAB_STYLE_SELECTED_COLOR = "tabBarSelectedButtonColor";
32
+    private static final String TAB_STYLE_BAR_BG_COLOR = "tabBarBackgroundColor";
33
+    private static final String TAB_STYLE_INACTIVE_TITLES = "tabShowInactiveTitles";
34
+
35
+    private static int DEFAULT_TAB_BAR_BG_COLOR = 0xFFFFFFFF;
36
+    private static int DEFAULT_TAB_BUTTON_COLOR = Color.GRAY;
37
+    private static int DEFAULT_TAB_SELECTED_COLOR = 0xFF0000FF;
38
+    private static boolean DEFAULT_TAB_INACTIVE_TITLES = true;
39
+
40
+    private AHBottomNavigation mBottomNavigation;
41
+    private FrameLayout mContentFrame;
42
+    private ArrayList<ScreenStack> mScreenStacks;
43
+    private int mCurrentStackPosition = 0;
44
+
45
+    @Override
46
+    protected void handleOnCreate() {
47
+        mReactInstanceManager = RctManager.getInstance().getReactInstanceManager();
48
+
49
+        setContentView(R.layout.bottom_tab_activity);
50
+        mToolbar = (RnnToolBar) findViewById(R.id.toolbar);
51
+        mBottomNavigation = (AHBottomNavigation) findViewById(R.id.bottom_tab_bar);
52
+        mContentFrame = (FrameLayout) findViewById(R.id.contentFrame);
53
+
54
+        ArrayList<Screen> screens = (ArrayList<Screen>) getIntent().getSerializableExtra(EXTRA_SCREENS);
55
+        mBottomNavigation.setForceTint(true);
56
+        setupToolbar(screens);
57
+        setupTabs(getIntent().getExtras());
58
+        setupPages(screens);
59
+    }
60
+
61
+    private void setupPages(ArrayList<Screen> screens) {
62
+        new SetupTabsTask(this, screens).execute();
63
+    }
64
+
65
+    private void setupToolbar(ArrayList<Screen> screens) {
66
+        Screen initialScreen = screens.get(0);
67
+        setNavigationStyle(initialScreen);
68
+        mToolbar.setScreens(screens);
69
+        mToolbar.setTitle(initialScreen.title);
70
+        setSupportActionBar(mToolbar);
71
+    }
72
+
73
+    @Override
74
+    public void setNavigationStyle(Screen screen) {
75
+        super.setNavigationStyle(screen);
76
+        mToolbar.setTitle(screen.title);
77
+    }
78
+
79
+    private void setupTabs(Bundle style) {
80
+
81
+        mBottomNavigation.setForceTitlesDisplay(style.getBoolean(TAB_STYLE_INACTIVE_TITLES, DEFAULT_TAB_INACTIVE_TITLES));
82
+        mBottomNavigation.setForceTint(true);
83
+        mBottomNavigation.setDefaultBackgroundColor(getColor(style, TAB_STYLE_BAR_BG_COLOR, DEFAULT_TAB_BAR_BG_COLOR));
84
+        mBottomNavigation.setInactiveColor(getColor(style, TAB_STYLE_BUTTON_COLOR, DEFAULT_TAB_BUTTON_COLOR));
85
+        mBottomNavigation.setAccentColor(getColor(style, TAB_STYLE_SELECTED_COLOR, DEFAULT_TAB_SELECTED_COLOR));
86
+    }
87
+
88
+    private static int getColor(Bundle bundle, String key, int defaultColor) {
89
+        if (bundle.containsKey(key)) {
90
+            return Color.parseColor(bundle.getString(key));
91
+        }
92
+        else {
93
+            return defaultColor;
94
+        }
95
+    }
96
+
97
+
98
+    @Override
99
+    public boolean onCreateOptionsMenu(Menu menu) {
100
+        boolean ret = super.onCreateOptionsMenu(menu);
101
+        mToolbar.handleOnCreateOptionsMenuAsync();
102
+        return ret;
103
+    }
104
+
105
+    @Override
106
+    public void push(Screen screen) {
107
+        super.push(screen);
108
+        mScreenStacks.get(mCurrentStackPosition).push(screen);
109
+    }
110
+
111
+    @Override
112
+    public Screen pop(String navigatorId) {
113
+        super.pop(navigatorId);
114
+        Screen screen = mScreenStacks.get(mCurrentStackPosition).pop();
115
+        setNavigationStyle(screen);
116
+        return screen;
117
+    }
118
+
119
+    @Override
120
+    protected Screen getCurrentScreen() {
121
+        return mScreenStacks.get(mCurrentStackPosition).peek();
122
+    }
123
+
124
+    @Override
125
+    protected String getCurrentNavigatorId() {
126
+        return mScreenStacks.get(mCurrentStackPosition).peek().navigatorId;
127
+    }
128
+
129
+    @Override
130
+    public int getScreenStackSize() {
131
+        return mScreenStacks.get(mCurrentStackPosition).getStackSize();
132
+    }
133
+
134
+    @Override
135
+    public void onTabSelected(int position, boolean wasSelected) {
136
+        if (wasSelected) {
137
+            return;
138
+        }
139
+        mContentFrame.removeAllViews();
140
+        mContentFrame.addView(mScreenStacks.get(position), new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
141
+        mCurrentStackPosition = position;
142
+        setNavigationStyle(mScreenStacks.get(mCurrentStackPosition).peek());
143
+    }
144
+
145
+
146
+    private static class SetupTabsTask extends AsyncTask<Void, Void, Map<Screen, Drawable>> {
147
+
148
+        private BottomTabActivity activity;
149
+        private ArrayList<Screen> screens;
150
+
151
+        public SetupTabsTask(BottomTabActivity context, ArrayList<Screen> screens) {
152
+            this.activity = context;
153
+            this.screens = screens;
154
+        }
155
+
156
+        @Override
157
+        protected Map<Screen, Drawable> doInBackground(Void... params) {
158
+            Map<Screen, Drawable> icons = new HashMap<>();
159
+            for (Screen screen : this.screens) {
160
+                if (screen.icon != null) {
161
+                    icons.put(screen, screen.getIcon(this.activity));
162
+                }
163
+            }
164
+            return icons;
165
+        }
166
+
167
+        @Override
168
+        protected void onPostExecute(Map<Screen, Drawable> icons) {
169
+            activity.setTabsWithIcons(this.screens, icons);
170
+        }
171
+    }
172
+
173
+    private void setTabsWithIcons(ArrayList<Screen> screens, Map<Screen, Drawable> icons) {
174
+        mScreenStacks = new ArrayList<>();
175
+        for(Screen screen: screens) {
176
+            ScreenStack stack = new ScreenStack(this);
177
+            stack.push(screen);
178
+            mScreenStacks.add(stack);
179
+            AHBottomNavigationItem item = new AHBottomNavigationItem(screen.label, icons.get(screen), Color.GRAY);
180
+            mBottomNavigation.addItem(item);
181
+            mBottomNavigation.setOnTabSelectedListener(this);
182
+        }
183
+        this.onTabSelected(0, false);
184
+    }
185
+}

+ 1
- 0
android/app/src/main/java/com/reactnativenavigation/activities/TabActivity.java View File

@@ -42,6 +42,7 @@ public class TabActivity extends BaseReactActivity {
42 42
         setNavigationStyle(initialScreen);
43 43
         mToolbar.setScreens(screens);
44 44
         mToolbar.setTitle(initialScreen.title);
45
+        mToolbar.setupToolbarButtonsAsync(initialScreen);
45 46
         setSupportActionBar(mToolbar);
46 47
     }
47 48
 

+ 2
- 44
android/app/src/main/java/com/reactnativenavigation/core/objects/Button.java View File

@@ -10,6 +10,7 @@ import android.view.MenuItem;
10 10
 
11 11
 import com.facebook.react.bridge.ReadableMap;
12 12
 import com.reactnativenavigation.BuildConfig;
13
+import com.reactnativenavigation.utils.IconUtils;
13 14
 import com.reactnativenavigation.utils.ResourceDrawableIdHelper;
14 15
 
15 16
 import java.io.Serializable;
@@ -24,13 +25,10 @@ import java.util.concurrent.atomic.AtomicInteger;
24 25
 public class Button extends JsonObject implements Serializable {
25 26
     private static final long serialVersionUID = -570145217281069067L;
26 27
 
27
-    public static final String LOCAL_RESOURCE_URI_SCHEME = "res";
28 28
     private static final String KEY_ID = "id";
29 29
     private static final String KEY_TITLE = "title";
30 30
     private static final String KEY_ICON = "icon";
31 31
 
32
-    private static ResourceDrawableIdHelper sResDrawableIdHelper = new ResourceDrawableIdHelper();
33
-
34 32
     public String id;
35 33
     public String title;
36 34
     private String mIconSource;
@@ -49,47 +47,7 @@ public class Button extends JsonObject implements Serializable {
49 47
     }
50 48
 
51 49
     public Drawable getIcon(Context ctx) {
52
-        if (mIconSource == null) {
53
-            return null;
54
-        }
55
-
56
-        try {
57
-            Drawable icon;
58
-            Uri iconUri = getIconUri(ctx);
59
-
60
-            if (LOCAL_RESOURCE_URI_SCHEME.equals(iconUri.getScheme())) {
61
-                icon = sResDrawableIdHelper.getResourceDrawable(ctx, mIconSource);
62
-            } else {
63
-                URL url = new URL(iconUri.toString());
64
-                Bitmap bitmap = BitmapFactory.decodeStream(url.openStream());
65
-                icon = new BitmapDrawable(bitmap);
66
-            }
67
-            return icon;
68
-        } catch (Exception e) {
69
-            if (BuildConfig.DEBUG) {
70
-                e.printStackTrace();
71
-            }
72
-        }
73
-        return null;
74
-    }
75
-
76
-    private Uri getIconUri(Context context) {
77
-        Uri ret = null;
78
-        if (mIconSource != null) {
79
-            try {
80
-                ret = Uri.parse(mIconSource);
81
-                // Verify scheme is set, so that relative uri (used by static resources) are not handled.
82
-                if (ret.getScheme() == null) {
83
-                    ret = null;
84
-                }
85
-            } catch (Exception e) {
86
-                // Ignore malformed uri, then attempt to extract resource ID.
87
-            }
88
-            if (ret == null) {
89
-                ret = sResDrawableIdHelper.getResourceDrawableUri(context, mIconSource);
90
-            }
91
-        }
92
-        return ret;
50
+       return IconUtils.getIcon(ctx, mIconSource);
93 51
     }
94 52
 
95 53
     public int getItemId() {

+ 10
- 3
android/app/src/main/java/com/reactnativenavigation/core/objects/Screen.java View File

@@ -1,5 +1,7 @@
1 1
 package com.reactnativenavigation.core.objects;
2 2
 
3
+import android.content.Context;
4
+import android.graphics.drawable.Drawable;
3 5
 import android.support.annotation.ColorInt;
4 6
 import android.support.annotation.NonNull;
5 7
 import android.support.annotation.Nullable;
@@ -7,6 +9,7 @@ import android.support.annotation.Nullable;
7 9
 import com.facebook.react.bridge.ReadableArray;
8 10
 import com.facebook.react.bridge.ReadableMap;
9 11
 import com.facebook.react.bridge.ReadableNativeMap;
12
+import com.reactnativenavigation.utils.IconUtils;
10 13
 
11 14
 import java.io.Serializable;
12 15
 import java.util.ArrayList;
@@ -37,7 +40,7 @@ public class Screen extends JsonObject implements Serializable {
37 40
     private static final String KEY_TAB_NORMAL_TEXT_COLOR = "tabNormalTextColor";
38 41
     private static final String KEY_TAB_SELECTED_TEXT_COLOR = "tabSelectedTextColor";
39 42
     private static final String KEY_TAB_INDICATOR_COLOR = "tabIndicatorColor";
40
-    public static final String KEY_PROPS = "passProps";
43
+    private static final String KEY_PROPS = "passProps";
41 44
 
42 45
     public final String title;
43 46
     public final String label;
@@ -45,7 +48,7 @@ public class Screen extends JsonObject implements Serializable {
45 48
     public final String screenInstanceId;
46 49
     public final String navigatorId;
47 50
     public final String navigatorEventId;
48
-    public final int icon;
51
+    public final String icon;
49 52
     public final ArrayList<Button> buttons;
50 53
     public HashMap<String, Object> passedProps = new HashMap<>();
51 54
 
@@ -71,7 +74,7 @@ public class Screen extends JsonObject implements Serializable {
71 74
         screenInstanceId = getString(screen, KEY_SCREEN_INSTANCE_ID);
72 75
         navigatorId = getString(screen, KEY_NAVIGATOR_ID);
73 76
         navigatorEventId = getString(screen, KEY_NAVIGATOR_EVENT_ID);
74
-        icon = getInt(screen, KEY_ICON);
77
+        icon = getString(screen, KEY_ICON);
75 78
         if(screen.hasKey(KEY_PROPS)) {
76 79
             passedProps = ((ReadableNativeMap) screen.getMap(KEY_PROPS)).toHashMap();
77 80
         }
@@ -90,6 +93,10 @@ public class Screen extends JsonObject implements Serializable {
90 93
         return ret;
91 94
     }
92 95
 
96
+    public Drawable getIcon(Context ctx) {
97
+        return IconUtils.getIcon(ctx, icon);
98
+    }
99
+
93 100
     public void setToolbarStyle(ReadableMap screen) {
94 101
         ReadableMap style = getMap(screen, KEY_TOOL_BAR_STYLE);
95 102
         if (style != null) {

+ 9
- 3
android/app/src/main/java/com/reactnativenavigation/modules/RctActivityModule.java View File

@@ -9,12 +9,15 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
9 9
 import com.facebook.react.bridge.ReactMethod;
10 10
 import com.facebook.react.bridge.ReadableArray;
11 11
 import com.facebook.react.bridge.ReadableMap;
12
+import com.facebook.react.bridge.ReadableNativeMap;
12 13
 import com.reactnativenavigation.activities.BaseReactActivity;
14
+import com.reactnativenavigation.activities.BottomTabActivity;
13 15
 import com.reactnativenavigation.activities.SingleScreenActivity;
14 16
 import com.reactnativenavigation.activities.TabActivity;
15 17
 import com.reactnativenavigation.controllers.ModalController;
16 18
 import com.reactnativenavigation.core.objects.Screen;
17 19
 import com.reactnativenavigation.modal.RnnModal;
20
+import com.reactnativenavigation.utils.BridgeUtils;
18 21
 import com.reactnativenavigation.utils.ContextProvider;
19 22
 
20 23
 import java.util.ArrayList;
@@ -35,14 +38,17 @@ public class RctActivityModule extends ReactContextBaseJavaModule {
35 38
     }
36 39
 
37 40
     @ReactMethod
38
-    public void startTabBasedApp(ReadableArray screens) {
41
+    public void startTabBasedApp(ReadableArray screens, ReadableMap style) {
39 42
         Activity context = ContextProvider.getActivityContext();
40 43
         if (context != null && !context.isFinishing()) {
41
-            Intent intent = new Intent(context, TabActivity.class);
44
+            Intent intent = new Intent(context, BottomTabActivity.class);
42 45
             intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
43 46
 
44 47
             Bundle extras = new Bundle();
45
-            extras.putSerializable(TabActivity.EXTRA_SCREENS, createScreens(screens));
48
+            extras.putSerializable(BottomTabActivity.EXTRA_SCREENS, createScreens(screens));
49
+            if (style != null) {
50
+                BridgeUtils.addMapToBundle(((ReadableNativeMap) style).toHashMap(), extras);
51
+            }
46 52
             intent.putExtras(extras);
47 53
             
48 54
             context.startActivity(intent);

+ 67
- 0
android/app/src/main/java/com/reactnativenavigation/utils/IconUtils.java View File

@@ -0,0 +1,67 @@
1
+package com.reactnativenavigation.utils;
2
+
3
+import android.content.Context;
4
+import android.graphics.Bitmap;
5
+import android.graphics.BitmapFactory;
6
+import android.graphics.drawable.BitmapDrawable;
7
+import android.graphics.drawable.Drawable;
8
+import android.net.Uri;
9
+
10
+import com.reactnativenavigation.BuildConfig;
11
+
12
+import java.net.URL;
13
+
14
+/**
15
+ * Created by yedidyak on 29/05/2016.
16
+ */
17
+public class IconUtils {
18
+
19
+
20
+    public static final String LOCAL_RESOURCE_URI_SCHEME = "res";
21
+    private static ResourceDrawableIdHelper sResDrawableIdHelper = new ResourceDrawableIdHelper();
22
+
23
+    public static Drawable getIcon(Context ctx, String iconSource) {
24
+        if (iconSource == null) {
25
+            return null;
26
+        }
27
+
28
+        try {
29
+            Drawable icon;
30
+            Uri iconUri = getIconUri(ctx, iconSource);
31
+
32
+            if (LOCAL_RESOURCE_URI_SCHEME.equals(iconUri.getScheme())) {
33
+                icon = sResDrawableIdHelper.getResourceDrawable(ctx, iconSource);
34
+            } else {
35
+                URL url = new URL(iconUri.toString());
36
+                Bitmap bitmap = BitmapFactory.decodeStream(url.openStream());
37
+                icon = new BitmapDrawable(bitmap);
38
+            }
39
+            return icon;
40
+        } catch (Exception e) {
41
+            if (BuildConfig.DEBUG) {
42
+                e.printStackTrace();
43
+            }
44
+        }
45
+        return null;
46
+    }
47
+
48
+    private static Uri getIconUri(Context context, String iconSource) {
49
+        Uri ret = null;
50
+        if (iconSource != null) {
51
+            try {
52
+                ret = Uri.parse(iconSource);
53
+                // Verify scheme is set, so that relative uri (used by static resources) are not handled.
54
+                if (ret.getScheme() == null) {
55
+                    ret = null;
56
+                }
57
+            } catch (Exception e) {
58
+                // Ignore malformed uri, then attempt to extract resource ID.
59
+            }
60
+            if (ret == null) {
61
+                ret = sResDrawableIdHelper.getResourceDrawableUri(context, iconSource);
62
+            }
63
+        }
64
+        return ret;
65
+    }
66
+
67
+}

+ 9
- 0
android/app/src/main/java/com/reactnativenavigation/views/RnnToolBar.java View File

@@ -164,6 +164,15 @@ public class RnnToolBar extends Toolbar {
164 164
 
165 165
             Menu menu = ((BaseReactActivity) context).getMenu();
166 166
 
167
+            if (menu == null) {
168
+                RnnToolBar toolBar = mToolbarWR.get();
169
+                if (toolBar != null) {
170
+                    toolBar.mSetupToolbarTask = null;
171
+                }
172
+                mToolbarWR.clear();
173
+                return;
174
+            }
175
+
167 176
             // Remove prev screen buttons
168 177
             for (Button btn : mOldButtons) {
169 178
                 menu.removeItem(btn.getItemId());

+ 33
- 0
android/app/src/main/res/layout/bottom_tab_activity.xml View File

@@ -0,0 +1,33 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+    xmlns:tools="http://schemas.android.com/tools"
4
+    xmlns:app="http://schemas.android.com/apk/res-auto"
5
+    android:orientation="vertical"
6
+    android:layout_width="match_parent"
7
+    android:layout_height="match_parent"
8
+    tools:context=".activities.TabActivity">
9
+
10
+    <android.support.design.widget.AppBarLayout
11
+        android:id="@+id/appbar"
12
+        android:layout_width="match_parent"
13
+        android:layout_height="wrap_content"
14
+        android:fitsSystemWindows="true">
15
+        <com.reactnativenavigation.views.RnnToolBar
16
+            android:id="@+id/toolbar"
17
+            android:layout_width="match_parent"
18
+            android:layout_height="?attr/actionBarSize"
19
+            app:layout_scrollFlags="scroll|enterAlways"/>
20
+    </android.support.design.widget.AppBarLayout>
21
+
22
+    <FrameLayout
23
+        android:id="@+id/contentFrame"
24
+        android:layout_width="match_parent"
25
+        android:layout_height="0dp"
26
+        android:layout_weight="1"/>
27
+
28
+    <com.aurelhubert.ahbottomnavigation.AHBottomNavigation
29
+        android:id="@+id/bottom_tab_bar"
30
+        android:layout_width="match_parent"
31
+        android:layout_height="wrap_content"/>
32
+
33
+</LinearLayout>

+ 7
- 1
src/platformSpecific.android.js View File

@@ -35,9 +35,15 @@ function startTabBasedApp(params) {
35 35
     addNavigatorParams(tab, null, idx);
36 36
     addNavigatorButtons(tab);
37 37
     addNavigationStyleParams(tab);
38
+    if (tab.icon) {
39
+      const icon = resolveAssetSource(tab.icon);
40
+      if (icon) {
41
+        tab.icon = icon.uri;
42
+      }
43
+    }
38 44
   });
39 45
 
40
-  RctActivity.startTabBasedApp(params.tabs);
46
+  RctActivity.startTabBasedApp(params.tabs, params.tabsStyle);
41 47
 }
42 48
 
43 49
 function navigatorPush(navigator, params) {