Skip to content

Commit 4b7b220

Browse files
committed
added API26PrependNewLineOnStyledSpecialTextEvent and new value return ObservedOperationResultType to make sure whether we found a sequence but want the queue cleared within context
1 parent 9e1da97 commit 4b7b220

File tree

7 files changed

+223
-10
lines changed

7 files changed

+223
-10
lines changed

aztec/src/main/kotlin/org/wordpress/aztec/watchers/event/buckets/API26Bucket.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package org.wordpress.aztec.watchers.event.buckets
22

33
import org.wordpress.aztec.watchers.event.sequence.known.space.API26InWordSpaceInsertionEvent
4+
import org.wordpress.aztec.watchers.event.sequence.known.space.API26PrependNewLineOnStyledSpecialTextEvent
45
import org.wordpress.aztec.watchers.event.sequence.known.space.API26PrependNewLineOnStyledTextEvent
56

67
class API26Bucket : Bucket() {
78
init {
89
// constructor - here add all identified sequences for this bucket
910
userOperations.add(API26InWordSpaceInsertionEvent())
1011
userOperations.add(API26PrependNewLineOnStyledTextEvent())
12+
userOperations.add(API26PrependNewLineOnStyledSpecialTextEvent())
1113
//mUserOperations.add(new ...);
1214
//mUserOperations.add(new ...);
1315
//mUserOperations.add(new ...);

aztec/src/main/kotlin/org/wordpress/aztec/watchers/event/sequence/ObservationQueue.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,19 @@ class ObservationQueue(val injector: IEventInjector) : EventSequence<TextWatcher
5555
}
5656
} else {
5757
// does this particular event look like a part of any of the user operations as defined in this bucket?
58-
if (operation.isUserOperationObservedInSequence(this)) {
58+
val result = operation.isUserOperationObservedInSequence(this)
59+
if (operation.isFound(result)) {
5960
// replace user operation with ONE TextWatcherEvent and inject this one in the actual
6061
// textwatchers
6162
val replacementEvent = operation.buildReplacementEventWithSequenceData(this)
6263
injector.executeEvent(replacementEvent)
6364
clear()
6465
}
66+
67+
// regardless of the operation being found, let's check if it needs the queue to be cleared
68+
if (operation.needsClear(result)) {
69+
clear()
70+
}
6571
}
6672
}
6773
}

aztec/src/main/kotlin/org/wordpress/aztec/watchers/event/sequence/UserOperationEvent.kt

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,29 @@
11
package org.wordpress.aztec.watchers.event.sequence
22

3+
import org.wordpress.android.util.AppLog
4+
import org.wordpress.aztec.spans.AztecCodeSpan
5+
import org.wordpress.aztec.spans.AztecHeadingSpan
6+
import org.wordpress.aztec.spans.AztecListItemSpan
7+
import org.wordpress.aztec.spans.AztecPreformatSpan
8+
import org.wordpress.aztec.watchers.event.text.BeforeTextChangedEventData
39
import org.wordpress.aztec.watchers.event.text.TextWatcherEvent
410

511
abstract class UserOperationEvent(var sequence: EventSequence<TextWatcherEvent> = EventSequence()) {
612

13+
enum class ObservedOperationResultType {
14+
SEQUENCE_FOUND,
15+
SEQUENCE_NOT_FOUND,
16+
SEQUENCE_FOUND_CLEAR_QUEUE
17+
}
18+
19+
fun isFound(resultType: ObservedOperationResultType) : Boolean {
20+
return resultType == ObservedOperationResultType.SEQUENCE_FOUND
21+
}
22+
23+
fun needsClear(resultType: ObservedOperationResultType) : Boolean {
24+
return resultType == ObservedOperationResultType.SEQUENCE_FOUND_CLEAR_QUEUE
25+
}
26+
727
fun addSequenceStep(event: TextWatcherEvent) {
828
sequence.add(event)
929
}
@@ -43,7 +63,28 @@ abstract class UserOperationEvent(var sequence: EventSequence<TextWatcherEvent>
4363
return true
4464
}
4565

46-
abstract fun isUserOperationObservedInSequence(sequence: EventSequence<TextWatcherEvent>) : Boolean
66+
fun isEventFoundWithinABlock(data: BeforeTextChangedEventData) : Boolean {
67+
// ok finally let's make sure we are not within a Block element
68+
val inputStart = data.start + data.count
69+
val inputEnd = data.start + data.count + 1
70+
71+
val text = data.textBefore!!
72+
val isInsideList = text.getSpans(inputStart, inputEnd, AztecListItemSpan::class.java).isNotEmpty()
73+
val isInsidePre = text.getSpans(inputStart, inputEnd, AztecPreformatSpan::class.java).isNotEmpty()
74+
val isInsideCode = text.getSpans(inputStart, inputEnd, AztecCodeSpan::class.java).isNotEmpty()
75+
var insideHeading = text.getSpans(inputStart, inputEnd, AztecHeadingSpan::class.java).isNotEmpty()
76+
77+
if (insideHeading && (text.length > inputEnd && text[inputEnd] == '\n')) {
78+
insideHeading = false
79+
}
80+
81+
AppLog.d(AppLog.T.EDITOR, "SEQUENCE OBSERVED COMPLETELY, IS IT WITHIN BLOCK?: " +
82+
(isInsideList || insideHeading || isInsidePre || isInsideCode))
83+
84+
return isInsideList || insideHeading || isInsidePre || isInsideCode
85+
}
86+
87+
abstract fun isUserOperationObservedInSequence(sequence: EventSequence<TextWatcherEvent>) : ObservedOperationResultType
4788
abstract fun buildReplacementEventWithSequenceData(sequence: EventSequence<TextWatcherEvent>) : TextWatcherEvent
4889
}
4990

