From 1c7fe3fd3e421745b0e09b5082955b110cdd33ff Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 8 Nov 2023 15:17:22 +0100 Subject: [PATCH] Re-order conversations list when needed + scroll to bottom when a new message is sent or received in a conversation --- .../chat/fragment/ConversationFragment.kt | 24 ++++++++++++++ .../fragment/ConversationsListFragment.kt | 7 ++++ .../ui/main/chat/model/ConversationModel.kt | 8 ++++- .../viewmodel/ConversationInfoViewModel.kt | 24 ++++++++------ .../chat/viewmodel/ConversationViewModel.kt | 24 ++++++++------ .../viewmodel/ConversationsListViewModel.kt | 32 +++++++++++++++++++ 6 files changed, 100 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt index 6d628bc69..02b6b8f4a 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt @@ -43,6 +43,7 @@ import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout.OnTabSelectedListener @@ -99,6 +100,18 @@ class ConversationFragment : GenericFragment() { } } + private val dataObserver = object : AdapterDataObserver() { + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + if (positionStart == 0 && adapter.itemCount == itemCount) { + // First time we fill the list with messages + Log.i("$TAG [$itemCount] events have been loaded") + } else { + Log.i("$TAG [$itemCount] new events have been loaded, scrolling to bottom") + binding.eventsList.smoothScrollToPosition(adapter.itemCount - 1) + } + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -302,9 +315,20 @@ class ConversationFragment : GenericFragment() { if (viewModel.scrollingPosition != SCROLLING_POSITION_NOT_SET) { binding.eventsList.scrollToPosition(viewModel.scrollingPosition) } + + try { + adapter.registerAdapterDataObserver(dataObserver) + } catch (e: IllegalStateException) { + Log.e("$TAG Failed to register data observer to adapter: $e") + } } override fun onPause() { + try { + adapter.unregisterAdapterDataObserver(dataObserver) + } catch (e: IllegalStateException) { + Log.e("$TAG Failed to unregister data observer to adapter: $e") + } coreContext.notificationsManager.resetCurrentlyDisplayedChatRoomId() val layoutManager = binding.eventsList.layoutManager as LinearLayoutManager diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsListFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsListFragment.kt index 5539a0306..ee4f12ffd 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsListFragment.kt @@ -129,6 +129,13 @@ class ConversationsListFragment : AbstractTopBarFragment() { } } + listViewModel.chatRoomsReOrderedEvent.observe(viewLifecycleOwner) { + it.consume { + Log.i("$TAG Conversations list have been re-ordered, scrolling to top") + binding.conversationsList.scrollToPosition(0) + } + } + // TopBarFragment related setViewModelAndTitle( diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/ConversationModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/ConversationModel.kt index c20b9f0b7..db882ac73 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/ConversationModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/ConversationModel.kt @@ -92,17 +92,24 @@ class ConversationModel @WorkerThread constructor(val chatRoom: ChatRoom) { @WorkerThread override fun onMessagesReceived(chatRoom: ChatRoom, chatMessages: Array) { updateLastMessage() + updateLastUpdatedTime() } @WorkerThread override fun onChatMessageSending(chatRoom: ChatRoom, eventLog: EventLog) { updateLastMessage() + updateLastUpdatedTime() } @WorkerThread override fun onChatRoomRead(chatRoom: ChatRoom) { unreadMessageCount.postValue(chatRoom.unreadMessagesCount) } + + override fun onSubjectChanged(chatRoom: ChatRoom, eventLog: EventLog) { + subject.postValue(chatRoom.subject) + updateLastUpdatedTime() + } } private val chatMessageListener = object : ChatMessageListenerStub() { @@ -159,7 +166,6 @@ class ConversationModel @WorkerThread constructor(val chatRoom: ChatRoom) { isEphemeral.postValue(chatRoom.isEphemeralEnabled) updateLastMessage() - updateLastUpdatedTime() unreadMessageCount.postValue(chatRoom.unreadMessagesCount) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt index 40b900f1d..7270456a1 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt @@ -135,17 +135,23 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() { return@postOnCoreThread } - if (room != null && (!::chatRoom.isInitialized || chatRoom != room)) { - Log.i("$TAG Chat room object available in sharedViewModel, using it") - chatRoom = room - chatRoom.addListener(chatRoomListener) - configureChatRoom() - chatRoomFoundEvent.postValue(Event(true)) - return@postOnCoreThread - } - val localAddress = Factory.instance().createAddress(localSipUri) val remoteAddress = Factory.instance().createAddress(remoteSipUri) + + if (room != null && (!::chatRoom.isInitialized || chatRoom != room)) { + if (localAddress?.weakEqual(room.localAddress) == true && remoteAddress?.weakEqual( + room.peerAddress + ) == true + ) { + Log.i("$TAG Chat room object available in sharedViewModel, using it") + chatRoom = room + chatRoom.addListener(chatRoomListener) + configureChatRoom() + chatRoomFoundEvent.postValue(Event(true)) + return@postOnCoreThread + } + } + if (localAddress != null && remoteAddress != null) { Log.i("$TAG Searching for chat room in Core using local & peer SIP addresses") val found = core.searchChatRoom( diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt index 7c7e40fb1..97785d91f 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt @@ -222,17 +222,23 @@ class ConversationViewModel @UiThread constructor() : ViewModel() { return@postOnCoreThread } - if (room != null && (!::chatRoom.isInitialized || chatRoom != room)) { - Log.i("$TAG Chat room object available in sharedViewModel, using it") - chatRoom = room - chatRoom.addListener(chatRoomListener) - configureChatRoom() - chatRoomFoundEvent.postValue(Event(true)) - return@postOnCoreThread - } - val localAddress = Factory.instance().createAddress(localSipUri) val remoteAddress = Factory.instance().createAddress(remoteSipUri) + + if (room != null && (!::chatRoom.isInitialized || chatRoom != room)) { + if (localAddress?.weakEqual(room.localAddress) == true && remoteAddress?.weakEqual( + room.peerAddress + ) == true + ) { + Log.i("$TAG Chat room object available in sharedViewModel, using it") + chatRoom = room + chatRoom.addListener(chatRoomListener) + configureChatRoom() + chatRoomFoundEvent.postValue(Event(true)) + return@postOnCoreThread + } + } + if (localAddress != null && remoteAddress != null) { Log.i("$TAG Searching for chat room in Core using local & peer SIP addresses") val found = core.searchChatRoom( diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt index 70c40517d..802a09d97 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt @@ -24,6 +24,7 @@ import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.contacts.ContactsManager +import org.linphone.core.ChatMessage import org.linphone.core.ChatRoom import org.linphone.core.ChatRoom.Capabilities import org.linphone.core.Core @@ -32,6 +33,7 @@ import org.linphone.core.tools.Log import org.linphone.ui.main.chat.model.ConversationModel import org.linphone.ui.main.model.isInSecureMode import org.linphone.ui.main.viewmodel.AbstractTopBarViewModel +import org.linphone.utils.Event import org.linphone.utils.LinphoneUtils class ConversationsListViewModel @UiThread constructor() : AbstractTopBarViewModel() { @@ -43,6 +45,10 @@ class ConversationsListViewModel @UiThread constructor() : AbstractTopBarViewMod val fetchInProgress = MutableLiveData() + val chatRoomsReOrderedEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + private val coreListener = object : CoreListenerStub() { @WorkerThread override fun onChatRoomStateChanged( @@ -61,6 +67,20 @@ class ConversationsListViewModel @UiThread constructor() : AbstractTopBarViewMod else -> {} } } + + @WorkerThread + override fun onMessageSent(core: Core, chatRoom: ChatRoom, message: ChatMessage) { + reorderChatRooms() + } + + @WorkerThread + override fun onMessagesReceived( + core: Core, + chatRoom: ChatRoom, + messages: Array + ) { + reorderChatRooms() + } } private val contactsListener = object : ContactsManager.ContactsListener { @@ -145,4 +165,16 @@ class ConversationsListViewModel @UiThread constructor() : AbstractTopBarViewMod conversations.postValue(list) fetchInProgress.postValue(false) } + + @WorkerThread + private fun reorderChatRooms() { + Log.i("$TAG Re-ordering chat rooms") + val sortedList = arrayListOf() + sortedList.addAll(conversations.value.orEmpty()) + sortedList.sortBy { + it.lastUpdateTime.value + } + conversations.postValue(sortedList) + chatRoomsReOrderedEvent.postValue(Event(true)) + } }