Add placeholder support for ConversationListAdapter.

This commit is contained in:
Greyson Parrelli 2020-06-12 12:13:48 -07:00 committed by Greyson Parrelli
parent 49f75d7036
commit cf98a22269
4 changed files with 51 additions and 31 deletions

View File

@ -65,10 +65,6 @@ class ConversationDataSource extends PositionalDataSource<MessageRecord> {
int totalCount = db.getConversationCount(threadId); int totalCount = db.getConversationCount(threadId);
int effectiveCount = params.requestedStartPosition; int effectiveCount = params.requestedStartPosition;
if (totalCount == 0 || params.requestedStartPosition > totalCount) {
}
try (MmsSmsDatabase.Reader reader = db.readerFor(db.getConversation(threadId, params.requestedStartPosition, params.requestedLoadSize))) { try (MmsSmsDatabase.Reader reader = db.readerFor(db.getConversation(threadId, params.requestedStartPosition, params.requestedLoadSize))) {
MessageRecord record; MessageRecord record;
while ((record = reader.getNext()) != null && effectiveCount < totalCount && !isInvalid()) { while ((record = reader.getNext()) != null && effectiveCount < totalCount && !isInvalid()) {
@ -83,7 +79,7 @@ class ConversationDataSource extends PositionalDataSource<MessageRecord> {
callback.onResult(result.getItems(), params.requestedStartPosition, result.getTotal()); callback.onResult(result.getItems(), params.requestedStartPosition, result.getTotal());
} }
Log.d(TAG, "[Initial Load] " + (System.currentTimeMillis() - start) + " ms" + (isInvalid() ? " -- invalidated" : "")); Log.d(TAG, "[Initial Load] " + (System.currentTimeMillis() - start) + " ms | thread: " + threadId + ", start: " + params.requestedStartPosition + ", size: " + params.requestedLoadSize + (isInvalid() ? " -- invalidated" : ""));
} }
@Override @Override
@ -102,7 +98,7 @@ class ConversationDataSource extends PositionalDataSource<MessageRecord> {
callback.onResult(records); callback.onResult(records);
Log.d(TAG, "[Update] " + (System.currentTimeMillis() - start) + " ms" + (isInvalid() ? " -- invalidated" : "")); Log.d(TAG, "[Update] " + (System.currentTimeMillis() - start) + " ms | thread: " + threadId + ", start: " + params.startPosition + ", size: " + params.loadSize + (isInvalid() ? " -- invalidated" : ""));
} }
static class Factory extends DataSource.Factory<Integer, MessageRecord> { static class Factory extends DataSource.Factory<Integer, MessageRecord> {

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.conversationlist;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.paging.PagedListAdapter; import androidx.paging.PagedListAdapter;
@ -16,7 +17,7 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.CachedInflater; import org.thoughtcrime.securesms.util.CachedInflater;
import org.thoughtcrime.securesms.util.Stopwatch; import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -28,7 +29,11 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
class ConversationPagedListAdapter extends PagedListAdapter<Conversation, ConversationPagedListAdapter.ConversationViewHolder> { class ConversationListAdapter extends PagedListAdapter<Conversation, RecyclerView.ViewHolder> {
private static final int TYPE_THREAD = 1;
private static final int TYPE_ACTION = 2;
private static final int TYPE_PLACEHOLDER = 3;
private enum Payload { private enum Payload {
TYPING_INDICATOR, TYPING_INDICATOR,
@ -42,7 +47,7 @@ class ConversationPagedListAdapter extends PagedListAdapter<Conversation, Conver
private final Set<Long> typingSet = new HashSet<>(); private final Set<Long> typingSet = new HashSet<>();
private int archived; private int archived;
protected ConversationPagedListAdapter(@NonNull GlideRequests glideRequests, @NonNull OnConversationClickListener onConversationClickListener) { protected ConversationListAdapter(@NonNull GlideRequests glideRequests, @NonNull OnConversationClickListener onConversationClickListener) {
super(new ConversationDiffCallback()); super(new ConversationDiffCallback());
this.glideRequests = glideRequests; this.glideRequests = glideRequests;
@ -50,10 +55,10 @@ class ConversationPagedListAdapter extends PagedListAdapter<Conversation, Conver
} }
@Override @Override
public @NonNull ConversationViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public @NonNull RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == R.layout.conversation_list_item_action) { if (viewType == TYPE_ACTION) {
ConversationViewHolder holder = new ConversationViewHolder(LayoutInflater.from(parent.getContext()) ConversationViewHolder holder = new ConversationViewHolder(LayoutInflater.from(parent.getContext())
.inflate(viewType, parent, false)); .inflate(R.layout.conversation_list_item_action, parent, false));
holder.itemView.setOnClickListener(v -> { holder.itemView.setOnClickListener(v -> {
int position = holder.getAdapterPosition(); int position = holder.getAdapterPosition();
@ -64,9 +69,9 @@ class ConversationPagedListAdapter extends PagedListAdapter<Conversation, Conver
}); });
return holder; return holder;
} else { } else if (viewType == TYPE_THREAD) {
ConversationViewHolder holder = new ConversationViewHolder(CachedInflater.from(parent.getContext()) ConversationViewHolder holder = new ConversationViewHolder(CachedInflater.from(parent.getContext())
.inflate(viewType, parent, false)); .inflate(R.layout.conversation_list_item_view, parent, false));
holder.itemView.setOnClickListener(v -> { holder.itemView.setOnClickListener(v -> {
int position = holder.getAdapterPosition(); int position = holder.getAdapterPosition();
@ -86,11 +91,17 @@ class ConversationPagedListAdapter extends PagedListAdapter<Conversation, Conver
return false; return false;
}); });
return holder; return holder;
} else if (viewType == TYPE_PLACEHOLDER) {
View v = new FrameLayout(parent.getContext());
v.setLayoutParams(new FrameLayout.LayoutParams(1, ViewUtil.dpToPx(100)));
return new PlaceholderViewHolder(v);
} else {
throw new IllegalStateException("Unknown type! " + viewType);
} }
} }
@Override @Override
public void onBindViewHolder(@NonNull ConversationViewHolder holder, int position, @NonNull List<Object> payloads) { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
if (payloads.isEmpty()) { if (payloads.isEmpty()) {
onBindViewHolder(holder, position); onBindViewHolder(holder, position);
} else { } else {
@ -99,9 +110,9 @@ class ConversationPagedListAdapter extends PagedListAdapter<Conversation, Conver
Payload payload = (Payload) payloadObject; Payload payload = (Payload) payloadObject;
if (payload == Payload.SELECTION) { if (payload == Payload.SELECTION) {
holder.getConversationListItem().setBatchMode(batchMode); ((ConversationViewHolder) holder).getConversationListItem().setBatchMode(batchMode);
} else { } else {
holder.getConversationListItem().updateTypingIndicator(typingSet); ((ConversationViewHolder) holder).getConversationListItem().updateTypingIndicator(typingSet);
} }
} }
} }
@ -109,9 +120,11 @@ class ConversationPagedListAdapter extends PagedListAdapter<Conversation, Conver
} }
@Override @Override
public void onBindViewHolder(@NonNull ConversationViewHolder holder, int position) { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType() == R.layout.conversation_list_item_action) { if (holder.getItemViewType() == TYPE_ACTION) {
holder.getConversationListItem().bind(new ThreadRecord.Builder(100) ConversationViewHolder casted = (ConversationViewHolder) holder;
casted.getConversationListItem().bind(new ThreadRecord.Builder(100)
.setBody("") .setBody("")
.setDate(100) .setDate(100)
.setRecipient(Recipient.UNKNOWN) .setRecipient(Recipient.UNKNOWN)
@ -122,10 +135,11 @@ class ConversationPagedListAdapter extends PagedListAdapter<Conversation, Conver
typingSet, typingSet,
getBatchSelectionIds(), getBatchSelectionIds(),
batchMode); batchMode);
} else { } else if (holder.getItemViewType() == TYPE_THREAD) {
Conversation conversation = Objects.requireNonNull(getItem(position)); ConversationViewHolder casted = (ConversationViewHolder) holder;
Conversation conversation = Objects.requireNonNull(getItem(position));
holder.getConversationListItem().bind(conversation.getThreadRecord(), casted.getConversationListItem().bind(conversation.getThreadRecord(),
glideRequests, glideRequests,
conversation.getLocale(), conversation.getLocale(),
typingSet, typingSet,
@ -135,8 +149,10 @@ class ConversationPagedListAdapter extends PagedListAdapter<Conversation, Conver
} }
@Override @Override
public void onViewRecycled(@NonNull ConversationViewHolder holder) { public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) {
holder.getConversationListItem().unbind(); if (holder instanceof ConversationViewHolder) {
((ConversationViewHolder) holder).getConversationListItem().unbind();
}
} }
void setTypingThreads(@NonNull Set<Long> typingThreadSet) { void setTypingThreads(@NonNull Set<Long> typingThreadSet) {
@ -184,9 +200,11 @@ class ConversationPagedListAdapter extends PagedListAdapter<Conversation, Conver
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
if (archived > 0 && position == getItemCount() - 1) { if (archived > 0 && position == getItemCount() - 1) {
return R.layout.conversation_list_item_action; return TYPE_ACTION;
} else if (getItem(position) == null) {
return TYPE_PLACEHOLDER;
} else { } else {
return R.layout.conversation_list_item_view; return TYPE_THREAD;
} }
} }
@ -244,6 +262,12 @@ class ConversationPagedListAdapter extends PagedListAdapter<Conversation, Conver
} }
} }
private static class PlaceholderViewHolder extends RecyclerView.ViewHolder {
PlaceholderViewHolder(@NonNull View itemView) {
super(itemView);
}
}
interface OnConversationClickListener { interface OnConversationClickListener {
void onConversationClick(Conversation conversation); void onConversationClick(Conversation conversation);
boolean onConversationLongClick(Conversation conversation); boolean onConversationLongClick(Conversation conversation);

View File

@ -137,7 +137,7 @@ import static android.app.Activity.RESULT_OK;
public class ConversationListFragment extends MainFragment implements ActionMode.Callback, public class ConversationListFragment extends MainFragment implements ActionMode.Callback,
ConversationPagedListAdapter.OnConversationClickListener, ConversationListAdapter.OnConversationClickListener,
ConversationListSearchAdapter.EventListener, ConversationListSearchAdapter.EventListener,
MainNavigator.BackHandler, MainNavigator.BackHandler,
MegaphoneActionController MegaphoneActionController
@ -167,7 +167,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
private View toolbarShadow; private View toolbarShadow;
private ConversationListViewModel viewModel; private ConversationListViewModel viewModel;
private RecyclerView.Adapter activeAdapter; private RecyclerView.Adapter activeAdapter;
private ConversationPagedListAdapter defaultAdapter; private ConversationListAdapter defaultAdapter;
private ConversationListSearchAdapter searchAdapter; private ConversationListSearchAdapter searchAdapter;
private StickyHeaderDecoration searchAdapterDecoration; private StickyHeaderDecoration searchAdapterDecoration;
private ViewGroup megaphoneContainer; private ViewGroup megaphoneContainer;
@ -465,7 +465,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
} }
private void initializeListAdapters() { private void initializeListAdapters() {
defaultAdapter = new ConversationPagedListAdapter(GlideApp.with(this), this); defaultAdapter = new ConversationListAdapter(GlideApp.with(this), this);
searchAdapter = new ConversationListSearchAdapter(GlideApp.with(this), this, Locale.getDefault()); searchAdapter = new ConversationListSearchAdapter(GlideApp.with(this), this, Locale.getDefault());
searchAdapterDecoration = new StickyHeaderDecoration(searchAdapter, false, false); searchAdapterDecoration = new StickyHeaderDecoration(searchAdapter, false, false);

View File

@ -69,7 +69,7 @@ class ConversationListViewModel extends ViewModel {
PagedList.Config config = new PagedList.Config.Builder() PagedList.Config config = new PagedList.Config.Builder()
.setPageSize(15) .setPageSize(15)
.setInitialLoadSizeHint(30) .setInitialLoadSizeHint(30)
.setEnablePlaceholders(false) .setEnablePlaceholders(true)
.build(); .build();
this.conversationList = new LivePagedListBuilder<>(factory, config).setFetchExecutor(ConversationListDataSource.EXECUTOR) this.conversationList = new LivePagedListBuilder<>(factory, config).setFetchExecutor(ConversationListDataSource.EXECUTOR)