mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Only load 30 messages when opening conversation, loading more messages when scrolling up
This commit is contained in:
parent
b919f51ecb
commit
ef47624b9d
3 changed files with 162 additions and 14 deletions
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-android
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.ui.main.chat
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
internal abstract class ConversationScrollListener(private val mLayoutManager: LinearLayoutManager) :
|
||||
RecyclerView.OnScrollListener() {
|
||||
companion object {
|
||||
// The minimum amount of items to have below your current scroll position
|
||||
// before loading more.
|
||||
private const val mVisibleThreshold = 5
|
||||
}
|
||||
|
||||
// The total number of items in the data set after the last load
|
||||
private var previousTotalItemCount = 0
|
||||
|
||||
// True if we are still waiting for the last set of data to load.
|
||||
private var loading = true
|
||||
|
||||
// This happens many times a second during a scroll, so be wary of the code you place here.
|
||||
// We are given a few useful parameters to help us work out if we need to load some more data,
|
||||
// but first we check if we are waiting for the previous load to finish.
|
||||
override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
|
||||
val totalItemCount = mLayoutManager.itemCount
|
||||
val firstVisibleItemPosition: Int = mLayoutManager.findFirstVisibleItemPosition()
|
||||
val lastVisibleItemPosition: Int = mLayoutManager.findLastCompletelyVisibleItemPosition()
|
||||
|
||||
// If the total item count is zero and the previous isn't, assume the
|
||||
// list is invalidated and should be reset back to initial state
|
||||
if (totalItemCount < previousTotalItemCount) {
|
||||
previousTotalItemCount = totalItemCount
|
||||
if (totalItemCount == 0) {
|
||||
loading = true
|
||||
}
|
||||
}
|
||||
|
||||
// If it’s still loading, we check to see if the data set count has
|
||||
// changed, if so we conclude it has finished loading and update the current page
|
||||
// number and total item count.
|
||||
if (loading && totalItemCount > previousTotalItemCount) {
|
||||
loading = false
|
||||
previousTotalItemCount = totalItemCount
|
||||
}
|
||||
|
||||
val userHasScrolledUp = lastVisibleItemPosition != totalItemCount - 1
|
||||
if (userHasScrolledUp) {
|
||||
onScrolledUp()
|
||||
} else {
|
||||
onScrolledToEnd()
|
||||
}
|
||||
|
||||
// If it isn’t currently loading, we check to see if we have breached
|
||||
// the mVisibleThreshold and need to reload more data.
|
||||
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
|
||||
// threshold should reflect how many total columns there are too
|
||||
if (!loading &&
|
||||
firstVisibleItemPosition < mVisibleThreshold &&
|
||||
firstVisibleItemPosition >= 0 &&
|
||||
lastVisibleItemPosition < totalItemCount - mVisibleThreshold
|
||||
) {
|
||||
onLoadMore(totalItemCount)
|
||||
loading = true
|
||||
}
|
||||
}
|
||||
|
||||
// Defines the process for actually loading more data based on page
|
||||
protected abstract fun onLoadMore(totalItemsCount: Int)
|
||||
|
||||
// Called when user has started to scroll up, opposed to onScrolledToEnd()
|
||||
protected abstract fun onScrolledUp()
|
||||
|
||||
// Called when user has scrolled and reached the end of the items
|
||||
protected abstract fun onScrolledToEnd()
|
||||
}
|
||||
|
|
@ -51,9 +51,7 @@ import androidx.lifecycle.lifecycleScope
|
|||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
|
||||
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
|
|
@ -72,6 +70,7 @@ import org.linphone.databinding.ChatBubbleLongPressMenuBinding
|
|||
import org.linphone.databinding.ChatConversationFragmentBinding
|
||||
import org.linphone.databinding.ChatConversationPopupMenuBinding
|
||||
import org.linphone.ui.main.MainActivity
|
||||
import org.linphone.ui.main.chat.ConversationScrollListener
|
||||
import org.linphone.ui.main.chat.adapter.ConversationEventAdapter
|
||||
import org.linphone.ui.main.chat.adapter.MessageBottomSheetAdapter
|
||||
import org.linphone.ui.main.chat.model.MessageDeliveryModel
|
||||
|
|
@ -177,6 +176,10 @@ class ConversationFragment : SlidingPaneChildFragment() {
|
|||
|
||||
private val dataObserver = object : AdapterDataObserver() {
|
||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
if (positionStart > 0) {
|
||||
adapter.notifyItemChanged(positionStart - 1) // For grouping purposes
|
||||
}
|
||||
|
||||
if (viewModel.isUserScrollingUp.value == true) {
|
||||
Log.i(
|
||||
"$TAG [$itemCount] events have been loaded but user was scrolling up in conversation, do not scroll"
|
||||
|
|
@ -218,6 +221,8 @@ class ConversationFragment : SlidingPaneChildFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private lateinit var scrollListener: ConversationScrollListener
|
||||
|
||||
private var currentChatMessageModelForBottomSheet: MessageModel? = null
|
||||
private val bottomSheetCallback = object : BottomSheetCallback() {
|
||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||
|
|
@ -616,17 +621,25 @@ class ConversationFragment : SlidingPaneChildFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
binding.eventsList.addOnScrollListener(object : OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
val scrollingUp = layoutManager.findLastCompletelyVisibleItemPosition() != adapter.itemCount - 1
|
||||
viewModel.isUserScrollingUp.value = scrollingUp
|
||||
|
||||
if (!scrollingUp) {
|
||||
Log.i("$TAG Last message is visible, considering conversation as read")
|
||||
viewModel.markAsRead()
|
||||
}
|
||||
scrollListener = object : ConversationScrollListener(layoutManager) {
|
||||
@UiThread
|
||||
override fun onLoadMore(totalItemsCount: Int) {
|
||||
viewModel.loadMoreData(totalItemsCount)
|
||||
}
|
||||
})
|
||||
|
||||
@UiThread
|
||||
override fun onScrolledUp() {
|
||||
viewModel.isUserScrollingUp.value = true
|
||||
}
|
||||
|
||||
@UiThread
|
||||
override fun onScrolledToEnd() {
|
||||
viewModel.isUserScrollingUp.value = false
|
||||
Log.i("$TAG Last message is visible, considering conversation as read")
|
||||
viewModel.markAsRead()
|
||||
}
|
||||
}
|
||||
binding.eventsList.addOnScrollListener(scrollListener)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
|
@ -651,6 +664,10 @@ class ConversationFragment : SlidingPaneChildFragment() {
|
|||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
if (::scrollListener.isInitialized) {
|
||||
binding.eventsList.removeOnScrollListener(scrollListener)
|
||||
}
|
||||
|
||||
coreContext.postOnCoreThread {
|
||||
bottomSheetReactionsModel?.destroy()
|
||||
bottomSheetDeliveryModel?.destroy()
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import org.linphone.utils.LinphoneUtils
|
|||
class ConversationViewModel @UiThread constructor() : ViewModel() {
|
||||
companion object {
|
||||
private const val TAG = "[Conversation ViewModel]"
|
||||
private const val MESSAGES_PER_PAGE = 30
|
||||
|
||||
const val MAX_TIME_TO_GROUP_MESSAGES = 60 // 1 minute
|
||||
const val SCROLLING_POSITION_NOT_SET = -1
|
||||
|
|
@ -158,7 +159,7 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
|
|||
)
|
||||
|
||||
val lastEvent = eventsList.lastOrNull()
|
||||
val newEvent = newList.lastOrNull()
|
||||
val newEvent = newList.firstOrNull()
|
||||
if (lastEvent != null && newEvent != null && shouldWeGroupTwoEvents(
|
||||
newEvent.eventLog,
|
||||
lastEvent.eventLog
|
||||
|
|
@ -462,6 +463,43 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun loadMoreData(totalItemsCount: Int) {
|
||||
coreContext.postOnCoreThread {
|
||||
val maxSize: Int = chatRoom.historyEventsSize
|
||||
Log.i("$TAG Loading more data, current total is $totalItemsCount, max size is $maxSize")
|
||||
|
||||
if (totalItemsCount < maxSize) {
|
||||
var upperBound: Int = totalItemsCount + MESSAGES_PER_PAGE
|
||||
if (upperBound > maxSize) {
|
||||
upperBound = maxSize
|
||||
}
|
||||
|
||||
val history = chatRoom.getHistoryRangeEvents(totalItemsCount, upperBound)
|
||||
val list = getEventsListFromHistory(history, searchFilter.value.orEmpty())
|
||||
|
||||
val lastEvent = list.lastOrNull()
|
||||
val newEvent = eventsList.firstOrNull()
|
||||
if (lastEvent != null && newEvent != null && shouldWeGroupTwoEvents(
|
||||
newEvent.eventLog,
|
||||
lastEvent.eventLog
|
||||
)
|
||||
) {
|
||||
if (lastEvent.model is MessageModel) {
|
||||
lastEvent.model.groupedWithNextMessage.postValue(true)
|
||||
}
|
||||
if (newEvent.model is MessageModel) {
|
||||
newEvent.model.groupedWithPreviousMessage.postValue(true)
|
||||
}
|
||||
}
|
||||
|
||||
list.addAll(eventsList)
|
||||
eventsList = list
|
||||
events.postValue(eventsList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun configureChatRoom() {
|
||||
scrollingPosition = SCROLLING_POSITION_NOT_SET
|
||||
|
|
@ -524,7 +562,7 @@ class ConversationViewModel @UiThread constructor() : ViewModel() {
|
|||
private fun computeEvents(filter: String = "") {
|
||||
eventsList.forEach(EventLogModel::destroy)
|
||||
|
||||
val history = chatRoom.getHistoryEvents(0)
|
||||
val history = chatRoom.getHistoryEvents(MESSAGES_PER_PAGE)
|
||||
val list = getEventsListFromHistory(history, filter)
|
||||
eventsList = list
|
||||
events.postValue(eventsList)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue