@@ -98,13 +98,20 @@ import org.wordpress.aztec.watchers.ParagraphCollapseRemover
9898import org.wordpress.aztec.watchers.SuggestionWatcher
9999import org.wordpress.aztec.watchers.TextDeleter
100100import org.wordpress.aztec.watchers.ZeroIndexContentWatcher
101+ import org.wordpress.aztec.watchers.event.IEventInjector
102+ import org.wordpress.aztec.watchers.event.sequence.ObservationQueue
103+ import org.wordpress.aztec.watchers.event.sequence.known.space.steps.TextWatcherEventInsertText
104+ import org.wordpress.aztec.watchers.event.text.AfterTextChangedEventData
105+ import org.wordpress.aztec.watchers.event.text.BeforeTextChangedEventData
106+ import org.wordpress.aztec.watchers.event.text.OnTextChangedEventData
107+ import org.wordpress.aztec.watchers.event.text.TextWatcherEvent
101108import org.xml.sax.Attributes
102109import java.util.ArrayList
103110import java.util.Arrays
104111import java.util.LinkedList
105112
106113@Suppress(" UNUSED_PARAMETER" )
107- class AztecText : AppCompatEditText , TextWatcher , UnknownHtmlSpan .OnUnknownHtmlTappedListener {
114+ class AztecText : AppCompatEditText , TextWatcher , UnknownHtmlSpan .OnUnknownHtmlTappedListener , IEventInjector {
108115 companion object {
109116 val BLOCK_EDITOR_HTML_KEY = " RETAINED_BLOCK_HTML_KEY"
110117 val BLOCK_EDITOR_START_INDEX_KEY = " BLOCK_EDITOR_START_INDEX_KEY"
@@ -155,6 +162,7 @@ class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknownHtmlT
155162 private var consumeSelectionChangedEvent: Boolean = false
156163 private var consumeHistoryEvent: Boolean = false
157164 private var isInlineTextHandlerEnabled: Boolean = true
165+ private var bypassObservationQueue: Boolean = false
158166
159167 private var onSelectionChangedListener: OnSelectionChangedListener ? = null
160168 private var onImeBackListener: OnImeBackListener ? = null
@@ -205,6 +213,9 @@ class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknownHtmlT
205213 var maxImagesWidth: Int = 0
206214 var minImagesWidth: Int = 0
207215
216+ var observationQueue: ObservationQueue = ObservationQueue (this )
217+ var textWatcherEventBuilder: TextWatcherEvent .Builder = TextWatcherEvent .Builder ()
218+
208219 interface OnSelectionChangedListener {
209220 fun onSelectionChanged (selStart : Int , selEnd : Int )
210221 }
@@ -434,8 +445,39 @@ class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknownHtmlT
434445 DeleteMediaElementWatcher .install(this )
435446
436447 // History related logging has to happen before the changes in [ParagraphCollapseRemover]
437- addTextChangedListener( this )
448+ addHistoryLoggingWatcher( )
438449 ParagraphCollapseRemover .install(this )
450+
451+ // finally add the TextChangedListener
452+ addTextChangedListener(this )
453+ }
454+
455+ private fun addHistoryLoggingWatcher () {
456+ val historyLoggingWatcher = object : TextWatcher {
457+ override fun beforeTextChanged (text : CharSequence , start : Int , count : Int , after : Int ) {
458+ if (! isViewInitialized) return
459+ if (! isTextChangedListenerDisabled() && ! consumeHistoryEvent) {
460+ history.beforeTextChanged(toFormattedHtml())
461+ }
462+ }
463+ override fun onTextChanged (text : CharSequence , start : Int , before : Int , count : Int ) {
464+ if (! isViewInitialized) return
465+ }
466+ override fun afterTextChanged (text : Editable ) {
467+ if (isTextChangedListenerDisabled()) {
468+ return
469+ }
470+
471+ isMediaAdded = text.getSpans(0 , text.length, AztecMediaSpan ::class .java).isNotEmpty()
472+
473+ if (consumeHistoryEvent) {
474+ consumeHistoryEvent = false
475+ }
476+
477+ history.handleHistory(this @AztecText)
478+ }
479+ }
480+ addTextChangedListener(historyLoggingWatcher)
439481 }
440482
441483 override fun onWindowFocusChanged (hasWindowFocus : Boolean ) {
@@ -800,26 +842,37 @@ class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknownHtmlT
800842 override fun beforeTextChanged (text : CharSequence , start : Int , count : Int , after : Int ) {
801843 if (! isViewInitialized) return
802844
803- if (! isTextChangedListenerDisabled() && ! consumeHistoryEvent) {
804- history.beforeTextChanged(toFormattedHtml())
845+ if (! bypassObservationQueue) {
846+ // we need to make a copy to preserve the contents as they were before the change
847+ val textCopy = SpannableStringBuilder (text)
848+ val data = BeforeTextChangedEventData (textCopy, start, count, after)
849+ textWatcherEventBuilder.beforeEventData = data
805850 }
806851 }
807852
808853 override fun onTextChanged (text : CharSequence , start : Int , before : Int , count : Int ) {
809854 if (! isViewInitialized) return
855+
856+ if (! bypassObservationQueue) {
857+ val textCopy = SpannableStringBuilder (text)
858+ val data = OnTextChangedEventData (textCopy, start, before, count)
859+ textWatcherEventBuilder.onEventData = data
860+ }
810861 }
811862
812863 override fun afterTextChanged (text : Editable ) {
813864 if (isTextChangedListenerDisabled()) {
814865 return
815866 }
816867
817- isMediaAdded = text.getSpans(0 , text.length, AztecMediaSpan ::class .java).isNotEmpty()
868+ if (! bypassObservationQueue) {
869+ val textCopy = Editable .Factory .getInstance().newEditable(editableText)
870+ val data = AfterTextChangedEventData (textCopy)
871+ textWatcherEventBuilder.afterEventData = data
818872
819- if (consumeHistoryEvent) {
820- consumeHistoryEvent = false
873+ // now that we have a full event cycle (before, on, and after) we can add the event to the observation queue
874+ observationQueue.add(textWatcherEventBuilder.build())
821875 }
822- history.handleHistory(this )
823876 }
824877
825878 fun redo () {
@@ -1046,6 +1099,14 @@ class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknownHtmlT
10461099 consumeEditEvent = false
10471100 }
10481101
1102+ fun disableObservationQueue () {
1103+ bypassObservationQueue = true
1104+ }
1105+
1106+ fun enableObservationQueue () {
1107+ bypassObservationQueue = false
1108+ }
1109+
10491110 fun isTextChangedListenerDisabled (): Boolean {
10501111 return consumeEditEvent
10511112 }
@@ -1455,4 +1516,17 @@ class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknownHtmlT
14551516 override fun onUnknownHtmlTapped (unknownHtmlSpan : UnknownHtmlSpan ) {
14561517 showBlockEditorDialog(unknownHtmlSpan)
14571518 }
1519+
1520+ override fun executeEvent (data : TextWatcherEvent ) {
1521+ disableObservationQueue()
1522+
1523+ if (data is TextWatcherEventInsertText ) {
1524+ // here replace the inserted thing with a new "normal" insertion
1525+ val afterData = data.afterEventData
1526+ setText(afterData.textAfter)
1527+ setSelection(data.insertionStart+ data.insertionLength)
1528+ }
1529+
1530+ enableObservationQueue()
1531+ }
14581532}
0 commit comments