Skip to content

Commit 48ec631

Browse files
committed
feat: add Android support and custom menu implementation
- Add Android native implementation for NitroText - Implement custom menu support with MenuItem type - Add menus prop to NitroText component - Update Android build configuration - Add MenuItem bridge code for Android - Update component descriptors and specs for menu support
1 parent 46c6275 commit 48ec631

26 files changed

Lines changed: 572 additions & 263 deletions

android/build.gradle

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,6 @@ dependencies {
137137
// Add a dependency on NitroModules
138138
implementation project(":react-native-nitro-modules")
139139

140-
implementation "org.jsoup:jsoup:1.18.1"
141-
142140
}
143141

144142
if (isNewArchitectureEnabled()) {

android/src/main/java/com/nitrotext/HybridNitroText.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,10 @@ class HybridNitroText(val context: ThemedReactContext) : HybridNitroTextSpec(),
2222
impl.setFragments(value)
2323
}
2424

25-
override var renderer: NitroRenderer?
25+
override var renderer: Renderer?
2626
get() = null
2727
set(value) {
28-
impl.setRenderer(value)
29-
}
30-
31-
override var richTextStyleRules: Array<RichTextStyleRule>?
32-
get() = null
33-
set(value) {
34-
impl.setRichTextStyleRules(value)
28+
// HTML parsing is now done in JS/TS layer, renderer is ignored
3529
}
3630

3731
override var selectable: Boolean?
@@ -86,6 +80,12 @@ class HybridNitroText(val context: ThemedReactContext) : HybridNitroTextSpec(),
8680
get() = null
8781
set(value) { value }
8882

