Should improve scrolling performances in conversation

This commit is contained in:
Sylvain Berfini 2021-06-10 17:20:19 +02:00
parent e2a04f2e95
commit 5cf34edc07
7 changed files with 107 additions and 74 deletions

View file

@ -36,6 +36,7 @@ import org.linphone.R
import org.linphone.activities.main.adapters.SelectionListAdapter import org.linphone.activities.main.adapters.SelectionListAdapter
import org.linphone.activities.main.chat.data.ChatMessageData import org.linphone.activities.main.chat.data.ChatMessageData
import org.linphone.activities.main.chat.data.EventData import org.linphone.activities.main.chat.data.EventData
import org.linphone.activities.main.chat.data.EventLogData
import org.linphone.activities.main.chat.data.OnContentClickedListener import org.linphone.activities.main.chat.data.OnContentClickedListener
import org.linphone.activities.main.viewmodels.ListTopBarViewModel import org.linphone.activities.main.viewmodels.ListTopBarViewModel
import org.linphone.core.ChatMessage import org.linphone.core.ChatMessage
@ -51,7 +52,7 @@ import org.linphone.utils.Event
class ChatMessagesListAdapter( class ChatMessagesListAdapter(
selectionVM: ListTopBarViewModel, selectionVM: ListTopBarViewModel,
private val viewLifecycleOwner: LifecycleOwner private val viewLifecycleOwner: LifecycleOwner
) : SelectionListAdapter<EventLog, RecyclerView.ViewHolder>(selectionVM, ChatMessageDiffCallback()) { ) : SelectionListAdapter<EventLogData, RecyclerView.ViewHolder>(selectionVM, ChatMessageDiffCallback()) {
companion object { companion object {
const val MAX_TIME_TO_GROUP_MESSAGES = 60 // 1 minute const val MAX_TIME_TO_GROUP_MESSAGES = 60 // 1 minute
} }
@ -127,7 +128,7 @@ class ChatMessagesListAdapter(
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
val eventLog = getItem(position) val eventLog = getItem(position)
return eventLog.type.toInt() return eventLog.eventLog.type.toInt()
} }
fun disableContextMenu() { fun disableContextMenu() {
@ -137,12 +138,13 @@ class ChatMessagesListAdapter(
inner class ChatMessageViewHolder( inner class ChatMessageViewHolder(
val binding: ChatMessageListCellBinding val binding: ChatMessageListCellBinding
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
fun bind(eventLog: EventLog) { fun bind(eventLog: EventLogData) {
with(binding) { with(binding) {
if (eventLog.type == EventLog.Type.ConferenceChatMessage) { if (eventLog.eventLog.type == EventLog.Type.ConferenceChatMessage) {
val chatMessage = eventLog.chatMessage val chatMessageViewModel = eventLog.data as ChatMessageData
chatMessage ?: return chatMessageViewModel.setContentClickListener(contentClickedListener)
val chatMessageViewModel = ChatMessageData(chatMessage, contentClickedListener)
val chatMessage = chatMessageViewModel.chatMessage
data = chatMessageViewModel data = chatMessageViewModel
lifecycleOwner = viewLifecycleOwner lifecycleOwner = viewLifecycleOwner
@ -165,8 +167,8 @@ class ChatMessagesListAdapter(
if (adapterPosition > 0) { if (adapterPosition > 0) {
val previousItem = getItem(adapterPosition - 1) val previousItem = getItem(adapterPosition - 1)
if (previousItem.type == EventLog.Type.ConferenceChatMessage) { if (previousItem.eventLog.type == EventLog.Type.ConferenceChatMessage) {
val previousMessage = previousItem.chatMessage val previousMessage = previousItem.eventLog.chatMessage
if (previousMessage != null && previousMessage.fromAddress.weakEqual(chatMessage.fromAddress)) { if (previousMessage != null && previousMessage.fromAddress.weakEqual(chatMessage.fromAddress)) {
if (chatMessage.time - previousMessage.time < MAX_TIME_TO_GROUP_MESSAGES) { if (chatMessage.time - previousMessage.time < MAX_TIME_TO_GROUP_MESSAGES) {
hasPrevious = true hasPrevious = true
@ -177,8 +179,8 @@ class ChatMessagesListAdapter(
if (adapterPosition >= 0 && adapterPosition < itemCount - 1) { if (adapterPosition >= 0 && adapterPosition < itemCount - 1) {
val nextItem = getItem(adapterPosition + 1) val nextItem = getItem(adapterPosition + 1)
if (nextItem.type == EventLog.Type.ConferenceChatMessage) { if (nextItem.eventLog.type == EventLog.Type.ConferenceChatMessage) {
val nextMessage = nextItem.chatMessage val nextMessage = nextItem.eventLog.chatMessage
if (nextMessage != null && nextMessage.fromAddress.weakEqual(chatMessage.fromAddress)) { if (nextMessage != null && nextMessage.fromAddress.weakEqual(chatMessage.fromAddress)) {
if (nextMessage.time - chatMessage.time < MAX_TIME_TO_GROUP_MESSAGES) { if (nextMessage.time - chatMessage.time < MAX_TIME_TO_GROUP_MESSAGES) {
hasNext = true hasNext = true
@ -319,9 +321,9 @@ class ChatMessagesListAdapter(
inner class EventViewHolder( inner class EventViewHolder(
private val binding: ChatEventListCellBinding private val binding: ChatEventListCellBinding
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
fun bind(eventLog: EventLog) { fun bind(eventLog: EventLogData) {
with(binding) { with(binding) {
val eventViewModel = EventData(eventLog) val eventViewModel = eventLog.data as EventData
data = eventViewModel data = eventViewModel
binding.lifecycleOwner = viewLifecycleOwner binding.lifecycleOwner = viewLifecycleOwner
@ -344,24 +346,24 @@ class ChatMessagesListAdapter(
} }
} }
private class ChatMessageDiffCallback : DiffUtil.ItemCallback<EventLog>() { private class ChatMessageDiffCallback : DiffUtil.ItemCallback<EventLogData>() {
override fun areItemsTheSame( override fun areItemsTheSame(
oldItem: EventLog, oldItem: EventLogData,
newItem: EventLog newItem: EventLogData
): Boolean { ): Boolean {
return if (oldItem.type == EventLog.Type.ConferenceChatMessage && return if (oldItem.eventLog.type == EventLog.Type.ConferenceChatMessage &&
newItem.type == EventLog.Type.ConferenceChatMessage) { newItem.eventLog.type == EventLog.Type.ConferenceChatMessage) {
oldItem.chatMessage?.time == newItem.chatMessage?.time && oldItem.eventLog.chatMessage?.time == newItem.eventLog.chatMessage?.time &&
oldItem.chatMessage?.isOutgoing == newItem.chatMessage?.isOutgoing oldItem.eventLog.chatMessage?.isOutgoing == newItem.eventLog.chatMessage?.isOutgoing
} else oldItem.notifyId == newItem.notifyId } else oldItem.eventLog.notifyId == newItem.eventLog.notifyId
} }
override fun areContentsTheSame( override fun areContentsTheSame(
oldItem: EventLog, oldItem: EventLogData,
newItem: EventLog newItem: EventLogData
): Boolean { ): Boolean {
return if (newItem.type == EventLog.Type.ConferenceChatMessage) { return if (newItem.eventLog.type == EventLog.Type.ConferenceChatMessage) {
newItem.chatMessage?.state == ChatMessage.State.Displayed newItem.eventLog.chatMessage?.state == ChatMessage.State.Displayed
} else false } else true
} }
} }

View file

@ -39,8 +39,10 @@ import org.linphone.utils.ImageUtils
class ChatMessageContentData( class ChatMessageContentData(
private val chatMessage: ChatMessage, private val chatMessage: ChatMessage,
private val contentIndex: Int, private val contentIndex: Int,
private val listener: OnContentClickedListener?
) { ) {
var listener: OnContentClickedListener? = null
val isImage = MutableLiveData<Boolean>() val isImage = MutableLiveData<Boolean>()
val isVideo = MutableLiveData<Boolean>() val isVideo = MutableLiveData<Boolean>()
val isAudio = MutableLiveData<Boolean>() val isAudio = MutableLiveData<Boolean>()

View file

@ -31,10 +31,9 @@ import org.linphone.core.ChatMessageListenerStub
import org.linphone.utils.AppUtils import org.linphone.utils.AppUtils
import org.linphone.utils.TimestampUtils import org.linphone.utils.TimestampUtils
class ChatMessageData( class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMessage.fromAddress) {
val chatMessage: ChatMessage,
private var contentListener: OnContentClickedListener? = null private var contentListener: OnContentClickedListener? = null
) : GenericContactData(chatMessage.fromAddress) {
val sendInProgress = MutableLiveData<Boolean>() val sendInProgress = MutableLiveData<Boolean>()
val transferInProgress = MutableLiveData<Boolean>() val transferInProgress = MutableLiveData<Boolean>()
@ -120,6 +119,14 @@ class ChatMessageData(
} }
} }
fun setContentClickListener(listener: OnContentClickedListener) {
contentListener = listener
for (data in contents.value.orEmpty()) {
data.listener = listener
}
}
private fun updateChatMessageState(state: ChatMessage.State) { private fun updateChatMessageState(state: ChatMessage.State) {
transferInProgress.value = state == ChatMessage.State.FileTransferInProgress transferInProgress.value = state == ChatMessage.State.FileTransferInProgress
@ -145,7 +152,9 @@ class ChatMessageData(
for (index in 0 until contentsList.size) { for (index in 0 until contentsList.size) {
val content = contentsList[index] val content = contentsList[index]
if (content.isFileTransfer || content.isFile) { if (content.isFileTransfer || content.isFile) {
list.add(ChatMessageContentData(chatMessage, index, contentListener)) val data = ChatMessageContentData(chatMessage, index)
data.listener = contentListener
list.add(data)
} else if (content.isText) { } else if (content.isText) {
val spannable = Spannable.Factory.getInstance().newSpannable(content.utf8Text) val spannable = Spannable.Factory.getInstance().newSpannable(content.utf8Text)
LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS) LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS)

View file

@ -23,12 +23,19 @@ import android.content.Context
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R import org.linphone.R
import org.linphone.contact.Contact import org.linphone.contact.GenericContactData
import org.linphone.core.EventLog import org.linphone.core.EventLog
import org.linphone.core.tools.Log
import org.linphone.utils.LinphoneUtils
class EventData(private val eventLog: EventLog) { class EventData(private val eventLog: EventLog) : GenericContactData(
if (eventLog.type == EventLog.Type.ConferenceSecurityEvent) {
eventLog.securityEventFaultyDeviceAddress!!
} else {
if (eventLog.participantAddress == null) {
eventLog.peerAddress!!
} else {
eventLog.participantAddress!!
}
}) {
val text = MutableLiveData<String>() val text = MutableLiveData<String>()
val isSecurity: Boolean by lazy { val isSecurity: Boolean by lazy {
@ -38,32 +45,12 @@ class EventData(private val eventLog: EventLog) {
} }
} }
private val contact: Contact? by lazy {
val address = eventLog.participantAddress ?: eventLog.securityEventFaultyDeviceAddress
if (address != null) {
coreContext.contactsManager.findContactByAddress(address)
} else {
Log.e("[Event ViewModel] Unexpected null address for event $eventLog")
null
}
}
private val displayName: String by lazy {
val address = eventLog.participantAddress ?: eventLog.securityEventFaultyDeviceAddress
if (address != null) {
LinphoneUtils.getDisplayName(address)
} else {
Log.e("[Event ViewModel] Unexpected null address for event $eventLog")
""
}
}
init { init {
updateEventText() updateEventText()
} }
private fun getName(): String { private fun getName(): String {
return contact?.fullName ?: displayName return contact.value?.fullName ?: displayName
} }
private fun updateEventText() { private fun updateEventText() {

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2010-2021 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.activities.main.chat.data
import org.linphone.contact.GenericContactData
import org.linphone.core.EventLog
class EventLogData(val eventLog: EventLog) {
val data: GenericContactData = if (eventLog.type == EventLog.Type.ConferenceChatMessage) {
ChatMessageData(eventLog.chatMessage!!)
} else {
EventData(eventLog)
}
}

View file

@ -47,6 +47,7 @@ import org.linphone.activities.*
import org.linphone.activities.main.MainActivity import org.linphone.activities.main.MainActivity
import org.linphone.activities.main.chat.ChatScrollListener import org.linphone.activities.main.chat.ChatScrollListener
import org.linphone.activities.main.chat.adapters.ChatMessagesListAdapter import org.linphone.activities.main.chat.adapters.ChatMessagesListAdapter
import org.linphone.activities.main.chat.data.EventLogData
import org.linphone.activities.main.chat.viewmodels.* import org.linphone.activities.main.chat.viewmodels.*
import org.linphone.activities.main.fragments.MasterFragment import org.linphone.activities.main.fragments.MasterFragment
import org.linphone.activities.main.viewmodels.DialogViewModel import org.linphone.activities.main.viewmodels.DialogViewModel
@ -345,7 +346,7 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
} }
override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) { override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) {
val list = ArrayList<EventLog>() val list = ArrayList<EventLogData>()
for (index in indexesOfItemToDelete) { for (index in indexesOfItemToDelete) {
val eventLog = adapter.currentList[index] val eventLog = adapter.currentList[index]
list.add(eventLog) list.add(eventLog)

View file

@ -24,6 +24,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import java.util.* import java.util.*
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.activities.main.chat.data.EventLogData
import org.linphone.core.* import org.linphone.core.*
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.mediastream.Version import org.linphone.mediastream.Version
@ -45,7 +46,7 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
private const val MESSAGES_PER_PAGE = 20 private const val MESSAGES_PER_PAGE = 20
} }
val events = MutableLiveData<ArrayList<EventLog>>() val events = MutableLiveData<ArrayList<EventLogData>>()
val messageUpdatedEvent: MutableLiveData<Event<Int>> by lazy { val messageUpdatedEvent: MutableLiveData<Event<Int>> by lazy {
MutableLiveData<Event<Int>>() MutableLiveData<Event<Int>>()
@ -67,8 +68,8 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
chatMessage ?: return chatMessage ?: return
chatMessage.userData = events.value.orEmpty().size chatMessage.userData = events.value.orEmpty().size
val existingEvent = events.value.orEmpty().find { val existingEvent = events.value.orEmpty().find { data ->
it.type == EventLog.Type.ConferenceChatMessage && it.chatMessage == chatMessage data.eventLog == eventLog
} }
if (existingEvent != null) { if (existingEvent != null) {
Log.w("[Chat Messages] Found already present chat message, don't add it it's probably the result of an auto download") Log.w("[Chat Messages] Found already present chat message, don't add it it's probably the result of an auto download")
@ -165,19 +166,19 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
LinphoneUtils.deleteFilesAttachedToChatMessage(chatMessage) LinphoneUtils.deleteFilesAttachedToChatMessage(chatMessage)
chatRoom.deleteMessage(chatMessage) chatRoom.deleteMessage(chatMessage)
val list = arrayListOf<EventLog>() val list = arrayListOf<EventLogData>()
list.addAll(events.value.orEmpty()) list.addAll(events.value.orEmpty())
list.removeAt(position) list.removeAt(position)
events.value = list events.value = list
} }
fun deleteEventLogs(listToDelete: ArrayList<EventLog>) { fun deleteEventLogs(listToDelete: ArrayList<EventLogData>) {
val list = arrayListOf<EventLog>() val list = arrayListOf<EventLogData>()
list.addAll(events.value.orEmpty()) list.addAll(events.value.orEmpty())
for (eventLog in listToDelete) { for (eventLog in listToDelete) {
LinphoneUtils.deleteFilesAttachedToEventLog(eventLog) LinphoneUtils.deleteFilesAttachedToEventLog(eventLog.eventLog)
eventLog.deleteFromDatabase() eventLog.eventLog.deleteFromDatabase()
list.remove(eventLog) list.remove(eventLog)
} }
@ -195,9 +196,9 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
} }
val history: Array<EventLog> = chatRoom.getHistoryRangeEvents(totalItemsCount, upperBound) val history: Array<EventLog> = chatRoom.getHistoryRangeEvents(totalItemsCount, upperBound)
val list = arrayListOf<EventLog>() val list = arrayListOf<EventLogData>()
for (message in history) { for (eventLog in history) {
list.add(message) list.add(EventLogData(eventLog))
} }
list.addAll(events.value.orEmpty()) list.addAll(events.value.orEmpty())
events.value = list events.value = list
@ -205,19 +206,19 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
} }
private fun addEvent(eventLog: EventLog) { private fun addEvent(eventLog: EventLog) {
val list = arrayListOf<EventLog>() val list = arrayListOf<EventLogData>()
list.addAll(events.value.orEmpty()) list.addAll(events.value.orEmpty())
if (!list.contains(eventLog)) { if (!list.contains(eventLog)) {
list.add(eventLog) list.add(EventLogData(eventLog))
} }
events.value = list events.value = list
} }
private fun getEvents(): ArrayList<EventLog> { private fun getEvents(): ArrayList<EventLogData> {
val list = arrayListOf<EventLog>() val list = arrayListOf<EventLogData>()
val history = chatRoom.getHistoryEvents(MESSAGES_PER_PAGE) val history = chatRoom.getHistoryEvents(MESSAGES_PER_PAGE)
for (message in history) { for (eventLog in history) {
list.add(message) list.add(EventLogData(eventLog))
} }
return list return list
} }