Make SIP URI in chat messages clickable as well as http links
This commit is contained in:
parent
32941122ce
commit
46ef080d62
10 changed files with 126 additions and 6 deletions
|
@ -20,6 +20,7 @@ Group changes to describe their impact on the project, as follows:
|
|||
- Unread messages indicator in chat conversation that separates read & unread messages
|
||||
- Notify incoming/outgoing calls on bluetooth devices using self-managed connections from telecom manager API (disables SDK audio focus)
|
||||
- Ask Android to not process what user types in an encrypted chat room to improve privacy, see [IME_FLAG_NO_PERSONALIZED_LEARNING](https://developer.android.com/reference/android/view/inputmethod/EditorInfo#IME_FLAG_NO_PERSONALIZED_LEARNING)
|
||||
- SIP URIs in chat messages are clickable to easily initiate a call
|
||||
- New video call UI on foldable device like Galaxy Z Fold
|
||||
- Setting to automatically record all calls
|
||||
- When using a physical keyboard, use left control + enter keys to send message
|
||||
|
|
|
@ -322,6 +322,14 @@ internal fun DetailChatRoomFragment.navigateToEmptyChatRoom() {
|
|||
)
|
||||
}
|
||||
|
||||
internal fun DetailChatRoomFragment.navigateToDialer(args: Bundle?) {
|
||||
findMasterNavController().navigate(
|
||||
R.id.action_global_dialerFragment,
|
||||
args,
|
||||
popupTo(R.id.dialerFragment, true)
|
||||
)
|
||||
}
|
||||
|
||||
internal fun ChatRoomCreationFragment.navigateToGroupInfo() {
|
||||
if (findNavController().currentDestination?.id == R.id.chatRoomCreationFragment) {
|
||||
findNavController().navigate(
|
||||
|
|
|
@ -86,6 +86,10 @@ class ChatMessagesListAdapter(
|
|||
MutableLiveData<Event<Content>>()
|
||||
}
|
||||
|
||||
val sipUriClickedEvent: MutableLiveData<Event<String>> by lazy {
|
||||
MutableLiveData<Event<String>>()
|
||||
}
|
||||
|
||||
val scrollToChatMessageEvent: MutableLiveData<Event<ChatMessage>> by lazy {
|
||||
MutableLiveData<Event<ChatMessage>>()
|
||||
}
|
||||
|
@ -94,6 +98,10 @@ class ChatMessagesListAdapter(
|
|||
override fun onContentClicked(content: Content) {
|
||||
openContentEvent.value = Event(content)
|
||||
}
|
||||
|
||||
override fun onSipAddressClicked(sipUri: String) {
|
||||
sipUriClickedEvent.value = Event(sipUri)
|
||||
}
|
||||
}
|
||||
|
||||
private var contextMenuDisabled: Boolean = false
|
||||
|
|
|
@ -354,4 +354,6 @@ class ChatMessageContentData(
|
|||
|
||||
interface OnContentClickedListener {
|
||||
fun onContentClicked(content: Content)
|
||||
|
||||
fun onSipAddressClicked(sipUri: String)
|
||||
}
|
||||
|
|
|
@ -24,12 +24,14 @@ import android.text.Spannable
|
|||
import android.text.util.Linkify
|
||||
import androidx.core.text.util.LinkifyCompat
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import java.util.regex.Pattern
|
||||
import org.linphone.R
|
||||
import org.linphone.contact.GenericContactData
|
||||
import org.linphone.core.ChatMessage
|
||||
import org.linphone.core.ChatMessageListenerStub
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.AppUtils
|
||||
import org.linphone.utils.PatternClickableSpan
|
||||
import org.linphone.utils.TimestampUtils
|
||||
|
||||
class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMessage.fromAddress) {
|
||||
|
@ -182,7 +184,16 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
|
|||
} else if (content.isText) {
|
||||
val spannable = Spannable.Factory.getInstance().newSpannable(content.utf8Text?.trim())
|
||||
LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS)
|
||||
text.value = spannable
|
||||
text.value = PatternClickableSpan()
|
||||
.add(
|
||||
Pattern.compile("(sips?):([^@]+)(?:@([^ ]+))?"),
|
||||
object : PatternClickableSpan.SpannableClickedListener {
|
||||
override fun onSpanClicked(text: String) {
|
||||
Log.i("[Chat Message Data] Clicked on SIP URI: $text")
|
||||
contentListener?.onSipAddressClicked(text)
|
||||
}
|
||||
}
|
||||
).build(spannable)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -457,6 +457,19 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
|||
}
|
||||
}
|
||||
|
||||
adapter.sipUriClickedEvent.observe(
|
||||
viewLifecycleOwner
|
||||
) {
|
||||
it.consume { sipUri ->
|
||||
val args = Bundle()
|
||||
args.putString("URI", sipUri)
|
||||
args.putBoolean("Transfer", false)
|
||||
// If auto start call setting is enabled, ignore it
|
||||
args.putBoolean("SkipAutoCallStart", true)
|
||||
navigateToDialer(args)
|
||||
}
|
||||
}
|
||||
|
||||
adapter.scrollToChatMessageEvent.observe(
|
||||
viewLifecycleOwner
|
||||
) {
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.linphone.activities.main.chat.views
|
|||
|
||||
import android.content.Context
|
||||
import android.text.Layout
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import kotlin.math.ceil
|
||||
|
@ -40,6 +41,12 @@ class MultiLineWrapContentWidthTextView : AppCompatTextView {
|
|||
defStyleAttr: Int
|
||||
) : super(context, attrs, defStyleAttr)
|
||||
|
||||
override fun setText(text: CharSequence?, type: BufferType?) {
|
||||
super.setText(text, type)
|
||||
// Required for PatternClickableSpan
|
||||
movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
||||
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
|
||||
var wSpec = widthSpec
|
||||
val widthMode = MeasureSpec.getMode(wSpec)
|
||||
|
|
|
@ -246,10 +246,8 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
|
|||
val args = Bundle()
|
||||
args.putString("URI", remoteAddress.asStringUriOnly())
|
||||
args.putBoolean("Transfer", sharedViewModel.pendingCallTransfer)
|
||||
args.putBoolean(
|
||||
"SkipAutoCallStart",
|
||||
true
|
||||
) // If auto start call setting is enabled, ignore it
|
||||
// If auto start call setting is enabled, ignore it
|
||||
args.putBoolean("SkipAutoCallStart", true)
|
||||
navigateToDialer(args)
|
||||
} else {
|
||||
val localAddress = callLogGroup.lastCallLog.localAddress
|
||||
|
|
73
app/src/main/java/org/linphone/utils/PatternClickableSpan.kt
Normal file
73
app/src/main/java/org/linphone/utils/PatternClickableSpan.kt
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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 {
|
||||
var patterns: ArrayList<SpannablePatternItem> = 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
|
||||
}
|
||||
}
|
|
@ -187,7 +187,6 @@
|
|||
android:onLongClick="@{contextMenuClickListener}"
|
||||
android:text="@{data.text}"
|
||||
android:visibility="@{data.text.length > 0 ? View.VISIBLE : View.GONE}"
|
||||
android:autoLink="web"
|
||||
android:layout_gravity="@{data.chatMessage.outgoing ? Gravity.RIGHT : Gravity.LEFT}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
Loading…
Reference in a new issue