/*
* Copyright (C) 2014 Simon Vig Therkildsen
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.simonvt.cathode.ui.adapter;
import android.database.Cursor;
import android.provider.BaseColumns;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
import net.simonvt.cathode.provider.DatabaseContract.LastModifiedColumns;
import net.simonvt.schematic.Cursors;
import timber.log.Timber;
public abstract class HeaderCursorAdapter<T extends RecyclerView.ViewHolder>
extends BaseAdapter<T> {
public static class Header {
public final int header;
public long headerId;
public Cursor cursor;
int size;
private Header(int header, long headerId) {
this.header = header;
this.headerId = headerId;
}
void updateSize() {
final int cursorSize = cursor != null ? cursor.getCount() : 0;
if (cursorSize > 0) {
size = cursorSize + 1;
} else {
size = 0;
}
}
void setCursor(Cursor cursor) {
this.cursor = cursor;
updateSize();
}
}
public static class SpanLookup extends GridLayoutManager.SpanSizeLookup {
@Override public int getSpanSize(int i) {
return 0;
}
}
List<Header> headers = new ArrayList<>();
long headerIdOffset;
int itemCount;
final List<Integer> headerPositions = new ArrayList<>();
private SparseArray<Long> itemIds = new SparseArray<>();
private AdapterNotifier notifier;
public HeaderCursorAdapter() {
setHasStableIds(true);
notifier = new AdapterNotifier(this);
}
public void addHeader(int header) {
addHeader(header, null);
}
public void addHeader(int headerRes, Cursor cursor) {
final long headerId = Long.MAX_VALUE - headerIdOffset--;
Header header = new Header(headerRes, headerId);
header.setCursor(cursor);
headers.add(header);
notifyChanged();
}
public void notifyChanged() {
itemIds.clear();
itemCount = 0;
headerPositions.clear();
for (Header header : headers) {
header.updateSize();
if (header.size > 0) {
headerPositions.add(itemCount);
itemCount += header.size;
}
}
notifier.notifyChanged();
}
public void updateCursorForHeader(int headerRes, Cursor cursor) {
for (Header header : headers) {
if (headerRes == header.header) {
header.setCursor(cursor);
notifyChanged();
return;
}
}
throw new IllegalArgumentException("No header for resource id " + headerRes + " found");
}
private long getId(int position) {
int offset = 0;
for (int i = 0; i < headers.size(); i++) {
Header header = headers.get(i);
if (position < offset + header.size) {
final int offsetPosition = position - offset;
if (offsetPosition == 0) {
return header.headerId;
}
header.cursor.moveToPosition(offsetPosition - 1);
return getItemId(position, header.cursor);
}
offset += header.size;
}
throw new IllegalStateException("No id found for position " + position);
}
protected long getItemId(int position, Cursor cursor) {
return Cursors.getLong(cursor, BaseColumns._ID);
}
@Override public final long getItemId(int position) {
Long id = itemIds.get(position);
if (id == null) {
id = getId(position);
itemIds.put(position, id);
}
return id;
}
@Override public long getLastModified(int position) {
if (headerPositions.contains(position)) {
return 0;
}
Cursor cursor = getCursor(position);
return Cursors.getLong(cursor, LastModifiedColumns.LAST_MODIFIED);
}
public Header getHeader(int position) {
int offset = 0;
for (Header header : headers) {
if (position < offset + header.size) {
return header;
}
offset += header.size;
}
throw new RuntimeException(
"[" + this.getClass().getName() + "] No header found for position " + position);
}
public int getCursorPosition(int position) {
int offset = 0;
for (Header header : headers) {
if (position < offset + header.size) {
return position - offset - 1;
}
offset += header.size;
}
throw new RuntimeException("No cursor position found for position " + position);
}
public boolean isHeader(int position) {
return headerPositions.contains(position);
}
public List<Cursor> getCursors() {
List<Cursor> cursors = new ArrayList<>();
for (Header header : headers) {
cursors.add(header.cursor);
}
return cursors;
}
public Cursor getCursor(int position) {
if (position >= itemCount) {
throw new IndexOutOfBoundsException("Invalid index " + position + ", size is " + itemCount);
}
if (isHeader(position)) {
Timber.i("Count: %d", getItemCount());
Timber.i("Header count: %d", headers.size());
for (Header header : headers) {
Timber.i("Header id: %d", header.headerId);
Timber.i("Header size: %d", header.size);
}
throw new RuntimeException("Trying to get cursor for a header position " + position);
}
int offset = 0;
for (Header header : headers) {
if (position < offset + header.size) {
header.cursor.moveToPosition(position - offset - 1);
return header.cursor;
}
offset += header.size;
}
throw new RuntimeException("No cursor found");
}
@Override public int getItemCount() {
return itemCount;
}
@Override public final int getItemViewType(int position) {
if (isHeader(position)) {
return HeaderSpanLookup.TYPE_HEADER;
}
return getItemViewType(getHeader(position).header, getCursor(position));
}
protected abstract int getItemViewType(int headerRes, Cursor cursor);
@Override public final T onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == HeaderSpanLookup.TYPE_HEADER) {
return onCreateHeaderHolder(parent);
} else {
return onCreateItemHolder(parent, viewType);
}
}
protected abstract T onCreateItemHolder(ViewGroup parent, int viewType);
protected abstract T onCreateHeaderHolder(ViewGroup parent);
@Override public final void onBindViewHolder(T holder, int position) {
if (holder.getItemViewType() == HeaderSpanLookup.TYPE_HEADER) {
onBindHeader(holder, getHeader(position).header);
} else {
onBindViewHolder(holder, getCursor(position), position);
}
}
protected abstract void onBindHeader(T holder, int headerRes);
protected abstract void onBindViewHolder(T holder, Cursor cursor, int position);
}