Browse Source

Android buttons (#2256)

* upstream merge

* Fixed bad merge

* More bad merges

* Mostly done, however a few properties missing and tests remain.

* async icons loading

* Left button

* Whoops!

* Added button size

* updated docs

* refactor

* Rebase fixes

* Rebase fixes

* rebase fix

* Rebase fixes

* rebase fixes

* reset contributing
Johan 6 years ago
parent
commit
2c26d4f7c3
27 changed files with 10208 additions and 38 deletions
  1. 7
    0
      AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/NavigationOptionsTest.java
  2. 7
    8
      README.md
  3. 77
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Button.java
  4. 19
    5
      lib/android/app/src/main/java/com/reactnativenavigation/parse/NavigationOptions.java
  5. 6
    4
      lib/android/app/src/main/java/com/reactnativenavigation/presentation/OptionsPresenter.java
  6. 9
    2
      lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationEvent.java
  7. 5
    0
      lib/android/app/src/main/java/com/reactnativenavigation/react/ReactView.java
  8. 66
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/ImageUtils.java
  9. 13
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/UiUtils.java
  10. 2
    1
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ContainerViewController.java
  11. 5
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsViewPager.java
  12. 2
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/Container.java
  13. 6
    1
      lib/android/app/src/main/java/com/reactnativenavigation/views/ContainerLayout.java
  14. 146
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/TitleBarButton.java
  15. 50
    2
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopBar.java
  16. 5
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopTab.java
  17. 7
    2
      lib/android/app/src/main/java/com/reactnativenavigation/views/TopTabsLayout.java
  18. 6
    1
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestContainerLayout.java
  19. 56
    0
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestContainerView.java
  20. 5
    0
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/TopTabLayoutMock.java
  21. 1
    1
      lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/OptionsApplyingTest.java
  22. 2
    1
      lib/android/app/src/test/java/com/reactnativenavigation/views/TopBarTest.java
  23. 11
    2
      lib/src/params/Button.js
  24. 12
    2
      lib/src/params/Button.test.js
  25. 2
    2
      lib/src/params/NavigationOptions.test.js
  26. 9666
    0
      package-lock.json
  27. 15
    4
      playground/src/containers/OptionsScreen.js

+ 7
- 0
AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/NavigationOptionsTest.java View File

32
 //		elementByText("SHOW TOP BAR").click();
32
 //		elementByText("SHOW TOP BAR").click();
33
 //		assertExists(By.text("Static Title"));
33
 //		assertExists(By.text("Static Title"));
34
 //	}
34
 //	}
35
+
36
+    @Test
37
+    public void testRightButtons() throws Exception {
38
+        elementByText("PUSH OPTIONS SCREEN").click();
39
+        assertExists(By.text("ONE"));
40
+        elementByText("ONE").click();
41
+    }
35
 }
42
 }

+ 7
- 8
README.md View File

103
 
103
 
104
 |       buttons        | iOS  | Android | contributors|
104
 |       buttons        | iOS  | Android | contributors|
105
 |--------------------|-----|----|-----|
105
 |--------------------|-----|----|-----|
106
-| id    |   ✅      |      [Contribute](/docs/docs/CONTRIBUTING.md)    |@Johan-dutoit|
107
-| testID   |     ✅   |    [Contribute](/docs/docs/CONTRIBUTING.md)      | @Johan-dutoit|
108
-| color             |    ✅     |      [Contribute](/docs/docs/CONTRIBUTING.md)       | @Johan-dutoit|
109
-| icon          |   ✅     |     [Contribute](/docs/docs/CONTRIBUTING.md)       | @Johan-dutoit|
110
-| disableTint        |    ✅       |     [Contribute](/docs/docs/CONTRIBUTING.md)      | @Johan-dutoit|
111
-| fontSize        |    ✅       |     [Contribute](/docs/docs/CONTRIBUTING.md)      | @Johan-dutoit |
112
-| fontWeight        |    ✅       |     [Contribute](/docs/docs/CONTRIBUTING.md)      | @Johan-dutoit |
113
-| fontWeight        |    ✅       |     [Contribute](/docs/docs/CONTRIBUTING.md)      | @Johan-dutoit |
106
+| id    |   ✅      |     @Johan-dutoit  |@Johan-dutoit|
107
+| testID   |     ✅   |   [Contribute](/docs/docs/CONTRIBUTING.md)  | @Johan-dutoit|
108
+| color             |    ✅     |     @Johan-dutoit     | @Johan-dutoit|
109
+| icon          |   ✅     |    @Johan-dutoit     | @Johan-dutoit|
110
+| disableTint        |    ✅       |    @Johan-dutoit    | @Johan-dutoit|
111
+| fontSize        |    ✅       |    @Johan-dutoit    | @Johan-dutoit |
112
+| fontWeight        |    ✅       |    [Contribute](/docs/docs/CONTRIBUTING.md)   |  [Contribute](/docs/docs/CONTRIBUTING.md) |
114
 
113
 
115
 |       statusBar        | iOS  | Android | contributors|
114
 |       statusBar        | iOS  | Android | contributors|
116
 |--------------------|-----|----|-----|
115
 |--------------------|-----|----|-----|

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

