@@ -58,7 +58,8 @@ enum class EditorHistoryAction {
5858
5959data class EditorHistoryItem (
6060 val editorHistoryAction : EditorHistoryAction ,
61- val strokes : List <InkView .Brush >
61+ val strokes : List <InkView .Brush >,
62+ val iinkHistoryId : String
6263)
6364
6465data 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