only load partial conversation by default

Closes #4252
Fixes #3911
// FREEBIE
This commit is contained in:
Jake McGinty
2015-08-04 12:29:26 -07:00
committed by Moxie Marlinspike
parent 4a3faf9086
commit ae97495c47
10 changed files with 224 additions and 46 deletions

View File

@@ -20,17 +20,34 @@ import android.content.Context;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.View;
import android.view.ViewGroup;
import org.thoughtcrime.securesms.util.VisibleForTesting;
/**
* RecyclerView.Adapter that manages a Cursor, comparable to the CursorAdapter usable in ListView/GridView.
*/
public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
private final Context context;
public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final Context context;
private final DataSetObserver observer = new AdapterDataSetObserver();
private Cursor cursor;
private boolean valid;
@VisibleForTesting final static int HEADER_TYPE = Integer.MIN_VALUE;
@VisibleForTesting final static int FOOTER_TYPE = Integer.MIN_VALUE + 1;
private Cursor cursor;
private boolean valid;
private @Nullable View header;
private @Nullable View footer;
private static class HeaderFooterViewHolder extends RecyclerView.ViewHolder {
public HeaderFooterViewHolder(View itemView) {
super(itemView);
}
}
protected CursorRecyclerViewAdapter(Context context, Cursor cursor) {
this.context = context;
@@ -39,18 +56,32 @@ public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHold
valid = true;
cursor.registerDataSetObserver(observer);
}
setHasStableIds(false);
}
public Context getContext() {
protected @NonNull Context getContext() {
return context;
}
public Cursor getCursor() {
public @Nullable Cursor getCursor() {
return cursor;
}
public void setHeaderView(@Nullable View header) {
this.header = header;
}
public void setFooterView(@Nullable View footer) {
this.footer = footer;
}
public boolean hasHeaderView() {
return header != null;
}
public boolean hasFooterView() {
return footer != null;
}
public void changeCursor(Cursor cursor) {
Cursor old = swapCursor(cursor);
if (old != null) {
@@ -78,29 +109,50 @@ public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHold
return oldCursor;
}
@Override
public int getItemCount() {
return isActiveCursor() ? cursor.getCount() : 0;
if (!isActiveCursor()) return 0;
return cursor.getCount()
+ (hasHeaderView() ? 1 : 0)
+ (hasFooterView() ? 1 : 0);
}
@SuppressWarnings("unchecked")
@Override
public long getItemId(int position) {
return isActiveCursor() && cursor.moveToPosition(position)
? cursor.getLong(cursor.getColumnIndexOrThrow("_id"))
: 0;
public final void onViewRecycled(ViewHolder holder) {
if (!(holder instanceof HeaderFooterViewHolder)) {
onItemViewRecycled((VH)holder);
}
}
public abstract void onBindViewHolder(VH viewHolder, @NonNull Cursor cursor);
public void onItemViewRecycled(VH holder) {}
@Override public final ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case HEADER_TYPE: return new HeaderFooterViewHolder(header);
case FOOTER_TYPE: return new HeaderFooterViewHolder(footer);
default: return onCreateItemViewHolder(parent, viewType);
}
}
public abstract VH onCreateItemViewHolder(ViewGroup parent, int viewType);
@SuppressWarnings("unchecked")
@Override
public void onBindViewHolder(VH viewHolder, int position) {
moveToPositionOrThrow(position);
onBindViewHolder(viewHolder, cursor);
public final void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
if (!isHeaderPosition(position) && !isFooterPosition(position)) {
moveToPositionOrThrow(getCursorPosition(position));
onBindItemViewHolder((VH)viewHolder, cursor);
}
}
public abstract void onBindItemViewHolder(VH viewHolder, @NonNull Cursor cursor);
@Override public int getItemViewType(int position) {
moveToPositionOrThrow(position);
if (isHeaderPosition(position)) return HEADER_TYPE;
if (isFooterPosition(position)) return FOOTER_TYPE;
moveToPositionOrThrow(getCursorPosition(position));
return getItemViewType(cursor);
}
@@ -125,6 +177,18 @@ public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHold
return valid && cursor != null;
}
private boolean isFooterPosition(int position) {
return hasFooterView() && position == getItemCount() - 1;
}
private boolean isHeaderPosition(int position) {
return hasHeaderView() && position == 0;
}
private int getCursorPosition(int position) {
return hasHeaderView() ? position - 1 : position;
}
private class AdapterDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {

View File

@@ -42,7 +42,7 @@ public class MmsSmsDatabase extends Database {
super(context, databaseHelper);
}
public Cursor getConversation(long threadId) {
public Cursor getConversation(long threadId, long limit) {
String[] projection = {MmsSmsColumns.ID, SmsDatabase.BODY, SmsDatabase.TYPE,
MmsSmsColumns.THREAD_ID,
SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT,
@@ -60,12 +60,16 @@ public class MmsSmsDatabase extends Database {
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
Cursor cursor = queryTables(projection, selection, selection, order, null, null);
Cursor cursor = queryTables(projection, selection, selection, order, null, limit > 0 ? String.valueOf(limit) : null);
setNotifyConverationListeners(cursor, threadId);
return cursor;
}
public Cursor getConversation(long threadId) {
return getConversation(threadId, 0);
}
public Cursor getIdentityConflictMessagesForThread(long threadId) {
String[] projection = {MmsSmsColumns.ID, SmsDatabase.BODY, SmsDatabase.TYPE,
MmsSmsColumns.THREAD_ID,

View File

@@ -7,15 +7,21 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.util.AbstractCursorLoader;
public class ConversationLoader extends AbstractCursorLoader {
private final long threadId;
private final long threadId;
private long limit;
public ConversationLoader(Context context, long threadId) {
public ConversationLoader(Context context, long threadId, long limit) {
super(context);
this.threadId = threadId;
this.limit = limit;
}
public boolean hasLimit() {
return limit > 0;
}
@Override
public Cursor getCursor() {
return DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId);
return DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId, limit);
}
}