1
+package com.reactnativenavigation.parse;
2
+
3
+import android.support.annotation.ColorInt;
4
+import android.view.MenuItem;
5
+
6
+import org.json.JSONArray;
7
+import org.json.JSONObject;
8
+
9
+import java.util.ArrayList;
10
+
11
+import static com.reactnativenavigation.parse.DEFAULT_VALUES.NO_VALUE;
12
+import static com.reactnativenavigation.parse.NavigationOptions.NO_INT_VALUE;
13
+
14
+public class Button {
15
+	public String id;
16
+	public String title;
17
+	public NavigationOptions.BooleanOptions disabled;
18
+	public NavigationOptions.BooleanOptions disableIconTint;
19
+	public int showAsAction;
20
+	@ColorInt public int buttonColor;
21
+	public int buttonFontSize;
22
+	public String buttonFontWeight;
23
+	public String icon;
24
+
25
+	private static Button parseJson(JSONObject json)  {
26
+		Button button = new Button();
27
+		button.id = json.optString("id");
28
+		button.title = json.optString("title", NO_VALUE);
29
+		button.disabled = NavigationOptions.BooleanOptions.parse(json.optString("disabled", NO_VALUE));
30
+		button.disableIconTint = NavigationOptions.BooleanOptions.parse(json.optString("disableIconTint", NO_VALUE));
31
+		button.showAsAction = parseShowAsAction(json.optString("showAsAction", NO_VALUE));
32
+		button.buttonColor = json.optInt("buttonColor", NO_INT_VALUE);
33
+		button.buttonFontSize = json.optInt("buttonFontSize", NO_INT_VALUE);
34
+		button.buttonFontWeight = json.optString("buttonFontWeight", NO_VALUE);
35
+
36
+		JSONObject iconObject = json.optJSONObject("icon");
37
+		if (iconObject != null) {
38
+			button.icon = iconObject.optString("uri", NO_VALUE);
39
+		}
40
+
41
+		return button;
42
+	}
43
+
44
+	public static ArrayList<Button> parseJsonArray(JSONArray jsonArray) {
45
+		ArrayList<Button> buttons = new ArrayList<>();
46
+
47
+		if (jsonArray == null) {
48
+			return null;
49
+		}
50
+
51
+		for (int i = 0; i < jsonArray.length(); i++) {
52
+			JSONObject json = jsonArray.optJSONObject(i);
53
+			Button button =	Button.parseJson(json);
54
+			buttons.add(button);
55
+		}
56
+
57
+		return buttons;
58
+	}
59
+
60
+	private static int parseShowAsAction(String showAsAction) {
61
+		if (NO_VALUE.equals(showAsAction)) {
62
+			return MenuItem.SHOW_AS_ACTION_IF_ROOM;
63
+		}
64
+
65
+		switch (showAsAction) {
66
+			case "always":
67
+				return MenuItem.SHOW_AS_ACTION_ALWAYS;
68
+			case "never":
69
+				return MenuItem.SHOW_AS_ACTION_NEVER;
70
+			case "withText":
71
+				return MenuItem.SHOW_AS_ACTION_WITH_TEXT;
72
+			case "ifRoom":
73
+			default:
74
+				return MenuItem.SHOW_AS_ACTION_IF_ROOM;
75
+		}
76
+	}
77
+}

+ 19
- 5
lib/android/app/src/main/java/com/reactnativenavigation/parse/NavigationOptions.java View File

4
 
4
 
5
 import org.json.JSONObject;
5
 import org.json.JSONObject;
6
 
6
 