aztec/src/main/kotlin/org/wordpress/aztec/watchers/event/sequence/known/space/API26InWordSpaceInsertionEvent.kt

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class API26InWordSpaceInsertionEvent : UserOperationEvent() {
4242
addSequenceStep(step4)
4343
}
4444

45-
override fun isUserOperationObservedInSequence(sequence: EventSequence<TextWatcherEvent>): Boolean {
45+
override fun isUserOperationObservedInSequence(sequence: EventSequence<TextWatcherEvent>): ObservedOperationResultType {
4646
/* here check:
4747
4848
If we have 2 deletes followed by 2 inserts AND:
@@ -55,7 +55,7 @@ class API26InWordSpaceInsertionEvent : UserOperationEvent() {
5555

5656
// populate data in our own sequence to be able to run the comparator checks
5757
if (!isUserOperationPartiallyObservedInSequence(sequence)) {
58-
return false
58+
return ObservedOperationResultType.SEQUENCE_NOT_FOUND
5959
}
6060

6161
// ok all events are good individually and match the sequence we want to compare against.
@@ -68,12 +68,19 @@ class API26InWordSpaceInsertionEvent : UserOperationEvent() {
6868
// now check that the inserted character is actually a space
6969
val data = firstEvent.beforeEventData
7070
if (lastEvent.afterEventData.textAfter!![data.start + data.count] == SPACE) {
71-
return true
71+
// okay sequence has been observed completely, let's make sure we are not within a Block
72+
if (!isEventFoundWithinABlock(data)) {
73+
return ObservedOperationResultType.SEQUENCE_FOUND
74+
} else {
75+
// we're within a Block, things are going to be handled by the BlockHandler so let's just request
76+
// a queue clear only
77+
return ObservedOperationResultType.SEQUENCE_FOUND_CLEAR_QUEUE
78+
}
7279
}
7380
}
7481
}
7582

76-
return false
83+
return ObservedOperationResultType.SEQUENCE_NOT_FOUND
7784
}
7885

7986
override fun buildReplacementEventWithSequenceData(sequence: EventSequence<TextWatcherEvent>): TextWatcherEvent {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package org.wordpress.aztec.watchers.event.sequence.known.space
2+
3+
import org.wordpress.android.util.AppLog
4+
import org.wordpress.aztec.Constants
5+
import org.wordpress.aztec.watchers.event.sequence.EventSequence
6+
import org.wordpress.aztec.watchers.event.sequence.UserOperationEvent
7+
import org.wordpress.aztec.watchers.event.sequence.known.space.steps.TextWatcherEventDeleteText
8+
import org.wordpress.aztec.watchers.event.sequence.known.space.steps.TextWatcherEventInsertText
9+
import org.wordpress.aztec.watchers.event.sequence.known.space.steps.TextWatcherEventInsertTextDelAfter
10+
import org.wordpress.aztec.watchers.event.text.AfterTextChangedEventData
11+
import org.wordpress.aztec.watchers.event.text.TextWatcherEvent
12+
13+
/*
14+
This case implements the behavior observed in https://github.com/wordpress-mobile/AztecEditor-Android/issues/610
15+
special case for block formated text like HEADING, LIST, etc.
16+
*/
17+
class API26PrependNewLineOnStyledSpecialTextEvent : UserOperationEvent() {
18+
19+
init {
20+
// here we populate our model of reference (which is the sequence of events we expect to find)
21+
// note we don' populate the TextWatcherEvents with actual data, but rather we just want
22+
// to instantiate them so we can populate them later and test whether data holds true to their
23+
// validation.
24+
25+
// 1 generic delete, followed by 1 special insert, then 1 generic insert
26+
val builder = TextWatcherEventDeleteText.Builder()
27+
val step1 = builder.build()
28+
29+
val builderStep2 = TextWatcherEventInsertTextDelAfter.Builder()
30+
val step2 = builderStep2.build()
31+
32+
val builderStep3 = TextWatcherEventInsertText.Builder()
33+
val step3 = builderStep3.build()
34+
35+
// add each of the steps that make up for the identified API26InWordSpaceInsertionEvent here
36+
clear()
37+
addSequenceStep(step1)
38+
addSequenceStep(step2)
39+
addSequenceStep(step3)
40+
}
41+
42+
override fun isUserOperationObservedInSequence(sequence: EventSequence<TextWatcherEvent>): ObservedOperationResultType {
43+
/* here check:
44+
45+
If we have 1 delete followed by 2 inserts AND:
46+
1) checking the first BEFORETEXTCHANGED and
47+
2) checking the LAST AFTERTEXTCHANGED
48+
text length is longer by 1, and the item that is now located start of AFTERTEXTCHANGED is a NEWLINE character.
49+
50+
*/
51+
if (this.sequence.size == sequence.size) {
52+
53+
AppLog.d(AppLog.T.EDITOR, "aca estamos")
54+
// populate data in our own sequence to be able to run the comparator checks
55+
if (!isUserOperationPartiallyObservedInSequence(sequence)) {
56+
return ObservedOperationResultType.SEQUENCE_NOT_FOUND
57+
}
58+
59+
AppLog.d(AppLog.T.EDITOR, "aca estamos 2")
60+
61+
// ok all events are good individually and match the sequence we want to compare against.
62+
// now let's make sure the BEFORE / AFTER situation is what we are trying to identify
63+
val firstEvent = sequence.first()
64+
val lastEvent = sequence.last()
65+
val midEvent = sequence[1]
66+
67+
// if new text length is equal as original text length
68+
if (firstEvent.beforeEventData.textBefore?.length == lastEvent.afterEventData.textAfter!!.length) {
69+
//but, middle event has a new line at the start index of change
70+
if (midEvent.onEventData.textOn!![midEvent.onEventData.start] == Constants.NEWLINE) {
71+
// okay sequence has been observed completely, let's make sure we are not within a Block
72+
if (!isEventFoundWithinABlock(firstEvent.beforeEventData)) {
73+
return ObservedOperationResultType.SEQUENCE_FOUND
74+
} else {
75+
// we're within a Block, things are going to be handled by the BlockHandler so let's just request
76+
// a queue clear only
77+
return ObservedOperationResultType.SEQUENCE_FOUND_CLEAR_QUEUE
78+
}
79+
}
80+
}
81+
}
82+
83+
return ObservedOperationResultType.SEQUENCE_NOT_FOUND
84+
}
85+
86+
override fun buildReplacementEventWithSequenceData(sequence: EventSequence<TextWatcherEvent>): TextWatcherEvent {
87+
val builder = TextWatcherEventInsertText.Builder()
88+
// here make it all up as a unique event that does the insert as usual, as we'd get it on older APIs
89+
val firstEvent = sequence.first()
90+
91+
val (oldText) = firstEvent.beforeEventData
92+
93+
val indexWhereToInsertNewLine = firstEvent.beforeEventData.start
94+
oldText?.insert(indexWhereToInsertNewLine, Constants.NEWLINE_STRING)
95+
96+
builder.afterEventData = AfterTextChangedEventData(oldText)
97+
val replacementEvent = builder.build()
98+
replacementEvent.insertionStart = indexWhereToInsertNewLine
99+
replacementEvent.insertionLength = 1
100+
101+
return replacementEvent
102+
}
103+
}

aztec/src/main/kotlin/org/wordpress/aztec/watchers/event/sequence/known/space/API26PrependNewLineOnStyledTextEvent.kt

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class API26PrependNewLineOnStyledTextEvent : UserOperationEvent() {
3737
addSequenceStep(step3)
3838
}
3939

40-
override fun isUserOperationObservedInSequence(sequence: EventSequence<TextWatcherEvent>): Boolean {
40+
override fun isUserOperationObservedInSequence(sequence: EventSequence<TextWatcherEvent>): ObservedOperationResultType {
4141
/* here check:
4242
4343
If we have 1 delete followed by 2 inserts AND:
@@ -50,7 +50,7 @@ class API26PrependNewLineOnStyledTextEvent : UserOperationEvent() {
5050

5151
// populate data in our own sequence to be able to run the comparator checks
5252
if (!isUserOperationPartiallyObservedInSequence(sequence)) {
53-
return false
53+
return ObservedOperationResultType.SEQUENCE_NOT_FOUND
5454
}
5555

5656
// ok all events are good individually and match the sequence we want to compare against.
@@ -63,12 +63,19 @@ class API26PrependNewLineOnStyledTextEvent : UserOperationEvent() {
6363
// now check that the inserted character is actually a NEWLINE
6464
val data = firstEvent.beforeEventData
6565
if (lastEvent.afterEventData.textAfter!![data.start] == Constants.NEWLINE) {
66-
return true
66+
// okay sequence has been observed completely, let's make sure we are not within a Block
67+
if (!isEventFoundWithinABlock(data)) {
68+
return ObservedOperationResultType.SEQUENCE_FOUND
69+
} else {
70+
// we're within a Block, things are going to be handled by the BlockHandler so let's just request
71+
// a queue clear only
72+
return ObservedOperationResultType.SEQUENCE_FOUND_CLEAR_QUEUE
73+
}
6774
}
6875
}
6976
}
7077

71-
return false
78+
return ObservedOperationResultType.SEQUENCE_NOT_FOUND
7279
}
7380

7481
override fun buildReplacementEventWithSequenceData(sequence: EventSequence<TextWatcherEvent>): TextWatcherEvent {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package org.wordpress.aztec.watchers.event.sequence.known.space.steps
2+
3+
import org.wordpress.android.util.AppLog
4+
import org.wordpress.aztec.watchers.EndOfBufferMarkerAdder
5+
import org.wordpress.aztec.watchers.event.text.AfterTextChangedEventData
6+
import org.wordpress.aztec.watchers.event.text.BeforeTextChangedEventData
7+
import org.wordpress.aztec.watchers.event.text.OnTextChangedEventData
8+
import org.wordpress.aztec.watchers.event.text.TextWatcherEvent
9+
10+
/*
11+
This is a special kind of observed midway event, where the afterTextChanged event has the same content as the beforeTextChanged, but
12+
both the beforeTextChanged and onTextChanged event data correspond to an insertion.
13+
*/
14+
15+
class TextWatcherEventInsertTextDelAfter(beforeEventData: BeforeTextChangedEventData, onEventData: OnTextChangedEventData, afterEventData: AfterTextChangedEventData) : TextWatcherEvent(beforeEventData, onEventData, afterEventData) {
16+
17+
private var beforeText: CharSequence? = null
18+
19+
private fun testBeforeTextChangedEventData(data: BeforeTextChangedEventData): Boolean {
20+
beforeText = data.textBefore
21+
AppLog.d(AppLog.T.EDITOR, "INSERTSPECIAL testBeforeTextChangedEventData: " + (data.count == 0 && data.after > 0))
22+
return data.count == 0 && data.after > 0
23+
}
24+
25+
private fun testOnTextChangedEventData(data: OnTextChangedEventData): Boolean {
26+
AppLog.d(AppLog.T.EDITOR, "INSERTSPECIAL testOnTextChangedEventData: " + (data.start >= 0 && data.count > 0 && data.textOn!!.length > 0))
27+
return data.start >= 0 && data.count > 0 && data.textOn!!.length > 0
28+
}
29+
30+
private fun testAfterTextChangedEventData(data: AfterTextChangedEventData): Boolean {
31+
AppLog.d(AppLog.T.EDITOR, "INSERTSPECIAL testAfterTextChangedEventData: " + (EndOfBufferMarkerAdder.safeLength(beforeText!!) == EndOfBufferMarkerAdder.safeLength(data.textAfter!!)))
32+
return EndOfBufferMarkerAdder.safeLength(beforeText!!) == EndOfBufferMarkerAdder.safeLength(data.textAfter!!)
33+
}
34+
35+
override fun testFitsBeforeOnAndAfter(): Boolean {
36+
return (testBeforeTextChangedEventData(beforeEventData)
37+
&& testOnTextChangedEventData(onEventData)
38+
&& testAfterTextChangedEventData(afterEventData))
39+
}
40+
41+
class Builder : TextWatcherEvent.Builder() {
42+
override fun build(): TextWatcherEventInsertTextDelAfter {
43+
super.setGenericEventDataIfNotInit()
44+
return TextWatcherEventInsertTextDelAfter(beforeEventData, onEventData, afterEventData)
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)