Re-order conversations list when needed + scroll to bottom when a new message is sent or received in a conversation

This commit is contained in:
Sylvain Berfini 2023-11-08 15:17:22 +01:00
parent 8cbe832a67
commit 1c7fe3fd3e
6 changed files with 100 additions and 19 deletions

View file

@ -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

View file

@ -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(

View file

@ -92,17 +92,24 @@ class ConversationModel @WorkerThread constructor(val chatRoom: ChatRoom) {
@WorkerThread
override fun onMessagesReceived(chatRoom: ChatRoom, chatMessages: Array<out ChatMessage>) {
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)

View file

@ -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(

View file

@ -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(

View file

@ -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<Boolean>()
val chatRoomsReOrderedEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
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<out ChatMessage>
) {
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<ConversationModel>()
sortedList.addAll(conversations.value.orEmpty())
sortedList.sortBy {
it.lastUpdateTime.value
}
conversations.postValue(sortedList)
chatRoomsReOrderedEvent.postValue(Event(true))
}
}