package com.sunlightlabs.android.congress.utils;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import android.content.res.Resources;
import android.support.v4.app.Fragment;
import android.support.v4.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import com.sunlightlabs.android.congress.R;
public abstract class DateAdapterHelper<Content> {
protected ListFragment context;
public Resources resources;
public LayoutInflater inflater;
public DateAdapter adapter;
public List<Content> contents;
public List<ItemWrapper> items;
// manage sticky header
private ViewGroup stickyHeader;
private TextView stickyHeaderLeft;
private TextView stickyHeaderRight;
private OnScrollListener auxScrollListener;
public interface StickyHeader {}
public DateAdapterHelper(ListFragment context) {
this.context = context;
this.inflater = LayoutInflater.from(context.getActivity());
this.resources = context.getResources();
if (context instanceof StickyHeader) {
stickyHeader = (ViewGroup) context.getView().findViewById(R.id.header_container);
stickyHeader.addView(stickyDateView(inflater), 0);
}
}
public DateAdapter adapterFor(List<Content> contents) {
this.contents = contents;
this.items = processContents(contents);
this.adapter = new DateAdapter(this, context, items);
if (context instanceof StickyHeader) {
context.getListView().setOnScrollListener(adapter);
// trigger it once
if (contents.size() > 0) {
// updateStickyHeader(dateFor(contents.get(0)));
stickyHeader.setVisibility(View.VISIBLE);
}
} else
context.getListView().setOnScrollListener(auxScrollListener);
return adapter;
}
public void notifyDataSetChanged() {
// this.contents should have been updated in the context
// this.items is cleared and added to so that the memory reference stays the same,
// and the adapter's notifyDataSetChanged method can be used (will keep the list in-place when it's updated)
this.items.clear();
this.items.addAll(processContents(contents));
this.adapter.notifyDataSetChanged();
}
public void setOnScrollListener(OnScrollListener listener) {
auxScrollListener = listener;
}
abstract public Date dateFor(Content content);
// must override
public View contentView(Content content) { return null; }
// override if you want to make use of this aspect of the adapter
public View contentView(Content content, boolean showTime) {
return contentView(content); // ignored by default
}
// Optionally override this
public View dateView(Date date) {
return Utils.dateView(context.getActivity(), date, Utils.fullDateThisYear(date));
}
// override this to control how the date view gets populated
public void updateStickyHeader(Date date, View view, TextView left, TextView right) {}
// called from the internal adapter
private void updateStickyHeader(Date date) {
updateStickyHeader(date, this.stickyHeader.getChildAt(0), stickyHeaderLeft, stickyHeaderRight);
}
public View stickyDateView(LayoutInflater inflater) {
View view = inflater.inflate(R.layout.list_item_date, null);
stickyHeaderLeft = (TextView) view.findViewById(R.id.date_left);
stickyHeaderRight = (TextView) view.findViewById(R.id.date_right);
return view;
}
private List<ItemWrapper> processContents(List<Content> contents) {
SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd yyyy");
SimpleDateFormat timeFormat = new SimpleDateFormat("h:mm aa");
List<ItemWrapper> items = new ArrayList<ItemWrapper>();
String currentDate = "";
String currentTime = "";
for (int i=0; i<contents.size(); i++) {
Content content = contents.get(i);
ContentWrapper wrapper = new ContentWrapper(content);
// figure out whether we need to insert a date header first,
// and whether the showTime flag should be marked
Date contentDate = dateFor(content);
String date = dateFormat.format(contentDate);
String time = timeFormat.format(contentDate);
if (!currentDate.equals(date))
items.add(new DateWrapper(contentDate));
if (!currentTime.equals(time))
wrapper.showTime = true;
currentDate = date;
currentTime = time;
items.add(wrapper);
}
return items;
}
public class DateAdapter extends ArrayAdapter<DateAdapterHelper<Content>.ItemWrapper> implements OnScrollListener {
DateAdapterHelper<Content> helper;
private static final int TYPE_DATE = 0;
private static final int TYPE_CONTENT = 1;
public DateAdapter(DateAdapterHelper<Content> helper, Fragment context, List<DateAdapterHelper<Content>.ItemWrapper> items) {
super(context.getActivity(), 0, items);
this.helper = helper;
}
@Override
public boolean isEnabled(int position) {
return false;
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public int getItemViewType(int position) {
DateAdapterHelper<Content>.ItemWrapper item = getItem(position);
if (item instanceof DateAdapterHelper.DateWrapper)
return TYPE_DATE;
else
return TYPE_CONTENT;
}
@Override
public int getViewTypeCount() {
return 2;
}
@SuppressWarnings("unchecked")
@Override
public View getView(int position, View view, ViewGroup parent) {
DateAdapterHelper<Content>.ItemWrapper item = getItem(position);
if (item instanceof DateAdapterHelper.DateWrapper)
return dateView(((DateWrapper) item).date);
else {
ContentWrapper wrapper = (ContentWrapper) item;
return contentView(wrapper.content, wrapper.showTime);
}
}
// this will only run (the OnScrollListener will only be set) if the helper is attached to a fragment implementing StickyHeader
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
int position = firstVisibleItem;
// guaranteed to be at least one item after a date wrapper, and it will be a content item,
// because of how the adapter builds itself
if (getItemViewType(position) == TYPE_DATE)
position += 1;
Content content = ((ContentWrapper) getItem(position)).content;
helper.updateStickyHeader(dateFor(content));
if (helper.auxScrollListener != null)
helper.auxScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// pass on the scroll event if an auxiliary scroll listener is registered
if (helper.auxScrollListener != null)
helper.auxScrollListener.onScrollStateChanged(view, scrollState);
}
}
public class ItemWrapper {}
public class DateWrapper extends ItemWrapper {
public Date date;
public DateWrapper(Date date) { this.date = date; }
}
public class ContentWrapper extends ItemWrapper {
public Content content;
public boolean showTime;
public ContentWrapper(Content content) {
this.content = content;
this.showTime = false;
}
}
}