How to create mentions just like Twitter and Facebook in Jetpack Compose Android app

89 Views Asked by At

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.

0

There are 0 best solutions below