package com.mehmetakiftutuncu.eshotroid.adapter; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Locale; import java.util.Set; import android.annotation.TargetApi; import android.content.Context; import android.os.AsyncTask; import android.os.Build; import android.support.v4.view.ViewPager; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.Filter; import android.widget.SectionIndexer; import android.widget.TextView; import com.mehmetakiftutuncu.eshotroid.BuildConfig; import com.mehmetakiftutuncu.eshotroid.R; import com.mehmetakiftutuncu.eshotroid.activity.Main; import com.mehmetakiftutuncu.eshotroid.database.BusDatabase; import com.mehmetakiftutuncu.eshotroid.model.Bus; import com.mehmetakiftutuncu.eshotroid.model.BusTimeTypes; import com.mehmetakiftutuncu.eshotroid.task.GetBusTimesPageTask; import com.mehmetakiftutuncu.eshotroid.utility.Constants; /** Bus list adapter class is the adapter that supplies the bus list with items * from an ArrayList of Bus items * * @author mehmetakiftutuncu */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class BusListAdapter extends ArrayAdapter<Bus> implements SectionIndexer { /** {@link Context} of the activity that uses this adapter */ private Context myContext; /** A map between the sections and bus numbers */ private HashMap<String, Integer> myIndexer; /** Sections in the list (which will in fact be bus numbers themselves for * fast scrolling) */ private String[] mySections; /** Original list of items containing all the busses */ private ArrayList<Bus> myOriginalItems; /** Current list of items whose contents depend on the search query that * filters this adapter */ private ArrayList<Bus> myCurrentItems; /** Tag for debugging */ public static final String LOG_TAG = "Eshotroid_BusListAdapter"; /** Holder class for each item */ static class ViewHolder { public CheckBox favorite; public TextView number; public TextView name; } /** Constructor of the adapter */ public BusListAdapter(Context context, ArrayList<Bus> items) { super(context, R.layout.item_bus, items); myContext = context; myOriginalItems = items; myCurrentItems = new ArrayList<Bus>(); myCurrentItems.addAll(myOriginalItems); // Generate the sections based on current items updateSectionlist(); } @Override public Filter getFilter() { // Return a custom filter return new Filter() { @Override protected FilterResults performFiltering(CharSequence constraint) { FilterResults result = new FilterResults(); // If there is a constraint (i.e. there is a search query) if(constraint != null && constraint.toString().length() > 0) { // Modify the constraint for easier searching Locale trLocale = new Locale("tr"); constraint = constraint.toString().trim().toLowerCase(trLocale); ArrayList<Bus> filteredItems = new ArrayList<Bus>(); // Check each item for(int i = 0, l = myOriginalItems.size(); i < l; i++) { Bus bus = myOriginalItems.get(i); /* Custom search logic here; bus number, source, * destination and route are checked for matching */ String number = String.valueOf(bus.getNumber()); String source = bus.getSource() != null ? bus.getSource() : ""; String destination = bus.getDestination() != null ? bus.getDestination() : ""; String route = bus.getRoute() != null ? bus.getRoute() : ""; if( number.contains(constraint) || source.toLowerCase(trLocale).contains(constraint) || destination.toLowerCase(trLocale).contains(constraint) || route.toLowerCase(trLocale).contains(constraint)) { filteredItems.add(bus); } } // Update the filter attributes result.count = filteredItems.size(); result.values = filteredItems; } else { result.count = -1; } return result; } @SuppressWarnings("unchecked") @Override protected void publishResults(CharSequence constraint, FilterResults results) { // If there is at least a match for the search if(results != null && results.count >= 0) { // Set the current items as the result myCurrentItems = (ArrayList<Bus>) results.values; } else { // No match, show all items myCurrentItems = myOriginalItems; } // Notify that the list should update the items it shows notifyDataSetInvalidated(); } }; } @Override public int getCount() { /* This is important, if not checked here, the list will never know how * many items it has causing crashes */ return myCurrentItems != null ? myCurrentItems.size() : 0; } @Override public Bus getItem(int position) { /* If there is a set of current items meaning that the list is filtered * after some search operation */ if(myCurrentItems != null && myCurrentItems.size() > 0) { // Return the item in current items list return myCurrentItems.get(position); } return super.getItem(position); } @Override public View getView(int position, View convertView, ViewGroup parent) { // Try to use the view in memory View row = convertView; // If the view is not ready if(row == null) { // Get inflater LayoutInflater inflater = (LayoutInflater) myContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); // Inflate and create the view row = inflater.inflate(R.layout.item_bus, parent, false); /* Create a new holder for this view to hold references of it's * components */ ViewHolder myViewHolder = new ViewHolder(); // Find references for components and store them in holder myViewHolder.favorite = (CheckBox) row.findViewById(R.id.checkBox_item_bus_isFavorited); myViewHolder.number = (TextView) row.findViewById(R.id.textView_item_bus_number); myViewHolder.name = (TextView) row.findViewById(R.id.textView_item_bus_name); /* Add the holder to the view as a tag so references can be accessed later */ row.setTag(myViewHolder); } // Get the holder of this view to access it's components ViewHolder holder = (ViewHolder) row.getTag(); // Get the current bus final Bus bus = myCurrentItems.get(position); holder.favorite.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { /* Do stuff only if the status is changed (^ is XOR which gives * true if the boolean values are not the same) */ if(bus.isFavorited() ^ isChecked) { if(isChecked) { if(BuildConfig.DEBUG) Log.d(LOG_TAG, "Adding " + bus.getNumber() + " to favorited busses..."); } else { if(BuildConfig.DEBUG) Log.d(LOG_TAG, "Removing " + bus.getNumber() + " from favorited busses..."); } // Update the value of the bus bus.setFavorited(isChecked); // Update the value in database BusDatabase db = BusDatabase.getDatabase(myContext); db.addOrUpdate(bus); db.closeDatabase(); // Get main activity Main main = (Main) myContext; // Get main view pager ViewPager viewPager = main.getViewPager(); // If on favorite busses page if(viewPager.getCurrentItem() == Constants.PAGE_ID_FAVORITE_BUSSES) { // Reload list of all busses main.onListOfBussesLoaded(); } // Reload favorite busses main.onFavoriteBussesLoaded(); if(isChecked) { db = BusDatabase.getDatabase(myContext); Bus newBus = db.get(bus.getNumber()); db.closeDatabase(); if(newBus.getTimesH() == null) { if(BuildConfig.DEBUG) Log.d(LOG_TAG, "Bus is favorited. Downloading times for H..."); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) new GetBusTimesPageTask(myContext, newBus, BusTimeTypes.WEEK_DAY).executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); else new GetBusTimesPageTask(myContext, newBus, BusTimeTypes.WEEK_DAY).execute(); } if(newBus.getTimesC() == null) { if(BuildConfig.DEBUG) Log.d(LOG_TAG, "Bus is favorited. Downloading times for C..."); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) new GetBusTimesPageTask(myContext, newBus, BusTimeTypes.SATURDAY).executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); else new GetBusTimesPageTask(myContext, newBus, BusTimeTypes.SATURDAY).execute(); } if(newBus.getTimesP() == null) { if(BuildConfig.DEBUG) Log.d(LOG_TAG, "Bus is favorited. Downloading times for P..."); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) new GetBusTimesPageTask(myContext, newBus, BusTimeTypes.SUNDAY).executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); else new GetBusTimesPageTask(myContext, newBus, BusTimeTypes.SUNDAY).execute(); } } } } }); // Set the values of each component using the references in holder holder.favorite.setChecked(bus.isFavorited()); holder.number.setText("" + bus.getNumber()); holder.name.setText(bus.getSource() + " - " + bus.getDestination()); return row; } /** Updates the section list so that sections can be seen during fast * scrolling */ public void updateSectionlist() { myIndexer = new HashMap<String, Integer>(); int size = myOriginalItems.size(); // Put each bus and it's index in the map for(int i = 0; i < size; i++) { myIndexer.put("" + myOriginalItems.get(i).getNumber(), i); } Set<String> sectionLetters = myIndexer.keySet(); ArrayList<String> sectionList = new ArrayList<String>(sectionLetters); // Sort busses according to their numbers using a custom comparator Collections.sort(sectionList, new Comparator<String>() { @Override public int compare(String lhs, String rhs) { if(lhs.length() < rhs.length()) { return -1; } else if(lhs.length() > rhs.length()) { return 1; } else { int l = Integer.parseInt(lhs); int r = Integer.parseInt(rhs); if(l < r) { return -1; } else if(l > r) { return 1; } else { return 0; } } } }); mySections = new String[sectionList.size()]; // Update sections sectionList.toArray(mySections); } public int getPositionForSection(int section) { return myIndexer.get(mySections[section]); } public int getSectionForPosition(int position) { return 0; } public Object[] getSections() { return mySections; } }