package com.sunlightlabs.android.congress.fragments; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.ListFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ListView; import android.widget.TextView; import com.sunlightlabs.android.congress.R; import com.sunlightlabs.android.congress.notifications.Footer; import com.sunlightlabs.android.congress.notifications.Subscriber; import com.sunlightlabs.android.congress.notifications.Subscription; import com.sunlightlabs.android.congress.utils.FragmentUtils; import com.sunlightlabs.android.congress.utils.PaginationListener; import com.sunlightlabs.android.congress.utils.Utils; import com.sunlightlabs.congress.models.Bill; import com.sunlightlabs.congress.models.CongressException; import com.sunlightlabs.congress.models.Legislator; import com.sunlightlabs.congress.services.BillService; public class BillListFragment extends ListFragment implements PaginationListener.Paginates { public static final int PER_PAGE = 20; public static final int BILLS_ACTIVE = 0; public static final int BILLS_ALL = 1; public static final int BILLS_SPONSOR = 2; public static final int BILLS_SEARCH_NEWEST = 3; public static final int BILLS_SEARCH_RELEVANT = 4; public static final int BILLS_CODE = 5; public static final int BILLS_LAW = 6; List<Bill> bills; List<String> newIds; int type; Legislator sponsor; String bill_type; int number; String query; PaginationListener pager; View loadingView; public static BillListFragment forAll() { BillListFragment frag = new BillListFragment(); Bundle args = new Bundle(); args.putInt("type", BILLS_ALL); frag.setArguments(args); frag.setRetainInstance(true); return frag; } public static BillListFragment forActive() { BillListFragment frag = new BillListFragment(); Bundle args = new Bundle(); args.putInt("type", BILLS_ACTIVE); frag.setArguments(args); frag.setRetainInstance(true); return frag; } public static BillListFragment forLaw() { BillListFragment frag = new BillListFragment(); Bundle args = new Bundle(); args.putInt("type", BILLS_LAW); frag.setArguments(args); frag.setRetainInstance(true); return frag; } public static BillListFragment forSponsor(Legislator sponsor) { BillListFragment frag = new BillListFragment(); Bundle args = new Bundle(); args.putInt("type", BILLS_SPONSOR); args.putSerializable("sponsor", sponsor); frag.setArguments(args); frag.setRetainInstance(true); return frag; } public static BillListFragment forCode(String bill_type, int number) { BillListFragment frag = new BillListFragment(); Bundle args = new Bundle(); args.putInt("type", BILLS_CODE); args.putString("bill_type", bill_type); args.putInt("number", number); frag.setArguments(args); frag.setRetainInstance(true); return frag; } public static BillListFragment forSearch(String query, int type) { BillListFragment frag = new BillListFragment(); Bundle args = new Bundle(); args.putInt("type", type); args.putString("query", query); frag.setArguments(args); frag.setRetainInstance(true); return frag; } public BillListFragment() {} @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle args = getArguments(); type = args.getInt("type"); query = args.getString("query"); bill_type = args.getString("bill_type"); number = args.getInt("number"); sponsor = (Legislator) args.getSerializable("sponsor"); loadBills(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.list_footer, container, false); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); newIds = FragmentUtils.newIds(this, subscriberClass()); setupControls(); if (bills != null) displayBills(); } @Override public void onResume() { super.onResume(); if (bills != null) setupSubscription(); } public void setupControls() { ((Button) getView().findViewById(R.id.refresh)).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { refresh(); } }); if (type != BILLS_CODE) { loadingView = LayoutInflater.from(getActivity()).inflate(R.layout.loading_page, null); loadingView.setVisibility(View.GONE); getListView().addFooterView(loadingView); pager = new PaginationListener(this); getListView().setOnScrollListener(pager); } FragmentUtils.setLoading(this, R.string.bills_loading); } @Override public void onListItemClick(ListView parent, View v, int position, long id) { startActivity(Utils.billIntent(getActivity(), (Bill) parent.getItemAtPosition(position))); } private void refresh() { bills = null; FragmentUtils.setLoading(this, R.string.bills_loading); FragmentUtils.showLoading(this); loadBills(); } public void loadBills() { new LoadBillsTask(this, 1).execute(); } @Override public void loadNextPage(int page) { getListView().setOnScrollListener(null); loadingView.setVisibility(View.VISIBLE); new LoadBillsTask(this, page).execute(); } // handles coming in with any page of bills, even the first one public void onLoadBills(List<Bill> bills, int page) { if (!isAdded()) return; if (page == 1) { // if new IDs, sort them to the top. // (new IDs will only affect page 1) if (newIds != null) { Collections.sort(bills, new Comparator<Bill>() { @Override public int compare(Bill a, Bill b) { boolean hasA = newIds.contains(a.id); boolean hasB = newIds.contains(b.id); if (hasA && !hasB) return -1; else if (!hasA && hasB) return 1; else return 0; } }); } this.bills = bills; displayBills(); } else { this.bills.addAll(bills); loadingView.setVisibility(View.GONE); ((BillAdapter) getListAdapter()).notifyDataSetChanged(); } // only re-enable the pagination if we got a full page back if (bills.size() == PER_PAGE) getListView().setOnScrollListener(pager); } public void onLoadBills(CongressException exception) { if (isAdded()) FragmentUtils.showRefresh(this, R.string.bills_error); } // only run for the first page of bill results public void displayBills() { if (bills.size() > 0) { setListAdapter(new BillAdapter(this, bills)); setupSubscription(); } else { if (type == BILLS_SEARCH_NEWEST) { FragmentUtils.showEmpty(this, R.string.bills_empty_search_newest); setupSubscription(); } else if (type == BILLS_SEARCH_RELEVANT) { FragmentUtils.showEmpty(this, R.string.bills_empty_search_relevant); setupSubscription(); } else if (type == BILLS_CODE) { FragmentUtils.showEmpty(this, R.string.bills_empty_code); setupSubscription(); } else if (type == BILLS_SPONSOR) { FragmentUtils.showEmpty(this, R.string.bills_empty_sponsor); setupSubscription(); } else // active bills, all bills FragmentUtils.showRefresh(this, R.string.bills_error); // should not happen } } private String subscriberClass() { if (type == BILLS_ALL) return "BillsRecentSubscriber"; // can't turn on until this is a tab, not a thing through LegislatorLoader // else if (type == BILLS_SPONSOR) // return "BillsLegislatorSubscriber"; else if (type == BILLS_ACTIVE) return "BillsActiveSubscriber"; else if (type == BILLS_SEARCH_NEWEST) return "BillsSearchSubscriber"; else return null; } private void setupSubscription() { Subscription subscription = null; if (type == BILLS_ALL) subscription = new Subscription("RecentBills", getResources().getString(R.string.subscriber_bills_all), "BillsRecentSubscriber", null); else if (type == BILLS_SPONSOR) subscription = new Subscription(sponsor.bioguide_id, Subscriber.notificationName(sponsor), "BillsLegislatorSubscriber", null); else if (type == BILLS_ACTIVE) subscription = new Subscription("ActiveBills", getResources().getString(R.string.subscriber_bills_active), "BillsActiveSubscriber", null); else if (type == BILLS_LAW) subscription = new Subscription("EnactedLegislation", getResources().getString(R.string.subscriber_bills_law), "BillsLawSubscriber", null); else if (type == BILLS_SEARCH_NEWEST) subscription = new Subscription(query, query, "BillsSearchSubscriber", query); // no subscription offered for a bill code search // no subscription offered for "best match" searches if (subscription != null) Footer.setup(this, subscription, bills); } private static class LoadBillsTask extends AsyncTask<Void,Void,List<Bill>> { private BillListFragment context; private CongressException exception; private int page; public LoadBillsTask(BillListFragment context, int page) { this.context = context; this.page = page; FragmentUtils.setupAPI(context); } @Override public List<Bill> doInBackground(Void... nothing) { try { Map<String,String> params = new HashMap<String,String>(); switch (context.type) { case BILLS_ALL: return BillService.recentlyIntroduced(page, PER_PAGE); case BILLS_ACTIVE: return BillService.recentlyActive(page, PER_PAGE); case BILLS_LAW: return BillService.recentlyLaw(page, PER_PAGE); case BILLS_SPONSOR: return BillService.recentlySponsored(context.sponsor.bioguide_id, page, PER_PAGE); case BILLS_CODE: params.put("bill_type", context.bill_type); params.put("number", String.valueOf(context.number)); return BillService.where(params, page, PER_PAGE); case BILLS_SEARCH_NEWEST: params.put("order", "introduced_on"); return BillService.search(context.query, params, page, PER_PAGE); case BILLS_SEARCH_RELEVANT: params.put("order", "_score"); return BillService.search(context.query, params, page, PER_PAGE); default: throw new CongressException("Not sure what type of bills to find."); } } catch(CongressException exception) { this.exception = exception; return null; } } @Override public void onPostExecute(List<Bill> bills) { if (exception != null) context.onLoadBills(exception); else context.onLoadBills(bills, page); } } private static class BillAdapter extends ArrayAdapter<Bill> { private LayoutInflater inflater; private BillListFragment context; public BillAdapter(BillListFragment context, List<Bill> bills) { super(context.getActivity(), 0, bills); this.inflater = LayoutInflater.from(context.getActivity()); this.context = context; } @Override public int getViewTypeCount() { return 1; } @Override public View getView(int position, View view, ViewGroup parent) { Bill bill = getItem(position); ViewHolder holder; if (view == null) { view = inflater.inflate(R.layout.bill_item, null); holder = new ViewHolder(); holder.code = (TextView) view.findViewById(R.id.code); holder.date = (TextView) view.findViewById(R.id.date); holder.title = (TextView) view.findViewById(R.id.title); holder.last_action = (TextView) view.findViewById(R.id.last_action); holder.newResult = view.findViewById(R.id.new_result); view.setTag(holder); } else holder = (ViewHolder) view.getTag(); switch (context.type) { case BILLS_ACTIVE: shortDate(holder.date, bill.last_action_at); break; case BILLS_SEARCH_RELEVANT: longDate(holder.date, bill.last_action_at); break; case BILLS_SEARCH_NEWEST: case BILLS_ALL: case BILLS_SPONSOR: case BILLS_CODE: default: shortDate(holder.date, bill.introduced_on); break; } holder.code.setText(Bill.formatCode(bill.bill_type, bill.number)); if (bill.short_title != null) { String title = Utils.truncate(bill.short_title, 250); holder.title.setTextSize(18); holder.title.setText(title); } else if (bill.official_title != null) { String title = Utils.truncate(bill.official_title, 250); holder.title.setTextSize(14); holder.title.setText(title); } else { holder.title.setTextSize(14); holder.title.setText(R.string.bill_no_title); } if (context.type == BILLS_ACTIVE) { holder.last_action.setText(actionText(bill.lastAction)); holder.last_action.setVisibility(View.VISIBLE); } else holder.last_action.setVisibility(View.GONE); if (context.newIds != null && context.newIds.contains(bill.id)) holder.newResult.setVisibility(View.VISIBLE); else holder.newResult.setVisibility(View.GONE); return view; } static class ViewHolder { TextView code, date, title, last_action; View newResult; } private void shortDate(TextView view, Date date) { if (date.getYear() == Calendar.getInstance().get(Calendar.YEAR)) { view.setTextSize(18); view.setText(new SimpleDateFormat("MMM d", Locale.US).format(date).toUpperCase(Locale.US)); } else longDate(view, date); } private void longDate(TextView view, Date date) { view.setTextSize(14); view.setText(new SimpleDateFormat("MMM d, ''yy", Locale.US).format(date).toUpperCase(Locale.US)); } private String actionText(Bill.Action action) { return Utils.truncate(action.text, 80); } } }