7
+import java.util.ArrayList;
8
+
7
 public class NavigationOptions implements DEFAULT_VALUES {
9
 public class NavigationOptions implements DEFAULT_VALUES {
8
 
10
 
9
     public enum BooleanOptions {
11
     public enum BooleanOptions {
33
 		result.topTabsOptions = TopTabsOptions.parse(json.optJSONObject("topTabs"));
35
 		result.topTabsOptions = TopTabsOptions.parse(json.optJSONObject("topTabs"));
34
         result.topTabOptions = TopTabOptions.parse(json.optJSONObject("topTab"));
36
         result.topTabOptions = TopTabOptions.parse(json.optJSONObject("topTab"));
35
 		result.bottomTabsOptions = BottomTabsOptions.parse(json.optJSONObject("bottomTabs"));
37
 		result.bottomTabsOptions = BottomTabsOptions.parse(json.optJSONObject("bottomTabs"));
38
+		result.rightButtons = Button.parseJsonArray(json.optJSONArray("rightButtons"));
39
+        result.leftButtons = Button.parseJsonArray(json.optJSONArray("leftButtons"));
36
 
40
 
37
 		return result.withDefaultOptions(defaultOptions);
41
 		return result.withDefaultOptions(defaultOptions);
38
 	}
42
 	}
39
 
43
 
40
-	public TopBarOptions topBarOptions = new TopBarOptions();
41
-    public TopTabsOptions topTabsOptions = new TopTabsOptions();
42
-    public TopTabOptions topTabOptions = new TopTabOptions();
43
-    public BottomTabsOptions bottomTabsOptions = new BottomTabsOptions();
44
+    @NonNull public TopBarOptions topBarOptions = new TopBarOptions();
45
+    @NonNull private TopTabsOptions topTabsOptions = new TopTabsOptions();
46
+    @NonNull public TopTabOptions topTabOptions = new TopTabOptions();
47
+    @NonNull public BottomTabsOptions bottomTabsOptions = new BottomTabsOptions();
48
+    public ArrayList<Button> leftButtons;
49
+    public ArrayList<Button> rightButtons;
44
 
50
 
45
 	public void mergeWith(final NavigationOptions other) {
51
 	public void mergeWith(final NavigationOptions other) {
46
         topBarOptions.mergeWith(other.topBarOptions);
52
         topBarOptions.mergeWith(other.topBarOptions);
47
         topTabsOptions.mergeWith(other.topTabsOptions);
53
         topTabsOptions.mergeWith(other.topTabsOptions);
48
         bottomTabsOptions.mergeWith(other.bottomTabsOptions);
54
         bottomTabsOptions.mergeWith(other.bottomTabsOptions);
49
-	}
55
+
56
+        if(other.leftButtons != null) {
57
+            leftButtons = other.leftButtons;
58
+        }
59
+
60
+        if(other.rightButtons != null) {
61
+            rightButtons = other.rightButtons;
62
+        }
63
+    }
50
 
64
 
51
     NavigationOptions withDefaultOptions(final NavigationOptions other) {
65
     NavigationOptions withDefaultOptions(final NavigationOptions other) {
52
         topBarOptions.mergeWithDefault(other.topBarOptions);
66
         topBarOptions.mergeWithDefault(other.topBarOptions);

+ 6
- 4
lib/android/app/src/main/java/com/reactnativenavigation/presentation/OptionsPresenter.java View File

3
 import android.view.View;
3
 import android.view.View;
4
 
4
 
5
 import com.reactnativenavigation.anim.StackAnimator;
5
 import com.reactnativenavigation.anim.StackAnimator;
6
+import com.reactnativenavigation.parse.Button;
6
 import com.reactnativenavigation.parse.NavigationOptions;
7
 import com.reactnativenavigation.parse.NavigationOptions;
7
 import com.reactnativenavigation.parse.TopBarOptions;
8
 import com.reactnativenavigation.parse.TopBarOptions;
8
-import com.reactnativenavigation.parse.TopTabsOptions;
9
 import com.reactnativenavigation.utils.TypefaceLoader;
9
 import com.reactnativenavigation.utils.TypefaceLoader;
10
 import com.reactnativenavigation.views.TopBar;
10
 import com.reactnativenavigation.views.TopBar;
11
 
11
 
12
+import java.util.ArrayList;
13
+
12
 public class OptionsPresenter {
14
 public class OptionsPresenter {
13
 
15
 
14
 	private final StackAnimator animator;
16
 	private final StackAnimator animator;
23
 
25
 
24
 	public void applyOptions(NavigationOptions options) {
26
 	public void applyOptions(NavigationOptions options) {
25
         applyTopBarOptions(options.topBarOptions);
27
         applyTopBarOptions(options.topBarOptions);
26
-        applyTopTabsOptions(options.topTabsOptions);
28
+        applyButtons(options.leftButtons, options.rightButtons);
27
 	}
29
 	}
28
 
30
 
29
     private void applyTopBarOptions(TopBarOptions options) {
31
     private void applyTopBarOptions(TopBarOptions options) {
64
 		}
66
 		}
65
 	}
67
 	}
66
 
68
 
67
-    private void applyTopTabsOptions(TopTabsOptions topTabsOptions) {
68
-        // TODO: -guyca
69
+    private void applyButtons(ArrayList<Button> leftButtons, ArrayList<Button> rightButtons) {
70
+        topBar.setButtons(leftButtons, rightButtons);
69
     }
71
     }
70
 }
72
 }

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

3
 import com.facebook.react.bridge.Arguments;
3
 import com.facebook.react.bridge.Arguments;
4
 import com.facebook.react.bridge.ReactContext;
4
 import com.facebook.react.bridge.ReactContext;
5
 import com.facebook.react.bridge.WritableMap;
5
 import com.facebook.react.bridge.WritableMap;
6
+import com.facebook.react.bridge.WritableNativeArray;
6
 import com.facebook.react.modules.core.DeviceEventManagerModule;
7
 import com.facebook.react.modules.core.DeviceEventManagerModule;
7
 
8
 
9
+import org.json.JSONException;
10
+import org.json.JSONObject;
11
+
8
 import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
12
 import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
9
 
13
 
10
 public class NavigationEvent {
14
 public class NavigationEvent {
32
 	}
36
 	}
33
 
37
 
34
 	public void sendOnNavigationButtonPressed(String id, String buttonId) {
38
 	public void sendOnNavigationButtonPressed(String id, String buttonId) {
35
-		//TODO!
36
-		//emit(onNavigationButtonPressed, id);
39
+		WritableMap map = Arguments.createMap();
40
+		map.putString("containerId", id);
41
+		map.putString("buttonId", buttonId);
42
+
43
+		emit(onNavigationButtonPressed, map);
37
 	}
44
 	}
38
 
45
 
39
 	private void emit(String eventName) {
46
 	private void emit(String eventName) {

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

59
 	public void sendContainerStop() {
59
 	public void sendContainerStop() {
60
 		new NavigationEvent(reactInstanceManager.getCurrentReactContext()).containerDidDisappear(containerId);
60
 		new NavigationEvent(reactInstanceManager.getCurrentReactContext()).containerDidDisappear(containerId);
61
 	}
61
 	}
62
+
63
+    @Override
64
+	public void sendOnNavigationButtonPressed(String buttonId) {
65
+		new NavigationEvent(reactInstanceManager.getCurrentReactContext()).sendOnNavigationButtonPressed(containerId, buttonId);
66
+	}
62
 }
67
 }

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

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
+import android.os.StrictMode;
10
+import android.support.annotation.NonNull;
11
+
12
+import java.io.IOException;
13
+import java.io.InputStream;
14
+import java.net.URL;
15
+
16
+public class ImageUtils {
17
+
18
+	public interface ImageLoadingListener {
19
+		void onComplete(@NonNull Drawable drawable);
20
+
21
+		void onError(Throwable error);
22
+	}
23
+
24
+	public static void tryLoadIcon(final Context context, final String uri, final ImageLoadingListener listener) {
25
+		if (uri == null) {
26
+			if (listener != null) {
27
+				listener.onError(new IllegalArgumentException("Uri is null"));
28
+			}
29
+			return;
30
+		}
31
+		runWorkerThread(new Runnable() {
32
+			@Override
33
+			public void run() {
34
+				loadIcon(context, uri, listener);
35
+			}
36
+		});
37
+	}
38
+
39
+	private static void loadIcon(Context context, String uri, final ImageLoadingListener listener) {
40
+		try {
41
+			InputStream is = null;
42
+			if (uri.contains("http")) {
43
+				URL url = new URL(uri);
44
+				is = url.openStream();
45
+			} else {
46
+				is = context.getContentResolver().openInputStream(Uri.parse(uri));
47
+			}
48
+
49
+			Bitmap bitmap = BitmapFactory.decodeStream(is);
50
+			Drawable drawable = new BitmapDrawable(context.getResources(), bitmap);
51
+			if (listener != null) {
52
+				listener.onComplete(drawable);
53
+			}
54
+		} catch (IOException e) {
55
+			if (listener != null) {
56
+				listener.onError(e);
57
+			} else {
58
+				e.printStackTrace();
59
+			}
60
+		}
61
+	}
62
+
63
+	private static void runWorkerThread(Runnable runnable) {
64
+		new Thread(runnable).start();
65
+	}
66
+}

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

1
 package com.reactnativenavigation.utils;
1
 package com.reactnativenavigation.utils;
2
 
2
 
3
+import android.graphics.PorterDuff;
4
+import android.graphics.PorterDuffColorFilter;
5
+import android.graphics.drawable.Drawable;
6
+import android.os.Handler;
7
+import android.os.Looper;
3
 import android.view.View;
8
 import android.view.View;
4
 import android.view.ViewTreeObserver;
9
 import android.view.ViewTreeObserver;
5
 
10
 
14
 			}
19
 			}
