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

[PBE-3881] message list scrolls down on reaction #5280

Merged
merged 4 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import io.getstream.chat.android.ui.common.state.messages.list.SystemMessageItem
import io.getstream.chat.android.ui.common.state.messages.list.ThreadDateSeparatorItemState
import io.getstream.chat.android.ui.common.state.messages.list.TypingItemState
import io.getstream.chat.android.ui.common.state.messages.list.UnreadSeparatorItemState
import io.getstream.chat.android.ui.common.state.messages.list.lastItemOrNull
import io.getstream.chat.android.ui.common.state.messages.list.stringify
import io.getstream.chat.android.ui.common.utils.extensions.onFirst
import io.getstream.chat.android.ui.common.utils.extensions.shouldShowMessageFooter
Expand All @@ -118,10 +119,8 @@ import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
Expand Down Expand Up @@ -617,16 +616,13 @@ public class MessageListController(
val last = newState.messageItems.filterIsInstance<MessageItemState>().lastOrNull()?.stringify()
logger.d { "[updateMessageList] #messageList; first: $first, last: $last" }

val newLastMessage =
newState.messageItems.lastOrNull { it is MessageItemState || it is SystemMessageItemState }?.let {
when (it) {
is MessageItemState -> it.message
is SystemMessageItemState -> it.message
else -> null
}
}
val oldLastMessage = _messageListState.value.lastItemOrNull<HasMessageListItemState>()?.message
val newLastMessage = newState.lastItemOrNull<HasMessageListItemState>()?.message

val newMessageState = getNewMessageState(newLastMessage, lastLoadedMessage)
val newMessageState = when (oldLastMessage?.id != newLastMessage?.id) {
true -> getNewMessageState(newLastMessage, lastLoadedMessage)
else -> null
}
JcMinarro marked this conversation as resolved.
Show resolved Hide resolved
setMessageListState(newState.copy(newMessageState = newMessageState))
if (newMessageState != null) lastLoadedMessage = newLastMessage
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public data class MessageListState(
internal fun MessageListState.stringify(): String {
return "MessageListState(" +
"messageItems.size: ${messageItems.size}, " +
"newMessageState: $newMessageState, " +
"endOfNewMessagesReached: $endOfNewMessagesReached, " +
"endOfOldMessagesReached: $endOfOldMessagesReached, " +
"isLoading: $isLoading, " +
Expand All @@ -59,6 +60,9 @@ internal fun MessageListState.stringify(): String {
"currentUser.id: ${currentUser?.id}, " +
"parentMessageId: $parentMessageId, " +
"unreadCount: $unreadCount, " +
"newMessageState: $newMessageState, " +
"selectedMessageState: $selectedMessageState)"
}

internal inline fun <reified T : MessageListItemState> MessageListState.lastItemOrNull(): T? {
return messageItems.lastOrNull { it is T } as T?
}
2 changes: 0 additions & 2 deletions stream-chat-android-ui-components/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,6 @@
<ID>MaxLineLength:MessageListItemStyle.kt$MessageListItemStyle.Builder$require(messageMaxWidthFactorTheirs in 0.75..1.0) { "messageMaxWidthFactorTheirs cannot be lower than 0.75 and greater than 1! Current value: $messageMaxWidthFactorTheirs" }</ID>
<ID>MaxLineLength:MessageListItemViewHolderFactory.kt$MessageListItemViewHolderFactory$*</ID>
<ID>MaxLineLength:MessageListListenerContainerImpl.kt$MessageListListenerContainerImpl$attachmentDownloadClickListener: OnAttachmentDownloadClickListener = OnAttachmentDownloadClickListener(EmptyFunctions.ONE_PARAM)</ID>
<ID>MaxLineLength:MessageListScrollHelper.kt$MessageListScrollHelper$logger.d { "[shouldScrollToBottomBeVisible] bottomOffset: $bottomOffset, areNewestMessagesLoaded: $areNewestMessagesLoaded" }</ID>
<ID>MaxLineLength:MessageListScrollHelper.kt$MessageListScrollHelper.&lt;no name provided>$logger.d { "[onScrolled] scrollToBottomButtonEnabled: $scrollToBottomButtonEnabled, currentList.size: ${currentList.size}" }</ID>
<ID>MaxLineLength:MessageListView.kt$MessageListView.MessagesStart.Companion$?:</ID>
<ID>MaxLineLength:MessageListView.kt$MessageListView.NewMessagesBehaviour.Companion$?:</ID>
<ID>MaxLineLength:MessageListViewModelFactory.kt$MessageListViewModelFactory$?:</ID>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1219,13 +1219,15 @@ public class MessageListView : ConstraintLayout {
messageListViewStyle?.messagesStart?.let(::chatMessageStart)
}

logger.v { "[handleNewWrapper] filteredList.size: ${filteredList.size}" }
logger.v {
"[handleNewWrapper] isOldListEmpty: $isOldListEmpty, filteredList.size: ${filteredList.size}"
}
adapter.submitList(filteredList) {
scrollHelper.onMessageListChanged(
isThreadStart = isThreadStart,
hasNewMessages = listItem.hasNewMessages,
isInitialList = isOldListEmpty && filteredList.isNotEmpty(),
areNewestMessagesLoaded = listItem.areNewestMessagesLoaded,
endOfNewMessagesReached = listItem.areNewestMessagesLoaded,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ internal class MessageListScrollHelper(

internal var unreadCountEnabled: Boolean = true

private var areNewestMessagesLoaded: Boolean = true
private var endOfNewMessagesReached: Boolean = true

private var bottomOffset: Int = 0

Expand Down Expand Up @@ -103,7 +103,10 @@ internal class MessageListScrollHelper(
}

override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
logger.d { "[onScrolled] scrollToBottomButtonEnabled: $scrollToBottomButtonEnabled, currentList.size: ${currentList.size}" }
logger.d {
"[onScrolled] scrollToBottomButtonEnabled: $scrollToBottomButtonEnabled" +
", currentList.size: ${currentList.size}"
}
if (!scrollToBottomButtonEnabled || currentList.isEmpty()) {
return
}
Expand Down Expand Up @@ -138,12 +141,15 @@ internal class MessageListScrollHelper(
*/
private fun shouldScrollToBottomBeVisible(): Boolean {
bottomOffset = calculateBottomOffset()
logger.d { "[shouldScrollToBottomBeVisible] bottomOffset: $bottomOffset, areNewestMessagesLoaded: $areNewestMessagesLoaded" }
isAtBottom = bottomOffset <= 0 && areNewestMessagesLoaded
logger.d {
"[shouldScrollToBottomBeVisible] bottomOffset: $bottomOffset" +
", endOfNewMessagesReached: $endOfNewMessagesReached"
}
isAtBottom = bottomOffset <= 0 && endOfNewMessagesReached

return when {
adapter.itemCount == 0 -> false
!areNewestMessagesLoaded -> true
!endOfNewMessagesReached -> true
else -> {
val hasInvisibleUnreadMessage = !isAtBottom
val hasScrolledUpEnough = bottomOffset > SCROLL_BUTTON_VISIBILITY_THRESHOLD
Expand Down Expand Up @@ -203,21 +209,24 @@ internal class MessageListScrollHelper(
isThreadStart: Boolean,
hasNewMessages: Boolean,
isInitialList: Boolean,
areNewestMessagesLoaded: Boolean,
endOfNewMessagesReached: Boolean,
) {
logger.d { "[onMessageListChanged] areNewestMessagesLoaded: $areNewestMessagesLoaded" }
this.areNewestMessagesLoaded = areNewestMessagesLoaded
logger.d {
"[onMessageListChanged] isInitialList: $isInitialList" +
", endOfNewMessagesReached: $endOfNewMessagesReached"
}
this.endOfNewMessagesReached = endOfNewMessagesReached
scrollButtonView.isVisible = shouldScrollToBottomBeVisible()

if (!isThreadStart && shouldKeepScrollPosition(areNewestMessagesLoaded, hasNewMessages)) {
if (!isThreadStart && shouldKeepScrollPosition(endOfNewMessagesReached, hasNewMessages)) {
return
}

if (isThreadStart) {
layoutManager.scrollToPosition(currentList.lastIndex)
return
}
val shouldScrollToBottom = shouldScrollToBottom(isInitialList, areNewestMessagesLoaded, hasNewMessages)
val shouldScrollToBottom = shouldScrollToBottom(isInitialList, endOfNewMessagesReached, hasNewMessages)
logger.v { "[onMessageListChanged] shouldScrollToBottom: $shouldScrollToBottom" }
if (shouldScrollToBottom) {
layoutManager.scrollToPosition(currentList.lastIndex)
Expand All @@ -235,11 +244,16 @@ internal class MessageListScrollHelper(

private fun shouldScrollToBottom(
isInitialList: Boolean,
areNewestMessagesLoaded: Boolean,
endOfNewMessagesReached: Boolean,
hasNewMessages: Boolean,
): Boolean {
logger.v {
"[shouldScrollToBottom] isInitialList: $isInitialList, " +
"endOfNewMessagesReached: $endOfNewMessagesReached, hasNewMessages: $hasNewMessages" +
", isLastMessageMine: ${isLastMessageMine()}, "
}
return hasNewMessages &&
areNewestMessagesLoaded &&
endOfNewMessagesReached &&
(isInitialList || isLastMessageMine() || isAtBottom || alwaysScrollToBottom)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,21 @@ import io.getstream.chat.android.ui.feature.messages.list.adapter.MessageListIte

/**
* MessageListItemWrapper wraps a list of [MessageListItem] with a few extra fields.
*
* @param items The list of [MessageListItem]s to be shown in the list.
* @param hasNewMessages Whether the user has new messages or not.
* @param isTyping Whether the user is typing or not.
* @param isThread Whether the user is in a thread or not.
* @param areNewestMessagesLoaded Whether the newest messages are loaded or not.
*/
public data class MessageListItemWrapper(
val items: List<MessageListItem> = listOf(),
val hasNewMessages: Boolean = false,
val isTyping: Boolean = false,
val isThread: Boolean = false,
@Deprecated(
"The name of this field will to be aligned with `MessageListState.endOfNewMessagesReached` field.",
)
val areNewestMessagesLoaded: Boolean = true,
) {

Expand All @@ -34,8 +43,13 @@ public data class MessageListItemWrapper(
}

private fun stringify(): String {
return "MessageListItemWrapper(items=${items.size}, first: ${items.firstOrNull()?.stringify()}" +
", hasNewMessages=$hasNewMessages, isTyping=$isTyping, isThread=$isThread" +
", areNewestMessagesLoaded=$areNewestMessagesLoaded)"
return "MessageListItemWrapper(" +
"endOfNewMessagesReached=$areNewestMessagesLoaded" +
", hasNewMessages=$hasNewMessages" +
", items=${items.size}" +
", first: ${items.firstOrNull()?.stringify()}" +
", isTyping=$isTyping" +
", isThread=$isThread" +
")"
}
}
Loading