package edu.mit.mitmobile2.tour;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import edu.mit.mitmobile2.MITMenuItem;
import edu.mit.mitmobile2.MITPlainSecondaryTitleBar;
import edu.mit.mitmobile2.NewModule;
import edu.mit.mitmobile2.NewModuleActivity;
import edu.mit.mitmobile2.R;
import edu.mit.mitmobile2.RemoteImageView;
import edu.mit.mitmobile2.TitleBarSwitch;
import edu.mit.mitmobile2.TitleBarSwitch.OnToggledListener;
import edu.mit.mitmobile2.maps.MITMapView;
import edu.mit.mitmobile2.objs.MapItem;
import edu.mit.mitmobile2.tour.Tour.GeoPoint;
import edu.mit.mitmobile2.tour.Tour.SideTripTourMapItem;
import edu.mit.mitmobile2.tour.Tour.SiteTourMapItem;
import edu.mit.mitmobile2.tour.Tour.TourMapItem;
import edu.mit.mitmobile2.tour.Tour.TourSiteStatus;
import edu.mit.mitmobile2.tour.Tour.TourMapItem.LocationSupplier;
import edu.mit.mitmobile2.tour.TourStopsMapData.OnTourSiteSelectedListener;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
public class TourMapActivity extends NewModuleActivity implements OnTourSiteSelectedListener {
private static final String TOUR_STOPS_KEY = "tour_stops";
private static final String TOUR_PATH_KEY = "tour_path";
private static final String TOUR_ACTIVE_KEY = "tour_active";
private static String LIST = "LIST";
private static String MAP = "MAP";
ListView mTourListView;
TitleBarSwitch mMapListSwitch;
View mMapLegend;
private MITMapView mMapView;
TourStartHelpActionRow mStartHelpActionRow;
boolean mTourActive;
int mTourCurrentPosition;
List<SiteTourMapItem> mSiteTourMapItems;
ArrayList<TourMapItem> mTourMapItems = new ArrayList<TourMapItem>();
LocationManager mLocationManager;
String mBestLocationProviderName;
String mWorstLocationProviderName;
Long mShowClosestBalloonInitialTime;
private TourItemAdapter mTourListAdapter;
private ArrayList<GeoPoint> mGeoPoints;
private MITPlainSecondaryTitleBar mSecondaryTitleBar;
private TourStopsMapData mStopsData;
private static int HELP_SELECT_STOP = 2;
public static void launch(Context context, ArrayList<TourMapItem> tourItems, ArrayList<GeoPoint> geoPoints, boolean tourActive) {
Intent intent = new Intent(context, TourMapActivity.class);
intent.putParcelableArrayListExtra(TOUR_STOPS_KEY, tourItems);
intent.putParcelableArrayListExtra(TOUR_PATH_KEY, geoPoints);
intent.putExtra(TOUR_ACTIVE_KEY, tourActive);
context.startActivity(intent);
}
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
Intent i = getIntent();
setContentView(R.layout.tour_map);
mMapView = (MITMapView) findViewById(R.id.tourMapView);
mSiteTourMapItems = i.getParcelableArrayListExtra(TOUR_STOPS_KEY);
mGeoPoints = i.getParcelableArrayListExtra(TOUR_PATH_KEY);
mTourActive = i.getBooleanExtra(TOUR_ACTIVE_KEY, false);
mTourCurrentPosition = getCurrentPosition();
// be default show sidetrips in list if tour not yet active
mShowingSidetrips = !mTourActive;
setTourItemsList(mShowingSidetrips);
mTourListView = (ListView) findViewById(R.id.mapListView);
mTourListAdapter = new TourItemAdapter(this, mTourMapItems);
mTourListView.setAdapter(mTourListAdapter);
mTourListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TourMapItem tourMapItem = mTourMapItems.get(position);
if(tourMapItem.getClass() == SiteTourMapItem.class) {
SiteTourMapItem siteItem = (SiteTourMapItem) tourMapItem;
launchTour(siteItem);
} else if(tourMapItem.getClass() == SideTripTourMapItem.class) {
SideTripTourMapItem sidetripItem = (SideTripTourMapItem) tourMapItem;
TourSideTripActivity.launch(
TourMapActivity.this, sidetripItem.getParent().getSiteGuid(),
sidetripItem.getSideTripId(), sidetripItem.isOnSideTrip());
}
}
});
float density = getResources().getDisplayMetrics().density;
float routeWidth = getResources().getDimension(R.dimen.tourPathWidth) / density;
TourRouteMapData routeData = new TourRouteMapData(mGeoPoints, routeWidth);
mMapView.pause();
mMapView.addMapItems(routeData.getMapItems(), "route");
mStopsData = new TourStopsMapData(mSiteTourMapItems, this);
mMapView.addMapItems(mStopsData.getMapItems(), "stops");
mMapView.fitMapItems();
mMapView.unpause();
mSecondaryTitleBar = new MITPlainSecondaryTitleBar(this);
mMapListSwitch = new TitleBarSwitch(this);
mMapListSwitch.setLabels(MAP, LIST);
mMapListSwitch.setSelected(MAP);
mMapListSwitch.setOnToggledListener(new OnToggledListener() {
@Override
public void onToggled(String selected) {
toggleMapList(selected);
}
});
getTitleBar().addSecondaryBar(mSecondaryTitleBar);
mStartHelpActionRow = (TourStartHelpActionRow) findViewById(R.id.tourMapStartHelp);
mStartHelpActionRow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(TourMapActivity.this, TourStartHelpActivity.class);
startActivityForResult(intent, HELP_SELECT_STOP);
}
});
mMapLegend = findViewById(R.id.tourMapLegend);
mLocationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
Criteria fineCriteria = new Criteria();
fineCriteria.setAccuracy(Criteria.ACCURACY_FINE);
mBestLocationProviderName = mLocationManager.getBestProvider(fineCriteria, true);
Criteria criteria = new Criteria();
mWorstLocationProviderName = mLocationManager.getBestProvider(criteria, true);
// set the locations manager for tour map items (this allows the tour map items to calculate there distance from user)
for(TourMapItem mapItem : mSiteTourMapItems) {
mapItem.setLocationSupplier(mLocationSupplier);
}
if(mTourActive) {
mSecondaryTitleBar.setTitle("Tour Overview");
displayCallout(mSiteTourMapItems.get(mTourCurrentPosition));
} else {
// tour not active, show information to help the user know where to start
mShowClosestBalloonInitialTime = System.currentTimeMillis();
mStartHelpActionRow.setVisibility(View.VISIBLE);
mSecondaryTitleBar.setTitle("Select a Starting Point");
mMapLegend.setVisibility(View.GONE);
}
mSecondaryTitleBar.addActionView(mMapListSwitch);
}
@Override
public void onTourSiteSelected(TourMapItem tourMapItem) {
if(tourMapItem.getClass() == SiteTourMapItem.class) {
SiteTourMapItem siteItem = (SiteTourMapItem) tourMapItem;
launchTour(siteItem);
}
}
MapActivityLocationSupplier mLocationSupplier = new MapActivityLocationSupplier();
private class MapActivityLocationSupplier implements LocationSupplier {
Location mLocation;
public void setLocation(Location location) {
if(location.getProvider().equals(mBestLocationProviderName) ||
mLocation == null ||
mLocation.getProvider().equals(mWorstLocationProviderName)) {
// only update the location if it comes from the best provider, or the old data is not very good.
mLocation = location;
}
}
@Override
public Location getLocation() {
if (mLocation != null) {
return mLocation;
} else {
String provider = mBestLocationProviderName;
if (provider == null) {
provider = mWorstLocationProviderName;
}
if (provider != null) {
return mLocationManager.getLastKnownLocation(provider);
} else {
return null;
}
}
}
};
LocationListener mLocationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
mLocationSupplier.setLocation(location);
if(mShowClosestBalloonInitialTime != null) {
// do not wait more than 7 seconds for the initial fix
if(System.currentTimeMillis() - mShowClosestBalloonInitialTime < 7 * 1000) {
if(showClosestSiteBalloon(location)) {
mShowClosestBalloonInitialTime = null;
}
} else {
mShowClosestBalloonInitialTime = null;
}
}
mTourListAdapter.notifyDataSetChanged();
}
@Override
public void onProviderDisabled(String provider) {}
@Override
public void onProviderEnabled(String provider) {}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {}
};
@Override
public void onResume() {
super.onResume();
if(mBestLocationProviderName != null) {
mLocationManager.requestLocationUpdates(mBestLocationProviderName, 5 * 1000, 0, mLocationListener);
}
if(mWorstLocationProviderName != null) {
mLocationManager.requestLocationUpdates(mWorstLocationProviderName, 5 * 1000, 0, mLocationListener);
}
}
@Override
public void onPause() {
super.onPause();
mLocationManager.removeUpdates(mLocationListener);
}
private boolean showClosestSiteBalloon(Location location) {
// search for closest site
// find the two closest sites (see if one is significantly closer than the other)
// and compare the closeness to the accuracy of the location fix (to see if we trust which one is closer)
TourMapItem closest = null;
TourMapItem secondClosest = null;
for(TourMapItem mapItem : mSiteTourMapItems) {
if(closest == null) {
closest = mapItem;
} else if(mapItem.distance() < closest.distance()) {
secondClosest = closest;
closest = mapItem;
} else if( (secondClosest == null) || (mapItem.distance() < secondClosest.distance()) ) {
secondClosest = mapItem;
}
}
// check if the accuracy of the location is sufficient to distinguish
// between the closest and second closest location
if(closest.distanceBetween(secondClosest) < location.getAccuracy()) {
return false;
}
// check to see if we are so far away that we rather use the default first stop
// dont show closest if we are further than 2km
if(closest.distance() > 2000) {
//mSiteMarkers.showBalloon(mSiteTourMapItems.get(0));
return true;
}
// now that we have survived all the sanity checks actually show the balloon
// for the closest tour site;
//mSiteMarkers.showBalloon(closest);
return true;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent resultIntent) {
if(requestCode == HELP_SELECT_STOP && resultCode == RESULT_OK) {
TourMapItem item = resultIntent.getParcelableExtra(TourStartHelpActivity.SELECTED_SITE);
item.setLocationSupplier(mLocationSupplier);
displayCallout(item);
}
}
private void displayCallout(TourMapItem tourMapItem) {
if (mStopsData != null) {
MapItem mapItem = mStopsData.getMapItem(tourMapItem);
mMapView.displayCallout(this, mapItem);
}
}
private int getCurrentPosition() {
for(int i = 0; i < mSiteTourMapItems.size(); i++) {
TourMapItem item = mSiteTourMapItems.get(i);
if(item.getStatus() == TourSiteStatus.CURRENT) {
return i;
}
}
return -1;
}
private int getTourItemPosition(String siteGuid) {
for(int i = 0; i < mSiteTourMapItems.size(); i++) {
SiteTourMapItem item = mSiteTourMapItems.get(i);
if(item.getSiteGuid().equals(siteGuid)) {
return i;
}
}
return -1;
}
void launchTour(final SiteTourMapItem tourItem) {
int position = getTourItemPosition(tourItem.getSiteGuid());
if(mTourActive) {
if(position == mTourCurrentPosition) {
TourStopSliderActivity.launchFromSiteGuid(this, tourItem.getSiteGuid(), false);
} else if(position == mTourCurrentPosition + 1) {
TourStopSliderActivity.launchFromSiteGuid(this, tourItem.getSiteGuid(), true);
} else {
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
alertBuilder.setTitle("Skip Notification");
alertBuilder.setNegativeButton("No", null);
alertBuilder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
TourStopSliderActivity.launchFromSiteGuid(TourMapActivity.this, tourItem.getSiteGuid(), false);
}
});
String message;
if(position > mTourCurrentPosition) {
message = "Are you sure you want to skip " + (position-mTourCurrentPosition) + " stops?";
} else {
message = "Are you sure you want to go back " + (mTourCurrentPosition-position) + " stops?";
}
alertBuilder.setMessage(message);
alertBuilder.create().show();
}
} else {
TourStopSliderActivity.launchFromSiteGuid(this, tourItem.getSiteGuid(), false);
}
}
private boolean isListVisible() {
if (mTourListView != null) {
return (mTourListView.getVisibility() == View.VISIBLE);
}
return false;
}
@Override
protected List<MITMenuItem> getSecondaryMenuItems() {
ArrayList<MITMenuItem> items = new ArrayList<MITMenuItem>();
if (isListVisible()) {
if (!mShowingSidetrips) {
items.add(new MITMenuItem("showsidetrips", "Show Side Trips"));
} else {
items.add(new MITMenuItem("hidesidetrips", "Hide Side Trips"));
}
}
return items;
}
@Override
protected void onOptionSelected(String optionId) {
if (optionId.equals("showsidetrips") || optionId.equals("hidesidetrips")) {
showOrHideSidetrips(optionId);
}
}
private void toggleMapList(String selected) {
if(selected.equals(LIST)) {
mTourListView.setVisibility(View.VISIBLE);
mMapView.setVisibility(View.GONE);
if(mTourActive) {
mMapLegend.setVisibility(View.GONE);
} else {
mStartHelpActionRow.setVisibility(View.GONE);
}
} else if(selected.equals(MAP)) {
mTourListView.setVisibility(View.GONE);
mMapView.setVisibility(View.VISIBLE);
if(mTourActive) {
mMapLegend.setVisibility(View.VISIBLE);
} else {
mStartHelpActionRow.setVisibility(View.VISIBLE);
}
}
refreshTitleBarOptions();
}
/*
* methods for hiding and showing the side trips in the list view
*/
private boolean mShowingSidetrips = true;
private void showOrHideSidetrips(String optionId) {
mShowingSidetrips = optionId.equals("showsidetrips");
setTourItemsList(mShowingSidetrips);
mTourListAdapter.notifyDataSetChanged();
refreshTitleBarOptions();
}
private void setTourItemsList(boolean includeSidetrips) {
mTourMapItems.clear();
for(SiteTourMapItem item : mSiteTourMapItems) {
mTourMapItems.add(item);
if(includeSidetrips) {
mTourMapItems.addAll(item.getSideTrips());
}
}
}
class TourItemAdapter extends ArrayAdapter<TourMapItem> {
// we need to cache the views to prevent the thumbnail images
// from reloading the bitmap images, when distances change
class RowViewHashMap extends LinkedHashMap<String, View> {
private static final long serialVersionUID = 1L;
protected static final int MAX_ENTRIES = 10;
@Override
protected boolean removeEldestEntry(Map.Entry<String, View> eldest) {
return size() > MAX_ENTRIES;
}
}
RowViewHashMap mViewCache = new RowViewHashMap();
private Context mContext;
public TourItemAdapter(Context context, List<TourMapItem> items) {
super(context, 0, 0, items);
mContext = context;
}
@Override
public View getView(int position, View view, ViewGroup parent) {
TourMapItem item = getItem(position);
int contentId = -1;
int titleId = -1;
int distanceId = -1;
if(item.getClass() == SiteTourMapItem.class) {
contentId = R.id.tourItemSiteContent;
titleId = R.id.tourItemSiteTitle;
distanceId = R.id.tourItemSiteDistance;
} else if(item.getClass() == SideTripTourMapItem.class) {
contentId = R.id.tourItemSideTripContent;
titleId = R.id.tourItemSideTripTitle;
distanceId = R.id.tourItemSideTripDistance;
}
if(!mViewCache.containsKey(item.getId())) {
LayoutInflater inflator = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflator.inflate(R.layout.tour_list_item, null);
// fill in most of the views context (except for distance)
RemoteImageView imageView = (RemoteImageView) view.findViewById(R.id.tourItemImage);
imageView.setURL(item.getPhotoUrl());
view.findViewById(contentId).setVisibility(View.VISIBLE);
TextView titleView = (TextView) view.findViewById(titleId);
titleView.setText(item.getTitle());
ImageView statusImage = (ImageView) view.findViewById(R.id.tourItemStatus);
int resourceId = 0;
if(item.getStatus() == TourSiteStatus.VISITED) {
resourceId = R.drawable.map_past;
} else if(item.getStatus() == TourSiteStatus.CURRENT) {
resourceId = R.drawable.map_currentstop;
} else if(item.getStatus() == TourSiteStatus.FUTURE) {
resourceId = R.drawable.map_future;
}
statusImage.setImageDrawable(getResources().getDrawable(resourceId));
} else {
view = mViewCache.get(item.getId());
}
TextView distanceView = (TextView) view.findViewById(distanceId);
Float distance = item.distance();
if(distance != null) {
distanceView.setText(LocaleMeasurements.getDistance(item.distance()));
} else {
distanceView.setText(null);
}
// only cache views which are visible (to prevent flicker)
// invisible views cant filcker
if(position >= mTourListView.getFirstVisiblePosition()) {
mViewCache.put(item.getId(), view);
}
return view;
}
}
@Override
protected NewModule getNewModule() {
return new TourModule();
}
@Override
public boolean isModuleHomeActivity() {
return false;
}
@Override
protected boolean isScrollable() {
return false;
}
}