15
 		});
20
 		});
16
 	}
21
 	}
22
+
23
+	public static void tintDrawable(Drawable drawable, int tint) {
24
+		drawable.setColorFilter(new PorterDuffColorFilter(tint, PorterDuff.Mode.SRC_IN));
25
+	}
26
+
27
+	public static void runOnMainThread(Runnable runnable) {
28
+		new Handler(Looper.getMainLooper()).post(runnable);
29
+	}
17
 }
30
 }

+ 2
- 1
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ContainerViewController.java View File

29
 
29
 
30
 		void sendContainerStop();
30
 		void sendContainerStop();
31
 
31
 
32
-	}
32
+        void sendOnNavigationButtonPressed(String buttonId);
33
+    }
33
 
34
 
34
 	private final String containerName;
35
 	private final String containerName;
35
 
36
 

+ 5
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/toptabs/TopTabsViewPager.java View File

5
 import android.support.v4.view.ViewPager;
5
 import android.support.v4.view.ViewPager;
6
 import android.view.ViewGroup;
6
 import android.view.ViewGroup;
7
 
7
 
8
+import com.reactnativenavigation.viewcontrollers.ContainerViewController;
8
 import com.reactnativenavigation.viewcontrollers.ViewController;
9
 import com.reactnativenavigation.viewcontrollers.ViewController;
9
 
10
 
10
 import java.util.List;
11
 import java.util.List;
36
             tab.destroy();
37
             tab.destroy();
37
         }
38
         }
38
     }
39
     }
40
+
41
+    public void sendOnNavigationButtonPressed(String id) {
42
+        ((ContainerViewController.IReactView) tabs.get(getCurrentItem()).getView()).sendOnNavigationButtonPressed(id);
43
+    }
39
 }
44
 }

+ 2
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/Container.java View File

7
 public interface Container {
7
 public interface Container {
8
     void applyOptions(NavigationOptions options);
8
     void applyOptions(NavigationOptions options);
9
 
9
 
10
+    void sendOnNavigationButtonPressed(String id);
11
+
10
     @RestrictTo(RestrictTo.Scope.TESTS)
12
     @RestrictTo(RestrictTo.Scope.TESTS)
11
     TopBar getTopBar();
13
     TopBar getTopBar();
12
 }
14
 }

+ 6
- 1
lib/android/app/src/main/java/com/reactnativenavigation/views/ContainerLayout.java View File

19
 
19
 
20
 	public ContainerLayout(Context context, IReactView reactView) {
20
 	public ContainerLayout(Context context, IReactView reactView) {
21
 		super(context);
21
 		super(context);
22
-		this.topBar = new TopBar(context);
22
+		this.topBar = new TopBar(context, this);
23
 		this.reactView = reactView;
23
 		this.reactView = reactView;
24
         optionsPresenter = new OptionsPresenter(topBar, reactView.asView());
24
         optionsPresenter = new OptionsPresenter(topBar, reactView.asView());
25
         initViews();
25
         initViews();
62
     }
62
     }
63
 
63
 
64
 	@Override
64
 	@Override
65
+    public void sendOnNavigationButtonPressed(String buttonId) {
66
+        reactView.sendOnNavigationButtonPressed(buttonId);
67
+    }
68
+
69
+    @Override
65
     @RestrictTo(RestrictTo.Scope.TESTS)
