From 3e569ad0b0dbc2506b9e7b3b4298b1b00ab34409 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Sat, 7 Feb 2026 22:36:20 -0800 Subject: [PATCH 1/3] Remove FrescoBasedReactTextInlineImageViewManager from ReactPackages (#55435) Summary: This is never used when using Fabric. Changelog: [Internal] Reviewed By: javache, cortinico Differential Revision: D92482459 --- .../main/java/com/facebook/react/shell/MainReactPackage.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.kt index 64452e80f3d2c7..9927bf571536e5 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.kt @@ -58,7 +58,6 @@ import com.facebook.react.views.swiperefresh.SwipeRefreshLayoutManager import com.facebook.react.views.switchview.ReactSwitchManager import com.facebook.react.views.text.PreparedLayoutTextViewManager import com.facebook.react.views.text.ReactTextViewManager -import com.facebook.react.views.text.frescosupport.FrescoBasedReactTextInlineImageViewManager import com.facebook.react.views.textinput.ReactTextInputManager import com.facebook.react.views.unimplementedview.ReactUnimplementedViewManager import com.facebook.react.views.view.ReactViewManager @@ -146,7 +145,6 @@ constructor(private val config: MainPackageConfig? = null) : ReactSafeAreaViewManager(), SwipeRefreshLayoutManager(), // Native equivalents - FrescoBasedReactTextInlineImageViewManager(), ReactImageManager(), ReactModalHostManager(), ReactTextInputManager(), @@ -183,8 +181,6 @@ constructor(private val config: MainPackageConfig? = null) : ReactSwitchManager.REACT_CLASS to ModuleSpec.viewManagerSpec { ReactSwitchManager() }, SwipeRefreshLayoutManager.REACT_CLASS to ModuleSpec.viewManagerSpec { SwipeRefreshLayoutManager() }, - FrescoBasedReactTextInlineImageViewManager.REACT_CLASS to - ModuleSpec.viewManagerSpec { FrescoBasedReactTextInlineImageViewManager() }, ReactImageManager.REACT_CLASS to ModuleSpec.viewManagerSpec { ReactImageManager() }, ReactModalHostManager.REACT_CLASS to ModuleSpec.viewManagerSpec { ReactModalHostManager() }, From c9412cd37b57c425cb261837175984d0d5b23cfb Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Sat, 7 Feb 2026 22:36:20 -0800 Subject: [PATCH 2/3] Delete "react.views.text.frescosupport" (#55436) Summary: The component is no longer referenced anywhere after the previous changes, and can be deleted. Changelog: [Internal] Reviewed By: cortinico Differential Revision: D92484075 --- ...escoBasedReactTextInlineImageShadowNode.kt | 156 ---------------- .../FrescoBasedReactTextInlineImageSpan.kt | 174 ------------------ ...scoBasedReactTextInlineImageViewManager.kt | 54 ------ 3 files changed, 384 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageShadowNode.kt delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageSpan.kt delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageViewManager.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageShadowNode.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageShadowNode.kt deleted file mode 100644 index cb6db4754349c6..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageShadowNode.kt +++ /dev/null @@ -1,156 +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. - */ - -@file:Suppress("DEPRECATION") - -package com.facebook.react.views.text.frescosupport - -import android.content.Context -import android.net.Uri -import com.facebook.common.logging.FLog -import com.facebook.common.util.UriUtil -import com.facebook.drawee.controller.AbstractDraweeControllerBuilder -import com.facebook.imagepipeline.request.ImageRequest -import com.facebook.react.bridge.Dynamic -import com.facebook.react.bridge.ReadableArray -import com.facebook.react.bridge.ReadableMap -import com.facebook.react.bridge.ReadableType -import com.facebook.react.common.ReactConstants -import com.facebook.react.common.annotations.internal.LegacyArchitecture -import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel -import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger -import com.facebook.react.uimanager.ViewProps -import com.facebook.react.uimanager.annotations.ReactProp -import com.facebook.react.views.text.internal.ReactTextInlineImageShadowNode -import com.facebook.react.views.text.internal.span.TextInlineImageSpan -import com.facebook.yoga.YogaConstants -import java.util.Locale - -/** Shadow node that represents an inline image. Loading is done using Fresco. */ -@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR) -@Deprecated( - message = "This class is part of Legacy Architecture and will be removed in a future release", - level = DeprecationLevel.WARNING, -) -internal class FrescoBasedReactTextInlineImageShadowNode( - private val draweeControllerBuilder: AbstractDraweeControllerBuilder<*, ImageRequest, *, *>, - private val callerContext: Any?, -) : ReactTextInlineImageShadowNode() { - - private var uri: Uri? = null - private var headers: ReadableMap? = null - private var width = YogaConstants.UNDEFINED - private var resizeMode: String? = null - private var height = YogaConstants.UNDEFINED - private var tintColor = 0 - - @ReactProp(name = "src") - fun setSource(sources: ReadableArray?) { - val source = - if (sources == null || sources.size() == 0 || sources.getType(0) != ReadableType.Map) null - else checkNotNull(sources.getMap(0)).getString("uri") - var tempUri: Uri? = null - if (source != null) { - try { - tempUri = Uri.parse(source) - // Verify scheme is set, so that relative uri (used by static resources) are not handled. - if (tempUri.scheme == null) { - tempUri = null - } - } catch (e: Exception) { - // ignore malformed uri, then attempt to extract resource ID. - } - if (tempUri == null) { - tempUri = getResourceDrawableUri(themedContext, source) - } - } - if (tempUri != uri) { - markUpdated() - } - uri = tempUri - } - - @ReactProp(name = "headers") - fun setHeaders(newHeaders: ReadableMap?) { - headers = newHeaders - } - - @ReactProp(name = "tintColor", customType = "Color") - fun setTintColor(newTintColor: Int) { - tintColor = newTintColor - } - - /** Besides width/height, all other layout props on inline images are ignored */ - override fun setWidth(newWidth: Dynamic) { - if (newWidth.type == ReadableType.Number) { - width = newWidth.asDouble().toFloat() - } else { - FLog.w(ReactConstants.TAG, "Inline images must not have percentage based width") - width = YogaConstants.UNDEFINED - } - } - - override fun setHeight(newHeight: Dynamic) { - if (newHeight.type == ReadableType.Number) { - height = newHeight.asDouble().toFloat() - } else { - FLog.w(ReactConstants.TAG, "Inline images must not have percentage based height") - height = YogaConstants.UNDEFINED - } - } - - @ReactProp(name = ViewProps.RESIZE_MODE) - fun setResizeMode(newResizeMode: String?) { - resizeMode = newResizeMode - } - - fun getUri(): Uri? = uri - - fun getHeaders(): ReadableMap? = headers - - override fun isVirtual(): Boolean = true - - override fun buildInlineImageSpan(): TextInlineImageSpan { - val resources = themedContext.resources - val finalWidth = Math.ceil(width.toDouble()).toInt() - val finalHeight = Math.ceil(height.toDouble()).toInt() - return FrescoBasedReactTextInlineImageSpan( - resources, - finalHeight, - finalWidth, - tintColor, - getUri(), - getHeaders(), - getDraweeControllerBuilder(), - getCallerContext(), - resizeMode, - ) - } - - fun getDraweeControllerBuilder() = draweeControllerBuilder - - fun getCallerContext(): Any? = callerContext - - // TODO: t9053573 is tracking that this code should be shared - companion object { - fun getResourceDrawableUri(context: Context, name: String?): Uri? { - if (name == null || name.isEmpty()) { - return null - } - val formattedName = name.lowercase(Locale.getDefault()).replace("-", "_") - val resId = context.resources.getIdentifier(formattedName, "drawable", context.packageName) - return Uri.Builder().scheme(UriUtil.LOCAL_RESOURCE_SCHEME).path(resId.toString()).build() - } - - init { - LegacyArchitectureLogger.assertLegacyArchitecture( - "FrescoBasedReactTextInlineImageShadowNode", - LegacyArchitectureLogLevel.ERROR, - ) - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageSpan.kt deleted file mode 100644 index 117a7020322405..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageSpan.kt +++ /dev/null @@ -1,174 +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.frescosupport - -import android.content.res.Resources -import android.graphics.BlendMode -import android.graphics.BlendModeColorFilter -import android.graphics.Canvas -import android.graphics.Paint -import android.graphics.PorterDuff -import android.graphics.PorterDuffColorFilter -import android.graphics.drawable.Drawable -import android.net.Uri -import android.os.Build -import android.widget.TextView -import com.facebook.drawee.controller.AbstractDraweeControllerBuilder -import com.facebook.drawee.generic.GenericDraweeHierarchy -import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder -import com.facebook.drawee.view.DraweeHolder -import com.facebook.imagepipeline.request.ImageRequest -import com.facebook.imagepipeline.request.ImageRequestBuilder -import com.facebook.react.bridge.ReadableMap -import com.facebook.react.modules.fresco.ReactNetworkImageRequest -import com.facebook.react.uimanager.PixelUtil -import com.facebook.react.views.image.ImageResizeMode -import com.facebook.react.views.text.internal.span.TextInlineImageSpan - -/** - * FrescoBasedTextInlineImageSpan is a span for Images that are inside . It computes its size - * based on the input size. When it is time to draw, it will use the Fresco framework to get the - * right Drawable and let that draw. - * - * Since Fresco needs to callback to the TextView that contains this, in the ViewManager, you must - * tell the Span about the TextView - * - * Note: It borrows code from DynamicDrawableSpan and if that code updates how it computes size or - * draws, we need to update this as well. - */ -internal class FrescoBasedReactTextInlineImageSpan( - resources: Resources, - height: Int, - width: Int, - private val tintColor: Int, - uri: Uri?, - private val headers: ReadableMap?, - private val draweeControllerBuilder: AbstractDraweeControllerBuilder<*, ImageRequest, *, *>, - private val callerContext: Any?, - private val resizeMode: String?, -) : TextInlineImageSpan() { - - private var textView: TextView? = null - private val _uri: Uri = uri ?: Uri.EMPTY - private val _width: Int = PixelUtil.toPixelFromDIP(width.toDouble()).toInt() - private val _height: Int = PixelUtil.toPixelFromDIP(height.toDouble()).toInt() - private val draweeHolder: DraweeHolder = - DraweeHolder(GenericDraweeHierarchyBuilder.newInstance(resources).build()) - - override val width: Int - get() = _width - - override val height: Int - get() = _height - - override var drawable: Drawable? = null - private set - - /** - * The ReactTextView that holds this ImageSpan is responsible for passing these methods on so that - * we can do proper lifetime management for Fresco - */ - override fun onDetachedFromWindow() { - draweeHolder.onDetach() - } - - override fun onStartTemporaryDetach() { - draweeHolder.onDetach() - } - - override fun onAttachedToWindow() { - draweeHolder.onAttach() - } - - override fun onFinishTemporaryDetach() { - draweeHolder.onAttach() - } - - override fun getSize( - paint: Paint, - text: CharSequence, - start: Int, - end: Int, - fm: Paint.FontMetricsInt?, - ): Int { - // NOTE: This getSize code is copied from DynamicDrawableSpan and modified - // to not use a Drawable - - fm?.let { fm -> - fm.ascent = -_height - fm.descent = 0 - - fm.top = fm.ascent - fm.bottom = 0 - } - - return _width - } - - override fun setTextView(textView: TextView?) { - this.textView = textView - } - - override fun draw( - canvas: Canvas, - text: CharSequence, - start: Int, - end: Int, - x: Float, - top: Int, - y: Int, - bottom: Int, - paint: Paint, - ) { - if (drawable == null) { - val imageRequestBuilder = ImageRequestBuilder.newBuilderWithSource(_uri) - val imageRequest: ImageRequest = - ReactNetworkImageRequest.fromBuilderWithHeaders(imageRequestBuilder, headers) - - draweeHolder.hierarchy.setActualImageScaleType(ImageResizeMode.toScaleType(resizeMode)) - - draweeControllerBuilder.reset() - draweeControllerBuilder.oldController = draweeHolder.controller - - callerContext?.let { draweeControllerBuilder.setCallerContext(it) } - - draweeControllerBuilder.setImageRequest(imageRequest) - - val draweeController = draweeControllerBuilder.build() - draweeHolder.controller = draweeController - draweeControllerBuilder.reset() - - checkNotNull(draweeHolder.topLevelDrawable).apply { - setBounds(0, 0, _width, _height) - if (tintColor != 0) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - colorFilter = BlendModeColorFilter(tintColor, BlendMode.SRC_IN) - } else { - colorFilter = PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN) - } - } - callback = textView - drawable = this - } - } - - // NOTE: This drawing code is copied from DynamicDrawableSpan - - canvas.save() - - // Align to center - val _drawable = checkNotNull(drawable) - val fontHeight = (paint.descent() - paint.ascent()).toInt() - val centerY = y + paint.descent().toInt() - fontHeight / 2 - val transY = centerY - _drawable.bounds.height() / 2 - - canvas.translate(x, transY.toFloat()) - _drawable.draw(canvas) - canvas.restore() - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageViewManager.kt deleted file mode 100644 index e1ee66a6b1aea2..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageViewManager.kt +++ /dev/null @@ -1,54 +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.frescosupport - -import android.view.View -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.drawee.controller.AbstractDraweeControllerBuilder -import com.facebook.imagepipeline.request.ImageRequest -import com.facebook.react.module.annotations.ReactModule -import com.facebook.react.uimanager.BaseViewManager -import com.facebook.react.uimanager.ThemedReactContext - -/** - * Manages Images embedded in Text nodes using Fresco. Since they are used only as a virtual nodes - * any type of native view operation will throw an [IllegalStateException]. - */ -@Suppress("DEPRECATION") -@ReactModule(name = FrescoBasedReactTextInlineImageViewManager.REACT_CLASS) -internal class FrescoBasedReactTextInlineImageViewManager -@JvmOverloads -constructor( - private val draweeControllerBuilder: - @JvmSuppressWildcards - AbstractDraweeControllerBuilder<*, ImageRequest, *, *>? = - null, - private val callerContext: Any? = null, -) : BaseViewManager() { - - override fun getName(): String = REACT_CLASS - - override fun createViewInstance(context: ThemedReactContext): View { - throw IllegalStateException("RCTTextInlineImage doesn't map into a native view") - } - - override fun createShadowNodeInstance(): FrescoBasedReactTextInlineImageShadowNode = - FrescoBasedReactTextInlineImageShadowNode( - draweeControllerBuilder ?: Fresco.newDraweeControllerBuilder(), - callerContext, - ) - - override fun getShadowNodeClass(): Class = - FrescoBasedReactTextInlineImageShadowNode::class.java - - override fun updateExtraData(root: View, extraData: Any) = Unit - - companion object { - const val REACT_CLASS: String = "RCTTextInlineImage" - } -} From a5aff324d1b4c7a835b3a25d6be2396abc52f676 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Sat, 7 Feb 2026 22:36:20 -0800 Subject: [PATCH 3/3] Cleanup Paper Handling of Inline Images and Padding in Text Components (#55437) Summary: This deletes some Paper specific paths in Text and image code, related to inline view handling (done in native component layer in Paper), and how padding is set. It also changed `Spannable` to `Spanned` in a couple places, in preparation of reusing `ReactTextViewManager` with state generated by Facsimile (for selectable text). I left the actions performed by Paper ShadowNode as something stublike, since that code should never be executed. `ReactTextUpdate` is made internal, since the shape of native component state is subject to change. Changelog: [Android][Breaking] - Make ReactTextUpdate internal Differential Revision: D92459725 --- .../ReactAndroid/api/ReactAndroid.api | 37 ------ .../react/views/text/ReactTextUpdate.kt | 69 +----------- .../react/views/text/ReactTextView.java | 105 ++---------------- .../react/views/text/ReactTextViewManager.kt | 11 +- .../ReactTextInlineImageShadowNode.kt | 30 ----- .../text/internal/span/TextInlineImageSpan.kt | 58 ---------- .../react/views/textinput/ReactEditText.kt | 77 +------------ .../views/textinput/ReactTextInputManager.kt | 31 ------ 8 files changed, 16 insertions(+), 402 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/ReactTextInlineImageShadowNode.kt delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/TextInlineImageSpan.kt diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 94e3879b76a608..924307ea4032db 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -6133,44 +6133,17 @@ public final class com/facebook/react/views/text/ReactTextShadowNode : com/faceb public final fun setShouldNotifyOnTextLayout (Z)V } -public final class com/facebook/react/views/text/ReactTextUpdate { - public static final field Companion Lcom/facebook/react/views/text/ReactTextUpdate$Companion; - public fun (Landroid/text/Spannable;IZFFFFI)V - public fun (Landroid/text/Spannable;IZFFFFIII)V - public fun (Landroid/text/Spannable;IZIII)V - public static final fun buildReactTextUpdateFromState (Landroid/text/Spannable;IIII)Lcom/facebook/react/views/text/ReactTextUpdate; - public final fun containsImages ()Z - public final fun getContainsImages ()Z - public final fun getJsEventCounter ()I - public final fun getJustificationMode ()I - public final fun getPaddingBottom ()F - public final fun getPaddingLeft ()F - public final fun getPaddingRight ()F - public final fun getPaddingTop ()F - public final fun getText ()Landroid/text/Spannable; - public final fun getTextAlign ()I - public final fun getTextBreakStrategy ()I -} - -public final class com/facebook/react/views/text/ReactTextUpdate$Companion { - public final fun buildReactTextUpdateFromState (Landroid/text/Spannable;IIII)Lcom/facebook/react/views/text/ReactTextUpdate; -} - public class com/facebook/react/views/text/ReactTextView : androidx/appcompat/widget/AppCompatTextView, com/facebook/react/uimanager/ReactCompoundView { public fun (Landroid/content/Context;)V protected fun dispatchHoverEvent (Landroid/view/MotionEvent;)Z public fun dispatchKeyEvent (Landroid/view/KeyEvent;)Z public fun getSpanned ()Landroid/text/Spannable; public fun hasOverlappingRendering ()Z - public fun invalidateDrawable (Landroid/graphics/drawable/Drawable;)V public fun onAttachedToWindow ()V - public fun onDetachedFromWindow ()V protected fun onDraw (Landroid/graphics/Canvas;)V - public fun onFinishTemporaryDetach ()V public final fun onFocusChanged (ZILandroid/graphics/Rect;)V protected fun onLayout (ZIIII)V protected fun onMeasure (II)V - public fun onStartTemporaryDetach ()V public fun reactTagForTouch (FF)I public fun setAdjustFontSizeToFit (Z)V public fun setBackgroundColor (I)V @@ -6193,7 +6166,6 @@ public class com/facebook/react/views/text/ReactTextView : androidx/appcompat/wi public fun setText (Lcom/facebook/react/views/text/ReactTextUpdate;)V public fun setTextIsSelectable (Z)V public fun updateView ()V - protected fun verifyDrawable (Landroid/graphics/drawable/Drawable;)Z } public final class com/facebook/react/views/text/ReactTextViewManager : com/facebook/react/views/text/ReactTextAnchorViewManager, com/facebook/react/uimanager/IViewManagerWithChildren, com/facebook/react/views/text/ReactTextViewManagerCallback { @@ -6344,7 +6316,6 @@ public class com/facebook/react/views/textinput/ReactEditText : androidx/appcomp public final fun canUpdateWithEventCount (I)Z protected final fun finalize ()V public final fun getBorderColor (I)I - protected final fun getContainsImages ()Z public final fun getDisableFullscreenUI ()Z public final fun getDragAndDropFilter ()Ljava/util/List; protected final fun getNativeEventCount ()I @@ -6354,27 +6325,21 @@ public class com/facebook/react/views/textinput/ReactEditText : androidx/appcomp public final fun getSubmitBehavior ()Ljava/lang/String; protected final fun hideSoftKeyboard ()V public final fun incrementAndGetEventCounter ()I - public fun invalidateDrawable (Landroid/graphics/drawable/Drawable;)V public fun isLayoutRequested ()Z protected final fun isSettingTextFromJS ()Z protected final fun isSettingTextFromState ()Z public final fun maybeSetSelection (III)V - public final fun maybeSetTextFromJS (Lcom/facebook/react/views/text/ReactTextUpdate;)V - public final fun maybeSetTextFromState (Lcom/facebook/react/views/text/ReactTextUpdate;)V public final fun maybeUpdateTypeface ()V public fun onAttachedToWindow ()V public fun onConfigurationChanged (Landroid/content/res/Configuration;)V public fun onCreateInputConnection (Landroid/view/inputmethod/EditorInfo;)Landroid/view/inputmethod/InputConnection; - public fun onDetachedFromWindow ()V public fun onDragEvent (Landroid/view/DragEvent;)Z public fun onDraw (Landroid/graphics/Canvas;)V - public fun onFinishTemporaryDetach ()V protected fun onFocusChanged (ZILandroid/graphics/Rect;)V public fun onKeyUp (ILandroid/view/KeyEvent;)Z protected fun onLayout (ZIIII)V protected fun onScrollChanged (IIII)V protected fun onSelectionChanged (II)V - public fun onStartTemporaryDetach ()V public fun onTextContextMenuItem (I)Z public fun onTouchEvent (Landroid/view/MotionEvent;)Z public fun removeTextChangedListener (Landroid/text/TextWatcher;)V @@ -6387,7 +6352,6 @@ public class com/facebook/react/views/textinput/ReactEditText : androidx/appcomp public final fun setBorderRadius (FI)V public final fun setBorderStyle (Ljava/lang/String;)V public final fun setBorderWidth (IF)V - protected final fun setContainsImages (Z)V public final fun setContentSizeWatcher (Lcom/facebook/react/views/textinput/ContentSizeWatcher;)V public final fun setContextMenuHidden (Z)V public final fun setDisableFullscreenUI (Z)V @@ -6418,7 +6382,6 @@ public class com/facebook/react/views/textinput/ReactEditText : androidx/appcomp public final fun shouldBlurOnReturn ()Z public final fun shouldSubmitOnReturn ()Z protected final fun showSoftKeyboard ()Z - protected fun verifyDrawable (Landroid/graphics/drawable/Drawable;)Z } public final class com/facebook/react/views/textinput/ReactEditText$Companion { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.kt index e882fb22e22b87..dad6ce9aa1c547 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.kt @@ -7,80 +7,20 @@ package com.facebook.react.views.text -import android.text.Layout -import android.text.Spannable -import com.facebook.react.common.ReactConstants +import android.text.Spanned /** Class that contains the data needed for a text update. Used by both and . */ -public class ReactTextUpdate( - public val text: Spannable, +internal class ReactTextUpdate( + public val text: Spanned, public val jsEventCounter: Int, - public val containsImages: Boolean, - public val paddingLeft: Float, - public val paddingTop: Float, - public val paddingRight: Float, - public val paddingBottom: Float, public val textAlign: Int, public val textBreakStrategy: Int, public val justificationMode: Int, ) { - - /** - * @deprecated Use a non-deprecated constructor for ReactTextUpdate instead. This one remains - * because it's being used by a unit test that isn't currently open source. - */ - public constructor( - text: Spannable, - jsEventCounter: Int, - containsImages: Boolean, - paddingStart: Float, - paddingTop: Float, - paddingEnd: Float, - paddingBottom: Float, - textAlign: Int, - ) : this( - text, - jsEventCounter, - containsImages, - paddingStart, - paddingTop, - paddingEnd, - paddingBottom, - textAlign, - Layout.BREAK_STRATEGY_HIGH_QUALITY, - Layout.JUSTIFICATION_MODE_NONE, - ) - - public constructor( - text: Spannable, - jsEventCounter: Int, - containsImages: Boolean, - textAlign: Int, - textBreakStrategy: Int, - justificationMode: Int, - ) : this( - text, - jsEventCounter, - containsImages, - ReactConstants.UNSET.toFloat(), - ReactConstants.UNSET.toFloat(), - ReactConstants.UNSET.toFloat(), - ReactConstants.UNSET.toFloat(), - textAlign, - textBreakStrategy, - justificationMode, - ) - - @Deprecated( - "This is just for backwards compatibility and will be removed some time in the future", - ReplaceWith("containsImages"), - ) - public fun containsImages(): Boolean = containsImages - public companion object { @JvmStatic public fun buildReactTextUpdateFromState( - text: Spannable, + text: Spanned, jsEventCounter: Int, textAlign: Int, textBreakStrategy: Int, @@ -89,7 +29,6 @@ public class ReactTextUpdate( ReactTextUpdate( text, jsEventCounter, - false, textAlign, textBreakStrategy, justificationMode, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java index 906dfbce04905d..5e3cf945fdacdb 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java @@ -10,10 +10,10 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; -import android.graphics.drawable.Drawable; 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; @@ -53,7 +53,6 @@ import com.facebook.react.uimanager.style.LogicalEdge; import com.facebook.react.uimanager.style.Overflow; import com.facebook.react.views.text.internal.span.ReactTagSpan; -import com.facebook.react.views.text.internal.span.TextInlineImageSpan; import com.facebook.react.views.text.internal.span.TextInlineViewPlaceholderSpan; import com.facebook.yoga.YogaMeasureMode; @@ -66,7 +65,6 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie // https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/widget/TextView.java#L854 private static final int DEFAULT_GRAVITY = Gravity.TOP | Gravity.START; - private boolean mContainsImages; private int mNumberOfLines; private @Nullable TextUtils.TruncateAt mEllipsizeLocation; private boolean mAdjustsFontSizeToFit; @@ -377,38 +375,21 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { public void setText(ReactTextUpdate update) { try (SystraceSection s = new SystraceSection("ReactTextView.setText(ReactTextUpdate)")) { - mContainsImages = update.containsImages(); // Android's TextView crashes when it tries to relayout if LayoutParams are // null; explicitly set the LayoutParams to prevent this crash. See: // https://github.com/facebook/react-native/pull/7011 if (getLayoutParams() == null) { setLayoutParams(EMPTY_LAYOUT_PARAMS); } - Spannable spannable = update.getText(); + Spanned spanned = update.getText(); if (mLinkifyMaskType > 0) { - Linkify.addLinks(spannable, mLinkifyMaskType); + if (!(spanned instanceof Spannable)) { + spanned = new SpannableString(spanned); + } + Linkify.addLinks((Spannable) spanned, mLinkifyMaskType); setMovementMethod(LinkMovementMethod.getInstance()); } - setText(spannable); - float paddingLeft = update.getPaddingLeft(); - float paddingTop = update.getPaddingTop(); - float paddingRight = update.getPaddingRight(); - float paddingBottom = update.getPaddingBottom(); - - // In Fabric padding is set by the update of Layout Metrics and not as part of the "setText" - // operation - // TODO T56559197: remove this condition when we migrate 100% to Fabric - if (paddingLeft != ReactConstants.UNSET - && paddingTop != ReactConstants.UNSET - && paddingRight != ReactConstants.UNSET - && paddingBottom != ReactConstants.UNSET) { - - setPadding( - (int) Math.floor(paddingLeft), - (int) Math.floor(paddingTop), - (int) Math.floor(paddingRight), - (int) Math.floor(paddingBottom)); - } + setText(spanned); int nextTextAlign = update.getTextAlign(); if (nextTextAlign != getGravityHorizontal()) { @@ -481,58 +462,6 @@ public int reactTagForTouch(float touchX, float touchY) { return target; } - @Override - protected boolean verifyDrawable(Drawable drawable) { - if (mContainsImages && getText() instanceof Spanned) { - Spanned text = (Spanned) getText(); - TextInlineImageSpan[] spans = text.getSpans(0, text.length(), TextInlineImageSpan.class); - for (TextInlineImageSpan span : spans) { - if (span.getDrawable() == drawable) { - return true; - } - } - } - return super.verifyDrawable(drawable); - } - - @Override - public void invalidateDrawable(Drawable drawable) { - if (mContainsImages && getText() instanceof Spanned) { - Spanned text = (Spanned) getText(); - TextInlineImageSpan[] spans = text.getSpans(0, text.length(), TextInlineImageSpan.class); - for (TextInlineImageSpan span : spans) { - if (span.getDrawable() == drawable) { - invalidate(); - } - } - } - super.invalidateDrawable(drawable); - } - - @Override - public void onDetachedFromWindow() { - super.onDetachedFromWindow(); - if (mContainsImages && getText() instanceof Spanned) { - Spanned text = (Spanned) getText(); - TextInlineImageSpan[] spans = text.getSpans(0, text.length(), TextInlineImageSpan.class); - for (TextInlineImageSpan span : spans) { - span.onDetachedFromWindow(); - } - } - } - - @Override - public void onStartTemporaryDetach() { - super.onStartTemporaryDetach(); - if (mContainsImages && getText() instanceof Spanned) { - Spanned text = (Spanned) getText(); - TextInlineImageSpan[] spans = text.getSpans(0, text.length(), TextInlineImageSpan.class); - for (TextInlineImageSpan span : spans) { - span.onStartTemporaryDetach(); - } - } - } - @Override public void setTextIsSelectable(boolean selectable) { mTextIsSelectable = selectable; @@ -551,26 +480,6 @@ public void onAttachedToWindow() { } else { setTextIsSelectable(false); } - - if (mContainsImages && getText() instanceof Spanned) { - Spanned text = (Spanned) getText(); - TextInlineImageSpan[] spans = text.getSpans(0, text.length(), TextInlineImageSpan.class); - for (TextInlineImageSpan span : spans) { - span.onAttachedToWindow(); - } - } - } - - @Override - public void onFinishTemporaryDetach() { - super.onFinishTemporaryDetach(); - if (mContainsImages && getText() instanceof Spanned) { - Spanned text = (Spanned) getText(); - TextInlineImageSpan[] spans = text.getSpans(0, text.length(), TextInlineImageSpan.class); - for (TextInlineImageSpan span : spans) { - span.onFinishTemporaryDetach(); - } - } } @Override diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.kt index 180f1059463114..26630b47873044 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.kt @@ -9,6 +9,7 @@ package com.facebook.react.views.text import android.os.Build import android.text.Spannable +import android.text.Spanned import com.facebook.react.R import com.facebook.react.common.annotations.UnstableReactNativeAPI import com.facebook.react.common.mapbuffer.MapBuffer @@ -20,7 +21,6 @@ import com.facebook.react.uimanager.ReactStylesDiffMap import com.facebook.react.uimanager.StateWrapper import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.annotations.ReactProp -import com.facebook.react.views.text.internal.span.TextInlineImageSpan import java.util.HashMap /** @@ -75,17 +75,13 @@ public constructor( override fun updateExtraData(view: ReactTextView, extraData: Any) { SystraceSection("ReactTextViewManager.updateExtraData").use { s -> val update = extraData as ReactTextUpdate - val spannable: Spannable = update.text - @Suppress("DEPRECATION") - if (update.containsImages()) { - TextInlineImageSpan.possiblyUpdateInlineImageSpans(spannable, view) - } + val spanned: Spanned = update.text view.setText(update) // If this text view contains any clickable spans, set a view tag and reset the accessibility // delegate so that these can be picked up by the accessibility system. val accessibilityLinks: ReactTextViewAccessibilityDelegate.AccessibilityLinks = - ReactTextViewAccessibilityDelegate.AccessibilityLinks(spannable) + ReactTextViewAccessibilityDelegate.AccessibilityLinks(spanned) view.setTag( R.id.accessibility_links, if (accessibilityLinks.size() > 0) accessibilityLinks else null, @@ -159,7 +155,6 @@ public constructor( return ReactTextUpdate( spanned, -1, // UNUSED FOR TEXT - false, // TODO add this into local Data TextLayoutManager.getTextGravity(attributedString, spanned), textBreakStrategy, TextAttributeProps.getJustificationMode(props, currentJustificationMode), diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/ReactTextInlineImageShadowNode.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/ReactTextInlineImageShadowNode.kt deleted file mode 100644 index 84b23a6cbe9273..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/ReactTextInlineImageShadowNode.kt +++ /dev/null @@ -1,30 +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. - */ - -@file:Suppress("DEPRECATION") - -package com.facebook.react.views.text.internal - -import com.facebook.react.common.annotations.internal.LegacyArchitecture -import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel -import com.facebook.react.uimanager.LayoutShadowNode -import com.facebook.react.views.text.internal.span.TextInlineImageSpan -import com.facebook.yoga.YogaNode - -/** Base class for [YogaNode]s that represent inline images. */ -@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR) -@Deprecated( - message = "This class is part of Legacy Architecture and will be removed in a future release", - level = DeprecationLevel.WARNING, -) -internal abstract class ReactTextInlineImageShadowNode : LayoutShadowNode() { - /** - * Build a [TextInlineImageSpan] from this node. This will be added to the TextView in place of - * this node. - */ - abstract fun buildInlineImageSpan(): TextInlineImageSpan -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/TextInlineImageSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/TextInlineImageSpan.kt deleted file mode 100644 index 099ca17595dd33..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/TextInlineImageSpan.kt +++ /dev/null @@ -1,58 +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.internal.span - -import android.graphics.drawable.Drawable -import android.text.Spannable -import android.text.style.ReplacementSpan -import android.view.View -import android.widget.TextView - -/** Base class for inline image spans. */ -internal abstract class TextInlineImageSpan : ReplacementSpan(), ReactSpan { - /** Get the drawable that is span represents. */ - abstract val drawable: Drawable? - - /** Called by the text view from [View.onDetachedFromWindow], */ - abstract fun onDetachedFromWindow() - - /** Called by the text view from [View.onStartTemporaryDetach]. */ - abstract fun onStartTemporaryDetach() - - /** Called by the text view from [View.onAttachedToWindow]. */ - abstract fun onAttachedToWindow() - - /** Called by the text view from [View.onFinishTemporaryDetach]. */ - abstract fun onFinishTemporaryDetach() - - /** Set the textview that will contain this span. */ - abstract fun setTextView(textView: TextView?) - - /** Get the width of the span. */ - abstract val width: Int - - /** Get the height of the span. */ - abstract val height: Int - - companion object { - /** - * For TextInlineImageSpan we need to update the Span to know that the window is attached and - * the TextView that we will set as the callback on the Drawable. - * - * @param spannable The spannable that may contain TextInlineImageSpans - * @param view The view which will be set as the callback for the Drawable - */ - @JvmStatic - fun possiblyUpdateInlineImageSpans(spannable: Spannable, view: TextView?) { - spannable.getSpans(0, spannable.length, TextInlineImageSpan::class.java).forEach { s -> - s.onAttachedToWindow() - s.setTextView(view) - } - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt index fb8f4b1bf79ca6..ff410da5caed84 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt @@ -14,7 +14,6 @@ import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Rect -import android.graphics.drawable.Drawable import android.os.Build import android.os.Bundle import android.text.Editable @@ -86,7 +85,6 @@ import com.facebook.react.views.text.internal.span.ReactSpan import com.facebook.react.views.text.internal.span.ReactStrikethroughSpan import com.facebook.react.views.text.internal.span.ReactTextPaintHolderSpan import com.facebook.react.views.text.internal.span.ReactUnderlineSpan -import com.facebook.react.views.text.internal.span.TextInlineImageSpan import java.util.concurrent.CopyOnWriteArrayList import kotlin.math.max import kotlin.math.min @@ -119,7 +117,6 @@ public open class ReactEditText public constructor(context: Context) : AppCompat private var listeners: CopyOnWriteArrayList? public var stagedInputType: Int - protected var containsImages: Boolean = false public var submitBehavior: String? = null public var dragAndDropFilter: List? = null @@ -639,13 +636,13 @@ public open class ReactEditText public constructor(context: Context) : AppCompat public fun incrementAndGetEventCounter(): Int = ++nativeEventCount - public fun maybeSetTextFromJS(reactTextUpdate: ReactTextUpdate) { + internal fun maybeSetTextFromJS(reactTextUpdate: ReactTextUpdate) { isSettingTextFromJS = true maybeSetText(reactTextUpdate) isSettingTextFromJS = false } - public fun maybeSetTextFromState(reactTextUpdate: ReactTextUpdate) { + internal fun maybeSetTextFromState(reactTextUpdate: ReactTextUpdate) { isSettingTextFromState = true maybeSetText(reactTextUpdate) isSettingTextFromState = false @@ -679,9 +676,6 @@ public open class ReactEditText public constructor(context: Context) : AppCompat manageSpans(spannableStringBuilder) stripStyleEquivalentSpans(spannableStringBuilder) - @Suppress("DEPRECATION") - containsImages = reactTextUpdate.containsImages() - // When we update text, we trigger onChangeText code that will // try to update state if the wrapper is available. Temporarily disable // to prevent an (asynchronous) infinite loop. @@ -921,54 +915,6 @@ public open class ReactEditText public constructor(context: Context) : AppCompat } } - override fun verifyDrawable(drawable: Drawable): Boolean { - if (containsImages) { - val text: Spanned? = text - val spans = checkNotNull(text).getSpans(0, text.length, TextInlineImageSpan::class.java) - for (span in spans) { - if (span.drawable === drawable) { - return true - } - } - } - return super.verifyDrawable(drawable) - } - - override fun invalidateDrawable(drawable: Drawable) { - if (containsImages) { - val text: Spanned? = text - val spans = checkNotNull(text).getSpans(0, text.length, TextInlineImageSpan::class.java) - for (span in spans) { - if (span.drawable === drawable) { - invalidate() - } - } - } - super.invalidateDrawable(drawable) - } - - public override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - if (containsImages) { - val text: Spanned? = text - val spans = checkNotNull(text).getSpans(0, text.length, TextInlineImageSpan::class.java) - for (span in spans) { - span.onDetachedFromWindow() - } - } - } - - override fun onStartTemporaryDetach() { - super.onStartTemporaryDetach() - if (containsImages) { - val text: Spanned? = text - val spans = checkNotNull(text).getSpans(0, text.length, TextInlineImageSpan::class.java) - for (span in spans) { - span.onStartTemporaryDetach() - } - } - } - public override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) @@ -994,14 +940,6 @@ public open class ReactEditText public constructor(context: Context) : AppCompat // Restore the selection since `setTextIsSelectable` changed it. maybeSetSelection(selectionStart, selectionEnd) - if (containsImages) { - val text: Spanned? = text - val spans = checkNotNull(text).getSpans(0, text.length, TextInlineImageSpan::class.java) - for (span in spans) { - span.onAttachedToWindow() - } - } - if (autoFocus && !didAttachToWindow) { requestFocusProgrammatically() } @@ -1009,17 +947,6 @@ public open class ReactEditText public constructor(context: Context) : AppCompat didAttachToWindow = true } - override fun onFinishTemporaryDetach() { - super.onFinishTemporaryDetach() - if (containsImages) { - val text: Spanned? = text - val spans = checkNotNull(text).getSpans(0, text.length, TextInlineImageSpan::class.java) - for (span in spans) { - span.onFinishTemporaryDetach() - } - } - } - override fun setBackgroundColor(color: Int) { setBackgroundColor(this, color) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt index 21244cd0b1959a..4c0fb6a4ba84dd 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt @@ -73,7 +73,6 @@ import com.facebook.react.views.text.ReactTextViewManagerCallback import com.facebook.react.views.text.ReactTypefaceUtils.parseFontVariant import com.facebook.react.views.text.TextAttributeProps import com.facebook.react.views.text.TextLayoutManager -import com.facebook.react.views.text.internal.span.TextInlineImageSpan.Companion.possiblyUpdateInlineImageSpans import java.util.LinkedList /** Manages instances of TextInput. */ @@ -195,11 +194,6 @@ public open class ReactTextInputManager public constructor() : return ReactTextUpdate( sb, mostRecentEventCount, - false, - 0f, - 0f, - 0f, - 0f, Gravity.NO_GRAVITY, 0, 0, @@ -208,31 +202,6 @@ public open class ReactTextInputManager public constructor() : override fun updateExtraData(view: ReactEditText, extraData: Any) { if (extraData is ReactTextUpdate) { - // TODO T58784068: delete this block of code, these are always unset in Fabric - val paddingLeft = extraData.paddingLeft.toInt() - val paddingTop = extraData.paddingTop.toInt() - val paddingRight = extraData.paddingRight.toInt() - val paddingBottom = extraData.paddingBottom.toInt() - if ( - paddingLeft != UNSET || - paddingTop != UNSET || - paddingRight != UNSET || - paddingBottom != UNSET - ) { - view.setPadding( - if (paddingLeft != UNSET) paddingLeft else view.paddingLeft, - if (paddingTop != UNSET) paddingTop else view.paddingTop, - if (paddingRight != UNSET) paddingRight else view.paddingRight, - if (paddingBottom != UNSET) paddingBottom else view.paddingBottom, - ) - } - - @Suppress("DEPRECATION") - if (extraData.containsImages()) { - val spannable = extraData.text - possiblyUpdateInlineImageSpans(spannable, view) - } - // Ensure that selection is handled correctly on text update val isCurrentSelectionEmpty = view.selectionStart == view.selectionEnd var selectionStart = UNSET