diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/PreparedLayoutTextViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/PreparedLayoutTextViewManager.kt new file mode 100644 index 00000000000..df5fbc29cc2 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/PreparedLayoutTextViewManager.kt @@ -0,0 +1,219 @@ +/* + * 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.text.Layout +import android.text.Spannable +import android.view.View +import com.facebook.react.R +import com.facebook.react.internal.SystraceSection +import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.uimanager.BackgroundStyleApplicator +import com.facebook.react.uimanager.BaseViewManager +import com.facebook.react.uimanager.IViewGroupManager +import com.facebook.react.uimanager.LayoutShadowNode +import com.facebook.react.uimanager.LengthPercentage +import com.facebook.react.uimanager.LengthPercentageType +import com.facebook.react.uimanager.ReactStylesDiffMap +import com.facebook.react.uimanager.ReferenceStateWrapper +import com.facebook.react.uimanager.StateWrapper +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewProps +import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.uimanager.annotations.ReactPropGroup +import com.facebook.react.uimanager.style.BorderRadiusProp +import com.facebook.react.uimanager.style.BorderStyle +import com.facebook.react.uimanager.style.LogicalEdge +import com.facebook.react.uimanager.style.Overflow +import com.facebook.react.views.text.ReactTextViewAccessibilityDelegate.AccessibilityLinks +import com.facebook.react.views.text.internal.span.ReactClickableSpan +import java.util.HashMap + +@ReactModule(name = PreparedLayoutTextViewManager.REACT_CLASS) +internal class PreparedLayoutTextViewManager : + BaseViewManager(), + IViewGroupManager { + + init { + setupViewRecycling() + } + + override fun prepareToRecycleView( + reactContext: ThemedReactContext, + view: PreparedLayoutTextView + ): PreparedLayoutTextView? { + val preparedView = super.prepareToRecycleView(reactContext, view) + preparedView?.recycleView() + return preparedView + } + + override fun getName(): String = REACT_CLASS + + override fun updateViewAccessibility(view: PreparedLayoutTextView) { + ReactTextViewAccessibilityDelegate.setDelegate( + view, view.isFocusable, view.importantForAccessibility) + } + + public override fun createViewInstance(context: ThemedReactContext): PreparedLayoutTextView = + PreparedLayoutTextView(context) + + override fun updateExtraData(view: PreparedLayoutTextView, extraData: Any) { + SystraceSection("PreparedLayoutTextViewManager.updateExtraData").use { _ -> + val layout = extraData as Layout + view.layout = layout + + // 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. + if (layout.text is Spannable) { + val spannableText = layout.text as Spannable + + val clickableSpans = + spannableText.getSpans(0, layout.text.length, ReactClickableSpan::class.java) + view.setTag( + R.id.accessibility_links, + if (clickableSpans.size > 0) AccessibilityLinks(clickableSpans, spannableText) + else null) + ReactTextViewAccessibilityDelegate.resetDelegate( + view, view.isFocusable, view.importantForAccessibility) + } + } + } + + override fun updateState( + view: PreparedLayoutTextView, + props: ReactStylesDiffMap, + stateWrapper: StateWrapper + ): Any? = (stateWrapper as? ReferenceStateWrapper)?.stateDataReference + + override fun getExportedCustomDirectEventTypeConstants(): MutableMap? { + val baseEventTypeConstants = super.getExportedCustomDirectEventTypeConstants() + val eventTypeConstants = baseEventTypeConstants ?: HashMap() + eventTypeConstants.put("topTextLayout", mapOf("registrationName" to "onTextLayout")) + return eventTypeConstants + } + + @ReactProp(name = "overflow") + public fun setOverflow(view: PreparedLayoutTextView, overflow: String?): Unit { + view.overflow = overflow?.let { Overflow.fromString(it) } ?: Overflow.HIDDEN + } + + @ReactProp(name = "accessible") + public fun setAccessible(view: PreparedLayoutTextView, accessible: Boolean): Unit { + view.isFocusable = accessible + } + + @ReactProp(name = "selectable", defaultBoolean = false) + public fun setSelectable(view: PreparedLayoutTextView, isSelectable: Boolean): Unit { + // T222052152: Implement fine-grained text selection for PreparedLayoutTextView + // view.setTextIsSelectable(isSelectable); + } + + @ReactProp(name = "selectionColor", customType = "Color") + public fun setSelectionColor(view: PreparedLayoutTextView, color: Int?): Unit { + if (color == null) { + view.selectionColor = DefaultStyleValuesUtil.getDefaultTextColorHighlight(view.context) + } else { + view.selectionColor = color + } + } + + @ReactPropGroup( + names = + [ + ViewProps.BORDER_RADIUS, + ViewProps.BORDER_TOP_LEFT_RADIUS, + ViewProps.BORDER_TOP_RIGHT_RADIUS, + ViewProps.BORDER_BOTTOM_RIGHT_RADIUS, + ViewProps.BORDER_BOTTOM_LEFT_RADIUS], + defaultFloat = Float.NaN) + public fun setBorderRadius(view: PreparedLayoutTextView, index: Int, borderRadius: Float): Unit { + val radius = + if (borderRadius.isNaN()) null + else LengthPercentage(borderRadius, LengthPercentageType.POINT) + BackgroundStyleApplicator.setBorderRadius(view, BorderRadiusProp.values()[index], radius) + } + + @ReactProp(name = "borderStyle") + public fun setBorderStyle(view: PreparedLayoutTextView, borderStyle: String?): Unit { + val parsedBorderStyle = if (borderStyle == null) null else BorderStyle.fromString(borderStyle) + BackgroundStyleApplicator.setBorderStyle(view, parsedBorderStyle) + } + + @ReactPropGroup( + names = + [ + ViewProps.BORDER_WIDTH, + ViewProps.BORDER_LEFT_WIDTH, + ViewProps.BORDER_RIGHT_WIDTH, + ViewProps.BORDER_TOP_WIDTH, + ViewProps.BORDER_BOTTOM_WIDTH, + ViewProps.BORDER_START_WIDTH, + ViewProps.BORDER_END_WIDTH], + defaultFloat = Float.NaN) + public fun setBorderWidth(view: PreparedLayoutTextView, index: Int, width: Float): Unit { + BackgroundStyleApplicator.setBorderWidth(view, LogicalEdge.values()[index], width) + } + + @ReactPropGroup( + names = + [ + ViewProps.BORDER_COLOR, + ViewProps.BORDER_LEFT_COLOR, + ViewProps.BORDER_RIGHT_COLOR, + ViewProps.BORDER_TOP_COLOR, + ViewProps.BORDER_BOTTOM_COLOR, + ViewProps.BORDER_START_COLOR, + ViewProps.BORDER_END_COLOR, + ViewProps.BORDER_BLOCK_COLOR, + ViewProps.BORDER_BLOCK_END_COLOR, + ViewProps.BORDER_BLOCK_START_COLOR, + ], + customType = "Color") + public fun setBorderColor(view: PreparedLayoutTextView, index: Int, color: Int?): Unit { + BackgroundStyleApplicator.setBorderColor(view, LogicalEdge.values()[index], color) + } + + @ReactProp(name = "disabled", defaultBoolean = false) + public fun setDisabled(view: PreparedLayoutTextView, disabled: Boolean): Unit { + view.setEnabled(!disabled) + } + + override fun setPadding( + view: PreparedLayoutTextView, + left: Int, + top: Int, + right: Int, + bottom: Int + ): Unit { + view.setPadding(left, top, right, bottom) + } + + override fun getShadowNodeClass(): Class { + throw UnsupportedOperationException( + "PreparedLayoutTextViewManager does not use legacy arch shadow nodes") + } + + override fun addView(parent: PreparedLayoutTextView, child: View, index: Int) { + parent.addView(child, index) + } + + override fun getChildAt(parent: PreparedLayoutTextView, index: Int): View? = + parent.getChildAt(index) + + override fun removeViewAt(parent: PreparedLayoutTextView, index: Int) { + parent.removeViewAt(index) + } + + override fun getChildCount(parent: PreparedLayoutTextView): Int = parent.childCount + + override fun needsCustomLayoutForChildren(): Boolean = false + + public companion object { + public const val REACT_CLASS: String = "RCTText" + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewAccessibilityDelegate.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewAccessibilityDelegate.kt index 2f207e49178..3e686ac2126 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewAccessibilityDelegate.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewAccessibilityDelegate.kt @@ -270,7 +270,7 @@ internal class ReactTextViewAccessibilityDelegate : ReactAccessibilityDelegate { return null } - public class AccessibilityLinks(spans: Array, text: Spannable) { + public class AccessibilityLinks(spans: Array, text: Spannable) { private val links: List init {