From bda911a9173b5fb7296b709d2e8919ec75ddb311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Thu, 18 Jun 2026 12:46:33 +0200 Subject: [PATCH 1/4] feat(android): add support for custom style --- .../swmansion/enriched/common/CustomStyle.kt | 6 + .../common/parser/EnrichedColorParser.kt | 273 ++++++++++++++++++ .../common/parser/EnrichedParser.java | 75 +++++ .../common/parser/EnrichedSpanFactory.kt | 6 + .../common/spans/EnrichedCustomStyleSpan.kt | 20 ++ .../enriched/text/EnrichedTextSpanFactory.kt | 7 + .../text/spans/EnrichedTextCustomStyleSpan.kt | 16 + .../EnrichedTextInputSpannableFactory.kt | 7 + .../textinput/EnrichedTextInputView.kt | 8 + .../textinput/EnrichedTextInputViewManager.kt | 2 +- .../spans/EnrichedInputCustomStyleSpan.kt | 16 + .../textinput/utils/EnrichedSelection.kt | 28 ++ .../textinput/utils/EnrichedSpanState.kt | 32 ++ .../textinput/watchers/EnrichedTextWatcher.kt | 1 + 14 files changed, 496 insertions(+), 1 deletion(-) create mode 100644 android/src/main/java/com/swmansion/enriched/common/CustomStyle.kt create mode 100644 android/src/main/java/com/swmansion/enriched/common/parser/EnrichedColorParser.kt create mode 100644 android/src/main/java/com/swmansion/enriched/common/spans/EnrichedCustomStyleSpan.kt create mode 100644 android/src/main/java/com/swmansion/enriched/text/spans/EnrichedTextCustomStyleSpan.kt create mode 100644 android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputCustomStyleSpan.kt diff --git a/android/src/main/java/com/swmansion/enriched/common/CustomStyle.kt b/android/src/main/java/com/swmansion/enriched/common/CustomStyle.kt new file mode 100644 index 00000000..ef8ced02 --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/common/CustomStyle.kt @@ -0,0 +1,6 @@ +package com.swmansion.enriched.common + +data class CustomStyle( + val foregroundColor: Int? = null, + val backgroundColor: Int? = null, +) diff --git a/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedColorParser.kt b/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedColorParser.kt new file mode 100644 index 00000000..fdac22e8 --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedColorParser.kt @@ -0,0 +1,273 @@ +package com.swmansion.enriched.common.parser + +import android.graphics.Color +import androidx.core.graphics.toColorInt +import kotlin.math.roundToInt + +object EnrichedColorParser { + private val CSS_NAMED_COLORS = + mapOf( + "aliceblue" to "#F0F8FFFF", + "antiquewhite" to "#FAEBD7FF", + "aqua" to "#00FFFFFF", + "aquamarine" to "#7FFFD4FF", + "azure" to "#F0FFFFFF", + "beige" to "#F5F5DCFF", + "bisque" to "#FFE4C4FF", + "black" to "#000000FF", + "blanchedalmond" to "#FFEBCDFF", + "blue" to "#0000FFFF", + "blueviolet" to "#8A2BE2FF", + "brown" to "#A52A2AFF", + "burlywood" to "#DEB887FF", + "cadetblue" to "#5F9EA0FF", + "chartreuse" to "#7FFF00FF", + "chocolate" to "#D2691EFF", + "coral" to "#FF7F50FF", + "cornflowerblue" to "#6495EDFF", + "cornsilk" to "#FFF8DCFF", + "crimson" to "#DC143CFF", + "cyan" to "#00FFFFFF", + "darkblue" to "#00008BFF", + "darkcyan" to "#008B8BFF", + "darkgoldenrod" to "#B8860BFF", + "darkgray" to "#A9A9A9FF", + "darkgrey" to "#A9A9A9FF", + "darkgreen" to "#006400FF", + "darkkhaki" to "#BDB76BFF", + "darkmagenta" to "#8B008BFF", + "darkolivegreen" to "#556B2FFF", + "darkorange" to "#FF8C00FF", + "darkorchid" to "#9932CCFF", + "darkred" to "#8B0000FF", + "darksalmon" to "#E9967AFF", + "darkseagreen" to "#8FBC8FFF", + "darkslateblue" to "#483D8BFF", + "darkslategray" to "#2F4F4FFF", + "darkslategrey" to "#2F4F4FFF", + "darkturquoise" to "#00CED1FF", + "darkviolet" to "#9400D3FF", + "deeppink" to "#FF1493FF", + "deepskyblue" to "#00BFFFFF", + "dimgray" to "#696969FF", + "dimgrey" to "#696969FF", + "dodgerblue" to "#1E90FFFF", + "firebrick" to "#B22222FF", + "floralwhite" to "#FFFAF0FF", + "forestgreen" to "#228B22FF", + "fuchsia" to "#FF00FFFF", + "gainsboro" to "#DCDCDCFF", + "ghostwhite" to "#F8F8FFFF", + "gold" to "#FFD700FF", + "goldenrod" to "#DAA520FF", + "gray" to "#808080FF", + "grey" to "#808080FF", + "green" to "#008000FF", + "greenyellow" to "#ADFF2FFF", + "honeydew" to "#F0FFF0FF", + "hotpink" to "#FF69B4FF", + "indianred" to "#CD5C5CFF", + "indigo" to "#4B0082FF", + "ivory" to "#FFFFF0FF", + "khaki" to "#F0E68CFF", + "lavender" to "#E6E6FAFF", + "lavenderblush" to "#FFF0F5FF", + "lawngreen" to "#7CFC00FF", + "lemonchiffon" to "#FFFACDFF", + "lightblue" to "#ADD8E6FF", + "lightcoral" to "#F08080FF", + "lightcyan" to "#E0FFFFFF", + "lightgoldenrodyellow" to "#FAFAD2FF", + "lightgray" to "#D3D3D3FF", + "lightgrey" to "#D3D3D3FF", + "lightgreen" to "#90EE90FF", + "lightpink" to "#FFB6C1FF", + "lightsalmon" to "#FFA07AFF", + "lightseagreen" to "#20B2AAFF", + "lightskyblue" to "#87CEFAFF", + "lightslategray" to "#778899FF", + "lightslategrey" to "#778899FF", + "lightsteelblue" to "#B0C4DEFF", + "lightyellow" to "#FFFFE0FF", + "lime" to "#00FF00FF", + "limegreen" to "#32CD32FF", + "linen" to "#FAF0E6FF", + "magenta" to "#FF00FFFF", + "maroon" to "#800000FF", + "mediumaquamarine" to "#66CDAAFF", + "mediumblue" to "#0000CDFF", + "mediumorchid" to "#BA55D3FF", + "mediumpurple" to "#9370D8FF", + "mediumseagreen" to "#3CB371FF", + "mediumslateblue" to "#7B68EEFF", + "mediumspringgreen" to "#00FA9AFF", + "mediumturquoise" to "#48D1CCFF", + "mediumvioletred" to "#C71585FF", + "midnightblue" to "#191970FF", + "mintcream" to "#F5FFFAFF", + "mistyrose" to "#FFE4E1FF", + "moccasin" to "#FFE4B5FF", + "navajowhite" to "#FFDEADFF", + "navy" to "#000080FF", + "oldlace" to "#FDF5E6FF", + "olive" to "#808000FF", + "olivedrab" to "#6B8E23FF", + "orange" to "#FFA500FF", + "orangered" to "#FF4500FF", + "orchid" to "#DA70D6FF", + "palegoldenrod" to "#EEE8AAFF", + "palegreen" to "#98FB98FF", + "paleturquoise" to "#AFEEEEFF", + "palevioletred" to "#D87093FF", + "papayawhip" to "#FFEFD5FF", + "peachpuff" to "#FFDAB9FF", + "peru" to "#CD853FFF", + "pink" to "#FFC0CBFF", + "plum" to "#DDA0DDFF", + "powderblue" to "#B0E0E6FF", + "purple" to "#800080FF", + "rebeccapurple" to "#663399FF", + "red" to "#FF0000FF", + "rosybrown" to "#BC8F8FFF", + "royalblue" to "#4169E1FF", + "saddlebrown" to "#8B4513FF", + "salmon" to "#FA8072FF", + "sandybrown" to "#F4A460FF", + "seagreen" to "#2E8B57FF", + "seashell" to "#FFF5EEFF", + "sienna" to "#A0522DFF", + "silver" to "#C0C0C0FF", + "skyblue" to "#87CEEBFF", + "slateblue" to "#6A5ACDFF", + "slategray" to "#708090FF", + "slategrey" to "#708090FF", + "snow" to "#FFFAFAFF", + "springgreen" to "#00FF7FFF", + "steelblue" to "#4682B4FF", + "tan" to "#D2B48CFF", + "teal" to "#008080FF", + "thistle" to "#D8BFD8FF", + "tomato" to "#FF6347FF", + "turquoise" to "#40E0D0FF", + "violet" to "#EE82EEFF", + "wheat" to "#F5DEB3FF", + "white" to "#FFFFFFFF", + "whitesmoke" to "#F5F5F5FF", + "yellow" to "#FFFF00FF", + "yellowgreen" to "#9ACD32FF", + ) + + @JvmStatic + fun parseCssColor(value: String?): Int? { + if (value.isNullOrBlank()) return null + + var str = value.trim().lowercase() + + // Handle Named Colors + if (CSS_NAMED_COLORS.containsKey(str)) { + str = CSS_NAMED_COLORS[str]!! + } + + // Handle Hex (#FFF, #FFFFFF, #FFFFFFFF CSS format) + if (str.startsWith("#")) { + val hex = str.substring(1) + return try { + when (hex.length) { + 3 -> { + // #RGB -> #RRGGBB + val expanded = "${hex[0]}${hex[0]}${hex[1]}${hex[1]}${hex[2]}${hex[2]}" + "#$expanded".toColorInt() + } + + 4 -> { + // #RGBA -> #AARRGGBB + val expanded = "${hex[3]}${hex[3]}${hex[0]}${hex[0]}${hex[1]}${hex[1]}${hex[2]}${hex[2]}" + "#$expanded".toColorInt() + } + + 6 -> { + // #RRGGBB + str.toColorInt() + } + + 8 -> { + // #RRGGBBAA -> #AARRGGBB + val aarrggbb = "#${hex.substring(6, 8)}${hex.substring(0, 6)}" + aarrggbb.toColorInt() + } + + else -> { + null + } + } + } catch (_: IllegalArgumentException) { + null + } + } + + // Handle rgb() and rgba() + if (str.startsWith("rgb")) { + return try { + val start = str.indexOf('(') + 1 + val end = str.indexOf(')') + if (start <= 0 || end <= start) return null + + val parts = str.substring(start, end).split(",") + if (parts.size >= 3) { + val r = + parts[0] + .trim() + .toFloat() + .roundToInt() + .coerceIn(0, 255) + val g = + parts[1] + .trim() + .toFloat() + .roundToInt() + .coerceIn(0, 255) + val b = + parts[2] + .trim() + .toFloat() + .roundToInt() + .coerceIn(0, 255) + + val a = + if (parts.size == 4) { + (parts[3].trim().toFloat() * 255f).roundToInt().coerceIn(0, 255) + } else { + 255 + } + + Color.argb(a, r, g, b) + } else { + null + } + } catch (_: Exception) { + null + } + } + + // Catch any native Android colors if missed + return try { + str.toColorInt() + } catch (_: IllegalArgumentException) { + null + } + } + + @JvmStatic + fun colorToHex(color: Int): String { + val alpha = (color ushr 24) and 0xFF + val red = (color shr 16) and 0xFF + val green = (color shr 8) and 0xFF + val blue = color and 0xFF + + return if (alpha == 255) { + String.format("#%02X%02X%02X", red, green, blue) + } else { + String.format("#%02X%02X%02X%02X", red, green, blue, alpha) + } + } +} 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 02d52f70..3b767ead 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 @@ -11,6 +11,7 @@ import com.swmansion.enriched.common.spans.EnrichedBoldSpan; import com.swmansion.enriched.common.spans.EnrichedCheckboxListSpan; import com.swmansion.enriched.common.spans.EnrichedCodeBlockSpan; +import com.swmansion.enriched.common.spans.EnrichedCustomStyleSpan; import com.swmansion.enriched.common.spans.EnrichedH1Span; import com.swmansion.enriched.common.spans.EnrichedH2Span; import com.swmansion.enriched.common.spans.EnrichedH3Span; @@ -338,6 +339,30 @@ private static void withinParagraph(StringBuilder out, Spanned text, int start, // Don't output the placeholder character underlying the image. i = next; } + if (style[j] instanceof EnrichedCustomStyleSpan) { + EnrichedCustomStyleSpan cs = (EnrichedCustomStyleSpan) style[j]; + Integer fgColor = cs.getForegroundColor(); + Integer bgColor = cs.getBackgroundColor(); + if (fgColor != null || bgColor != null) { + StringBuilder cssProps = new StringBuilder(); + if (fgColor != null) { + cssProps + .append("color: ") + .append(EnrichedColorParser.colorToHex(fgColor)) + .append(";"); + } + if (bgColor != null) { + if (cssProps.length() > 0) cssProps.append(" "); + cssProps + .append("background-color: ") + .append(EnrichedColorParser.colorToHex(bgColor)) + .append(";"); + } + out.append(""); + } else { + out.append(""); + } + } } withinStyle(out, text, i, next); for (int j = style.length - 1; j >= 0; j--) { @@ -362,6 +387,9 @@ private static void withinParagraph(StringBuilder out, Spanned text, int start, if (style[j] instanceof EnrichedItalicSpan) { out.append(""); } + if (style[j] instanceof EnrichedCustomStyleSpan) { + out.append(""); + } } } } @@ -417,6 +445,12 @@ class HtmlToSpannedConverter implements ContentHandler { private static final Pattern CSS_ALIGNMENT_PATTERN = Pattern.compile("text-align\\s*:\\s*(left|center|right)", Pattern.CASE_INSENSITIVE); + private static final Pattern CSS_FG_PATTERN = + Pattern.compile("(?:^|;)\\s*(? void endMention(Editable text, T style, EnrichedSpanFactory void endSpan(Editable text, T style, EnrichedSpanFactory spanFactory) { + CustomStyleMark mark = getLast(text, CustomStyleMark.class); + if (mark == null) return; + setSpanFromMark(text, mark, spanFactory.createCustomStyleSpan(mark.mFg, mark.mBg)); + } + public void setDocumentLocator(Locator locator) {} public void startDocument() {} @@ -1024,6 +1089,16 @@ public Newline(int numNewlines) { } } + private static class CustomStyleMark { + public final Integer mFg; + public final Integer mBg; + + public CustomStyleMark(Integer fg, Integer bg) { + mFg = fg; + mBg = bg; + } + } + private static class Alignment { final String mCssValue; diff --git a/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedSpanFactory.kt b/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedSpanFactory.kt index f61ef8d6..9272c6cc 100644 --- a/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedSpanFactory.kt +++ b/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedSpanFactory.kt @@ -5,6 +5,7 @@ import com.swmansion.enriched.common.spans.EnrichedBlockQuoteSpan import com.swmansion.enriched.common.spans.EnrichedBoldSpan import com.swmansion.enriched.common.spans.EnrichedCheckboxListSpan import com.swmansion.enriched.common.spans.EnrichedCodeBlockSpan +import com.swmansion.enriched.common.spans.EnrichedCustomStyleSpan import com.swmansion.enriched.common.spans.EnrichedH1Span import com.swmansion.enriched.common.spans.EnrichedH2Span import com.swmansion.enriched.common.spans.EnrichedH3Span @@ -79,4 +80,9 @@ interface EnrichedSpanFactory { fun createBlockQuoteSpan(style: T): EnrichedBlockQuoteSpan fun createCodeBlockSpan(style: T): EnrichedCodeBlockSpan + + fun createCustomStyleSpan( + foregroundColor: Int?, + backgroundColor: Int?, + ): EnrichedCustomStyleSpan } diff --git a/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedCustomStyleSpan.kt b/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedCustomStyleSpan.kt new file mode 100644 index 00000000..4e8e63d0 --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedCustomStyleSpan.kt @@ -0,0 +1,20 @@ +package com.swmansion.enriched.common.spans + +import android.text.TextPaint +import android.text.style.CharacterStyle +import com.swmansion.enriched.common.spans.interfaces.EnrichedInlineSpan + +open class EnrichedCustomStyleSpan( + private val foregroundColor: Int?, + private val backgroundColor: Int?, +) : CharacterStyle(), + EnrichedInlineSpan { + fun getForegroundColor(): Int? = foregroundColor + + fun getBackgroundColor(): Int? = backgroundColor + + override fun updateDrawState(textPaint: TextPaint) { + foregroundColor?.let { textPaint.color = it } + backgroundColor?.let { textPaint.bgColor = it } + } +} diff --git a/android/src/main/java/com/swmansion/enriched/text/EnrichedTextSpanFactory.kt b/android/src/main/java/com/swmansion/enriched/text/EnrichedTextSpanFactory.kt index c2e1db3d..e44d20d6 100644 --- a/android/src/main/java/com/swmansion/enriched/text/EnrichedTextSpanFactory.kt +++ b/android/src/main/java/com/swmansion/enriched/text/EnrichedTextSpanFactory.kt @@ -1,11 +1,13 @@ package com.swmansion.enriched.text import com.swmansion.enriched.common.parser.EnrichedSpanFactory +import com.swmansion.enriched.common.spans.EnrichedCustomStyleSpan import com.swmansion.enriched.text.spans.EnrichedTextAlignmentSpan import com.swmansion.enriched.text.spans.EnrichedTextBlockQuoteSpan import com.swmansion.enriched.text.spans.EnrichedTextBoldSpan import com.swmansion.enriched.text.spans.EnrichedTextCheckboxListSpan import com.swmansion.enriched.text.spans.EnrichedTextCodeBlockSpan +import com.swmansion.enriched.text.spans.EnrichedTextCustomStyleSpan import com.swmansion.enriched.text.spans.EnrichedTextH1Span import com.swmansion.enriched.text.spans.EnrichedTextH2Span import com.swmansion.enriched.text.spans.EnrichedTextH3Span @@ -80,4 +82,9 @@ class EnrichedTextSpanFactory : EnrichedSpanFactory { override fun createBlockQuoteSpan(style: EnrichedTextStyle) = EnrichedTextBlockQuoteSpan(style) override fun createCodeBlockSpan(style: EnrichedTextStyle) = EnrichedTextCodeBlockSpan(style) + + override fun createCustomStyleSpan( + foregroundColor: Int?, + backgroundColor: Int?, + ): EnrichedCustomStyleSpan = EnrichedTextCustomStyleSpan(foregroundColor, backgroundColor) } diff --git a/android/src/main/java/com/swmansion/enriched/text/spans/EnrichedTextCustomStyleSpan.kt b/android/src/main/java/com/swmansion/enriched/text/spans/EnrichedTextCustomStyleSpan.kt new file mode 100644 index 00000000..f948be2d --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/text/spans/EnrichedTextCustomStyleSpan.kt @@ -0,0 +1,16 @@ +package com.swmansion.enriched.text.spans + +import com.swmansion.enriched.common.spans.EnrichedCustomStyleSpan +import com.swmansion.enriched.text.EnrichedTextStyle +import com.swmansion.enriched.text.spans.interfaces.EnrichedTextSpan + +class EnrichedTextCustomStyleSpan( + foregroundColor: Int?, + backgroundColor: Int?, +) : EnrichedCustomStyleSpan(foregroundColor, backgroundColor), + EnrichedTextSpan { + override val dependsOnHtmlStyle: Boolean = false + + override fun rebuildWithStyle(style: EnrichedTextStyle): EnrichedTextCustomStyleSpan = + EnrichedTextCustomStyleSpan(getForegroundColor(), getBackgroundColor()) +} diff --git a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputSpannableFactory.kt b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputSpannableFactory.kt index d18e16c0..fb0cfa55 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputSpannableFactory.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputSpannableFactory.kt @@ -1,12 +1,14 @@ package com.swmansion.enriched.textinput import com.swmansion.enriched.common.parser.EnrichedSpanFactory +import com.swmansion.enriched.common.spans.EnrichedCustomStyleSpan import com.swmansion.enriched.common.spans.EnrichedImageSpan import com.swmansion.enriched.textinput.spans.EnrichedInputAlignmentSpan import com.swmansion.enriched.textinput.spans.EnrichedInputBlockQuoteSpan import com.swmansion.enriched.textinput.spans.EnrichedInputBoldSpan import com.swmansion.enriched.textinput.spans.EnrichedInputCheckboxListSpan import com.swmansion.enriched.textinput.spans.EnrichedInputCodeBlockSpan +import com.swmansion.enriched.textinput.spans.EnrichedInputCustomStyleSpan import com.swmansion.enriched.textinput.spans.EnrichedInputH1Span import com.swmansion.enriched.textinput.spans.EnrichedInputH2Span import com.swmansion.enriched.textinput.spans.EnrichedInputH3Span @@ -82,4 +84,9 @@ class EnrichedTextInputSpannableFactory : EnrichedSpanFactory { override fun createBlockQuoteSpan(style: HtmlStyle) = EnrichedInputBlockQuoteSpan(style) override fun createCodeBlockSpan(style: HtmlStyle) = EnrichedInputCodeBlockSpan(style) + + override fun createCustomStyleSpan( + foregroundColor: Int?, + backgroundColor: Int?, + ) = EnrichedInputCustomStyleSpan(foregroundColor, backgroundColor) } 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 cdcad4e2..3a2503a1 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt @@ -58,6 +58,7 @@ import com.swmansion.enriched.textinput.spans.EnrichedLineHeightSpan import com.swmansion.enriched.textinput.spans.EnrichedSpans import com.swmansion.enriched.textinput.spans.interfaces.EnrichedInputSpan import com.swmansion.enriched.textinput.styles.AlignmentStyles +import com.swmansion.enriched.textinput.styles.CustomStyles import com.swmansion.enriched.textinput.styles.HtmlStyle import com.swmansion.enriched.textinput.styles.InlineStyles import com.swmansion.enriched.textinput.styles.ListStyles @@ -89,6 +90,7 @@ class EnrichedTextInputView : val shortcutsHandler: ShortcutsHandler? = ShortcutsHandler(this) val parametrizedStyles: ParametrizedStyles? = ParametrizedStyles(this) val alignmentStyles: AlignmentStyles? = AlignmentStyles(this) + val customStyles: CustomStyles? = CustomStyles(this) var isDuringTransaction: Boolean = false var isRemovingMany: Boolean = false var scrollEnabled: Boolean = true @@ -1002,6 +1004,12 @@ class EnrichedTextInputView : selection?.validateStyles() } + fun setStyle(styleJSON: String) { + runAsATransaction { + customStyles?.setStyle(styleJSON) + } + } + fun requestHTML(requestId: Int) { val html = try { diff --git a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputViewManager.kt b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputViewManager.kt index 3cb4a5e8..ad5fcd34 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputViewManager.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputViewManager.kt @@ -483,7 +483,7 @@ class EnrichedTextInputViewManager : view: EnrichedTextInputView?, styleJSON: String, ) { - // TODO: Implement + view?.setStyle(styleJSON) } override fun measure( diff --git a/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputCustomStyleSpan.kt b/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputCustomStyleSpan.kt new file mode 100644 index 00000000..5257a40b --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputCustomStyleSpan.kt @@ -0,0 +1,16 @@ +package com.swmansion.enriched.textinput.spans + +import com.swmansion.enriched.common.spans.EnrichedCustomStyleSpan +import com.swmansion.enriched.textinput.spans.interfaces.EnrichedInputSpan +import com.swmansion.enriched.textinput.styles.HtmlStyle + +class EnrichedInputCustomStyleSpan( + foregroundColor: Int?, + backgroundColor: Int?, +) : EnrichedCustomStyleSpan(foregroundColor, backgroundColor), + EnrichedInputSpan { + override val dependsOnHtmlStyle: Boolean = false + + override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedInputCustomStyleSpan = + EnrichedInputCustomStyleSpan(getForegroundColor(), getBackgroundColor()) +} diff --git a/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSelection.kt b/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSelection.kt index 002e1954..3cd42f47 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSelection.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSelection.kt @@ -5,10 +5,12 @@ import android.text.Spannable import com.facebook.react.bridge.ReactContext import com.facebook.react.uimanager.UIManagerHelper import com.swmansion.enriched.common.EnrichedConstants +import com.swmansion.enriched.common.spans.EnrichedCustomStyleSpan import com.swmansion.enriched.textinput.EnrichedTextInputView import com.swmansion.enriched.textinput.events.OnChangeSelectionEvent import com.swmansion.enriched.textinput.events.OnLinkDetectedEvent import com.swmansion.enriched.textinput.events.OnMentionDetectedEvent +import com.swmansion.enriched.textinput.spans.EnrichedInputCustomStyleSpan import com.swmansion.enriched.textinput.spans.EnrichedInputLinkSpan import com.swmansion.enriched.textinput.spans.EnrichedInputMentionSpan import com.swmansion.enriched.textinput.spans.EnrichedSpans @@ -89,6 +91,7 @@ class EnrichedSelection( for ((style, config) in EnrichedSpans.inlineSpans) { state.setStart(style, getInlineStyleStart(config.clazz)) } + validateCustomStyles() } else { view.isRemovingMany = false } @@ -137,6 +140,31 @@ class EnrichedSelection( return styleStart } + private fun validateCustomStyles() { + val state = view.spanState ?: return + val (start, end) = getInlineSelection() + val spannable = view.text as Spannable + val spans = spannable.getSpans(start, end, EnrichedInputCustomStyleSpan::class.java) + + var foundFg: Int? = null + var foundBg: Int? = null + + for (span in spans) { + val spanStart = spannable.getSpanStart(span) + val spanEnd = spannable.getSpanEnd(span) + + if (start == end && start == spanStart) { + continue + } else if (start >= spanStart && end <= spanEnd) { + foundFg = span.getForegroundColor() + foundBg = span.getBackgroundColor() + break + } + } + + state.setCustomStyle(foundFg, foundBg) + } + fun getParagraphSelection(): Pair { val (currentStart, currentEnd) = getInlineSelection() val spannable = view.text as Spannable diff --git a/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSpanState.kt b/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSpanState.kt index ec4e2728..c58832b9 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSpanState.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSpanState.kt @@ -5,6 +5,8 @@ import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.events.EventDispatcher +import com.swmansion.enriched.common.CustomStyle +import com.swmansion.enriched.common.parser.EnrichedColorParser import com.swmansion.enriched.textinput.EnrichedTextInputView import com.swmansion.enriched.textinput.events.OnChangeStateEvent import com.swmansion.enriched.textinput.spans.EnrichedSpans @@ -54,6 +56,8 @@ class EnrichedSpanState( private set var currentAlignment: String = "auto" private set + var customStyle: CustomStyle? = null + private set fun setBoldStart(start: Int?) { this.boldStart = start @@ -155,6 +159,14 @@ class EnrichedSpanState( emitStateChangeEvent() } + fun setCustomStyle( + fgColor: Int?, + bgColor: Int?, + ) { + this.customStyle = CustomStyle(fgColor, bgColor) + emitStateChangeEvent() + } + fun getStart(name: String): Int? { val start = when (name) { @@ -254,6 +266,7 @@ class EnrichedSpanState( payload.putMap("mention", getStyleState(activeStyles, EnrichedSpans.MENTION)) payload.putMap("checkboxList", getStyleState(activeStyles, EnrichedSpans.CHECKBOX_LIST)) payload.putString("alignment", currentAlignment) + payload.putMap("customStyle", getCustomStylePayload()) return payload } @@ -312,6 +325,25 @@ class EnrichedSpanState( return state } + private fun getCustomStylePayload(): WritableMap { + val customStyleMap = Arguments.createMap() + val customStyle = view.spanState?.customStyle + + if (customStyle?.foregroundColor != null) { + customStyleMap.putString("foregroundColor", EnrichedColorParser.colorToHex(customStyle.foregroundColor)) + } else { + customStyleMap.putString("foregroundColor", "") + } + + if (customStyle?.backgroundColor != null) { + customStyleMap.putString("backgroundColor", EnrichedColorParser.colorToHex(customStyle.backgroundColor)) + } else { + customStyleMap.putString("backgroundColor", "") + } + + return customStyleMap + } + companion object { const val NAME = "ReactNativeEnrichedView" } diff --git a/android/src/main/java/com/swmansion/enriched/textinput/watchers/EnrichedTextWatcher.kt b/android/src/main/java/com/swmansion/enriched/textinput/watchers/EnrichedTextWatcher.kt index bcc41294..c283321b 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/watchers/EnrichedTextWatcher.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/watchers/EnrichedTextWatcher.kt @@ -75,6 +75,7 @@ class EnrichedTextWatcher( private fun applyStyles(s: Editable) { view.inlineStyles?.afterTextChanged(s, startCursorPosition, endCursorPosition) + view.customStyles?.afterTextChanged(s, startCursorPosition, endCursorPosition) view.paragraphStyles?.afterTextChanged(s, endCursorPosition, previousTextLength) view.listStyles?.afterTextChanged(s, endCursorPosition, previousTextLength) view.alignmentStyles?.afterTextChanged(s, endCursorPosition, deletedText, anchorAlignmentToRestore) From 407adb707b97d32def3381e1522a572806a83351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Fri, 19 Jun 2026 13:53:56 +0200 Subject: [PATCH 2/4] feat: handle customStyle span creation --- .../android/custom_style_colors.png | Bin 0 -> 32993 bytes .../android/custom_style_colors_visual.png | Bin 0 -> 49173 bytes .../textinput/EnrichedTextInputView.kt | 3 + .../enriched/textinput/spans/EnrichedSpans.kt | 14 +- .../enriched/textinput/styles/CustomStyles.kt | 212 ++++++++++++++++++ 5 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 .maestro/enrichedInput/screenshots/android/custom_style_colors.png create mode 100644 .maestro/enrichedText/screenshots/android/custom_style_colors_visual.png create mode 100644 android/src/main/java/com/swmansion/enriched/textinput/styles/CustomStyles.kt diff --git a/.maestro/enrichedInput/screenshots/android/custom_style_colors.png b/.maestro/enrichedInput/screenshots/android/custom_style_colors.png new file mode 100644 index 0000000000000000000000000000000000000000..cf9f1fe18e5dbfa4f65adb6b3783d392a18c06ba GIT binary patch literal 32993 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 literal 0 HcmV?d00001 diff --git a/.maestro/enrichedText/screenshots/android/custom_style_colors_visual.png b/.maestro/enrichedText/screenshots/android/custom_style_colors_visual.png new file mode 100644 index 0000000000000000000000000000000000000000..e9e95d2421e4b13d3a2da154e6819bd1ac12d0b4 GIT binary patch literal 49173 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 WcxO;qa6IVjp)!&RFTOuF^!_i^0GHJO literal 0 HcmV?d00001 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 3a2503a1..7584393b 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt @@ -1005,6 +1005,9 @@ class EnrichedTextInputView : } fun setStyle(styleJSON: String) { + val isValid = verifyStyle(EnrichedSpans.CUSTOM_STYLE) + if (!isValid) return + runAsATransaction { customStyles?.setStyle(styleJSON) } diff --git a/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedSpans.kt b/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedSpans.kt index 0e40e9e8..d164b7db 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedSpans.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedSpans.kt @@ -50,6 +50,9 @@ object EnrichedSpans { const val IMAGE = "image" const val MENTION = "mention" + // custom style + const val CUSTOM_STYLE = "custom_style" + val inlineSpans: Map = mapOf( BOLD to BaseSpanConfig(EnrichedInputBoldSpan::class.java), @@ -85,7 +88,12 @@ object EnrichedSpans { MENTION to BaseSpanConfig(EnrichedInputMentionSpan::class.java), ) - val allSpans: Map = inlineSpans + paragraphSpans + listSpans + parametrizedStyles + val customStyles: Map = + mapOf( + CUSTOM_STYLE to BaseSpanConfig(EnrichedInputCustomStyleSpan::class.java), + ) + + val allSpans: Map = inlineSpans + paragraphSpans + listSpans + parametrizedStyles + customStyles fun getMergingConfigForStyle( style: String, @@ -231,6 +239,10 @@ object EnrichedSpans { ) } + CUSTOM_STYLE -> { + StylesMergingConfig() + } + else -> { null } diff --git a/android/src/main/java/com/swmansion/enriched/textinput/styles/CustomStyles.kt b/android/src/main/java/com/swmansion/enriched/textinput/styles/CustomStyles.kt new file mode 100644 index 00000000..b7bb7f4c --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/textinput/styles/CustomStyles.kt @@ -0,0 +1,212 @@ +package com.swmansion.enriched.textinput.styles + +import android.text.Editable +import android.text.Spannable +import com.swmansion.enriched.common.spans.EnrichedCustomStyleSpan +import com.swmansion.enriched.textinput.EnrichedTextInputView +import com.swmansion.enriched.textinput.spans.EnrichedInputCustomStyleSpan +import org.json.JSONObject + +class CustomStyles( + private val view: EnrichedTextInputView, +) { + fun setStyle(styleJSON: String) { + val selection = view.selection ?: return + val (start, end) = selection.getInlineSelection() + + val json = runCatching { JSONObject(styleJSON) }.getOrNull() ?: return + + val hasFg = json.has("foregroundColor") + val hasBg = json.has("backgroundColor") + + val fgColor = if (hasFg && !json.isNull("foregroundColor")) json.getInt("foregroundColor") else null + val bgColor = if (hasBg && !json.isNull("backgroundColor")) json.getInt("backgroundColor") else null + + if (start == end) { + val currentStyle = view.spanState?.customStyle + val finalFg = if (hasFg) fgColor else currentStyle?.foregroundColor + val finalBg = if (hasBg) bgColor else currentStyle?.backgroundColor + + view.spanState?.setCustomStyle(finalFg, finalBg) + } else { + val spannable = view.text as Spannable + applyCustomStyleSpan(spannable, start, end, hasFg, fgColor, hasBg, bgColor) + view.selection.validateStyles() + } + } + + private fun applyCustomStyleSpan( + spannable: Spannable, + start: Int, + end: Int, + hasFg: Boolean, + fgColor: Int?, + hasBg: Boolean, + bgColor: Int?, + ) { + val existingSpans = spannable.getSpans(start, end, EnrichedInputCustomStyleSpan::class.java) + val boundaries = mutableSetOf(start, end) + + // Snapshot boundaries and spans before any modifications + val oldSpans = + existingSpans.mapNotNull { span -> + val spanStart = spannable.getSpanStart(span) + val spanEnd = spannable.getSpanEnd(span) + if (spanStart == -1 || spanEnd == -1) null else Triple(span, spanStart, spanEnd) + } + + // Remove old spans, restore outer edges, and collect internal boundaries + for ((span, spanStart, spanEnd) in oldSpans) { + val spanFg = span.getForegroundColor() + val spanBg = span.getBackgroundColor() + + spannable.removeSpan(span) + + if (spanStart < start) setCustomSpan(spannable, spanStart, start, spanFg, spanBg) + if (spanEnd > end) setCustomSpan(spannable, end, spanEnd, spanFg, spanBg) + + if (spanStart in start..end) boundaries.add(spanStart) + if (spanEnd in start..end) boundaries.add(spanEnd) + } + + // Build the new merged spans chunk-by-chunk + val sortedBoundaries = boundaries.sorted() + + for (i in 0 until sortedBoundaries.size - 1) { + val chunkStart = sortedBoundaries[i] + val chunkEnd = sortedBoundaries[i + 1] + + // Find the old span that fully covers this specific chunk + val oldSpan = oldSpans.firstOrNull { it.second <= chunkStart && it.third >= chunkEnd }?.first + + val finalFg = if (hasFg) fgColor else oldSpan?.getForegroundColor() + val finalBg = if (hasBg) bgColor else oldSpan?.getBackgroundColor() + + setCustomSpan(spannable, chunkStart, chunkEnd, finalFg, finalBg) + } + } + + fun afterTextChanged( + s: Editable, + startCursorPosition: Int, + endCursorPosition: Int, + ) { + val isInsertion = endCursorPosition > startCursorPosition + val activeStyle = view.spanState?.customStyle + + if (isInsertion) { + val activeFg = activeStyle?.foregroundColor + val activeBg = activeStyle?.backgroundColor + + // Split existing spans if they don't match the current active colors + splitCustomSpanOnInsertion(s, startCursorPosition, endCursorPosition, activeFg, activeBg) + + setCustomSpan(s, startCursorPosition, endCursorPosition, activeFg, activeBg) + } + + // Merge any adjacent spans that have the exact same colors + collapseAdjacentCustomSpans(s, startCursorPosition, endCursorPosition) + } + + private fun splitCustomSpanOnInsertion( + spannable: Spannable, + insertStart: Int, + insertEnd: Int, + activeFg: Int?, + activeBg: Int?, + ) { + val spans = spannable.getSpans(insertStart, insertEnd, EnrichedInputCustomStyleSpan::class.java) + + for (span in spans) { + val spanStart = spannable.getSpanStart(span) + val spanEnd = spannable.getSpanEnd(span) + if (spanStart < 0 || spanEnd < 0) continue + + val spanFg = span.getForegroundColor() + val spanBg = span.getBackgroundColor() + + // If the existing span perfectly matches the active state, leave it + if (spanFg == activeFg && spanBg == activeBg) continue + + // Colors differ. We must split the old span so it doesn't cover the new text + spannable.removeSpan(span) + + if (spanStart < insertStart) { + setCustomSpan(spannable, spanStart, insertStart, spanFg, spanBg) + } + if (spanEnd > insertEnd) { + setCustomSpan(spannable, insertEnd, spanEnd, spanFg, spanBg) + } + } + } + + private fun collapseAdjacentCustomSpans( + spannable: Spannable, + start: Int, + end: Int, + ) { + // Look slightly outside the typed area to catch adjacent spans + val searchStart = (start - 1).coerceAtLeast(0) + val searchEnd = (end + 1).coerceAtMost(spannable.length) + + val spans = spannable.getSpans(searchStart, searchEnd, EnrichedInputCustomStyleSpan::class.java) + if (spans.isEmpty()) return + + // Sort spans and extract their boundaries simultaneously + val sortedSpans = + spans + .mapNotNull { span -> + val spanStart = spannable.getSpanStart(span) + val spanEnd = spannable.getSpanEnd(span) + if (spanStart == -1 || spanEnd == -1) null else Triple(span, spanStart, spanEnd) + }.sortedBy { it.second } + + // Wipe all spans in this region immediately (we safely hold their data in sortedSpans) + sortedSpans.forEach { spannable.removeSpan(it.first) } + + var (_, currentStart, currentEnd) = sortedSpans[0] + var currentFg = sortedSpans[0].first.getForegroundColor() + var currentBg = sortedSpans[0].first.getBackgroundColor() + + // Iterate and merge + for (i in 1 until sortedSpans.size) { + val (span, spanStart, spanEnd) = sortedSpans[i] + val spanFg = span.getForegroundColor() + val spanBg = span.getBackgroundColor() + + // If spans are touching/overlapping AND their colors match perfectly extend the span + if (spanStart <= currentEnd && spanFg == currentFg && spanBg == currentBg) { + currentEnd = maxOf(currentEnd, spanEnd) + } else { + // Colors changed or there is a gap. Commit the current merged block. + setCustomSpan(spannable, currentStart, currentEnd, currentFg, currentBg) + + // Start a new tracking block + currentStart = spanStart + currentEnd = spanEnd + currentFg = spanFg + currentBg = spanBg + } + } + + // Commit the final block + setCustomSpan(spannable, currentStart, currentEnd, currentFg, currentBg) + } + + private fun setCustomSpan( + spannable: Spannable, + start: Int, + end: Int, + fg: Int?, + bg: Int?, + ) { + if (start >= end || (fg == null && bg == null)) return + + spannable.setSpan( + EnrichedInputCustomStyleSpan(fg, bg), + start, + end, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, + ) + } +} From 40fafb384c3b1d8ef3a61f69324319917e4f872d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Fri, 19 Jun 2026 14:38:16 +0200 Subject: [PATCH 3/4] feat: add priority to custom style spans --- .../java/com/swmansion/enriched/common/EnrichedSpanFlags.kt | 5 ++++- .../swmansion/enriched/common/parser/EnrichedParser.java | 5 +++-- .../com/swmansion/enriched/textinput/styles/CustomStyles.kt | 6 ++++-- 3 files changed, 11 insertions(+), 5 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 d9a0ddb5..0466bbb0 100644 --- a/android/src/main/java/com/swmansion/enriched/common/EnrichedSpanFlags.kt +++ b/android/src/main/java/com/swmansion/enriched/common/EnrichedSpanFlags.kt @@ -2,6 +2,7 @@ package com.swmansion.enriched.common import android.text.Spannable import com.swmansion.enriched.common.spans.EnrichedAlignmentSpan +import com.swmansion.enriched.common.spans.EnrichedCustomStyleSpan 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. @@ -10,7 +11,8 @@ import com.swmansion.enriched.common.spans.interfaces.EnrichedInlineSpan object EnrichedSpanFlags { private const val ALIGNMENT_SPAN_PRIORITY = 0 private const val INLINE_SPAN_PRIORITY = 1 - private const val PARAGRAPH_SPAN_PRIORITY = 2 + private const val CUSTOM_STYLE_SPAN_PRIORITY = 2 + private const val PARAGRAPH_SPAN_PRIORITY = 3 @JvmStatic @JvmOverloads @@ -21,6 +23,7 @@ object EnrichedSpanFlags { val priority = when (span) { is EnrichedAlignmentSpan -> ALIGNMENT_SPAN_PRIORITY + is EnrichedCustomStyleSpan -> CUSTOM_STYLE_SPAN_PRIORITY is EnrichedInlineSpan -> INLINE_SPAN_PRIORITY else -> PARAGRAPH_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 30ce990f..553c89c6 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 @@ -531,7 +531,7 @@ public Spanned convert() { if (mSpannableStringBuilder.getSpanStart(span) != start + 1) continue; int spanEnd = mSpannableStringBuilder.getSpanEnd(span); mSpannableStringBuilder.removeSpan(span); - mSpannableStringBuilder.setSpan(span, start, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + mSpannableStringBuilder.setSpan(span, start, spanEnd, EnrichedSpanFlags.forSpan(span)); } } @@ -868,7 +868,8 @@ private static void setParagraphSpanFromMark(Editable text, Object mark, Object. private static void start(Editable text, Object mark) { int len = text.length(); - text.setSpan(mark, len, len, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + text.setSpan( + mark, len, len, EnrichedSpanFlags.forSpan(mark, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)); } private static void end(Editable text, Class kind, Object repl) { diff --git a/android/src/main/java/com/swmansion/enriched/textinput/styles/CustomStyles.kt b/android/src/main/java/com/swmansion/enriched/textinput/styles/CustomStyles.kt index b7bb7f4c..07c10480 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/styles/CustomStyles.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/styles/CustomStyles.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.common.spans.EnrichedCustomStyleSpan import com.swmansion.enriched.textinput.EnrichedTextInputView import com.swmansion.enriched.textinput.spans.EnrichedInputCustomStyleSpan @@ -202,11 +203,12 @@ class CustomStyles( ) { if (start >= end || (fg == null && bg == null)) return + val span = EnrichedInputCustomStyleSpan(fg, bg) spannable.setSpan( - EnrichedInputCustomStyleSpan(fg, bg), + span, start, end, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, + EnrichedSpanFlags.forSpan(span), ) } } From 807dbe8c296e8ea179ef896b249f2458e036a071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Mon, 22 Jun 2026 09:54:13 +0200 Subject: [PATCH 4/4] fix: remove unused imports --- .../enriched/textinput/EnrichedTextInputSpannableFactory.kt | 1 - .../java/com/swmansion/enriched/textinput/styles/CustomStyles.kt | 1 - .../com/swmansion/enriched/textinput/utils/EnrichedSelection.kt | 1 - 3 files changed, 3 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputSpannableFactory.kt b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputSpannableFactory.kt index fb0cfa55..c5fae89b 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputSpannableFactory.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputSpannableFactory.kt @@ -1,7 +1,6 @@ package com.swmansion.enriched.textinput import com.swmansion.enriched.common.parser.EnrichedSpanFactory -import com.swmansion.enriched.common.spans.EnrichedCustomStyleSpan import com.swmansion.enriched.common.spans.EnrichedImageSpan import com.swmansion.enriched.textinput.spans.EnrichedInputAlignmentSpan import com.swmansion.enriched.textinput.spans.EnrichedInputBlockQuoteSpan diff --git a/android/src/main/java/com/swmansion/enriched/textinput/styles/CustomStyles.kt b/android/src/main/java/com/swmansion/enriched/textinput/styles/CustomStyles.kt index 07c10480..3c94f158 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/styles/CustomStyles.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/styles/CustomStyles.kt @@ -3,7 +3,6 @@ package com.swmansion.enriched.textinput.styles import android.text.Editable import android.text.Spannable import com.swmansion.enriched.common.EnrichedSpanFlags -import com.swmansion.enriched.common.spans.EnrichedCustomStyleSpan import com.swmansion.enriched.textinput.EnrichedTextInputView import com.swmansion.enriched.textinput.spans.EnrichedInputCustomStyleSpan import org.json.JSONObject diff --git a/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSelection.kt b/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSelection.kt index 3cd42f47..d2ab6d7c 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSelection.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSelection.kt @@ -5,7 +5,6 @@ import android.text.Spannable import com.facebook.react.bridge.ReactContext import com.facebook.react.uimanager.UIManagerHelper import com.swmansion.enriched.common.EnrichedConstants -import com.swmansion.enriched.common.spans.EnrichedCustomStyleSpan import com.swmansion.enriched.textinput.EnrichedTextInputView import com.swmansion.enriched.textinput.events.OnChangeSelectionEvent import com.swmansion.enriched.textinput.events.OnLinkDetectedEvent