fix to scroll to last read message

This will fix to scroll to the last read message when a chat is opened.

Some refactorings were made that are not necessary for the fix (I tried to also show the "Unread messages" hint in the adapter but came to the conclusion this is not a good idea until chatkit is removed. Chatkit doesn't support to add some item in between but only at the end or start which will make it too complicated..)

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2024-09-03 18:52:39 +02:00
parent 014b58321d
commit 20724c5efb
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
4 changed files with 91 additions and 41 deletions

View File

@ -832,6 +832,14 @@ class ChatActivity :
.collect()
}
this.lifecycleScope.launch {
chatViewModel.getLastReadMessageFlow
.onEach { lastRead ->
scrollToAndCenterMessageWithId(lastRead.toString())
}
.collect()
}
chatViewModel.reactionDeletedViewState.observe(this) { state ->
when (state) {
is ChatViewModel.ReactionDeletedSuccessState -> {
@ -2059,6 +2067,18 @@ class ChatActivity :
}
}
private fun scrollToAndCenterMessageWithId(messageId: String) {
adapter?.let {
val position = it.getMessagePositionByIdInReverse(messageId)
if (position != -1) {
layoutManager?.scrollToPositionWithOffset(
position,
binding.messagesListView.height / 2
)
}
}
}
private fun writeContactToVcfFile(cursor: Cursor, file: File) {
val lookupKey = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.LOOKUP_KEY))
val uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_VCARD_URI, lookupKey)
@ -2492,34 +2512,11 @@ class ChatActivity :
private fun processMessagesFromTheFuture(chatMessageList: List<ChatMessage>) {
val newMessagesAvailable = (adapter?.itemCount ?: 0) > 0 && chatMessageList.isNotEmpty()
val insertNewMessagesNotice = if (newMessagesAvailable) {
chatMessageList.any { it.actorId != conversationUser!!.userId }
} else {
false
}
val scrollToEndOnUpdate = layoutManager?.findFirstVisibleItemPosition() == 0
val insertNewMessagesNotice = shouldInsertNewMessagesNotice(newMessagesAvailable, chatMessageList)
val scrollToEndOnUpdate = isScrolledToBottom()
if (insertNewMessagesNotice) {
val unreadChatMessage = ChatMessage()
unreadChatMessage.jsonMessageId = -1
unreadChatMessage.actorId = "-1"
unreadChatMessage.timestamp = chatMessageList[0].timestamp
unreadChatMessage.message = context.getString(R.string.nc_new_messages)
adapter?.addToStart(unreadChatMessage, false)
if (scrollToEndOnUpdate) {
binding.scrollDownButton.visibility = View.GONE
newMessagesCount = 0
} else {
if (binding.unreadMessagesPopup.isShown) {
newMessagesCount++
} else {
newMessagesCount = 1
binding.scrollDownButton.visibility = View.GONE
binding.unreadMessagesPopup.show()
}
}
updateUnreadMessageInfos(chatMessageList, scrollToEndOnUpdate)
}
for (chatMessage in chatMessageList) {
@ -2544,6 +2541,42 @@ class ChatActivity :
}
}
private fun isScrolledToBottom() = layoutManager?.findFirstVisibleItemPosition() == 0
private fun shouldInsertNewMessagesNotice(
newMessagesAvailable: Boolean,
chatMessageList: List<ChatMessage>
) = if (newMessagesAvailable) {
chatMessageList.any { it.actorId != conversationUser!!.userId }
} else {
false
}
private fun updateUnreadMessageInfos(
chatMessageList: List<ChatMessage>,
scrollToEndOnUpdate: Boolean
) {
val unreadChatMessage = ChatMessage()
unreadChatMessage.jsonMessageId = -1
unreadChatMessage.actorId = "-1"
unreadChatMessage.timestamp = chatMessageList[0].timestamp
unreadChatMessage.message = context.getString(R.string.nc_new_messages)
adapter?.addToStart(unreadChatMessage, false)
if (scrollToEndOnUpdate) {
binding.scrollDownButton.visibility = View.GONE
newMessagesCount = 0
} else {
if (binding.unreadMessagesPopup.isShown) {
newMessagesCount++
} else {
newMessagesCount = 1
binding.scrollDownButton.visibility = View.GONE
binding.unreadMessagesPopup.show()
}
}
}
private fun processMessagesNotFromTheFuture(chatMessageList: List<ChatMessage>) {
var countGroupedMessages = 0
@ -2579,10 +2612,7 @@ class ChatActivity :
private fun scrollToFirstUnreadMessage() {
adapter?.let {
layoutManager?.scrollToPositionWithOffset(
it.getMessagePositionByIdInReverse("-1"),
binding.messagesListView.height / 2
)
scrollToAndCenterMessageWithId("-1")
}
}

View File

@ -32,6 +32,8 @@ interface ChatMessageRepository : LifecycleAwareManager {
val lastCommonReadFlow: Flow<Int>
val lastReadMessageFlow: Flow<Int>
fun setData(conversationModel: ConversationModel, credentials: String, urlForChatting: String)
fun loadInitialMessages(withNetworkParams: Bundle): Job

View File

@ -81,6 +81,13 @@ class OfflineFirstChatRepository @Inject constructor(
private val _lastCommonReadFlow:
MutableSharedFlow<Int> = MutableSharedFlow()
override val lastReadMessageFlow:
Flow<Int>
get() = _lastReadMessageFlow
private val _lastReadMessageFlow:
MutableSharedFlow<Int> = MutableSharedFlow()
private var newXChatLastCommonRead: Int? = null
private var itIsPaused = false
private val scope = CoroutineScope(Dispatchers.IO)
@ -114,25 +121,33 @@ class OfflineFirstChatRepository @Inject constructor(
sync(withNetworkParams)
Log.d(TAG, "newestMessageId after sync: " + chatDao.getNewestMessageId(internalConversationId))
val newestMessageId = chatDao.getNewestMessageId(internalConversationId)
Log.d(TAG, "newestMessageId after sync: $newestMessageId")
showLast100MessagesBeforeAndEqual(
internalConversationId,
chatDao.getNewestMessageId(internalConversationId)
)
updateUiForLastCommonRead(200)
// delay is a dirty workaround to make sure messages are added to adapter on initial load before dealing
// with them (otherwise there is a race condition).
delay(DELAY_TO_ENSURE_MESSAGES_ARE_ADDED)
updateUiForLastCommonRead()
updateUiForLastReadMessage(newestMessageId)
initMessagePolling()
}
private fun updateUiForLastCommonRead(delay: Long) {
scope.launch {
// delay is a dirty workaround to make sure messages are added to adapter on initial load before setting
// their read status(otherwise there is a race condition between adding messages and setting their read
// status).
if (delay > 0) {
delay(delay)
private suspend fun updateUiForLastReadMessage(newestMessageId: Long) {
val scrollToLastRead = conversationModel.lastReadMessage.toLong() < newestMessageId
if (scrollToLastRead) {
_lastReadMessageFlow.emit(conversationModel.lastReadMessage)
}
}
private fun updateUiForLastCommonRead() {
scope.launch {
newXChatLastCommonRead?.let {
_lastCommonReadFlow.emit(it)
}
@ -163,7 +178,7 @@ class OfflineFirstChatRepository @Inject constructor(
}
showLast100MessagesBefore(internalConversationId, beforeMessageId)
updateUiForLastCommonRead(0)
updateUiForLastCommonRead()
}
override fun initMessagePolling(): Job =
@ -195,7 +210,7 @@ class OfflineFirstChatRepository @Inject constructor(
_messageFlow.emit(pair)
}
updateUiForLastCommonRead(0)
updateUiForLastCommonRead()
val newestMessage = chatDao.getNewestMessageId(internalConversationId).toInt()
@ -612,5 +627,6 @@ class OfflineFirstChatRepository @Inject constructor(
private const val HTTP_CODE_OK: Int = 200
private const val HTTP_CODE_NOT_MODIFIED = 304
private const val HTTP_CODE_PRECONDITION_FAILED = 412
private const val DELAY_TO_ENSURE_MESSAGES_ARE_ADDED: Long = 100
}
}

View File

@ -122,6 +122,8 @@ class ChatViewModel @Inject constructor(
val getLastCommonReadFlow = chatRepository.lastCommonReadFlow
val getLastReadMessageFlow = chatRepository.lastReadMessageFlow
val getConversationFlow = conversationRepository.conversationFlow
.onEach {
_getRoomViewState.value = GetRoomSuccessState