Skip to content

Commit 9b3830b

Browse files
committed
feat: mentions with space - android
1 parent fa2109e commit 9b3830b

1 file changed

Lines changed: 57 additions & 21 deletions

File tree

android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,13 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) {
5858
}
5959

6060
fun afterTextChanged(s: Editable, endCursorPosition: Int) {
61-
val result = getWordAtIndex(s, endCursorPosition) ?: return
61+
val currentWord = getWordAtIndex(s, endCursorPosition) ?: return
62+
afterTextChangedLinks(currentWord)
6263

63-
afterTextChangedLinks(result)
64-
afterTextChangedMentions(result)
64+
val mentionText = getMentionTextAtIndex(s, endCursorPosition)
65+
if (mentionText != null) {
66+
afterTextChangedMentions(currentWord, mentionText)
67+
}
6568
}
6669

6770
fun detectAllLinks() {
@@ -85,7 +88,34 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) {
8588
}
8689
}
8790

88-
private fun getWordAtIndex(s: Editable, index: Int): Triple<String, Int, Int>? {
91+
// Returns the mention string (max two words) along with its start and end indices
92+
private fun getMentionTextAtIndex(s: Editable, index: Int): TextRange? {
93+
if (index < 0 ) return null
94+
95+
var start = index
96+
var end = index
97+
var whitespaceReached = false
98+
99+
while (start > 0) {
100+
if (Character.isWhitespace(s[start - 1])) {
101+
// One whitespace is allowed for mentions (to cover two-word mentions)
102+
if (whitespaceReached) break
103+
whitespaceReached = true
104+
}
105+
106+
start--
107+
}
108+
109+
while (end < s.length && !Character.isWhitespace(s[end])) {
110+
end++
111+
}
112+
113+
val result = s.subSequence(start, end).toString()
114+
115+
return TextRange(result, start, end)
116+
}
117+
118+
private fun getWordAtIndex(s: Editable, index: Int): TextRange? {
89119
if (index < 0 ) return null
90120

91121
var start = index
@@ -101,7 +131,7 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) {
101131

102132
val result = s.subSequence(start, end).toString()
103133

104-
return Triple(result, start, end)
134+
return TextRange(result, start, end)
105135
}
106136

107137
private fun canLinkBeApplied(): Boolean {
@@ -120,7 +150,7 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) {
120150
return true
121151
}
122152

123-
private fun afterTextChangedLinks(result: Triple<String, Int, Int>) {
153+
private fun afterTextChangedLinks(result: TextRange) {
124154
// Do not detect link if it's applied manually
125155
if (isSettingLinkSpan || !canLinkBeApplied()) return
126156

@@ -141,33 +171,35 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) {
141171
}
142172
}
143173

144-
private fun afterTextChangedMentions(result: Triple<String, Int, Int>) {
174+
private fun afterTextChangedMentions(currentWord: TextRange, mentionText: TextRange) {
145175
val mentionHandler = view.mentionHandler ?: return
146176
val spannable = view.text as Spannable
147-
val (word, start, end) = result
148177

149178
val indicatorsPattern = mentionIndicators.joinToString("|") { Regex.escape(it) }
150-
val mentionIndicatorRegex = Regex("^($indicatorsPattern)")
151-
val mentionRegex= Regex("^($indicatorsPattern)\\w*")
179+
val mentionIndicatorRegex = Regex("($indicatorsPattern)")
152180

153-
val spans = spannable.getSpans(start, end, EnrichedMentionSpan::class.java)
181+
val spans = spannable.getSpans(currentWord.start, currentWord.end, EnrichedMentionSpan::class.java)
154182
for (span in spans) {
155183
spannable.removeSpan(span)
156184
}
157185

158-
if (mentionRegex.matches(word)) {
159-
val indicator = mentionIndicatorRegex.find(word)?.value ?: ""
160-
val text = word.replaceFirst(indicator, "")
186+
val match = mentionIndicatorRegex.find(mentionText.text)
187+
if (match == null) {
188+
mentionHandler.endMention()
189+
return
190+
}
161191

162-
// Means we are starting mention
163-
if (text.isEmpty()) {
164-
mentionStart = start
165-
}
192+
val indicator = match.value
193+
val indicatorIndexInWord = match.range.first
194+
val textStart = indicatorIndexInWord + indicator.length
195+
val text = if (textStart <= mentionText.text.length) mentionText.text.substring(textStart) else ""
166196

167-
mentionHandler.onMention(indicator, text)
168-
} else {
169-
mentionHandler.endMention()
197+
// Means we are starting mention (indicator present but no text after it)
198+
if (text.isEmpty()) {
199+
mentionStart = mentionText.start + indicatorIndexInWord
170200
}
201+
202+
mentionHandler.onMention(indicator, text)
171203
}
172204

173205
fun setImageSpan(src: String) {
@@ -245,4 +277,8 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) {
245277
val spannable = view.text as Spannable
246278
return removeSpansForRange(spannable, start, end, config.clazz)
247279
}
280+
281+
companion object {
282+
data class TextRange(val text: String, val start: Int, val end: Int)
283+
}
248284
}

0 commit comments

Comments
 (0)