瀏覽代碼

Merge pull request #4136 from nextcloud/bugfix/noid/fixUnreadMessageScrollOnEnterChat

fix to scroll to last read message
Marcel Hibbe 9 月之前
父節點
當前提交
822af2c967

+ 60 - 30
app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt

@@ -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")
         }
     }
 

+ 2 - 0
app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt

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

+ 27 - 11
app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt

@@ -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) {
+    private suspend fun updateUiForLastReadMessage(newestMessageId: Long) {
+        val scrollToLastRead = conversationModel.lastReadMessage.toLong() < newestMessageId
+        if (scrollToLastRead) {
+            _lastReadMessageFlow.emit(conversationModel.lastReadMessage)
+        }
+    }
+
+    private fun updateUiForLastCommonRead() {
         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)
-            }
             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
     }
 }

+ 2 - 0
app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt

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