Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -2782,7 +2782,7 @@ public class com/facebook/react/fabric/mounting/SurfaceMountingManager {
public fun setJSResponder (IIZ)V
public fun stopSurface ()V
public fun updateEventEmitter (ILcom/facebook/react/fabric/events/EventEmitterWrapper;)V
public fun updateLayout (IIIIIII)V
public fun updateLayout (IIIIIIII)V
public fun updateOverflowInset (IIIII)V
public fun updatePadding (IIIII)V
public fun updateProps (ILcom/facebook/react/bridge/ReadableMap;)V
Expand Down Expand Up @@ -5506,12 +5506,11 @@ public class com/facebook/react/uimanager/drawable/CSSBackgroundDrawable : andro
public fun getComputedBorderRadius ()Lcom/facebook/react/uimanager/style/ComputedBorderRadius;
public fun getDirectionAwareBorderInsets ()Landroid/graphics/RectF;
public fun getFullBorderWidth ()F
public fun getLayoutDirection ()I
public fun getOpacity ()I
public fun getOutline (Landroid/graphics/Outline;)V
public fun getResolvedLayoutDirection ()I
public fun hasRoundedBorders ()Z
protected fun onBoundsChange (Landroid/graphics/Rect;)V
public fun onResolvedLayoutDirectionChanged (I)Z
public fun paddingBoxPath ()Landroid/graphics/Path;
public fun setAlpha (I)V
public fun setBorderColor (IFF)V
Expand All @@ -5521,9 +5520,9 @@ public class com/facebook/react/uimanager/drawable/CSSBackgroundDrawable : andro
public fun setBorderWidth (IF)V
public fun setColor (I)V
public fun setColorFilter (Landroid/graphics/ColorFilter;)V
public fun setLayoutDirectionOverride (I)V
public fun setRadius (F)V
public fun setRadius (FI)V
public fun setResolvedLayoutDirection (I)Z
}

