Skip to content

Commit 0450d40

Browse files
committed
Synchronizing app undo stack with iink.
[Offscreen] Remove useless encapsulated withContext calls. [Offscreen] Remove proper gesture stroke. [Offscreen] Better code
1 parent b8b3c7b commit 0450d40

File tree

2 files changed

+73
-61
lines changed

2 files changed

+73
-61
lines changed

samples/offscreen-interactivity/src/main/java/com/myscript/iink/demo/inksample/InkApplication.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class InkApplication: Application() {
2020
conf.setStringArray("configuration-manager.search-path", arrayOf(confDir))
2121
val tempDir = File(cacheDir, "tmp")
2222
conf.setString("content-package.temp-folder", tempDir.absolutePath)
23+
conf.setBoolean("offscreen-editor.history-manager.enable", true);
2324
}
2425
}
2526
}

samples/offscreen-interactivity/src/main/java/com/myscript/iink/demo/inksample/ui/InkViewModel.kt

Lines changed: 72 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ enum class EditorHistoryAction {
5858

5959
data class EditorHistoryItem(
6060
val editorHistoryAction: EditorHistoryAction,
61-
val strokes: List<InkView.Brush>
61+
val strokes: List<InkView.Brush>,
62+
val iinkHistoryId: String
6263
)
6364

6465
data class EditorHistoryState(
@@ -89,7 +90,8 @@ class InkViewModel(
8990

9091
private val undoRedoStack = mutableListOf<List<EditorHistoryItem>>()
9192
private var undoRedoIndex = 0
92-
private val _editorHistoryState: MutableLiveData<EditorHistoryState> = MutableLiveData(EditorHistoryState())
93+
private val strokeIdsMappingDeleted: MutableMap<String /* id of iink stroke */, String /* id of app stroke */> = mutableMapOf()
94+
private val _editorHistoryState = MutableLiveData(EditorHistoryState())
9395
val editorHistoryState: LiveData<EditorHistoryState>
9496
get() = _editorHistoryState
9597

@@ -115,7 +117,7 @@ class InkViewModel(
115117
// to millimeters, which are used in offscreen editor coordinates, and it can perform this conversion in reverse as well.
116118
private var converter: DisplayMetricsConverter? = null
117119
// Maps the data model IDs of the iink offscreen editor to the data model IDs of the application.
118-
private val strokeIdsMapping: MutableMap<String /* id of iink stroke*/, String /* id of app's stroke*/> = mutableMapOf()
120+
private val strokeIdsMapping: MutableMap<String /* id of iink stroke */, String /* id of app stroke */> = mutableMapOf()
119121

120122
private val contentFile: File
121123
get() = File(dataDir, "OffscreenEditor.iink")
@@ -177,8 +179,9 @@ class InkViewModel(
177179
Log.d(TAG, "IOffscreenGestureHandler onStrikethrough gesture detected")
178180
val itemIdHelper = itemIdHelper ?: return OffscreenGestureAction.ADD_STROKE
179181

180-
viewModelScope.launch(Dispatchers.Main) {
182+
viewModelScope.launch(uiDispatcher) {
181183
val remainingStrokes = _strokes.value?.toMutableList() ?: mutableListOf()
184+
val strokesToRemove = mutableListOf<InkView.Brush>()
182185

183186
// With workDispatcher, this snippet below will not be processed in parallel but rather one at a time.
184187
// This is especially useful when you want to ensure that tasks are executed in a specific order,
@@ -187,32 +190,29 @@ class InkViewModel(
187190
// ItemIds may refer to partial strokes, retrieve the corresponding full strokes ids
188191
val fullStrokeIds = itemIds.map(itemIdHelper::getFullItemId)
189192

190-
val strokesToRemove = mutableListOf<InkView.Brush>()
191193
fullStrokeIds.forEach { strokeId ->
192194
val appStrokeId = strokeIdsMapping[strokeId]
193195
remainingStrokes.firstOrNull { it.id == appStrokeId }?.let { strokeBrush ->
194196
strokesToRemove.add(strokeBrush)
195197
}
196198
}
197199

198-
removeLastFromUndoRedoStack()
199-
val newHistory = addToUndoRedoStack(EditorHistoryAction.REMOVE, strokesToRemove)
200-
withContext(uiDispatcher) {
201-
_editorHistoryState.value = newHistory
202-
}
203-
204200
// Erase the gesture stroke (gestureStrokeId) and the erased strokes (fullItemIds) in your application
205201
(fullStrokeIds + gestureStrokeId).forEach { strokeId ->
206-
val appStrokeId = strokeIdsMapping[strokeId]
207-
strokeIdsMapping.remove(strokeId)
208-
val strokeBrush = remainingStrokes.firstOrNull { it.id == appStrokeId }
209-
remainingStrokes.remove(strokeBrush)
202+
strokeIdsMapping.remove(strokeId)?.let { appStrokeId ->
203+
strokeIdsMappingDeleted[strokeId] = appStrokeId
204+
val strokeBrush = remainingStrokes.firstOrNull { it.id == appStrokeId }
205+
remainingStrokes.remove(strokeBrush)
206+
}
210207
}
211208

212209
// Erase the scratched strokes (fullItemIds) in offscreen editor
213210
val strokeIdsToErase = (fullStrokeIds - gestureStrokeId).toTypedArray()
214-
editor.erase(strokeIdsToErase)
211+
offscreenEditor?.erase(strokeIdsToErase)
215212
}
213+
removeGestureFromUndoRedoStack(gestureStrokeId)
214+
_editorHistoryState.value = addToUndoRedoStack(EditorHistoryAction.REMOVE, strokesToRemove, iinkHistoryId())
215+
216216
_strokes.value = remainingStrokes
217217
}
218218
// Discard the gesture stroke (gestureStrokeId) in offscreen editor
@@ -228,6 +228,7 @@ class InkViewModel(
228228

229229
viewModelScope.launch(uiDispatcher) {
230230
val remainingStrokes = _strokes.value?.toMutableList() ?: mutableListOf()
231+
val strokesToRemove = mutableListOf<InkView.Brush>()
231232

232233
withContext(workDispatcher) {
233234
// Retrieve the full stroke ids
@@ -257,25 +258,20 @@ class InkViewModel(
257258
emptyArray()
258259
}
259260

260-
val strokesToRemove = mutableListOf<InkView.Brush>()
261261
fullItemIds.forEach { strokeId ->
262262
val appStrokeId = strokeIdsMapping[strokeId]
263263
remainingStrokes.firstOrNull { it.id == appStrokeId }?.let {
264264
strokesToRemove.add(it)
265265
}
266266
}
267-
removeLastFromUndoRedoStack()
268-
val newHistory = addToUndoRedoStack(EditorHistoryAction.REMOVE, strokesToRemove)
269-
withContext(uiDispatcher) {
270-
_editorHistoryState.value = newHistory
271-
}
272267

273268
// Erase the erased strokes and gesture strokes in your application
274269
(fullItemIds + gestureStrokeId).forEach { strokeId ->
275-
val appStrokeId = strokeIdsMapping[strokeId]
276-
strokeIdsMapping.remove(strokeId)
277-
val strokeBrush = remainingStrokes.firstOrNull { it.id == appStrokeId }
278-
remainingStrokes.remove(strokeBrush)
270+
strokeIdsMapping.remove(strokeId)?.let { appStrokeId ->
271+
strokeIdsMappingDeleted[strokeId] = appStrokeId
272+
val strokeBrush = remainingStrokes.firstOrNull { it.id == appStrokeId }
273+
remainingStrokes.remove(strokeBrush)
274+
}
279275
}
280276

281277
// Convert remaining events to brushes and map to remaining item ids
@@ -287,6 +283,8 @@ class InkViewModel(
287283
// Add back the remaining strokes
288284
remainingStrokes.addAll(brushes)
289285
}
286+
removeGestureFromUndoRedoStack(gestureStrokeId)
287+
_editorHistoryState.value = addToUndoRedoStack(EditorHistoryAction.REMOVE, strokesToRemove, iinkHistoryId())
290288

291289
_strokes.value = remainingStrokes
292290
}
@@ -373,8 +371,8 @@ class InkViewModel(
373371
}
374372
}
375373

376-
private fun addToUndoRedoStack(action: EditorHistoryAction, strokes: List<InkView.Brush>): EditorHistoryState {
377-
return addToUndoRedoStack(listOf(EditorHistoryItem(action, strokes)))
374+
private fun addToUndoRedoStack(action: EditorHistoryAction, strokes: List<InkView.Brush>, iinkHistoryId: String): EditorHistoryState {
375+
return addToUndoRedoStack(listOf(EditorHistoryItem(action, strokes, iinkHistoryId)))
378376
}
379377

380378
private fun addToUndoRedoStack(editorHistoryItems: List<EditorHistoryItem>): EditorHistoryState {
@@ -394,44 +392,28 @@ class InkViewModel(
394392
}
395393

396394
private fun addStrokesForUndoRedo(initialStrokes: List<InkView.Brush>, strokesToAdd: List<InkView.Brush>): List<InkView.Brush> {
397-
val strokes = initialStrokes.toMutableList()
398-
strokes.addAll(strokesToAdd)
399-
400-
val pointerEvents = strokesToAdd.flatMap { brush ->
401-
brush.stroke.toPointerEvents().map { pointerEvent ->
402-
pointerEvent.convertPointerEvent(converter)
403-
}
404-
}.toTypedArray()
405-
406-
if (pointerEvents.isNotEmpty()) {
407-
val addedStrokes = offscreenEditor?.addStrokes(pointerEvents, false)
408-
409-
if (addedStrokes != null) {
410-
strokesToAdd.forEachIndexed { index, brush ->
411-
if (index in addedStrokes.indices) {
412-
strokeIdsMapping[addedStrokes[index]] = brush.id
413-
}
414-
}
395+
strokesToAdd.map(InkView.Brush::id).forEach { id ->
396+
strokeIdsMappingDeleted[id]?.also { appStrokeId ->
397+
strokeIdsMapping[id] = appStrokeId
398+
strokeIdsMappingDeleted.remove(id)
415399
}
416400
}
417401

418-
return strokes
402+
return initialStrokes + strokesToAdd
419403
}
420404

421405
private fun removeStrokesForUndoRedo(initialStrokes: List<InkView.Brush>, strokesToRemove: List<InkView.Brush>): List<InkView.Brush> {
422-
val updatedStrokes = initialStrokes.filter {
423-
it.id !in strokesToRemove.map { strokeToRemove -> strokeToRemove.id }
424-
}
406+
val strokeIdsToRemove = strokesToRemove.map(InkView.Brush::id)
425407

426-
val strokeToUndoMapping = strokeIdsMapping.filter { (_, appStrokeId) ->
427-
appStrokeId in strokesToRemove.map { strokeToRemove -> strokeToRemove.id }
428-
}
429-
offscreenEditor?.erase(strokeToUndoMapping.keys.toTypedArray())
430-
strokeToUndoMapping.forEach {
431-
strokeIdsMapping.remove(it.key)
408+
strokeIdsToRemove.forEach { id ->
409+
strokeIdsMapping.remove(id)?.let { appStrokeId ->
410+
strokeIdsMappingDeleted[id] = appStrokeId
411+
}
432412
}
433413

434-
return updatedStrokes
414+
return initialStrokes.filter {
415+
it.id !in strokeIdsToRemove
416+
}
435417
}
436418

437419
private fun clearUndoRedoStack(): EditorHistoryState {
@@ -442,10 +424,20 @@ class InkViewModel(
442424
}
443425
}
444426

445-
private fun removeLastFromUndoRedoStack(): EditorHistoryState {
427+
private fun removeGestureFromUndoRedoStack(gestureId: String): EditorHistoryState {
446428
synchronized(undoRedoStack) {
447-
undoRedoStack.removeAt(--undoRedoIndex)
429+
run gestureFinder@{
430+
undoRedoStack.asReversed().forEach { historyItems ->
431+
historyItems.forEach { item ->
432+
if (item.strokes.any { it.id == gestureId }) {
433+
undoRedoStack.remove(historyItems)
434+
return@gestureFinder
435+
}
436+
}
437+
}
438+
}
448439

440+
--undoRedoIndex
449441
return EditorHistoryState(
450442
canUndo = undoRedoIndex > 0,
451443
canRedo = undoRedoIndex < undoRedoStack.size
@@ -466,6 +458,12 @@ class InkViewModel(
466458
EditorHistoryAction.ADD -> removeStrokesForUndoRedo(initialStrokes, item.strokes)
467459
EditorHistoryAction.REMOVE -> addStrokesForUndoRedo(initialStrokes, item.strokes)
468460
}
461+
462+
var continueUndoing = true
463+
do {
464+
continueUndoing = iinkHistoryId() != item.iinkHistoryId
465+
offscreenEditor?.historyManager?.undo()
466+
} while (continueUndoing)
469467
}
470468

471469
_editorHistoryState.value = EditorHistoryState(
@@ -488,6 +486,10 @@ class InkViewModel(
488486
EditorHistoryAction.ADD -> addStrokesForUndoRedo(initialStrokes, item.strokes)
489487
EditorHistoryAction.REMOVE -> removeStrokesForUndoRedo(initialStrokes, item.strokes)
490488
}
489+
490+
do {
491+
offscreenEditor?.historyManager?.redo()
492+
} while (iinkHistoryId() != item.iinkHistoryId)
491493
}
492494

493495
_editorHistoryState.value = EditorHistoryState(
@@ -499,10 +501,14 @@ class InkViewModel(
499501

500502
fun clearInk() {
501503
viewModelScope.launch(uiDispatcher) {
504+
if (_strokes.value?.isEmpty() == true) return@launch
505+
502506
offscreenEditor?.clear()
507+
508+
strokeIdsMappingDeleted.putAll(strokeIdsMapping)
503509
strokeIdsMapping.clear()
504510

505-
_editorHistoryState.value = addToUndoRedoStack(EditorHistoryAction.REMOVE, _strokes.value ?: emptyList())
511+
_editorHistoryState.value = addToUndoRedoStack(EditorHistoryAction.REMOVE, _strokes.value ?: emptyList(), iinkHistoryId())
506512

507513
_strokes.value = emptyList()
508514
}
@@ -580,10 +586,15 @@ class InkViewModel(
580586
strokeIdsMapping[strokeId] = brush.id
581587
}
582588

583-
_editorHistoryState.value = addToUndoRedoStack(EditorHistoryAction.ADD, listOf(brush))
589+
_editorHistoryState.value = addToUndoRedoStack(EditorHistoryAction.ADD, listOf(brush), iinkHistoryId())
584590
}
585591
}
586592

593+
private fun iinkHistoryId(): String {
594+
val historyManager = requireNotNull(offscreenEditor?.historyManager)
595+
return historyManager.getUndoRedoIdAt(historyManager.undoRedoStackIndex - 1)
596+
}
597+
587598
@VisibleForTesting
588599
public override fun onCleared() {
589600
super.onCleared()

0 commit comments

Comments
 (0)