react-native-advanced-text 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,94 +1,263 @@
1
+ // File: AdvancedTextView.kt
1
2
  package com.advancedtext
2
3
 
4
+ import android.content.Context
3
5
  import android.graphics.Color
4
- import android.view.ViewGroup
5
- import com.facebook.react.bridge.ReadableArray
6
- import com.facebook.react.module.annotations.ReactModule
7
- import com.facebook.react.uimanager.SimpleViewManager
8
- import com.facebook.react.uimanager.ThemedReactContext
9
- import com.facebook.react.uimanager.ViewManagerDelegate
10
- import com.facebook.react.uimanager.annotations.ReactProp
11
- import com.facebook.react.viewmanagers.AdvancedTextViewManagerInterface
12
- import com.facebook.react.viewmanagers.AdvancedTextViewManagerDelegate
13
-
14
- @ReactModule(name = AdvancedTextViewManager.NAME)
15
- class AdvancedTextViewManager : SimpleViewManager<AdvancedTextView>(),
16
- AdvancedTextViewManagerInterface<AdvancedTextView> {
17
- private val mDelegate: ViewManagerDelegate<AdvancedTextView>
18
-
19
- init {
20
- mDelegate = AdvancedTextViewManagerDelegate(this)
6
+ import android.text.SpannableString
7
+ import android.text.Spanned
8
+ import android.text.TextPaint
9
+ import android.text.method.LinkMovementMethod
10
+ import android.text.style.ClickableSpan
11
+ import android.text.style.BackgroundColorSpan
12
+ import android.util.AttributeSet
13
+ import android.util.Log
14
+ import android.view.ContextMenu
15
+ import android.view.MenuItem
16
+ import android.view.View
17
+ import android.widget.TextView
18
+ import com.facebook.react.bridge.Arguments
19
+ import com.facebook.react.bridge.ReactContext
20
+ import com.facebook.react.uimanager.events.RCTEventEmitter
21
+ import android.text.Selection
22
+
23
+ class AdvancedTextView : TextView, View.OnCreateContextMenuListener {
24
+
25
+ private val TAG = "AdvancedTextView"
26
+
27
+ private var highlightedWords: List<HighlightedWord> = emptyList()
28
+ private var menuOptions: List<String> = emptyList()
29
+ private var indicatorWordIndex: Int = -1
30
+ private var lastSelectedText: String = ""
31
+ private var isSelectionEnabled: Boolean = true
32
+
33
+ constructor(context: Context?) : super(context) { init() }
34
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { init() }
35
+ constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { init() }
36
+
37
+ private fun init() {
38
+ Log.d(TAG, "AdvancedTextView initialized")
39
+
40
+ // Set default text appearance
41
+ setTextColor(Color.BLACK)
42
+ textSize = 16f
43
+ setPadding(16, 16, 16, 16)
44
+
45
+ movementMethod = LinkMovementMethod.getInstance()
46
+ setTextIsSelectable(true)
47
+ setOnCreateContextMenuListener(this)
48
+
49
+ // Ensure minimum height for visibility during debugging
50
+ minHeight = 100
21
51
  }
22
52
 
23
- override fun getDelegate(): ViewManagerDelegate<AdvancedTextView>? {
24
- return mDelegate
53
+ fun setAdvancedText(text: String) {
54
+ Log.d(TAG, "setAdvancedText: $text (length=${text.length})")
55
+ this.text = text
56
+ updateTextWithHighlights()
57
+ // Force layout update
58
+ requestLayout()
59
+ invalidate()
25
60
  }
26
61
 
27
- override fun getName(): String {
28
- return NAME
62
+ fun setMenuOptions(menuOptions: List<String>) {
63
+ Log.d(TAG, "setMenuOptions received from RN: $menuOptions")
64
+ this.menuOptions = menuOptions
29
65
  }
30
66
 
31
- public override fun createViewInstance(context: ThemedReactContext): AdvancedTextView {
32
- val view = AdvancedTextView(context)
33
- // Set default layout params to ensure the view is visible
34
- view.layoutParams = ViewGroup.LayoutParams(
35
- ViewGroup.LayoutParams.MATCH_PARENT,
36
- ViewGroup.LayoutParams.WRAP_CONTENT
37
- )
38
- return view
67
+ fun setHighlightedWords(highlightedWords: List<HighlightedWord>) {
68
+ Log.d(TAG, "setHighlightedWords received from RN: $highlightedWords")
69
+ this.highlightedWords = highlightedWords
70
+ updateTextWithHighlights()
39
71
  }
40
72
 
41
- @ReactProp(name = "text")
42
- override fun setText(view: AdvancedTextView?, text: String?) {
43
- view?.setAdvancedText(text ?: "")
73
+ fun setIndicatorWordIndex(index: Int) {
74
+ Log.d(TAG, "setIndicatorWordIndex received: $index")
75
+ this.indicatorWordIndex = index
76
+ updateTextWithHighlights()
44
77
  }
45
78
 
46
- @ReactProp(name = "highlightedWords")
47
- override fun setHighlightedWords(view: AdvancedTextView?, highlightedWords: ReadableArray?) {
48
- val words = mutableListOf<HighlightedWord>()
49
- highlightedWords?.let {
50
- for (i in 0 until it.size()) {
51
- val map = it.getMap(i)
52
- map?.let { wordMap ->
53
- words.add(
54
- HighlightedWord(
55
- index = wordMap.getInt("index"),
56
- highlightColor = wordMap.getString("highlightColor") ?: "#FFFF00"
79
+ private fun updateTextWithHighlights() {
80
+ val textValue = this.text?.toString() ?: ""
81
+ Log.d(TAG, "updateTextWithHighlights called")
82
+ Log.d(TAG, "Current text: $textValue")
83
+ Log.d(TAG, "Highlighted words: $highlightedWords")
84
+ Log.d(TAG, "Indicator index: $indicatorWordIndex")
85
+
86
+ if (textValue.isEmpty()) {
87
+ Log.d(TAG, "No text available, skipping")
88
+ return
89
+ }
90
+
91
+ val spannableString = SpannableString(textValue)
92
+ val words = textValue.split("\\s+".toRegex())
93
+
94
+ var currentIndex = 0
95
+ words.forEachIndexed { wordIndex, word ->
96
+
97
+ if (word.isNotEmpty()) {
98
+ val wordStart = textValue.indexOf(word, currentIndex)
99
+ if (wordStart >= 0) {
100
+ val wordEnd = wordStart + word.length
101
+
102
+ highlightedWords.find { it.index == wordIndex }?.let { highlightedWord ->
103
+ val color = try {
104
+ Color.parseColor(highlightedWord.highlightColor)
105
+ } catch (e: IllegalArgumentException) {
106
+ Log.e(TAG, "Invalid color: ${highlightedWord.highlightColor}, using yellow")
107
+ Color.YELLOW
108
+ }
109
+ Log.d(TAG, "Applying highlight to word '$word' at index $wordIndex with color ${highlightedWord.highlightColor}")
110
+
111
+ spannableString.setSpan(
112
+ BackgroundColorSpan(color),
113
+ wordStart,
114
+ wordEnd,
115
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
116
+ )
117
+ }
118
+
119
+ if (wordIndex == indicatorWordIndex) {
120
+ Log.d(TAG, "Applying indicator span to word '$word' at index $wordIndex")
121
+
122
+ spannableString.setSpan(
123
+ IndicatorSpan(),
124
+ wordStart,
125
+ wordEnd,
126
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
57
127
  )
128
+ }
129
+
130
+ // clickable span
131
+ spannableString.setSpan(
132
+ WordClickableSpan(wordIndex, word),
133
+ wordStart,
134
+ wordEnd,
135
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
58
136
  )
137
+
138
+ currentIndex = wordEnd
59
139
  }
60
140
  }
61
141
  }
62
- view?.setHighlightedWords(words)
142
+
143
+ setText(spannableString, BufferType.SPANNABLE)
144
+ Log.d(TAG, "Text updated with spans")
63
145
  }
64
146
 
65
- @ReactProp(name = "menuOptions")
66
- override fun setMenuOptions(view: AdvancedTextView?, menuOptions: ReadableArray?) {
67
- val options = mutableListOf<String>()
68
- menuOptions?.let {
69
- for (i in 0 until it.size()) {
70
- it.getString(i)?.let { option ->
71
- options.add(option)
147
+ override fun onCreateContextMenu(menu: ContextMenu?, v: View?, menuInfo: ContextMenu.ContextMenuInfo?) {
148
+ val selectionStart = selectionStart
149
+ val selectionEnd = selectionEnd
150
+
151
+ Log.d(TAG, "onCreateContextMenu triggered. selectionStart=$selectionStart selectionEnd=$selectionEnd")
152
+
153
+ if (selectionStart >= 0 && selectionEnd >= 0 && selectionStart != selectionEnd) {
154
+ lastSelectedText = text.subSequence(selectionStart, selectionEnd).toString()
155
+
156
+ Log.d(TAG, "User selected text: '$lastSelectedText'")
157
+ Log.d(TAG, "Menu options available: $menuOptions")
158
+
159
+ menu?.clear()
160
+
161
+ menuOptions.forEachIndexed { index, option ->
162
+ menu?.add(0, index, index, option)?.setOnMenuItemClickListener {
163
+ Log.d(TAG, "Menu item clicked: $option")
164
+ onMenuItemClick(it, lastSelectedText)
165
+ true
72
166
  }
73
167
  }
168
+
169
+ sendSelectionEvent(lastSelectedText, "selection")
74
170
  }
75
- view?.setMenuOptions(options)
76
171
  }
77
172
 
78
- @ReactProp(name = "indicatorWordIndex")
79
- override fun setIndicatorWordIndex(view: AdvancedTextView?, index: Int) {
80
- view?.setIndicatorWordIndex(index)
173
+ private fun onMenuItemClick(item: MenuItem, selectedText: String): Boolean {
174
+ val menuItemText = menuOptions[item.itemId]
175
+ Log.d(TAG, "onMenuItemClick: menuOption='$menuItemText', selectedText='$selectedText'")
176
+ sendSelectionEvent(selectedText, menuItemText)
177
+ return true
81
178
  }
82
179
 
83
- // Add this method to register custom events
84
- override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> {
85
- return mapOf(
86
- "onWordPress" to mapOf("registrationName" to "onWordPress"),
87
- "onSelection" to mapOf("registrationName" to "onSelection")
88
- )
180
+ private fun sendSelectionEvent(selectedText: String, eventType: String) {
181
+ Log.d(TAG, "sendSelectionEvent -> eventType='$eventType' selectedText='$selectedText'")
182
+
183
+ try {
184
+ val reactContext = context as? ReactContext ?: return
185
+ val event = Arguments.createMap().apply {
186
+ putString("selectedText", selectedText)
187
+ putString("event", eventType)
188
+ }
189
+
190
+ reactContext.getJSModule(RCTEventEmitter::class.java)
191
+ .receiveEvent(id, "onSelection", event)
192
+ } catch (e: Exception) {
193
+ Log.e(TAG, "Error sending selection event", e)
194
+ }
195
+ }
196
+
197
+ private inner class WordClickableSpan(
198
+ private val wordIndex: Int,
199
+ private val word: String
200
+ ) : ClickableSpan() {
201
+
202
+ override fun onClick(widget: View) {
203
+ Log.d(TAG, "Word clicked: '$word' (index=$wordIndex)")
204
+ sendWordPressEvent(word, wordIndex)
205
+ }
206
+
207
+ override fun updateDrawState(ds: TextPaint) {
208
+ super.updateDrawState(ds)
209
+ ds.color = currentTextColor
210
+ ds.isUnderlineText = false
211
+ }
212
+ }
213
+
214
+ private inner class IndicatorSpan : ClickableSpan() {
215
+ override fun onClick(widget: View) {
216
+ Log.d(TAG, "IndicatorSpan clicked (shouldn't trigger action)")
217
+ }
218
+
219
+ override fun updateDrawState(ds: TextPaint) {
220
+ ds.color = Color.RED
221
+ ds.isFakeBoldText = true
222
+ ds.isUnderlineText = false
223
+ }
89
224
  }
90
225
 
91
- companion object {
92
- const val NAME = "AdvancedTextView"
226
+ private fun sendWordPressEvent(word: String, index: Int) {
227
+ Log.d(TAG, "sendWordPressEvent -> word='$word', index=$index")
228
+
229
+ try {
230
+ val reactContext = context as? ReactContext ?: return
231
+ val event = Arguments.createMap().apply {
232
+ putString("word", word)
233
+ putInt("index", index)
234
+ }
235
+
236
+ reactContext.getJSModule(RCTEventEmitter::class.java)
237
+ .receiveEvent(id, "onWordPress", event)
238
+ } catch (e: Exception) {
239
+ Log.e(TAG, "Error sending word press event", e)
240
+ }
241
+ }
242
+
243
+ fun clearSelection() {
244
+ Log.d(TAG, "clearSelection called")
245
+ val spannable = this.text as? android.text.Spannable ?: return
246
+ Selection.removeSelection(spannable)
247
+ }
248
+
249
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
250
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
251
+ Log.d(TAG, "onMeasure: width=${measuredWidth}, height=${measuredHeight}")
252
+ }
253
+
254
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
255
+ super.onLayout(changed, left, top, right, bottom)
256
+ Log.d(TAG, "onLayout: changed=$changed, bounds=[$left,$top,$right,$bottom]")
93
257
  }
94
258
  }
259
+
260
+ data class HighlightedWord(
261
+ val index: Int,
262
+ val highlightColor: String
263
+ )
@@ -1,6 +1,7 @@
1
+ // File: AdvancedTextViewManager.kt
2
+ // This should be the ONLY content in this file
1
3
  package com.advancedtext
2
4
 
3
- import android.graphics.Color
4
5
  import android.view.ViewGroup
5
6
  import com.facebook.react.bridge.ReadableArray
6
7
  import com.facebook.react.module.annotations.ReactModule
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-advanced-text",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": " Advanced text component for React Native with custom select options.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",