I have been working on a Jetpack compose app and need to implement a chat screen. I need to add mentions functionality in textfield. Just like Twitter (X) uses in its add tweet textfield where you type @ and suggestions appear which could be added to textfield. I want them to delete with a single backspace even if the cursor is not in the end of text.
DropdownMenuItem(
onClick = {
val oldText = noteMessage.text
val mentionTextLength = mentionText?.length ?: 0
val newText = oldText.replaceRange(
startIndex = oldText.length - mentionTextLength,
endIndex = oldText.length,
replacement = "${it.getFirstName()} "
)
noteMessage = noteMessage.copy(
text = newText,
selection = TextRange(newText.length)
)
val start = oldText.length - mentionTextLength
val end = oldText.length + it.getFirstName().length
mentionedMembers.add(Pair(memberData, Pair(start, end))
},
modifier = Modifier.background(JcColors.SurfacePrimary)
) {
Text(text = it.name)
}
CustomTextField(
enabled = !viewModel.isSendingMessage.value,
value = noteMessage,
onValueChange = {
val lastCharacter = it.text.lastOrNull()
when {
(lastCharacter == '@') -> isMentioningTeammate = true
(lastCharacter == ' ') -> isMentioningTeammate = false
}
mentionText = if (isMentioningTeammate) {
it.text.split(Regex("\\s+"))
.lastOrNull()?.removePrefix("@")
} else {
null
}
val cursorPosition = it.selection.start
val oldTextLength = noteMessage.text.length
val newTextLength = it.text.length
val removeIndexList = mutableListOf<Int>()
var finalText = it
if (oldTextLength != newTextLength) {
mentionedMembers.forEachIndexed { index, (memberData, startEndPosition) ->
if (oldTextLength < newTextLength) {
//something is added
if (cursorPosition < startEndPosition.first) {
val newMentionMemberData = Pair(
memberData,
Pair(
startEndPosition.first + 1,
startEndPosition.second + 1
)
)
mentionedMembers[index] = newMentionMemberData
} else if (cursorPosition > startEndPosition.first && cursorPosition < startEndPosition.second) {
removeIndexList.add(index)
}
} else {
//something is deleted
if (cursorPosition < startEndPosition.first) {
val newMentionMemberData = Pair(
memberData,
Pair(
startEndPosition.first - 1,
startEndPosition.second - 1
)
)
mentionedMembers[index] = newMentionMemberData
} else if (cursorPosition > startEndPosition.first && cursorPosition < startEndPosition.second) {
removeIndexList.add(index)
}
}
}
//to avoid concurrent modification error
removeIndexList.forEach { removeAtIndex ->
mentionedMembers.removeAt(
removeAtIndex
)
}
}
noteMessage = finalText
// clear mentioned list if note message is empty
if (it.text.isEmpty()) {
mentionedMembers.clear()
isMentioningTeammate = false
}
},
textStyle = MaterialTheme.typography.body2,
visualTransformation = { annotatedString ->
TransformedText(
text = buildAnnotatedString {
append(annotatedString)
mentionedMembers.forEach {
addStyle(
style = SpanStyle(color = JcColors.TextNoteMentionHighlight),
start = it.second.first,
end = it.second.second
)
}
},
offsetMapping = OffsetMapping.Identity
)
}
)
This index based approach doesn't feel right and also it has many edge cases. If there is any alternate approach please let me know. Would really appreciate some help on this.