mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-20 03:59:35 +01:00
fix to scroll to last read message
This will fix to scroll to the last read message when a chat is opened. Some refactorings were made that are not necessary for the fix (I tried to also show the "Unread messages" hint in the adapter but came to the conclusion this is not a good idea until chatkit is removed. Chatkit doesn't support to add some item in between but only at the end or start which will make it too complicated..) Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
parent
014b58321d
commit
20724c5efb
@ -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