Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix to scroll to last read message #4136

Merged
merged 1 commit into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 60 additions & 30 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 -> {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand All @@ -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

Expand Down Expand Up @@ -2579,10 +2612,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, urlForChatting: String)

fun loadInitialMessages(withNetworkParams: Bundle): Job
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 @@ -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)
}
Expand Down Expand Up @@ -163,7 +178,7 @@ class OfflineFirstChatRepository @Inject constructor(
}

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

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

updateUiForLastCommonRead(0)
updateUiForLastCommonRead()

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

Expand Down Expand Up @@ -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
}
}
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
Loading