mirror of
https://github.com/nextcloud/talk-android
synced 2025-07-19 18:55:05 +01:00
Merge pull request #4136 from nextcloud/bugfix/noid/fixUnreadMessageScrollOnEnterChat
fix to scroll to last read message
This commit is contained in:
commit
822af2c967
@ -832,6 +832,14 @@ class ChatActivity :
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.lifecycleScope.launch {
|
||||||
|
chatViewModel.getLastReadMessageFlow
|
||||||
|
.onEach { lastRead ->
|
||||||
|
scrollToAndCenterMessageWithId(lastRead.toString())
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
chatViewModel.reactionDeletedViewState.observe(this) { state ->
|
chatViewModel.reactionDeletedViewState.observe(this) { state ->
|
||||||
when (state) {
|
when (state) {
|
||||||
is ChatViewModel.ReactionDeletedSuccessState -> {
|
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) {
|
private fun writeContactToVcfFile(cursor: Cursor, file: File) {
|
||||||
val lookupKey = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.LOOKUP_KEY))
|
val lookupKey = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.LOOKUP_KEY))
|
||||||
val uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_VCARD_URI, lookupKey)
|
val uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_VCARD_URI, lookupKey)
|
||||||
@ -2492,34 +2512,11 @@ class ChatActivity :
|
|||||||
|
|
||||||
private fun processMessagesFromTheFuture(chatMessageList: List<ChatMessage>) {
|
private fun processMessagesFromTheFuture(chatMessageList: List<ChatMessage>) {
|
||||||
val newMessagesAvailable = (adapter?.itemCount ?: 0) > 0 && chatMessageList.isNotEmpty()
|
val newMessagesAvailable = (adapter?.itemCount ?: 0) > 0 && chatMessageList.isNotEmpty()
|
||||||
val insertNewMessagesNotice = if (newMessagesAvailable) {
|
val insertNewMessagesNotice = shouldInsertNewMessagesNotice(newMessagesAvailable, chatMessageList)
|
||||||
chatMessageList.any { it.actorId != conversationUser!!.userId }
|
val scrollToEndOnUpdate = isScrolledToBottom()
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
val scrollToEndOnUpdate = layoutManager?.findFirstVisibleItemPosition() == 0
|
|
||||||
|
|
||||||
if (insertNewMessagesNotice) {
|
if (insertNewMessagesNotice) {
|
||||||
val unreadChatMessage = ChatMessage()
|
updateUnreadMessageInfos(chatMessageList, scrollToEndOnUpdate)
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (chatMessage in chatMessageList) {
|
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>) {
|
private fun processMessagesNotFromTheFuture(chatMessageList: List<ChatMessage>) {
|
||||||
var countGroupedMessages = 0
|
var countGroupedMessages = 0
|
||||||
|
|
||||||
@ -2579,10 +2612,7 @@ class ChatActivity :
|
|||||||
|
|
||||||
private fun scrollToFirstUnreadMessage() {
|
private fun scrollToFirstUnreadMessage() {
|
||||||
adapter?.let {
|
adapter?.let {
|
||||||
layoutManager?.scrollToPositionWithOffset(
|
scrollToAndCenterMessageWithId("-1")
|
||||||
it.getMessagePositionByIdInReverse("-1"),
|
|
||||||
binding.messagesListView.height / 2
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,8 @@ interface ChatMessageRepository : LifecycleAwareManager {
|
|||||||
|
|
||||||
val lastCommonReadFlow: Flow<Int>
|
val lastCommonReadFlow: Flow<Int>
|
||||||
|
|
||||||
|
val lastReadMessageFlow: Flow<Int>
|
||||||
|
|
||||||
fun setData(conversationModel: ConversationModel, credentials: String, urlForChatting: String)
|
fun setData(conversationModel: ConversationModel, credentials: String, urlForChatting: String)
|
||||||
|
|
||||||
fun loadInitialMessages(withNetworkParams: Bundle): Job
|
fun loadInitialMessages(withNetworkParams: Bundle): Job
|
||||||
|
@ -81,6 +81,13 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||||||
private val _lastCommonReadFlow:
|
private val _lastCommonReadFlow:
|
||||||
MutableSharedFlow<Int> = MutableSharedFlow()
|
MutableSharedFlow<Int> = MutableSharedFlow()
|
||||||
|
|
||||||
|
override val lastReadMessageFlow:
|
||||||
|
Flow<Int>
|
||||||
|
get() = _lastReadMessageFlow
|
||||||
|
|
||||||
|
private val _lastReadMessageFlow:
|
||||||
|
MutableSharedFlow<Int> = MutableSharedFlow()
|
||||||
|
|
||||||
private var newXChatLastCommonRead: Int? = null
|
private var newXChatLastCommonRead: Int? = null
|
||||||
private var itIsPaused = false
|
private var itIsPaused = false
|
||||||
private val scope = CoroutineScope(Dispatchers.IO)
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
@ -114,25 +121,33 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||||||
|
|
||||||
sync(withNetworkParams)
|
sync(withNetworkParams)
|
||||||
|
|
||||||
Log.d(TAG, "newestMessageId after sync: " + chatDao.getNewestMessageId(internalConversationId))
|
val newestMessageId = chatDao.getNewestMessageId(internalConversationId)
|
||||||
|
Log.d(TAG, "newestMessageId after sync: $newestMessageId")
|
||||||
|
|
||||||
showLast100MessagesBeforeAndEqual(
|
showLast100MessagesBeforeAndEqual(
|
||||||
internalConversationId,
|
internalConversationId,
|
||||||
chatDao.getNewestMessageId(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()
|
initMessagePolling()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateUiForLastCommonRead(delay: Long) {
|
private suspend fun updateUiForLastReadMessage(newestMessageId: Long) {
|
||||||
scope.launch {
|
val scrollToLastRead = conversationModel.lastReadMessage.toLong() < newestMessageId
|
||||||
// delay is a dirty workaround to make sure messages are added to adapter on initial load before setting
|
if (scrollToLastRead) {
|
||||||
// their read status(otherwise there is a race condition between adding messages and setting their read
|
_lastReadMessageFlow.emit(conversationModel.lastReadMessage)
|
||||||
// status).
|
|
||||||
if (delay > 0) {
|
|
||||||
delay(delay)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUiForLastCommonRead() {
|
||||||
|
scope.launch {
|
||||||
newXChatLastCommonRead?.let {
|
newXChatLastCommonRead?.let {
|
||||||
_lastCommonReadFlow.emit(it)
|
_lastCommonReadFlow.emit(it)
|
||||||
}
|
}
|
||||||
@ -163,7 +178,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
showLast100MessagesBefore(internalConversationId, beforeMessageId)
|
showLast100MessagesBefore(internalConversationId, beforeMessageId)
|
||||||
updateUiForLastCommonRead(0)
|
updateUiForLastCommonRead()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initMessagePolling(): Job =
|
override fun initMessagePolling(): Job =
|
||||||
@ -195,7 +210,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||||||
_messageFlow.emit(pair)
|
_messageFlow.emit(pair)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUiForLastCommonRead(0)
|
updateUiForLastCommonRead()
|
||||||
|
|
||||||
val newestMessage = chatDao.getNewestMessageId(internalConversationId).toInt()
|
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_OK: Int = 200
|
||||||
private const val HTTP_CODE_NOT_MODIFIED = 304
|
private const val HTTP_CODE_NOT_MODIFIED = 304
|
||||||
private const val HTTP_CODE_PRECONDITION_FAILED = 412
|
private const val HTTP_CODE_PRECONDITION_FAILED = 412
|
||||||
|
private const val DELAY_TO_ENSURE_MESSAGES_ARE_ADDED: Long = 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,6 +122,8 @@ class ChatViewModel @Inject constructor(
|
|||||||
|
|
||||||
val getLastCommonReadFlow = chatRepository.lastCommonReadFlow
|
val getLastCommonReadFlow = chatRepository.lastCommonReadFlow
|
||||||
|
|
||||||
|
val getLastReadMessageFlow = chatRepository.lastReadMessageFlow
|
||||||
|
|
||||||
val getConversationFlow = conversationRepository.conversationFlow
|
val getConversationFlow = conversationRepository.conversationFlow
|
||||||
.onEach {
|
.onEach {
|
||||||
_getRoomViewState.value = GetRoomSuccessState
|
_getRoomViewState.value = GetRoomSuccessState
|
||||||
|
Loading…
Reference in New Issue
Block a user