From 12401d0b18d545d69962a1f3eb2f751088c7f047 Mon Sep 17 00:00:00 2001 From: khaykov Date: Wed, 24 Jul 2024 19:19:11 -0500 Subject: [PATCH 1/4] Fix leak in media span. --- .../org/wordpress/aztec/spans/AztecMediaSpan.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecMediaSpan.kt b/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecMediaSpan.kt index 978784067..e519116ba 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecMediaSpan.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecMediaSpan.kt @@ -11,12 +11,18 @@ import java.lang.ref.WeakReference import java.util.ArrayList abstract class AztecMediaSpan(drawable: Drawable?, override var attributes: AztecAttributes = AztecAttributes(), - var onMediaDeletedListener: AztecText.OnMediaDeletedListener? = null, + onMediaDeletedListener: AztecText.OnMediaDeletedListener? = null, editor: AztecText? = null) : AztecDynamicImageSpan(drawable), IAztecAttributedSpan { abstract val TAG: String private val overlays: ArrayList> = ArrayList() + private var onMediaDeletedListenerRef = WeakReference(onMediaDeletedListener) + + fun setOnMediaDeletedListener(listener: AztecText.OnMediaDeletedListener?) { + onMediaDeletedListenerRef = WeakReference(listener) + } + init { textView = editor?.let { WeakReference(editor) } } @@ -96,9 +102,9 @@ abstract class AztecMediaSpan(drawable: Drawable?, override var attributes: Azte abstract fun onClick() fun onMediaDeleted() { - onMediaDeletedListener?.onMediaDeleted(attributes) + onMediaDeletedListenerRef.get()?.onMediaDeleted(attributes) } fun beforeMediaDeleted() { - onMediaDeletedListener?.beforeMediaDeleted(attributes) + onMediaDeletedListenerRef.get()?.beforeMediaDeleted(attributes) } } From 5b0653ea50d94b1568e593456d53925f3fd9c803 Mon Sep 17 00:00:00 2001 From: khaykov Date: Wed, 24 Jul 2024 19:19:24 -0500 Subject: [PATCH 2/4] Use context ref in task list span. --- .../kotlin/org/wordpress/aztec/spans/AztecTaskListSpan.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecTaskListSpan.kt b/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecTaskListSpan.kt index 8cb39c679..6dc0a5b0e 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecTaskListSpan.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecTaskListSpan.kt @@ -30,6 +30,7 @@ import org.wordpress.aztec.ITextFormat import org.wordpress.aztec.R import org.wordpress.aztec.formatting.BlockFormatter import org.wordpress.aztec.setTaskList +import java.lang.ref.WeakReference fun createTaskListSpan( nestingLevel: Int, @@ -61,11 +62,12 @@ class AztecTaskListSpanAligned( open class AztecTaskListSpan( override var nestingLevel: Int, override var attributes: AztecAttributes = AztecAttributes(), - val context: Context, + context: Context, var listStyle: BlockFormatter.ListStyle = BlockFormatter.ListStyle(0, 0, 0, 0, 0), var onRefresh: ((AztecTaskListSpan) -> Unit)? = null ) : AztecListSpan(nestingLevel, listStyle.verticalPadding) { private var toggled: Boolean = false + private var contextRef: WeakReference = WeakReference(context) override val TAG = "ul" override val startTag: String @@ -82,7 +84,7 @@ open class AztecTaskListSpan( top: Int, baseline: Int, bottom: Int, text: CharSequence, start: Int, end: Int, first: Boolean, l: Layout) { - if (!first) return + if (!first || contextRef.get() == null) return val spanStart = (text as Spanned).getSpanStart(this) val spanEnd = text.getSpanEnd(this) @@ -99,7 +101,7 @@ open class AztecTaskListSpan( val drawableHeight = (0.8 * (p.fontMetrics.bottom - p.fontMetrics.top)) // Make sure the marker is correctly aligned on RTL languages val markerStartPosition: Float = x + (listStyle.indicatorMargin * dir) * 1f - val d: Drawable = context.resources.getDrawable(R.drawable.ic_checkbox, null) + val d: Drawable = contextRef.get()!!.resources.getDrawable(R.drawable.ic_checkbox, null) val leftBound = markerStartPosition.toInt() if (isChecked(text, lineIndex)) { d.state = intArrayOf(android.R.attr.state_checked) From 9dc15100ee116e5db45c0d5c60d44465b91888f9 Mon Sep 17 00:00:00 2001 From: khaykov Date: Wed, 24 Jul 2024 19:20:01 -0500 Subject: [PATCH 3/4] Use setter for media span listeners. Clear task list refresh listener when aztec is destroyed. --- .../kotlin/org/wordpress/aztec/AztecText.kt | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt b/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt index 0693fd306..d7b81370b 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt @@ -955,6 +955,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown blockEditorDialog!!.dismiss() } EnhancedMovementMethod.setLinkTappedListener(null) + clearTaskListRefreshListeners() } // We are exposing this method in order to allow subclasses to set their own alpha value @@ -1711,19 +1712,19 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown val imageSpans = editable.getSpans(start, end, AztecImageSpan::class.java) imageSpans.forEach { it.onImageTappedListener = onImageTappedListener - it.onMediaDeletedListener = onMediaDeletedListener + it.setOnMediaDeletedListener(onMediaDeletedListener) } val videoSpans = editable.getSpans(start, end, AztecVideoSpan::class.java) videoSpans.forEach { it.onVideoTappedListener = onVideoTappedListener - it.onMediaDeletedListener = onMediaDeletedListener + it.setOnMediaDeletedListener(onMediaDeletedListener) } val audioSpans = editable.getSpans(start, end, AztecAudioSpan::class.java) audioSpans.forEach { it.onAudioTappedListener = onAudioTappedListener - it.onMediaDeletedListener = onMediaDeletedListener + it.setOnMediaDeletedListener(onMediaDeletedListener) } val unknownHtmlSpans = editable.getSpans(start, end, UnknownHtmlSpan::class.java) @@ -1763,9 +1764,9 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown taskList.onRefresh = null this.editableText.removeSpan(taskList) val newSpan = if (taskList is AztecTaskListSpanAligned) { - AztecTaskListSpanAligned(taskList.nestingLevel, taskList.attributes, taskList.context, taskList.listStyle, taskList.align) + AztecTaskListSpanAligned(taskList.nestingLevel, taskList.attributes, context, taskList.listStyle, taskList.align) } else { - AztecTaskListSpan(taskList.nestingLevel, taskList.attributes, taskList.context, taskList.listStyle) + AztecTaskListSpan(taskList.nestingLevel, taskList.attributes, context, taskList.listStyle) } newSpan.onRefresh = { refreshTaskListSpan(it) @@ -1774,6 +1775,13 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown setSelection(selStart, selEnd) } + private fun clearTaskListRefreshListeners() { + val taskLists = this.editableText.getSpans(0, this.editableText.length, AztecTaskListSpan::class.java) + taskLists.forEach { taskList -> + taskList.onRefresh = null + } + } + fun disableTextChangedListener() { consumeEditEvent = true } @@ -2217,7 +2225,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown * Use this method to insert a custom AztecMediaSpan at the cursor position */ fun insertMediaSpan(span: AztecMediaSpan) { - span.onMediaDeletedListener = onMediaDeletedListener + span.setOnMediaDeletedListener(onMediaDeletedListener) lineBlockFormatter.insertMediaSpan(shouldAddMediaInline, span) } @@ -2323,7 +2331,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown text.removeSpan(clickableSpan) text.removeSpan(mediaSpan) - aztecMediaSpan.onMediaDeletedListener = onMediaDeletedListener + aztecMediaSpan.setOnMediaDeletedListener(onMediaDeletedListener) lineBlockFormatter.insertMediaSpanOverCurrentChar(aztecMediaSpan, start) contentChangeWatcher.notifyContentChanged() } From 1d078420cefaaf1a7d69c773ad54524e5cf30f0f Mon Sep 17 00:00:00 2001 From: khaykov Date: Thu, 25 Jul 2024 16:03:55 -0500 Subject: [PATCH 4/4] Make drawable null safe in task list span --- .../org/wordpress/aztec/spans/AztecTaskListSpan.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecTaskListSpan.kt b/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecTaskListSpan.kt index 6dc0a5b0e..38baa6298 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecTaskListSpan.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecTaskListSpan.kt @@ -101,12 +101,12 @@ open class AztecTaskListSpan( val drawableHeight = (0.8 * (p.fontMetrics.bottom - p.fontMetrics.top)) // Make sure the marker is correctly aligned on RTL languages val markerStartPosition: Float = x + (listStyle.indicatorMargin * dir) * 1f - val d: Drawable = contextRef.get()!!.resources.getDrawable(R.drawable.ic_checkbox, null) + val d: Drawable? = contextRef.get()?.resources?.getDrawable(R.drawable.ic_checkbox, null) val leftBound = markerStartPosition.toInt() if (isChecked(text, lineIndex)) { - d.state = intArrayOf(android.R.attr.state_checked) + d?.state = intArrayOf(android.R.attr.state_checked) } else { - d.state = intArrayOf() + d?.state = intArrayOf() } val (startShift, endShift) = if (dir > 0) { 0.8 to 0.2 @@ -114,11 +114,11 @@ open class AztecTaskListSpan( 0.2 to 0.8 } - d.setBounds((leftBound - drawableHeight * startShift).toInt().coerceAtLeast(0), + d?.setBounds((leftBound - drawableHeight * startShift).toInt().coerceAtLeast(0), (baseline - drawableHeight * 0.8).toInt(), (leftBound + drawableHeight * endShift).toInt(), (baseline + drawableHeight * 0.2).toInt()) - d.draw(c) + d?.draw(c) p.color = oldColor p.style = style