public abstract interface class com/facebook/react/uimanager/events/BatchEventDispatchedListener {
Expand Down Expand Up @@ -6608,6 +6607,7 @@ public class com/facebook/react/views/scroll/OnScrollDispatchHelper {

public class com/facebook/react/views/scroll/ReactHorizontalScrollContainerView : com/facebook/react/views/view/ReactViewGroup {
public fun <init> (Landroid/content/Context;)V
public fun getLayoutDirection ()I
protected fun onLayout (ZIIII)V
public fun setRemoveClippedSubviews (Z)V
}
Expand Down Expand Up @@ -7901,7 +7901,6 @@ public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGro
public fun onInterceptTouchEvent (Landroid/view/MotionEvent;)Z
protected fun onLayout (ZIIII)V
protected fun onMeasure (II)V
public fun onRtlPropertiesChanged (I)V
protected fun onSizeChanged (IIII)V
public fun onTouchEvent (Landroid/view/MotionEvent;)Z
public fun removeView (Landroid/view/View;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import com.facebook.react.fabric.events.EventEmitterWrapper;
import com.facebook.react.fabric.mounting.MountingManager.MountItemExecutor;
import com.facebook.react.fabric.mounting.mountitems.MountItem;
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
import com.facebook.react.modules.core.ReactChoreographer;
import com.facebook.react.touch.JSResponderHandler;
import com.facebook.react.uimanager.IViewGroupManager;
Expand Down Expand Up @@ -934,7 +935,14 @@ public void sendAccessibilityEvent(int reactTag, int eventType) {

@UiThread
public void updateLayout(
int reactTag, int parentTag, int x, int y, int width, int height, int displayType) {
int reactTag,
int parentTag,
int x,
int y,
int width,
int height,
int displayType,
int layoutDirection) {
if (isStopped()) {
return;
}
Expand All @@ -950,6 +958,13 @@ public void updateLayout(
throw new IllegalStateException("Unable to find View for tag: " + reactTag);
}

if (ReactNativeFeatureFlags.setAndroidLayoutDirection()) {
viewToUpdate.setLayoutDirection(
layoutDirection == 1
? View.LAYOUT_DIRECTION_LTR
: layoutDirection == 2 ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_INHERIT);
}

viewToUpdate.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@
import static com.facebook.react.fabric.FabricUIManager.IS_DEVELOPMENT_ENVIRONMENT;
import static com.facebook.react.fabric.mounting.mountitems.FabricNameComponentMapping.getFabricComponentName;

import androidx.annotation.NonNull;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.bridge.ReactMarkerConstants;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.fabric.events.EventEmitterWrapper;
import com.facebook.react.fabric.mounting.MountingManager;
import com.facebook.react.fabric.mounting.SurfaceMountingManager;
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
import com.facebook.react.uimanager.StateWrapper;
import com.facebook.systrace.Systrace;

Expand All @@ -35,6 +36,7 @@
* allocations in C++ and JNI round-trips.
*/
@DoNotStrip
@Nullsafe(Nullsafe.Mode.LOCAL)
final class IntBufferBatchMountItem implements BatchMountItem {
static final String TAG = IntBufferBatchMountItem.class.getSimpleName();

Expand All @@ -55,8 +57,8 @@ final class IntBufferBatchMountItem implements BatchMountItem {
private final int mSurfaceId;
private final int mCommitNumber;

private final @NonNull int[] mIntBuffer;
private final @NonNull Object[] mObjBuffer;
private final int[] mIntBuffer;
private final Object[] mObjBuffer;

private final int mIntBufferLen;
private final int mObjBufferLen;
Expand Down Expand Up @@ -91,7 +93,7 @@ private void endMarkers() {
}

@Override
public void execute(@NonNull MountingManager mountingManager) {
public void execute(MountingManager mountingManager) {
SurfaceMountingManager surfaceMountingManager = mountingManager.getSurfaceManager(mSurfaceId);
if (surfaceMountingManager == null) {
FLog.e(
Expand Down Expand Up @@ -149,9 +151,14 @@ public void execute(@NonNull MountingManager mountingManager) {
int height = mIntBuffer[i++];
int displayType = mIntBuffer[i++];

surfaceMountingManager.updateLayout(
reactTag, parentTag, x, y, width, height, displayType);

if (ReactNativeFeatureFlags.setAndroidLayoutDirection()) {
int layoutDirection = mIntBuffer[i++];
surfaceMountingManager.updateLayout(
reactTag, parentTag, x, y, width, height, displayType, layoutDirection);
} else {
surfaceMountingManager.updateLayout(
reactTag, parentTag, x, y, width, height, displayType, 0);
}
} else if (type == INSTRUCTION_UPDATE_PADDING) {
surfaceMountingManager.updatePadding(
mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ public object MountItemFactory {
@JvmStatic
public fun createIntBufferBatchMountItem(
surfaceId: Int,
intBuf: IntArray?,
objBuf: Array<Any?>?,
intBuf: IntArray,
objBuf: Array<Any?>,
commitNumber: Int
): MountItem = IntBufferBatchMountItem(surfaceId, intBuf, objBuf, commitNumber)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<c35ee97cf5c4b5f77cdd045341f2848b>>
* @generated SignedSource<<0d5f4b26573154fb42c312b03c0dc6a7>>
*/

/**
Expand Down Expand Up @@ -124,6 +124,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun preventDoubleTextMeasure(): Boolean = accessor.preventDoubleTextMeasure()

/**
* Propagate layout direction to Android views.
*/
@JvmStatic
public fun setAndroidLayoutDirection(): Boolean = accessor.setAndroidLayoutDirection()

/**
* When enabled, it uses the modern fork of RuntimeScheduler that allows scheduling tasks with priorities from any thread.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<1b84e5fa96120a511db6f831afb73eab>>
* @generated SignedSource<<60f7a791ff3eb270c26ebf598f65d8d5>>
*/

/**
Expand Down Expand Up @@ -36,6 +36,7 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
private var inspectorEnableModernCDPRegistryCache: Boolean? = null
private var lazyAnimationCallbacksCache: Boolean? = null
private var preventDoubleTextMeasureCache: Boolean? = null
private var setAndroidLayoutDirectionCache: Boolean? = null
private var useModernRuntimeSchedulerCache: Boolean? = null
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
private var useStateAlignmentMechanismCache: Boolean? = null
Expand Down Expand Up @@ -184,6 +185,15 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
return cached
}

override fun setAndroidLayoutDirection(): Boolean {
var cached = setAndroidLayoutDirectionCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.setAndroidLayoutDirection()
setAndroidLayoutDirectionCache = cached
}
return cached
}

override fun useModernRuntimeScheduler(): Boolean {
var cached = useModernRuntimeSchedulerCache
if (cached == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<2cd7ab4688ca2179ba3a19e9a062f695>>
* @generated SignedSource<<a217de50b6148069ad6ff915c1446f45>>
*/

/**
Expand Down Expand Up @@ -60,6 +60,8 @@ public object ReactNativeFeatureFlagsCxxInterop {

@DoNotStrip @JvmStatic public external fun preventDoubleTextMeasure(): Boolean

@DoNotStrip @JvmStatic public external fun setAndroidLayoutDirection(): Boolean

@DoNotStrip @JvmStatic public external fun useModernRuntimeScheduler(): Boolean

@DoNotStrip @JvmStatic public external fun useNativeViewConfigsInBridgelessMode(): Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<a2c4f2c950a7b92163fdc6219864d6ca>>
* @generated SignedSource<<c86391a2ae3dc6fbf5b8327b37bc30d2>>
*/

/**
Expand Down Expand Up @@ -55,6 +55,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi

override fun preventDoubleTextMeasure(): Boolean = false

override fun setAndroidLayoutDirection(): Boolean = false

override fun useModernRuntimeScheduler(): Boolean = false

override fun useNativeViewConfigsInBridgelessMode(): Boolean = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<48f657b282ef658ab7e9024f470d37ad>>
* @generated SignedSource<<8f6a50230ef99335baa5929070b94ddc>>
*/

/**
Expand Down Expand Up @@ -40,6 +40,7 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
private var inspectorEnableModernCDPRegistryCache: Boolean? = null
private var lazyAnimationCallbacksCache: Boolean? = null
private var preventDoubleTextMeasureCache: Boolean? = null
private var setAndroidLayoutDirectionCache: Boolean? = null
private var useModernRuntimeSchedulerCache: Boolean? = null
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
private var useStateAlignmentMechanismCache: Boolean? = null
Expand Down Expand Up @@ -204,6 +205,16 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
return cached
}

override fun setAndroidLayoutDirection(): Boolean {
var cached = setAndroidLayoutDirectionCache
if (cached == null) {
cached = currentProvider.setAndroidLayoutDirection()
accessedFeatureFlags.add("setAndroidLayoutDirection")
setAndroidLayoutDirectionCache = cached
}
return cached
}

override fun useModernRuntimeScheduler(): Boolean {
var cached = useModernRuntimeSchedulerCache
if (cached == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<0f3d31f94f4bded41936fe4ecafbdd4a>>
* @generated SignedSource<<fcc8430187565628d83479182550ff21>>
*/

/**
Expand Down Expand Up @@ -55,6 +55,8 @@ public interface ReactNativeFeatureFlagsProvider {

@DoNotStrip public fun preventDoubleTextMeasure(): Boolean

@DoNotStrip public fun setAndroidLayoutDirection(): Boolean

@DoNotStrip public fun useModernRuntimeScheduler(): Boolean

@DoNotStrip public fun useNativeViewConfigsInBridgelessMode(): Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,30 @@

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import androidx.annotation.Nullable;
import androidx.core.text.TextUtilsCompat;
import androidx.core.util.Preconditions;
import androidx.core.view.ViewCompat;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Nullsafe;
import java.lang.reflect.Method;
import java.util.Locale;

@Nullsafe(Nullsafe.Mode.LOCAL)
public class I18nUtil {
private static I18nUtil sharedI18nUtilInstance = null;
private static @Nullable I18nUtil sharedI18nUtilInstance = null;

private static final String TAG = "I18nUtil";
private static final String SHARED_PREFS_NAME = "com.facebook.react.modules.i18nmanager.I18nUtil";
private static final String KEY_FOR_PREFS_ALLOWRTL = "RCTI18nUtil_allowRTL";
private static final String KEY_FOR_PREFS_FORCERTL = "RCTI18nUtil_forceRTL";
private static final String KEY_FOR_PERFS_MAKE_RTL_FLIP_LEFT_AND_RIGHT_STYLES =
"RCTI18nUtil_makeRTLFlipLeftAndRightStyles";

private boolean mHasCheckedRtlSupport = false;
private boolean mHasRtlSupport = true;

private I18nUtil() {
// Exists only to defeat instantiation.
}
Expand All @@ -42,19 +53,46 @@ public static I18nUtil getInstance() {
* </ul>
*/
public boolean isRTL(Context context) {
if (!applicationHasRtlSupport(context)) {
return false;
}

if (isRTLForced(context)) {
return true;
}

return isRTLAllowed(context) && isDevicePreferredLanguageRTL();
}

/**
* Android relies on the presence of `android:supportsRtl="true"` being set in order to resolve
* RTL as a layout direction for native Android views. RTL in React Native relies on this being
* set.
*/
private boolean applicationHasRtlSupport(Context context) {
if (!mHasCheckedRtlSupport) {
mHasCheckedRtlSupport = true;
ApplicationInfo applicationInfo = context.getApplicationInfo();
try {
Method hasRtlSupport = ApplicationInfo.class.getMethod("hasRtlSupport");
mHasRtlSupport =
Preconditions.checkNotNull((Boolean) hasRtlSupport.invoke(applicationInfo));
} catch (ReflectiveOperationException e) {
FLog.w(TAG, "Failed to check if application has RTL support");
}
}
return mHasRtlSupport;
}

/**
* Should be used very early during app start up Before the bridge is initialized
*
* @return whether the app allows RTL layout, default is true
*/
private boolean isRTLAllowed(Context context) {
return isPrefSet(context, KEY_FOR_PREFS_ALLOWRTL, true);
// We should only claim to allow RTL if `android:supportsRtl="true"` is set, otherwise the
// platform apart from Yoga ignores layout direction.
return applicationHasRtlSupport(context) && isPrefSet(context, KEY_FOR_PREFS_ALLOWRTL, true);
}

public void allowRTL(Context context, boolean allowRTL) {
Expand Down
Loading