Skip to content

Commit

Permalink
Apply layout direction directly on views (wix#5368)
Browse files Browse the repository at this point in the history
Apply layout direction directly on views

This commit changes how RNN handles layout direction and adds support for LOCALE layout direction on Android.
Until now RNN delegated handling layout direction to RN's I18nUtil. This usually is enough but under certain circumstances
layout direction has to be applied directly on relevant views by RNN - usually when there are conflicts with another dependency
which handles RTL.
  • Loading branch information
guyca authored and vshkl committed Feb 5, 2020
1 parent a6b1a74 commit bd366e5
Show file tree
Hide file tree
Showing 16 changed files with 236 additions and 141 deletions.
Expand Up @@ -21,7 +21,7 @@
import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
import com.reactnativenavigation.viewcontrollers.modal.ModalStack;
import com.reactnativenavigation.viewcontrollers.navigator.Navigator;
import com.reactnativenavigation.viewcontrollers.navigator.RootPresenter;
import com.reactnativenavigation.presentation.RootPresenter;

public class NavigationActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity, JsDevReloadHandler.ReloadListener {
@Nullable
Expand All @@ -33,7 +33,12 @@ public class NavigationActivity extends AppCompatActivity implements DefaultHard
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addDefaultSplashLayout();
navigator = new Navigator(this, new ChildControllersRegistry(), new ModalStack(this), new OverlayManager(), new RootPresenter(this));
navigator = new Navigator(this,
new ChildControllersRegistry(),
new ModalStack(this),
new OverlayManager(),
new RootPresenter(this)
);
navigator.bindViews();
getReactGateway().onActivityCreated(this);
}
Expand Down
@@ -0,0 +1,53 @@
package com.reactnativenavigation.parse;

import android.text.TextUtils;
import android.view.View;

import java.util.Locale;

public enum LayoutDirection {
RTL(View.LAYOUT_DIRECTION_RTL),
LTR(View.LAYOUT_DIRECTION_LTR),
LOCALE(View.LAYOUT_DIRECTION_LOCALE),
DEFAULT(View.LAYOUT_DIRECTION_LTR);

private final int direction;

LayoutDirection(int direction) {
this.direction = direction;
}

public static LayoutDirection fromString(String direction) {
switch (direction) {
case "rtl":
return RTL;
case "ltr":
return LTR;
case "locale":
return LOCALE;
default:
return DEFAULT;
}
}

public boolean hasValue() {
return this != DEFAULT;
}

public int get() {
return direction;
}

public boolean isRtl() {
switch (direction) {
case View.LAYOUT_DIRECTION_LTR:
return false;
case View.LAYOUT_DIRECTION_RTL:
return true;
case View.LAYOUT_DIRECTION_LOCALE:
return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL;
default:
return false;
}
}
}
Expand Up @@ -4,12 +4,8 @@
import com.reactnativenavigation.parse.params.NullColor;
import com.reactnativenavigation.parse.params.NullNumber;
import com.reactnativenavigation.parse.params.Number;
import com.reactnativenavigation.parse.params.Text;
import com.reactnativenavigation.parse.params.NullText;
import com.reactnativenavigation.parse.parsers.ColorParser;
import com.reactnativenavigation.parse.parsers.NumberParser;
import com.reactnativenavigation.parse.parsers.TextParser;


import org.json.JSONObject;

Expand All @@ -22,7 +18,7 @@ public static LayoutOptions parse(JSONObject json) {
result.componentBackgroundColor = ColorParser.parse(json, "componentBackgroundColor");
result.topMargin = NumberParser.parse(json, "topMargin");
result.orientation = OrientationOptions.parse(json);
result.direction = TextParser.parse(json, "direction");
result.direction = LayoutDirection.fromString(json.optString("direction", ""));

return result;
}
Expand All @@ -31,7 +27,7 @@ public static LayoutOptions parse(JSONObject json) {
public Colour componentBackgroundColor = new NullColor();
public Number topMargin = new NullNumber();
public OrientationOptions orientation = new OrientationOptions();
public Text direction = new NullText();
public LayoutDirection direction = LayoutDirection.DEFAULT;

public void mergeWith(LayoutOptions other) {
if (other.backgroundColor.hasValue()) backgroundColor = other.backgroundColor;
Expand Down
Expand Up @@ -3,7 +3,6 @@
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;

import com.aurelhubert.ahbottomnavigation.notification.AHNotification;
import com.reactnativenavigation.parse.BottomTabOptions;
Expand All @@ -26,8 +25,6 @@ public class BottomTabPresenter {
private Options defaultOptions;
private final BottomTabFinder bottomTabFinder;
private BottomTabs bottomTabs;
private final int defaultSelectedTextColor;
private final int defaultTextColor;
private final List<ViewController> tabs;
private final int defaultDotIndicatorSize;

Expand All @@ -37,8 +34,6 @@ public BottomTabPresenter(Context context, List<ViewController> tabs, ImageLoade
this.bottomTabFinder = new BottomTabFinder(tabs);
this.imageLoader = imageLoader;
this.defaultOptions = defaultOptions;
defaultSelectedTextColor = defaultOptions.bottomTabOptions.selectedIconColor.get(ContextCompat.getColor(context, com.aurelhubert.ahbottomnavigation.R.color.colorBottomNavigationAccent));
defaultTextColor = defaultOptions.bottomTabOptions.iconColor.get(ContextCompat.getColor(context, com.aurelhubert.ahbottomnavigation.R.color.colorBottomNavigationInactive));
defaultDotIndicatorSize = dpToPx(context, 6);
}

Expand Down
Expand Up @@ -50,56 +50,59 @@ public void applyLayoutParamsOptions(Options options, int tabIndex) {
}

public void mergeOptions(Options options) {
mergeBottomTabsOptions(options.bottomTabsOptions, options.animations);
mergeBottomTabsOptions(options);
}

public void applyOptions(Options options) {
Options withDefaultOptions = options.copy().withDefaultOptions(defaultOptions);
applyBottomTabsOptions(withDefaultOptions.bottomTabsOptions, withDefaultOptions.animations);
applyBottomTabsOptions(options.copy().withDefaultOptions(defaultOptions));
}

public void applyChildOptions(Options options, Component child) {
int tabIndex = bottomTabFinder.findByComponent(child);
if (tabIndex >= 0) {
Options withDefaultOptions = options.copy().withDefaultOptions(defaultOptions);
applyBottomTabsOptions(withDefaultOptions.bottomTabsOptions, withDefaultOptions.animations);
applyBottomTabsOptions(withDefaultOptions);
applyDrawBehind(withDefaultOptions.bottomTabsOptions, tabIndex);
}
}

public void mergeChildOptions(Options options, Component child) {
mergeBottomTabsOptions(options.bottomTabsOptions, options.animations);
mergeBottomTabsOptions(options);
int tabIndex = bottomTabFinder.findByComponent(child);
if (tabIndex >= 0) mergeDrawBehind(options.bottomTabsOptions, tabIndex);
}

private void mergeBottomTabsOptions(BottomTabsOptions options, AnimationsOptions animations) {
if (options.titleDisplayMode.hasValue()) {
bottomTabs.setTitleState(options.titleDisplayMode.toState());
private void mergeBottomTabsOptions(Options options) {
BottomTabsOptions bottomTabsOptions = options.bottomTabsOptions;
AnimationsOptions animations = options.animations;

if (options.layout.direction.hasValue()) bottomTabs.setLayoutDirection(options.layout.direction);
if (bottomTabsOptions.titleDisplayMode.hasValue()) {
bottomTabs.setTitleState(bottomTabsOptions.titleDisplayMode.toState());
}
if (options.backgroundColor.hasValue()) {
bottomTabs.setBackgroundColor(options.backgroundColor.get());
if (bottomTabsOptions.backgroundColor.hasValue()) {
bottomTabs.setBackgroundColor(bottomTabsOptions.backgroundColor.get());
}
if (options.currentTabIndex.hasValue()) {
int tabIndex = options.currentTabIndex.get();
if (bottomTabsOptions.currentTabIndex.hasValue()) {
int tabIndex = bottomTabsOptions.currentTabIndex.get();
if (tabIndex >= 0) tabSelector.selectTab(tabIndex);
}
if (options.testId.hasValue()) {
bottomTabs.setTag(options.testId.get());
if (bottomTabsOptions.testId.hasValue()) {
bottomTabs.setTag(bottomTabsOptions.testId.get());
}
if (options.currentTabId.hasValue()) {
int tabIndex = bottomTabFinder.findByControllerId(options.currentTabId.get());
if (bottomTabsOptions.currentTabId.hasValue()) {
int tabIndex = bottomTabFinder.findByControllerId(bottomTabsOptions.currentTabId.get());
if (tabIndex >= 0) tabSelector.selectTab(tabIndex);
}
if (options.visible.isTrue()) {
if (options.animate.isTrueOrUndefined()) {
if (bottomTabsOptions.visible.isTrue()) {
if (bottomTabsOptions.animate.isTrueOrUndefined()) {
animator.show(animations);
} else {
bottomTabs.restoreBottomNavigation(false);
}
}
if (options.visible.isFalse()) {
if (options.animate.isTrueOrUndefined()) {
if (bottomTabsOptions.visible.isFalse()) {
if (bottomTabsOptions.animate.isTrueOrUndefined()) {
animator.hide(animations);
} else {
bottomTabs.hideBottomNavigation(false);
Expand Down Expand Up @@ -127,34 +130,38 @@ private void mergeDrawBehind(BottomTabsOptions options, int tabIndex) {
}
}

private void applyBottomTabsOptions(BottomTabsOptions options, AnimationsOptions animationsOptions) {
bottomTabs.setTitleState(options.titleDisplayMode.get(TitleState.SHOW_WHEN_ACTIVE));
bottomTabs.setBackgroundColor(options.backgroundColor.get(Color.WHITE));
if (options.currentTabIndex.hasValue()) {
int tabIndex = options.currentTabIndex.get();
private void applyBottomTabsOptions(Options options) {
BottomTabsOptions bottomTabsOptions = options.bottomTabsOptions;
AnimationsOptions animationsOptions = options.animations;

bottomTabs.setLayoutDirection(options.layout.direction);
bottomTabs.setTitleState(bottomTabsOptions.titleDisplayMode.get(TitleState.SHOW_WHEN_ACTIVE));
bottomTabs.setBackgroundColor(bottomTabsOptions.backgroundColor.get(Color.WHITE));
if (bottomTabsOptions.currentTabIndex.hasValue()) {
int tabIndex = bottomTabsOptions.currentTabIndex.get();
if (tabIndex >= 0) tabSelector.selectTab(tabIndex);
}
if (options.testId.hasValue()) bottomTabs.setTag(options.testId.get());
if (options.currentTabId.hasValue()) {
int tabIndex = bottomTabFinder.findByControllerId(options.currentTabId.get());
if (bottomTabsOptions.testId.hasValue()) bottomTabs.setTag(bottomTabsOptions.testId.get());
if (bottomTabsOptions.currentTabId.hasValue()) {
int tabIndex = bottomTabFinder.findByControllerId(bottomTabsOptions.currentTabId.get());
if (tabIndex >= 0) tabSelector.selectTab(tabIndex);
}
if (options.visible.isTrueOrUndefined()) {
if (options.animate.isTrueOrUndefined()) {
if (bottomTabsOptions.visible.isTrueOrUndefined()) {
if (bottomTabsOptions.animate.isTrueOrUndefined()) {
animator.show(animationsOptions);
} else {
bottomTabs.restoreBottomNavigation(false);
}
}
if (options.visible.isFalse()) {
if (options.animate.isTrueOrUndefined()) {
if (bottomTabsOptions.visible.isFalse()) {
if (bottomTabsOptions.animate.isTrueOrUndefined()) {
animator.hide(animationsOptions);
} else {
bottomTabs.hideBottomNavigation(false);
}
}
if (options.elevation.hasValue()) {
bottomTabs.setUseElevation(true, options.elevation.get().floatValue());
if (bottomTabsOptions.elevation.hasValue()) {
bottomTabs.setUseElevation(true, bottomTabsOptions.elevation.get().floatValue());
}
}
}
@@ -0,0 +1,16 @@
package com.reactnativenavigation.presentation;

import com.facebook.react.ReactInstanceManager;
import com.facebook.react.modules.i18nmanager.I18nUtil;
import com.reactnativenavigation.parse.Options;
import com.reactnativenavigation.viewcontrollers.ViewController;

public class LayoutDirectionApplier {
public void apply(ViewController root, Options options, ReactInstanceManager instanceManager) {
if (options.layout.direction.hasValue() && instanceManager.getCurrentReactContext() != null) {
root.getActivity().getWindow().getDecorView().setLayoutDirection(options.layout.direction.get());
I18nUtil.getInstance().allowRTL(instanceManager.getCurrentReactContext(), options.layout.direction.isRtl());
I18nUtil.getInstance().forceRTL(instanceManager.getCurrentReactContext(), options.layout.direction.isRtl());
}
}
}
@@ -1,37 +1,35 @@
package com.reactnativenavigation.viewcontrollers.navigator;
package com.reactnativenavigation.presentation;

import android.content.Context;
import android.widget.FrameLayout;
import android.view.View;

import com.facebook.react.ReactInstanceManager;
import com.reactnativenavigation.anim.NavigationAnimator;
import com.reactnativenavigation.parse.Options;
import com.reactnativenavigation.utils.CommandListener;
import com.reactnativenavigation.viewcontrollers.ViewController;
import com.reactnativenavigation.views.element.ElementTransitionManager;

import com.facebook.react.modules.i18nmanager.I18nUtil;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.ReactInstanceManager;

public class RootPresenter {
private NavigationAnimator animator;
private LayoutDirectionApplier layoutDirectionApplier;
private FrameLayout rootLayout;

void setRootContainer(FrameLayout rootLayout) {
public void setRootContainer(FrameLayout rootLayout) {
this.rootLayout = rootLayout;
}

public RootPresenter(Context context) {
animator = new NavigationAnimator(context, new ElementTransitionManager());
this(new NavigationAnimator(context, new ElementTransitionManager()), new LayoutDirectionApplier());
}

RootPresenter(NavigationAnimator animator) {
public RootPresenter(NavigationAnimator animator, LayoutDirectionApplier layoutDirectionApplier) {
this.animator = animator;
this.layoutDirectionApplier = layoutDirectionApplier;
}

void setRoot(ViewController root, Options defaultOptions, CommandListener listener, ReactInstanceManager reactInstanceManager) {
setLayoutDirection(root, defaultOptions, (ReactApplicationContext) reactInstanceManager.getCurrentReactContext());
public void setRoot(ViewController root, Options defaultOptions, CommandListener listener, ReactInstanceManager reactInstanceManager) {
layoutDirectionApplier.apply(root, defaultOptions, reactInstanceManager);
rootLayout.addView(root.getView());
Options options = root.resolveCurrentOptions(defaultOptions);
root.setWaitForRender(options.animations.setRoot.waitForRender);
Expand All @@ -53,15 +51,4 @@ private void animateSetRootAndReportSuccess(ViewController root, CommandListener
listener.onSuccess(root.getId());
}
}

private void setLayoutDirection(ViewController root, Options defaultOptions, ReactApplicationContext reactContext) {
if (defaultOptions.layout.direction.hasValue()) {
I18nUtil i18nUtil = I18nUtil.getInstance();
Boolean isRtl = defaultOptions.layout.direction.get().equals("rtl");

root.getActivity().getWindow().getDecorView().setLayoutDirection(isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
i18nUtil.allowRTL(reactContext, isRtl);
i18nUtil.forceRTL(reactContext, isRtl);
}
}
}

0 comments on commit bd366e5

Please sign in to comment.