diff --git a/android/app/src/main/java/com/reactnativenavigation/animation/VisibilityAnimator.java b/android/app/src/main/java/com/reactnativenavigation/animation/VisibilityAnimator.java index 81afaa0f15a..dec6bd150ef 100644 --- a/android/app/src/main/java/com/reactnativenavigation/animation/VisibilityAnimator.java +++ b/android/app/src/main/java/com/reactnativenavigation/animation/VisibilityAnimator.java @@ -1,6 +1,9 @@ package com.reactnativenavigation.animation; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; +import android.support.annotation.Nullable; import android.support.v4.view.animation.LinearOutSlowInInterpolator; import android.view.View; @@ -24,12 +27,12 @@ public VisibilityAnimator(View view, HideDirection hideDirection, int height) { this.hiddenEndValue = hideDirection == HideDirection.Up ? -height : height; } - public void setVisible(boolean visible, boolean animate) { + public void setVisible(boolean visible, boolean animate, @Nullable Runnable onAnimationEnd) { cancelAnimator(); if (visible) { - show(animate); + show(animate, onAnimationEnd); } else { - hide(animate); + hide(animate, onAnimationEnd); } } @@ -40,31 +43,39 @@ private void cancelAnimator() { } } - private void show(boolean animate) { + private void show(boolean animate, @Nullable Runnable onAnimationEnd) { if (animate) { - animator = createAnimator(true); + animator = createAnimator(true, onAnimationEnd); animator.start(); } else { view.setTranslationY(SHOW_END_VALUE); view.setVisibility(View.VISIBLE); + if (onAnimationEnd != null) onAnimationEnd.run(); } } - private void hide(boolean animate) { + private void hide(boolean animate, @Nullable Runnable onAnimationEnd) { if (animate) { - animator = createAnimator(false); + animator = createAnimator(false, onAnimationEnd); animator.start(); } else { view.setTranslationY(hiddenEndValue); view.setVisibility(View.GONE); + if (onAnimationEnd != null) onAnimationEnd.run(); } } - private ObjectAnimator createAnimator(final boolean show) { + private ObjectAnimator createAnimator(final boolean show, @Nullable final Runnable onAnimationEnd) { view.setVisibility(View.VISIBLE); final ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, show ? SHOW_END_VALUE : hiddenEndValue); animator.setDuration(DURATION); animator.setInterpolator(interpolator); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (onAnimationEnd != null) onAnimationEnd.run(); + } + }); return animator; } } diff --git a/android/app/src/main/java/com/reactnativenavigation/controllers/Modal.java b/android/app/src/main/java/com/reactnativenavigation/controllers/Modal.java index 8be486a9567..f4c0a5982f2 100644 --- a/android/app/src/main/java/com/reactnativenavigation/controllers/Modal.java +++ b/android/app/src/main/java/com/reactnativenavigation/controllers/Modal.java @@ -19,10 +19,12 @@ import com.reactnativenavigation.params.Orientation; import com.reactnativenavigation.params.ScreenParams; import com.reactnativenavigation.params.SlidingOverlayParams; +import com.reactnativenavigation.params.StyleParams; import com.reactnativenavigation.params.TitleBarButtonParams; import com.reactnativenavigation.params.TitleBarLeftButtonParams; import com.reactnativenavigation.params.parsers.ModalAnimationFactory; import com.reactnativenavigation.screens.NavigationType; +import com.reactnativenavigation.utils.StatusBar; import java.util.List; @@ -112,6 +114,14 @@ interface OnModalDismissedListener { this.screenParams = screenParams; createContent(); setAnimation(screenParams); + setStatusBarStyle(screenParams.styleParams); + } + + private void setStatusBarStyle(StyleParams styleParams) { + Window window = getWindow(); + if (window == null) return; + StatusBar.setColor(window, styleParams.statusBarColor); + StatusBar.setTextColorScheme(window.getDecorView(), styleParams.statusBarTextColorScheme); } public AppCompatActivity getActivity() { diff --git a/android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java b/android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java index 93ae16fdbfa..7ac1a989fed 100644 --- a/android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java +++ b/android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java @@ -176,13 +176,20 @@ private void destroyJsIfNeeded() { @Override public void invokeDefaultOnBackPressed() { - super.onBackPressed(); + if (layout != null && !layout.onBackPressed()) { + super.onBackPressed(); + } } @Override public void onBackPressed() { - if (layout != null && !layout.onBackPressed()) { + if (layout != null && layout.handleBackInJs()) { + return; + } + if (getReactGateway().isInitialized()) { getReactGateway().onBackPressed(); + } else { + super.onBackPressed(); } } diff --git a/android/app/src/main/java/com/reactnativenavigation/layouts/BottomTabsLayout.java b/android/app/src/main/java/com/reactnativenavigation/layouts/BottomTabsLayout.java index 179fa1ee906..3c59cec8cc9 100644 --- a/android/app/src/main/java/com/reactnativenavigation/layouts/BottomTabsLayout.java +++ b/android/app/src/main/java/com/reactnativenavigation/layouts/BottomTabsLayout.java @@ -6,6 +6,7 @@ import android.support.annotation.Nullable; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.AppCompatActivity; +import android.util.Log; import android.view.View; import android.widget.RelativeLayout; @@ -31,6 +32,7 @@ import com.reactnativenavigation.screens.NavigationType; import com.reactnativenavigation.screens.Screen; import com.reactnativenavigation.screens.ScreenStack; +import com.reactnativenavigation.utils.Task; import com.reactnativenavigation.utils.ViewUtils; import com.reactnativenavigation.views.BottomTabs; import com.reactnativenavigation.views.LightBox; @@ -150,7 +152,7 @@ public View asView() { @Override public boolean onBackPressed() { - if (getCurrentScreenStack().handleBackPressInJs()) { + if (handleBackInJs()) { return true; } @@ -164,6 +166,11 @@ public boolean onBackPressed() { } } + @Override + public boolean handleBackInJs() { + return getCurrentScreenStack().handleBackPressInJs(); + } + @Override public void setTopBarVisible(String screenInstanceId, boolean hidden, boolean animated) { for (int i = 0; i < bottomTabs.getItemsCount(); i++) { @@ -172,6 +179,7 @@ public void setTopBarVisible(String screenInstanceId, boolean hidden, boolean an } public void setBottomTabsVisible(boolean hidden, boolean animated) { + getCurrentScreenStack().peek().updateBottomTabsVisibility(hidden); bottomTabs.setVisibility(hidden, animated); } @@ -332,8 +340,13 @@ public void selectBottomTabByTabIndex(Integer index) { bottomTabs.setCurrentItem(index); } - public void selectBottomTabByNavigatorId(String navigatorId) { - bottomTabs.setCurrentItem(getScreenStackIndex(navigatorId)); + public void selectBottomTabByNavigatorId(final String navigatorId) { + performOnStack(navigatorId, new Task() { + @Override + public void run(ScreenStack param) { + bottomTabs.setCurrentItem(getScreenStackIndex(navigatorId)); + } + }); } private boolean hasBackgroundColor(StyleParams params) { @@ -351,14 +364,18 @@ private void setStyleFromScreen(StyleParams params) { } @Override - public void push(ScreenParams params) { - ScreenStack screenStack = getScreenStack(params.getNavigatorId()); - screenStack.push(params, createScreenLayoutParams(params)); - setStyleFromScreen(params.styleParams); - if (isCurrentStack(screenStack)) { - alignSnackbarContainerWithBottomTabs((LayoutParams) snackbarAndFabContainer.getLayoutParams(), params.styleParams); - EventBus.instance.post(new ScreenChangedEvent(params)); - } + public void push(final ScreenParams params) { + performOnStack(params.getNavigatorId(), new Task() { + @Override + public void run(ScreenStack screenStack) { + screenStack.push(params, createScreenLayoutParams(params)); + setStyleFromScreen(params.styleParams); + if (isCurrentStack(screenStack)) { + alignSnackbarContainerWithBottomTabs((LayoutParams) snackbarAndFabContainer.getLayoutParams(), params.styleParams); + EventBus.instance.post(new ScreenChangedEvent(params)); + } + } + }); } @Override @@ -387,13 +404,17 @@ public void onScreenPopAnimationEnd() { @Override public void newStack(final ScreenParams params) { - ScreenStack screenStack = getScreenStack(params.getNavigatorId()); - screenStack.newStack(params, createScreenLayoutParams(params)); - if (isCurrentStack(screenStack)) { - setStyleFromScreen(params.styleParams); - alignSnackbarContainerWithBottomTabs((LayoutParams) snackbarAndFabContainer.getLayoutParams(), params.styleParams); - EventBus.instance.post(new ScreenChangedEvent(params)); - } + performOnStack(params.getNavigatorId(), new Task() { + @Override + public void run(ScreenStack screenStack) { + screenStack.newStack(params, createScreenLayoutParams(params)); + if (isCurrentStack(screenStack)) { + setStyleFromScreen(params.styleParams); + alignSnackbarContainerWithBottomTabs((LayoutParams) snackbarAndFabContainer.getLayoutParams(), params.styleParams); + EventBus.instance.post(new ScreenChangedEvent(params)); + } + } + }); } private void alignSnackbarContainerWithBottomTabs(LayoutParams lp, StyleParams styleParams) { @@ -404,6 +425,17 @@ private void alignSnackbarContainerWithBottomTabs(LayoutParams lp, StyleParams s } } + private void performOnStack(String navigatorId, Task task) { + try { + ScreenStack screenStack = getScreenStack(navigatorId); + task.run(screenStack); + } catch (ScreenStackNotFoundException e) { + Log.e("Navigation", "Could not perform action on stack [" + navigatorId + "]." + + "This should not have happened, it probably means a navigator action" + + "was called from an unmounted tab."); + } + } + @Override public void destroy() { snackbarAndFabContainer.destroy(); @@ -478,7 +510,7 @@ private ScreenStack getCurrentScreenStack() { private @NonNull - ScreenStack getScreenStack(String navigatorId) { + ScreenStack getScreenStack(String navigatorId) throws ScreenStackNotFoundException { int index = getScreenStackIndex(navigatorId); return screenStacks[index]; } diff --git a/android/app/src/main/java/com/reactnativenavigation/layouts/Layout.java b/android/app/src/main/java/com/reactnativenavigation/layouts/Layout.java index 53613756cd8..51d7bbe4728 100644 --- a/android/app/src/main/java/com/reactnativenavigation/layouts/Layout.java +++ b/android/app/src/main/java/com/reactnativenavigation/layouts/Layout.java @@ -21,6 +21,8 @@ public interface Layout extends ScreenStackContainer { boolean onBackPressed(); + boolean handleBackInJs(); + void setTopBarVisible(String screenInstanceId, boolean hidden, boolean animated); void setTitleBarTitle(String screenInstanceId, String title); diff --git a/android/app/src/main/java/com/reactnativenavigation/layouts/SingleScreenLayout.java b/android/app/src/main/java/com/reactnativenavigation/layouts/SingleScreenLayout.java index 571e59f30c3..80b20891fad 100644 --- a/android/app/src/main/java/com/reactnativenavigation/layouts/SingleScreenLayout.java +++ b/android/app/src/main/java/com/reactnativenavigation/layouts/SingleScreenLayout.java @@ -109,7 +109,7 @@ private void createFabAndSnackbarContainer() { @Override public boolean onBackPressed() { - if (stack.handleBackPressInJs()) { + if (handleBackInJs()) { return true; } @@ -122,6 +122,11 @@ public boolean onBackPressed() { } } + @Override + public boolean handleBackInJs() { + return stack.handleBackPressInJs(); + } + @Override public void destroy() { stack.destroy(); diff --git a/android/app/src/main/java/com/reactnativenavigation/params/StatusBarTextColorScheme.java b/android/app/src/main/java/com/reactnativenavigation/params/StatusBarTextColorScheme.java index f96c87ae328..ae903e11633 100644 --- a/android/app/src/main/java/com/reactnativenavigation/params/StatusBarTextColorScheme.java +++ b/android/app/src/main/java/com/reactnativenavigation/params/StatusBarTextColorScheme.java @@ -6,15 +6,15 @@ public enum StatusBarTextColorScheme { Light, Dark, Undefined; - public static StatusBarTextColorScheme fromString(@Nullable String colorScheme) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || colorScheme == null) return Undefined; + public static StatusBarTextColorScheme fromString(@Nullable String colorScheme, StatusBarTextColorScheme defaultScheme) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || colorScheme == null) return defaultScheme; switch (colorScheme) { case "light": return Light; case "dark": return Dark; default: - return Undefined; + return defaultScheme; } } } diff --git a/android/app/src/main/java/com/reactnativenavigation/params/StyleParams.java b/android/app/src/main/java/com/reactnativenavigation/params/StyleParams.java index 3ff19f00ad2..6ac36affcbd 100644 --- a/android/app/src/main/java/com/reactnativenavigation/params/StyleParams.java +++ b/android/app/src/main/java/com/reactnativenavigation/params/StyleParams.java @@ -52,8 +52,10 @@ public int getColor(int defaultColor) { public static class Font { private Typeface typeface; + String fontFamilyName; public Font(String font) { + fontFamilyName = font; typeface = new TypefaceLoader(font).getTypeFace(); } @@ -61,7 +63,7 @@ public Font() { } public boolean hasFont() { - return typeface != null; + return typeface != null && fontFamilyName != null; } public Typeface get() { @@ -70,6 +72,11 @@ public Typeface get() { } return typeface; } + + @Override + public String toString() { + return fontFamilyName; + } } public Orientation orientation; @@ -98,6 +105,8 @@ public Typeface get() { public boolean topBarTranslucent; public Color titleBarTitleColor; public Color titleBarSubtitleColor; + public int titleBarSubtitleFontSize; + public Font titleBarSubtitleFontFamily; public Color titleBarButtonColor; public Color titleBarDisabledButtonColor; public Font titleBarTitleFont; @@ -106,6 +115,7 @@ public Typeface get() { public boolean titleBarTitleTextCentered; public int titleBarHeight; public boolean backButtonHidden; + public Font titleBarButtonFontFamily; public Color topTabTextColor; public Color topTabIconColor; diff --git a/android/app/src/main/java/com/reactnativenavigation/params/TitleBarButtonParams.java b/android/app/src/main/java/com/reactnativenavigation/params/TitleBarButtonParams.java index 9b8a69e7083..d8017f935c7 100644 --- a/android/app/src/main/java/com/reactnativenavigation/params/TitleBarButtonParams.java +++ b/android/app/src/main/java/com/reactnativenavigation/params/TitleBarButtonParams.java @@ -7,7 +7,7 @@ public class TitleBarButtonParams extends BaseTitleBarButtonParams { @Override public void setStyleFromScreen(StyleParams styleParams) { super.setStyleFromScreen(styleParams); - font = styleParams.titleBarTitleFont; + font = styleParams.titleBarButtonFontFamily.hasFont() ? styleParams.titleBarButtonFontFamily : styleParams.titleBarTitleFont; } public boolean hasFont() { diff --git a/android/app/src/main/java/com/reactnativenavigation/params/parsers/StyleParamsParser.java b/android/app/src/main/java/com/reactnativenavigation/params/parsers/StyleParamsParser.java index 349635afbfd..92a1be3c8f0 100644 --- a/android/app/src/main/java/com/reactnativenavigation/params/parsers/StyleParamsParser.java +++ b/android/app/src/main/java/com/reactnativenavigation/params/parsers/StyleParamsParser.java @@ -30,7 +30,7 @@ public StyleParams parse() { result.orientation = Orientation.fromString(params.getString("orientation", getDefaultOrientation())); result.statusBarColor = getColor("statusBarColor", getDefaultStatusBarColor()); result.statusBarHidden = getBoolean("statusBarHidden", getDefaultStatusHidden()); - result.statusBarTextColorScheme = StatusBarTextColorScheme.fromString(params.getString("statusBarTextColorScheme")); + result.statusBarTextColorScheme = StatusBarTextColorScheme.fromString(params.getString("statusBarTextColorScheme"), getDefaultStatusBarTextColorScheme()); result.contextualMenuStatusBarColor = getColor("contextualMenuStatusBarColor", getDefaultContextualMenuStatusBarColor()); result.contextualMenuButtonsColor = getColor("contextualMenuButtonsColor", getDefaultContextualMenuButtonsColor()); result.contextualMenuBackgroundColor = getColor("contextualMenuBackgroundColor", getDefaultContextualMenuBackgroundColor()); @@ -55,7 +55,10 @@ public StyleParams parse() { result.topBarBorderWidth = Float.parseFloat(params.getString("topBarBorderWidth", getDefaultTopBarBorderWidth())); result.titleBarSubtitleColor = getColor("titleBarSubtitleColor", getDefaultSubtitleBarColor()); + result.titleBarSubtitleFontSize = getInt("titleBarSubtitleFontSize", getDefaultSubtitleTextFontSize()); + result.titleBarSubtitleFontFamily = getFont("titleBarSubtitleFontFamily", getDefaultSubtitleFontFamily()); result.titleBarButtonColor = getColor("titleBarButtonColor", getTitleBarButtonColor()); + result.titleBarButtonFontFamily = getFont("titleBarButtonFontFamily", getDefaultTitleBarButtonFont()); result.titleBarDisabledButtonColor = getColor("titleBarDisabledButtonColor", getTitleBarDisabledButtonColor()); result.titleBarTitleFont = getFont("titleBarTitleFontFamily", getDefaultTitleTextFontFamily()); result.titleBarTitleFontSize = getInt("titleBarTitleFontSize", getDefaultTitleTextFontSize()); @@ -100,6 +103,10 @@ public StyleParams parse() { return result; } + private StatusBarTextColorScheme getDefaultStatusBarTextColorScheme() { + return AppStyle.appStyle == null ? StatusBarTextColorScheme.Undefined : AppStyle.appStyle.statusBarTextColorScheme; + } + private String getDefaultOrientation() { return AppStyle.appStyle == null ? null : AppStyle.appStyle.orientation.name; } @@ -111,6 +118,10 @@ private StyleParams createDefaultStyleParams() { result.titleBarHideOnScroll = false; result.orientation = Orientation.auto; result.bottomTabFontFamily = new StyleParams.Font(); + result.titleBarTitleFont = new StyleParams.Font(); + result.titleBarSubtitleFontFamily = new StyleParams.Font(); + result.titleBarButtonFontFamily = new StyleParams.Font(); + result.titleBarHeight = -1; return result; } @@ -278,6 +289,18 @@ private int getDefaultTitleTextFontSize() { return AppStyle.appStyle == null ? -1 : AppStyle.appStyle.titleBarTitleFontSize; } + private int getDefaultSubtitleTextFontSize() { + return AppStyle.appStyle == null ? -1 : AppStyle.appStyle.titleBarSubtitleFontSize; + } + + private StyleParams.Font getDefaultSubtitleFontFamily() { + return AppStyle.appStyle == null ? new StyleParams.Font() : AppStyle.appStyle.titleBarSubtitleFontFamily; + } + + private StyleParams.Font getDefaultTitleBarButtonFont() { + return AppStyle.appStyle == null ? new StyleParams.Font() : AppStyle.appStyle.titleBarButtonFontFamily; + } + private boolean getDefaultTitleTextFontBold() { return AppStyle.appStyle != null && AppStyle.appStyle.titleBarTitleFontBold; } diff --git a/android/app/src/main/java/com/reactnativenavigation/screens/Screen.java b/android/app/src/main/java/com/reactnativenavigation/screens/Screen.java index 1fb344025db..8f1777c8ceb 100644 --- a/android/app/src/main/java/com/reactnativenavigation/screens/Screen.java +++ b/android/app/src/main/java/com/reactnativenavigation/screens/Screen.java @@ -1,14 +1,13 @@ package com.reactnativenavigation.screens; +import android.animation.LayoutTransition; import android.annotation.TargetApi; import android.content.res.Configuration; import android.graphics.Color; import android.os.Build; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; -import android.view.View; import android.view.Window; -import android.view.WindowManager; import android.widget.RelativeLayout; import com.facebook.react.bridge.Callback; @@ -29,6 +28,7 @@ import com.reactnativenavigation.params.TitleBarButtonParams; import com.reactnativenavigation.params.TitleBarLeftButtonParams; import com.reactnativenavigation.params.parsers.StyleParamsParser; +import com.reactnativenavigation.utils.StatusBar; import com.reactnativenavigation.views.ContentView; import com.reactnativenavigation.views.LeftButtonOnClickListener; import com.reactnativenavigation.views.TopBar; @@ -115,6 +115,10 @@ public void setStyle() { } } + public void updateBottomTabsVisibility(boolean hidden) { + styleParams.bottomTabsHidden = hidden; + } + private void createViews() { createAndAddTopBar(); createTitleBar(); @@ -135,7 +139,7 @@ private void createTitleBar() { topBar.setReactView(screenParams.styleParams); } else { topBar.setTitle(screenParams.title, styleParams); - topBar.setSubtitle(screenParams.subtitle); + topBar.setSubtitle(screenParams.subtitle, styleParams); } } @@ -165,44 +169,16 @@ private void addTopBar() { addView(topBar, new LayoutParams(MATCH_PARENT, WRAP_CONTENT)); } - @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void setStatusBarColor(StyleParams.Color statusBarColor) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return; - - final Window window = ((NavigationActivity) activity).getScreenWindow(); - if (statusBarColor.hasColor()) { - window.setStatusBarColor(statusBarColor.getColor()); - } else { - window.setStatusBarColor(Color.BLACK); - } + StatusBar.setColor(((NavigationActivity) activity).getScreenWindow(), statusBarColor); } private void setStatusBarHidden(boolean statusBarHidden) { - final Window window = ((NavigationActivity) activity).getScreenWindow(); - if (statusBarHidden) { - window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); - } else { - window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - } + StatusBar.setHidden(((NavigationActivity) activity).getScreenWindow(), statusBarHidden); } - @TargetApi(Build.VERSION_CODES.M) private void setStatusBarTextColorScheme(StatusBarTextColorScheme textColorScheme) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return; - if (StatusBarTextColorScheme.Dark.equals(textColorScheme)) { - int flags = getSystemUiVisibility(); - flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - setSystemUiVisibility(flags); - } else { - clearLightStatusBar(); - } - } - - public void clearLightStatusBar() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return; - int flags = getSystemUiVisibility(); - flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - setSystemUiVisibility(flags); + StatusBar.setTextColorScheme(this, textColorScheme); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @@ -235,6 +211,12 @@ public BaseScreenParams getScreenParams() { public void setTopBarVisible(boolean visible, boolean animate) { screenParams.styleParams.titleBarHidden = !visible; + if (animate && styleParams.drawScreenBelowTopBar) { + setLayoutTransition(new LayoutTransition()); + getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); + } else { + setLayoutTransition(null); + } topBar.setVisible(visible, animate); } @@ -243,7 +225,7 @@ public void setTitleBarTitle(String title) { } public void setTitleBarSubtitle(String subtitle) { - topBar.setSubtitle(subtitle); + topBar.setSubtitle(subtitle, styleParams); } public void setTitleBarRightButtons(String navigatorEventId, List titleBarButtons) { diff --git a/android/app/src/main/java/com/reactnativenavigation/utils/StatusBar.java b/android/app/src/main/java/com/reactnativenavigation/utils/StatusBar.java new file mode 100644 index 00000000000..d5f3f07096b --- /dev/null +++ b/android/app/src/main/java/com/reactnativenavigation/utils/StatusBar.java @@ -0,0 +1,51 @@ +package com.reactnativenavigation.utils; + +import android.annotation.TargetApi; +import android.graphics.Color; +import android.os.Build; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; + +import com.reactnativenavigation.params.StatusBarTextColorScheme; +import com.reactnativenavigation.params.StyleParams; + +public class StatusBar { + + public static void setHidden(Window window, boolean statusBarHidden) { + if (statusBarHidden) { + window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public static void setColor(Window window, StyleParams.Color statusBarColor) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return; + if (statusBarColor.hasColor()) { + window.setStatusBarColor(statusBarColor.getColor()); + } else { + window.setStatusBarColor(Color.BLACK); + } + } + + @TargetApi(Build.VERSION_CODES.M) + public static void setTextColorScheme(View view, StatusBarTextColorScheme textColorScheme) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return; + if (StatusBarTextColorScheme.Dark.equals(textColorScheme)) { + int flags = view.getSystemUiVisibility(); + flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + view.setSystemUiVisibility(flags); + } else { + clearLightStatusBar(view); + } + } + + private static void clearLightStatusBar(View view) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return; + int flags = view.getSystemUiVisibility(); + flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + view.setSystemUiVisibility(flags); + } +} diff --git a/android/app/src/main/java/com/reactnativenavigation/utils/ViewUtils.java b/android/app/src/main/java/com/reactnativenavigation/utils/ViewUtils.java index 86c38d042b0..cbc0fb50da9 100644 --- a/android/app/src/main/java/com/reactnativenavigation/utils/ViewUtils.java +++ b/android/app/src/main/java/com/reactnativenavigation/utils/ViewUtils.java @@ -113,7 +113,7 @@ public static T findChildByClass(ViewGroup root, Class clazz) { public static T findChildByClass(ViewGroup root, Class clazz, Matcher matcher) { for (int i = 0; i < root.getChildCount(); i++) { View view = root.getChildAt(i); - if (clazz.isAssignableFrom(view.getClass())) { + if (clazz.isAssignableFrom(view.getClass()) && (matcher == null || matcher.match((T) view))) { return (T) view; } diff --git a/android/app/src/main/java/com/reactnativenavigation/views/BottomTabs.java b/android/app/src/main/java/com/reactnativenavigation/views/BottomTabs.java index 48148d83e1e..84d8845343a 100644 --- a/android/app/src/main/java/com/reactnativenavigation/views/BottomTabs.java +++ b/android/app/src/main/java/com/reactnativenavigation/views/BottomTabs.java @@ -86,7 +86,7 @@ private boolean hasTabsWithLabels() { public void setVisibility(boolean hidden, boolean animated) { if (visibilityAnimator != null) { - visibilityAnimator.setVisible(!hidden, animated); + visibilityAnimator.setVisible(!hidden, animated, null); } else { setVisibility(hidden); } diff --git a/android/app/src/main/java/com/reactnativenavigation/views/SideMenu.java b/android/app/src/main/java/com/reactnativenavigation/views/SideMenu.java index c6c25dd11be..c7cac62cba8 100644 --- a/android/app/src/main/java/com/reactnativenavigation/views/SideMenu.java +++ b/android/app/src/main/java/com/reactnativenavigation/views/SideMenu.java @@ -58,11 +58,11 @@ private void destroySideMenu(ContentView sideMenuView) { } public void setVisible(boolean visible, boolean animated, Side side) { - if (!isShown() && visible) { + if (visible) { openDrawer(animated, side); } - if (isShown() && !visible) { + if (!visible) { closeDrawer(animated, side); } } diff --git a/android/app/src/main/java/com/reactnativenavigation/views/TitleBar.java b/android/app/src/main/java/com/reactnativenavigation/views/TitleBar.java index a74db560331..46aab85b2fb 100644 --- a/android/app/src/main/java/com/reactnativenavigation/views/TitleBar.java +++ b/android/app/src/main/java/com/reactnativenavigation/views/TitleBar.java @@ -9,6 +9,7 @@ import android.support.annotation.Nullable; import android.support.v7.widget.ActionMenuView; import android.support.v7.widget.Toolbar; +import android.text.TextUtils; import android.view.Menu; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; @@ -79,6 +80,8 @@ public void setStyle(StyleParams params) { setTitleTextFontSize(params); setTitleTextFontWeight(params); setSubtitleTextColor(params); + setSubtitleFontSize(params); + setSubtitleFont(params); colorOverflowButton(params); setBackground(params); centerTitle(params); @@ -90,9 +93,32 @@ public void setVisibility(boolean titleBarHidden) { public void setTitle(String title, StyleParams styleParams) { setTitle(title); + setTitleTextFont(styleParams); centerTitle(styleParams); } + public void setSubtitle(CharSequence subtitle, StyleParams styleParams) { + super.setSubtitle(subtitle); + setSubtitleFontSize(styleParams); + setSubtitleFont(styleParams); + } + + private void setSubtitleFontSize(StyleParams params) { + TextView subtitleView = getSubtitleView(); + if (subtitleView != null && params.titleBarSubtitleFontSize > 0) { + subtitleView.setTextSize(params.titleBarSubtitleFontSize); + } + } + + private void setSubtitleFont(StyleParams params) { + if (params.titleBarSubtitleFontFamily.hasFont()) { + TextView subtitleView = getSubtitleView(); + if (subtitleView != null) { + subtitleView.setTypeface(params.titleBarSubtitleFontFamily.get()); + } + } + } + private void centerTitle(final StyleParams params) { final View titleView = getTitleView(); if (titleView == null) { @@ -280,6 +306,17 @@ public boolean match(TextView child) { }); } + @Nullable + private TextView getSubtitleView() { + if (TextUtils.isEmpty(getSubtitle())) return null; + return ViewUtils.findChildByClass(this, TextView.class, new ViewUtils.Matcher() { + @Override + public boolean match(TextView child) { + return child.getText().equals(getSubtitle()); + } + }); + } + public void setButtonColor(StyleParams.Color titleBarButtonColor) { if (!titleBarButtonColor.hasColor()) { return; diff --git a/android/app/src/main/java/com/reactnativenavigation/views/TopBar.java b/android/app/src/main/java/com/reactnativenavigation/views/TopBar.java index 206e05c2548..22834bb8c95 100644 --- a/android/app/src/main/java/com/reactnativenavigation/views/TopBar.java +++ b/android/app/src/main/java/com/reactnativenavigation/views/TopBar.java @@ -93,8 +93,8 @@ public void setTitle(String title, StyleParams styleParams) { titleBar.setTitle(title, styleParams); } - public void setSubtitle(String subtitle) { - titleBar.setSubtitle(subtitle); + public void setSubtitle(String subtitle, StyleParams styleParams) { + titleBar.setSubtitle(subtitle, styleParams); } public void setReactView(@NonNull StyleParams styleParams) { @@ -261,7 +261,16 @@ public void onViewPagerScreenChanged(BaseScreenParams screenParams) { } public void setVisible(boolean visible, boolean animate) { - titleBar.setVisibility(!visible); - visibilityAnimator.setVisible(visible, animate); + if (visible) { + titleBar.setVisibility(false); + visibilityAnimator.setVisible(true, animate, null); + } else { + visibilityAnimator.setVisible(false, animate, new Runnable() { + @Override + public void run() { + titleBar.setVisibility(true); + } + }); + } } } diff --git a/android/app/src/main/java/com/reactnativenavigation/views/slidingOverlay/SlidingOverlaysQueue.java b/android/app/src/main/java/com/reactnativenavigation/views/slidingOverlay/SlidingOverlaysQueue.java index 84de948bc5f..f60901f6b5d 100644 --- a/android/app/src/main/java/com/reactnativenavigation/views/slidingOverlay/SlidingOverlaysQueue.java +++ b/android/app/src/main/java/com/reactnativenavigation/views/slidingOverlay/SlidingOverlaysQueue.java @@ -25,7 +25,7 @@ public void run() { } else { SlidingOverlay currentOverlay = queue.peek(); - if (currentOverlay.isVisible()) { + if (currentOverlay != null && currentOverlay.isVisible()) { if (autoDismissTimer != null) { autoDismissTimer.cancel(); autoDismissTimer = null; @@ -60,7 +60,7 @@ else if (currentOverlay.isVisible()) { @Override public void onSlidingOverlayShown() { - Integer autoDismissTimerSec = queue.peek().getAutoDismissTimerSec(); + Integer autoDismissTimerSec = queue.peek() == null ? null : queue.peek().getAutoDismissTimerSec(); if (autoDismissTimerSec != null || pendingHide || queue.size() > 1) { int autoDismissDuration = autoDismissTimerSec != null @@ -75,7 +75,9 @@ public void run() { NavigationApplication.instance.runOnMainThread(new Runnable() { @Override public void run() { - queue.peek().hide(); + if(queue.peek() != null) { + queue.peek().hide(); + } } }); } diff --git a/docs/screen-api.md b/docs/screen-api.md index 3f2d60d6ea4..8c67557f21b 100644 --- a/docs/screen-api.md +++ b/docs/screen-api.md @@ -219,7 +219,8 @@ Set the badge on a tab (any string or numeric value). ```js this.props.navigator.setTabBadge({ tabIndex: 0, // (optional) if missing, the badge will be added to this screen's tab - badge: 17 // badge value, null to remove badge + badge: 17, // badge value, null to remove badge + badgeColor: '#006400', // (optional) if missing, the badge will use the default color }); ``` ## setTabButton(params = {}) diff --git a/docs/styling-the-navigator.md b/docs/styling-the-navigator.md index 21d277fdf4f..f993559d377 100644 --- a/docs/styling-the-navigator.md +++ b/docs/styling-the-navigator.md @@ -68,7 +68,8 @@ this.props.navigator.setStyle({ statusBarHidden: false, // make the status bar hidden regardless of nav bar state statusBarTextColorScheme: 'dark', // text color of status bar, 'dark' / 'light' (remembered across pushes) navBarSubtitleColor: 'red', // subtitle color - navBarSubtitleFontFamily: 'font-name', // subtitle font + navBarSubtitleFontFamily: 'font-name', // subtitle font, 'sans-serif-thin' for example + navBarSubtitleFontSize: 13, // subtitle font size screenBackgroundColor: 'white', // Default screen color, visible before the actual react view is rendered screenBackgroundImageName: 'ios:, android: ', // Optional. default screen background image. orientation: 'portrait' // Sets a specific orientation to a modal and all screens pushed to it. Default: 'auto'. Supported values: 'auto', 'landscape', 'portrait' @@ -97,6 +98,7 @@ this.props.navigator.setStyle({ // Android only navigationBarColor: '#000000', // change the background color of the bottom native navigation bar. navBarTitleTextCentered: true, // default: false. centers the title. + navBarButtonFontFamily: 'sans-serif-thin', // Change the font family of textual buttons topBarElevationShadowEnabled: false, // default: true. Disables TopBar elevation shadow on Lolipop and above statusBarColor: '#000000', // change the color of the status bar. collapsingToolBarImage: "http://lorempixel.com/400/200/", // Collapsing Toolbar image. diff --git a/docs/styling-the-tab-bar.md b/docs/styling-the-tab-bar.md index 4c813a04221..39cdd405ca9 100644 --- a/docs/styling-the-tab-bar.md +++ b/docs/styling-the-tab-bar.md @@ -38,7 +38,7 @@ Navigation.startTabBasedApp({ tabBarButtonColor: '#ffffff', tabBarSelectedButtonColor: '#63d7cc', tabBarTranslucent: false, - tabFontFamily: 'Avenir-Medium.ttf' // for asset file or use existing font family name + tabFontFamily: 'Avenir-Medium' // existing font family name or asset file without extension which can be '.ttf' or '.otf' (searched only if '.ttf' asset not found) }, ... } diff --git a/example/src/screens/types/Modal.js b/example/src/screens/types/Modal.js index 55d085e4e33..d645900532e 100644 --- a/example/src/screens/types/Modal.js +++ b/example/src/screens/types/Modal.js @@ -1,7 +1,34 @@ import React, {Component} from 'react'; -import {StyleSheet, View, Text, Button} from 'react-native'; +import {StyleSheet, View, Text, Button, TouchableOpacity, Platform} from 'react-native'; +import {Navigation} from 'react-native-navigation'; + +const CloseModalButton = ({text}) => + navigator.dismissModal()} +> + + {text} + +; +Navigation.registerComponent('CloseModalButton', () => CloseModalButton); class Modal extends Component { + static navigatorButtons = { + rightButtons: [ + { + id: 'close-modal-button', + component: Platform.OS === 'ios' ? 'CloseModalButton' : null, + passProps: { + text: 'Close' + } + } + ] + }; + + componentWillMount() { + navigator = this.props.navigator; + } onPushScreen = () => { this.props.navigator.push({ @@ -58,6 +85,24 @@ const styles = StyleSheet.create({ }, button: { marginTop: 16 + }, + buttonContainer: { + width: 48, + height: 48, + justifyContent: 'center', + alignItems: 'center' + }, + closeModalButton: { + backgroundColor: 'tomato', + width: 50, + height: 25, + borderRadius: 2, + overflow: 'hidden', + justifyContent: 'center', + alignItems: 'center' + }, + buttonText: { + color: 'white' } }); diff --git a/ios/RCCDrawerController/RCCDrawerController.m b/ios/RCCDrawerController/RCCDrawerController.m index bc070d7f09e..63b29ff752f 100755 --- a/ios/RCCDrawerController/RCCDrawerController.m +++ b/ios/RCCDrawerController/RCCDrawerController.m @@ -32,12 +32,14 @@ - (instancetype)initWithProps:(NSDictionary *)props children:(NSArray *)children // left NSString *componentLeft = props[@"componentLeft"]; NSDictionary *passPropsLeft = props[@"passPropsLeft"]; - if (componentLeft) leftViewController = [[RCCViewController alloc] initWithComponent:componentLeft passProps:passPropsLeft navigatorStyle:nil globalProps:globalProps bridge:bridge]; + NSDictionary *styleLeft = props[@"styleLeft"]; + if (componentLeft) leftViewController = [[RCCViewController alloc] initWithComponent:componentLeft passProps:passPropsLeft navigatorStyle:styleLeft globalProps:globalProps bridge:bridge]; // right NSString *componentRight = props[@"componentRight"]; NSDictionary *passPropsRight = props[@"passPropsRight"]; - if (componentRight) rightViewController = [[RCCViewController alloc] initWithComponent:componentRight passProps:passPropsRight navigatorStyle:nil globalProps:globalProps bridge:bridge]; + NSDictionary *styleRight = props[@"styleRight"]; + if (componentRight) rightViewController = [[RCCViewController alloc] initWithComponent:componentRight passProps:passPropsRight navigatorStyle:styleRight globalProps:globalProps bridge:bridge]; self = [super initWithCenterViewController:centerViewController leftDrawerViewController:leftViewController diff --git a/ios/RCCTabBarController.m b/ios/RCCTabBarController.m index cd9c7497afa..d9f7955a287 100755 --- a/ios/RCCTabBarController.m +++ b/ios/RCCTabBarController.m @@ -263,6 +263,12 @@ - (void)performAction:(NSString*)performAction actionParams:(NSDictionary*)actio } else { + NSString *badgeColor = actionParams[@"badgeColor"]; + UIColor *color = badgeColor != (id)[NSNull null] ? [RCTConvert UIColor:badgeColor] : nil; + + if ([viewController.tabBarItem respondsToSelector:@selector(badgeColor)]) { + viewController.tabBarItem.badgeColor = color; + } viewController.tabBarItem.badgeValue = [NSString stringWithFormat:@"%@", badge]; } } @@ -338,6 +344,10 @@ - (void)performAction:(NSString*)performAction actionParams:(NSDictionary*)actio if ([performAction isEqualToString:@"setTabBarHidden"]) { BOOL hidden = [actionParams[@"hidden"] boolValue]; + + CGRect nextFrame = self.tabBar.frame; + nextFrame.origin.y = UIScreen.mainScreen.bounds.size.height - (hidden ? 0 : self.tabBar.frame.size.height); + [UIView animateWithDuration: ([actionParams[@"animated"] boolValue] ? 0.45 : 0) delay: 0 usingSpringWithDamping: 0.75 @@ -345,7 +355,7 @@ - (void)performAction:(NSString*)performAction actionParams:(NSDictionary*)actio options: (hidden ? UIViewAnimationOptionCurveEaseIn : UIViewAnimationOptionCurveEaseOut) animations:^() { - self.tabBar.transform = hidden ? CGAffineTransformMakeTranslation(0, self.tabBar.frame.size.height) : CGAffineTransformIdentity; + [self.tabBar setFrame:nextFrame]; } completion:^(BOOL finished) { diff --git a/scripts/android-sdk-licenses/android-googletv-license b/scripts/android-sdk-licenses/android-googletv-license new file mode 100644 index 00000000000..07d43f0b82f --- /dev/null +++ b/scripts/android-sdk-licenses/android-googletv-license @@ -0,0 +1,2 @@ + +601085b94cd77f0b54ff86406957099ebe79c4d6 \ No newline at end of file diff --git a/scripts/android-sdk-licenses/android-sdk-license b/scripts/android-sdk-licenses/android-sdk-license index e7327c5e066..c311cf48c37 100644 --- a/scripts/android-sdk-licenses/android-sdk-license +++ b/scripts/android-sdk-licenses/android-sdk-license @@ -1 +1,2 @@ -8933bad161af4178b1185d1a37fbf41ea5269c55 \ No newline at end of file + +d56f5187479451eabf01fb78af6dfcb131a6481e \ No newline at end of file diff --git a/scripts/android-sdk-licenses/android-sdk-preview-license b/scripts/android-sdk-licenses/android-sdk-preview-license new file mode 100644 index 00000000000..da4552d2c36 --- /dev/null +++ b/scripts/android-sdk-licenses/android-sdk-preview-license @@ -0,0 +1,2 @@ + +84831b9409646a918e30573bab4c9c91346d8abd \ No newline at end of file diff --git a/scripts/android-sdk-licenses/google-gdk-license b/scripts/android-sdk-licenses/google-gdk-license new file mode 100644 index 00000000000..db3b42fd809 --- /dev/null +++ b/scripts/android-sdk-licenses/google-gdk-license @@ -0,0 +1,2 @@ + +33b6a2b64607f11b759f320ef9dff4ae5c47d97a \ No newline at end of file diff --git a/scripts/android-sdk-licenses/intel-android-extra-license b/scripts/android-sdk-licenses/intel-android-extra-license new file mode 100644 index 00000000000..f82e65b6c8c --- /dev/null +++ b/scripts/android-sdk-licenses/intel-android-extra-license @@ -0,0 +1,2 @@ + +d975f751698a77b662f1254ddbeed3901e976f5a \ No newline at end of file diff --git a/scripts/android-sdk-licenses/mips-android-sysimage-license b/scripts/android-sdk-licenses/mips-android-sysimage-license new file mode 100644 index 00000000000..8f4f164ec39 --- /dev/null +++ b/scripts/android-sdk-licenses/mips-android-sysimage-license @@ -0,0 +1,2 @@ + +e9acab5b5fbb560a72cfaecce8946896ff6aab9d \ No newline at end of file diff --git a/src/deprecated/controllers/index.js b/src/deprecated/controllers/index.js index b7967f772e5..02623393c23 100755 --- a/src/deprecated/controllers/index.js +++ b/src/deprecated/controllers/index.js @@ -244,6 +244,7 @@ var Controllers = { return RCCManager.TabBarControllerIOS(id, "setTabBarHidden", params); }, setBadge: function (params) { + _processProperties(params); return RCCManager.TabBarControllerIOS(id, "setBadge", params); }, switchTo: function (params) { diff --git a/src/deprecated/platformSpecificDeprecated.android.js b/src/deprecated/platformSpecificDeprecated.android.js index 57b06b275be..4cc61a184f5 100644 --- a/src/deprecated/platformSpecificDeprecated.android.js +++ b/src/deprecated/platformSpecificDeprecated.android.js @@ -162,7 +162,10 @@ function convertStyleParams(originalStyleObject) { titleBarHideOnScroll: originalStyleObject.navBarHideOnScroll, titleBarTitleColor: processColor(originalStyleObject.navBarTextColor), titleBarSubtitleColor: processColor(originalStyleObject.navBarSubtitleColor), + titleBarSubtitleFontSize: originalStyleObject.navBarSubtitleFontSize, + titleBarSubtitleFontFamily: originalStyleObject.navBarSubtitleFontFamily, titleBarButtonColor: processColor(originalStyleObject.navBarButtonColor), + titleBarButtonFontFamily: originalStyleObject.navBarButtonFontFamily, titleBarDisabledButtonColor: processColor(originalStyleObject.titleBarDisabledButtonColor), titleBarTitleFontFamily: originalStyleObject.navBarTextFontFamily, titleBarTitleFontSize: originalStyleObject.navBarTextFontSize, diff --git a/src/deprecated/platformSpecificDeprecated.ios.js b/src/deprecated/platformSpecificDeprecated.ios.js index 5b43c3a0d41..ff9eabff48b 100644 --- a/src/deprecated/platformSpecificDeprecated.ios.js +++ b/src/deprecated/platformSpecificDeprecated.ios.js @@ -46,17 +46,31 @@ function startTabBasedApp(params) { return this.renderBody(); } else { const navigatorID = controllerID + '_drawer'; + + const leftScreenId = _.uniqueId('screenInstanceID'); + const rightScreenId = _.uniqueId('screenInstanceID') + + const { navigatorStyle: leftNavigatorStyle } = params.drawer.left + ? _mergeScreenSpecificSettings(params.drawer.left.screen, leftScreenId, params.drawer.left) + : {}; + + const { navigatorStyle: rightNavigatorStyle } = params.drawer.right + ? _mergeScreenSpecificSettings(params.drawer.right.screen, rightScreenId, params.drawer.right) + : {}; + return ( {this.renderBody()} @@ -384,7 +398,8 @@ function navigatorSetTabBadge(navigator, params) { if (params.tabIndex || params.tabIndex === 0) { Controllers.TabBarControllerIOS(controllerID + '_tabs').setBadge({ tabIndex: params.tabIndex, - badge: params.badge + badge: params.badge, + badgeColor: params.badgeColor }); } else { Controllers.TabBarControllerIOS(controllerID + '_tabs').setBadge({ @@ -567,7 +582,7 @@ function showInAppNotification(params) { navigatorEventID, navigatorID }; - + savePassProps(params); let args = {