mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 19:38:08 +00:00
Started mention of participant in chat message
This commit is contained in:
parent
5e069033b3
commit
7aed1d83e3
5 changed files with 77 additions and 16 deletions
|
|
@ -20,8 +20,11 @@
|
|||
package org.linphone.ui.main.chat.model
|
||||
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.text.set
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import java.util.regex.Pattern
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
|
|
@ -34,6 +37,7 @@ import org.linphone.ui.main.contacts.model.ContactAvatarModel
|
|||
import org.linphone.utils.Event
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
import org.linphone.utils.PatternClickableSpan
|
||||
import org.linphone.utils.SpannableClickedListener
|
||||
import org.linphone.utils.TimestampUtils
|
||||
|
||||
class ChatMessageModel @WorkerThread constructor(
|
||||
|
|
@ -48,6 +52,9 @@ class ChatMessageModel @WorkerThread constructor(
|
|||
) {
|
||||
companion object {
|
||||
private const val TAG = "[Chat Message Model]"
|
||||
|
||||
private const val SIP_URI_REGEXP = "(?:<?sips?:)[a-zA-Z0-9+_.\\-]+(?:@([a-zA-Z0-9+_.\\-;=~]+))+(>)?"
|
||||
private const val MENTION_REGEXP = "@(?:[A-Za-z0-9._-]+)"
|
||||
}
|
||||
|
||||
val id = chatMessage.messageId
|
||||
|
|
@ -100,14 +107,64 @@ class ChatMessageModel @WorkerThread constructor(
|
|||
for (content in chatMessage.contents) {
|
||||
if (content.isText) {
|
||||
val textContent = content.utf8Text.orEmpty().trim()
|
||||
val spannable = Spannable.Factory.getInstance().newSpannable(textContent)
|
||||
val spannableBuilder = SpannableStringBuilder(textContent)
|
||||
|
||||
// Check for mentions
|
||||
val chatRoom = chatMessage.chatRoom
|
||||
val matcher = Pattern.compile(MENTION_REGEXP).matcher(textContent)
|
||||
while (matcher.find()) {
|
||||
val start = matcher.start()
|
||||
val end = matcher.end()
|
||||
val source = textContent.subSequence(start + 1, end) // +1 to remove @
|
||||
Log.i("$TAG Found mention [$source]")
|
||||
|
||||
// Find address matching username
|
||||
val address = if (chatRoom.localAddress.username == source) {
|
||||
Log.i("$TAG mention found in local address")
|
||||
coreContext.core.accountList.find {
|
||||
it.params.identityAddress?.username == source
|
||||
}?.params?.identityAddress
|
||||
} else if (chatRoom.peerAddress.username == source) {
|
||||
Log.i("$TAG mention found in peer address")
|
||||
chatRoom.peerAddress
|
||||
} else {
|
||||
Log.i("$TAG looking for mention in participants")
|
||||
chatRoom.participants.find {
|
||||
it.address.username == source
|
||||
}?.address
|
||||
}
|
||||
// Find display name for address
|
||||
if (address != null) {
|
||||
val displayName = coreContext.contactsManager.findDisplayName(address)
|
||||
Log.i(
|
||||
"$TAG Using display name [$displayName] instead of username [$source]"
|
||||
)
|
||||
spannableBuilder.replace(start, end, "@$displayName")
|
||||
val span = PatternClickableSpan.StyledClickableSpan(
|
||||
object :
|
||||
SpannableClickedListener {
|
||||
override fun onSpanClicked(text: String) {
|
||||
Log.i("$TAG Clicked on [$text] span")
|
||||
}
|
||||
}
|
||||
)
|
||||
spannableBuilder.setSpan(
|
||||
span,
|
||||
start,
|
||||
start + displayName.length + 1,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Add clickable span for SIP URIs
|
||||
text.postValue(
|
||||
PatternClickableSpan()
|
||||
.add(
|
||||
Pattern.compile(
|
||||
"(?:<?sips?:)[a-zA-Z0-9+_.\\-]+(?:@([a-zA-Z0-9+_.\\-;=~]+))+(>)?"
|
||||
SIP_URI_REGEXP
|
||||
),
|
||||
object : PatternClickableSpan.SpannableClickedListener {
|
||||
object : SpannableClickedListener {
|
||||
@UiThread
|
||||
override fun onSpanClicked(text: String) {
|
||||
coreContext.postOnCoreThread {
|
||||
|
|
@ -121,7 +178,8 @@ class ChatMessageModel @WorkerThread constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
).build(spannable)
|
||||
)
|
||||
.build(spannableBuilder)
|
||||
)
|
||||
textFound = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,11 @@ import android.text.Spanned
|
|||
import android.text.style.ClickableSpan
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.AnyThread
|
||||
import androidx.annotation.UiThread
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@AnyThread
|
||||
class PatternClickableSpan {
|
||||
private var patterns: ArrayList<SpannablePatternItem> = ArrayList()
|
||||
|
||||
|
|
@ -34,18 +37,14 @@ class PatternClickableSpan {
|
|||
var listener: SpannableClickedListener
|
||||
)
|
||||
|
||||
interface SpannableClickedListener {
|
||||
fun onSpanClicked(text: String)
|
||||
}
|
||||
|
||||
inner class StyledClickableSpan(var item: SpannablePatternItem) : ClickableSpan() {
|
||||
class StyledClickableSpan(var listener: SpannableClickedListener) : ClickableSpan() {
|
||||
override fun onClick(widget: View) {
|
||||
val tv = widget as TextView
|
||||
val span = tv.text as Spanned
|
||||
val start = span.getSpanStart(this)
|
||||
val end = span.getSpanEnd(this)
|
||||
val text = span.subSequence(start, end)
|
||||
item.listener.onSpanClicked(text.toString())
|
||||
listener.onSpanClicked(text.toString())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,17 +56,21 @@ class PatternClickableSpan {
|
|||
return this
|
||||
}
|
||||
|
||||
fun build(editable: CharSequence?): SpannableStringBuilder {
|
||||
val ssb = SpannableStringBuilder(editable)
|
||||
fun build(ssb: SpannableStringBuilder): SpannableStringBuilder {
|
||||
for (item in patterns) {
|
||||
val matcher = item.pattern.matcher(ssb)
|
||||
while (matcher.find()) {
|
||||
val start = matcher.start()
|
||||
val end = matcher.end()
|
||||
val url = StyledClickableSpan(item)
|
||||
val url = StyledClickableSpan(item.listener)
|
||||
ssb.setSpan(url, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
}
|
||||
return ssb
|
||||
}
|
||||
}
|
||||
|
||||
interface SpannableClickedListener {
|
||||
@UiThread
|
||||
fun onSpanClicked(text: String)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<corners android:topRightRadius="16dp" android:topLeftRadius="16dp" />
|
||||
<solid android:color="@color/gray_main2_100"/>
|
||||
<solid android:color="@color/gray_200"/>
|
||||
</shape>
|
||||
|
|
@ -75,7 +75,7 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:background="@drawable/shape_chat_bubble_incoming_reply"
|
||||
android:background="@drawable/shape_chat_bubble_reply"
|
||||
android:visibility="@{model.isReply ? View.VISIBLE : View.GONE, default=gone}"
|
||||
app:layout_constraintTop_toTopOf="@id/reply"
|
||||
app:layout_constraintStart_toStartOf="@id/reply"
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
android:onClick="@{scrollToRepliedMessageClickListener}"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/shape_chat_bubble_incoming_reply"
|
||||
android:background="@drawable/shape_chat_bubble_reply"
|
||||
android:visibility="@{model.isReply ? View.VISIBLE : View.GONE, default=gone}"
|
||||
app:layout_constraintTop_toTopOf="@id/reply"
|
||||
app:layout_constraintStart_toStartOf="@id/reply"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue