@@ -7,7 +7,6 @@ import android.graphics.drawable.Drawable
77import android.text.Editable
88import android.text.Layout
99import android.text.Spanned
10- import android.util.Log
1110import android.view.MotionEvent
1211import android.view.View
1312import android.view.ViewTreeObserver
@@ -26,6 +25,7 @@ import org.wordpress.aztec.AztecContentChangeWatcher
2625import org.wordpress.aztec.AztecText
2726import org.wordpress.aztec.Constants
2827import org.wordpress.aztec.Html
28+ import org.wordpress.aztec.plugins.html2visual.IHtmlPreprocessor
2929import org.wordpress.aztec.plugins.html2visual.IHtmlTagHandler
3030import org.wordpress.aztec.spans.AztecMediaClickableSpan
3131import org.xml.sax.Attributes
@@ -53,7 +53,8 @@ class PlaceholderManager(
5353 Html .MediaCallback ,
5454 AztecText .OnMediaDeletedListener ,
5555 AztecText .OnVisibilityChangeListener ,
56- CoroutineScope {
56+ CoroutineScope ,
57+ IHtmlPreprocessor {
5758 private val adapters = mutableMapOf<String , PlaceholderAdapter >()
5859 private val positionToIdMutex = Mutex ()
5960 private val positionToId = mutableSetOf<Placeholder >()
@@ -110,37 +111,29 @@ class PlaceholderManager(
110111 * @param shouldMergeItem this method should return true when the previous type is compatible and should be updated
111112 * @param updateItem function to update current parameters with new params
112113 */
113- suspend fun insertOrUpdateItem (type : String , shouldMergeItem : (currentItemType: String ) -> Boolean = { true }, updateItem : (currentAttributes: Map <String , String >? , currentType: String? ) -> Map <String , String >) {
114- val previousIndex = (aztecText.selectionStart - 1 ).coerceAtLeast(0 )
115- val indexBeforePrevious = (aztecText.selectionStart - 2 ).coerceAtLeast(0 )
116- val from = if (aztecText.editableText[previousIndex] == Constants .IMG_CHAR ) {
117- previousIndex
118- } else if (aztecText.editableText[previousIndex] == ' \n ' ) {
119- indexBeforePrevious
120- } else {
121- aztecText.selectionStart
122- }
123- val editableText = aztecText.editableText
124- val currentItem = editableText.getSpans(
125- from,
126- aztecText.selectionStart,
127- AztecPlaceholderSpan ::class .java
128- ).lastOrNull()
129- val currentType = currentItem?.attributes?.getValue(TYPE_ATTRIBUTE )
114+ suspend fun insertOrUpdateItem (
115+ type : String ,
116+ shouldMergeItem : (currentItemType: String ) -> Boolean = { true },
117+ updateItem : (
118+ currentAttributes: Map <String , String >? ,
119+ currentType: String? ,
120+ placeAtStart: Boolean
121+ ) -> Map <String , String >
122+ ) {
123+ val targetItem = getTargetItem()
124+ val targetSpan = targetItem?.span
125+ val currentType = targetSpan?.attributes?.getValue(TYPE_ATTRIBUTE )
130126 if (currentType != null && shouldMergeItem(currentType)) {
131127 val adapter = adapters[type]
132128 ? : throw IllegalArgumentException (" Adapter for inserted type not found. Register it with `registerAdapter` method" )
133129 val currentAttributes = mutableMapOf<String , String >()
134- val uuid = currentItem .attributes.getValue(UUID_ATTRIBUTE )
135- for (i in 0 until currentItem .attributes.length) {
136- val name = currentItem .attributes.getQName(i)
137- val value = currentItem .attributes.getValue(name)
130+ val uuid = targetSpan .attributes.getValue(UUID_ATTRIBUTE )
131+ for (i in 0 until targetSpan .attributes.length) {
132+ val name = targetSpan .attributes.getQName(i)
133+ val value = targetSpan .attributes.getValue(name)
138134 currentAttributes[name] = value
139135 }
140- val updatedAttributes = updateItem(currentAttributes, currentType)
141- removeItem(false ) { aztecAttributes ->
142- aztecAttributes.getValue(UUID_ATTRIBUTE ) == uuid
143- }
136+ val updatedAttributes = updateItem(currentAttributes, currentType, targetItem.placeAtStart)
144137 val attrs = AztecAttributes ().apply {
145138 updatedAttributes.forEach { (key, value) ->
146139 setValue(key, value)
@@ -149,25 +142,66 @@ class PlaceholderManager(
149142 attrs.setValue(UUID_ATTRIBUTE , uuid)
150143 attrs.setValue(TYPE_ATTRIBUTE , type)
151144 val drawable = buildPlaceholderDrawable(adapter, attrs)
152- aztecText.insertMediaSpan(AztecPlaceholderSpan (aztecText.context, drawable, 0 , attrs,
153- this , aztecText, WeakReference (adapter), TAG = htmlTag))
145+ val span = AztecPlaceholderSpan (aztecText.context, drawable, 0 , attrs,
146+ this , aztecText, WeakReference (adapter), TAG = htmlTag)
147+ aztecText.replaceMediaSpan(span) { attributes ->
148+ attributes.getValue(UUID_ATTRIBUTE ) == uuid
149+ }
154150 insertContentOverSpanWithId(uuid)
155151 } else {
156- insertItem(type, * updateItem(null , null ).toList().toTypedArray())
152+ insertItem(type, * updateItem(null , null , false ).toList().toTypedArray())
157153 }
158154 }
159155
156+ private data class TargetItem (val span : AztecPlaceholderSpan , val placeAtStart : Boolean )
157+
158+ private fun getTargetItem (): TargetItem ? {
159+ if (aztecText.length() == 0 ) {
160+ return null
161+ }
162+ val limitLength = aztecText.length() - 1
163+ val selectionStart = aztecText.selectionStart
164+ val selectionStartMinusOne = (selectionStart - 1 ).coerceIn(0 , limitLength)
165+ val selectionStartMinusTwo = (selectionStart - 2 ).coerceIn(0 , limitLength)
166+ val selectionEnd = aztecText.selectionEnd
167+ val selectionEndPlusOne = (selectionStart + 1 ).coerceIn(0 , limitLength)
168+ val selectionEndPlusTwo = (selectionStart + 2 ).coerceIn(0 , limitLength)
169+ val editableText = aztecText.editableText
170+ var placeAtStart = false
171+ val (from, to) = if (editableText[selectionStartMinusOne] == Constants .IMG_CHAR ) {
172+ selectionStartMinusOne to selectionStart
173+ } else if (editableText[selectionStartMinusOne] == ' \n ' && editableText[selectionStartMinusTwo] == Constants .IMG_CHAR ) {
174+ selectionStartMinusTwo to selectionStart
175+ } else if (editableText[selectionEndPlusOne] == Constants .IMG_CHAR ) {
176+ placeAtStart = true
177+ selectionEndPlusOne to (selectionEndPlusOne + 1 ).coerceIn(0 , limitLength)
178+ } else if (editableText[selectionEndPlusOne] == ' \n ' && editableText[selectionEndPlusTwo] == Constants .IMG_CHAR ) {
179+ placeAtStart = true
180+ selectionEndPlusTwo to (selectionEndPlusTwo + 1 ).coerceIn(0 , limitLength)
181+ } else {
182+ selectionStart to selectionEnd
183+ }
184+ return editableText.getSpans(
185+ from,
186+ to,
187+ AztecPlaceholderSpan ::class .java
188+ ).map { TargetItem (it, placeAtStart) }.lastOrNull()
189+ }
190+
160191 /* *
161192 * Call this method to remove a placeholder from both the AztecText and the overlaying layer programatically.
162193 * @param predicate determines whether a span should be removed
163194 */
164- fun removeItem (notifyContentChange : Boolean = true, predicate : (Attributes ) -> Boolean ) {
165- aztecText.removeMedia(notifyContentChange) { predicate(it) }
195+ fun removeItem (predicate : (Attributes ) -> Boolean ) {
196+ aztecText.removeMedia { predicate(it) }
166197 }
167198
168199 private suspend fun buildPlaceholderDrawable (adapter : PlaceholderAdapter , attrs : AztecAttributes ): Drawable {
169200 val drawable = ContextCompat .getDrawable(aztecText.context, android.R .color.transparent)!!
170- updateDrawableBounds(adapter, attrs, drawable)
201+ val editorWidth = if (aztecText.width > 0 ) {
202+ aztecText.width - aztecText.paddingStart - aztecText.paddingEnd
203+ } else aztecText.maxImagesWidth
204+ drawable.setBounds(0 , 0 , adapter.calculateWidth(attrs, editorWidth), adapter.calculateHeight(attrs, editorWidth))
171205 return drawable
172206 }
173207
@@ -340,6 +374,12 @@ class PlaceholderManager(
340374 override fun handleTag (opening : Boolean , tag : String , output : Editable , attributes : Attributes , nestingLevel : Int ): Boolean {
341375 if (opening) {
342376 val type = attributes.getValue(TYPE_ATTRIBUTE )
377+ attributes.getValue(UUID_ATTRIBUTE )?.also { uuid ->
378+ container.findViewWithTag<View >(uuid)?.let {
379+ it.visibility = View .GONE
380+ container.removeView(it)
381+ }
382+ }
343383 val adapter = adapters[type] ? : return false
344384 val aztecAttributes = AztecAttributes (attributes)
345385 aztecAttributes.setValue(UUID_ATTRIBUTE , generateUuid())
@@ -396,35 +436,23 @@ class PlaceholderManager(
396436 spans.forEach {
397437 val type = it.attributes.getValue(TYPE_ATTRIBUTE )
398438 val adapter = adapters[type] ? : return @forEach
399- updateDrawableBounds (adapter, it.attributes, it.drawable )
439+ it.drawable = buildPlaceholderDrawable (adapter, it.attributes)
400440 aztecText.refreshText(false )
401441 insertInPosition(it.attributes, aztecText.editableText.getSpanStart(it))
402442 }
403443 }
404444 }
405445 }
406446
407- private suspend fun updateDrawableBounds (adapter : PlaceholderAdapter , attrs : AztecAttributes , drawable : Drawable ? ) {
408- val editorWidth = if (aztecText.width > 0 ) {
409- aztecText.width - aztecText.paddingStart - aztecText.paddingEnd - EDITOR_INNER_PADDING
410- } else aztecText.maxImagesWidth
411- if (drawable?.bounds?.right != editorWidth) {
412- drawable?.setBounds(0 , 0 , adapter.calculateWidth(attrs, editorWidth), adapter.calculateHeight(attrs, editorWidth))
413- }
414- }
415-
416447 private suspend fun clearAllViews () {
417- Log .d(" vojta" , " Before clearing all with lock" )
418448 positionToIdMutex.withLock {
419- Log .d(" vojta" , " Clearing all with lock" )
420449 for (placeholder in positionToId) {
421450 container.findViewWithTag<View >(placeholder.uuid)?.let {
422451 it.visibility = View .GONE
423452 container.removeView(it)
424453 }
425454 }
426455 positionToId.clear()
427- Log .d(" vojta" , " Cleared all with lock" )
428456 }
429457 }
430458
@@ -553,9 +581,17 @@ class PlaceholderManager(
553581 data class Placeholder (val elementPosition : Int , val uuid : String )
554582
555583 companion object {
584+ private const val TAG = " PlaceholderManager"
556585 private const val DEFAULT_HTML_TAG = " placeholder"
557586 private const val UUID_ATTRIBUTE = " uuid"
558587 private const val TYPE_ATTRIBUTE = " type"
559588 private const val EDITOR_INNER_PADDING = 20
560589 }
590+
591+ override fun beforeHtmlProcessed (source : String ): String {
592+ runBlocking {
593+ clearAllViews()
594+ }
595+ return source
596+ }
561597}
0 commit comments