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.
- package/LICENSE +28 -0
- package/README.md +37 -0
- package/TypeRichTextInput.podspec +20 -0
- package/android/build.gradle +99 -0
- package/android/generated/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/TypeRichTextInputViewManagerDelegate.java +100 -0
- package/android/generated/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/TypeRichTextInputViewManagerInterface.java +38 -0
- package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/ComponentDescriptors.cpp +22 -0
- package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/ComponentDescriptors.h +24 -0
- package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/EventEmitters.cpp +70 -0
- package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/EventEmitters.h +59 -0
- package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/Props.cpp +132 -0
- package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/Props.h +51 -0
- package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/ShadowNodes.cpp +17 -0
- package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/ShadowNodes.h +23 -0
- package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/States.cpp +16 -0
- package/android/generated/android/app/build/generated/source/codegen/jni/react/renderer/components/TypeRichTextInputViewSpec/States.h +20 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/typerich/MeasurementStore.kt +148 -0
- package/android/src/main/java/com/typerich/TypeRichTextInputView.kt +503 -0
- package/android/src/main/java/com/typerich/TypeRichTextInputViewLayoutManager.kt +30 -0
- package/android/src/main/java/com/typerich/TypeRichTextInputViewManager.kt +188 -0
- package/android/src/main/java/com/typerich/TypeRichTextInputViewPackage.kt +19 -0
- package/android/src/main/java/com/typerich/events/OnChangeSelectionEvent.kt +29 -0
- package/android/src/main/java/com/typerich/events/OnChangeTextEvent.kt +35 -0
- package/android/src/main/java/com/typerich/events/OnInputBlurEvent.kt +27 -0
- package/android/src/main/java/com/typerich/events/OnInputFocusEvent.kt +27 -0
- package/android/src/main/java/com/typerich/events/OnPasteImageEvent.kt +45 -0
- package/android/src/main/new_arch/CMakeLists.txt +73 -0
- package/android/src/main/new_arch/TypeRichTextInputViewSpec.cpp +22 -0
- package/android/src/main/new_arch/TypeRichTextInputViewSpec.h +26 -0
- package/android/src/main/new_arch/react/renderer/components/TypeRichTextInputView/TypeRichTextInputViewComponentDescriptor.h +36 -0
- package/android/src/main/new_arch/react/renderer/components/TypeRichTextInputView/TypeRichTextInputViewMeasurementManager.cpp +83 -0
- package/android/src/main/new_arch/react/renderer/components/TypeRichTextInputView/TypeRichTextInputViewMeasurementManager.h +25 -0
- package/android/src/main/new_arch/react/renderer/components/TypeRichTextInputView/TypeRichTextInputViewShadowNode.cpp +132 -0
- package/android/src/main/new_arch/react/renderer/components/TypeRichTextInputView/TypeRichTextInputViewShadowNode.h +54 -0
- package/android/src/main/new_arch/react/renderer/components/TypeRichTextInputView/TypeRichTextInputViewState.cpp +9 -0
- package/android/src/main/new_arch/react/renderer/components/TypeRichTextInputView/TypeRichTextInputViewState.h +28 -0
- package/android/src/main/new_arch/react/renderer/components/TypeRichTextInputView/conversions.h +21 -0
- package/ios/TypeRichTextInputView.h +14 -0
- package/ios/TypeRichTextInputView.mm +71 -0
- package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/ComponentDescriptors.cpp +22 -0
- package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/ComponentDescriptors.h +24 -0
- package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/EventEmitters.cpp +70 -0
- package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/EventEmitters.h +59 -0
- package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/Props.cpp +132 -0
- package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/Props.h +51 -0
- package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/RCTComponentViewHelpers.h +80 -0
- package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/ShadowNodes.cpp +17 -0
- package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/ShadowNodes.h +23 -0
- package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/States.cpp +16 -0
- package/ios/generated/build/generated/ios/react/renderer/components/TypeRichTextInputViewSpec/States.h +20 -0
- package/lib/module/TypeRichTextInput.js +50 -0
- package/lib/module/TypeRichTextInput.js.map +1 -0
- package/lib/module/TypeRichTextInputNativeComponent.ts +92 -0
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types/react-native-codegen.d.js +2 -0
- package/lib/module/types/react-native-codegen.d.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/TypeRichTextInput.d.ts +35 -0
- package/lib/typescript/src/TypeRichTextInput.d.ts.map +1 -0
- package/lib/typescript/src/TypeRichTextInputNativeComponent.d.ts +57 -0
- package/lib/typescript/src/TypeRichTextInputNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +180 -0
- package/react-native.config.js +13 -0
- package/src/TypeRichTextInput.tsx +115 -0
- package/src/TypeRichTextInputNativeComponent.ts +92 -0
- package/src/index.tsx +6 -0
- 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
|
+
}
|