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.databinding.ChatRoomListCellBinding
import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils
class ChatRoomsListAdapter(
selectionVM: ListTopBarViewModel,
private val viewLifecycleOwner: LifecycleOwner
) : SelectionListAdapter<ChatRoom, RecyclerView.ViewHolder>(selectionVM, ChatRoomDiffCallback()) {
) : SelectionListAdapter<ChatRoomData, RecyclerView.ViewHolder>(selectionVM, ChatRoomDiffCallback()) {
val selectedChatRoomEvent: MutableLiveData<Event<ChatRoom>> by lazy {
MutableLiveData<Event<ChatRoom>>()
}
@ -57,11 +56,6 @@ class ChatRoomsListAdapter(
(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) {
isForwardPending = pending
notifyItemRangeChanged(0, itemCount)
@ -70,9 +64,10 @@ class ChatRoomsListAdapter(
inner class ViewHolder(
private val binding: ChatRoomListCellBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(chatRoom: ChatRoom) {
fun bind(chatRoomData: ChatRoomData) {
with(binding) {
data = ChatRoomData(chatRoom)
chatRoomData.update()
data = chatRoomData
lifecycleOwner = viewLifecycleOwner
@ -90,7 +85,7 @@ class ChatRoomsListAdapter(
if (selectionViewModel.isEditionEnabled.value == true) {
selectionViewModel.onToggleSelect(bindingAdapterPosition)
} 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(
oldItem: ChatRoom,
newItem: ChatRoom
oldItem: ChatRoomData,
newItem: ChatRoomData
): Boolean {
return oldItem == newItem
return oldItem.id == newItem.id
}
override fun areContentsTheSame(
oldItem: ChatRoom,
newItem: ChatRoom
oldItem: ChatRoomData,
newItem: ChatRoomData
): Boolean {
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.R
import org.linphone.contact.ContactDataInterface
import org.linphone.contact.ContactsUpdatedListenerStub
import org.linphone.core.*
import org.linphone.core.tools.Log
import org.linphone.utils.AppUtils
import org.linphone.utils.LinphoneUtils
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 displayName: MutableLiveData<String> = MutableLiveData<String>()
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 coroutineScope: CoroutineScope = coreContext.coroutineScope
val id = LinphoneUtils.getChatRoomId(chatRoom)
val unreadMessagesCount = MutableLiveData<Int>()
val subject = MutableLiveData<String>()
@ -82,7 +85,26 @@ class ChatRoomData(private val chatRoom: ChatRoom) : ContactDataInterface {
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 {
coreContext.contactsManager.addListener(contactsListener)
}
fun destroy() {
coreContext.contactsManager.removeListener(contactsListener)
}
fun update() {
unreadMessagesCount.value = chatRoom.unreadMessagesCount
presenceStatus.value = ConsolidatedPresence.Offline
@ -96,6 +118,11 @@ class ChatRoomData(private val chatRoom: ChatRoom) : ContactDataInterface {
notificationsMuted.value = areNotificationsMuted()
}
fun markAsRead() {
chatRoom.markAsRead()
unreadMessagesCount.value = 0
}
private fun updateSecurityIcon() {
val level = chatRoom.securityLevel
securityLevel.value = level
@ -133,8 +160,9 @@ class ChatRoomData(private val chatRoom: ChatRoom) : ContactDataInterface {
val remoteAddress = if (basicChatRoom) {
chatRoom.peerAddress
} else {
if (chatRoom.participants.isNotEmpty()) {
chatRoom.participants[0].address
val participants = chatRoom.participants
if (participants.isNotEmpty()) {
participants.first().address
} else {
Log.e("[Chat Room] ${chatRoom.peerAddress} doesn't have any participant (state ${chatRoom.state})!")
null

View file

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

View file

@ -22,16 +22,16 @@ package org.linphone.activities.main.chat.viewmodels
import androidx.lifecycle.MutableLiveData
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.activities.main.chat.data.ChatRoomData
import org.linphone.activities.main.viewmodels.MessageNotifierViewModel
import org.linphone.compatibility.Compatibility
import org.linphone.contact.ContactsUpdatedListenerStub
import org.linphone.core.*
import org.linphone.core.tools.Log
import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils
class ChatRoomsListViewModel : MessageNotifierViewModel() {
val chatRooms = MutableLiveData<ArrayList<ChatRoom>>()
val chatRooms = MutableLiveData<ArrayList<ChatRoomData>>()
val fileSharingPending = MutableLiveData<Boolean>()
@ -45,10 +45,31 @@ class ChatRoomsListViewModel : MessageNotifierViewModel() {
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() {
override fun onChatRoomStateChanged(core: Core, chatRoom: ChatRoom, state: ChatRoom.State) {
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) {
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)
@ -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
init {
groupChatAvailable.value = LinphoneUtils.isGroupChatAvailable()
updateChatRooms()
coreContext.core.addListener(listener)
coreContext.contactsManager.addListener(contactsListener)
}
override fun onCleared() {
coreContext.contactsManager.removeListener(contactsListener)
coreContext.core.removeListener(listener)
super.onCleared()
@ -139,8 +144,12 @@ class ChatRoomsListViewModel : MessageNotifierViewModel() {
}
fun updateChatRooms() {
val list = arrayListOf<ChatRoom>()
list.addAll(coreContext.core.chatRooms)
chatRooms.value.orEmpty().forEach(ChatRoomData::destroy)
val list = arrayListOf<ChatRoomData>()
for (chatRoom in coreContext.core.chatRooms) {
list.add(ChatRoomData(chatRoom))
}
chatRooms.value = list
}
@ -151,15 +160,16 @@ class ChatRoomsListViewModel : MessageNotifierViewModel() {
}
private fun reorderChatRooms() {
val list = arrayListOf<ChatRoom>()
val list = arrayListOf<ChatRoomData>()
list.addAll(chatRooms.value.orEmpty())
list.sortByDescending { chatRoom -> chatRoom.lastUpdateTime }
list.sortByDescending { data -> data.chatRoom.lastUpdateTime }
chatRooms.value = list
}
private fun findChatRoomIndex(chatRoom: ChatRoom): Int {
for ((index, cr) in chatRooms.value.orEmpty().withIndex()) {
if (LinphoneUtils.areChatRoomsTheSame(cr, chatRoom)) {
val id = LinphoneUtils.getChatRoomId(chatRoom)
for ((index, data) in chatRooms.value.orEmpty().withIndex()) {
if (id == data.id) {
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 {
return getChatRoomId(room.localAddress, room.peerAddress)
}