diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt b/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt index e07f91646..6e3132543 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt @@ -2275,7 +2275,6 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown text.getSpans(0, text.length, AztecMediaSpan::class.java).firstOrNull { attributePredicate.matches(it.attributes) }?.let { mediaSpan -> - mediaSpan.beforeMediaDeleted() val start = text.getSpanStart(mediaSpan) val end = text.getSpanEnd(mediaSpan) @@ -2283,7 +2282,6 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown text.removeSpan(clickableSpan) text.removeSpan(mediaSpan) - mediaSpan.onMediaDeleted() aztecMediaSpan.onMediaDeletedListener = onMediaDeletedListener lineBlockFormatter.insertMediaSpanOverCurrentChar(aztecMediaSpan, start) contentChangeWatcher.notifyContentChanged() diff --git a/build.gradle b/build.gradle index bf09f0cef..b1570d08d 100644 --- a/build.gradle +++ b/build.gradle @@ -70,7 +70,7 @@ ext { ext { gradlePluginVersion = '3.3.1' - kotlinCoroutinesVersion = '1.1.0' + kotlinCoroutinesVersion = '1.6.4' tagSoupVersion = '1.2.1' glideVersion = '4.10.0' picassoVersion = '2.5.2' diff --git a/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/ImageWithCaptionAdapter.kt b/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/ImageWithCaptionAdapter.kt index e3f5d8eff..cae1957d0 100644 --- a/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/ImageWithCaptionAdapter.kt +++ b/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/ImageWithCaptionAdapter.kt @@ -81,8 +81,10 @@ class ImageWithCaptionAdapter( private const val CAPTION_ATTRIBUTE = "caption" private const val SRC_ATTRIBUTE = "src" - suspend fun insertImageWithCaption(placeholderManager: PlaceholderManager, src: String, caption: String) { - placeholderManager.insertOrUpdateItem(ADAPTER_TYPE) { currentAttributes, type, placeAtStart -> + suspend fun insertImageWithCaption(placeholderManager: PlaceholderManager, src: String, caption: String, shouldMergePlaceholders: Boolean = true) { + placeholderManager.insertOrUpdateItem(ADAPTER_TYPE, { + shouldMergePlaceholders + }) { currentAttributes, type, placeAtStart -> if (currentAttributes == null || type != ADAPTER_TYPE) { mapOf(SRC_ATTRIBUTE to src, CAPTION_ATTRIBUTE to caption) } else { diff --git a/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt b/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt index c519ef640..c4898c100 100644 --- a/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt +++ b/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt @@ -123,8 +123,19 @@ class PlaceholderManager( val targetItem = getTargetItem() val targetSpan = targetItem?.span val currentType = targetSpan?.attributes?.getValue(TYPE_ATTRIBUTE) - if (currentType != null && shouldMergeItem(currentType)) { - updateSpan(type, targetItem.span, targetItem.placeAtStart, updateItem, currentType) + if (currentType != null) { + if (shouldMergeItem(currentType)) { + updateSpan(type, targetItem.span, targetItem.placeAtStart, updateItem, currentType) + } else { + val (newLinePosition, targetSelection) = if (targetItem.placeAtStart) { + targetItem.spanStart to targetItem.spanStart + } else { + targetItem.spanEnd to targetItem.spanEnd + 2 + } + aztecText.text.insert(newLinePosition, Constants.NEWLINE_STRING) + aztecText.setSelection(targetSelection) + insertItem(type, *updateItem(null, null, false).toList().toTypedArray()) + } } else { insertItem(type, *updateItem(null, null, false).toList().toTypedArray()) } @@ -192,7 +203,7 @@ class PlaceholderManager( return true } - private data class TargetItem(val span: AztecPlaceholderSpan, val placeAtStart: Boolean) + private data class TargetItem(val span: AztecPlaceholderSpan, val placeAtStart: Boolean, val spanStart: Int, val spanEnd: Int) private fun getTargetItem(): TargetItem? { if (aztecText.length() == 0) { @@ -224,7 +235,7 @@ class PlaceholderManager( from, to, AztecPlaceholderSpan::class.java - ).map { TargetItem(it, placeAtStart) }.lastOrNull() + ).map { TargetItem(it, placeAtStart, editableText.getSpanStart(it), editableText.getSpanEnd(it)) }.lastOrNull() } /** @@ -286,7 +297,6 @@ class PlaceholderManager( } } val targetPosition = aztecText.getElementPosition(predicate) ?: return - insertInPosition(aztecAttributes ?: return, targetPosition) } @@ -318,39 +328,51 @@ class PlaceholderManager( parentTextViewRect.top += parentTextViewTopAndBottomOffset parentTextViewRect.bottom = parentTextViewRect.top + height - positionToIdMutex.withLock { - positionToId.removeAll { - it.uuid == uuid + var box = container.findViewWithTag(uuid)?.apply { + id = uuid.hashCode() + } + val newWidth = adapter.calculateWidth(attrs, windowWidth) - EDITOR_INNER_PADDING + val newHeight = height - EDITOR_INNER_PADDING + val padding = 10 + val newLeftPadding = parentTextViewRect.left + padding + aztecText.paddingStart + val newTopPadding = parentTextViewRect.top + padding + box?.let { existingView -> + val currentParams = existingView.layoutParams as FrameLayout.LayoutParams + val widthSame = currentParams.width == newWidth + val heightSame = currentParams.height == newHeight + val topMarginSame = currentParams.topMargin == newTopPadding + val leftMarginSame = currentParams.leftMargin == newLeftPadding + if (widthSame && heightSame && topMarginSame && leftMarginSame) { + return + } + container.removeView(box) + positionToIdMutex.withLock { + positionToId.removeAll { + it.uuid == uuid + } } } + box = adapter.createView(container.context, uuid, attrs) - var box = container.findViewWithTag(uuid) - val exists = box != null - if (!exists) { - box = adapter.createView(container.context, uuid, attrs) - } - val params = FrameLayout.LayoutParams( - adapter.calculateWidth(attrs, windowWidth) - EDITOR_INNER_PADDING, - height - EDITOR_INNER_PADDING - ) - val padding = 10 - params.setMargins( - parentTextViewRect.left + padding + aztecText.paddingStart, - parentTextViewRect.top + padding, - 0, - 0 - ) - box.layoutParams = params - box.tag = uuid + box.id = uuid.hashCode() box.setBackgroundColor(Color.TRANSPARENT) box.setOnTouchListener(adapter) + box.tag = uuid + box.layoutParams = FrameLayout.LayoutParams( + newWidth, + newHeight + ).apply { + leftMargin = newLeftPadding + topMargin = newTopPadding + } + positionToIdMutex.withLock { positionToId.add(Placeholder(targetPosition, uuid)) } - if (!exists && box.parent == null) { + if (box.parent == null) { container.addView(box) - adapter.onViewCreated(box, uuid) } + adapter.onViewCreated(box, uuid) } private fun validateAttributes(attributes: AztecAttributes): Boolean { @@ -418,7 +440,13 @@ class PlaceholderManager( /** * This method handled a `placeholder` tag found in the HTML. It creates a placeholder and inserts a view over it. */ - override fun handleTag(opening: Boolean, tag: String, output: Editable, attributes: Attributes, nestingLevel: Int): Boolean { + override fun handleTag( + opening: Boolean, + tag: String, + output: Editable, + attributes: Attributes, + nestingLevel: Int + ): Boolean { if (opening) { val type = attributes.getValue(TYPE_ATTRIBUTE) attributes.getValue(UUID_ATTRIBUTE)?.also { uuid -> diff --git a/media-placeholders/src/test/java/org/wordpress/aztec/placeholders/PlaceholderTest.kt b/media-placeholders/src/test/java/org/wordpress/aztec/placeholders/PlaceholderTest.kt index 1821227d9..a1efec8e3 100644 --- a/media-placeholders/src/test/java/org/wordpress/aztec/placeholders/PlaceholderTest.kt +++ b/media-placeholders/src/test/java/org/wordpress/aztec/placeholders/PlaceholderTest.kt @@ -27,7 +27,8 @@ class PlaceholderTest { lateinit var toolbar: AztecToolbar lateinit var placeholderManager: PlaceholderManager - private val uuid: String = "uuid123" + private val uuid1: String = "uuid1" + private val uuid2: String = "uuid2" /** * Initialize variables. @@ -38,8 +39,9 @@ class PlaceholderTest { container = FrameLayout(activity) editText = AztecText(activity) container.addView(editText, FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)) + var counter = 0 placeholderManager = PlaceholderManager(editText, container, generateUuid = { - uuid + listOf(uuid1, uuid2)[counter++] }) placeholderManager.registerAdapter(ImageWithCaptionAdapter()) editText.setCalypsoMode(false) @@ -65,10 +67,10 @@ class PlaceholderTest { editText.setSelection(0) ImageWithCaptionAdapter.insertImageWithCaption(placeholderManager, "image.jpg", "Caption 123") - Assert.assertEquals("

Line 1

", editText.toHtml()) + Assert.assertEquals("

Line 1

", editText.toHtml()) placeholderManager.removeItem { - it.getValue("uuid") == uuid + it.getValue("uuid") == uuid1 } Assert.assertEquals(initialHtml, editText.toHtml()) @@ -85,10 +87,10 @@ class PlaceholderTest { editText.setSelection(editText.editableText.indexOf("1")) ImageWithCaptionAdapter.insertImageWithCaption(placeholderManager, "image.jpg", "Caption 123") - Assert.assertEquals("

Line 123

Line 2

", editText.toHtml()) + Assert.assertEquals("

Line 123

Line 2

", editText.toHtml()) placeholderManager.removeItem { - it.getValue("uuid") == uuid + it.getValue("uuid") == uuid1 } Assert.assertEquals(initialHtml, editText.toHtml()) @@ -97,7 +99,7 @@ class PlaceholderTest { @Test @Throws(Exception::class) - fun insertOrUpdateAPlaceholderAtTheBeginning() { + fun updatePlaceholderAtTheBeginning() { runBlocking { val initialHtml = "

Line 1

" editText.fromHtml(initialHtml) @@ -109,7 +111,7 @@ class PlaceholderTest { Assert.assertEquals("${placeholderWithCaption("Caption 1 - Caption 2")}

Line 1

", editText.toHtml()) placeholderManager.removeItem { - it.getValue("uuid") == uuid + it.getValue("uuid") == uuid1 } Assert.assertEquals(initialHtml, editText.toHtml()) @@ -118,7 +120,28 @@ class PlaceholderTest { @Test @Throws(Exception::class) - fun insertOrUpdateAPlaceholderWhenInsertingBeforeNewLine() { + fun doNotUpdatePlaceholderAtTheBeginningWhenMergeDisabled() { + runBlocking { + val initialHtml = "

Line 1

" + editText.fromHtml(initialHtml) + + editText.setSelection(0) + ImageWithCaptionAdapter.insertImageWithCaption(placeholderManager, "image.jpg", "Caption 1") + ImageWithCaptionAdapter.insertImageWithCaption(placeholderManager, "image.jpg", "Caption 2", shouldMergePlaceholders = false) + + Assert.assertEquals("

Line 1

", editText.toHtml()) + + placeholderManager.removeItem { + it.getValue("uuid") == uuid1 + } + + Assert.assertEquals("

Line 1

", editText.toHtml()) + } + } + + @Test + @Throws(Exception::class) + fun updatePlaceholderWhenInsertingBeforeNewLine() { runBlocking { val initialHtml = "

Line 1

${placeholderWithCaption("First")}

Line 2

" editText.fromHtml(initialHtml) @@ -132,7 +155,21 @@ class PlaceholderTest { @Test @Throws(Exception::class) - fun insertOrUpdateAPlaceholderWhenInsertingRightBefore() { + fun doNotUpdatePlaceholderWhenInsertingBeforeNewLineAndMergeDisabled() { + runBlocking { + val initialHtml = "

Line 1

${placeholderWithCaption("First")}

Line 2

" + editText.fromHtml(initialHtml) + + editText.setSelection(editText.editableText.indexOf("1")) + ImageWithCaptionAdapter.insertImageWithCaption(placeholderManager, "image.jpg", "Second", shouldMergePlaceholders = false) + + Assert.assertEquals("

Line 1


Line 2

", editText.toHtml()) + } + } + + @Test + @Throws(Exception::class) + fun updatePlaceholderWhenInsertingRightBefore() { runBlocking { val initialHtml = "

Line 1

${placeholderWithCaption("First")}

Line 2

" editText.fromHtml(initialHtml) @@ -144,18 +181,32 @@ class PlaceholderTest { } } + @Test + @Throws(Exception::class) + fun doNotUpdatePlaceholderWhenInsertingRightBeforeAndMergeDisabled() { + runBlocking { + val initialHtml = "

Line 1

${placeholderWithCaption("First")}

Line 2

" + editText.fromHtml(initialHtml) + + editText.setSelection(editText.editableText.indexOf("1") + 1) + ImageWithCaptionAdapter.insertImageWithCaption(placeholderManager, "image.jpg", "Second", shouldMergePlaceholders = false) + + Assert.assertEquals("

Line 1


${placeholderWithCaption("First")}

Line 2

", editText.toHtml()) + } + } + private fun placeholderWithCaption(caption: String): String { - return "" + return "" } @Test @Throws(Exception::class) fun updatePlaceholderWhenItShouldBe() { runBlocking { - val initialHtml = "

Line

" + val initialHtml = "

Line

" editText.fromHtml(initialHtml) - placeholderManager.removeOrUpdate("uuid123", shouldUpdateItem = { + placeholderManager.removeOrUpdate("uuid1", shouldUpdateItem = { true }) { currentAttributes -> val result = mutableMapOf() @@ -164,7 +215,7 @@ class PlaceholderTest { result } - Assert.assertEquals("

Line

", editText.toHtml()) + Assert.assertEquals("

Line

", editText.toHtml()) } } @@ -172,13 +223,13 @@ class PlaceholderTest { @Throws(Exception::class) fun updatePlaceholderAtTheEnd() { runBlocking { - val initialHtml = "

First Line

Second Line

" + val initialHtml = "

First Line

Second Line

" editText.fromHtml(initialHtml) editText.setSelection(editText.editableText.indexOf("First") + 1) val initialSelectionStart = editText.selectionStart val initialSelectionEnd = editText.selectionEnd - placeholderManager.removeOrUpdate("uuid123", shouldUpdateItem = { + placeholderManager.removeOrUpdate("uuid1", shouldUpdateItem = { true }) { currentAttributes -> val result = mutableMapOf() @@ -187,7 +238,7 @@ class PlaceholderTest { result } - Assert.assertEquals("

First Line

Second Line

", editText.toHtml()) + Assert.assertEquals("

First Line

Second Line

", editText.toHtml()) Assert.assertEquals(initialSelectionStart, editText.selectionStart) Assert.assertEquals(initialSelectionEnd, editText.selectionEnd) } @@ -197,10 +248,10 @@ class PlaceholderTest { @Throws(Exception::class) fun removePlaceholderWhenItShouldNotBeUpdated() { runBlocking { - val initialHtml = "

Line

" + val initialHtml = "

Line

" editText.fromHtml(initialHtml) - placeholderManager.removeOrUpdate("uuid123", shouldUpdateItem = { + placeholderManager.removeOrUpdate("uuid1", shouldUpdateItem = { false }) { currentAttributes -> val result = mutableMapOf()