react-native-typerich 0.1.0

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.
Files changed (74) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +37 -0
  3. package/TypeRichTextInput.podspec +20 -0
  4. package/android/build.gradle +99 -0
  5. package/android/generated/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/TypeRichTextInputViewManagerDelegate.java +100 -0
  6. package/android/generated/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/TypeRichTextInputViewManagerInterface.java +38 -0
  7. package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/ComponentDescriptors.cpp +22 -0
  8. package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/ComponentDescriptors.h +24 -0
  9. package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/EventEmitters.cpp +70 -0
  10. package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/EventEmitters.h +59 -0
  11. package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/Props.cpp +132 -0
  12. package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/Props.h +51 -0
  13. package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/ShadowNodes.cpp +17 -0
  14. package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/ShadowNodes.h +23 -0
  15. package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/States.cpp +16 -0
  16. package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/States.h +20 -0
  17. package/android/gradle.properties +5 -0
  18. package/android/src/main/AndroidManifest.xml +2 -0
  19. package/android/src/main/AndroidManifestNew.xml +2 -0
  20. package/android/src/main/java/com/typerich/MeasurementStore.kt +148 -0
  21. package/android/src/main/java/com/typerich/TypeRichTextInputView.kt +503 -0
  22. package/android/src/main/java/com/typerich/TypeRichTextInputViewLayoutManager.kt +30 -0
  23. package/android/src/main/java/com/typerich/TypeRichTextInputViewManager.kt +188 -0
  24. package/android/src/main/java/com/typerich/TypeRichTextInputViewPackage.kt +19 -0
  25. package/android/src/main/java/com/typerich/events/OnChangeSelectionEvent.kt +29 -0
  26. package/android/src/main/java/com/typerich/events/OnChangeTextEvent.kt +35 -0
  27. package/android/src/main/java/com/typerich/events/OnInputBlurEvent.kt +27 -0
  28. package/android/src/main/java/com/typerich/events/OnInputFocusEvent.kt +27 -0
  29. package/android/src/main/java/com/typerich/events/OnPasteImageEvent.kt +45 -0
  30. package/android/src/main/new_arch/CMakeLists.txt +73 -0
  31. package/android/src/main/new_arch/TypeRichTextInputViewSpec.cpp +22 -0
  32. package/android/src/main/new_arch/TypeRichTextInputViewSpec.h +26 -0
  33. package/android/src/main/new_arch/react/renderer/components/TypeRichTextInputView/TypeRichTextInputViewComponentDescriptor.h +36 -0
  34. package/android/src/main/new_arch/react/renderer/components/TypeRichTextInputView/TypeRichTextInputViewMeasurementManager.cpp +83 -0
  35. package/android/src/main/new_arch/react/renderer/components/TypeRichTextInputView/TypeRichTextInputViewMeasurementManager.h +25 -0
  36. package/android/src/main/new_arch/react/renderer/components/TypeRichTextInputView/TypeRichTextInputViewShadowNode.cpp +132 -0
  37. package/android/src/main/new_arch/react/renderer/components/TypeRichTextInputView/TypeRichTextInputViewShadowNode.h +54 -0
  38. package/android/src/main/new_arch/react/renderer/components/TypeRichTextInputView/TypeRichTextInputViewState.cpp +9 -0
  39. package/android/src/main/new_arch/react/renderer/components/TypeRichTextInputView/TypeRichTextInputViewState.h +28 -0
  40. package/android/src/main/new_arch/react/renderer/components/TypeRichTextInputView/conversions.h +21 -0
  41. package/ios/TypeRichTextInputView.h +14 -0
  42. package/ios/TypeRichTextInputView.mm +71 -0
  43. package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/ComponentDescriptors.cpp +22 -0
  44. package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/ComponentDescriptors.h +24 -0
  45. package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/EventEmitters.cpp +70 -0
  46. package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/EventEmitters.h +59 -0
  47. package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/Props.cpp +132 -0
  48. package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/Props.h +51 -0
  49. package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/RCTComponentViewHelpers.h +80 -0
  50. package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/ShadowNodes.cpp +17 -0
  51. package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/ShadowNodes.h +23 -0
  52. package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/States.cpp +16 -0
  53. package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/States.h +20 -0
  54. package/lib/module/TypeRichTextInput.js +50 -0
  55. package/lib/module/TypeRichTextInput.js.map +1 -0
  56. package/lib/module/TypeRichTextInputNativeComponent.ts +92 -0
  57. package/lib/module/index.js +5 -0
  58. package/lib/module/index.js.map +1 -0
  59. package/lib/module/package.json +1 -0
  60. package/lib/module/types/react-native-codegen.d.js +2 -0
  61. package/lib/module/types/react-native-codegen.d.js.map +1 -0
  62. package/lib/typescript/package.json +1 -0
  63. package/lib/typescript/src/TypeRichTextInput.d.ts +35 -0
  64. package/lib/typescript/src/TypeRichTextInput.d.ts.map +1 -0
  65. package/lib/typescript/src/TypeRichTextInputNativeComponent.d.ts +57 -0
  66. package/lib/typescript/src/TypeRichTextInputNativeComponent.d.ts.map +1 -0
  67. package/lib/typescript/src/index.d.ts +4 -0
  68. package/lib/typescript/src/index.d.ts.map +1 -0
  69. package/package.json +180 -0
  70. package/react-native.config.js +13 -0
  71. package/src/TypeRichTextInput.tsx +115 -0
  72. package/src/TypeRichTextInputNativeComponent.ts +92 -0
  73. package/src/index.tsx +6 -0
  74. package/src/types/react-native-codegen.d.ts +11 -0
