From 41ed28e7f6ef9f06962ed65c515fbb81e9d0eff6 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 3 Oct 2018 16:32:10 +0200 Subject: [PATCH] Added pager for chat messages to prevent long loading time --- .../org/linphone/chat/ChatEventsAdapter.java | 14 ++- .../org/linphone/chat/ChatScrollListener.java | 104 ++++++++++++++++++ .../org/linphone/chat/GroupChatFragment.java | 45 +++++++- 3 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 src/android/org/linphone/chat/ChatScrollListener.java diff --git a/src/android/org/linphone/chat/ChatEventsAdapter.java b/src/android/org/linphone/chat/ChatEventsAdapter.java index 5365f8c6d..d7d7b4568 100644 --- a/src/android/org/linphone/chat/ChatEventsAdapter.java +++ b/src/android/org/linphone/chat/ChatEventsAdapter.java @@ -69,6 +69,7 @@ import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; @@ -93,6 +94,7 @@ public class ChatEventsAdapter extends SelectableAdapter { mContext = mFragment.getActivity(); mItemResource = itemResource; mHistory = new ArrayList<>(Arrays.asList(history)); + Collections.reverse(mHistory); mParticipants = participants; mClickListener = clickListener; mListener = new ChatMessageListenerStub() { @@ -414,9 +416,16 @@ public class ChatEventsAdapter extends SelectableAdapter { } public void addToHistory(EventLog log) { - mHistory.add(log); - notifyDataSetChanged(); + notifyItemChanged(0); + mHistory.add(0, log); + notifyItemInserted(0); + } + public void addAllToHistory(ArrayList logs) { + int currentSize = mHistory.size() - 1; + Collections.reverse(logs); + mHistory.addAll(logs); + notifyItemRangeInserted(currentSize + 1, logs.size()); } public void setContacts(ArrayList participants) { @@ -425,6 +434,7 @@ public class ChatEventsAdapter extends SelectableAdapter { public void refresh(EventLog[] history) { mHistory = new ArrayList<>(Arrays.asList(history)); + Collections.reverse(mHistory); notifyDataSetChanged(); } diff --git a/src/android/org/linphone/chat/ChatScrollListener.java b/src/android/org/linphone/chat/ChatScrollListener.java new file mode 100644 index 000000000..96b85a20e --- /dev/null +++ b/src/android/org/linphone/chat/ChatScrollListener.java @@ -0,0 +1,104 @@ +package org.linphone.chat; + +/* +ChatScrollListener.java +Copyright (C) 2010-2018 Belledonne Communications, Grenoble, France + +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 2 +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, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; + +public abstract class ChatScrollListener extends RecyclerView.OnScrollListener { + // The minimum amount of items to have below your current scroll position + // before loading more. + private int visibleThreshold = 5; + // The current offset index of data you have loaded + private int currentPage = 0; + // The total number of items in the dataset after the last load + private int previousTotalItemCount = 0; + // True if we are still waiting for the last set of data to load. + private boolean loading = true; + // Sets the starting page index + private int startingPageIndex = 0; + + private LinearLayoutManager mLayoutManager; + + public ChatScrollListener(LinearLayoutManager layoutManager) { + mLayoutManager = layoutManager; + } + + public int getLastVisibleItem(int[] lastVisibleItemPositions) { + int maxSize = 0; + for (int i = 0; i < lastVisibleItemPositions.length; i++) { + if (i == 0) { + maxSize = lastVisibleItemPositions[i]; + } + else if (lastVisibleItemPositions[i] > maxSize) { + maxSize = lastVisibleItemPositions[i]; + } + } + return maxSize; + } + + // 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 + public void onScrolled(RecyclerView view, int dx, int dy) { + int lastVisibleItemPosition = 0; + int totalItemCount = mLayoutManager.getItemCount(); + + lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition(); + + // 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) { + this.currentPage = this.startingPageIndex; + this.previousTotalItemCount = totalItemCount; + if (totalItemCount == 0) { + this.loading = true; + } + } + // If it’s still loading, we check to see if the dataset 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; + } + + // If it isn’t currently loading, we check to see if we have breached + // the visibleThreshold 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 && (lastVisibleItemPosition + visibleThreshold) > totalItemCount) { + currentPage++; + onLoadMore(currentPage, totalItemCount, view); + loading = true; + } + } + + // Call this method whenever performing new searches + public void resetState() { + this.currentPage = this.startingPageIndex; + this.previousTotalItemCount = 0; + this.loading = true; + } + + // Defines the process for actually loading more data based on page + public abstract void onLoadMore(int page, int totalItemsCount, RecyclerView view); +} diff --git a/src/android/org/linphone/chat/GroupChatFragment.java b/src/android/org/linphone/chat/GroupChatFragment.java index 41645f04d..e07e59702 100644 --- a/src/android/org/linphone/chat/GroupChatFragment.java +++ b/src/android/org/linphone/chat/GroupChatFragment.java @@ -32,6 +32,8 @@ import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; import android.os.Environment; +import android.os.Handler; +import android.os.Looper; import android.os.Parcelable; import android.provider.MediaStore; import android.support.v7.widget.LinearLayoutManager; @@ -80,6 +82,8 @@ import org.linphone.ui.SelectableHelper; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import static android.content.Context.INPUT_METHOD_SERVICE; @@ -87,7 +91,9 @@ import static org.linphone.fragments.FragmentsAvailable.CHAT; public class GroupChatFragment extends Fragment implements ChatRoomListener, ContactsUpdatedListener, ChatBubbleViewHolder.ClickListener, SelectableHelper.DeleteListener { private static final int ADD_PHOTO = 1337; + private static final int MESSAGES_PER_PAGE = 20; + private Handler mHandler = new Handler(Looper.getMainLooper()); private ImageView mBackButton, mCallButton, mBackToCallButton, mGroupInfosButton; private ImageView mAttachImageButton, mSendMessageButton; private TextView mRoomLabel, mParticipantsLabel, mRemoteComposing; @@ -106,6 +112,7 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con private ArrayList mParticipants; private LinearLayoutManager layoutManager; private int mContextMenuMessagePosition; + private ChatScrollListener mChatScrollListener; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -218,9 +225,17 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con mChatEventsList = view.findViewById(R.id.chat_message_list); mSelectionHelper = new SelectableHelper(view, this); - layoutManager = new LinearLayoutManager(mContext); + layoutManager = new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, true); mChatEventsList.setLayoutManager(layoutManager); + mChatScrollListener = new ChatScrollListener(layoutManager) { + @Override + public void onLoadMore(int page, int totalItemsCount, RecyclerView view) { + loadMoreData(totalItemsCount); + } + }; + mChatEventsList.addOnScrollListener(mChatScrollListener); + if (getArguments() != null) { String fileSharedUri = getArguments().getString("fileSharedUri"); if (fileSharedUri != null) { @@ -350,9 +365,9 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con eventLog.deleteFromDatabase(); } if (mChatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt())) { - mEventsAdapter.refresh(mChatRoom.getHistoryMessageEvents(0)); + mEventsAdapter.refresh(mChatRoom.getHistoryMessageEvents(MESSAGES_PER_PAGE)); } else { - mEventsAdapter.refresh(mChatRoom.getHistoryEvents(0)); + mEventsAdapter.refresh(mChatRoom.getHistoryEvents(MESSAGES_PER_PAGE)); } } @@ -424,6 +439,24 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con return super.onContextItemSelected(item); } + private void loadMoreData(final int totalItemsCount) { + mHandler.post(new Runnable() { + @Override + public void run() { + int maxSize = mChatRoom.getHistoryEventsSize(); + if (totalItemsCount < maxSize) { + int upperBound = totalItemsCount + MESSAGES_PER_PAGE; + if (upperBound > maxSize) { + upperBound = maxSize; + } + EventLog[] newLogs = mChatRoom.getHistoryRangeEvents(totalItemsCount, upperBound); + ArrayList logsList = new ArrayList<>(Arrays.asList(newLogs)); + mEventsAdapter.addAllToHistory(logsList); + } + } + }); + } + /** * Keyboard management */ @@ -563,9 +596,9 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con if (mChatRoom == null) return; if (mChatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt())) { - mEventsAdapter = new ChatEventsAdapter(this, mSelectionHelper, R.layout.chat_bubble, mChatRoom.getHistoryMessageEvents(0), mParticipants, this); + mEventsAdapter = new ChatEventsAdapter(this, mSelectionHelper, R.layout.chat_bubble, mChatRoom.getHistoryMessageEvents(MESSAGES_PER_PAGE), mParticipants, this); } else { - mEventsAdapter = new ChatEventsAdapter(this, mSelectionHelper, R.layout.chat_bubble, mChatRoom.getHistoryEvents(0), mParticipants, this); + mEventsAdapter = new ChatEventsAdapter(this, mSelectionHelper, R.layout.chat_bubble, mChatRoom.getHistoryEvents(MESSAGES_PER_PAGE), mParticipants, this); } mSelectionHelper.setAdapter(mEventsAdapter); mChatEventsList.setAdapter(mEventsAdapter); @@ -573,7 +606,7 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con } public void scrollToBottom() { - mChatEventsList.getLayoutManager().scrollToPosition(mEventsAdapter.getCount() - 1); + mChatEventsList.getLayoutManager().scrollToPosition(0); } public String getRemoteSipUri() {