2015-01-19 02:11:30 +00:00
|
|
|
/**
|
|
|
|
* Copyright (C) 2015 Open Whisper Systems
|
|
|
|
*
|
|
|
|
* 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.thoughtcrime.securesms.database;
|
|
|
|
|
|
|
|
import android.content.Context;
|
|
|
|
import android.database.Cursor;
|
|
|
|
import android.database.DataSetObserver;
|
2015-09-15 22:28:27 +00:00
|
|
|
import android.support.annotation.NonNull;
|
2015-08-04 19:29:26 +00:00
|
|
|
import android.support.annotation.Nullable;
|
2016-11-03 16:36:45 +00:00
|
|
|
import android.support.annotation.VisibleForTesting;
|
2015-01-19 02:11:30 +00:00
|
|
|
import android.support.v7.widget.RecyclerView;
|
2015-08-04 19:29:26 +00:00
|
|
|
import android.support.v7.widget.RecyclerView.ViewHolder;
|
|
|
|
import android.view.View;
|
|
|
|
import android.view.ViewGroup;
|
|
|
|
|
2015-01-19 02:11:30 +00:00
|
|
|
/**
|
|
|
|
* RecyclerView.Adapter that manages a Cursor, comparable to the CursorAdapter usable in ListView/GridView.
|
|
|
|
*/
|
2015-08-04 19:29:26 +00:00
|
|
|
public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
|
|
|
private final Context context;
|
2015-01-19 02:11:30 +00:00
|
|
|
private final DataSetObserver observer = new AdapterDataSetObserver();
|
|
|
|
|
2015-11-23 00:04:49 +00:00
|
|
|
@VisibleForTesting static final int HEADER_TYPE = Integer.MIN_VALUE;
|
|
|
|
@VisibleForTesting static final int FOOTER_TYPE = Integer.MIN_VALUE + 1;
|
|
|
|
@VisibleForTesting static final long HEADER_ID = Long.MIN_VALUE;
|
|
|
|
@VisibleForTesting static final long FOOTER_ID = Long.MIN_VALUE + 1;
|
2015-08-04 19:29:26 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2015-01-19 02:11:30 +00:00
|
|
|
|
|
|
|
protected CursorRecyclerViewAdapter(Context context, Cursor cursor) {
|
|
|
|
this.context = context;
|
|
|
|
this.cursor = cursor;
|
|
|
|
if (cursor != null) {
|
|
|
|
valid = true;
|
|
|
|
cursor.registerDataSetObserver(observer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-04 19:29:26 +00:00
|
|
|
protected @NonNull Context getContext() {
|
2015-01-19 02:11:30 +00:00
|
|
|
return context;
|
|
|
|
}
|
|
|
|
|
2015-08-04 19:29:26 +00:00
|
|
|
public @Nullable Cursor getCursor() {
|
2015-01-19 02:11:30 +00:00
|
|
|
return cursor;
|
|
|
|
}
|
|
|
|
|
2015-08-04 19:29:26 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-01-19 02:11:30 +00:00
|
|
|
public void changeCursor(Cursor cursor) {
|
|
|
|
Cursor old = swapCursor(cursor);
|
|
|
|
if (old != null) {
|
|
|
|
old.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public Cursor swapCursor(Cursor newCursor) {
|
|
|
|
if (newCursor == cursor) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
final Cursor oldCursor = cursor;
|
|
|
|
if (oldCursor != null) {
|
|
|
|
oldCursor.unregisterDataSetObserver(observer);
|
|
|
|
}
|
|
|
|
|
|
|
|
cursor = newCursor;
|
|
|
|
if (cursor != null) {
|
|
|
|
cursor.registerDataSetObserver(observer);
|
|
|
|
}
|
|
|
|
|
|
|
|
valid = cursor != null;
|
|
|
|
notifyDataSetChanged();
|
|
|
|
return oldCursor;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int getItemCount() {
|
2015-08-04 19:29:26 +00:00
|
|
|
if (!isActiveCursor()) return 0;
|
|
|
|
|
|
|
|
return cursor.getCount()
|
|
|
|
+ (hasHeaderView() ? 1 : 0)
|
|
|
|
+ (hasFooterView() ? 1 : 0);
|
2015-01-19 02:11:30 +00:00
|
|
|
}
|
|
|
|
|
2015-08-04 19:29:26 +00:00
|
|
|
@SuppressWarnings("unchecked")
|
2015-01-19 02:11:30 +00:00
|
|
|
@Override
|
2015-08-04 19:29:26 +00:00
|
|
|
public final void onViewRecycled(ViewHolder holder) {
|
|
|
|
if (!(holder instanceof HeaderFooterViewHolder)) {
|
|
|
|
onItemViewRecycled((VH)holder);
|
|
|
|
}
|
2015-01-19 02:11:30 +00:00
|
|
|
}
|
|
|
|
|
2015-08-04 19:29:26 +00:00
|
|
|
public void onItemViewRecycled(VH holder) {}
|
2015-01-19 02:11:30 +00:00
|
|
|
|
2015-11-13 21:20:16 +00:00
|
|
|
@Override
|
|
|
|
public final ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
2015-08-04 19:29:26 +00:00
|
|
|
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")
|
2015-01-19 02:11:30 +00:00
|
|
|
@Override
|
2015-08-04 19:29:26 +00:00
|
|
|
public final void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
|
|
|
if (!isHeaderPosition(position) && !isFooterPosition(position)) {
|
2015-11-23 00:04:49 +00:00
|
|
|
onBindItemViewHolder((VH)viewHolder, getCursorAtPositionOrThrow(position));
|
2015-08-04 19:29:26 +00:00
|
|
|
}
|
2015-09-15 22:28:27 +00:00
|
|
|
}
|
|
|
|
|
2015-08-04 19:29:26 +00:00
|
|
|
public abstract void onBindItemViewHolder(VH viewHolder, @NonNull Cursor cursor);
|
|
|
|
|
2015-11-13 21:20:16 +00:00
|
|
|
@Override
|
|
|
|
public final int getItemViewType(int position) {
|
2015-08-04 19:29:26 +00:00
|
|
|
if (isHeaderPosition(position)) return HEADER_TYPE;
|
|
|
|
if (isFooterPosition(position)) return FOOTER_TYPE;
|
2015-11-23 00:04:49 +00:00
|
|
|
return getItemViewType(getCursorAtPositionOrThrow(position));
|
2015-09-15 22:28:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public int getItemViewType(@NonNull Cursor cursor) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-11-13 21:20:16 +00:00
|
|
|
@Override
|
|
|
|
public final long getItemId(int position) {
|
2015-11-23 00:04:49 +00:00
|
|
|
if (isHeaderPosition(position)) return HEADER_ID;
|
|
|
|
if (isFooterPosition(position)) return FOOTER_ID;
|
|
|
|
long itemId = getItemId(getCursorAtPositionOrThrow(position));
|
|
|
|
return itemId <= Long.MIN_VALUE + 1 ? itemId + 2 : itemId;
|
2015-11-13 21:20:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public long getItemId(@NonNull Cursor cursor) {
|
|
|
|
return cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
|
|
|
|
}
|
|
|
|
|
2015-11-23 00:04:49 +00:00
|
|
|
protected @NonNull Cursor getCursorAtPositionOrThrow(final int position) {
|
2015-01-19 02:11:30 +00:00
|
|
|
if (!isActiveCursor()) {
|
|
|
|
throw new IllegalStateException("this should only be called when the cursor is valid");
|
|
|
|
}
|
2015-11-23 00:04:49 +00:00
|
|
|
if (!cursor.moveToPosition(getCursorPosition(position))) {
|
2015-01-19 02:11:30 +00:00
|
|
|
throw new IllegalStateException("couldn't move cursor to position " + position);
|
|
|
|
}
|
2015-11-23 00:04:49 +00:00
|
|
|
return cursor;
|
2015-01-19 02:11:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private boolean isActiveCursor() {
|
|
|
|
return valid && cursor != null;
|
|
|
|
}
|
|
|
|
|
2015-08-04 19:29:26 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-01-19 02:11:30 +00:00
|
|
|
private class AdapterDataSetObserver extends DataSetObserver {
|
|
|
|
@Override
|
|
|
|
public void onChanged() {
|
|
|
|
super.onChanged();
|
|
|
|
valid = true;
|
|
|
|
notifyDataSetChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onInvalidated() {
|
|
|
|
super.onInvalidated();
|
|
|
|
valid = false;
|
|
|
|
notifyDataSetChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|