70
     @RestrictTo(RestrictTo.Scope.TESTS)
66
     public TopBar getTopBar() {
71
     public TopBar getTopBar() {
67
         return topBar;
72
         return topBar;

+ 146
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/TitleBarButton.java View File

1
+package com.reactnativenavigation.views;
2
+
3
+import android.content.Context;
4
+import android.graphics.Color;
5
+import android.graphics.drawable.Drawable;
6
+import android.support.annotation.NonNull;
7
+import android.support.v7.widget.Toolbar;
8
+import android.text.Spannable;
9
+import android.text.SpannableString;
10
+import android.text.style.AbsoluteSizeSpan;
11
+import android.util.Log;
12
+import android.view.Menu;
13
+import android.view.MenuItem;
14
+import android.view.View;
15
+import android.widget.TextView;
16
+
17
+import com.reactnativenavigation.parse.Button;
18
+import com.reactnativenavigation.parse.NavigationOptions;
19
+import com.reactnativenavigation.utils.ImageUtils;
20
+import com.reactnativenavigation.utils.UiUtils;
21
+
22
+import java.util.ArrayList;
23
+
24
+public class TitleBarButton implements MenuItem.OnMenuItemClickListener {
25
+	private Toolbar toolbar;
26
+	private final Button button;
27
+	private Container container;
28
+	private Drawable icon;
29
+
30
+	TitleBarButton(Container container, Toolbar toolbar, Button button) {
31
+		this.container = container;
32
+		this.toolbar = toolbar;
33
+		this.button = button;
34
+	}
35
+
36
+	void addToMenu(Context context, final Menu menu) {
37
+		MenuItem menuItem = menu.add(button.title);
38
+		menuItem.setShowAsAction(button.showAsAction);
39
+		menuItem.setEnabled(button.disabled != NavigationOptions.BooleanOptions.True);
40
+		menuItem.setOnMenuItemClickListener(this);
41
+
42
+		if (hasIcon()) {
43
+			applyIcon(context, menuItem);
44
+		} else {
45
+			setTextColor();
46
+			setFontSize(menuItem);
47
+		}
48
+	}
49
+
50
+	void applyNavigationIcon(Context context) {
51
+		if (!hasIcon()) {
52
+			Log.w("RNN", "Left button needs to have an icon");
53
+			return;
54
+		}
55
+
56
+		ImageUtils.tryLoadIcon(context, button.icon, new ImageUtils.ImageLoadingListener() {
57
+			@Override
58
+			public void onComplete(@NonNull Drawable drawable) {
59
+				icon = drawable;
60
+				UiUtils.runOnMainThread(() -> {
61
+                    setIconColor();
62
+                    setNavigationClickListener();
63
+                    toolbar.setNavigationIcon(icon);
64
+                });
65
+			}
66
+
67
+			@Override
68
+			public void onError(Throwable error) {
69
+				//TODO: handle
70
+				error.printStackTrace();
71
+			}
72
+		});
73
+	}
74
+
75
+	private void applyIcon(Context context, final MenuItem menuItem) {
76
+		ImageUtils.tryLoadIcon(context, button.icon, new ImageUtils.ImageLoadingListener() {
77
+			@Override
78
+			public void onComplete(@NonNull Drawable drawable) {
79
+				icon = drawable;
80
+				UiUtils.runOnMainThread(() -> {
81
+                    menuItem.setIcon(icon);
82
+                    setIconColor();
83
+                });
84
+			}
85
+
86
+			@Override
87
+			public void onError(Throwable error) {
88
+				//TODO: handle
89
+				error.printStackTrace();
90
+			}
91
+		});
92
+	}
93
+
94
+	private void setIconColor() {
95
+		if (button.disabled == NavigationOptions.BooleanOptions.False || button.disabled == NavigationOptions.BooleanOptions.NoValue) {
96
+			UiUtils.tintDrawable(icon, button.buttonColor);
97
+			return;
98
+		}
99
+
100
+		if (button.disableIconTint == NavigationOptions.BooleanOptions.True) {
101
+			UiUtils.tintDrawable(icon, button.buttonColor);
102
+		} else {
103
+			UiUtils.tintDrawable(icon, Color.LTGRAY);
104
+		}
105
+	}
106
+
107
+	private void setTextColor() {
108
+		UiUtils.runOnPreDrawOnce(this.toolbar, () -> {
109
+            ArrayList<View> outViews = findActualTextViewInMenuByLabel();
110
+            setTextColorForFoundButtonViews(outViews);
111
+        });
112
+	}
113
+
114
+	private void setFontSize(MenuItem menuItem) {
115
+		SpannableString spanString = new SpannableString(button.title);
116
+		spanString.setSpan(new AbsoluteSizeSpan(button.buttonFontSize, true), 0, button.title.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
117
+		menuItem.setTitleCondensed(spanString);
118
+	}
119
+
120
+	private void setNavigationClickListener() {
121
+		toolbar.setNavigationOnClickListener(view -> container.sendOnNavigationButtonPressed(button.id));
122
+	}
123
+
124
+	@Override
125
+	public boolean onMenuItemClick(MenuItem menuItem) {
126
+		this.container.sendOnNavigationButtonPressed(button.id);
127
+		return true;
128
+	}
129
+
130
+	@NonNull
131
+	private ArrayList<View> findActualTextViewInMenuByLabel() {
132
+		ArrayList<View> outViews = new ArrayList<>();
133
+		this.toolbar.findViewsWithText(outViews, button.title, View.FIND_VIEWS_WITH_TEXT);
134
+		return outViews;
135
+	}
136
+
137
+	private void setTextColorForFoundButtonViews(ArrayList<View> buttons) {
138
+		for (View button : buttons) {
139
+			((TextView) button).setTextColor(this.button.buttonColor);
140
+		}
141
+	}
142
+
143
+	private boolean hasIcon() {
144
+		return button.icon != null;
145
+	}
146
+}

+ 50
- 2
lib/android/app/src/main/java/com/reactnativenavigation/views/TopBar.java View File

1
 package com.reactnativenavigation.views;
1
 package com.reactnativenavigation.views;
2
 
2
 
3
+import android.annotation.SuppressLint;
3
 import android.content.Context;
4
 import android.content.Context;
4
 import android.graphics.Typeface;
5
 import android.graphics.Typeface;
5
 import android.support.annotation.ColorInt;
6
 import android.support.annotation.ColorInt;
6
 import android.support.annotation.Nullable;
7
 import android.support.annotation.Nullable;
7
 import android.support.design.widget.AppBarLayout;
8
 import android.support.design.widget.AppBarLayout;
8
 import android.support.v7.widget.Toolbar;
9
 import android.support.v7.widget.Toolbar;
10
+import android.util.Log;
11
+import android.view.Menu;
9
 import android.view.View;
12
 import android.view.View;
10
 import android.view.ViewGroup;
13
 import android.view.ViewGroup;
11
 import android.widget.TextView;
14
 import android.widget.TextView;
12
 
15
 
16
+import com.reactnativenavigation.parse.Button;
13
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsViewPager;
17
 import com.reactnativenavigation.viewcontrollers.toptabs.TopTabsViewPager;
14
 
18
 
19
+import java.util.ArrayList;
20
+
21
+@SuppressLint("ViewConstructor")
15
 public class TopBar extends AppBarLayout {
22
 public class TopBar extends AppBarLayout {
16
 	private final Toolbar titleBar;
23
 	private final Toolbar titleBar;
17
-	private TopTabs topTabs;
24
+    private Container container;
25
+    private TopTabs topTabs;
18
 
26
 
19
-    public TopBar(final Context context) {
27
+    public TopBar(final Context context, Container container) {
20
         super(context);
28
         super(context);
21
         titleBar = new Toolbar(context);
29
         titleBar = new Toolbar(context);
30
+        this.container = container;
22
         addView(titleBar);
31
         addView(titleBar);
23
     }
32
     }
24
 
33
 
48
 		}
57
 		}
49
 	}
58
 	}
50
 
59
 
60
+	public void setButtons(ArrayList<Button> leftButtons, ArrayList<Button> rightButtons) {
61
+		setLeftButtons(leftButtons);
62
+		setRightButtons(rightButtons);
63
+    }
64
+
51
 	public TextView getTitleTextView() {
65
 	public TextView getTitleTextView() {
52
 		return findTextView(titleBar);
66
 		return findTextView(titleBar);
53
 	}
67
 	}
71
 		return null;
85
 		return null;
72
 	}
