/* * Copyright (C) 2012-2013 Paul Watts (paulcwatts@gmail.com) * * 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 org.onebusaway.android.ui; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.common.api.GoogleApiClient; import org.onebusaway.android.R; import org.onebusaway.android.app.Application; import org.onebusaway.android.directions.util.ConversionUtils; import org.onebusaway.android.directions.util.CustomAddress; import org.onebusaway.android.directions.util.OTPConstants; import org.onebusaway.android.directions.util.PlacesAutoCompleteAdapter; import org.onebusaway.android.directions.util.TripRequestBuilder; import org.onebusaway.android.io.ObaAnalytics; import org.onebusaway.android.io.elements.ObaRegion; import org.onebusaway.android.map.googlemapsv2.ProprietaryMapHelpV2; import org.onebusaway.android.util.LocationUtils; import org.onebusaway.android.util.PreferenceUtils; import org.onebusaway.android.util.UIUtils; import android.app.DatePickerDialog; import android.app.Dialog; import android.app.TimePickerDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.res.TypedArray; import android.location.Location; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v7.app.AlertDialog; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.CheckBox; import android.widget.DatePicker; import android.widget.EditText; import android.widget.ImageButton; import android.widget.Spinner; import android.widget.TextView; import android.widget.TimePicker; import android.widget.Toast; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; public class TripPlanFragment extends Fragment { public TripPlanFragment setPlanErrorUrl(String planErrorUrl) { mPlanErrorUrl = planErrorUrl; return this; } public TripPlanFragment setPlanRequestUrl(String planRequestUrl) { mPlanRequestUrl = planRequestUrl; return this; } /** * Allows calling activity to register to know when to send request. */ interface Listener { /** * Called when the fields have been populated and a trip plan can occur. */ void onTripRequestReady(); } private static final int USE_FROM_ADDRESS = 1; private static final int USE_TO_ADDRESS = 2; private AutoCompleteTextView mFromAddressTextArea; private AutoCompleteTextView mToAddressTextArea; private ImageButton mFromCurrentLocationImageButton; private ImageButton mToCurrentLocationImageButton; private Spinner mDate; private ArrayAdapter mDateAdapter; private Spinner mTime; private ArrayAdapter mTimeAdapter; private Spinner mLeavingChoice; ArrayAdapter<CharSequence> mLeavingChoiceAdapter; Calendar mMyCalendar; protected GoogleApiClient mGoogleApiClient; private static final String TAG = "TripPlanFragment"; private CustomAddress mFromAddress, mToAddress; private TripRequestBuilder mBuilder; private Listener mListener; private String mPlanErrorUrl; private String mPlanRequestUrl; private void resetDateTimeLabels() { String dateText = new SimpleDateFormat(OTPConstants.TRIP_PLAN_DATE_STRING_FORMAT, Locale.getDefault()) .format(mMyCalendar.getTime()); String timeText = new SimpleDateFormat(OTPConstants.TRIP_PLAN_TIME_STRING_FORMAT, Locale.getDefault()) .format(mMyCalendar.getTime()); mDateAdapter.insert(dateText, 0); mDateAdapter.notifyDataSetChanged(); mTimeAdapter.insert(timeText, 0); mTimeAdapter.notifyDataSetChanged(); } // Create view, initialize state @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Init Google Play Services as early as possible in the Fragment lifecycle to give it time if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(getActivity()) == ConnectionResult.SUCCESS) { mGoogleApiClient = LocationUtils.getGoogleApiClientWithCallbacks(getContext()); mGoogleApiClient.connect(); } Bundle bundle = getArguments(); mBuilder = new TripRequestBuilder(bundle); final View view = inflater.inflate(R.layout.fragment_trip_plan, container, false); setHasOptionsMenu(true); mFromAddressTextArea = (AutoCompleteTextView) view.findViewById(R.id.fromAddressTextArea); mToAddressTextArea = (AutoCompleteTextView) view.findViewById(R.id.toAddressTextArea); mFromCurrentLocationImageButton = (ImageButton) view.findViewById(R.id.fromCurrentLocationImageButton); mToCurrentLocationImageButton = (ImageButton) view.findViewById(R.id.toCurrentLocationImageButton); mDate = (Spinner) view.findViewById(R.id.date); mDateAdapter = new ArrayAdapter(getActivity(), R.layout.simple_list_item); mDate.setAdapter(mDateAdapter); mTime = (Spinner) view.findViewById(R.id.time); mTimeAdapter = new ArrayAdapter(getActivity(), R.layout.simple_list_item); mTime.setAdapter(mTimeAdapter); mLeavingChoice = (Spinner) view.findViewById(R.id.leavingChoiceSpinner); mLeavingChoiceAdapter = ArrayAdapter.createFromResource(getContext(), R.array.trip_plan_leaving_arriving_array, R.layout.simple_list_item); mLeavingChoiceAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mLeavingChoice.setAdapter(mLeavingChoiceAdapter); // Set mLeavingChoice onclick adapter in onResume() so we do not fire it when setting it final TimePickerDialog.OnTimeSetListener timeCallback = new TimePickerDialog.OnTimeSetListener() { @Override public void onTimeSet(TimePicker view, int hour, int minute) { mMyCalendar.set(Calendar.HOUR_OF_DAY, hour); mMyCalendar.set(Calendar.MINUTE, minute); resetDateTimeLabels(); mBuilder.setDateTime(mMyCalendar); checkRequestAndSubmit(); } }; final DatePickerDialog.OnDateSetListener dateCallback = new DatePickerDialog.OnDateSetListener() { @Override public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { mMyCalendar.set(Calendar.YEAR, year); mMyCalendar.set(Calendar.MONTH, monthOfYear); mMyCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth); resetDateTimeLabels(); mBuilder.setDateTime(mMyCalendar); checkRequestAndSubmit(); } }; mDate.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { if (motionEvent.getAction() == MotionEvent.ACTION_UP) { new DatePickerDialog(view.getContext(), dateCallback, mMyCalendar .get(Calendar.YEAR), mMyCalendar.get(Calendar.MONTH), mMyCalendar.get(Calendar.DAY_OF_MONTH)).show(); } return true; } }); mTime.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { if (motionEvent.getAction() == MotionEvent.ACTION_UP) { new TimePickerDialog(view.getContext(), timeCallback, mMyCalendar.get(Calendar.HOUR_OF_DAY), mMyCalendar.get(Calendar.MINUTE), false).show(); } return true; } }); ImageButton resetTimeButton = (ImageButton) view.findViewById(R.id.resetTimeImageButton); resetTimeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mMyCalendar = Calendar.getInstance(); mBuilder.setDateTime(mMyCalendar); resetDateTimeLabels(); checkRequestAndSubmit(); } }); setUpAutocomplete(mFromAddressTextArea, USE_FROM_ADDRESS); setUpAutocomplete(mToAddressTextArea, USE_TO_ADDRESS); mToCurrentLocationImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mToAddressTextArea.setText(getString(R.string.tripplanner_current_location)); mToAddress = makeAddressFromLocation(); mBuilder.setTo(mToAddress); checkRequestAndSubmit(); } }); mFromCurrentLocationImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mFromAddressTextArea.setText(getString(R.string.tripplanner_current_location)); mFromAddress = makeAddressFromLocation(); mBuilder.setFrom(mFromAddress); checkRequestAndSubmit(); } }); // Start: default from address is Current Location, to address is unset return view; } private void checkRequestAndSubmit() { if (mBuilder.ready() && mListener != null) { mListener.onTripRequestReady(); } } // Populate data fields @Override public void onResume() { super.onResume(); mFromAddress = mBuilder.getFrom(); if (mFromAddress == null) { mFromAddress = makeAddressFromLocation(); mBuilder.setFrom(mFromAddress); } setAddressText(mFromAddressTextArea, mFromAddress); mToAddress = mBuilder.getTo(); if (mToAddress == null) { mToAddress = CustomAddress.getEmptyAddress(); mBuilder.setTo(mToAddress); } setAddressText(mToAddressTextArea, mToAddress); boolean arriving = mBuilder.getArriveBy(); if (mMyCalendar == null) { Date date = mBuilder.getDateTime(); if (date == null) { date = new Date(); } mMyCalendar = Calendar.getInstance(); mMyCalendar.setTime(date); if (arriving) { mBuilder.setArrivalTime(mMyCalendar); } else { mBuilder.setDepartureTime(mMyCalendar); } } resetDateTimeLabels(); String leavingChoice = getString(arriving ? R.string.trip_plan_arriving : R.string.trip_plan_leaving); mLeavingChoice.setSelection(mLeavingChoiceAdapter.getPosition(leavingChoice), false); mLeavingChoice.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { String item = (String) parent.getItemAtPosition(position); if (item.equals(getString(R.string.trip_plan_arriving))) { mBuilder.setArrivalTime(mMyCalendar); } else { mBuilder.setDepartureTime(mMyCalendar); } checkRequestAndSubmit(); } @Override public void onNothingSelected(AdapterView<?> parent) { // do nothing } }); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.menu_trip_plan, menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); switch (id) { case R.id.action_settings: advancedSettings(); return true; case R.id.action_reverse: reverseTrip(); return true; case R.id.action_report_trip_problem: reportTripPlanProblem(); return true; default: break; } return super.onOptionsItemSelected(item); } /** * Set Listener of this fragment. */ public void setListener(Listener listener) { mListener = listener; } private void advancedSettings() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity()); final TypedArray transitModeResource = getContext().getResources().obtainTypedArray(R.array.transit_mode_array); final boolean unitsAreImperial = !PreferenceUtils.getUnitsAreMetricFromPreferences(getContext()); dialogBuilder.setTitle(R.string.trip_plan_advanced_settings) .setView(R.layout.trip_plan_advanced_settings_dialog); dialogBuilder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int which) { Dialog dialog = (Dialog) dialogInterface; boolean optimizeTransfers = ((CheckBox) dialog.findViewById(R.id.checkbox_minimize_transfers)) .isChecked(); Spinner spinnerTravelBy = (Spinner) dialog.findViewById(R.id.spinner_travel_by); int modeId = transitModeResource.getResourceId(spinnerTravelBy.getSelectedItemPosition(), 0); boolean wheelchair = ((CheckBox) dialog.findViewById(R.id.checkbox_wheelchair_acccesible)) .isChecked(); String maxWalkString = ((EditText) dialog.findViewById( R.id.number_maximum_walk_distance)).getText().toString(); double maxWalkDistance; if (TextUtils.isEmpty(maxWalkString)) { maxWalkDistance = Double.MAX_VALUE; } else { double d = Double.parseDouble(maxWalkString); maxWalkDistance = unitsAreImperial ? ConversionUtils.feetToMeters(d) : d; } mBuilder.setOptimizeTransfers(optimizeTransfers) .setModeSetById(modeId) .setWheelchairAccessible(wheelchair) .setMaxWalkDistance(maxWalkDistance); checkRequestAndSubmit(); } }); final AlertDialog dialog = dialogBuilder.create(); dialog.show(); CheckBox minimizeTransfersCheckbox = (CheckBox) dialog.findViewById(R.id.checkbox_minimize_transfers); Spinner spinnerTravelBy = (Spinner) dialog.findViewById(R.id.spinner_travel_by); CheckBox wheelchairCheckbox = (CheckBox) dialog.findViewById(R.id.checkbox_wheelchair_acccesible); EditText maxWalkEditText = (EditText) dialog.findViewById( R.id.number_maximum_walk_distance); minimizeTransfersCheckbox.setChecked(mBuilder.getOptimizeTransfers()); wheelchairCheckbox.setChecked(mBuilder.getWheelchairAccessible()); ArrayAdapter adapter = ArrayAdapter.createFromResource(getActivity(), R.array.transit_mode_array, android.R.layout.simple_spinner_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinnerTravelBy.setAdapter(adapter); int modeSetId = mBuilder.getModeSetId(); if (modeSetId != -1) { for (int i = 0; i < transitModeResource.length(); i++) { if (transitModeResource.getResourceId(i, -1) == modeSetId) { spinnerTravelBy.setSelection(i); break; } } } Double maxWalk = mBuilder.getMaxWalkDistance(); if (maxWalk != null && Double.MAX_VALUE != maxWalk) { if (unitsAreImperial) { maxWalk = ConversionUtils.metersToFeet(maxWalk); } maxWalkEditText.setText(String.format("%d", maxWalk.longValue())); } if (unitsAreImperial) { TextView label = (TextView) dialog.findViewById(R.id.label_minimum_walk_distance); label.setText(getString(R.string.feet_abbreviation)); } } private void reverseTrip() { mFromAddress = mBuilder.getTo(); mToAddress = mBuilder.getFrom(); mBuilder.setFrom(mFromAddress).setTo(mToAddress); setAddressText(mFromAddressTextArea, mFromAddress); setAddressText(mToAddressTextArea, mToAddress); if (mBuilder.ready() && mListener != null) { mListener.onTripRequestReady(); } } private void reportTripPlanProblem() { String email = Application.get().getCurrentRegion().getOtpContactEmail(); if (!TextUtils.isEmpty(email)) { Location loc = Application.getLastKnownLocation(getActivity().getApplicationContext(), null); String locString = null; if (loc != null) { locString = LocationUtils.printLocationDetails(loc); } if (mPlanErrorUrl != null) { UIUtils.sendEmail(getActivity(), email, locString, mPlanErrorUrl, true); } else if (mPlanRequestUrl != null) { UIUtils.sendEmail(getActivity(), email, locString, mPlanRequestUrl, false); } else { UIUtils.sendEmail(getActivity(), email, locString, null, true); } ObaAnalytics.reportEventWithCategory(ObaAnalytics.ObaEventCategory.UI_ACTION.toString(), getString(R.string.analytics_action_problem), getString(R.string.analytics_label_app_feedback_otp)); } } private void setAddressText(TextView tv, CustomAddress address) { if (address != null && address.getAddressLine(0) != null) { tv.setText(address.toString()); } else { tv.setText(null); } } private CustomAddress makeAddressFromLocation() { CustomAddress address = CustomAddress.getEmptyAddress(); Location loc = Application.getLastKnownLocation(getContext(), mGoogleApiClient); if (loc == null) { if (getContext() != null) { Toast.makeText(getContext(), getString(R.string.main_location_unavailable), Toast.LENGTH_SHORT).show(); } } else { address.setLatitude(loc.getLatitude()); address.setLongitude(loc.getLongitude()); } address.setAddressLine(0, getString(R.string.tripplanner_current_location)); return address; } @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { if (resultCode != -1) { Log.e(TAG, "Error getting geocoding results"); return; } CustomAddress address = ProprietaryMapHelpV2.getCustomAddressFromPlacesIntent(Application.get().getApplicationContext(), intent); // Note that onResume will run after this function. We need to put new objects in the bundle. if (requestCode == USE_FROM_ADDRESS) { mFromAddress = address; mBuilder.setFrom(mFromAddress); mFromAddressTextArea.setText(mFromAddress.toString()); } else if (requestCode == USE_TO_ADDRESS) { mToAddress = address; mBuilder.setTo(mToAddress); mToAddressTextArea.setText(mToAddress.toString()); } checkRequestAndSubmit(); } private void setUpAutocomplete(AutoCompleteTextView tv, final int use) { ObaRegion region = Application.get().getCurrentRegion(); // Use Google widget if available if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(getContext()) == ConnectionResult.SUCCESS) { tv.setFocusable(false); tv.setOnClickListener(new ProprietaryMapHelpV2.StartPlacesAutocompleteOnClick(use, this, region)); return; } // else, set up autocomplete with Android geocoder tv.setAdapter(new PlacesAutoCompleteAdapter(getContext(), android.R.layout.simple_list_item_1, region)); tv.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { CustomAddress addr = (CustomAddress) parent.getAdapter().getItem(position); if (use == USE_FROM_ADDRESS) { mFromAddress = addr; mBuilder.setFrom(mFromAddress); } else if (use == USE_TO_ADDRESS) { mToAddress = addr; mBuilder.setTo(mToAddress); } checkRequestAndSubmit(); } }); } }