diff options
Diffstat (limited to 'main/src')
112 files changed, 2691 insertions, 1159 deletions
diff --git a/main/src/cgeo/geocaching/AbstractPopupActivity.java b/main/src/cgeo/geocaching/AbstractPopupActivity.java index 5b9b509..5f24030 100644 --- a/main/src/cgeo/geocaching/AbstractPopupActivity.java +++ b/main/src/cgeo/geocaching/AbstractPopupActivity.java @@ -108,9 +108,6 @@ public abstract class AbstractPopupActivity extends AbstractActivity implements } @Override - public abstract void navigateTo(); - - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // set theme @@ -202,9 +199,6 @@ public abstract class AbstractPopupActivity extends AbstractActivity implements return super.onTouchEvent(event); } - @Override - public abstract void showNavigationMenu(); - protected abstract void startDefaultNavigation2(); protected final void addCacheDetails() { diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java index b853949..079562e 100644 --- a/main/src/cgeo/geocaching/CacheDetailActivity.java +++ b/main/src/cgeo/geocaching/CacheDetailActivity.java @@ -35,6 +35,7 @@ import cgeo.geocaching.ui.IndexOutOfBoundsAvoidingTextView; import cgeo.geocaching.ui.LoggingUI; import cgeo.geocaching.ui.OwnerActionsClickListener; import cgeo.geocaching.ui.WeakReferenceHandler; +import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.ui.logs.CacheLogsViewCreator; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.ClipboardUtils; @@ -382,7 +383,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc buildDetailsContextMenu(menu, res.getString(R.string.cache_logs), false); break; case R.id.waypoint: - menu.setHeaderTitle(res.getString(R.string.waypoint)); + menu.setHeaderTitle(selectedWaypoint.getName() + " (" + res.getString(R.string.waypoint) + ")"); getMenuInflater().inflate(R.menu.waypoint_options, menu); final boolean isOriginalWaypoint = selectedWaypoint.getWaypointType().equals(WaypointType.ORIGINAL); menu.findItem(R.id.menu_waypoint_reset_cache_coords).setVisible(isOriginalWaypoint); @@ -1615,18 +1616,8 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } private void warnPersonalNoteNeedsStoring() { - final AlertDialog.Builder builder = new AlertDialog.Builder(CacheDetailActivity.this); - builder.setTitle(R.string.cache_personal_note_unstored); - builder.setMessage(R.string.cache_personal_note_store); - builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - // do nothing - } - }); - - builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + Dialogs.confirm(CacheDetailActivity.this, R.string.cache_personal_note_unstored, R.string.cache_personal_note_store, + new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { @@ -1635,36 +1626,19 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } }); - final AlertDialog dialog = builder.create(); - dialog.setOwnerActivity(CacheDetailActivity.this); - dialog.show(); } private void warnPersonalNoteExceedsLimit() { - final AlertDialog.Builder builder = new AlertDialog.Builder(CacheDetailActivity.this); - builder.setTitle(R.string.cache_personal_note_limit); - String lang = getString(R.string.cache_personal_note_truncation, GCConstants.PERSONAL_NOTE_MAX_CHARS); - builder.setMessage(lang); - builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - // do nothing - } - }); + Dialogs.confirm(CacheDetailActivity.this, R.string.cache_personal_note_limit, getString(R.string.cache_personal_note_truncation, GCConstants.PERSONAL_NOTE_MAX_CHARS), + new DialogInterface.OnClickListener() { - builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - uploadPersonalNote(); - } + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + uploadPersonalNote(); + } - }); - final AlertDialog dialog = builder.create(); - dialog.setOwnerActivity(CacheDetailActivity.this); - dialog.show(); + }); } } diff --git a/main/src/cgeo/geocaching/CacheListActivity.java b/main/src/cgeo/geocaching/CacheListActivity.java index 8226f38..08d41d0 100644 --- a/main/src/cgeo/geocaching/CacheListActivity.java +++ b/main/src/cgeo/geocaching/CacheListActivity.java @@ -39,6 +39,7 @@ import cgeo.geocaching.sorting.ComparatorUserInterface; import cgeo.geocaching.ui.CacheListAdapter; import cgeo.geocaching.ui.LoggingUI; import cgeo.geocaching.ui.WeakReferenceHandler; +import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.AsyncTaskWithProgress; import cgeo.geocaching.utils.DateUtils; import cgeo.geocaching.utils.GeoDirHandler; @@ -474,7 +475,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA // refresh standard list if it has changed (new caches downloaded) if (type == CacheListType.OFFLINE && listId >= StoredList.STANDARD_LIST_ID && search != null) { final SearchResult newSearch = DataStore.getBatchOfStoredCaches(coords, Settings.getCacheType(), listId); - if (newSearch.getTotal() != search.getTotal()) { + if (newSearch.getTotalCountGC() != search.getTotalCountGC()) { refreshCurrentList(); } } @@ -944,7 +945,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA boolean enableMore = (type != CacheListType.OFFLINE && cacheList.size() < MAX_LIST_ITEMS); if (enableMore && search != null) { - final int count = search.getTotal(); + final int count = search.getTotalCountGC(); enableMore = enableMore && count > 0 && cacheList.size() < count; } @@ -1027,27 +1028,15 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } public void removeFromHistoryCheck() { - final AlertDialog.Builder dialog = new AlertDialog.Builder(this); - dialog.setCancelable(true); - dialog.setTitle(res.getString(R.string.caches_removing_from_history)); - dialog.setMessage((adapter != null && adapter.getCheckedCount() > 0) ? res.getString(R.string.cache_remove_from_history) - : res.getString(R.string.cache_clear_history)); - dialog.setPositiveButton(getString(android.R.string.yes), new DialogInterface.OnClickListener() { + int message = (adapter != null && adapter.getCheckedCount() > 0) ? R.string.cache_remove_from_history + : R.string.cache_clear_history; + Dialogs.confirmYesNo(this, R.string.caches_removing_from_history, message, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { removeFromHistory(); dialog.cancel(); } }); - dialog.setNegativeButton(getString(android.R.string.no), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - - final AlertDialog alert = dialog.create(); - alert.show(); } public void removeFromHistory() { @@ -1072,16 +1061,8 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } public void dropStored(final boolean removeListAfterwards) { - final AlertDialog.Builder dialog = new AlertDialog.Builder(this); - dialog.setCancelable(true); - dialog.setTitle(res.getString(R.string.caches_drop_stored)); - - if (adapter.getCheckedCount() > 0) { - dialog.setMessage(res.getString(R.string.caches_drop_selected_ask)); - } else { - dialog.setMessage(res.getString(R.string.caches_drop_all_ask)); - } - dialog.setPositiveButton(getString(android.R.string.yes), new DialogInterface.OnClickListener() { + int message = (adapter.getCheckedCount() > 0) ? R.string.caches_drop_selected_ask : R.string.caches_drop_all_ask; + Dialogs.confirmYesNo(this, R.string.caches_drop_stored, message, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { @@ -1089,16 +1070,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA dialog.cancel(); } }); - dialog.setNegativeButton(getString(android.R.string.no), new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - - final AlertDialog alert = dialog.create(); - alert.show(); } public void dropSelected(boolean removeListAfterwards) { @@ -1407,24 +1378,12 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } // ask him, if there are caches on the list - final AlertDialog.Builder alert = new AlertDialog.Builder(this); - - alert.setTitle(R.string.list_dialog_remove_title); - alert.setMessage(R.string.list_dialog_remove_description); - alert.setPositiveButton(R.string.list_dialog_remove, new DialogInterface.OnClickListener() { + Dialogs.confirm(this, R.string.list_dialog_remove_title, R.string.list_dialog_remove_description, R.string.list_dialog_remove, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int whichButton) { removeListInternal(); } }); - alert.setNegativeButton(res.getString(R.string.list_dialog_cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int whichButton) { - dialog.dismiss(); - } - }); - - alert.show(); } /** @@ -1631,7 +1590,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } if (coords != null) { loader = new CoordsGeocacheListLoader(app, coords); - } + } else { loader = new AddressGeocacheListLoader(app, address); } diff --git a/main/src/cgeo/geocaching/CacheMenuHandler.java b/main/src/cgeo/geocaching/CacheMenuHandler.java index 887f6cf..84a08f5 100644 --- a/main/src/cgeo/geocaching/CacheMenuHandler.java +++ b/main/src/cgeo/geocaching/CacheMenuHandler.java @@ -5,12 +5,12 @@ import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; import cgeo.geocaching.geopoint.GeopointFormatter; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.ui.AbstractUIFactory; +import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.ProcessUtils; import org.apache.commons.lang3.StringUtils; import android.app.Activity; -import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; @@ -101,28 +101,17 @@ public class CacheMenuHandler extends AbstractUIFactory { Uri.parse(ICalendar.URI_SCHEME + "://" + ICalendar.URI_HOST + "?" + params.toString()))); } else { // Inform user the calendar add-on is not installed and let them get it from Google Play - new AlertDialog.Builder(activity) - .setTitle(res.getString(R.string.addon_missing_title)) - .setMessage(new StringBuilder(res.getString(R.string.helper_calendar_missing)) - .append(' ') - .append(res.getString(R.string.addon_download_prompt)) - .toString()) - .setPositiveButton(activity.getString(android.R.string.yes), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(ICalendar.CALENDAR_ADDON_URI)); - activity.startActivity(intent); - } - }) - .setNegativeButton(activity.getString(android.R.string.no), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }) - .create() - .show(); + Dialogs.confirmYesNo(activity, R.string.addon_missing_title, new StringBuilder(res.getString(R.string.helper_calendar_missing)) + .append(' ') + .append(res.getString(R.string.addon_download_prompt)) + .toString(), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(ICalendar.CALENDAR_ADDON_URI)); + activity.startActivity(intent); + } + }); } } diff --git a/main/src/cgeo/geocaching/CachePopup.java b/main/src/cgeo/geocaching/CachePopup.java index d88000f..9186497 100644 --- a/main/src/cgeo/geocaching/CachePopup.java +++ b/main/src/cgeo/geocaching/CachePopup.java @@ -26,6 +26,12 @@ public class CachePopup extends AbstractPopupActivity { private final Progress progress = new Progress(); private class StoreCacheHandler extends CancellableHandler { + private final int progressMessage; + + public StoreCacheHandler(final int progressMessage) { + this.progressMessage = progressMessage; + } + @Override public void handleRegularMessage(Message msg) { if (UPDATE_LOAD_PROGRESS_DETAIL == msg.what && msg.obj instanceof String) { @@ -36,7 +42,7 @@ public class CachePopup extends AbstractPopupActivity { } private void updateStatusMsg(final String msg) { - progress.setMessage(res.getString(R.string.cache_dialog_offline_save_message) + progress.setMessage(res.getString(progressMessage) + "\n\n" + msg); } @@ -49,23 +55,6 @@ public class CachePopup extends AbstractPopupActivity { } } - private class RefreshCacheHandler extends CancellableHandler { - @Override - public void handleRegularMessage(Message msg) { - if (UPDATE_LOAD_PROGRESS_DETAIL == msg.what && msg.obj instanceof String) { - updateStatusMsg((String) msg.obj); - } else { - init(); - } - } - - private void updateStatusMsg(final String msg) { - progress.setMessage(res.getString(R.string.cache_dialog_refresh_message) - + "\n\n" - + msg); - } - } - public CachePopup() { super(R.layout.popup); } @@ -133,7 +122,7 @@ public class CachePopup extends AbstractPopupActivity { } protected void storeCache(final int listId) { - final StoreCacheHandler storeCacheHandler = new StoreCacheHandler(); + final StoreCacheHandler storeCacheHandler = new StoreCacheHandler(R.string.cache_dialog_offline_save_message); progress.show(CachePopup.this, res.getString(R.string.cache_dialog_offline_save_title), res.getString(R.string.cache_dialog_offline_save_message), true, storeCacheHandler.cancelMessage()); new StoreCacheThread(listId, storeCacheHandler).start(); } @@ -168,7 +157,7 @@ public class CachePopup extends AbstractPopupActivity { return; } - final RefreshCacheHandler refreshCacheHandler = new RefreshCacheHandler(); + final StoreCacheHandler refreshCacheHandler = new StoreCacheHandler(R.string.cache_dialog_offline_save_message); progress.show(CachePopup.this, res.getString(R.string.cache_dialog_refresh_title), res.getString(R.string.cache_dialog_refresh_message), true, refreshCacheHandler.cancelMessage()); new RefreshCacheThread(refreshCacheHandler).start(); } diff --git a/main/src/cgeo/geocaching/CgeoApplication.java b/main/src/cgeo/geocaching/CgeoApplication.java index 0af8117..3d2f758 100644 --- a/main/src/cgeo/geocaching/CgeoApplication.java +++ b/main/src/cgeo/geocaching/CgeoApplication.java @@ -1,7 +1,7 @@ package cgeo.geocaching; -import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.network.StatusUpdater; +import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.IObserver; import cgeo.geocaching.utils.Log; @@ -75,7 +75,7 @@ public class CgeoApplication extends Application { dialog.dismiss(); boolean success = atomic.get(); String message = success ? res.getString(R.string.init_dbmove_success) : res.getString(R.string.init_dbmove_failed); - ActivityMixin.helpDialog(fromActivity, res.getString(R.string.init_dbmove_dbmove), message); + Dialogs.message(fromActivity, R.string.init_dbmove_dbmove, message); } }); } diff --git a/main/src/cgeo/geocaching/DirectionProvider.java b/main/src/cgeo/geocaching/DirectionProvider.java index b4fb86c..ae58fed 100644 --- a/main/src/cgeo/geocaching/DirectionProvider.java +++ b/main/src/cgeo/geocaching/DirectionProvider.java @@ -3,6 +3,8 @@ package cgeo.geocaching; import cgeo.geocaching.compatibility.Compatibility; import cgeo.geocaching.utils.MemorySubject; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import android.app.Activity; import android.content.Context; import android.hardware.Sensor; @@ -54,6 +56,7 @@ public class DirectionProvider extends MemorySubject<Float> implements SensorEve } @Override + @SuppressFBWarnings("FE_FLOATING_POINT_EQUALITY") public void onSensorChanged(final SensorEvent event) { final float direction = event.values[0]; if (direction != previous) { diff --git a/main/src/cgeo/geocaching/EditWaypointActivity.java b/main/src/cgeo/geocaching/EditWaypointActivity.java index c31ad40..6d0f822 100644 --- a/main/src/cgeo/geocaching/EditWaypointActivity.java +++ b/main/src/cgeo/geocaching/EditWaypointActivity.java @@ -12,6 +12,7 @@ import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.ui.dialog.CoordinatesInputDialog; +import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.GeoDirHandler; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.TextUtils; @@ -331,7 +332,7 @@ public class EditWaypointActivity extends AbstractActivity { if (StringUtils.isBlank(bearingText) && StringUtils.isBlank(distanceText) && StringUtils.isBlank(latText) && StringUtils.isBlank(lonText)) { - helpDialog(res.getString(R.string.err_point_no_position_given_title), res.getString(R.string.err_point_no_position_given)); + Dialogs.message(EditWaypointActivity.this, R.string.err_point_no_position_given_title, R.string.err_point_no_position_given); return; } @@ -359,7 +360,7 @@ public class EditWaypointActivity extends AbstractActivity { try { bearing = Double.parseDouble(bearingText); } catch (NumberFormatException e) { - helpDialog(res.getString(R.string.err_point_bear_and_dist_title), res.getString(R.string.err_point_bear_and_dist)); + Dialogs.message(EditWaypointActivity.this, R.string.err_point_bear_and_dist_title, R.string.err_point_bear_and_dist); return; } diff --git a/main/src/cgeo/geocaching/Geocache.java b/main/src/cgeo/geocaching/Geocache.java index c589e9b..6262792 100644 --- a/main/src/cgeo/geocaching/Geocache.java +++ b/main/src/cgeo/geocaching/Geocache.java @@ -32,6 +32,8 @@ import cgeo.geocaching.utils.LogTemplateProvider.LogContext; import cgeo.geocaching.utils.MatcherWrapper; import cgeo.geocaching.utils.UncertainProperty; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.Predicate; import org.apache.commons.lang3.BooleanUtils; @@ -366,6 +368,7 @@ public class Geocache implements ICache, IWaypoint { * the other cache to compare this one to * @return true if both caches have the same content */ + @SuppressFBWarnings("FE_FLOATING_POINT_EQUALITY") private boolean isEqualTo(final Geocache other) { return detailed == other.detailed && StringUtils.equalsIgnoreCase(geocode, other.geocode) && @@ -1578,7 +1581,7 @@ public class Geocache implements ICache, IWaypoint { public static void storeCache(Geocache origCache, String geocode, int listId, boolean forceRedownload, CancellableHandler handler) { try { - Geocache cache; + Geocache cache = null; // get cache details, they may not yet be complete if (origCache != null) { SearchResult search = null; @@ -1593,9 +1596,9 @@ public class Geocache implements ICache, IWaypoint { } } else if (StringUtils.isNotBlank(geocode)) { final SearchResult search = searchByGeocode(geocode, null, listId, forceRedownload, handler); - cache = search.getFirstCacheFromResult(LoadFlags.LOAD_CACHE_OR_DB); - } else { - cache = null; + if (search != null) { + cache = search.getFirstCacheFromResult(LoadFlags.LOAD_CACHE_OR_DB); + } } if (cache == null) { @@ -1710,7 +1713,7 @@ public class Geocache implements ICache, IWaypoint { patterns.add(Pattern.compile("\\b(\\d{1,2})\\:(\\d\\d)\\b")); if (StringUtils.isNotBlank(hourLocalized)) { // 17 - 20 o'clock - patterns.add(Pattern.compile("\\b(\\d{1,2})(?:\\.00)?" + "\\s*-\\s*" + "(?:\\d{1,2})(?:\\.00)?" + "\\s+" + Pattern.quote(hourLocalized), Pattern.CASE_INSENSITIVE)); + patterns.add(Pattern.compile("\\b(\\d{1,2})(?:\\.00)?" + "\\s*(?:-|[a-z]+)\\s*" + "(?:\\d{1,2})(?:\\.00)?" + "\\s+" + Pattern.quote(hourLocalized), Pattern.CASE_INSENSITIVE)); // 12 o'clock, 12.00 o'clock patterns.add(Pattern.compile("\\b(\\d{1,2})(?:\\.00)?\\s+" + Pattern.quote(hourLocalized), Pattern.CASE_INSENSITIVE)); } @@ -1719,10 +1722,10 @@ public class Geocache implements ICache, IWaypoint { final MatcherWrapper matcher = new MatcherWrapper(pattern, getDescription()); while (matcher.find()) { try { - final int hours = Integer.valueOf(matcher.group(1)); + final int hours = Integer.parseInt(matcher.group(1)); int minutes = 0; if (matcher.groupCount() >= 2) { - minutes = Integer.valueOf(matcher.group(2)); + minutes = Integer.parseInt(matcher.group(2)); } if (hours >= 0 && hours < 24 && minutes >= 0 && minutes < 60) { return String.valueOf(hours * 60 + minutes); @@ -1810,4 +1813,21 @@ public class Geocache implements ICache, IWaypoint { public String getWaypointPrefix(String name) { return getConnector().getWaypointPrefix(name); } + + /** + * Get number of overall finds for a cache, or 0 if the number of finds is not known. + * + * @return + */ + public int getFindsCount() { + if (getLogCounts().isEmpty()) { + setLogCounts(DataStore.loadLogCounts(getGeocode())); + } + Integer logged = getLogCounts().get(LogType.FOUND_IT); + if (logged != null) { + return logged; + } + return 0; + } + } diff --git a/main/src/cgeo/geocaching/LogTrackableActivity.java b/main/src/cgeo/geocaching/LogTrackableActivity.java index e549fdc..5246fa9 100644 --- a/main/src/cgeo/geocaching/LogTrackableActivity.java +++ b/main/src/cgeo/geocaching/LogTrackableActivity.java @@ -4,7 +4,7 @@ import butterknife.ButterKnife; import butterknife.InjectView; import cgeo.geocaching.connector.gc.GCParser; -import cgeo.geocaching.connector.gc.Login; +import cgeo.geocaching.connector.gc.GCLogin; import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.network.Network; @@ -76,7 +76,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat showToast(res.getString(R.string.info_log_type_changed)); } - if (Login.isEmpty(viewstates)) { + if (GCLogin.isEmpty(viewstates)) { if (attempts < 2) { showToast(res.getString(R.string.err_log_load_data_again)); new LoadDataThread().start(); @@ -200,7 +200,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat possibleLogTypes = Trackable.getPossibleLogTypes(); } - if (Login.isEmpty(viewstates)) { + if (GCLogin.isEmpty(viewstates)) { buttonPost.setEnabled(false); buttonPost.setOnTouchListener(null); buttonPost.setOnClickListener(null); @@ -294,7 +294,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat final String page = Network.getResponseData(Network.getRequest("http://www.geocaching.com/track/log.aspx", params)); - viewstates = Login.getViewstates(page); + viewstates = GCLogin.getViewstates(page); final List<LogType> typesPre = GCParser.parseTypes(page); if (CollectionUtils.isNotEmpty(typesPre)) { diff --git a/main/src/cgeo/geocaching/MainActivity.java b/main/src/cgeo/geocaching/MainActivity.java index 1d4c22c..d96b97c 100644 --- a/main/src/cgeo/geocaching/MainActivity.java +++ b/main/src/cgeo/geocaching/MainActivity.java @@ -15,10 +15,10 @@ import cgeo.geocaching.maps.CGeoMap; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.settings.SettingsActivity; import cgeo.geocaching.ui.Formatter; +import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.DatabaseBackupUtils; import cgeo.geocaching.utils.GeoDirHandler; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.ProcessUtils; import cgeo.geocaching.utils.RunnableWithArgument; import cgeo.geocaching.utils.Version; @@ -27,7 +27,6 @@ import com.google.zxing.integration.android.IntentResult; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.eclipse.jdt.annotation.NonNull; import android.app.AlertDialog; import android.app.AlertDialog.Builder; @@ -72,7 +71,6 @@ public class MainActivity extends AbstractActivity { @InjectView(R.id.offline_count) protected TextView countBubble; @InjectView(R.id.info_area) protected LinearLayout infoArea; - private static final String SCAN_INTENT = "com.google.zxing.client.android.SCAN"; public static final int SEARCH_REQUEST_CODE = 2; private int version = 0; @@ -268,7 +266,7 @@ public class MainActivity extends AbstractActivity { @Override public boolean onPrepareOptionsMenu(final Menu menu) { super.onPrepareOptionsMenu(menu); - menu.findItem(R.id.menu_scan).setEnabled(ProcessUtils.isIntentAvailable(SCAN_INTENT)); + menu.findItem(R.id.menu_pocket_queries).setVisible(Settings.isPremiumMember()); return true; } @@ -291,6 +289,18 @@ public class MainActivity extends AbstractActivity { case R.id.menu_scan: startScannerApplication(); return true; + case R.id.menu_pocket_queries: + if (!Settings.isPremiumMember()) { + return true; + } + new PocketQueryList.UserInterface(MainActivity.this).promptForListSelection(new RunnableWithArgument<PocketQueryList>() { + + @Override + public void run(final PocketQueryList pql) { + CacheListActivity.startActivityPocket(MainActivity.this, pql); + } + }); + return true; default: return super.onOptionsItemSelected(item); } @@ -299,6 +309,11 @@ public class MainActivity extends AbstractActivity { private void startScannerApplication() { IntentIntegrator integrator = new IntentIntegrator(this); + // integrator dialog is English only, therefore localize it + integrator.setButtonYesByID(android.R.string.yes); + integrator.setButtonNoByID(android.R.string.no); + integrator.setTitleByID(R.string.menu_scan_geo); + integrator.setMessageByID(R.string.menu_scan_description); integrator.initiateScan(IntentIntegrator.QR_CODE_TYPES); } @@ -318,11 +333,7 @@ public class MainActivity extends AbstractActivity { if (query == null) { query = ""; } - new AlertDialog.Builder(this) - .setMessage(res.getString(R.string.unknown_scan) + "\n\n" + query) - .setPositiveButton(getString(android.R.string.ok), null) - .create() - .show(); + Dialogs.message(this, res.getString(R.string.unknown_scan) + "\n\n" + query); } } } @@ -513,26 +524,6 @@ public class MainActivity extends AbstractActivity { } }); nearestView.setBackgroundResource(R.drawable.main_nearby); - - nearestView.setOnLongClickListener(new View.OnLongClickListener() { - - @Override - public boolean onLongClick(View v) { - if (!Settings.isPremiumMember()) { - return true; - } - new PocketQueryList.UserInterface(MainActivity.this).promptForListSelection(new RunnableWithArgument<PocketQueryList>() { - - @Override - public void run(final @NonNull PocketQueryList pql) { - CacheListActivity.startActivityPocket(MainActivity.this, pql); - } - }); - return true; - } - }); - nearestView.setLongClickable(true); - } navType.setText(res.getString(geo.getLocationProvider().resourceId)); @@ -665,10 +656,10 @@ public class MainActivity extends AbstractActivity { int checks = 0; while (!DataStore.isInitialized()) { try { - wait(500); + sleep(500); checks++; } catch (Exception e) { - // nothing; + Log.e("MainActivity.CountBubbleUpdateThread.run", e); } if (checks > 10) { diff --git a/main/src/cgeo/geocaching/NavigateAnyPointActivity.java b/main/src/cgeo/geocaching/NavigateAnyPointActivity.java index c68c979..f24e86e 100644 --- a/main/src/cgeo/geocaching/NavigateAnyPointActivity.java +++ b/main/src/cgeo/geocaching/NavigateAnyPointActivity.java @@ -13,6 +13,7 @@ import cgeo.geocaching.settings.Settings; import cgeo.geocaching.ui.AbstractViewHolder; import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.ui.dialog.CoordinatesInputDialog; +import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.GeoDirHandler; import cgeo.geocaching.utils.Log; @@ -518,7 +519,7 @@ public class NavigateAnyPointActivity extends AbstractActivity { try { bearing = Double.parseDouble(bearingText); } catch (NumberFormatException e) { - helpDialog(res.getString(R.string.err_point_bear_and_dist_title), res.getString(R.string.err_point_bear_and_dist)); + Dialogs.message(this, R.string.err_point_bear_and_dist_title, R.string.err_point_bear_and_dist); return null; } diff --git a/main/src/cgeo/geocaching/SearchActivity.java b/main/src/cgeo/geocaching/SearchActivity.java index 334d99a..9952e18 100644 --- a/main/src/cgeo/geocaching/SearchActivity.java +++ b/main/src/cgeo/geocaching/SearchActivity.java @@ -12,6 +12,7 @@ import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.ui.dialog.CoordinatesInputDialog; +import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.EditUtils; import org.apache.commons.lang3.StringUtils; @@ -276,7 +277,7 @@ public class SearchActivity extends AbstractActivity { final String keyText = StringUtils.trim(keywordEditText.getText().toString()); if (StringUtils.isBlank(keyText)) { - helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_keyword)); + Dialogs.message(this, R.string.warn_search_help_title, R.string.warn_search_help_keyword); return; } @@ -287,7 +288,7 @@ public class SearchActivity extends AbstractActivity { final String addText = StringUtils.trim(addressEditText.getText().toString()); if (StringUtils.isBlank(addText)) { - helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_address)); + Dialogs.message(this, R.string.warn_search_help_title, R.string.warn_search_help_address); return; } @@ -300,7 +301,7 @@ public class SearchActivity extends AbstractActivity { final String usernameText = StringUtils.trim(userNameEditText.getText().toString()); if (StringUtils.isBlank(usernameText)) { - helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_user)); + Dialogs.message(this, R.string.warn_search_help_title, R.string.warn_search_help_user); return; } @@ -315,7 +316,7 @@ public class SearchActivity extends AbstractActivity { final String usernameText = StringUtils.trimToEmpty(userName); if (StringUtils.isBlank(usernameText)) { - helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_user)); + Dialogs.message(this, R.string.warn_search_help_title, R.string.warn_search_help_user); return; } @@ -326,7 +327,7 @@ public class SearchActivity extends AbstractActivity { final String geocodeText = StringUtils.trim(geocodeEditText.getText().toString()); if (StringUtils.isBlank(geocodeText) || geocodeText.equalsIgnoreCase("GC")) { - helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_gccode)); + Dialogs.message(this, R.string.warn_search_help_title, R.string.warn_search_help_gccode); return; } @@ -337,7 +338,7 @@ public class SearchActivity extends AbstractActivity { final String trackableText = StringUtils.trim(trackableEditText.getText().toString()); if (StringUtils.isBlank(trackableText) || trackableText.equalsIgnoreCase("TB")) { - helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_tb)); + Dialogs.message(this, R.string.warn_search_help_title, R.string.warn_search_help_tb); return; } diff --git a/main/src/cgeo/geocaching/SearchResult.java b/main/src/cgeo/geocaching/SearchResult.java index 0bdf6c6..5d63a2d 100644 --- a/main/src/cgeo/geocaching/SearchResult.java +++ b/main/src/cgeo/geocaching/SearchResult.java @@ -1,6 +1,6 @@ package cgeo.geocaching; -import cgeo.geocaching.connector.gc.Login; +import cgeo.geocaching.connector.gc.GCLogin; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.enumerations.LoadFlags.LoadFlag; @@ -28,7 +28,11 @@ public class SearchResult implements Parcelable { private StatusCode error = null; private String url = ""; public String[] viewstates = null; - private int totalCnt = 0; + /** + * Overall number of search results matching our search on geocaching.com. If this number is higher than 20, we have + * to fetch multiple pages to get all caches. + */ + private int totalCountGC = 0; final public static Parcelable.Creator<SearchResult> CREATOR = new Parcelable.Creator<SearchResult>() { @Override @@ -60,20 +64,23 @@ public class SearchResult implements Parcelable { error = searchResult.error; url = searchResult.url; viewstates = searchResult.viewstates; - setTotal(searchResult.getTotal()); + setTotalCountGC(searchResult.getTotalCountGC()); } /** * Build a search result from an existing collection of geocodes. * - * @param geocodes a non-null collection of geocodes - * @param total the total number of geocodes (FIXME: what is the meaning of this number wrt to geocodes.size()?) + * @param geocodes + * a non-null collection of geocodes + * @param totalCountGC + * the total number of caches matching that search on geocaching.com (as we always get only the next 20 + * from a web page) */ - public SearchResult(final Collection<String> geocodes, final int total) { + public SearchResult(final Collection<String> geocodes, final int totalCountGC) { this.geocodes = new HashSet<String>(geocodes.size()); this.geocodes.addAll(geocodes); this.filteredGeocodes = new HashSet<String>(); - this.setTotal(total); + this.setTotalCountGC(totalCountGC); } /** @@ -99,7 +106,7 @@ public class SearchResult implements Parcelable { viewstates = new String[length]; in.readStringArray(viewstates); } - setTotal(in.readInt()); + setTotalCountGC(in.readInt()); } /** @@ -136,7 +143,7 @@ public class SearchResult implements Parcelable { out.writeInt(viewstates.length); out.writeStringArray(viewstates); } - out.writeInt(getTotal()); + out.writeInt(getTotalCountGC()); } @Override @@ -173,19 +180,23 @@ public class SearchResult implements Parcelable { } public void setViewstates(String[] viewstates) { - if (Login.isEmpty(viewstates)) { + if (GCLogin.isEmpty(viewstates)) { return; } + // lazy initialization of viewstates + if (this.viewstates == null) { + this.viewstates = new String[viewstates.length]; + } System.arraycopy(viewstates, 0, this.viewstates, 0, viewstates.length); } - public int getTotal() { - return totalCnt; + public int getTotalCountGC() { + return totalCountGC; } - public void setTotal(int totalCnt) { - this.totalCnt = totalCnt; + public void setTotalCountGC(int totalCountGC) { + this.totalCountGC = totalCountGC; } /** @@ -214,7 +225,7 @@ public class SearchResult implements Parcelable { } } // decrease maximum number of caches by filtered ones - result.setTotal(result.getTotal() - excluded); + result.setTotalCountGC(result.getTotalCountGC() - excluded); GCVote.loadRatings(cachesForVote); return result; } @@ -268,12 +279,18 @@ public class SearchResult implements Parcelable { } public void addSearchResult(SearchResult other) { - if (other != null) { - addGeocodes(other.geocodes); - addFilteredGeocodes(other.filteredGeocodes); - if (StringUtils.isBlank(url)) { - url = other.url; - } + if (other == null) { + return; + } + addGeocodes(other.geocodes); + addFilteredGeocodes(other.filteredGeocodes); + if (StringUtils.isBlank(url)) { + url = other.url; + } + // copy the GC total search results number to be able to use "More caches" button + if (getTotalCountGC() == 0 && other.getTotalCountGC() != 0) { + setViewstates(other.getViewstates()); + setTotalCountGC(other.getTotalCountGC()); } } diff --git a/main/src/cgeo/geocaching/UsefulAppsActivity.java b/main/src/cgeo/geocaching/UsefulAppsActivity.java index c0f2d6f..c70143f 100644 --- a/main/src/cgeo/geocaching/UsefulAppsActivity.java +++ b/main/src/cgeo/geocaching/UsefulAppsActivity.java @@ -10,6 +10,7 @@ import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.text.Html; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; @@ -46,7 +47,9 @@ public class UsefulAppsActivity extends AbstractActivity { private void installFromMarket(Activity activity) { try { - Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + packageName)); + // allow also opening pure http URLs in addition to market packages + final String url = (packageName.startsWith("http:")) ? packageName : "market://details?id=" + packageName; + final Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); marketIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); activity.startActivity(marketIntent); @@ -58,6 +61,7 @@ public class UsefulAppsActivity extends AbstractActivity { private static final HelperApp[] HELPER_APPS = { new HelperApp(R.string.helper_calendar_title, R.string.helper_calendar_description, R.drawable.cgeo, "cgeo.calendar"), + new HelperApp(R.string.helper_sendtocgeo_title, R.string.helper_sendtocgeo_description, R.drawable.cgeo, "http://send2.cgeo.org"), new HelperApp(R.string.helper_pocketquery_title, R.string.helper_pocketquery_description, R.drawable.helper_pocketquery, "org.pquery"), new HelperApp(R.string.helper_locus_title, R.string.helper_locus_description, R.drawable.helper_locus, "menion.android.locus"), new HelperApp(R.string.helper_google_translate_title, R.string.helper_google_translate_description, R.drawable.helper_google_translate, "com.google.android.apps.translate"), @@ -92,7 +96,7 @@ public class UsefulAppsActivity extends AbstractActivity { private void fillViewHolder(ViewHolder holder, HelperApp app) { holder.title.setText(res.getString(app.titleId)); holder.image.setImageDrawable(res.getDrawable(app.iconId)); - holder.description.setText(res.getString(app.descriptionId)); + holder.description.setText(Html.fromHtml(res.getString(app.descriptionId))); } }); diff --git a/main/src/cgeo/geocaching/Waypoint.java b/main/src/cgeo/geocaching/Waypoint.java index dda83d9..47977bc 100644 --- a/main/src/cgeo/geocaching/Waypoint.java +++ b/main/src/cgeo/geocaching/Waypoint.java @@ -270,7 +270,6 @@ public class Waypoint implements IWaypoint { /** * Delegates the creation of the waypoint-id for gpx-export to the waypoint * - * @param prefix * @return */ public String getGpxId() { diff --git a/main/src/cgeo/geocaching/activity/AbstractActivity.java b/main/src/cgeo/geocaching/activity/AbstractActivity.java index 1f44536..df21a8c 100644 --- a/main/src/cgeo/geocaching/activity/AbstractActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractActivity.java @@ -9,7 +9,6 @@ import cgeo.geocaching.settings.Settings; import android.content.Context; import android.content.res.Resources; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.view.View; @@ -58,15 +57,6 @@ public abstract class AbstractActivity extends FragmentActivity implements IAbst } @Override - public final void helpDialog(final String title, final String message) { - ActivityMixin.helpDialog(this, title, message); - } - - protected final void helpDialog(final String title, final String message, final Drawable icon) { - ActivityMixin.helpDialog(this, title, message, icon); - } - - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initializeCommonFields(); diff --git a/main/src/cgeo/geocaching/activity/AbstractListActivity.java b/main/src/cgeo/geocaching/activity/AbstractListActivity.java index d2bc0b4..a5d5c14 100644 --- a/main/src/cgeo/geocaching/activity/AbstractListActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractListActivity.java @@ -3,7 +3,6 @@ package cgeo.geocaching.activity; import cgeo.geocaching.CgeoApplication; import android.content.res.Resources; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.v4.app.FragmentListActivity; import android.view.View; @@ -48,15 +47,6 @@ public abstract class AbstractListActivity extends FragmentListActivity implemen } @Override - public final void helpDialog(final String title, final String message) { - ActivityMixin.helpDialog(this, title, message, null); - } - - public final void helpDialog(final String title, final String message, final Drawable icon) { - ActivityMixin.helpDialog(this, title, message, icon); - } - - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initializeCommonFields(); diff --git a/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java b/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java index 952726e..049fc7d 100644 --- a/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java @@ -7,8 +7,11 @@ import com.viewpagerindicator.TitlePageIndicator; import com.viewpagerindicator.TitleProvider; import org.apache.commons.lang3.tuple.Pair; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import android.app.Activity; +import android.os.Bundle; import android.os.Parcelable; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; @@ -43,6 +46,10 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends private final Map<Page, PageViewCreator> viewCreators = new HashMap<Page, PageViewCreator>(); /** + * Store the states of the page views to be able to persist them when destroyed and reinstantiated again + */ + private final Map<Page, Bundle> viewStates = new HashMap<Page, Bundle>(); + /** * The {@link ViewPager} for this activity. */ private ViewPager viewPager; @@ -76,6 +83,17 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends * Handles changed data-sets. */ public void notifyDataSetChanged(); + + /** + * Gets state of the view + */ + public @Nullable + Bundle getViewState(); + + /** + * Set the state of the view + */ + public void setViewState(@NonNull Bundle state); } /** @@ -93,6 +111,19 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends @Override public void destroyItem(ViewGroup container, int position, Object object) { + + final Page page = pageOrder.get(position); + + // Store the state of the view if the page supports it + PageViewCreator creator = viewCreators.get(page); + if (creator != null) { + @Nullable + Bundle state = creator.getViewState(); + if (state != null) { + viewStates.put(page, state); + } + } + container.removeView((View) object); } @@ -107,6 +138,7 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends @Override public Object instantiateItem(ViewGroup container, int position) { + final Page page = pageOrder.get(position); PageViewCreator creator = viewCreators.get(page); @@ -114,6 +146,7 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends if (null == creator && null != page) { creator = AbstractViewPagerActivity.this.createViewCreator(page); viewCreators.put(page, creator); + viewStates.put(page, new Bundle()); } View view = null; @@ -123,12 +156,18 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends // Result from getView() is maybe cached, but it should be valid because the // creator should be informed about data-changes with notifyDataSetChanged() view = creator.getView(); + + // Restore the state of the view if the page supports it + Bundle state = viewStates.get(page); + if (state != null) { + creator.setViewState(state); + } + container.addView(view, 0); } } catch (Exception e) { Log.e("ViewPagerAdapter.instantiateItem ", e); } - return view; } @@ -225,10 +264,15 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends protected abstract String getTitle(Page page); protected final void reinitializeViewPager() { + // notify all creators that the data has changed for (PageViewCreator creator : viewCreators.values()) { creator.notifyDataSetChanged(); } + // reset the stored view states of all pages + for (Bundle state : viewStates.values()) { + state.clear(); + } pageOrder.clear(); final Pair<List<? extends Page>, Integer> pagesAndIndex = getOrderedPages(); diff --git a/main/src/cgeo/geocaching/activity/ActivityMixin.java b/main/src/cgeo/geocaching/activity/ActivityMixin.java index 9b1e433..c1a2678 100644 --- a/main/src/cgeo/geocaching/activity/ActivityMixin.java +++ b/main/src/cgeo/geocaching/activity/ActivityMixin.java @@ -8,10 +8,7 @@ import cgeo.geocaching.settings.Settings; import org.apache.commons.lang3.StringUtils; import android.app.Activity; -import android.app.AlertDialog; -import android.content.DialogInterface; import android.content.Intent; -import android.graphics.drawable.Drawable; import android.os.Build; import android.view.Gravity; import android.view.View; @@ -94,30 +91,6 @@ public final class ActivityMixin { } } - public static void helpDialog(final Activity activity, final String title, final String message, final Drawable icon) { - if (StringUtils.isBlank(message)) { - return; - } - - AlertDialog.Builder dialog = new AlertDialog.Builder(activity).setTitle(title).setMessage(message).setCancelable(true); - dialog.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - if (icon != null) { - dialog.setIcon(icon); - } - - AlertDialog alert = dialog.create(); - alert.show(); - } - - public static void helpDialog(Activity activity, String title, String message) { - helpDialog(activity, title, message, null); - } - public static void keepScreenOn(final Activity abstractActivity, boolean keepScreenOn) { if (keepScreenOn) { abstractActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); @@ -130,7 +103,7 @@ public final class ActivityMixin { /** * insert text into the EditText at the current cursor position - * + * * @param editText * @param insertText * @param moveCursor diff --git a/main/src/cgeo/geocaching/activity/IAbstractActivity.java b/main/src/cgeo/geocaching/activity/IAbstractActivity.java index 61c218b..7ca2322 100644 --- a/main/src/cgeo/geocaching/activity/IAbstractActivity.java +++ b/main/src/cgeo/geocaching/activity/IAbstractActivity.java @@ -10,7 +10,5 @@ public interface IAbstractActivity { public void showShortToast(String text); - public void helpDialog(String title, String message); - public void invalidateOptionsMenuCompatible(); } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java b/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java index dd02bc1..bf0e776 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java @@ -71,7 +71,8 @@ public final class NavigationAppFactory extends AbstractAppFactory { CACHE_BEACON(new CacheBeaconApp(), 14, R.string.pref_navigation_menu_cache_beacon), GCC(new GccApp(), 15, R.string.pref_navigation_menu_gcc), - WHERE_YOU_GO(new WhereYouGoApp(), 16, R.string.pref_navigation_menu_where_you_go); + WHERE_YOU_GO(new WhereYouGoApp(), 16, R.string.pref_navigation_menu_where_you_go), + PEBBLE(new PebbleApp(), 17, R.string.pref_navigation_menu_pebble); NavigationAppsEnum(final App app, final int id, final int preferenceKey) { this.app = app; @@ -142,8 +143,6 @@ public final class NavigationAppFactory extends AbstractAppFactory { public static void showNavigationMenu(final Activity activity, final Geocache cache, final Waypoint waypoint, final Geopoint destination, final boolean showInternalMap, final boolean showDefaultNavigation) { - final AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setTitle(R.string.cache_menu_navigate); final List<NavigationAppsEnum> items = new ArrayList<NavigationAppFactory.NavigationAppsEnum>(); final int defaultNavigationTool = Settings.getDefaultNavigationTool(); for (final NavigationAppsEnum navApp : getInstalledNavigationApps()) { @@ -166,26 +165,25 @@ public final class NavigationAppFactory extends AbstractAppFactory { } } } + + if (items.size() == 1) { + invokeNavigation(activity, cache, waypoint, destination, items.get(0).app); + return; + } + /* * Using an ArrayAdapter with list of NavigationAppsEnum items avoids * handling between mapping list positions allows us to do dynamic filtering of the list based on use case. */ final ArrayAdapter<NavigationAppsEnum> adapter = new ArrayAdapter<NavigationAppsEnum>(activity, android.R.layout.select_dialog_item, items); + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(R.string.cache_menu_navigate); builder.setAdapter(adapter, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int item) { final NavigationAppsEnum selectedItem = adapter.getItem(item); - final App app = selectedItem.app; - if (cache != null) { - navigateCache(activity, cache, app); - } - else if (waypoint != null) { - navigateWaypoint(activity, waypoint, app); - } - else { - navigateGeopoint(activity, destination, app); - } + invokeNavigation(activity, cache, waypoint, destination, selectedItem.app); } }); final AlertDialog alert = builder.create(); @@ -225,7 +223,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { /** * Handles menu selections for menu entries created with * {@link #showNavigationMenu(Activity, Geocache, Waypoint, Geopoint)}. - * + * * @param item * @param activity * @param cache @@ -343,4 +341,16 @@ public final class NavigationAppFactory extends AbstractAppFactory { return NavigationAppsEnum.COMPASS.app; } + private static void invokeNavigation(final Activity activity, final Geocache cache, final Waypoint waypoint, final Geopoint destination, final App app) { + if (cache != null) { + navigateCache(activity, cache, app); + } + else if (waypoint != null) { + navigateWaypoint(activity, waypoint, app); + } + else { + navigateGeopoint(activity, destination, app); + } + } + } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java b/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java new file mode 100644 index 0000000..8ba3bef --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java @@ -0,0 +1,44 @@ +package cgeo.geocaching.apps.cache.navi;
+
+import cgeo.geocaching.Geocache;
+import cgeo.geocaching.R;
+import cgeo.geocaching.geopoint.Geopoint;
+
+import android.app.Activity;
+import android.content.Intent;
+
+/**
+ * Application for communication with the Pebble watch.
+ *
+ */
+class PebbleApp extends AbstractPointNavigationApp {
+
+ private static final String INTENT = "com.webmajstr.pebble_gc.NAVIGATE_TO";
+ private static final String PACKAGE_NAME = "com.webmajstr.pebble_gc";
+
+ PebbleApp() {
+ super(getString(R.string.cache_menu_pebble), R.id.cache_app_pebble, INTENT, PACKAGE_NAME);
+ }
+
+ @Override
+ public void navigate(Activity activity, Geopoint point) {
+ final Intent pebbleIntent = new Intent(INTENT);
+ pebbleIntent.putExtra("latitude", point.getLatitude());
+ pebbleIntent.putExtra("longitude", point.getLongitude());
+ activity.startActivity(pebbleIntent);
+ }
+
+ @Override
+ public void navigate(Activity activity, Geocache cache) {
+ final Intent pebbleIntent = new Intent(INTENT);
+ pebbleIntent.putExtra("latitude", cache.getCoords().getLatitude());
+ pebbleIntent.putExtra("longitude", cache.getCoords().getLongitude());
+ pebbleIntent.putExtra("difficulty", cache.getDifficulty());
+ pebbleIntent.putExtra("terrain", cache.getTerrain());
+ pebbleIntent.putExtra("name", cache.getName());
+ pebbleIntent.putExtra("code", cache.getGeocode());
+ pebbleIntent.putExtra("size", cache.getSize().getL10n());
+ activity.startActivity(pebbleIntent);
+ }
+
+}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/connector/AbstractConnector.java b/main/src/cgeo/geocaching/connector/AbstractConnector.java index b10366e..ffb1b1f 100644 --- a/main/src/cgeo/geocaching/connector/AbstractConnector.java +++ b/main/src/cgeo/geocaching/connector/AbstractConnector.java @@ -1,21 +1,28 @@ package cgeo.geocaching.connector; +import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.Geocache; import cgeo.geocaching.LogCacheActivity; import cgeo.geocaching.R; +import cgeo.geocaching.connector.capability.ISearchByCenter; +import cgeo.geocaching.connector.capability.ISearchByGeocode; +import cgeo.geocaching.connector.capability.ISearchByKeyword; +import cgeo.geocaching.connector.capability.ISearchByViewPort; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.geopoint.Geopoint; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; import java.util.ArrayList; +import java.util.Collection; import java.util.List; public abstract class AbstractConnector implements IConnector { @Override - public boolean canHandle(String geocode) { + public boolean canHandle(@NonNull final String geocode) { return false; } @@ -88,7 +95,7 @@ public abstract class AbstractConnector implements IConnector { } @Override - public String getLicenseText(final Geocache cache) { + public String getLicenseText(final @NonNull Geocache cache) { return null; } @@ -132,15 +139,12 @@ public abstract class AbstractConnector implements IConnector { abstract protected String getCacheUrlPrefix(); @Override - public String getLongCacheUrl(final Geocache cache) { + public String getLongCacheUrl(final @NonNull Geocache cache) { return getCacheUrl(cache); } - /** - * {@link IConnector} - */ @Override - public boolean isActivated() { + public boolean isActive() { return false; } @@ -200,4 +204,48 @@ public abstract class AbstractConnector implements IConnector { // Default: just return the name return name; } + + @Override + public int getMaxTerrain() { + return 5; + } + + @Override + public final Collection<String> getCapabilities() { + ArrayList<String> builder = new ArrayList<String>(); + builder.add(capability(ISearchByViewPort.class, R.string.feature_search_live_map)); + builder.add(capability(ISearchByKeyword.class, R.string.feature_search_keyword)); + builder.add(capability(ISearchByCenter.class, R.string.feature_search_center)); + builder.add(capability(ISearchByGeocode.class, R.string.feature_search_geocode)); + if (supportsUserActions()) { + builder.add(feature(R.string.feature_search_user)); + } + if (supportsLogging()) { + builder.add(feature(R.string.feature_online_logging)); + } + if (supportsLogImages()) { + builder.add(feature(R.string.feature_log_images)); + } + if (supportsPersonalNote()) { + builder.add(feature(R.string.feature_personal_notes)); + } + if (supportsOwnCoordinates()) { + builder.add(feature(R.string.feature_own_coordinates)); + } + if (supportsWatchList()) { + builder.add(feature(R.string.feature_watch_list)); + } + return builder; + } + + private String capability(Class<? extends IConnector> clazz, final int featureResourceId) { + if (clazz.isInstance(this)) { + return feature(featureResourceId); + } + return StringUtils.EMPTY; + } + + private static String feature(int featureResourceId) { + return CgeoApplication.getInstance().getString(featureResourceId); + } } diff --git a/main/src/cgeo/geocaching/connector/AbstractLoggingManager.java b/main/src/cgeo/geocaching/connector/AbstractLoggingManager.java new file mode 100644 index 0000000..9e702c4 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/AbstractLoggingManager.java @@ -0,0 +1,20 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.TrackableLog; + +import java.util.Collections; +import java.util.List; + +public abstract class AbstractLoggingManager implements ILoggingManager { + + @Override + public boolean hasLoaderError() { + return false; + } + + @Override + public List<TrackableLog> getTrackables() { + return Collections.emptyList(); + } + +} diff --git a/main/src/cgeo/geocaching/connector/AbstractLogin.java b/main/src/cgeo/geocaching/connector/AbstractLogin.java new file mode 100644 index 0000000..6527685 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/AbstractLogin.java @@ -0,0 +1,77 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.CgeoApplication; +import cgeo.geocaching.R; +import cgeo.geocaching.enumerations.StatusCode; +import cgeo.geocaching.network.Cookies; +import cgeo.geocaching.settings.Settings; + +import org.apache.commons.lang3.StringUtils; + +public abstract class AbstractLogin { + + /** + * {@code true} if logged in, {@code false} otherwise + */ + private boolean actualLoginStatus = false; + private String actualUserName = StringUtils.EMPTY; + /** + * Number of caches found. An unknown number is signaled by the value -1, while 0 really indicates zero caches found + * by the user. + */ + private int actualCachesFound = -1; + private String actualStatus = StringUtils.EMPTY; + + public void setActualCachesFound(final int found) { + actualCachesFound = found; + } + + public String getActualStatus() { + return actualStatus; + } + + protected void setActualStatus(final String status) { + actualStatus = status; + } + + public boolean isActualLoginStatus() { + return actualLoginStatus; + } + + protected void setActualLoginStatus(boolean loginStatus) { + actualLoginStatus = loginStatus; + } + + public String getActualUserName() { + return actualUserName; + } + + protected void setActualUserName(String userName) { + actualUserName = userName; + } + + public int getActualCachesFound() { + return actualCachesFound; + } + + protected void resetLoginStatus() { + Cookies.clearCookies(); + Settings.setCookieStore(null); + + setActualLoginStatus(false); + } + + protected void clearLoginInfo() { + resetLoginStatus(); + + setActualCachesFound(-1); + setActualStatus(CgeoApplication.getInstance().getString(R.string.err_login)); + } + + public StatusCode login() { + return login(true); + } + + protected abstract StatusCode login(boolean retry); + +} diff --git a/main/src/cgeo/geocaching/connector/ConnectorFactory.java b/main/src/cgeo/geocaching/connector/ConnectorFactory.java index 83f8142..3df98e0 100644 --- a/main/src/cgeo/geocaching/connector/ConnectorFactory.java +++ b/main/src/cgeo/geocaching/connector/ConnectorFactory.java @@ -8,7 +8,9 @@ import cgeo.geocaching.connector.capability.ILogin; import cgeo.geocaching.connector.capability.ISearchByCenter; import cgeo.geocaching.connector.capability.ISearchByKeyword; import cgeo.geocaching.connector.capability.ISearchByViewPort; +import cgeo.geocaching.connector.ec.ECConnector; import cgeo.geocaching.connector.gc.GCConnector; +import cgeo.geocaching.connector.gc.MapTokens; import cgeo.geocaching.connector.oc.OCApiConnector; import cgeo.geocaching.connector.oc.OCApiConnector.ApiSupport; import cgeo.geocaching.connector.oc.OCApiLiveConnector; @@ -24,12 +26,16 @@ import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; public final class ConnectorFactory { private static final UnknownConnector UNKNOWN_CONNECTOR = new UnknownConnector(); - private static final IConnector[] CONNECTORS = new IConnector[] { + private static final Collection<IConnector> CONNECTORS = Collections.unmodifiableCollection(Arrays.asList(new IConnector[] { GCConnector.getInstance(), + ECConnector.getInstance(), new OCApiLiveConnector("opencaching.de", "www.opencaching.de", "OC", "CC BY-NC-ND, alle Logeinträge © jeweiliger Autor", R.string.oc_de_okapi_consumer_key, R.string.oc_de_okapi_consumer_secret, R.string.pref_connectorOCActive, R.string.pref_ocde_tokenpublic, R.string.pref_ocde_tokensecret, ApiSupport.current), @@ -43,71 +49,54 @@ public final class ConnectorFactory { new OCApiLiveConnector("opencaching.pl", "www.opencaching.pl", "OP", "CC BY-SA 3.0", R.string.oc_pl_okapi_consumer_key, R.string.oc_pl_okapi_consumer_secret, R.string.pref_connectorOCPLActive, R.string.pref_ocpl_tokenpublic, R.string.pref_ocpl_tokensecret, ApiSupport.current), - new OCApiConnector("OpenCaching.US", "www.opencaching.us", "OU", "pTsYAYSXFcfcRQnYE6uA", "CC BY-NC-SA 2.5", ApiSupport.oldapi), + new OCApiConnector("OpenCaching.US", "www.opencaching.us", "OU", "pTsYAYSXFcfcRQnYE6uA", "CC BY-NC-SA 2.5", ApiSupport.current), new OXConnector(), new GeocachingAustraliaConnector(), new GeopeitusConnector(), new WaymarkingConnector(), UNKNOWN_CONNECTOR // the unknown connector MUST be the last one - }; + })); @NonNull public static final UnknownTrackableConnector UNKNOWN_TRACKABLE_CONNECTOR = new UnknownTrackableConnector(); - private static final TrackableConnector[] TRACKABLE_CONNECTORS = new TrackableConnector[] { + private static final Collection<TrackableConnector> TRACKABLE_CONNECTORS = Collections.unmodifiableCollection(Arrays.asList(new TrackableConnector[] { new GeokretyConnector(), // GK must be first, as it overlaps with the secret codes of travel bugs TravelBugConnector.getInstance(), UNKNOWN_TRACKABLE_CONNECTOR // must be last - }; + })); - private static final ISearchByViewPort[] searchByViewPortConns; + private static final Collection<ISearchByViewPort> searchByViewPortConns = getMatchingConnectors(ISearchByViewPort.class); - private static final ISearchByCenter[] searchByCenterConns; + private static final Collection<ISearchByCenter> searchByCenterConns = getMatchingConnectors(ISearchByCenter.class); - private static final ISearchByKeyword[] searchByKeywordConns; + private static final Collection<ISearchByKeyword> searchByKeywordConns = getMatchingConnectors(ISearchByKeyword.class); - static { - final List<ISearchByViewPort> vpConns = new ArrayList<ISearchByViewPort>(); - for (final IConnector conn : CONNECTORS) { - if (conn instanceof ISearchByViewPort) { - vpConns.add((ISearchByViewPort) conn); - } - } - searchByViewPortConns = vpConns.toArray(new ISearchByViewPort[vpConns.size()]); - - final List<ISearchByCenter> centerConns = new ArrayList<ISearchByCenter>(); - for (final IConnector conn : CONNECTORS) { - // GCConnector is handled specially, omit it here! - if (conn instanceof ISearchByCenter && !(conn instanceof GCConnector)) { - centerConns.add((ISearchByCenter) conn); - } - } - searchByCenterConns = centerConns.toArray(new ISearchByCenter[centerConns.size()]); - - final List<ISearchByKeyword> keywordConns = new ArrayList<ISearchByKeyword>(); - for (final IConnector conn : CONNECTORS) { - // GCConnector is handled specially, omit it here! - if (conn instanceof ISearchByKeyword && !(conn instanceof GCConnector)) { - keywordConns.add((ISearchByKeyword) conn); + @SuppressWarnings("unchecked") + private static <T extends IConnector> Collection<T> getMatchingConnectors(final Class<T> clazz) { + final List<T> matching = new ArrayList<T>(); + for (final IConnector connector : CONNECTORS) { + if (clazz.isInstance(connector)) { + matching.add((T) connector); } } - searchByKeywordConns = keywordConns.toArray(new ISearchByKeyword[keywordConns.size()]); + return Collections.unmodifiableCollection(matching); } - public static IConnector[] getConnectors() { + public static Collection<IConnector> getConnectors() { return CONNECTORS; } - public static ISearchByCenter[] getSearchByCenterConnectors() { + public static Collection<ISearchByCenter> getSearchByCenterConnectors() { return searchByCenterConns; } - public static ISearchByKeyword[] getSearchByKeywordConnectors() { + public static Collection<ISearchByKeyword> getSearchByKeywordConnectors() { return searchByKeywordConns; } public static ILogin[] getActiveLiveConnectors() { final List<ILogin> liveConns = new ArrayList<ILogin>(); for (final IConnector conn : CONNECTORS) { - if (conn instanceof ILogin && conn.isActivated()) { + if (conn instanceof ILogin && conn.isActive()) { liveConns.add((ILogin) conn); } } @@ -147,6 +136,9 @@ public final class ConnectorFactory { public static IConnector getConnector(final String geocodeInput) { // this may come from user input final String geocode = StringUtils.trim(geocodeInput); + if (geocode == null) { + return UNKNOWN_CONNECTOR; + } if (isInvalidGeocode(geocode)) { return UNKNOWN_CONNECTOR; } @@ -164,12 +156,11 @@ public final class ConnectorFactory { } /** @see ISearchByViewPort#searchByViewport */ - public static SearchResult searchByViewport(final Viewport viewport, final String[] tokens) { - + public static SearchResult searchByViewport(final @NonNull Viewport viewport, final MapTokens tokens) { final SearchResult result = new SearchResult(); - for (final ISearchByViewPort vpconn : searchByViewPortConns) { - if (vpconn.isActivated()) { - result.addSearchResult(vpconn.searchByViewport(viewport, tokens)); + for (final ISearchByViewPort connector : searchByViewPortConns) { + if (connector.isActive()) { + result.addSearchResult(connector.searchByViewport(viewport, tokens)); } } return result; @@ -185,7 +176,7 @@ public final class ConnectorFactory { return null; } - public static TrackableConnector[] getTrackableConnectors() { + public static Collection<TrackableConnector> getTrackableConnectors() { return TRACKABLE_CONNECTORS; } diff --git a/main/src/cgeo/geocaching/connector/GeocachingAustraliaConnector.java b/main/src/cgeo/geocaching/connector/GeocachingAustraliaConnector.java index ac2fb37..3992013 100644 --- a/main/src/cgeo/geocaching/connector/GeocachingAustraliaConnector.java +++ b/main/src/cgeo/geocaching/connector/GeocachingAustraliaConnector.java @@ -4,6 +4,7 @@ import cgeo.geocaching.Geocache; import cgeo.geocaching.ICache; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; public class GeocachingAustraliaConnector extends AbstractConnector { @@ -13,7 +14,7 @@ public class GeocachingAustraliaConnector extends AbstractConnector { } @Override - public String getCacheUrl(final Geocache cache) { + public String getCacheUrl(final @NonNull Geocache cache) { return getCacheUrlPrefix() + cache.getGeocode(); } @@ -28,7 +29,7 @@ public class GeocachingAustraliaConnector extends AbstractConnector { } @Override - public boolean canHandle(final String geocode) { + public boolean canHandle(final @NonNull String geocode) { return (StringUtils.startsWithIgnoreCase(geocode, "GA") || StringUtils.startsWithIgnoreCase(geocode, "TP")) && isNumericId(geocode.substring(2)); } diff --git a/main/src/cgeo/geocaching/connector/GeopeitusConnector.java b/main/src/cgeo/geocaching/connector/GeopeitusConnector.java index 500f752..aa08485 100644 --- a/main/src/cgeo/geocaching/connector/GeopeitusConnector.java +++ b/main/src/cgeo/geocaching/connector/GeopeitusConnector.java @@ -4,6 +4,7 @@ import cgeo.geocaching.Geocache; import cgeo.geocaching.ICache; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; public class GeopeitusConnector extends AbstractConnector { @@ -13,7 +14,7 @@ public class GeopeitusConnector extends AbstractConnector { } @Override - public String getCacheUrl(final Geocache cache) { + public String getCacheUrl(final @NonNull Geocache cache) { return getCacheUrlPrefix() + StringUtils.stripStart(cache.getGeocode().substring(2), "0"); } @@ -28,7 +29,7 @@ public class GeopeitusConnector extends AbstractConnector { } @Override - public boolean canHandle(String geocode) { + public boolean canHandle(@NonNull String geocode) { return StringUtils.startsWith(geocode, "GE") && isNumericId(geocode.substring(2)); } diff --git a/main/src/cgeo/geocaching/connector/IConnector.java b/main/src/cgeo/geocaching/connector/IConnector.java index a96ee10..34922f5 100644 --- a/main/src/cgeo/geocaching/connector/IConnector.java +++ b/main/src/cgeo/geocaching/connector/IConnector.java @@ -6,6 +6,9 @@ import cgeo.geocaching.LogCacheActivity; import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.geopoint.Geopoint; +import org.eclipse.jdt.annotation.NonNull; + +import java.util.Collection; import java.util.List; public interface IConnector { @@ -17,20 +20,21 @@ public interface IConnector { public String getName(); /** - * return true, if this connector is responsible for the given cache + * Check if this connector is responsible for the given geocode. * * @param geocode - * @return + * geocode of a cache + * @return return {@code true}, if this connector is responsible for the cache */ - public boolean canHandle(final String geocode); + public boolean canHandle(final @NonNull String geocode); /** - * get browser URL for the given cache + * Get the browser URL for the given cache. * * @param cache * @return */ - public String getCacheUrl(final Geocache cache); + public String getCacheUrl(final @NonNull Geocache cache); /** * get long browser URL for the given cache @@ -38,7 +42,7 @@ public interface IConnector { * @param cache * @return */ - public String getLongCacheUrl(final Geocache cache); + public String getLongCacheUrl(final @NonNull Geocache cache); /** * enable/disable watchlist controls in cache details @@ -92,19 +96,19 @@ public interface IConnector { public ILoggingManager getLoggingManager(final LogCacheActivity activity, final Geocache cache); /** - * get host name of the connector server for dynamic loading of data + * Get host name of the connector server for dynamic loading of data. * * @return */ public String getHost(); /** - * get cache data license text + * Get cache data license text. This is displayed somewhere near the cache details. * * @param cache * @return */ - public String getLicenseText(final Geocache cache); + public String getLicenseText(final @NonNull Geocache cache); /** * enable/disable user actions in cache details @@ -178,13 +182,13 @@ public interface IConnector { public boolean uploadModifiedCoordinates(Geocache cache, Geopoint wpt); /** - * Return true if this connector is activated for online - * interaction (download details, do searches, ...) + * Return {@code true} if this connector is active for online interaction (download details, do searches, ...). If + * this is {@code false}, the connector will still be used for already stored offline caches. * * @return */ - public boolean isActivated(); + public boolean isActive(); /** * Check if the current user is the owner of the given cache. @@ -213,7 +217,7 @@ public interface IConnector { public int getCacheMapMarkerId(boolean disabled); /** - * Get the list of <b>potentially</b> possible log types for a cache. Those may still be filter further during the + * Get the list of <b>potentially</b> possible log types for a cache. Those may still be filtered further during the * actual logging activity. * * @param geocache @@ -222,8 +226,8 @@ public interface IConnector { public List<LogType> getPossibleLogTypes(Geocache geocache); /** - * Get the gpx id for a waypoint when exporting. For some connectors there is an inherent name logic, - * for others its just the 'prefix' + * Get the GPX id for a waypoint when exporting. For some connectors there is an inherent name logic, + * for others its just the 'prefix'. * * @param prefix * @return @@ -231,10 +235,24 @@ public interface IConnector { public String getWaypointGpxId(String prefix, String geocode); /** - * Get the 'prefix' (key) for a waypoint from the 'name' in the gpx file - * + * Get the 'prefix' (key) for a waypoint from the 'name' in the GPX file + * * @param name * @return */ public String getWaypointPrefix(String name); + + /** + * Get the maximum value for Terrain + * + * @return + */ + public int getMaxTerrain(); + + /** + * Get a user readable collection of all online features of this connector. + * + * @return + */ + public Collection<String> getCapabilities(); } diff --git a/main/src/cgeo/geocaching/connector/NoLoggingManager.java b/main/src/cgeo/geocaching/connector/NoLoggingManager.java index 04a73c1..e2e5d4c 100644 --- a/main/src/cgeo/geocaching/connector/NoLoggingManager.java +++ b/main/src/cgeo/geocaching/connector/NoLoggingManager.java @@ -11,7 +11,7 @@ import java.util.Calendar; import java.util.Collections; import java.util.List; -public class NoLoggingManager implements ILoggingManager { +public class NoLoggingManager extends AbstractLoggingManager { @Override public void init() { @@ -34,11 +34,6 @@ public class NoLoggingManager implements ILoggingManager { } @Override - public List<TrackableLog> getTrackables() { - return Collections.emptyList(); - } - - @Override public List<LogType> getPossibleLogTypes() { return Collections.emptyList(); } diff --git a/main/src/cgeo/geocaching/connector/UnknownConnector.java b/main/src/cgeo/geocaching/connector/UnknownConnector.java index e9fecb9..05593d7 100644 --- a/main/src/cgeo/geocaching/connector/UnknownConnector.java +++ b/main/src/cgeo/geocaching/connector/UnknownConnector.java @@ -4,6 +4,7 @@ import cgeo.geocaching.Geocache; import cgeo.geocaching.ICache; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; public class UnknownConnector extends AbstractConnector { @@ -13,7 +14,7 @@ public class UnknownConnector extends AbstractConnector { } @Override - public String getCacheUrl(Geocache cache) { + public String getCacheUrl(@NonNull Geocache cache) { return null; // we have no url for these caches } @@ -28,7 +29,7 @@ public class UnknownConnector extends AbstractConnector { } @Override - public boolean canHandle(final String geocode) { + public boolean canHandle(final @NonNull String geocode) { return StringUtils.isNotBlank(geocode); } diff --git a/main/src/cgeo/geocaching/connector/WaymarkingConnector.java b/main/src/cgeo/geocaching/connector/WaymarkingConnector.java index f184f6e..282ee31 100644 --- a/main/src/cgeo/geocaching/connector/WaymarkingConnector.java +++ b/main/src/cgeo/geocaching/connector/WaymarkingConnector.java @@ -4,6 +4,7 @@ import cgeo.geocaching.Geocache; import cgeo.geocaching.ICache; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; public class WaymarkingConnector extends AbstractConnector { @@ -13,7 +14,7 @@ public class WaymarkingConnector extends AbstractConnector { } @Override - public String getCacheUrl(Geocache cache) { + public String getCacheUrl(@NonNull Geocache cache) { return getCacheUrlPrefix() + cache.getGeocode(); } @@ -34,7 +35,7 @@ public class WaymarkingConnector extends AbstractConnector { } @Override - public boolean canHandle(String geocode) { + public boolean canHandle(@NonNull String geocode) { return StringUtils.startsWith(geocode, "WM"); } } diff --git a/main/src/cgeo/geocaching/connector/capability/ICredentials.java b/main/src/cgeo/geocaching/connector/capability/ICredentials.java new file mode 100644 index 0000000..2d5cd0b --- /dev/null +++ b/main/src/cgeo/geocaching/connector/capability/ICredentials.java @@ -0,0 +1,21 @@ +package cgeo.geocaching.connector.capability; + + +/** + * Marker interface for connectors which have user name and password as credentials. + * + */ +public interface ICredentials { + /** + * Get preference key of the user name. + * + */ + public int getUsernamePreferenceKey(); + + /** + * Get preference key of the password. + * + */ + public int getPasswordPreferenceKey(); + +} diff --git a/main/src/cgeo/geocaching/connector/capability/ISearchByCenter.java b/main/src/cgeo/geocaching/connector/capability/ISearchByCenter.java index 91dd094..adc36c7 100644 --- a/main/src/cgeo/geocaching/connector/capability/ISearchByCenter.java +++ b/main/src/cgeo/geocaching/connector/capability/ISearchByCenter.java @@ -3,11 +3,14 @@ package cgeo.geocaching.connector.capability; import cgeo.geocaching.SearchResult; import cgeo.geocaching.connector.IConnector; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.loaders.RecaptchaReceiver; + +import org.eclipse.jdt.annotation.NonNull; /** * connector capability for online searching caches around a center coordinate, sorted by distance * */ public interface ISearchByCenter extends IConnector { - public SearchResult searchByCenter(final Geopoint center); + public SearchResult searchByCenter(final @NonNull Geopoint center, final @NonNull RecaptchaReceiver recaptchaReceiver); } diff --git a/main/src/cgeo/geocaching/connector/capability/ISearchByGeocode.java b/main/src/cgeo/geocaching/connector/capability/ISearchByGeocode.java index 4c16049..7abc235 100644 --- a/main/src/cgeo/geocaching/connector/capability/ISearchByGeocode.java +++ b/main/src/cgeo/geocaching/connector/capability/ISearchByGeocode.java @@ -4,10 +4,12 @@ import cgeo.geocaching.SearchResult; import cgeo.geocaching.connector.IConnector; import cgeo.geocaching.utils.CancellableHandler; +import org.eclipse.jdt.annotation.Nullable; + /** * connector capability of searching online for a cache by geocode * */ public interface ISearchByGeocode extends IConnector { - public SearchResult searchByGeocode(final String geocode, final String guid, final CancellableHandler handler); + public SearchResult searchByGeocode(final @Nullable String geocode, final @Nullable String guid, final CancellableHandler handler); } diff --git a/main/src/cgeo/geocaching/connector/capability/ISearchByKeyword.java b/main/src/cgeo/geocaching/connector/capability/ISearchByKeyword.java index 09b2423..9b3e6eb 100644 --- a/main/src/cgeo/geocaching/connector/capability/ISearchByKeyword.java +++ b/main/src/cgeo/geocaching/connector/capability/ISearchByKeyword.java @@ -2,11 +2,15 @@ package cgeo.geocaching.connector.capability; import cgeo.geocaching.SearchResult; import cgeo.geocaching.connector.IConnector; +import cgeo.geocaching.loaders.RecaptchaReceiver; + +import org.eclipse.jdt.annotation.NonNull; /** - * connector capability of searching online for a cache by name - * + * Connector capability of searching online for a cache by keyword. + * */ public interface ISearchByKeyword extends IConnector { - public SearchResult searchByName(final String name); + // TODO: The recaptcha receiver is only needed for GC. Would be good to refactor this away from the generic interface. + public SearchResult searchByKeyword(final @NonNull String keyword, final @NonNull RecaptchaReceiver recaptchaReceiver); } diff --git a/main/src/cgeo/geocaching/connector/capability/ISearchByViewPort.java b/main/src/cgeo/geocaching/connector/capability/ISearchByViewPort.java index 4954017..7981ba8 100644 --- a/main/src/cgeo/geocaching/connector/capability/ISearchByViewPort.java +++ b/main/src/cgeo/geocaching/connector/capability/ISearchByViewPort.java @@ -2,8 +2,11 @@ package cgeo.geocaching.connector.capability; import cgeo.geocaching.SearchResult; import cgeo.geocaching.connector.IConnector; +import cgeo.geocaching.connector.gc.MapTokens; import cgeo.geocaching.geopoint.Viewport; +import org.eclipse.jdt.annotation.NonNull; + public interface ISearchByViewPort extends IConnector { - public SearchResult searchByViewport(final Viewport viewport, final String[] tokens); + public SearchResult searchByViewport(final @NonNull Viewport viewport, final MapTokens tokens); } diff --git a/main/src/cgeo/geocaching/connector/ec/ECApi.java b/main/src/cgeo/geocaching/connector/ec/ECApi.java new file mode 100644 index 0000000..f91bcdb --- /dev/null +++ b/main/src/cgeo/geocaching/connector/ec/ECApi.java @@ -0,0 +1,220 @@ +package cgeo.geocaching.connector.ec; + +import cgeo.geocaching.DataStore; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.connector.LogResult; +import cgeo.geocaching.enumerations.CacheType; +import cgeo.geocaching.enumerations.LoadFlags.SaveFlag; +import cgeo.geocaching.enumerations.LogType; +import cgeo.geocaching.enumerations.StatusCode; +import cgeo.geocaching.files.GPX10Parser; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.Viewport; +import cgeo.geocaching.list.StoredList; +import cgeo.geocaching.network.Network; +import cgeo.geocaching.network.Parameters; +import cgeo.geocaching.utils.Log; + +import ch.boye.httpclientandroidlib.HttpResponse; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.FastDateFormat; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; + +public class ECApi { + + private static final String API_HOST = "http://extremcaching.com/exports/"; + + private static final FastDateFormat LOG_DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSSZ", TimeZone.getTimeZone("UTC"), Locale.US); + + public static String cleanCode(String geocode) { + return geocode.replace("EC", ""); + } + + public static Geocache searchByGeoCode(final String geocode) { + final Parameters params = new Parameters("id", cleanCode(geocode), "cgeo", "1"); + final HttpResponse response = apiRequest("gpx.php", params); + + final Collection<Geocache> caches = importCachesFromGPXResponse(response); + if (CollectionUtils.isNotEmpty(caches)) { + return caches.iterator().next(); + } + return null; + } + + public static Collection<Geocache> searchByBBox(final Viewport viewport) { + + if (viewport.getLatitudeSpan() == 0 || viewport.getLongitudeSpan() == 0) { + return Collections.emptyList(); + } + + final Parameters params = new Parameters("fnc", "bbox"); + params.add("lat1", String.valueOf(viewport.getLatitudeMin())); + params.add("lat2", String.valueOf(viewport.getLatitudeMax())); + params.add("lon1", String.valueOf(viewport.getLongitudeMin())); + params.add("lon2", String.valueOf(viewport.getLongitudeMax())); + final HttpResponse response = apiRequest(params); + + return importCachesFromJSON(response); + } + + + public static Collection<Geocache> searchByCenter(final Geopoint center) { + + final Parameters params = new Parameters("fnc", "center"); + params.add("distance", "20"); + params.add("lat", String.valueOf(center.getLatitude())); + params.add("lon", String.valueOf(center.getLongitude())); + final HttpResponse response = apiRequest(params); + + return importCachesFromJSON(response); + } + + public static LogResult postLog(final Geocache cache, final LogType logType, final Calendar date, final String log) { + return postLog(cache, logType, date, log, false); + } + + public static LogResult postLog(final Geocache cache, final LogType logType, final Calendar date, final String log, boolean isRetry) { + final Parameters params = new Parameters("cache_id", cache.getGeocode()); + params.add("type", logType.type); + params.add("log", log); + params.add("date", LOG_DATE_FORMAT.format(date.getTime())); + + final String uri = API_HOST + "log.php"; + final HttpResponse response = Network.postRequest(uri, params); + + if (response == null) { + return new LogResult(StatusCode.LOG_POST_ERROR_EC, ""); + } + if (!isRetry && response.getStatusLine().getStatusCode() == 403) { + if (ECLogin.getInstance().login() == StatusCode.NO_ERROR) { + apiRequest(uri, params, true); + } + } + if (response.getStatusLine().getStatusCode() != 200) { + return new LogResult(StatusCode.LOG_POST_ERROR_EC, ""); + } + + final String data = Network.getResponseDataAlways(response); + if (!StringUtils.isBlank(data) && StringUtils.contains(data, "success")) { + final String uid = StringUtils.remove(data, "success:"); + return new LogResult(StatusCode.NO_ERROR, uid); + } + + return new LogResult(StatusCode.LOG_POST_ERROR_EC, ""); + } + + + private static HttpResponse apiRequest(final Parameters params) { + return apiRequest("api.php", params, false); + } + + private static HttpResponse apiRequest(final String uri, final Parameters params) { + return apiRequest(uri, params, false); + } + + private static HttpResponse apiRequest(final String uri, final Parameters params, final boolean isRetry) { + final HttpResponse response = Network.getRequest(API_HOST + uri, params); + + if (response == null) { + return null; + } + if (!isRetry && response.getStatusLine().getStatusCode() == 403) { + if (ECLogin.getInstance().login() == StatusCode.NO_ERROR) { + apiRequest(uri, params, true); + } + } + if (response.getStatusLine().getStatusCode() != 200) { + return null; + } + return response; + } + + private static Collection<Geocache> importCachesFromGPXResponse(final HttpResponse response) { + if (response == null) { + return Collections.emptyList(); + } + + try { + return new GPX10Parser(StoredList.TEMPORARY_LIST_ID).parse(response.getEntity().getContent(), null); + } catch (Exception e) { + Log.e("Error importing gpx from extremcaching.com", e); + return Collections.emptyList(); + } + } + + private static List<Geocache> importCachesFromJSON(final HttpResponse response) { + + if (response != null) { + try { + final String data = Network.getResponseDataAlways(response); + if (StringUtils.isBlank(data) || StringUtils.equals(data, "[]")) { + return Collections.emptyList(); + } + final JSONArray json = new JSONArray(data); + final int len = json.length(); + final List<Geocache> caches = new ArrayList<Geocache>(len); + for (int i = 0; i < len; i++) { + final Geocache cache = parseCache(json.getJSONObject(i)); + if (cache != null) { + caches.add(cache); + } + } + return caches; + } catch (final JSONException e) { + Log.w("JSONResult", e); + } + } + + return Collections.emptyList(); + + } + + private static Geocache parseCache(final JSONObject response) { + final Geocache cache = new Geocache(); + cache.setReliableLatLon(true); + try { + cache.setGeocode("EC" + response.getString("cache_id")); + cache.setName(response.getString("title")); + cache.setCoords(new Geopoint(response.getString("lat"), response.getString("lon"))); + cache.setType(getCacheType(response.getString("type"))); + cache.setDifficulty((float) response.getDouble("difficulty")); + cache.setTerrain((float) response.getDouble("terrain")); + cache.setFound(response.getInt("found") == 1 ? true : false); + + DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_CACHE)); + } catch (final JSONException e) { + Log.e("ECApi.parseCache", e); + return null; + } + return cache; + } + + private static CacheType getCacheType(final String cacheType) { + if (cacheType.equalsIgnoreCase("Tradi")) { + return CacheType.TRADITIONAL; + } + if (cacheType.equalsIgnoreCase("Multi")) { + return CacheType.MULTI; + } + if (cacheType.equalsIgnoreCase("Event")) { + return CacheType.EVENT; + } + if (cacheType.equalsIgnoreCase("Mystery")) { + return CacheType.MYSTERY; + } + return CacheType.UNKNOWN; + } +} diff --git a/main/src/cgeo/geocaching/connector/ec/ECConnector.java b/main/src/cgeo/geocaching/connector/ec/ECConnector.java new file mode 100644 index 0000000..3c66d7d --- /dev/null +++ b/main/src/cgeo/geocaching/connector/ec/ECConnector.java @@ -0,0 +1,232 @@ +package cgeo.geocaching.connector.ec; + +import cgeo.geocaching.CgeoApplication; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.ICache; +import cgeo.geocaching.LogCacheActivity; +import cgeo.geocaching.R; +import cgeo.geocaching.SearchResult; +import cgeo.geocaching.connector.AbstractConnector; +import cgeo.geocaching.connector.ILoggingManager; +import cgeo.geocaching.connector.capability.ICredentials; +import cgeo.geocaching.connector.capability.ILogin; +import cgeo.geocaching.connector.capability.ISearchByCenter; +import cgeo.geocaching.connector.capability.ISearchByGeocode; +import cgeo.geocaching.connector.capability.ISearchByViewPort; +import cgeo.geocaching.connector.gc.MapTokens; +import cgeo.geocaching.enumerations.LogType; +import cgeo.geocaching.enumerations.StatusCode; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.Viewport; +import cgeo.geocaching.loaders.RecaptchaReceiver; +import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.settings.SettingsActivity; +import cgeo.geocaching.utils.CancellableHandler; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import android.content.Context; +import android.os.Handler; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; + +public class ECConnector extends AbstractConnector implements ISearchByGeocode, ISearchByCenter, ISearchByViewPort, ILogin, ICredentials { + + private static final String CACHE_URL = "http://extremcaching.com/index.php/output-2/"; + + /** + * Pattern for EC codes + */ + private final static Pattern PATTERN_EC_CODE = Pattern.compile("EC[0-9]+", Pattern.CASE_INSENSITIVE); + + private ECConnector() { + // singleton + } + + /** + * initialization on demand holder pattern + */ + private static class Holder { + private static final @NonNull ECConnector INSTANCE = new ECConnector(); + } + + public static @NonNull + ECConnector getInstance() { + return Holder.INSTANCE; + } + + @Override + public boolean canHandle(@NonNull String geocode) { + return ECConnector.PATTERN_EC_CODE.matcher(geocode).matches(); + } + + @Override + public String getCacheUrl(@NonNull Geocache cache) { + return CACHE_URL + cache.getGeocode().replace("EC", ""); + } + + @Override + public String getName() { + return "extremcaching.com"; + } + + @Override + public String getHost() { + return "extremcaching.com"; + } + + @Override + public SearchResult searchByGeocode(final @Nullable String geocode, final @Nullable String guid, final CancellableHandler handler) { + if (geocode == null) { + return null; + } + CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_loadpage); + + final Geocache cache = ECApi.searchByGeoCode(geocode); + + return cache != null ? new SearchResult(cache) : null; + } + + @Override + public SearchResult searchByViewport(@NonNull Viewport viewport, final MapTokens tokens) { + final Collection<Geocache> caches = ECApi.searchByBBox(viewport); + if (caches == null) { + return null; + } + final SearchResult searchResult = new SearchResult(caches); + return searchResult.filterSearchResults(false, false, Settings.getCacheType()); + } + + @Override + public SearchResult searchByCenter(@NonNull Geopoint center, final @NonNull RecaptchaReceiver recaptchaReceiver) { + final Collection<Geocache> caches = ECApi.searchByCenter(center); + if (caches == null) { + return null; + } + final SearchResult searchResult = new SearchResult(caches); + return searchResult.filterSearchResults(false, false, Settings.getCacheType()); + } + + @Override + public boolean isOwner(final ICache cache) { + return false; + } + + @Override + protected String getCacheUrlPrefix() { + return CACHE_URL; + } + + @Override + public boolean isActive() { + return Settings.isECConnectorActive(); + } + + @Override + public boolean login(Handler handler, Context fromActivity) { + // login + final StatusCode status = ECLogin.getInstance().login(); + + if (status == StatusCode.NO_ERROR) { + CgeoApplication.getInstance().checkLogin = false; + } + + if (CgeoApplication.getInstance().showLoginToast && handler != null) { + handler.sendMessage(handler.obtainMessage(0, status)); + CgeoApplication.getInstance().showLoginToast = false; + + // invoke settings activity to insert login details + if (status == StatusCode.NO_LOGIN_INFO_STORED && fromActivity != null) { + SettingsActivity.jumpToServicesPage(fromActivity); + } + } + return status == StatusCode.NO_ERROR; + } + + @Override + public String getUserName() { + return ECLogin.getInstance().getActualUserName(); + } + + @Override + public int getCachesFound() { + return ECLogin.getInstance().getActualCachesFound(); + } + + @Override + public String getLoginStatusString() { + return ECLogin.getInstance().getActualStatus(); + } + + @Override + public boolean isLoggedIn() { + return ECLogin.getInstance().isActualLoginStatus(); + } + + @Override + public int getCacheMapMarkerId(boolean disabled) { + final String icons = Settings.getECIconSet(); + if (StringUtils.equals(icons, "1")) { + return disabled ? R.drawable.marker_disabled_other : R.drawable.marker_other; + } + return disabled ? R.drawable.marker_disabled_oc : R.drawable.marker_oc; + } + + @Override + public String getLicenseText(final @NonNull Geocache cache) { + // NOT TO BE TRANSLATED + return "© " + cache.getOwnerDisplayName() + ", <a href=\"" + getCacheUrl(cache) + "\">" + getName() + "</a>, CC BY-NC-ND 3.0, alle Logeinträge © jeweiliger Autor"; + } + + @Override + public boolean supportsLogging() { + return true; + } + + @Override + public boolean canLog(Geocache cache) { + return true; + } + + @Override + public ILoggingManager getLoggingManager(final LogCacheActivity activity, final Geocache cache) { + return new ECLoggingManager(activity, this, cache); + } + + @Override + public List<LogType> getPossibleLogTypes(Geocache geocache) { + final List<LogType> logTypes = new ArrayList<LogType>(); + if (geocache.isEventCache()) { + logTypes.add(LogType.WILL_ATTEND); + logTypes.add(LogType.ATTENDED); + } else { + logTypes.add(LogType.FOUND_IT); + } + if (!geocache.isEventCache()) { + logTypes.add(LogType.DIDNT_FIND_IT); + } + logTypes.add(LogType.NOTE); + return logTypes; + } + + @Override + public int getMaxTerrain() { + return 7; + } + + @Override + public int getUsernamePreferenceKey() { + return R.string.pref_ecusername; + } + + @Override + public int getPasswordPreferenceKey() { + return R.string.pref_ecpassword; + } + +} diff --git a/main/src/cgeo/geocaching/connector/ec/ECConstants.java b/main/src/cgeo/geocaching/connector/ec/ECConstants.java new file mode 100644 index 0000000..7c02d8e --- /dev/null +++ b/main/src/cgeo/geocaching/connector/ec/ECConstants.java @@ -0,0 +1,19 @@ +package cgeo.geocaching.connector.ec; + +import java.util.regex.Pattern; + +/** + * For further information about patterns have a look at + * http://download.oracle.com/javase/1.4.2/docs/api/java/util/regex/Pattern.html + */ +public final class ECConstants { + + public static final Pattern PATTERN_LOGIN_NAME = Pattern.compile("\"mod_login_greetingfrontpage-teaser\">Hallo, ([^<]+)</span>"); + public static final Pattern PATTERN_LOGIN_SECURITY = Pattern.compile("<input type=\"hidden\" name=\"return\" value=\"(.*)\" />[^<]*<input type=\"hidden\" name=\"(.*)\" value=\"1\" />"); + public static final Pattern PATTERN_CACHES_FOUND = Pattern.compile("Gefundene Caches.*?>([0-9]+)<"); + + private ECConstants() { + // this class shall not have instances + } + +} diff --git a/main/src/cgeo/geocaching/connector/ec/ECLoggingManager.java b/main/src/cgeo/geocaching/connector/ec/ECLoggingManager.java new file mode 100644 index 0000000..ded2d71 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/ec/ECLoggingManager.java @@ -0,0 +1,48 @@ +package cgeo.geocaching.connector.ec; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.LogCacheActivity; +import cgeo.geocaching.TrackableLog; +import cgeo.geocaching.connector.AbstractLoggingManager; +import cgeo.geocaching.connector.ImageResult; +import cgeo.geocaching.connector.LogResult; +import cgeo.geocaching.enumerations.LogType; + +import android.net.Uri; + +import java.util.Calendar; +import java.util.List; + +public class ECLoggingManager extends AbstractLoggingManager { + + private final ECConnector connector; + private final Geocache cache; + private LogCacheActivity activity; + + public ECLoggingManager(final LogCacheActivity activity, final ECConnector connector, final Geocache cache) { + this.connector = connector; + this.cache = cache; + this.activity = activity; + } + + @Override + public final void init() { + activity.onLoadFinished(); + } + + @Override + public final LogResult postLog(final Geocache cache, final LogType logType, final Calendar date, final String log, final String logPassword, final List<TrackableLog> trackableLogs) { + return ECApi.postLog(cache, logType, date, log); + } + + @Override + public final ImageResult postLogImage(final String logId, final String imageCaption, final String imageDescription, final Uri imageUri) { + return null; + } + + @Override + public List<LogType> getPossibleLogTypes() { + return connector.getPossibleLogTypes(cache); + } + +} diff --git a/main/src/cgeo/geocaching/connector/ec/ECLogin.java b/main/src/cgeo/geocaching/connector/ec/ECLogin.java new file mode 100644 index 0000000..089688e --- /dev/null +++ b/main/src/cgeo/geocaching/connector/ec/ECLogin.java @@ -0,0 +1,138 @@ +package cgeo.geocaching.connector.ec; + +import cgeo.geocaching.CgeoApplication; +import cgeo.geocaching.R; +import cgeo.geocaching.connector.AbstractLogin; +import cgeo.geocaching.enumerations.StatusCode; +import cgeo.geocaching.network.Cookies; +import cgeo.geocaching.network.Network; +import cgeo.geocaching.network.Parameters; +import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.TextUtils; + +import ch.boye.httpclientandroidlib.HttpResponse; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.regex.Matcher; + +public class ECLogin extends AbstractLogin { + + private ECLogin() { + // singleton + } + + private static class SingletonHolder { + private static ECLogin INSTANCE = new ECLogin(); + } + + public static ECLogin getInstance() { + return SingletonHolder.INSTANCE; + } + + @Override + protected StatusCode login(boolean retry) { + final ImmutablePair<String, String> login = Settings.getCredentials(ECConnector.getInstance()); + + if (StringUtils.isEmpty(login.left) || StringUtils.isEmpty(login.right)) { + clearLoginInfo(); + Log.e("ECLogin.login: No login information stored"); + return StatusCode.NO_LOGIN_INFO_STORED; + } + + setActualStatus(CgeoApplication.getInstance().getString(R.string.init_login_popup_working)); + HttpResponse loginResponse = Network.getRequest("https://extremcaching.com/community/profil1"); + String loginData = Network.getResponseData(loginResponse); + + if (StringUtils.isBlank(loginData)) { + Log.e("ECLogin.login: Failed to retrieve login page (1st)"); + return StatusCode.CONNECTION_FAILED_EC; // no login page + } + + if (getLoginStatus(loginData)) { + Log.i("Already logged in Extremcaching.com as " + login.left); + return StatusCode.NO_ERROR; // logged in + } + + final Parameters params = new Parameters( + "username", login.left, + "password", login.right, + "remember", "yes"); + + Matcher m = ECConstants.PATTERN_LOGIN_SECURITY.matcher(loginData); + if (m.find() && m.groupCount() == 2) { + params.add("return", m.group(1)); + params.add(m.group(2), "1"); + } else { + Log.e("ECLogin.login security tokens in login form not found"); + return StatusCode.COMMUNICATION_ERROR; + } + + loginResponse = Network.postRequest("http://extremcaching.com/component/users/?task=user.login", params); + loginData = Network.getResponseData(loginResponse); + + if (StringUtils.isBlank(loginData)) { + Log.e("ECLogin.login: Failed to retrieve login page (2nd)"); + return StatusCode.COMMUNICATION_ERROR; // no login page + } + assert loginData != null; // Caught above + + if (getLoginStatus(loginData)) { + Log.i("Successfully logged in Extremcaching.com as " + login.left); + + Settings.setCookieStore(Cookies.dumpCookieStore()); + + return StatusCode.NO_ERROR; // logged in + } + + if (loginData.contains("Benutzername und Passwort falsch")) { // Yes, it's hardcoded in German (translation is done using Javascript and Google Translate) + Log.i("Failed to log in Extremcaching.com as " + login.left + " because of wrong username/password"); + return StatusCode.WRONG_LOGIN_DATA; // wrong login + } + + Log.i("Failed to log in Extremcaching.com as " + login.left + " for some unknown reason"); + if (retry) { + return login(false); + } + + return StatusCode.UNKNOWN_ERROR; // can't login + } + + + /** + * Check if the user has been logged in when he retrieved the data. + * + * @param page + * @return <code>true</code> if user is logged in, <code>false</code> otherwise + */ + public boolean getLoginStatus(@Nullable final String page) { + if (StringUtils.isBlank(page)) { + Log.e("ECLogin.getLoginStatus: No page given"); + return false; + } + assert page != null; + + setActualStatus(CgeoApplication.getInstance().getString(R.string.init_login_popup_ok)); + + // on every page except login page + setActualLoginStatus(TextUtils.matches(page, ECConstants.PATTERN_LOGIN_NAME)); + if (isActualLoginStatus()) { + setActualUserName(TextUtils.getMatch(page, ECConstants.PATTERN_LOGIN_NAME, true, "???")); + int cachesCount = 0; + try { + cachesCount = Integer.parseInt(TextUtils.getMatch(page, ECConstants.PATTERN_CACHES_FOUND, true, "0")); + } catch (final NumberFormatException e) { + Log.e("ECLogin.getLoginStatus: bad cache count", e); + } + setActualCachesFound(cachesCount); + return true; + } + + setActualStatus(CgeoApplication.getInstance().getString(R.string.init_login_popup_failed)); + return false; + } + +} diff --git a/main/src/cgeo/geocaching/connector/gc/GCConnector.java b/main/src/cgeo/geocaching/connector/gc/GCConnector.java index 6a61405..4349d5d 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConnector.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConnector.java @@ -9,13 +9,16 @@ import cgeo.geocaching.R; import cgeo.geocaching.SearchResult; import cgeo.geocaching.connector.AbstractConnector; import cgeo.geocaching.connector.ILoggingManager; +import cgeo.geocaching.connector.capability.ICredentials; import cgeo.geocaching.connector.capability.ILogin; import cgeo.geocaching.connector.capability.ISearchByCenter; import cgeo.geocaching.connector.capability.ISearchByGeocode; +import cgeo.geocaching.connector.capability.ISearchByKeyword; import cgeo.geocaching.connector.capability.ISearchByViewPort; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Viewport; +import cgeo.geocaching.loaders.RecaptchaReceiver; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.settings.SettingsActivity; import cgeo.geocaching.utils.CancellableHandler; @@ -23,13 +26,15 @@ import cgeo.geocaching.utils.Log; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import android.content.Context; import android.os.Handler; import java.util.regex.Pattern; -public class GCConnector extends AbstractConnector implements ISearchByGeocode, ISearchByCenter, ISearchByViewPort, ILogin { +public class GCConnector extends AbstractConnector implements ISearchByGeocode, ISearchByCenter, ISearchByViewPort, ISearchByKeyword, ILogin, ICredentials { private static final String CACHE_URL_SHORT = "http://coord.info/"; // Double slash is used to force open in browser @@ -53,28 +58,26 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, * initialization on demand holder pattern */ private static class Holder { - private static final GCConnector INSTANCE = new GCConnector(); + private static final @NonNull GCConnector INSTANCE = new GCConnector(); } - public static GCConnector getInstance() { + public static @NonNull + GCConnector getInstance() { return Holder.INSTANCE; } @Override - public boolean canHandle(String geocode) { - if (geocode == null) { - return false; - } + public boolean canHandle(@NonNull String geocode) { return GCConnector.PATTERN_GC_CODE.matcher(geocode).matches(); } @Override - public String getLongCacheUrl(Geocache cache) { + public String getLongCacheUrl(@NonNull Geocache cache) { return CACHE_URL_LONG + cache.getGeocode(); } @Override - public String getCacheUrl(Geocache cache) { + public String getCacheUrl(@NonNull Geocache cache) { return CACHE_URL_SHORT + cache.getGeocode(); } @@ -129,7 +132,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, } @Override - public SearchResult searchByGeocode(final String geocode, final String guid, final CancellableHandler handler) { + public SearchResult searchByGeocode(final @Nullable String geocode, final @Nullable String guid, final CancellableHandler handler) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_loadpage); @@ -166,7 +169,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, } @Override - public SearchResult searchByViewport(Viewport viewport, String[] tokens) { + public SearchResult searchByViewport(@NonNull Viewport viewport, final MapTokens tokens) { return GCMap.searchByViewport(viewport, tokens); } @@ -209,8 +212,9 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, * * This must not be called from the UI thread. * - * @param cache the cache to add - * @return <code>true</code> if the cache was sucessfully added, <code>false</code> otherwise + * @param cache + * the cache to add + * @return <code>true</code> if the cache was successfully added, <code>false</code> otherwise */ public static boolean addToFavorites(Geocache cache) { @@ -226,8 +230,9 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, * * This must not be called from the UI thread. * - * @param cache the cache to add - * @return <code>true</code> if the cache was sucessfully added, <code>false</code> otherwise + * @param cache + * the cache to add + * @return <code>true</code> if the cache was successfully added, <code>false</code> otherwise */ public static boolean removeFromFavorites(Geocache cache) { @@ -266,9 +271,8 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, } @Override - public SearchResult searchByCenter(Geopoint center) { - // TODO make search by coordinate use this method. currently it is just a marker that this connector supports search by center - return null; + public SearchResult searchByCenter(@NonNull Geopoint center, final @NonNull RecaptchaReceiver recaptchaReceiver) { + return GCParser.searchByCoords(center, Settings.getCacheType(), Settings.isShowCaptcha(), recaptchaReceiver); } @Override @@ -282,7 +286,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, } @Override - public boolean isActivated() { + public boolean isActive() { return Settings.isGCConnectorActive(); } @@ -297,11 +301,11 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, @Override public boolean login(Handler handler, Context fromActivity) { // login - final StatusCode status = Login.login(); + final StatusCode status = GCLogin.getInstance().login(); if (status == StatusCode.NO_ERROR) { CgeoApplication.getInstance().checkLogin = false; - Login.detectGcCustomDate(); + GCLogin.detectGcCustomDate(); } if (CgeoApplication.getInstance().showLoginToast && handler != null) { @@ -318,22 +322,22 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, @Override public String getUserName() { - return Login.getActualUserName(); + return GCLogin.getInstance().getActualUserName(); } @Override public int getCachesFound() { - return Login.getActualCachesFound(); + return GCLogin.getInstance().getActualCachesFound(); } @Override public String getLoginStatusString() { - return Login.getActualStatus(); + return GCLogin.getInstance().getActualStatus(); } @Override public boolean isLoggedIn() { - return Login.isActualLoginStatus(); + return GCLogin.getInstance().isActualLoginStatus(); } @Override @@ -353,4 +357,19 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, } return prefix; } + + @Override + public SearchResult searchByKeyword(@NonNull String keyword, final @NonNull RecaptchaReceiver recaptchaReceiver) { + return GCParser.searchByKeyword(keyword, Settings.getCacheType(), Settings.isShowCaptcha(), recaptchaReceiver); + } + + @Override + public int getUsernamePreferenceKey() { + return R.string.pref_username; + } + + @Override + public int getPasswordPreferenceKey() { + return R.string.pref_password; + } } diff --git a/main/src/cgeo/geocaching/connector/gc/GCLoggingManager.java b/main/src/cgeo/geocaching/connector/gc/GCLoggingManager.java index 8bed2ea..06d6411 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCLoggingManager.java +++ b/main/src/cgeo/geocaching/connector/gc/GCLoggingManager.java @@ -5,7 +5,7 @@ import cgeo.geocaching.LogCacheActivity; import cgeo.geocaching.R; import cgeo.geocaching.TrackableLog; import cgeo.geocaching.activity.ActivityMixin; -import cgeo.geocaching.connector.ILoggingManager; +import cgeo.geocaching.connector.AbstractLoggingManager; import cgeo.geocaching.connector.ImageResult; import cgeo.geocaching.connector.LogResult; import cgeo.geocaching.enumerations.LogType; @@ -28,7 +28,7 @@ import java.util.Calendar; import java.util.Collections; import java.util.List; -public class GCLoggingManager implements ILoggingManager, LoaderManager.LoaderCallbacks<String> { +public class GCLoggingManager extends AbstractLoggingManager implements LoaderManager.LoaderCallbacks<String> { private final LogCacheActivity activity; private final Geocache cache; @@ -60,7 +60,7 @@ public class GCLoggingManager implements ILoggingManager, LoaderManager.LoaderCa hasLoaderError = true; } else { - viewstates = Login.getViewstates(page); + viewstates = GCLogin.getViewstates(page); trackables = GCParser.parseTrackableLog(page); possibleLogTypes = GCParser.parseTypes(page); diff --git a/main/src/cgeo/geocaching/connector/gc/Login.java b/main/src/cgeo/geocaching/connector/gc/GCLogin.java index beb49f1..e072c8f 100644 --- a/main/src/cgeo/geocaching/connector/gc/Login.java +++ b/main/src/cgeo/geocaching/connector/gc/GCLogin.java @@ -2,6 +2,7 @@ package cgeo.geocaching.connector.gc; import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.R; +import cgeo.geocaching.connector.AbstractLogin; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.network.Cookies; import cgeo.geocaching.network.HtmlImage; @@ -13,6 +14,7 @@ import cgeo.geocaching.utils.MatcherWrapper; import cgeo.geocaching.utils.TextUtils; import ch.boye.httpclientandroidlib.HttpResponse; + import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; @@ -29,18 +31,12 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; -public abstract class Login { +public class GCLogin extends AbstractLogin { private static final String DEFAULT_CUSTOM_DATE_FORMAT = "MM/dd/yyyy"; private final static String ENGLISH = "<a href=\"#\">English▼</a>"; - // false = not logged in - private static boolean actualLoginStatus = false; - private static String actualUserName = StringUtils.EMPTY; - private static int actualCachesFound = -1; - private static String actualStatus = StringUtils.EMPTY; - private final static Map<String, SimpleDateFormat> GC_CUSTOM_DATE_FORMATS; public static final String LANGUAGE_CHANGE_URI = "http://www.geocaching.com/my/souvenirs.aspx"; @@ -64,20 +60,31 @@ public abstract class Login { GC_CUSTOM_DATE_FORMATS = Collections.unmodifiableMap(map); } - public static StatusCode login() { - return login(true); + private GCLogin() { + // singleton } - private static StatusCode login(boolean retry) { - final ImmutablePair<String, String> login = Settings.getGcLogin(); + public static GCLogin getInstance() { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder { + private static final GCLogin INSTANCE = new GCLogin(); + } - if (StringUtils.isEmpty(login.left) || StringUtils.isEmpty(login.right)) { + @Override + protected StatusCode login(boolean retry) { + final ImmutablePair<String, String> credentials = Settings.getGcCredentials(); + final String username = credentials.left; + final String password = credentials.right; + + if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { clearLoginInfo(); Log.e("Login.login: No login information stored"); return StatusCode.NO_LOGIN_INFO_STORED; } - Login.setActualStatus(CgeoApplication.getInstance().getString(R.string.init_login_popup_working)); + setActualStatus(CgeoApplication.getInstance().getString(R.string.init_login_popup_working)); HttpResponse loginResponse = Network.getRequest("https://www.geocaching.com/login/default.aspx"); String loginData = Network.getResponseData(loginResponse); if (loginResponse != null && loginResponse.getStatusLine().getStatusCode() == 503 && TextUtils.matches(loginData, GCConstants.PATTERN_MAINTENANCE)) { @@ -86,12 +93,12 @@ public abstract class Login { if (StringUtils.isBlank(loginData)) { Log.e("Login.login: Failed to retrieve login page (1st)"); - return StatusCode.CONNECTION_FAILED; // no loginpage + return StatusCode.CONNECTION_FAILED; // no login page } - if (Login.getLoginStatus(loginData)) { - Log.i("Already logged in Geocaching.com as " + login.left + " (" + Settings.getMemberStatus() + ')'); - Login.switchToEnglish(loginData); + if (getLoginStatus(loginData)) { + Log.i("Already logged in Geocaching.com as " + username + " (" + Settings.getMemberStatus() + ')'); + switchToEnglish(loginData); return StatusCode.NO_ERROR; // logged in } @@ -101,16 +108,16 @@ public abstract class Login { final Parameters params = new Parameters( "__EVENTTARGET", "", "__EVENTARGUMENT", "", - "ctl00$ContentBody$tbUsername", login.left, - "ctl00$ContentBody$tbPassword", login.right, + "ctl00$ContentBody$tbUsername", username, + "ctl00$ContentBody$tbPassword", password, "ctl00$ContentBody$cbRememberMe", "on", "ctl00$ContentBody$btnSignIn", "Login"); - final String[] viewstates = Login.getViewstates(loginData); + final String[] viewstates = GCLogin.getViewstates(loginData); if (isEmpty(viewstates)) { Log.e("Login.login: Failed to find viewstates"); return StatusCode.LOGIN_PARSE_ERROR; // no viewstates } - Login.putViewstates(params, viewstates); + GCLogin.putViewstates(params, viewstates); loginResponse = Network.postRequest("https://www.geocaching.com/login/default.aspx", params); loginData = Network.getResponseData(loginResponse); @@ -122,35 +129,35 @@ public abstract class Login { } assert loginData != null; // Caught above - if (Login.getLoginStatus(loginData)) { - Log.i("Successfully logged in Geocaching.com as " + login.left + " (" + Settings.getMemberStatus() + ')'); + if (getLoginStatus(loginData)) { + Log.i("Successfully logged in Geocaching.com as " + username + " (" + Settings.getMemberStatus() + ')'); - Login.switchToEnglish(loginData); + switchToEnglish(loginData); Settings.setCookieStore(Cookies.dumpCookieStore()); return StatusCode.NO_ERROR; // logged in } if (loginData.contains("Your username/password combination does not match.")) { - Log.i("Failed to log in Geocaching.com as " + login.left + " because of wrong username/password"); + Log.i("Failed to log in Geocaching.com as " + username + " because of wrong username/password"); return StatusCode.WRONG_LOGIN_DATA; // wrong login } if (loginData.contains("You must validate your account before you can log in.")) { - Log.i("Failed to log in Geocaching.com as " + login.left + " because account needs to be validated first"); + Log.i("Failed to log in Geocaching.com as " + username + " because account needs to be validated first"); return StatusCode.UNVALIDATED_ACCOUNT; } - Log.i("Failed to log in Geocaching.com as " + login.left + " for some unknown reason"); + Log.i("Failed to log in Geocaching.com as " + username + " for some unknown reason"); if (retry) { - Login.switchToEnglish(loginData); + switchToEnglish(loginData); return login(false); } return StatusCode.UNKNOWN_ERROR; // can't login } - public static StatusCode logout() { + public StatusCode logout() { final HttpResponse logoutResponse = Network.getRequest("https://www.geocaching.com/login/default.aspx?RESET=Y&redir=http%3a%2f%2fwww.geocaching.com%2fdefault.aspx%3f"); final String logoutData = Network.getResponseData(logoutResponse); if (logoutResponse != null && logoutResponse.getStatusLine().getStatusCode() == 503 && TextUtils.matches(logoutData, GCConstants.PATTERN_MAINTENANCE)) { @@ -162,51 +169,6 @@ public abstract class Login { return StatusCode.NO_ERROR; } - private static void resetLoginStatus() { - Cookies.clearCookies(); - Settings.setCookieStore(null); - - setActualLoginStatus(false); - } - - private static void clearLoginInfo() { - resetLoginStatus(); - - setActualCachesFound(-1); - setActualStatus(CgeoApplication.getInstance().getString(R.string.err_login)); - } - - static void setActualCachesFound(final int found) { - actualCachesFound = found; - } - - public static String getActualStatus() { - return actualStatus; - } - - private static void setActualStatus(final String status) { - actualStatus = status; - } - - public static boolean isActualLoginStatus() { - return actualLoginStatus; - } - - private static void setActualLoginStatus(boolean loginStatus) { - actualLoginStatus = loginStatus; - } - - public static String getActualUserName() { - return actualUserName; - } - - private static void setActualUserName(String userName) { - actualUserName = userName; - } - - public static int getActualCachesFound() { - return actualCachesFound; - } /** * Check if the user has been logged in when he retrieved the data. @@ -214,7 +176,7 @@ public abstract class Login { * @param page * @return <code>true</code> if user is logged in, <code>false</code> otherwise */ - public static boolean getLoginStatus(@Nullable final String page) { + public boolean getLoginStatus(@Nullable final String page) { if (StringUtils.isBlank(page)) { Log.e("Login.checkLogin: No page given"); return false; @@ -253,7 +215,7 @@ public abstract class Login { return false; } - private static void switchToEnglish(String previousPage) { + private void switchToEnglish(String previousPage) { if (previousPage != null && previousPage.contains(ENGLISH)) { Log.i("Geocaching.com language already set to English"); // get find count @@ -267,7 +229,7 @@ public abstract class Login { final Parameters params = new Parameters( "__EVENTTARGET", "ctl00$uxLocaleList$uxLocaleList$ctl00$uxLocaleItem", // switch to english "__EVENTARGUMENT", ""); - Login.transferViewstates(page, params); + GCLogin.transferViewstates(page, params); final HttpResponse response = Network.postRequest(LANGUAGE_CHANGE_URI, params, new Parameters("Referer", LANGUAGE_CHANGE_URI)); if (Network.isSuccess(response)) { Log.i("changed language on geocaching.com to English"); @@ -277,7 +239,7 @@ public abstract class Login { } } - public static BitmapDrawable downloadAvatarAndGetMemberStatus() { + public BitmapDrawable downloadAvatarAndGetMemberStatus() { try { final String responseData = StringUtils.defaultString(Network.getResponseData(Network.getRequest("http://www.geocaching.com/my/"))); final String profile = TextUtils.replaceWhitespace(responseData); @@ -455,7 +417,7 @@ public abstract class Login { * @param uri * @return */ - public static String postRequestLogged(final String uri, final Parameters params) { + public String postRequestLogged(final String uri, final Parameters params) { final String data = Network.getResponseData(Network.postRequest(uri, params)); if (getLoginStatus(data)) { @@ -478,7 +440,7 @@ public abstract class Login { * @return */ @Nullable - public static String getRequestLogged(@NonNull final String uri, @Nullable final Parameters params) { + public String getRequestLogged(@NonNull final String uri, @Nullable final Parameters params) { final HttpResponse response = Network.getRequest(uri, params); final String data = Network.getResponseData(response, canRemoveWhitespace(uri)); @@ -506,12 +468,18 @@ public abstract class Login { return !StringUtils.contains(uri, "cache_details"); } - /** Get user session & session token from the Live Map. Needed for following requests */ - public static String[] getMapTokens() { + /** + * Get user session & session token from the Live Map. Needed for following requests. + * + * @return first is user session, second is session token + */ + public static @NonNull + MapTokens getMapTokens() { final HttpResponse response = Network.getRequest(GCConstants.URL_LIVE_MAP); final String data = Network.getResponseData(response); final String userSession = TextUtils.getMatch(data, GCConstants.PATTERN_USERSESSION, ""); final String sessionToken = TextUtils.getMatch(data, GCConstants.PATTERN_SESSIONTOKEN, ""); - return new String[] { userSession, sessionToken }; + return new MapTokens(userSession, sessionToken); } + } diff --git a/main/src/cgeo/geocaching/connector/gc/GCMap.java b/main/src/cgeo/geocaching/connector/gc/GCMap.java index e2c7dfa..aabeb56 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCMap.java +++ b/main/src/cgeo/geocaching/connector/gc/GCMap.java @@ -1,9 +1,9 @@ package cgeo.geocaching.connector.gc; +import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.DataStore; import cgeo.geocaching.Geocache; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LiveMapStrategy.Strategy; @@ -85,7 +85,7 @@ public class GCMap { cache.setDifficulty(Float.parseFloat(difficultyObj.getString("text"))); // 3.5 JSONObject terrainObj = dataObject.getJSONObject("terrain"); cache.setTerrain(Float.parseFloat(terrainObj.getString("text"))); // 1.5 - cache.setHidden(Login.parseGcCustomDate(dataObject.getString("hidden"), "MM/dd/yyyy")); // 7/23/2001 + cache.setHidden(GCLogin.parseGcCustomDate(dataObject.getString("hidden"), "MM/dd/yyyy")); // 7/23/2001 JSONObject containerObj = dataObject.getJSONObject("container"); cache.setSize(CacheSize.getById(containerObj.getString("text"))); // Regular JSONObject typeObj = dataObject.getJSONObject("type"); @@ -254,7 +254,7 @@ public class GCMap { * Live map tokens * @return */ - public static SearchResult searchByViewport(final Viewport viewport, final String[] tokens) { + public static SearchResult searchByViewport(final Viewport viewport, final MapTokens tokens) { int speed = (int) CgeoApplication.getInstance().currentGeo().getSpeed() * 60 * 60 / 1000; // in km/h Strategy strategy = Settings.getLiveMapStrategy(); if (strategy == Strategy.AUTO) { @@ -284,7 +284,7 @@ public class GCMap { * Strategy for data retrieval and parsing, @see Strategy * @return */ - private static SearchResult searchByViewport(final Viewport viewport, final String[] tokens, Strategy strategy) { + private static SearchResult searchByViewport(final Viewport viewport, final MapTokens tokens, Strategy strategy) { Log.d("GCMap.searchByViewport" + viewport.toString()); final SearchResult searchResult = new SearchResult(); @@ -310,7 +310,7 @@ public class GCMap { "ep", "1", "app", "cgeo"); if (tokens != null) { - params.put("k", tokens[0], "st", tokens[1]); + params.put("k", tokens.getUserSession(), "st", tokens.getSessionToken()); } if (Settings.isExcludeMyCaches()) { // works only for PM params.put("hf", "1", "hh", "1"); // hide found, hide hidden diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java index 4f5d293..f05bc04 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCParser.java +++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java @@ -79,7 +79,7 @@ public abstract class GCParser { final SearchResult searchResult = new SearchResult(); searchResult.setUrl(url); - searchResult.viewstates = Login.getViewstates(page); + searchResult.viewstates = GCLogin.getViewstates(page); // recaptcha if (showCaptcha) { @@ -203,7 +203,7 @@ public abstract class GCParser { final String dateHidden = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_HIDDEN_DATE, false, 1, null, false); if (StringUtils.isNotBlank(dateHidden)) { try { - Date date = Login.parseGcCustomDate(dateHidden); + Date date = GCLogin.parseGcCustomDate(dateHidden); if (date != null) { cache.setHidden(date); } @@ -268,7 +268,7 @@ public abstract class GCParser { try { final String result = TextUtils.getMatch(page, GCConstants.PATTERN_SEARCH_TOTALCOUNT, false, 1, null, true); if (null != result) { - searchResult.setTotal(Integer.parseInt(result) - excludedCaches); + searchResult.setTotalCountGC(Integer.parseInt(result) - excludedCaches); } } catch (final NumberFormatException e) { Log.w("GCParser.parseSearch: Failed to parse cache count"); @@ -276,10 +276,7 @@ public abstract class GCParser { String recaptchaText = null; if (thread != null && StringUtils.isNotBlank(thread.getChallenge())) { - if (thread.getText() == null) { - thread.waitForUser(); - } - + thread.waitForUser(); recaptchaText = thread.getText(); } @@ -465,13 +462,13 @@ public abstract class GCParser { try { String hiddenString = TextUtils.getMatch(tableInside, GCConstants.PATTERN_HIDDEN, true, null); if (StringUtils.isNotBlank(hiddenString)) { - cache.setHidden(Login.parseGcCustomDate(hiddenString)); + cache.setHidden(GCLogin.parseGcCustomDate(hiddenString)); } if (cache.getHiddenDate() == null) { // event date hiddenString = TextUtils.getMatch(tableInside, GCConstants.PATTERN_HIDDENEVENT, true, null); if (StringUtils.isNotBlank(hiddenString)) { - cache.setHidden(Login.parseGcCustomDate(hiddenString)); + cache.setHidden(GCLogin.parseGcCustomDate(hiddenString)); } } } catch (final ParseException e) { @@ -497,7 +494,7 @@ public abstract class GCParser { try { final String foundDateString = TextUtils.getMatch(page, GCConstants.PATTERN_FOUND_DATE, true, null); if (StringUtils.isNotBlank(foundDateString)) { - cache.setVisitedDate(Login.parseGcCustomDate(foundDateString).getTime()); + cache.setVisitedDate(GCLogin.parseGcCustomDate(foundDateString).getTime()); } } catch (final ParseException e) { // failed to parse cache found date @@ -775,7 +772,7 @@ public abstract class GCParser { return search; } - if (Login.isEmpty(viewstates)) { + if (GCLogin.isEmpty(viewstates)) { Log.e("GCParser.searchByNextPage: No viewstate given"); return search; } @@ -786,10 +783,10 @@ public abstract class GCParser { final Parameters params = new Parameters( "__EVENTTARGET", "ctl00$ContentBody$pgrBottom$ctl08", "__EVENTARGUMENT", ""); - Login.putViewstates(params, viewstates); + GCLogin.putViewstates(params, viewstates); - final String page = Login.postRequestLogged(uri, params); - if (!Login.getLoginStatus(page)) { + final String page = GCLogin.getInstance().postRequestLogged(uri, params); + if (!GCLogin.getInstance().getLoginStatus(page)) { Log.e("GCParser.postLogTrackable: Can not log in geocaching"); return search; } @@ -853,7 +850,7 @@ public abstract class GCParser { final String uri = "http://www.geocaching.com/seek/nearest.aspx"; final String fullUri = uri + "?" + addFToParams(params, my, true); - final String page = Login.getRequestLogged(uri, addFToParams(params, my, true)); + final String page = GCLogin.getInstance().getRequestLogged(uri, addFToParams(params, my, true)); if (StringUtils.isBlank(page)) { Log.e("GCParser.searchByAny: No data from server"); @@ -869,17 +866,17 @@ public abstract class GCParser { final SearchResult search = searchResult.filterSearchResults(Settings.isExcludeDisabledCaches(), false, cacheType); - Login.getLoginStatus(page); + GCLogin.getInstance().getLoginStatus(page); return search; } - public static SearchResult searchByCoords(final Geopoint coords, final CacheType cacheType, final boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) { + public static SearchResult searchByCoords(final @NonNull Geopoint coords, final CacheType cacheType, final boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) { final Parameters params = new Parameters("lat", Double.toString(coords.getLatitude()), "lng", Double.toString(coords.getLongitude())); return searchByAny(cacheType, false, showCaptcha, params, recaptchaReceiver); } - public static SearchResult searchByKeyword(final String keyword, final CacheType cacheType, final boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) { + public static SearchResult searchByKeyword(final @NonNull String keyword, final CacheType cacheType, final boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) { if (StringUtils.isBlank(keyword)) { Log.e("GCParser.searchByKeyword: No keyword given"); return null; @@ -890,7 +887,7 @@ public abstract class GCParser { } private static boolean isSearchForMyCaches(final String userName) { - if (userName.equalsIgnoreCase(Settings.getGcLogin().left)) { + if (userName.equalsIgnoreCase(Settings.getGcCredentials().left)) { Log.i("Overriding users choice because of self search, downloading all caches."); return true; } @@ -976,7 +973,7 @@ public abstract class GCParser { params.put("id", id); } - final String page = Login.getRequestLogged("http://www.geocaching.com/track/details.aspx", params); + final String page = GCLogin.getInstance().getRequestLogged("http://www.geocaching.com/track/details.aspx", params); if (StringUtils.isBlank(page)) { Log.e("GCParser.searchTrackable: No data from server"); @@ -997,7 +994,7 @@ public abstract class GCParser { final Parameters params = new Parameters(); - final String page = Login.getRequestLogged("http://www.geocaching.com/pocket/default.aspx", params); + final String page = GCLogin.getInstance().getRequestLogged("http://www.geocaching.com/pocket/default.aspx", params); if (StringUtils.isBlank(page)) { Log.e("GCParser.searchPocketQueryList: No data from server"); @@ -1043,7 +1040,7 @@ public abstract class GCParser { public static ImmutablePair<StatusCode, String> postLog(final String geocode, final String cacheid, final String[] viewstates, final LogType logType, final int year, final int month, final int day, final String log, final List<TrackableLog> trackables) { - if (Login.isEmpty(viewstates)) { + if (GCLogin.isEmpty(viewstates)) { Log.e("GCParser.postLog: No viewstate given"); return new ImmutablePair<StatusCode, String>(StatusCode.LOG_POST_ERROR, ""); } @@ -1064,7 +1061,7 @@ public abstract class GCParser { "__EVENTARGUMENT", "", "__LASTFOCUS", "", "ctl00$ContentBody$LogBookPanel1$ddLogType", Integer.toString(logType.id), - "ctl00$ContentBody$LogBookPanel1$uxDateVisited", Login.getCustomGcDateFormat().format(new GregorianCalendar(year, month - 1, day).getTime()), + "ctl00$ContentBody$LogBookPanel1$uxDateVisited", GCLogin.getCustomGcDateFormat().format(new GregorianCalendar(year, month - 1, day).getTime()), "ctl00$ContentBody$LogBookPanel1$uxDateVisited$Month", Integer.toString(month), "ctl00$ContentBody$LogBookPanel1$uxDateVisited$Day", Integer.toString(day), "ctl00$ContentBody$LogBookPanel1$uxDateVisited$Year", Integer.toString(year), @@ -1077,7 +1074,7 @@ public abstract class GCParser { "ctl00$ContentBody$LogBookPanel1$btnSubmitLog", "Submit Log Entry", "ctl00$ContentBody$LogBookPanel1$uxLogCreationSource", "Old", "ctl00$ContentBody$uxVistOtherListingGC", ""); - Login.putViewstates(params, viewstates); + GCLogin.putViewstates(params, viewstates); if (trackables != null && !trackables.isEmpty()) { // we have some trackables to proceed final StringBuilder hdnSelected = new StringBuilder(); @@ -1094,8 +1091,8 @@ public abstract class GCParser { } final String uri = new Uri.Builder().scheme("http").authority("www.geocaching.com").path("/seek/log.aspx").encodedQuery("ID=" + cacheid).build().toString(); - String page = Login.postRequestLogged(uri, params); - if (!Login.getLoginStatus(page)) { + String page = GCLogin.getInstance().postRequestLogged(uri, params); + if (!GCLogin.getInstance().getLoginStatus(page)) { Log.e("GCParser.postLog: Cannot log in geocaching"); return new ImmutablePair<StatusCode, String>(StatusCode.NOT_LOGGED_IN, ""); } @@ -1106,15 +1103,15 @@ public abstract class GCParser { try { if (matcher.find() && matcher.groupCount() > 0) { - final String[] viewstatesConfirm = Login.getViewstates(page); + final String[] viewstatesConfirm = GCLogin.getViewstates(page); - if (Login.isEmpty(viewstatesConfirm)) { + if (GCLogin.isEmpty(viewstatesConfirm)) { Log.e("GCParser.postLog: No viewstate for confirm log"); return new ImmutablePair<StatusCode, String>(StatusCode.LOG_POST_ERROR, ""); } params.clear(); - Login.putViewstates(params, viewstatesConfirm); + GCLogin.putViewstates(params, viewstatesConfirm); params.put("__EVENTTARGET", ""); params.put("__EVENTARGUMENT", ""); params.put("__LASTFOCUS", ""); @@ -1159,10 +1156,10 @@ public abstract class GCParser { DataStore.saveVisitDate(geocode); } - Login.getLoginStatus(page); + GCLogin.getInstance().getLoginStatus(page); // the log-successful-page contains still the old value - if (Login.getActualCachesFound() >= 0) { - Login.setActualCachesFound(Login.getActualCachesFound() + 1); + if (GCLogin.getInstance().getActualCachesFound() >= 0) { + GCLogin.getInstance().setActualCachesFound(GCLogin.getInstance().getActualCachesFound() + 1); } final String logID = TextUtils.getMatch(page, GCConstants.PATTERN_LOG_IMAGE_UPLOAD, ""); @@ -1193,14 +1190,14 @@ public abstract class GCParser { public static ImmutablePair<StatusCode, String> uploadLogImage(final String logId, final String caption, final String description, final Uri imageUri) { final String uri = new Uri.Builder().scheme("http").authority("www.geocaching.com").path("/seek/upload.aspx").encodedQuery("LID=" + logId).build().toString(); - final String page = Login.getRequestLogged(uri, null); + final String page = GCLogin.getInstance().getRequestLogged(uri, null); if (StringUtils.isBlank(page)) { Log.e("GCParser.uploadLogImage: No data from server"); return new ImmutablePair<StatusCode, String>(StatusCode.UNKNOWN_ERROR, null); } assert page != null; - final String[] viewstates = Login.getViewstates(page); + final String[] viewstates = GCLogin.getViewstates(page); final Parameters uploadParams = new Parameters( "__EVENTTARGET", "", @@ -1208,7 +1205,7 @@ public abstract class GCParser { "ctl00$ContentBody$ImageUploadControl1$uxFileCaption", caption, "ctl00$ContentBody$ImageUploadControl1$uxFileDesc", description, "ctl00$ContentBody$ImageUploadControl1$uxUpload", "Upload"); - Login.putViewstates(uploadParams, viewstates); + GCLogin.putViewstates(uploadParams, viewstates); final File image = new File(imageUri.getPath()); final String response = Network.getResponseData(Network.postRequest(uri, uploadParams, "ctl00$ContentBody$ImageUploadControl1$uxFileUpload", "image/jpeg", image)); @@ -1232,7 +1229,7 @@ public abstract class GCParser { */ public static StatusCode postLogTrackable(final String tbid, final String trackingCode, final String[] viewstates, final LogType logType, final int year, final int month, final int day, final String log) { - if (Login.isEmpty(viewstates)) { + if (GCLogin.isEmpty(viewstates)) { Log.e("GCParser.postLogTrackable: No viewstate given"); return StatusCode.LOG_POST_ERROR; } @@ -1253,13 +1250,13 @@ public abstract class GCParser { "__LASTFOCUS", "", "ctl00$ContentBody$LogBookPanel1$ddLogType", Integer.toString(logType.id), "ctl00$ContentBody$LogBookPanel1$tbCode", trackingCode); - Login.putViewstates(params, viewstates); + GCLogin.putViewstates(params, viewstates); if (currentDate.get(Calendar.YEAR) == year && (currentDate.get(Calendar.MONTH) + 1) == month && currentDate.get(Calendar.DATE) == day) { params.put("ctl00$ContentBody$LogBookPanel1$DateTimeLogged", ""); params.put("ctl00$ContentBody$LogBookPanel1$uxDateVisited", ""); } else { params.put("ctl00$ContentBody$LogBookPanel1$DateTimeLogged", Integer.toString(month) + "/" + Integer.toString(day) + "/" + Integer.toString(year)); - params.put("ctl00$ContentBody$LogBookPanel1$uxDateVisited", Login.getCustomGcDateFormat().format(new GregorianCalendar(year, month - 1, day).getTime())); + params.put("ctl00$ContentBody$LogBookPanel1$uxDateVisited", GCLogin.getCustomGcDateFormat().format(new GregorianCalendar(year, month - 1, day).getTime())); } params.put( "ctl00$ContentBody$LogBookPanel1$DateTimeLogged$Day", Integer.toString(day), @@ -1275,8 +1272,8 @@ public abstract class GCParser { "ctl00$ContentBody$uxVistOtherListingGC", ""); final String uri = new Uri.Builder().scheme("http").authority("www.geocaching.com").path("/track/log.aspx").encodedQuery("wid=" + tbid).build().toString(); - final String page = Login.postRequestLogged(uri, params); - if (!Login.getLoginStatus(page)) { + final String page = GCLogin.getInstance().postRequestLogged(uri, params); + if (!GCLogin.getInstance().getLoginStatus(page)) { Log.e("GCParser.postLogTrackable: Cannot log in geocaching"); return StatusCode.NOT_LOGGED_IN; } @@ -1305,7 +1302,7 @@ public abstract class GCParser { */ static boolean addToWatchlist(final Geocache cache) { final String uri = "http://www.geocaching.com/my/watchlist.aspx?w=" + cache.getCacheId(); - final String page = Login.postRequestLogged(uri, null); + final String page = GCLogin.getInstance().postRequestLogged(uri, null); if (StringUtils.isBlank(page)) { Log.e("GCParser.addToWatchlist: No data from server"); @@ -1331,7 +1328,7 @@ public abstract class GCParser { */ static boolean removeFromWatchlist(final Geocache cache) { final String uri = "http://www.geocaching.com/my/watchlist.aspx?ds=1&action=rem&id=" + cache.getCacheId(); - String page = Login.postRequestLogged(uri, null); + String page = GCLogin.getInstance().postRequestLogged(uri, null); if (StringUtils.isBlank(page)) { Log.e("GCParser.removeFromWatchlist: No data from server"); @@ -1343,7 +1340,7 @@ public abstract class GCParser { "__EVENTTARGET", "", "__EVENTARGUMENT", "", "ctl00$ContentBody$btnYes", "Yes"); - Login.transferViewstates(page, params); + GCLogin.transferViewstates(page, params); page = Network.getResponseData(Network.postRequest(uri, params)); final boolean guidOnPage = cache.isGuidContainedInPage(page); @@ -1367,7 +1364,7 @@ public abstract class GCParser { params.put("log", log); params.put("numlogs", numlogs); - return Login.getRequestLogged("http://www.geocaching.com/seek/cache_details.aspx", params); + return GCLogin.getInstance().getRequestLogged("http://www.geocaching.com/seek/cache_details.aspx", params); } /** @@ -1563,7 +1560,7 @@ public abstract class GCParser { while (matcherLogs.find()) { long date = 0; try { - date = Login.parseGcCustomDate(matcherLogs.group(2)).getTime(); + date = GCLogin.parseGcCustomDate(matcherLogs.group(2)).getTime(); } catch (final ParseException e) { } @@ -1694,7 +1691,7 @@ public abstract class GCParser { long date = 0; try { - date = Login.parseGcCustomDate(entry.getString("Visited")).getTime(); + date = GCLogin.parseGcCustomDate(entry.getString("Visited")).getTime(); } catch (final ParseException e) { Log.e("GCParser.loadLogsFromDetails: failed to parse log date."); } diff --git a/main/src/cgeo/geocaching/connector/gc/MapTokens.java b/main/src/cgeo/geocaching/connector/gc/MapTokens.java new file mode 100644 index 0000000..78ce4cb --- /dev/null +++ b/main/src/cgeo/geocaching/connector/gc/MapTokens.java @@ -0,0 +1,23 @@ +package cgeo.geocaching.connector.gc; + +import android.util.Pair; + +/** + * Wrapper type to make map tokens more type safe than with a String array. + * + */ +public final class MapTokens extends Pair<String, String> { + + public MapTokens(String userSession, String sessionToken) { + super(userSession, sessionToken); + } + + public String getUserSession() { + return first; + } + + public String getSessionToken() { + return second; + } + +} diff --git a/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java b/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java index d43d06b..46e4c96 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java +++ b/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java @@ -8,6 +8,8 @@ import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.CryptUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; public class OCApiConnector extends OCConnector implements ISearchByGeocode { @@ -41,13 +43,13 @@ public class OCApiConnector extends OCConnector implements ISearchByGeocode { } @Override - public String getLicenseText(final Geocache cache) { + public String getLicenseText(final @NonNull Geocache cache) { // NOT TO BE TRANSLATED return "© " + cache.getOwnerDisplayName() + ", <a href=\"" + getCacheUrl(cache) + "\">" + getName() + "</a>, " + licenseString; } @Override - public SearchResult searchByGeocode(final String geocode, final String guid, final CancellableHandler handler) { + public SearchResult searchByGeocode(final @Nullable String geocode, final @Nullable String guid, final CancellableHandler handler) { final Geocache cache = OkapiClient.getCache(geocode); if (cache == null) { return null; @@ -56,7 +58,7 @@ public class OCApiConnector extends OCConnector implements ISearchByGeocode { } @Override - public boolean isActivated() { + public boolean isActive() { // currently always active, but only for details download return true; } diff --git a/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java b/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java index b1b9088..b45f809 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java +++ b/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java @@ -10,13 +10,16 @@ import cgeo.geocaching.connector.capability.ILogin; import cgeo.geocaching.connector.capability.ISearchByCenter; import cgeo.geocaching.connector.capability.ISearchByKeyword; import cgeo.geocaching.connector.capability.ISearchByViewPort; +import cgeo.geocaching.connector.gc.MapTokens; import cgeo.geocaching.connector.oc.UserInfo.UserInfoStatus; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Viewport; +import cgeo.geocaching.loaders.RecaptchaReceiver; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.CryptUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; import android.content.Context; import android.os.Handler; @@ -39,17 +42,17 @@ public class OCApiLiveConnector extends OCApiConnector implements ISearchByCente } @Override - public boolean isActivated() { + public boolean isActive() { return Settings.isOCConnectorActive(isActivePrefKeyId); } @Override - public SearchResult searchByViewport(Viewport viewport, String[] tokens) { + public SearchResult searchByViewport(@NonNull Viewport viewport, MapTokens tokens) { return new SearchResult(OkapiClient.getCachesBBox(viewport, this)); } @Override - public SearchResult searchByCenter(Geopoint center) { + public SearchResult searchByCenter(@NonNull Geopoint center, final @NonNull RecaptchaReceiver recaptchaReceiver) { return new SearchResult(OkapiClient.getCachesAround(center, this)); } @@ -155,7 +158,7 @@ public class OCApiLiveConnector extends OCApiConnector implements ISearchByCente } @Override - public SearchResult searchByName(final String name) { + public SearchResult searchByKeyword(final @NonNull String name, final @NonNull RecaptchaReceiver recaptchaReceiver) { final Geopoint currentPos = CgeoApplication.getInstance().currentGeo().getCoords(); return new SearchResult(OkapiClient.getCachesNamed(currentPos, name, this)); } diff --git a/main/src/cgeo/geocaching/connector/oc/OCConnector.java b/main/src/cgeo/geocaching/connector/oc/OCConnector.java index b5c62ea..1ba88d5 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCConnector.java +++ b/main/src/cgeo/geocaching/connector/oc/OCConnector.java @@ -6,6 +6,8 @@ import cgeo.geocaching.R; import cgeo.geocaching.connector.AbstractConnector; import cgeo.geocaching.enumerations.LogType; +import org.eclipse.jdt.annotation.NonNull; + import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; @@ -27,10 +29,7 @@ public class OCConnector extends AbstractConnector { } @Override - public boolean canHandle(String geocode) { - if (geocode == null) { - return false; - } + public boolean canHandle(@NonNull String geocode) { return codePattern.matcher(geocode).matches(); } @@ -40,7 +39,7 @@ public class OCConnector extends AbstractConnector { } @Override - public String getCacheUrl(Geocache cache) { + public String getCacheUrl(@NonNull Geocache cache) { return getCacheUrlPrefix() + cache.getGeocode(); } diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiLoggingManager.java b/main/src/cgeo/geocaching/connector/oc/OkapiLoggingManager.java index c6be3cb..6836e6f 100644 --- a/main/src/cgeo/geocaching/connector/oc/OkapiLoggingManager.java +++ b/main/src/cgeo/geocaching/connector/oc/OkapiLoggingManager.java @@ -3,7 +3,7 @@ package cgeo.geocaching.connector.oc; import cgeo.geocaching.Geocache; import cgeo.geocaching.LogCacheActivity; import cgeo.geocaching.TrackableLog; -import cgeo.geocaching.connector.ILoggingManager; +import cgeo.geocaching.connector.AbstractLoggingManager; import cgeo.geocaching.connector.ImageResult; import cgeo.geocaching.connector.LogResult; import cgeo.geocaching.enumerations.LogType; @@ -12,10 +12,9 @@ import cgeo.geocaching.enumerations.StatusCode; import android.net.Uri; import java.util.Calendar; -import java.util.Collections; import java.util.List; -public class OkapiLoggingManager implements ILoggingManager { +public class OkapiLoggingManager extends AbstractLoggingManager { private final OCApiLiveConnector connector; private final Geocache cache; @@ -45,16 +44,6 @@ public class OkapiLoggingManager implements ILoggingManager { } @Override - public final boolean hasLoaderError() { - return false; - } - - @Override - public final List<TrackableLog> getTrackables() { - return Collections.emptyList(); - } - - @Override public List<LogType> getPossibleLogTypes() { return connector.getPossibleLogTypes(cache); } diff --git a/main/src/cgeo/geocaching/connector/ox/OXConnector.java b/main/src/cgeo/geocaching/connector/ox/OXConnector.java index af33bb6..7d4cf7f 100644 --- a/main/src/cgeo/geocaching/connector/ox/OXConnector.java +++ b/main/src/cgeo/geocaching/connector/ox/OXConnector.java @@ -3,13 +3,21 @@ package cgeo.geocaching.connector.ox; import cgeo.geocaching.Geocache; import cgeo.geocaching.ICache; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.settings.Settings; import cgeo.geocaching.connector.AbstractConnector; import cgeo.geocaching.connector.capability.ISearchByCenter; import cgeo.geocaching.connector.capability.ISearchByGeocode; +import cgeo.geocaching.connector.capability.ISearchByKeyword; +import cgeo.geocaching.connector.capability.ISearchByViewPort; +import cgeo.geocaching.connector.gc.MapTokens; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.Viewport; +import cgeo.geocaching.loaders.RecaptchaReceiver; +import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.CancellableHandler; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + import java.util.Collection; import java.util.regex.Pattern; @@ -17,17 +25,17 @@ import java.util.regex.Pattern; * connector for OpenCaching.com * */ -public class OXConnector extends AbstractConnector implements ISearchByCenter, ISearchByGeocode { +public class OXConnector extends AbstractConnector implements ISearchByCenter, ISearchByGeocode, ISearchByViewPort, ISearchByKeyword { private static final Pattern PATTERN_GEOCODE = Pattern.compile("OX[A-Z0-9]+", Pattern.CASE_INSENSITIVE); @Override - public boolean canHandle(String geocode) { + public boolean canHandle(@NonNull String geocode) { return PATTERN_GEOCODE.matcher(geocode).matches(); } @Override - public String getCacheUrl(Geocache cache) { + public String getCacheUrl(@NonNull Geocache cache) { return getCacheUrlPrefix() + cache.getGeocode(); } @@ -42,9 +50,9 @@ public class OXConnector extends AbstractConnector implements ISearchByCenter, I } @Override - public String getLicenseText(Geocache cache) { + public String getLicenseText(@NonNull Geocache cache) { // NOT TO BE TRANSLATED - return "<a href=\"" + getCacheUrl(cache) + "\">" + getName() + "</a> data licensed under the Creative Commons BY-SA 3.0 License"; + return "<a href=\"" + getCacheUrl(cache) + "\">" + getName() + "</a> data licensed under the Creative Commons CC-BY-SA 3.0 License"; } @Override @@ -53,7 +61,10 @@ public class OXConnector extends AbstractConnector implements ISearchByCenter, I } @Override - public SearchResult searchByGeocode(String geocode, String guid, CancellableHandler handler) { + public SearchResult searchByGeocode(final @Nullable String geocode, final @Nullable String guid, final CancellableHandler handler) { + if (geocode == null) { + return null; + } final Geocache cache = OpenCachingApi.searchByGeoCode(geocode); if (cache == null) { return null; @@ -63,16 +74,34 @@ public class OXConnector extends AbstractConnector implements ISearchByCenter, I } @Override - public SearchResult searchByCenter(Geopoint center) { - Collection<Geocache> caches = OpenCachingApi.searchByCenter(center); - if (caches == null) { - return null; - } - return new SearchResult(caches); + public SearchResult searchByCenter(@NonNull Geopoint center, final @NonNull RecaptchaReceiver recaptchaReceiver) { + return createSearchResult(OpenCachingApi.searchByCenter(center)); } @Override protected String getCacheUrlPrefix() { return "http://www.opencaching.com/#!geocache/"; } + + @Override + public SearchResult searchByViewport(@NonNull Viewport viewport, final MapTokens tokens) { + return createSearchResult(OpenCachingApi.searchByBoundingBox(viewport)); + } + + @Override + public boolean isActive() { + return Settings.isOXConnectorActive(); + } + + @Override + public SearchResult searchByKeyword(final @NonNull String name, final @NonNull RecaptchaReceiver recaptchaReceiver) { + return createSearchResult(OpenCachingApi.searchByKeyword(name)); + } + + private static SearchResult createSearchResult(Collection<Geocache> caches) { + if (caches == null) { + return null; + } + return new SearchResult(caches); + } } diff --git a/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java b/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java index f40a3e8..f72b698 100644 --- a/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java +++ b/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java @@ -3,6 +3,9 @@ package cgeo.geocaching.connector.ox; import cgeo.geocaching.Geocache; import cgeo.geocaching.files.GPX10Parser; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; + public class OXGPXParser extends GPX10Parser { private final boolean isDetailed; @@ -19,5 +22,16 @@ public class OXGPXParser extends GPX10Parser { cache.setDetailedUpdate(cache.getUpdated()); cache.setDetailed(true); } + removeTitleFromShortDescription(cache); + } + + /** + * The short description of OX caches contains "title by owner, type(T/D/Awesomeness)". That is a lot of + * duplication. + * + * @param cache + */ + private static void removeTitleFromShortDescription(final @NonNull Geocache cache) { + cache.setShortDescription(StringUtils.substringAfterLast(cache.getShortDescription(), ",")); } } diff --git a/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java b/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java index 9d1dfc7..2defc52 100644 --- a/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java +++ b/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java @@ -3,6 +3,7 @@ package cgeo.geocaching.connector.ox; import cgeo.geocaching.Geocache; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter; +import cgeo.geocaching.geopoint.Viewport; import cgeo.geocaching.list.StoredList; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; @@ -12,19 +13,20 @@ import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; import org.apache.commons.collections4.CollectionUtils; +import org.eclipse.jdt.annotation.NonNull; import java.util.Collection; import java.util.Collections; public class OpenCachingApi { + private static final String API_URL_CACHES_GPX = "http://www.opencaching.com/api/geocache.gpx"; private static final String DEV_KEY = CryptUtils.rot13("PtqQnHo9RUTht3Np"); - public static Geocache searchByGeoCode(final String geocode) { - final HttpResponse response = Network.getRequest("http://www.opencaching.com/api/geocache/" + geocode + ".gpx", + public static Geocache searchByGeoCode(final @NonNull String geocode) { + final HttpResponse response = getRequest("http://www.opencaching.com/api/geocache/" + geocode + ".gpx", new Parameters( - "Authorization", DEV_KEY, - "log_limit", "30", + "log_limit", "50", "hint", "true", "description", "html")); final Collection<Geocache> caches = importCachesFromResponse(response, true); @@ -34,13 +36,18 @@ public class OpenCachingApi { return null; } + private static HttpResponse getRequest(String string, Parameters parameters) { + parameters.add("Authorization", DEV_KEY); + return Network.getRequest(string, parameters); + } + private static Collection<Geocache> importCachesFromResponse(final HttpResponse response, final boolean isDetailed) { if (response == null) { return Collections.emptyList(); } Collection<Geocache> caches; try { - caches = new OXGPXParser(StoredList.STANDARD_LIST_ID, isDetailed).parse(response.getEntity().getContent(), null); + caches = new OXGPXParser(StoredList.TEMPORARY_LIST_ID, isDetailed).parse(response.getEntity().getContent(), null); } catch (Exception e) { Log.e("Error importing from OpenCaching.com", e); return Collections.emptyList(); @@ -48,17 +55,39 @@ public class OpenCachingApi { return caches; } - public static Collection<Geocache> searchByCenter(final Geopoint center) { - final HttpResponse response = Network.getRequest("http://www.opencaching.com/api/geocache/.gpx", + public static Collection<Geocache> searchByCenter(final @NonNull Geopoint center) { + final HttpResponse response = getRequest(API_URL_CACHES_GPX, new Parameters( - "Authorization", DEV_KEY, "log_limit", "0", "hint", "false", "description", "none", - "limit", "10", + "limit", "20", "center", center.format(GeopointFormatter.Format.LAT_LON_DECDEGREE_COMMA))); return importCachesFromResponse(response, false); } + public static Collection<Geocache> searchByBoundingBox(final @NonNull Viewport viewport) { + final String bbox = viewport.bottomLeft.format(GeopointFormatter.Format.LAT_LON_DECDEGREE_COMMA) + "," + viewport.topRight.format(GeopointFormatter.Format.LAT_LON_DECDEGREE_COMMA); + final HttpResponse response = getRequest(API_URL_CACHES_GPX, + new Parameters( + "log_limit", "0", + "hint", "false", + "description", "none", + "limit", "100", + "bbox", bbox)); + return importCachesFromResponse(response, false); + } + + public static Collection<Geocache> searchByKeyword(final @NonNull String name) { + final HttpResponse response = getRequest(API_URL_CACHES_GPX, + new Parameters( + "log_limit", "5", + "hint", "false", + "description", "none", + "limit", "100", + "name", name)); + return importCachesFromResponse(response, false); + } + } diff --git a/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java b/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java index 67180b3..9cb5dc5 100644 --- a/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java +++ b/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java @@ -37,15 +37,15 @@ public class GeokretyParser { try { final int indexId = attributes.getIndex("id"); if (indexId > -1) { - trackable.setGeocode(geocode(Integer.valueOf(attributes.getValue("id")))); + trackable.setGeocode(geocode(Integer.parseInt(attributes.getValue("id")))); } final int indexDist = attributes.getIndex("dist"); if (indexDist > -1) { - trackable.setDistance(Float.valueOf(attributes.getValue("dist"))); + trackable.setDistance(Float.parseFloat(attributes.getValue("dist"))); } final int indexType = attributes.getIndex("type"); if (indexType > -1) { - trackable.setType(getType(Integer.valueOf(attributes.getValue("type")))); + trackable.setType(getType(Integer.parseInt(attributes.getValue("type")))); } final int indexWaypoint = attributes.getIndex("waypoint"); if (indexWaypoint > -1) { diff --git a/main/src/cgeo/geocaching/enumerations/CacheAttribute.java b/main/src/cgeo/geocaching/enumerations/CacheAttribute.java index 31fc023..4ace4a8 100644 --- a/main/src/cgeo/geocaching/enumerations/CacheAttribute.java +++ b/main/src/cgeo/geocaching/enumerations/CacheAttribute.java @@ -79,7 +79,7 @@ public enum CacheAttribute { STROLLER(41, -1, "stroller", R.drawable.attribute_stroller, R.string.attribute_stroller_yes, R.string.attribute_stroller_no), FUEL(58, -1, "fuel", R.drawable.attribute_fuel, R.string.attribute_fuel_yes, R.string.attribute_fuel_no), FOOD(59, -1, "food", R.drawable.attribute_food, R.string.attribute_food_yes, R.string.attribute_food_no), - OC_ONLY(-1, -1, "oc_only", R.drawable.attribute_oc_only, R.string.attribute_oc_only_yes, R.string.attribute_oc_only_no), + OC_ONLY(-1, 1, "oc_only", R.drawable.attribute_oc_only, R.string.attribute_oc_only_yes, R.string.attribute_oc_only_no), LINK_ONLY(-1, -1, "link_only", R.drawable.attribute_link_only, R.string.attribute_link_only_yes, R.string.attribute_link_only_no), LETTERBOX(-1, 4, "letterbox", R.drawable.attribute_letterbox, R.string.attribute_letterbox_yes, R.string.attribute_letterbox_no), RAILWAY(-1, 60, "railway", R.drawable.attribute_railway, R.string.attribute_railway_yes, R.string.attribute_railway_no), @@ -109,7 +109,11 @@ public enum CacheAttribute { OTHER_CACHE(-1, 13, "other_cache", R.drawable.attribute_other_cache, R.string.attribute_other_cache_yes, R.string.attribute_other_cache_no), ASK_OWNER(-1, 17, "ask_owner", R.drawable.attribute_ask_owner, R.string.attribute_ask_owner_yes, R.string.attribute_ask_owner_no), UNKNOWN(-1, -1, "unknown", R.drawable.attribute_unknown, R.string.attribute_unknown_yes, R.string.attribute_unknown_no), - GEOTOUR(67, -1, "geotour", R.drawable.attribute_geotour, R.string.attribute_geotour_yes, R.string.attribute_geotour_no); + GEOTOUR(67, -1, "geotour", R.drawable.attribute_geotour, R.string.attribute_geotour_yes, R.string.attribute_geotour_no), + KIDS_2(-1, 70, "kids_2", R.drawable.attribute_kids_2, R.string.attribute_kids_2_yes, R.string.attribute_kids_2_no), + HISTORIC_SITE(-1, 29, "historic_site", R.drawable.attribute_historic_site, R.string.attribute_historic_site_yes, R.string.attribute_historic_site_no), + MAGNETIC(-1, 6, "magnetic", R.drawable.attribute_magnetic, R.string.attribute_magnetic_yes, R.string.attribute_magnetic_no), + USB_CACHE(-1, 10, "usb_cache", R.drawable.attribute_usb_cache, R.string.attribute_usb_cache_yes, R.string.attribute_usb_cache_no); // THIS LIST IS GENERATED: don't change anything here but read // project/attributes/readme.txt diff --git a/main/src/cgeo/geocaching/enumerations/CacheSize.java b/main/src/cgeo/geocaching/enumerations/CacheSize.java index a6f8df3..ee42c66 100644 --- a/main/src/cgeo/geocaching/enumerations/CacheSize.java +++ b/main/src/cgeo/geocaching/enumerations/CacheSize.java @@ -1,7 +1,7 @@ package cgeo.geocaching.enumerations; -import cgeo.geocaching.R; import cgeo.geocaching.CgeoApplication; +import cgeo.geocaching.R; import java.util.Collections; import java.util.HashMap; @@ -56,6 +56,28 @@ public enum CacheSize { if (resultNormalized != null) { return resultNormalized; } + return getByNumber(id); + } + + /** + * Bad GPX files can contain the container size encoded as number. + * + * @param id + * @return + */ + private static CacheSize getByNumber(final String id) { + try { + int numerical = Integer.parseInt(id); + if (numerical != 0) { + for (CacheSize size : CacheSize.values()) { + if (size.comparable == numerical) { + return size; + } + } + } + } catch (NumberFormatException e) { + // ignore, as this might be a number or not + } return UNKNOWN; } diff --git a/main/src/cgeo/geocaching/enumerations/StatusCode.java b/main/src/cgeo/geocaching/enumerations/StatusCode.java index 102b9e9..e1a1aaa 100644 --- a/main/src/cgeo/geocaching/enumerations/StatusCode.java +++ b/main/src/cgeo/geocaching/enumerations/StatusCode.java @@ -11,6 +11,7 @@ public enum StatusCode { LOG_SAVED(R.string.info_log_saved), LOGIN_PARSE_ERROR(R.string.err_parse), CONNECTION_FAILED(R.string.err_server), + CONNECTION_FAILED_EC(R.string.err_server_ec), NO_LOGIN_INFO_STORED(R.string.err_login), UNKNOWN_ERROR(R.string.err_unknown), COMMUNICATION_ERROR(R.string.err_comm), @@ -21,6 +22,7 @@ public enum StatusCode { PREMIUM_ONLY(R.string.err_premium_only), MAINTENANCE(R.string.err_maintenance), LOG_POST_ERROR(R.string.err_log_post_failed), + LOG_POST_ERROR_EC(R.string.err_log_post_failed_ec), NO_LOG_TEXT(R.string.warn_log_text_fill), NO_DATA_FROM_SERVER(R.string.err_log_failed_server), NOT_LOGGED_IN(R.string.init_login_popup_failed), diff --git a/main/src/cgeo/geocaching/export/FieldnoteExport.java b/main/src/cgeo/geocaching/export/FieldnoteExport.java index 1ae97f3..4da480a 100644 --- a/main/src/cgeo/geocaching/export/FieldnoteExport.java +++ b/main/src/cgeo/geocaching/export/FieldnoteExport.java @@ -5,7 +5,7 @@ import cgeo.geocaching.Geocache; import cgeo.geocaching.LogEntry; import cgeo.geocaching.R; import cgeo.geocaching.activity.ActivityMixin; -import cgeo.geocaching.connector.gc.Login; +import cgeo.geocaching.connector.gc.GCLogin; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; @@ -175,30 +175,30 @@ class FieldnoteExport extends AbstractExport { if (upload) { publishProgress(STATUS_UPLOAD); - if (!Login.isActualLoginStatus()) { + if (!GCLogin.getInstance().isActualLoginStatus()) { // no need to upload (possibly large file) if we're not logged in - final StatusCode loginState = Login.login(); + final StatusCode loginState = GCLogin.getInstance().login(); if (loginState != StatusCode.NO_ERROR) { Log.e("FieldnoteExport.ExportTask upload: Login failed"); } } final String uri = "http://www.geocaching.com/my/uploadfieldnotes.aspx"; - final String page = Login.getRequestLogged(uri, null); + final String page = GCLogin.getInstance().getRequestLogged(uri, null); if (StringUtils.isBlank(page)) { Log.e("FieldnoteExport.ExportTask get page: No data from server"); return false; } - final String[] viewstates = Login.getViewstates(page); + final String[] viewstates = GCLogin.getViewstates(page); final Parameters uploadParams = new Parameters( "__EVENTTARGET", "", "__EVENTARGUMENT", "", "ctl00$ContentBody$btnUpload", "Upload Field Note"); - Login.putViewstates(uploadParams, viewstates); + GCLogin.putViewstates(uploadParams, viewstates); Network.getResponseData(Network.postRequest(uri, uploadParams, "ctl00$ContentBody$FieldNoteLoader", "text/plain", exportFile)); diff --git a/main/src/cgeo/geocaching/files/GPXImporter.java b/main/src/cgeo/geocaching/files/GPXImporter.java index bf0aa72..07ef285 100644 --- a/main/src/cgeo/geocaching/files/GPXImporter.java +++ b/main/src/cgeo/geocaching/files/GPXImporter.java @@ -1,14 +1,15 @@ package cgeo.geocaching.files; +import cgeo.geocaching.DataStore; import cgeo.geocaching.Geocache; import cgeo.geocaching.R; import cgeo.geocaching.SearchResult; import cgeo.geocaching.StaticMapsProvider; -import cgeo.geocaching.DataStore; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.activity.Progress; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.Log; @@ -417,19 +418,19 @@ public class GPXImporter { progressHandler.cancel(); final StringBuilder bufferSkipped = new StringBuilder(20); bufferSkipped.append(res.getString(R.string.gpx_import_static_maps_skipped)).append(", ").append(msg.arg1).append(' ').append(res.getString(R.string.gpx_import_caches_imported)); - ActivityMixin.helpDialog(fromActivity, res.getString(R.string.gpx_import_title_caches_imported), bufferSkipped.toString()); + Dialogs.message(fromActivity, R.string.gpx_import_title_caches_imported, bufferSkipped.toString()); importFinished(); break; case IMPORT_STEP_FINISHED: progress.dismiss(); - ActivityMixin.helpDialog(fromActivity, res.getString(R.string.gpx_import_title_caches_imported), msg.arg1 + " " + res.getString(R.string.gpx_import_caches_imported)); + Dialogs.message(fromActivity, R.string.gpx_import_title_caches_imported, msg.arg1 + " " + res.getString(R.string.gpx_import_caches_imported)); importFinished(); break; case IMPORT_STEP_FINISHED_WITH_ERROR: progress.dismiss(); - ActivityMixin.helpDialog(fromActivity, res.getString(R.string.gpx_import_title_caches_import_failed), res.getString(msg.arg1) + "\n\n" + msg.obj); + Dialogs.message(fromActivity, R.string.gpx_import_title_caches_import_failed, res.getString(msg.arg1) + "\n\n" + msg.obj); importFinished(); break; diff --git a/main/src/cgeo/geocaching/files/GPXParser.java b/main/src/cgeo/geocaching/files/GPXParser.java index 3e96291..d26a48c 100644 --- a/main/src/cgeo/geocaching/files/GPXParser.java +++ b/main/src/cgeo/geocaching/files/GPXParser.java @@ -104,6 +104,10 @@ public abstract class GPXParser extends FileParser { */ private final Set<String> result = new HashSet<String>(100); private ProgressInputStream progressStream; + /** + * URL contained in the header of the GPX file. Used to guess where the file is coming from. + */ + protected String scriptUrl; private final class UserDataListener implements EndTextElementListener { private final int index; @@ -265,6 +269,14 @@ public abstract class GPXParser extends FileParser { final RootElement root = new RootElement(namespace, "gpx"); final Element waypoint = root.getChild(namespace, "wpt"); + root.getChild(namespace, "url").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String body) { + scriptUrl = body; + } + }); + // waypoint - attributes waypoint.setStartElementListener(new StartElementListener() { @@ -338,7 +350,12 @@ public abstract class GPXParser extends FileParser { if (cache.getName().length() > 2 || StringUtils.isNotBlank(parentCacheCode)) { if (StringUtils.isBlank(parentCacheCode)) { - parentCacheCode = "GC" + cache.getName().substring(2).toUpperCase(Locale.US); + if (StringUtils.containsIgnoreCase(scriptUrl, "extremcaching")) { + parentCacheCode = cache.getName().substring(2); + } + else { + parentCacheCode = "GC" + cache.getName().substring(2).toUpperCase(Locale.US); + } } // lookup cache for waypoint in already parsed caches final Geocache cacheForWaypoint = DataStore.loadCache(parentCacheCode, LoadFlags.LOAD_CACHE_OR_DB); @@ -382,14 +399,19 @@ public abstract class GPXParser extends FileParser { } }); - // waypoint.getName() + // waypoint.name waypoint.getChild(namespace, "name").setEndTextElementListener(new EndTextElementListener() { @Override public void end(String body) { name = body; - final String content = body.trim(); + String content = body.trim(); + + // extremcaching.com manipulates the GC code by adding GC in front of ECxxx + if (StringUtils.startsWithIgnoreCase(content, "GCEC") && StringUtils.containsIgnoreCase(scriptUrl, "extremcaching")) { + content = content.substring(2); + } cache.setName(content); findGeoCode(cache.getName()); @@ -834,6 +856,9 @@ public abstract class GPXParser extends FileParser { } /** + * Overwrite this method in a GPX parser sub class to modify the {@link Geocache}, after it has been fully parsed + * from the GPX file and before it gets stored. + * * @param cache * currently imported cache */ diff --git a/main/src/cgeo/geocaching/files/SimpleDirChooser.java b/main/src/cgeo/geocaching/files/SimpleDirChooser.java index 3f6182c..3e09cc4 100644 --- a/main/src/cgeo/geocaching/files/SimpleDirChooser.java +++ b/main/src/cgeo/geocaching/files/SimpleDirChooser.java @@ -29,6 +29,7 @@ import java.io.File; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; /** @@ -141,7 +142,7 @@ public class SimpleDirChooser extends AbstractListActivity { } } catch (RuntimeException e) { } - Collections.sort(listDirs); + Collections.sort(listDirs, Option.NAME_COMPARATOR); if (dir.getParent() != null) { listDirs.add(0, new Option(PARENT_DIR, dir.getParent(), false)); } @@ -247,15 +248,21 @@ public class SimpleDirChooser extends AbstractListActivity { } } - /** - * Note: this class has a natural ordering that is inconsistent with equals. - */ - public static class Option implements Comparable<Option> { + public static class Option { private final String name; private final String path; private boolean checked = false; private boolean writeable = false; + private static Comparator<Option> NAME_COMPARATOR = new Comparator<SimpleDirChooser.Option>() { + + @Override + public int compare(Option lhs, Option rhs) { + return String.CASE_INSENSITIVE_ORDER.compare(lhs.name, rhs.name); + } + + }; + public Option(String name, String path, boolean writeable) { this.name = name; this.path = path; @@ -281,14 +288,6 @@ public class SimpleDirChooser extends AbstractListActivity { public boolean isWriteable() { return writeable; } - - @Override - public int compareTo(Option other) { - if (other != null && this.name != null) { - return String.CASE_INSENSITIVE_ORDER.compare(this.name, other.getName()); - } - throw new IllegalArgumentException(""); - } } public static class DirOnlyFilenameFilter implements FilenameFilter { diff --git a/main/src/cgeo/geocaching/filter/TerrainFilter.java b/main/src/cgeo/geocaching/filter/TerrainFilter.java index d74f954..f14313c 100644 --- a/main/src/cgeo/geocaching/filter/TerrainFilter.java +++ b/main/src/cgeo/geocaching/filter/TerrainFilter.java @@ -20,7 +20,7 @@ class TerrainFilter extends AbstractRangeFilter { public static class Factory implements IFilterFactory { private static final int TERRAIN_MIN = 1; - private static final int TERRAIN_MAX = 5; + private static final int TERRAIN_MAX = 7; @Override public List<IFilter> getFilters() { diff --git a/main/src/cgeo/geocaching/gcvote/GCVote.java b/main/src/cgeo/geocaching/gcvote/GCVote.java index b245aa9..fd4b914 100644 --- a/main/src/cgeo/geocaching/gcvote/GCVote.java +++ b/main/src/cgeo/geocaching/gcvote/GCVote.java @@ -11,8 +11,10 @@ import cgeo.geocaching.utils.MatcherWrapper; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; +import org.eclipse.jdt.annotation.NonNull; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -219,19 +221,12 @@ public final class GCVote { return result != null && result.trim().equalsIgnoreCase("ok"); } - public static void loadRatings(final ArrayList<Geocache> caches) { + public static void loadRatings(final @NonNull ArrayList<Geocache> caches) { if (!Settings.isRatingWanted()) { return; } - final ArrayList<String> geocodes = new ArrayList<String>(caches.size()); - for (final Geocache cache : caches) { - String geocode = cache.getGeocode(); - if (StringUtils.isNotBlank(geocode)) { - geocodes.add(geocode); - } - } - + final ArrayList<String> geocodes = getVotableGeocodes(caches); if (geocodes.isEmpty()) { return; } @@ -256,6 +251,24 @@ public final class GCVote { } } + /** + * Get geocodes of all the caches, which can be used with GCVote. Non-GC caches will be filtered out. + * + * @param caches + * @return + */ + private static @NonNull + ArrayList<String> getVotableGeocodes(final @NonNull Collection<Geocache> caches) { + final ArrayList<String> geocodes = new ArrayList<String>(caches.size()); + for (final Geocache cache : caches) { + String geocode = cache.getGeocode(); + if (StringUtils.isNotBlank(geocode) && cache.supportsGCVote()) { + geocodes.add(geocode); + } + } + return geocodes; + } + public static boolean isValidRating(final float rating) { return rating >= MIN_RATING && rating <= MAX_RATING; } diff --git a/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java b/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java index 1cc9706..0ba8932 100644 --- a/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java +++ b/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java @@ -68,11 +68,6 @@ public abstract class AbstractSearchLoader extends AsyncTaskLoader<SearchResult> } @Override - public boolean takeContentChanged() { - return super.takeContentChanged(); - } - - @Override protected void onStartLoading() { forceLoad(); } @@ -91,7 +86,9 @@ public abstract class AbstractSearchLoader extends AsyncTaskLoader<SearchResult> @Override public synchronized void waitForUser() { try { - wait(); + while (getText() == null) { + wait(); + } } catch (InterruptedException e) { Log.w("searchThread is not waiting for user…"); } diff --git a/main/src/cgeo/geocaching/loaders/CoordsGeocacheListLoader.java b/main/src/cgeo/geocaching/loaders/CoordsGeocacheListLoader.java index 34b3a61..3874b47 100644 --- a/main/src/cgeo/geocaching/loaders/CoordsGeocacheListLoader.java +++ b/main/src/cgeo/geocaching/loaders/CoordsGeocacheListLoader.java @@ -1,18 +1,18 @@ package cgeo.geocaching.loaders; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.settings.Settings; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.capability.ISearchByCenter; -import cgeo.geocaching.connector.gc.GCParser; import cgeo.geocaching.geopoint.Geopoint; +import org.eclipse.jdt.annotation.NonNull; + import android.content.Context; public class CoordsGeocacheListLoader extends AbstractSearchLoader { - private final Geopoint coords; + private final @NonNull Geopoint coords; - public CoordsGeocacheListLoader(Context context, Geopoint coords) { + public CoordsGeocacheListLoader(final Context context, final @NonNull Geopoint coords) { super(context); this.coords = coords; } @@ -21,13 +21,10 @@ public class CoordsGeocacheListLoader extends AbstractSearchLoader { public SearchResult runSearch() { SearchResult search = new SearchResult(); - if (Settings.isGCConnectorActive()) { - search = GCParser.searchByCoords(coords, Settings.getCacheType(), Settings.isShowCaptcha(), this); - } for (ISearchByCenter centerConn : ConnectorFactory.getSearchByCenterConnectors()) { - if (centerConn.isActivated()) { - search.addSearchResult(centerConn.searchByCenter(coords)); + if (centerConn.isActive()) { + search.addSearchResult(centerConn.searchByCenter(coords, this)); } } diff --git a/main/src/cgeo/geocaching/loaders/KeywordGeocacheListLoader.java b/main/src/cgeo/geocaching/loaders/KeywordGeocacheListLoader.java index c8132e7..9c16ee4 100644 --- a/main/src/cgeo/geocaching/loaders/KeywordGeocacheListLoader.java +++ b/main/src/cgeo/geocaching/loaders/KeywordGeocacheListLoader.java @@ -3,16 +3,16 @@ package cgeo.geocaching.loaders; import cgeo.geocaching.SearchResult; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.capability.ISearchByKeyword; -import cgeo.geocaching.connector.gc.GCParser; -import cgeo.geocaching.settings.Settings; + +import org.eclipse.jdt.annotation.NonNull; import android.content.Context; public class KeywordGeocacheListLoader extends AbstractSearchLoader { - private final String keyword; + private final @NonNull String keyword; - public KeywordGeocacheListLoader(Context context, String keyword) { + public KeywordGeocacheListLoader(Context context, final @NonNull String keyword) { super(context); this.keyword = keyword; } @@ -20,13 +20,10 @@ public class KeywordGeocacheListLoader extends AbstractSearchLoader { @Override public SearchResult runSearch() { SearchResult searchResult = new SearchResult(); - if (Settings.isGCConnectorActive()) { - searchResult = GCParser.searchByKeyword(keyword, Settings.getCacheType(), Settings.isShowCaptcha(), this); - } for (ISearchByKeyword connector : ConnectorFactory.getSearchByKeywordConnectors()) { - if (connector.isActivated()) { - searchResult.addSearchResult(connector.searchByName(keyword)); + if (connector.isActive()) { + searchResult.addSearchResult(connector.searchByKeyword(keyword, this)); } } diff --git a/main/src/cgeo/geocaching/maps/CGeoMap.java b/main/src/cgeo/geocaching/maps/CGeoMap.java index 28e1b71..927ff28 100644 --- a/main/src/cgeo/geocaching/maps/CGeoMap.java +++ b/main/src/cgeo/geocaching/maps/CGeoMap.java @@ -11,7 +11,8 @@ import cgeo.geocaching.SearchResult; import cgeo.geocaching.Waypoint; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.connector.ConnectorFactory; -import cgeo.geocaching.connector.gc.Login; +import cgeo.geocaching.connector.gc.GCLogin; +import cgeo.geocaching.connector.gc.MapTokens; import cgeo.geocaching.connector.gc.Tile; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LiveMapStrategy.Strategy; @@ -138,7 +139,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto // status data /** Last search result used for displaying header */ private SearchResult lastSearchResult = null; - private String[] tokens = null; + private MapTokens tokens = null; private boolean noMapTokenShowed = false; // map status data private boolean followMyLocation = false; @@ -153,8 +154,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private volatile boolean downloaded = false; // overlays private CachesOverlay overlayCaches = null; - private ScaleOverlay overlayScale = null; - private PositionOverlay overlayPosition = null; + private PositionAndScaleOverlay overlayPositionAndScale = null; // data for overlays private static final int[][] INSET_RELIABLE = { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } }; // center, 33x40 / 45x51 / 60x68 private static final int[][] INSET_TYPE = { { 5, 8, 6, 10 }, { 4, 4, 5, 11 }, { 4, 4, 5, 11 } }; // center, 22x22 / 36x36 @@ -347,8 +347,8 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto outState.putInt(BUNDLE_MAP_SOURCE, currentSourceId); outState.putIntArray(BUNDLE_MAP_STATE, currentMapState()); outState.putBoolean(BUNDLE_LIVE_ENABLED, isLiveEnabled); - if (overlayPosition != null) { - outState.putParcelableArrayList(BUNDLE_TRAIL_HISTORY, overlayPosition.getHistory()); + if (overlayPositionAndScale != null) { + outState.putParcelableArrayList(BUNDLE_TRAIL_HISTORY, overlayPositionAndScale.getHistory()); } } @@ -429,17 +429,13 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto overlayCaches = mapView.createAddMapOverlay(mapView.getContext(), getResources().getDrawable(R.drawable.marker)); } - if (overlayPosition == null) { - overlayPosition = mapView.createAddPositionOverlay(activity); + if (overlayPositionAndScale == null) { + overlayPositionAndScale = mapView.createAddPositionAndScaleOverlay(activity); if (trailHistory != null) { - overlayPosition.setHistory(trailHistory); + overlayPositionAndScale.setHistory(trailHistory); } } - if (overlayScale == null) { - overlayScale = mapView.createAddScaleOverlay(activity); - } - mapView.repaintRequired(null); mapView.getMapController().setZoom(Settings.getMapZoom()); @@ -517,13 +513,8 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto @Override public void onPause() { - if (loadTimer != null) { - loadTimer.stopIt(); - loadTimer = null; - } - + stopTimer(); deleteGeoDirObservers(); - savePrefs(); if (mapView != null) { @@ -622,7 +613,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto switch (id) { case R.id.menu_trail_mode: Settings.setMapTrail(!Settings.isMapTrail()); - mapView.repaintRequired(overlayPosition); + mapView.repaintRequired(overlayPositionAndScale); ActivityMixin.invalidateOptionsMenu(activity); return true; case R.id.menu_map_live: @@ -929,8 +920,8 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto try { if (mapView != null) { - if (overlayPosition == null) { - overlayPosition = mapView.createAddPositionOverlay(activity); + if (overlayPositionAndScale == null) { + overlayPositionAndScale = mapView.createAddPositionAndScaleOverlay(activity); } boolean needsRepaintForDistance = needsRepaintForDistance(); @@ -943,9 +934,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } if (needsRepaintForDistance || needsRepaintForHeading) { - overlayPosition.setCoordinates(currentLocation); - overlayPosition.setHeading(currentHeading); - mapView.repaintRequired(overlayPosition); + overlayPositionAndScale.setCoordinates(currentLocation); + overlayPositionAndScale.setHeading(currentHeading); + mapView.repaintRequired(overlayPositionAndScale); } } } catch (RuntimeException e) { @@ -955,7 +946,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } boolean needsRepaintForHeading() { - return Math.abs(AngleUtils.difference(currentHeading, overlayPosition.getHeading())) > MIN_HEADING_DELTA; + return Math.abs(AngleUtils.difference(currentHeading, overlayPositionAndScale.getHeading())) > MIN_HEADING_DELTA; } boolean needsRepaintForDistance() { @@ -964,7 +955,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto return false; } - final Location lastLocation = overlayPosition.getCoordinates(); + final Location lastLocation = overlayPositionAndScale.getCoordinates(); float dist = Float.MAX_VALUE; if (lastLocation != null) { @@ -994,15 +985,19 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto (new DisplayPointThread()).start(); } else { // start timer - if (loadTimer != null) { - loadTimer.stopIt(); - loadTimer = null; - } + stopTimer(); loadTimer = new LoadTimer(); loadTimer.start(); } } + private synchronized void stopTimer() { + if (loadTimer != null) { + loadTimer.stopIt(); + loadTimer = null; + } + } + /** * loading timer Triggers every 250ms and checks for viewport change and starts a {@link LoadRunnable}. */ @@ -1172,8 +1167,8 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto do { if (tokens == null) { - tokens = Login.getMapTokens(); - if (noMapTokenHandler != null && tokens == null) { + tokens = GCLogin.getMapTokens(); + if (noMapTokenHandler != null && (StringUtils.isEmpty(tokens.getUserSession()) || StringUtils.isEmpty(tokens.getSessionToken()))) { noMapTokenHandler.sendEmptyMessage(0); } } @@ -1181,7 +1176,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto searchResult = ConnectorFactory.searchByViewport(viewport.resize(0.8), tokens); downloaded = true; if (searchResult.getError() == StatusCode.NOT_LOGGED_IN && Settings.isGCConnectorActive()) { - Login.login(); + GCLogin.getInstance().login(); tokens = null; } else { break; @@ -1311,9 +1306,6 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto protected DoRunnable(final Viewport viewport) { this.viewport = viewport; } - - @Override - public abstract void run(); } /** @@ -1429,7 +1421,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } } - private static void filter(Collection<Geocache> caches) { + private static synchronized void filter(Collection<Geocache> caches) { boolean excludeMine = Settings.isExcludeMyCaches(); boolean excludeDisabled = Settings.isExcludeDisabledCaches(); @@ -1480,7 +1472,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto mapController.setCenter(mapItemFactory.getGeoPointBase(new Geopoint(mapState[0] / 1.0e6, mapState[1] / 1.0e6))); mapController.setZoom(mapState[2]); } catch (RuntimeException e) { - // nothing at all + Log.e("centermap", e); } centered = true; @@ -1504,7 +1496,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto mapController.zoomToSpan((int) (viewport.getLatitudeSpan() * 1e6), (int) (viewport.getLongitudeSpan() * 1e6)); } } catch (RuntimeException e) { - // nothing at all + Log.e("centermap", e); } centered = true; @@ -1513,7 +1505,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto try { mapController.setCenter(makeGeoPoint(coordsCenter)); } catch (Exception e) { - // nothing at all + Log.e("centermap", e); } centered = true; diff --git a/main/src/cgeo/geocaching/maps/PositionAndScaleOverlay.java b/main/src/cgeo/geocaching/maps/PositionAndScaleOverlay.java new file mode 100644 index 0000000..6b34b75 --- /dev/null +++ b/main/src/cgeo/geocaching/maps/PositionAndScaleOverlay.java @@ -0,0 +1,73 @@ +package cgeo.geocaching.maps; + +import cgeo.geocaching.maps.interfaces.GeneralOverlay; +import cgeo.geocaching.maps.interfaces.MapProjectionImpl; +import cgeo.geocaching.maps.interfaces.MapViewImpl; +import cgeo.geocaching.maps.interfaces.OverlayImpl; + +import android.app.Activity; +import android.graphics.Canvas; +import android.graphics.Point; +import android.location.Location; + +import java.util.ArrayList; + +public class PositionAndScaleOverlay implements GeneralOverlay { + private OverlayImpl ovlImpl = null; + + PositionDrawer positionDrawer = null; + ScaleDrawer scaleDrawer = null; + + public PositionAndScaleOverlay(Activity activity, OverlayImpl ovlImpl) { + this.ovlImpl = ovlImpl; + positionDrawer = new PositionDrawer(activity); + scaleDrawer = new ScaleDrawer(activity); + } + + public void setCoordinates(Location coordinatesIn) { + positionDrawer.setCoordinates(coordinatesIn); + } + + public Location getCoordinates() { + return positionDrawer.getCoordinates(); + } + + public void setHeading(float bearingNow) { + positionDrawer.setHeading(bearingNow); + } + + public float getHeading() { + return positionDrawer.getHeading(); + } + + @Override + public void drawOverlayBitmap(Canvas canvas, Point drawPosition, + MapProjectionImpl projection, byte drawZoomLevel) { + + drawInternal(canvas, projection, getOverlayImpl().getMapViewImpl()); + } + + @Override + public void draw(Canvas canvas, MapViewImpl mapView, boolean shadow) { + + drawInternal(canvas, mapView.getMapProjection(), mapView); + } + + private void drawInternal(Canvas canvas, MapProjectionImpl projection, MapViewImpl mapView) { + positionDrawer.drawPosition(canvas, projection); + scaleDrawer.drawScale(canvas, mapView); + } + + @Override + public OverlayImpl getOverlayImpl() { + return this.ovlImpl; + } + + public ArrayList<Location> getHistory() { + return positionDrawer.getHistory(); + } + + public void setHistory(ArrayList<Location> history) { + positionDrawer.setHistory(history); + } +} diff --git a/main/src/cgeo/geocaching/maps/PositionOverlay.java b/main/src/cgeo/geocaching/maps/PositionDrawer.java index b371eae..1a5dcaf 100644 --- a/main/src/cgeo/geocaching/maps/PositionOverlay.java +++ b/main/src/cgeo/geocaching/maps/PositionDrawer.java @@ -2,12 +2,9 @@ package cgeo.geocaching.maps; import cgeo.geocaching.R; import cgeo.geocaching.geopoint.Geopoint; -import cgeo.geocaching.maps.interfaces.GeneralOverlay; import cgeo.geocaching.maps.interfaces.GeoPointImpl; import cgeo.geocaching.maps.interfaces.MapItemFactory; import cgeo.geocaching.maps.interfaces.MapProjectionImpl; -import cgeo.geocaching.maps.interfaces.MapViewImpl; -import cgeo.geocaching.maps.interfaces.OverlayImpl; import cgeo.geocaching.settings.Settings; import android.app.Activity; @@ -23,7 +20,8 @@ import android.location.Location; import java.util.ArrayList; -public class PositionOverlay implements GeneralOverlay { +public class PositionDrawer { + private Location coordinates = null; private GeoPointImpl location = null; private float heading = 0f; @@ -39,47 +37,14 @@ public class PositionOverlay implements GeneralOverlay { private PaintFlagsDrawFilter remfil = null; private PositionHistory positionHistory = new PositionHistory(); private Activity activity; - private MapItemFactory mapItemFactory = null; - private OverlayImpl ovlImpl = null; + private MapItemFactory mapItemFactory; - public PositionOverlay(Activity activity, OverlayImpl ovlImpl) { + public PositionDrawer(Activity activity) { this.activity = activity; this.mapItemFactory = Settings.getMapProvider().getMapItemFactory(); - this.ovlImpl = ovlImpl; - } - - public void setCoordinates(Location coordinatesIn) { - coordinates = coordinatesIn; - location = mapItemFactory.getGeoPointBase(new Geopoint(coordinates)); - } - - public Location getCoordinates() { - return coordinates; - } - - public void setHeading(float bearingNow) { - heading = bearingNow; - } - - public float getHeading() { - return heading; - } - - @Override - public void drawOverlayBitmap(Canvas canvas, Point drawPosition, - MapProjectionImpl projection, byte drawZoomLevel) { - - drawInternal(canvas, projection); - } - - @Override - public void draw(Canvas canvas, MapViewImpl mapView, boolean shadow) { - - drawInternal(canvas, mapView.getMapProjection()); } - private void drawInternal(Canvas canvas, MapProjectionImpl projection) { - + void drawPosition(Canvas canvas, MapProjectionImpl projection) { if (coordinates == null || location == null) { return; } @@ -194,13 +159,6 @@ public class PositionOverlay implements GeneralOverlay { canvas.drawBitmap(arrow, matrix, null); canvas.setDrawFilter(remfil); - - //super.draw(canvas, mapView, shadow); - } - - @Override - public OverlayImpl getOverlayImpl() { - return this.ovlImpl; } public ArrayList<Location> getHistory() { @@ -210,4 +168,22 @@ public class PositionOverlay implements GeneralOverlay { public void setHistory(ArrayList<Location> history) { positionHistory.setHistory(history); } + + public void setHeading(float bearingNow) { + heading = bearingNow; + } + + public float getHeading() { + return heading; + } + + public void setCoordinates(Location coordinatesIn) { + coordinates = coordinatesIn; + location = mapItemFactory.getGeoPointBase(new Geopoint(coordinates)); + } + + public Location getCoordinates() { + return coordinates; + } + } diff --git a/main/src/cgeo/geocaching/maps/ScaleOverlay.java b/main/src/cgeo/geocaching/maps/ScaleDrawer.java index bee6acf..fb46408 100644 --- a/main/src/cgeo/geocaching/maps/ScaleOverlay.java +++ b/main/src/cgeo/geocaching/maps/ScaleDrawer.java @@ -2,11 +2,8 @@ package cgeo.geocaching.maps; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Units; -import cgeo.geocaching.maps.interfaces.GeneralOverlay; import cgeo.geocaching.maps.interfaces.GeoPointImpl; -import cgeo.geocaching.maps.interfaces.MapProjectionImpl; import cgeo.geocaching.maps.interfaces.MapViewImpl; -import cgeo.geocaching.maps.interfaces.OverlayImpl; import org.apache.commons.lang3.tuple.ImmutablePair; @@ -14,46 +11,29 @@ import android.app.Activity; import android.graphics.BlurMaskFilter; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.Point; import android.graphics.Typeface; import android.util.DisplayMetrics; -public class ScaleOverlay implements GeneralOverlay { - +public class ScaleDrawer { private static final double SCALE_WIDTH_FACTOR = 1.0 / 2.5; private Paint scale = null; private Paint scaleShadow = null; private BlurMaskFilter blur = null; private float pixelDensity = 0; - private OverlayImpl ovlImpl = null; - - public ScaleOverlay(Activity activity, OverlayImpl overlayImpl) { - this.ovlImpl = overlayImpl; + public ScaleDrawer(Activity activity) { DisplayMetrics metrics = new DisplayMetrics(); activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); pixelDensity = metrics.density; } - @Override - public void drawOverlayBitmap(Canvas canvas, Point drawPosition, - MapProjectionImpl projection, byte drawZoomLevel) { - drawInternal(canvas, getOverlayImpl().getMapViewImpl()); - } - - @Override - public void draw(Canvas canvas, MapViewImpl mapView, boolean shadow) { - drawInternal(canvas, mapView); - } - static private double keepSignificantDigit(final double distance) { final double scale = Math.pow(10, Math.floor(Math.log10(distance))); return scale * Math.floor(distance / scale); } - private void drawInternal(Canvas canvas, MapViewImpl mapView) { - + void drawScale(Canvas canvas, MapViewImpl mapView) { final double span = mapView.getLongitudeSpan() / 1e6; final GeoPointImpl center = mapView.getMapViewCenter(); @@ -109,8 +89,4 @@ public class ScaleOverlay implements GeneralOverlay { canvas.drawText(String.format(formatString, distanceRound) + " " + scaled.right, (float) (pixels - (10 * pixelDensity)), (bottom - (10 * pixelDensity)), scale); } - @Override - public OverlayImpl getOverlayImpl() { - return ovlImpl; - } } diff --git a/main/src/cgeo/geocaching/maps/google/GoogleMapView.java b/main/src/cgeo/geocaching/maps/google/GoogleMapView.java index 3cf258e..d02e3c2 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleMapView.java +++ b/main/src/cgeo/geocaching/maps/google/GoogleMapView.java @@ -2,11 +2,9 @@ package cgeo.geocaching.maps.google; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import cgeo.geocaching.settings.Settings; import cgeo.geocaching.geopoint.Viewport; import cgeo.geocaching.maps.CachesOverlay; -import cgeo.geocaching.maps.PositionOverlay; -import cgeo.geocaching.maps.ScaleOverlay; +import cgeo.geocaching.maps.PositionAndScaleOverlay; import cgeo.geocaching.maps.interfaces.GeneralOverlay; import cgeo.geocaching.maps.interfaces.GeoPointImpl; import cgeo.geocaching.maps.interfaces.MapControllerImpl; @@ -14,13 +12,15 @@ import cgeo.geocaching.maps.interfaces.MapProjectionImpl; import cgeo.geocaching.maps.interfaces.MapViewImpl; import cgeo.geocaching.maps.interfaces.OnMapDragListener; import cgeo.geocaching.maps.interfaces.OverlayImpl; -import cgeo.geocaching.maps.interfaces.OverlayImpl.OverlayType; +import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.Log; import com.google.android.maps.GeoPoint; import com.google.android.maps.MapView; import com.google.android.maps.Overlay; +import org.apache.commons.lang3.reflect.MethodUtils; + import android.app.Activity; import android.content.Context; import android.graphics.Canvas; @@ -31,6 +31,7 @@ import android.view.GestureDetector.SimpleOnGestureListener; import android.view.Gravity; import android.view.MotionEvent; import android.widget.FrameLayout; +import android.widget.ZoomButtonsController; public class GoogleMapView extends MapView implements MapViewImpl { private GestureDetector gestureDetector; @@ -71,9 +72,14 @@ public class GoogleMapView extends MapView implements MapViewImpl { // Push zoom controls to the right FrameLayout.LayoutParams zoomParams = new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); zoomParams.gravity = Gravity.RIGHT; - getZoomButtonsController().getZoomControls().setLayoutParams(zoomParams); + // The call to retrieve the zoom buttons controller is undocumented and works so far on all devices + // supported by Google Play, but fails at least on one Jolla. + final ZoomButtonsController controller = (ZoomButtonsController) MethodUtils.invokeMethod(this, "getZoomButtonsController"); + controller.getZoomControls().setLayoutParams(zoomParams); super.displayZoomControls(takeFocus); + } catch (NoSuchMethodException e) { + Log.w("GoogleMapView.displayZoomControls: unable to explicitly place the zoom buttons"); } catch (Exception e) { Log.e("GoogleMapView.displayZoomControls", e); } @@ -119,19 +125,11 @@ public class GoogleMapView extends MapView implements MapViewImpl { } @Override - public PositionOverlay createAddPositionOverlay(Activity activity) { - - GoogleOverlay ovl = new GoogleOverlay(activity, OverlayType.PositionOverlay); - getOverlays().add(ovl); - return (PositionOverlay) ovl.getBase(); - } - - @Override - public ScaleOverlay createAddScaleOverlay(Activity activity) { + public PositionAndScaleOverlay createAddPositionAndScaleOverlay(Activity activity) { - GoogleOverlay ovl = new GoogleOverlay(activity, OverlayType.ScaleOverlay); + GoogleOverlay ovl = new GoogleOverlay(activity); getOverlays().add(ovl); - return (ScaleOverlay) ovl.getBase(); + return (PositionAndScaleOverlay) ovl.getBase(); } @Override diff --git a/main/src/cgeo/geocaching/maps/google/GoogleOverlay.java b/main/src/cgeo/geocaching/maps/google/GoogleOverlay.java index bf4f606..e937773 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleOverlay.java +++ b/main/src/cgeo/geocaching/maps/google/GoogleOverlay.java @@ -1,7 +1,6 @@ package cgeo.geocaching.maps.google; -import cgeo.geocaching.maps.PositionOverlay; -import cgeo.geocaching.maps.ScaleOverlay; +import cgeo.geocaching.maps.PositionAndScaleOverlay; import cgeo.geocaching.maps.interfaces.GeneralOverlay; import cgeo.geocaching.maps.interfaces.MapViewImpl; import cgeo.geocaching.maps.interfaces.OverlayImpl; @@ -17,20 +16,11 @@ import java.util.concurrent.locks.ReentrantLock; public class GoogleOverlay extends Overlay implements OverlayImpl { - private GeneralOverlay overlayBase = null; + private PositionAndScaleOverlay overlayBase = null; private Lock lock = new ReentrantLock(); - public GoogleOverlay(Activity activityIn, OverlayType ovlType) { - switch (ovlType) { - case PositionOverlay: - overlayBase = new PositionOverlay(activityIn, this); - break; - case ScaleOverlay: - overlayBase = new ScaleOverlay(activityIn, this); - break; - default: - throw new IllegalArgumentException(); - } + public GoogleOverlay(Activity activityIn) { + overlayBase = new PositionAndScaleOverlay(activityIn, this); } @Override diff --git a/main/src/cgeo/geocaching/maps/interfaces/MapViewImpl.java b/main/src/cgeo/geocaching/maps/interfaces/MapViewImpl.java index 5481891..cb7ddc6 100644 --- a/main/src/cgeo/geocaching/maps/interfaces/MapViewImpl.java +++ b/main/src/cgeo/geocaching/maps/interfaces/MapViewImpl.java @@ -2,8 +2,7 @@ package cgeo.geocaching.maps.interfaces; import cgeo.geocaching.geopoint.Viewport; import cgeo.geocaching.maps.CachesOverlay; -import cgeo.geocaching.maps.PositionOverlay; -import cgeo.geocaching.maps.ScaleOverlay; +import cgeo.geocaching.maps.PositionAndScaleOverlay; import android.app.Activity; import android.content.Context; @@ -47,9 +46,7 @@ public interface MapViewImpl { CachesOverlay createAddMapOverlay(Context context, Drawable drawable); - ScaleOverlay createAddScaleOverlay(Activity activity); - - PositionOverlay createAddPositionOverlay(Activity activity); + PositionAndScaleOverlay createAddPositionAndScaleOverlay(Activity activity); void setMapSource(); diff --git a/main/src/cgeo/geocaching/maps/interfaces/OverlayImpl.java b/main/src/cgeo/geocaching/maps/interfaces/OverlayImpl.java index a17b5fb..0984755 100644 --- a/main/src/cgeo/geocaching/maps/interfaces/OverlayImpl.java +++ b/main/src/cgeo/geocaching/maps/interfaces/OverlayImpl.java @@ -6,11 +6,6 @@ package cgeo.geocaching.maps.interfaces; */ public interface OverlayImpl { - public enum OverlayType { - PositionOverlay, - ScaleOverlay - } - void lock(); void unlock(); diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java index dc4e82c..78aa47d 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java @@ -3,8 +3,7 @@ package cgeo.geocaching.maps.mapsforge; import cgeo.geocaching.R; import cgeo.geocaching.geopoint.Viewport; import cgeo.geocaching.maps.CachesOverlay; -import cgeo.geocaching.maps.PositionOverlay; -import cgeo.geocaching.maps.ScaleOverlay; +import cgeo.geocaching.maps.PositionAndScaleOverlay; import cgeo.geocaching.maps.interfaces.GeneralOverlay; import cgeo.geocaching.maps.interfaces.GeoPointImpl; import cgeo.geocaching.maps.interfaces.MapControllerImpl; @@ -13,7 +12,6 @@ import cgeo.geocaching.maps.interfaces.MapSource; import cgeo.geocaching.maps.interfaces.MapViewImpl; import cgeo.geocaching.maps.interfaces.OnMapDragListener; import cgeo.geocaching.maps.interfaces.OverlayImpl; -import cgeo.geocaching.maps.interfaces.OverlayImpl.OverlayType; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.Log; @@ -111,17 +109,10 @@ public class MapsforgeMapView extends MapView implements MapViewImpl { } @Override - public PositionOverlay createAddPositionOverlay(Activity activity) { - MapsforgeOverlay ovl = new MapsforgeOverlay(activity, OverlayType.PositionOverlay); + public PositionAndScaleOverlay createAddPositionAndScaleOverlay(Activity activity) { + MapsforgeOverlay ovl = new MapsforgeOverlay(activity); getOverlays().add(ovl); - return (PositionOverlay) ovl.getBase(); - } - - @Override - public ScaleOverlay createAddScaleOverlay(Activity activity) { - MapsforgeOverlay ovl = new MapsforgeOverlay(activity, OverlayType.ScaleOverlay); - getOverlays().add(ovl); - return (ScaleOverlay) ovl.getBase(); + return (PositionAndScaleOverlay) ovl.getBase(); } @Override diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeOverlay.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeOverlay.java index f61e523..74a8601 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeOverlay.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeOverlay.java @@ -1,7 +1,6 @@ package cgeo.geocaching.maps.mapsforge; -import cgeo.geocaching.maps.PositionOverlay; -import cgeo.geocaching.maps.ScaleOverlay; +import cgeo.geocaching.maps.PositionAndScaleOverlay; import cgeo.geocaching.maps.interfaces.GeneralOverlay; import cgeo.geocaching.maps.interfaces.MapViewImpl; import cgeo.geocaching.maps.interfaces.OverlayImpl; @@ -18,21 +17,11 @@ import java.util.concurrent.locks.ReentrantLock; public class MapsforgeOverlay extends Overlay implements OverlayImpl { - private GeneralOverlay overlayBase = null; + private PositionAndScaleOverlay overlayBase = null; private Lock lock = new ReentrantLock(); - public MapsforgeOverlay(Activity activityIn, OverlayImpl.OverlayType ovlType) { - - switch (ovlType) { - case PositionOverlay: - overlayBase = new PositionOverlay(activityIn, this); - break; - case ScaleOverlay: - overlayBase = new ScaleOverlay(activityIn, this); - break; - default: - throw new IllegalStateException(); - } + public MapsforgeOverlay(Activity activityIn) { + overlayBase = new PositionAndScaleOverlay(activityIn, this); } @Override diff --git a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java index a074e70..c741a31 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java @@ -1,11 +1,9 @@ package cgeo.geocaching.maps.mapsforge.v024; import cgeo.geocaching.R; -import cgeo.geocaching.settings.Settings; import cgeo.geocaching.geopoint.Viewport; import cgeo.geocaching.maps.CachesOverlay; -import cgeo.geocaching.maps.PositionOverlay; -import cgeo.geocaching.maps.ScaleOverlay; +import cgeo.geocaching.maps.PositionAndScaleOverlay; import cgeo.geocaching.maps.interfaces.GeneralOverlay; import cgeo.geocaching.maps.interfaces.GeoPointImpl; import cgeo.geocaching.maps.interfaces.MapControllerImpl; @@ -13,7 +11,7 @@ import cgeo.geocaching.maps.interfaces.MapProjectionImpl; import cgeo.geocaching.maps.interfaces.MapViewImpl; import cgeo.geocaching.maps.interfaces.OnMapDragListener; import cgeo.geocaching.maps.interfaces.OverlayImpl; -import cgeo.geocaching.maps.interfaces.OverlayImpl.OverlayType; +import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.Log; import org.mapsforge.android.mapsold.GeoPoint; @@ -102,17 +100,10 @@ public class MapsforgeMapView024 extends MapView implements MapViewImpl { } @Override - public PositionOverlay createAddPositionOverlay(Activity activity) { - MapsforgeOverlay ovl = new MapsforgeOverlay(activity, OverlayType.PositionOverlay); - getOverlays().add(ovl); - return (PositionOverlay) ovl.getBase(); - } - - @Override - public ScaleOverlay createAddScaleOverlay(Activity activity) { - MapsforgeOverlay ovl = new MapsforgeOverlay(activity, OverlayType.ScaleOverlay); + public PositionAndScaleOverlay createAddPositionAndScaleOverlay(Activity activity) { + MapsforgeOverlay ovl = new MapsforgeOverlay(activity); getOverlays().add(ovl); - return (ScaleOverlay) ovl.getBase(); + return (PositionAndScaleOverlay) ovl.getBase(); } @Override diff --git a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeOverlay.java b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeOverlay.java index 8c9e0c3..655e0b9 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeOverlay.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeOverlay.java @@ -1,7 +1,6 @@ package cgeo.geocaching.maps.mapsforge.v024; -import cgeo.geocaching.maps.PositionOverlay; -import cgeo.geocaching.maps.ScaleOverlay; +import cgeo.geocaching.maps.PositionAndScaleOverlay; import cgeo.geocaching.maps.interfaces.GeneralOverlay; import cgeo.geocaching.maps.interfaces.MapViewImpl; import cgeo.geocaching.maps.interfaces.OverlayImpl; @@ -18,21 +17,11 @@ import java.util.concurrent.locks.ReentrantLock; public class MapsforgeOverlay extends Overlay implements OverlayImpl { - private GeneralOverlay overlayBase = null; + private PositionAndScaleOverlay overlayBase = null; private Lock lock = new ReentrantLock(); - public MapsforgeOverlay(Activity activityIn, OverlayImpl.OverlayType ovlType) { - - switch (ovlType) { - case PositionOverlay: - overlayBase = new PositionOverlay(activityIn, this); - break; - case ScaleOverlay: - overlayBase = new ScaleOverlay(activityIn, this); - break; - default: - throw new IllegalStateException(); - } + public MapsforgeOverlay(Activity activityIn) { + overlayBase = new PositionAndScaleOverlay(activityIn, this); } @Override diff --git a/main/src/cgeo/geocaching/settings/AbstractAttributeBasedPrefence.java b/main/src/cgeo/geocaching/settings/AbstractAttributeBasedPrefence.java new file mode 100644 index 0000000..1930c17 --- /dev/null +++ b/main/src/cgeo/geocaching/settings/AbstractAttributeBasedPrefence.java @@ -0,0 +1,54 @@ +package cgeo.geocaching.settings; + +import org.eclipse.jdt.annotation.Nullable; + +import android.content.Context; +import android.content.res.TypedArray; +import android.preference.Preference; +import android.util.AttributeSet; + +/** + * Base class for preferences which evaluate their XML attributes for further processing. + * + */ +public abstract class AbstractAttributeBasedPrefence extends Preference { + + public AbstractAttributeBasedPrefence(Context context) { + super(context); + } + + public AbstractAttributeBasedPrefence(Context context, AttributeSet attrs) { + super(context, attrs); + processAttributes(context, attrs, 0); + } + + public AbstractAttributeBasedPrefence(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + processAttributes(context, attrs, defStyle); + } + + private void processAttributes(Context context, @Nullable AttributeSet attrs, int defStyle) { + if (attrs == null) { + return; + } + TypedArray types = context.obtainStyledAttributes(attrs, getAttributeNames(), + defStyle, 0); + + processAttributeValues(types); + + types.recycle(); + } + + /** + * Evaluate the attributes which where requested in {@link AbstractAttributeBasedPrefence#getAttributeNames()}. + * + * @param values + */ + protected abstract void processAttributeValues(TypedArray values); + + /** + * @return the names of the attributes you want to read in your preference implementation + */ + protected abstract int[] getAttributeNames(); + +} diff --git a/main/src/cgeo/geocaching/settings/AbstractCheckCredentialsPreference.java b/main/src/cgeo/geocaching/settings/AbstractCheckCredentialsPreference.java new file mode 100644 index 0000000..d3aae5c --- /dev/null +++ b/main/src/cgeo/geocaching/settings/AbstractCheckCredentialsPreference.java @@ -0,0 +1,116 @@ +package cgeo.geocaching.settings; + +import cgeo.geocaching.R; +import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.enumerations.StatusCode; +import cgeo.geocaching.network.Cookies; +import cgeo.geocaching.ui.dialog.Dialogs; +import cgeo.geocaching.utils.Log; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; + +import android.annotation.SuppressLint; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Message; +import android.preference.Preference; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +public abstract class AbstractCheckCredentialsPreference extends Preference { + + public AbstractCheckCredentialsPreference(Context context) { + super(context); + } + + public AbstractCheckCredentialsPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AbstractCheckCredentialsPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected View onCreateView(ViewGroup parent) { + setOnPreferenceClickListener(new LoginCheckClickListener()); + return super.onCreateView(parent); + } + + protected abstract ImmutablePair<String, String> getCredentials(); + + protected abstract Object login(); + + private class LoginCheckClickListener implements OnPreferenceClickListener { + private Resources res; + private SettingsActivity activity; + + private ProgressDialog loginDialog; + @SuppressLint("HandlerLeak") + private Handler logInHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + try { + if (loginDialog != null && loginDialog.isShowing()) { + loginDialog.dismiss(); + } + + if (msg.obj == null || (msg.obj instanceof Drawable)) { + Dialogs.message(activity, R.string.init_login_popup, R.string.init_login_popup_ok, (Drawable) msg.obj); + } else { + Dialogs.message(activity, R.string.init_login_popup, + res.getString(R.string.init_login_popup_failed_reason) + + " " + + ((StatusCode) msg.obj).getErrorString(res) + + "."); + } + } catch (Exception e) { + ActivityMixin.showToast(activity, R.string.err_login_failed); + Log.e("SettingsActivity.logInHandler", e); + } finally { + if (loginDialog != null && loginDialog.isShowing()) { + loginDialog.dismiss(); + } + // enable/disable basic member preferences + activity.initBasicMemberPreferences(); + } + } + }; + + @Override + public boolean onPreferenceClick(Preference preference) { + this.activity = (SettingsActivity) AbstractCheckCredentialsPreference.this.getContext(); + this.res = activity.getResources(); + + ImmutablePair<String, String> credentials = getCredentials(); + + // check credentials for validity + if (StringUtils.isBlank(credentials.getLeft()) + || StringUtils.isBlank(credentials.getRight())) { + ActivityMixin.showToast(activity, R.string.err_missing_auth); + return false; + } + + loginDialog = ProgressDialog.show(activity, + res.getString(R.string.init_login_popup), + res.getString(R.string.init_login_popup_working), true); + loginDialog.setCancelable(false); + Cookies.clearCookies(); + + (new Thread() { + @Override + public void run() { + Object payload = login(); + logInHandler.obtainMessage(0, payload).sendToTarget(); + } + }).start(); + + return false; // no shared preference has to be changed + } + } +} diff --git a/main/src/cgeo/geocaching/settings/CapabilitiesPreference.java b/main/src/cgeo/geocaching/settings/CapabilitiesPreference.java new file mode 100644 index 0000000..d2e19b7 --- /dev/null +++ b/main/src/cgeo/geocaching/settings/CapabilitiesPreference.java @@ -0,0 +1,91 @@ +package cgeo.geocaching.settings; + +import cgeo.geocaching.CgeoApplication; +import cgeo.geocaching.R; +import cgeo.geocaching.connector.ConnectorFactory; +import cgeo.geocaching.connector.IConnector; + +import org.apache.commons.lang3.StringUtils; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.TypedArray; +import android.preference.Preference; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebView; + +/** + * Preference for displaying the supported capabilities of an {@link IConnector} implementation. + */ +public class CapabilitiesPreference extends AbstractAttributeBasedPrefence { + + private String connectorCode; + + public CapabilitiesPreference(Context context) { + super(context); + } + + public CapabilitiesPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CapabilitiesPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public View getView(View convertView, ViewGroup parent) { + setOnPreferenceClickListener(new ClickListener()); + return super.getView(convertView, parent); + } + + private final class ClickListener implements OnPreferenceClickListener { + @Override + public boolean onPreferenceClick(final Preference preference) { + WebView htmlView = new WebView(preference.getContext()); + htmlView.loadData(createCapabilitiesMessage(), "text/html", null); + AlertDialog.Builder builder = new AlertDialog.Builder(preference.getContext()); + builder.setView(htmlView) + .setIcon(android.R.drawable.ic_dialog_info) + .setTitle(R.string.settings_features) + .setPositiveButton(R.string.err_none, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + builder.create().show(); + return false; + } + } + + public String createCapabilitiesMessage() { + // TODO: this needs a better key for the connectors + IConnector connector = ConnectorFactory.getConnector(connectorCode + "1234"); + if (connector == null) { + return StringUtils.EMPTY; + } + StringBuilder builder = new StringBuilder("<p>" + + CgeoApplication.getInstance().getString(R.string.feature_description) + "<ul>"); + + for (String capability : connector.getCapabilities()) { + builder.append("<li>").append(capability).append("</li>"); + } + + builder.append("</ul></p>"); + return builder.toString(); + } + + @Override + protected void processAttributeValues(TypedArray values) { + connectorCode = values.getString(0); + } + + @Override + protected int[] getAttributeNames() { + return new int[] { R.attr.connector }; + } +} diff --git a/main/src/cgeo/geocaching/settings/CheckBoxWithPopupPreference.java b/main/src/cgeo/geocaching/settings/CheckBoxWithPopupPreference.java index 4e64b9a..e36e007 100644 --- a/main/src/cgeo/geocaching/settings/CheckBoxWithPopupPreference.java +++ b/main/src/cgeo/geocaching/settings/CheckBoxWithPopupPreference.java @@ -1,13 +1,10 @@ package cgeo.geocaching.settings; import cgeo.geocaching.R; +import cgeo.geocaching.ui.UrlPopup; -import android.app.AlertDialog; import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; import android.content.res.TypedArray; -import android.net.Uri; import android.preference.CheckBoxPreference; import android.preference.Preference; import android.util.AttributeSet; @@ -63,26 +60,7 @@ public class CheckBoxWithPopupPreference extends CheckBoxPreference { if (!(Boolean) newValue) { return true; } - AlertDialog.Builder builder = new AlertDialog.Builder( - preference.getContext()); - builder.setMessage(text) - .setIcon(android.R.drawable.ic_dialog_info) - .setTitle(title) - .setPositiveButton(R.string.err_none, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }) - .setNegativeButton(urlButton, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse(url)); - preference.getContext().startActivity(i); - } - }); - builder.create().show(); + new UrlPopup(preference.getContext()).show(title, text, url, urlButton); return true; } }); diff --git a/main/src/cgeo/geocaching/settings/CheckECCredentialsPreference.java b/main/src/cgeo/geocaching/settings/CheckECCredentialsPreference.java new file mode 100644 index 0000000..46a3661 --- /dev/null +++ b/main/src/cgeo/geocaching/settings/CheckECCredentialsPreference.java @@ -0,0 +1,40 @@ +package cgeo.geocaching.settings; + +import cgeo.geocaching.connector.ec.ECConnector; +import cgeo.geocaching.connector.ec.ECLogin; +import cgeo.geocaching.enumerations.StatusCode; + +import org.apache.commons.lang3.tuple.ImmutablePair; + +import android.content.Context; +import android.util.AttributeSet; + +public class CheckECCredentialsPreference extends AbstractCheckCredentialsPreference { + + public CheckECCredentialsPreference(Context context) { + super(context); + } + + public CheckECCredentialsPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CheckECCredentialsPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected ImmutablePair<String, String> getCredentials() { + return Settings.getCredentials(ECConnector.getInstance()); + } + + @Override + protected Object login() { + final StatusCode loginResult = ECLogin.getInstance().login(); + Object payload = loginResult; + if (loginResult == StatusCode.NO_ERROR) { + payload = null; + } + return payload; + } +} diff --git a/main/src/cgeo/geocaching/settings/CheckGcCredentialsPreference.java b/main/src/cgeo/geocaching/settings/CheckGcCredentialsPreference.java index 724ab80..12c8b24 100644 --- a/main/src/cgeo/geocaching/settings/CheckGcCredentialsPreference.java +++ b/main/src/cgeo/geocaching/settings/CheckGcCredentialsPreference.java @@ -1,28 +1,14 @@ package cgeo.geocaching.settings; -import cgeo.geocaching.R; -import cgeo.geocaching.activity.ActivityMixin; -import cgeo.geocaching.connector.gc.Login; +import cgeo.geocaching.connector.gc.GCLogin; import cgeo.geocaching.enumerations.StatusCode; -import cgeo.geocaching.network.Cookies; -import cgeo.geocaching.utils.Log; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; -import android.annotation.SuppressLint; -import android.app.ProgressDialog; import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.Message; -import android.preference.Preference; import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -public class CheckGcCredentialsPreference extends Preference { +public class CheckGcCredentialsPreference extends AbstractCheckCredentialsPreference { public CheckGcCredentialsPreference(Context context) { super(context); @@ -37,87 +23,18 @@ public class CheckGcCredentialsPreference extends Preference { } @Override - protected View onCreateView(ViewGroup parent) { - setOnPreferenceClickListener(GC_LOGIN_CHECK); - return super.onCreateView(parent); + protected ImmutablePair<String, String> getCredentials() { + return Settings.getGcCredentials(); } - private final GcLoginCheck GC_LOGIN_CHECK = new GcLoginCheck(); - - private class GcLoginCheck implements OnPreferenceClickListener { - private Resources res; - private SettingsActivity activity; - - private ProgressDialog loginDialog; - @SuppressLint("HandlerLeak") - private Handler logInHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - try { - if (loginDialog != null && loginDialog.isShowing()) { - loginDialog.dismiss(); - } - - if (msg.obj == null || (msg.obj instanceof Drawable)) { - ActivityMixin.helpDialog(activity, - res.getString(R.string.init_login_popup), - res.getString(R.string.init_login_popup_ok), - (Drawable) msg.obj); - } else { - ActivityMixin.helpDialog(activity, - res.getString(R.string.init_login_popup), - res.getString(R.string.init_login_popup_failed_reason) - + " " - + ((StatusCode) msg.obj).getErrorString(res) - + "."); - } - } catch (Exception e) { - ActivityMixin.showToast(activity, R.string.err_login_failed); - Log.e("SettingsActivity.logInHandler", e); - } finally { - if (loginDialog != null && loginDialog.isShowing()) { - loginDialog.dismiss(); - } - // enable/disable basic member preferences - activity.initBasicMemberPreferences(); - } - } - }; - - @Override - public boolean onPreferenceClick(Preference preference) { - this.activity = (SettingsActivity) CheckGcCredentialsPreference.this.getContext(); - this.res = activity.getResources(); - - ImmutablePair<String, String> credentials = Settings.getGcLogin(); - - // check credentials for validity - if (StringUtils.isBlank(credentials.getLeft()) - || StringUtils.isBlank(credentials.getRight())) { - ActivityMixin.showToast(activity, R.string.err_missing_auth); - return false; - } - - loginDialog = ProgressDialog.show(activity, - res.getString(R.string.init_login_popup), - res.getString(R.string.init_login_popup_working), true); - loginDialog.setCancelable(false); - Cookies.clearCookies(); - - (new Thread() { - @Override - public void run() { - final StatusCode loginResult = Login.login(); - Object payload = loginResult; - if (loginResult == StatusCode.NO_ERROR) { - Login.detectGcCustomDate(); - payload = Login.downloadAvatarAndGetMemberStatus(); - } - logInHandler.obtainMessage(0, payload).sendToTarget(); - } - }).start(); - - return false; // no shared preference has to be changed + @Override + protected Object login() { + final StatusCode loginResult = GCLogin.getInstance().login(); + Object payload = loginResult; + if (loginResult == StatusCode.NO_ERROR) { + GCLogin.detectGcCustomDate(); + payload = GCLogin.getInstance().downloadAvatarAndGetMemberStatus(); } + return payload; } } diff --git a/main/src/cgeo/geocaching/settings/EditPasswordPreference.java b/main/src/cgeo/geocaching/settings/EditPasswordPreference.java index 20d0250..af07041 100644 --- a/main/src/cgeo/geocaching/settings/EditPasswordPreference.java +++ b/main/src/cgeo/geocaching/settings/EditPasswordPreference.java @@ -1,15 +1,16 @@ package cgeo.geocaching.settings; +import org.apache.commons.lang3.StringUtils; + import android.content.Context; import android.preference.EditTextPreference; import android.util.AttributeSet; /** - * This is just a dummy preference, to be able check for the type. + * Password preference. It will only show a row of asterisks as summary instead of the password. * <p> * Use it exactly as an EditTextPreference - * - * @see SettingsActivity - search for EditPasswordPreference + * */ public class EditPasswordPreference extends EditTextPreference { @@ -25,4 +26,13 @@ public class EditPasswordPreference extends EditTextPreference { super(context, attrs, defStyle); } + @Override + public void setSummary(CharSequence summary) { + if (StringUtils.isBlank(summary)) { + super.setSummary(StringUtils.EMPTY); + } else { + super.setSummary(StringUtils.repeat("\u2022 ", 10)); + } + } + } diff --git a/main/src/cgeo/geocaching/settings/InfoPreference.java b/main/src/cgeo/geocaching/settings/InfoPreference.java index ea740b4..8040a62 100644 --- a/main/src/cgeo/geocaching/settings/InfoPreference.java +++ b/main/src/cgeo/geocaching/settings/InfoPreference.java @@ -1,14 +1,11 @@ package cgeo.geocaching.settings; import cgeo.geocaching.R; +import cgeo.geocaching.ui.UrlPopup; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; import android.content.res.TypedArray; -import android.net.Uri; import android.preference.Preference; import android.util.AttributeSet; import android.view.LayoutInflater; @@ -17,48 +14,61 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; -public class InfoPreference extends Preference { - - // strings for the popup dialog +/** + * Preference which shows a dialog containing textual explanation. The dialog has two buttons, where one will open a + * hyper link with more detailed information. + * <p> + * The URL for the hyper link and the text are given as custom attributes in the preference XML definition. + * </p> + * + */ +public class InfoPreference extends AbstractAttributeBasedPrefence { + + /** + * Content of the dialog, filled from preferences XML. + */ private String text; + /** + * URL for the second button, filled from preferences XML. + */ private String url; + /** + * text for the second button to open an URL, filled from preferences XML. + */ private String urlButton; private LayoutInflater inflater; public InfoPreference(Context context) { super(context); - init(context, null, 0); + init(context); } public InfoPreference(Context context, AttributeSet attrs) { super(context, attrs); - init(context, attrs, 0); + init(context); } public InfoPreference(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - init(context, attrs, defStyle); + init(context); } - private void init(Context context, AttributeSet attrs, int defStyle) { + private void init(Context context) { inflater = ((Activity) context).getLayoutInflater(); - setPersistent(false); + } - if (attrs == null) { - return; // coward's retreat - } - - TypedArray types = context.obtainStyledAttributes(attrs, new int[] { - android.R.attr.text, R.attr.url, R.attr.urlButton }, - defStyle, 0); - - text = types.getString(0); - url = types.getString(1); - urlButton = types.getString(2); + @Override + protected int[] getAttributeNames() { + return new int[] { android.R.attr.text, R.attr.url, R.attr.urlButton }; + } - types.recycle(); + @Override + protected void processAttributeValues(TypedArray values) { + text = values.getString(0); + url = values.getString(1); + urlButton = values.getString(2); } @Override @@ -69,39 +79,30 @@ public class InfoPreference extends Preference { @Override public boolean onPreferenceClick(final Preference preference) { - AlertDialog.Builder builder = new AlertDialog.Builder( - preference.getContext()); - builder.setMessage(text) - .setIcon(android.R.drawable.ic_dialog_info) - .setTitle(preference.getTitle()) - .setPositiveButton(R.string.err_none, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }) - .setNegativeButton(urlButton, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse(url)); - preference.getContext().startActivity(i); - } - }); - builder.create().show(); + new UrlPopup(preference.getContext()).show(preference.getTitle().toString(), text, url, urlButton); + // don't update the preference value return false; } }); - // show an Info Icon - View v = super.onCreateView(parent); - - ImageView i = (ImageView) inflater.inflate(R.layout.preference_info_icon, parent, false); - LinearLayout l = (LinearLayout) v.findViewById(android.R.id.widget_frame); - l.setVisibility(View.VISIBLE); - l.addView(i); + return addInfoIcon(parent); + } - return v; + /** + * Add an info icon at the left hand side of the preference. + * + * @param parent + * @return + */ + private View addInfoIcon(ViewGroup parent) { + View preferenceView = super.onCreateView(parent); + + ImageView iconView = (ImageView) inflater.inflate(R.layout.preference_info_icon, parent, false); + LinearLayout frame = (LinearLayout) preferenceView.findViewById(android.R.id.widget_frame); + frame.setVisibility(View.VISIBLE); + frame.addView(iconView); + + return preferenceView; } } diff --git a/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java b/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java index fbf08fa..094b8f5 100644 --- a/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java +++ b/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java @@ -4,6 +4,7 @@ import cgeo.geocaching.R; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; +import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; @@ -45,14 +46,11 @@ public class RegisterSend2CgeoPreference extends Preference { } if (msg.what > 0) { - ActivityMixin.helpDialog(activity, - activity.getString(R.string.init_sendToCgeo), + Dialogs.message(activity, R.string.init_sendToCgeo, activity.getString(R.string.init_sendToCgeo_register_ok) .replace("####", String.valueOf(msg.what))); } else { - ActivityMixin.helpDialog(activity, - activity.getString(R.string.init_sendToCgeo), - activity.getString(R.string.init_sendToCgeo_register_fail)); + Dialogs.message(activity, R.string.init_sendToCgeo, R.string.init_sendToCgeo_register_fail); } } catch (Exception e) { ActivityMixin.showToast(activity, R.string.init_sendToCgeo_register_fail); diff --git a/main/src/cgeo/geocaching/settings/Settings.java b/main/src/cgeo/geocaching/settings/Settings.java index 146182a..186e5d9 100644 --- a/main/src/cgeo/geocaching/settings/Settings.java +++ b/main/src/cgeo/geocaching/settings/Settings.java @@ -3,8 +3,10 @@ package cgeo.geocaching.settings; import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.R; import cgeo.geocaching.apps.cache.navi.NavigationAppFactory.NavigationAppsEnum; +import cgeo.geocaching.connector.capability.ICredentials; +import cgeo.geocaching.connector.gc.GCConnector; import cgeo.geocaching.connector.gc.GCConstants; -import cgeo.geocaching.connector.gc.Login; +import cgeo.geocaching.connector.gc.GCLogin; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LiveMapStrategy.Strategy; import cgeo.geocaching.enumerations.LogType; @@ -24,6 +26,7 @@ import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import android.content.Context; @@ -270,14 +273,22 @@ public class Settings { } /** - * Get login and password information. + * Get login and password information of Geocaching.com. * * @return a pair either with (login, password) or (empty, empty) if no valid information is stored */ - public static ImmutablePair<String, String> getGcLogin() { + public static ImmutablePair<String, String> getGcCredentials() { + return getCredentials(GCConnector.getInstance()); + } - final String username = getString(R.string.pref_username, null); - final String password = getString(R.string.pref_password, null); + /** + * Get login and password information. + * + * @return a pair either with (login, password) or (empty, empty) if no valid information is stored + */ + public static ImmutablePair<String, String> getCredentials(final @NonNull ICredentials connector) { + final String username = getString(connector.getUsernamePreferenceKey(), null); + final String password = getString(connector.getPasswordPreferenceKey(), null); if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { return new ImmutablePair<String, String>(StringUtils.EMPTY, StringUtils.EMPTY); @@ -294,6 +305,14 @@ public class Settings { return getBoolean(R.string.pref_connectorGCActive, true); } + public static boolean isECConnectorActive() { + return getBoolean(R.string.pref_connectorECActive, false); + } + + public static boolean isOXConnectorActive() { + return getBoolean(R.string.pref_connectorOXActive, false); + } + public static boolean isPremiumMember() { // Basic Member, Premium Member, ??? return GCConstants.MEMBER_STATUS_PM.equalsIgnoreCase(Settings.getMemberStatus()); @@ -468,7 +487,7 @@ public class Settings { /** * @return User selected date format on GC.com - * @see Login#GC_CUSTOM_DATE_FORMATS + * @see GCLogin#GC_CUSTOM_DATE_FORMATS */ public static String getGcCustomDate() { return getString(R.string.pref_gccustomdate, null); @@ -611,8 +630,6 @@ public class Settings { * @return */ private static int getConvertedMapId() { - // what the heck is happening here?? hashCodes of Strings? - // why not strings? final int id = Integer.parseInt(getString(R.string.pref_mapsource, String.valueOf(MAP_SOURCE_DEFAULT))); switch (id) { @@ -925,18 +942,16 @@ public class Settings { putBoolean(R.string.pref_excludemine, exclude); } - static boolean setLogin(final String username, final String password) { - + static void setLogin(final String username, final String password) { if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { // erase username and password - boolean a = remove(R.string.pref_username); - boolean b = remove(R.string.pref_password); - return a && b; + remove(R.string.pref_username); + remove(R.string.pref_password); + return; } // save username and password - boolean a = putString(R.string.pref_username, username); - boolean b = putString(R.string.pref_password, password); - return a && b; + putString(R.string.pref_username, username); + putString(R.string.pref_password, password); } public static long getFieldnoteExportDate() { @@ -982,4 +997,8 @@ public class Settings { return getBoolean(R.string.pref_fieldNoteExportOnlyNew, false); } + public static String getECIconSet() { + return getString(R.string.pref_ec_icons, "1"); + } + } diff --git a/main/src/cgeo/geocaching/settings/SettingsActivity.java b/main/src/cgeo/geocaching/settings/SettingsActivity.java index 403b11d..cd9296e 100644 --- a/main/src/cgeo/geocaching/settings/SettingsActivity.java +++ b/main/src/cgeo/geocaching/settings/SettingsActivity.java @@ -9,7 +9,7 @@ import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; import cgeo.geocaching.apps.cache.navi.NavigationAppFactory.NavigationAppsEnum; import cgeo.geocaching.compatibility.Compatibility; import cgeo.geocaching.connector.gc.GCConnector; -import cgeo.geocaching.connector.gc.Login; +import cgeo.geocaching.connector.gc.GCLogin; import cgeo.geocaching.files.SimpleDirChooser; import cgeo.geocaching.maps.MapProviderFactory; import cgeo.geocaching.maps.interfaces.MapSource; @@ -128,7 +128,8 @@ public class SettingsActivity extends PreferenceActivity { R.string.pref_gpxExportDir, R.string.pref_gpxImportDir, R.string.pref_mapDirectory, R.string.pref_defaultNavigationTool, R.string.pref_defaultNavigationTool2, R.string.pref_webDeviceName, - R.string.pref_fakekey_preference_backup_info, R.string.pref_twitter_cache_message, R.string.pref_twitter_trackable_message }) { + R.string.pref_fakekey_preference_backup_info, R.string.pref_twitter_cache_message, R.string.pref_twitter_trackable_message, + R.string.pref_ecusername, R.string.pref_ecpassword, R.string.pref_ec_icons }) { bindSummaryToStringValue(k); } getPreference(R.string.pref_units).setDefaultValue(Settings.getImperialUnitsDefault()); @@ -149,9 +150,11 @@ public class SettingsActivity extends PreferenceActivity { getPreference(R.string.pref_connectorOCActive).setOnPreferenceChangeListener(VALUE_CHANGE_LISTENER); getPreference(R.string.pref_connectorOCPLActive).setOnPreferenceChangeListener(VALUE_CHANGE_LISTENER); getPreference(R.string.pref_connectorGCActive).setOnPreferenceChangeListener(VALUE_CHANGE_LISTENER); + getPreference(R.string.pref_connectorECActive).setOnPreferenceChangeListener(VALUE_CHANGE_LISTENER); setWebsite(R.string.pref_fakekey_gc_website, GCConnector.getInstance().getHost()); setWebsite(R.string.pref_fakekey_ocde_website, "opencaching.de"); setWebsite(R.string.pref_fakekey_ocpl_website, "opencaching.pl"); + setWebsite(R.string.pref_fakekey_ec_website, "extremcaching.com"); setWebsite(R.string.pref_fakekey_gcvote_website, "gcvote.com"); setWebsite(R.string.pref_fakekey_sendtocgeo_website, "send2.cgeo.org"); } @@ -466,20 +469,14 @@ public class SettingsActivity extends PreferenceActivity { public boolean onPreferenceChange(final Preference preference, final Object value) { String stringValue = value.toString(); - if (preference instanceof EditPasswordPreference) { - if (StringUtils.isBlank((String) value)) { - preference.setSummary(StringUtils.EMPTY); - } else { - preference.setSummary(StringUtils.repeat("\u2022 ", 10)); - } - } else if (isPreference(preference, R.string.pref_mapsource)) { + if (isPreference(preference, R.string.pref_mapsource)) { // reset the cached map source MapSource mapSource; try { - final int mapSourceId = Integer.valueOf(stringValue); + final int mapSourceId = Integer.parseInt(stringValue); mapSource = MapProviderFactory.getMapSource(mapSourceId); } catch (final NumberFormatException e) { - Log.e("SettingsActivity.onPreferenceChange: bad source id `" + stringValue + "'"); + Log.e("SettingsActivity.onPreferenceChange: bad source id '" + stringValue + "'"); mapSource = null; } // If there is no corresponding map source (because some map sources were @@ -495,7 +492,7 @@ public class SettingsActivity extends PreferenceActivity { } Settings.setMapSource(mapSource); preference.setSummary(mapSource.getName()); - } else if (isPreference(preference, R.string.pref_connectorOCActive) || isPreference(preference, R.string.pref_connectorOCPLActive) || isPreference(preference, R.string.pref_connectorGCActive)) { + } else if (isPreference(preference, R.string.pref_connectorOCActive) || isPreference(preference, R.string.pref_connectorOCPLActive) || isPreference(preference, R.string.pref_connectorGCActive) || isPreference(preference, R.string.pref_connectorECActive)) { // // reset log-in status if connector activation was changed CgeoApplication.getInstance().checkLogin = true; } else if (preference instanceof ListPreference) { @@ -523,10 +520,10 @@ public class SettingsActivity extends PreferenceActivity { // simple string representation. preference.setSummary(stringValue); } - if ((isPreference(preference, R.string.pref_username) && !stringValue.equals(Settings.getUsername())) || (isPreference(preference, R.string.pref_password) && !stringValue.equals(Settings.getGcLogin().getRight()))) { + if ((isPreference(preference, R.string.pref_username) && !stringValue.equals(Settings.getUsername())) || (isPreference(preference, R.string.pref_password) && !stringValue.equals(Settings.getGcCredentials().getRight()))) { // reset log-in if gc user or password is changed - if (Login.isActualLoginStatus()) { - Login.logout(); + if (GCLogin.getInstance().isActualLoginStatus()) { + GCLogin.getInstance().logout(); } CgeoApplication.getInstance().checkLogin = true; } diff --git a/main/src/cgeo/geocaching/settings/TemplateTextPreference.java b/main/src/cgeo/geocaching/settings/TemplateTextPreference.java index 9eaaa67..a703231 100644 --- a/main/src/cgeo/geocaching/settings/TemplateTextPreference.java +++ b/main/src/cgeo/geocaching/settings/TemplateTextPreference.java @@ -48,7 +48,7 @@ public class TemplateTextPreference extends DialogPreference { settingsActivity = (SettingsActivity) this.getContext(); editText = (EditText) view.findViewById(R.id.signature_dialog_text); - editText.setText(getPersistedString(initialValue != null ? initialValue.toString() : StringUtils.EMPTY)); + editText.setText(getPersistedString(initialValue != null ? initialValue : StringUtils.EMPTY)); Button button = (Button) view.findViewById(R.id.signature_templates); button.setOnClickListener(new View.OnClickListener() { diff --git a/main/src/cgeo/geocaching/settings/TextPreference.java b/main/src/cgeo/geocaching/settings/TextPreference.java index bcd03ff..eecf4cc 100644 --- a/main/src/cgeo/geocaching/settings/TextPreference.java +++ b/main/src/cgeo/geocaching/settings/TextPreference.java @@ -4,21 +4,26 @@ import cgeo.geocaching.R; import android.content.Context; import android.content.res.TypedArray; -import android.preference.Preference; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; /** - * Preference to simply show a text message. + * Preference to simply show a text message. Links are not shown. * <p> - * Links are not shown - I tried everything (koem) - * <p> - * example: <cgeo.geocaching.settings.TextPreference android:text="@string/legal_note" - * android:layout="@string/text_preference_default_layout" /> + * Usage: The displayed text is taken from the "android:text" attribute of the preference definition. Example: + * + * <pre> + * <cgeo.geocaching.settings.TextPreference + * android:text="@string/legal_note" + * android:layout="@string/text_preference_default_layout" + * /> + * </pre> + * + * </p> */ -public class TextPreference extends Preference { +public class TextPreference extends AbstractAttributeBasedPrefence { private String text; private TextView summaryView; @@ -30,23 +35,20 @@ public class TextPreference extends Preference { public TextPreference(Context context, AttributeSet attrs) { super(context, attrs); - processAttributes(context, attrs, 0); } public TextPreference(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - processAttributes(context, attrs, defStyle); } - private void processAttributes(Context context, AttributeSet attrs, int defStyle) { - if (attrs == null) { - return; - } + @Override + protected int[] getAttributeNames() { + return new int[] { android.R.attr.text }; + } - TypedArray types = context.obtainStyledAttributes(attrs, new int[] { - android.R.attr.text }, defStyle, 0); - this.text = types.getString(0); - types.recycle(); + @Override + protected void processAttributeValues(TypedArray values) { + this.text = values.getString(0); } @Override @@ -67,20 +69,21 @@ public class TextPreference extends Preference { @Override public void setSummary(CharSequence summaryText) { // the layout hasn't been inflated yet, save the summaryText for later use - if (this.summaryView == null) { + if (summaryView == null) { this.summaryText = summaryText; return; } - // if summaryText is null, take it from the previous saved summary + // if summaryText is null, take it from the previously saved summary if (summaryText == null) { if (this.summaryText == null) { return; } - this.summaryView.setText(this.summaryText); + summaryView.setText(this.summaryText); } else { - this.summaryView.setText(summaryText); + summaryView.setText(summaryText); } this.summaryView.setVisibility(View.VISIBLE); } + } diff --git a/main/src/cgeo/geocaching/settings/WpThresholdPreference.java b/main/src/cgeo/geocaching/settings/WpThresholdPreference.java index 867714f..4c43acf 100644 --- a/main/src/cgeo/geocaching/settings/WpThresholdPreference.java +++ b/main/src/cgeo/geocaching/settings/WpThresholdPreference.java @@ -1,7 +1,6 @@ package cgeo.geocaching.settings; import cgeo.geocaching.R; -import cgeo.geocaching.settings.Settings; import android.content.Context; import android.preference.Preference; @@ -14,7 +13,7 @@ import android.widget.TextView; public class WpThresholdPreference extends Preference { - TextView valueView; + private TextView valueView; public WpThresholdPreference(Context context) { super(context); diff --git a/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java b/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java index 2dee713..a1c04a4 100644 --- a/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java +++ b/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java @@ -45,4 +45,5 @@ public abstract class AbstractCacheComparator implements CacheComparator { * cache2. */ protected abstract int compareCaches(final Geocache cache1, final Geocache cache2); + } diff --git a/main/src/cgeo/geocaching/sorting/FindsComparator.java b/main/src/cgeo/geocaching/sorting/FindsComparator.java index b147fad..c889776 100644 --- a/main/src/cgeo/geocaching/sorting/FindsComparator.java +++ b/main/src/cgeo/geocaching/sorting/FindsComparator.java @@ -1,8 +1,6 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.DataStore; import cgeo.geocaching.Geocache; -import cgeo.geocaching.enumerations.LogType; public class FindsComparator extends AbstractCacheComparator { @@ -13,20 +11,9 @@ public class FindsComparator extends AbstractCacheComparator { @Override protected int compareCaches(Geocache cache1, Geocache cache2) { - int finds1 = getFindsCount(cache1); - int finds2 = getFindsCount(cache2); + int finds1 = cache1.getFindsCount(); + int finds2 = cache2.getFindsCount(); return finds2 - finds1; } - private static int getFindsCount(Geocache cache) { - if (cache.getLogCounts().isEmpty()) { - cache.setLogCounts(DataStore.loadLogCounts(cache.getGeocode())); - } - Integer logged = cache.getLogCounts().get(LogType.FOUND_IT); - if (logged != null) { - return logged; - } - return 0; - } - } diff --git a/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java b/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java index b5edf17..f438762 100644 --- a/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java +++ b/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java @@ -3,9 +3,7 @@ */ package cgeo.geocaching.sorting; -import cgeo.geocaching.DataStore; import cgeo.geocaching.Geocache; -import cgeo.geocaching.enumerations.LogType; /** * sorts caches by popularity ratio (favorites per find in %). @@ -23,8 +21,8 @@ public class PopularityRatioComparator extends AbstractCacheComparator { float ratio1 = 0.0f; float ratio2 = 0.0f; - int finds1 = getFindsCount(cache1); - int finds2 = getFindsCount(cache2); + int finds1 = cache1.getFindsCount(); + int finds2 = cache2.getFindsCount(); if (finds1 != 0) { ratio1 = (((float) cache1.getFavoritePoints()) / ((float) finds1)); @@ -41,15 +39,4 @@ public class PopularityRatioComparator extends AbstractCacheComparator { return 0; } - - private static int getFindsCount(Geocache cache) { - if (cache.getLogCounts().isEmpty()) { - cache.setLogCounts(DataStore.loadLogCounts(cache.getGeocode())); - } - Integer logged = cache.getLogCounts().get(LogType.FOUND_IT); - if (logged != null) { - return logged; - } - return 0; - } } diff --git a/main/src/cgeo/geocaching/ui/AbstractCachingListViewPageViewCreator.java b/main/src/cgeo/geocaching/ui/AbstractCachingListViewPageViewCreator.java new file mode 100644 index 0000000..799b695 --- /dev/null +++ b/main/src/cgeo/geocaching/ui/AbstractCachingListViewPageViewCreator.java @@ -0,0 +1,57 @@ +package cgeo.geocaching.ui; + +import cgeo.geocaching.activity.AbstractViewPagerActivity.PageViewCreator; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import android.os.Bundle; +import android.support.v4.view.ViewPager; +import android.view.View; +import android.widget.ListView; + +/** + * {@link PageViewCreator} for {@link ListView}, which can save scroll state on purging a page from the + * {@link ViewPager}, and restore the state on re-recreation. + * + */ +public abstract class AbstractCachingListViewPageViewCreator extends AbstractCachingPageViewCreator<ListView> { + private static final String STATE_POSITION_FROM_TOP = "positionFromTop"; + private static final String STATE_POSITION = "position"; + + /** + * Get the state of the current view + * + * @return the state encapsulated in a bundle + */ + @Override + public @Nullable + Bundle getViewState() { + if (view == null) { + return null; + } + int position = view.getFirstVisiblePosition(); + View child = view.getChildAt(0); + int positionFromTop = (child == null) ? 0 : child.getTop(); + Bundle state = new Bundle(); + state.putInt(STATE_POSITION, position); + state.putInt(STATE_POSITION_FROM_TOP, positionFromTop); + return state; + } + + /** + * Restore a previously stored state of the view + * + */ + @Override + public void setViewState(@NonNull Bundle state) { + if (view == null) { + return; + } + int logViewPosition = state.getInt(STATE_POSITION); + int logViewPositionFromTop = state.getInt(STATE_POSITION_FROM_TOP); + view.setSelectionFromTop(logViewPosition, logViewPositionFromTop); + return; + } + +} diff --git a/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java b/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java index 333ef11..ed5d182 100644 --- a/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java +++ b/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java @@ -2,6 +2,12 @@ package cgeo.geocaching.ui; import cgeo.geocaching.activity.AbstractViewPagerActivity.PageViewCreator; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import android.os.Bundle; import android.view.View; /** @@ -28,5 +34,25 @@ public abstract class AbstractCachingPageViewCreator<ViewClass extends View> imp } @Override + @SuppressFBWarnings("USM_USELESS_ABSTRACT_METHOD") public abstract ViewClass getDispatchedView(); + + /** + * Gets the state of the view but returns an empty state if not overridden + * + * @return empty bundle + */ + @Override + public @Nullable + Bundle getViewState() { + return new Bundle(); + } + + /** + * Restores the state of the view but just returns if not overridden. + */ + @Override + public void setViewState(@NonNull Bundle state) { + return; + } } diff --git a/main/src/cgeo/geocaching/ui/AddressListAdapter.java b/main/src/cgeo/geocaching/ui/AddressListAdapter.java index 0d5fba7..8134235 100644 --- a/main/src/cgeo/geocaching/ui/AddressListAdapter.java +++ b/main/src/cgeo/geocaching/ui/AddressListAdapter.java @@ -11,7 +11,6 @@ import cgeo.geocaching.geopoint.Units; import org.apache.commons.lang3.StringUtils; import android.app.Activity; -import android.content.Context; import android.location.Address; import android.view.LayoutInflater; import android.view.View; @@ -35,9 +34,9 @@ public class AddressListAdapter extends ArrayAdapter<Address> { } } - public AddressListAdapter(final Context context) { + public AddressListAdapter(final Activity context) { super(context, 0); - inflater = ((Activity) context).getLayoutInflater(); + inflater = context.getLayoutInflater(); location = CgeoApplication.getInstance().currentGeo().getCoords(); } diff --git a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java index f1cee05..7fe77c4 100644 --- a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java +++ b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java @@ -4,6 +4,7 @@ import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.Geocache; import cgeo.geocaching.R; import cgeo.geocaching.Waypoint; +import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Units; @@ -53,24 +54,28 @@ public final class CacheDetailsCreator { } public RelativeLayout addStars(final int nameId, final float value) { + return addStars(nameId, value, 5); + } + + public RelativeLayout addStars(final int nameId, final float value, final int max) { final RelativeLayout layout = (RelativeLayout) activity.getLayoutInflater().inflate(R.layout.cache_information_item, null); final TextView nameView = (TextView) layout.findViewById(R.id.name); lastValueView = (TextView) layout.findViewById(R.id.value); final LinearLayout layoutStars = (LinearLayout) layout.findViewById(R.id.stars); nameView.setText(activity.getResources().getString(nameId)); - lastValueView.setText(String.format("%.1f", value) + ' ' + activity.getResources().getString(R.string.cache_rating_of) + " 5"); - createStarImages(layoutStars, value); + lastValueView.setText(String.format("%.1f", value) + ' ' + activity.getResources().getString(R.string.cache_rating_of) + " " + String.format("%d", max)); + createStarImages(layoutStars, value, max); layoutStars.setVisibility(View.VISIBLE); parentView.addView(layout); return layout; } - private void createStarImages(final ViewGroup starsContainer, final float value) { + private void createStarImages(final ViewGroup starsContainer, final float value, final int max) { final LayoutInflater inflater = LayoutInflater.from(activity); - for (int i = 0; i < 5; i++) { + for (int i = 0; i < max; i++) { ImageView star = (ImageView) inflater.inflate(R.layout.star_image, null); if (value - i >= 0.75) { star.setImageResource(R.drawable.star_on); @@ -130,7 +135,7 @@ public final class CacheDetailsCreator { public void addTerrain(Geocache cache) { if (cache.getTerrain() > 0) { - addStars(R.string.cache_terrain, cache.getTerrain()); + addStars(R.string.cache_terrain, cache.getTerrain(), ConnectorFactory.getConnector(cache).getMaxTerrain()); } } diff --git a/main/src/cgeo/geocaching/ui/CompassView.java b/main/src/cgeo/geocaching/ui/CompassView.java index 3b4ed36..5e80f32 100644 --- a/main/src/cgeo/geocaching/ui/CompassView.java +++ b/main/src/cgeo/geocaching/ui/CompassView.java @@ -80,7 +80,9 @@ public class CompassView extends View implements PeriodicHandlerListener { setfil = new PaintFlagsDrawFilter(0, Paint.FILTER_BITMAP_FLAG); remfil = new PaintFlagsDrawFilter(Paint.FILTER_BITMAP_FLAG, 0); - initialDisplay = true; + synchronized (this) { + initialDisplay = true; + } redrawHandler.start(); } @@ -148,7 +150,7 @@ public class CompassView extends View implements PeriodicHandlerListener { } @Override - public void onPeriodic() { + public synchronized void onPeriodic() { final float newAzimuthShown = smoothUpdate(northMeasured, azimuthShown); final float newCacheHeadingShown = smoothUpdate(cacheHeadingMeasured, cacheHeadingShown); if (Math.abs(AngleUtils.difference(azimuthShown, newAzimuthShown)) >= 2 || diff --git a/main/src/cgeo/geocaching/ui/GPXListAdapter.java b/main/src/cgeo/geocaching/ui/GPXListAdapter.java index 988bb81..ae18ab4 100644 --- a/main/src/cgeo/geocaching/ui/GPXListAdapter.java +++ b/main/src/cgeo/geocaching/ui/GPXListAdapter.java @@ -5,11 +5,11 @@ import butterknife.InjectView; import cgeo.geocaching.GpxFileListActivity; import cgeo.geocaching.R; import cgeo.geocaching.files.GPXImporter; +import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.FileUtils; import cgeo.geocaching.utils.Log; import android.app.Activity; -import android.app.AlertDialog; import android.content.DialogInterface; import android.view.LayoutInflater; import android.view.View; @@ -74,25 +74,13 @@ public class GPXListAdapter extends ArrayAdapter<File> { @Override public boolean onLongClick(View v) { - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setTitle(R.string.gpx_import_delete_title) - .setMessage(activity.getString(R.string.gpx_import_delete_message, file.getName())) - .setCancelable(false) - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - FileUtils.deleteIgnoringFailure(file); - GPXListAdapter.this.remove(file); - } - }) - .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - AlertDialog alert = builder.create(); - alert.show(); + Dialogs.confirmYesNo(activity, R.string.gpx_import_delete_title, activity.getString(R.string.gpx_import_delete_message, file.getName()), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + FileUtils.deleteIgnoringFailure(file); + GPXListAdapter.this.remove(file); + } + }); return true; } }); diff --git a/main/src/cgeo/geocaching/ui/UrlPopup.java b/main/src/cgeo/geocaching/ui/UrlPopup.java new file mode 100644 index 0000000..5a8dba4 --- /dev/null +++ b/main/src/cgeo/geocaching/ui/UrlPopup.java @@ -0,0 +1,40 @@ +package cgeo.geocaching.ui; + +import cgeo.geocaching.R; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; + +public class UrlPopup { + + private final Context context; + + public UrlPopup(final Context context) { + this.context = context; + } + + public void show(final String title, final String message, final String url, final String urlButtonTitle) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setMessage(message) + .setIcon(android.R.drawable.ic_dialog_info) + .setTitle(title) + .setPositiveButton(R.string.err_none, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }) + .setNegativeButton(urlButtonTitle, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse(url)); + context.startActivity(i); + } + }); + builder.create().show(); + } +} diff --git a/main/src/cgeo/geocaching/ui/dialog/Dialogs.java b/main/src/cgeo/geocaching/ui/dialog/Dialogs.java new file mode 100644 index 0000000..6064c41 --- /dev/null +++ b/main/src/cgeo/geocaching/ui/dialog/Dialogs.java @@ -0,0 +1,291 @@ +package cgeo.geocaching.ui.dialog; + +import cgeo.geocaching.CgeoApplication; + +import org.eclipse.jdt.annotation.Nullable; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.content.DialogInterface.OnClickListener; +import android.graphics.drawable.Drawable; + +/** + * Wrapper for {@link AlertDialog}. If you want to show a simple text, use one of the + * {@link #message(Activity, String, String, Drawable)} variants. If you want the user to confirm using Okay/Cancel or + * Yes/No, select one of the {@link #confirm(Activity, String, String, String, OnClickListener)} or + * {@link #confirmYesNo(Activity, String, String, OnClickListener)} variants. + * + */ +public final class Dialogs { + private Dialogs() { + // utility class + } + + /** + * Confirm using two buttons "yourText" and "Cancel", where "Cancel" just closes the dialog. + * + * @param context + * activity hosting the dialog + * @param title + * dialog title + * @param msg + * dialog message + * @param positiveButton + * text of the positive button (which would typically be the OK button) + * @param okayListener + * listener of the positive button + */ + public static AlertDialog.Builder confirm(final Activity context, final String title, final String msg, final String positiveButton, final OnClickListener okayListener) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + AlertDialog dialog = builder.setTitle(title) + .setCancelable(true) + .setMessage(msg) + .setPositiveButton(positiveButton, okayListener) + .setNegativeButton(android.R.string.cancel, null) + .create(); + dialog.setOwnerActivity(context); + dialog.show(); + return builder; + } + + /** + * Confirm using two buttons "yourText" and "Cancel", where "Cancel" just closes the dialog. + * + * @param context + * activity hosting the dialog + * @param title + * dialog title + * @param msg + * dialog message + * @param positiveButton + * text of the positive button (which would typically be the OK button) + * @param okayListener + * listener of the positive button + */ + public static AlertDialog.Builder confirm(final Activity context, final int title, final int msg, final int positiveButton, final OnClickListener okayListener) { + return confirm(context, getString(title), getString(msg), getString(positiveButton), okayListener); + } + + /** + * Confirm using two buttons "Yes" and "No", where "No" just closes the dialog. + * + * @param context + * activity hosting the dialog + * @param title + * dialog title + * @param msg + * dialog message + * @param yesListener + * listener of the positive button + */ + public static AlertDialog.Builder confirmYesNo(final Activity context, final String title, final String msg, final OnClickListener yesListener) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + AlertDialog dialog = builder.setTitle(title) + .setCancelable(true) + .setMessage(msg) + .setPositiveButton(android.R.string.yes, yesListener) + .setNegativeButton(android.R.string.no, null) + .create(); + dialog.setOwnerActivity(context); + dialog.show(); + return builder; + } + + /** + * Confirm using two buttons "Yes" and "No", where "No" just closes the dialog. + * + * @param context + * activity hosting the dialog + * @param title + * dialog title + * @param msg + * dialog message + * @param yesListener + * listener of the positive button + */ + public static AlertDialog.Builder confirmYesNo(final Activity context, final String title, final int msg, final OnClickListener yesListener) { + return confirmYesNo(context, title, getString(msg), yesListener); + } + + /** + * Confirm using two buttons "Yes" and "No", where "No" just closes the dialog. + * + * @param context + * activity hosting the dialog + * @param title + * dialog title + * @param msg + * dialog message + * @param yesListener + * listener of the positive button + */ + public static AlertDialog.Builder confirmYesNo(final Activity context, final int title, final String msg, final OnClickListener yesListener) { + return confirmYesNo(context, getString(title), msg, yesListener); + } + + /** + * Confirm using two buttons "Yes" and "No", where "No" just closes the dialog. + * + * @param context + * activity hosting the dialog + * @param title + * dialog title + * @param msg + * dialog message + * @param yesListener + * listener of the positive button + */ + public static AlertDialog.Builder confirmYesNo(final Activity context, final int title, final int msg, final OnClickListener yesListener) { + return confirmYesNo(context, getString(title), getString(msg), yesListener); + } + + /** + * Confirm using two buttons "OK" and "Cancel", where "Cancel" just closes the dialog. + * + * @param context + * activity hosting the dialog + * @param title + * dialog title + * @param msg + * dialog message + * @param okayListener + * listener of the positive button + */ + public static AlertDialog.Builder confirm(final Activity context, final String title, final String msg, final OnClickListener okayListener) { + return confirm(context, title, msg, getString(android.R.string.ok), okayListener); + } + + /** + * Confirm using two buttons "OK" and "Cancel", where "Cancel" just closes the dialog. + * + * @param context + * activity hosting the dialog + * @param title + * dialog title + * @param msg + * dialog message + * @param okayListener + * listener of the positive button + */ + public static AlertDialog.Builder confirm(final Activity context, final int title, final String msg, final OnClickListener okayListener) { + return confirm(context, getString(title), msg, okayListener); + } + + /** + * Confirm using two buttons "OK" and "Cancel", where "Cancel" just closes the dialog. + * + * @param context + * activity hosting the dialog + * @param title + * dialog title + * @param msg + * dialog message + * @param okayListener + * listener of the positive button + */ + public static AlertDialog.Builder confirm(final Activity context, final int title, final int msg, final OnClickListener okayListener) { + return confirm(context, getString(title), getString(msg), okayListener); + } + + private static String getString(int resourceId) { + return CgeoApplication.getInstance().getString(resourceId); + } + + /** + * Show a message dialog with a single "OK" button. + * + * @param context + * activity owning the dialog + * @param message + * message dialog content + */ + public static void message(final Activity context, final String message) { + message(context, null, message); + } + + /** + * Show a message dialog with a single "OK" button. + * + * @param context + * activity owning the dialog + * @param title + * message dialog title + * @param message + * message dialog content + */ + public static void message(final Activity context, final @Nullable String title, final String message) { + message(context, title, message, null); + } + + /** + * Show a message dialog with a single "OK" button and an icon. + * + * @param context + * activity owning the dialog + * @param title + * message dialog title + * @param message + * message dialog content + * @param icon + * message dialog title icon + */ + public static void message(final Activity context, final @Nullable String title, final String message, final @Nullable Drawable icon) { + Builder builder = new AlertDialog.Builder(context) + .setMessage(message) + .setCancelable(true) + .setPositiveButton(getString(android.R.string.ok), null); + if (title != null) { + builder.setTitle(title); + } + if (icon != null) { + builder.setIcon(icon); + } + builder.create().show(); + } + + /** + * Show a message dialog with a single "OK" button and an icon. + * + * @param context + * activity owning the dialog + * @param title + * message dialog title + * @param message + * message dialog content + */ + public static void message(final Activity context, final int title, final String message) { + message(context, getString(title), message); + } + + /** + * Show a message dialog with a single "OK" button and an icon. + * + * @param context + * activity owning the dialog + * @param title + * message dialog title + * @param message + * message dialog content + */ + public static void message(final Activity context, final int title, final int message) { + message(context, getString(title), getString(message)); + } + + /** + * Show a message dialog with a single "OK" button and an icon. + * + * @param context + * activity owning the dialog + * @param title + * message dialog title + * @param message + * message dialog content + * @param icon + * message dialog title icon + */ + public static void message(final Activity context, final int title, final int message, final @Nullable Drawable icon) { + message(context, getString(title), getString(message), icon); + } + +} diff --git a/main/src/cgeo/geocaching/ui/dialog/NoTitleDialog.java b/main/src/cgeo/geocaching/ui/dialog/NoTitleDialog.java index fc5ebe6..8660a7b 100644 --- a/main/src/cgeo/geocaching/ui/dialog/NoTitleDialog.java +++ b/main/src/cgeo/geocaching/ui/dialog/NoTitleDialog.java @@ -1,5 +1,7 @@ package cgeo.geocaching.ui.dialog; +import cgeo.geocaching.utils.Log; + import android.app.Dialog; import android.content.Context; import android.os.Bundle; @@ -24,7 +26,7 @@ public abstract class NoTitleDialog extends Dialog { requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); } catch (final Exception e) { - // nothing + Log.e("NoTitleDialog.onCreate", e); } } } diff --git a/main/src/cgeo/geocaching/ui/logs/CacheLogsViewCreator.java b/main/src/cgeo/geocaching/ui/logs/CacheLogsViewCreator.java index 8fe3866..6311476 100644 --- a/main/src/cgeo/geocaching/ui/logs/CacheLogsViewCreator.java +++ b/main/src/cgeo/geocaching/ui/logs/CacheLogsViewCreator.java @@ -1,10 +1,10 @@ package cgeo.geocaching.ui.logs; import cgeo.geocaching.CacheDetailActivity; +import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.Geocache; import cgeo.geocaching.LogEntry; import cgeo.geocaching.R; -import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.ui.UserActionsClickListener; @@ -109,4 +109,4 @@ public class CacheLogsViewCreator extends LogsViewCreator { return new UserActionsClickListener(getCache()); } -}
\ No newline at end of file +} diff --git a/main/src/cgeo/geocaching/ui/logs/LogsViewCreator.java b/main/src/cgeo/geocaching/ui/logs/LogsViewCreator.java index 15634d3..fb72ee5 100644 --- a/main/src/cgeo/geocaching/ui/logs/LogsViewCreator.java +++ b/main/src/cgeo/geocaching/ui/logs/LogsViewCreator.java @@ -8,7 +8,7 @@ import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.activity.Progress; import cgeo.geocaching.list.StoredList; import cgeo.geocaching.network.HtmlImage; -import cgeo.geocaching.ui.AbstractCachingPageViewCreator; +import cgeo.geocaching.ui.AbstractCachingListViewPageViewCreator; import cgeo.geocaching.ui.AnchorAwareLinkMovementMethod; import cgeo.geocaching.ui.DecryptTextClickListener; import cgeo.geocaching.ui.Formatter; @@ -30,7 +30,7 @@ import android.widget.TextView; import java.util.ArrayList; import java.util.List; -public abstract class LogsViewCreator extends AbstractCachingPageViewCreator<ListView> { +public abstract class LogsViewCreator extends AbstractCachingListViewPageViewCreator { protected final AbstractActivity activity; diff --git a/main/src/cgeo/geocaching/utils/DatabaseBackupUtils.java b/main/src/cgeo/geocaching/utils/DatabaseBackupUtils.java index b291a8a..4ce2e0c 100644 --- a/main/src/cgeo/geocaching/utils/DatabaseBackupUtils.java +++ b/main/src/cgeo/geocaching/utils/DatabaseBackupUtils.java @@ -3,8 +3,8 @@ package cgeo.geocaching.utils; import cgeo.geocaching.DataStore; import cgeo.geocaching.MainActivity; import cgeo.geocaching.R; -import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.ui.Formatter; +import cgeo.geocaching.ui.dialog.Dialogs; import org.apache.commons.lang3.StringUtils; @@ -42,7 +42,7 @@ public class DatabaseBackupUtils { dialog.dismiss(); boolean restored = restoreSuccessful.get(); String message = restored ? res.getString(R.string.init_restore_success) : res.getString(R.string.init_restore_failed); - ActivityMixin.helpDialog(activity, res.getString(R.string.init_backup_restore), message); + Dialogs.message(activity, R.string.init_backup_restore, message); if (activity instanceof MainActivity) { ((MainActivity) activity).updateCacheCounter(); } @@ -57,9 +57,7 @@ public class DatabaseBackupUtils { // avoid overwriting an existing backup with an empty database // (can happen directly after reinstalling the app) if (DataStore.getAllCachesCount() == 0) { - ActivityMixin.helpDialog(activity, - context.getString(R.string.init_backup), - context.getString(R.string.init_backup_unnecessary)); + Dialogs.message(activity, R.string.init_backup, R.string.init_backup_unnecessary); return false; } @@ -74,8 +72,8 @@ public class DatabaseBackupUtils { @Override public void run() { dialog.dismiss(); - ActivityMixin.helpDialog(activity, - context.getString(R.string.init_backup_backup), + Dialogs.message(activity, + R.string.init_backup_backup, backupFileName != null ? context.getString(R.string.init_backup_success) + "\n" + backupFileName diff --git a/main/src/cgeo/geocaching/utils/MatcherWrapper.java b/main/src/cgeo/geocaching/utils/MatcherWrapper.java index 78b1170..c99d3c4 100644 --- a/main/src/cgeo/geocaching/utils/MatcherWrapper.java +++ b/main/src/cgeo/geocaching/utils/MatcherWrapper.java @@ -1,5 +1,7 @@ package cgeo.geocaching.utils; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -44,6 +46,7 @@ public class MatcherWrapper { * @param input * @return */ + @SuppressFBWarnings("DM_STRING_CTOR") private static String newString(String input) { if (input == null) { return null; diff --git a/main/src/cgeo/geocaching/utils/TextUtils.java b/main/src/cgeo/geocaching/utils/TextUtils.java index 14caf1d..efbb2d7 100644 --- a/main/src/cgeo/geocaching/utils/TextUtils.java +++ b/main/src/cgeo/geocaching/utils/TextUtils.java @@ -3,6 +3,8 @@ */ package cgeo.geocaching.utils; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import org.eclipse.jdt.annotation.Nullable; import java.util.regex.Matcher; @@ -36,6 +38,7 @@ public final class TextUtils { * Find the last occurring value * @return defaultValue or the n-th group if the pattern matches (trimmed if wanted) */ + @SuppressFBWarnings("DM_STRING_CTOR") public static String getMatch(@Nullable final String data, final Pattern p, final boolean trim, final int group, final String defaultValue, final boolean last) { if (data != null) { |