86
 	}
73
 
87
 
88
+	private void setLeftButtons(ArrayList<Button> leftButtons) {
89
+		if(leftButtons == null || leftButtons.isEmpty()) {
90
+			titleBar.setNavigationIcon(null);
91
+			return;
92
+		}
93
+
94
+		if(leftButtons.size() > 1) {
95
+			Log.w("RNN", "Use a custom TopBar to have more than one left button");
96
+		}
97
+
98
+		Button leftButton = leftButtons.get(0);
99
+		setLeftButton(leftButton);
100
+	}
101
+
102
+	private void setLeftButton(final Button button) {
103
+		TitleBarButton leftBarButton = new TitleBarButton(container, this.titleBar, button);
104
+		leftBarButton.applyNavigationIcon(getContext());
105
+	}
106
+
107
+	private void setRightButtons(ArrayList<Button> rightButtons) {
108
+		if(rightButtons == null || rightButtons.size() == 0) {
109
+			return;
110
+		}
111
+
112
+		Menu menu = getTitleBar().getMenu();
113
+		menu.clear();
114
+
115
+		for (int i = 0; i < rightButtons.size(); i++){
116
+	   		Button button = rightButtons.get(i);
117
+			TitleBarButton titleBarButton = new TitleBarButton(container, this.titleBar, button);
118
+			titleBarButton.addToMenu(getContext(), menu);
119
+       }
120
+    }
121
+
74
 	public Toolbar getTitleBar() {
122
 	public Toolbar getTitleBar() {
75
 		return titleBar;
123
 		return titleBar;
76
 	}
124
 	}

+ 5
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/TopTab.java View File

42
 	public void sendContainerStop() {
42
 	public void sendContainerStop() {
43
 		reactView.sendContainerStop();
43
 		reactView.sendContainerStop();
44
 	}
44
 	}
45
+
46
+    @Override
47
+    public void sendOnNavigationButtonPressed(String buttonId) {
48
+        reactView.sendOnNavigationButtonPressed(buttonId);
49
+    }
45
 }
50
 }

+ 7
- 2
lib/android/app/src/main/java/com/reactnativenavigation/views/TopTabsLayout.java View File

23
 
23
 