@@ -0,0 +1,503 @@
1
+ package com.typerich
2
+
3
+ import android.content.Context
4
+ import android.content.ClipboardManager
5
+ import android.net.Uri
6
+ import android.graphics.BlendMode
7
+ import android.graphics.BlendModeColorFilter
8
+ import android.graphics.Color
9
+ import android.graphics.Rect
10
+ import android.graphics.text.LineBreaker
11
+ import android.os.Build
12
+ import android.text.Editable
13
+ import android.text.InputType
14
+ import android.text.TextWatcher
15
+ import android.util.AttributeSet
16
+ import android.util.TypedValue
17
+ import android.view.Gravity
18
+ import android.view.MotionEvent
19
+ import android.view.inputmethod.EditorInfo
20
+ import android.view.inputmethod.InputConnection
21
+ import android.view.inputmethod.InputMethodManager
22
+ import androidx.appcompat.widget.AppCompatEditText
23
+ import androidx.core.view.inputmethod.EditorInfoCompat
24
+ import androidx.core.view.inputmethod.InputConnectionCompat
25
+ import com.facebook.react.bridge.ReactContext
26
+ import com.facebook.react.common.ReactConstants
27
+ import com.facebook.react.uimanager.PixelUtil
28
+ import com.facebook.react.uimanager.StateWrapper
29
+ import com.facebook.react.uimanager.UIManagerHelper
30
+ import com.facebook.react.views.text.ReactTypefaceUtils.applyStyles
31
+ import com.facebook.react.views.text.ReactTypefaceUtils.parseFontStyle
32
+ import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight
33
+ import com.typerich.events.OnChangeTextEvent
34
+ import com.typerich.events.OnInputBlurEvent
35
+ import com.typerich.events.OnInputFocusEvent
36
+ import com.typerich.events.OnPasteImageEvent
37
+ import java.io.File
38
+ import kotlin.math.ceil
39
+
40
+ class TypeRichTextInputView : AppCompatEditText {
41
+ var stateWrapper: StateWrapper? = null
42
+
43
+ lateinit var layoutManager: TypeRichTextInputViewLayoutManager
44
+
45
+ var isDuringTransaction: Boolean = false
46
+ var isRemovingMany: Boolean = false
47
+ var scrollEnabled: Boolean = true
48
+
49
+ var experimentalSynchronousEvents: Boolean = false
50
+
51
+ var fontSize: Float? = null
52
+ private var autoFocus = false
53
+ private var typefaceDirty = false
54
+ private var didAttachToWindow = false
55
+ private var detectScrollMovement = false
56
+ private var fontFamily: String? = null
57
+ private var fontStyle: Int = ReactConstants.UNSET
58
+ private var fontWeight: Int = ReactConstants.UNSET
59
+ private var defaultValue: CharSequence? = null
60
+ private var defaultValueDirty: Boolean = false
61
+
62
+ private var inputMethodManager: InputMethodManager? = null
63
+
64
+ constructor(context: Context) : super(context) {
65
+ prepareComponent()
66
+ }
67
+
68
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
69
+ prepareComponent()
70
+ }
71
+
72
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
73
+ context,
74
+ attrs,
75
+ defStyleAttr
76
+ ) {
77
+ prepareComponent()
78
+ }
79
+
80
+ init {
81
+ inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
82
+ }
83
+
84
+ private fun prepareComponent() {
85
+ isSingleLine = true
86
+ isHorizontalScrollBarEnabled = false
87
+ isVerticalScrollBarEnabled = true
88
+ gravity = Gravity.TOP or Gravity.START
89
+ inputType = InputType.TYPE_CLASS_TEXT
90
+
91
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
92
+ breakStrategy = LineBreaker.BREAK_STRATEGY_HIGH_QUALITY
93
+ }
94
+
95
+ setPadding(0, 0, 0, 0)
96
+ setBackgroundColor(Color.TRANSPARENT)
97
+
98
+ layoutManager = TypeRichTextInputViewLayoutManager(this)
99
+
100
+ addTextChangedListener(object : TextWatcher {
101
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
102
+
103
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
104
+ if (!isDuringTransaction) {
105
+ val reactContext = context as ReactContext
106
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
107
+ val dispatcher =
108
+ UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
109
+
110
+ dispatcher?.dispatchEvent(
111
+ OnChangeTextEvent(
112
+ surfaceId,
113
+ id,
114
+ s?.toString() ?: "",
115
+ experimentalSynchronousEvents
116
+ )
117
+ )
118
+ }
119
+ layoutManager.invalidateLayout()
120
+
121
+ }
122
+ override fun afterTextChanged(s: Editable?) {}
123
+ })
124
+ }
125
+
126
+ // https://developer.android.com/develop/ui/views/touch-and-input/image-keyboard
127
+ // for gboard stickers and images
128
+ override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection? {
129
+ val ic = super.onCreateInputConnection(outAttrs) ?: return null
130
+
131
+ EditorInfoCompat.setContentMimeTypes(
132
+ outAttrs,
133
+ arrayOf("image/png", "image/jpg", "image/jpeg", "image/gif", "image/webp")
134
+ )
135
+
136
+ return InputConnectionCompat.createWrapper(ic, outAttrs, onCommitContent)
137
+ }
138
+
139
+ private val onCommitContent = InputConnectionCompat.OnCommitContentListener { info, flags, _ ->
140
+ try {
141
+ // request permission if needed
142
+ if ((flags and InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
143
+ try {
144
+ info.requestPermission()
145
+ } catch (ex: Exception) {
146
+ // permission failed
147
+ }
148
+ }
149
+
150
+ val uri = info.contentUri
151
+
152
+ // SAFE mime extraction: check mimeTypeCount; fallback if none
153
+ val mime = try {
154
+ val desc = info.description
155
+ if (desc != null && desc.mimeTypeCount > 0) {
156
+ desc.getMimeType(0) ?: "image/*"
157
+ } else {
158
+ "image/*"
159
+ }
160
+ } catch (ex: Exception) {
161
+ "image/*"
162
+ }
163
+
164
+ val meta = buildMetaForUri(uri, mime)
165
+ dispatchImagePasteEvent(meta)
166
+
167
+ if ((flags and InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
168
+ try { info.releasePermission() } catch (_: Exception) {}
169
+ }
170
+
171
+ true
172
+ } catch (e: Exception) {
173
+ e.printStackTrace()
174
+ false
175
+ }
176
+ }
177
+
178
+ // paste handler
179
+ override fun onTextContextMenuItem(id: Int): Boolean {
180
+ if (id == android.R.id.paste || id == android.R.id.pasteAsPlainText) {
181
+ val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
182
+ ?: return super.onTextContextMenuItem(id)
183
+
184
+ val clip = clipboard.primaryClip ?: return super.onTextContextMenuItem(id)
185
+ val item = clip.getItemAt(0)
186
+
187
+ // uri
188
+ item.uri?.let { uri ->
189
+ val mime = context.contentResolver.getType(uri) ?: "image/*"
190
+ dispatchImagePasteEvent(buildMetaForUri(uri, mime))
191
+ return true
192
+ }
193
+
194
+ // intent
195
+ item.intent?.data?.let { uri ->
196
+ val mime = context.contentResolver.getType(uri) ?: "image/*"
197
+ dispatchImagePasteEvent(buildMetaForUri(uri, mime))
198
+ return true
199
+ }
200
+
201
+ // text
202
+ val text = item.coerceToText(context).toString()
203
+ this.append(text)
204
+ return true
205
+ }
206
+
207
+ return super.onTextContextMenuItem(id)
208
+ }
209
+
210
+ // --- helper: convert content URI to cached file metadata ---
211
+ private data class PasteImageMeta(val fileName: String, val fileSize: Long, val type: String, val uri: String)
212
+
213
+ private fun buildMetaForUri(srcUri: Uri, mime: String): PasteImageMeta {
214
+ val ext = when (mime) {
215
+ "image/png" -> ".png"
216
+ "image/jpeg", "image/jpg" -> ".jpg"
217
+ "image/webp" -> ".webp"
218
+ "image/gif" -> ".gif"
219
+ else -> ".bin"
220
+ }
221
+
222
+ val temp = File(context.cacheDir, "typerich_${System.nanoTime()}$ext")
223
+ context.contentResolver.openInputStream(srcUri)?.use { input ->
224
+ temp.outputStream().use { out -> input.copyTo(out) }
225
+ }
226
+
227
+ return PasteImageMeta(
228
+ fileName = temp.name,
229
+ fileSize = temp.length(),
230
+ type = mime,
231
+ uri = Uri.fromFile(temp).toString()
232
+ )
233
+ }
234
+
235
+ private fun dispatchImagePasteEvent(meta: PasteImageMeta) {
236
+ val reactContext = context as ReactContext
237
+ val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
238
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
239
+ try {
240
+ dispatcher?.dispatchEvent(
241
+ OnPasteImageEvent(surfaceId, id, meta.uri,meta.type,meta.fileName,meta.fileSize.toDouble(),null,experimentalSynchronousEvents)
242
+ )
243
+ } catch (e: Exception) {
244
+ e.printStackTrace()
245
+ }
246
+ }
247
+
248
+ override fun onTouchEvent(ev: MotionEvent): Boolean {
249
+ when (ev.action) {
250
+ MotionEvent.ACTION_DOWN -> {
251
+ detectScrollMovement = true
252
+ parent.requestDisallowInterceptTouchEvent(true)
253
+ }
254
+
255
+ MotionEvent.ACTION_MOVE ->
256
+ if (detectScrollMovement) {
257
+ if (!canScrollVertically(-1) &&
258
+ !canScrollVertically(1) &&
259
+ !canScrollHorizontally(-1) &&
260
+ !canScrollHorizontally(1)
261
+ ) {
262
+ parent.requestDisallowInterceptTouchEvent(false)
263
+ }
264
+ detectScrollMovement = false
265
+ }
266
+ }
267
+
268
+ return super.onTouchEvent(ev)
269
+ }
270
+
271
+ override fun canScrollVertically(direction: Int): Boolean {
272
+ return scrollEnabled
273
+ }
274
+
275
+ override fun canScrollHorizontally(direction: Int): Boolean {
276
+ return scrollEnabled
277
+ }
278
+
279
+ override fun onSelectionChanged(selStart: Int, selEnd: Int) {
280
+ super.onSelectionChanged(selStart, selEnd)
281
+ // you can later dispatch OnChangeSelectionEvent here if needed
282
+ }
283
+
284
+ override fun clearFocus() {
285
+ super.clearFocus()
286
+ inputMethodManager?.hideSoftInputFromWindow(windowToken, 0)
287
+ }
288
+
289
+ override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
290
+ super.onFocusChanged(focused, direction, previouslyFocusedRect)
291
+ val reactContext = context as ReactContext
292
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
293
+ val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
294
+
295
+ if (focused) {
296
+ dispatcher?.dispatchEvent(
297
+ OnInputFocusEvent(
298
+ surfaceId,
299
+ id,
300
+ experimentalSynchronousEvents
301
+ )
302
+ )
303
+ } else {
304
+ dispatcher?.dispatchEvent(
305
+ OnInputBlurEvent(
306
+ surfaceId,
307
+ id,
308
+ experimentalSynchronousEvents
309
+ )
310
+ )
311
+ }
312
+ }
313
+
314
+ fun requestFocusProgrammatically() {
315
+ requestFocus()
316
+ inputMethodManager?.showSoftInput(this, 0)
317
+ setSelection(text?.length ?: 0)
318
+ }
319
+
320
+ fun setMultiline(enabled: Boolean) {
321
+ isSingleLine = !enabled
322
+ if (enabled) {
323
+ inputType = inputType or InputType.TYPE_TEXT_FLAG_MULTI_LINE
324
+ } else {
325
+ inputType = inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE.inv()
326
+ }
327
+ }
328
+ fun setNumberOfLines(lines: Int) {
329
+ maxLines = lines
330
+ minLines = 1
331
+
332
+ // if (lines > 0) {
333
+ // setLines(lines) // Only if you want fixed height
334
+ // isVerticalScrollBarEnabled = true
335
+ // }
336
+ }
337
+
338
+
339
+ fun setValue(value: CharSequence?) {
340
+ if (value == null) return
341
+
342
+ runAsATransaction {
343
+ setText(value.toString())
344
+ setSelection(text?.length ?: 0)
345
+ }
346
+ }
347
+
348
+ fun setAutoFocus(autoFocus: Boolean) {
349
+ this.autoFocus = autoFocus
350
+ }
351
+
352
+ fun setPlaceholder(placeholder: String?) {
353
+ if (placeholder == null) return
354
+ hint = placeholder
355
+ }
356
+
357
+ fun setPlaceholderTextColor(colorInt: Int?) {
358
+ if (colorInt == null) return
359
+ setHintTextColor(colorInt)
360
+ }
361
+
362
+ fun setSelectionColor(colorInt: Int?) {
363
+ if (colorInt == null) return
364
+ highlightColor = colorInt
365
+ }
366
+
367
+ fun setCursorColor(colorInt: Int?) {
368
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
369
+ val cursorDrawable = textCursorDrawable ?: return
370
+
371
+ if (colorInt != null) {
372
+ cursorDrawable.colorFilter =
373
+ BlendModeColorFilter(colorInt, BlendMode.SRC_IN)
374
+ } else {
375
+ cursorDrawable.clearColorFilter()
376
+ }
377
+
378
+ textCursorDrawable = cursorDrawable
379
+ }
380
+ }
381
+
382
+ fun setColor(colorInt: Int?) {
383
+ if (colorInt == null) {
384
+ setTextColor(Color.BLACK)
385
+ return
386
+ }
387
+ setTextColor(colorInt)
388
+ }
389
+
390
+ fun setFontSize(size: Float) {
391
+ if (size == 0f) return
392
+
393
+ val sizePx = ceil(PixelUtil.toPixelFromSP(size))
394
+ fontSize = sizePx
395
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, sizePx)
396
+ }
397
+
398
+ fun setFontFamily(family: String?) {
399
+ if (family != fontFamily) {
400
+ fontFamily = family
401
+ typefaceDirty = true
402
+ }
403
+ }
404
+
405
+ fun setFontWeight(weight: String?) {
406
+ val fontWeight = parseFontWeight(weight)
407
+ if (fontWeight != fontStyle) {
408
+ this.fontWeight = fontWeight
409
+ typefaceDirty = true
410
+ }
411
+ }
412
+
413
+ fun setFontStyle(style: String?) {
414
+ val fontStyle = parseFontStyle(style)
415
+ if (fontStyle != this.fontStyle) {
416
+ this.fontStyle = fontStyle
417
+ typefaceDirty = true
418
+ }
419
+ }
420
+
421
+ fun setAutoCapitalize(flagName: String?) {
422
+ val flag = when (flagName) {
423
+ "none" -> InputType.TYPE_NULL
424
+ "sentences" -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
425
+ "words" -> InputType.TYPE_TEXT_FLAG_CAP_WORDS
426
+ "characters" -> InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
427
+ else -> InputType.TYPE_NULL
428
+ }
429
+
430
+ inputType =
431
+ (inputType and
432
+ InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS.inv() and
433
+ InputType.TYPE_TEXT_FLAG_CAP_WORDS.inv() and
434
+ InputType.TYPE_TEXT_FLAG_CAP_SENTENCES.inv()
435
+ ) or if (flag == InputType.TYPE_NULL) 0 else flag
436
+ }
437
+
438
+ fun setSecureTextEntry(isSecure: Boolean) {
439
+ transformationMethod =
440
+ if (isSecure)
441
+ android.text.method.PasswordTransformationMethod.getInstance()
442
+ else
443
+ null
444
+
445
+ // Prevent text from showing in suggestions/autofill if secure
446
+ isLongClickable = !isSecure
447
+ setTextIsSelectable(!isSecure)
448
+ }
449
+
450
+
451
+ override fun isLayoutRequested(): Boolean {
452
+ return false
453
+ }
454
+
455
+ fun afterUpdateTransaction() {
456
+ updateTypeface()
457
+ updateDefaultValue()
458
+ }
459
+
460
+ fun setDefaultValue(value: CharSequence?) {
461
+ defaultValue = value
462
+ defaultValueDirty = true
463
+ }
464
+
465
+ private fun updateDefaultValue() {
466
+ if (!defaultValueDirty) return
467
+ defaultValueDirty = false
468
+ setText(defaultValue?.toString() ?: "")
469
+ }
470
+
471
+ private fun updateTypeface() {
472
+ if (!typefaceDirty) return
473
+ typefaceDirty = false
474
+
475
+ val newTypeface =
476
+ applyStyles(typeface, fontStyle, fontWeight, fontFamily, context.assets)
477
+ typeface = newTypeface
478
+ paint.typeface = newTypeface
479
+ }
480
+
481
+ fun runAsATransaction(block: () -> Unit) {
482
+ try {
483
+ isDuringTransaction = true
484
+ block()
485
+ } finally {
486
+ isDuringTransaction = false
487
+ }
488
+ }
489
+
490
+ override fun onAttachedToWindow() {
491
+ super.onAttachedToWindow()
492
+
493
+ if (autoFocus && !didAttachToWindow) {
494
+ requestFocusProgrammatically()
495
+ }
496
+
497
+ didAttachToWindow = true
498
+ }
499
+
500
+ companion object {
501
+ const val CLIPBOARD_TAG = "react-native-typerich-clipboard"
502
+ }
503
+ }
@@ -0,0 +1,30 @@
1
+ package com.typerich
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+
5
+ class TypeRichTextInputViewLayoutManager(private val view: TypeRichTextInputView) {
6
+ private var forceHeightRecalculationCounter: Int = 0
7
+
8
+ fun invalidateLayout() {
9
+ val text = view.text
10
+ val paint = view.paint
11
+
12
+ val needUpdate = MeasurementStore.store(view.id, text, paint)
13
+ if (!needUpdate) return
14
+
15
+ val counter = forceHeightRecalculationCounter
16
+ forceHeightRecalculationCounter++
17
+
18
+ val lineCount = view.layout?.lineCount ?: 1
19
+
20
+ val state = Arguments.createMap()
21
+ state.putInt("forceHeightRecalculationCounter", counter)
22
+ state.putInt("lineCount", lineCount)
23
+
24
+ view.stateWrapper?.updateState(state)
25
+ }
26
+
27
+ fun releaseMeasurementStore() {
28
+ MeasurementStore.release(view.id)
29
+ }
30
+ }
@@ -0,0 +1,188 @@
1
+ package com.typerich
2
+
3
+ import android.content.Context
4
+ import com.facebook.react.bridge.ReadableMap
5
+ import com.facebook.react.module.annotations.ReactModule
6
+ import com.facebook.react.uimanager.*
7
+ import com.facebook.react.uimanager.annotations.ReactProp
8
+ import com.facebook.react.viewmanagers.TypeRichTextInputViewManagerDelegate
9
+ import com.facebook.react.viewmanagers.TypeRichTextInputViewManagerInterface
10
+ import com.facebook.yoga.YogaMeasureMode
11
+ import com.typerich.events.OnInputBlurEvent
12
+ import com.typerich.events.OnInputFocusEvent
13
+ import com.typerich.events.OnChangeSelectionEvent
14
+ import com.typerich.events.OnChangeTextEvent
15
+ import com.typerich.events.OnPasteImageEvent
16
+
17
+ @ReactModule(name = TypeRichTextInputViewManager.NAME)
18
+ class TypeRichTextInputViewManager :
19
+ SimpleViewManager<TypeRichTextInputView>(),
20
+ TypeRichTextInputViewManagerInterface<TypeRichTextInputView> {
21
+
22
+ private val mDelegate: ViewManagerDelegate<TypeRichTextInputView> =
23
+ TypeRichTextInputViewManagerDelegate(this)
24
+
25
+ override fun getDelegate(): ViewManagerDelegate<TypeRichTextInputView>? = mDelegate
26
+
27
+ override fun getName(): String = NAME
28
+
29
+ override fun createViewInstance(context: ThemedReactContext): TypeRichTextInputView {
30
+ return TypeRichTextInputView(context)
31
+ }
32
+
33
+ override fun updateState(
34
+ view: TypeRichTextInputView,
35
+ props: ReactStylesDiffMap?,
36
+ stateWrapper: StateWrapper?
37
+ ): Any? {
38
+ view.stateWrapper = stateWrapper
39
+ return super.updateState(view, props, stateWrapper)
40
+ }
41
+
42
+ override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> =
43
+ mutableMapOf(
44
+ OnInputFocusEvent.EVENT_NAME to mapOf("registrationName" to OnInputFocusEvent.EVENT_NAME),
45
+ OnInputBlurEvent.EVENT_NAME to mapOf("registrationName" to OnInputBlurEvent.EVENT_NAME),
46
+ OnChangeTextEvent.EVENT_NAME to mapOf("registrationName" to OnChangeTextEvent.EVENT_NAME),
47
+ OnChangeSelectionEvent.EVENT_NAME to mapOf("registrationName" to OnChangeSelectionEvent.EVENT_NAME),
48
+ OnPasteImageEvent.EVENT_NAME to mapOf("registrationName" to OnPasteImageEvent.EVENT_NAME),
49
+ )
50
+
51
+ @ReactProp(name = "defaultValue")
52
+ override fun setDefaultValue(view: TypeRichTextInputView?, value: String?) {
53
+ view?.setDefaultValue(value)
54
+ }
55
+
56
+ @ReactProp(name = "color")
57
+ override fun setColor(view: TypeRichTextInputView, value: Int?) {
58
+ if (value != null) view.setTextColor(value)
59
+ }
60
+
61
+ @ReactProp(name = "placeholder")
62
+ override fun setPlaceholder(view: TypeRichTextInputView?, value: String?) {
63
+ view?.setPlaceholder(value)
64
+ }
65
+
66
+ @ReactProp(name = "placeholderTextColor", customType = "Color")
67
+ override fun setPlaceholderTextColor(view: TypeRichTextInputView?, color: Int?) {
68
+ view?.setPlaceholderTextColor(color)
69
+ }
70
+
71
+ @ReactProp(name = "cursorColor", customType = "Color")
72
+ override fun setCursorColor(view: TypeRichTextInputView?, color: Int?) {
73
+ view?.setCursorColor(color)
74
+ }
75
+
76
+ @ReactProp(name = "selectionColor", customType = "Color")
77
+ override fun setSelectionColor(view: TypeRichTextInputView?, color: Int?) {
78
+ view?.setSelectionColor(color)
79
+ }
80
+
81
+ @ReactProp(name = "autoFocus", defaultBoolean = false)
82
+ override fun setAutoFocus(view: TypeRichTextInputView?, autoFocus: Boolean) {
83
+ view?.setAutoFocus(autoFocus)
84
+ }
85
+
86
+ @ReactProp(name = "editable", defaultBoolean = true)
87
+ override fun setEditable(view: TypeRichTextInputView?, editable: Boolean) {
88
+ view?.isEnabled = editable
89
+ }
90
+
91
+ @ReactProp(name = "fontSize", defaultFloat = ViewDefaults.FONT_SIZE_SP)
92
+ override fun setFontSize(view: TypeRichTextInputView?, size: Float) {
93
+ view?.setFontSize(size)
94
+ }
95
+
96
+ @ReactProp(name = "fontFamily")
97
+ override fun setFontFamily(view: TypeRichTextInputView?, family: String?) {
98
+ view?.setFontFamily(family)
99
+ }
100
+
101
+ @ReactProp(name = "fontWeight")
102
+ override fun setFontWeight(view: TypeRichTextInputView?, weight: String?) {
103
+ view?.setFontWeight(weight)
104
+ }
105
+
106
+ @ReactProp(name = "fontStyle")
107
+ override fun setFontStyle(view: TypeRichTextInputView?, style: String?) {
108
+ view?.setFontStyle(style)
109
+ }
110
+
111
+ @ReactProp(name = "scrollEnabled")
112
+ override fun setScrollEnabled(view: TypeRichTextInputView, scrollEnabled: Boolean) {
113
+ view.scrollEnabled = scrollEnabled
114
+ }
115
+
116
+ @ReactProp(name = "multiline")
117
+ override fun setMultiline(view: TypeRichTextInputView?, value: Boolean) {
118
+ view?.setMultiline(value)
119
+ }
120
+
121
+ @ReactProp(name = "numberOfLines")
122
+ override fun setNumberOfLines(view: TypeRichTextInputView?, lines: Int) {
123
+ view?.setNumberOfLines(lines)
124
+ }
125
+
126
+ @ReactProp(name = "secureTextEntry")
127
+ override fun setSecureTextEntry(view: TypeRichTextInputView?, value: Boolean) {
128
+ view?.setSecureTextEntry(value)
129
+ }
130
+
131
+
132
+ override fun onAfterUpdateTransaction(view: TypeRichTextInputView) {
133
+ super.onAfterUpdateTransaction(view)
134
+ view.afterUpdateTransaction()
135
+ }
136
+
137
+ override fun setPadding(
138
+ view: TypeRichTextInputView?,
139
+ left: Int,
140
+ top: Int,
141
+ right: Int,
142
+ bottom: Int
143
+ ) {
144
+ super.setPadding(view, left, top, right, bottom)
145
+ view?.setPadding(left, top, right, bottom)
146
+ }
147
+
148
+ override fun setAutoCapitalize(view: TypeRichTextInputView?, flag: String?) {
149
+ view?.setAutoCapitalize(flag)
150
+ }
151
+
152
+ override fun setAndroidExperimentalSynchronousEvents(
153
+ view: TypeRichTextInputView?,
154
+ value: Boolean
155
+ ) {
156
+ view?.experimentalSynchronousEvents = value
157
+ }
158
+
159
+ override fun focus(view: TypeRichTextInputView?) {
160
+ view?.requestFocusProgrammatically()
161
+ }
162
+
163
+ override fun blur(view: TypeRichTextInputView?) {
164
+ view?.clearFocus()
165
+ }
166
+
167
+ override fun setValue(view: TypeRichTextInputView?, text: String) {
168
+ }
169
+
170
+ override fun measure(
171
+ context: Context,
172
+ localData: ReadableMap?,
173
+ props: ReadableMap?,
174
+ state: ReadableMap?,
175
+ width: Float,
176
+ widthMode: YogaMeasureMode?,
177
+ height: Float,
178
+ heightMode: YogaMeasureMode?,
179
+ attachmentsPositions: FloatArray?
180
+ ): Long {
181
+ val id = localData?.getInt("viewTag")
182
+ return MeasurementStore.getMeasureById(context, id, width, props)
183
+ }
184
+
185
+ companion object {
186
+ const val NAME = "TypeRichTextInputView"
187
+ }
188
+ }