react-native-advanced-text 0.1.21 → 0.1.23

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,4 +1,3 @@
1
- // File: AdvancedTextView.kt
2
1
  package com.advancedtext
3
2
 
4
3
  import android.content.Context
@@ -34,7 +33,6 @@ class AdvancedTextView : TextView {
34
33
  private var currentText: String = ""
35
34
  private var textColor: String = "#000000"
36
35
 
37
- // Cache for word positions to avoid recalculating
38
36
  private var wordPositions: List<WordPosition> = emptyList()
39
37
 
40
38
  constructor(context: Context?) : super(context) { init() }
@@ -42,51 +40,52 @@ class AdvancedTextView : TextView {
42
40
  constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { init() }
43
41
 
44
42
  private fun init() {
45
- Log.d(TAG, "AdvancedTextView initialized")
46
-
47
- textSize = 16f
48
- setPadding(16, 16, 16, 16)
49
-
50
- movementMethod = ClickableMovementMethod.getInstance()
51
- isClickable = false
52
- isLongClickable = false
53
-
54
- setTextIsSelectable(true)
55
-
56
- customSelectionActionModeCallback = object : ActionMode.Callback {
57
- override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
58
- customActionMode = mode
59
- return true
60
- }
61
-
62
- override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
63
- menu?.clear()
64
- val selectionStart = selectionStart
65
- val selectionEnd = selectionEnd
66
-
67
- if (selectionStart >= 0 && selectionEnd >= 0 && selectionStart != selectionEnd) {
68
- lastSelectedText = text.subSequence(selectionStart, selectionEnd).toString()
69
- menuOptions.forEachIndexed { index, option ->
70
- menu?.add(0, index, index, option)
71
- }
72
- sendSelectionEvent(lastSelectedText, "selection")
73
- return true
74
- }
75
- return false
76
- }
77
-
78
- override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
79
- item?.let {
80
- sendSelectionEvent(lastSelectedText, it.title.toString())
81
- mode?.finish()
82
- return true
83
- }
84
- return false
85
- }
86
-
87
- override fun onDestroyActionMode(mode: ActionMode?) {
88
- customActionMode = null
89
- }
43
+ Log.d(TAG, "AdvancedTextView initialized")
44
+
45
+ textSize = 16f
46
+ setPadding(16, 16, 16, 16)
47
+ movementMethod = LinkMovementMethod.getInstance()
48
+ setTextIsSelectable(true)
49
+
50
+
51
+ customSelectionActionModeCallback = object : ActionMode.Callback {
52
+ override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
53
+ customActionMode = mode
54
+ return true
55
+ }
56
+
57
+ override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
58
+ menu?.clear()
59
+
60
+ val selectionStart = selectionStart
61
+ val selectionEnd = selectionEnd
62
+
63
+ if (selectionStart >= 0 && selectionEnd >= 0 && selectionStart != selectionEnd) {
64
+ lastSelectedText = text.subSequence(selectionStart, selectionEnd).toString()
65
+
66
+ menuOptions.forEachIndexed { index, option ->
67
+ menu?.add(0, index, index, option)
68
+ }
69
+
70
+ sendSelectionEvent(lastSelectedText, "selection")
71
+ return true
72
+ }
73
+ return false
74
+ }
75
+
76
+ override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
77
+ item?.let {
78
+ val menuItemText = it.title.toString()
79
+ sendSelectionEvent(lastSelectedText, menuItemText)
80
+ mode?.finish()
81
+ return true
82
+ }
83
+ return false
84
+ }
85
+
86
+ override fun onDestroyActionMode(mode: ActionMode?) {
87
+ customActionMode = null
88
+ }
90
89
  }
91
90
  }
92
91
 
