diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt index 464df834e..7e567fca6 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt @@ -308,7 +308,7 @@ class ConversationFragment : GenericFragment() { layout.setCopyClickListener { Log.i("$TAG Copying message text into clipboard") - val text = chatMessageModel.text + val text = chatMessageModel.text.value?.toString() val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val label = "Message" clipboard.setPrimaryClip(ClipData.newPlainText(label, text)) diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageModel.kt index 6aec7362a..dbf0850af 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageModel.kt @@ -19,9 +19,11 @@ */ package org.linphone.ui.main.chat.model +import android.text.Spannable import androidx.annotation.UiThread import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData +import java.util.regex.Pattern import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.core.Address import org.linphone.core.ChatMessage @@ -31,6 +33,7 @@ import org.linphone.core.tools.Log 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.TimestampUtils class ChatMessageModel @WorkerThread constructor( @@ -53,7 +56,7 @@ class ChatMessageModel @WorkerThread constructor( val statusIcon = MutableLiveData() - val text = LinphoneUtils.getTextDescribingMessage(chatMessage) + val text = MutableLiveData() val timestamp = chatMessage.time @@ -92,6 +95,42 @@ class ChatMessageModel @WorkerThread constructor( chatMessage.addListener(chatMessageListener) statusIcon.postValue(LinphoneUtils.getChatIconResId(chatMessage.state)) updateReactionsList() + + var textFound = false + for (content in chatMessage.contents) { + if (content.isText) { + val textContent = content.utf8Text.orEmpty().trim() + val spannable = Spannable.Factory.getInstance().newSpannable(textContent) + text.postValue( + PatternClickableSpan() + .add( + Pattern.compile( + "(?:)?" + ), + object : PatternClickableSpan.SpannableClickedListener { + @UiThread + override fun onSpanClicked(text: String) { + coreContext.postOnCoreThread { + Log.i("$TAG Clicked on SIP URI: $text") + val address = coreContext.core.interpretUrl(text) + if (address != null) { + coreContext.startCall(address) + } else { + Log.w("$TAG Failed to parse [$text] as SIP URI") + } + } + } + } + ).build(spannable) + ) + textFound = true + } + } + if (!textFound) { + val describe = LinphoneUtils.getTextDescribingMessage(chatMessage) + val spannable = Spannable.Factory.getInstance().newSpannable(describe) + text.postValue(spannable) + } } @WorkerThread diff --git a/app/src/main/java/org/linphone/ui/main/chat/view/ChatBubbleTextView.kt b/app/src/main/java/org/linphone/ui/main/chat/view/ChatBubbleTextView.kt index 610de22f1..2cf04edde 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/view/ChatBubbleTextView.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/view/ChatBubbleTextView.kt @@ -23,9 +23,10 @@ import android.content.Context import android.text.method.LinkMovementMethod import android.util.AttributeSet import android.view.View.MeasureSpec.* +import androidx.annotation.UiThread import androidx.appcompat.widget.AppCompatTextView -import kotlin.math.ceil +@UiThread class ChatBubbleTextView : AppCompatTextView { constructor(context: Context) : super(context) @@ -42,20 +43,4 @@ class ChatBubbleTextView : AppCompatTextView { // Required for PatternClickableSpan movementMethod = LinkMovementMethod.getInstance() } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - - val lines = layout.lineCount - if (lines > 1 && getMode(widthMeasureSpec) != EXACTLY) { - val textWidth = (0 until lines).maxOf(layout::getLineWidth) - val padding = compoundPaddingLeft + compoundPaddingRight - val w = ceil(textWidth).toInt() + padding - - if (w < measuredWidth) { - val newWidthMeasureSpec = makeMeasureSpec(w, AT_MOST) - super.onMeasure(newWidthMeasureSpec, heightMeasureSpec) - } - } - } } diff --git a/app/src/main/java/org/linphone/utils/PatternClickableSpan.kt b/app/src/main/java/org/linphone/utils/PatternClickableSpan.kt new file mode 100644 index 000000000..19b1f8735 --- /dev/null +++ b/app/src/main/java/org/linphone/utils/PatternClickableSpan.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2010-2022 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.utils + +import android.text.SpannableStringBuilder +import android.text.Spanned +import android.text.style.ClickableSpan +import android.view.View +import android.widget.TextView +import java.util.regex.Pattern + +class PatternClickableSpan { + private var patterns: ArrayList = ArrayList() + + inner class SpannablePatternItem( + var pattern: Pattern, + var listener: SpannableClickedListener + ) + + interface SpannableClickedListener { + fun onSpanClicked(text: String) + } + + inner class StyledClickableSpan(var item: SpannablePatternItem) : 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()) + } + } + + fun add( + pattern: Pattern, + listener: SpannableClickedListener + ): PatternClickableSpan { + patterns.add(SpannablePatternItem(pattern, listener)) + return this + } + + fun build(editable: CharSequence?): SpannableStringBuilder { + val ssb = SpannableStringBuilder(editable) + 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) + ssb.setSpan(url, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + return ssb + } +} diff --git a/app/src/main/res/drawable/shape_chat_bubble_incoming_first.xml b/app/src/main/res/drawable/shape_chat_bubble_incoming_first.xml index e82f7df3c..513b02d4d 100644 --- a/app/src/main/res/drawable/shape_chat_bubble_incoming_first.xml +++ b/app/src/main/res/drawable/shape_chat_bubble_incoming_first.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_chat_bubble_incoming_full.xml b/app/src/main/res/drawable/shape_chat_bubble_incoming_full.xml index df3a98fea..6ea2e234d 100644 --- a/app/src/main/res/drawable/shape_chat_bubble_incoming_full.xml +++ b/app/src/main/res/drawable/shape_chat_bubble_incoming_full.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_chat_bubble_incoming_reply.xml b/app/src/main/res/drawable/shape_chat_bubble_incoming_reply.xml index 98ea7684c..39659b658 100644 --- a/app/src/main/res/drawable/shape_chat_bubble_incoming_reply.xml +++ b/app/src/main/res/drawable/shape_chat_bubble_incoming_reply.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_chat_bubble_outgoing_first.xml b/app/src/main/res/drawable/shape_chat_bubble_outgoing_first.xml index 1d29b6a85..348f7f12f 100644 --- a/app/src/main/res/drawable/shape_chat_bubble_outgoing_first.xml +++ b/app/src/main/res/drawable/shape_chat_bubble_outgoing_first.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_chat_bubble_outgoing_full.xml b/app/src/main/res/drawable/shape_chat_bubble_outgoing_full.xml index 6ea2e234d..57fe57f90 100644 --- a/app/src/main/res/drawable/shape_chat_bubble_outgoing_full.xml +++ b/app/src/main/res/drawable/shape_chat_bubble_outgoing_full.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_chat_bubble_reactions_incoming_background.xml b/app/src/main/res/drawable/shape_chat_bubble_reactions_incoming_background.xml index da536aa15..1389886ba 100644 --- a/app/src/main/res/drawable/shape_chat_bubble_reactions_incoming_background.xml +++ b/app/src/main/res/drawable/shape_chat_bubble_reactions_incoming_background.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_chat_bubble_reactions_outgoing_background.xml b/app/src/main/res/drawable/shape_chat_bubble_reactions_outgoing_background.xml index 1389886ba..7ad1af3dc 100644 --- a/app/src/main/res/drawable/shape_chat_bubble_reactions_outgoing_background.xml +++ b/app/src/main/res/drawable/shape_chat_bubble_reactions_outgoing_background.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_bubble_incoming.xml b/app/src/main/res/layout/chat_bubble_incoming.xml index 659b9e8ed..b6f8501df 100644 --- a/app/src/main/res/layout/chat_bubble_incoming.xml +++ b/app/src/main/res/layout/chat_bubble_incoming.xml @@ -121,7 +121,7 @@ app:layout_constraintTop_toBottomOf="@id/reply" app:layout_constraintBottom_toBottomOf="@id/date_time"/> - -