mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
913c77d4cb | ||
|
|
384677f58e | ||
|
|
2e76fc8e8e | ||
|
|
5fc23d7c62 | ||
|
|
e92da16a9b | ||
|
|
ae5df7ee35 | ||
|
|
dacd8f26fd | ||
|
|
ede037ade7 | ||
|
|
fa921b3c7b |
@@ -27,11 +27,11 @@
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 20.19.4",
|
||||
"electron": ">=36.3.0"
|
||||
"electron": ">=37.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.6",
|
||||
"electron": "36.3.0"
|
||||
"electron": "37.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"semver": "^7.1.3"
|
||||
|
||||
@@ -6213,97 +6213,74 @@ public final class com/facebook/react/views/text/ReactTypefaceUtils {
|
||||
public static final fun parseFontWeight (Ljava/lang/String;)I
|
||||
}
|
||||
|
||||
public class com/facebook/react/views/text/TextAttributeProps {
|
||||
public static final field TA_KEY_ACCESSIBILITY_ROLE S
|
||||
public static final field TA_KEY_ALIGNMENT S
|
||||
public static final field TA_KEY_ALLOW_FONT_SCALING S
|
||||
public static final field TA_KEY_BACKGROUND_COLOR S
|
||||
public static final field TA_KEY_BEST_WRITING_DIRECTION S
|
||||
public static final field TA_KEY_FONT_FAMILY S
|
||||
public static final field TA_KEY_FONT_SIZE S
|
||||
public static final field TA_KEY_FONT_SIZE_MULTIPLIER S
|
||||
public static final field TA_KEY_FONT_STYLE S
|
||||
public static final field TA_KEY_FONT_VARIANT S
|
||||
public static final field TA_KEY_FONT_WEIGHT S
|
||||
public static final field TA_KEY_FOREGROUND_COLOR S
|
||||
public static final field TA_KEY_IS_HIGHLIGHTED S
|
||||
public static final field TA_KEY_LAYOUT_DIRECTION S
|
||||
public static final field TA_KEY_LETTER_SPACING S
|
||||
public static final field TA_KEY_LINE_BREAK_STRATEGY S
|
||||
public static final field TA_KEY_LINE_HEIGHT S
|
||||
public static final field TA_KEY_MAX_FONT_SIZE_MULTIPLIER S
|
||||
public static final field TA_KEY_OPACITY S
|
||||
public static final field TA_KEY_ROLE S
|
||||
public static final field TA_KEY_TEXT_DECORATION_COLOR S
|
||||
public static final field TA_KEY_TEXT_DECORATION_LINE S
|
||||
public static final field TA_KEY_TEXT_DECORATION_STYLE S
|
||||
public static final field TA_KEY_TEXT_SHADOW_COLOR S
|
||||
public static final field TA_KEY_TEXT_SHADOW_OFFSET_DX S
|
||||
public static final field TA_KEY_TEXT_SHADOW_OFFSET_DY S
|
||||
public static final field TA_KEY_TEXT_SHADOW_RADIUS S
|
||||
public static final field TA_KEY_TEXT_TRANSFORM S
|
||||
public final class com/facebook/react/views/text/TextAttributeProps {
|
||||
public static final field Companion Lcom/facebook/react/views/text/TextAttributeProps$Companion;
|
||||
public static final field TA_KEY_ACCESSIBILITY_ROLE I
|
||||
public static final field TA_KEY_ALIGNMENT I
|
||||
public static final field TA_KEY_ALLOW_FONT_SCALING I
|
||||
public static final field TA_KEY_BACKGROUND_COLOR I
|
||||
public static final field TA_KEY_BEST_WRITING_DIRECTION I
|
||||
public static final field TA_KEY_FONT_FAMILY I
|
||||
public static final field TA_KEY_FONT_SIZE I
|
||||
public static final field TA_KEY_FONT_SIZE_MULTIPLIER I
|
||||
public static final field TA_KEY_FONT_STYLE I
|
||||
public static final field TA_KEY_FONT_VARIANT I
|
||||
public static final field TA_KEY_FONT_WEIGHT I
|
||||
public static final field TA_KEY_FOREGROUND_COLOR I
|
||||
public static final field TA_KEY_IS_HIGHLIGHTED I
|
||||
public static final field TA_KEY_LAYOUT_DIRECTION I
|
||||
public static final field TA_KEY_LETTER_SPACING I
|
||||
public static final field TA_KEY_LINE_BREAK_STRATEGY I
|
||||
public static final field TA_KEY_LINE_HEIGHT I
|
||||
public static final field TA_KEY_MAX_FONT_SIZE_MULTIPLIER I
|
||||
public static final field TA_KEY_OPACITY I
|
||||
public static final field TA_KEY_ROLE I
|
||||
public static final field TA_KEY_TEXT_DECORATION_COLOR I
|
||||
public static final field TA_KEY_TEXT_DECORATION_LINE I
|
||||
public static final field TA_KEY_TEXT_DECORATION_STYLE I
|
||||
public static final field TA_KEY_TEXT_SHADOW_COLOR I
|
||||
public static final field TA_KEY_TEXT_SHADOW_OFFSET_DX I
|
||||
public static final field TA_KEY_TEXT_SHADOW_OFFSET_DY I
|
||||
public static final field TA_KEY_TEXT_SHADOW_RADIUS I
|
||||
public static final field TA_KEY_TEXT_TRANSFORM I
|
||||
public static final field UNSET I
|
||||
protected field mAccessibilityRole Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$AccessibilityRole;
|
||||
protected field mAllowFontScaling Z
|
||||
protected field mBackgroundColor I
|
||||
protected field mColor I
|
||||
protected field mContainsImages Z
|
||||
protected field mFontFamily Ljava/lang/String;
|
||||
protected field mFontFeatureSettings Ljava/lang/String;
|
||||
protected field mFontSize I
|
||||
protected field mFontSizeInput F
|
||||
protected field mFontStyle I
|
||||
protected field mFontWeight I
|
||||
protected field mHeightOfTallestInlineImage F
|
||||
protected field mIncludeFontPadding Z
|
||||
protected field mIsBackgroundColorSet Z
|
||||
protected field mIsColorSet Z
|
||||
protected field mIsLineThroughTextDecorationSet Z
|
||||
protected field mIsUnderlineTextDecorationSet Z
|
||||
protected field mLayoutDirection I
|
||||
protected field mLetterSpacingInput F
|
||||
protected field mLineHeight F
|
||||
protected field mLineHeightInput F
|
||||
protected field mMaxFontSizeMultiplier F
|
||||
protected field mNumberOfLines I
|
||||
protected field mOpacity F
|
||||
protected field mRole Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$Role;
|
||||
protected field mTextAlign I
|
||||
protected field mTextShadowColor I
|
||||
protected field mTextShadowOffsetDx F
|
||||
protected field mTextShadowOffsetDy F
|
||||
protected field mTextShadowRadius F
|
||||
protected field mTextTransform Lcom/facebook/react/views/text/TextTransform;
|
||||
public static fun fromMapBuffer (Lcom/facebook/react/common/mapbuffer/MapBuffer;)Lcom/facebook/react/views/text/TextAttributeProps;
|
||||
public static fun fromReadableMap (Lcom/facebook/react/uimanager/ReactStylesDiffMap;)Lcom/facebook/react/views/text/TextAttributeProps;
|
||||
public fun getAccessibilityRole ()Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$AccessibilityRole;
|
||||
public fun getBackgroundColor ()I
|
||||
public fun getColor ()I
|
||||
public fun getEffectiveFontSize ()I
|
||||
public fun getEffectiveLetterSpacing ()F
|
||||
public fun getEffectiveLineHeight ()F
|
||||
public static fun getEllipsizeMode (Ljava/lang/String;)Landroid/text/TextUtils$TruncateAt;
|
||||
public fun getFontFamily ()Ljava/lang/String;
|
||||
public fun getFontFeatureSettings ()Ljava/lang/String;
|
||||
public fun getFontStyle ()I
|
||||
public fun getFontWeight ()I
|
||||
public static fun getHyphenationFrequency (Ljava/lang/String;)I
|
||||
public static fun getJustificationMode (Lcom/facebook/react/uimanager/ReactStylesDiffMap;I)I
|
||||
public static fun getLayoutDirection (Ljava/lang/String;)I
|
||||
public fun getLetterSpacing ()F
|
||||
public fun getOpacity ()F
|
||||
public fun getRole ()Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$Role;
|
||||
public static fun getTextAlignment (Lcom/facebook/react/uimanager/ReactStylesDiffMap;ZI)I
|
||||
public static fun getTextBreakStrategy (Ljava/lang/String;)I
|
||||
public fun getTextShadowColor ()I
|
||||
public fun getTextShadowOffsetDx ()F
|
||||
public fun getTextShadowOffsetDy ()F
|
||||
public fun getTextShadowRadius ()F
|
||||
public fun getTextTransform ()Lcom/facebook/react/views/text/TextTransform;
|
||||
public fun isBackgroundColorSet ()Z
|
||||
public fun isColorSet ()Z
|
||||
public fun isLineThroughTextDecorationSet ()Z
|
||||
public fun isUnderlineTextDecorationSet ()Z
|
||||
public final fun getAccessibilityRole ()Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$AccessibilityRole;
|
||||
public final fun getAllowFontScaling ()Z
|
||||
public final fun getBackgroundColor ()Ljava/lang/Integer;
|
||||
public final fun getColor ()Ljava/lang/Integer;
|
||||
public final fun getEffectiveLetterSpacing ()F
|
||||
public final fun getEffectiveLineHeight ()F
|
||||
public final fun getFontFamily ()Ljava/lang/String;
|
||||
public final fun getFontFeatureSettings ()Ljava/lang/String;
|
||||
public final fun getFontSize ()I
|
||||
public final fun getFontStyle ()I
|
||||
public final fun getFontWeight ()I
|
||||
public final fun getLayoutDirection ()I
|
||||
public final fun getLetterSpacing ()F
|
||||
public final fun getLineHeight ()F
|
||||
public final fun getMaxFontSizeMultiplier ()F
|
||||
public final fun getNumberOfLines ()I
|
||||
public final fun getOpacity ()F
|
||||
public final fun getRole ()Lcom/facebook/react/uimanager/ReactAccessibilityDelegate$Role;
|
||||
public final fun getTextShadowColor ()I
|
||||
public final fun getTextShadowOffsetDx ()F
|
||||
public final fun getTextShadowOffsetDy ()F
|
||||
public final fun getTextShadowRadius ()F
|
||||
public final fun isBackgroundColorSet ()Z
|
||||
public final fun isColorSet ()Z
|
||||
public final fun isLineThroughTextDecorationSet ()Z
|
||||
public final fun isUnderlineTextDecorationSet ()Z
|
||||
}
|
||||
|
||||
public final class com/facebook/react/views/text/TextAttributeProps$Companion {
|
||||
public final fun fromMapBuffer (Lcom/facebook/react/common/mapbuffer/MapBuffer;)Lcom/facebook/react/views/text/TextAttributeProps;
|
||||
public final fun fromReadableMap (Lcom/facebook/react/uimanager/ReactStylesDiffMap;)Lcom/facebook/react/views/text/TextAttributeProps;
|
||||
public final fun getEllipsizeMode (Ljava/lang/String;)Landroid/text/TextUtils$TruncateAt;
|
||||
public final fun getHyphenationFrequency (Ljava/lang/String;)I
|
||||
public final fun getJustificationMode (Lcom/facebook/react/uimanager/ReactStylesDiffMap;I)I
|
||||
public final fun getLayoutDirection (Ljava/lang/String;)I
|
||||
public final fun getTextAlignment (Lcom/facebook/react/uimanager/ReactStylesDiffMap;ZI)I
|
||||
public final fun getTextBreakStrategy (Ljava/lang/String;)I
|
||||
}
|
||||
|
||||
public final class com/facebook/react/views/text/TextAttributes {
|
||||
|
||||
+19
@@ -70,6 +70,7 @@ import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatur
|
||||
import com.facebook.react.internal.interop.InteropEventEmitter;
|
||||
import com.facebook.react.modules.core.ReactChoreographer;
|
||||
import com.facebook.react.modules.i18nmanager.I18nUtil;
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder;
|
||||
import com.facebook.react.uimanager.GuardedFrameCallback;
|
||||
import com.facebook.react.uimanager.IllegalViewOperationException;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
@@ -97,6 +98,7 @@ import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
@@ -725,6 +727,23 @@ public class FabricUIManager
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to get the encoded screen size without vertical insets for a given surface.
|
||||
* It's used by the Modal component to determine the size of the screen without vertical insets.
|
||||
* The method is private as it's accessed via JNI from C++.
|
||||
*
|
||||
* @param surfaceId The surface ID of the surface for which the Modal is going to render.
|
||||
* @return The encoded screen size as a long (both width and height) are represented without
|
||||
* vertical insets.
|
||||
*/
|
||||
private long getEncodedScreenSizeWithoutVerticalInsets(int surfaceId) {
|
||||
SurfaceMountingManager surfaceMountingManager = mMountingManager.getSurfaceManager(surfaceId);
|
||||
Objects.requireNonNull(surfaceMountingManager);
|
||||
ThemedReactContext context = Objects.requireNonNull(surfaceMountingManager.getContext());
|
||||
return DisplayMetricsHolder.getEncodedScreenSizeWithoutVerticalInsets(
|
||||
context.getCurrentActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUIManagerEventListener(UIManagerListener listener) {
|
||||
mListeners.add(listener);
|
||||
|
||||
+30
@@ -16,6 +16,7 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.window.layout.WindowMetricsCalculator
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import com.facebook.react.bridge.WritableNativeMap
|
||||
import com.facebook.react.uimanager.PixelUtil.pxToDp
|
||||
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
|
||||
|
||||
/**
|
||||
@@ -140,4 +141,33 @@ public object DisplayMetricsHolder {
|
||||
WindowInsetsCompat.Type.displayCutout())
|
||||
.top
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encoded screen size without vertical insets.
|
||||
*
|
||||
* This is needed to render components that needs to be correctly positioned on the screen on
|
||||
* their first frame. Modal is one of such components.
|
||||
*
|
||||
* @param activity the [Activity] to get the insets from.
|
||||
* @return the encoded screen size as a [Long] value, where the first 32 bits represent the width
|
||||
* and the last 32 bits represent the height in dp (density-independent pixels).
|
||||
*/
|
||||
// This annotation can be removed once FabricUIManager is migrated to Kotlin
|
||||
@JvmName("getEncodedScreenSizeWithoutVerticalInsets")
|
||||
@JvmStatic
|
||||
internal fun getEncodedScreenSizeWithoutVerticalInsets(activity: Activity?): Long {
|
||||
val windowInsets = activity?.window?.decorView?.let(ViewCompat::getRootWindowInsets) ?: return 0
|
||||
val insets =
|
||||
windowInsets.getInsets(
|
||||
WindowInsetsCompat.Type.statusBars() or
|
||||
WindowInsetsCompat.Type.navigationBars() or
|
||||
WindowInsetsCompat.Type.displayCutout())
|
||||
val verticalInsets = insets.top + insets.bottom
|
||||
return encodeFloatsToLong(
|
||||
(checkNotNull(screenDisplayMetrics).widthPixels).toFloat().pxToDp(),
|
||||
(checkNotNull(screenDisplayMetrics).heightPixels - verticalInsets).toFloat().pxToDp())
|
||||
}
|
||||
|
||||
internal fun encodeFloatsToLong(width: Float, height: Float): Long =
|
||||
(width.toRawBits().toLong()) shl 32 or (height.toRawBits().toLong())
|
||||
}
|
||||
|
||||
-26
@@ -41,8 +41,6 @@ import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
||||
import com.facebook.react.common.annotations.VisibleForTesting
|
||||
import com.facebook.react.common.build.ReactBuildConfig
|
||||
import com.facebook.react.config.ReactFeatureFlags
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder.getStatusBarHeightPx
|
||||
import com.facebook.react.uimanager.JSPointerDispatcher
|
||||
import com.facebook.react.uimanager.JSTouchDispatcher
|
||||
import com.facebook.react.uimanager.PixelUtil.pxToDp
|
||||
@@ -52,13 +50,11 @@ import com.facebook.react.uimanager.ThemedReactContext
|
||||
import com.facebook.react.uimanager.UIManagerModule
|
||||
import com.facebook.react.uimanager.events.EventDispatcher
|
||||
import com.facebook.react.views.common.ContextUtils
|
||||
import com.facebook.react.views.modal.ReactModalHostView.DialogRootViewGroup
|
||||
import com.facebook.react.views.view.ReactViewGroup
|
||||
import com.facebook.react.views.view.disableEdgeToEdge
|
||||
import com.facebook.react.views.view.enableEdgeToEdge
|
||||
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
|
||||
import com.facebook.react.views.view.setStatusBarTranslucency
|
||||
import com.facebook.yoga.annotations.DoNotStrip
|
||||
|
||||
/**
|
||||
* ReactModalHostView is a view that sits in the view hierarchy representing a Modal view.
|
||||
@@ -73,7 +69,6 @@ import com.facebook.yoga.annotations.DoNotStrip
|
||||
* addition and removal of views to the DialogRootViewGroup.
|
||||
*/
|
||||
@SuppressLint("ViewConstructor")
|
||||
@DoNotStrip
|
||||
public class ReactModalHostView(context: ThemedReactContext) :
|
||||
ViewGroup(context), LifecycleEventListener {
|
||||
|
||||
@@ -132,7 +127,6 @@ public class ReactModalHostView(context: ThemedReactContext) :
|
||||
private var createNewDialog = false
|
||||
|
||||
init {
|
||||
initStatusBarHeight(context)
|
||||
dialogRootViewGroup = DialogRootViewGroup(context)
|
||||
}
|
||||
|
||||
@@ -485,26 +479,6 @@ public class ReactModalHostView(context: ThemedReactContext) :
|
||||
|
||||
private companion object {
|
||||
private const val TAG = "ReactModalHost"
|
||||
|
||||
// We store the status bar height to be able to properly position
|
||||
// the modal on the first render.
|
||||
private var statusBarHeight = 0
|
||||
|
||||
private fun initStatusBarHeight(reactContext: ReactContext) {
|
||||
statusBarHeight = getStatusBarHeightPx(reactContext.currentActivity)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@DoNotStrip
|
||||
private fun getScreenDisplayMetricsWithoutInsets(): Long {
|
||||
val displayMetrics = DisplayMetricsHolder.getScreenDisplayMetrics()
|
||||
return encodeFloatsToLong(
|
||||
displayMetrics.widthPixels.toFloat().pxToDp(),
|
||||
(displayMetrics.heightPixels - statusBarHeight).toFloat().pxToDp())
|
||||
}
|
||||
|
||||
private fun encodeFloatsToLong(width: Float, height: Float): Long =
|
||||
(width.toRawBits().toLong()) shl 32 or (height.toRawBits().toLong())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
-811
@@ -1,811 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.text;
|
||||
|
||||
import android.os.Build;
|
||||
import android.text.Layout;
|
||||
import android.text.TextUtils;
|
||||
import android.util.LayoutDirection;
|
||||
import android.view.Gravity;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.mapbuffer.MapBuffer;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.ReactAccessibilityDelegate.AccessibilityRole;
|
||||
import com.facebook.react.uimanager.ReactAccessibilityDelegate.Role;
|
||||
import com.facebook.react.uimanager.ReactStylesDiffMap;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
// TODO: T63643819 refactor naming of TextAttributeProps to make explicit that this represents
|
||||
// TextAttributes and not TextProps. As part of this refactor extract methods that don't belong to
|
||||
// TextAttributeProps (e.g. TextAlign)
|
||||
@Nullsafe(Nullsafe.Mode.LOCAL)
|
||||
public class TextAttributeProps {
|
||||
|
||||
// constants for Text Attributes serialization
|
||||
public static final short TA_KEY_FOREGROUND_COLOR = 0;
|
||||
public static final short TA_KEY_BACKGROUND_COLOR = 1;
|
||||
public static final short TA_KEY_OPACITY = 2;
|
||||
public static final short TA_KEY_FONT_FAMILY = 3;
|
||||
public static final short TA_KEY_FONT_SIZE = 4;
|
||||
public static final short TA_KEY_FONT_SIZE_MULTIPLIER = 5;
|
||||
public static final short TA_KEY_FONT_WEIGHT = 6;
|
||||
public static final short TA_KEY_FONT_STYLE = 7;
|
||||
public static final short TA_KEY_FONT_VARIANT = 8;
|
||||
public static final short TA_KEY_ALLOW_FONT_SCALING = 9;
|
||||
public static final short TA_KEY_LETTER_SPACING = 10;
|
||||
public static final short TA_KEY_LINE_HEIGHT = 11;
|
||||
public static final short TA_KEY_ALIGNMENT = 12;
|
||||
public static final short TA_KEY_BEST_WRITING_DIRECTION = 13;
|
||||
public static final short TA_KEY_TEXT_DECORATION_COLOR = 14;
|
||||
public static final short TA_KEY_TEXT_DECORATION_LINE = 15;
|
||||
public static final short TA_KEY_TEXT_DECORATION_STYLE = 16;
|
||||
public static final short TA_KEY_TEXT_SHADOW_RADIUS = 18;
|
||||
public static final short TA_KEY_TEXT_SHADOW_COLOR = 19;
|
||||
public static final short TA_KEY_TEXT_SHADOW_OFFSET_DX = 20;
|
||||
public static final short TA_KEY_TEXT_SHADOW_OFFSET_DY = 21;
|
||||
public static final short TA_KEY_IS_HIGHLIGHTED = 22;
|
||||
public static final short TA_KEY_LAYOUT_DIRECTION = 23;
|
||||
public static final short TA_KEY_ACCESSIBILITY_ROLE = 24;
|
||||
public static final short TA_KEY_LINE_BREAK_STRATEGY = 25;
|
||||
public static final short TA_KEY_ROLE = 26;
|
||||
public static final short TA_KEY_TEXT_TRANSFORM = 27;
|
||||
public static final short TA_KEY_MAX_FONT_SIZE_MULTIPLIER = 29;
|
||||
|
||||
public static final int UNSET = -1;
|
||||
|
||||
private static final String PROP_SHADOW_OFFSET = "textShadowOffset";
|
||||
private static final String PROP_SHADOW_OFFSET_WIDTH = "width";
|
||||
private static final String PROP_SHADOW_OFFSET_HEIGHT = "height";
|
||||
private static final String PROP_SHADOW_RADIUS = "textShadowRadius";
|
||||
private static final String PROP_SHADOW_COLOR = "textShadowColor";
|
||||
|
||||
private static final String PROP_TEXT_TRANSFORM = "textTransform";
|
||||
|
||||
private static final int DEFAULT_TEXT_SHADOW_COLOR = 0x55000000;
|
||||
private static final int DEFAULT_JUSTIFICATION_MODE =
|
||||
(Build.VERSION.SDK_INT < Build.VERSION_CODES.O) ? 0 : Layout.JUSTIFICATION_MODE_NONE;
|
||||
private static final int DEFAULT_BREAK_STRATEGY = Layout.BREAK_STRATEGY_HIGH_QUALITY;
|
||||
private static final int DEFAULT_HYPHENATION_FREQUENCY = Layout.HYPHENATION_FREQUENCY_NONE;
|
||||
|
||||
protected float mLineHeight = Float.NaN;
|
||||
protected boolean mIsColorSet = false;
|
||||
protected boolean mAllowFontScaling = true;
|
||||
protected float mMaxFontSizeMultiplier = Float.NaN;
|
||||
protected int mColor;
|
||||
protected boolean mIsBackgroundColorSet = false;
|
||||
protected int mBackgroundColor;
|
||||
protected float mOpacity = Float.NaN;
|
||||
|
||||
protected int mNumberOfLines = ReactConstants.UNSET;
|
||||
protected int mFontSize = ReactConstants.UNSET;
|
||||
protected float mFontSizeInput = ReactConstants.UNSET;
|
||||
protected float mLineHeightInput = ReactConstants.UNSET;
|
||||
protected float mLetterSpacingInput = Float.NaN;
|
||||
protected int mTextAlign = Gravity.NO_GRAVITY;
|
||||
|
||||
// `ReactConstants.UNSET` is -1, same as `LayoutDirection.UNDEFINED` (which is a hidden symbol)
|
||||
protected int mLayoutDirection = ReactConstants.UNSET;
|
||||
|
||||
@NonNull protected TextTransform mTextTransform = TextTransform.NONE;
|
||||
|
||||
protected float mTextShadowOffsetDx = 0;
|
||||
protected float mTextShadowOffsetDy = 0;
|
||||
protected float mTextShadowRadius = 0;
|
||||
protected int mTextShadowColor = DEFAULT_TEXT_SHADOW_COLOR;
|
||||
|
||||
protected boolean mIsUnderlineTextDecorationSet = false;
|
||||
protected boolean mIsLineThroughTextDecorationSet = false;
|
||||
protected boolean mIncludeFontPadding = true;
|
||||
|
||||
protected @Nullable AccessibilityRole mAccessibilityRole = null;
|
||||
protected @Nullable Role mRole = null;
|
||||
|
||||
protected int mFontStyle = ReactConstants.UNSET;
|
||||
protected int mFontWeight = ReactConstants.UNSET;
|
||||
|
||||
/**
|
||||
* NB: If a font family is used that does not have a style in a certain Android version (ie.
|
||||
* monospace bold pre Android 5.0), that style (ie. bold) will not be inherited by nested Text
|
||||
* nodes. To retain that style, you have to add it to those nodes explicitly.
|
||||
*
|
||||
* <p>Example, Android 4.4:
|
||||
*
|
||||
* <pre>
|
||||
* <Text style={{fontFamily="serif" fontWeight="bold"}}>Bold Text</Text>
|
||||
* <Text style={{fontFamily="sans-serif"}}>Bold Text</Text>
|
||||
* <Text style={{fontFamily="serif}}>Bold Text</Text>
|
||||
*
|
||||
* <Text style={{fontFamily="monospace" fontWeight="bold"}}>Not Bold Text</Text>
|
||||
* <Text style={{fontFamily="sans-serif"}}>Not Bold Text</Text>
|
||||
* <Text style={{fontFamily="serif}}>Not Bold Text</Text>
|
||||
*
|
||||
* <Text style={{fontFamily="monospace" fontWeight="bold"}}>Not Bold Text</Text>
|
||||
* <Text style={{fontFamily="sans-serif" fontWeight="bold"}}>Bold Text</Text>
|
||||
* <Text style={{fontFamily="serif}}>Bold Text</Text>
|
||||
* </pre>
|
||||
*/
|
||||
protected @Nullable String mFontFamily = null;
|
||||
|
||||
/**
|
||||
* @see android.graphics.Paint#setFontFeatureSettings
|
||||
*/
|
||||
protected @Nullable String mFontFeatureSettings = null;
|
||||
|
||||
protected boolean mContainsImages = false;
|
||||
protected float mHeightOfTallestInlineImage = Float.NaN;
|
||||
|
||||
private TextAttributeProps() {}
|
||||
|
||||
/** Build a TextAttributeProps using data from the {@link MapBuffer} received as a parameter. */
|
||||
public static TextAttributeProps fromMapBuffer(MapBuffer props) {
|
||||
TextAttributeProps result = new TextAttributeProps();
|
||||
|
||||
// TODO T83483191: Review constants that are not being set!
|
||||
Iterator<MapBuffer.Entry> iterator = props.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
MapBuffer.Entry entry = iterator.next();
|
||||
switch (entry.getKey()) {
|
||||
case TA_KEY_FOREGROUND_COLOR:
|
||||
result.setColor(entry.getIntValue());
|
||||
break;
|
||||
case TA_KEY_BACKGROUND_COLOR:
|
||||
result.setBackgroundColor(entry.getIntValue());
|
||||
break;
|
||||
case TA_KEY_OPACITY:
|
||||
result.setOpacity((float) entry.getDoubleValue());
|
||||
break;
|
||||
case TA_KEY_FONT_FAMILY:
|
||||
result.setFontFamily(entry.getStringValue());
|
||||
break;
|
||||
case TA_KEY_FONT_SIZE:
|
||||
result.setFontSize((float) entry.getDoubleValue());
|
||||
break;
|
||||
case TA_KEY_FONT_SIZE_MULTIPLIER:
|
||||
break;
|
||||
case TA_KEY_FONT_WEIGHT:
|
||||
result.setFontWeight(entry.getStringValue());
|
||||
break;
|
||||
case TA_KEY_FONT_STYLE:
|
||||
result.setFontStyle(entry.getStringValue());
|
||||
break;
|
||||
case TA_KEY_FONT_VARIANT:
|
||||
result.setFontVariant(entry.getMapBufferValue());
|
||||
break;
|
||||
case TA_KEY_ALLOW_FONT_SCALING:
|
||||
result.setAllowFontScaling(entry.getBooleanValue());
|
||||
break;
|
||||
case TA_KEY_LETTER_SPACING:
|
||||
result.setLetterSpacing((float) entry.getDoubleValue());
|
||||
break;
|
||||
case TA_KEY_LINE_HEIGHT:
|
||||
result.setLineHeight((float) entry.getDoubleValue());
|
||||
break;
|
||||
case TA_KEY_ALIGNMENT:
|
||||
break;
|
||||
case TA_KEY_BEST_WRITING_DIRECTION:
|
||||
break;
|
||||
case TA_KEY_TEXT_DECORATION_COLOR:
|
||||
break;
|
||||
case TA_KEY_TEXT_DECORATION_LINE:
|
||||
result.setTextDecorationLine(entry.getStringValue());
|
||||
break;
|
||||
case TA_KEY_TEXT_DECORATION_STYLE:
|
||||
break;
|
||||
case TA_KEY_TEXT_SHADOW_RADIUS:
|
||||
result.setTextShadowRadius((float) entry.getDoubleValue());
|
||||
break;
|
||||
case TA_KEY_TEXT_SHADOW_COLOR:
|
||||
result.setTextShadowColor(entry.getIntValue());
|
||||
break;
|
||||
case TA_KEY_TEXT_SHADOW_OFFSET_DX:
|
||||
result.setTextShadowOffsetDx((float) entry.getDoubleValue());
|
||||
break;
|
||||
case TA_KEY_TEXT_SHADOW_OFFSET_DY:
|
||||
result.setTextShadowOffsetDy((float) entry.getDoubleValue());
|
||||
break;
|
||||
case TA_KEY_IS_HIGHLIGHTED:
|
||||
break;
|
||||
case TA_KEY_LAYOUT_DIRECTION:
|
||||
result.setLayoutDirection(entry.getStringValue());
|
||||
break;
|
||||
case TA_KEY_ACCESSIBILITY_ROLE:
|
||||
result.setAccessibilityRole(entry.getStringValue());
|
||||
break;
|
||||
case TA_KEY_ROLE:
|
||||
result.setRole(Role.values()[entry.getIntValue()]);
|
||||
break;
|
||||
case TA_KEY_TEXT_TRANSFORM:
|
||||
result.setTextTransform(entry.getStringValue());
|
||||
break;
|
||||
case TA_KEY_MAX_FONT_SIZE_MULTIPLIER:
|
||||
result.setMaxFontSizeMultiplier((float) entry.getDoubleValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO T83483191: Review why the following props are not serialized:
|
||||
// setNumberOfLines
|
||||
// setColor
|
||||
// setIncludeFontPadding
|
||||
return result;
|
||||
}
|
||||
|
||||
public static TextAttributeProps fromReadableMap(ReactStylesDiffMap props) {
|
||||
TextAttributeProps result = new TextAttributeProps();
|
||||
result.setNumberOfLines(getIntProp(props, ViewProps.NUMBER_OF_LINES, ReactConstants.UNSET));
|
||||
result.setLineHeight(getFloatProp(props, ViewProps.LINE_HEIGHT, ReactConstants.UNSET));
|
||||
result.setLetterSpacing(getFloatProp(props, ViewProps.LETTER_SPACING, Float.NaN));
|
||||
result.setAllowFontScaling(getBooleanProp(props, ViewProps.ALLOW_FONT_SCALING, true));
|
||||
result.setMaxFontSizeMultiplier(
|
||||
getFloatProp(props, ViewProps.MAX_FONT_SIZE_MULTIPLIER, Float.NaN));
|
||||
result.setFontSize(getFloatProp(props, ViewProps.FONT_SIZE, ReactConstants.UNSET));
|
||||
result.setColor(props.hasKey(ViewProps.COLOR) ? props.getInt(ViewProps.COLOR, 0) : null);
|
||||
result.setColor(
|
||||
props.hasKey(ViewProps.FOREGROUND_COLOR)
|
||||
? props.getInt(ViewProps.FOREGROUND_COLOR, 0)
|
||||
: null);
|
||||
result.setBackgroundColor(
|
||||
props.hasKey(ViewProps.BACKGROUND_COLOR)
|
||||
? props.getInt(ViewProps.BACKGROUND_COLOR, 0)
|
||||
: null);
|
||||
result.setOpacity(getFloatProp(props, ViewProps.OPACITY, Float.NaN));
|
||||
result.setFontFamily(getStringProp(props, ViewProps.FONT_FAMILY));
|
||||
result.setFontWeight(getStringProp(props, ViewProps.FONT_WEIGHT));
|
||||
result.setFontStyle(getStringProp(props, ViewProps.FONT_STYLE));
|
||||
result.setFontVariant(getArrayProp(props, ViewProps.FONT_VARIANT));
|
||||
result.setIncludeFontPadding(getBooleanProp(props, ViewProps.INCLUDE_FONT_PADDING, true));
|
||||
result.setTextDecorationLine(getStringProp(props, ViewProps.TEXT_DECORATION_LINE));
|
||||
result.setTextShadowOffset(
|
||||
props.hasKey(PROP_SHADOW_OFFSET) ? props.getMap(PROP_SHADOW_OFFSET) : null);
|
||||
result.setTextShadowRadius(getFloatProp(props, PROP_SHADOW_RADIUS, 1));
|
||||
result.setTextShadowColor(getIntProp(props, PROP_SHADOW_COLOR, DEFAULT_TEXT_SHADOW_COLOR));
|
||||
result.setTextTransform(getStringProp(props, PROP_TEXT_TRANSFORM));
|
||||
result.setLayoutDirection(getStringProp(props, ViewProps.LAYOUT_DIRECTION));
|
||||
result.setAccessibilityRole(getStringProp(props, ViewProps.ACCESSIBILITY_ROLE));
|
||||
result.setRole(getStringProp(props, ViewProps.ROLE));
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int getTextAlignment(ReactStylesDiffMap props, boolean isRTL, int defaultValue) {
|
||||
if (!props.hasKey(ViewProps.TEXT_ALIGN)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
String textAlignPropValue = props.getString(ViewProps.TEXT_ALIGN);
|
||||
if ("justify".equals(textAlignPropValue)) {
|
||||
return Gravity.LEFT;
|
||||
} else {
|
||||
if (textAlignPropValue == null || "auto".equals(textAlignPropValue)) {
|
||||
return Gravity.NO_GRAVITY;
|
||||
} else if ("left".equals(textAlignPropValue)) {
|
||||
return isRTL ? Gravity.RIGHT : Gravity.LEFT;
|
||||
} else if ("right".equals(textAlignPropValue)) {
|
||||
return isRTL ? Gravity.LEFT : Gravity.RIGHT;
|
||||
} else if ("center".equals(textAlignPropValue)) {
|
||||
return Gravity.CENTER_HORIZONTAL;
|
||||
} else {
|
||||
FLog.w(ReactConstants.TAG, "Invalid textAlign: " + textAlignPropValue);
|
||||
return Gravity.NO_GRAVITY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int getJustificationMode(ReactStylesDiffMap props, int defaultValue) {
|
||||
if (!props.hasKey(ViewProps.TEXT_ALIGN)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
String textAlignPropValue = props.getString(ViewProps.TEXT_ALIGN);
|
||||
if ("justify".equals(textAlignPropValue) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
return Layout.JUSTIFICATION_MODE_INTER_WORD;
|
||||
}
|
||||
return DEFAULT_JUSTIFICATION_MODE;
|
||||
}
|
||||
|
||||
private static boolean getBooleanProp(
|
||||
ReactStylesDiffMap mProps, String name, boolean defaultValue) {
|
||||
if (mProps.hasKey(name)) {
|
||||
return mProps.getBoolean(name, defaultValue);
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable String getStringProp(ReactStylesDiffMap mProps, String name) {
|
||||
if (mProps.hasKey(name)) {
|
||||
return mProps.getString(name);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getIntProp(ReactStylesDiffMap mProps, String name, int defaultvalue) {
|
||||
if (mProps.hasKey(name)) {
|
||||
return mProps.getInt(name, defaultvalue);
|
||||
} else {
|
||||
return defaultvalue;
|
||||
}
|
||||
}
|
||||
|
||||
private static float getFloatProp(ReactStylesDiffMap mProps, String name, float defaultvalue) {
|
||||
if (mProps.hasKey(name)) {
|
||||
return mProps.getFloat(name, defaultvalue);
|
||||
} else {
|
||||
return defaultvalue;
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable ReadableArray getArrayProp(ReactStylesDiffMap mProps, String name) {
|
||||
if (mProps.hasKey(name)) {
|
||||
return mProps.getArray(name);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a line height which takes into account the requested line height
|
||||
// and the height of the inline images.
|
||||
public float getEffectiveLineHeight() {
|
||||
boolean useInlineViewHeight =
|
||||
!Float.isNaN(mLineHeight)
|
||||
&& !Float.isNaN(mHeightOfTallestInlineImage)
|
||||
&& mHeightOfTallestInlineImage > mLineHeight;
|
||||
return useInlineViewHeight ? mHeightOfTallestInlineImage : mLineHeight;
|
||||
}
|
||||
|
||||
private void setNumberOfLines(int numberOfLines) {
|
||||
mNumberOfLines = numberOfLines == 0 ? ReactConstants.UNSET : numberOfLines;
|
||||
}
|
||||
|
||||
private void setLineHeight(float lineHeight) {
|
||||
mLineHeightInput = lineHeight;
|
||||
if (lineHeight == ReactConstants.UNSET) {
|
||||
mLineHeight = Float.NaN;
|
||||
} else {
|
||||
mLineHeight =
|
||||
mAllowFontScaling
|
||||
? PixelUtil.toPixelFromSP(lineHeight)
|
||||
: PixelUtil.toPixelFromDIP(lineHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private void setLetterSpacing(float letterSpacing) {
|
||||
mLetterSpacingInput = letterSpacing;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public TextTransform getTextTransform() {
|
||||
return mTextTransform;
|
||||
}
|
||||
|
||||
public float getLetterSpacing() {
|
||||
float letterSpacingPixels =
|
||||
mAllowFontScaling
|
||||
? PixelUtil.toPixelFromSP(mLetterSpacingInput)
|
||||
: PixelUtil.toPixelFromDIP(mLetterSpacingInput);
|
||||
|
||||
if (mFontSize <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"FontSize should be a positive value. Current value: " + mFontSize);
|
||||
}
|
||||
// `letterSpacingPixels` and `mFontSize` are both in pixels,
|
||||
// yielding an accurate em value.
|
||||
return letterSpacingPixels / mFontSize;
|
||||
}
|
||||
|
||||
public float getEffectiveLetterSpacing() {
|
||||
return getLetterSpacing();
|
||||
}
|
||||
|
||||
public int getEffectiveFontSize() {
|
||||
return mFontSize;
|
||||
}
|
||||
|
||||
private void setAllowFontScaling(boolean allowFontScaling) {
|
||||
if (allowFontScaling != mAllowFontScaling) {
|
||||
mAllowFontScaling = allowFontScaling;
|
||||
setFontSize(mFontSizeInput);
|
||||
setLineHeight(mLineHeightInput);
|
||||
}
|
||||
}
|
||||
|
||||
private void setMaxFontSizeMultiplier(float maxFontSizeMultiplier) {
|
||||
if (maxFontSizeMultiplier != mMaxFontSizeMultiplier) {
|
||||
mMaxFontSizeMultiplier = maxFontSizeMultiplier;
|
||||
setFontSize(mFontSizeInput);
|
||||
setLineHeight(mLineHeightInput);
|
||||
}
|
||||
}
|
||||
|
||||
private void setFontSize(float fontSize) {
|
||||
mFontSizeInput = fontSize;
|
||||
if (fontSize != ReactConstants.UNSET) {
|
||||
fontSize =
|
||||
mAllowFontScaling
|
||||
? (float) Math.ceil(PixelUtil.toPixelFromSP(fontSize, mMaxFontSizeMultiplier))
|
||||
: (float) Math.ceil(PixelUtil.toPixelFromDIP(fontSize));
|
||||
}
|
||||
mFontSize = (int) fontSize;
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return mColor;
|
||||
}
|
||||
|
||||
private void setColor(@Nullable Integer color) {
|
||||
mIsColorSet = (color != null);
|
||||
if (color != null) {
|
||||
mColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isColorSet() {
|
||||
return mIsColorSet;
|
||||
}
|
||||
|
||||
public int getBackgroundColor() {
|
||||
return mBackgroundColor;
|
||||
}
|
||||
|
||||
private void setBackgroundColor(@Nullable Integer color) {
|
||||
// TODO: Don't apply background color to anchor TextView since it will be applied on the View
|
||||
// directly
|
||||
// if (!isVirtualAnchor()) {
|
||||
mIsBackgroundColorSet = (color != null);
|
||||
if (color != null) {
|
||||
mBackgroundColor = color;
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
public float getOpacity() {
|
||||
return mOpacity;
|
||||
}
|
||||
|
||||
private void setOpacity(float opacity) {
|
||||
mOpacity = opacity;
|
||||
}
|
||||
|
||||
public boolean isBackgroundColorSet() {
|
||||
return mIsBackgroundColorSet;
|
||||
}
|
||||
|
||||
public int getFontStyle() {
|
||||
return mFontStyle;
|
||||
}
|
||||
|
||||
public @Nullable String getFontFamily() {
|
||||
return mFontFamily;
|
||||
}
|
||||
|
||||
private void setFontFamily(@Nullable String fontFamily) {
|
||||
mFontFamily = fontFamily;
|
||||
}
|
||||
|
||||
private void setFontVariant(@Nullable ReadableArray fontVariant) {
|
||||
mFontFeatureSettings = ReactTypefaceUtils.parseFontVariant(fontVariant);
|
||||
}
|
||||
|
||||
private void setFontVariant(@Nullable MapBuffer fontVariant) {
|
||||
if (fontVariant == null || fontVariant.getCount() == 0) {
|
||||
mFontFeatureSettings = null;
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> features = new ArrayList<>();
|
||||
Iterator<MapBuffer.Entry> iterator = fontVariant.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
MapBuffer.Entry entry = iterator.next();
|
||||
String value = entry.getStringValue();
|
||||
if (value != null) {
|
||||
switch (value) {
|
||||
case "small-caps":
|
||||
features.add("'smcp'");
|
||||
break;
|
||||
case "oldstyle-nums":
|
||||
features.add("'onum'");
|
||||
break;
|
||||
case "lining-nums":
|
||||
features.add("'lnum'");
|
||||
break;
|
||||
case "tabular-nums":
|
||||
features.add("'tnum'");
|
||||
break;
|
||||
case "proportional-nums":
|
||||
features.add("'pnum'");
|
||||
break;
|
||||
case "stylistic-one":
|
||||
features.add("'ss01'");
|
||||
break;
|
||||
case "stylistic-two":
|
||||
features.add("'ss02'");
|
||||
break;
|
||||
case "stylistic-three":
|
||||
features.add("'ss03'");
|
||||
break;
|
||||
case "stylistic-four":
|
||||
features.add("'ss04'");
|
||||
break;
|
||||
case "stylistic-five":
|
||||
features.add("'ss05'");
|
||||
break;
|
||||
case "stylistic-six":
|
||||
features.add("'ss06'");
|
||||
break;
|
||||
case "stylistic-seven":
|
||||
features.add("'ss07'");
|
||||
break;
|
||||
case "stylistic-eight":
|
||||
features.add("'ss08'");
|
||||
break;
|
||||
case "stylistic-nine":
|
||||
features.add("'ss09'");
|
||||
break;
|
||||
case "stylistic-ten":
|
||||
features.add("'ss10'");
|
||||
break;
|
||||
case "stylistic-eleven":
|
||||
features.add("'ss11'");
|
||||
break;
|
||||
case "stylistic-twelve":
|
||||
features.add("'ss12'");
|
||||
break;
|
||||
case "stylistic-thirteen":
|
||||
features.add("'ss13'");
|
||||
break;
|
||||
case "stylistic-fourteen":
|
||||
features.add("'ss14'");
|
||||
break;
|
||||
case "stylistic-fifteen":
|
||||
features.add("'ss15'");
|
||||
break;
|
||||
case "stylistic-sixteen":
|
||||
features.add("'ss16'");
|
||||
break;
|
||||
case "stylistic-seventeen":
|
||||
features.add("'ss17'");
|
||||
break;
|
||||
case "stylistic-eighteen":
|
||||
features.add("'ss18'");
|
||||
break;
|
||||
case "stylistic-nineteen":
|
||||
features.add("'ss19'");
|
||||
break;
|
||||
case "stylistic-twenty":
|
||||
features.add("'ss20'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
mFontFeatureSettings = TextUtils.join(", ", features);
|
||||
}
|
||||
|
||||
public @Nullable String getFontFeatureSettings() {
|
||||
return mFontFeatureSettings;
|
||||
}
|
||||
|
||||
public int getFontWeight() {
|
||||
return mFontWeight;
|
||||
}
|
||||
|
||||
private void setFontWeight(@Nullable String fontWeightString) {
|
||||
mFontWeight = ReactTypefaceUtils.parseFontWeight(fontWeightString);
|
||||
}
|
||||
|
||||
private void setFontStyle(@Nullable String fontStyleString) {
|
||||
mFontStyle = ReactTypefaceUtils.parseFontStyle(fontStyleString);
|
||||
}
|
||||
|
||||
private void setIncludeFontPadding(boolean includepad) {
|
||||
mIncludeFontPadding = includepad;
|
||||
}
|
||||
|
||||
private void setTextDecorationLine(@Nullable String textDecorationLineString) {
|
||||
mIsUnderlineTextDecorationSet = false;
|
||||
mIsLineThroughTextDecorationSet = false;
|
||||
if (textDecorationLineString != null) {
|
||||
for (String textDecorationLineSubString : textDecorationLineString.split("-")) {
|
||||
if ("underline".equals(textDecorationLineSubString)) {
|
||||
mIsUnderlineTextDecorationSet = true;
|
||||
} else if ("strikethrough".equals(textDecorationLineSubString)) {
|
||||
mIsLineThroughTextDecorationSet = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isUnderlineTextDecorationSet() {
|
||||
return mIsUnderlineTextDecorationSet;
|
||||
}
|
||||
|
||||
public boolean isLineThroughTextDecorationSet() {
|
||||
return mIsLineThroughTextDecorationSet;
|
||||
}
|
||||
|
||||
private void setTextShadowOffset(@Nullable ReadableMap offsetMap) {
|
||||
mTextShadowOffsetDx = 0;
|
||||
mTextShadowOffsetDy = 0;
|
||||
|
||||
if (offsetMap != null) {
|
||||
if (offsetMap.hasKey(PROP_SHADOW_OFFSET_WIDTH)
|
||||
&& !offsetMap.isNull(PROP_SHADOW_OFFSET_WIDTH)) {
|
||||
mTextShadowOffsetDx =
|
||||
PixelUtil.toPixelFromDIP(offsetMap.getDouble(PROP_SHADOW_OFFSET_WIDTH));
|
||||
}
|
||||
if (offsetMap.hasKey(PROP_SHADOW_OFFSET_HEIGHT)
|
||||
&& !offsetMap.isNull(PROP_SHADOW_OFFSET_HEIGHT)) {
|
||||
mTextShadowOffsetDy =
|
||||
PixelUtil.toPixelFromDIP(offsetMap.getDouble(PROP_SHADOW_OFFSET_HEIGHT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float getTextShadowOffsetDx() {
|
||||
return mTextShadowOffsetDx;
|
||||
}
|
||||
|
||||
private void setTextShadowOffsetDx(float dx) {
|
||||
mTextShadowOffsetDx = PixelUtil.toPixelFromDIP(dx);
|
||||
}
|
||||
|
||||
public float getTextShadowOffsetDy() {
|
||||
return mTextShadowOffsetDy;
|
||||
}
|
||||
|
||||
private void setTextShadowOffsetDy(float dy) {
|
||||
mTextShadowOffsetDy = PixelUtil.toPixelFromDIP(dy);
|
||||
}
|
||||
|
||||
public static int getLayoutDirection(@Nullable String layoutDirection) {
|
||||
int androidLayoutDirection;
|
||||
if (layoutDirection == null || "undefined".equals(layoutDirection)) {
|
||||
androidLayoutDirection = ReactConstants.UNSET;
|
||||
} else if ("rtl".equals(layoutDirection)) {
|
||||
androidLayoutDirection = LayoutDirection.RTL;
|
||||
} else if ("ltr".equals(layoutDirection)) {
|
||||
androidLayoutDirection = LayoutDirection.LTR;
|
||||
} else {
|
||||
FLog.w(ReactConstants.TAG, "Invalid layoutDirection: " + layoutDirection);
|
||||
androidLayoutDirection = ReactConstants.UNSET;
|
||||
}
|
||||
return androidLayoutDirection;
|
||||
}
|
||||
|
||||
private void setLayoutDirection(@Nullable String layoutDirection) {
|
||||
mLayoutDirection = getLayoutDirection(layoutDirection);
|
||||
}
|
||||
|
||||
public float getTextShadowRadius() {
|
||||
return mTextShadowRadius;
|
||||
}
|
||||
|
||||
private void setTextShadowRadius(float textShadowRadius) {
|
||||
if (textShadowRadius != mTextShadowRadius) {
|
||||
mTextShadowRadius = textShadowRadius;
|
||||
}
|
||||
}
|
||||
|
||||
public int getTextShadowColor() {
|
||||
return mTextShadowColor;
|
||||
}
|
||||
|
||||
private void setTextShadowColor(int textShadowColor) {
|
||||
if (textShadowColor != mTextShadowColor) {
|
||||
mTextShadowColor = textShadowColor;
|
||||
}
|
||||
}
|
||||
|
||||
private void setTextTransform(@Nullable String textTransform) {
|
||||
if (textTransform == null || "none".equals(textTransform)) {
|
||||
mTextTransform = TextTransform.NONE;
|
||||
} else if ("uppercase".equals(textTransform)) {
|
||||
mTextTransform = TextTransform.UPPERCASE;
|
||||
} else if ("lowercase".equals(textTransform)) {
|
||||
mTextTransform = TextTransform.LOWERCASE;
|
||||
} else if ("capitalize".equals(textTransform)) {
|
||||
mTextTransform = TextTransform.CAPITALIZE;
|
||||
} else {
|
||||
FLog.w(ReactConstants.TAG, "Invalid textTransform: " + textTransform);
|
||||
mTextTransform = TextTransform.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable AccessibilityRole getAccessibilityRole() {
|
||||
return mAccessibilityRole;
|
||||
}
|
||||
|
||||
private void setAccessibilityRole(@Nullable String accessibilityRole) {
|
||||
if (accessibilityRole == null) {
|
||||
mAccessibilityRole = null;
|
||||
} else {
|
||||
mAccessibilityRole = AccessibilityRole.fromValue(accessibilityRole);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Role getRole() {
|
||||
return mRole;
|
||||
}
|
||||
|
||||
private void setRole(@Nullable String role) {
|
||||
if (role == null) {
|
||||
mRole = null;
|
||||
} else {
|
||||
mRole = Role.fromValue(role);
|
||||
}
|
||||
}
|
||||
|
||||
private void setRole(Role role) {
|
||||
mRole = role;
|
||||
}
|
||||
|
||||
public static int getTextBreakStrategy(@Nullable String textBreakStrategy) {
|
||||
int androidTextBreakStrategy = DEFAULT_BREAK_STRATEGY;
|
||||
if (textBreakStrategy != null) {
|
||||
switch (textBreakStrategy) {
|
||||
case "simple":
|
||||
androidTextBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
|
||||
break;
|
||||
case "balanced":
|
||||
androidTextBreakStrategy = Layout.BREAK_STRATEGY_BALANCED;
|
||||
break;
|
||||
default:
|
||||
androidTextBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return androidTextBreakStrategy;
|
||||
}
|
||||
|
||||
public static int getHyphenationFrequency(@Nullable String hyphenationFrequency) {
|
||||
int androidHyphenationFrequency = DEFAULT_HYPHENATION_FREQUENCY;
|
||||
if (hyphenationFrequency != null) {
|
||||
switch (hyphenationFrequency) {
|
||||
case "none":
|
||||
androidHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
|
||||
break;
|
||||
case "normal":
|
||||
androidHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NORMAL;
|
||||
break;
|
||||
default:
|
||||
androidHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_FULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return androidHyphenationFrequency;
|
||||
}
|
||||
|
||||
public static @Nullable TextUtils.TruncateAt getEllipsizeMode(@Nullable String ellipsizeMode) {
|
||||
@Nullable TextUtils.TruncateAt truncateAt = null;
|
||||
if (ellipsizeMode != null) {
|
||||
switch (ellipsizeMode) {
|
||||
case "head":
|
||||
truncateAt = TextUtils.TruncateAt.START;
|
||||
break;
|
||||
case "middle":
|
||||
truncateAt = TextUtils.TruncateAt.MIDDLE;
|
||||
break;
|
||||
case "tail":
|
||||
truncateAt = TextUtils.TruncateAt.END;
|
||||
break;
|
||||
case "clip":
|
||||
truncateAt = null;
|
||||
}
|
||||
}
|
||||
return truncateAt;
|
||||
}
|
||||
}
|
||||
+561
@@ -0,0 +1,561 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.text
|
||||
|
||||
import android.os.Build
|
||||
import android.text.Layout
|
||||
import android.text.TextUtils
|
||||
import android.text.TextUtils.TruncateAt
|
||||
import android.util.LayoutDirection
|
||||
import android.view.Gravity
|
||||
import com.facebook.common.logging.FLog
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.common.ReactConstants
|
||||
import com.facebook.react.common.mapbuffer.MapBuffer
|
||||
import com.facebook.react.uimanager.PixelUtil.toPixelFromDIP
|
||||
import com.facebook.react.uimanager.PixelUtil.toPixelFromSP
|
||||
import com.facebook.react.uimanager.ReactAccessibilityDelegate
|
||||
import com.facebook.react.uimanager.ReactAccessibilityDelegate.AccessibilityRole
|
||||
import com.facebook.react.uimanager.ReactStylesDiffMap
|
||||
import com.facebook.react.uimanager.ViewProps
|
||||
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontStyle
|
||||
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontVariant
|
||||
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight
|
||||
import kotlin.math.ceil
|
||||
|
||||
// TODO: T63643819 refactor naming of TextAttributeProps to make explicit that this represents
|
||||
// TextAttributes and not TextProps. As part of this refactor extract methods that don't belong to
|
||||
// TextAttributeProps (e.g. TextAlign)
|
||||
public class TextAttributeProps private constructor() {
|
||||
public var lineHeight: Float = Float.NaN
|
||||
private set(value) {
|
||||
lineHeightInput = value
|
||||
field =
|
||||
if (value == ReactConstants.UNSET.toFloat()) {
|
||||
Float.NaN
|
||||
} else {
|
||||
if (allowFontScaling) toPixelFromSP(value) else toPixelFromDIP(value)
|
||||
}
|
||||
}
|
||||
|
||||
public var isColorSet: Boolean = false
|
||||
private set
|
||||
|
||||
public var allowFontScaling: Boolean = true
|
||||
private set(value) {
|
||||
if (value != field) {
|
||||
field = value
|
||||
setFontSize(fontSizeInput)
|
||||
lineHeight = lineHeightInput
|
||||
}
|
||||
}
|
||||
|
||||
public var maxFontSizeMultiplier: Float = Float.NaN
|
||||
private set(value) {
|
||||
if (value != field) {
|
||||
field = value
|
||||
setFontSize(fontSizeInput)
|
||||
lineHeight = lineHeightInput
|
||||
}
|
||||
}
|
||||
|
||||
public var isBackgroundColorSet: Boolean = false
|
||||
private set
|
||||
|
||||
public var opacity: Float = Float.NaN
|
||||
private set
|
||||
|
||||
public var numberOfLines: Int = ReactConstants.UNSET
|
||||
private set
|
||||
|
||||
public var fontSize: Int = ReactConstants.UNSET
|
||||
private set
|
||||
|
||||
private var fontSizeInput: Float = ReactConstants.UNSET.toFloat()
|
||||
private var lineHeightInput: Float = ReactConstants.UNSET.toFloat()
|
||||
private var letterSpacingInput: Float = Float.NaN
|
||||
|
||||
// `ReactConstants.UNSET` is -1, same as `LayoutDirection.UNDEFINED` (which is a hidden symbol)
|
||||
public var layoutDirection: Int = ReactConstants.UNSET
|
||||
private set
|
||||
|
||||
internal var textTransform: TextTransform = TextTransform.NONE
|
||||
|
||||
public var isUnderlineTextDecorationSet: Boolean = false
|
||||
private set
|
||||
|
||||
public var isLineThroughTextDecorationSet: Boolean = false
|
||||
private set
|
||||
|
||||
private var includeFontPadding: Boolean = true
|
||||
|
||||
public var accessibilityRole: AccessibilityRole? = null
|
||||
private set
|
||||
|
||||
public var role: ReactAccessibilityDelegate.Role? = null
|
||||
private set
|
||||
|
||||
public var fontStyle: Int = ReactConstants.UNSET
|
||||
private set
|
||||
|
||||
public var fontWeight: Int = ReactConstants.UNSET
|
||||
private set
|
||||
|
||||
/**
|
||||
* NB: If a font family is used that does not have a style in a certain Android version (ie.
|
||||
* monospace bold pre Android 5.0), that style (ie. bold) will not be inherited by nested Text
|
||||
* nodes. To retain that style, you have to add it to those nodes explicitly.
|
||||
*
|
||||
* Example, Android 4.4:
|
||||
* <pre>
|
||||
* <Text style={{fontFamily="serif" fontWeight="bold" }}>Bold Text</Text>
|
||||
* <Text style={{fontFamily="sans-serif"}}>Bold Text</Text>
|
||||
* <Text style={{fontFamily="serif"}}>Bold Text</Text>
|
||||
*
|
||||
* <Text style={{fontFamily="monospace" fontWeight="bold" }}>Not Bold Text</Text>
|
||||
* <Text style={{fontFamily="sans-serif"}}>Not Bold Text</Text>
|
||||
* <Text style={{fontFamily="serif"}}>Not Bold Text</Text>
|
||||
*
|
||||
* <Text style={{fontFamily="monospace" fontWeight="bold" }}>Not Bold Text</Text>
|
||||
* <Text style={{fontFamily="sans-serif" fontWeight="bold" }}>Bold Text</Text>
|
||||
* <Text style={{fontFamily="serif"}}>Bold Text</Text>
|
||||
* </pre> *
|
||||
*/
|
||||
public var fontFamily: String? = null
|
||||
private set
|
||||
|
||||
/** @see android.graphics.Paint.setFontFeatureSettings */
|
||||
public var fontFeatureSettings: String? = null
|
||||
private set
|
||||
|
||||
@Deprecated("Use lineHeight instead", ReplaceWith("lineHeight"))
|
||||
public val effectiveLineHeight: Float
|
||||
get() = lineHeight
|
||||
|
||||
private fun setNumberOfLines(numberOfLines: Int) {
|
||||
this.numberOfLines = if (numberOfLines == 0) ReactConstants.UNSET else numberOfLines
|
||||
}
|
||||
|
||||
public var letterSpacing: Float
|
||||
get() {
|
||||
val letterSpacingPixels =
|
||||
if (allowFontScaling) toPixelFromSP(letterSpacingInput)
|
||||
else toPixelFromDIP(letterSpacingInput)
|
||||
|
||||
require(fontSize > 0) { "FontSize should be a positive value. Current value: $fontSize" }
|
||||
// `letterSpacingPixels` and `fontSize` are both in pixels,
|
||||
// yielding an accurate em value.
|
||||
return letterSpacingPixels / fontSize
|
||||
}
|
||||
private set(letterSpacing) {
|
||||
letterSpacingInput = letterSpacing
|
||||
}
|
||||
|
||||
public val effectiveLetterSpacing: Float
|
||||
get() = letterSpacing
|
||||
|
||||
private fun setFontSize(fontSize: Float) {
|
||||
var fontSizeLocal = fontSize
|
||||
fontSizeInput = fontSizeLocal
|
||||
if (fontSizeLocal != ReactConstants.UNSET.toFloat()) {
|
||||
fontSizeLocal =
|
||||
if (allowFontScaling)
|
||||
ceil(toPixelFromSP(fontSize, maxFontSizeMultiplier).toDouble()).toFloat()
|
||||
else ceil(toPixelFromDIP(fontSize).toDouble()).toFloat()
|
||||
}
|
||||
this.fontSize = fontSizeLocal.toInt()
|
||||
}
|
||||
|
||||
public var color: Int? = null
|
||||
private set(value) {
|
||||
isColorSet = (value != null)
|
||||
if (value != null) {
|
||||
field = value
|
||||
}
|
||||
}
|
||||
|
||||
public var backgroundColor: Int? = 0
|
||||
private set(color) {
|
||||
// TODO: Don't apply background color to anchor TextView since it will be applied on the
|
||||
// View directly
|
||||
// if (!isVirtualAnchor()) {
|
||||
isBackgroundColorSet = (color != null)
|
||||
if (color != null) {
|
||||
field = color
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
private fun setFontVariant(fontVariant: ReadableArray?) {
|
||||
fontFeatureSettings = parseFontVariant(fontVariant)
|
||||
}
|
||||
|
||||
private fun setFontVariant(fontVariant: MapBuffer?) {
|
||||
if (fontVariant == null || fontVariant.count == 0) {
|
||||
fontFeatureSettings = null
|
||||
return
|
||||
}
|
||||
|
||||
val features: MutableList<String?> = ArrayList()
|
||||
val iterator = fontVariant.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val entry = iterator.next()
|
||||
val value = entry.stringValue
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
if (value != null) {
|
||||
when (value) {
|
||||
"small-caps" -> features.add("'smcp'")
|
||||
"oldstyle-nums" -> features.add("'onum'")
|
||||
"lining-nums" -> features.add("'lnum'")
|
||||
"tabular-nums" -> features.add("'tnum'")
|
||||
"proportional-nums" -> features.add("'pnum'")
|
||||
"stylistic-one" -> features.add("'ss01'")
|
||||
"stylistic-two" -> features.add("'ss02'")
|
||||
"stylistic-three" -> features.add("'ss03'")
|
||||
"stylistic-four" -> features.add("'ss04'")
|
||||
"stylistic-five" -> features.add("'ss05'")
|
||||
"stylistic-six" -> features.add("'ss06'")
|
||||
"stylistic-seven" -> features.add("'ss07'")
|
||||
"stylistic-eight" -> features.add("'ss08'")
|
||||
"stylistic-nine" -> features.add("'ss09'")
|
||||
"stylistic-ten" -> features.add("'ss10'")
|
||||
"stylistic-eleven" -> features.add("'ss11'")
|
||||
"stylistic-twelve" -> features.add("'ss12'")
|
||||
"stylistic-thirteen" -> features.add("'ss13'")
|
||||
"stylistic-fourteen" -> features.add("'ss14'")
|
||||
"stylistic-fifteen" -> features.add("'ss15'")
|
||||
"stylistic-sixteen" -> features.add("'ss16'")
|
||||
"stylistic-seventeen" -> features.add("'ss17'")
|
||||
"stylistic-eighteen" -> features.add("'ss18'")
|
||||
"stylistic-nineteen" -> features.add("'ss19'")
|
||||
"stylistic-twenty" -> features.add("'ss20'")
|
||||
}
|
||||
}
|
||||
}
|
||||
fontFeatureSettings = TextUtils.join(", ", features)
|
||||
}
|
||||
|
||||
private fun setFontWeight(fontWeightString: String?) {
|
||||
fontWeight = parseFontWeight(fontWeightString)
|
||||
}
|
||||
|
||||
private fun setFontStyle(fontStyleString: String?) {
|
||||
fontStyle = parseFontStyle(fontStyleString)
|
||||
}
|
||||
|
||||
private fun setTextDecorationLine(textDecorationLineString: String?) {
|
||||
isUnderlineTextDecorationSet = false
|
||||
isLineThroughTextDecorationSet = false
|
||||
if (textDecorationLineString != null) {
|
||||
for (textDecorationLineSubString in
|
||||
textDecorationLineString
|
||||
.split("-".toRegex())
|
||||
.dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()) {
|
||||
if ("underline" == textDecorationLineSubString) {
|
||||
isUnderlineTextDecorationSet = true
|
||||
} else if ("strikethrough" == textDecorationLineSubString) {
|
||||
isLineThroughTextDecorationSet = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setTextShadowOffset(offsetMap: ReadableMap?) {
|
||||
textShadowOffsetDx = 0f
|
||||
textShadowOffsetDy = 0f
|
||||
|
||||
if (offsetMap != null) {
|
||||
if (offsetMap.hasKey(PROP_SHADOW_OFFSET_WIDTH) &&
|
||||
!offsetMap.isNull(PROP_SHADOW_OFFSET_WIDTH)) {
|
||||
textShadowOffsetDx = toPixelFromDIP(offsetMap.getDouble(PROP_SHADOW_OFFSET_WIDTH))
|
||||
}
|
||||
if (offsetMap.hasKey(PROP_SHADOW_OFFSET_HEIGHT) &&
|
||||
!offsetMap.isNull(PROP_SHADOW_OFFSET_HEIGHT)) {
|
||||
textShadowOffsetDy = toPixelFromDIP(offsetMap.getDouble(PROP_SHADOW_OFFSET_HEIGHT))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var textShadowOffsetDx: Float = 0f
|
||||
private set(value) {
|
||||
field = toPixelFromDIP(value)
|
||||
}
|
||||
|
||||
public var textShadowOffsetDy: Float = 0f
|
||||
private set(value) {
|
||||
field = toPixelFromDIP(value)
|
||||
}
|
||||
|
||||
private fun setLayoutDirection(layoutDirection: String?) {
|
||||
this.layoutDirection = getLayoutDirection(layoutDirection)
|
||||
}
|
||||
|
||||
public var textShadowRadius: Float = 0f
|
||||
private set(value) {
|
||||
if (value != field) {
|
||||
field = value
|
||||
}
|
||||
}
|
||||
|
||||
public var textShadowColor: Int = DEFAULT_TEXT_SHADOW_COLOR
|
||||
private set(value) {
|
||||
if (value != field) {
|
||||
field = value
|
||||
}
|
||||
}
|
||||
|
||||
private fun setTextTransform(textTransform: String?) {
|
||||
this.textTransform =
|
||||
when (textTransform) {
|
||||
null,
|
||||
"none" -> TextTransform.NONE
|
||||
"uppercase" -> TextTransform.UPPERCASE
|
||||
"lowercase" -> TextTransform.LOWERCASE
|
||||
"capitalize" -> TextTransform.CAPITALIZE
|
||||
else -> {
|
||||
FLog.w(ReactConstants.TAG, "Invalid textTransform: $textTransform")
|
||||
TextTransform.NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAccessibilityRole(accessibilityRole: String?) {
|
||||
this.accessibilityRole =
|
||||
if (accessibilityRole == null) null else AccessibilityRole.fromValue(accessibilityRole)
|
||||
}
|
||||
|
||||
private fun setRole(role: String?) {
|
||||
if (role == null) {
|
||||
this.role = null
|
||||
} else {
|
||||
this.role = ReactAccessibilityDelegate.Role.fromValue(role)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setRole(role: ReactAccessibilityDelegate.Role) {
|
||||
this.role = role
|
||||
}
|
||||
|
||||
public companion object {
|
||||
// constants for Text Attributes serialization
|
||||
public const val TA_KEY_FOREGROUND_COLOR: Int = 0
|
||||
public const val TA_KEY_BACKGROUND_COLOR: Int = 1
|
||||
public const val TA_KEY_OPACITY: Int = 2
|
||||
public const val TA_KEY_FONT_FAMILY: Int = 3
|
||||
public const val TA_KEY_FONT_SIZE: Int = 4
|
||||
public const val TA_KEY_FONT_SIZE_MULTIPLIER: Int = 5
|
||||
public const val TA_KEY_FONT_WEIGHT: Int = 6
|
||||
public const val TA_KEY_FONT_STYLE: Int = 7
|
||||
public const val TA_KEY_FONT_VARIANT: Int = 8
|
||||
public const val TA_KEY_ALLOW_FONT_SCALING: Int = 9
|
||||
public const val TA_KEY_LETTER_SPACING: Int = 10
|
||||
public const val TA_KEY_LINE_HEIGHT: Int = 11
|
||||
public const val TA_KEY_ALIGNMENT: Int = 12
|
||||
public const val TA_KEY_BEST_WRITING_DIRECTION: Int = 13
|
||||
public const val TA_KEY_TEXT_DECORATION_COLOR: Int = 14
|
||||
public const val TA_KEY_TEXT_DECORATION_LINE: Int = 15
|
||||
public const val TA_KEY_TEXT_DECORATION_STYLE: Int = 16
|
||||
public const val TA_KEY_TEXT_SHADOW_RADIUS: Int = 18
|
||||
public const val TA_KEY_TEXT_SHADOW_COLOR: Int = 19
|
||||
public const val TA_KEY_TEXT_SHADOW_OFFSET_DX: Int = 20
|
||||
public const val TA_KEY_TEXT_SHADOW_OFFSET_DY: Int = 21
|
||||
public const val TA_KEY_IS_HIGHLIGHTED: Int = 22
|
||||
public const val TA_KEY_LAYOUT_DIRECTION: Int = 23
|
||||
public const val TA_KEY_ACCESSIBILITY_ROLE: Int = 24
|
||||
public const val TA_KEY_LINE_BREAK_STRATEGY: Int = 25
|
||||
public const val TA_KEY_ROLE: Int = 26
|
||||
public const val TA_KEY_TEXT_TRANSFORM: Int = 27
|
||||
public const val TA_KEY_MAX_FONT_SIZE_MULTIPLIER: Int = 29
|
||||
|
||||
public const val UNSET: Int = -1
|
||||
|
||||
private const val PROP_SHADOW_OFFSET = "textShadowOffset"
|
||||
private const val PROP_SHADOW_OFFSET_WIDTH = "width"
|
||||
private const val PROP_SHADOW_OFFSET_HEIGHT = "height"
|
||||
private const val PROP_SHADOW_RADIUS = "textShadowRadius"
|
||||
private const val PROP_SHADOW_COLOR = "textShadowColor"
|
||||
|
||||
private const val PROP_TEXT_TRANSFORM = "textTransform"
|
||||
|
||||
private const val DEFAULT_TEXT_SHADOW_COLOR = 0x55000000
|
||||
private val DEFAULT_JUSTIFICATION_MODE =
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) 0 else Layout.JUSTIFICATION_MODE_NONE
|
||||
private const val DEFAULT_BREAK_STRATEGY = Layout.BREAK_STRATEGY_HIGH_QUALITY
|
||||
private const val DEFAULT_HYPHENATION_FREQUENCY = Layout.HYPHENATION_FREQUENCY_NONE
|
||||
|
||||
/** Build a TextAttributeProps using data from the [MapBuffer] received as a parameter. */
|
||||
public fun fromMapBuffer(props: MapBuffer): TextAttributeProps {
|
||||
val result = TextAttributeProps()
|
||||
|
||||
// TODO T83483191: Review constants that are not being set!
|
||||
val iterator = props.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val entry = iterator.next()
|
||||
when (entry.key) {
|
||||
TA_KEY_FOREGROUND_COLOR -> result.color = entry.intValue
|
||||
TA_KEY_BACKGROUND_COLOR -> result.backgroundColor = entry.intValue
|
||||
TA_KEY_OPACITY -> result.opacity = entry.doubleValue.toFloat()
|
||||
TA_KEY_FONT_FAMILY -> result.fontFamily = entry.stringValue
|
||||
TA_KEY_FONT_SIZE -> result.setFontSize(entry.doubleValue.toFloat())
|
||||
TA_KEY_FONT_SIZE_MULTIPLIER -> {}
|
||||
TA_KEY_FONT_WEIGHT -> result.setFontWeight(entry.stringValue)
|
||||
TA_KEY_FONT_STYLE -> result.setFontStyle(entry.stringValue)
|
||||
TA_KEY_FONT_VARIANT -> result.setFontVariant(entry.mapBufferValue)
|
||||
TA_KEY_ALLOW_FONT_SCALING -> result.allowFontScaling = entry.booleanValue
|
||||
TA_KEY_LETTER_SPACING -> result.letterSpacing = entry.doubleValue.toFloat()
|
||||
TA_KEY_LINE_HEIGHT -> result.lineHeight = entry.doubleValue.toFloat()
|
||||
TA_KEY_ALIGNMENT -> {}
|
||||
TA_KEY_BEST_WRITING_DIRECTION -> {}
|
||||
TA_KEY_TEXT_DECORATION_COLOR -> {}
|
||||
TA_KEY_TEXT_DECORATION_LINE -> result.setTextDecorationLine(entry.stringValue)
|
||||
TA_KEY_TEXT_DECORATION_STYLE -> {}
|
||||
TA_KEY_TEXT_SHADOW_RADIUS -> result.textShadowRadius = entry.doubleValue.toFloat()
|
||||
TA_KEY_TEXT_SHADOW_COLOR -> result.textShadowColor = entry.intValue
|
||||
TA_KEY_TEXT_SHADOW_OFFSET_DX -> result.textShadowOffsetDx = entry.doubleValue.toFloat()
|
||||
TA_KEY_TEXT_SHADOW_OFFSET_DY -> result.textShadowOffsetDy = entry.doubleValue.toFloat()
|
||||
TA_KEY_IS_HIGHLIGHTED -> {}
|
||||
TA_KEY_LAYOUT_DIRECTION -> result.setLayoutDirection(entry.stringValue)
|
||||
TA_KEY_ACCESSIBILITY_ROLE -> result.setAccessibilityRole(entry.stringValue)
|
||||
TA_KEY_ROLE -> result.setRole(ReactAccessibilityDelegate.Role.entries[entry.intValue])
|
||||
TA_KEY_TEXT_TRANSFORM -> result.setTextTransform(entry.stringValue)
|
||||
TA_KEY_MAX_FONT_SIZE_MULTIPLIER ->
|
||||
result.maxFontSizeMultiplier = entry.doubleValue.toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO T83483191: Review why the following props are not serialized:
|
||||
// setNumberOfLines
|
||||
// setColor
|
||||
// setIncludeFontPadding
|
||||
return result
|
||||
}
|
||||
|
||||
public fun fromReadableMap(props: ReactStylesDiffMap): TextAttributeProps {
|
||||
val result = TextAttributeProps()
|
||||
result.setNumberOfLines(getIntProp(props, ViewProps.NUMBER_OF_LINES, ReactConstants.UNSET))
|
||||
result.lineHeight = getFloatProp(props, ViewProps.LINE_HEIGHT, ReactConstants.UNSET.toFloat())
|
||||
result.letterSpacing = getFloatProp(props, ViewProps.LETTER_SPACING, Float.NaN)
|
||||
result.allowFontScaling = getBooleanProp(props, ViewProps.ALLOW_FONT_SCALING, true)
|
||||
result.maxFontSizeMultiplier =
|
||||
getFloatProp(props, ViewProps.MAX_FONT_SIZE_MULTIPLIER, Float.NaN)
|
||||
result.setFontSize(getFloatProp(props, ViewProps.FONT_SIZE, ReactConstants.UNSET.toFloat()))
|
||||
result.color = if (props.hasKey(ViewProps.COLOR)) props.getInt(ViewProps.COLOR, 0) else null
|
||||
result.color =
|
||||
if (props.hasKey(ViewProps.FOREGROUND_COLOR)) props.getInt(ViewProps.FOREGROUND_COLOR, 0)
|
||||
else null
|
||||
result.backgroundColor =
|
||||
if (props.hasKey(ViewProps.BACKGROUND_COLOR)) props.getInt(ViewProps.BACKGROUND_COLOR, 0)
|
||||
else null
|
||||
result.opacity = getFloatProp(props, ViewProps.OPACITY, Float.NaN)
|
||||
result.fontFamily = getStringProp(props, ViewProps.FONT_FAMILY)
|
||||
result.setFontWeight(getStringProp(props, ViewProps.FONT_WEIGHT))
|
||||
result.setFontStyle(getStringProp(props, ViewProps.FONT_STYLE))
|
||||
result.setFontVariant(getArrayProp(props, ViewProps.FONT_VARIANT))
|
||||
result.includeFontPadding = getBooleanProp(props, ViewProps.INCLUDE_FONT_PADDING, true)
|
||||
result.setTextDecorationLine(getStringProp(props, ViewProps.TEXT_DECORATION_LINE))
|
||||
result.setTextShadowOffset(
|
||||
if (props.hasKey(PROP_SHADOW_OFFSET)) props.getMap(PROP_SHADOW_OFFSET) else null)
|
||||
result.textShadowRadius = getFloatProp(props, PROP_SHADOW_RADIUS, 1f)
|
||||
result.textShadowColor = getIntProp(props, PROP_SHADOW_COLOR, DEFAULT_TEXT_SHADOW_COLOR)
|
||||
result.setTextTransform(getStringProp(props, PROP_TEXT_TRANSFORM))
|
||||
result.setLayoutDirection(getStringProp(props, ViewProps.LAYOUT_DIRECTION))
|
||||
result.setAccessibilityRole(getStringProp(props, ViewProps.ACCESSIBILITY_ROLE))
|
||||
result.setRole(getStringProp(props, ViewProps.ROLE))
|
||||
return result
|
||||
}
|
||||
|
||||
public fun getTextAlignment(props: ReactStylesDiffMap, isRTL: Boolean, defaultValue: Int): Int {
|
||||
if (!props.hasKey(ViewProps.TEXT_ALIGN)) {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return when (val textAlignPropValue = props.getString(ViewProps.TEXT_ALIGN)) {
|
||||
"justify" -> Gravity.LEFT
|
||||
null,
|
||||
"auto" -> Gravity.NO_GRAVITY
|
||||
"left" -> if (isRTL) Gravity.RIGHT else Gravity.LEFT
|
||||
"right" -> if (isRTL) Gravity.LEFT else Gravity.RIGHT
|
||||
"center" -> Gravity.CENTER_HORIZONTAL
|
||||
else -> {
|
||||
FLog.w(ReactConstants.TAG, "Invalid textAlign: $textAlignPropValue")
|
||||
Gravity.NO_GRAVITY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun getJustificationMode(props: ReactStylesDiffMap, defaultValue: Int): Int {
|
||||
if (!props.hasKey(ViewProps.TEXT_ALIGN)) {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
val textAlignPropValue = props.getString(ViewProps.TEXT_ALIGN)
|
||||
if ("justify" == textAlignPropValue && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
return Layout.JUSTIFICATION_MODE_INTER_WORD
|
||||
}
|
||||
return DEFAULT_JUSTIFICATION_MODE
|
||||
}
|
||||
|
||||
private fun getBooleanProp(
|
||||
props: ReactStylesDiffMap,
|
||||
name: String,
|
||||
defaultValue: Boolean
|
||||
): Boolean = if (props.hasKey(name)) props.getBoolean(name, defaultValue) else defaultValue
|
||||
|
||||
private fun getStringProp(props: ReactStylesDiffMap, name: String): String? =
|
||||
if (props.hasKey(name)) props.getString(name) else null
|
||||
|
||||
private fun getIntProp(props: ReactStylesDiffMap, name: String, defaultValue: Int): Int =
|
||||
if (props.hasKey(name)) props.getInt(name, defaultValue) else defaultValue
|
||||
|
||||
private fun getFloatProp(props: ReactStylesDiffMap, name: String, defaultValue: Float): Float =
|
||||
if (props.hasKey(name)) props.getFloat(name, defaultValue) else defaultValue
|
||||
|
||||
private fun getArrayProp(props: ReactStylesDiffMap, name: String): ReadableArray? =
|
||||
if (props.hasKey(name)) props.getArray(name) else null
|
||||
|
||||
public fun getLayoutDirection(layoutDirection: String?): Int {
|
||||
return when (layoutDirection) {
|
||||
null,
|
||||
"undefined" -> ReactConstants.UNSET
|
||||
"rtl" -> LayoutDirection.RTL
|
||||
"ltr" -> LayoutDirection.LTR
|
||||
else -> {
|
||||
FLog.w(ReactConstants.TAG, "Invalid layoutDirection: $layoutDirection")
|
||||
ReactConstants.UNSET
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun getTextBreakStrategy(textBreakStrategy: String?): Int =
|
||||
when (textBreakStrategy) {
|
||||
null -> DEFAULT_BREAK_STRATEGY
|
||||
"simple" -> Layout.BREAK_STRATEGY_SIMPLE
|
||||
"balanced" -> Layout.BREAK_STRATEGY_BALANCED
|
||||
else -> Layout.BREAK_STRATEGY_HIGH_QUALITY
|
||||
}
|
||||
|
||||
public fun getHyphenationFrequency(hyphenationFrequency: String?): Int =
|
||||
when (hyphenationFrequency) {
|
||||
null -> DEFAULT_HYPHENATION_FREQUENCY
|
||||
"none" -> Layout.HYPHENATION_FREQUENCY_NONE
|
||||
"normal" -> Layout.HYPHENATION_FREQUENCY_NORMAL
|
||||
else -> Layout.HYPHENATION_FREQUENCY_FULL
|
||||
}
|
||||
|
||||
public fun getEllipsizeMode(ellipsizeMode: String?): TruncateAt? =
|
||||
when (ellipsizeMode) {
|
||||
"head" -> TruncateAt.START
|
||||
"middle" -> TruncateAt.MIDDLE
|
||||
"tail" -> TruncateAt.END
|
||||
"clip" -> null
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
-41
@@ -231,7 +231,7 @@ internal object TextLayoutManager {
|
||||
TextAttributeProps.fromMapBuffer(fragment.getMapBuffer(FR_KEY_TEXT_ATTRIBUTES))
|
||||
|
||||
sb.append(
|
||||
TextTransform.apply(fragment.getString(FR_KEY_STRING), textAttributes.mTextTransform))
|
||||
TextTransform.apply(fragment.getString(FR_KEY_STRING), textAttributes.textTransform))
|
||||
|
||||
val end = sb.length
|
||||
val reactTag =
|
||||
@@ -246,10 +246,10 @@ internal object TextLayoutManager {
|
||||
TextInlineViewPlaceholderSpan(reactTag, width.toInt(), height.toInt())))
|
||||
} else if (end >= start) {
|
||||
val roleIsLink =
|
||||
if (textAttributes.mRole != null)
|
||||
(textAttributes.mRole == ReactAccessibilityDelegate.Role.LINK)
|
||||
if (textAttributes.role != null)
|
||||
(textAttributes.role == ReactAccessibilityDelegate.Role.LINK)
|
||||
else
|
||||
(textAttributes.mAccessibilityRole ==
|
||||
(textAttributes.accessibilityRole ==
|
||||
ReactAccessibilityDelegate.AccessibilityRole.LINK)
|
||||
if (roleIsLink) {
|
||||
if (ReactNativeFeatureFlags.enablePreparedTextLayout()) {
|
||||
@@ -258,13 +258,17 @@ internal object TextLayoutManager {
|
||||
ops.add(SetSpanOperation(start, end, ReactClickableSpan(reactTag)))
|
||||
}
|
||||
}
|
||||
if (textAttributes.mIsColorSet) {
|
||||
ops.add(SetSpanOperation(start, end, ReactForegroundColorSpan(textAttributes.mColor)))
|
||||
if (textAttributes.isColorSet) {
|
||||
textAttributes.color
|
||||
?.let { ReactForegroundColorSpan(it) }
|
||||
?.let { SetSpanOperation(start, end, it) }
|
||||
?.let { ops.add(it) }
|
||||
}
|
||||
if (textAttributes.mIsBackgroundColorSet) {
|
||||
ops.add(
|
||||
SetSpanOperation(
|
||||
start, end, ReactBackgroundColorSpan(textAttributes.mBackgroundColor)))
|
||||
if (textAttributes.isBackgroundColorSet) {
|
||||
textAttributes.backgroundColor
|
||||
?.let { ReactBackgroundColorSpan(it) }
|
||||
?.let { SetSpanOperation(start, end, it) }
|
||||
?.let { ops.add(it) }
|
||||
}
|
||||
if (!textAttributes.opacity.isNaN()) {
|
||||
ops.add(SetSpanOperation(start, end, ReactOpacitySpan(textAttributes.opacity)))
|
||||
@@ -273,45 +277,43 @@ internal object TextLayoutManager {
|
||||
ops.add(
|
||||
SetSpanOperation(start, end, CustomLetterSpacingSpan(textAttributes.letterSpacing)))
|
||||
}
|
||||
ops.add(SetSpanOperation(start, end, ReactAbsoluteSizeSpan(textAttributes.mFontSize)))
|
||||
if (textAttributes.mFontStyle != ReactConstants.UNSET ||
|
||||
textAttributes.mFontWeight != ReactConstants.UNSET ||
|
||||
textAttributes.mFontFamily != null) {
|
||||
ops.add(SetSpanOperation(start, end, ReactAbsoluteSizeSpan(textAttributes.fontSize)))
|
||||
if (textAttributes.fontStyle != ReactConstants.UNSET ||
|
||||
textAttributes.fontWeight != ReactConstants.UNSET ||
|
||||
textAttributes.fontFamily != null) {
|
||||
ops.add(
|
||||
SetSpanOperation(
|
||||
start,
|
||||
end,
|
||||
CustomStyleSpan(
|
||||
textAttributes.mFontStyle,
|
||||
textAttributes.mFontWeight,
|
||||
textAttributes.mFontFeatureSettings,
|
||||
textAttributes.mFontFamily,
|
||||
textAttributes.fontStyle,
|
||||
textAttributes.fontWeight,
|
||||
textAttributes.fontFeatureSettings,
|
||||
textAttributes.fontFamily,
|
||||
context.assets)))
|
||||
}
|
||||
if (textAttributes.mIsUnderlineTextDecorationSet) {
|
||||
if (textAttributes.isUnderlineTextDecorationSet) {
|
||||
ops.add(SetSpanOperation(start, end, ReactUnderlineSpan()))
|
||||
}
|
||||
if (textAttributes.mIsLineThroughTextDecorationSet) {
|
||||
if (textAttributes.isLineThroughTextDecorationSet) {
|
||||
ops.add(SetSpanOperation(start, end, ReactStrikethroughSpan()))
|
||||
}
|
||||
if ((textAttributes.mTextShadowOffsetDx != 0f ||
|
||||
textAttributes.mTextShadowOffsetDy != 0f ||
|
||||
textAttributes.mTextShadowRadius != 0f) &&
|
||||
Color.alpha(textAttributes.mTextShadowColor) != 0) {
|
||||
if ((textAttributes.textShadowOffsetDx != 0f ||
|
||||
textAttributes.textShadowOffsetDy != 0f ||
|
||||
textAttributes.textShadowRadius != 0f) &&
|
||||
Color.alpha(textAttributes.textShadowColor) != 0) {
|
||||
ops.add(
|
||||
SetSpanOperation(
|
||||
start,
|
||||
end,
|
||||
ShadowStyleSpan(
|
||||
textAttributes.mTextShadowOffsetDx,
|
||||
textAttributes.mTextShadowOffsetDy,
|
||||
textAttributes.mTextShadowRadius,
|
||||
textAttributes.mTextShadowColor)))
|
||||
textAttributes.textShadowOffsetDx,
|
||||
textAttributes.textShadowOffsetDy,
|
||||
textAttributes.textShadowRadius,
|
||||
textAttributes.textShadowColor)))
|
||||
}
|
||||
if (!textAttributes.effectiveLineHeight.isNaN()) {
|
||||
ops.add(
|
||||
SetSpanOperation(
|
||||
start, end, CustomLineHeightSpan(textAttributes.effectiveLineHeight)))
|
||||
if (!textAttributes.lineHeight.isNaN()) {
|
||||
ops.add(SetSpanOperation(start, end, CustomLineHeightSpan(textAttributes.lineHeight)))
|
||||
}
|
||||
|
||||
if (ReactNativeFeatureFlags.enablePreparedTextLayout()) {
|
||||
@@ -409,12 +411,16 @@ internal object TextLayoutManager {
|
||||
}
|
||||
|
||||
if (fragment.props.isColorSet) {
|
||||
spannable.setSpan(ReactForegroundColorSpan(fragment.props.color), start, end, spanFlags)
|
||||
spannable.setSpan(
|
||||
fragment.props.color?.let { ReactForegroundColorSpan(it) }, start, end, spanFlags)
|
||||
}
|
||||
|
||||
if (fragment.props.isBackgroundColorSet) {
|
||||
spannable.setSpan(
|
||||
ReactBackgroundColorSpan(fragment.props.backgroundColor), start, end, spanFlags)
|
||||
fragment.props.backgroundColor?.let { ReactBackgroundColorSpan(it) },
|
||||
start,
|
||||
end,
|
||||
spanFlags)
|
||||
}
|
||||
|
||||
if (!fragment.props.opacity.isNaN()) {
|
||||
@@ -426,8 +432,7 @@ internal object TextLayoutManager {
|
||||
CustomLetterSpacingSpan(fragment.props.letterSpacing), start, end, spanFlags)
|
||||
}
|
||||
|
||||
// TODO: Should this be using effectiveFontSize instead of fontSize?
|
||||
spannable.setSpan(ReactAbsoluteSizeSpan(fragment.props.mFontSize), start, end, spanFlags)
|
||||
spannable.setSpan(ReactAbsoluteSizeSpan(fragment.props.fontSize), start, end, spanFlags)
|
||||
|
||||
if (fragment.props.fontStyle != ReactConstants.UNSET ||
|
||||
fragment.props.fontWeight != ReactConstants.UNSET ||
|
||||
@@ -467,9 +472,8 @@ internal object TextLayoutManager {
|
||||
spanFlags)
|
||||
}
|
||||
|
||||
if (!fragment.props.effectiveLineHeight.isNaN()) {
|
||||
spannable.setSpan(
|
||||
CustomLineHeightSpan(fragment.props.effectiveLineHeight), start, end, spanFlags)
|
||||
if (!fragment.props.lineHeight.isNaN()) {
|
||||
spannable.setSpan(CustomLineHeightSpan(fragment.props.lineHeight), start, end, spanFlags)
|
||||
}
|
||||
|
||||
if (ReactNativeFeatureFlags.enablePreparedTextLayout()) {
|
||||
@@ -611,8 +615,8 @@ internal object TextLayoutManager {
|
||||
baseTextAttributes: TextAttributeProps,
|
||||
context: Context
|
||||
) {
|
||||
if (baseTextAttributes.effectiveFontSize != ReactConstants.UNSET) {
|
||||
paint.textSize = baseTextAttributes.effectiveFontSize.toFloat()
|
||||
if (baseTextAttributes.fontSize != ReactConstants.UNSET) {
|
||||
paint.textSize = baseTextAttributes.fontSize.toFloat()
|
||||
}
|
||||
|
||||
if (baseTextAttributes.fontStyle != ReactConstants.UNSET ||
|
||||
|
||||
+22
@@ -11,12 +11,18 @@
|
||||
|
||||
package com.facebook.react.bridge
|
||||
|
||||
import android.app.Application
|
||||
import com.facebook.react.bridge.queue.MessageQueueThreadSpec
|
||||
import com.facebook.react.bridge.queue.ReactQueueConfiguration
|
||||
import com.facebook.react.bridge.queue.ReactQueueConfigurationImpl
|
||||
import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec
|
||||
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
||||
import com.facebook.react.runtime.BridgelessReactContext
|
||||
import com.facebook.react.runtime.ReactHostImpl
|
||||
import com.facebook.react.runtime.internal.bolts.Task
|
||||
import com.facebook.react.uimanager.UIManagerModule
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.spy
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
|
||||
@@ -49,4 +55,20 @@ object ReactTestHelper {
|
||||
whenever(reactInstance.isDestroyed).thenReturn(false)
|
||||
return reactInstance
|
||||
}
|
||||
|
||||
@OptIn(UnstableReactNativeAPI::class)
|
||||
fun createTestReactApplicationContext(application: Application): ReactApplicationContext {
|
||||
val reactHost =
|
||||
spy(
|
||||
ReactHostImpl(
|
||||
RuntimeEnvironment.getApplication(),
|
||||
mock(),
|
||||
mock(),
|
||||
Task.Companion.IMMEDIATE_EXECUTOR,
|
||||
Task.Companion.IMMEDIATE_EXECUTOR,
|
||||
false /* allowPackagerServerAccess */,
|
||||
false /* useDevSupport */,
|
||||
))
|
||||
return BridgelessReactContext(application, reactHost)
|
||||
}
|
||||
}
|
||||
|
||||
+16
-7
@@ -5,36 +5,45 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.modules.clipboard
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import com.facebook.react.bridge.BridgeReactContext
|
||||
import com.facebook.react.bridge.ReactTestHelper.createTestReactApplicationContext
|
||||
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
|
||||
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
|
||||
import com.facebook.testutils.shadows.ShadowSoLoader
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@SuppressLint("ClipboardManager", "DeprecatedClass")
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(shadows = [ShadowSoLoader::class])
|
||||
class ClipboardModuleTest {
|
||||
private lateinit var clipboardModule: ClipboardModule
|
||||
private lateinit var clipboardManager: ClipboardManager
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
clipboardModule = ClipboardModule(BridgeReactContext(RuntimeEnvironment.getApplication()))
|
||||
ReactNativeFeatureFlagsForTests.setUp()
|
||||
clipboardModule =
|
||||
ClipboardModule(createTestReactApplicationContext(RuntimeEnvironment.getApplication()))
|
||||
clipboardManager =
|
||||
RuntimeEnvironment.getApplication().getSystemService(Context.CLIPBOARD_SERVICE)
|
||||
as ClipboardManager
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
ReactNativeFeatureFlags.dangerouslyReset()
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@Test
|
||||
fun testSetString() {
|
||||
clipboardModule.setString(TEST_CONTENT)
|
||||
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.uimanager
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.view.WindowInsets
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import com.facebook.testutils.shadows.ShadowNativeLoader
|
||||
import com.facebook.testutils.shadows.ShadowNativeMap
|
||||
import com.facebook.testutils.shadows.ShadowReadableNativeMap
|
||||
import com.facebook.testutils.shadows.ShadowSoLoader
|
||||
import com.facebook.testutils.shadows.ShadowWritableNativeMap
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(
|
||||
shadows =
|
||||
[
|
||||
ShadowSoLoader::class,
|
||||
ShadowNativeLoader::class,
|
||||
ShadowNativeMap::class,
|
||||
ShadowWritableNativeMap::class,
|
||||
ShadowReadableNativeMap::class,
|
||||
])
|
||||
class DisplayMetricsHolderTest {
|
||||
|
||||
private lateinit var context: Context
|
||||
private lateinit var displayMetrics: DisplayMetrics
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = RuntimeEnvironment.getApplication()
|
||||
displayMetrics = context.resources.displayMetrics
|
||||
DisplayMetricsHolder.setWindowDisplayMetrics(null)
|
||||
DisplayMetricsHolder.setScreenDisplayMetrics(null)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun getWindowDisplayMetrics_failsIfDisplayMetricsIsNotInitialized() {
|
||||
DisplayMetricsHolder.getWindowDisplayMetrics()
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun getScreenDisplayMetrics_failsIfDisplayMetricsIsNotInitialized() {
|
||||
DisplayMetricsHolder.getScreenDisplayMetrics()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setAndGetWindowDisplayMetrics_returnsSetValue() {
|
||||
DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics)
|
||||
val result = DisplayMetricsHolder.getWindowDisplayMetrics()
|
||||
assertThat(result).isEqualTo(displayMetrics)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setAndGetScreenDisplayMetrics_returnsSetValue() {
|
||||
DisplayMetricsHolder.setScreenDisplayMetrics(displayMetrics)
|
||||
val result = DisplayMetricsHolder.getScreenDisplayMetrics()
|
||||
assertThat(result).isEqualTo(displayMetrics)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initScreenDisplayMetrics_setsMetrics() {
|
||||
DisplayMetricsHolder.initScreenDisplayMetrics(context)
|
||||
assertThat(DisplayMetricsHolder.getScreenDisplayMetrics()).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initWindowDisplayMetrics_setsMetrics() {
|
||||
DisplayMetricsHolder.initWindowDisplayMetrics(context)
|
||||
assertThat(DisplayMetricsHolder.getWindowDisplayMetrics()).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initWindowMetricsIfNotInitialized_onlyInitializesOnce() {
|
||||
DisplayMetricsHolder.initWindowDisplayMetrics(context)
|
||||
val firstWindow = DisplayMetricsHolder.getWindowDisplayMetrics()
|
||||
// Should not reinitialize
|
||||
DisplayMetricsHolder.initWindowDisplayMetrics(context)
|
||||
val secondWindow = DisplayMetricsHolder.getWindowDisplayMetrics()
|
||||
assertThat(secondWindow).isEqualTo(firstWindow)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initScreenMetricsIfNotInitialized_onlyInitializesOnce() {
|
||||
DisplayMetricsHolder.initScreenDisplayMetrics(context)
|
||||
val firstScreen = DisplayMetricsHolder.getScreenDisplayMetrics()
|
||||
// Should not reinitialize
|
||||
DisplayMetricsHolder.initScreenDisplayMetrics(context)
|
||||
val secondScreen = DisplayMetricsHolder.getScreenDisplayMetrics()
|
||||
assertThat(secondScreen).isEqualTo(firstScreen)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun getDisplayMetricsWritableMap_failsIfNotInitialized() {
|
||||
DisplayMetricsHolder.getDisplayMetricsWritableMap(1.0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getDisplayMetricsWritableMap_returnsCorrectMap() {
|
||||
// Use the official initialization method to ensure both metrics are set
|
||||
DisplayMetricsHolder.initWindowDisplayMetrics(context)
|
||||
DisplayMetricsHolder.initScreenDisplayMetrics(context)
|
||||
val map: WritableMap = DisplayMetricsHolder.getDisplayMetricsWritableMap(1.0)
|
||||
assertThat(map.hasKey("windowPhysicalPixels")).isTrue()
|
||||
assertThat(map.hasKey("screenPhysicalPixels")).isTrue()
|
||||
val windowMap = map.getMap("windowPhysicalPixels")
|
||||
val screenMap = map.getMap("screenPhysicalPixels")
|
||||
checkNotNull(windowMap)
|
||||
checkNotNull(screenMap)
|
||||
assertThat(windowMap.hasKey("width")).isTrue()
|
||||
assertThat(windowMap.hasKey("height")).isTrue()
|
||||
assertThat(windowMap.hasKey("scale")).isTrue()
|
||||
assertThat(windowMap.hasKey("fontScale")).isTrue()
|
||||
assertThat(windowMap.hasKey("densityDpi")).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
@TargetApi(30)
|
||||
fun getEncodedScreenSizeWithoutVerticalInsets_returnsEncodedValue() {
|
||||
DisplayMetricsHolder.initScreenDisplayMetrics(context)
|
||||
|
||||
val activity: Activity = mock()
|
||||
val window: Window = mock()
|
||||
val decorView: View = mock()
|
||||
val windowInsets: WindowInsets = mock()
|
||||
whenever(activity.window).thenReturn(window)
|
||||
whenever(window.decorView).thenReturn(decorView)
|
||||
|
||||
whenever(decorView.rootWindowInsets).thenReturn(windowInsets)
|
||||
whenever(windowInsets.getInsets(anyInt()))
|
||||
.thenReturn(android.graphics.Insets.of(10, 20, 10, 20))
|
||||
|
||||
// Should return a non-zero encoded value
|
||||
val encoded = DisplayMetricsHolder.getEncodedScreenSizeWithoutVerticalInsets(activity)
|
||||
assertThat(encoded).isNotZero()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getEncodedScreenSizeWithoutVerticalInsets_returnsZeroIfActivityIsNull() {
|
||||
val encoded = DisplayMetricsHolder.getEncodedScreenSizeWithoutVerticalInsets(null)
|
||||
assertThat(encoded).isZero()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun encodeFloatsToLong_encodesWidthAndHeightCorrectly() {
|
||||
val width = 123.45f
|
||||
val height = 67.89f
|
||||
val encoded = DisplayMetricsHolder.encodeFloatsToLong(width, height)
|
||||
val decodedWidth = Float.fromBits((encoded shr 32).toInt())
|
||||
val decodedHeight = Float.fromBits(encoded.toInt())
|
||||
assertThat(decodedWidth).isEqualTo(width)
|
||||
assertThat(decodedHeight).isEqualTo(height)
|
||||
}
|
||||
}
|
||||
@@ -231,8 +231,9 @@ inline char hexDigit(unsigned x) {
|
||||
// ASCII characters
|
||||
bool isAllASCII(const char16_t* utf16, size_t length) {
|
||||
for (const char16_t* e = utf16 + length; utf16 != e; ++utf16) {
|
||||
if (*utf16 > 0x7F)
|
||||
if (*utf16 > 0x7F) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "ModalHostViewComponentDescriptor.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
#ifdef ANDROID
|
||||
State::Shared ModalHostViewComponentDescriptor::createInitialState(
|
||||
const Props::Shared& props,
|
||||
const ShadowNodeFamily::Shared& family) const {
|
||||
// For Android, we need to get the size of the screen without the vertical
|
||||
// insets to correctly position the modal on the first rendering.
|
||||
// For this reason we provide the `createInitialState` implementation
|
||||
// that will query FabricUIManager for the size of the screen without
|
||||
// vertical insets.
|
||||
|
||||
int surfaceId = family->getSurfaceId();
|
||||
|
||||
const jni::global_ref<jobject>& fabricUIManager =
|
||||
contextContainer_->at<jni::global_ref<jobject>>("FabricUIManager");
|
||||
|
||||
static auto getEncodedScreenSizeWithoutVerticalInsets =
|
||||
jni::findClassStatic(UIManagerJavaDescriptor)
|
||||
->getMethod<jlong(jint)>("getEncodedScreenSizeWithoutVerticalInsets");
|
||||
|
||||
auto result =
|
||||
getEncodedScreenSizeWithoutVerticalInsets(fabricUIManager, surfaceId);
|
||||
|
||||
// Inspired from yogaMeasureToSize from conversions.h
|
||||
int32_t wBits = 0xFFFFFFFF & (result >> 32);
|
||||
int32_t hBits = 0xFFFFFFFF & result;
|
||||
|
||||
auto* measuredWidth = reinterpret_cast<float*>(&wBits);
|
||||
auto* measuredHeight = reinterpret_cast<float*>(&hBits);
|
||||
|
||||
return std::make_shared<ModalHostViewShadowNode::ConcreteState>(
|
||||
std::make_shared<const ModalHostViewState>(ModalHostViewState(
|
||||
Size{.width = *measuredWidth, .height = *measuredHeight})),
|
||||
family);
|
||||
}
|
||||
#endif // ANDROID
|
||||
|
||||
} // namespace facebook::react
|
||||
+10
@@ -37,6 +37,16 @@ class ModalHostViewComponentDescriptor final
|
||||
|
||||
ConcreteComponentDescriptor::adopt(shadowNode);
|
||||
}
|
||||
|
||||
#ifdef ANDROID
|
||||
State::Shared createInitialState(
|
||||
const Props::Shared& props,
|
||||
const ShadowNodeFamily::Shared& family) const override;
|
||||
#endif // ANDROID
|
||||
|
||||
private:
|
||||
constexpr static auto UIManagerJavaDescriptor =
|
||||
"com/facebook/react/fabric/FabricUIManager";
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fbjni/fbjni.h>
|
||||
#include <react/renderer/graphics/Size.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class JReactModalHostView
|
||||
: public facebook::jni::JavaClass<JReactModalHostView> {
|
||||
public:
|
||||
static auto constexpr kJavaDescriptor =
|
||||
"Lcom/facebook/react/views/modal/ReactModalHostView;";
|
||||
|
||||
static Size getDisplayMetrics() {
|
||||
static auto method =
|
||||
JReactModalHostView::javaClassStatic()->getStaticMethod<jlong()>(
|
||||
"getScreenDisplayMetricsWithoutInsets");
|
||||
auto result = method(javaClassStatic());
|
||||
|
||||
// Inspired from yogaMeassureToSize from conversions.h
|
||||
int32_t wBits = 0xFFFFFFFF & (result >> 32);
|
||||
int32_t hBits = 0xFFFFFFFF & result;
|
||||
|
||||
auto* measuredWidth = reinterpret_cast<float*>(&wBits);
|
||||
auto* measuredHeight = reinterpret_cast<float*>(&hBits);
|
||||
|
||||
return Size{.width = *measuredWidth, .height = *measuredHeight};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
+1
-2
@@ -7,12 +7,11 @@
|
||||
|
||||
#include <react/renderer/components/modal/ModalHostViewUtils.h>
|
||||
#include <react/renderer/graphics/Size.h>
|
||||
#include "JReactModalHostView.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
Size ModalHostViewScreenSize() {
|
||||
return JReactModalHostView::getDisplayMetrics();
|
||||
return Size{0, 0};
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
@@ -85,6 +85,7 @@ class NewArchitectureHelper
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-Fabric", "React_Fabric", ["react/renderer/components/view/platform/cxx"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-FabricImage", "React_FabricImage", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon", "ReactCommon", ["react/nativemodule/core"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-runtimeexecutor", "React_runtimeexecutor", ["platform/ios"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-NativeModulesApple", "React_NativeModulesApple", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-RCTFabric", "RCTFabric", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-utils", "React_utils", []))
|
||||
|
||||
@@ -313,6 +313,7 @@ class ReactNativePodsUtils
|
||||
header_search_paths = config.build_settings["HEADER_SEARCH_PATHS"] ||= "$(inherited)"
|
||||
|
||||
ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon", "ReactCommon", ["react/nativemodule/core"])
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-runtimeexecutor", "React_runtimeexecutor", ["platform/ios"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon-Samples", "ReactCommon_Samples", ["platform/ios"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-Fabric", "React_Fabric", ["react/renderer/components/view/platform/cxx"], false))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-NativeModulesApple", "React_NativeModulesApple", []))
|
||||
@@ -563,6 +564,7 @@ class ReactNativePodsUtils
|
||||
|
||||
def self.set_reactcommon_searchpaths(target_installation_result)
|
||||
header_search_paths = ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon", "ReactCommon", ["react/nativemodule/core"])
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-runtimeexecutor", "React_runtimeexecutor", ["platform/ios"]))
|
||||
.map { |search_path| "\"#{search_path}\"" }
|
||||
ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "ReactCommon", header_search_paths)
|
||||
end
|
||||
|
||||
+2
@@ -429,6 +429,7 @@ if use_frameworks
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-FabricImage\\", \\"React_FabricImage\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-graphics\\", \\"React_graphics\\", [\\"react/renderer/graphics/platform/ios\\"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"ReactCommon\\", \\"ReactCommon\\", [\\"react/nativemodule/core\\"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-runtimeexecutor\\", \\"React_runtimeexecutor\\", [\\"platform/ios\\"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-NativeModulesApple\\", \\"React_NativeModulesApple\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-RCTFabric\\", \\"RCTFabric\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-debug\\", \\"React_debug\\", []))
|
||||
@@ -907,6 +908,7 @@ if use_frameworks
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-FabricImage\\", \\"React_FabricImage\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-graphics\\", \\"React_graphics\\", [\\"react/renderer/graphics/platform/ios\\"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"ReactCommon\\", \\"ReactCommon\\", [\\"react/nativemodule/core\\"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-runtimeexecutor\\", \\"React_runtimeexecutor\\", [\\"platform/ios\\"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-NativeModulesApple\\", \\"React_NativeModulesApple\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-RCTFabric\\", \\"RCTFabric\\", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-debug\\", \\"React_debug\\", []))
|
||||
|
||||
@@ -33,6 +33,7 @@ if use_frameworks
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-FabricImage", "React_FabricImage", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-graphics", "React_graphics", ["react/renderer/graphics/platform/ios"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon", "ReactCommon", ["react/nativemodule/core"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-runtimeexecutor", "React_runtimeexecutor", ["platform/ios"]))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-NativeModulesApple", "React_NativeModulesApple", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-RCTFabric", "RCTFabric", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-debug", "React_debug", []))
|
||||
|
||||
+12
-28
@@ -8,52 +8,36 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {BenchmarkResult} from '../src/Benchmark';
|
||||
|
||||
import {markdownTable} from './utils';
|
||||
|
||||
type TestTaskTiming = {
|
||||
name: string,
|
||||
latency: {
|
||||
mean: number,
|
||||
min: number,
|
||||
max: number,
|
||||
p50: number,
|
||||
p75: number,
|
||||
p99: number,
|
||||
},
|
||||
};
|
||||
|
||||
export type BenchmarkTestArtifact = {
|
||||
type: string,
|
||||
timings: $ReadOnlyArray<TestTaskTiming>,
|
||||
};
|
||||
|
||||
export const printBenchmarkResultsRanking = (
|
||||
testResults: Array<{
|
||||
benchmarkResults: Array<{
|
||||
title: string,
|
||||
testArtifact: mixed,
|
||||
result: BenchmarkResult,
|
||||
}>,
|
||||
) => {
|
||||
const testTaskTimings: {[string]: {[string]: number}} = {};
|
||||
let numTestVariants = 0;
|
||||
|
||||
for (const testResult of testResults) {
|
||||
// $FlowExpectedError[incompatible-cast]
|
||||
const testArtifact = testResult?.testArtifact as ?BenchmarkTestArtifact;
|
||||
for (const benchmarkResult of benchmarkResults) {
|
||||
const result = benchmarkResult.result;
|
||||
if (
|
||||
testArtifact == null ||
|
||||
testArtifact.timings == null ||
|
||||
testArtifact.type !== 'benchmark' ||
|
||||
testResult.title == null
|
||||
result == null ||
|
||||
result.timings == null ||
|
||||
benchmarkResult.title == null
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
numTestVariants++;
|
||||
for (const taskTiming of testArtifact.timings) {
|
||||
for (const taskTiming of result.timings) {
|
||||
const taskName = taskTiming.name;
|
||||
if (testTaskTimings[taskName] === undefined) {
|
||||
testTaskTimings[taskName] = {};
|
||||
}
|
||||
testTaskTimings[taskName][testResult.title] = taskTiming.latency.p50;
|
||||
testTaskTimings[taskName][benchmarkResult.title] =
|
||||
taskTiming.latency?.p50 ?? taskTiming.latency.mean;
|
||||
}
|
||||
}
|
||||
if (numTestVariants <= 1 || Object.keys(testTaskTimings).length === 0) {
|
||||
|
||||
+18
-19
@@ -14,7 +14,7 @@ import type {
|
||||
TestSuiteResult,
|
||||
} from '../runtime/setup';
|
||||
import type {TestSnapshotResults} from '../runtime/snapshotContext';
|
||||
import type {BenchmarkTestArtifact} from './benchmarkUtils';
|
||||
import type {BenchmarkResult} from '../src/Benchmark';
|
||||
import type {
|
||||
AsyncCommandResult,
|
||||
ConsoleLogMessage,
|
||||
@@ -80,7 +80,7 @@ function buildError(
|
||||
|
||||
async function processRNTesterCommandResult(
|
||||
result: AsyncCommandResult,
|
||||
): Promise<TestSuiteResult> {
|
||||
): Promise<[TestSuiteResult, ?BenchmarkResult]> {
|
||||
const stdoutChunks = [];
|
||||
const stderrChunks = [];
|
||||
|
||||
@@ -92,7 +92,7 @@ async function processRNTesterCommandResult(
|
||||
stderrChunks.push(chunk);
|
||||
});
|
||||
|
||||
let testResult;
|
||||
let testResult, benchmarkResult;
|
||||
|
||||
const rl = readline.createInterface({input: result.childProcess.stdout});
|
||||
rl.on('line', (rawLine: string) => {
|
||||
@@ -116,6 +116,9 @@ async function processRNTesterCommandResult(
|
||||
case 'test-result':
|
||||
testResult = parsed;
|
||||
break;
|
||||
case 'benchmark-result':
|
||||
benchmarkResult = parsed;
|
||||
break;
|
||||
case 'console-log':
|
||||
printConsoleLog(parsed);
|
||||
break;
|
||||
@@ -152,7 +155,7 @@ async function processRNTesterCommandResult(
|
||||
);
|
||||
}
|
||||
|
||||
return testResult;
|
||||
return [testResult, benchmarkResult];
|
||||
}
|
||||
|
||||
function generateBytecodeBundle({
|
||||
@@ -223,6 +226,7 @@ module.exports = async function runTest(
|
||||
);
|
||||
|
||||
const testResultsByConfig = [];
|
||||
const benchmarkResults = [];
|
||||
|
||||
const skippedTestResults = ({
|
||||
ancestorTitles,
|
||||
@@ -241,7 +245,6 @@ module.exports = async function runTest(
|
||||
snapshotResults: {} as TestSnapshotResults,
|
||||
status: 'pending' as TestCaseResult['status'],
|
||||
testFilePath: testPath,
|
||||
testArtifact: {} as mixed,
|
||||
title,
|
||||
},
|
||||
];
|
||||
@@ -371,9 +374,8 @@ module.exports = async function runTest(
|
||||
hermesVariant: testConfig.hermesVariant,
|
||||
});
|
||||
|
||||
const processedResult = await processRNTesterCommandResult(
|
||||
rnTesterCommandResult,
|
||||
);
|
||||
const [processedResult, benchmarkResult] =
|
||||
await processRNTesterCommandResult(rnTesterCommandResult);
|
||||
|
||||
if (containsError(processedResult) || EnvironmentOptions.profileJS) {
|
||||
await createSourceMap({
|
||||
@@ -439,6 +441,13 @@ module.exports = async function runTest(
|
||||
});
|
||||
}
|
||||
|
||||
if (benchmarkResult != null) {
|
||||
benchmarkResults.push({
|
||||
title: testResults[0]?.ancestorTitles?.[0] ?? maybeCommonAncestor,
|
||||
result: benchmarkResult,
|
||||
});
|
||||
}
|
||||
|
||||
testResultsByConfig.push(testResults);
|
||||
}
|
||||
|
||||
@@ -455,17 +464,7 @@ module.exports = async function runTest(
|
||||
snapshotResults,
|
||||
);
|
||||
|
||||
printBenchmarkResultsRanking(
|
||||
testResults.map(testResult => {
|
||||
// $FlowExpectedError[incompatible-cast]
|
||||
const testArtifact = testResult.testArtifact as ?BenchmarkTestArtifact;
|
||||
const title = testResult.ancestorTitles[0];
|
||||
return {
|
||||
title,
|
||||
testArtifact,
|
||||
};
|
||||
}),
|
||||
);
|
||||
printBenchmarkResultsRanking(benchmarkResults);
|
||||
|
||||
return {
|
||||
testFilePath: testPath,
|
||||
|
||||
+9
-3
@@ -8,6 +8,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {BenchmarkResult} from '../src/Benchmark';
|
||||
import type {SnapshotConfig, TestSnapshotResults} from './snapshotContext';
|
||||
|
||||
import {getConstants} from '../src/Constants';
|
||||
@@ -26,7 +27,6 @@ export type TestCaseResult = {
|
||||
failureDetails: Array<FailureDetail>,
|
||||
numPassingAsserts: number,
|
||||
snapshotResults: TestSnapshotResults,
|
||||
testArtifact?: mixed,
|
||||
// location: string,
|
||||
};
|
||||
|
||||
@@ -315,7 +315,6 @@ function runSpec(spec: Spec): TestCaseResult {
|
||||
failureDetails: [],
|
||||
numPassingAsserts: 0,
|
||||
snapshotResults: {},
|
||||
testArtifact: null,
|
||||
};
|
||||
|
||||
if (!shouldRunSuite(spec)) {
|
||||
@@ -330,7 +329,7 @@ function runSpec(spec: Spec): TestCaseResult {
|
||||
|
||||
try {
|
||||
invokeHooks(spec.parentContext, 'beforeEachHooks');
|
||||
result.testArtifact = spec.implementation();
|
||||
spec.implementation();
|
||||
invokeHooks(spec.parentContext, 'afterEachHooks');
|
||||
|
||||
status = 'passed';
|
||||
@@ -402,6 +401,13 @@ function reportTestSuiteResult(testSuiteResult: TestSuiteResult): void {
|
||||
);
|
||||
}
|
||||
|
||||
export function reportBenchmarkResult(result: BenchmarkResult): void {
|
||||
// Force the import of the native module to be lazy
|
||||
const NativeFantom =
|
||||
require('react-native/src/private/testing/fantom/specs/NativeFantom').default;
|
||||
NativeFantom.reportTestSuiteResultsJSON(JSON.stringify(result));
|
||||
}
|
||||
|
||||
function validateEmptyMessageQueue(): void {
|
||||
// Force the import of the native module to be lazy
|
||||
const NativeFantom =
|
||||
|
||||
+24
-3
@@ -8,6 +8,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {reportBenchmarkResult} from '../runtime/setup';
|
||||
import {getConstants} from './index';
|
||||
import nullthrows from 'nullthrows';
|
||||
import NativeCPUTime from 'react-native/src/private/testing/fantom/specs/NativeCPUTime';
|
||||
@@ -30,6 +31,23 @@ export type SuiteOptions = $ReadOnly<{
|
||||
|
||||
export type TestOptions = FnOptions;
|
||||
|
||||
export type TestTaskTiming = {
|
||||
name: string,
|
||||
latency: {
|
||||
mean: number,
|
||||
min: number,
|
||||
max: number,
|
||||
p50?: number,
|
||||
p75?: number,
|
||||
p99?: number,
|
||||
},
|
||||
};
|
||||
|
||||
export type BenchmarkResult = {
|
||||
type: string,
|
||||
timings: $ReadOnlyArray<TestTaskTiming>,
|
||||
};
|
||||
|
||||
type InternalTestOptions = $ReadOnly<{
|
||||
...FnOptions,
|
||||
only?: boolean,
|
||||
@@ -176,7 +194,7 @@ export function suite(
|
||||
'Failing focused test to prevent it from being committed',
|
||||
);
|
||||
}
|
||||
return createBenchmarkTestArtifact(bench, tasks);
|
||||
reportBenchmarkResult(createBenchmarkResultsObject(bench, tasks));
|
||||
});
|
||||
|
||||
const test = (
|
||||
@@ -255,9 +273,12 @@ function printBenchmarkResults(bench: Bench) {
|
||||
console.log('');
|
||||
}
|
||||
|
||||
function createBenchmarkTestArtifact(bench: Bench, tasks: Array<TestTask>) {
|
||||
function createBenchmarkResultsObject(
|
||||
bench: Bench,
|
||||
tasks: Array<TestTask>,
|
||||
): BenchmarkResult {
|
||||
return {
|
||||
type: 'benchmark',
|
||||
type: 'benchmark-result',
|
||||
timings: tasks.map((task, i) => {
|
||||
const result = bench.results[i];
|
||||
const {min, max, mean, p50, p75, p99} = result.latency;
|
||||
|
||||
@@ -4126,10 +4126,10 @@ electron-to-chromium@^1.5.73:
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.113.tgz#1175b8ba4170541e44e9afa8b992e5bbfff0d150"
|
||||
integrity sha512-wjT2O4hX+wdWPJ76gWSkMhcHAV2PTMX+QetUCPYEdCIe+cxmgzzSSiGRCKW8nuh4mwKZlpv0xvoW7OF2X+wmHg==
|
||||
|
||||
electron@36.3.0:
|
||||
version "36.3.0"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-36.3.0.tgz#2f08fb25fb57338e80f33b324ed292f8d3c05b25"
|
||||
integrity sha512-xk5AJtjxfFRAmehcChWSz/duxI+bJiUCIkH2IX2z8K1SyGsWdpqg+NoJ20JRQ6dpSJw89KXH6juESp8nSyBexA==
|
||||
electron@37.2.4:
|
||||
version "37.2.4"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-37.2.4.tgz#c8458dfbdbbe99753e4a6711b1b86a90338c7198"
|
||||
integrity sha512-F1WDDvY60TpFwGyW+evNB5q0Em8PamcDTVIKB2NaiaKEbNC2Fabn8Wyxy5g+Anirr1K40eKGjfSJhWEUbI1TOw==
|
||||
dependencies:
|
||||
"@electron/get" "^2.0.0"
|
||||
"@types/node" "^22.7.7"
|
||||
|
||||
Reference in New Issue
Block a user