Proper fix for chat rooms blinking

This commit is contained in:
Sylvain Berfini 2023-03-31 16:33:24 +02:00
parent 8338ea8814
commit 31e30e6214
5 changed files with 81 additions and 55 deletions

View file

@ -33,12 +33,11 @@ import org.linphone.activities.main.viewmodels.ListTopBarViewModel
import org.linphone.core.ChatRoom import org.linphone.core.ChatRoom
import org.linphone.databinding.ChatRoomListCellBinding import org.linphone.databinding.ChatRoomListCellBinding
import org.linphone.utils.Event import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils
class ChatRoomsListAdapter( class ChatRoomsListAdapter(
selectionVM: ListTopBarViewModel, selectionVM: ListTopBarViewModel,
private val viewLifecycleOwner: LifecycleOwner private val viewLifecycleOwner: LifecycleOwner
) : SelectionListAdapter<ChatRoom, RecyclerView.ViewHolder>(selectionVM, ChatRoomDiffCallback()) { ) : SelectionListAdapter<ChatRoomData, RecyclerView.ViewHolder>(selectionVM, ChatRoomDiffCallback()) {
val selectedChatRoomEvent: MutableLiveData<Event<ChatRoom>> by lazy { val selectedChatRoomEvent: MutableLiveData<Event<ChatRoom>> by lazy {
MutableLiveData<Event<ChatRoom>>() MutableLiveData<Event<ChatRoom>>()
} }
@ -57,11 +56,6 @@ class ChatRoomsListAdapter(
(holder as ViewHolder).bind(getItem(position)) (holder as ViewHolder).bind(getItem(position))
} }
override fun getItemId(position: Int): Long {
val room = getItem(position)
return LinphoneUtils.getChatRoomId(room).hashCode().toLong()
}
fun forwardPending(pending: Boolean) { fun forwardPending(pending: Boolean) {
isForwardPending = pending isForwardPending = pending
notifyItemRangeChanged(0, itemCount) notifyItemRangeChanged(0, itemCount)
@ -70,9 +64,10 @@ class ChatRoomsListAdapter(
inner class ViewHolder( inner class ViewHolder(
private val binding: ChatRoomListCellBinding private val binding: ChatRoomListCellBinding
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
fun bind(chatRoom: ChatRoom) { fun bind(chatRoomData: ChatRoomData) {
with(binding) { with(binding) {
data = ChatRoomData(chatRoom) chatRoomData.update()
data = chatRoomData
lifecycleOwner = viewLifecycleOwner lifecycleOwner = viewLifecycleOwner
@ -90,7 +85,7 @@ class ChatRoomsListAdapter(
if (selectionViewModel.isEditionEnabled.value == true) { if (selectionViewModel.isEditionEnabled.value == true) {
selectionViewModel.onToggleSelect(bindingAdapterPosition) selectionViewModel.onToggleSelect(bindingAdapterPosition)
} else { } else {
selectedChatRoomEvent.value = Event(chatRoom) selectedChatRoomEvent.value = Event(chatRoomData.chatRoom)
} }
} }
@ -109,17 +104,17 @@ class ChatRoomsListAdapter(
} }
} }
private class ChatRoomDiffCallback : DiffUtil.ItemCallback<ChatRoom>() { private class ChatRoomDiffCallback : DiffUtil.ItemCallback<ChatRoomData>() {
override fun areItemsTheSame( override fun areItemsTheSame(
oldItem: ChatRoom, oldItem: ChatRoomData,
newItem: ChatRoom newItem: ChatRoomData
): Boolean { ): Boolean {
return oldItem == newItem return oldItem.id == newItem.id
} }
override fun areContentsTheSame( override fun areContentsTheSame(
oldItem: ChatRoom, oldItem: ChatRoomData,
newItem: ChatRoom newItem: ChatRoomData
): Boolean { ): Boolean {
return false // To force redraw return false // To force redraw
} }

View file

@ -31,13 +31,14 @@ import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R import org.linphone.R
import org.linphone.contact.ContactDataInterface import org.linphone.contact.ContactDataInterface
import org.linphone.contact.ContactsUpdatedListenerStub
import org.linphone.core.* import org.linphone.core.*
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.utils.AppUtils import org.linphone.utils.AppUtils
import org.linphone.utils.LinphoneUtils import org.linphone.utils.LinphoneUtils
import org.linphone.utils.TimestampUtils import org.linphone.utils.TimestampUtils
class ChatRoomData(private val chatRoom: ChatRoom) : ContactDataInterface { class ChatRoomData(val chatRoom: ChatRoom) : ContactDataInterface {
override val contact: MutableLiveData<Friend> = MutableLiveData<Friend>() override val contact: MutableLiveData<Friend> = MutableLiveData<Friend>()
override val displayName: MutableLiveData<String> = MutableLiveData<String>() override val displayName: MutableLiveData<String> = MutableLiveData<String>()
override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>() override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>()
@ -46,6 +47,8 @@ class ChatRoomData(private val chatRoom: ChatRoom) : ContactDataInterface {
override val presenceStatus: MutableLiveData<ConsolidatedPresence> = MutableLiveData<ConsolidatedPresence>() override val presenceStatus: MutableLiveData<ConsolidatedPresence> = MutableLiveData<ConsolidatedPresence>()
override val coroutineScope: CoroutineScope = coreContext.coroutineScope override val coroutineScope: CoroutineScope = coreContext.coroutineScope
val id = LinphoneUtils.getChatRoomId(chatRoom)
val unreadMessagesCount = MutableLiveData<Int>() val unreadMessagesCount = MutableLiveData<Int>()
val subject = MutableLiveData<String>() val subject = MutableLiveData<String>()
@ -82,7 +85,26 @@ class ChatRoomData(private val chatRoom: ChatRoom) : ContactDataInterface {
chatRoom.hasCapability(ChatRoomCapabilities.Encrypted.toInt()) chatRoom.hasCapability(ChatRoomCapabilities.Encrypted.toInt())
} }
private val contactsListener = object : ContactsUpdatedListenerStub() {
override fun onContactsUpdated() {
if (oneToOneChatRoom && contact.value == null) {
searchMatchingContact()
if (contact.value != null) {
formatLastMessage(chatRoom.lastMessageInHistory)
}
}
}
}
init { init {
coreContext.contactsManager.addListener(contactsListener)
}
fun destroy() {
coreContext.contactsManager.removeListener(contactsListener)
}
fun update() {
unreadMessagesCount.value = chatRoom.unreadMessagesCount unreadMessagesCount.value = chatRoom.unreadMessagesCount
presenceStatus.value = ConsolidatedPresence.Offline presenceStatus.value = ConsolidatedPresence.Offline
@ -96,6 +118,11 @@ class ChatRoomData(private val chatRoom: ChatRoom) : ContactDataInterface {
notificationsMuted.value = areNotificationsMuted() notificationsMuted.value = areNotificationsMuted()
} }
fun markAsRead() {
chatRoom.markAsRead()
unreadMessagesCount.value = 0
}
private fun updateSecurityIcon() { private fun updateSecurityIcon() {
val level = chatRoom.securityLevel val level = chatRoom.securityLevel
securityLevel.value = level securityLevel.value = level
@ -133,8 +160,9 @@ class ChatRoomData(private val chatRoom: ChatRoom) : ContactDataInterface {
val remoteAddress = if (basicChatRoom) { val remoteAddress = if (basicChatRoom) {
chatRoom.peerAddress chatRoom.peerAddress
} else { } else {
if (chatRoom.participants.isNotEmpty()) { val participants = chatRoom.participants
chatRoom.participants[0].address if (participants.isNotEmpty()) {
participants.first().address
} else { } else {
Log.e("[Chat Room] ${chatRoom.peerAddress} doesn't have any participant (state ${chatRoom.state})!") Log.e("[Chat Room] ${chatRoom.peerAddress} doesn't have any participant (state ${chatRoom.state})!")
null null

View file

@ -156,7 +156,6 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
_adapter = ChatRoomsListAdapter(listSelectionViewModel, viewLifecycleOwner) _adapter = ChatRoomsListAdapter(listSelectionViewModel, viewLifecycleOwner)
// SubmitList is done on a background thread // SubmitList is done on a background thread
// We need this adapter data observer to know when to scroll // We need this adapter data observer to know when to scroll
adapter.setHasStableIds(true)
adapter.registerAdapterDataObserver(observer) adapter.registerAdapterDataObserver(observer)
binding.chatList.setHasFixedSize(true) binding.chatList.setHasFixedSize(true)
@ -185,9 +184,8 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
if (index < 0 || index >= adapter.currentList.size) { if (index < 0 || index >= adapter.currentList.size) {
Log.e("[Chat] Index is out of bound, can't mark chat room as read") Log.e("[Chat] Index is out of bound, can't mark chat room as read")
} else { } else {
val chatRoom = adapter.currentList[viewHolder.bindingAdapterPosition] val data = adapter.currentList[viewHolder.bindingAdapterPosition]
chatRoom.markAsRead() data.markAsRead()
adapter.notifyItemChanged(viewHolder.bindingAdapterPosition)
} }
} }
@ -209,7 +207,7 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
viewModel.showDeleteButton( viewModel.showDeleteButton(
{ {
val deletedChatRoom = val deletedChatRoom =
adapter.currentList[index] adapter.currentList[index].chatRoom
listViewModel.deleteChatRoom(deletedChatRoom) listViewModel.deleteChatRoom(deletedChatRoom)
if (!binding.slidingPane.isSlideable && if (!binding.slidingPane.isSlideable &&
deletedChatRoom == sharedViewModel.selectedChatRoom.value deletedChatRoom == sharedViewModel.selectedChatRoom.value
@ -382,7 +380,7 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
val list = ArrayList<ChatRoom>() val list = ArrayList<ChatRoom>()
var closeSlidingPane = false var closeSlidingPane = false
for (index in indexesOfItemToDelete) { for (index in indexesOfItemToDelete) {
val chatRoom = adapter.currentList[index] val chatRoom = adapter.currentList[index].chatRoom
list.add(chatRoom) list.add(chatRoom)
if (chatRoom == sharedViewModel.selectedChatRoom.value) { if (chatRoom == sharedViewModel.selectedChatRoom.value) {

View file

@ -22,16 +22,16 @@ package org.linphone.activities.main.chat.viewmodels
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.activities.main.chat.data.ChatRoomData
import org.linphone.activities.main.viewmodels.MessageNotifierViewModel import org.linphone.activities.main.viewmodels.MessageNotifierViewModel
import org.linphone.compatibility.Compatibility import org.linphone.compatibility.Compatibility
import org.linphone.contact.ContactsUpdatedListenerStub
import org.linphone.core.* import org.linphone.core.*
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.utils.Event import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils import org.linphone.utils.LinphoneUtils
class ChatRoomsListViewModel : MessageNotifierViewModel() { class ChatRoomsListViewModel : MessageNotifierViewModel() {
val chatRooms = MutableLiveData<ArrayList<ChatRoom>>() val chatRooms = MutableLiveData<ArrayList<ChatRoomData>>()
val fileSharingPending = MutableLiveData<Boolean>() val fileSharingPending = MutableLiveData<Boolean>()
@ -45,10 +45,31 @@ class ChatRoomsListViewModel : MessageNotifierViewModel() {
MutableLiveData<Event<Int>>() MutableLiveData<Event<Int>>()
} }
private val chatRoomListener = object : ChatRoomListenerStub() {
override fun onStateChanged(chatRoom: ChatRoom, newState: ChatRoom.State) {
if (newState == ChatRoom.State.Deleted) {
Log.i("[Chat Rooms] Chat room [${LinphoneUtils.getChatRoomId(chatRoom)}] is in Deleted state, removing it from list")
val list = arrayListOf<ChatRoomData>()
val id = LinphoneUtils.getChatRoomId(chatRoom)
for (data in chatRooms.value.orEmpty()) {
if (data.id != id) {
list.add(data)
}
}
chatRooms.value = list
}
}
}
private val listener: CoreListenerStub = object : CoreListenerStub() { private val listener: CoreListenerStub = object : CoreListenerStub() {
override fun onChatRoomStateChanged(core: Core, chatRoom: ChatRoom, state: ChatRoom.State) { override fun onChatRoomStateChanged(core: Core, chatRoom: ChatRoom, state: ChatRoom.State) {
if (state == ChatRoom.State.Created) { if (state == ChatRoom.State.Created) {
updateChatRooms() Log.i("[Chat Rooms] Chat room [${LinphoneUtils.getChatRoomId(chatRoom)}] is in Created state, adding it to list")
val data = ChatRoomData(chatRoom)
val list = arrayListOf<ChatRoomData>()
list.add(data)
list.addAll(chatRooms.value.orEmpty())
chatRooms.value = list
} else if (state == ChatRoom.State.TerminationFailed) { } else if (state == ChatRoom.State.TerminationFailed) {
Log.e("[Chat Rooms] Group chat room removal for address ${chatRoom.peerAddress.asStringUriOnly()} has failed !") Log.e("[Chat Rooms] Group chat room removal for address ${chatRoom.peerAddress.asStringUriOnly()} has failed !")
onMessageToNotifyEvent.value = Event(R.string.chat_room_removal_failed_snack) onMessageToNotifyEvent.value = Event(R.string.chat_room_removal_failed_snack)
@ -80,31 +101,15 @@ class ChatRoomsListViewModel : MessageNotifierViewModel() {
} }
} }
private val chatRoomListener = object : ChatRoomListenerStub() {
override fun onStateChanged(chatRoom: ChatRoom, newState: ChatRoom.State) {
if (newState == ChatRoom.State.Deleted) {
updateChatRooms()
}
}
}
private val contactsListener = object : ContactsUpdatedListenerStub() {
override fun onContactsUpdated() {
updateChatRooms()
}
}
private var chatRoomsToDeleteCount = 0 private var chatRoomsToDeleteCount = 0
init { init {
groupChatAvailable.value = LinphoneUtils.isGroupChatAvailable() groupChatAvailable.value = LinphoneUtils.isGroupChatAvailable()
updateChatRooms() updateChatRooms()
coreContext.core.addListener(listener) coreContext.core.addListener(listener)
coreContext.contactsManager.addListener(contactsListener)
} }
override fun onCleared() { override fun onCleared() {
coreContext.contactsManager.removeListener(contactsListener)
coreContext.core.removeListener(listener) coreContext.core.removeListener(listener)
super.onCleared() super.onCleared()
@ -139,8 +144,12 @@ class ChatRoomsListViewModel : MessageNotifierViewModel() {
} }
fun updateChatRooms() { fun updateChatRooms() {
val list = arrayListOf<ChatRoom>() chatRooms.value.orEmpty().forEach(ChatRoomData::destroy)
list.addAll(coreContext.core.chatRooms)
val list = arrayListOf<ChatRoomData>()
for (chatRoom in coreContext.core.chatRooms) {
list.add(ChatRoomData(chatRoom))
}
chatRooms.value = list chatRooms.value = list
} }
@ -151,15 +160,16 @@ class ChatRoomsListViewModel : MessageNotifierViewModel() {
} }
private fun reorderChatRooms() { private fun reorderChatRooms() {
val list = arrayListOf<ChatRoom>() val list = arrayListOf<ChatRoomData>()
list.addAll(chatRooms.value.orEmpty()) list.addAll(chatRooms.value.orEmpty())
list.sortByDescending { chatRoom -> chatRoom.lastUpdateTime } list.sortByDescending { data -> data.chatRoom.lastUpdateTime }
chatRooms.value = list chatRooms.value = list
} }
private fun findChatRoomIndex(chatRoom: ChatRoom): Int { private fun findChatRoomIndex(chatRoom: ChatRoom): Int {
for ((index, cr) in chatRooms.value.orEmpty().withIndex()) { val id = LinphoneUtils.getChatRoomId(chatRoom)
if (LinphoneUtils.areChatRoomsTheSame(cr, chatRoom)) { for ((index, data) in chatRooms.value.orEmpty().withIndex()) {
if (id == data.id) {
return index return index
} }
} }

View file

@ -224,11 +224,6 @@ class LinphoneUtils {
) )
} }
fun areChatRoomsTheSame(chatRoom1: ChatRoom, chatRoom2: ChatRoom): Boolean {
return chatRoom1.localAddress.weakEqual(chatRoom2.localAddress) &&
chatRoom1.peerAddress.weakEqual(chatRoom2.peerAddress)
}
fun getChatRoomId(room: ChatRoom): String { fun getChatRoomId(room: ChatRoom): String {
return getChatRoomId(room.localAddress, room.peerAddress) return getChatRoomId(room.localAddress, room.peerAddress)
} }