react-native-advanced-text 0.1.27 → 0.1.28
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,637 +1,318 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@interface AdvancedTextView () <RCTAdvancedTextViewViewProtocol, UIGestureRecognizerDelegate, UITextViewDelegate>
|
|
63
|
-
|
|
64
|
-
@property (nonatomic, strong) CustomTextView *textView;
|
|
65
|
-
|
|
66
|
-
@end
|
|
67
|
-
|
|
68
|
-
@implementation AdvancedTextView
|
|
69
|
-
|
|
70
|
-
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
71
|
-
{
|
|
72
|
-
NSLog(@"[AdvancedTextView] componentDescriptorProvider called");
|
|
73
|
-
return concreteComponentDescriptorProvider<AdvancedTextViewComponentDescriptor>();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
- (instancetype)initWithFrame:(CGRect)frame
|
|
77
|
-
{
|
|
78
|
-
NSLog(@"[AdvancedTextView] initWithFrame called");
|
|
79
|
-
if (self = [super initWithFrame:frame]) {
|
|
80
|
-
@try {
|
|
81
|
-
static const auto defaultProps = std::make_shared<const AdvancedTextViewProps>();
|
|
82
|
-
_props = defaultProps;
|
|
83
|
-
|
|
84
|
-
_wordRanges = [NSMutableArray array];
|
|
85
|
-
_highlightColors = [NSMutableDictionary dictionary];
|
|
86
|
-
_indicatorWordIndex = -1;
|
|
87
|
-
_fontSize = 16.0;
|
|
88
|
-
_fontWeight = @"normal";
|
|
89
|
-
_textColor = [UIColor labelColor];
|
|
90
|
-
_textAlign = @"left";
|
|
91
|
-
_fontFamily = @"System";
|
|
92
|
-
|
|
93
|
-
[self setupTextView];
|
|
94
|
-
[self setupGestureRecognizers];
|
|
95
|
-
|
|
96
|
-
NSLog(@"[AdvancedTextView] Initialization successful");
|
|
97
|
-
} @catch (NSException *exception) {
|
|
98
|
-
NSLog(@"[AdvancedTextView] Exception in init: %@", exception);
|
|
99
|
-
@throw;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return self;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
- (void)setupTextView
|
|
107
|
-
{
|
|
108
|
-
NSLog(@"[AdvancedTextView] setupTextView called");
|
|
109
|
-
@try {
|
|
110
|
-
_textView = [[CustomTextView alloc] initWithFrame:self.bounds];
|
|
111
|
-
_textView.parentView = self;
|
|
112
|
-
_textView.editable = NO;
|
|
113
|
-
_textView.selectable = YES;
|
|
114
|
-
_textView.scrollEnabled = YES;
|
|
115
|
-
_textView.backgroundColor = [UIColor clearColor];
|
|
116
|
-
_textView.textContainerInset = UIEdgeInsetsMake(8, 8, 8, 8);
|
|
117
|
-
_textView.font = [UIFont systemFontOfSize:16];
|
|
118
|
-
_textView.textColor = [UIColor labelColor];
|
|
119
|
-
_textView.delegate = self;
|
|
120
|
-
|
|
121
|
-
self.contentView = _textView;
|
|
122
|
-
NSLog(@"[AdvancedTextView] TextView setup successful");
|
|
123
|
-
} @catch (NSException *exception) {
|
|
124
|
-
NSLog(@"[AdvancedTextView] Exception in setupTextView: %@", exception);
|
|
125
|
-
@throw;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
- (void)setupGestureRecognizers
|
|
130
|
-
{
|
|
131
|
-
NSLog(@"[AdvancedTextView] setupGestureRecognizers called");
|
|
132
|
-
@try {
|
|
133
|
-
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]
|
|
134
|
-
initWithTarget:self
|
|
135
|
-
action:@selector(handleTap:)];
|
|
136
|
-
tapGesture.delegate = self;
|
|
137
|
-
[_textView addGestureRecognizer:tapGesture];
|
|
138
|
-
|
|
139
|
-
NSLog(@"[AdvancedTextView] Gesture recognizers setup successful");
|
|
140
|
-
} @catch (NSException *exception) {
|
|
141
|
-
NSLog(@"[AdvancedTextView] Exception in setupGestureRecognizers: %@", exception);
|
|
142
|
-
@throw;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
|
|
147
|
-
{
|
|
148
|
-
NSLog(@"[AdvancedTextView] updateProps called");
|
|
149
|
-
@try {
|
|
150
|
-
const auto &oldViewProps = *std::static_pointer_cast<AdvancedTextViewProps const>(_props);
|
|
151
|
-
const auto &newViewProps = *std::static_pointer_cast<AdvancedTextViewProps const>(props);
|
|
152
|
-
|
|
153
|
-
BOOL textChanged = NO;
|
|
154
|
-
BOOL highlightsChanged = NO;
|
|
155
|
-
BOOL menuChanged = NO;
|
|
156
|
-
BOOL indicatorChanged = NO;
|
|
157
|
-
BOOL styleChanged = NO;
|
|
158
|
-
|
|
159
|
-
if (oldViewProps.fontSize != newViewProps.fontSize && newViewProps.fontSize) {
|
|
160
|
-
NSLog(@"[AdvancedTextView] Updating fontSize to: %f", newViewProps.fontSize);
|
|
161
|
-
_fontSize = static_cast<CGFloat>(newViewProps.fontSize);
|
|
162
|
-
styleChanged = YES;
|
|
163
|
-
}
|
|
1
|
+
package com.advancedtext
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.text.SpannableString
|
|
6
|
+
import android.text.Spanned
|
|
7
|
+
import android.text.TextPaint
|
|
8
|
+
import android.text.method.LinkMovementMethod
|
|
9
|
+
import android.text.style.ClickableSpan
|
|
10
|
+
import android.text.style.BackgroundColorSpan
|
|
11
|
+
import android.text.style.ForegroundColorSpan
|
|
12
|
+
import android.util.AttributeSet
|
|
13
|
+
import android.util.Log
|
|
14
|
+
import android.view.ActionMode
|
|
15
|
+
import android.view.Menu
|
|
16
|
+
import android.view.MenuItem
|
|
17
|
+
import android.view.View
|
|
18
|
+
import android.widget.TextView
|
|
19
|
+
import com.facebook.react.bridge.Arguments
|
|
20
|
+
import com.facebook.react.bridge.ReactContext
|
|
21
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
22
|
+
import android.text.Selection
|
|
23
|
+
import android.graphics.Typeface
|
|
24
|
+
|
|
25
|
+
class AdvancedTextView : TextView {
|
|
26
|
+
|
|
27
|
+
private val TAG = "AdvancedTextView"
|
|
28
|
+
|
|
29
|
+
private var highlightedWords: List<HighlightedWord> = emptyList()
|
|
30
|
+
private var menuOptions: List<String> = emptyList()
|
|
31
|
+
private var indicatorWordIndex: Int = -1
|
|
32
|
+
private var lastSelectedText: String = ""
|
|
33
|
+
private var customActionMode: ActionMode? = null
|
|
34
|
+
private var currentText: String = ""
|
|
35
|
+
private var textColor: String = "#000000"
|
|
36
|
+
private var fontSize: Float = 16f
|
|
37
|
+
private var fontWeight: String = "normal"
|
|
38
|
+
private var textAlign: String = "left"
|
|
39
|
+
private var fontFamily: String = "sans-serif"
|
|
40
|
+
|
|
41
|
+
private var wordPositions: List<WordPosition> = emptyList()
|
|
42
|
+
|
|
43
|
+
constructor(context: Context?) : super(context) { init() }
|
|
44
|
+
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { init() }
|
|
45
|
+
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { init() }
|
|
46
|
+
|
|
47
|
+
private fun init() {
|
|
48
|
+
Log.d(TAG, "AdvancedTextView initialized")
|
|
49
|
+
|
|
50
|
+
textSize = 16f
|
|
51
|
+
setPadding(16, 16, 16, 16)
|
|
52
|
+
movementMethod = LinkMovementMethod.getInstance()
|
|
53
|
+
setTextIsSelectable(true)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
customSelectionActionModeCallback = object : ActionMode.Callback {
|
|
57
|
+
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
|
58
|
+
customActionMode = mode
|
|
59
|
+
return true
|
|
60
|
+
}
|
|
164
61
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
_textAlign = [NSString stringWithUTF8String:newViewProps.textAlign.c_str()];
|
|
168
|
-
styleChanged = YES;
|
|
169
|
-
}
|
|
62
|
+
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
|
63
|
+
menu?.clear()
|
|
170
64
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
_fontWeight = [NSString stringWithUTF8String:newViewProps.fontWeight.c_str()];
|
|
174
|
-
styleChanged = YES;
|
|
175
|
-
}
|
|
65
|
+
val selectionStart = selectionStart
|
|
66
|
+
val selectionEnd = selectionEnd
|
|
176
67
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
_fontFamily = [NSString stringWithUTF8String:newViewProps.fontFamily.c_str()];
|
|
180
|
-
styleChanged = YES;
|
|
181
|
-
}
|
|
68
|
+
if (selectionStart >= 0 && selectionEnd >= 0 && selectionStart != selectionEnd) {
|
|
69
|
+
lastSelectedText = text.subSequence(selectionStart, selectionEnd).toString()
|
|
182
70
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
_textColor = [self hexStringToColor:colorStr];
|
|
187
|
-
styleChanged = YES;
|
|
188
|
-
}
|
|
71
|
+
menuOptions.forEachIndexed { index, option ->
|
|
72
|
+
menu?.add(0, index, index, option)
|
|
73
|
+
}
|
|
189
74
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
NSLog(@"[AdvancedTextView] Text changed");
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (oldViewProps.highlightedWords.size() != newViewProps.highlightedWords.size()) {
|
|
196
|
-
highlightsChanged = YES;
|
|
197
|
-
} else {
|
|
198
|
-
for (size_t i = 0; i < oldViewProps.highlightedWords.size(); i++) {
|
|
199
|
-
const auto &oldHW = oldViewProps.highlightedWords[i];
|
|
200
|
-
const auto &newHW = newViewProps.highlightedWords[i];
|
|
201
|
-
if (oldHW.index != newHW.index || oldHW.highlightColor != newHW.highlightColor) {
|
|
202
|
-
highlightsChanged = YES;
|
|
203
|
-
break;
|
|
75
|
+
sendSelectionEvent(lastSelectedText, "selection")
|
|
76
|
+
return true
|
|
204
77
|
}
|
|
78
|
+
return false
|
|
205
79
|
}
|
|
206
|
-
}
|
|
207
80
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
break;
|
|
81
|
+
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
|
82
|
+
item?.let {
|
|
83
|
+
val menuItemText = it.title.toString()
|
|
84
|
+
sendSelectionEvent(lastSelectedText, menuItemText)
|
|
85
|
+
mode?.finish()
|
|
86
|
+
return true
|
|
215
87
|
}
|
|
88
|
+
return false
|
|
216
89
|
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (oldViewProps.indicatorWordIndex != newViewProps.indicatorWordIndex) {
|
|
220
|
-
indicatorChanged = YES;
|
|
221
|
-
}
|
|
222
90
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
91
|
+
override fun onDestroyActionMode(mode: ActionMode?) {
|
|
92
|
+
customActionMode = null
|
|
93
|
+
}
|
|
226
94
|
}
|
|
95
|
+
}
|
|
227
96
|
|
|
228
|
-
if (highlightsChanged) {
|
|
229
|
-
[self updateHighlightedWords:newViewProps.highlightedWords];
|
|
230
|
-
}
|
|
231
97
|
|
|
232
|
-
if (menuChanged) {
|
|
233
|
-
[self updateMenuOptions:newViewProps.menuOptions];
|
|
234
|
-
}
|
|
235
98
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
99
|
+
fun setAdvancedText(text: String) {
|
|
100
|
+
if (currentText == text) {
|
|
101
|
+
Log.d(TAG, "Text unchanged, skipping update")
|
|
102
|
+
return
|
|
239
103
|
}
|
|
240
104
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
[super updateProps:props oldProps:oldProps];
|
|
247
|
-
NSLog(@"[AdvancedTextView] updateProps completed successfully");
|
|
248
|
-
} @catch (NSException *exception) {
|
|
249
|
-
NSLog(@"[AdvancedTextView] Exception in updateProps: %@", exception.reason);
|
|
250
|
-
@throw;
|
|
105
|
+
Log.d(TAG, "setAdvancedText: length=${text.length}")
|
|
106
|
+
currentText = text
|
|
107
|
+
calculateWordPositions(text)
|
|
108
|
+
updateTextWithHighlights()
|
|
251
109
|
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
- (void)updateTextContent:(NSString *)text
|
|
255
|
-
{
|
|
256
|
-
NSLog(@"[AdvancedTextView] updateTextContent called with text length: %lu",
|
|
257
|
-
(unsigned long)text.length);
|
|
258
|
-
@try {
|
|
259
|
-
if (!text) {
|
|
260
|
-
NSLog(@"[AdvancedTextView] Text is nil, skipping update");
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
_textView.text = text;
|
|
265
|
-
|
|
266
|
-
[_wordRanges removeAllObjects];
|
|
267
|
-
|
|
268
|
-
NSRange searchRange = NSMakeRange(0, text.length);
|
|
269
|
-
NSCharacterSet *whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
|
|
270
|
-
|
|
271
|
-
NSInteger wordIndex = 0;
|
|
272
|
-
while (searchRange.location < text.length) {
|
|
273
|
-
while (searchRange.location < text.length &&
|
|
274
|
-
[whitespaceSet characterIsMember:[text characterAtIndex:searchRange.location]]) {
|
|
275
|
-
searchRange.location++;
|
|
276
|
-
searchRange.length = text.length - searchRange.location;
|
|
277
|
-
}
|
|
278
110
|
|
|
279
|
-
|
|
111
|
+
fun setAdvancedTextColor(colorInt: Int) {
|
|
112
|
+
textColor = String.format("#%06X", 0xFFFFFF and colorInt)
|
|
113
|
+
updateTextWithHighlights()
|
|
114
|
+
}
|
|
280
115
|
|
|
281
|
-
NSUInteger wordStart = searchRange.location;
|
|
282
|
-
while (searchRange.location < text.length &&
|
|
283
|
-
![whitespaceSet characterIsMember:[text characterAtIndex:searchRange.location]]) {
|
|
284
|
-
searchRange.location++;
|
|
285
|
-
}
|
|
286
116
|
|
|
287
|
-
|
|
288
|
-
|
|
117
|
+
fun setAdvancedTextSize(size: Float) {
|
|
118
|
+
if (fontSize == size) return
|
|
119
|
+
fontSize = size
|
|
120
|
+
updateTextWithHighlights()
|
|
121
|
+
}
|
|
289
122
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
123
|
+
fun setAdvancedFontWeight(weight: String) {
|
|
124
|
+
if (fontWeight == weight) return
|
|
125
|
+
fontWeight = weight
|
|
126
|
+
updateTextWithHighlights()
|
|
127
|
+
}
|
|
295
128
|
|
|
296
|
-
|
|
297
|
-
|
|
129
|
+
fun setAdvancedTextAlign(align: String) {
|
|
130
|
+
if (textAlign == align) return
|
|
131
|
+
textAlign = align
|
|
132
|
+
when (align) {
|
|
133
|
+
"left" -> textAlignment = View.TEXT_ALIGNMENT_TEXT_START
|
|
134
|
+
"center" -> textAlignment = View.TEXT_ALIGNMENT_CENTER
|
|
135
|
+
"right" -> textAlignment = View.TEXT_ALIGNMENT_TEXT_END
|
|
136
|
+
else -> textAlignment = View.TEXT_ALIGNMENT_TEXT_START
|
|
298
137
|
}
|
|
299
|
-
|
|
300
|
-
NSLog(@"[AdvancedTextView] Parsed %ld words", (long)_wordRanges.count);
|
|
301
|
-
[self updateTextAppearance];
|
|
302
|
-
} @catch (NSException *exception) {
|
|
303
|
-
NSLog(@"[AdvancedTextView] Exception in updateTextContent: %@", exception);
|
|
304
|
-
@throw;
|
|
305
138
|
}
|
|
306
|
-
}
|
|
307
139
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
[_highlightColors removeAllObjects];
|
|
314
|
-
|
|
315
|
-
for (const auto &hw : highlightedWords) {
|
|
316
|
-
NSInteger index = hw.index;
|
|
317
|
-
NSString *colorString = [NSString stringWithUTF8String:hw.highlightColor.c_str()];
|
|
318
|
-
UIColor *color = [self hexStringToColor:colorString];
|
|
140
|
+
fun setAdvancedFontFamily(family: String) {
|
|
141
|
+
if (fontFamily == family) return
|
|
142
|
+
fontFamily = family
|
|
143
|
+
typeface = Typeface.create(family, Typeface.NORMAL)
|
|
144
|
+
}
|
|
319
145
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
146
|
+
fun setMenuOptions(menuOptions: List<String>) {
|
|
147
|
+
if (this.menuOptions == menuOptions) return
|
|
148
|
+
this.menuOptions = menuOptions
|
|
149
|
+
}
|
|
324
150
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
151
|
+
fun setHighlightedWords(highlightedWords: List<HighlightedWord>) {
|
|
152
|
+
if (this.highlightedWords == highlightedWords) return
|
|
153
|
+
this.highlightedWords = highlightedWords
|
|
154
|
+
updateTextWithHighlights()
|
|
329
155
|
}
|
|
330
|
-
}
|
|
331
156
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
NSMutableArray *menuArray = [NSMutableArray array];
|
|
337
|
-
for (const auto &option : options) {
|
|
338
|
-
NSString *optionStr = [NSString stringWithUTF8String:option.c_str()];
|
|
339
|
-
[menuArray addObject:optionStr];
|
|
340
|
-
NSLog(@"[AdvancedTextView] Added menu option: %@", optionStr);
|
|
341
|
-
}
|
|
342
|
-
_menuOptions = [menuArray copy];
|
|
343
|
-
} @catch (NSException *exception) {
|
|
344
|
-
NSLog(@"[AdvancedTextView] Exception in updateMenuOptions: %@", exception);
|
|
345
|
-
@throw;
|
|
157
|
+
fun setIndicatorWordIndex(index: Int) {
|
|
158
|
+
if (this.indicatorWordIndex == index) return
|
|
159
|
+
this.indicatorWordIndex = index
|
|
160
|
+
updateTextWithHighlights()
|
|
346
161
|
}
|
|
347
|
-
}
|
|
348
162
|
|
|
349
|
-
|
|
350
|
-
{
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
return;
|
|
163
|
+
private fun calculateWordPositions(text: String) {
|
|
164
|
+
if (text.isEmpty()) {
|
|
165
|
+
wordPositions = emptyList()
|
|
166
|
+
return
|
|
354
167
|
}
|
|
355
168
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
UIFont *font = nil;
|
|
169
|
+
val positions = mutableListOf<WordPosition>()
|
|
170
|
+
val regex = "\\S+".toRegex()
|
|
361
171
|
|
|
362
|
-
|
|
363
|
-
|
|
172
|
+
regex.findAll(text).forEachIndexed { index, match ->
|
|
173
|
+
positions.add(WordPosition(
|
|
174
|
+
index = index,
|
|
175
|
+
start = match.range.first,
|
|
176
|
+
end = match.range.last + 1,
|
|
177
|
+
word = match.value
|
|
178
|
+
))
|
|
364
179
|
}
|
|
365
180
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
} else if (_fontWeight && [_fontWeight.lowercaseString isEqualToString:@"italic"]) {
|
|
370
|
-
font = [UIFont italicSystemFontOfSize:_fontSize > 0 ? _fontSize : 16.0];
|
|
371
|
-
} else {
|
|
372
|
-
font = [UIFont systemFontOfSize:_fontSize > 0 ? _fontSize : 16.0];
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
[attributedString addAttribute:NSFontAttributeName
|
|
377
|
-
value:font
|
|
378
|
-
range:NSMakeRange(0, attributedString.length)];
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
UIColor *color = _textColor ?: [UIColor labelColor];
|
|
382
|
-
[attributedString addAttribute:NSForegroundColorAttributeName
|
|
383
|
-
value:color
|
|
384
|
-
range:NSMakeRange(0, attributedString.length)];
|
|
181
|
+
wordPositions = positions
|
|
182
|
+
Log.d(TAG, "Calculated ${wordPositions.size} word positions")
|
|
183
|
+
}
|
|
385
184
|
|
|
185
|
+
private fun updateTextWithHighlights() {
|
|
186
|
+
if (currentText.isEmpty()) {
|
|
187
|
+
Log.d(TAG, "No text available, skipping")
|
|
188
|
+
return
|
|
189
|
+
}
|
|
386
190
|
|
|
387
|
-
|
|
388
|
-
NSNumber *index = wordInfo[@"index"];
|
|
389
|
-
NSValue *rangeValue = wordInfo[@"range"];
|
|
390
|
-
NSRange range = [rangeValue rangeValue];
|
|
191
|
+
val spannableString = SpannableString(currentText)
|
|
391
192
|
|
|
392
|
-
|
|
393
|
-
|
|
193
|
+
wordPositions.forEach { wordPos ->
|
|
194
|
+
highlightedWords.find { it.index == wordPos.index }?.let { highlightedWord ->
|
|
195
|
+
val color = parseColor(highlightedWord.highlightColor)
|
|
196
|
+
spannableString.setSpan(
|
|
197
|
+
BackgroundColorSpan(color),
|
|
198
|
+
wordPos.start,
|
|
199
|
+
wordPos.end,
|
|
200
|
+
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
201
|
+
)
|
|
394
202
|
}
|
|
395
203
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
204
|
+
if (wordPos.index == indicatorWordIndex) {
|
|
205
|
+
spannableString.setSpan(
|
|
206
|
+
ForegroundColorSpan(Color.parseColor(textColor)),
|
|
207
|
+
wordPos.start,
|
|
208
|
+
wordPos.end,
|
|
209
|
+
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
210
|
+
)
|
|
401
211
|
}
|
|
402
212
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
213
|
+
spannableString.setSpan(
|
|
214
|
+
WordClickableSpan(wordPos.index, wordPos.word),
|
|
215
|
+
wordPos.start,
|
|
216
|
+
wordPos.end,
|
|
217
|
+
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
218
|
+
)
|
|
409
219
|
}
|
|
410
220
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
} else if ([_textAlign.lowercaseString isEqualToString:@"right"]) {
|
|
417
|
-
_textView.textAlignment = NSTextAlignmentRight;
|
|
418
|
-
} else {
|
|
419
|
-
_textView.textAlignment = NSTextAlignmentLeft;
|
|
420
|
-
}
|
|
421
|
-
} else {
|
|
422
|
-
_textView.textAlignment = NSTextAlignmentLeft;
|
|
221
|
+
textAlignment = when (textAlign) {
|
|
222
|
+
"left" -> View.TEXT_ALIGNMENT_TEXT_START
|
|
223
|
+
"center" -> View.TEXT_ALIGNMENT_CENTER
|
|
224
|
+
"right" -> View.TEXT_ALIGNMENT_TEXT_END
|
|
225
|
+
else -> View.TEXT_ALIGNMENT_TEXT_START
|
|
423
226
|
}
|
|
424
227
|
|
|
425
|
-
|
|
426
|
-
NSLog(@"[AdvancedTextView] Exception in updateTextAppearance: %@", exception.reason);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
- (void)handleTap:(UITapGestureRecognizer *)gesture
|
|
432
|
-
{
|
|
433
|
-
@try {
|
|
434
|
-
if (gesture.state != UIGestureRecognizerStateEnded) return;
|
|
435
|
-
|
|
436
|
-
CGPoint location = [gesture locationInView:_textView];
|
|
437
|
-
NSInteger wordIndex = [self wordIndexAtPoint:location];
|
|
438
|
-
|
|
439
|
-
_textView.selectedTextRange = nil;
|
|
228
|
+
setTextSize(fontSize)
|
|
440
229
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
[self emitWordPressEvent:word index:wordIndex];
|
|
230
|
+
typeface = when (fontWeight) {
|
|
231
|
+
"bold" -> Typeface.create(fontFamily, Typeface.BOLD)
|
|
232
|
+
"italic" -> Typeface.create(fontFamily, Typeface.ITALIC)
|
|
233
|
+
else -> Typeface.create(fontFamily, Typeface.NORMAL)
|
|
446
234
|
}
|
|
447
|
-
} @catch (NSException *exception) {
|
|
448
|
-
NSLog(@"[AdvancedTextView] Exception in handleTap: %@", exception);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
#pragma mark - UITextViewDelegate
|
|
453
|
-
|
|
454
|
-
- (void)textViewDidChangeSelection:(UITextView *)textView
|
|
455
|
-
{
|
|
456
|
-
NSString *selectedText = [textView.text substringWithRange:textView.selectedRange];
|
|
457
235
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
if (_menuOptions && _menuOptions.count > 0) {
|
|
462
|
-
[self setupCustomMenuItems];
|
|
236
|
+
post {
|
|
237
|
+
setText(spannableString, BufferType.SPANNABLE)
|
|
238
|
+
Log.d(TAG, "Text updated with ${wordPositions.size} spans")
|
|
463
239
|
}
|
|
464
240
|
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
- (void)setupCustomMenuItems
|
|
468
|
-
{
|
|
469
|
-
NSLog(@"[AdvancedTextView] Setting up %lu custom menu items", (unsigned long)_menuOptions.count);
|
|
470
241
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
NSLog(@"[AdvancedTextView] Created menu item: %@", option);
|
|
242
|
+
private fun parseColor(colorString: String): Int {
|
|
243
|
+
return try {
|
|
244
|
+
Color.parseColor(colorString)
|
|
245
|
+
} catch (e: IllegalArgumentException) {
|
|
246
|
+
Log.e(TAG, "Invalid color: $colorString, using yellow")
|
|
247
|
+
Color.YELLOW
|
|
248
|
+
}
|
|
479
249
|
}
|
|
480
250
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
NSString *selectedText = [_textView.text substringWithRange:_textView.selectedRange];
|
|
490
|
-
|
|
491
|
-
[self emitSelectionEvent:selectedText menuOption:sender.title];
|
|
492
|
-
|
|
493
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
494
|
-
self->_textView.selectedTextRange = nil;
|
|
495
|
-
NSLog(@"[AdvancedTextView] Selection cleared");
|
|
496
|
-
});
|
|
497
|
-
}
|
|
251
|
+
private fun sendSelectionEvent(selectedText: String, eventType: String) {
|
|
252
|
+
try {
|
|
253
|
+
val reactContext = context as? ReactContext ?: return
|
|
254
|
+
val event = Arguments.createMap().apply {
|
|
255
|
+
putString("selectedText", selectedText)
|
|
256
|
+
putString("event", eventType)
|
|
257
|
+
}
|
|
498
258
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
return -1;
|
|
259
|
+
reactContext.getJSModule(RCTEventEmitter::class.java)
|
|
260
|
+
.receiveEvent(id, "onSelection", event)
|
|
261
|
+
} catch (e: Exception) {
|
|
262
|
+
Log.e(TAG, "Error sending selection event", e)
|
|
504
263
|
}
|
|
264
|
+
}
|
|
505
265
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
NSTextContainer *textContainer = _textView.textContainer;
|
|
511
|
-
|
|
512
|
-
NSUInteger characterIndex = [layoutManager characterIndexForPoint:point
|
|
513
|
-
inTextContainer:textContainer
|
|
514
|
-
fractionOfDistanceBetweenInsertionPoints:nil];
|
|
266
|
+
private inner class WordClickableSpan(
|
|
267
|
+
private val wordIndex: Int,
|
|
268
|
+
private val word: String
|
|
269
|
+
) : ClickableSpan() {
|
|
515
270
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
NSRange range = [rangeValue rangeValue];
|
|
271
|
+
override fun onClick(widget: View) {
|
|
272
|
+
Log.d(TAG, "WordClickableSpan onClick triggered: '$word' (index=$wordIndex)")
|
|
519
273
|
|
|
520
|
-
|
|
521
|
-
|
|
274
|
+
widget.post {
|
|
275
|
+
sendWordPressEvent(word, wordIndex)
|
|
522
276
|
}
|
|
523
277
|
}
|
|
524
278
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
- (void)setFontSize:(CGFloat)fontSize {
|
|
532
|
-
_fontSize = fontSize;
|
|
533
|
-
[self updateTextAppearance];
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
- (void)setFontWeight:(NSString *)fontWeight {
|
|
537
|
-
_fontWeight = fontWeight;
|
|
538
|
-
[self updateTextAppearance];
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
- (void)setTextColor:(UIColor *)textColor {
|
|
542
|
-
_textColor = textColor;
|
|
543
|
-
[self updateTextAppearance];
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
- (void)setTextAlign:(NSString *)textAlign {
|
|
547
|
-
_textAlign = textAlign;
|
|
548
|
-
[self updateTextAppearance];
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
- (void)setFontFamily:(NSString *)fontFamily {
|
|
552
|
-
_fontFamily = fontFamily;
|
|
553
|
-
[self updateTextAppearance];
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
- (void)emitWordPressEvent:(NSString *)word index:(NSInteger)index
|
|
557
|
-
{
|
|
558
|
-
NSLog(@"[AdvancedTextView] emitWordPressEvent: %@ at index: %ld", word, (long)index);
|
|
559
|
-
@try {
|
|
560
|
-
if (_eventEmitter) {
|
|
561
|
-
auto emitter = std::static_pointer_cast<const AdvancedTextViewEventEmitter>(_eventEmitter);
|
|
562
|
-
|
|
563
|
-
AdvancedTextViewEventEmitter::OnWordPress event;
|
|
564
|
-
event.word = [word UTF8String];
|
|
565
|
-
event.index = static_cast<int>(index);
|
|
566
|
-
|
|
567
|
-
emitter->onWordPress(event);
|
|
279
|
+
override fun updateDrawState(ds: TextPaint) {
|
|
280
|
+
super.updateDrawState(ds)
|
|
281
|
+
ds.isUnderlineText = false
|
|
282
|
+
ds.color = Color.parseColor(textColor)
|
|
568
283
|
}
|
|
569
|
-
} @catch (NSException *exception) {
|
|
570
|
-
NSLog(@"[AdvancedTextView] Exception in emitWordPressEvent: %@", exception);
|
|
571
284
|
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
- (void)emitSelectionEvent:(NSString *)selectedText menuOption:(NSString *)option
|
|
575
|
-
{
|
|
576
|
-
NSLog(@"[AdvancedTextView] emitSelectionEvent: %@ with option: %@", selectedText, option);
|
|
577
|
-
@try {
|
|
578
|
-
if (_eventEmitter) {
|
|
579
|
-
auto emitter = std::static_pointer_cast<const AdvancedTextViewEventEmitter>(_eventEmitter);
|
|
580
285
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
286
|
+
private fun sendWordPressEvent(word: String, index: Int) {
|
|
287
|
+
try {
|
|
288
|
+
val reactContext = context as? ReactContext ?: return
|
|
289
|
+
val event = Arguments.createMap().apply {
|
|
290
|
+
putString("word", word)
|
|
291
|
+
putInt("index", index)
|
|
292
|
+
}
|
|
584
293
|
|
|
585
|
-
|
|
294
|
+
reactContext.getJSModule(RCTEventEmitter::class.java)
|
|
295
|
+
.receiveEvent(id, "onWordPress", event)
|
|
296
|
+
} catch (e: Exception) {
|
|
297
|
+
Log.e(TAG, "Error sending word press event", e)
|
|
586
298
|
}
|
|
587
|
-
} @catch (NSException *exception) {
|
|
588
|
-
NSLog(@"[AdvancedTextView] Exception in emitSelectionEvent: %@", exception);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
- (void)layoutSubviews
|
|
593
|
-
{
|
|
594
|
-
@try {
|
|
595
|
-
[super layoutSubviews];
|
|
596
|
-
_textView.frame = self.bounds;
|
|
597
|
-
} @catch (NSException *exception) {
|
|
598
|
-
NSLog(@"[AdvancedTextView] Exception in layoutSubviews: %@", exception);
|
|
599
299
|
}
|
|
600
|
-
}
|
|
601
300
|
|
|
602
|
-
|
|
603
|
-
{
|
|
604
|
-
|
|
605
|
-
if (!stringToConvert || [stringToConvert length] == 0) {
|
|
606
|
-
return nil;
|
|
301
|
+
fun clearSelection() {
|
|
302
|
+
(text as? android.text.Spannable)?.let {
|
|
303
|
+
Selection.removeSelection(it)
|
|
607
304
|
}
|
|
608
|
-
|
|
609
|
-
NSString *noHashString = [stringToConvert stringByReplacingOccurrencesOfString:@"#" withString:@""];
|
|
610
|
-
NSScanner *stringScanner = [NSScanner scannerWithString:noHashString];
|
|
611
|
-
|
|
612
|
-
unsigned hex;
|
|
613
|
-
if (![stringScanner scanHexInt:&hex]) {
|
|
614
|
-
return nil;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
int r = (hex >> 16) & 0xFF;
|
|
618
|
-
int g = (hex >> 8) & 0xFF;
|
|
619
|
-
int b = (hex) & 0xFF;
|
|
620
|
-
|
|
621
|
-
return [UIColor colorWithRed:r / 255.0f green:g / 255.0f blue:b / 255.0f alpha:1.0f];
|
|
622
|
-
} @catch (NSException *exception) {
|
|
623
|
-
return nil;
|
|
624
305
|
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
Class<RCTComponentViewProtocol> AdvancedTextViewCls(void)
|
|
628
|
-
{
|
|
629
|
-
return AdvancedTextView.class;
|
|
630
|
-
}
|
|
631
306
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
307
|
+
data class WordPosition(
|
|
308
|
+
val index: Int,
|
|
309
|
+
val start: Int,
|
|
310
|
+
val end: Int,
|
|
311
|
+
val word: String
|
|
312
|
+
)
|
|
635
313
|
}
|
|
636
314
|
|
|
637
|
-
|
|
315
|
+
data class HighlightedWord(
|
|
316
|
+
val index: Int,
|
|
317
|
+
val highlightColor: String
|
|
318
|
+
)
|
package/ios/AdvancedTextView.mm
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
using namespace facebook::react;
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
// Forward declaration
|
|
14
13
|
@class AdvancedTextView;
|
|
15
14
|
|
|
16
15
|
@interface AdvancedTextView () <RCTAdvancedTextViewViewProtocol, UIGestureRecognizerDelegate, UITextViewDelegate>
|
|
@@ -19,14 +18,17 @@ using namespace facebook::react;
|
|
|
19
18
|
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIColor *> *highlightColors;
|
|
20
19
|
@property (nonatomic, strong) NSArray<NSString *> *menuOptions;
|
|
21
20
|
@property (nonatomic, assign) NSInteger indicatorWordIndex;
|
|
21
|
+
@property (nonatomic, assign) CGFloat fontSize;
|
|
22
|
+
@property (nonatomic, strong) NSString *fontWeight;
|
|
23
|
+
@property (nonatomic, strong) UIColor *textColor;
|
|
24
|
+
@property (nonatomic, strong) NSString *textAlign;
|
|
25
|
+
@property (nonatomic, strong) NSString *fontFamily;
|
|
22
26
|
|
|
23
|
-
// ✅ ADD THIS LINE
|
|
24
27
|
- (void)handleCustomMenuAction:(UIMenuItem *)sender;
|
|
25
28
|
|
|
26
29
|
@end
|
|
27
30
|
|
|
28
31
|
|
|
29
|
-
// Custom UITextView subclass to override menu behavior
|
|
30
32
|
@interface CustomTextView : UITextView
|
|
31
33
|
@property (nonatomic, weak) AdvancedTextView *parentView;
|
|
32
34
|
@end
|
|
@@ -37,22 +39,18 @@ using namespace facebook::react;
|
|
|
37
39
|
{
|
|
38
40
|
NSLog(@"[CustomTextView] canPerformAction: %@", NSStringFromSelector(action));
|
|
39
41
|
|
|
40
|
-
// Only allow our custom menu actions
|
|
41
42
|
if (action == @selector(handleCustomMenuAction:)) {
|
|
42
43
|
NSLog(@"[CustomTextView] ✅ Allowing custom action");
|
|
43
44
|
return YES;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
// Block ALL system actions
|
|
47
47
|
NSLog(@"[CustomTextView] ❌ Blocking system action: %@", NSStringFromSelector(action));
|
|
48
48
|
return NO;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
- (void)handleCustomMenuAction:(UIMenuItem *)sender
|
|
52
52
|
{
|
|
53
|
-
// Forward to parent view
|
|
54
53
|
if (self.parentView) {
|
|
55
|
-
|
|
56
54
|
[self.parentView handleCustomMenuAction:sender];
|
|
57
55
|
}
|
|
58
56
|
}
|
|
@@ -86,6 +84,11 @@ using namespace facebook::react;
|
|
|
86
84
|
_wordRanges = [NSMutableArray array];
|
|
87
85
|
_highlightColors = [NSMutableDictionary dictionary];
|
|
88
86
|
_indicatorWordIndex = -1;
|
|
87
|
+
_fontSize = 16.0;
|
|
88
|
+
_fontWeight = @"normal";
|
|
89
|
+
_textColor = [UIColor labelColor];
|
|
90
|
+
_textAlign = @"left";
|
|
91
|
+
_fontFamily = @"System";
|
|
89
92
|
|
|
90
93
|
[self setupTextView];
|
|
91
94
|
[self setupGestureRecognizers];
|
|
@@ -127,7 +130,6 @@ using namespace facebook::react;
|
|
|
127
130
|
{
|
|
128
131
|
NSLog(@"[AdvancedTextView] setupGestureRecognizers called");
|
|
129
132
|
@try {
|
|
130
|
-
// Single tap for word selection
|
|
131
133
|
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]
|
|
132
134
|
initWithTarget:self
|
|
133
135
|
action:@selector(handleTap:)];
|
|
@@ -152,14 +154,44 @@ using namespace facebook::react;
|
|
|
152
154
|
BOOL highlightsChanged = NO;
|
|
153
155
|
BOOL menuChanged = NO;
|
|
154
156
|
BOOL indicatorChanged = NO;
|
|
157
|
+
BOOL styleChanged = NO;
|
|
158
|
+
|
|
159
|
+
if (oldViewProps.fontSize != newViewProps.fontSize && newViewProps.fontSize) {
|
|
160
|
+
NSLog(@"[AdvancedTextView] Updating fontSize to: %f", newViewProps.fontSize);
|
|
161
|
+
_fontSize = static_cast<CGFloat>(newViewProps.fontSize);
|
|
162
|
+
styleChanged = YES;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (oldViewProps.textAlign != newViewProps.textAlign && !newViewProps.textAlign.empty()) {
|
|
166
|
+
NSLog(@"[AdvancedTextView] Updating textAlign to: %s", newViewProps.textAlign.c_str());
|
|
167
|
+
_textAlign = [NSString stringWithUTF8String:newViewProps.textAlign.c_str()];
|
|
168
|
+
styleChanged = YES;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (oldViewProps.fontWeight != newViewProps.fontWeight && !newViewProps.fontWeight.empty()) {
|
|
172
|
+
NSLog(@"[AdvancedTextView] Updating fontWeight to: %s", newViewProps.fontWeight.c_str());
|
|
173
|
+
_fontWeight = [NSString stringWithUTF8String:newViewProps.fontWeight.c_str()];
|
|
174
|
+
styleChanged = YES;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (oldViewProps.fontFamily != newViewProps.fontFamily && !newViewProps.fontFamily.empty()) {
|
|
178
|
+
NSLog(@"[AdvancedTextView] Updating fontFamily to: %s", newViewProps.fontFamily.c_str());
|
|
179
|
+
_fontFamily = [NSString stringWithUTF8String:newViewProps.fontFamily.c_str()];
|
|
180
|
+
styleChanged = YES;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (oldViewProps.color != newViewProps.color && !newViewProps.color.empty()) {
|
|
184
|
+
NSLog(@"[AdvancedTextView] Updating color to: %s", newViewProps.color.c_str());
|
|
185
|
+
NSString *colorStr = [NSString stringWithUTF8String:newViewProps.color.c_str()];
|
|
186
|
+
_textColor = [self hexStringToColor:colorStr];
|
|
187
|
+
styleChanged = YES;
|
|
188
|
+
}
|
|
155
189
|
|
|
156
|
-
// Check text change
|
|
157
190
|
if (oldViewProps.text != newViewProps.text) {
|
|
158
191
|
textChanged = YES;
|
|
159
192
|
NSLog(@"[AdvancedTextView] Text changed");
|
|
160
193
|
}
|
|
161
194
|
|
|
162
|
-
// Check highlighted words change
|
|
163
195
|
if (oldViewProps.highlightedWords.size() != newViewProps.highlightedWords.size()) {
|
|
164
196
|
highlightsChanged = YES;
|
|
165
197
|
} else {
|
|
@@ -173,7 +205,6 @@ using namespace facebook::react;
|
|
|
173
205
|
}
|
|
174
206
|
}
|
|
175
207
|
|
|
176
|
-
// Check menu options change
|
|
177
208
|
if (oldViewProps.menuOptions.size() != newViewProps.menuOptions.size()) {
|
|
178
209
|
menuChanged = YES;
|
|
179
210
|
} else {
|
|
@@ -185,12 +216,10 @@ using namespace facebook::react;
|
|
|
185
216
|
}
|
|
186
217
|
}
|
|
187
218
|
|
|
188
|
-
// Check indicator change
|
|
189
219
|
if (oldViewProps.indicatorWordIndex != newViewProps.indicatorWordIndex) {
|
|
190
220
|
indicatorChanged = YES;
|
|
191
221
|
}
|
|
192
222
|
|
|
193
|
-
// Apply updates
|
|
194
223
|
if (textChanged) {
|
|
195
224
|
NSString *text = [NSString stringWithUTF8String:newViewProps.text.c_str()];
|
|
196
225
|
[self updateTextContent:text];
|
|
@@ -209,6 +238,11 @@ using namespace facebook::react;
|
|
|
209
238
|
[self updateTextAppearance];
|
|
210
239
|
}
|
|
211
240
|
|
|
241
|
+
if (styleChanged) {
|
|
242
|
+
NSLog(@"[AdvancedTextView] Style properties changed, updating appearance");
|
|
243
|
+
[self updateTextAppearance];
|
|
244
|
+
}
|
|
245
|
+
|
|
212
246
|
[super updateProps:props oldProps:oldProps];
|
|
213
247
|
NSLog(@"[AdvancedTextView] updateProps completed successfully");
|
|
214
248
|
} @catch (NSException *exception) {
|
|
@@ -229,7 +263,6 @@ using namespace facebook::react;
|
|
|
229
263
|
|
|
230
264
|
_textView.text = text;
|
|
231
265
|
|
|
232
|
-
// Parse text into words and their ranges
|
|
233
266
|
[_wordRanges removeAllObjects];
|
|
234
267
|
|
|
235
268
|
NSRange searchRange = NSMakeRange(0, text.length);
|
|
@@ -237,7 +270,6 @@ using namespace facebook::react;
|
|
|
237
270
|
|
|
238
271
|
NSInteger wordIndex = 0;
|
|
239
272
|
while (searchRange.location < text.length) {
|
|
240
|
-
// Skip whitespace
|
|
241
273
|
while (searchRange.location < text.length &&
|
|
242
274
|
[whitespaceSet characterIsMember:[text characterAtIndex:searchRange.location]]) {
|
|
243
275
|
searchRange.location++;
|
|
@@ -246,7 +278,6 @@ using namespace facebook::react;
|
|
|
246
278
|
|
|
247
279
|
if (searchRange.location >= text.length) break;
|
|
248
280
|
|
|
249
|
-
// Find word end
|
|
250
281
|
NSUInteger wordStart = searchRange.location;
|
|
251
282
|
while (searchRange.location < text.length &&
|
|
252
283
|
![whitespaceSet characterIsMember:[text characterAtIndex:searchRange.location]]) {
|
|
@@ -318,21 +349,41 @@ using namespace facebook::react;
|
|
|
318
349
|
- (void)updateTextAppearance
|
|
319
350
|
{
|
|
320
351
|
@try {
|
|
321
|
-
if (
|
|
352
|
+
if (!_textView.text || _textView.text.length == 0) {
|
|
322
353
|
return;
|
|
323
354
|
}
|
|
324
355
|
|
|
325
356
|
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
|
|
326
357
|
initWithString:_textView.text];
|
|
327
358
|
|
|
359
|
+
|
|
360
|
+
UIFont *font = nil;
|
|
361
|
+
|
|
362
|
+
if (_fontFamily && _fontFamily.length > 0) {
|
|
363
|
+
font = [UIFont fontWithName:_fontFamily size:_fontSize > 0 ? _fontSize : 16.0];
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (!font) {
|
|
367
|
+
if (_fontWeight && [_fontWeight.lowercaseString isEqualToString:@"bold"]) {
|
|
368
|
+
font = [UIFont boldSystemFontOfSize:_fontSize > 0 ? _fontSize : 16.0];
|
|
369
|
+
} else if (_fontWeight && [_fontWeight.lowercaseString isEqualToString:@"italic"]) {
|
|
370
|
+
font = [UIFont italicSystemFontOfSize:_fontSize > 0 ? _fontSize : 16.0];
|
|
371
|
+
} else {
|
|
372
|
+
font = [UIFont systemFontOfSize:_fontSize > 0 ? _fontSize : 16.0];
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
328
376
|
[attributedString addAttribute:NSFontAttributeName
|
|
329
|
-
value:
|
|
377
|
+
value:font
|
|
330
378
|
range:NSMakeRange(0, attributedString.length)];
|
|
331
379
|
|
|
380
|
+
|
|
381
|
+
UIColor *color = _textColor ?: [UIColor labelColor];
|
|
332
382
|
[attributedString addAttribute:NSForegroundColorAttributeName
|
|
333
|
-
value:
|
|
383
|
+
value:color
|
|
334
384
|
range:NSMakeRange(0, attributedString.length)];
|
|
335
385
|
|
|
386
|
+
|
|
336
387
|
for (NSDictionary *wordInfo in _wordRanges) {
|
|
337
388
|
NSNumber *index = wordInfo[@"index"];
|
|
338
389
|
NSValue *rangeValue = wordInfo[@"range"];
|
|
@@ -358,11 +409,25 @@ using namespace facebook::react;
|
|
|
358
409
|
}
|
|
359
410
|
|
|
360
411
|
_textView.attributedText = attributedString;
|
|
412
|
+
|
|
413
|
+
if (_textAlign) {
|
|
414
|
+
if ([_textAlign.lowercaseString isEqualToString:@"center"]) {
|
|
415
|
+
_textView.textAlignment = NSTextAlignmentCenter;
|
|
416
|
+
} else if ([_textAlign.lowercaseString isEqualToString:@"right"]) {
|
|
417
|
+
_textView.textAlignment = NSTextAlignmentRight;
|
|
418
|
+
} else {
|
|
419
|
+
_textView.textAlignment = NSTextAlignmentLeft;
|
|
420
|
+
}
|
|
421
|
+
} else {
|
|
422
|
+
_textView.textAlignment = NSTextAlignmentLeft;
|
|
423
|
+
}
|
|
424
|
+
|
|
361
425
|
} @catch (NSException *exception) {
|
|
362
426
|
NSLog(@"[AdvancedTextView] Exception in updateTextAppearance: %@", exception.reason);
|
|
363
427
|
}
|
|
364
428
|
}
|
|
365
429
|
|
|
430
|
+
|
|
366
431
|
- (void)handleTap:(UITapGestureRecognizer *)gesture
|
|
367
432
|
{
|
|
368
433
|
@try {
|
|
@@ -371,7 +436,6 @@ using namespace facebook::react;
|
|
|
371
436
|
CGPoint location = [gesture locationInView:_textView];
|
|
372
437
|
NSInteger wordIndex = [self wordIndexAtPoint:location];
|
|
373
438
|
|
|
374
|
-
// Dismiss any existing selection
|
|
375
439
|
_textView.selectedTextRange = nil;
|
|
376
440
|
|
|
377
441
|
if (wordIndex >= 0 && wordIndex < _wordRanges.count) {
|
|
@@ -464,6 +528,31 @@ using namespace facebook::react;
|
|
|
464
528
|
}
|
|
465
529
|
}
|
|
466
530
|
|
|
531
|
+
- (void)setFontSize:(CGFloat)fontSize {
|
|
532
|
+
_fontSize = fontSize;
|
|
533
|
+
[self updateTextAppearance];
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
- (void)setFontWeight:(NSString *)fontWeight {
|
|
537
|
+
_fontWeight = fontWeight;
|
|
538
|
+
[self updateTextAppearance];
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
- (void)setTextColor:(UIColor *)textColor {
|
|
542
|
+
_textColor = textColor;
|
|
543
|
+
[self updateTextAppearance];
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
- (void)setTextAlign:(NSString *)textAlign {
|
|
547
|
+
_textAlign = textAlign;
|
|
548
|
+
[self updateTextAppearance];
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
- (void)setFontFamily:(NSString *)fontFamily {
|
|
552
|
+
_fontFamily = fontFamily;
|
|
553
|
+
[self updateTextAppearance];
|
|
554
|
+
}
|
|
555
|
+
|
|
467
556
|
- (void)emitWordPressEvent:(NSString *)word index:(NSInteger)index
|
|
468
557
|
{
|
|
469
558
|
NSLog(@"[AdvancedTextView] emitWordPressEvent: %@ at index: %ld", word, (long)index);
|
package/package.json
CHANGED