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

fix to scroll to last read message
This commit is contained in:
Marcel Hibbe 2024-09-04 09:49:24 +02:00 committed by GitHub
commit 822af2c967
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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) {
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
}
}

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