24
     public TopTabsLayout(Context context, List<TopTabController> tabs, TopTabsAdapter adapter) {
24
     public TopTabsLayout(Context context, List<TopTabController> tabs, TopTabsAdapter adapter) {
25
         super(context);
25
         super(context);
26
-        topBar = new TopBar(context);
27
-        optionsPresenter = new OptionsPresenter(topBar, viewPager);
26
+        topBar = new TopBar(context, this);
28
         viewPager = new TopTabsViewPager(context, tabs, adapter);
27
         viewPager = new TopTabsViewPager(context, tabs, adapter);
28
+        optionsPresenter = new OptionsPresenter(topBar, viewPager);
29
         initViews();
29
         initViews();
30
     }
30
     }
31
 
31
 
41
         optionsPresenter.applyOptions(options);
41
         optionsPresenter.applyOptions(options);
42
     }
42
     }
43
 
43
 
44
+    @Override
45
+    public void sendOnNavigationButtonPressed(String id) {
46
+        viewPager.sendOnNavigationButtonPressed(id);
47
+    }
48
+
44
     @Override
49
     @Override
45
     @RestrictTo(RestrictTo.Scope.TESTS)
50
     @RestrictTo(RestrictTo.Scope.TESTS)
46
     public TopBar getTopBar() {
51
     public TopBar getTopBar() {

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

15
 
15
 
16
     public TestContainerLayout(final Context context) {
16
     public TestContainerLayout(final Context context) {
17
 		super(context);
17
 		super(context);
18
-        topBar = new TopBar(context);
18
+        topBar = new TopBar(context, this);
19
         optionsPresenter = new OptionsPresenter(topBar, new View(context));
19
         optionsPresenter = new OptionsPresenter(topBar, new View(context));
20
     }
20
     }
21
 
21
 
49
     public void applyOptions(NavigationOptions options) {
49
     public void applyOptions(NavigationOptions options) {
50
         optionsPresenter.applyOptions(options);
50
         optionsPresenter.applyOptions(options);
51
     }
51
     }
52
+
53
+    @Override
54
+    public void sendOnNavigationButtonPressed(String id) {
55
+
56
+    }
52
 }
57
 }

+ 56
- 0
lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestContainerView.java View File

1
+package com.reactnativenavigation.mocks;
2
+
3
+import android.content.Context;
4
+import android.view.View;
5
+
6
+import com.reactnativenavigation.parse.NavigationOptions;
7
+import com.reactnativenavigation.views.ReactContainer;
8
+import com.reactnativenavigation.views.TopBar;
9
+
10
+public class TestContainerView extends View implements ReactContainer {
11
+
12
+	private TopBar topBar;
13
+
14
+	public TestContainerView(final Context context) {
15
+		super(context);
16
+		topBar = new TopBar(context, this);
17
+
18
+	}
19
+
20
+	@Override
21
+	public boolean isReady() {
22
+		return false;
23
+	}
24
+
25
+	@Override
26
+	public View asView() {
27
+		return this;
28
+	}
29
+
30
+	@Override
31
+	public void destroy() {
32
+	}
33
+
34
+	@Override
35
+	public void sendContainerStart() {
36
+	}
37
+
38
+	@Override
39
+	public void sendContainerStop() {
40
+	}
41
+
42
+    @Override
43
+    public void applyOptions(NavigationOptions options) {
44
+
45
+    }
46
+
47
+    @Override
48
+    public void sendOnNavigationButtonPressed(String id) {
49
+
50
+    }
51
+
52
+    @Override
53
+    public TopBar getTopBar() {
54
+        return topBar;
55
+    }
56
+}

+ 5
- 0
lib/android/app/src/test/java/com/reactnativenavigation/mocks/TopTabLayoutMock.java View File

35
     public void sendContainerStop() {
35
     public void sendContainerStop() {
36
 
36
 
37
     }
37
     }
38
+
39
+    @Override
40
+    public void sendOnNavigationButtonPressed(String buttonId) {
41
+
42
+    }
38
 }
43
 }

+ 1
- 1
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/OptionsApplyingTest.java View File

134
 
134
 
135
 		assertThat(uut.getTopBar().getVisibility()).isEqualTo(View.GONE);
135
 		assertThat(uut.getTopBar().getVisibility()).isEqualTo(View.GONE);
136
 	}
136
 	}
137
-}
137
+}

+ 2
- 1
lib/android/app/src/test/java/com/reactnativenavigation/views/TopBarTest.java View File

9
 public class TopBarTest extends BaseTest {
9
 public class TopBarTest extends BaseTest {
10
 	@Test
10
 	@Test
11
 	public void title() throws Exception {
11
 	public void title() throws Exception {
12
-		TopBar topBar = new TopBar(newActivity());
12
+		//TODO:  fix null
13
+		TopBar topBar = new TopBar(newActivity(), null);
13
 		assertThat(topBar.getTitle()).isEmpty();
14
 		assertThat(topBar.getTitle()).isEmpty();
14
 
15
 
15
 		topBar.setTitle("new title");
16
 		topBar.setTitle("new title");

+ 11
- 2
lib/src/params/Button.js View File

3
    * @property {String} id
3
    * @property {String} id
4
    * @property {string} [testID]
4
    * @property {string} [testID]
5
    * @property {string} [title]
5
    * @property {string} [title]
6
-   * @property {string} [color]
6
+   * @property {string} [buttonColor]
7
+   * @property {string} [showAsAction]
8
+   * @property {int} [buttonFontWeight]
9
+   * @property {boolean} [disableIconTint]
10
+   * @property {boolean} [disabled]
7
    */
11
    */
8
   constructor(params) {
12
   constructor(params) {
9
     this.id = params.id;
13
     this.id = params.id;
10
     this.testID = params.testID;
14
     this.testID = params.testID;
11
     this.title = params.title;
15
     this.title = params.title;
12
-    this.color = params.color;
16
+    this.buttonColor = params.buttonColor;
17
+    this.showAsAction = params.showAsAction;
18
+    this.buttonFontWeight = params.buttonFontWeight;
19
+    this.buttonFontSize = params.buttonFontSize;
20
+    this.disableIconTint = params.disableIconTint;
21
+    this.disabled = params.disabled;
13
   }
22
   }
14
 }
23
 }
15
 
24
 

+ 12
- 2
lib/src/params/Button.test.js View File

4
   id: 'myBtn',
4
   id: 'myBtn',
5
   testID: 'BTN',
5
   testID: 'BTN',
6
   title: 'My Button',
6
   title: 'My Button',
7
-  color: 'red'
7
+  buttonColor: 'red',
8
+  buttonFontSize: 16,
9
+  buttonFontWeight: 300,
10
+  showAsAction: 'never',
11
+  disableIconTint: true,
12
+  disabled: true
8
 };
13
 };
