/** * ORcycle, Copyright 2014, 2015, PSU Transportation, Technology, and People Lab. * * @author Robin Murray <robin5@pdx.edu> (code) * @author Miguel Figliozzi <figliozzi@pdx.edu> and ORcycle team (general app * design and features, report questionnaires and new ORcycle features) * * For more information on the project, go to * http://www.pdx.edu/transportation-lab/orcycle and http://www.pdx.edu/transportation-lab/app-development * * Updated/modified for Oregon pilot study and app deployment. * * ORcycle 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 any later version. * ORcycle 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 * ORcycle. If not, see <http://www.gnu.org/licenses/>. * */ package edu.pdx.cecs.orcycle; import java.util.ArrayList; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.database.SQLException; import android.os.Bundle; import android.support.v4.app.Fragment; import android.util.Log; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ListView; import android.widget.Toast; public class FragmentSavedTripsSection extends Fragment { private static final String MODULE_TAG = "FragmentSavedTripsSection"; public static final String ARG_SECTION_NUMBER = "section_number"; private SavedTripsAdapter savedTripsAdapter; private ListView lvSavedTrips; private MenuItem menuDelete; private MenuItem menuUpload; private ActionMode editMode; private final ActionMode.Callback editModeCallback = new EditModeCallback(); Long tripIdToUpload; Cursor cursorTrips; // ********************************************************************************* // * Fragment Life Cycle // ********************************************************************************* public FragmentSavedTripsSection() { } /** * Called once the fragment has been created in order for it * to create it's user interface. */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.v(MODULE_TAG, "Cycle: onCreateView()"); View rootView = null; Intent intent; Bundle extras; try { if (null != (rootView = inflater.inflate(R.layout.activity_saved_trips, null))) { lvSavedTrips = (ListView) rootView.findViewById(R.id.listViewSavedTrips); lvSavedTrips.setOnItemClickListener(new SavedTrips_OnItemClickListener()); setHasOptionsMenu(true); if (null != (intent = getActivity().getIntent())) { if (null != (extras = intent.getExtras())) { if (!extras.getBoolean(TabsConfig.EXTRA_KEEP_ME, false)) { cleanTrips(); } } } //tripIdArray.clear(); } } catch (Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } return rootView; } @Override public void onResume() { super.onResume(); try { Log.v(MODULE_TAG, "Cycle: SavedTrips onResume"); populateTripList(lvSavedTrips); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } /** * Creates menu items */ @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { try { // Inflate the menu items for use in the action bar inflater.inflate(R.menu.saved_trips, menu); super.onCreateOptionsMenu(menu, inflater); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } /** * Handles menu item selections */ @Override public boolean onOptionsItemSelected(MenuItem item) { try { // Handle presses on the action bar items switch (item.getItemId()) { case R.id.action_edit_saved_trips: return startActionModeEdit(); default: return super.onOptionsItemSelected(item); } } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } return false; } // ********************************************************************************* // * Fragment Actions // ********************************************************************************* void populateTripList(ListView lv) { // Get list from the real phone database. W00t! final DbAdapter mDb = new DbAdapter(getActivity()); mDb.open(); try { cursorTrips = mDb.fetchAllTrips(); savedTripsAdapter = new SavedTripsAdapter(getActivity(), R.layout.saved_trips_list_item, cursorTrips, getResources().getColor(R.color.default_color), getResources().getColor(R.color.pressed_color)); lv.setAdapter(savedTripsAdapter); lv.invalidate(); } catch (SQLException sqle) { // Do nothing, for now! } finally { mDb.close(); } // registerForContextMenu(lvSavedTrips); TODO: Validate this isn't needed } private void cleanTrips() { final DbAdapter mDb = new DbAdapter(getActivity()); mDb.open(); try { // Clean up any bad trips & coords from crashes int cleanedTrips = 0; // cleanedTrips = mDb.cleanTripsCoordsTables(); TODO: Why was this taken out? if (cleanedTrips > 0) { Toast.makeText(getActivity(), "" + cleanedTrips + " bad trip(s) removed.", Toast.LENGTH_SHORT).show(); } } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } finally { mDb.close(); } } private void retryTripUpload(long tripId) { TripUploader uploader = new TripUploader(getActivity(), MyApplication.getInstance().getUserId()); FragmentSavedTripsSection f2 = (FragmentSavedTripsSection) getActivity() .getSupportFragmentManager().findFragmentByTag( "android:switcher:" + R.id.pager + ":1"); uploader.setSavedTripsAdapter(savedTripsAdapter); uploader.setFragmentSavedTripsSection(f2); uploader.setListView(lvSavedTrips); uploader.execute(); } private void actionDeleteSelectedTrips(ArrayList<Long> tripIds) { try { // delete selected trips for (long tripId: tripIds) { try { deleteTrip(tripId); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } private void deleteTrip(long tripId) { DbAdapter mDbHelper = new DbAdapter(getActivity()); mDbHelper.open(); try { mDbHelper.deleteAllCoordsForTrip(tripId); mDbHelper.deletePauses(tripId); mDbHelper.deleteAnswers(tripId); mDbHelper.deleteTrip(tripId); } finally { mDbHelper.close(); } lvSavedTrips.invalidate(); populateTripList(lvSavedTrips); } private void clearSelections() { int numListViewItems = lvSavedTrips.getChildCount(); savedTripsAdapter.clearSelectedItems(); // Reset all list items to their normal color for (int i = 0; i < numListViewItems; i++) { lvSavedTrips.getChildAt(i).setBackgroundColor(getResources().getColor(R.color.default_color)); } } // ********************************************************************************* // * Item Click Listener // ********************************************************************************* private final class SavedTrips_OnItemClickListener implements AdapterView.OnItemClickListener { public void onItemClick(AdapterView<?> parent, View v, int pos, long id) { int uploadStatus; try { cursorTrips.moveToPosition(pos); if (editMode == null) { uploadStatus = cursorTrips.getInt(cursorTrips.getColumnIndex("status")); if (uploadStatus == TripData.STATUS_SENT /* 2 */) { transitionToTripMapActivity(id); } else if (uploadStatus == TripData.STATUS_COMPLETE /* 1 */) { dialogTripNotUploaded(id); } } else { savedTripsAdapter.toggleSelection(id); if (savedTripsAdapter.isSelected(id)) { v.setBackgroundColor(getResources().getColor(R.color.pressed_color)); } else { v.setBackgroundColor(getResources().getColor(R.color.default_color)); } menuDelete.setEnabled(savedTripsAdapter.numSelectedItems() > 0); editMode.setTitle(savedTripsAdapter.numSelectedItems() + " Selected"); } } catch (Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } } // ********************************************************************************* // * Edit Action Mode // ********************************************************************************* /** * Starts the edit action mode. * @return true if new action mode was started, false otherwise. */ private boolean startActionModeEdit() { if (editMode != null) { return false; } // Start the CAB using the ActionMode.Callback defined above editMode = getActivity().startActionMode(editModeCallback); return true; } private final class EditModeCallback implements ActionMode.Callback { /** * Called when the action mode is created; startActionMode() was called */ @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { try { // Inflate a menu resource providing context menu items MenuInflater inflater = mode.getMenuInflater(); inflater.inflate(R.menu.saved_trips_context_menu, menu); return true; } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } return false; } /** * Called each time the action mode is shown. Always * called after onCreateActionMode, but may be called * multiple times if the mode is invalidated. */ @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { try { int numSelectedItems = savedTripsAdapter.getSelectedItems().size(); menuDelete = menu.findItem(R.id.action_delete_saved_trips); menuDelete.setEnabled(numSelectedItems > 0); menuUpload = menu.findItem(R.id.action_upload_saved_trips); // Search the saved trips, and look for the first completed trip int flag = 1; for (int i = 0; i < lvSavedTrips.getCount(); i++) { cursorTrips.moveToPosition(i); // Calculate flag value flag = flag * (cursorTrips.getInt(cursorTrips.getColumnIndex(DbAdapter.K_TRIP_STATUS)) - 1); // if the status was 0 then flag = flag * -1 (STATUS_INCOMPLETE) // if the status was 1 then flag = flag * 0 = 0 (STATUS_COMPLETE) // if the status was 2 then flag = flag * 1 (STATUS_SENT) // Note: Once the flag becomes 0 it will stay 0 if (flag == 0) { // then a completed entry was found tripIdToUpload = cursorTrips.getLong(cursorTrips.getColumnIndex(DbAdapter.K_TRIP_ROWID)); Log.v(MODULE_TAG, "Next trip ID to upload" + tripIdToUpload); break; } } // Enable the upload menu item if any trips to be uploaded menuUpload.setEnabled(flag == 0); // Set title bar number of selections mode.setTitle(numSelectedItems + " Selected"); return true; } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } return false; // Return false if nothing is done } /** * Called when the user selects a contextual menu item */ @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { try { switch (item.getItemId()) { case R.id.action_delete_saved_trips: // delete selected trips actionDeleteSelectedTrips(savedTripsAdapter.getSelectedItems()); mode.finish(); // Action picked, so close the CAB return true; case R.id.action_upload_saved_trips: try { retryTripUpload(tripIdToUpload); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } mode.finish(); // Action picked, so close the CAB return true; default: return false; } } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } return false; } /** * Called when the user exits the action mode */ @Override public void onDestroyActionMode(ActionMode mode) { try { editMode = null; clearSelections(); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } } // ********************************************************************************* // * Dialog Trip not Uploaded // ********************************************************************************* private void dialogTripNotUploaded(final long tripId) { try { final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Upload Trip"); builder.setMessage("Do you want to upload this trip?"); builder.setNegativeButton("Upload", new dialogTripNotUploaded_UploadButton(tripId)); builder.setPositiveButton("Cancel", new dialogTripNotUploaded_CancelButton()); final AlertDialog alert = builder.create(); alert.show(); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } private final class dialogTripNotUploaded_CancelButton implements DialogInterface.OnClickListener { public void onClick(DialogInterface dialog, int id) { try { dialog.cancel(); // continue } catch (Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } } private final class dialogTripNotUploaded_UploadButton implements DialogInterface.OnClickListener { private final long tripId; private dialogTripNotUploaded_UploadButton(long tripId) { this.tripId = tripId; } public void onClick(DialogInterface dialog, int id) { try { dialog.cancel(); retryTripUpload(tripId); } catch (Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } } // ********************************************************************************* // * Transitions // ********************************************************************************* private void transitionToTripMapActivity(long tripId) { Intent intent = new Intent(getActivity(), TripMapActivity.class); intent.putExtra(TripMapActivity.EXTRA_TRIP_ID, tripId); intent.putExtra(TripMapActivity.EXTRA_IS_NEW_TRIP, false); intent.putExtra(TripMapActivity.EXTRA_TRIP_SOURCE, TripMapActivity.EXTRA_TRIP_SOURCE_SAVED_TRIPS); startActivity(intent); getActivity().overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left); getActivity().finish(); } }