Skip to content

Commit 9bb9a28

Browse files
NickGerlemanfacebook-github-bot
authored andcommitted
PreparedLayoutTextViewManager
Summary: This implements the view manager for `PreparedLayoutTextView`, originating by taking the view managers composing `ReactTextView`, converting to Kotlin, and removing everything no longer needed. In Facsimile, anything influencing text appearance is applied earlier, when creating the Fabric layout, so there are many less setters here. Most visual attributes are instead present in the state we are presenting. We have tasks for some of these, that need to be reimplemented, as they do not currently influence the Spannable being measured. That includes e.g. `ReactTextViewManagerCallback`, used for injection, and `dataDetectorType` for linkifying Spannable. Changelog: [Internal] Differential Revision: D73287706 Reviewed By: rshest
1 parent 3b1376f commit 9bb9a28

File tree

2 files changed

+220
-1
lines changed

2 files changed

+220
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.views.text
9+
10+
import android.text.Layout
11+
import android.text.Spannable
12+
import android.view.View
13+
import com.facebook.react.R
14+
import com.facebook.react.internal.SystraceSection
15+
import com.facebook.react.module.annotations.ReactModule
16+
import com.facebook.react.uimanager.BackgroundStyleApplicator
17+
import com.facebook.react.uimanager.BaseViewManager
18+
import com.facebook.react.uimanager.IViewGroupManager
19+
import com.facebook.react.uimanager.LayoutShadowNode
20+
import com.facebook.react.uimanager.LengthPercentage
21+
import com.facebook.react.uimanager.LengthPercentageType
22+
import com.facebook.react.uimanager.ReactStylesDiffMap
23+
import com.facebook.react.uimanager.ReferenceStateWrapper
24+
import com.facebook.react.uimanager.StateWrapper
25+
import com.facebook.react.uimanager.ThemedReactContext
26+
import com.facebook.react.uimanager.ViewProps
27+
import com.facebook.react.uimanager.annotations.ReactProp
28+
import com.facebook.react.uimanager.annotations.ReactPropGroup
29+
import com.facebook.react.uimanager.style.BorderRadiusProp
30+
import com.facebook.react.uimanager.style.BorderStyle
31+
import com.facebook.react.uimanager.style.LogicalEdge
32+
import com.facebook.react.uimanager.style.Overflow
33+
import com.facebook.react.views.text.ReactTextViewAccessibilityDelegate.AccessibilityLinks
34+
import com.facebook.react.views.text.internal.span.ReactClickableSpan
35+
import java.util.HashMap
36+
37+
@ReactModule(name = PreparedLayoutTextViewManager.REACT_CLASS)
38+
internal class PreparedLayoutTextViewManager :
39+
BaseViewManager<PreparedLayoutTextView, LayoutShadowNode>(),
40+
IViewGroupManager<PreparedLayoutTextView> {
41+
42+
init {
43+
setupViewRecycling()
44+
}
45+
46+
override fun prepareToRecycleView(
47+
reactContext: ThemedReactContext,
48+
view: PreparedLayoutTextView
49+
): PreparedLayoutTextView? {
50+
val preparedView = super.prepareToRecycleView(reactContext, view)
51+
preparedView?.recycleView()
52+
return preparedView
53+
}
54+
55+
override fun getName(): String = REACT_CLASS
56+
57+
override fun updateViewAccessibility(view: PreparedLayoutTextView) {
58+
ReactTextViewAccessibilityDelegate.setDelegate(
59+
view, view.isFocusable, view.importantForAccessibility)
60+
}
61+
62+
public override fun createViewInstance(context: ThemedReactContext): PreparedLayoutTextView =
63+
PreparedLayoutTextView(context)
64+
65+
override fun updateExtraData(view: PreparedLayoutTextView, extraData: Any) {
66+
SystraceSection("PreparedLayoutTextViewManager.updateExtraData").use { _ ->
67+
val layout = extraData as Layout
68+
view.layout = layout
69+
70+
// If this text view contains any clickable spans, set a view tag and reset the accessibility
71+
// delegate so that these can be picked up by the accessibility system.
72+
if (layout.text is Spannable) {
73+
val spannableText = layout.text as Spannable
74+
75+
val clickableSpans =
76+
spannableText.getSpans(0, layout.text.length, ReactClickableSpan::class.java)
77+
view.setTag(
78+
R.id.accessibility_links,
79+
if (clickableSpans.size > 0) AccessibilityLinks(clickableSpans, spannableText)
80+
else null)
81+
ReactTextViewAccessibilityDelegate.resetDelegate(
82+
view, view.isFocusable, view.importantForAccessibility)
83+
}
84+
}
85+
}
86+
87+
override fun updateState(
88+
view: PreparedLayoutTextView,
89+
props: ReactStylesDiffMap,
90+
stateWrapper: StateWrapper
91+
): Any? = (stateWrapper as? ReferenceStateWrapper)?.stateDataReference
92+
93+
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any>? {
94+
val baseEventTypeConstants = super.getExportedCustomDirectEventTypeConstants()
95+
val eventTypeConstants = baseEventTypeConstants ?: HashMap()
96+
eventTypeConstants.put("topTextLayout", mapOf("registrationName" to "onTextLayout"))
97+
return eventTypeConstants
98+
}
99+
100+
@ReactProp(name = "overflow")
101+
public fun setOverflow(view: PreparedLayoutTextView, overflow: String?): Unit {
102+
view.overflow = overflow?.let { Overflow.fromString(it) } ?: Overflow.HIDDEN
103+
}
104+
105+
@ReactProp(name = "accessible")
106+
public fun setAccessible(view: PreparedLayoutTextView, accessible: Boolean): Unit {
107+
view.isFocusable = accessible
108+
}
109+
110+
@ReactProp(name = "selectable", defaultBoolean = false)
111+
public fun setSelectable(view: PreparedLayoutTextView, isSelectable: Boolean): Unit {
112+
// T222052152: Implement fine-grained text selection for PreparedLayoutTextView
113+
// view.setTextIsSelectable(isSelectable);
114+
}
115+
116+
@ReactProp(name = "selectionColor", customType = "Color")
117+
public fun setSelectionColor(view: PreparedLayoutTextView, color: Int?): Unit {
118+
if (color == null) {
119+
view.selectionColor = DefaultStyleValuesUtil.getDefaultTextColorHighlight(view.context)
120+
} else {
121+
view.selectionColor = color
122+
}
123+
}
124+
125+
@ReactPropGroup(
126+
names =
127+
[
128+
ViewProps.BORDER_RADIUS,
129+
ViewProps.BORDER_TOP_LEFT_RADIUS,
130+
ViewProps.BORDER_TOP_RIGHT_RADIUS,
131+
ViewProps.BORDER_BOTTOM_RIGHT_RADIUS,
132+
ViewProps.BORDER_BOTTOM_LEFT_RADIUS],
133+
defaultFloat = Float.NaN)
134+
public fun setBorderRadius(view: PreparedLayoutTextView, index: Int, borderRadius: Float): Unit {
135+
val radius =
136+
if (borderRadius.isNaN()) null
137+
else LengthPercentage(borderRadius, LengthPercentageType.POINT)
138+
BackgroundStyleApplicator.setBorderRadius(view, BorderRadiusProp.values()[index], radius)
139+
}
140+
141+
@ReactProp(name = "borderStyle")
142+
public fun setBorderStyle(view: PreparedLayoutTextView, borderStyle: String?): Unit {
143+
val parsedBorderStyle = if (borderStyle == null) null else BorderStyle.fromString(borderStyle)
144+
BackgroundStyleApplicator.setBorderStyle(view, parsedBorderStyle)
145+
}
146+
147+
@ReactPropGroup(
148+
names =
149+
[
150+
ViewProps.BORDER_WIDTH,
151+
ViewProps.BORDER_LEFT_WIDTH,
152+
ViewProps.BORDER_RIGHT_WIDTH,
153+
ViewProps.BORDER_TOP_WIDTH,
154+
ViewProps.BORDER_BOTTOM_WIDTH,
155+
ViewProps.BORDER_START_WIDTH,
156+
ViewProps.BORDER_END_WIDTH],
157+
defaultFloat = Float.NaN)
158+
public fun setBorderWidth(view: PreparedLayoutTextView, index: Int, width: Float): Unit {
159+
BackgroundStyleApplicator.setBorderWidth(view, LogicalEdge.values()[index], width)
160+
}
161+
162+
@ReactPropGroup(
163+
names =
164+
[
165+
ViewProps.BORDER_COLOR,
166+
ViewProps.BORDER_LEFT_COLOR,
167+
ViewProps.BORDER_RIGHT_COLOR,
168+
ViewProps.BORDER_TOP_COLOR,
169+
ViewProps.BORDER_BOTTOM_COLOR,
170+
ViewProps.BORDER_START_COLOR,
171+
ViewProps.BORDER_END_COLOR,
172+
ViewProps.BORDER_BLOCK_COLOR,
173+
ViewProps.BORDER_BLOCK_END_COLOR,
174+
ViewProps.BORDER_BLOCK_START_COLOR,
175+
],
176+
customType = "Color")
177+
public fun setBorderColor(view: PreparedLayoutTextView, index: Int, color: Int?): Unit {
178+
BackgroundStyleApplicator.setBorderColor(view, LogicalEdge.values()[index], color)
179+
}
180+
181+
@ReactProp(name = "disabled", defaultBoolean = false)
182+
public fun setDisabled(view: PreparedLayoutTextView, disabled: Boolean): Unit {
183+
view.setEnabled(!disabled)
184+
}
185+
186+
override fun setPadding(
187+
view: PreparedLayoutTextView,
188+
left: Int,
189+
top: Int,
190+
right: Int,
191+
bottom: Int
192+
): Unit {
193+
view.setPadding(left, top, right, bottom)
194+
}
195+
196+
override fun getShadowNodeClass(): Class<out LayoutShadowNode> {
197+
throw UnsupportedOperationException(
198+
"PreparedLayoutTextViewManager does not use legacy arch shadow nodes")
199+
}
200+
201+
override fun addView(parent: PreparedLayoutTextView, child: View, index: Int) {
202+
parent.addView(child, index)
203+
}
204+
205+
override fun getChildAt(parent: PreparedLayoutTextView, index: Int): View? =
206+
parent.getChildAt(index)
207+
208+
override fun removeViewAt(parent: PreparedLayoutTextView, index: Int) {
209+
parent.removeViewAt(index)
210+
}
211+
212+
override fun getChildCount(parent: PreparedLayoutTextView): Int = parent.childCount
213+
214+
override fun needsCustomLayoutForChildren(): Boolean = false
215+
216+
public companion object {
217+
public const val REACT_CLASS: String = "RCTText"
218+
}
219+
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewAccessibilityDelegate.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ internal class ReactTextViewAccessibilityDelegate : ReactAccessibilityDelegate {
270270
return null
271271
}
272272

273-
public class AccessibilityLinks(spans: Array<ClickableSpan?>, text: Spannable) {
273+
public class AccessibilityLinks(spans: Array<out ClickableSpan>, text: Spannable) {
274274
private val links: List<AccessibleLink>
275275

276276
init {

0 commit comments

Comments
 (0)