@@ -155,9 +154,7 @@ class AdvancedTextView : TextView {
155
154
 
156
155
  val spannableString = SpannableString(currentText)
157
156
 
158
- // Apply spans efficiently
159
157
  wordPositions.forEach { wordPos ->
160
- // Apply highlights
161
158
  highlightedWords.find { it.index == wordPos.index }?.let { highlightedWord ->
162
159
  val color = parseColor(highlightedWord.highlightColor)
163
160
  spannableString.setSpan(
@@ -168,7 +165,6 @@ class AdvancedTextView : TextView {
168
165
  )
169
166
  }
170
167
 
171
- // Apply indicator color
172
168
  if (wordPos.index == indicatorWordIndex) {
173
169
  spannableString.setSpan(
174
170
  ForegroundColorSpan(Color.parseColor(textColor)),
@@ -178,7 +174,6 @@ class AdvancedTextView : TextView {
178
174
  )
179
175
  }
180
176
 
181
- // Make words clickable
182
177
  spannableString.setSpan(
183
178
  WordClickableSpan(wordPos.index, wordPos.word),
184
179
  wordPos.start,
@@ -187,7 +182,6 @@ class AdvancedTextView : TextView {
187
182
  )
188
183
  }
189
184
 
190
- // Use post to ensure UI thread and avoid layout issues
191
185
  post {
192
186
  setText(spannableString, BufferType.SPANNABLE)
193
187
  Log.d(TAG, "Text updated with ${wordPositions.size} spans")
@@ -224,14 +218,11 @@ class AdvancedTextView : TextView {
224
218
  ) : ClickableSpan() {
225
219
 
226
220
  override fun onClick(widget: View) {
227
- Log.d(TAG, "Word clicked: '$word' (index=$wordIndex)")
228
- val spannable = widget as? TextView
229
- spannable?.text?.let {
230
- if (it is android.text.Spannable) {
231
- Selection.removeSelection(it)
232
- }
221
+ Log.d(TAG, "WordClickableSpan onClick triggered: '$word' (index=$wordIndex)")
222
+
223
+ widget.post {
224
+ sendWordPressEvent(word, wordIndex)
233
225
  }
234
- sendWordPressEvent(word, wordIndex)
235
226
  }
236
227
 
237
228
  override fun updateDrawState(ds: TextPaint) {
@@ -1,14 +1,15 @@
1
+ #ifndef AdvancedTextView_h
2
+ #define AdvancedTextView_h
3
+
1
4
  #import <React/RCTViewComponentView.h>
2
5
  #import <UIKit/UIKit.h>
3
6
 
4
- #ifndef AdvancedTextViewNativeComponent_h
5
- #define AdvancedTextViewNativeComponent_h
6
-
7
7
  NS_ASSUME_NONNULL_BEGIN
8
8
 
9
9
  @interface AdvancedTextView : RCTViewComponentView
10
+
10
11
  @end
11
12
 
12
13
  NS_ASSUME_NONNULL_END
13
14
 
14
- #endif /* AdvancedTextViewNativeComponent_h */
15
+ #endif /* AdvancedTextView_h */
@@ -9,63 +9,583 @@
9
9
 
10
10
  using namespace facebook::react;
11
11
 
12
- @interface AdvancedTextView () <RCTAdvancedTextViewViewProtocol>
12
+ @interface AdvancedTextView () <RCTAdvancedTextViewViewProtocol, UIGestureRecognizerDelegate, UITextViewDelegate>
13
+
14
+ @property (nonatomic, strong) UITextView *textView;
15
+ @property (nonatomic, strong) NSMutableArray<NSDictionary *> *wordRanges;
16
+ @property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIColor *> *highlightColors;
17
+ @property (nonatomic, strong) NSArray<NSString *> *menuOptions;
18
+ @property (nonatomic, assign) NSInteger indicatorWordIndex;
13
19
 
14
20
  @end
15
21
 
16
- @implementation AdvancedTextView {
17
- UIView * _view;
18
- }
22
+ @implementation AdvancedTextView
19
23
 
20
24
  + (ComponentDescriptorProvider)componentDescriptorProvider
21
25
  {
26
+ NSLog(@"[AdvancedTextView] componentDescriptorProvider called");
22
27
  return concreteComponentDescriptorProvider<AdvancedTextViewComponentDescriptor>();
23
28
  }
24
29
 
25
30
  - (instancetype)initWithFrame:(CGRect)frame
26
31
  {
27
- if (self = [super initWithFrame:frame]) {
28
- static const auto defaultProps = std::make_shared<const AdvancedTextViewProps>();
29
- _props = defaultProps;
32
+ NSLog(@"[AdvancedTextView] initWithFrame called");
33
+ if (self = [super initWithFrame:frame]) {
34
+ @try {
35
+ static const auto defaultProps = std::make_shared<const AdvancedTextViewProps>();
36
+ _props = defaultProps;
37
+
38
+ _wordRanges = [NSMutableArray array];
39
+ _highlightColors = [NSMutableDictionary dictionary];
40
+ _indicatorWordIndex = -1;
41
+
42
+ [self setupTextView];
43
+ [self setupGestureRecognizers];
44
+
45
+ NSLog(@"[AdvancedTextView] Initialization successful");
46
+ } @catch (NSException *exception) {
47
+ NSLog(@"[AdvancedTextView] Exception in init: %@", exception);
48
+ @throw;
49
+ }
50
+ }
30
51
 
31
- _view = [[UIView alloc] init];
52
+ return self;
53
+ }
32
54
 
33
- self.contentView = _view;
34
- }
55
+ - (void)setupTextView
56
+ {
57
+ NSLog(@"[AdvancedTextView] setupTextView called");
58
+ @try {
59
+ _textView = [[UITextView alloc] initWithFrame:self.bounds];
60
+ _textView.editable = NO;
61
+ _textView.selectable = YES; // Allow text selection
62
+ _textView.scrollEnabled = YES;
63
+ _textView.backgroundColor = [UIColor clearColor];
64
+ _textView.textContainerInset = UIEdgeInsetsMake(8, 8, 8, 8);
65
+ _textView.font = [UIFont systemFontOfSize:16];
66
+ _textView.textColor = [UIColor labelColor];
67
+ _textView.delegate = self;
35
68
 
36
- return self;
69
+ self.contentView = _textView;
70
+ NSLog(@"[AdvancedTextView] TextView setup successful");
71
+ } @catch (NSException *exception) {
72
+ NSLog(@"[AdvancedTextView] Exception in setupTextView: %@", exception);
73
+ @throw;
74
+ }
75
+ }
76
+
77
+ - (void)setupGestureRecognizers
78
+ {
79
+ NSLog(@"[AdvancedTextView] setupGestureRecognizers called");
80
+ @try {
81
+ // Single tap for word selection
82
+ UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]
83
+ initWithTarget:self
84
+ action:@selector(handleTap:)];
85
+ tapGesture.delegate = self;
86
+ [_textView addGestureRecognizer:tapGesture];
87
+
88
+ NSLog(@"[AdvancedTextView] Gesture recognizers setup successful");
89
+ } @catch (NSException *exception) {
90
+ NSLog(@"[AdvancedTextView] Exception in setupGestureRecognizers: %@", exception);
91
+ @throw;
92
+ }
37
93
  }
38
94
 
39
95
  - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
40
96
  {
41
- const auto &oldViewProps = *std::static_pointer_cast<AdvancedTextViewProps const>(_props);
42
- const auto &newViewProps = *std::static_pointer_cast<AdvancedTextViewProps const>(props);
97
+ NSLog(@"[AdvancedTextView] updateProps called");
98
+ @try {
99
+ const auto &oldViewProps = *std::static_pointer_cast<AdvancedTextViewProps const>(_props);
100
+ const auto &newViewProps = *std::static_pointer_cast<AdvancedTextViewProps const>(props);
101
+
102
+ BOOL textChanged = NO;
103
+ BOOL highlightsChanged = NO;
104
+ BOOL menuChanged = NO;
105
+ BOOL indicatorChanged = NO;
106
+
107
+ // Check text change
108
+ if (oldViewProps.text != newViewProps.text) {
109
+ textChanged = YES;
110
+ NSLog(@"[AdvancedTextView] Text changed: %s", newViewProps.text.c_str());
111
+ }
112
+
113
+ // Check highlighted words change
114
+ if (oldViewProps.highlightedWords.size() != newViewProps.highlightedWords.size()) {
115
+ highlightsChanged = YES;
116
+ NSLog(@"[AdvancedTextView] Highlights size changed: %zu -> %zu",
117
+ oldViewProps.highlightedWords.size(), newViewProps.highlightedWords.size());
118
+ } else {
119
+ for (size_t i = 0; i < oldViewProps.highlightedWords.size(); i++) {
120
+ const auto &oldHW = oldViewProps.highlightedWords[i];
121
+ const auto &newHW = newViewProps.highlightedWords[i];
122
+ if (oldHW.index != newHW.index || oldHW.highlightColor != newHW.highlightColor) {
123
+ highlightsChanged = YES;
124
+ NSLog(@"[AdvancedTextView] Highlight changed at index %zu", i);
125
+ break;
126
+ }
127
+ }
128
+ }
129
+
130
+ // Check menu options change
131
+ if (oldViewProps.menuOptions.size() != newViewProps.menuOptions.size()) {
132
+ menuChanged = YES;
133
+ NSLog(@"[AdvancedTextView] Menu options size changed: %zu -> %zu",
134
+ oldViewProps.menuOptions.size(), newViewProps.menuOptions.size());
135
+ } else {
136
+ for (size_t i = 0; i < oldViewProps.menuOptions.size(); i++) {
137
+ if (oldViewProps.menuOptions[i] != newViewProps.menuOptions[i]) {
138
+ menuChanged = YES;
139
+ NSLog(@"[AdvancedTextView] Menu option changed at index %zu", i);
140
+ break;
141
+ }
142
+ }
143
+ }
144
+
145
+ // Check indicator change
146
+ if (oldViewProps.indicatorWordIndex != newViewProps.indicatorWordIndex) {
147
+ indicatorChanged = YES;
148
+ NSLog(@"[AdvancedTextView] Indicator changed: %d -> %d",
149
+ oldViewProps.indicatorWordIndex, newViewProps.indicatorWordIndex);
150
+ }
43
151
 
44
- if (oldViewProps.color != newViewProps.color) {
45
- NSString * colorToConvert = [[NSString alloc] initWithUTF8String: newViewProps.color.c_str()];
46
- [_view setBackgroundColor:[self hexStringToColor:colorToConvert]];
152
+ // Apply updates
153
+ if (textChanged) {
154
+ NSString *text = [NSString stringWithUTF8String:newViewProps.text.c_str()];
155
+ [self updateTextContent:text];
156
+ }
157
+
158
+ if (highlightsChanged) {
159
+ [self updateHighlightedWords:newViewProps.highlightedWords];
160
+ }
161
+
162
+ if (menuChanged) {
163
+ [self updateMenuOptions:newViewProps.menuOptions];
164
+ }
165
+
166
+ if (indicatorChanged) {
167
+ _indicatorWordIndex = newViewProps.indicatorWordIndex;
168
+ [self updateTextAppearance];
169
+ }
170
+
171
+ [super updateProps:props oldProps:oldProps];
172
+ NSLog(@"[AdvancedTextView] updateProps completed successfully");
173
+ } @catch (NSException *exception) {
174
+ NSLog(@"[AdvancedTextView] Exception in updateProps: %@, reason: %@",
175
+ exception.name, exception.reason);
176
+ @throw;
47
177
  }
178
+ }
179
+
180
+ - (void)updateTextContent:(NSString *)text
181
+ {
182
+ NSLog(@"[AdvancedTextView] updateTextContent called with text length: %lu",
183
+ (unsigned long)text.length);
184
+ @try {
185
+ if (!text) {
186
+ NSLog(@"[AdvancedTextView] Text is nil, skipping update");
187
+ return;
188
+ }
189
+
190
+ // IMPORTANT: Set the text first!
191
+ _textView.text = text;
192
+ NSLog(@"[AdvancedTextView] Set textView.text with length: %lu", (unsigned long)_textView.text.length);
193
+
194
+ // Parse text into words and their ranges
195
+ [_wordRanges removeAllObjects];
48
196
 
49
- [super updateProps:props oldProps:oldProps];
197
+ NSRange searchRange = NSMakeRange(0, text.length);
198
+ NSCharacterSet *whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
199
+
200
+ NSInteger wordIndex = 0;
201
+ while (searchRange.location < text.length) {
202
+ // Skip whitespace
203
+ while (searchRange.location < text.length &&
204
+ [whitespaceSet characterIsMember:[text characterAtIndex:searchRange.location]]) {
205
+ searchRange.location++;
206
+ searchRange.length = text.length - searchRange.location;
207
+ }
208
+
209
+ if (searchRange.location >= text.length) break;
210
+
211
+ // Find word end
212
+ NSUInteger wordStart = searchRange.location;
213
+ while (searchRange.location < text.length &&
214
+ ![whitespaceSet characterIsMember:[text characterAtIndex:searchRange.location]]) {
215
+ searchRange.location++;
216
+ }
217
+
218
+ NSRange wordRange = NSMakeRange(wordStart, searchRange.location - wordStart);
219
+ NSString *word = [text substringWithRange:wordRange];
220
+
221
+ [_wordRanges addObject:@{
222
+ @"word": word,
223
+ @"range": [NSValue valueWithRange:wordRange],
224
+ @"index": @(wordIndex)
225
+ }];
226
+
227
+ wordIndex++;
228
+ searchRange.length = text.length - searchRange.location;
229
+ }
230
+
231
+ NSLog(@"[AdvancedTextView] Parsed %ld words", (long)_wordRanges.count);
232
+ [self updateTextAppearance];
233
+ } @catch (NSException *exception) {
234
+ NSLog(@"[AdvancedTextView] Exception in updateTextContent: %@", exception);
235
+ @throw;
236
+ }
50
237
  }
51
238
 
52
- Class<RCTComponentViewProtocol> AdvancedTextViewCls(void)
239
+ - (void)updateHighlightedWords:(const std::vector<AdvancedTextViewHighlightedWordsStruct> &)highlightedWords
53
240
  {
54
- return AdvancedTextView.class;
241
+ NSLog(@"[AdvancedTextView] updateHighlightedWords called with %zu highlights",
242
+ highlightedWords.size());
243
+ @try {
244
+ [_highlightColors removeAllObjects];
245
+
246
+ for (const auto &hw : highlightedWords) {
247
+ NSInteger index = hw.index;
248
+ NSString *colorString = [NSString stringWithUTF8String:hw.highlightColor.c_str()];
249
+ UIColor *color = [self hexStringToColor:colorString];
250
+
251
+ if (color) {
252
+ _highlightColors[@(index)] = color;
253
+ NSLog(@"[AdvancedTextView] Added highlight for word %ld with color %@",
254
+ (long)index, colorString);
255
+ } else {
256
+ NSLog(@"[AdvancedTextView] Failed to parse color: %@", colorString);
257
+ }
258
+ }
259
+
260
+ [self updateTextAppearance];
261
+ } @catch (NSException *exception) {
262
+ NSLog(@"[AdvancedTextView] Exception in updateHighlightedWords: %@", exception);
263
+ @throw;
264
+ }
265
+ }
266
+
267
+ - (void)updateMenuOptions:(const std::vector<std::string> &)options
268
+ {
269
+ NSLog(@"[AdvancedTextView] updateMenuOptions called with %zu options", options.size());
270
+ @try {
271
+ NSMutableArray *menuArray = [NSMutableArray array];
272
+ for (const auto &option : options) {
273
+ NSString *optionStr = [NSString stringWithUTF8String:option.c_str()];
274
+ [menuArray addObject:optionStr];
275
+ NSLog(@"[AdvancedTextView] Added menu option: %@", optionStr);
276
+ }
277
+ _menuOptions = [menuArray copy];
278
+ } @catch (NSException *exception) {
279
+ NSLog(@"[AdvancedTextView] Exception in updateMenuOptions: %@", exception);
280
+ @throw;
281
+ }
282
+ }
283
+
284
+ - (void)updateTextAppearance
285
+ {
286
+ NSLog(@"[AdvancedTextView] updateTextAppearance called");
287
+ @try {
288
+ if (_wordRanges.count == 0) {
289
+ NSLog(@"[AdvancedTextView] No word ranges, skipping appearance update");
290
+ return;
291
+ }
292
+
293
+ if (!_textView.text || _textView.text.length == 0) {
294
+ NSLog(@"[AdvancedTextView] TextView has no text");
295
+ return;
296
+ }
297
+
298
+ NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
299
+ initWithString:_textView.text];
300
+
301
+ // Apply default attributes
302
+ [attributedString addAttribute:NSFontAttributeName
303
+ value:[UIFont systemFontOfSize:16]
304
+ range:NSMakeRange(0, attributedString.length)];
305
+
306
+ [attributedString addAttribute:NSForegroundColorAttributeName
307
+ value:[UIColor labelColor]
308
+ range:NSMakeRange(0, attributedString.length)];
309
+
310
+ // Apply highlights
311
+ for (NSDictionary *wordInfo in _wordRanges) {
312
+ NSNumber *index = wordInfo[@"index"];
313
+ NSValue *rangeValue = wordInfo[@"range"];
314
+ NSRange range = [rangeValue rangeValue];
315
+
316
+ // Validate range
317
+ if (range.location + range.length > attributedString.length) {
318
+ NSLog(@"[AdvancedTextView] Invalid range at index %@: {%lu, %lu} for string length %lu",
319
+ index, (unsigned long)range.location, (unsigned long)range.length,
320
+ (unsigned long)attributedString.length);
321
+ continue;
322
+ }
323
+
324
+ UIColor *highlightColor = _highlightColors[index];
325
+ if (highlightColor) {
326
+ [attributedString addAttribute:NSBackgroundColorAttributeName
327
+ value:highlightColor
328
+ range:range];
329
+ }
330
+
331
+ // Add background color indicator for indicated word
332
+ if (_indicatorWordIndex >= 0 && [index integerValue] == _indicatorWordIndex) {
333
+ // Use a semi-transparent blue background for the current word
334
+ UIColor *indicatorColor = [[UIColor systemBlueColor] colorWithAlphaComponent:0.3];
335
+ [attributedString addAttribute:NSBackgroundColorAttributeName
336
+ value:indicatorColor
337
+ range:range];
338
+ }
339
+ }
340
+
341
+ _textView.attributedText = attributedString;
342
+ NSLog(@"[AdvancedTextView] Text appearance updated successfully");
343
+ } @catch (NSException *exception) {
344
+ NSLog(@"[AdvancedTextView] Exception in updateTextAppearance: %@, reason: %@",
345
+ exception.name, exception.reason);
346
+ @throw;
347
+ }
348
+ }
349
+
350
+ - (void)handleTap:(UITapGestureRecognizer *)gesture
351
+ {
352
+ NSLog(@"[AdvancedTextView] handleTap called");
353
+ @try {
354
+ if (gesture.state != UIGestureRecognizerStateEnded) return;
355
+
356
+ CGPoint location = [gesture locationInView:_textView];
357
+ NSInteger wordIndex = [self wordIndexAtPoint:location];
358
+
359
+ NSLog(@"[AdvancedTextView] Tap at point: {%.2f, %.2f}, word index: %ld",
360
+ location.x, location.y, (long)wordIndex);
361
+
362
+ // Dismiss any existing selection
363
+ _textView.selectedTextRange = nil;
364
+
365
+ if (wordIndex >= 0 && wordIndex < _wordRanges.count) {
366
+ NSDictionary *wordInfo = _wordRanges[wordIndex];
367
+ NSString *word = wordInfo[@"word"];
368
+
369
+ NSLog(@"[AdvancedTextView] Word pressed: %@", word);
370
+ [self emitWordPressEvent:word index:wordIndex];
371
+ }
372
+ } @catch (NSException *exception) {
373
+ NSLog(@"[AdvancedTextView] Exception in handleTap: %@", exception);
374
+ }
55
375
  }
56
376
 
57
- - hexStringToColor:(NSString *)stringToConvert
377
+ #pragma mark - UITextViewDelegate
378
+
379
+ - (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction
58
380
  {
59
- NSString *noHashString = [stringToConvert stringByReplacingOccurrencesOfString:@"#" withString:@""];
60
- NSScanner *stringScanner = [NSScanner scannerWithString:noHashString];
381
+ return YES;
382
+ }
383
+
384
+ - (void)textViewDidChangeSelection:(UITextView *)textView
385
+ {
386
+ NSLog(@"[AdvancedTextView] Selection changed");
387
+
388
+ // Get selected text
389
+ NSString *selectedText = [textView.text substringWithRange:textView.selectedRange];
61
390
 
62
- unsigned hex;
63
- if (![stringScanner scanHexInt:&hex]) return nil;
64
- int r = (hex >> 16) & 0xFF;
65
- int g = (hex >> 8) & 0xFF;
66
- int b = (hex) & 0xFF;
391
+ if (selectedText.length > 0) {
392
+ NSLog(@"[AdvancedTextView] Selected text: %@", selectedText);
67
393
 
68
- return [UIColor colorWithRed:r / 255.0f green:g / 255.0f blue:b / 255.0f alpha:1.0f];
394
+ // Add custom menu items
395
+ if (_menuOptions && _menuOptions.count > 0) {
396
+ [self setupCustomMenuItems];
397
+ }
398
+ }
399
+ }
400
+
401
+ - (void)setupCustomMenuItems
402
+ {
403
+ NSLog(@"[AdvancedTextView] Setting up custom menu items");
404
+
405
+ UIMenuController *menuController = [UIMenuController sharedMenuController];
406
+ NSMutableArray *customItems = [NSMutableArray array];
407
+
408
+ for (NSString *option in _menuOptions) {
409
+ UIMenuItem *item = [[UIMenuItem alloc] initWithTitle:option
410
+ action:@selector(handleCustomMenuAction:)];
411
+ [customItems addObject:item];
412
+ }
413
+
414
+ menuController.menuItems = customItems;
415
+ }
416
+
417
+ - (void)handleCustomMenuAction:(UIMenuItem *)sender
418
+ {
419
+ NSLog(@"[AdvancedTextView] Custom menu action: %@", sender.title);
420
+
421
+ NSString *selectedText = [_textView.text substringWithRange:_textView.selectedRange];
422
+ [self emitSelectionEvent:selectedText menuOption:sender.title];
423
+
424
+ // Clear selection after action
425
+ _textView.selectedTextRange = nil;
426
+ }
427
+
428
+ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
429
+ {
430
+ // Allow custom menu items
431
+ if (action == @selector(handleCustomMenuAction:)) {
432
+ return YES;
433
+ }
434
+
435
+ // Allow default actions (copy, etc.)
436
+ if (action == @selector(copy:) ||
437
+ action == @selector(selectAll:) ||
438
+ action == @selector(select:)) {
439
+ return [super canPerformAction:action withSender:sender];
440
+ }
441
+
442
+ return NO;
443
+ }
444
+
445
+ - (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
446
+ {
447
+ // Removed - using native iOS text selection menu instead
448
+ }
449
+
450
+ - (NSInteger)wordIndexAtPoint:(CGPoint)point
451
+ {
452
+ @try {
453
+ if (!_textView.layoutManager || !_textView.textContainer) {
454
+ NSLog(@"[AdvancedTextView] Layout manager or text container is nil");
455
+ return -1;
456
+ }
457
+
458
+ // Adjust point for text container insets
459
+ point.x -= _textView.textContainerInset.left;
460
+ point.y -= _textView.textContainerInset.top;
461
+
462
+ NSLayoutManager *layoutManager = _textView.layoutManager;
463
+ NSTextContainer *textContainer = _textView.textContainer;
464
+
465
+ NSUInteger characterIndex = [layoutManager characterIndexForPoint:point
466
+ inTextContainer:textContainer
467
+ fractionOfDistanceBetweenInsertionPoints:nil];
468
+
469
+ NSLog(@"[AdvancedTextView] Character index at point: %lu", (unsigned long)characterIndex);
470
+
471
+ // Find which word this character belongs to
472
+ for (NSDictionary *wordInfo in _wordRanges) {
473
+ NSValue *rangeValue = wordInfo[@"range"];
474
+ NSRange range = [rangeValue rangeValue];
475
+
476
+ if (NSLocationInRange(characterIndex, range)) {
477
+ return [wordInfo[@"index"] integerValue];
478
+ }
479
+ }
480
+
481
+ return -1;
482
+ } @catch (NSException *exception) {
483
+ NSLog(@"[AdvancedTextView] Exception in wordIndexAtPoint: %@", exception);
484
+ return -1;
485
+ }
486
+ }
487
+
488
+ - (void)showContextMenuForWord:(NSString *)word atIndex:(NSInteger)index location:(CGPoint)location
489
+ {
490
+ // Removed - using native iOS text selection menu instead
491
+ }
492
+
493
+ - (UIViewController *)findViewController
494
+ {
495
+ // Removed - no longer needed
496
+ return nil;
497
+ }
498
+
499
+ - (void)emitWordPressEvent:(NSString *)word index:(NSInteger)index
500
+ {
501
+ NSLog(@"[AdvancedTextView] emitWordPressEvent: %@ at index: %ld", word, (long)index);
502
+ @try {
503
+ if (_eventEmitter) {
504
+ auto emitter = std::static_pointer_cast<const AdvancedTextViewEventEmitter>(_eventEmitter);
505
+
506
+ AdvancedTextViewEventEmitter::OnWordPress event;
507
+ event.word = [word UTF8String];
508
+ event.index = static_cast<int>(index);
509
+
510
+ emitter->onWordPress(event);
511
+ NSLog(@"[AdvancedTextView] Word press event emitted successfully");
512
+ } else {
513
+ NSLog(@"[AdvancedTextView] Event emitter is null");
514
+ }
515
+ } @catch (NSException *exception) {
516
+ NSLog(@"[AdvancedTextView] Exception in emitWordPressEvent: %@", exception);
517
+ }
518
+ }
519
+
520
+ - (void)emitSelectionEvent:(NSString *)selectedText menuOption:(NSString *)option
521
+ {
522
+ NSLog(@"[AdvancedTextView] emitSelectionEvent: %@ with option: %@", selectedText, option);
523
+ @try {
524
+ if (_eventEmitter) {
525
+ auto emitter = std::static_pointer_cast<const AdvancedTextViewEventEmitter>(_eventEmitter);
526
+
527
+ AdvancedTextViewEventEmitter::OnSelection event;
528
+ event.selectedText = [selectedText UTF8String];
529
+ event.event = [option UTF8String];
530
+
531
+ emitter->onSelection(event);
532
+ NSLog(@"[AdvancedTextView] Selection event emitted successfully");
533
+ } else {
534
+ NSLog(@"[AdvancedTextView] Event emitter is null");
535
+ }
536
+ } @catch (NSException *exception) {
537
+ NSLog(@"[AdvancedTextView] Exception in emitSelectionEvent: %@", exception);
538
+ }
539
+ }
540
+
541
+ - (void)layoutSubviews
542
+ {
543
+ NSLog(@"[AdvancedTextView] layoutSubviews called");
544
+ @try {
545
+ [super layoutSubviews];
546
+ _textView.frame = self.bounds;
547
+ } @catch (NSException *exception) {
548
+ NSLog(@"[AdvancedTextView] Exception in layoutSubviews: %@", exception);
549
+ }
550
+ }
551
+
552
+ - (UIColor *)hexStringToColor:(NSString *)stringToConvert
553
+ {
554
+ @try {
555
+ if (!stringToConvert || [stringToConvert length] == 0) {
556
+ NSLog(@"[AdvancedTextView] Empty color string");
557
+ return nil;
558
+ }
559
+
560
+ NSString *noHashString = [stringToConvert stringByReplacingOccurrencesOfString:@"#" withString:@""];
561
+ NSScanner *stringScanner = [NSScanner scannerWithString:noHashString];
562
+
563
+ unsigned hex;
564
+ if (![stringScanner scanHexInt:&hex]) {
565
+ NSLog(@"[AdvancedTextView] Failed to parse hex color: %@", stringToConvert);
566
+ return nil;
567
+ }
568
+
569
+ int r = (hex >> 16) & 0xFF;
570
+ int g = (hex >> 8) & 0xFF;
571
+ int b = (hex) & 0xFF;
572
+
573
+ return [UIColor colorWithRed:r / 255.0f green:g / 255.0f blue:b / 255.0f alpha:1.0f];
574
+ } @catch (NSException *exception) {
575
+ NSLog(@"[AdvancedTextView] Exception in hexStringToColor: %@", exception);
576
+ return nil;
577
+ }
578
+ }
579
+
580
+ Class<RCTComponentViewProtocol> AdvancedTextViewCls(void)
581
+ {
582
+ NSLog(@"[AdvancedTextView] AdvancedTextViewCls called");
583
+ return AdvancedTextView.class;
584
+ }
585
+
586
+ - (void)dealloc
587
+ {
588
+ NSLog(@"[AdvancedTextView] dealloc called");
69
589
  }
70
590
 
71
591
  @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-advanced-text",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
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",
@@ -1,59 +0,0 @@
1
- package com.advancedtext
2
-
3
- import android.text.Layout
4
- import android.text.Selection
5
- import android.text.Spannable
6
- import android.text.method.BaseMovementMethod
7
- import android.text.style.ClickableSpan
8
- import android.view.MotionEvent
9
- import android.widget.TextView
10
-
11
- class ClickableMovementMethod : BaseMovementMethod() {
12
-
13
- companion object {
14
- private var sInstance: ClickableMovementMethod? = null
15
-
16
- fun getInstance(): ClickableMovementMethod {
17
- if (sInstance == null) {
18
- sInstance = ClickableMovementMethod()
19
- }
20
- return sInstance!!
21
- }
22
- }
23
-
24
- override fun canSelectArbitrarily(): Boolean = false
25
-
26
- override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean {
27
- val action = event.actionMasked
28
- if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
29
-
30
- var x = (event.x - widget.totalPaddingLeft + widget.scrollX).toInt()
31
- var y = (event.y - widget.totalPaddingTop + widget.scrollY).toInt()
32
-
33
- val layout: Layout = widget.layout
34
- val links: Array<ClickableSpan>? = if (y < 0 || y > layout.height) {
35
- null
36
- } else {
37
- val line = layout.getLineForVertical(y)
38
- if (x < layout.getLineLeft(line) || x > layout.getLineRight(line)) null
39
- else {
40
- val offset = layout.getOffsetForHorizontal(line, x.toFloat())
41
- buffer.getSpans(offset, offset, ClickableSpan::class.java)
42
- }
43
- }
44
-
45
- if (!links.isNullOrEmpty()) {
46
- if (action == MotionEvent.ACTION_UP) links[0].onClick(widget)
47
- else Selection.setSelection(buffer, buffer.getSpanStart(links[0]), buffer.getSpanEnd(links[0]))
48
- return true
49
- } else {
50
- Selection.removeSelection(buffer)
51
- }
52
- }
53
- return false
54
- }
55
-
56
- override fun initialize(widget: TextView, text: Spannable) {
57
- Selection.removeSelection(text)
58
- }
59
- }