83+
override var menus: Array<MenuItem>?
84+
get() = null
85+
set(value) {
86+
view.customMenus = value
87+
}
88+
8989
override var onTextLayout: ((TextLayoutEvent) -> Unit)?
9090
get() = onTextLayoutCallback
9191
set(value) {

android/src/main/java/com/nitrotext/NitroTextImpl.kt

Lines changed: 35 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,26 @@ import android.text.SpannableStringBuilder
77
import android.text.TextUtils
88
import android.text.style.AbsoluteSizeSpan
99
import android.text.style.BackgroundColorSpan
10-
import android.text.style.ClickableSpan
1110
import android.text.style.ForegroundColorSpan
1211
import android.text.style.StrikethroughSpan
1312
import android.text.style.StyleSpan
1413
import android.text.style.TypefaceSpan
1514
import android.text.style.UnderlineSpan
1615
import android.view.Gravity
17-
import android.text.method.ArrowKeyMovementMethod
18-
import android.text.method.LinkMovementMethod
1916
import androidx.appcompat.widget.AppCompatTextView
2017
import com.facebook.react.uimanager.PixelUtil
2118
import com.margelo.nitro.nitrotext.*
2219
import androidx.core.graphics.toColorInt
23-
import com.nitrotext.renderers.NitroHtmlRenderer
2420
import com.nitrotext.spans.NitroLineHeightSpan
21+
import com.nitrotext.spans.UrlSpanNoUnderline
22+
import android.text.method.LinkMovementMethod
23+
import android.text.method.ArrowKeyMovementMethod
24+
import com.nitrotext.HtmlLinkMovementMethod
2525

2626
class NitroTextImpl(private val view: AppCompatTextView) {
2727
// Stored props
2828
private var fragments: Array<Fragment>? = null
2929
private var text: String? = null
30-
private var renderer: NitroRenderer = NitroRenderer.PLAINTEXT
31-
private var richTextStyleRules: Array<RichTextStyleRule>? = null
3230

3331
private var selectable: Boolean? = null
3432
private var selectionColor: String? = null
@@ -63,25 +61,23 @@ class NitroTextImpl(private val view: AppCompatTextView) {
6361
applyAlignment()
6462

6563
val frags = fragments
66-
val content = text
67-
68-
if (renderer == NitroRenderer.HTML && !content.isNullOrEmpty()) {
69-
applyHtml(content)
70-
} else if (!frags.isNullOrEmpty()) {
64+
65+
if (!frags.isNullOrEmpty()) {
7166
applyFragments(frags)
7267
} else {
7368
applySimpleText()
7469
}
7570

7671
applyLetterSpacing()
72+
73+
// Request layout update so container can resize based on content
74+
// This will trigger onMeasure which will calculate the correct height
75+
view.requestLayout()
7776
}
7877

7978
// Setters
8079
fun setFragments(value: Array<Fragment>?) { fragments = value }
8180
fun setText(value: String?) { text = value }
82-
fun setRenderer(value: NitroRenderer?) { renderer = value ?: NitroRenderer.PLAINTEXT }
83-
fun setRichTextStyleRules(value: Array<RichTextStyleRule>?) { richTextStyleRules = value }
84-
8581
fun setSelectable(value: Boolean?) { selectable = value }
8682
fun setSelectionColor(value: String?) { selectionColor = value }
8783
fun setNumberOfLines(value: Double?) { numberOfLines = value }
@@ -190,6 +186,7 @@ class NitroTextImpl(private val view: AppCompatTextView) {
190186
private fun applyFragments(fragments: Array<Fragment>) {
191187
val builder = SpannableStringBuilder()
192188
val containerLineHeightPx = resolveLineHeight(lineHeight)
189+
var hasLinks = false
193190
var start: Int
194191
for (frag in fragments) {
195192
val fragText = transformText(frag.text, frag.textTransform) ?: ""
@@ -222,12 +219,36 @@ class NitroTextImpl(private val view: AppCompatTextView) {
222219
frag.lineHeight?.let { lh ->
223220
resolveLineHeight(lh)?.let { applyLineHeightSpan(builder, start, end, it) }
224221
}
222+
// Links
223+
frag.linkUrl?.let { url ->
224+
if (url.isNotEmpty()) {
225+
builder.setSpan(UrlSpanNoUnderline(url), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
226+
hasLinks = true
227+
// Apply default link color if fragment doesn't have explicit color
228+
if (frag.fontColor == null) {
229+
// Use system link color (typically blue)
230+
val linkColor = android.graphics.Color.parseColor("#007AFF") // iOS-like blue
231+
builder.setSpan(ForegroundColorSpan(linkColor), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
232+
}
233+
}
234+
}
225235
}
226236
containerLineHeightPx?.let { applyLineHeightSpan(builder, 0, builder.length, it) }
227237
view.text = builder
228238

229239
// Apply default text color for runs without explicit color
230240
view.setTextColor(resolvedFontColor())
241+
242+
// Set up movement method for links
243+
val isSelectable = selectable == true
244+
view.linksClickable = hasLinks
245+
view.movementMethod = when {
246+
hasLinks && isSelectable -> LinkMovementMethod.getInstance()
247+
hasLinks -> HtmlLinkMovementMethod
248+
isSelectable -> ArrowKeyMovementMethod.getInstance()
249+
else -> null
250+
}
251+
view.setTextIsSelectable(isSelectable)
231252
}
232253

233254
private fun applyDecorationSpans(builder: SpannableStringBuilder, start: Int, end: Int, line: TextDecorationLine?) {
@@ -278,50 +299,6 @@ class NitroTextImpl(private val view: AppCompatTextView) {
278299
return parsed ?: Color.BLACK
279300
}
280301

281-
private fun baseRichTextStyle(): RichTextStyle {
282-
return RichTextStyle(
283-
fontColor = fontColor,
284-
fragmentBackgroundColor = fragmentBackgroundColor,
285-
fontSize = fontSize,
286-
fontWeight = fontWeight,
287-
fontStyle = fontStyle,
288-
fontFamily = fontFamily,
289-
lineHeight = lineHeight,
290-
letterSpacing = letterSpacing,
291-
textAlign = textAlign,
292-
textTransform = textTransform,
293-
textDecorationLine = textDecorationLine,
294-
textDecorationColor = textDecorationColor,
295-
textDecorationStyle = textDecorationStyle,
296-
marginTop = null,
297-
marginBottom = null,
298-
marginLeft = null,
299-
marginRight = null,
300-
)
301-
}
302-
303-
private fun applyHtml(html: String) {
304-
val renderer = NitroHtmlRenderer(
305-
context = view.context,
306-
defaultTextSizePx = view.textSize,
307-
allowFontScaling = allowFontScaling,
308-
maxFontSizeMultiplier = maxFontSizeMultiplier,
309-
)
310-
val spannable = renderer.render(html, baseRichTextStyle(), richTextStyleRules)
311-
trimTrailingNewlines(spannable)
312-
view.text = spannable
313-
val hasLinks = spannable.getSpans(0, spannable.length, ClickableSpan::class.java).isNotEmpty()
314-
view.linksClickable = hasLinks
315-
val isSelectable = selectable == true
316-
view.movementMethod = when {
317-
hasLinks && isSelectable -> LinkMovementMethod.getInstance()
318-
hasLinks -> HtmlLinkMovementMethod
319-
isSelectable -> ArrowKeyMovementMethod.getInstance()
320-
else -> null
321-
}
322-
view.setTextIsSelectable(isSelectable)
323-
view.setTextColor(resolvedFontColor())
324-
}
325302

326303
private fun trimTrailingNewlines(builder: SpannableStringBuilder) {
327304
var length = builder.length

0 commit comments

Comments
 (0)