9
 
14
 
10
 describe('Button', () => {
15
 describe('Button', () => {
13
     expect(uut.id).toEqual('myBtn');
18
     expect(uut.id).toEqual('myBtn');
14
     expect(uut.testID).toEqual('BTN');
19
     expect(uut.testID).toEqual('BTN');
15
     expect(uut.title).toEqual('My Button');
20
     expect(uut.title).toEqual('My Button');
16
-    expect(uut.color).toEqual('red');
21
+    expect(uut.buttonColor).toEqual('red');
22
+    expect(uut.showAsAction).toEqual('never');
23
+    expect(uut.buttonFontWeight).toEqual(300);
24
+    expect(uut.buttonFontSize).toEqual(16);
25
+    expect(uut.disableIconTint).toEqual(true);
26
+    expect(uut.disabled).toEqual(true);
17
   });
27
   });
18
 });
28
 });

+ 2
- 2
lib/src/params/NavigationOptions.test.js View File

9
     id: 'myBtn',
9
     id: 'myBtn',
10
     testID: 'BTN',
10
     testID: 'BTN',
11
     title: 'My Button',
11
     title: 'My Button',
12
-    color: 'red'
12
+    buttonColor: 'red'
13
   }
13
   }
14
 ];
14
 ];
15
 const LEFT_BUTTONS = [
15
 const LEFT_BUTTONS = [
17
     id: 'myBtn',
17
     id: 'myBtn',
18
     testID: 'BTN',
18
     testID: 'BTN',
19
     title: 'My Button',
19
     title: 'My Button',
20
-    color: 'red'
20
+    buttonColor: 'red'
21
   }
21
   }
22
 ];
22
 ];
23
 const NAVIGATION_OPTIONS = {
23
 const NAVIGATION_OPTIONS = {

+ 9666
- 0
package-lock.json
File diff suppressed because it is too large
View File


+ 15
- 4
playground/src/containers/OptionsScreen.js View File

26
         id: BUTTON_ONE,
26
         id: BUTTON_ONE,
27
         testID: BUTTON_ONE,
27
         testID: BUTTON_ONE,
28
         title: 'One',
28
         title: 'One',
29
+        buttonFontSize: 28,
29
         buttonColor: 'red'
30
         buttonColor: 'red'
30
       }],
31
       }],
31
       leftButtons: [{
32
       leftButtons: [{
32
         id: BUTTON_LEFT,
33
         id: BUTTON_LEFT,
33
         testID: BUTTON_LEFT,
34
         testID: BUTTON_LEFT,
35
+        icon: require('../../img/navicon_add.png'),
34
         title: 'Left',
36
         title: 'Left',
35
         buttonColor: 'purple'
37
         buttonColor: 'purple'
36
       }]
38
       }]
73
           id: BUTTON_TWO,
75
           id: BUTTON_TWO,
74
           testID: BUTTON_TWO,
76
           testID: BUTTON_TWO,
75
           title: 'Two',
77
           title: 'Two',
76
-          // icon: require('../../img/navicon_add.png'),
77
-          // disableIconTint: true,
78
-          // disabled: true
78
+          icon: require('../../img/navicon_add.png'),
79
+          disableIconTint: true,
80
+          // disabled: true,
81
+          showAsAction: 'ifRoom',
79
           buttonColor: 'green',
82
           buttonColor: 'green',
80
           buttonFontSize: 28,
83
           buttonFontSize: 28,
81
           buttonFontWeight: '800'
84
           buttonFontWeight: '800'
82
-        }]
85
+        }],
86
+        leftButtons: []
83
       });
87
       });
84
     } else if (id === BUTTON_TWO) {
88
     } else if (id === BUTTON_TWO) {
85
       Navigation.setOptions(this.props.containerId, {
89
       Navigation.setOptions(this.props.containerId, {
88
           testID: BUTTON_ONE,
92
           testID: BUTTON_ONE,
89
           title: 'One',
93
           title: 'One',
90
           buttonColor: 'red'
94
           buttonColor: 'red'
95
+        }],
96
+        leftButtons: [{
97
+          id: BUTTON_LEFT,
98
+          testID: BUTTON_LEFT,
99
+          icon: require('../../img/navicon_add.png'),
100
+          title: 'Left',
101
+          buttonColor: 'purple'
91
         }]
102
         }]
92
       });
103
       });
93
     } else if (id === BUTTON_LEFT) {
104
     } else if (id === BUTTON_LEFT) {