@apollohg/react-native-prose-editor 0.5.16 → 0.5.17
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/android/src/main/java/com/apollohg/editor/EditorEditText.kt +1396 -143
- package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +403 -59
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +1666 -79
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +209 -87
- package/android/src/main/java/com/apollohg/editor/PositionBridge.kt +27 -0
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +58 -9
- package/dist/NativeEditorBridge.d.ts +34 -1
- package/dist/NativeEditorBridge.js +243 -83
- package/dist/NativeRichTextEditor.js +998 -137
- package/dist/addons.d.ts +7 -0
- package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
- package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
- package/ios/NativeEditorExpoView.swift +830 -17
- package/ios/NativeEditorModule.swift +304 -108
- package/ios/PositionBridge.swift +24 -1
- package/ios/RichTextEditorView.swift +715 -89
- package/package.json +2 -1
- package/rust/android/arm64-v8a/libeditor_core.so +0 -0
- package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
- package/rust/android/x86_64/libeditor_core.so +0 -0
|
@@ -5,7 +5,10 @@ import android.content.Context
|
|
|
5
5
|
import android.content.ContextWrapper
|
|
6
6
|
import android.graphics.Rect
|
|
7
7
|
import android.graphics.RectF
|
|
8
|
+
import android.os.Handler
|
|
9
|
+
import android.os.Looper
|
|
8
10
|
import android.os.SystemClock
|
|
11
|
+
import android.util.Log
|
|
9
12
|
import android.view.Gravity
|
|
10
13
|
import android.view.MotionEvent
|
|
11
14
|
import android.view.View
|
|
@@ -20,8 +23,334 @@ import expo.modules.kotlin.viewevent.EventDispatcher
|
|
|
20
23
|
import expo.modules.kotlin.views.ExpoView
|
|
21
24
|
import org.json.JSONArray
|
|
22
25
|
import org.json.JSONObject
|
|
26
|
+
import java.lang.ref.WeakReference
|
|
27
|
+
import java.util.concurrent.CountDownLatch
|
|
28
|
+
import java.util.concurrent.TimeUnit
|
|
29
|
+
import java.util.WeakHashMap
|
|
30
|
+
import java.util.concurrent.atomic.AtomicInteger
|
|
31
|
+
import java.util.concurrent.atomic.AtomicReference
|
|
23
32
|
import uniffi.editor_core.*
|
|
24
33
|
|
|
34
|
+
private const val DESTROY_INVALIDATION_AWAIT_TIMEOUT_MS = 250L
|
|
35
|
+
|
|
36
|
+
private class WeakNativeEditorExpoView private constructor(
|
|
37
|
+
val view: WeakReference<NativeEditorExpoView?>
|
|
38
|
+
) {
|
|
39
|
+
constructor(view: NativeEditorExpoView) : this(WeakReference(view))
|
|
40
|
+
|
|
41
|
+
companion object {
|
|
42
|
+
fun cleared(): WeakNativeEditorExpoView =
|
|
43
|
+
WeakNativeEditorExpoView(WeakReference<NativeEditorExpoView?>(null))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
internal object NativeEditorViewRegistry {
|
|
48
|
+
private data class CommandPreparationSnapshot(
|
|
49
|
+
val view: NativeEditorExpoView?,
|
|
50
|
+
val isDetached: Boolean,
|
|
51
|
+
val isDestroyed: Boolean
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
private val viewsByEditorId = mutableMapOf<Long, WeakNativeEditorExpoView>()
|
|
55
|
+
private val detachedEditorOwnersByEditorId = mutableMapOf<Long, WeakNativeEditorExpoView>()
|
|
56
|
+
private val destroyedEditorIds = mutableSetOf<Long>()
|
|
57
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
58
|
+
|
|
59
|
+
@Synchronized
|
|
60
|
+
fun markEditorCreated(editorId: Long) {
|
|
61
|
+
if (editorId == 0L) return
|
|
62
|
+
destroyedEditorIds.remove(editorId)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@Synchronized
|
|
66
|
+
fun register(editorId: Long, view: NativeEditorExpoView): Boolean {
|
|
67
|
+
if (editorId == 0L) return false
|
|
68
|
+
if (destroyedEditorIds.contains(editorId)) return false
|
|
69
|
+
viewsByEditorId[editorId] = WeakNativeEditorExpoView(view)
|
|
70
|
+
detachedEditorOwnersByEditorId.remove(editorId)
|
|
71
|
+
return true
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@Synchronized
|
|
75
|
+
fun unregister(
|
|
76
|
+
editorId: Long,
|
|
77
|
+
view: NativeEditorExpoView,
|
|
78
|
+
blockCommandsUntilRegistered: Boolean = false
|
|
79
|
+
) {
|
|
80
|
+
if (editorId == 0L) return
|
|
81
|
+
val registeredView = viewsByEditorId[editorId]?.view?.get()
|
|
82
|
+
if (registeredView === view) {
|
|
83
|
+
viewsByEditorId.remove(editorId)
|
|
84
|
+
}
|
|
85
|
+
if (blockCommandsUntilRegistered) {
|
|
86
|
+
detachedEditorOwnersByEditorId[editorId] = WeakNativeEditorExpoView(view)
|
|
87
|
+
} else {
|
|
88
|
+
val detachedOwner = detachedEditorOwnersByEditorId[editorId]?.view?.get()
|
|
89
|
+
if (registeredView === view || detachedOwner === view) {
|
|
90
|
+
detachedEditorOwnersByEditorId.remove(editorId)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@Synchronized
|
|
96
|
+
fun isDestroyed(editorId: Long): Boolean = destroyedEditorIds.contains(editorId)
|
|
97
|
+
|
|
98
|
+
@Synchronized
|
|
99
|
+
internal fun forceDetachedOwnerClearedForTesting(editorId: Long) {
|
|
100
|
+
detachedEditorOwnersByEditorId[editorId] = WeakNativeEditorExpoView.cleared()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fun invalidateDestroyedEditor(editorId: Long) {
|
|
104
|
+
if (editorId == 0L) return
|
|
105
|
+
val affectedViews = synchronized(this) {
|
|
106
|
+
destroyedEditorIds.add(editorId)
|
|
107
|
+
val views = listOfNotNull(
|
|
108
|
+
viewsByEditorId.remove(editorId)?.view?.get(),
|
|
109
|
+
detachedEditorOwnersByEditorId.remove(editorId)?.view?.get()
|
|
110
|
+
).distinct()
|
|
111
|
+
views
|
|
112
|
+
}
|
|
113
|
+
if (affectedViews.isEmpty()) return
|
|
114
|
+
val invalidate = Runnable {
|
|
115
|
+
affectedViews.forEach { view ->
|
|
116
|
+
view.handleEditorDestroyed(editorId)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
120
|
+
invalidate.run()
|
|
121
|
+
} else {
|
|
122
|
+
val latch = CountDownLatch(1)
|
|
123
|
+
val posted = mainHandler.post {
|
|
124
|
+
try {
|
|
125
|
+
invalidate.run()
|
|
126
|
+
} finally {
|
|
127
|
+
latch.countDown()
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (!posted) return
|
|
131
|
+
try {
|
|
132
|
+
latch.await(DESTROY_INVALIDATION_AWAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
|
133
|
+
} catch (_: InterruptedException) {
|
|
134
|
+
Thread.currentThread().interrupt()
|
|
135
|
+
} catch (_: Throwable) {
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
fun prepareForCommandJSON(editorId: Long): String {
|
|
142
|
+
val prepare = {
|
|
143
|
+
val snapshot = synchronized(this) {
|
|
144
|
+
val isDestroyed = destroyedEditorIds.contains(editorId)
|
|
145
|
+
if (isDestroyed) {
|
|
146
|
+
return@synchronized CommandPreparationSnapshot(
|
|
147
|
+
view = null,
|
|
148
|
+
isDetached = false,
|
|
149
|
+
isDestroyed = true
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
val candidate = viewsByEditorId[editorId]?.view?.get()
|
|
153
|
+
if (candidate == null) {
|
|
154
|
+
viewsByEditorId.remove(editorId)
|
|
155
|
+
}
|
|
156
|
+
val detachedOwner = detachedEditorOwnersByEditorId[editorId]?.view?.get()
|
|
157
|
+
val isDetached = if (detachedOwner == null) {
|
|
158
|
+
detachedEditorOwnersByEditorId.remove(editorId)
|
|
159
|
+
false
|
|
160
|
+
} else {
|
|
161
|
+
true
|
|
162
|
+
}
|
|
163
|
+
CommandPreparationSnapshot(
|
|
164
|
+
view = candidate,
|
|
165
|
+
isDetached = isDetached,
|
|
166
|
+
isDestroyed = false
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
snapshot.view?.prepareForEditorCommandJSON()
|
|
170
|
+
?: commandPreparationJSON(
|
|
171
|
+
ready = !snapshot.isDetached && !snapshot.isDestroyed,
|
|
172
|
+
blockedReason = if (snapshot.isDestroyed) {
|
|
173
|
+
"destroyed"
|
|
174
|
+
} else if (snapshot.isDetached) {
|
|
175
|
+
"detached"
|
|
176
|
+
} else {
|
|
177
|
+
null
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
183
|
+
return prepare()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
val result = AtomicReference(commandPreparationJSON(ready = false, blockedReason = "unknown"))
|
|
187
|
+
val state = AtomicInteger(PREFLIGHT_STATE_QUEUED)
|
|
188
|
+
val latch = CountDownLatch(1)
|
|
189
|
+
if (!mainHandler.post {
|
|
190
|
+
try {
|
|
191
|
+
if (state.compareAndSet(PREFLIGHT_STATE_QUEUED, PREFLIGHT_STATE_RUNNING)) {
|
|
192
|
+
result.set(prepare())
|
|
193
|
+
state.set(PREFLIGHT_STATE_DONE)
|
|
194
|
+
}
|
|
195
|
+
} finally {
|
|
196
|
+
latch.countDown()
|
|
197
|
+
}
|
|
198
|
+
}) {
|
|
199
|
+
return commandPreparationJSON(ready = false, blockedReason = "unknown")
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
if (!latch.await(DESTROY_INVALIDATION_AWAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
|
|
203
|
+
if (state.compareAndSet(PREFLIGHT_STATE_QUEUED, PREFLIGHT_STATE_CANCELLED)) {
|
|
204
|
+
return commandPreparationJSON(ready = false, blockedReason = "unknown")
|
|
205
|
+
}
|
|
206
|
+
if (state.get() == PREFLIGHT_STATE_RUNNING) {
|
|
207
|
+
latch.await()
|
|
208
|
+
return result.get()
|
|
209
|
+
}
|
|
210
|
+
return commandPreparationJSON(ready = false, blockedReason = "unknown")
|
|
211
|
+
}
|
|
212
|
+
} catch (_: InterruptedException) {
|
|
213
|
+
var interrupted = true
|
|
214
|
+
if (state.compareAndSet(PREFLIGHT_STATE_QUEUED, PREFLIGHT_STATE_CANCELLED)) {
|
|
215
|
+
Thread.currentThread().interrupt()
|
|
216
|
+
return commandPreparationJSON(ready = false, blockedReason = "unknown")
|
|
217
|
+
}
|
|
218
|
+
while (state.get() == PREFLIGHT_STATE_RUNNING) {
|
|
219
|
+
try {
|
|
220
|
+
latch.await()
|
|
221
|
+
break
|
|
222
|
+
} catch (_: InterruptedException) {
|
|
223
|
+
interrupted = true
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (interrupted) {
|
|
227
|
+
Thread.currentThread().interrupt()
|
|
228
|
+
}
|
|
229
|
+
return if (state.get() == PREFLIGHT_STATE_DONE) {
|
|
230
|
+
result.get()
|
|
231
|
+
} else {
|
|
232
|
+
commandPreparationJSON(ready = false, blockedReason = "unknown")
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return result.get()
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
fun commandPreparationJSON(
|
|
239
|
+
ready: Boolean,
|
|
240
|
+
updateJSON: String? = null,
|
|
241
|
+
blockedReason: String? = null
|
|
242
|
+
): String {
|
|
243
|
+
return JSONObject().apply {
|
|
244
|
+
put("ready", ready)
|
|
245
|
+
if (updateJSON != null) {
|
|
246
|
+
put("updateJSON", updateJSON)
|
|
247
|
+
}
|
|
248
|
+
if (!ready && blockedReason != null) {
|
|
249
|
+
put("blockedReason", blockedReason)
|
|
250
|
+
}
|
|
251
|
+
}.toString()
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private const val PREFLIGHT_STATE_QUEUED = 0
|
|
255
|
+
private const val PREFLIGHT_STATE_RUNNING = 1
|
|
256
|
+
private const val PREFLIGHT_STATE_CANCELLED = 2
|
|
257
|
+
private const val PREFLIGHT_STATE_DONE = 3
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private object NativeEditorOutsideTapDispatcher {
|
|
261
|
+
private val dispatchers = WeakHashMap<Window, OutsideTapWindowCallback>()
|
|
262
|
+
|
|
263
|
+
fun register(window: Window, view: NativeEditorExpoView) {
|
|
264
|
+
val currentCallback = window.callback ?: return
|
|
265
|
+
val previousDispatcher = dispatchers[window]
|
|
266
|
+
val dispatcher = if (currentCallback is OutsideTapWindowCallback) {
|
|
267
|
+
previousDispatcher
|
|
268
|
+
?.takeIf { it !== currentCallback }
|
|
269
|
+
?.transferViewsTo(currentCallback)
|
|
270
|
+
currentCallback
|
|
271
|
+
} else {
|
|
272
|
+
OutsideTapWindowCallback(window, currentCallback).also { nextDispatcher ->
|
|
273
|
+
previousDispatcher?.transferViewsTo(nextDispatcher)
|
|
274
|
+
window.callback = nextDispatcher
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
dispatchers[window] = dispatcher
|
|
278
|
+
dispatcher.add(view)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
fun unregister(window: Window, view: NativeEditorExpoView) {
|
|
282
|
+
val dispatcher = dispatchers[window] ?: return
|
|
283
|
+
if (!dispatcher.remove(view)) return
|
|
284
|
+
dispatchers.remove(window)
|
|
285
|
+
if (window.callback === dispatcher) {
|
|
286
|
+
window.callback = dispatcher.baseCallback
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private class OutsideTapWindowCallback(
|
|
291
|
+
private val window: Window,
|
|
292
|
+
val baseCallback: Window.Callback
|
|
293
|
+
) : Window.Callback by baseCallback {
|
|
294
|
+
private val views = mutableListOf<WeakReference<NativeEditorExpoView>>()
|
|
295
|
+
private var disabled = false
|
|
296
|
+
|
|
297
|
+
fun add(view: NativeEditorExpoView) {
|
|
298
|
+
prune()
|
|
299
|
+
if (views.any { it.get() === view }) return
|
|
300
|
+
views.add(WeakReference(view))
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
fun liveViews(): List<NativeEditorExpoView> {
|
|
304
|
+
prune()
|
|
305
|
+
return views.mapNotNull { it.get() }
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
fun transferViewsTo(target: OutsideTapWindowCallback) {
|
|
309
|
+
liveViews().forEach { target.add(it) }
|
|
310
|
+
views.clear()
|
|
311
|
+
disabled = true
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
fun remove(view: NativeEditorExpoView): Boolean {
|
|
315
|
+
views.removeAll { it.get()?.let { candidate -> candidate === view } != false }
|
|
316
|
+
return views.isEmpty()
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
|
320
|
+
if (disabled) {
|
|
321
|
+
return baseCallback.dispatchTouchEvent(event)
|
|
322
|
+
}
|
|
323
|
+
val activeViews = liveViews()
|
|
324
|
+
if (event.action != MotionEvent.ACTION_DOWN || activeViews.isEmpty()) {
|
|
325
|
+
return baseCallback.dispatchTouchEvent(event)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
val decisions = activeViews.map { view ->
|
|
329
|
+
view to view.shouldScheduleOutsideTapBlurForWindowEvent(event)
|
|
330
|
+
}
|
|
331
|
+
val result = baseCallback.dispatchTouchEvent(event)
|
|
332
|
+
decisions.forEach { (view, shouldBlur) ->
|
|
333
|
+
if (shouldBlur) {
|
|
334
|
+
view.scheduleOutsideTapBlurFromWindowDispatcher()
|
|
335
|
+
} else {
|
|
336
|
+
view.cancelOutsideTapBlurFromWindowDispatcher()
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return result
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private fun prune() {
|
|
343
|
+
views.removeAll { it.get() == null }
|
|
344
|
+
if (views.isEmpty() && window.callback === this) {
|
|
345
|
+
window.callback = baseCallback
|
|
346
|
+
if (dispatchers[window] === this) {
|
|
347
|
+
dispatchers.remove(window)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
25
354
|
/**
|
|
26
355
|
* Expo Modules wrapper view that hosts a [RichTextEditorView] and bridges
|
|
27
356
|
* editor events to React Native via [EventDispatcher].
|
|
@@ -43,13 +372,33 @@ class NativeEditorExpoView(
|
|
|
43
372
|
}
|
|
44
373
|
}
|
|
45
374
|
|
|
375
|
+
private sealed class PendingNativeAction {
|
|
376
|
+
data class ToolbarItemPress(val item: NativeToolbarItem) : PendingNativeAction()
|
|
377
|
+
data class MentionSuggestionSelect(val suggestion: NativeMentionSuggestion) : PendingNativeAction()
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private data class PendingNativeActionScope(
|
|
381
|
+
val editorId: Long,
|
|
382
|
+
val documentVersion: Int?,
|
|
383
|
+
val allowedDocumentVersion: Int?,
|
|
384
|
+
val hadFocus: Boolean,
|
|
385
|
+
val hadVisibleToolbar: Boolean,
|
|
386
|
+
val selectionAnchor: Int?,
|
|
387
|
+
val selectionHead: Int?,
|
|
388
|
+
val mentionAnchor: Int? = null,
|
|
389
|
+
val mentionHead: Int? = null,
|
|
390
|
+
val mentionQuery: String? = null
|
|
391
|
+
)
|
|
392
|
+
|
|
46
393
|
val richTextView: RichTextEditorView = RichTextEditorView(context)
|
|
47
394
|
private val keyboardToolbarView = EditorKeyboardToolbarView(context)
|
|
395
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
48
396
|
|
|
49
397
|
private val onEditorUpdate by EventDispatcher<Map<String, Any>>()
|
|
50
398
|
private val onSelectionChange by EventDispatcher<Map<String, Any>>()
|
|
51
399
|
private val onFocusChange by EventDispatcher<Map<String, Any>>()
|
|
52
400
|
private val onContentHeightChange by EventDispatcher<Map<String, Any>>()
|
|
401
|
+
private val onEditorReady by EventDispatcher<Map<String, Any>>()
|
|
53
402
|
@Suppress("unused")
|
|
54
403
|
private val onToolbarAction by EventDispatcher<Map<String, Any>>()
|
|
55
404
|
@Suppress("unused")
|
|
@@ -57,34 +406,85 @@ class NativeEditorExpoView(
|
|
|
57
406
|
|
|
58
407
|
/** Guard flag: when true, editor updates originated from JS and should not echo back. */
|
|
59
408
|
var isApplyingJSUpdate = false
|
|
409
|
+
internal var blockEditorUpdatePreflightForTesting = false
|
|
410
|
+
internal var blockThemePreflightForTesting = false
|
|
411
|
+
internal var onToolbarActionForTesting: ((Map<String, Any>) -> Unit)? = null
|
|
412
|
+
internal var onAddonEventForTesting: ((Map<String, Any>) -> Unit)? = null
|
|
413
|
+
internal var onFocusChangeForTesting: ((Map<String, Any>) -> Unit)? = null
|
|
414
|
+
internal var onEditorReadyForTesting: ((Map<String, Any>) -> Unit)? = null
|
|
415
|
+
internal var onRefreshToolbarStateFromEditorSelectionForTesting: (() -> String?)? = null
|
|
416
|
+
internal var onBeforePrepareForEditorCommandForTesting: (() -> Unit)? = null
|
|
417
|
+
private var isAttachedToNativeWindow = false
|
|
60
418
|
private var didApplyAutoFocus = false
|
|
61
419
|
private var heightBehavior = EditorHeightBehavior.FIXED
|
|
62
420
|
private var lastEmittedContentHeight = 0
|
|
63
|
-
private var
|
|
64
|
-
private var previousWindowCallback: Window.Callback? = null
|
|
421
|
+
private var outsideTapWindow: Window? = null
|
|
65
422
|
private var toolbarFramesInWindow: List<RectF> = emptyList()
|
|
66
423
|
private var lastToolbarTouchUptimeMs: Long? = null
|
|
67
424
|
private var pendingOutsideTapBlur: Runnable? = null
|
|
68
425
|
private var pendingKeyboardDismiss: Runnable? = null
|
|
426
|
+
private var pendingToolbarRefocus: Runnable? = null
|
|
427
|
+
private var pendingToolbarRefocusEditorId: Long? = null
|
|
428
|
+
private var pendingToolbarRefocusGeneration = 0
|
|
429
|
+
private var autoFocusRequested = false
|
|
69
430
|
private var addons = NativeEditorAddons(null)
|
|
70
431
|
private var mentionQueryState: MentionQueryState? = null
|
|
71
432
|
private var lastMentionEventJson: String? = null
|
|
433
|
+
private var lastMentionEventEditorId: Long? = null
|
|
72
434
|
private var lastThemeJson: String? = null
|
|
435
|
+
private var pendingThemeJson: String? = null
|
|
436
|
+
private var hasPendingTheme = false
|
|
437
|
+
private var pendingThemeRetryScheduled = false
|
|
438
|
+
private var pendingThemeRetryEditorId: Long? = null
|
|
439
|
+
private var pendingThemeRetryGeneration = 0
|
|
440
|
+
private var pendingThemeRetryAttempts = 0
|
|
73
441
|
private var lastAddonsJson: String? = null
|
|
74
442
|
private var lastRemoteSelectionsJson: String? = null
|
|
75
443
|
private var lastToolbarItemsJson: String? = null
|
|
76
444
|
private var lastToolbarFrameJson: String? = null
|
|
445
|
+
private var lastDocumentVersion: Int? = null
|
|
77
446
|
private var toolbarState = NativeToolbarState.empty
|
|
78
447
|
private var showsToolbar = true
|
|
79
448
|
private var toolbarPlacement = ToolbarPlacement.KEYBOARD
|
|
80
449
|
private var currentImeBottom = 0
|
|
81
450
|
private var pendingEditorUpdateJson: String? = null
|
|
451
|
+
private var pendingEditorUpdateEditorId: Long? = null
|
|
82
452
|
private var pendingEditorUpdateRevision = 0
|
|
83
453
|
private var appliedEditorUpdateRevision = 0
|
|
454
|
+
private var pendingEditorUpdateRetryScheduled = false
|
|
455
|
+
private var pendingEditorUpdateRetryEditorId: Long? = null
|
|
456
|
+
private var pendingEditorUpdateRetryGeneration = 0
|
|
457
|
+
private var pendingEditorUpdateRetryAttempts = 0
|
|
458
|
+
private var pendingEditorUpdateForcedRecoveryAttempted = false
|
|
459
|
+
private var pendingViewCommandUpdateJson: String? = null
|
|
460
|
+
private var pendingViewCommandUpdateEditorId: Long? = null
|
|
461
|
+
private var pendingViewCommandUpdateRetryScheduled = false
|
|
462
|
+
private var pendingViewCommandUpdateRetryGeneration = 0
|
|
463
|
+
private var pendingViewCommandUpdateRetryAttempts = 0
|
|
464
|
+
private var pendingPreflightWakeScheduled = false
|
|
465
|
+
private var pendingPreflightWakeGeneration = 0
|
|
466
|
+
private var pendingBlurRetry: Runnable? = null
|
|
467
|
+
private var pendingBlurRetryEditorId: Long? = null
|
|
468
|
+
private var pendingBlurRetryGeneration = 0
|
|
469
|
+
private var pendingBlurRetryAttempts = 0
|
|
470
|
+
private var pendingDetachPreflightRetryScheduled = false
|
|
471
|
+
private var pendingDetachPreflightRetryEditorId: Long? = null
|
|
472
|
+
private var pendingDetachPreflightRetryGeneration = 0
|
|
473
|
+
private var pendingDetachPreflightRetryAttempts = 0
|
|
474
|
+
private var pendingNativeAction: PendingNativeAction? = null
|
|
475
|
+
private var pendingNativeActionScope: PendingNativeActionScope? = null
|
|
476
|
+
private var pendingNativeActionRetryScheduled = false
|
|
477
|
+
private var pendingNativeActionRetryEditorId: Long? = null
|
|
478
|
+
private var pendingNativeActionRetryGeneration = 0
|
|
479
|
+
private var pendingNativeActionRetryAttempts = 0
|
|
480
|
+
private var lastReadyEditorId: Long? = null
|
|
84
481
|
|
|
85
482
|
init {
|
|
86
483
|
addView(richTextView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
|
|
87
484
|
richTextView.editorEditText.editorListener = this
|
|
485
|
+
richTextView.onBeforeDetachedFromWindow = {
|
|
486
|
+
prepareForDetachFromWindow()
|
|
487
|
+
}
|
|
88
488
|
keyboardToolbarView.onPressItem = { item ->
|
|
89
489
|
handleToolbarItemPress(item)
|
|
90
490
|
}
|
|
@@ -102,36 +502,130 @@ class NativeEditorExpoView(
|
|
|
102
502
|
// Observe EditText focus changes.
|
|
103
503
|
richTextView.editorEditText.setOnFocusChangeListener { _, hasFocus ->
|
|
104
504
|
if (hasFocus) {
|
|
505
|
+
cancelPendingToolbarRefocus()
|
|
105
506
|
installOutsideTapBlurHandlerIfNeeded()
|
|
106
507
|
refreshMentionQuery()
|
|
107
508
|
} else {
|
|
108
509
|
if (shouldPreserveFocusAfterToolbarTouch()) {
|
|
109
|
-
|
|
110
|
-
focus()
|
|
111
|
-
}
|
|
510
|
+
scheduleToolbarRefocus()
|
|
112
511
|
return@setOnFocusChangeListener
|
|
113
512
|
}
|
|
114
513
|
uninstallOutsideTapBlurHandler()
|
|
115
514
|
clearMentionQueryState()
|
|
515
|
+
clearPendingNativeActionRetry()
|
|
116
516
|
}
|
|
117
517
|
updateKeyboardToolbarVisibility()
|
|
118
|
-
val event = mapOf<String, Any>(
|
|
119
|
-
|
|
518
|
+
val event = mapOf<String, Any>(
|
|
519
|
+
"isFocused" to hasFocus,
|
|
520
|
+
"editorId" to richTextView.editorId
|
|
521
|
+
)
|
|
522
|
+
onFocusChangeForTesting?.invoke(event) ?: onFocusChange(event)
|
|
120
523
|
}
|
|
121
524
|
}
|
|
122
525
|
|
|
123
526
|
fun setEditorId(id: Long) {
|
|
124
|
-
|
|
527
|
+
if (id != 0L && NativeEditorViewRegistry.isDestroyed(id)) {
|
|
528
|
+
setEditorId(0L)
|
|
529
|
+
return
|
|
530
|
+
}
|
|
531
|
+
val previousEditorId = richTextView.editorId
|
|
532
|
+
if (previousEditorId == id && richTextView.editorEditText.editorId == id) {
|
|
533
|
+
if (id != 0L && isAttachedToNativeWindow) {
|
|
534
|
+
if (!NativeEditorViewRegistry.register(id, this)) {
|
|
535
|
+
handleEditorDestroyed(id)
|
|
536
|
+
return
|
|
537
|
+
}
|
|
538
|
+
applyPendingEditorUpdateIfNeeded()
|
|
539
|
+
applyPendingThemeIfNeeded()
|
|
540
|
+
refreshReadyStateIfSettled()
|
|
541
|
+
applyAutoFocusIfNeeded()
|
|
542
|
+
} else if (id != 0L) {
|
|
543
|
+
NativeEditorViewRegistry.unregister(
|
|
544
|
+
id,
|
|
545
|
+
this,
|
|
546
|
+
blockCommandsUntilRegistered = true
|
|
547
|
+
)
|
|
548
|
+
}
|
|
549
|
+
return
|
|
550
|
+
}
|
|
551
|
+
if (previousEditorId != id) {
|
|
552
|
+
NativeEditorViewRegistry.unregister(previousEditorId, this)
|
|
553
|
+
lastDocumentVersion = null
|
|
554
|
+
cancelPendingToolbarRefocus()
|
|
555
|
+
cancelPendingEditorUpdateRetry()
|
|
556
|
+
if (pendingEditorUpdateEditorId != null && pendingEditorUpdateEditorId != id) {
|
|
557
|
+
clearPendingEditorUpdateState()
|
|
558
|
+
}
|
|
559
|
+
appliedEditorUpdateRevision = 0
|
|
560
|
+
clearPendingViewCommandUpdateRetry()
|
|
561
|
+
cancelPendingThemeRetry()
|
|
562
|
+
if (hasPendingTheme) {
|
|
563
|
+
pendingThemeRetryEditorId = id
|
|
564
|
+
}
|
|
565
|
+
cancelPendingBlurRetry()
|
|
566
|
+
clearPendingNativeActionRetry()
|
|
567
|
+
clearMentionQueryState(resetLastEvent = true)
|
|
568
|
+
lastReadyEditorId = null
|
|
569
|
+
}
|
|
570
|
+
if (!isAttachedToNativeWindow) {
|
|
571
|
+
richTextView.setEditorIdWhileDetached(id)
|
|
572
|
+
if (id != 0L) {
|
|
573
|
+
NativeEditorViewRegistry.unregister(
|
|
574
|
+
id,
|
|
575
|
+
this,
|
|
576
|
+
blockCommandsUntilRegistered = true
|
|
577
|
+
)
|
|
578
|
+
} else {
|
|
579
|
+
toolbarState = NativeToolbarState.empty
|
|
580
|
+
keyboardToolbarView.applyState(toolbarState)
|
|
581
|
+
}
|
|
582
|
+
return
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (hasPendingEditorUpdateForEditor(id)) {
|
|
586
|
+
richTextView.setEditorIdWhileDetached(id)
|
|
587
|
+
richTextView.rebindEditorIfNeeded(notifyListener = false)
|
|
588
|
+
} else {
|
|
589
|
+
richTextView.editorId = id
|
|
590
|
+
}
|
|
591
|
+
if (id != 0L) {
|
|
592
|
+
if (!NativeEditorViewRegistry.register(id, this)) {
|
|
593
|
+
handleEditorDestroyed(id)
|
|
594
|
+
return
|
|
595
|
+
}
|
|
596
|
+
} else {
|
|
597
|
+
toolbarState = NativeToolbarState.empty
|
|
598
|
+
keyboardToolbarView.applyState(toolbarState)
|
|
599
|
+
}
|
|
600
|
+
applyPendingEditorUpdateIfNeeded()
|
|
601
|
+
applyPendingThemeIfNeeded()
|
|
602
|
+
refreshReadyStateIfSettled()
|
|
603
|
+
applyAutoFocusIfNeeded()
|
|
125
604
|
}
|
|
126
605
|
|
|
127
606
|
fun setThemeJson(themeJson: String?) {
|
|
607
|
+
if (lastThemeJson == themeJson && !hasPendingTheme) return
|
|
608
|
+
pendingThemeJson = themeJson
|
|
609
|
+
hasPendingTheme = true
|
|
610
|
+
pendingThemeRetryEditorId = richTextView.editorId
|
|
611
|
+
pendingThemeRetryAttempts = 0
|
|
612
|
+
applyPendingThemeIfNeeded()
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
private fun applyThemeJson(themeJson: String?) {
|
|
128
616
|
if (lastThemeJson == themeJson) return
|
|
129
617
|
lastThemeJson = themeJson
|
|
130
618
|
val theme = EditorTheme.fromJson(themeJson)
|
|
131
619
|
richTextView.applyTheme(theme)
|
|
132
620
|
keyboardToolbarView.applyTheme(theme?.toolbar)
|
|
133
621
|
keyboardToolbarView.applyMentionTheme(theme?.mentions ?: addons.mentions?.theme)
|
|
622
|
+
keyboardToolbarView.requestLayout()
|
|
134
623
|
updateKeyboardToolbarLayout()
|
|
624
|
+
updateEditorViewportInset(forceMeasureToolbar = true)
|
|
625
|
+
post {
|
|
626
|
+
updateKeyboardToolbarLayout()
|
|
627
|
+
updateEditorViewportInset(forceMeasureToolbar = true)
|
|
628
|
+
}
|
|
135
629
|
}
|
|
136
630
|
|
|
137
631
|
fun setHeightBehavior(rawHeightBehavior: String) {
|
|
@@ -159,6 +653,7 @@ class NativeEditorExpoView(
|
|
|
159
653
|
|
|
160
654
|
fun setAddonsJson(addonsJson: String?) {
|
|
161
655
|
if (lastAddonsJson == addonsJson) return
|
|
656
|
+
clearPendingNativeActionRetry()
|
|
162
657
|
lastAddonsJson = addonsJson
|
|
163
658
|
addons = NativeEditorAddons.fromJson(addonsJson)
|
|
164
659
|
keyboardToolbarView.applyMentionTheme(richTextView.editorEditText.theme?.mentions ?: addons.mentions?.theme)
|
|
@@ -174,9 +669,12 @@ class NativeEditorExpoView(
|
|
|
174
669
|
}
|
|
175
670
|
|
|
176
671
|
fun setAutoFocus(autoFocus: Boolean) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
672
|
+
autoFocusRequested = autoFocus
|
|
673
|
+
applyAutoFocusIfNeeded()
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
private fun applyAutoFocusIfNeeded() {
|
|
677
|
+
if (!autoFocusRequested || didApplyAutoFocus || !canFocusCurrentEditor()) return
|
|
180
678
|
didApplyAutoFocus = true
|
|
181
679
|
focus()
|
|
182
680
|
}
|
|
@@ -193,13 +691,34 @@ class NativeEditorExpoView(
|
|
|
193
691
|
richTextView.editorEditText.setKeyboardType(keyboardType)
|
|
194
692
|
}
|
|
195
693
|
|
|
694
|
+
fun setEditable(editable: Boolean) {
|
|
695
|
+
if (richTextView.editorEditText.isEditable == editable) return
|
|
696
|
+
if (!editable) {
|
|
697
|
+
cancelPendingToolbarRefocus()
|
|
698
|
+
clearPendingNativeActionRetry()
|
|
699
|
+
}
|
|
700
|
+
richTextView.editorEditText.isEditable = editable
|
|
701
|
+
updateKeyboardToolbarVisibility()
|
|
702
|
+
}
|
|
703
|
+
|
|
196
704
|
fun setShowToolbar(showToolbar: Boolean) {
|
|
705
|
+
if (showsToolbar == showToolbar) return
|
|
706
|
+
if (!showToolbar) {
|
|
707
|
+
cancelPendingToolbarRefocus()
|
|
708
|
+
clearPendingNativeActionRetry()
|
|
709
|
+
}
|
|
197
710
|
showsToolbar = showToolbar
|
|
198
711
|
updateKeyboardToolbarVisibility()
|
|
199
712
|
}
|
|
200
713
|
|
|
201
714
|
fun setToolbarPlacement(rawToolbarPlacement: String?) {
|
|
202
|
-
|
|
715
|
+
val nextPlacement = ToolbarPlacement.fromRaw(rawToolbarPlacement)
|
|
716
|
+
if (toolbarPlacement == nextPlacement) return
|
|
717
|
+
if (nextPlacement != ToolbarPlacement.KEYBOARD) {
|
|
718
|
+
cancelPendingToolbarRefocus()
|
|
719
|
+
clearPendingNativeActionRetry()
|
|
720
|
+
}
|
|
721
|
+
toolbarPlacement = nextPlacement
|
|
203
722
|
updateKeyboardToolbarVisibility()
|
|
204
723
|
}
|
|
205
724
|
|
|
@@ -209,6 +728,7 @@ class NativeEditorExpoView(
|
|
|
209
728
|
|
|
210
729
|
fun setToolbarItemsJson(toolbarItemsJson: String?) {
|
|
211
730
|
if (lastToolbarItemsJson == toolbarItemsJson) return
|
|
731
|
+
clearPendingNativeActionRetry()
|
|
212
732
|
lastToolbarItemsJson = toolbarItemsJson
|
|
213
733
|
keyboardToolbarView.setItems(NativeToolbarItem.fromJson(toolbarItemsJson))
|
|
214
734
|
}
|
|
@@ -267,23 +787,495 @@ class NativeEditorExpoView(
|
|
|
267
787
|
pendingEditorUpdateJson = editorUpdateJson
|
|
268
788
|
}
|
|
269
789
|
|
|
790
|
+
fun setPendingEditorUpdateEditorId(editorUpdateEditorId: Long?) {
|
|
791
|
+
pendingEditorUpdateEditorId = editorUpdateEditorId
|
|
792
|
+
}
|
|
793
|
+
|
|
270
794
|
fun setPendingEditorUpdateRevision(editorUpdateRevision: Int) {
|
|
795
|
+
if (pendingEditorUpdateRevision != editorUpdateRevision) {
|
|
796
|
+
pendingEditorUpdateRetryAttempts = 0
|
|
797
|
+
pendingEditorUpdateForcedRecoveryAttempted = false
|
|
798
|
+
}
|
|
271
799
|
pendingEditorUpdateRevision = editorUpdateRevision
|
|
272
800
|
}
|
|
273
801
|
|
|
802
|
+
private fun hasPendingEditorUpdateForEditor(editorId: Long): Boolean =
|
|
803
|
+
pendingEditorUpdateJson != null &&
|
|
804
|
+
pendingEditorUpdateRevision != 0 &&
|
|
805
|
+
pendingEditorUpdateRevision != appliedEditorUpdateRevision &&
|
|
806
|
+
pendingEditorUpdateEditorId == editorId
|
|
807
|
+
|
|
808
|
+
private fun hasPendingEditorUpdateForCurrentEditor(): Boolean =
|
|
809
|
+
hasPendingEditorUpdateForEditor(richTextView.editorId)
|
|
810
|
+
|
|
811
|
+
private fun pendingEditorUpdateCommandPreparationJSON(): String =
|
|
812
|
+
NativeEditorViewRegistry.commandPreparationJSON(
|
|
813
|
+
ready = false,
|
|
814
|
+
blockedReason = "pendingUpdate"
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
private fun shouldBlockEditorCommandForPendingUpdate(): Boolean =
|
|
818
|
+
hasPendingEditorUpdateForCurrentEditor()
|
|
819
|
+
|
|
820
|
+
private fun refreshReadyStateIfSettled() {
|
|
821
|
+
if (handleDestroyedCurrentEditorIfNeeded()) return
|
|
822
|
+
if (hasPendingEditorUpdateForCurrentEditor()) return
|
|
823
|
+
if (!isAttachedToNativeWindow) return
|
|
824
|
+
if (richTextView.editorEditText.editorId != richTextView.editorId) return
|
|
825
|
+
refreshToolbarStateFromEditorSelection()
|
|
826
|
+
refreshMentionQuery()
|
|
827
|
+
emitEditorReadyIfNeeded()
|
|
828
|
+
}
|
|
829
|
+
|
|
274
830
|
fun applyPendingEditorUpdateIfNeeded() {
|
|
275
|
-
|
|
831
|
+
if (handleDestroyedCurrentEditorIfNeeded()) return
|
|
276
832
|
if (pendingEditorUpdateRevision == 0) return
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
833
|
+
val revision = pendingEditorUpdateRevision
|
|
834
|
+
val editorId = richTextView.editorId
|
|
835
|
+
val expectedEditorId = pendingEditorUpdateEditorId
|
|
836
|
+
if (expectedEditorId == null) return
|
|
837
|
+
if (expectedEditorId != editorId) return
|
|
838
|
+
if (pendingEditorUpdateJson == null) {
|
|
839
|
+
clearPendingEditorUpdateState(resetAppliedRevision = false)
|
|
840
|
+
refreshReadyStateIfSettled()
|
|
841
|
+
return
|
|
842
|
+
}
|
|
843
|
+
val updateJson = pendingEditorUpdateJson ?: return
|
|
844
|
+
if (pendingEditorUpdateRevision == appliedEditorUpdateRevision) {
|
|
845
|
+
clearPendingEditorUpdateState(resetAppliedRevision = false)
|
|
846
|
+
emitEditorReady(editorUpdateRevision = revision)
|
|
847
|
+
refreshReadyStateIfSettled()
|
|
848
|
+
return
|
|
849
|
+
}
|
|
850
|
+
if (editorId != 0L && !isAttachedToNativeWindow) return
|
|
851
|
+
val apply = Runnable {
|
|
852
|
+
if (editorId != richTextView.editorId) return@Runnable
|
|
853
|
+
if (expectedEditorId != richTextView.editorId) return@Runnable
|
|
854
|
+
if (editorId != 0L && !isAttachedToNativeWindow) return@Runnable
|
|
855
|
+
if (revision != pendingEditorUpdateRevision) return@Runnable
|
|
856
|
+
if (revision == appliedEditorUpdateRevision) {
|
|
857
|
+
clearPendingEditorUpdateState(resetAppliedRevision = false)
|
|
858
|
+
emitEditorReady(editorUpdateRevision = revision)
|
|
859
|
+
refreshReadyStateIfSettled()
|
|
860
|
+
return@Runnable
|
|
861
|
+
}
|
|
862
|
+
if (applyEditorUpdate(updateJson, scheduleViewCommandRetry = false)) {
|
|
863
|
+
appliedEditorUpdateRevision = revision
|
|
864
|
+
pendingEditorUpdateJson = null
|
|
865
|
+
pendingEditorUpdateEditorId = null
|
|
866
|
+
pendingEditorUpdateRevision = 0
|
|
867
|
+
pendingEditorUpdateRetryAttempts = 0
|
|
868
|
+
pendingEditorUpdateForcedRecoveryAttempted = false
|
|
869
|
+
cancelPendingEditorUpdateRetry()
|
|
870
|
+
emitEditorReady(editorUpdateRevision = revision)
|
|
871
|
+
refreshReadyStateIfSettled()
|
|
872
|
+
} else {
|
|
873
|
+
schedulePendingEditorUpdateRetry()
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
877
|
+
apply.run()
|
|
878
|
+
} else if (!post(apply)) {
|
|
879
|
+
richTextView.post(apply)
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
private fun clearPendingEditorUpdateState(resetAppliedRevision: Boolean = true) {
|
|
884
|
+
pendingEditorUpdateJson = null
|
|
885
|
+
pendingEditorUpdateEditorId = null
|
|
886
|
+
pendingEditorUpdateRevision = 0
|
|
887
|
+
if (resetAppliedRevision) {
|
|
888
|
+
appliedEditorUpdateRevision = 0
|
|
889
|
+
}
|
|
890
|
+
cancelPendingEditorUpdateRetry()
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
private fun cancelPendingEditorUpdateRetry() {
|
|
894
|
+
pendingEditorUpdateRetryScheduled = false
|
|
895
|
+
pendingEditorUpdateRetryEditorId = null
|
|
896
|
+
pendingEditorUpdateRetryAttempts = 0
|
|
897
|
+
pendingEditorUpdateForcedRecoveryAttempted = false
|
|
898
|
+
pendingEditorUpdateRetryGeneration += 1
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
private fun schedulePendingEditorUpdateRetry() {
|
|
902
|
+
if (pendingEditorUpdateRetryScheduled) return
|
|
903
|
+
val pastFastRetryBudget =
|
|
904
|
+
pendingEditorUpdateRetryAttempts >= MAX_PENDING_UPDATE_RETRY_ATTEMPTS
|
|
905
|
+
if (
|
|
906
|
+
pastFastRetryBudget &&
|
|
907
|
+
!pendingEditorUpdateForcedRecoveryAttempted &&
|
|
908
|
+
richTextView.editorId != 0L &&
|
|
909
|
+
richTextView.editorEditText.editorId == richTextView.editorId
|
|
910
|
+
) {
|
|
911
|
+
pendingEditorUpdateForcedRecoveryAttempted = true
|
|
912
|
+
richTextView.editorEditText.discardTransientNativeInputForExternalRecovery()
|
|
913
|
+
}
|
|
914
|
+
if (!pastFastRetryBudget) {
|
|
915
|
+
pendingEditorUpdateRetryAttempts += 1
|
|
916
|
+
}
|
|
917
|
+
pendingEditorUpdateRetryEditorId = richTextView.editorId
|
|
918
|
+
pendingEditorUpdateRetryScheduled = true
|
|
919
|
+
pendingEditorUpdateRetryGeneration += 1
|
|
920
|
+
val retryGeneration = pendingEditorUpdateRetryGeneration
|
|
921
|
+
val delayMs = if (pastFastRetryBudget) {
|
|
922
|
+
PENDING_UPDATE_RECOVERY_RETRY_DELAY_MS
|
|
923
|
+
} else {
|
|
924
|
+
NATIVE_ACTION_RETRY_DELAY_MS * pendingEditorUpdateRetryAttempts
|
|
925
|
+
}
|
|
926
|
+
val retry = Runnable {
|
|
927
|
+
if (retryGeneration != pendingEditorUpdateRetryGeneration) return@Runnable
|
|
928
|
+
if (pendingEditorUpdateRetryEditorId != richTextView.editorId) {
|
|
929
|
+
clearPendingEditorUpdateState()
|
|
930
|
+
return@Runnable
|
|
931
|
+
}
|
|
932
|
+
pendingEditorUpdateRetryScheduled = false
|
|
933
|
+
pendingEditorUpdateRetryEditorId = null
|
|
934
|
+
applyPendingEditorUpdateIfNeeded()
|
|
935
|
+
}
|
|
936
|
+
mainHandler.postDelayed(retry, delayMs)
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
private fun clearPendingThemeRetry() {
|
|
940
|
+
pendingThemeJson = null
|
|
941
|
+
hasPendingTheme = false
|
|
942
|
+
cancelPendingThemeRetry()
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
private fun cancelPendingThemeRetry() {
|
|
946
|
+
pendingThemeRetryScheduled = false
|
|
947
|
+
pendingThemeRetryEditorId = null
|
|
948
|
+
pendingThemeRetryAttempts = 0
|
|
949
|
+
pendingThemeRetryGeneration += 1
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
private fun applyPendingThemeIfNeeded() {
|
|
953
|
+
if (handleDestroyedCurrentEditorIfNeeded()) return
|
|
954
|
+
if (!hasPendingTheme) return
|
|
955
|
+
val themeJson = pendingThemeJson
|
|
956
|
+
val editorId = richTextView.editorId
|
|
957
|
+
if (pendingThemeRetryEditorId != editorId) {
|
|
958
|
+
pendingThemeRetryEditorId = editorId
|
|
959
|
+
}
|
|
960
|
+
if (
|
|
961
|
+
blockThemePreflightForTesting ||
|
|
962
|
+
!richTextView.editorEditText.prepareForExternalEditorUpdate()
|
|
963
|
+
) {
|
|
964
|
+
schedulePendingThemeRetry()
|
|
965
|
+
return
|
|
966
|
+
}
|
|
967
|
+
pendingThemeJson = null
|
|
968
|
+
hasPendingTheme = false
|
|
969
|
+
cancelPendingThemeRetry()
|
|
970
|
+
applyThemeJson(themeJson)
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
private fun schedulePendingThemeRetry() {
|
|
974
|
+
if (pendingThemeRetryScheduled) return
|
|
975
|
+
if (pendingThemeRetryAttempts >= MAX_PENDING_UPDATE_RETRY_ATTEMPTS) return
|
|
976
|
+
pendingThemeRetryAttempts += 1
|
|
977
|
+
pendingThemeRetryEditorId = richTextView.editorId
|
|
978
|
+
pendingThemeRetryScheduled = true
|
|
979
|
+
pendingThemeRetryGeneration += 1
|
|
980
|
+
val retryGeneration = pendingThemeRetryGeneration
|
|
981
|
+
val delayMs = NATIVE_ACTION_RETRY_DELAY_MS * pendingThemeRetryAttempts
|
|
982
|
+
val retry = Runnable {
|
|
983
|
+
if (retryGeneration != pendingThemeRetryGeneration) return@Runnable
|
|
984
|
+
if (pendingThemeRetryEditorId != richTextView.editorId) {
|
|
985
|
+
clearPendingThemeRetry()
|
|
986
|
+
return@Runnable
|
|
987
|
+
}
|
|
988
|
+
pendingThemeRetryScheduled = false
|
|
989
|
+
applyPendingThemeIfNeeded()
|
|
990
|
+
}
|
|
991
|
+
mainHandler.postDelayed(retry, delayMs)
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
private fun clearPendingViewCommandUpdateRetry() {
|
|
995
|
+
pendingViewCommandUpdateJson = null
|
|
996
|
+
pendingViewCommandUpdateEditorId = null
|
|
997
|
+
pendingViewCommandUpdateRetryScheduled = false
|
|
998
|
+
pendingViewCommandUpdateRetryAttempts = 0
|
|
999
|
+
pendingViewCommandUpdateRetryGeneration += 1
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
private fun scheduleViewCommandUpdateRetry(updateJson: String) {
|
|
1003
|
+
if (pendingViewCommandUpdateJson != updateJson) {
|
|
1004
|
+
pendingViewCommandUpdateRetryAttempts = 0
|
|
1005
|
+
}
|
|
1006
|
+
pendingViewCommandUpdateJson = updateJson
|
|
1007
|
+
pendingViewCommandUpdateEditorId = richTextView.editorId
|
|
1008
|
+
if (pendingViewCommandUpdateRetryScheduled) return
|
|
1009
|
+
if (pendingViewCommandUpdateRetryAttempts >= MAX_PENDING_UPDATE_RETRY_ATTEMPTS) return
|
|
1010
|
+
pendingViewCommandUpdateRetryAttempts += 1
|
|
1011
|
+
pendingViewCommandUpdateRetryScheduled = true
|
|
1012
|
+
pendingViewCommandUpdateRetryGeneration += 1
|
|
1013
|
+
val retryGeneration = pendingViewCommandUpdateRetryGeneration
|
|
1014
|
+
val delayMs = NATIVE_ACTION_RETRY_DELAY_MS * pendingViewCommandUpdateRetryAttempts
|
|
1015
|
+
val retry = Runnable {
|
|
1016
|
+
if (retryGeneration != pendingViewCommandUpdateRetryGeneration) return@Runnable
|
|
1017
|
+
val retryJson = pendingViewCommandUpdateJson ?: run {
|
|
1018
|
+
pendingViewCommandUpdateRetryScheduled = false
|
|
1019
|
+
return@Runnable
|
|
1020
|
+
}
|
|
1021
|
+
if (pendingViewCommandUpdateEditorId != richTextView.editorId || richTextView.editorId == 0L) {
|
|
1022
|
+
clearPendingViewCommandUpdateRetry()
|
|
1023
|
+
return@Runnable
|
|
1024
|
+
}
|
|
1025
|
+
if (handleDestroyedCurrentEditorIfNeeded()) {
|
|
1026
|
+
clearPendingViewCommandUpdateRetry()
|
|
1027
|
+
return@Runnable
|
|
1028
|
+
}
|
|
1029
|
+
pendingViewCommandUpdateRetryScheduled = false
|
|
1030
|
+
if (applyEditorUpdate(retryJson, scheduleViewCommandRetry = true)) {
|
|
1031
|
+
clearPendingViewCommandUpdateRetry()
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
mainHandler.postDelayed(retry, delayMs)
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
private fun schedulePendingPreflightWake() {
|
|
1038
|
+
if (pendingPreflightWakeScheduled) return
|
|
1039
|
+
pendingPreflightWakeScheduled = true
|
|
1040
|
+
pendingPreflightWakeGeneration += 1
|
|
1041
|
+
val wakeGeneration = pendingPreflightWakeGeneration
|
|
1042
|
+
mainHandler.post {
|
|
1043
|
+
if (wakeGeneration != pendingPreflightWakeGeneration) return@post
|
|
1044
|
+
pendingPreflightWakeScheduled = false
|
|
1045
|
+
wakePendingPreflightWork()
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
private fun cancelPendingPreflightWake() {
|
|
1050
|
+
pendingPreflightWakeScheduled = false
|
|
1051
|
+
pendingPreflightWakeGeneration += 1
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
private fun wakePendingPreflightWork() {
|
|
1055
|
+
if (Looper.myLooper() != Looper.getMainLooper()) {
|
|
1056
|
+
schedulePendingPreflightWake()
|
|
1057
|
+
return
|
|
1058
|
+
}
|
|
1059
|
+
if (handleDestroyedCurrentEditorIfNeeded()) return
|
|
1060
|
+
if (pendingEditorUpdateJson != null) {
|
|
1061
|
+
pendingEditorUpdateRetryAttempts = 0
|
|
1062
|
+
pendingEditorUpdateForcedRecoveryAttempted = false
|
|
1063
|
+
applyPendingEditorUpdateIfNeeded()
|
|
1064
|
+
}
|
|
1065
|
+
if (hasPendingTheme) {
|
|
1066
|
+
pendingThemeRetryAttempts = 0
|
|
1067
|
+
applyPendingThemeIfNeeded()
|
|
1068
|
+
}
|
|
1069
|
+
pendingViewCommandUpdateJson?.let { updateJson ->
|
|
1070
|
+
pendingViewCommandUpdateRetryAttempts = 0
|
|
1071
|
+
pendingViewCommandUpdateRetryScheduled = false
|
|
1072
|
+
pendingViewCommandUpdateRetryGeneration += 1
|
|
1073
|
+
if (applyEditorUpdate(updateJson, scheduleViewCommandRetry = true)) {
|
|
1074
|
+
clearPendingViewCommandUpdateRetry()
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
retryPendingNativeActionFromWake()
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
private fun clearPendingNativeActionRetry() {
|
|
1081
|
+
pendingNativeAction = null
|
|
1082
|
+
pendingNativeActionScope = null
|
|
1083
|
+
pendingNativeActionRetryEditorId = null
|
|
1084
|
+
pendingNativeActionRetryScheduled = false
|
|
1085
|
+
pendingNativeActionRetryAttempts = 0
|
|
1086
|
+
pendingNativeActionRetryGeneration += 1
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
private fun currentNativeActionScope(action: PendingNativeAction): PendingNativeActionScope {
|
|
1090
|
+
val selection = richTextView.editorEditText.currentScalarSelection()
|
|
1091
|
+
val mentionScope = when (action) {
|
|
1092
|
+
is PendingNativeAction.MentionSuggestionSelect ->
|
|
1093
|
+
mentionQueryState ?: addons.mentions?.let { currentMentionQueryState(it.trigger) }
|
|
1094
|
+
is PendingNativeAction.ToolbarItemPress -> null
|
|
1095
|
+
}
|
|
1096
|
+
return PendingNativeActionScope(
|
|
1097
|
+
editorId = richTextView.editorId,
|
|
1098
|
+
documentVersion = lastDocumentVersion,
|
|
1099
|
+
allowedDocumentVersion = documentVersionFromUpdateJSON(pendingEditorUpdateJson),
|
|
1100
|
+
hadFocus = isEditorEffectivelyFocusedForNativeAction(),
|
|
1101
|
+
hadVisibleToolbar = isNativeActionToolbarVisible(action),
|
|
1102
|
+
selectionAnchor = selection?.first,
|
|
1103
|
+
selectionHead = selection?.second,
|
|
1104
|
+
mentionAnchor = mentionScope?.anchor,
|
|
1105
|
+
mentionHead = mentionScope?.head,
|
|
1106
|
+
mentionQuery = mentionScope?.query
|
|
1107
|
+
)
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
private fun isPendingNativeActionScopeCurrent(
|
|
1111
|
+
action: PendingNativeAction,
|
|
1112
|
+
scope: PendingNativeActionScope
|
|
1113
|
+
): Boolean {
|
|
1114
|
+
if (scope.editorId != richTextView.editorId) return false
|
|
1115
|
+
if (scope.hadFocus != isEditorEffectivelyFocusedForNativeAction()) return false
|
|
1116
|
+
if (scope.hadVisibleToolbar != isNativeActionToolbarVisible(action)) return false
|
|
1117
|
+
if (
|
|
1118
|
+
scope.documentVersion != lastDocumentVersion &&
|
|
1119
|
+
(scope.allowedDocumentVersion == null || scope.allowedDocumentVersion != lastDocumentVersion)
|
|
1120
|
+
) {
|
|
1121
|
+
return false
|
|
1122
|
+
}
|
|
1123
|
+
val selection = richTextView.editorEditText.currentScalarSelection()
|
|
1124
|
+
if (scope.selectionAnchor != selection?.first || scope.selectionHead != selection?.second) {
|
|
1125
|
+
return false
|
|
1126
|
+
}
|
|
1127
|
+
if (action is PendingNativeAction.MentionSuggestionSelect) {
|
|
1128
|
+
val mentions = addons.mentions ?: return false
|
|
1129
|
+
val currentQuery = currentMentionQueryState(mentions.trigger) ?: return false
|
|
1130
|
+
if (
|
|
1131
|
+
scope.mentionAnchor != currentQuery.anchor ||
|
|
1132
|
+
scope.mentionHead != currentQuery.head ||
|
|
1133
|
+
scope.mentionQuery != currentQuery.query
|
|
1134
|
+
) {
|
|
1135
|
+
return false
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
return true
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
private fun isNativeActionToolbarVisible(action: PendingNativeAction): Boolean {
|
|
1142
|
+
if (!showsToolbar || toolbarPlacement != ToolbarPlacement.KEYBOARD) return false
|
|
1143
|
+
if (keyboardToolbarView.parent == null || keyboardToolbarView.visibility != View.VISIBLE) return false
|
|
1144
|
+
if (action is PendingNativeAction.MentionSuggestionSelect) {
|
|
1145
|
+
return keyboardToolbarView.isShowingMentionSuggestions
|
|
1146
|
+
}
|
|
1147
|
+
return true
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
private fun isEditorEffectivelyFocusedForNativeAction(): Boolean =
|
|
1151
|
+
richTextView.editorEditText.hasFocus() ||
|
|
1152
|
+
(pendingToolbarRefocus != null && pendingToolbarRefocusEditorId == richTextView.editorId)
|
|
1153
|
+
|
|
1154
|
+
private fun clearPendingNativeActionRetryIfScopeChanged() {
|
|
1155
|
+
val action = pendingNativeAction ?: return
|
|
1156
|
+
val scope = pendingNativeActionScope ?: return
|
|
1157
|
+
if (!isPendingNativeActionScopeCurrent(action, scope)) {
|
|
1158
|
+
clearPendingNativeActionRetry()
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
private fun schedulePendingNativeActionRetry(action: PendingNativeAction) {
|
|
1163
|
+
val isSameAction = pendingNativeAction == action
|
|
1164
|
+
if (isSameAction) {
|
|
1165
|
+
pendingNativeActionRetryAttempts += 1
|
|
1166
|
+
} else {
|
|
1167
|
+
pendingNativeActionRetryAttempts = 1
|
|
1168
|
+
pendingNativeActionScope = currentNativeActionScope(action)
|
|
1169
|
+
}
|
|
1170
|
+
if (pendingNativeActionRetryAttempts > MAX_NATIVE_ACTION_RETRY_ATTEMPTS) {
|
|
1171
|
+
pendingNativeAction = action
|
|
1172
|
+
pendingNativeActionRetryEditorId = richTextView.editorId
|
|
1173
|
+
pendingNativeActionRetryScheduled = false
|
|
1174
|
+
return
|
|
1175
|
+
}
|
|
1176
|
+
pendingNativeAction = action
|
|
1177
|
+
pendingNativeActionRetryEditorId = richTextView.editorId
|
|
1178
|
+
if (pendingNativeActionRetryScheduled) return
|
|
1179
|
+
pendingNativeActionRetryScheduled = true
|
|
1180
|
+
pendingNativeActionRetryGeneration += 1
|
|
1181
|
+
val retryGeneration = pendingNativeActionRetryGeneration
|
|
1182
|
+
val retry = Runnable {
|
|
1183
|
+
if (retryGeneration != pendingNativeActionRetryGeneration) return@Runnable
|
|
1184
|
+
val retryAction = pendingNativeAction ?: run {
|
|
1185
|
+
pendingNativeActionRetryScheduled = false
|
|
1186
|
+
return@Runnable
|
|
1187
|
+
}
|
|
1188
|
+
val retryScope = pendingNativeActionScope ?: run {
|
|
1189
|
+
clearPendingNativeActionRetry()
|
|
1190
|
+
return@Runnable
|
|
1191
|
+
}
|
|
1192
|
+
if (pendingNativeActionRetryEditorId != richTextView.editorId || richTextView.editorId == 0L) {
|
|
1193
|
+
clearPendingNativeActionRetry()
|
|
1194
|
+
return@Runnable
|
|
1195
|
+
}
|
|
1196
|
+
if (!isPendingNativeActionScopeCurrent(retryAction, retryScope)) {
|
|
1197
|
+
clearPendingNativeActionRetry()
|
|
1198
|
+
return@Runnable
|
|
1199
|
+
}
|
|
1200
|
+
pendingNativeActionRetryScheduled = false
|
|
1201
|
+
val allowNextRetry = pendingNativeActionRetryAttempts < MAX_NATIVE_ACTION_RETRY_ATTEMPTS
|
|
1202
|
+
when (retryAction) {
|
|
1203
|
+
is PendingNativeAction.ToolbarItemPress ->
|
|
1204
|
+
handleToolbarItemPress(retryAction.item, allowPreflightRetry = allowNextRetry)
|
|
1205
|
+
is PendingNativeAction.MentionSuggestionSelect ->
|
|
1206
|
+
insertMentionSuggestion(retryAction.suggestion, allowPreflightRetry = allowNextRetry)
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
mainHandler.postDelayed(retry, NATIVE_ACTION_RETRY_DELAY_MS)
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
private fun retryPendingNativeActionFromWake() {
|
|
1213
|
+
val action = pendingNativeAction ?: return
|
|
1214
|
+
val scope = pendingNativeActionScope ?: run {
|
|
1215
|
+
clearPendingNativeActionRetry()
|
|
1216
|
+
return
|
|
1217
|
+
}
|
|
1218
|
+
if (!isPendingNativeActionScopeCurrent(action, scope)) {
|
|
1219
|
+
clearPendingNativeActionRetry()
|
|
1220
|
+
return
|
|
1221
|
+
}
|
|
1222
|
+
pendingNativeActionRetryAttempts = 0
|
|
1223
|
+
pendingNativeActionRetryScheduled = false
|
|
1224
|
+
when (action) {
|
|
1225
|
+
is PendingNativeAction.ToolbarItemPress ->
|
|
1226
|
+
handleToolbarItemPress(action.item, allowPreflightRetry = true)
|
|
1227
|
+
is PendingNativeAction.MentionSuggestionSelect ->
|
|
1228
|
+
insertMentionSuggestion(action.suggestion, allowPreflightRetry = true)
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
private fun documentVersionFromUpdateJSON(updateJSON: String?): Int? =
|
|
1233
|
+
try {
|
|
1234
|
+
if (updateJSON == null) null
|
|
1235
|
+
else {
|
|
1236
|
+
val version = JSONObject(updateJSON).optInt("documentVersion", Int.MIN_VALUE)
|
|
1237
|
+
version.takeIf { it != Int.MIN_VALUE }
|
|
1238
|
+
}
|
|
1239
|
+
} catch (_: Throwable) {
|
|
1240
|
+
null
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
private fun noteDocumentVersionFromUpdateJSON(updateJSON: String?) {
|
|
1244
|
+
documentVersionFromUpdateJSON(updateJSON)?.let { version ->
|
|
1245
|
+
lastDocumentVersion = version
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
private fun addPreflightUpdateToEvent(
|
|
1250
|
+
event: MutableMap<String, Any>,
|
|
1251
|
+
updateJSON: String?
|
|
1252
|
+
) {
|
|
1253
|
+
if (updateJSON == null) return
|
|
1254
|
+
event["updateJson"] = updateJSON
|
|
1255
|
+
documentVersionFromUpdateJSON(updateJSON)?.let { version ->
|
|
1256
|
+
event["documentVersion"] = version
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
private fun emitAddonEvent(payload: Map<String, Any>) {
|
|
1261
|
+
onAddonEventForTesting?.invoke(payload) ?: onAddonEvent(payload)
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
private fun canFocusCurrentEditor(): Boolean {
|
|
1265
|
+
val editorId = richTextView.editorId
|
|
1266
|
+
return editorId != 0L &&
|
|
1267
|
+
isAttachedToNativeWindow &&
|
|
1268
|
+
!NativeEditorViewRegistry.isDestroyed(editorId)
|
|
280
1269
|
}
|
|
281
1270
|
|
|
282
1271
|
fun focus() {
|
|
1272
|
+
if (!canFocusCurrentEditor()) return
|
|
283
1273
|
cancelPendingOutsideTapBlur()
|
|
284
1274
|
cancelPendingKeyboardDismiss()
|
|
1275
|
+
cancelPendingBlurRetry()
|
|
285
1276
|
richTextView.editorEditText.requestFocus()
|
|
286
1277
|
richTextView.editorEditText.post {
|
|
1278
|
+
if (!canFocusCurrentEditor()) return@post
|
|
287
1279
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
|
288
1280
|
imm?.showSoftInput(richTextView.editorEditText, InputMethodManager.SHOW_IMPLICIT)
|
|
289
1281
|
}
|
|
@@ -293,24 +1285,96 @@ class NativeEditorExpoView(
|
|
|
293
1285
|
cancelPendingOutsideTapBlur()
|
|
294
1286
|
cancelPendingKeyboardDismiss()
|
|
295
1287
|
clearRecentToolbarTouch()
|
|
1288
|
+
performBlur(deferKeyboardDismiss = false, allowRetry = true)
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
private fun performBlur(deferKeyboardDismiss: Boolean, allowRetry: Boolean) {
|
|
1292
|
+
if (handleDestroyedCurrentEditorIfNeeded()) return
|
|
1293
|
+
if (!richTextView.editorEditText.prepareForExternalEditorUpdate()) {
|
|
1294
|
+
if (allowRetry && pendingBlurRetryAttempts < MAX_PENDING_UPDATE_RETRY_ATTEMPTS) {
|
|
1295
|
+
schedulePendingBlurRetry(deferKeyboardDismiss)
|
|
1296
|
+
return
|
|
1297
|
+
}
|
|
1298
|
+
if (handleDestroyedCurrentEditorIfNeeded()) return
|
|
1299
|
+
richTextView.editorEditText.restoreAuthorizedTextIfNeeded()
|
|
1300
|
+
}
|
|
1301
|
+
completeBlur(deferKeyboardDismiss)
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
private fun completeBlur(deferKeyboardDismiss: Boolean) {
|
|
1305
|
+
cancelPendingBlurRetry()
|
|
296
1306
|
richTextView.editorEditText.clearFocus()
|
|
1307
|
+
if (deferKeyboardDismiss) {
|
|
1308
|
+
val dismiss = Runnable {
|
|
1309
|
+
pendingKeyboardDismiss = null
|
|
1310
|
+
if (!richTextView.editorEditText.hasFocus()) {
|
|
1311
|
+
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
|
1312
|
+
imm?.hideSoftInputFromWindow(richTextView.editorEditText.windowToken, 0)
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
pendingKeyboardDismiss = dismiss
|
|
1316
|
+
richTextView.editorEditText.post(dismiss)
|
|
1317
|
+
return
|
|
1318
|
+
}
|
|
297
1319
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
|
298
1320
|
imm?.hideSoftInputFromWindow(richTextView.editorEditText.windowToken, 0)
|
|
299
1321
|
}
|
|
300
1322
|
|
|
1323
|
+
private fun schedulePendingBlurRetry(deferKeyboardDismiss: Boolean) {
|
|
1324
|
+
pendingBlurRetry?.let {
|
|
1325
|
+
mainHandler.removeCallbacks(it)
|
|
1326
|
+
pendingBlurRetry = null
|
|
1327
|
+
}
|
|
1328
|
+
pendingBlurRetryAttempts += 1
|
|
1329
|
+
pendingBlurRetryEditorId = richTextView.editorId
|
|
1330
|
+
pendingBlurRetryGeneration += 1
|
|
1331
|
+
val retryGeneration = pendingBlurRetryGeneration
|
|
1332
|
+
val delayMs = NATIVE_ACTION_RETRY_DELAY_MS * pendingBlurRetryAttempts
|
|
1333
|
+
val retry = Runnable {
|
|
1334
|
+
pendingBlurRetry = null
|
|
1335
|
+
if (retryGeneration != pendingBlurRetryGeneration) return@Runnable
|
|
1336
|
+
if (pendingBlurRetryEditorId != richTextView.editorId) {
|
|
1337
|
+
pendingBlurRetryEditorId = null
|
|
1338
|
+
return@Runnable
|
|
1339
|
+
}
|
|
1340
|
+
pendingBlurRetryEditorId = null
|
|
1341
|
+
if (handleDestroyedCurrentEditorIfNeeded()) return@Runnable
|
|
1342
|
+
performBlur(deferKeyboardDismiss, allowRetry = true)
|
|
1343
|
+
}
|
|
1344
|
+
pendingBlurRetry = retry
|
|
1345
|
+
mainHandler.postDelayed(retry, delayMs)
|
|
1346
|
+
}
|
|
1347
|
+
|
|
301
1348
|
private fun blurWithDeferredKeyboardDismiss() {
|
|
302
1349
|
cancelPendingKeyboardDismiss()
|
|
303
1350
|
clearRecentToolbarTouch()
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
1351
|
+
performBlur(deferKeyboardDismiss = true, allowRetry = true)
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
private fun scheduleToolbarRefocus() {
|
|
1355
|
+
cancelPendingToolbarRefocus()
|
|
1356
|
+
val editorId = richTextView.editorId
|
|
1357
|
+
pendingToolbarRefocusEditorId = editorId
|
|
1358
|
+
pendingToolbarRefocusGeneration += 1
|
|
1359
|
+
val refocusGeneration = pendingToolbarRefocusGeneration
|
|
1360
|
+
val refocus = Runnable {
|
|
1361
|
+
pendingToolbarRefocus = null
|
|
1362
|
+
if (refocusGeneration != pendingToolbarRefocusGeneration) return@Runnable
|
|
1363
|
+
if (pendingToolbarRefocusEditorId != richTextView.editorId) return@Runnable
|
|
1364
|
+
pendingToolbarRefocusEditorId = null
|
|
1365
|
+
focus()
|
|
1366
|
+
}
|
|
1367
|
+
pendingToolbarRefocus = refocus
|
|
1368
|
+
richTextView.editorEditText.post(refocus)
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
private fun cancelPendingToolbarRefocus() {
|
|
1372
|
+
pendingToolbarRefocus?.let {
|
|
1373
|
+
richTextView.editorEditText.removeCallbacks(it)
|
|
1374
|
+
pendingToolbarRefocus = null
|
|
311
1375
|
}
|
|
312
|
-
|
|
313
|
-
|
|
1376
|
+
pendingToolbarRefocusEditorId = null
|
|
1377
|
+
pendingToolbarRefocusGeneration += 1
|
|
314
1378
|
}
|
|
315
1379
|
|
|
316
1380
|
private fun scheduleOutsideTapBlur() {
|
|
@@ -339,6 +1403,16 @@ class NativeEditorExpoView(
|
|
|
339
1403
|
}
|
|
340
1404
|
}
|
|
341
1405
|
|
|
1406
|
+
private fun cancelPendingBlurRetry() {
|
|
1407
|
+
pendingBlurRetry?.let {
|
|
1408
|
+
mainHandler.removeCallbacks(it)
|
|
1409
|
+
pendingBlurRetry = null
|
|
1410
|
+
}
|
|
1411
|
+
pendingBlurRetryEditorId = null
|
|
1412
|
+
pendingBlurRetryAttempts = 0
|
|
1413
|
+
pendingBlurRetryGeneration += 1
|
|
1414
|
+
}
|
|
1415
|
+
|
|
342
1416
|
fun getCaretRectJson(): String? {
|
|
343
1417
|
if (width <= 0 || height <= 0) return null
|
|
344
1418
|
val rect = richTextView.caretRect() ?: return null
|
|
@@ -353,12 +1427,182 @@ class NativeEditorExpoView(
|
|
|
353
1427
|
.toString()
|
|
354
1428
|
}
|
|
355
1429
|
|
|
1430
|
+
override fun onAttachedToWindow() {
|
|
1431
|
+
super.onAttachedToWindow()
|
|
1432
|
+
handleAttachedToWindow()
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
internal fun handleEditorDestroyed(editorId: Long) {
|
|
1436
|
+
if (richTextView.editorId != editorId && richTextView.editorEditText.editorId != editorId) {
|
|
1437
|
+
return
|
|
1438
|
+
}
|
|
1439
|
+
cancelPendingEditorUpdateRetry()
|
|
1440
|
+
clearPendingViewCommandUpdateRetry()
|
|
1441
|
+
cancelPendingThemeRetry()
|
|
1442
|
+
cancelPendingBlurRetry()
|
|
1443
|
+
cancelPendingDetachPreflightRetry()
|
|
1444
|
+
cancelPendingOutsideTapBlur()
|
|
1445
|
+
cancelPendingKeyboardDismiss()
|
|
1446
|
+
cancelPendingToolbarRefocus()
|
|
1447
|
+
cancelPendingPreflightWake()
|
|
1448
|
+
clearPendingNativeActionRetry()
|
|
1449
|
+
clearRecentToolbarTouch()
|
|
1450
|
+
uninstallOutsideTapBlurHandler()
|
|
1451
|
+
detachKeyboardToolbarIfNeeded()
|
|
1452
|
+
richTextView.setViewportBottomInsetPx(0)
|
|
1453
|
+
val editText = richTextView.editorEditText
|
|
1454
|
+
if (editText.hasFocus()) {
|
|
1455
|
+
editText.clearFocus()
|
|
1456
|
+
}
|
|
1457
|
+
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
|
1458
|
+
imm?.hideSoftInputFromWindow(editText.windowToken, 0)
|
|
1459
|
+
clearMentionQueryState(resetLastEvent = true)
|
|
1460
|
+
pendingEditorUpdateJson = null
|
|
1461
|
+
pendingEditorUpdateEditorId = null
|
|
1462
|
+
pendingEditorUpdateRevision = 0
|
|
1463
|
+
appliedEditorUpdateRevision = 0
|
|
1464
|
+
lastDocumentVersion = null
|
|
1465
|
+
lastReadyEditorId = null
|
|
1466
|
+
toolbarState = NativeToolbarState.empty
|
|
1467
|
+
keyboardToolbarView.applyState(toolbarState)
|
|
1468
|
+
keyboardToolbarView.visibility = View.GONE
|
|
1469
|
+
richTextView.editorId = 0L
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
private fun handleDestroyedCurrentEditorIfNeeded(): Boolean {
|
|
1473
|
+
val editorId = richTextView.editorId.takeIf { it != 0L }
|
|
1474
|
+
?: richTextView.editorEditText.editorId.takeIf { it != 0L }
|
|
1475
|
+
?: return false
|
|
1476
|
+
if (!NativeEditorViewRegistry.isDestroyed(editorId)) return false
|
|
1477
|
+
handleEditorDestroyed(editorId)
|
|
1478
|
+
return true
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
private fun handleAttachedToWindow() {
|
|
1482
|
+
isAttachedToNativeWindow = true
|
|
1483
|
+
cancelPendingDetachPreflightRetry()
|
|
1484
|
+
richTextView.clearDeferredEditorUnbind()
|
|
1485
|
+
val editorId = richTextView.editorId
|
|
1486
|
+
if (editorId == 0L) return
|
|
1487
|
+
if (NativeEditorViewRegistry.isDestroyed(editorId)) {
|
|
1488
|
+
handleEditorDestroyed(editorId)
|
|
1489
|
+
return
|
|
1490
|
+
}
|
|
1491
|
+
if (!NativeEditorViewRegistry.register(editorId, this)) {
|
|
1492
|
+
handleEditorDestroyed(editorId)
|
|
1493
|
+
return
|
|
1494
|
+
}
|
|
1495
|
+
richTextView.rebindEditorIfNeeded(
|
|
1496
|
+
notifyListener = !hasPendingEditorUpdateForEditor(editorId)
|
|
1497
|
+
)
|
|
1498
|
+
if (hasPendingTheme) {
|
|
1499
|
+
pendingThemeRetryEditorId = editorId
|
|
1500
|
+
}
|
|
1501
|
+
applyPendingEditorUpdateIfNeeded()
|
|
1502
|
+
applyPendingThemeIfNeeded()
|
|
1503
|
+
refreshReadyStateIfSettled()
|
|
1504
|
+
applyAutoFocusIfNeeded()
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
private fun emitEditorReady(editorUpdateRevision: Int? = null): Boolean {
|
|
1508
|
+
val editorId = richTextView.editorId
|
|
1509
|
+
if (editorId == 0L) return false
|
|
1510
|
+
if (!isAttachedToNativeWindow) return false
|
|
1511
|
+
if (richTextView.editorEditText.editorId != editorId) return false
|
|
1512
|
+
if (hasPendingEditorUpdateForCurrentEditor()) return false
|
|
1513
|
+
lastReadyEditorId = editorId
|
|
1514
|
+
val payload = mutableMapOf<String, Any>("editorId" to editorId)
|
|
1515
|
+
editorUpdateRevision?.let { payload["editorUpdateRevision"] = it }
|
|
1516
|
+
onEditorReadyForTesting?.invoke(payload) ?: onEditorReady(payload)
|
|
1517
|
+
return true
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
private fun emitEditorReadyIfNeeded() {
|
|
1521
|
+
val editorId = richTextView.editorId
|
|
1522
|
+
if (lastReadyEditorId == editorId) return
|
|
1523
|
+
emitEditorReady()
|
|
1524
|
+
}
|
|
1525
|
+
|
|
356
1526
|
override fun onDetachedFromWindow() {
|
|
1527
|
+
prepareForDetachFromWindow()
|
|
357
1528
|
super.onDetachedFromWindow()
|
|
1529
|
+
handleDetachedFromWindow()
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
private fun prepareForDetachFromWindow() {
|
|
1533
|
+
if (handleDestroyedCurrentEditorIfNeeded()) return
|
|
1534
|
+
val editorId = richTextView.editorId
|
|
1535
|
+
if (editorId == 0L || richTextView.editorEditText.editorId == 0L) return
|
|
1536
|
+
if (richTextView.editorEditText.prepareForExternalEditorUpdate()) {
|
|
1537
|
+
cancelPendingDetachPreflightRetry()
|
|
1538
|
+
richTextView.clearDeferredEditorUnbind()
|
|
1539
|
+
return
|
|
1540
|
+
}
|
|
1541
|
+
richTextView.deferEditorUnbindOnNextDetach()
|
|
1542
|
+
schedulePendingDetachPreflightRetry(editorId)
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
private fun schedulePendingDetachPreflightRetry(editorId: Long) {
|
|
1546
|
+
if (pendingDetachPreflightRetryScheduled) return
|
|
1547
|
+
if (pendingDetachPreflightRetryAttempts >= MAX_PENDING_UPDATE_RETRY_ATTEMPTS) {
|
|
1548
|
+
if (handleDestroyedCurrentEditorIfNeeded()) return
|
|
1549
|
+
richTextView.editorEditText.restoreAuthorizedTextIfNeeded()
|
|
1550
|
+
cancelPendingDetachPreflightRetry()
|
|
1551
|
+
richTextView.unbindEditorForDetachedViewIfNeeded()
|
|
1552
|
+
return
|
|
1553
|
+
}
|
|
1554
|
+
pendingDetachPreflightRetryAttempts += 1
|
|
1555
|
+
pendingDetachPreflightRetryEditorId = editorId
|
|
1556
|
+
pendingDetachPreflightRetryScheduled = true
|
|
1557
|
+
pendingDetachPreflightRetryGeneration += 1
|
|
1558
|
+
val retryGeneration = pendingDetachPreflightRetryGeneration
|
|
1559
|
+
val delayMs = NATIVE_ACTION_RETRY_DELAY_MS * pendingDetachPreflightRetryAttempts
|
|
1560
|
+
mainHandler.postDelayed({
|
|
1561
|
+
if (retryGeneration != pendingDetachPreflightRetryGeneration) return@postDelayed
|
|
1562
|
+
pendingDetachPreflightRetryScheduled = false
|
|
1563
|
+
if (isAttachedToNativeWindow || pendingDetachPreflightRetryEditorId != richTextView.editorId) {
|
|
1564
|
+
cancelPendingDetachPreflightRetry()
|
|
1565
|
+
return@postDelayed
|
|
1566
|
+
}
|
|
1567
|
+
if (handleDestroyedCurrentEditorIfNeeded()) return@postDelayed
|
|
1568
|
+
if (richTextView.editorEditText.prepareForExternalEditorUpdate()) {
|
|
1569
|
+
cancelPendingDetachPreflightRetry()
|
|
1570
|
+
richTextView.unbindEditorForDetachedViewIfNeeded()
|
|
1571
|
+
return@postDelayed
|
|
1572
|
+
}
|
|
1573
|
+
schedulePendingDetachPreflightRetry(editorId)
|
|
1574
|
+
}, delayMs)
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
private fun cancelPendingDetachPreflightRetry() {
|
|
1578
|
+
pendingDetachPreflightRetryScheduled = false
|
|
1579
|
+
pendingDetachPreflightRetryEditorId = null
|
|
1580
|
+
pendingDetachPreflightRetryAttempts = 0
|
|
1581
|
+
pendingDetachPreflightRetryGeneration += 1
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
private fun handleDetachedFromWindow() {
|
|
1585
|
+
isAttachedToNativeWindow = false
|
|
1586
|
+
NativeEditorViewRegistry.unregister(
|
|
1587
|
+
richTextView.editorId,
|
|
1588
|
+
this,
|
|
1589
|
+
blockCommandsUntilRegistered = true
|
|
1590
|
+
)
|
|
358
1591
|
cancelPendingOutsideTapBlur()
|
|
359
1592
|
cancelPendingKeyboardDismiss()
|
|
1593
|
+
cancelPendingToolbarRefocus()
|
|
1594
|
+
cancelPendingBlurRetry()
|
|
1595
|
+
cancelPendingEditorUpdateRetry()
|
|
1596
|
+
clearPendingViewCommandUpdateRetry()
|
|
1597
|
+
cancelPendingThemeRetry()
|
|
1598
|
+
clearPendingNativeActionRetry()
|
|
1599
|
+
cancelPendingPreflightWake()
|
|
1600
|
+
lastReadyEditorId = null
|
|
360
1601
|
uninstallOutsideTapBlurHandler()
|
|
1602
|
+
currentImeBottom = 0
|
|
1603
|
+
keyboardToolbarView.visibility = View.GONE
|
|
361
1604
|
detachKeyboardToolbarIfNeeded()
|
|
1605
|
+
richTextView.setViewportBottomInsetPx(0)
|
|
362
1606
|
}
|
|
363
1607
|
|
|
364
1608
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
@@ -436,30 +1680,131 @@ class NativeEditorExpoView(
|
|
|
436
1680
|
if (contentHeight <= 0) return
|
|
437
1681
|
if (!force && contentHeight == lastEmittedContentHeight) return
|
|
438
1682
|
lastEmittedContentHeight = contentHeight
|
|
439
|
-
onContentHeightChange(
|
|
1683
|
+
onContentHeightChange(
|
|
1684
|
+
mapOf(
|
|
1685
|
+
"contentHeight" to contentHeight,
|
|
1686
|
+
"editorId" to richTextView.editorId
|
|
1687
|
+
)
|
|
1688
|
+
)
|
|
440
1689
|
}
|
|
441
1690
|
|
|
442
1691
|
/** Applies an editor update from JS without echoing it back through events. */
|
|
443
|
-
fun applyEditorUpdate(updateJson: String)
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
1692
|
+
fun applyEditorUpdate(updateJson: String): Boolean =
|
|
1693
|
+
applyEditorUpdate(updateJson, scheduleViewCommandRetry = true)
|
|
1694
|
+
|
|
1695
|
+
private fun isEditorReadyForNativeUpdate(): Boolean {
|
|
1696
|
+
val editorId = richTextView.editorId
|
|
1697
|
+
return editorId == 0L || (isAttachedToNativeWindow && richTextView.editorEditText.editorId == editorId)
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
private fun applyEditorUpdate(
|
|
1701
|
+
updateJson: String,
|
|
1702
|
+
scheduleViewCommandRetry: Boolean,
|
|
1703
|
+
expectedEditorId: Long? = null
|
|
1704
|
+
): Boolean {
|
|
1705
|
+
if (Looper.myLooper() != Looper.getMainLooper()) {
|
|
1706
|
+
val postedEditorId = expectedEditorId ?: richTextView.editorId
|
|
1707
|
+
val apply = Runnable {
|
|
1708
|
+
if (postedEditorId != richTextView.editorId) return@Runnable
|
|
1709
|
+
applyEditorUpdate(updateJson, scheduleViewCommandRetry, postedEditorId)
|
|
1710
|
+
}
|
|
452
1711
|
if (!post(apply)) {
|
|
453
1712
|
richTextView.post(apply)
|
|
454
1713
|
}
|
|
1714
|
+
return false
|
|
1715
|
+
}
|
|
1716
|
+
if (expectedEditorId != null && expectedEditorId != richTextView.editorId) {
|
|
1717
|
+
return false
|
|
1718
|
+
}
|
|
1719
|
+
if (handleDestroyedCurrentEditorIfNeeded()) {
|
|
1720
|
+
return false
|
|
1721
|
+
}
|
|
1722
|
+
if (!isEditorReadyForNativeUpdate()) {
|
|
1723
|
+
if (scheduleViewCommandRetry) {
|
|
1724
|
+
scheduleViewCommandUpdateRetry(updateJson)
|
|
1725
|
+
}
|
|
1726
|
+
return false
|
|
1727
|
+
}
|
|
1728
|
+
if (
|
|
1729
|
+
blockEditorUpdatePreflightForTesting ||
|
|
1730
|
+
!richTextView.editorEditText.prepareForExternalEditorUpdate()
|
|
1731
|
+
) {
|
|
1732
|
+
if (scheduleViewCommandRetry) {
|
|
1733
|
+
scheduleViewCommandUpdateRetry(updateJson)
|
|
1734
|
+
}
|
|
1735
|
+
return false
|
|
1736
|
+
}
|
|
1737
|
+
isApplyingJSUpdate = true
|
|
1738
|
+
return try {
|
|
1739
|
+
richTextView.editorEditText.applyUpdateJSON(updateJson)
|
|
1740
|
+
true
|
|
1741
|
+
} catch (error: Throwable) {
|
|
1742
|
+
Log.w(LOG_TAG, "Failed to apply JS editor update", error)
|
|
1743
|
+
if (scheduleViewCommandRetry) {
|
|
1744
|
+
scheduleViewCommandUpdateRetry(updateJson)
|
|
1745
|
+
}
|
|
1746
|
+
false
|
|
1747
|
+
} finally {
|
|
1748
|
+
isApplyingJSUpdate = false
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
fun prepareForEditorCommandJSON(): String {
|
|
1753
|
+
if (Looper.myLooper() != Looper.getMainLooper()) {
|
|
1754
|
+
return NativeEditorViewRegistry.commandPreparationJSON(
|
|
1755
|
+
ready = false,
|
|
1756
|
+
blockedReason = "unknown"
|
|
1757
|
+
)
|
|
1758
|
+
}
|
|
1759
|
+
if (handleDestroyedCurrentEditorIfNeeded()) {
|
|
1760
|
+
return NativeEditorViewRegistry.commandPreparationJSON(
|
|
1761
|
+
ready = false,
|
|
1762
|
+
blockedReason = "destroyed"
|
|
1763
|
+
)
|
|
1764
|
+
}
|
|
1765
|
+
if (richTextView.editorId != 0L && !isAttachedToNativeWindow) {
|
|
1766
|
+
return NativeEditorViewRegistry.commandPreparationJSON(
|
|
1767
|
+
ready = false,
|
|
1768
|
+
blockedReason = "detached"
|
|
1769
|
+
)
|
|
1770
|
+
}
|
|
1771
|
+
if (richTextView.editorId != 0L && richTextView.editorEditText.editorId != richTextView.editorId) {
|
|
1772
|
+
return NativeEditorViewRegistry.commandPreparationJSON(
|
|
1773
|
+
ready = false,
|
|
1774
|
+
blockedReason = "detached"
|
|
1775
|
+
)
|
|
1776
|
+
}
|
|
1777
|
+
if (shouldBlockEditorCommandForPendingUpdate()) {
|
|
1778
|
+
return pendingEditorUpdateCommandPreparationJSON()
|
|
1779
|
+
}
|
|
1780
|
+
isApplyingJSUpdate = true
|
|
1781
|
+
return try {
|
|
1782
|
+
onBeforePrepareForEditorCommandForTesting?.invoke()
|
|
1783
|
+
val preparation = richTextView.editorEditText.prepareForExternalEditorCommand()
|
|
1784
|
+
NativeEditorViewRegistry.commandPreparationJSON(
|
|
1785
|
+
ready = preparation.ready,
|
|
1786
|
+
updateJSON = preparation.updateJSON,
|
|
1787
|
+
blockedReason = if (preparation.ready) null else "composition"
|
|
1788
|
+
)
|
|
1789
|
+
} finally {
|
|
1790
|
+
isApplyingJSUpdate = false
|
|
455
1791
|
}
|
|
456
1792
|
}
|
|
457
1793
|
|
|
458
1794
|
override fun onSelectionChanged(anchor: Int, head: Int) {
|
|
459
1795
|
val stateJson = refreshToolbarStateFromEditorSelection()
|
|
460
1796
|
refreshMentionQuery()
|
|
1797
|
+
clearPendingNativeActionRetryIfScopeChanged()
|
|
1798
|
+
schedulePendingPreflightWake()
|
|
461
1799
|
richTextView.refreshRemoteSelections()
|
|
462
|
-
val event = mutableMapOf<String, Any>(
|
|
1800
|
+
val event = mutableMapOf<String, Any>(
|
|
1801
|
+
"anchor" to anchor,
|
|
1802
|
+
"head" to head,
|
|
1803
|
+
"editorId" to richTextView.editorId
|
|
1804
|
+
)
|
|
1805
|
+
lastDocumentVersion?.let {
|
|
1806
|
+
event["documentVersion"] = it
|
|
1807
|
+
}
|
|
463
1808
|
if (stateJson != null) {
|
|
464
1809
|
event["stateJson"] = stateJson
|
|
465
1810
|
}
|
|
@@ -467,11 +1812,14 @@ class NativeEditorExpoView(
|
|
|
467
1812
|
}
|
|
468
1813
|
|
|
469
1814
|
override fun onEditorUpdate(updateJSON: String) {
|
|
1815
|
+
noteDocumentVersionFromUpdateJSON(updateJSON)
|
|
470
1816
|
NativeToolbarState.fromUpdateJson(updateJSON)?.let { state ->
|
|
471
1817
|
toolbarState = state
|
|
472
1818
|
keyboardToolbarView.applyState(state)
|
|
473
1819
|
}
|
|
474
1820
|
refreshMentionQuery()
|
|
1821
|
+
clearPendingNativeActionRetryIfScopeChanged()
|
|
1822
|
+
schedulePendingPreflightWake()
|
|
475
1823
|
richTextView.refreshRemoteSelections()
|
|
476
1824
|
if (heightBehavior == EditorHeightBehavior.AUTO_GROW) {
|
|
477
1825
|
post {
|
|
@@ -480,44 +1828,39 @@ class NativeEditorExpoView(
|
|
|
480
1828
|
}
|
|
481
1829
|
}
|
|
482
1830
|
if (isApplyingJSUpdate) return
|
|
483
|
-
val event = mapOf<String, Any>(
|
|
1831
|
+
val event = mapOf<String, Any>(
|
|
1832
|
+
"updateJson" to updateJSON,
|
|
1833
|
+
"editorId" to richTextView.editorId
|
|
1834
|
+
)
|
|
484
1835
|
onEditorUpdate(event)
|
|
485
1836
|
}
|
|
486
1837
|
|
|
487
1838
|
private fun installOutsideTapBlurHandlerIfNeeded() {
|
|
488
1839
|
val window = resolveActivity(context)?.window ?: return
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
|
494
|
-
val shouldBlur =
|
|
495
|
-
event.action == MotionEvent.ACTION_DOWN &&
|
|
496
|
-
richTextView.editorEditText.hasFocus() &&
|
|
497
|
-
isTouchOutsideEditor(event)
|
|
498
|
-
val result = currentCallback.dispatchTouchEvent(event)
|
|
499
|
-
if (shouldBlur) {
|
|
500
|
-
scheduleOutsideTapBlur()
|
|
501
|
-
} else if (event.action == MotionEvent.ACTION_DOWN) {
|
|
502
|
-
cancelPendingOutsideTapBlur()
|
|
503
|
-
}
|
|
504
|
-
return result
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
previousWindowCallback = currentCallback
|
|
509
|
-
outsideTapWindowCallback = wrappedCallback
|
|
510
|
-
window.callback = wrappedCallback
|
|
1840
|
+
if (outsideTapWindow === window) return
|
|
1841
|
+
uninstallOutsideTapBlurHandler()
|
|
1842
|
+
NativeEditorOutsideTapDispatcher.register(window, this)
|
|
1843
|
+
outsideTapWindow = window
|
|
511
1844
|
}
|
|
512
1845
|
|
|
513
1846
|
private fun uninstallOutsideTapBlurHandler() {
|
|
514
|
-
val window =
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
1847
|
+
val window = outsideTapWindow ?: return
|
|
1848
|
+
NativeEditorOutsideTapDispatcher.unregister(window, this)
|
|
1849
|
+
outsideTapWindow = null
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
internal fun shouldScheduleOutsideTapBlurForWindowEvent(event: MotionEvent): Boolean =
|
|
1853
|
+
isAttachedToNativeWindow &&
|
|
1854
|
+
event.action == MotionEvent.ACTION_DOWN &&
|
|
1855
|
+
richTextView.editorEditText.hasFocus() &&
|
|
1856
|
+
isTouchOutsideEditor(event)
|
|
1857
|
+
|
|
1858
|
+
internal fun scheduleOutsideTapBlurFromWindowDispatcher() {
|
|
1859
|
+
scheduleOutsideTapBlur()
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
internal fun cancelOutsideTapBlurFromWindowDispatcher() {
|
|
1863
|
+
cancelPendingOutsideTapBlur()
|
|
521
1864
|
}
|
|
522
1865
|
|
|
523
1866
|
private fun isTouchOutsideEditor(event: MotionEvent): Boolean {
|
|
@@ -555,6 +1898,125 @@ class NativeEditorExpoView(
|
|
|
555
1898
|
internal fun shouldPreserveFocusAfterToolbarTouchForTesting(): Boolean =
|
|
556
1899
|
shouldPreserveFocusAfterToolbarTouch()
|
|
557
1900
|
|
|
1901
|
+
internal fun setAttachedToNativeWindowForTesting(isAttached: Boolean) {
|
|
1902
|
+
isAttachedToNativeWindow = isAttached
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
internal fun handleAttachedToWindowForTesting() {
|
|
1906
|
+
handleAttachedToWindow()
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
internal fun handleDetachedFromWindowForTesting() {
|
|
1910
|
+
prepareForDetachFromWindow()
|
|
1911
|
+
handleDetachedFromWindow()
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
internal fun performBlurForTesting(deferKeyboardDismiss: Boolean = false) {
|
|
1915
|
+
performBlur(deferKeyboardDismiss = deferKeyboardDismiss, allowRetry = true)
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
internal fun pendingBlurRetryAttemptsForTesting(): Int = pendingBlurRetryAttempts
|
|
1919
|
+
|
|
1920
|
+
internal fun pendingDetachPreflightRetryAttemptsForTesting(): Int =
|
|
1921
|
+
pendingDetachPreflightRetryAttempts
|
|
1922
|
+
|
|
1923
|
+
internal fun hasPendingOutsideTapBlurForTesting(): Boolean = pendingOutsideTapBlur != null
|
|
1924
|
+
|
|
1925
|
+
internal fun hasPendingKeyboardDismissForTesting(): Boolean = pendingKeyboardDismiss != null
|
|
1926
|
+
|
|
1927
|
+
internal fun hasPendingPreflightWakeForTesting(): Boolean = pendingPreflightWakeScheduled
|
|
1928
|
+
|
|
1929
|
+
internal fun hasPendingToolbarRefocusForTesting(): Boolean = pendingToolbarRefocus != null
|
|
1930
|
+
|
|
1931
|
+
internal fun isKeyboardToolbarAttachedForTesting(): Boolean = keyboardToolbarView.parent != null
|
|
1932
|
+
|
|
1933
|
+
internal fun currentImeBottomForTesting(): Int = currentImeBottom
|
|
1934
|
+
|
|
1935
|
+
internal fun setCurrentImeBottomForTesting(bottom: Int) {
|
|
1936
|
+
currentImeBottom = bottom
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
internal fun updateAttachedKeyboardToolbarForInsetsForTesting() {
|
|
1940
|
+
updateAttachedKeyboardToolbarForInsets()
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
internal fun scheduleToolbarRefocusForTesting() {
|
|
1944
|
+
scheduleToolbarRefocus()
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
internal fun applyAutoFocusForTesting() {
|
|
1948
|
+
applyAutoFocusIfNeeded()
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
internal fun installOutsideTapBlurHandlerForTesting() {
|
|
1952
|
+
installOutsideTapBlurHandlerIfNeeded()
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
internal fun uninstallOutsideTapBlurHandlerForTesting() {
|
|
1956
|
+
uninstallOutsideTapBlurHandler()
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
internal fun schedulePendingPreflightWakeForTesting() {
|
|
1960
|
+
schedulePendingPreflightWake()
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
internal fun hasPendingNativeActionForTesting(): Boolean = pendingNativeAction != null
|
|
1964
|
+
|
|
1965
|
+
internal fun pendingNativeActionRetryAttemptsForTesting(): Int = pendingNativeActionRetryAttempts
|
|
1966
|
+
|
|
1967
|
+
internal fun lastDocumentVersionForTesting(): Int? = lastDocumentVersion
|
|
1968
|
+
|
|
1969
|
+
internal fun setLastDocumentVersionForTesting(documentVersion: Int?) {
|
|
1970
|
+
lastDocumentVersion = documentVersion
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
internal fun refreshToolbarStateFromEditorSelectionForTesting(): String? =
|
|
1974
|
+
refreshToolbarStateFromEditorSelection()
|
|
1975
|
+
|
|
1976
|
+
internal fun handleToolbarItemPressForTesting(item: NativeToolbarItem) {
|
|
1977
|
+
handleToolbarItemPress(item)
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
internal fun insertMentionSuggestionForTesting(suggestion: NativeMentionSuggestion) {
|
|
1981
|
+
insertMentionSuggestion(suggestion)
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
internal fun wakePendingPreflightWorkForTesting() {
|
|
1985
|
+
wakePendingPreflightWork()
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
internal fun emitEditorReadyForTesting(editorUpdateRevision: Int? = null): Boolean =
|
|
1989
|
+
emitEditorReady(editorUpdateRevision)
|
|
1990
|
+
|
|
1991
|
+
internal fun pendingEditorUpdateJsonForTesting(): String? = pendingEditorUpdateJson
|
|
1992
|
+
|
|
1993
|
+
internal fun pendingEditorUpdateRevisionForTesting(): Int = pendingEditorUpdateRevision
|
|
1994
|
+
|
|
1995
|
+
internal fun setAppliedEditorUpdateRevisionForTesting(editorUpdateRevision: Int) {
|
|
1996
|
+
appliedEditorUpdateRevision = editorUpdateRevision
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
internal fun pendingEditorUpdateEditorIdForTesting(): Long? = pendingEditorUpdateEditorId
|
|
2000
|
+
|
|
2001
|
+
internal fun pendingViewCommandUpdateJsonForTesting(): String? = pendingViewCommandUpdateJson
|
|
2002
|
+
|
|
2003
|
+
internal fun pendingViewCommandUpdateRetryAttemptsForTesting(): Int =
|
|
2004
|
+
pendingViewCommandUpdateRetryAttempts
|
|
2005
|
+
|
|
2006
|
+
internal fun scheduleViewCommandUpdateRetryForTesting(updateJson: String) {
|
|
2007
|
+
scheduleViewCommandUpdateRetry(updateJson)
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
internal fun pendingThemeJsonForTesting(): String? = pendingThemeJson.takeIf { hasPendingTheme }
|
|
2011
|
+
|
|
2012
|
+
internal fun lastThemeJsonForTesting(): String? = lastThemeJson
|
|
2013
|
+
|
|
2014
|
+
internal fun pendingThemeRetryAttemptsForTesting(): Int = pendingThemeRetryAttempts
|
|
2015
|
+
|
|
2016
|
+
internal fun applyPendingThemeForTesting() {
|
|
2017
|
+
applyPendingThemeIfNeeded()
|
|
2018
|
+
}
|
|
2019
|
+
|
|
558
2020
|
private fun isTouchInsideStandaloneToolbar(event: MotionEvent): Boolean {
|
|
559
2021
|
val visibleWindowFrame = Rect()
|
|
560
2022
|
getWindowVisibleDisplayFrame(visibleWindowFrame)
|
|
@@ -619,6 +2081,11 @@ class NativeEditorExpoView(
|
|
|
619
2081
|
private const val TOOLBAR_HIT_SLOP_DP = 8f
|
|
620
2082
|
private const val TOOLBAR_FOCUS_PRESERVE_MS = 750L
|
|
621
2083
|
private const val OUTSIDE_TAP_BLUR_DELAY_MS = 100L
|
|
2084
|
+
private const val NATIVE_ACTION_RETRY_DELAY_MS = 16L
|
|
2085
|
+
private const val PENDING_UPDATE_RECOVERY_RETRY_DELAY_MS = 250L
|
|
2086
|
+
private const val MAX_NATIVE_ACTION_RETRY_ATTEMPTS = 3
|
|
2087
|
+
private const val MAX_PENDING_UPDATE_RETRY_ATTEMPTS = 5
|
|
2088
|
+
private const val LOG_TAG = "NativeEditor"
|
|
622
2089
|
}
|
|
623
2090
|
|
|
624
2091
|
private fun resolveActivity(context: Context): Activity? {
|
|
@@ -658,8 +2125,12 @@ class NativeEditorExpoView(
|
|
|
658
2125
|
)
|
|
659
2126
|
}
|
|
660
2127
|
|
|
661
|
-
private fun clearMentionQueryState() {
|
|
2128
|
+
private fun clearMentionQueryState(resetLastEvent: Boolean = false) {
|
|
662
2129
|
mentionQueryState = null
|
|
2130
|
+
if (resetLastEvent) {
|
|
2131
|
+
lastMentionEventJson = null
|
|
2132
|
+
lastMentionEventEditorId = null
|
|
2133
|
+
}
|
|
663
2134
|
syncKeyboardToolbarMentionSuggestions(emptyList())
|
|
664
2135
|
}
|
|
665
2136
|
|
|
@@ -722,10 +2193,15 @@ class NativeEditorExpoView(
|
|
|
722
2193
|
.put("trigger", trigger)
|
|
723
2194
|
.put("range", JSONObject().put("anchor", anchor).put("head", head))
|
|
724
2195
|
.put("isActive", isActive)
|
|
2196
|
+
.apply {
|
|
2197
|
+
lastDocumentVersion?.let { put("documentVersion", it) }
|
|
2198
|
+
}
|
|
725
2199
|
.toString()
|
|
726
|
-
|
|
2200
|
+
val editorId = richTextView.editorId
|
|
2201
|
+
if (eventJson == lastMentionEventJson && editorId == lastMentionEventEditorId) return
|
|
727
2202
|
lastMentionEventJson = eventJson
|
|
728
|
-
|
|
2203
|
+
lastMentionEventEditorId = editorId
|
|
2204
|
+
emitAddonEvent(mapOf("eventJson" to eventJson, "editorId" to editorId))
|
|
729
2205
|
}
|
|
730
2206
|
|
|
731
2207
|
private fun resolvedMentionAttrs(
|
|
@@ -748,15 +2224,19 @@ class NativeEditorExpoView(
|
|
|
748
2224
|
.put("trigger", trigger)
|
|
749
2225
|
.put("suggestionKey", suggestion.key)
|
|
750
2226
|
.put("attrs", attrs)
|
|
2227
|
+
.apply {
|
|
2228
|
+
lastDocumentVersion?.let { put("documentVersion", it) }
|
|
2229
|
+
}
|
|
751
2230
|
.toString()
|
|
752
|
-
|
|
2231
|
+
emitAddonEvent(mapOf("eventJson" to eventJson, "editorId" to richTextView.editorId))
|
|
753
2232
|
}
|
|
754
2233
|
|
|
755
2234
|
private fun emitMentionSelectRequest(
|
|
756
2235
|
trigger: String,
|
|
757
2236
|
suggestion: NativeMentionSuggestion,
|
|
758
2237
|
attrs: JSONObject,
|
|
759
|
-
range: MentionQueryState
|
|
2238
|
+
range: MentionQueryState,
|
|
2239
|
+
preflightUpdateJSON: String?
|
|
760
2240
|
) {
|
|
761
2241
|
val eventJson = JSONObject()
|
|
762
2242
|
.put("type", "mentionsSelectRequest")
|
|
@@ -764,16 +2244,60 @@ class NativeEditorExpoView(
|
|
|
764
2244
|
.put("suggestionKey", suggestion.key)
|
|
765
2245
|
.put("attrs", attrs)
|
|
766
2246
|
.put("range", JSONObject().put("anchor", range.anchor).put("head", range.head))
|
|
2247
|
+
.apply {
|
|
2248
|
+
if (preflightUpdateJSON != null) {
|
|
2249
|
+
put("updateJson", preflightUpdateJSON)
|
|
2250
|
+
}
|
|
2251
|
+
(documentVersionFromUpdateJSON(preflightUpdateJSON) ?: lastDocumentVersion)
|
|
2252
|
+
?.let { put("documentVersion", it) }
|
|
2253
|
+
}
|
|
767
2254
|
.toString()
|
|
768
|
-
|
|
2255
|
+
emitAddonEvent(mapOf("eventJson" to eventJson, "editorId" to richTextView.editorId))
|
|
769
2256
|
}
|
|
770
2257
|
|
|
771
|
-
private fun insertMentionSuggestion(
|
|
2258
|
+
private fun insertMentionSuggestion(
|
|
2259
|
+
suggestion: NativeMentionSuggestion,
|
|
2260
|
+
allowPreflightRetry: Boolean = true
|
|
2261
|
+
) {
|
|
2262
|
+
if (handleDestroyedCurrentEditorIfNeeded()) return
|
|
2263
|
+
if (!richTextView.editorEditText.isEditable) {
|
|
2264
|
+
clearPendingNativeActionRetry()
|
|
2265
|
+
return
|
|
2266
|
+
}
|
|
772
2267
|
val mentions = addons.mentions ?: return
|
|
773
|
-
|
|
2268
|
+
if (shouldBlockEditorCommandForPendingUpdate()) {
|
|
2269
|
+
if (allowPreflightRetry) {
|
|
2270
|
+
schedulePendingNativeActionRetry(
|
|
2271
|
+
PendingNativeAction.MentionSuggestionSelect(suggestion)
|
|
2272
|
+
)
|
|
2273
|
+
}
|
|
2274
|
+
return
|
|
2275
|
+
}
|
|
2276
|
+
val preparation = richTextView.editorEditText.prepareForExternalEditorCommand()
|
|
2277
|
+
if (!preparation.ready) {
|
|
2278
|
+
if (allowPreflightRetry) {
|
|
2279
|
+
schedulePendingNativeActionRetry(
|
|
2280
|
+
PendingNativeAction.MentionSuggestionSelect(suggestion)
|
|
2281
|
+
)
|
|
2282
|
+
}
|
|
2283
|
+
return
|
|
2284
|
+
}
|
|
2285
|
+
val preflightUpdateJSON = preparation.updateJSON
|
|
2286
|
+
noteDocumentVersionFromUpdateJSON(preflightUpdateJSON)
|
|
2287
|
+
clearPendingNativeActionRetry()
|
|
2288
|
+
val queryState = currentMentionQueryState(mentions.trigger) ?: run {
|
|
2289
|
+
clearMentionQueryState()
|
|
2290
|
+
return
|
|
2291
|
+
}
|
|
2292
|
+
val freshSuggestions = filteredMentionSuggestions(queryState, mentions)
|
|
2293
|
+
if (freshSuggestions.none { it.key == suggestion.key }) {
|
|
2294
|
+
refreshMentionQuery()
|
|
2295
|
+
return
|
|
2296
|
+
}
|
|
2297
|
+
mentionQueryState = queryState
|
|
774
2298
|
val attrs = resolvedMentionAttrs(mentions.trigger, suggestion)
|
|
775
2299
|
if (mentions.resolveSelectionAttrs || mentions.resolveTheme) {
|
|
776
|
-
emitMentionSelectRequest(mentions.trigger, suggestion, attrs, queryState)
|
|
2300
|
+
emitMentionSelectRequest(mentions.trigger, suggestion, attrs, queryState, preflightUpdateJSON)
|
|
777
2301
|
lastMentionEventJson = null
|
|
778
2302
|
clearMentionQueryState()
|
|
779
2303
|
return
|
|
@@ -803,7 +2327,14 @@ class NativeEditorExpoView(
|
|
|
803
2327
|
|
|
804
2328
|
private fun refreshToolbarStateFromEditorSelection(): String? {
|
|
805
2329
|
if (richTextView.editorId == 0L) return null
|
|
2330
|
+
if (handleDestroyedCurrentEditorIfNeeded()) return null
|
|
2331
|
+
onRefreshToolbarStateFromEditorSelectionForTesting?.let { callback ->
|
|
2332
|
+
val stateJson = callback()
|
|
2333
|
+
noteDocumentVersionFromUpdateJSON(stateJson)
|
|
2334
|
+
return stateJson
|
|
2335
|
+
}
|
|
806
2336
|
val stateJson = editorGetSelectionState(richTextView.editorId.toULong())
|
|
2337
|
+
noteDocumentVersionFromUpdateJSON(stateJson)
|
|
807
2338
|
val state = NativeToolbarState.fromUpdateJson(stateJson) ?: return null
|
|
808
2339
|
toolbarState = state
|
|
809
2340
|
keyboardToolbarView.applyState(state)
|
|
@@ -847,6 +2378,9 @@ class NativeEditorExpoView(
|
|
|
847
2378
|
}
|
|
848
2379
|
|
|
849
2380
|
private fun updateAttachedKeyboardToolbarForInsets() {
|
|
2381
|
+
if (currentImeBottom <= 0) {
|
|
2382
|
+
clearPendingNativeActionRetry()
|
|
2383
|
+
}
|
|
850
2384
|
keyboardToolbarView.visibility = if (currentImeBottom > 0) View.VISIBLE else View.INVISIBLE
|
|
851
2385
|
updateEditorViewportInset()
|
|
852
2386
|
}
|
|
@@ -854,6 +2388,7 @@ class NativeEditorExpoView(
|
|
|
854
2388
|
private fun updateKeyboardToolbarVisibility() {
|
|
855
2389
|
val shouldAttach =
|
|
856
2390
|
showsToolbar &&
|
|
2391
|
+
canFocusCurrentEditor() &&
|
|
857
2392
|
toolbarPlacement == ToolbarPlacement.KEYBOARD &&
|
|
858
2393
|
richTextView.editorEditText.isEditable &&
|
|
859
2394
|
richTextView.editorEditText.hasFocus()
|
|
@@ -870,7 +2405,7 @@ class NativeEditorExpoView(
|
|
|
870
2405
|
updateEditorViewportInset()
|
|
871
2406
|
}
|
|
872
2407
|
|
|
873
|
-
private fun updateEditorViewportInset() {
|
|
2408
|
+
private fun updateEditorViewportInset(forceMeasureToolbar: Boolean = false) {
|
|
874
2409
|
val shouldReserveToolbarSpace =
|
|
875
2410
|
heightBehavior == EditorHeightBehavior.FIXED &&
|
|
876
2411
|
showsToolbar &&
|
|
@@ -889,7 +2424,7 @@ class NativeEditorExpoView(
|
|
|
889
2424
|
val toolbarTheme = richTextView.editorEditText.theme?.toolbar
|
|
890
2425
|
val density = resources.displayMetrics.density
|
|
891
2426
|
val horizontalInsetPx = ((toolbarTheme?.resolvedHorizontalInset() ?: 0f) * density).toInt()
|
|
892
|
-
if (keyboardToolbarView.measuredHeight == 0) {
|
|
2427
|
+
if (forceMeasureToolbar || keyboardToolbarView.measuredHeight == 0) {
|
|
893
2428
|
val availableWidth = (hostWidth - horizontalInsetPx * 2).coerceAtLeast(0)
|
|
894
2429
|
val widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST)
|
|
895
2430
|
val heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
|
|
@@ -904,7 +2439,46 @@ class NativeEditorExpoView(
|
|
|
904
2439
|
richTextView.editorEditText.performToolbarToggleList(listType, isActive)
|
|
905
2440
|
}
|
|
906
2441
|
|
|
907
|
-
private fun handleToolbarItemPress(
|
|
2442
|
+
private fun handleToolbarItemPress(
|
|
2443
|
+
item: NativeToolbarItem,
|
|
2444
|
+
allowPreflightRetry: Boolean = true
|
|
2445
|
+
) {
|
|
2446
|
+
if (handleDestroyedCurrentEditorIfNeeded()) return
|
|
2447
|
+
if (!richTextView.editorEditText.isEditable) {
|
|
2448
|
+
clearPendingNativeActionRetry()
|
|
2449
|
+
return
|
|
2450
|
+
}
|
|
2451
|
+
var preflightUpdateJSON: String? = null
|
|
2452
|
+
val needsEditorPreflight = when (item.type) {
|
|
2453
|
+
ToolbarItemKind.mark,
|
|
2454
|
+
ToolbarItemKind.heading,
|
|
2455
|
+
ToolbarItemKind.blockquote,
|
|
2456
|
+
ToolbarItemKind.list,
|
|
2457
|
+
ToolbarItemKind.command,
|
|
2458
|
+
ToolbarItemKind.node,
|
|
2459
|
+
ToolbarItemKind.action -> true
|
|
2460
|
+
ToolbarItemKind.group,
|
|
2461
|
+
ToolbarItemKind.separator -> false
|
|
2462
|
+
}
|
|
2463
|
+
if (needsEditorPreflight) {
|
|
2464
|
+
if (shouldBlockEditorCommandForPendingUpdate()) {
|
|
2465
|
+
if (allowPreflightRetry) {
|
|
2466
|
+
schedulePendingNativeActionRetry(PendingNativeAction.ToolbarItemPress(item))
|
|
2467
|
+
}
|
|
2468
|
+
return
|
|
2469
|
+
}
|
|
2470
|
+
val preparation = richTextView.editorEditText.prepareForExternalEditorCommand()
|
|
2471
|
+
if (!preparation.ready) {
|
|
2472
|
+
if (allowPreflightRetry) {
|
|
2473
|
+
schedulePendingNativeActionRetry(PendingNativeAction.ToolbarItemPress(item))
|
|
2474
|
+
}
|
|
2475
|
+
return
|
|
2476
|
+
}
|
|
2477
|
+
preflightUpdateJSON = preparation.updateJSON
|
|
2478
|
+
noteDocumentVersionFromUpdateJSON(preflightUpdateJSON)
|
|
2479
|
+
clearPendingNativeActionRetry()
|
|
2480
|
+
}
|
|
2481
|
+
if (handleDestroyedCurrentEditorIfNeeded()) return
|
|
908
2482
|
when (item.type) {
|
|
909
2483
|
ToolbarItemKind.mark -> item.mark?.let { richTextView.editorEditText.performToolbarToggleMark(it) }
|
|
910
2484
|
ToolbarItemKind.heading -> item.headingLevel?.let { richTextView.editorEditText.performToolbarToggleHeading(it) }
|
|
@@ -918,7 +2492,20 @@ class NativeEditorExpoView(
|
|
|
918
2492
|
null -> Unit
|
|
919
2493
|
}
|
|
920
2494
|
ToolbarItemKind.node -> item.nodeType?.let { richTextView.editorEditText.performToolbarInsertNode(it) }
|
|
921
|
-
ToolbarItemKind.action -> item.key?.let {
|
|
2495
|
+
ToolbarItemKind.action -> item.key?.let {
|
|
2496
|
+
if (handleDestroyedCurrentEditorIfNeeded()) return
|
|
2497
|
+
val payload = mutableMapOf<String, Any>(
|
|
2498
|
+
"key" to it,
|
|
2499
|
+
"editorId" to richTextView.editorId
|
|
2500
|
+
)
|
|
2501
|
+
addPreflightUpdateToEvent(payload, preflightUpdateJSON)
|
|
2502
|
+
if (!payload.containsKey("documentVersion")) {
|
|
2503
|
+
lastDocumentVersion?.let { version ->
|
|
2504
|
+
payload["documentVersion"] = version
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
onToolbarActionForTesting?.invoke(payload) ?: onToolbarAction(payload)
|
|
2508
|
+
}
|
|
922
2509
|
ToolbarItemKind.group -> Unit
|
|
923
2510
|
ToolbarItemKind.separator -> Unit
|
|
924
2511
|
}
|