diff --git a/app/src/main/java/org/linphone/contacts/ContactData.kt b/app/src/main/java/org/linphone/contacts/ContactData.kt
new file mode 100644
index 000000000..99647f6f0
--- /dev/null
+++ b/app/src/main/java/org/linphone/contacts/ContactData.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2010-2020 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.contacts
+
+import android.content.ContentUris
+import android.net.Uri
+import android.provider.ContactsContract
+import androidx.lifecycle.MutableLiveData
+import org.linphone.core.*
+
+class ContactData(val friend: Friend) {
+ val presenceStatus = MutableLiveData()
+
+ val name = MutableLiveData()
+
+ val avatar = getAvatarUri()
+
+ private val friendListener = object : FriendListenerStub() {
+ override fun onPresenceReceived(fr: Friend) {
+ presenceStatus.postValue(fr.consolidatedPresence)
+ }
+ }
+
+ init {
+ name.postValue(friend.name)
+ presenceStatus.postValue(friend.consolidatedPresence)
+
+ friend.addListener(friendListener)
+
+ presenceStatus.postValue(ConsolidatedPresence.Offline)
+ }
+
+ fun onDestroy() {
+ friend.removeListener(friendListener)
+ }
+
+ private fun getAvatarUri(): Uri? {
+ val refKey = friend.refKey
+ if (refKey != null) {
+ val lookupUri = ContentUris.withAppendedId(
+ ContactsContract.Contacts.CONTENT_URI,
+ refKey.toLong()
+ )
+ return Uri.withAppendedPath(
+ lookupUri,
+ ContactsContract.Contacts.Photo.CONTENT_DIRECTORY
+ )
+ }
+
+ return null
+ }
+}
diff --git a/app/src/main/java/org/linphone/contacts/ContactSelectionData.kt b/app/src/main/java/org/linphone/contacts/ContactSelectionData.kt
deleted file mode 100644
index 36cca5069..000000000
--- a/app/src/main/java/org/linphone/contacts/ContactSelectionData.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (c) 2010-2020 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.contacts
-
-import androidx.lifecycle.MutableLiveData
-import org.linphone.core.*
-
-class ContactSelectionData(searchResult: SearchResult) {
- val name = MutableLiveData()
-
- init {
- name.value = searchResult.friend?.name ?: searchResult.toString()
- }
-}
diff --git a/app/src/main/java/org/linphone/contacts/ContactsSelectionAdapter.kt b/app/src/main/java/org/linphone/contacts/ContactsSelectionAdapter.kt
index ed6055352..a94078e69 100644
--- a/app/src/main/java/org/linphone/contacts/ContactsSelectionAdapter.kt
+++ b/app/src/main/java/org/linphone/contacts/ContactsSelectionAdapter.kt
@@ -27,12 +27,11 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.linphone.R
-import org.linphone.core.SearchResult
import org.linphone.databinding.ContactSelectionCellBinding
class ContactsSelectionAdapter(
private val viewLifecycleOwner: LifecycleOwner
-) : ListAdapter(SearchResultDiffCallback()) {
+) : ListAdapter(ContactDataDiffCallback()) {
init {
}
@@ -53,10 +52,9 @@ class ContactsSelectionAdapter(
inner class ViewHolder(
private val binding: ContactSelectionCellBinding
) : RecyclerView.ViewHolder(binding.root) {
- fun bind(searchResult: SearchResult) {
+ fun bind(contactData: ContactData) {
with(binding) {
- val searchResultViewModel = ContactSelectionData(searchResult)
- data = searchResultViewModel
+ data = contactData
lifecycleOwner = viewLifecycleOwner
@@ -66,20 +64,18 @@ class ContactsSelectionAdapter(
}
}
-private class SearchResultDiffCallback : DiffUtil.ItemCallback() {
+private class ContactDataDiffCallback : DiffUtil.ItemCallback() {
override fun areItemsTheSame(
- oldItem: SearchResult,
- newItem: SearchResult
+ oldItem: ContactData,
+ newItem: ContactData
): Boolean {
- val oldAddress = oldItem.address
- val newAddress = newItem.address
- return if (oldAddress != null && newAddress != null) oldAddress.weakEqual(newAddress) else false
+ return oldItem.friend.refKey == newItem.friend.refKey
}
override fun areContentsTheSame(
- oldItem: SearchResult,
- newItem: SearchResult
+ oldItem: ContactData,
+ newItem: ContactData
): Boolean {
- return newItem.friend != null
+ return true
}
}
diff --git a/app/src/main/java/org/linphone/ui/conversations/ChatRoomData.kt b/app/src/main/java/org/linphone/ui/conversations/ChatRoomData.kt
index e5bcb19dd..39bb8ac48 100644
--- a/app/src/main/java/org/linphone/ui/conversations/ChatRoomData.kt
+++ b/app/src/main/java/org/linphone/ui/conversations/ChatRoomData.kt
@@ -19,11 +19,12 @@
*/
package org.linphone.ui.conversations
-import android.text.SpannableStringBuilder
import androidx.lifecycle.MutableLiveData
+import java.lang.StringBuilder
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
+import org.linphone.contacts.ContactData
import org.linphone.core.*
import org.linphone.core.tools.Log
import org.linphone.utils.LinphoneUtils
@@ -36,7 +37,7 @@ class ChatRoomData(val chatRoom: ChatRoom) {
val subject = MutableLiveData()
- val lastMessage = MutableLiveData()
+ val lastMessage = MutableLiveData()
val unreadChatCount = MutableLiveData()
@@ -56,6 +57,8 @@ class ChatRoomData(val chatRoom: ChatRoom) {
val lastMessageImdnIcon = MutableLiveData()
+ val contactData = MutableLiveData()
+
var chatRoomDataListener: ChatRoomDataListener? = null
val isOneToOne: Boolean by lazy {
@@ -92,9 +95,46 @@ class ChatRoomData(val chatRoom: ChatRoom) {
}
init {
- coreContext.postOnCoreThread { core ->
- chatRoom.addListener(chatRoomListener)
+ chatRoom.addListener(chatRoomListener)
+
+ if (chatRoom.hasCapability(ChatRoom.Capabilities.Basic.toInt())) {
+ val remoteAddress = chatRoom.peerAddress
+ val friend = chatRoom.core.findFriend(remoteAddress)
+ if (friend != null) {
+ contactData.postValue(ContactData(friend))
+ }
+ contactName.postValue(friend?.name ?: LinphoneUtils.getDisplayName(remoteAddress))
+ } else {
+ if (chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt())) {
+ val first = chatRoom.participants.firstOrNull()
+ if (first != null) {
+ val remoteAddress = first.address
+ val friend = chatRoom.core.findFriend(remoteAddress)
+ if (friend != null) {
+ contactData.postValue(ContactData(friend))
+ }
+ contactName.postValue(
+ friend?.name ?: LinphoneUtils.getDisplayName(remoteAddress)
+ )
+ } else {
+ Log.e("[Chat Room Data] No participant in the chat room!")
+ }
+ }
}
+ subject.postValue(
+ chatRoom.subject ?: LinphoneUtils.getDisplayName(chatRoom.peerAddress)
+ )
+
+ lastMessageImdnIcon.postValue(R.drawable.imdn_sent)
+ showLastMessageImdnIcon.postValue(false)
+ computeLastMessage()
+
+ unreadChatCount.postValue(chatRoom.unreadMessagesCount)
+ isComposing.postValue(chatRoom.isRemoteComposing)
+ isSecure.postValue(chatRoom.securityLevel == ChatRoom.SecurityLevel.Encrypted)
+ isSecureVerified.postValue(chatRoom.securityLevel == ChatRoom.SecurityLevel.Safe)
+ isEphemeral.postValue(chatRoom.isEphemeralEnabled)
+ isMuted.postValue(areNotificationsMuted())
}
fun onCleared() {
@@ -112,39 +152,6 @@ class ChatRoomData(val chatRoom: ChatRoom) {
return true
}
- fun update() {
- if (chatRoom.hasCapability(ChatRoom.Capabilities.Basic.toInt())) {
- val remoteAddress = chatRoom.peerAddress
- val friend = chatRoom.core.findFriend(remoteAddress)
- contactName.postValue(friend?.name ?: LinphoneUtils.getDisplayName(remoteAddress))
- } else {
- if (chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt())) {
- val first = chatRoom.participants.firstOrNull()
- if (first != null) {
- val remoteAddress = first.address
- val friend = chatRoom.core.findFriend(remoteAddress)
- contactName.postValue(
- friend?.name ?: LinphoneUtils.getDisplayName(remoteAddress)
- )
- } else {
- Log.e("[Chat Room Data] No participant in the chat room!")
- }
- }
- }
- subject.postValue(chatRoom.subject ?: LinphoneUtils.getDisplayName(chatRoom.peerAddress))
-
- lastMessageImdnIcon.postValue(R.drawable.imdn_sent)
- showLastMessageImdnIcon.postValue(false)
- computeLastMessage()
-
- unreadChatCount.postValue(chatRoom.unreadMessagesCount)
- isComposing.postValue(chatRoom.isRemoteComposing)
- isSecure.postValue(chatRoom.securityLevel == ChatRoom.SecurityLevel.Encrypted)
- isSecureVerified.postValue(chatRoom.securityLevel == ChatRoom.SecurityLevel.Safe)
- isEphemeral.postValue(chatRoom.isEphemeralEnabled)
- isMuted.postValue(areNotificationsMuted())
- }
-
private fun computeLastMessageImdnIcon(message: ChatMessage) {
val state = message.state
showLastMessageImdnIcon.postValue(
@@ -174,7 +181,7 @@ class ChatRoomData(val chatRoom: ChatRoom) {
val lastUpdateTime = chatRoom.lastUpdateTime
lastUpdate.postValue(TimestampUtils.toString(lastUpdateTime, true))
- val builder = SpannableStringBuilder()
+ val builder = StringBuilder()
val message = chatRoom.lastMessageInHistory
if (message != null) {
@@ -185,6 +192,10 @@ class ChatRoomData(val chatRoom: ChatRoom) {
message.addListener(object : ChatMessageListenerStub() {
override fun onMsgStateChanged(message: ChatMessage, state: ChatMessage.State) {
computeLastMessageImdnIcon(message)
+
+ if (state == ChatMessage.State.Displayed) {
+ message.removeListener(this)
+ }
}
})
}
@@ -206,7 +217,12 @@ class ChatRoomData(val chatRoom: ChatRoom) {
builder.trim()
}
- lastMessage.postValue(builder)
+ val text = builder.toString()
+ if (text.length > 128) { // This brings a huge performance improvement when scrolling
+ lastMessage.postValue(text.substring(0, 128))
+ } else {
+ lastMessage.postValue(text)
+ }
}
private fun areNotificationsMuted(): Boolean {
diff --git a/app/src/main/java/org/linphone/ui/conversations/ConversationsFragment.kt b/app/src/main/java/org/linphone/ui/conversations/ConversationsFragment.kt
index 7262342a4..4df1c5c14 100644
--- a/app/src/main/java/org/linphone/ui/conversations/ConversationsFragment.kt
+++ b/app/src/main/java/org/linphone/ui/conversations/ConversationsFragment.kt
@@ -93,6 +93,7 @@ class ConversationsFragment : Fragment() {
it.consume { data ->
}
}
+
adapter.chatRoomLongClickedEvent.observe(viewLifecycleOwner) {
it.consume { data ->
val modalBottomSheet = ConversationMenuDialogFragment(data.chatRoom) {
diff --git a/app/src/main/java/org/linphone/ui/conversations/ConversationsListAdapter.kt b/app/src/main/java/org/linphone/ui/conversations/ConversationsListAdapter.kt
index 8f4e22a58..1c3dd5d9e 100644
--- a/app/src/main/java/org/linphone/ui/conversations/ConversationsListAdapter.kt
+++ b/app/src/main/java/org/linphone/ui/conversations/ConversationsListAdapter.kt
@@ -68,7 +68,6 @@ class ConversationsListAdapter(
) : RecyclerView.ViewHolder(binding.root) {
fun bind(chatRoomData: ChatRoomData) {
with(binding) {
- chatRoomData.update()
data = chatRoomData
lifecycleOwner = viewLifecycleOwner
@@ -98,6 +97,6 @@ private class ConversationDiffCallback : DiffUtil.ItemCallback() {
}
override fun areContentsTheSame(oldItem: ChatRoomData, newItem: ChatRoomData): Boolean {
- return false
+ return true
}
}
diff --git a/app/src/main/java/org/linphone/ui/conversations/NewConversationViewModel.kt b/app/src/main/java/org/linphone/ui/conversations/NewConversationViewModel.kt
index 77b3ab612..335d16423 100644
--- a/app/src/main/java/org/linphone/ui/conversations/NewConversationViewModel.kt
+++ b/app/src/main/java/org/linphone/ui/conversations/NewConversationViewModel.kt
@@ -22,13 +22,14 @@ package org.linphone.ui.conversations
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
+import org.linphone.contacts.ContactData
import org.linphone.core.MagicSearch
import org.linphone.core.MagicSearchListenerStub
import org.linphone.core.SearchResult
import org.linphone.core.tools.Log
class NewConversationViewModel : ViewModel() {
- val contactsList = MutableLiveData>()
+ val contactsList = MutableLiveData>()
val filter = MutableLiveData()
private var previousFilter = "NotSet"
@@ -80,8 +81,16 @@ class NewConversationViewModel : ViewModel() {
private fun processMagicSearchResults(results: Array) {
Log.i("[New Conversation ViewModel] [${results.size}] matching results")
- val list = arrayListOf()
- list.addAll(results)
+ contactsList.value.orEmpty().forEach(ContactData::onDestroy)
+
+ val list = arrayListOf()
+ for (searchResult in results) {
+ val friend = searchResult.friend
+ if (friend != null) {
+ val data = ContactData(friend)
+ list.add(data)
+ }
+ }
contactsList.postValue(list)
}
}
diff --git a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt
index 7b17eefbf..32aa00b79 100644
--- a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt
+++ b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt
@@ -22,6 +22,10 @@ package org.linphone.utils
import android.widget.ImageView
import android.widget.TextView
import androidx.databinding.BindingAdapter
+import coil.load
+import coil.transform.CircleCropTransformation
+import org.linphone.R
+import org.linphone.contacts.ContactData
/**
* This file contains all the data binding necessary for the app
@@ -36,3 +40,13 @@ fun ImageView.setSourceImageResource(resource: Int) {
fun TextView.setTypeface(typeface: Int) {
this.setTypeface(null, typeface)
}
+
+@BindingAdapter("coilContact")
+fun loadContactPictureWithCoil(imageView: ImageView, contact: ContactData?) {
+ contact ?: return
+
+ imageView.load(contact.avatar) {
+ transformations(CircleCropTransformation())
+ error(R.drawable.contact_avatar)
+ }
+}
diff --git a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt
index f48796c03..2bf88b787 100644
--- a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt
+++ b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt
@@ -19,9 +19,14 @@
*/
package org.linphone.utils
+import android.content.ContentUris
+import android.net.Uri
+import android.provider.ContactsContract
+import java.io.IOException
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.Address
import org.linphone.core.ChatRoom
+import org.linphone.core.Friend
class LinphoneUtils {
companion object {
@@ -52,5 +57,42 @@ class LinphoneUtils {
// Do not return an empty display name
return address.displayName ?: address.username ?: address.asString()
}
+
+ fun Friend.getPictureUri(thumbnailPreferred: Boolean = false): Uri? {
+ val refKey = refKey
+ if (refKey != null) {
+ try {
+ val lookupUri = ContentUris.withAppendedId(
+ ContactsContract.Contacts.CONTENT_URI,
+ refKey.toLong()
+ )
+
+ if (!thumbnailPreferred) {
+ val pictureUri = Uri.withAppendedPath(
+ lookupUri,
+ ContactsContract.Contacts.Photo.DISPLAY_PHOTO
+ )
+ // Check that the URI points to a real file
+ val contentResolver = coreContext.context.contentResolver
+ try {
+ if (contentResolver.openAssetFileDescriptor(pictureUri, "r") != null) {
+ return pictureUri
+ }
+ } catch (ioe: IOException) { }
+ }
+
+ // Fallback to thumbnail if high res picture isn't available
+ return Uri.withAppendedPath(
+ lookupUri,
+ ContactsContract.Contacts.Photo.CONTENT_DIRECTORY
+ )
+ } catch (e: Exception) { }
+ } else if (photo != null) {
+ try {
+ return Uri.parse(photo)
+ } catch (e: Exception) { }
+ }
+ return null
+ }
}
}
diff --git a/app/src/main/res/layout/chat_room_list_cell.xml b/app/src/main/res/layout/chat_room_list_cell.xml
index 543d33276..6f4b81b4f 100644
--- a/app/src/main/res/layout/chat_room_list_cell.xml
+++ b/app/src/main/res/layout/chat_room_list_cell.xml
@@ -23,39 +23,47 @@
+
+
+ type="org.linphone.contacts.ContactData" />
@@ -34,6 +36,8 @@
android:text="@{data.name, default=`John Doe`}"
android:textColor="@color/black"
android:textSize="14sp"
+ android:maxLines="1"
+ android:ellipsize="end"
app:layout_constraintBottom_toBottomOf="@id/avatar"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toTopOf="parent" />