From b985274799708ad310a991efb6d33f192793385e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Tue, 9 Jun 2026 13:36:00 +0200 Subject: [PATCH 1/4] fix(android): set piority of applied spans --- .../enriched/common/EnrichedSpanFlags.kt | 35 +++++++++++++++++++ .../common/parser/EnrichedParser.java | 9 ++--- .../enriched/text/EnrichedTextView.kt | 3 +- .../textinput/EnrichedTextInputView.kt | 3 +- .../enriched/textinput/styles/InlineStyles.kt | 3 +- .../enriched/textinput/styles/ListStyles.kt | 7 ++-- .../textinput/styles/ParagraphStyles.kt | 7 ++-- .../textinput/styles/ParametrizedStyles.kt | 7 ++-- .../textinput/utils/EnrichedSpannable.kt | 3 +- .../enriched/textinput/utils/Utils.kt | 3 +- 10 files changed, 62 insertions(+), 18 deletions(-) create mode 100644 android/src/main/java/com/swmansion/enriched/common/EnrichedSpanFlags.kt diff --git a/android/src/main/java/com/swmansion/enriched/common/EnrichedSpanFlags.kt b/android/src/main/java/com/swmansion/enriched/common/EnrichedSpanFlags.kt new file mode 100644 index 00000000..2dc85fc5 --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/common/EnrichedSpanFlags.kt @@ -0,0 +1,35 @@ +package com.swmansion.enriched.common + +import android.text.Spannable +import com.swmansion.enriched.common.spans.interfaces.EnrichedInlineSpan +import com.swmansion.enriched.common.spans.interfaces.EnrichedSpan + +object EnrichedSpanFlags { + private const val PARAGRAPH_SPAN_PRIORITY = 2 + private const val INLINE_SPAN_PRIORITY = 1 + + @JvmField + val paragraphSpanFlags: Int = applyPriority(Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, PARAGRAPH_SPAN_PRIORITY) + + @JvmField + val inlineSpanFlags: Int = applyPriority(Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, INLINE_SPAN_PRIORITY) + + fun forSpan( + span: EnrichedSpan, + baseFlags: Int, + ): Int { + val isInlineSpan = span is EnrichedInlineSpan + val priority = if (isInlineSpan) INLINE_SPAN_PRIORITY else PARAGRAPH_SPAN_PRIORITY + return applyPriority(baseFlags, priority) + } + + private fun applyPriority( + flags: Int, + priority: Int, + ): Int { + // Cleaning up priority bits + val cleared = flags and Spannable.SPAN_PRIORITY.inv() + // Injecting priority bits + return cleared or ((priority shl Spannable.SPAN_PRIORITY_SHIFT) and Spannable.SPAN_PRIORITY) + } +} diff --git a/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedParser.java b/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedParser.java index 054b89c1..c6c30712 100644 --- a/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedParser.java +++ b/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedParser.java @@ -9,6 +9,7 @@ import android.text.style.AlignmentSpan; import android.text.style.ParagraphStyle; import com.swmansion.enriched.common.EnrichedConstants; +import com.swmansion.enriched.common.EnrichedSpanFlags; import com.swmansion.enriched.common.spans.EnrichedBoldSpan; import com.swmansion.enriched.common.spans.EnrichedCheckboxListSpan; import com.swmansion.enriched.common.spans.EnrichedCodeBlockSpan; @@ -435,7 +436,7 @@ public Spanned convert() { // TODO: verify if Spannable.SPAN_EXCLUSIVE_EXCLUSIVE does not break anything. // Previously it was SPAN_PARAGRAPH. I've changed that in order to fix ranges for list // items. - mSpannableStringBuilder.setSpan(obj[i], start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + mSpannableStringBuilder.setSpan(obj[i], start, end, EnrichedSpanFlags.paragraphSpanFlags); } } @@ -455,7 +456,7 @@ public Spanned convert() { mSpannableStringBuilder.removeSpan(zeroWidthSpaceSpan); mSpannableStringBuilder.setSpan( - zeroWidthSpaceSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + zeroWidthSpaceSpan, start, end, EnrichedSpanFlags.paragraphSpanFlags); } return mSpannableStringBuilder; @@ -735,7 +736,7 @@ private static void setSpanFromMark(Spannable text, Object mark, Object... spans int len = text.length(); if (where != len) { for (Object span : spans) { - text.setSpan(span, where, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(span, where, len, EnrichedSpanFlags.inlineSpanFlags); } } } @@ -758,7 +759,7 @@ private static void setParagraphSpanFromMark(Editable text, Object mark, Object. if (where != len) { for (Object span : spans) { - text.setSpan(span, where, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(span, where, len, EnrichedSpanFlags.paragraphSpanFlags); } } } diff --git a/android/src/main/java/com/swmansion/enriched/text/EnrichedTextView.kt b/android/src/main/java/com/swmansion/enriched/text/EnrichedTextView.kt index 7ff8ccd5..ebc87ebe 100644 --- a/android/src/main/java/com/swmansion/enriched/text/EnrichedTextView.kt +++ b/android/src/main/java/com/swmansion/enriched/text/EnrichedTextView.kt @@ -24,6 +24,7 @@ import com.facebook.react.views.text.ReactTypefaceUtils.applyStyles import com.facebook.react.views.text.ReactTypefaceUtils.parseFontStyle import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight import com.swmansion.enriched.common.EnrichedConstants +import com.swmansion.enriched.common.EnrichedSpanFlags import com.swmansion.enriched.common.GumboNormalizer import com.swmansion.enriched.common.parser.EnrichedParser import com.swmansion.enriched.text.spans.EnrichedTextImageSpan @@ -266,7 +267,7 @@ class EnrichedTextView : AppCompatTextView { spannable.removeSpan(span) val newSpan = span.rebuildWithStyle(enrichedStyle) - spannable.setSpan(newSpan, start, end, flags) + spannable.setSpan(newSpan, start, end, EnrichedSpanFlags.forSpan(newSpan, flags)) modified = true } diff --git a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt index ab9df604..c4cd3224 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt @@ -39,6 +39,7 @@ import com.facebook.react.views.text.ReactTypefaceUtils.applyStyles import com.facebook.react.views.text.ReactTypefaceUtils.parseFontStyle import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight import com.swmansion.enriched.common.EnrichedConstants +import com.swmansion.enriched.common.EnrichedSpanFlags import com.swmansion.enriched.common.GumboNormalizer import com.swmansion.enriched.common.parser.EnrichedParser import com.swmansion.enriched.textinput.events.MentionHandler @@ -1068,7 +1069,7 @@ class EnrichedTextInputView : spannable.removeSpan(span) val newSpan = span.rebuildWithStyle(htmlStyle) - spannable.setSpan(newSpan, start, end, flags) + spannable.setSpan(newSpan, start, end, EnrichedSpanFlags.forSpan(newSpan, flags)) } if (shouldEmitStateChange) { diff --git a/android/src/main/java/com/swmansion/enriched/textinput/styles/InlineStyles.kt b/android/src/main/java/com/swmansion/enriched/textinput/styles/InlineStyles.kt index 4e3b8660..91525fa6 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/styles/InlineStyles.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/styles/InlineStyles.kt @@ -2,6 +2,7 @@ package com.swmansion.enriched.textinput.styles import android.text.Editable import android.text.Spannable +import com.swmansion.enriched.common.EnrichedSpanFlags import com.swmansion.enriched.textinput.EnrichedTextInputView import com.swmansion.enriched.textinput.spans.EnrichedSpans import com.swmansion.enriched.textinput.utils.getSafeSpanBoundaries @@ -41,7 +42,7 @@ class InlineStyles( val span = type.getDeclaredConstructor(HtmlStyle::class.java).newInstance(view.htmlStyle) val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(minimum, maximum) - spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.inlineSpanFlags) } private fun setAndMergeSpans( diff --git a/android/src/main/java/com/swmansion/enriched/textinput/styles/ListStyles.kt b/android/src/main/java/com/swmansion/enriched/textinput/styles/ListStyles.kt index 01801239..02043929 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/styles/ListStyles.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/styles/ListStyles.kt @@ -5,6 +5,7 @@ import android.text.Spannable import android.text.SpannableStringBuilder import android.text.Spanned import com.swmansion.enriched.common.EnrichedConstants +import com.swmansion.enriched.common.EnrichedSpanFlags import com.swmansion.enriched.textinput.EnrichedTextInputView import com.swmansion.enriched.textinput.spans.EnrichedInputCheckboxListSpan import com.swmansion.enriched.textinput.spans.EnrichedInputOrderedListSpan @@ -65,18 +66,18 @@ class ListStyles( when (name) { EnrichedSpans.UNORDERED_LIST -> { val span = EnrichedInputUnorderedListSpan(view.htmlStyle) - spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.paragraphSpanFlags) } EnrichedSpans.ORDERED_LIST -> { val index = getOrderedListIndex(spannable, safeStart) val span = EnrichedInputOrderedListSpan(index, view.htmlStyle) - spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.paragraphSpanFlags) } EnrichedSpans.CHECKBOX_LIST -> { val span = EnrichedInputCheckboxListSpan(isChecked ?: false, view.htmlStyle) - spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.paragraphSpanFlags) // Invalidate layout to update checkbox drawing in case checkbox is bigger than line height view.layoutManager.invalidateLayout() diff --git a/android/src/main/java/com/swmansion/enriched/textinput/styles/ParagraphStyles.kt b/android/src/main/java/com/swmansion/enriched/textinput/styles/ParagraphStyles.kt index 09b06271..bd52efe9 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/styles/ParagraphStyles.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/styles/ParagraphStyles.kt @@ -5,6 +5,7 @@ import android.text.Spannable import android.text.SpannableStringBuilder import android.util.Log import com.swmansion.enriched.common.EnrichedConstants +import com.swmansion.enriched.common.EnrichedSpanFlags import com.swmansion.enriched.textinput.EnrichedTextInputView import com.swmansion.enriched.textinput.spans.EnrichedSpans import com.swmansion.enriched.textinput.spans.interfaces.EnrichedInputSpan @@ -89,7 +90,7 @@ class ParagraphStyles( } val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(newStart, newEnd) - spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.paragraphSpanFlags) } private fun setSpan( @@ -105,7 +106,7 @@ class ParagraphStyles( val span = type.getDeclaredConstructor(HtmlStyle::class.java).newInstance(view.htmlStyle) val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end) - spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.paragraphSpanFlags) } // Removes spans of the given type in the specified range. @@ -232,7 +233,7 @@ class ParagraphStyles( val (safeStart, safeEnd) = s.getSafeSpanBoundaries(newStart, newEnd) val span = type.getDeclaredConstructor(HtmlStyle::class.java).newInstance(view.htmlStyle) - s.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + s.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.paragraphSpanFlags) } private fun handleConflictsDuringNewlineDeletion( diff --git a/android/src/main/java/com/swmansion/enriched/textinput/styles/ParametrizedStyles.kt b/android/src/main/java/com/swmansion/enriched/textinput/styles/ParametrizedStyles.kt index 82a7922d..bb00cda0 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/styles/ParametrizedStyles.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/styles/ParametrizedStyles.kt @@ -5,6 +5,7 @@ import android.text.Spannable import android.text.SpannableStringBuilder import android.text.Spanned import com.swmansion.enriched.common.EnrichedConstants +import com.swmansion.enriched.common.EnrichedSpanFlags import com.swmansion.enriched.textinput.EnrichedTextInputView import com.swmansion.enriched.textinput.spans.EnrichedInputImageSpan import com.swmansion.enriched.textinput.spans.EnrichedInputLinkSpan @@ -63,7 +64,7 @@ class ParametrizedStyles( val spanEnd = start + text.length val span = EnrichedInputLinkSpan(url, view.htmlStyle, true) val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, spanEnd) - spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.inlineSpanFlags) view.selection?.validateStyles() isSettingLinkSpan = false @@ -160,7 +161,7 @@ class ParametrizedStyles( span, safeStart, safeEnd, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE, + EnrichedSpanFlags.inlineSpanFlags, ) } } @@ -373,7 +374,7 @@ class ParametrizedStyles( val span = EnrichedInputMentionSpan(text, indicator, attributes, view.htmlStyle) val spanEnd = start + text.length val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, spanEnd) - spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.inlineSpanFlags) val hasSpaceAtTheEnd = spannable.length > safeEnd && spannable[safeEnd] == ' ' if (!hasSpaceAtTheEnd) { diff --git a/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSpannable.kt b/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSpannable.kt index 60ebd1a1..576e1021 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSpannable.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSpannable.kt @@ -3,6 +3,7 @@ package com.swmansion.enriched.textinput.utils import android.text.Spannable import android.text.SpannableString import android.text.SpannableStringBuilder +import com.swmansion.enriched.common.EnrichedSpanFlags import com.swmansion.enriched.common.spans.interfaces.EnrichedBlockSpan import com.swmansion.enriched.common.spans.interfaces.EnrichedParagraphSpan import com.swmansion.enriched.common.spans.interfaces.EnrichedSpan @@ -144,7 +145,7 @@ fun Spannable.mergeSpannables( val (_, newParagraphEnd) = builder.getParagraphBounds(spanStart, pasteEnd) val flags = builder.getSpanFlags(span) builder.removeSpan(span) - builder.setSpan(span, spanStart, newParagraphEnd, flags) + builder.setSpan(span, spanStart, newParagraphEnd, EnrichedSpanFlags.forSpan(span, flags)) } } diff --git a/android/src/main/java/com/swmansion/enriched/textinput/utils/Utils.kt b/android/src/main/java/com/swmansion/enriched/textinput/utils/Utils.kt index 5f378a32..57f265b1 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/utils/Utils.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/utils/Utils.kt @@ -7,6 +7,7 @@ import android.text.Spanned import android.util.Log import android.view.MotionEvent import android.widget.TextView +import com.swmansion.enriched.common.EnrichedSpanFlags import com.swmansion.enriched.textinput.spans.EnrichedInputCheckboxListSpan import org.json.JSONObject @@ -78,7 +79,7 @@ fun TextView.setCheckboxClickListener() { // Reapply span so changes are visible without need to redraw entire TextView spannable.removeSpan(span) - spannable.setSpan(span, start, end, flags) + spannable.setSpan(span, start, end, EnrichedSpanFlags.forSpan(span, flags)) // For focused input, ensure cursor is active for affected paragraph if (tv.isFocused) { From 36f7c1de84348dff5cf79b87ced85ff0b06106d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Fri, 12 Jun 2026 08:07:22 +0200 Subject: [PATCH 2/4] fix: priority for images --- .../com/swmansion/enriched/common/parser/EnrichedParser.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedParser.java b/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedParser.java index c6c30712..8d31b8b3 100644 --- a/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedParser.java +++ b/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedParser.java @@ -433,7 +433,8 @@ public Spanned convert() { if (end == start) { mSpannableStringBuilder.removeSpan(obj[i]); } else { - // TODO: verify if Spannable.SPAN_EXCLUSIVE_EXCLUSIVE does not break anything. + // TODO: verify if Spannable.SPAN_EXCLUSIVE_EXCLUSIVE from + // EnrichedSpanFlags.paragraphSpanFlags does not break anything. // Previously it was SPAN_PARAGRAPH. I've changed that in order to fix ranges for list // items. mSpannableStringBuilder.setSpan(obj[i], start, end, EnrichedSpanFlags.paragraphSpanFlags); @@ -788,7 +789,7 @@ private static void startImg( spanFactory.createImageSpan(src, Integer.parseInt(width), Integer.parseInt(height)), len, text.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + EnrichedSpanFlags.inlineSpanFlags); } private static void startA(Editable text, Attributes attributes) { From 70893d79a8bde8c00e1579eac5c2f698a87dcd0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Fri, 19 Jun 2026 08:55:11 +0200 Subject: [PATCH 3/4] fix: apply priority for alignment spans --- .../enriched/common/EnrichedSpanFlags.kt | 25 ++++++++++--------- .../common/parser/EnrichedParser.java | 20 ++++++--------- .../textinput/styles/AlignmentStyles.kt | 13 +++++----- .../enriched/textinput/styles/InlineStyles.kt | 2 +- .../enriched/textinput/styles/ListStyles.kt | 6 ++--- .../textinput/styles/ParagraphStyles.kt | 6 ++--- .../textinput/styles/ParametrizedStyles.kt | 6 ++--- 7 files changed, 36 insertions(+), 42 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/common/EnrichedSpanFlags.kt b/android/src/main/java/com/swmansion/enriched/common/EnrichedSpanFlags.kt index 2dc85fc5..522bf18b 100644 --- a/android/src/main/java/com/swmansion/enriched/common/EnrichedSpanFlags.kt +++ b/android/src/main/java/com/swmansion/enriched/common/EnrichedSpanFlags.kt @@ -1,25 +1,26 @@ package com.swmansion.enriched.common import android.text.Spannable +import com.swmansion.enriched.common.spans.EnrichedAlignmentSpan import com.swmansion.enriched.common.spans.interfaces.EnrichedInlineSpan -import com.swmansion.enriched.common.spans.interfaces.EnrichedSpan object EnrichedSpanFlags { - private const val PARAGRAPH_SPAN_PRIORITY = 2 + private const val ALIGNMENT_SPAN_PRIORITY = 0 private const val INLINE_SPAN_PRIORITY = 1 + private const val PARAGRAPH_SPAN_PRIORITY = 2 - @JvmField - val paragraphSpanFlags: Int = applyPriority(Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, PARAGRAPH_SPAN_PRIORITY) - - @JvmField - val inlineSpanFlags: Int = applyPriority(Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, INLINE_SPAN_PRIORITY) - + @JvmStatic + @JvmOverloads fun forSpan( - span: EnrichedSpan, - baseFlags: Int, + span: Any?, + baseFlags: Int = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, ): Int { - val isInlineSpan = span is EnrichedInlineSpan - val priority = if (isInlineSpan) INLINE_SPAN_PRIORITY else PARAGRAPH_SPAN_PRIORITY + val priority = + when (span) { + is EnrichedAlignmentSpan -> ALIGNMENT_SPAN_PRIORITY + is EnrichedInlineSpan -> INLINE_SPAN_PRIORITY + else -> PARAGRAPH_SPAN_PRIORITY + } return applyPriority(baseFlags, priority) } diff --git a/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedParser.java b/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedParser.java index d255ed97..08198b3c 100644 --- a/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedParser.java +++ b/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedParser.java @@ -468,11 +468,7 @@ public Spanned convert() { if (end == start) { mSpannableStringBuilder.removeSpan(obj[i]); } else { - // TODO: verify if Spannable.SPAN_EXCLUSIVE_EXCLUSIVE from - // EnrichedSpanFlags.paragraphSpanFlags does not break anything. - // Previously it was SPAN_PARAGRAPH. I've changed that in order to fix ranges for list - // items. - mSpannableStringBuilder.setSpan(obj[i], start, end, EnrichedSpanFlags.paragraphSpanFlags); + mSpannableStringBuilder.setSpan(obj[i], start, end, EnrichedSpanFlags.forSpan(obj[i])); } } @@ -507,7 +503,7 @@ public Spanned convert() { mSpannableStringBuilder.removeSpan(zeroWidthSpaceSpan); mSpannableStringBuilder.setSpan( - zeroWidthSpaceSpan, start, end, EnrichedSpanFlags.paragraphSpanFlags); + zeroWidthSpaceSpan, start, end, EnrichedSpanFlags.forSpan(zeroWidthSpaceSpan)); } return mSpannableStringBuilder; @@ -804,7 +800,7 @@ private static void setSpanFromMark(Spannable text, Object mark, Object... spans int len = text.length(); if (where != len) { for (Object span : spans) { - text.setSpan(span, where, len, EnrichedSpanFlags.inlineSpanFlags); + text.setSpan(span, where, len, EnrichedSpanFlags.forSpan(span)); } } } @@ -827,7 +823,7 @@ private static void setParagraphSpanFromMark(Editable text, Object mark, Object. if (where != len) { for (Object span : spans) { - text.setSpan(span, where, len, EnrichedSpanFlags.paragraphSpanFlags); + text.setSpan(span, where, len, EnrichedSpanFlags.forSpan(span)); } } } @@ -852,11 +848,9 @@ private static void startImg( int len = text.length(); text.append(""); - text.setSpan( - spanFactory.createImageSpan(src, Integer.parseInt(width), Integer.parseInt(height)), - len, - text.length(), - EnrichedSpanFlags.inlineSpanFlags); + Object imageSpan = + spanFactory.createImageSpan(src, Integer.parseInt(width), Integer.parseInt(height)); + text.setSpan(imageSpan, len, text.length(), EnrichedSpanFlags.forSpan(imageSpan)); } private static void startA(Editable text, Attributes attributes) { diff --git a/android/src/main/java/com/swmansion/enriched/textinput/styles/AlignmentStyles.kt b/android/src/main/java/com/swmansion/enriched/textinput/styles/AlignmentStyles.kt index d7df9510..e7353f67 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/styles/AlignmentStyles.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/styles/AlignmentStyles.kt @@ -4,6 +4,7 @@ import android.text.Editable import android.text.Spannable import android.text.SpannableStringBuilder import com.swmansion.enriched.common.EnrichedConstants +import com.swmansion.enriched.common.EnrichedSpanFlags import com.swmansion.enriched.textinput.EnrichedTextInputView import com.swmansion.enriched.textinput.spans.EnrichedInputAlignmentSpan import com.swmansion.enriched.textinput.spans.EnrichedInputCheckboxListSpan @@ -24,12 +25,8 @@ class AlignmentStyles( flags: Int = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, ) { val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end) - spannable.setSpan( - EnrichedInputAlignmentSpan(cssValue), - safeStart, - safeEnd, - flags, - ) + val span = EnrichedInputAlignmentSpan(cssValue) + spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span, flags)) } private fun toCssValue(alignment: String): String = @@ -302,7 +299,9 @@ class AlignmentStyles( // INCLUSIVE_EXCLUSIVE is intentional here: autoStretchAlignmentSpan will convert // it to EXCLUSIVE_EXCLUSIVE once the merge is complete. val (safeStart, safeEnd) = s.getSafeSpanBoundaries(paraStart, paraEnd) - dominantTopSpan?.let { s.setSpan(it, safeStart, safeEnd, Spannable.SPAN_INCLUSIVE_EXCLUSIVE) } + dominantTopSpan?.let { + s.setSpan(it, safeStart, safeEnd, EnrichedSpanFlags.forSpan(it, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)) + } return cursorPosition } diff --git a/android/src/main/java/com/swmansion/enriched/textinput/styles/InlineStyles.kt b/android/src/main/java/com/swmansion/enriched/textinput/styles/InlineStyles.kt index a384b67b..802f78f1 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/styles/InlineStyles.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/styles/InlineStyles.kt @@ -42,7 +42,7 @@ class InlineStyles( val span = type.getDeclaredConstructor(HtmlStyle::class.java).newInstance(view.htmlStyle) val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(minimum, maximum) - spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.inlineSpanFlags) + spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span)) } private fun setAndMergeSpans( diff --git a/android/src/main/java/com/swmansion/enriched/textinput/styles/ListStyles.kt b/android/src/main/java/com/swmansion/enriched/textinput/styles/ListStyles.kt index 7674c4e7..236560f1 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/styles/ListStyles.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/styles/ListStyles.kt @@ -67,18 +67,18 @@ class ListStyles( when (name) { EnrichedSpans.UNORDERED_LIST -> { val span = EnrichedInputUnorderedListSpan(view.htmlStyle) - spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.paragraphSpanFlags) + spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span)) } EnrichedSpans.ORDERED_LIST -> { val index = getOrderedListIndex(spannable, safeStart) val span = EnrichedInputOrderedListSpan(index, view.htmlStyle) - spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.paragraphSpanFlags) + spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span)) } EnrichedSpans.CHECKBOX_LIST -> { val span = EnrichedInputCheckboxListSpan(isChecked ?: false, view.htmlStyle) - spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.paragraphSpanFlags) + spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span)) // Invalidate layout to update checkbox drawing in case checkbox is bigger than line height view.layoutManager.invalidateLayout() diff --git a/android/src/main/java/com/swmansion/enriched/textinput/styles/ParagraphStyles.kt b/android/src/main/java/com/swmansion/enriched/textinput/styles/ParagraphStyles.kt index 64504fd4..af822b7f 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/styles/ParagraphStyles.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/styles/ParagraphStyles.kt @@ -90,7 +90,7 @@ class ParagraphStyles( } val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(newStart, newEnd) - spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.paragraphSpanFlags) + spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span)) } private fun setSpan( @@ -106,7 +106,7 @@ class ParagraphStyles( val span = type.getDeclaredConstructor(HtmlStyle::class.java).newInstance(view.htmlStyle) val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end) - spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.paragraphSpanFlags) + spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span)) } // Removes spans of the given type in the specified range. @@ -233,7 +233,7 @@ class ParagraphStyles( val (safeStart, safeEnd) = s.getSafeSpanBoundaries(newStart, newEnd) val span = type.getDeclaredConstructor(HtmlStyle::class.java).newInstance(view.htmlStyle) - s.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.paragraphSpanFlags) + s.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span)) } private fun handleConflictsDuringNewlineDeletion( diff --git a/android/src/main/java/com/swmansion/enriched/textinput/styles/ParametrizedStyles.kt b/android/src/main/java/com/swmansion/enriched/textinput/styles/ParametrizedStyles.kt index 02956566..c441224b 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/styles/ParametrizedStyles.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/styles/ParametrizedStyles.kt @@ -64,7 +64,7 @@ class ParametrizedStyles( val spanEnd = start + text.length val span = EnrichedInputLinkSpan(url, view.htmlStyle, true) val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, spanEnd) - spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.inlineSpanFlags) + spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span)) view.selection?.validateStyles() isSettingLinkSpan = false @@ -161,7 +161,7 @@ class ParametrizedStyles( span, safeStart, safeEnd, - EnrichedSpanFlags.inlineSpanFlags, + EnrichedSpanFlags.forSpan(span), ) } } @@ -374,7 +374,7 @@ class ParametrizedStyles( val span = EnrichedInputMentionSpan(text, indicator, attributes, view.htmlStyle) val spanEnd = start + text.length val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, spanEnd) - spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.inlineSpanFlags) + spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span)) val hasSpaceAtTheEnd = spannable.length > safeEnd && spannable[safeEnd] == ' ' if (!hasSpaceAtTheEnd) { From 3c305638480ec848a86ae8a5b01bc4868629d70b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Fri, 19 Jun 2026 09:22:39 +0200 Subject: [PATCH 4/4] fix: add comment to EnrichedSpanFlags --- .../java/com/swmansion/enriched/common/EnrichedSpanFlags.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/android/src/main/java/com/swmansion/enriched/common/EnrichedSpanFlags.kt b/android/src/main/java/com/swmansion/enriched/common/EnrichedSpanFlags.kt index 522bf18b..d9a0ddb5 100644 --- a/android/src/main/java/com/swmansion/enriched/common/EnrichedSpanFlags.kt +++ b/android/src/main/java/com/swmansion/enriched/common/EnrichedSpanFlags.kt @@ -4,6 +4,9 @@ import android.text.Spannable import com.swmansion.enriched.common.spans.EnrichedAlignmentSpan import com.swmansion.enriched.common.spans.interfaces.EnrichedInlineSpan +// Higher priority spans are processed first, so styles with lower priorities are painted on top of previously applied styles. +// For example, inline styles are applied on top of paragraph styles, allowing them to override paragraph-level styling. +// Alignment styles are applied last, ensuring they position the final, fully styled text. object EnrichedSpanFlags { private const val ALIGNMENT_SPAN_PRIORITY = 0 private const val INLINE_SPAN_PRIORITY = 1