From 906d08eacf209d4725f5697fd72b8dcc12333575 Mon Sep 17 00:00:00 2001 From: Jianlong-Nie Date: Wed, 18 Mar 2026 13:03:32 +0800 Subject: [PATCH] fix(android): block mention events when cursor is inside a finalized mention span MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror iOS conflicting-styles behaviour in afterTextChangedMentions: - After determining the candidate range [finalStart, finalEnd], scan for EnrichedInputMentionSpan across the full range (not just currentWord). - If the span text matches the buffer, the mention is intact — call endMention() to suppress the event (fixes spurious mention list pop-up when a pre-filled @mention is loaded, e.g. in reply flows). - If the span text has diverged (user edited inside it), remove the stale span and record mentionStart = spanStart so setMentionSpan can later replace the correct range when the user picks a new mention. --- .../textinput/styles/ParametrizedStyles.kt | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) 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 ccbf0146..b35bb4ff 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 @@ -225,11 +225,6 @@ class ParametrizedStyles( val mentionIndicatorRegex = Regex("^($indicatorsPattern)") val mentionRegex = Regex("^($indicatorsPattern)\\w*") - val spans = spannable.getSpans(currentWord.start, currentWord.end, EnrichedInputMentionSpan::class.java) - for (span in spans) { - spannable.removeSpan(span) - } - var indicator: String var finalStart: Int val finalEnd = currentWord.end @@ -259,6 +254,25 @@ class ParametrizedStyles( indicator = mentionIndicatorRegex.find(currentWord.text)?.value ?: "" } + // Mirror iOS conflicting-styles behaviour: check the full candidate range for + // a finalized mention span. If the span's stored text still matches what is in + // the buffer the mention is intact — block the event (covers HTML-loaded + // mentions and typing adjacent to a freshly-selected mention). + // If the span is stale (user edited inside it), remove it and record mentionStart + // so setMentionSpan can replace text correctly when the user picks a new mention. + val rangeSpans = spannable.getSpans(finalStart, finalEnd, EnrichedInputMentionSpan::class.java) + for (span in rangeSpans) { + val spanStart = spannable.getSpanStart(span) + val spanEnd = spannable.getSpanEnd(span) + val currentSpanText = spannable.subSequence(spanStart, spanEnd).toString() + if (currentSpanText == span.getText()) { + mentionHandler.endMention() + return + } + spannable.removeSpan(span) + mentionStart = spanStart + } + // Extract text without indicator val text = spannable.subSequence(finalStart, finalEnd).toString().replaceFirst(indicator, "")