Skip to content

Commit

Permalink
Merge pull request #4141 from nextcloud/backport/4137/fixUnreadMessag…
Browse files Browse the repository at this point in the history
…eScrollOnEnterChat

fix to scroll to last read message
  • Loading branch information
mahibi authored Sep 4, 2024
2 parents 57e152b + ba80371 commit fbf7bc4
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 44 deletions.
94 changes: 61 additions & 33 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ class ChatActivity :

chatViewModel.loadMessages(
withCredentials = credentials!!,
withUrl = urlForChatting,
withUrl = urlForChatting
)
}

Expand Down Expand Up @@ -831,6 +831,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 -> {
Expand Down Expand Up @@ -2057,6 +2065,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)
Expand Down Expand Up @@ -2490,38 +2510,13 @@ 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) {
chatMessage.activeUser = conversationUser

Expand All @@ -2544,6 +2539,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

Expand Down Expand Up @@ -2579,10 +2610,7 @@ class ChatActivity :

private fun scrollToFirstUnreadMessage() {
adapter?.let {
layoutManager?.scrollToPositionWithOffset(
it.getMessagePositionByIdInReverse("-1"),
binding.messagesListView.height / 2
)
scrollToAndCenterMessageWithId("-1")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ interface ChatMessageRepository : LifecycleAwareManager {

val lastCommonReadFlow: Flow<Int>

val lastReadMessageFlow: Flow<Int>

fun setData(
conversationModel: ConversationModel,
credentials: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -118,25 +125,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)
}
Expand Down Expand Up @@ -167,7 +182,7 @@ class OfflineFirstChatRepository @Inject constructor(
}

showLast100MessagesBefore(internalConversationId, beforeMessageId)
updateUiForLastCommonRead(0)
updateUiForLastCommonRead()
}

override fun initMessagePolling(): Job =
Expand Down Expand Up @@ -199,7 +214,7 @@ class OfflineFirstChatRepository @Inject constructor(
_messageFlow.emit(pair)
}

updateUiForLastCommonRead(0)
updateUiForLastCommonRead()

val newestMessage = chatDao.getNewestMessageId(internalConversationId).toInt()

Expand Down Expand Up @@ -619,5 +634,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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ class ChatViewModel @Inject constructor(

val getLastCommonReadFlow = chatRepository.lastCommonReadFlow

val getLastReadMessageFlow = chatRepository.lastReadMessageFlow

val getConversationFlow = conversationRepository.conversationFlow
.onEach {
_getRoomViewState.value = GetRoomSuccessState
Expand Down

0 comments on commit fbf7bc4

Please sign in to comment.