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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -6056,6 +6056,7 @@ public final class com/facebook/react/views/text/DefaultStyleValuesUtil {
public static final fun getDefaultTextColor (Landroid/content/Context;)Landroid/content/res/ColorStateList;
public static final fun getDefaultTextColorHighlight (Landroid/content/Context;)I
public static final fun getDefaultTextColorHint (Landroid/content/Context;)Landroid/content/res/ColorStateList;
public static final fun getDefaultTextColorLink (Landroid/content/Context;)I
public static final fun getTextColorSecondary (Landroid/content/Context;)Landroid/content/res/ColorStateList;
}

Expand Down Expand Up @@ -6100,7 +6101,6 @@ public class com/facebook/react/views/text/ReactTextView : androidx/appcompat/wi
public fun setHyphenationFrequency (I)V
public fun setIncludeFontPadding (Z)V
public fun setLetterSpacing (F)V
public fun setLinkifyMask (I)V
public fun setMinimumFontSize (F)V
public fun setNumberOfLines (I)V
public fun setOverflow (Ljava/lang/String;)V
Expand Down Expand Up @@ -6135,7 +6135,6 @@ public final class com/facebook/react/views/text/ReactTextViewManager : com/face
public final fun setBorderRadius (Lcom/facebook/react/views/text/ReactTextView;IF)V
public final fun setBorderStyle (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V
public final fun setBorderWidth (Lcom/facebook/react/views/text/ReactTextView;IF)V
public final fun setDataDetectorType (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V
public final fun setDisabled (Lcom/facebook/react/views/text/ReactTextView;Z)V
public final fun setEllipsizeMode (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V
public final fun setFontSize (Lcom/facebook/react/views/text/ReactTextView;F)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ public object DefaultStyleValuesUtil {
getDefaultTextAttribute(context, android.R.attr.textColorHighlight)?.defaultColor
?: 0 // if the highlight color is not defined in the theme, return black

/**
* Utility method that returns the default text link color as defined by the theme
*
* @param context The Context
* @return The default color int for links as defined in the style
*/
@JvmStatic
public fun getDefaultTextColorLink(context: Context): Int =
getDefaultTextAttribute(context, android.R.attr.textColorLink)?.defaultColor
?: 0xFF0000EE.toInt() // fallback to a standard blue if not defined by the theme

private fun getDefaultTextAttribute(context: Context, attribute: Int): ColorStateList? {
context.theme.obtainStyledAttributes(intArrayOf(attribute)).use { typedArray ->
return typedArray.getColorStateList(0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@
import android.os.Build;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.text.style.URLSpan;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.KeyEvent;
Expand Down Expand Up @@ -71,7 +70,6 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie
private float mFontSize;
private float mMinimumFontSize;
private float mLetterSpacing;
private int mLinkifyMaskType;
private boolean mTextIsSelectable;
private boolean mShouldAdjustSpannableFontSize;
private Overflow mOverflow = Overflow.VISIBLE;
Expand All @@ -91,7 +89,6 @@ public ReactTextView(Context context) {
private void initView() {
mNumberOfLines = ViewDefaults.NUMBER_OF_LINES;
mAdjustsFontSizeToFit = false;
mLinkifyMaskType = 0;
mTextIsSelectable = false;
mShouldAdjustSpannableFontSize = false;
mEllipsizeLocation = TextUtils.TruncateAt.END;
Expand Down Expand Up @@ -131,17 +128,13 @@ private void initView() {
setGravity(DEFAULT_GRAVITY);
setNumberOfLines(mNumberOfLines);
setAdjustFontSizeToFit(mAdjustsFontSizeToFit);
setLinkifyMask(mLinkifyMaskType);
setTextIsSelectable(mTextIsSelectable);

// Default true:
// https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/widget/TextView.java#L9347
setIncludeFontPadding(true);
setEnabled(true);

// reset data detectors
setLinkifyMask(0);

setEllipsizeLocation(mEllipsizeLocation);

// View flags - defaults are here:
Expand Down Expand Up @@ -386,14 +379,14 @@ public void setText(ReactTextUpdate update) {
setLayoutParams(EMPTY_LAYOUT_PARAMS);
}
Spanned spanned = update.getText();
if (mLinkifyMaskType > 0) {
if (!(spanned instanceof Spannable)) {
spanned = new SpannableString(spanned);
}
Linkify.addLinks((Spannable) spanned, mLinkifyMaskType);
setText(spanned);

URLSpan[] urlSpans = spanned.getSpans(0, spanned.length(), URLSpan.class);
if (urlSpans != null && urlSpans.length > 0) {
setMovementMethod(LinkMovementMethod.getInstance());
} else if (getMovementMethod() instanceof LinkMovementMethod) {
setMovementMethod(null);
}
setText(spanned);

int nextTextAlign = update.getTextAlign();
if (nextTextAlign != getGravityHorizontal()) {
Expand Down Expand Up @@ -627,10 +620,6 @@ public void setSpanned(Spannable spanned) {
return mSpanned;
}

public void setLinkifyMask(int mask) {
mLinkifyMaskType = mask;
}

@Override
protected boolean dispatchHoverEvent(MotionEvent event) {
// if this view has an accessibility delegate set, and that delegate supports virtual view
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import android.text.Layout
import android.text.Spannable
import android.text.Spanned
import android.text.TextUtils
import android.text.util.Linkify
import android.view.Gravity
import com.facebook.common.logging.FLog
import com.facebook.react.R
Expand Down Expand Up @@ -152,8 +151,10 @@ public constructor(
TextLayoutManager.getOrCreateSpannableForText(
view.context,
attributedString,
paragraphAttributes,
reactTextViewManagerCallback,
)

view.setSpanned(spanned)

val minimumFontSize: Float =
Expand Down Expand Up @@ -345,31 +346,6 @@ public constructor(
view.isEnabled = !disabled
}

@ReactProp(name = "dataDetectorType")
public fun setDataDetectorType(view: ReactTextView, type: String?) {
when (type) {
"phoneNumber" -> {
view.setLinkifyMask(Linkify.PHONE_NUMBERS)
return
}
"link" -> {
view.setLinkifyMask(Linkify.WEB_URLS)
return
}
"email" -> {
view.setLinkifyMask(Linkify.EMAIL_ADDRESSES)
return
}
"all" -> {
@Suppress("DEPRECATION") view.setLinkifyMask(Linkify.ALL)
return
}
}

// "none" case, default, and null type are equivalent.
view.setLinkifyMask(0)
}

public companion object {
private const val TX_STATE_KEY_ATTRIBUTED_STRING: Short = 0
private const val TX_STATE_KEY_PARAGRAPH_ATTRIBUTES: Short = 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import android.text.StaticLayout
import android.text.TextDirectionHeuristics
import android.text.TextPaint
import android.text.TextUtils
import android.text.util.Linkify
import android.util.LayoutDirection
import android.view.Gravity
import android.view.View
Expand Down Expand Up @@ -88,6 +89,7 @@ internal object TextLayoutManager {
const val PA_KEY_MINIMUM_FONT_SIZE: Int = 6
const val PA_KEY_MAXIMUM_FONT_SIZE: Int = 7
const val PA_KEY_TEXT_ALIGN_VERTICAL: Int = 8
const val PA_KEY_DATA_DETECTOR_TYPE: Int = 9

private val TAG: String = TextLayoutManager::class.java.simpleName

Expand Down Expand Up @@ -530,6 +532,7 @@ internal object TextLayoutManager {
fun getOrCreateSpannableForText(
context: Context,
attributedString: MapBuffer,
paragraphAttributes: MapBuffer,
reactTextViewManagerCallback: ReactTextViewManagerCallback?,
): Spannable {
var text: Spannable?
Expand All @@ -541,6 +544,7 @@ internal object TextLayoutManager {
createSpannableFromAttributedString(
context,
attributedString.getMapBuffer(AS_KEY_FRAGMENTS),
paragraphAttributes,
reactTextViewManagerCallback,
null,
)
Expand All @@ -552,37 +556,47 @@ internal object TextLayoutManager {
private fun createSpannableFromAttributedString(
context: Context,
fragments: MapBuffer,
paragraphAttributes: MapBuffer,
reactTextViewManagerCallback: ReactTextViewManagerCallback?,
outputReactTags: IntArray?,
): Spannable {
if (ReactNativeFeatureFlags.enableAndroidTextMeasurementOptimizations()) {
val spannable = buildSpannableFromFragmentsOptimized(context, fragments, outputReactTags)
val spannable =
if (ReactNativeFeatureFlags.enableAndroidTextMeasurementOptimizations()) {
val s = buildSpannableFromFragmentsOptimized(context, fragments, outputReactTags)
reactTextViewManagerCallback?.onPostProcessSpannable(s)
s
} else {
val sb = SpannableStringBuilder()

reactTextViewManagerCallback?.onPostProcessSpannable(spannable)
return spannable
} else {
val sb = SpannableStringBuilder()
// The [SpannableStringBuilder] implementation require setSpan operation to be called
// up-to-bottom, otherwise all the spannables that are within the region for which one may
// set
// a new spannable will be wiped out
val ops: MutableList<SetSpanOperation> = ArrayList()

// The [SpannableStringBuilder] implementation require setSpan operation to be called
// up-to-bottom, otherwise all the spannables that are within the region for which one may set
// a new spannable will be wiped out
val ops: MutableList<SetSpanOperation> = ArrayList()
buildSpannableFromFragments(context, fragments, sb, ops, outputReactTags)

buildSpannableFromFragments(context, fragments, sb, ops, outputReactTags)
// TODO T31905686: add support for inline Images
// While setting the Spans on the final text, we also check whether any of them are
// images.
for (priorityIndex in ops.indices) {
val op = ops[ops.size - priorityIndex - 1]

// TODO T31905686: add support for inline Images
// While setting the Spans on the final text, we also check whether any of them are images.
for (priorityIndex in ops.indices) {
val op = ops[ops.size - priorityIndex - 1]
// Actual order of calling {@code execute} does NOT matter,
// but the {@code priorityIndex} DOES matter.
op.execute(sb, priorityIndex)
}

// Actual order of calling {@code execute} does NOT matter,
// but the {@code priorityIndex} DOES matter.
op.execute(sb, priorityIndex)
}
reactTextViewManagerCallback?.onPostProcessSpannable(sb)
sb
}

reactTextViewManagerCallback?.onPostProcessSpannable(sb)
return sb
val linkifyMask = getLinkifyMask(paragraphAttributes)
if (linkifyMask > 0) {
Linkify.addLinks(spannable, linkifyMask)
}

return spannable
}

private fun createLayout(
Expand Down Expand Up @@ -742,6 +756,8 @@ internal object TextLayoutManager {
baseTextAttributes: TextAttributeProps,
context: Context,
) {
paint.linkColor = DefaultStyleValuesUtil.getDefaultTextColorLink(context)

if (baseTextAttributes.fontSize != ReactConstants.UNSET) {
paint.textSize = baseTextAttributes.fontSize.toFloat()
}
Expand Down Expand Up @@ -809,7 +825,13 @@ internal object TextLayoutManager {
heightYogaMeasureMode: YogaMeasureMode,
reactTextViewManagerCallback: ReactTextViewManagerCallback?,
): Layout {
val text = getOrCreateSpannableForText(context, attributedString, reactTextViewManagerCallback)
val text =
getOrCreateSpannableForText(
context,
attributedString,
paragraphAttributes,
reactTextViewManagerCallback,
)

val paint: TextPaint
if (attributedString.contains(AS_KEY_CACHE_ID)) {
Expand Down Expand Up @@ -915,6 +937,19 @@ internal object TextLayoutManager {
)
}

private fun getLinkifyMask(paragraphAttributes: MapBuffer): Int {
if (!paragraphAttributes.contains(PA_KEY_DATA_DETECTOR_TYPE)) {
return 0
}
return when (paragraphAttributes.getString(PA_KEY_DATA_DETECTOR_TYPE)) {
"phoneNumber" -> Linkify.PHONE_NUMBERS
"link" -> Linkify.WEB_URLS
"email" -> Linkify.EMAIL_ADDRESSES
"all" -> @Suppress("DEPRECATION") Linkify.ALL
else -> 0
}
}

@JvmStatic
fun createPreparedLayout(
context: Context,
Expand All @@ -932,9 +967,11 @@ internal object TextLayoutManager {
createSpannableFromAttributedString(
context,
fragments,
paragraphAttributes,
reactTextViewManagerCallback,
reactTags,
)

val baseTextAttributes =
TextAttributeProps.fromMapBuffer(attributedString.getMapBuffer(AS_KEY_BASE_ATTRIBUTES))
val layout =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,7 @@ public open class ReactTextInputManager public constructor() :
TextLayoutManager.getOrCreateSpannableForText(
view.context,
attributedString,
paragraphAttributes,
reactTextViewManagerCallback,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,17 @@ bool ParagraphAttributes::operator==(const ParagraphAttributes& rhs) const {
adjustsFontSizeToFit,
includeFontPadding,
android_hyphenationFrequency,
textAlignVertical) ==
textAlignVertical,
dataDetectorType) ==
std::tie(
rhs.maximumNumberOfLines,
rhs.ellipsizeMode,
rhs.textBreakStrategy,
rhs.adjustsFontSizeToFit,
rhs.includeFontPadding,
rhs.android_hyphenationFrequency,
rhs.textAlignVertical) &&
rhs.textAlignVertical,
rhs.dataDetectorType) &&
floatEquality(minimumFontSize, rhs.minimumFontSize) &&
floatEquality(maximumFontSize, rhs.maximumFontSize) &&
floatEquality(minimumFontScale, rhs.minimumFontScale);
Expand Down Expand Up @@ -75,7 +77,11 @@ SharedDebugStringConvertibleList ParagraphAttributes::getDebugProps() const {
debugStringConvertibleItem(
"textAlignVertical",
textAlignVertical,
paragraphAttributes.textAlignVertical)};
paragraphAttributes.textAlignVertical),
debugStringConvertibleItem(
"dataDetectorType",
dataDetectorType,
paragraphAttributes.dataDetectorType)};
}
#endif

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ class ParagraphAttributes : public DebugStringConvertible {
*/
std::optional<TextAlignmentVertical> textAlignVertical{};

/*
* (Android only) Defines the type of data to be detected and converted to
* clickable URLs in the text.
*/
std::optional<DataDetectorType> dataDetectorType{};

bool operator==(const ParagraphAttributes &rhs) const;

#pragma mark - DebugStringConvertible
Expand Down Expand Up @@ -109,7 +115,8 @@ struct hash<facebook::react::ParagraphAttributes> {
attributes.includeFontPadding,
attributes.android_hyphenationFrequency,
attributes.minimumFontScale,
attributes.textAlignVertical);
attributes.textAlignVertical,
attributes.dataDetectorType);
}
};
} // namespace std
Loading
Loading