package cgeo.geocaching; import cgeo.geocaching.activity.AbstractActionBarActivity; import cgeo.geocaching.activity.INavigationSource; import cgeo.geocaching.apps.navi.NavigationAppFactory; import cgeo.geocaching.location.DistanceParser; import cgeo.geocaching.location.Geopoint; import cgeo.geocaching.location.GeopointFormatter; import cgeo.geocaching.models.Destination; import cgeo.geocaching.sensors.GeoData; import cgeo.geocaching.sensors.GeoDirHandler; import cgeo.geocaching.sensors.Sensors; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.storage.DataStore; import cgeo.geocaching.ui.AbstractViewHolder; import cgeo.geocaching.ui.NavigationActionProvider; import cgeo.geocaching.ui.dialog.CoordinatesInputDialog; import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.AndroidRxUtils; import cgeo.geocaching.utils.Formatter; import cgeo.geocaching.utils.Log; import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.view.MenuItemCompat; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnCreateContextMenuListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.Spinner; import android.widget.TextView; import java.util.List; import butterknife.BindView; import butterknife.ButterKnife; import io.reactivex.schedulers.Schedulers; import org.apache.commons.lang3.StringUtils; public class NavigateAnyPointActivity extends AbstractActionBarActivity implements CoordinatesInputDialog.CoordinateUpdate, INavigationSource { @BindView(R.id.historyList) protected ListView historyListView; // list header fields are optional, due to being expanded later than the list itself @Nullable @BindView(R.id.buttonLatitude) protected Button latButton; @Nullable @BindView(R.id.buttonLongitude) protected Button lonButton; @Nullable @BindView(R.id.distance) protected EditText distanceEditText; @Nullable @BindView(R.id.distanceUnit) protected Spinner distanceUnitSelector; @Nullable @BindView(R.id.current) protected Button buttonCurrent; @Nullable @BindView(R.id.bearing) protected EditText bearingEditText; private boolean changed = false; private List<Destination> historyOfSearchedLocations; private DestinationHistoryAdapter destinationHistoryAdapter; private TextView historyFooter; private static final int CONTEXT_MENU_NAVIGATE = 1; private static final int CONTEXT_MENU_DELETE_WAYPOINT = 2; private static final int CONTEXT_MENU_EDIT_WAYPOINT = 3; private int contextMenuItemPosition; private String distanceUnit = StringUtils.EMPTY; protected static class ViewHolder extends AbstractViewHolder { @BindView(R.id.simple_way_point_longitude) protected TextView longitude; @BindView(R.id.simple_way_point_latitude) protected TextView latitude; @BindView(R.id.date) protected TextView date; public ViewHolder(final View rowView) { super(rowView); } } private static class DestinationHistoryAdapter extends ArrayAdapter<Destination> { private LayoutInflater inflater = null; DestinationHistoryAdapter(final Context context, final List<Destination> objects) { super(context, 0, objects); } @Override public View getView(final int position, final View convertView, final ViewGroup parent) { View rowView = convertView; final ViewHolder viewHolder; if (rowView == null) { rowView = getInflater().inflate(R.layout.simple_way_point, parent, false); viewHolder = new ViewHolder(rowView); } else { viewHolder = (ViewHolder) rowView.getTag(); } fillViewHolder(viewHolder, getItem(position)); return rowView; } private static void fillViewHolder(final ViewHolder viewHolder, final Destination loc) { final String lonString = loc.getCoords().format(GeopointFormatter.Format.LON_DECMINUTE); final String latString = loc.getCoords().format(GeopointFormatter.Format.LAT_DECMINUTE); viewHolder.longitude.setText(lonString); viewHolder.latitude.setText(latString); viewHolder.date.setText(Formatter.formatShortDateTime(loc.getDate())); } private LayoutInflater getInflater() { if (inflater == null) { inflater = LayoutInflater.from(getContext()); } return inflater; } } @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState, R.layout.navigateanypoint_activity); ButterKnife.bind(this); createHistoryView(); init(); } private void createHistoryView() { final View pointControls = getLayoutInflater().inflate(R.layout.navigateanypoint_header, historyListView, false); historyListView.addHeaderView(pointControls, null, false); // inject a second time to also find the dynamically expanded views above ButterKnife.bind(this); if (getHistoryOfSearchedLocations().isEmpty()) { historyListView.addFooterView(getEmptyHistoryFooter(), null, false); } historyListView.setAdapter(getDestinationHistoryAdapter()); historyListView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(final AdapterView<?> arg0, final View arg1, final int arg2, final long arg3) { final Object selection = arg0.getItemAtPosition(arg2); if (selection instanceof Destination) { navigateTo(((Destination) selection).getCoords()); } } }); historyListView.setOnCreateContextMenuListener(new OnCreateContextMenuListener() { @Override public void onCreateContextMenu(final ContextMenu menu, final View v, final ContextMenuInfo menuInfo) { menu.add(Menu.NONE, CONTEXT_MENU_NAVIGATE, Menu.NONE, res.getString(R.string.cache_menu_navigate)); menu.add(Menu.NONE, CONTEXT_MENU_EDIT_WAYPOINT, Menu.NONE, R.string.waypoint_edit); menu.add(Menu.NONE, CONTEXT_MENU_DELETE_WAYPOINT, Menu.NONE, R.string.waypoint_delete); } }); } @Override public boolean onContextItemSelected(final MenuItem item) { final AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); final int position = menuInfo != null ? menuInfo.position : contextMenuItemPosition; final Object destination = historyListView.getItemAtPosition(position); switch (item.getItemId()) { case CONTEXT_MENU_NAVIGATE: contextMenuItemPosition = position; if (destination instanceof Destination) { NavigationAppFactory.showNavigationMenu(this, null, null, ((Destination) destination).getCoords()); return true; } break; case CONTEXT_MENU_DELETE_WAYPOINT: if (destination instanceof Destination) { removeFromHistory((Destination) destination); } return true; case CONTEXT_MENU_EDIT_WAYPOINT: if (destination instanceof Destination) { final Geopoint gp = ((Destination) destination).getCoords(); getLatButton().setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); getLonButton().setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); } return true; default: } return super.onContextItemSelected(item); } private TextView getEmptyHistoryFooter() { if (historyFooter == null) { historyFooter = (TextView) getLayoutInflater().inflate(R.layout.cacheslist_footer, historyListView, false); historyFooter.setText(R.string.search_history_empty); } return historyFooter; } private DestinationHistoryAdapter getDestinationHistoryAdapter() { if (destinationHistoryAdapter == null) { destinationHistoryAdapter = new DestinationHistoryAdapter(this, getHistoryOfSearchedLocations()); } return destinationHistoryAdapter; } private List<Destination> getHistoryOfSearchedLocations() { if (historyOfSearchedLocations == null) { // Load from database historyOfSearchedLocations = DataStore.loadHistoryOfSearchedLocations(); } return historyOfSearchedLocations; } @Override public void onConfigurationChanged(final Configuration newConfig) { super.onConfigurationChanged(newConfig); init(); } @Override public void onResume() { super.onResume(geoDirHandler.start(GeoDirHandler.UPDATE_GEODATA)); init(); } private void init() { getLatButton().setOnClickListener(new CoordDialogListener()); getLonButton().setOnClickListener(new CoordDialogListener()); final Geopoint coords = Settings.getAnyCoordinates(); if (coords != null) { getLatButton().setText(coords.format(GeopointFormatter.Format.LAT_DECMINUTE)); getLonButton().setText(coords.format(GeopointFormatter.Format.LON_DECMINUTE)); } getButtonCurrent().setOnClickListener(new CurrentListener()); getDestinationHistoryAdapter().notifyDataSetChanged(); disableSuggestions(getDistanceEditText()); initializeDistanceUnitSelector(); } private void initializeDistanceUnitSelector() { if (StringUtils.isBlank(distanceUnit)) { if (Settings.useImperialUnits()) { getDistanceUnitSelector().setSelection(2); // ft distanceUnit = res.getStringArray(R.array.distance_units)[2]; } else { getDistanceUnitSelector().setSelection(0); // m distanceUnit = res.getStringArray(R.array.distance_units)[0]; } } getDistanceUnitSelector().setOnItemSelectedListener(new ChangeDistanceUnit(this)); } private class CoordDialogListener implements View.OnClickListener { @Override public void onClick(final View arg0) { Geopoint gp = null; if (getLatButton().getText().length() > 0 && getLonButton().getText().length() > 0) { gp = new Geopoint(getLatButton().getText().toString() + " " + getLonButton().getText().toString()); } final CoordinatesInputDialog coordsDialog = CoordinatesInputDialog.getInstance(null, gp); coordsDialog.setCancelable(true); coordsDialog.show(getSupportFragmentManager(), "wpedit_dialog"); } } @Override public void updateCoordinates(final Geopoint gp) { getLatButton().setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); getLonButton().setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); changed = true; } private static class ChangeDistanceUnit implements OnItemSelectedListener { private ChangeDistanceUnit(final NavigateAnyPointActivity unitView) { this.unitView = unitView; } private final NavigateAnyPointActivity unitView; @Override public void onItemSelected(final AdapterView<?> arg0, final View arg1, final int arg2, final long arg3) { unitView.distanceUnit = (String) arg0.getItemAtPosition(arg2); } @Override public void onNothingSelected(final AdapterView<?> arg0) { } } @Override public boolean onCreateOptionsMenu(final Menu menu) { getMenuInflater().inflate(R.menu.navigate_any_point_activity_options, menu); final MenuItem menuItem = menu.findItem(R.id.menu_default_navigation); menuItem.setTitle(NavigationAppFactory.getDefaultNavigationApplication().getName()); final NavigationActionProvider navAction = (NavigationActionProvider) MenuItemCompat.getActionProvider(menuItem); if (navAction != null) { navAction.setNavigationSource(this); } return true; } @Override public boolean onPrepareOptionsMenu(final Menu menu) { super.onPrepareOptionsMenu(menu); try { final boolean visible = getDestination() != null; menu.findItem(R.id.menu_navigate).setVisible(visible); menu.findItem(R.id.menu_default_navigation).setVisible(visible); menu.findItem(R.id.menu_caches_around).setVisible(visible); menu.findItem(R.id.menu_clear_history).setVisible(!getHistoryOfSearchedLocations().isEmpty()); } catch (final RuntimeException ignored) { // nothing } return true; } @Override public boolean onOptionsItemSelected(final MenuItem item) { final int menuItem = item.getItemId(); final Geopoint coords = getDestinationAndAddToHistory(); switch (menuItem) { case R.id.menu_default_navigation: navigateTo(coords); return true; case R.id.menu_caches_around: cachesAround(coords); return true; case R.id.menu_clear_history: clearHistory(); return true; case R.id.menu_navigate: NavigationAppFactory.showNavigationMenu(this, null, null, coords); return true; } return super.onOptionsItemSelected(item); } private Geopoint getDestinationAndAddToHistory() { final Geopoint coords = getDestination(); addToHistory(coords); return coords; } private void addToHistory(@Nullable final Geopoint coords) { if (coords == null) { return; } // Add locations to history final Destination loc = new Destination(coords); if (!getHistoryOfSearchedLocations().contains(loc)) { getHistoryOfSearchedLocations().add(0, loc); AndroidRxUtils.andThenOnUi(Schedulers.io(), new Runnable() { @Override public void run() { // Save location DataStore.saveSearchedDestination(loc); } }, new Runnable() { @Override public void run() { // Ensure to remove the footer historyListView.removeFooterView(getEmptyHistoryFooter()); destinationHistoryAdapter.notifyDataSetChanged(); } }); } } private void removeFromHistory(final Destination destination) { if (getHistoryOfSearchedLocations().contains(destination)) { getHistoryOfSearchedLocations().remove(destination); // Save DataStore.removeSearchedDestination(destination); if (getHistoryOfSearchedLocations().isEmpty() && historyListView.getFooterViewsCount() == 0) { historyListView.addFooterView(getEmptyHistoryFooter()); } getDestinationHistoryAdapter().notifyDataSetChanged(); showToast(res.getString(R.string.search_remove_destination)); } } private void clearHistory() { if (!getHistoryOfSearchedLocations().isEmpty()) { getHistoryOfSearchedLocations().clear(); // Save DataStore.clearSearchedDestinations(); if (historyListView.getFooterViewsCount() == 0) { historyListView.addFooterView(getEmptyHistoryFooter()); } getDestinationHistoryAdapter().notifyDataSetChanged(); showToast(res.getString(R.string.search_history_cleared)); } } private void navigateTo(final Geopoint coords) { if (coords == null) { showToast(res.getString(R.string.err_location_unknown)); return; } NavigationAppFactory.startDefaultNavigationApplication(1, this, coords); } private void cachesAround(final Geopoint coords) { if (coords == null) { showToast(res.getString(R.string.err_location_unknown)); return; } CacheListActivity.startActivityCoordinates(this, coords, null); finish(); } private final GeoDirHandler geoDirHandler = new GeoDirHandler() { @Override public void updateGeoData(final GeoData geo) { try { getLatButton().setHint(geo.getCoords().format(GeopointFormatter.Format.LAT_DECMINUTE_RAW)); getLonButton().setHint(geo.getCoords().format(GeopointFormatter.Format.LON_DECMINUTE_RAW)); } catch (final RuntimeException e) { Log.w("Failed to update location", e); } } }; private class CurrentListener implements View.OnClickListener { @Override public void onClick(final View arg0) { final Geopoint coords = Sensors.getInstance().currentGeo().getCoords(); getLatButton().setText(coords.format(GeopointFormatter.Format.LAT_DECMINUTE)); getLonButton().setText(coords.format(GeopointFormatter.Format.LON_DECMINUTE)); changed = false; } } private Geopoint getDestination() { final String bearingText = getBearingEditText().getText().toString(); // combine distance from EditText and distanceUnit saved from Spinner final String distanceText = getDistanceEditText().getText().toString() + distanceUnit; final String latText = getLatButton().getText().toString(); final String lonText = getLonButton().getText().toString(); if (StringUtils.isBlank(bearingText) && StringUtils.isBlank(distanceText) && StringUtils.isBlank(latText) && StringUtils.isBlank(lonText)) { showToast(res.getString(R.string.err_point_no_position_given)); return null; } // get base coordinates Geopoint coords; if (StringUtils.isNotBlank(latText) && StringUtils.isNotBlank(lonText)) { try { coords = new Geopoint(latText, lonText); } catch (final Geopoint.ParseException e) { showToast(res.getString(e.resource)); return null; } } else { coords = Sensors.getInstance().currentGeo().getCoords(); } // apply projection if (StringUtils.isNotBlank(bearingText) && StringUtils.isNotBlank(distanceText)) { // bearing & distance final double bearing; try { bearing = Double.parseDouble(bearingText); } catch (final NumberFormatException ignored) { Dialogs.message(this, R.string.err_point_bear_and_dist_title, R.string.err_point_bear_and_dist); return null; } final double distance; try { distance = DistanceParser.parseDistance(distanceText, !Settings.useImperialUnits()); } catch (final NumberFormatException ignored) { showToast(res.getString(R.string.err_parse_dist)); return null; } coords = coords.project(bearing, distance); } saveCoords(coords); return coords; } private void saveCoords(final Geopoint coords) { if (!changed) { return; } Settings.setAnyCoordinates(coords); } @Override public void startDefaultNavigation() { navigateTo(getDestinationAndAddToHistory()); } @Override public void startDefaultNavigation2() { NavigationAppFactory.startDefaultNavigationApplication(2, this, getDestinationAndAddToHistory()); } private Button getLatButton() { return latButton; } private Button getLonButton() { return lonButton; } private EditText getDistanceEditText() { return distanceEditText; } private Spinner getDistanceUnitSelector() { return distanceUnitSelector; } private Button getButtonCurrent() { return buttonCurrent; } private EditText getBearingEditText() { return bearingEditText; } }