aboutsummaryrefslogtreecommitdiffstats
path: root/main/src
diff options
context:
space:
mode:
Diffstat (limited to 'main/src')
-rw-r--r--main/src/cgeo/calendar/CalendarAddon.java58
-rw-r--r--main/src/cgeo/calendar/ICalendar.java2
-rw-r--r--main/src/cgeo/contacts/IContacts.java2
-rw-r--r--main/src/cgeo/geocaching/AbstractPopupActivity.java2
-rw-r--r--main/src/cgeo/geocaching/CacheDetailActivity.java450
-rw-r--r--main/src/cgeo/geocaching/CacheListActivity.java289
-rw-r--r--main/src/cgeo/geocaching/CacheMenuHandler.java55
-rw-r--r--main/src/cgeo/geocaching/CachePopup.java6
-rw-r--r--main/src/cgeo/geocaching/CgeoApplication.java76
-rw-r--r--main/src/cgeo/geocaching/CompassActivity.java5
-rw-r--r--main/src/cgeo/geocaching/CreateShortcutActivity.java10
-rw-r--r--main/src/cgeo/geocaching/DataStore.java182
-rw-r--r--main/src/cgeo/geocaching/DirectionProvider.java92
-rw-r--r--main/src/cgeo/geocaching/EditWaypointActivity.java10
-rw-r--r--main/src/cgeo/geocaching/GeoDataProvider.java148
-rw-r--r--main/src/cgeo/geocaching/Geocache.java47
-rw-r--r--main/src/cgeo/geocaching/ImageSelectActivity.java3
-rw-r--r--main/src/cgeo/geocaching/ImagesActivity.java9
-rw-r--r--main/src/cgeo/geocaching/Intents.java4
-rw-r--r--main/src/cgeo/geocaching/LogCacheActivity.java8
-rw-r--r--main/src/cgeo/geocaching/LogTrackableActivity.java4
-rw-r--r--main/src/cgeo/geocaching/MainActivity.java210
-rw-r--r--main/src/cgeo/geocaching/PocketQueryList.java134
-rw-r--r--main/src/cgeo/geocaching/SearchActivity.java80
-rw-r--r--main/src/cgeo/geocaching/SearchResult.java2
-rw-r--r--main/src/cgeo/geocaching/StaticMapsActivity.java9
-rw-r--r--main/src/cgeo/geocaching/StaticMapsProvider.java4
-rw-r--r--main/src/cgeo/geocaching/StatusFragment.java129
-rw-r--r--main/src/cgeo/geocaching/TrackableActivity.java46
-rw-r--r--main/src/cgeo/geocaching/UsefulAppsActivity.java1
-rw-r--r--main/src/cgeo/geocaching/activity/AbstractActivity.java59
-rw-r--r--main/src/cgeo/geocaching/activity/AbstractListActivity.java23
-rw-r--r--main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java1
-rw-r--r--main/src/cgeo/geocaching/activity/ActivityMixin.java2
-rw-r--r--main/src/cgeo/geocaching/activity/Keyboard.java40
-rw-r--r--main/src/cgeo/geocaching/activity/Progress.java17
-rw-r--r--main/src/cgeo/geocaching/apps/AbstractLocusApp.java7
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/AbstractPointNavigationApp.java14
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java45
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsApp.java22
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java28
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java12
-rw-r--r--main/src/cgeo/geocaching/apps/cachelist/CacheListApp.java2
-rw-r--r--main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java3
-rw-r--r--main/src/cgeo/geocaching/connector/AbstractConnector.java16
-rw-r--r--main/src/cgeo/geocaching/connector/ConnectorFactory.java41
-rw-r--r--main/src/cgeo/geocaching/connector/UserAction.java10
-rw-r--r--main/src/cgeo/geocaching/connector/capability/FieldNotesCapability.java13
-rw-r--r--main/src/cgeo/geocaching/connector/ec/ECApi.java4
-rw-r--r--main/src/cgeo/geocaching/connector/ec/ECConnector.java2
-rw-r--r--main/src/cgeo/geocaching/connector/ec/ECLogin.java1
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCConnector.java73
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCConstants.java10
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCLogin.java15
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCMap.java2
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCParser.java79
-rw-r--r--main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java114
-rw-r--r--main/src/cgeo/geocaching/connector/gc/SearchHandler.java126
-rw-r--r--main/src/cgeo/geocaching/connector/gc/Tile.java5
-rw-r--r--main/src/cgeo/geocaching/connector/oc/IOCAuthParams.java4
-rw-r--r--main/src/cgeo/geocaching/connector/oc/OCApiConnector.java12
-rw-r--r--main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java6
-rw-r--r--main/src/cgeo/geocaching/connector/oc/OCAuthorizationActivity.java16
-rw-r--r--main/src/cgeo/geocaching/connector/oc/OkapiClient.java100
-rw-r--r--main/src/cgeo/geocaching/connector/ox/OXGPXParser.java4
-rw-r--r--main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java1
-rw-r--r--main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java4
-rw-r--r--main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java27
-rw-r--r--main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java8
-rw-r--r--main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java4
-rw-r--r--main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java17
-rw-r--r--main/src/cgeo/geocaching/enumerations/CacheAttribute.java8
-rw-r--r--main/src/cgeo/geocaching/enumerations/CacheListType.java31
-rw-r--r--main/src/cgeo/geocaching/enumerations/CacheSize.java37
-rw-r--r--main/src/cgeo/geocaching/enumerations/CacheType.java2
-rw-r--r--main/src/cgeo/geocaching/enumerations/LiveMapStrategy.java2
-rw-r--r--main/src/cgeo/geocaching/enumerations/LogType.java15
-rw-r--r--main/src/cgeo/geocaching/enumerations/LogTypeTrackable.java23
-rw-r--r--main/src/cgeo/geocaching/enumerations/StatusCode.java2
-rw-r--r--main/src/cgeo/geocaching/export/FieldNotes.java65
-rw-r--r--main/src/cgeo/geocaching/export/FieldnoteExport.java129
-rw-r--r--main/src/cgeo/geocaching/export/GpxExport.java2
-rw-r--r--main/src/cgeo/geocaching/export/GpxSerializer.java4
-rw-r--r--main/src/cgeo/geocaching/files/GPXParser.java135
-rw-r--r--main/src/cgeo/geocaching/files/LocalStorage.java20
-rw-r--r--main/src/cgeo/geocaching/files/SimpleDirChooser.java4
-rw-r--r--main/src/cgeo/geocaching/filter/DistanceFilter.java2
-rw-r--r--main/src/cgeo/geocaching/filter/FilterUserInterface.java13
-rw-r--r--main/src/cgeo/geocaching/filter/ModifiedFilter.java2
-rw-r--r--main/src/cgeo/geocaching/geopoint/Geopoint.java26
-rw-r--r--main/src/cgeo/geocaching/geopoint/GeopointParser.java36
-rw-r--r--main/src/cgeo/geocaching/geopoint/Viewport.java68
-rw-r--r--main/src/cgeo/geocaching/list/PseudoList.java6
-rw-r--r--main/src/cgeo/geocaching/list/StoredList.java40
-rw-r--r--main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java35
-rw-r--r--main/src/cgeo/geocaching/loaders/AddressGeocacheListLoader.java2
-rw-r--r--main/src/cgeo/geocaching/loaders/HistoryGeocacheListLoader.java2
-rw-r--r--main/src/cgeo/geocaching/loaders/NextPageGeocacheListLoader.java2
-rw-r--r--main/src/cgeo/geocaching/loaders/OfflineGeocacheListLoader.java4
-rw-r--r--main/src/cgeo/geocaching/loaders/RecaptchaReceiver.java2
-rw-r--r--main/src/cgeo/geocaching/maps/CGeoMap.java76
-rw-r--r--main/src/cgeo/geocaching/maps/MapProviderFactory.java2
-rw-r--r--main/src/cgeo/geocaching/maps/google/GoogleCacheOverlay.java5
-rw-r--r--main/src/cgeo/geocaching/maps/google/GoogleMapProvider.java2
-rw-r--r--main/src/cgeo/geocaching/maps/google/GoogleMapView.java11
-rw-r--r--main/src/cgeo/geocaching/maps/interfaces/ItemizedOverlayImpl.java2
-rw-r--r--main/src/cgeo/geocaching/maps/interfaces/MapViewImpl.java7
-rw-r--r--main/src/cgeo/geocaching/maps/mapsforge/MapsforgeCacheOverlay.java5
-rw-r--r--main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java8
-rw-r--r--main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeCacheOverlay.java5
-rw-r--r--main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java8
-rw-r--r--main/src/cgeo/geocaching/network/HtmlImage.java293
-rw-r--r--main/src/cgeo/geocaching/network/Network.java94
-rw-r--r--main/src/cgeo/geocaching/network/OAuth.java5
-rw-r--r--main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java88
-rw-r--r--main/src/cgeo/geocaching/network/StatusUpdater.java56
-rw-r--r--main/src/cgeo/geocaching/search/AutoCompleteAdapter.java71
-rw-r--r--main/src/cgeo/geocaching/search/SuggestionProvider.java57
-rw-r--r--main/src/cgeo/geocaching/settings/AbstractCheckCredentialsPreference.java93
-rw-r--r--main/src/cgeo/geocaching/settings/AbstractClickablePreference.java30
-rw-r--r--main/src/cgeo/geocaching/settings/CheckECCredentialsPreference.java14
-rw-r--r--main/src/cgeo/geocaching/settings/CheckGcCredentialsPreference.java18
-rw-r--r--main/src/cgeo/geocaching/settings/OAuthPreference.java24
-rw-r--r--main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java118
-rw-r--r--main/src/cgeo/geocaching/settings/Settings.java18
-rw-r--r--main/src/cgeo/geocaching/settings/SettingsActivity.java110
-rw-r--r--main/src/cgeo/geocaching/settings/TemplateTextPreference.java2
-rw-r--r--main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java35
-rw-r--r--main/src/cgeo/geocaching/sorting/CacheComparator.java4
-rw-r--r--main/src/cgeo/geocaching/sorting/ComparatorUserInterface.java9
-rw-r--r--main/src/cgeo/geocaching/sorting/DateComparator.java7
-rw-r--r--main/src/cgeo/geocaching/sorting/DifficultyComparator.java4
-rw-r--r--main/src/cgeo/geocaching/sorting/DistanceComparator.java5
-rw-r--r--main/src/cgeo/geocaching/sorting/FindsComparator.java4
-rw-r--r--main/src/cgeo/geocaching/sorting/GeocodeComparator.java13
-rw-r--r--main/src/cgeo/geocaching/sorting/InventoryComparator.java5
-rw-r--r--main/src/cgeo/geocaching/sorting/NameComparator.java4
-rw-r--r--main/src/cgeo/geocaching/sorting/PopularityComparator.java5
-rw-r--r--main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java5
-rw-r--r--main/src/cgeo/geocaching/sorting/RatingComparator.java5
-rw-r--r--main/src/cgeo/geocaching/sorting/SizeComparator.java4
-rw-r--r--main/src/cgeo/geocaching/sorting/StateComparator.java5
-rw-r--r--main/src/cgeo/geocaching/sorting/StorageTimeComparator.java5
-rw-r--r--main/src/cgeo/geocaching/sorting/TerrainComparator.java4
-rw-r--r--main/src/cgeo/geocaching/sorting/VisitComparator.java5
-rw-r--r--main/src/cgeo/geocaching/sorting/VoteComparator.java5
-rw-r--r--main/src/cgeo/geocaching/speech/SpeechService.java4
-rw-r--r--main/src/cgeo/geocaching/speech/TextFactory.java2
-rw-r--r--main/src/cgeo/geocaching/twitter/Twitter.java1
-rw-r--r--main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java1
-rw-r--r--main/src/cgeo/geocaching/ui/AnchorAwareLinkMovementMethod.java2
-rw-r--r--main/src/cgeo/geocaching/ui/CacheDetailsCreator.java21
-rw-r--r--main/src/cgeo/geocaching/ui/EditNoteDialog.java15
-rw-r--r--main/src/cgeo/geocaching/ui/ImagesList.java116
-rw-r--r--main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java4
-rw-r--r--main/src/cgeo/geocaching/ui/dialog/Dialogs.java29
-rw-r--r--main/src/cgeo/geocaching/ui/dialog/LiveMapInfoDialogBuilder.java13
-rw-r--r--main/src/cgeo/geocaching/utils/CancellableHandler.java16
-rw-r--r--main/src/cgeo/geocaching/utils/CryptUtils.java3
-rw-r--r--main/src/cgeo/geocaching/utils/FileUtils.java27
-rw-r--r--main/src/cgeo/geocaching/utils/GeoDirHandler.java108
-rw-r--r--main/src/cgeo/geocaching/utils/IObserver.java22
-rw-r--r--main/src/cgeo/geocaching/utils/ISubject.java57
-rw-r--r--main/src/cgeo/geocaching/utils/ImageUtils.java2
-rw-r--r--main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java4
-rw-r--r--main/src/cgeo/geocaching/utils/LogTemplateProvider.java4
-rw-r--r--main/src/cgeo/geocaching/utils/MemorySubject.java45
-rw-r--r--main/src/cgeo/geocaching/utils/RunnableWithArgument.java7
-rw-r--r--main/src/cgeo/geocaching/utils/Subject.java76
-rw-r--r--main/src/cgeo/geocaching/utils/SynchronizedDateFormat.java11
-rw-r--r--main/src/cgeo/geocaching/utils/TextUtils.java1
171 files changed, 3026 insertions, 2705 deletions
diff --git a/main/src/cgeo/calendar/CalendarAddon.java b/main/src/cgeo/calendar/CalendarAddon.java
new file mode 100644
index 0000000..117fb9a
--- /dev/null
+++ b/main/src/cgeo/calendar/CalendarAddon.java
@@ -0,0 +1,58 @@
+package cgeo.calendar;
+
+import cgeo.geocaching.Geocache;
+import cgeo.geocaching.R;
+import cgeo.geocaching.geopoint.GeopointFormatter;
+import cgeo.geocaching.network.Parameters;
+import cgeo.geocaching.ui.dialog.Dialogs;
+import cgeo.geocaching.utils.ProcessUtils;
+
+import org.apache.commons.lang3.StringUtils;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+
+import java.util.Date;
+
+public class CalendarAddon {
+ public static boolean isAvailable() {
+ return ProcessUtils.isIntentAvailable(ICalendar.INTENT, Uri.parse(ICalendar.URI_SCHEME + "://" + ICalendar.URI_HOST));
+ }
+
+ public static void addToCalendarWithIntent(final Activity activity, final Geocache cache) {
+ final Resources res = activity.getResources();
+ if (CalendarAddon.isAvailable()) {
+ final Date hiddenDate = cache.getHiddenDate();
+ final Parameters params = new Parameters(
+ ICalendar.PARAM_NAME, cache.getName(),
+ ICalendar.PARAM_NOTE, StringUtils.defaultString(cache.getPersonalNote()),
+ ICalendar.PARAM_HIDDEN_DATE, hiddenDate != null ? String.valueOf(hiddenDate.getTime()) : StringUtils.EMPTY,
+ ICalendar.PARAM_URL, StringUtils.defaultString(cache.getUrl()),
+ ICalendar.PARAM_COORDS, cache.getCoords() == null ? "" : cache.getCoords().format(GeopointFormatter.Format.LAT_LON_DECMINUTE_RAW),
+ ICalendar.PARAM_LOCATION, StringUtils.defaultString(cache.getLocation()),
+ ICalendar.PARAM_SHORT_DESC, StringUtils.defaultString(cache.getShortDescription()),
+ ICalendar.PARAM_START_TIME_MINUTES, StringUtils.defaultString(cache.guessEventTimeMinutes())
+ );
+
+ activity.startActivity(new Intent(ICalendar.INTENT,
+ 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
+ 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/calendar/ICalendar.java b/main/src/cgeo/calendar/ICalendar.java
index 933d248..6ecb6d5 100644
--- a/main/src/cgeo/calendar/ICalendar.java
+++ b/main/src/cgeo/calendar/ICalendar.java
@@ -14,6 +14,6 @@ public interface ICalendar {
static final String PARAM_NOTE = "note"; // personal note
static final String PARAM_NAME = "name"; // cache name
static final String PARAM_LOCATION = "location"; // cache location, or empty string
- static final String PARAM_COORDS = "coords"; // cache coords, or empty string
+ static final String PARAM_COORDS = "coords"; // cache coordinates, or empty string
static final String PARAM_START_TIME_MINUTES = "time"; // time of start
}
diff --git a/main/src/cgeo/contacts/IContacts.java b/main/src/cgeo/contacts/IContacts.java
index d46a5a4..d68b78a 100644
--- a/main/src/cgeo/contacts/IContacts.java
+++ b/main/src/cgeo/contacts/IContacts.java
@@ -1,8 +1,6 @@
package cgeo.contacts;
public interface IContacts {
- static final String CALENDAR_ADDON_URI = "market://details?id=cgeo.contacts";
-
static final String INTENT = "cgeo.contacts.FIND";
static final String URI_SCHEME = "find";
diff --git a/main/src/cgeo/geocaching/AbstractPopupActivity.java b/main/src/cgeo/geocaching/AbstractPopupActivity.java
index 5f24030..38e37da 100644
--- a/main/src/cgeo/geocaching/AbstractPopupActivity.java
+++ b/main/src/cgeo/geocaching/AbstractPopupActivity.java
@@ -40,7 +40,7 @@ public abstract class AbstractPopupActivity extends AbstractActivity implements
private final GeoDirHandler geoUpdate = new GeoDirHandler() {
@Override
- protected void updateGeoData(final IGeoData geo) {
+ public void updateGeoData(final IGeoData geo) {
try {
if (geo.getCoords() != null && cache != null && cache.getCoords() != null) {
cacheDistance.setText(Units.getDistanceFromKilometers(geo.getCoords().distanceTo(cache.getCoords())));
diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java
index 4068e38..7e8ab28 100644
--- a/main/src/cgeo/geocaching/CacheDetailActivity.java
+++ b/main/src/cgeo/geocaching/CacheDetailActivity.java
@@ -3,6 +3,7 @@ package cgeo.geocaching;
import butterknife.ButterKnife;
import butterknife.InjectView;
+import cgeo.calendar.CalendarAddon;
import cgeo.geocaching.activity.AbstractActivity;
import cgeo.geocaching.activity.AbstractViewPagerActivity;
import cgeo.geocaching.activity.Progress;
@@ -38,18 +39,14 @@ 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;
import cgeo.geocaching.utils.CryptUtils;
import cgeo.geocaching.utils.GeoDirHandler;
-import cgeo.geocaching.utils.HtmlUtils;
import cgeo.geocaching.utils.ImageUtils;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.MatcherWrapper;
-import cgeo.geocaching.utils.RunnableWithArgument;
import cgeo.geocaching.utils.SimpleCancellableHandler;
import cgeo.geocaching.utils.SimpleHandler;
import cgeo.geocaching.utils.TextUtils;
-import cgeo.geocaching.utils.TranslationUtils;
import cgeo.geocaching.utils.UnknownTagsHandler;
import org.apache.commons.collections4.CollectionUtils;
@@ -57,6 +54,14 @@ import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
+import rx.Observable;
+import rx.Observable.OnSubscribe;
+import rx.Observer;
+import rx.Subscriber;
+import rx.Subscription;
+import rx.android.observables.AndroidObservable;
+import rx.functions.Action1;
+import rx.schedulers.Schedulers;
import android.R.color;
import android.app.AlertDialog;
@@ -71,7 +76,6 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -80,7 +84,6 @@ import android.text.Editable;
import android.text.Html;
import android.text.Spannable;
import android.text.Spanned;
-import android.text.format.DateUtils;
import android.text.style.ForegroundColorSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
@@ -109,7 +112,6 @@ import android.widget.TextView.BufferType;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Date;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
@@ -164,6 +166,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
private TextView cacheDistanceView;
protected ImagesList imagesList;
+ private Subscription imagesSubscription;
/**
* waypoint selected in context menu. This variable will be gone when the waypoint context menu is a fragment.
*/
@@ -176,24 +179,15 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
// set title in code, as the activity needs a hard coded title due to the intent filters
setTitle(res.getString(R.string.cache));
- String geocode = null;
-
- // TODO Why can it happen that search is not null? onCreate should be called only once and it is not set before.
- if (search != null) {
- cache = search.getFirstCacheFromResult(LoadFlags.LOAD_ALL_DB_ONLY);
- if (cache != null && cache.getGeocode() != null) {
- geocode = cache.getGeocode();
- }
- }
-
// get parameters
final Bundle extras = getIntent().getExtras();
final Uri uri = getIntent().getData();
// try to get data from extras
String name = null;
+ String geocode = null;
String guid = null;
- if (geocode == null && extras != null) {
+ if (extras != null) {
geocode = extras.getString(Intents.EXTRA_GEOCODE);
name = extras.getString(Intents.EXTRA_NAME);
guid = extras.getString(Intents.EXTRA_GUID);
@@ -327,6 +321,14 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
}
@Override
+ public void onDestroy() {
+ if (imagesList != null) {
+ imagesSubscription.unsubscribe();
+ }
+ super.onDestroy();
+ }
+
+ @Override
public void onStop() {
if (cache != null) {
cache.setChangeNotificationHandler(null);
@@ -349,12 +351,12 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
assert view instanceof TextView;
clickedItemText = ((TextView) view).getText();
final String itemTitle = (String) ((TextView) ((View) view.getParent()).findViewById(R.id.name)).getText();
- buildDetailsContextMenu(menu, itemTitle, true);
+ buildDetailsContextMenu(menu, clickedItemText, itemTitle, true);
break;
case R.id.shortdesc:
assert view instanceof TextView;
clickedItemText = ((TextView) view).getText();
- buildDetailsContextMenu(menu, res.getString(R.string.cache_description), false);
+ buildDetailsContextMenu(menu, clickedItemText, res.getString(R.string.cache_description), false);
break;
case R.id.longdesc:
assert view instanceof TextView;
@@ -365,22 +367,28 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
} else {
clickedItemText = shortDesc + "\n\n" + ((TextView) view).getText();
}
- buildDetailsContextMenu(menu, res.getString(R.string.cache_description), false);
+ buildDetailsContextMenu(menu, clickedItemText, res.getString(R.string.cache_description), false);
break;
case R.id.personalnote:
assert view instanceof TextView;
clickedItemText = ((TextView) view).getText();
- buildDetailsContextMenu(menu, res.getString(R.string.cache_personal_note), true);
+ buildDetailsContextMenu(menu, clickedItemText, res.getString(R.string.cache_personal_note), true);
break;
case R.id.hint:
assert view instanceof TextView;
clickedItemText = ((TextView) view).getText();
- buildDetailsContextMenu(menu, res.getString(R.string.cache_hint), false);
+ buildDetailsContextMenu(menu, clickedItemText, res.getString(R.string.cache_hint), false);
break;
case R.id.log:
assert view instanceof TextView;
clickedItemText = ((TextView) view).getText();
- buildDetailsContextMenu(menu, res.getString(R.string.cache_logs), false);
+ buildDetailsContextMenu(menu, clickedItemText, res.getString(R.string.cache_logs), false);
+ break;
+ case R.id.date: // event date
+ assert view instanceof TextView;
+ clickedItemText = ((TextView) view).getText();
+ buildDetailsContextMenu(menu, clickedItemText, res.getString(R.string.cache_event), true);
+ menu.findItem(R.id.menu_calendar).setVisible(cache.canBeAddedToCalendar());
break;
case R.id.waypoint:
menu.setHeaderTitle(selectedWaypoint.getName() + " (" + res.getString(R.string.waypoint) + ")");
@@ -406,41 +414,13 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
}
}
- private void buildDetailsContextMenu(ContextMenu menu, String fieldTitle, boolean copyOnly) {
- menu.setHeaderTitle(fieldTitle);
- getMenuInflater().inflate(R.menu.details_context, menu);
- menu.findItem(R.id.menu_translate_to_sys_lang).setVisible(!copyOnly);
- if (!copyOnly) {
- if (clickedItemText.length() > TranslationUtils.TRANSLATION_TEXT_LENGTH_WARN) {
- showToast(res.getString(R.string.translate_length_warning));
- }
- menu.findItem(R.id.menu_translate_to_sys_lang).setTitle(res.getString(R.string.translate_to_sys_lang, Locale.getDefault().getDisplayLanguage()));
- }
- final boolean localeIsEnglish = StringUtils.equals(Locale.getDefault().getLanguage(), Locale.ENGLISH.getLanguage());
- menu.findItem(R.id.menu_translate_to_english).setVisible(!copyOnly && !localeIsEnglish);
- }
-
@Override
public boolean onContextItemSelected(MenuItem item) {
+ if (onClipboardItemSelected(item, clickedItemText)) {
+ return true;
+ }
switch (item.getItemId()) {
- // detail fields
- case R.id.menu_copy:
- ClipboardUtils.copyToClipboard(clickedItemText);
- showToast(res.getString(R.string.clipboard_copy_ok));
- return true;
- case R.id.menu_translate_to_sys_lang:
- TranslationUtils.startActivityTranslate(this, Locale.getDefault().getLanguage(), HtmlUtils.extractText(clickedItemText));
- return true;
- case R.id.menu_translate_to_english:
- TranslationUtils.startActivityTranslate(this, Locale.ENGLISH.getLanguage(), HtmlUtils.extractText(clickedItemText));
- return true;
- case R.id.menu_cache_share_field:
- final Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setType("text/plain");
- intent.putExtra(Intent.EXTRA_TEXT, clickedItemText.toString());
- startActivity(Intent.createChooser(intent, res.getText(R.string.cache_share_field)));
- return true;
- // waypoints
+ // waypoints
case R.id.menu_waypoint_edit:
if (selectedWaypoint != null) {
EditWaypointActivity.startActivityEditWaypoint(this, cache, selectedWaypoint.getId());
@@ -484,6 +464,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
new ResetCoordsThread(cache, handler, selectedWaypoint, true, false, progressDialog).start();
}
return true;
+ case R.id.menu_calendar:
+ CalendarAddon.addToCalendarWithIntent(this, cache);
+ return true;
default:
break;
}
@@ -542,20 +525,19 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
if (UPDATE_LOAD_PROGRESS_DETAIL == msg.what && msg.obj instanceof String) {
updateStatusMsg((String) msg.obj);
} else {
- CacheDetailActivity activity = ((CacheDetailActivity) activityRef.get());
+ final CacheDetailActivity activity = ((CacheDetailActivity) activityRef.get());
if (activity == null) {
return;
}
- SearchResult search = activity.getSearch();
- if (search == null) {
+ if (activity.search == null) {
showToast(R.string.err_dwld_details_failed);
dismissProgress();
finishActivity();
return;
}
- if (search.getError() != null) {
- activity.showToast(activity.getResources().getString(R.string.err_dwld_details_failed) + " " + search.getError().getErrorString(activity.getResources()) + ".");
+ if (activity.search.getError() != null) {
+ activity.showToast(activity.getResources().getString(R.string.err_dwld_details_failed) + " " + activity.search.getError().getErrorString(activity.getResources()) + ".");
dismissProgress();
finishActivity();
return;
@@ -691,7 +673,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
return;
}
imagesList = new ImagesList(this, cache.getGeocode());
- imagesList.loadImages(imageView, cache.getImages(), false);
+ imagesSubscription = imagesList.loadImages(imageView, cache.getImages(), false);
}
public static void startActivity(final Context context, final String geocode) {
@@ -922,9 +904,17 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
view = (ScrollView) getLayoutInflater().inflate(R.layout.cachedetail_details_page, null);
// Start loading preview map
- if (Settings.isStoreOfflineMaps()) {
- new PreviewMapTask().execute((Void) null);
- }
+ AndroidObservable.fromActivity(CacheDetailActivity.this, previewMap.subscribeOn(Schedulers.io())).subscribe(new Action1<BitmapDrawable>() {
+ @Override
+ public void call(final BitmapDrawable image) {
+ final Bitmap bitmap = image.getBitmap();
+ if (bitmap != null && bitmap.getWidth() > 10) {
+ final ImageView imageView = (ImageView) view.findViewById(R.id.map_preview);
+ imageView.setImageDrawable(image);
+ view.findViewById(R.id.map_preview_box).setVisibility(View.VISIBLE);
+ }
+ }
+ });
detailsList = (LinearLayout) view.findViewById(R.id.details_list);
final CacheDetailsCreator details = new CacheDetailsCreator(CacheDetailActivity.this, detailsList);
@@ -972,17 +962,10 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
ownerView.setOnClickListener(new OwnerActionsClickListener(cache));
}
- // cache hidden
- final Date hiddenDate = cache.getHiddenDate();
- if (hiddenDate != null) {
- final long time = hiddenDate.getTime();
- if (time > 0) {
- String dateString = Formatter.formatFullDate(time);
- if (cache.isEventCache()) {
- dateString = DateUtils.formatDateTime(CgeoApplication.getInstance().getBaseContext(), time, DateUtils.FORMAT_SHOW_WEEKDAY) + ", " + dateString;
- }
- details.add(cache.isEventCache() ? R.string.cache_event : R.string.cache_hidden, dateString);
- }
+ // hidden or event date
+ final TextView hiddenView = details.addHiddenDate(cache);
+ if (hiddenView != null) {
+ registerForContextMenu(hiddenView);
}
// cache location
@@ -1051,9 +1034,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
if (Settings.getChooseList()) {
// let user select list to store cache in
new StoredList.UserInterface(CacheDetailActivity.this).promptForListSelection(R.string.list_title,
- new RunnableWithArgument<Integer>() {
+ new Action1<Integer>() {
@Override
- public void run(final Integer selectedListId) {
+ public void call(final Integer selectedListId) {
storeCache(selectedListId, new StoreCacheHandler(CacheDetailActivity.this, progress));
}
}, true, StoredList.TEMPORARY_LIST_ID);
@@ -1302,9 +1285,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
@Override
public void onClick(View view) {
new StoredList.UserInterface(CacheDetailActivity.this).promptForListSelection(R.string.list_title,
- new RunnableWithArgument<Integer>() {
+ new Action1<Integer>() {
@Override
- public void run(final Integer selectedListId) {
+ public void call(final Integer selectedListId) {
switchListById(selectedListId);
}
}, true, cache.getListId());
@@ -1368,7 +1351,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
final LinearLayout layout = (LinearLayout) view.findViewById(R.id.favpoint_box);
final boolean supportsFavoritePoints = cache.supportsFavoritePoints();
layout.setVisibility(supportsFavoritePoints ? View.VISIBLE : View.GONE);
- if (!supportsFavoritePoints || cache.isOwner() || !Settings.isPremiumMember()) {
+ if (!supportsFavoritePoints || cache.isOwner() || !Settings.isGCPremiumMember()) {
return;
}
final Button buttonAdd = (Button) view.findViewById(R.id.add_to_favpoint);
@@ -1418,58 +1401,41 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
box.setVisibility(View.GONE);
}
}
+ }
- private class PreviewMapTask extends AsyncTask<Void, Void, BitmapDrawable> {
- @Override
- protected BitmapDrawable doInBackground(Void... parameters) {
- try {
- // persistent preview from storage
- Bitmap image = decode(cache);
+ private Observable<BitmapDrawable> previewMap = Observable.create(new OnSubscribe<BitmapDrawable>() {
+ @Override
+ public void call(final Subscriber<? super BitmapDrawable> subscriber) {
+ try {
+ // persistent preview from storage
+ Bitmap image = StaticMapsProvider.getPreviewMap(cache);
- if (image == null) {
+ if (image == null) {
+ if (Settings.isStoreOfflineMaps()) {
StaticMapsProvider.storeCachePreviewMap(cache);
- image = decode(cache);
- if (image == null) {
- return null;
- }
+ image = StaticMapsProvider.getPreviewMap(cache);
}
-
- return ImageUtils.scaleBitmapToFitDisplay(image);
- } catch (final Exception e) {
- Log.w("CacheDetailActivity.PreviewMapTask", e);
- return null;
}
- }
- private Bitmap decode(final Geocache cache) {
- return StaticMapsProvider.getPreviewMap(cache.getGeocode());
- }
-
- @Override
- protected void onPostExecute(BitmapDrawable image) {
- if (image == null) {
- return;
- }
-
- try {
- final Bitmap bitmap = image.getBitmap();
- if (bitmap == null || bitmap.getWidth() <= 10) {
- return;
- }
-
- final ImageView imageView = (ImageView) view.findViewById(R.id.map_preview);
- imageView.setImageDrawable(image);
- view.findViewById(R.id.map_preview_box).setVisibility(View.VISIBLE);
- } catch (final Exception e) {
- Log.e("CacheDetailActivity.PreviewMapTask", e);
+ if (image != null) {
+ subscriber.onNext(ImageUtils.scaleBitmapToFitDisplay(image));
}
+ subscriber.onCompleted();
+ } catch (final Exception e) {
+ Log.w("CacheDetailActivity.previewMap", e);
+ subscriber.onError(e);
}
}
- }
+
+ });
protected class DescriptionViewCreator extends AbstractCachingPageViewCreator<ScrollView> {
@InjectView(R.id.personalnote) protected TextView personalNoteView;
+ @InjectView(R.id.shortdesc) protected IndexOutOfBoundsAvoidingTextView shortDescView;
+ @InjectView(R.id.longdesc) protected IndexOutOfBoundsAvoidingTextView longDescView;
+ @InjectView(R.id.show_description) protected Button showDesc;
+ @InjectView(R.id.loading) protected View loadingView;
@Override
public ScrollView getDispatchedView() {
@@ -1483,7 +1449,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
// cache short description
if (StringUtils.isNotBlank(cache.getShortDescription())) {
- new LoadDescriptionTask(cache.getShortDescription(), view.findViewById(R.id.shortdesc), null, null).execute();
+ loadDescription(cache.getShortDescription(), shortDescView, null);
}
// long description
@@ -1491,7 +1457,6 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
if (Settings.isAutoLoadDescription()) {
loadLongDescription();
} else {
- final Button showDesc = (Button) view.findViewById(R.id.show_description);
showDesc.setVisibility(View.VISIBLE);
showDesc.setOnClickListener(new View.OnClickListener() {
@Override
@@ -1605,12 +1570,23 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
}
private void loadLongDescription() {
- final Button showDesc = (Button) view.findViewById(R.id.show_description);
showDesc.setVisibility(View.GONE);
showDesc.setOnClickListener(null);
view.findViewById(R.id.loading).setVisibility(View.VISIBLE);
- new LoadDescriptionTask(cache.getDescription(), view.findViewById(R.id.longdesc), view.findViewById(R.id.loading), view.findViewById(R.id.shortdesc)).execute();
+ final String longDescription = cache.getDescription();
+ loadDescription(longDescription, longDescView, loadingView);
+
+ // Hide the short description, if it is contained somewhere at the start of the long description.
+ if (shortDescView != null) {
+ final String shortDescription = cache.getShortDescription();
+ if (StringUtils.isNotBlank(shortDescription)) {
+ final int index = longDescription.indexOf(shortDescription);
+ if (index >= 0 && index < 200) {
+ shortDescView.setVisibility(View.GONE);
+ }
+ }
+ }
}
private void warnPersonalNoteNeedsStoring() {
@@ -1641,148 +1617,118 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
}
- /**
- * Loads the description in background. <br />
- * <br />
- * Params:
- * <ol>
- * <li>description string (String)</li>
- * <li>target description view (TextView)</li>
- * <li>loading indicator view (View, may be null)</li>
- * </ol>
- */
- private class LoadDescriptionTask extends AsyncTask<Object, Void, Void> {
- private final View loadingIndicatorView;
- private final IndexOutOfBoundsAvoidingTextView descriptionView;
- private final String descriptionString;
- private Spanned description;
- private final View shortDescView;
+ /**
+ * Load the description in the background.
+ * @param descriptionString the HTML description as retrieved from the connector
+ * @param descriptionView the view to fill
+ * @param loadingIndicatorView the loading indicator view, will be hidden when completed
+ */
+ private void loadDescription(final String descriptionString, final IndexOutOfBoundsAvoidingTextView descriptionView, final View loadingIndicatorView) {
+ // The producer produces successives (without then with images) versions of the description.
+ final Observable<Spanned> producer = Observable.create(new OnSubscribe<Spanned>() {
+ @Override
+ public void call(final Subscriber<? super Spanned> subscriber) {
+ try {
+ // Fast preview: parse only HTML without loading any images
+ final HtmlImageCounter imageCounter = new HtmlImageCounter();
+ final UnknownTagsHandler unknownTagsHandler = new UnknownTagsHandler();
+ Spanned description = Html.fromHtml(descriptionString, imageCounter, unknownTagsHandler);
+ addWarning(unknownTagsHandler, description);
+ subscriber.onNext(description);
+
+ if (imageCounter.getImageCount() > 0) {
+ // Complete view: parse again with loading images - if necessary ! If there are any images causing problems the user can see at least the preview
+ description = Html.fromHtml(descriptionString, new HtmlImage(cache.getGeocode(), true, cache.getListId(), false), unknownTagsHandler);
+ addWarning(unknownTagsHandler, description);
+ subscriber.onNext(description);
+ }
- public LoadDescriptionTask(final String description, final View descriptionView, final View loadingIndicatorView, final View shortDescView) {
- assert descriptionView instanceof IndexOutOfBoundsAvoidingTextView;
- this.descriptionString = description;
- this.descriptionView = (IndexOutOfBoundsAvoidingTextView) descriptionView;
- this.loadingIndicatorView = loadingIndicatorView;
- this.shortDescView = shortDescView;
- }
+ subscriber.onCompleted();
+ } catch (final Exception e) {
+ Log.e("loadDescription", e);
+ subscriber.onError(e);
+ }
+ }
- @Override
- protected Void doInBackground(Object... params) {
- try {
- // Fast preview: parse only HTML without loading any images
- final HtmlImageCounter imageCounter = new HtmlImageCounter();
- final UnknownTagsHandler unknownTagsHandler = new UnknownTagsHandler();
- description = Html.fromHtml(descriptionString, imageCounter, unknownTagsHandler);
- publishProgress();
-
- boolean needsRefresh = false;
- if (imageCounter.getImageCount() > 0) {
- // Complete view: parse again with loading images - if necessary ! If there are any images causing problems the user can see at least the preview
- description = Html.fromHtml(descriptionString, new HtmlImage(cache.getGeocode(), true, cache.getListId(), false), unknownTagsHandler);
- needsRefresh = true;
- }
-
- // If description has an HTML construct which may be problematic to render, add a note at the end of the long description.
- // Technically, it may not be a table, but a pre, which has the same problems as a table, so the message is ok even though
- // sometimes technically incorrect.
- if (unknownTagsHandler.isProblematicDetected() && descriptionView != null) {
+ // If description has an HTML construct which may be problematic to render, add a note at the end of the long description.
+ // Technically, it may not be a table, but a pre, which has the same problems as a table, so the message is ok even though
+ // sometimes technically incorrect.
+ private void addWarning(final UnknownTagsHandler unknownTagsHandler, final Spanned description) {
+ if (unknownTagsHandler.isProblematicDetected()) {
final int startPos = description.length();
final IConnector connector = ConnectorFactory.getConnector(cache);
final Spanned tableNote = Html.fromHtml(res.getString(R.string.cache_description_table_note, "<a href=\"" + cache.getUrl() + "\">" + connector.getName() + "</a>"));
((Editable) description).append("\n\n").append(tableNote);
((Editable) description).setSpan(new StyleSpan(Typeface.ITALIC), startPos, description.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- needsRefresh = true;
- }
-
- if (needsRefresh) {
- publishProgress();
- }
- } catch (final Exception e) {
- Log.e("LoadDescriptionTask: ", e);
- }
- return null;
- }
-
- @Override
- protected void onProgressUpdate(Void... values) {
- if (description == null) {
- showToast(res.getString(R.string.err_load_descr_failed));
- return;
- }
- if (StringUtils.isNotBlank(descriptionString)) {
- try {
- descriptionView.setText(description, TextView.BufferType.SPANNABLE);
- } catch (final Exception e) {
- // On 4.1, there is sometimes a crash on measuring the layout: https://code.google.com/p/android/issues/detail?id=35412
- Log.e("Android bug setting text: ", e);
- // remove the formatting by converting to a simple string
- descriptionView.setText(description.toString());
}
- descriptionView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance());
- fixTextColor(descriptionView, descriptionString);
- descriptionView.setVisibility(View.VISIBLE);
- registerForContextMenu(descriptionView);
-
- hideDuplicatedShortDescription();
}
- }
+ });
- /**
- * Hide the short description, if it is contained somewhere at the start of the long description.
- */
- private void hideDuplicatedShortDescription() {
- if (shortDescView != null) {
- final String shortDescription = cache.getShortDescription();
- if (StringUtils.isNotBlank(shortDescription)) {
- final int index = descriptionString.indexOf(shortDescription);
- if (index >= 0 && index < 200) {
- shortDescView.setVisibility(View.GONE);
+ AndroidObservable.fromActivity(this, producer.subscribeOn(Schedulers.io()))
+ .subscribe(new Observer<Spanned>() {
+ @Override
+ public void onCompleted() {
+ if (null != loadingIndicatorView) {
+ loadingIndicatorView.setVisibility(View.GONE);
+ }
}
- }
- }
- }
- @Override
- protected void onPostExecute(Void result) {
- if (null != loadingIndicatorView) {
- loadingIndicatorView.setVisibility(View.GONE);
- }
- }
+ @Override
+ public void onError(final Throwable throwable) {
+ showToast(res.getString(R.string.err_load_descr_failed));
+ }
- /**
- * Handle caches with black font color in dark skin and white font color in light skin
- * by changing background color of the view
- *
- * @param view
- * containing the text
- * @param text
- * to be checked
- */
- private void fixTextColor(final TextView view, final String text) {
- int backcolor;
- if (Settings.isLightSkin()) {
- backcolor = color.white;
-
- for (final Pattern pattern : LIGHT_COLOR_PATTERNS) {
- final MatcherWrapper matcher = new MatcherWrapper(pattern, text);
- if (matcher.find()) {
- view.setBackgroundResource(color.darker_gray);
- return;
+ @Override
+ public void onNext(final Spanned description) {
+ if (StringUtils.isNotBlank(descriptionString)) {
+ try {
+ descriptionView.setText(description, TextView.BufferType.SPANNABLE);
+ } catch (final Exception e) {
+ // On 4.1, there is sometimes a crash on measuring the layout: https://code.google.com/p/android/issues/detail?id=35412
+ Log.e("Android bug setting text: ", e);
+ // remove the formatting by converting to a simple string
+ descriptionView.setText(description.toString());
+ }
+ descriptionView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance());
+ fixTextColor(descriptionString);
+ descriptionView.setVisibility(View.VISIBLE);
+ registerForContextMenu(descriptionView);
+ }
}
- }
- } else {
- backcolor = color.black;
- for (final Pattern pattern : DARK_COLOR_PATTERNS) {
- final MatcherWrapper matcher = new MatcherWrapper(pattern, text);
- if (matcher.find()) {
- view.setBackgroundResource(color.darker_gray);
- return;
+ /**
+ * Handle caches with black font color in dark skin and white font color in light skin
+ * by changing background color of the view
+ *
+ * @param text
+ * to be checked
+ */
+ private void fixTextColor(final String text) {
+ int backcolor;
+ if (Settings.isLightSkin()) {
+ backcolor = color.white;
+
+ for (final Pattern pattern : LIGHT_COLOR_PATTERNS) {
+ final MatcherWrapper matcher = new MatcherWrapper(pattern, text);
+ if (matcher.find()) {
+ descriptionView.setBackgroundResource(color.darker_gray);
+ return;
+ }
+ }
+ } else {
+ backcolor = color.black;
+
+ for (final Pattern pattern : DARK_COLOR_PATTERNS) {
+ final MatcherWrapper matcher = new MatcherWrapper(pattern, text);
+ if (matcher.find()) {
+ descriptionView.setBackgroundResource(color.darker_gray);
+ return;
+ }
+ }
+ }
+ descriptionView.setBackgroundResource(backcolor);
}
- }
- }
- view.setBackgroundResource(backcolor);
- }
+ });
}
private class WaypointsViewCreator extends AbstractCachingPageViewCreator<ListView> {
@@ -2255,10 +2201,6 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
return cache;
}
- public SearchResult getSearch() {
- return search;
- }
-
private static class StoreCacheHandler extends SimpleCancellableHandler {
public StoreCacheHandler(CacheDetailActivity activity, Progress progress) {
diff --git a/main/src/cgeo/geocaching/CacheListActivity.java b/main/src/cgeo/geocaching/CacheListActivity.java
index cc8b178..fa84ac6 100644
--- a/main/src/cgeo/geocaching/CacheListActivity.java
+++ b/main/src/cgeo/geocaching/CacheListActivity.java
@@ -8,7 +8,7 @@ import cgeo.geocaching.activity.Progress;
import cgeo.geocaching.apps.cache.navi.NavigationAppFactory;
import cgeo.geocaching.apps.cachelist.CacheListAppFactory;
import cgeo.geocaching.compatibility.Compatibility;
-import cgeo.geocaching.connector.gc.SearchHandler;
+import cgeo.geocaching.connector.gc.RecaptchaHandler;
import cgeo.geocaching.enumerations.CacheListType;
import cgeo.geocaching.enumerations.CacheType;
import cgeo.geocaching.enumerations.LoadFlags;
@@ -18,11 +18,13 @@ import cgeo.geocaching.files.GPXImporter;
import cgeo.geocaching.filter.FilterUserInterface;
import cgeo.geocaching.filter.IFilter;
import cgeo.geocaching.geopoint.Geopoint;
+import cgeo.geocaching.list.PseudoList;
import cgeo.geocaching.list.StoredList;
import cgeo.geocaching.loaders.AbstractSearchLoader;
import cgeo.geocaching.loaders.AbstractSearchLoader.CacheListLoaderType;
import cgeo.geocaching.loaders.AddressGeocacheListLoader;
import cgeo.geocaching.loaders.CoordsGeocacheListLoader;
+import cgeo.geocaching.loaders.FinderGeocacheListLoader;
import cgeo.geocaching.loaders.HistoryGeocacheListLoader;
import cgeo.geocaching.loaders.KeywordGeocacheListLoader;
import cgeo.geocaching.loaders.NextPageGeocacheListLoader;
@@ -30,7 +32,6 @@ import cgeo.geocaching.loaders.OfflineGeocacheListLoader;
import cgeo.geocaching.loaders.OwnerGeocacheListLoader;
import cgeo.geocaching.loaders.PocketGeocacheListLoader;
import cgeo.geocaching.loaders.RemoveFromHistoryLoader;
-import cgeo.geocaching.loaders.FinderGeocacheListLoader;
import cgeo.geocaching.maps.CGeoMap;
import cgeo.geocaching.network.Cookies;
import cgeo.geocaching.network.Network;
@@ -43,10 +44,10 @@ 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.CancellableHandler;
import cgeo.geocaching.utils.DateUtils;
import cgeo.geocaching.utils.GeoDirHandler;
import cgeo.geocaching.utils.Log;
-import cgeo.geocaching.utils.RunnableWithArgument;
import ch.boye.httpclientandroidlib.HttpResponse;
@@ -55,6 +56,8 @@ import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNull;
+import rx.functions.Action1;
+
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
@@ -93,7 +96,11 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
private static final int MAX_LIST_ITEMS = 1000;
private static final int MSG_DONE = -1;
- private static final int MSG_CANCEL = -99;
+ private static final int MSG_SERVER_FAIL = -2;
+ private static final int MSG_NO_REGISTRATION = -3;
+ private static final int MSG_WAITING = 0;
+ private static final int MSG_LOADING = 1;
+ private static final int MSG_LOADED = 2;
private static final int REQUEST_CODE_IMPORT_GPX = 1;
@@ -111,8 +118,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
private int detailTotal = 0;
private int detailProgress = 0;
private long detailProgressTime = 0L;
- private LoadDetailsThread threadDetails = null;
- private LoadFromWebThread threadWeb = null;
private int listId = StoredList.TEMPORARY_LIST_ID; // Only meaningful for the OFFLINE type
private final GeoDirHandler geoDirHandler = new GeoDirHandler() {
@@ -267,10 +272,10 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
}
}
- private final Handler loadDetailsHandler = new Handler() {
+ private final CancellableHandler loadDetailsHandler = new CancellableHandler() {
@Override
- public void handleMessage(Message msg) {
+ public void handleRegularMessage(Message msg) {
setAdapter();
if (msg.what > -1) {
@@ -287,10 +292,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
} else {
progress.setMessage(res.getString(R.string.caches_downloading) + " " + minutesRemaining + " " + res.getQuantityString(R.plurals.caches_eta_mins, minutesRemaining));
}
- } else if (msg.what == MSG_CANCEL) {
- if (threadDetails != null) {
- threadDetails.kill();
- }
} else {
if (search != null) {
final Set<Geocache> cacheListTmp = search.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB);
@@ -304,13 +305,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
showProgress(false);
progress.dismiss();
-
- if (!isPaused()) {
- // If the current activity has been paused, then we do not want to fiddle with the
- // GPS and direction states. If the activity later gets resumed, its onResume()
- // function will take care of turning the GPS back on.
- startGeoAndDir();
- }
}
}
};
@@ -318,54 +312,53 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
/**
* TODO Possibly parts should be a Thread not a Handler
*/
- private final Handler downloadFromWebHandler = new Handler() {
+ private final CancellableHandler downloadFromWebHandler = new CancellableHandler() {
@Override
- public void handleMessage(Message msg) {
+ public void handleRegularMessage(Message msg) {
setAdapter();
adapter.notifyDataSetChanged();
- if (msg.what == 0) { //no caches
- progress.setMessage(res.getString(R.string.web_import_waiting));
- } else if (msg.what == 1) { //cache downloading
- progress.setMessage(res.getString(R.string.web_downloading) + " " + msg.obj + '…');
- } else if (msg.what == 2) { //Cache downloaded
- progress.setMessage(res.getString(R.string.web_downloaded) + " " + msg.obj + '…');
- refreshCurrentList();
- } else if (msg.what == -2) {
- progress.dismiss();
- showToast(res.getString(R.string.sendToCgeo_download_fail));
- finish();
- } else if (msg.what == -3) {
- progress.dismiss();
- showToast(res.getString(R.string.sendToCgeo_no_registration));
- finish();
- } else if (msg.what == MSG_CANCEL) {
- if (threadWeb != null) {
- threadWeb.kill();
- }
- } else {
- adapter.setSelectMode(false);
-
- replaceCacheListFromSearch();
-
- progress.dismiss();
+ switch (msg.what) {
+ case MSG_WAITING: //no caches
+ progress.setMessage(res.getString(R.string.web_import_waiting));
+ break;
+ case MSG_LOADING: //cache downloading
+ progress.setMessage(res.getString(R.string.web_downloading) + " " + msg.obj + '…');
+ break;
+ case MSG_LOADED: //Cache downloaded
+ progress.setMessage(res.getString(R.string.web_downloaded) + " " + msg.obj + '…');
+ refreshCurrentList();
+ break;
+ case MSG_SERVER_FAIL:
+ progress.dismiss();
+ showToast(res.getString(R.string.sendToCgeo_download_fail));
+ finish();
+ break;
+ case MSG_NO_REGISTRATION:
+ progress.dismiss();
+ showToast(res.getString(R.string.sendToCgeo_no_registration));
+ finish();
+ break;
+ default: // MSG_DONE
+ adapter.setSelectMode(false);
+ replaceCacheListFromSearch();
+ progress.dismiss();
+ break;
}
}
};
- private final Handler clearOfflineLogsHandler = new Handler() {
+ private final CancellableHandler clearOfflineLogsHandler = new CancellableHandler() {
@Override
- public void handleMessage(Message msg) {
- if (msg.what != MSG_CANCEL) {
- adapter.setSelectMode(false);
+ public void handleRegularMessage(Message msg) {
+ adapter.setSelectMode(false);
- refreshCurrentList();
+ refreshCurrentList();
- replaceCacheListFromSearch();
+ replaceCacheListFromSearch();
- progress.dismiss();
- }
+ progress.dismiss();
}
};
@@ -402,7 +395,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
if (isInvokedFromAttachment()) {
type = CacheListType.OFFLINE;
if (coords == null) {
- coords = new Geopoint(0.0, 0.0);
+ coords = Geopoint.ZERO;
}
}
@@ -422,7 +415,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
prepareFilterBar();
- currentLoader = (AbstractSearchLoader) getSupportLoaderManager().initLoader(type.ordinal(), extras, this);
+ currentLoader = (AbstractSearchLoader) getSupportLoaderManager().initLoader(type.getLoaderId(), extras, this);
// init
if (CollectionUtils.isNotEmpty(cacheList)) {
@@ -458,10 +451,10 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
}
private void importGpxAttachement() {
- new StoredList.UserInterface(this).promptForListSelection(R.string.gpx_import_select_list_title, new RunnableWithArgument<Integer>() {
+ new StoredList.UserInterface(this).promptForListSelection(R.string.gpx_import_select_list_title, new Action1<Integer>() {
@Override
- public void run(Integer listId) {
+ public void call(Integer listId) {
new GPXImporter(CacheListActivity.this, listId, importGpxAttachementFinishedHandler).importGPX();
switchListById(listId);
}
@@ -472,12 +465,15 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
public void onResume() {
super.onResume();
- startGeoAndDir();
+ geoDirHandler.startGeo();
+ if (Settings.isLiveMap()) {
+ geoDirHandler.startDir();
+ }
adapter.setSelectMode(false);
setAdapterCurrentCoordinates(true);
- if (loadCachesHandler != null && search != null) {
+ if (search != null) {
replaceCacheListFromSearch();
loadCachesHandler.sendEmptyMessage(0);
}
@@ -503,8 +499,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
@Override
public void onPause() {
- removeGeoAndDir();
-
+ geoDirHandler.stopGeoAndDir();
super.onPause();
}
@@ -675,9 +670,9 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
return true;
case R.id.menu_sort:
final CacheComparator oldComparator = adapter.getCacheComparator();
- new ComparatorUserInterface(this).selectComparator(oldComparator, new RunnableWithArgument<CacheComparator>() {
+ new ComparatorUserInterface(this).selectComparator(oldComparator, new Action1<CacheComparator>() {
@Override
- public void run(CacheComparator selectedComparator) {
+ public void call(CacheComparator selectedComparator) {
// selecting the same sorting twice will toggle the order
if (selectedComparator != null && oldComparator != null && selectedComparator.getClass().equals(oldComparator.getClass())) {
adapter.toggleInverseSort();
@@ -728,7 +723,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
}
public void clearOfflineLogs() {
- progress.show(this, null, res.getString(R.string.caches_clear_offlinelogs_progress), true, clearOfflineLogsHandler.obtainMessage(MSG_CANCEL));
+ progress.show(this, null, res.getString(R.string.caches_clear_offlinelogs_progress), true, clearOfflineLogsHandler.cancelMessage());
new ClearOfflineLogsThread(clearOfflineLogsHandler).start();
}
@@ -737,13 +732,12 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
*/
@Override
public void showFilterMenu(final View view) {
- new FilterUserInterface(this).selectFilter(new RunnableWithArgument<IFilter>() {
+ new FilterUserInterface(this).selectFilter(new Action1<IFilter>() {
@Override
- public void run(IFilter selectedFilter) {
+ public void call(IFilter selectedFilter) {
if (selectedFilter != null) {
setFilter(selectedFilter);
- }
- else {
+ } else {
// clear filter
setFilter(null);
}
@@ -793,10 +787,10 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
}
private void moveCachesToOtherList() {
- new StoredList.UserInterface(this).promptForListSelection(R.string.cache_menu_move_list, new RunnableWithArgument<Integer>() {
+ new StoredList.UserInterface(this).promptForListSelection(R.string.cache_menu_move_list, new Action1<Integer>() {
@Override
- public void run(Integer newListId) {
+ public void call(Integer newListId) {
DataStore.moveToList(adapter.getCheckedOrAllCaches(), newListId);
adapter.setSelectMode(false);
@@ -850,10 +844,10 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
});
break;
case R.id.menu_move_to_list:
- new StoredList.UserInterface(this).promptForListSelection(R.string.cache_menu_move_list, new RunnableWithArgument<Integer>() {
+ new StoredList.UserInterface(this).promptForListSelection(R.string.cache_menu_move_list, new Action1<Integer>() {
@Override
- public void run(Integer newListId) {
+ public void call(Integer newListId) {
DataStore.moveToList(Collections.singletonList(cache), newListId);
adapter.setSelectMode(false);
refreshCurrentList();
@@ -960,7 +954,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.getTotalCountGC();
- enableMore = enableMore && count > 0 && cacheList.size() < count;
+ enableMore = count > 0 && cacheList.size() < count;
}
if (enableMore) {
@@ -973,17 +967,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
listFooter.setClickable(enableMore);
}
- private void startGeoAndDir() {
- geoDirHandler.startGeo();
- if (Settings.isLiveMap()) {
- geoDirHandler.startDir();
- }
- }
-
- private void removeGeoAndDir() {
- geoDirHandler.stopGeoAndDir();
- }
-
private void importGpx() {
GpxFileListActivity.startSubActivity(this, listId);
}
@@ -1002,7 +985,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
// provided to this method as a parameter. Pull that uri using "resultData.getData()"
if (data != null) {
final Uri uri = data.getData();
- new GPXImporter(CacheListActivity.this, listId, importGpxAttachementFinishedHandler).importGPX(uri, null, getDisplayName(uri));
+ new GPXImporter(this, listId, importGpxAttachementFinishedHandler).importGPX(uri, null, getDisplayName(uri));
}
}
@@ -1038,9 +1021,9 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
if (Settings.getChooseList() && type != CacheListType.OFFLINE) {
// let user select list to store cache in
new StoredList.UserInterface(this).promptForListSelection(R.string.list_title,
- new RunnableWithArgument<Integer>() {
+ new Action1<Integer>() {
@Override
- public void run(final Integer selectedListId) {
+ public void call(final Integer selectedListId) {
refreshStored(caches, selectedListId);
}
}, true, StoredList.TEMPORARY_LIST_ID, newListName);
@@ -1062,12 +1045,12 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
message = res.getString(R.string.caches_downloading) + " " + etaTime + " " + res.getQuantityString(R.plurals.caches_eta_mins, etaTime);
}
- progress.show(this, null, message, ProgressDialog.STYLE_HORIZONTAL, loadDetailsHandler.obtainMessage(MSG_CANCEL));
+ progress.show(this, null, message, ProgressDialog.STYLE_HORIZONTAL, loadDetailsHandler.cancelMessage());
progress.setMaxProgressAndReset(detailTotal);
detailProgressTime = System.currentTimeMillis();
- threadDetails = new LoadDetailsThread(loadDetailsHandler, caches, storeListId);
+ final LoadDetailsThread threadDetails = new LoadDetailsThread(loadDetailsHandler, caches, storeListId);
threadDetails.start();
}
@@ -1091,16 +1074,16 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
}
final Bundle b = new Bundle();
b.putStringArray(Intents.EXTRA_CACHELIST, geocodes);
- getSupportLoaderManager().initLoader(CacheListLoaderType.REMOVE_FROM_HISTORY.ordinal(), b, this);
+ getSupportLoaderManager().initLoader(CacheListLoaderType.REMOVE_FROM_HISTORY.getLoaderId(), b, this);
}
public void importWeb() {
detailProgress = 0;
showProgress(false);
- progress.show(this, null, res.getString(R.string.web_import_waiting), true, downloadFromWebHandler.obtainMessage(MSG_CANCEL));
+ progress.show(this, null, res.getString(R.string.web_import_waiting), true, downloadFromWebHandler.cancelMessage());
- threadWeb = new LoadFromWebThread(downloadFromWebHandler, listId);
+ final LoadFromWebThread threadWeb = new LoadFromWebThread(downloadFromWebHandler, listId);
threadWeb.start();
}
@@ -1127,26 +1110,20 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
private class LoadDetailsThread extends Thread {
- final private Handler handler;
+ final private CancellableHandler handler;
final private int listIdLD;
- private volatile boolean needToStop = false;
final private List<Geocache> caches;
- public LoadDetailsThread(Handler handlerIn, List<Geocache> caches, int listId) {
- handler = handlerIn;
+ public LoadDetailsThread(CancellableHandler handler, List<Geocache> caches, int listId) {
+ this.handler = handler;
this.caches = caches;
// in case of online lists, set the list id to the standard list
this.listIdLD = Math.max(listId, StoredList.STANDARD_LIST_ID);
}
- public void kill() {
- needToStop = true;
- }
-
@Override
public void run() {
- removeGeoAndDir();
// First refresh caches that do not yet have static maps to get them a chance to get a copy
// before the limit expires, unless we do not want to store offline maps.
final List<Geocache> allCaches = Settings.isStoreOfflineMaps() ?
@@ -1173,7 +1150,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
*/
private boolean refreshCache(Geocache cache) {
try {
- if (needToStop) {
+ if (handler.isCancelled()) {
throw new InterruptedException("Stopped storing process.");
}
detailProgress++;
@@ -1190,85 +1167,55 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
}
}
- private class LoadFromWebThread extends Thread {
+ private static class LoadFromWebThread extends Thread {
- final private Handler handler;
+ final private CancellableHandler handler;
final private int listIdLFW;
- private volatile boolean needToStop = false;
- public LoadFromWebThread(Handler handlerIn, int listId) {
- handler = handlerIn;
+ public LoadFromWebThread(CancellableHandler handler, int listId) {
+ this.handler = handler;
listIdLFW = StoredList.getConcreteList(listId);
}
- public void kill() {
- needToStop = true;
- }
-
@Override
public void run() {
+ final long startTime = System.currentTimeMillis();
- removeGeoAndDir();
-
- int delay = -1;
- int times = 0;
-
- int ret = MSG_DONE;
- while (!needToStop && times < 3 * 60 / 5) { // maximum: 3 minutes, every 5 seconds
- //download new code
- String deviceCode = Settings.getWebDeviceCode();
- if (deviceCode == null) {
- deviceCode = "";
- }
- final Parameters params = new Parameters("code", deviceCode);
+ final String deviceCode = StringUtils.defaultString(Settings.getWebDeviceCode());
+ final Parameters params = new Parameters("code", deviceCode);
+ while (!handler.isCancelled() && System.currentTimeMillis() - startTime < 3 * 60000) { // maximum: 3 minutes
+ // Download new code
final HttpResponse responseFromWeb = Network.getRequest("http://send2.cgeo.org/read.html", params);
if (responseFromWeb != null && responseFromWeb.getStatusLine().getStatusCode() == 200) {
final String response = Network.getResponseData(responseFromWeb);
if (response != null && response.length() > 2) {
- delay = 1;
- handler.sendMessage(handler.obtainMessage(1, response));
- yield();
+ handler.sendMessage(handler.obtainMessage(MSG_LOADING, response));
Geocache.storeCache(null, response, listIdLFW, false, null);
- handler.sendMessage(handler.obtainMessage(2, response));
- yield();
+ handler.sendMessage(handler.obtainMessage(MSG_LOADED, response));
} else if ("RG".equals(response)) {
//Server returned RG (registration) and this device no longer registered.
Settings.setWebNameCode(null, null);
- ret = -3;
- needToStop = true;
+ handler.sendEmptyMessage(MSG_NO_REGISTRATION);
+ handler.cancel();
break;
} else {
- delay = 0;
- handler.sendEmptyMessage(0);
- yield();
+ try {
+ sleep(5000); // Wait for 5s if no cache found
+ } catch (final InterruptedException e) {
+ }
+ handler.sendEmptyMessage(MSG_WAITING);
}
- }
- if (responseFromWeb == null || responseFromWeb.getStatusLine().getStatusCode() != 200) {
- ret = -2;
- needToStop = true;
+ } else {
+ handler.sendEmptyMessage(MSG_SERVER_FAIL);
+ handler.cancel();
break;
}
-
- try {
- yield();
- if (delay == 0) {
- sleep(5000); //No caches 5s
- times++;
- } else {
- sleep(500); //Cache was loaded 0.5s
- times = 0;
- }
- } catch (final InterruptedException e) {
- Log.e("CacheListActivity.LoadFromWebThread.sleep", e);
- }
}
- handler.sendEmptyMessage(ret);
-
- startGeoAndDir();
+ handler.sendEmptyMessage(MSG_DONE);
}
}
@@ -1283,9 +1230,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
@Override
protected Void doInBackgroundInternal(Geocache[] caches) {
- removeGeoAndDir();
DataStore.markDropped(Arrays.asList(caches));
- startGeoAndDir();
return null;
}
@@ -1328,7 +1273,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
showFooterLoadingCaches();
listFooter.setOnClickListener(null);
- getSupportLoaderManager().restartLoader(CacheListLoaderType.NEXT_PAGE.ordinal(), null, CacheListActivity.this);
+ getSupportLoaderManager().restartLoader(CacheListLoaderType.NEXT_PAGE.getLoaderId(), null, CacheListActivity.this);
}
}
@@ -1349,11 +1294,11 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
}
@NonNull
- private RunnableWithArgument<Integer> getListSwitchingRunnable() {
- return new RunnableWithArgument<Integer>() {
+ private Action1<Integer> getListSwitchingRunnable() {
+ return new Action1<Integer>() {
@Override
- public void run(final Integer selectedListId) {
+ public void call(final Integer selectedListId) {
switchListById(selectedListId);
}
};
@@ -1364,6 +1309,12 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
return;
}
+ if (id == PseudoList.HISTORY_LIST.id) {
+ CacheListActivity.startActivityHistory(this);
+ finish();
+ return;
+ }
+
final StoredList list = DataStore.getList(id);
if (list == null) {
return;
@@ -1378,7 +1329,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
showFooterLoadingCaches();
DataStore.moveToList(adapter.getCheckedCaches(), listId);
- currentLoader = (OfflineGeocacheListLoader) getSupportLoaderManager().initLoader(CacheListType.OFFLINE.ordinal(), new Bundle(), this);
+ currentLoader = (OfflineGeocacheListLoader) getSupportLoaderManager().initLoader(CacheListType.OFFLINE.getLoaderId(), new Bundle(), this);
currentLoader.reset();
((OfflineGeocacheListLoader) currentLoader).setListId(listId);
((OfflineGeocacheListLoader) currentLoader).setSearchCenter(coords);
@@ -1433,7 +1384,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
* @param view
* unused here but needed since this method is referenced from XML layout
*/
- public void goMap(View view) {
+ public void goMap(@SuppressWarnings("unused") View view) {
if (search == null || CollectionUtils.isEmpty(cacheList)) {
showToast(res.getString(R.string.warn_no_cache_coord));
@@ -1483,12 +1434,12 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
return true;
}
- public static void startActivityUserName(final Activity context, final String userName) {
+ public static void startActivityFinder(final Activity context, final String userName) {
if (!isValidUsername(context, userName)) {
return;
}
final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.USERNAME);
+ cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.FINDER);
cachesIntent.putExtra(Intents.EXTRA_USERNAME, userName);
context.startActivity(cachesIntent);
}
@@ -1606,6 +1557,9 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
} else {
final StoredList list = DataStore.getList(listId);
// list.id may be different if listId was not valid
+ if (list.id != listId) {
+ showToast(getString(R.string.list_not_available));
+ }
listId = list.id;
title = list.title;
}
@@ -1670,8 +1624,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
break;
case POCKET:
final String guid = extras.getString(Intents.EXTRA_POCKET_GUID);
- final String pocket_name = extras.getString(Intents.EXTRA_NAME);
- title = pocket_name;
+ title = extras.getString(Intents.EXTRA_NAME);
loader = new PocketGeocacheListLoader(app, guid);
break;
}
@@ -1680,7 +1633,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
showFooterLoadingCaches();
if (loader != null) {
- loader.setRecaptchaHandler(new SearchHandler(this, res, loader));
+ loader.setRecaptchaHandler(new RecaptchaHandler(this, loader));
}
return loader;
}
diff --git a/main/src/cgeo/geocaching/CacheMenuHandler.java b/main/src/cgeo/geocaching/CacheMenuHandler.java
index d0f1005..cfe9eeb 100644
--- a/main/src/cgeo/geocaching/CacheMenuHandler.java
+++ b/main/src/cgeo/geocaching/CacheMenuHandler.java
@@ -1,27 +1,17 @@
package cgeo.geocaching;
-import cgeo.calendar.ICalendar;
+import cgeo.calendar.CalendarAddon;
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.content.DialogInterface;
-import android.content.Intent;
-import android.net.Uri;
import android.view.Menu;
import android.view.MenuItem;
-import java.util.Date;
-
/**
- * Shared menu handling for all activities having menu items related to a cache.
- *
+ * Shared menu handling for all activities having menu items related to a cache. <br>
+ * TODO: replace by a fragment
+ *
*/
public class CacheMenuHandler extends AbstractUIFactory {
@@ -58,7 +48,7 @@ public class CacheMenuHandler extends AbstractUIFactory {
cache.shareCache(activity, res);
return true;
case R.id.menu_calendar:
- addToCalendarWithIntent(activity, cache);
+ CalendarAddon.addToCalendarWithIntent(activity, cache);
return true;
default:
return false;
@@ -84,39 +74,4 @@ public class CacheMenuHandler extends AbstractUIFactory {
activity.getMenuInflater().inflate(R.menu.cache_options, menu);
onPrepareOptionsMenu(menu, cache);
}
-
- private static void addToCalendarWithIntent(final Activity activity, final Geocache cache) {
- final boolean calendarAddOnAvailable = ProcessUtils.isIntentAvailable(ICalendar.INTENT, Uri.parse(ICalendar.URI_SCHEME + "://" + ICalendar.URI_HOST));
-
- if (calendarAddOnAvailable) {
- final Date hiddenDate = cache.getHiddenDate();
- final Parameters params = new Parameters(
- ICalendar.PARAM_NAME, cache.getName(),
- ICalendar.PARAM_NOTE, StringUtils.defaultString(cache.getPersonalNote()),
- ICalendar.PARAM_HIDDEN_DATE, hiddenDate != null ? String.valueOf(hiddenDate.getTime()) : StringUtils.EMPTY,
- ICalendar.PARAM_URL, StringUtils.defaultString(cache.getUrl()),
- ICalendar.PARAM_COORDS, cache.getCoords() == null ? "" : cache.getCoords().format(GeopointFormatter.Format.LAT_LON_DECMINUTE_RAW),
- ICalendar.PARAM_LOCATION, StringUtils.defaultString(cache.getLocation()),
- ICalendar.PARAM_SHORT_DESC, StringUtils.defaultString(cache.getShortDescription()),
- ICalendar.PARAM_START_TIME_MINUTES, StringUtils.defaultString(cache.guessEventTimeMinutes())
- );
-
- activity.startActivity(new Intent(ICalendar.INTENT,
- 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
- 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 9186497..c6c7c1c 100644
--- a/main/src/cgeo/geocaching/CachePopup.java
+++ b/main/src/cgeo/geocaching/CachePopup.java
@@ -9,9 +9,9 @@ import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.ui.CacheDetailsCreator;
import cgeo.geocaching.utils.CancellableHandler;
import cgeo.geocaching.utils.Log;
-import cgeo.geocaching.utils.RunnableWithArgument;
import org.apache.commons.lang3.StringUtils;
+import rx.functions.Action1;
import android.content.Context;
import android.content.Intent;
@@ -110,9 +110,9 @@ public class CachePopup extends AbstractPopupActivity {
if (Settings.getChooseList()) {
// let user select list to store cache in
new StoredList.UserInterface(CachePopup.this).promptForListSelection(R.string.list_title,
- new RunnableWithArgument<Integer>() {
+ new Action1<Integer>() {
@Override
- public void run(final Integer selectedListId) {
+ public void call(final Integer selectedListId) {
storeCache(selectedListId);
}
}, true, StoredList.TEMPORARY_LIST_ID);
diff --git a/main/src/cgeo/geocaching/CgeoApplication.java b/main/src/cgeo/geocaching/CgeoApplication.java
index 2500d10..7bee97e 100644
--- a/main/src/cgeo/geocaching/CgeoApplication.java
+++ b/main/src/cgeo/geocaching/CgeoApplication.java
@@ -1,10 +1,10 @@
package cgeo.geocaching;
-import cgeo.geocaching.network.StatusUpdater;
import cgeo.geocaching.ui.dialog.Dialogs;
-import cgeo.geocaching.utils.IObserver;
import cgeo.geocaching.utils.Log;
+import rx.Observable;
+
import android.app.Activity;
import android.app.Application;
import android.app.ProgressDialog;
@@ -14,12 +14,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class CgeoApplication extends Application {
- private volatile GeoDataProvider geo;
- private volatile DirectionProvider dir;
+ private volatile Observable<IGeoData> geo;
+ private volatile Observable<Float> dir;
private boolean forceRelog = false; // c:geo needs to log into cache providers
public boolean showLoginToast = true; //login toast shown just once.
- private boolean liveMapHintShown = false; // livemap hint has been shown
- final private StatusUpdater statusUpdater = new StatusUpdater();
+ private boolean liveMapHintShownInThisSession = false; // livemap hint has been shown
private static CgeoApplication instance;
public CgeoApplication() {
@@ -35,26 +34,11 @@ public class CgeoApplication extends Application {
}
@Override
- public void onCreate() {
- new Thread(statusUpdater).start();
- }
-
- @Override
public void onLowMemory() {
Log.i("Cleaning applications cache.");
DataStore.removeAllFromCache();
}
- @Override
- public void onTerminate() {
- Log.d("Terminating c:geo…");
-
- DataStore.clean();
- DataStore.closeDb();
-
- super.onTerminate();
- }
-
/**
* Move the database to/from external cgdata in a new thread,
* showing a progress window
@@ -82,29 +66,11 @@ public class CgeoApplication extends Application {
}.start();
}
- /**
- * Register an observer to receive GeoData information.
- * <br/>
- * If there is a chance that no observers are registered before this
- * method is called, it is necessary to call it from a task implementing
- * a looper interface as the data provider will use listeners that
- * require a looper thread to run.
- *
- * @param observer a geodata observer
- */
- public void addGeoObserver(final IObserver<? super IGeoData> observer) {
- currentGeoObject().addObserver(observer);
- }
-
- public void deleteGeoObserver(final IObserver<? super IGeoData> observer) {
- currentGeoObject().deleteObserver(observer);
- }
-
- private GeoDataProvider currentGeoObject() {
+ public Observable<IGeoData> currentGeoObject() {
if (geo == null) {
synchronized(this) {
if (geo == null) {
- geo = new GeoDataProvider(this);
+ geo = GeoDataProvider.create(this);
}
}
}
@@ -112,22 +78,14 @@ public class CgeoApplication extends Application {
}
public IGeoData currentGeo() {
- return currentGeoObject().getMemory();
+ return currentGeoObject().first().toBlockingObservable().single();
}
- public void addDirectionObserver(final IObserver<? super Float> observer) {
- currentDirObject().addObserver(observer);
- }
-
- public void deleteDirectionObserver(final IObserver<? super Float> observer) {
- currentDirObject().deleteObserver(observer);
- }
-
- private DirectionProvider currentDirObject() {
+ public Observable<Float> currentDirObject() {
if (dir == null) {
synchronized(this) {
if (dir == null) {
- dir = new DirectionProvider(this);
+ dir = DirectionProvider.create(this);
}
}
}
@@ -135,19 +93,15 @@ public class CgeoApplication extends Application {
}
public Float currentDirection() {
- return currentDirObject().getMemory();
- }
-
- public StatusUpdater getStatusUpdater() {
- return statusUpdater;
+ return currentDirObject().first().toBlockingObservable().single();
}
- public boolean isLiveMapHintShown() {
- return liveMapHintShown;
+ public boolean isLiveMapHintShownInThisSession() {
+ return liveMapHintShownInThisSession;
}
- public void setLiveMapHintShown() {
- liveMapHintShown = true;
+ public void setLiveMapHintShownInThisSession() {
+ liveMapHintShownInThisSession = true;
}
/**
diff --git a/main/src/cgeo/geocaching/CompassActivity.java b/main/src/cgeo/geocaching/CompassActivity.java
index 8955afd..6fc2de1 100644
--- a/main/src/cgeo/geocaching/CompassActivity.java
+++ b/main/src/cgeo/geocaching/CompassActivity.java
@@ -150,12 +150,13 @@ public class CompassActivity extends AbstractActivity {
final CgeoApplication app = CgeoApplication.getInstance();
final IGeoData geo = app.currentGeo();
if (geo != null) {
- geoDirHandler.update(geo);
+ geoDirHandler.updateGeoData(geo);
}
final Float dir = app.currentDirection();
if (dir != null) {
- geoDirHandler.update(dir);
+ geoDirHandler.updateDirection(dir);
}
+
}
@Override
diff --git a/main/src/cgeo/geocaching/CreateShortcutActivity.java b/main/src/cgeo/geocaching/CreateShortcutActivity.java
index 7b91ba1..b6ea4f6 100644
--- a/main/src/cgeo/geocaching/CreateShortcutActivity.java
+++ b/main/src/cgeo/geocaching/CreateShortcutActivity.java
@@ -1,8 +1,10 @@
package cgeo.geocaching;
import cgeo.geocaching.activity.AbstractActivity;
+import cgeo.geocaching.list.PseudoList;
import cgeo.geocaching.list.StoredList;
-import cgeo.geocaching.utils.RunnableWithArgument;
+
+import rx.functions.Action1;
import android.content.Intent;
import android.content.Intent.ShortcutIconResource;
@@ -21,17 +23,17 @@ public class CreateShortcutActivity extends AbstractActivity {
}
private void promptForShortcut() {
- new StoredList.UserInterface(this).promptForListSelection(R.string.create_shortcut, new RunnableWithArgument<Integer>() {
+ new StoredList.UserInterface(this).promptForListSelection(R.string.create_shortcut, new Action1<Integer>() {
@Override
- public void run(final Integer listId) {
+ public void call(final Integer listId) {
final Intent shortcut = createShortcut(listId.intValue());
setResult(RESULT_OK, shortcut);
// finish activity to return the shortcut
finish();
}
- });
+ }, false, PseudoList.HISTORY_LIST.id);
}
protected Intent createShortcut(int listId) {
diff --git a/main/src/cgeo/geocaching/DataStore.java b/main/src/cgeo/geocaching/DataStore.java
index 6da1af8..5cc77dc 100644
--- a/main/src/cgeo/geocaching/DataStore.java
+++ b/main/src/cgeo/geocaching/DataStore.java
@@ -25,17 +25,20 @@ import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNull;
+import android.app.SearchManager;
import android.content.ContentValues;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.DatabaseUtils;
+import android.database.MatrixCursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteDoneException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement;
+import android.provider.BaseColumns;
import java.io.File;
import java.io.FilenameFilter;
@@ -811,12 +814,19 @@ public class DataStore {
/**
* Remove obsolete cache directories in c:geo private storage.
+ */
+ public static void removeObsoleteCacheDirectories() {
+ removeObsoleteCacheDirectories(database);
+ }
+
+ /**
+ * Remove obsolete cache directories in c:geo private storage.
*
* @param db
* the read-write database to use
*/
private static void removeObsoleteCacheDirectories(final SQLiteDatabase db) {
- final Pattern oldFilePattern = Pattern.compile("^[GC|TB|O][A-Z0-9]{4,7}$");
+ final Pattern oldFilePattern = Pattern.compile("^[GC|TB|EC|GK|O][A-Z0-9]{4,7}$");
final SQLiteStatement select = db.compileStatement("select count(*) from " + dbTableCaches + " where geocode = ?");
final File[] files = LocalStorage.getStorage().listFiles();
final ArrayList<File> toRemove = new ArrayList<File>(files.length);
@@ -868,28 +878,6 @@ public class DataStore {
db.execSQL("drop table if exists " + dbTableTrackables);
}
- public static String[] getRecentGeocodesForSearch() {
- init();
-
- try {
- long timestamp = System.currentTimeMillis() - DAYS_AFTER_CACHE_IS_DELETED;
- final Cursor cursor = database.query(
- dbTableCaches,
- new String[]{"geocode"},
- "(detailed = 1 and detailedupdate > ?) or reason > 0",
- new String[]{Long.toString(timestamp)},
- null,
- null,
- "detailedupdate desc",
- "100");
-
- return getFirstColumn(cursor);
- } catch (final Exception e) {
- Log.e("DataStore.allDetailedThere", e);
- return new String[0];
- }
- }
-
public static boolean isThere(String geocode, String guid, boolean detailed, boolean checkTime) {
init();
@@ -2302,11 +2290,6 @@ public class DataStore {
return new SearchResult(geocodes);
}
- /** delete caches from the DB store 3 days or more before */
- public static void clean() {
- clean(false);
- }
-
/**
* Remove caches with listId = 0
*
@@ -2355,6 +2338,8 @@ public class DataStore {
cursor.close();
+ geocodes = exceptCachesWithOfflineLog(geocodes);
+
if (!geocodes.isEmpty()) {
Log.d("Database clean: removing " + geocodes.size() + " geocaches from listId=0");
removeCaches(geocodes, LoadFlags.REMOVE_ALL);
@@ -2367,6 +2352,33 @@ public class DataStore {
databaseCleaned = true;
}
+ /**
+ * remove all geocodes from the given list of geocodes where an offline log exists
+ *
+ * @param geocodes
+ * @return
+ */
+ private static Set<String> exceptCachesWithOfflineLog(Set<String> geocodes) {
+ if (geocodes.isEmpty()) {
+ return geocodes;
+ }
+
+ init();
+ final Cursor cursor = database.query(
+ dbTableLogsOffline,
+ new String[] { "geocode" },
+ null,
+ null,
+ null,
+ null,
+ null);
+
+ final List<String> geocodesWithOfflineLog = Arrays.asList(getFirstColumn(cursor));
+
+ geocodes.removeAll(geocodesWithOfflineLog);
+ return geocodes;
+ }
+
public static void removeAllFromCache() {
// clean up CacheCache
cacheCache.removeAllFromCache();
@@ -2897,21 +2909,6 @@ public class DataStore {
return waypoints;
}
- public static String[] getTrackableCodes() {
- init();
-
- final Cursor cursor = database.query(
- dbTableTrackables,
- new String[] { "tbcode" },
- null,
- null,
- null,
- null,
- "updated DESC",
- "100");
- return getFirstColumn(cursor);
- }
-
/**
* Extract the first column of the cursor rows and close the cursor.
*
@@ -3098,4 +3095,103 @@ public class DataStore {
return missingFromSearch;
}
+ public static Cursor findSuggestions(final String searchTerm) {
+ // require 3 characters, otherwise there are to many results
+ if (StringUtils.length(searchTerm) < 3) {
+ return null;
+ }
+ init();
+ final MatrixCursor resultCursor = new MatrixCursor(new String[] {
+ BaseColumns._ID,
+ SearchManager.SUGGEST_COLUMN_TEXT_1,
+ SearchManager.SUGGEST_COLUMN_TEXT_2,
+ SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
+ SearchManager.SUGGEST_COLUMN_QUERY
+ });
+ try {
+ final String selectionArg = getSuggestionArgument(searchTerm);
+ findCaches(resultCursor, selectionArg);
+ findTrackables(resultCursor, selectionArg);
+ } catch (final Exception e) {
+ Log.e("DataStore.loadBatchOfStoredGeocodes", e);
+ }
+ return resultCursor;
+ }
+
+ private static void findCaches(final MatrixCursor resultCursor, final String selectionArg) {
+ Cursor cursor = database.query(
+ dbTableCaches,
+ new String[] { "geocode", "name" },
+ "geocode IS NOT NULL AND geocode != '' AND (geocode LIKE ? OR name LIKE ? OR owner LIKE ?)",
+ new String[] { selectionArg, selectionArg, selectionArg },
+ null,
+ null,
+ "name");
+ while (cursor.moveToNext()) {
+ final String geocode = cursor.getString(0);
+ resultCursor.addRow(new String[] {
+ String.valueOf(resultCursor.getCount()),
+ cursor.getString(1),
+ geocode,
+ Intents.ACTION_GEOCACHE,
+ geocode
+ });
+ }
+ cursor.close();
+ }
+
+ private static String getSuggestionArgument(String input) {
+ return "%" + StringUtils.trim(input) + "%";
+ }
+
+ private static void findTrackables(final MatrixCursor resultCursor, final String selectionArg) {
+ Cursor cursor = database.query(
+ dbTableTrackables,
+ new String[] { "tbcode", "title" },
+ "tbcode IS NOT NULL AND tbcode != '' AND (tbcode LIKE ? OR title LIKE ?)",
+ new String[] { selectionArg, selectionArg },
+ null,
+ null,
+ "title");
+ while (cursor.moveToNext()) {
+ final String tbcode = cursor.getString(0);
+ resultCursor.addRow(new String[] {
+ String.valueOf(resultCursor.getCount()),
+ cursor.getString(1),
+ tbcode,
+ Intents.ACTION_TRACKABLE,
+ tbcode
+ });
+ }
+ cursor.close();
+ }
+
+ public static String[] getSuggestions(final String table, final String column, final String input) {
+ Cursor cursor = database.rawQuery("SELECT DISTINCT " + column
+ + " FROM " + table
+ + " WHERE " + column + " LIKE ?"
+ + " ORDER BY " + column + " COLLATE NOCASE ASC;", new String[] { getSuggestionArgument(input) });
+ return getFirstColumn(cursor);
+ }
+
+ public static String[] getSuggestionsOwnerName(String input) {
+ return getSuggestions(dbTableCaches, "owner", input);
+ }
+
+ public static String[] getSuggestionsTrackableCode(String input) {
+ return getSuggestions(dbTableTrackables, "tbcode", input);
+ }
+
+ public static String[] getSuggestionsFinderName(String input) {
+ return getSuggestions(dbTableLogs, "author", input);
+ }
+
+ public static String[] getSuggestionsGeocode(String input) {
+ return getSuggestions(dbTableCaches, "geocode", input);
+ }
+
+ public static String[] getSuggestionsKeyword(String input) {
+ return getSuggestions(dbTableCaches, "name", input);
+ }
+
}
diff --git a/main/src/cgeo/geocaching/DirectionProvider.java b/main/src/cgeo/geocaching/DirectionProvider.java
index ae58fed..49a433e 100644
--- a/main/src/cgeo/geocaching/DirectionProvider.java
+++ b/main/src/cgeo/geocaching/DirectionProvider.java
@@ -1,9 +1,15 @@
package cgeo.geocaching;
import cgeo.geocaching.compatibility.Compatibility;
-import cgeo.geocaching.utils.MemorySubject;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import rx.Observable;
+import rx.Observable.OnSubscribe;
+import rx.Subscriber;
+import rx.Subscription;
+import rx.observables.ConnectableObservable;
+import rx.subjects.BehaviorSubject;
+import rx.subscriptions.Subscriptions;
+import rx.functions.Action0;
import android.app.Activity;
import android.content.Context;
@@ -12,58 +18,61 @@ import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
-public class DirectionProvider extends MemorySubject<Float> implements SensorEventListener {
+public class DirectionProvider implements OnSubscribe<Float> {
private final SensorManager sensorManager;
+ private final BehaviorSubject<Float> subject = BehaviorSubject.create(0.0f);
- // Previous values signaled to observers to avoid re-sending the same value when the
- // device doesn't change orientation. The orientation is usually given with a 1 degree
- // precision by Android, so it is not uncommon to obtain exactly the same value several
- // times.
- private float previous = -1;
-
- public DirectionProvider(final Context context) {
- sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
-
+ static public Observable<Float> create(final Context context) {
+ return new DirectionProvider((SensorManager) context.getSystemService(Context.SENSOR_SERVICE)).worker.refCount();
}
- @Override
- protected void onFirstObserver() {
- @SuppressWarnings("deprecation")
- // This will be removed when using a new location service. Until then, it is okay to be used.
- final Sensor defaultSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
- sensorManager.registerListener(this, defaultSensor, SensorManager.SENSOR_DELAY_NORMAL);
+ private DirectionProvider(final SensorManager sensorManager) {
+ this.sensorManager = sensorManager;
}
@Override
- protected void onLastObserver() {
- sensorManager.unregisterListener(this);
+ public void call(final Subscriber<? super Float> subscriber) {
+ subject.distinctUntilChanged().subscribe(subscriber);
}
- @Override
- public void onAccuracyChanged(final Sensor sensor, int accuracy) {
- /*
- * There is a bug in Android, which apparently causes this method to be called every
- * time the sensor _value_ changed, even if the _accuracy_ did not change. So logging
- * this event leads to the log being flooded with multiple entries _per second_,
- * which I experienced when running cgeo in a building (with GPS and network being
- * unreliable).
- *
- * See for example https://code.google.com/p/android/issues/detail?id=14792
- */
+ private final ConnectableObservable<Float> worker = new ConnectableObservable<Float>(this) {
+ @Override
+ public Subscription connect() {
+ @SuppressWarnings("deprecation")
+ // This will be removed when using a new location service. Until then, it is okay to be used.
+ final Sensor defaultSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
+ final SensorEventListener listener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(final SensorEvent event) {
+ subject.onNext(event.values[0]);
+ }
- //Log.i(Settings.tag, "Compass' accuracy is low (" + accuracy + ")");
- }
+ @Override
+ public void onAccuracyChanged(final Sensor sensor, final int accuracy) {
+ /*
+ * There is a bug in Android, which apparently causes this method to be called every
+ * time the sensor _value_ changed, even if the _accuracy_ did not change. So logging
+ * this event leads to the log being flooded with multiple entries _per second_,
+ * which I experienced when running cgeo in a building (with GPS and network being
+ * unreliable).
+ *
+ * See for example https://code.google.com/p/android/issues/detail?id=14792
+ */
- @Override
- @SuppressFBWarnings("FE_FLOATING_POINT_EQUALITY")
- public void onSensorChanged(final SensorEvent event) {
- final float direction = event.values[0];
- if (direction != previous) {
- notifyObservers(direction);
- previous = direction;
+ //Log.i(Settings.tag, "Compass' accuracy is low (" + accuracy + ")");
+ }
+ };
+
+ sensorManager.registerListener(listener, defaultSensor, SensorManager.SENSOR_DELAY_NORMAL);
+ return Subscriptions.create(new Action0() {
+ @Override
+ public void call() {
+ sensorManager.unregisterListener(listener);
+ }
+ });
}
- }
+ };
/**
* Take the phone rotation (through a given activity) in account and adjust the direction.
@@ -72,6 +81,7 @@ public class DirectionProvider extends MemorySubject<Float> implements SensorEve
* @param direction the unadjusted direction in degrees, in the [0, 360[ range
* @return the adjusted direction in degrees, in the [0, 360[ range
*/
+
public static float getDirectionNow(final Activity activity, final float direction) {
return Compatibility.getDirectionNow(direction, activity);
}
diff --git a/main/src/cgeo/geocaching/EditWaypointActivity.java b/main/src/cgeo/geocaching/EditWaypointActivity.java
index 6d0f822..9010d3a 100644
--- a/main/src/cgeo/geocaching/EditWaypointActivity.java
+++ b/main/src/cgeo/geocaching/EditWaypointActivity.java
@@ -17,10 +17,10 @@ import cgeo.geocaching.utils.GeoDirHandler;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.TextUtils;
-import com.googlecode.androidannotations.annotations.EActivity;
-import com.googlecode.androidannotations.annotations.Extra;
-import com.googlecode.androidannotations.annotations.InstanceState;
-import com.googlecode.androidannotations.annotations.ViewById;
+import org.androidannotations.annotations.EActivity;
+import org.androidannotations.annotations.Extra;
+import org.androidannotations.annotations.InstanceState;
+import org.androidannotations.annotations.ViewById;
import org.apache.commons.lang3.StringUtils;
@@ -110,12 +110,14 @@ public class EditWaypointActivity extends AbstractActivity {
buttonLon.setText(waypoint.getCoords().format(GeopointFormatter.Format.LON_DECMINUTE));
}
waypointName.setText(Html.fromHtml(StringUtils.trimToEmpty(waypoint.getName())).toString());
+ Dialogs.moveCursorToEnd(waypointName);
if (TextUtils.containsHtml(waypoint.getNote())) {
note.setText(Html.fromHtml(StringUtils.trimToEmpty(waypoint.getNote())).toString());
}
else {
note.setText(StringUtils.trimToEmpty(waypoint.getNote()));
}
+ Dialogs.moveCursorToEnd(note);
}
final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_ONLY);
setCoordsModificationVisibility(ConnectorFactory.getConnector(geocode), cache);
diff --git a/main/src/cgeo/geocaching/GeoDataProvider.java b/main/src/cgeo/geocaching/GeoDataProvider.java
index 73aefce..e18f735 100644
--- a/main/src/cgeo/geocaching/GeoDataProvider.java
+++ b/main/src/cgeo/geocaching/GeoDataProvider.java
@@ -3,9 +3,16 @@ package cgeo.geocaching;
import cgeo.geocaching.enumerations.LocationProviderType;
import cgeo.geocaching.geopoint.Geopoint;
import cgeo.geocaching.utils.Log;
-import cgeo.geocaching.utils.MemorySubject;
import org.apache.commons.lang3.StringUtils;
+import rx.Observable;
+import rx.Observable.OnSubscribe;
+import rx.Subscriber;
+import rx.Subscription;
+import rx.observables.ConnectableObservable;
+import rx.subjects.BehaviorSubject;
+import rx.subscriptions.Subscriptions;
+import rx.functions.Action0;
import android.content.Context;
import android.location.GpsSatellite;
@@ -15,22 +22,14 @@ import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Provide information about the user location. This class should be instantiated only once per application.
- */
-class GeoDataProvider extends MemorySubject<IGeoData> {
+class GeoDataProvider implements OnSubscribe<IGeoData> {
private static final String LAST_LOCATION_PSEUDO_PROVIDER = "last";
private final LocationManager geoManager;
- private final GpsStatus.Listener gpsStatusListener = new GpsStatusListener();
private final LocationData gpsLocation = new LocationData();
private final LocationData netLocation = new LocationData();
- private final Listener networkListener = new Listener(LocationManager.NETWORK_PROVIDER, netLocation);
- private final Listener gpsListener = new Listener(LocationManager.GPS_PROVIDER, gpsLocation);
- private final Unregisterer unregisterer = new Unregisterer();
+ private final BehaviorSubject<IGeoData> subject;
+
public boolean gpsEnabled = false;
public int satellitesVisible = 0;
public int satellitesFixed = 0;
@@ -111,51 +110,6 @@ class GeoDataProvider extends MemorySubject<IGeoData> {
}
}
- private class Unregisterer extends Thread {
-
- private boolean unregisterRequested = false;
- private final ArrayBlockingQueue<Boolean> queue = new ArrayBlockingQueue<Boolean>(1);
-
- public void cancelUnregister() {
- try {
- queue.put(false);
- } catch (final InterruptedException e) {
- // Do nothing
- }
- }
-
- public void lateUnregister() {
- try {
- queue.put(true);
- } catch (final InterruptedException e) {
- // Do nothing
- }
- }
-
- @Override
- public void run() {
- try {
- while (true) {
- if (unregisterRequested) {
- final Boolean element = queue.poll(2500, TimeUnit.MILLISECONDS);
- if (element == null) {
- // Timeout
- unregisterListeners();
- unregisterRequested = false;
- } else {
- unregisterRequested = element;
- }
- } else {
- unregisterRequested = queue.take();
- }
- }
- } catch (final InterruptedException e) {
- // Do nothing
- }
- }
-
- }
-
/**
* Build a new geo data provider object.
* <p/>
@@ -164,10 +118,50 @@ class GeoDataProvider extends MemorySubject<IGeoData> {
*
* @param context the context used to retrieve the system services
*/
- GeoDataProvider(final Context context) {
+ protected GeoDataProvider(final Context context) {
geoManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
- unregisterer.start();
+ subject = BehaviorSubject.create(findInitialLocation());
+ }
+
+ public static Observable<IGeoData> create(final Context context) {
+ final GeoDataProvider provider = new GeoDataProvider(context);
+ return provider.worker.refCount();
+ }
+ @Override
+ public void call(final Subscriber<? super IGeoData> subscriber) {
+ subject.subscribe(subscriber);
+ }
+
+ final ConnectableObservable<IGeoData> worker = new ConnectableObservable<IGeoData>(this) {
+ @Override
+ public Subscription connect() {
+ final GpsStatus.Listener gpsStatusListener = new GpsStatusListener();
+ geoManager.addGpsStatusListener(gpsStatusListener);
+
+ final Listener networkListener = new Listener(LocationManager.NETWORK_PROVIDER, netLocation);
+ final Listener gpsListener = new Listener(LocationManager.GPS_PROVIDER, gpsLocation);
+
+ for (final Listener listener : new Listener[] { networkListener, gpsListener }) {
+ try {
+ geoManager.requestLocationUpdates(listener.locationProvider, 0, 0, listener);
+ } catch (final Exception e) {
+ Log.w("There is no location provider " + listener.locationProvider);
+ }
+ }
+
+ return Subscriptions.create(new Action0() {
+ @Override
+ public void call() {
+ geoManager.removeUpdates(networkListener);
+ geoManager.removeUpdates(gpsListener);
+ geoManager.removeGpsStatusListener(gpsStatusListener);
+ }
+ });
+ }
+ };
+
+ private IGeoData findInitialLocation() {
final Location initialLocation = new Location(LAST_LOCATION_PSEUDO_PROVIDER);
try {
// Try to find a sensible initial location from the last locations known to Android.
@@ -195,45 +189,13 @@ class GeoDataProvider extends MemorySubject<IGeoData> {
}
// Start with an historical GeoData just in case someone queries it before we get
// a chance to get any information.
- notifyObservers(new GeoData(initialLocation, false, 0, 0));
+ return new GeoData(initialLocation, false, 0, 0);
}
private static void copyCoords(final Location target, final Location source) {
target.setLatitude(source.getLatitude());
target.setLongitude(source.getLongitude());
}
- private void registerListeners() {
- geoManager.addGpsStatusListener(gpsStatusListener);
-
- for (final Listener listener : new Listener[] { networkListener, gpsListener }) {
- try {
- geoManager.requestLocationUpdates(listener.locationProvider, 0, 0, listener);
- } catch (final Exception e) {
- Log.w("There is no location provider " + listener.locationProvider);
- }
- }
- }
-
- private synchronized void unregisterListeners() {
- // This method must be synchronized because it will be called asynchronously from the Unregisterer thread.
- // We check that no observers have been re-added to prevent a race condition.
- if (sizeObservers() == 0) {
- geoManager.removeUpdates(networkListener);
- geoManager.removeUpdates(gpsListener);
- geoManager.removeGpsStatusListener(gpsStatusListener);
- }
- }
-
- @Override
- protected void onFirstObserver() {
- unregisterer.cancelUnregister();
- registerListeners();
- }
-
- @Override
- protected void onLastObserver() {
- unregisterer.lateUnregister();
- }
private class Listener implements LocationListener {
private final String locationProvider;
@@ -336,7 +298,7 @@ class GeoDataProvider extends MemorySubject<IGeoData> {
// We do not necessarily get signalled when satellites go to 0/0.
final int visible = gpsLocation.isRecent() ? satellitesVisible : 0;
final IGeoData current = new GeoData(locationData.location, gpsEnabled, visible, satellitesFixed);
- notifyObservers(current);
+ subject.onNext(current);
}
}
diff --git a/main/src/cgeo/geocaching/Geocache.java b/main/src/cgeo/geocaching/Geocache.java
index 156c4b6..bac4600 100644
--- a/main/src/cgeo/geocaching/Geocache.java
+++ b/main/src/cgeo/geocaching/Geocache.java
@@ -165,7 +165,7 @@ public class Geocache implements ICache, IWaypoint {
*
* @param gpxParser
*/
- public Geocache(GPXParser gpxParser) {
+ public Geocache(@SuppressWarnings("unused") GPXParser gpxParser) {
setReliableLatLon(true);
setAttributes(Collections.<String> emptyList());
setWaypoints(Collections.<Waypoint> emptyList(), false);
@@ -370,6 +370,7 @@ public class Geocache implements ICache, IWaypoint {
* the other cache to compare this one to
* @return true if both caches have the same content
*/
+ @SuppressWarnings("deprecation")
@SuppressFBWarnings("FE_FLOATING_POINT_EQUALITY")
private boolean isEqualTo(final Geocache other) {
return detailed == other.detailed &&
@@ -438,22 +439,6 @@ public class Geocache implements ICache, IWaypoint {
return hidden.compareTo(cal.getTime()) >= 0;
}
- /**
- * Checks if a page contains the guid of a cache
- *
- * @param page
- * the page to search in, may be null
- * @return true if the page contains the guid of the cache, false otherwise
- */
- public boolean isGuidContainedInPage(final String page) {
- if (StringUtils.isBlank(page) || StringUtils.isBlank(guid)) {
- return false;
- }
- final Boolean found = Pattern.compile(guid, Pattern.CASE_INSENSITIVE).matcher(page).find();
- Log.i("Geocache.isGuidContainedInPage: guid '" + guid + "' " + (found ? "" : "not ") + "found");
- return found;
- }
-
public boolean isEventCache() {
return cacheType.getValue().isEvent();
}
@@ -817,13 +802,7 @@ public class Geocache implements ICache, IWaypoint {
}
public boolean showSize() {
- if (size == CacheSize.NOT_CHOSEN) {
- return false;
- }
- if (isEventCache() || isVirtual()) {
- return false;
- }
- return true;
+ return !(size == CacheSize.NOT_CHOSEN || isEventCache() || isVirtual());
}
public long getUpdated() {
@@ -1373,17 +1352,6 @@ public class Geocache implements ICache, IWaypoint {
}
/**
- * Retrieve a given waypoint.
- *
- * @param index
- * the index of the waypoint
- * @return waypoint or <code>null</code> if index is out of range
- */
- public Waypoint getWaypoint(final int index) {
- return index >= 0 && index < waypoints.size() ? waypoints.get(index) : null;
- }
-
- /**
* Lookup a waypoint by its id.
*
* @param id
@@ -1660,6 +1628,8 @@ public class Geocache implements ICache, IWaypoint {
StaticMapsProvider.downloadMaps(cache);
+ imgGetter.waitForBackgroundLoading(handler);
+
if (handler != null) {
handler.sendMessage(Message.obtain());
}
@@ -1719,8 +1689,9 @@ public class Geocache implements ICache, IWaypoint {
patterns.add(Pattern.compile("\\b(\\d{1,2})(?:\\.00)?\\s+" + Pattern.quote(hourLocalized), Pattern.CASE_INSENSITIVE));
}
+ final String searchText = getShortDescription() + ' ' + getDescription();
for (Pattern pattern : patterns) {
- final MatcherWrapper matcher = new MatcherWrapper(pattern, getDescription());
+ final MatcherWrapper matcher = new MatcherWrapper(pattern, searchText);
while (matcher.find()) {
try {
final int hours = Integer.parseInt(matcher.group(1));
@@ -1831,4 +1802,8 @@ public class Geocache implements ICache, IWaypoint {
return 0;
}
+ public boolean applyDistanceRule() {
+ return (getType().applyDistanceRule() || hasUserModifiedCoords()) && getConnector() == GCConnector.getInstance();
+ }
+
}
diff --git a/main/src/cgeo/geocaching/ImageSelectActivity.java b/main/src/cgeo/geocaching/ImageSelectActivity.java
index 12d1e84..766149c 100644
--- a/main/src/cgeo/geocaching/ImageSelectActivity.java
+++ b/main/src/cgeo/geocaching/ImageSelectActivity.java
@@ -6,6 +6,7 @@ import butterknife.InjectView;
import cgeo.geocaching.activity.AbstractActivity;
import cgeo.geocaching.files.LocalStorage;
import cgeo.geocaching.settings.Settings;
+import cgeo.geocaching.ui.dialog.Dialogs;
import cgeo.geocaching.utils.ImageUtils;
import cgeo.geocaching.utils.Log;
@@ -111,10 +112,12 @@ public class ImageSelectActivity extends AbstractActivity {
if (StringUtils.isNotBlank(imageCaption)) {
captionView.setText(imageCaption);
+ Dialogs.moveCursorToEnd(captionView);
}
if (StringUtils.isNotBlank(imageDescription)) {
descriptionView.setText(imageDescription);
+ Dialogs.moveCursorToEnd(captionView);
}
scaleView.setSelection(scaleChoiceIndex);
diff --git a/main/src/cgeo/geocaching/ImagesActivity.java b/main/src/cgeo/geocaching/ImagesActivity.java
index 29bc8c7..3da1ade 100644
--- a/main/src/cgeo/geocaching/ImagesActivity.java
+++ b/main/src/cgeo/geocaching/ImagesActivity.java
@@ -6,6 +6,7 @@ import cgeo.geocaching.ui.ImagesList;
import cgeo.geocaching.ui.ImagesList.ImageType;
import org.apache.commons.collections4.CollectionUtils;
+import rx.Subscription;
import android.content.Context;
import android.content.Intent;
@@ -22,8 +23,9 @@ public class ImagesActivity extends AbstractActivity {
private boolean offline;
private ArrayList<Image> imageNames;
- private ImagesList imagesList;
private ImageType imgType = ImageType.SpoilerImages;
+ private ImagesList imagesList;
+ private Subscription subscription;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -60,18 +62,19 @@ public class ImagesActivity extends AbstractActivity {
offline = DataStore.isOffline(geocode, null) && (imgType == ImageType.SpoilerImages
|| Settings.isStoreLogImages());
+
}
@Override
public void onStart() {
super.onStart();
- imagesList.loadImages(findViewById(R.id.spoiler_list), imageNames, offline);
+ subscription = imagesList.loadImages(findViewById(R.id.spoiler_list), imageNames, offline);
}
@Override
public void onStop() {
// Reclaim native memory faster than the finalizers would
- imagesList.removeAllViews();
+ subscription.unsubscribe();
super.onStop();
}
diff --git a/main/src/cgeo/geocaching/Intents.java b/main/src/cgeo/geocaching/Intents.java
index d9d9829..5c969a1 100644
--- a/main/src/cgeo/geocaching/Intents.java
+++ b/main/src/cgeo/geocaching/Intents.java
@@ -29,4 +29,8 @@ public class Intents {
public static final String EXTRA_WAYPOINT_ID = PREFIX + "waypoint_id";
public static final String EXTRA_CACHELIST = PREFIX + "cache_list";
public static final String EXTRA_POCKET_GUID = PREFIX + "pocket_guid";
+
+ private static final String PREFIX_ACTION = "cgeo.geocaching.intent.action.";
+ public static final String ACTION_GEOCACHE = PREFIX_ACTION + "GEOCACHE";
+ public static final String ACTION_TRACKABLE = PREFIX_ACTION + "TRACKABLE";
}
diff --git a/main/src/cgeo/geocaching/LogCacheActivity.java b/main/src/cgeo/geocaching/LogCacheActivity.java
index 301de01..4d2815b 100644
--- a/main/src/cgeo/geocaching/LogCacheActivity.java
+++ b/main/src/cgeo/geocaching/LogCacheActivity.java
@@ -13,6 +13,7 @@ import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.twitter.Twitter;
import cgeo.geocaching.ui.Formatter;
import cgeo.geocaching.ui.dialog.DateDialog;
+import cgeo.geocaching.ui.dialog.Dialogs;
import cgeo.geocaching.utils.AsyncTaskWithProgress;
import cgeo.geocaching.utils.DateUtils;
import cgeo.geocaching.utils.Log;
@@ -151,7 +152,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
final TextView actionButton = (TextView) inventoryItem.findViewById(R.id.action);
actionButton.setId(tb.id);
actionButtons.put(actionButton.getId(), tb);
- actionButton.setText(res.getString(tb.action.resourceId) + " â–¼");
+ actionButton.setText(tb.action.getLabel() + " â–¼");
actionButton.setOnClickListener(new View.OnClickListener() {
@Override
@@ -301,6 +302,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
final EditText logView = (EditText) findViewById(R.id.log);
if (StringUtils.isBlank(currentLogText()) && StringUtils.isNotBlank(text)) {
logView.setText(text);
+ Dialogs.moveCursorToEnd(logView);
}
tweetCheck.setChecked(true);
@@ -633,11 +635,11 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
alert.create().show();
}
- private String[] getTBLogTypes() {
+ private static String[] getTBLogTypes() {
final LogTypeTrackable[] logTypeValues = LogTypeTrackable.values();
String[] logTypes = new String[logTypeValues.length];
for (int i = 0; i < logTypes.length; i++) {
- logTypes[i] = res.getString(logTypeValues[i].resourceId);
+ logTypes[i] = logTypeValues[i].getLabel();
}
return logTypes;
}
diff --git a/main/src/cgeo/geocaching/LogTrackableActivity.java b/main/src/cgeo/geocaching/LogTrackableActivity.java
index 5246fa9..fabe391 100644
--- a/main/src/cgeo/geocaching/LogTrackableActivity.java
+++ b/main/src/cgeo/geocaching/LogTrackableActivity.java
@@ -3,8 +3,8 @@ package cgeo.geocaching;
import butterknife.ButterKnife;
import butterknife.InjectView;
-import cgeo.geocaching.connector.gc.GCParser;
import cgeo.geocaching.connector.gc.GCLogin;
+import cgeo.geocaching.connector.gc.GCParser;
import cgeo.geocaching.enumerations.LogType;
import cgeo.geocaching.enumerations.StatusCode;
import cgeo.geocaching.network.Network;
@@ -13,6 +13,7 @@ import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.twitter.Twitter;
import cgeo.geocaching.ui.Formatter;
import cgeo.geocaching.ui.dialog.DateDialog;
+import cgeo.geocaching.ui.dialog.Dialogs;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.LogTemplateProvider.LogContext;
@@ -127,6 +128,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat
if (StringUtils.isNotBlank(extras.getString(Intents.EXTRA_TRACKING_CODE))) {
trackingEditText.setText(extras.getString(Intents.EXTRA_TRACKING_CODE));
+ Dialogs.moveCursorToEnd(trackingEditText);
}
}
diff --git a/main/src/cgeo/geocaching/MainActivity.java b/main/src/cgeo/geocaching/MainActivity.java
index 924c66d..6d89991 100644
--- a/main/src/cgeo/geocaching/MainActivity.java
+++ b/main/src/cgeo/geocaching/MainActivity.java
@@ -10,6 +10,7 @@ import cgeo.geocaching.enumerations.CacheType;
import cgeo.geocaching.enumerations.StatusCode;
import cgeo.geocaching.geopoint.Geopoint;
import cgeo.geocaching.geopoint.Units;
+import cgeo.geocaching.list.PseudoList;
import cgeo.geocaching.list.StoredList;
import cgeo.geocaching.maps.CGeoMap;
import cgeo.geocaching.settings.Settings;
@@ -19,14 +20,17 @@ 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.RunnableWithArgument;
import cgeo.geocaching.utils.Version;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
-
-import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
+import rx.Observable;
+import rx.Observable.OnSubscribe;
+import rx.Subscriber;
+import rx.android.observables.AndroidObservable;
+import rx.schedulers.Schedulers;
+import rx.functions.Action1;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
@@ -48,7 +52,6 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -77,8 +80,6 @@ public class MainActivity extends AbstractActivity {
private boolean cleanupRunning = false;
private int countBubbleCnt = 0;
private Geopoint addCoords = null;
- private List<Address> addresses = null;
- private boolean addressObtaining = false;
private boolean initialized = false;
private final UpdateLocation locationUpdater = new UpdateLocation();
@@ -104,7 +105,7 @@ public class MainActivity extends AbstractActivity {
if (conn.isLoggedIn()) {
userInfo.append(conn.getUserName());
if (conn.getCachesFound() >= 0) {
- userInfo.append(" (").append(String.valueOf(conn.getCachesFound())).append(')');
+ userInfo.append(" (").append(conn.getCachesFound()).append(')');
}
userInfo.append(Formatter.SEPARATOR);
}
@@ -115,40 +116,24 @@ public class MainActivity extends AbstractActivity {
}
};
- private Handler obtainAddressHandler = new Handler() {
-
- @Override
- public void handleMessage(final Message msg) {
- try {
- if (CollectionUtils.isNotEmpty(addresses)) {
- final Address address = addresses.get(0);
- final ArrayList<String> addressParts = new ArrayList<String>();
-
- final String countryName = address.getCountryName();
- if (countryName != null) {
- addressParts.add(countryName);
- }
- final String locality = address.getLocality();
- if (locality != null) {
- addressParts.add(locality);
- } else {
- final String adminArea = address.getAdminArea();
- if (adminArea != null) {
- addressParts.add(adminArea);
- }
- }
-
- addCoords = app.currentGeo().getCoords();
+ private static String formatAddress(final Address address) {
+ final ArrayList<String> addressParts = new ArrayList<String>();
- navLocation.setText(StringUtils.join(addressParts, ", "));
- }
- } catch (RuntimeException e) {
- // nothing
+ final String countryName = address.getCountryName();
+ if (countryName != null) {
+ addressParts.add(countryName);
+ }
+ final String locality = address.getLocality();
+ if (locality != null) {
+ addressParts.add(locality);
+ } else {
+ final String adminArea = address.getAdminArea();
+ if (adminArea != null) {
+ addressParts.add(adminArea);
}
-
- addresses = null;
}
- };
+ return StringUtils.join(addressParts, ", ");
+ }
private class SatellitesHandler extends GeoDirHandler {
@@ -285,7 +270,7 @@ public class MainActivity extends AbstractActivity {
@Override
public boolean onPrepareOptionsMenu(final Menu menu) {
super.onPrepareOptionsMenu(menu);
- menu.findItem(R.id.menu_pocket_queries).setVisible(Settings.isPremiumMember());
+ menu.findItem(R.id.menu_pocket_queries).setVisible(Settings.isGCPremiumMember());
return true;
}
@@ -309,13 +294,13 @@ public class MainActivity extends AbstractActivity {
startScannerApplication();
return true;
case R.id.menu_pocket_queries:
- if (!Settings.isPremiumMember()) {
+ if (!Settings.isGCPremiumMember()) {
return true;
}
- new PocketQueryList.UserInterface(MainActivity.this).promptForListSelection(new RunnableWithArgument<PocketQueryList>() {
+ PocketQueryList.promptForListSelection(this, new Action1<PocketQueryList>() {
@Override
- public void run(final PocketQueryList pql) {
+ public void call(final PocketQueryList pql) {
CacheListActivity.startActivityPocket(MainActivity.this, pql);
}
});
@@ -388,14 +373,14 @@ public class MainActivity extends AbstractActivity {
@Override
public boolean onLongClick(final View v) {
- new StoredList.UserInterface(MainActivity.this).promptForListSelection(R.string.list_title, new RunnableWithArgument<Integer>() {
+ new StoredList.UserInterface(MainActivity.this).promptForListSelection(R.string.list_title, new Action1<Integer>() {
@Override
- public void run(final Integer selectedListId) {
+ public void call(final Integer selectedListId) {
Settings.saveLastList(selectedListId);
CacheListActivity.startActivityOffline(MainActivity.this);
}
- });
+ }, false, PseudoList.HISTORY_LIST.id);
return true;
}
});
@@ -525,52 +510,60 @@ public class MainActivity extends AbstractActivity {
@Override
public void updateGeoData(final IGeoData geo) {
- try {
- if (geo.getCoords() != null) {
- if (!nearestView.isClickable()) {
- nearestView.setFocusable(true);
- nearestView.setClickable(true);
- nearestView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- cgeoFindNearest(v);
- }
- });
- nearestView.setBackgroundResource(R.drawable.main_nearby);
+ if (!nearestView.isClickable()) {
+ nearestView.setFocusable(true);
+ nearestView.setClickable(true);
+ nearestView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ cgeoFindNearest(v);
}
+ });
+ nearestView.setBackgroundResource(R.drawable.main_nearby);
+ }
- navType.setText(res.getString(geo.getLocationProvider().resourceId));
+ navType.setText(res.getString(geo.getLocationProvider().resourceId));
- if (geo.getAccuracy() >= 0) {
- int speed = Math.round(geo.getSpeed()) * 60 * 60 / 1000;
- navAccuracy.setText("±" + Units.getDistanceFromMeters(geo.getAccuracy()) + Formatter.SEPARATOR + Units.getSpeed(speed));
- } else {
- navAccuracy.setText(null);
- }
+ if (geo.getAccuracy() >= 0) {
+ int speed = Math.round(geo.getSpeed()) * 60 * 60 / 1000;
+ navAccuracy.setText("±" + Units.getDistanceFromMeters(geo.getAccuracy()) + Formatter.SEPARATOR + Units.getSpeed(speed));
+ } else {
+ navAccuracy.setText(null);
+ }
- if (Settings.isShowAddress()) {
- if (addCoords == null) {
- navLocation.setText(res.getString(R.string.loc_no_addr));
- }
- if (addCoords == null || (geo.getCoords().distanceTo(addCoords) > 0.5 && !addressObtaining)) {
- (new ObtainAddressThread()).start();
+ if (Settings.isShowAddress()) {
+ if (addCoords == null) {
+ navLocation.setText(R.string.loc_no_addr);
+ }
+ if (addCoords == null || (geo.getCoords().distanceTo(addCoords) > 0.5)) {
+ final Observable<String> address = Observable.create(new OnSubscribe<String>() {
+ @Override
+ public void call(final Subscriber<? super String> subscriber) {
+ try {
+ addCoords = geo.getCoords();
+ final Geocoder geocoder = new Geocoder(MainActivity.this, Locale.getDefault());
+ final Geopoint coords = app.currentGeo().getCoords();
+ final List<Address> addresses = geocoder.getFromLocation(coords.getLatitude(), coords.getLongitude(), 1);
+ if (!addresses.isEmpty()) {
+ subscriber.onNext(formatAddress(addresses.get(0)));
+ }
+ subscriber.onCompleted();
+ } catch (final Exception e) {
+ subscriber.onError(e);
+ }
}
- } else {
- navLocation.setText(geo.getCoords().toString());
- }
- } else {
- if (nearestView.isClickable()) {
- nearestView.setFocusable(false);
- nearestView.setClickable(false);
- nearestView.setOnClickListener(null);
- nearestView.setBackgroundResource(R.drawable.main_nearby_disabled);
- }
- navType.setText(null);
- navAccuracy.setText(null);
- navLocation.setText(res.getString(R.string.loc_trying));
+ }).subscribeOn(Schedulers.io());
+ AndroidObservable.fromActivity(MainActivity.this, address)
+ .onErrorResumeNext(Observable.from(geo.getCoords().toString()))
+ .subscribe(new Action1<String>() {
+ @Override
+ public void call(final String address) {
+ navLocation.setText(address);
+ }
+ });
}
- } catch (RuntimeException e) {
- Log.w("Failed to update location.");
+ } else {
+ navLocation.setText(geo.getCoords().toString());
}
}
}
@@ -579,7 +572,7 @@ public class MainActivity extends AbstractActivity {
* @param v
* unused here but needed since this method is referenced from XML layout
*/
- public void cgeoFindOnMap(final View v) {
+ public void cgeoFindOnMap(@SuppressWarnings("unused") final View v) {
findOnMap.setPressed(true);
CGeoMap.startActivityLiveMap(this);
}
@@ -588,7 +581,7 @@ public class MainActivity extends AbstractActivity {
* @param v
* unused here but needed since this method is referenced from XML layout
*/
- public void cgeoFindNearest(final View v) {
+ public void cgeoFindNearest(@SuppressWarnings("unused") final View v) {
if (app.currentGeo().getCoords() == null) {
return;
}
@@ -601,7 +594,7 @@ public class MainActivity extends AbstractActivity {
* @param v
* unused here but needed since this method is referenced from XML layout
*/
- public void cgeoFindByOffline(final View v) {
+ public void cgeoFindByOffline(@SuppressWarnings("unused") final View v) {
findByOffline.setPressed(true);
CacheListActivity.startActivityOffline(this);
}
@@ -610,7 +603,7 @@ public class MainActivity extends AbstractActivity {
* @param v
* unused here but needed since this method is referenced from XML layout
*/
- public void cgeoSearch(final View v) {
+ public void cgeoSearch(@SuppressWarnings("unused") final View v) {
advanced.setPressed(true);
startActivity(new Intent(this, SearchActivity.class));
}
@@ -619,7 +612,7 @@ public class MainActivity extends AbstractActivity {
* @param v
* unused here but needed since this method is referenced from XML layout
*/
- public void cgeoPoint(final View v) {
+ public void cgeoPoint(@SuppressWarnings("unused") final View v) {
any.setPressed(true);
startActivity(new Intent(this, NavigateAnyPointActivity.class));
}
@@ -628,7 +621,7 @@ public class MainActivity extends AbstractActivity {
* @param v
* unused here but needed since this method is referenced from XML layout
*/
- public void cgeoFilter(final View v) {
+ public void cgeoFilter(@SuppressWarnings("unused") final View v) {
filter.setPressed(true);
filter.performClick();
}
@@ -637,7 +630,7 @@ public class MainActivity extends AbstractActivity {
* @param v
* unused here but needed since this method is referenced from XML layout
*/
- public void cgeoNavSettings(final View v) {
+ public void cgeoNavSettings(@SuppressWarnings("unused") final View v) {
startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
}
@@ -714,40 +707,11 @@ public class MainActivity extends AbstractActivity {
}
}
- private class ObtainAddressThread extends Thread {
-
- public ObtainAddressThread() {
- setPriority(Thread.MIN_PRIORITY);
- }
-
- @Override
- public void run() {
- if (addressObtaining) {
- return;
- }
- addressObtaining = true;
-
- try {
- final Geocoder geocoder = new Geocoder(MainActivity.this, Locale.getDefault());
- final Geopoint coords = app.currentGeo().getCoords();
- addresses = geocoder.getFromLocation(coords.getLatitude(), coords.getLongitude(), 1);
- } catch (final IOException e) {
- Log.i("Failed to obtain address");
- } catch (final IllegalArgumentException e) {
- Log.w("ObtainAddressThread.run", e);
- }
-
- obtainAddressHandler.sendEmptyMessage(0);
-
- addressObtaining = false;
- }
- }
-
/**
* @param view
* unused here but needed since this method is referenced from XML layout
*/
- public void showAbout(final View view) {
+ public void showAbout(@SuppressWarnings("unused") final View view) {
startActivity(new Intent(this, AboutActivity.class));
}
@@ -755,7 +719,7 @@ public class MainActivity extends AbstractActivity {
* @param view
* unused here but needed since this method is referenced from XML layout
*/
- public void goSearch(final View view) {
+ public void goSearch(@SuppressWarnings("unused") final View view) {
onSearchRequested();
}
diff --git a/main/src/cgeo/geocaching/PocketQueryList.java b/main/src/cgeo/geocaching/PocketQueryList.java
index 9d1110d..9b48f92 100644
--- a/main/src/cgeo/geocaching/PocketQueryList.java
+++ b/main/src/cgeo/geocaching/PocketQueryList.java
@@ -2,15 +2,20 @@ package cgeo.geocaching;
import cgeo.geocaching.activity.ActivityMixin;
import cgeo.geocaching.connector.gc.GCParser;
-import cgeo.geocaching.utils.RunnableWithArgument;
+
+import org.apache.commons.collections4.CollectionUtils;
+import rx.Observable;
+import rx.Observable.OnSubscribe;
+import rx.Subscriber;
+import rx.android.observables.AndroidObservable;
+import rx.schedulers.Schedulers;
+import rx.functions.Action1;
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
-import android.content.res.Resources;
-import android.os.Handler;
-import android.os.Message;
import java.util.List;
@@ -20,105 +25,62 @@ public final class PocketQueryList {
private final int maxCaches;
private final String name;
- public PocketQueryList(String guid, String name, int maxCaches) {
+ public PocketQueryList(final String guid, final String name, final int maxCaches) {
this.guid = guid;
this.name = name;
this.maxCaches = maxCaches;
}
- public static class UserInterface {
-
- List<PocketQueryList> pocketQueryList = null;
- RunnableWithArgument<PocketQueryList> runAfterwards;
-
- private Handler loadPocketQueryHandler = new Handler() {
-
- @Override
- public void handleMessage(Message msg) {
- if ((pocketQueryList == null) || (pocketQueryList.size() == 0)) {
- if (waitDialog != null) {
- waitDialog.dismiss();
- }
-
- ActivityMixin.showToast(activity, res.getString(R.string.warn_no_pocket_query_found));
-
- return;
- }
-
- if (waitDialog != null) {
- waitDialog.dismiss();
- }
-
- final CharSequence[] items = new CharSequence[pocketQueryList.size()];
-
- for (int i = 0; i < pocketQueryList.size(); i++) {
- PocketQueryList pq = pocketQueryList.get(i);
- items[i] = pq.name;
- }
+ public String getGuid() {
+ return guid;
+ }
- AlertDialog.Builder builder = new AlertDialog.Builder(activity);
- builder.setTitle(res.getString(R.string.search_pocket_select));
- builder.setItems(items, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialogInterface, int itemId) {
- final PocketQueryList query = pocketQueryList.get(itemId);
- dialogInterface.dismiss();
- runAfterwards.run(query);
- }
- });
- builder.create().show();
+ public int getMaxCaches() {
+ return maxCaches;
+ }
- }
- };
+ public String getName() {
+ return name;
+ }
- private class LoadPocketQueryListThread extends Thread {
- final private Handler handler;
+ public static void promptForListSelection(final Activity activity, final Action1<PocketQueryList> runAfterwards) {
+ final Dialog waitDialog = ProgressDialog.show(activity, activity.getString(R.string.search_pocket_title), activity.getString(R.string.search_pocket_loading), true, true);
- public LoadPocketQueryListThread(Handler handlerIn) {
- handler = handlerIn;
+ AndroidObservable.fromActivity(activity, Observable.create(new OnSubscribe<List<PocketQueryList>>() {
+ @Override
+ public void call(final Subscriber<? super List<PocketQueryList>> subscriber) {
+ subscriber.onNext(GCParser.searchPocketQueryList());
+ subscriber.onCompleted();
}
-
+ }).subscribeOn(Schedulers.io())).subscribe(new Action1<List<PocketQueryList>>() {
@Override
- public void run() {
- pocketQueryList = GCParser.searchPocketQueryList();
- handler.sendMessage(Message.obtain());
+ public void call(final List<PocketQueryList> pocketQueryLists) {
+ waitDialog.dismiss();
+ selectFromPocketQueries(activity, pocketQueryLists, runAfterwards);
}
+ });
+ }
+ private static void selectFromPocketQueries(final Activity activity, final List<PocketQueryList> pocketQueryList, final Action1<PocketQueryList> runAfterwards) {
+ if (CollectionUtils.isEmpty(pocketQueryList)) {
+ ActivityMixin.showToast(activity, activity.getString(R.string.warn_no_pocket_query_found));
+ return;
}
- private final Activity activity;
- private final CgeoApplication app;
- private final Resources res;
- private ProgressDialog waitDialog = null;
-
- public UserInterface(final Activity activity) {
- this.activity = activity;
- app = CgeoApplication.getInstance();
- res = app.getResources();
- }
-
- public void promptForListSelection(final RunnableWithArgument<PocketQueryList> runAfterwards) {
+ final CharSequence[] items = new CharSequence[pocketQueryList.size()];
- this.runAfterwards = runAfterwards;
-
- waitDialog = ProgressDialog.show(activity, res.getString(R.string.search_pocket_title), res.getString(R.string.search_pocket_loading), true, true);
-
- LoadPocketQueryListThread thread = new LoadPocketQueryListThread(loadPocketQueryHandler);
- thread.start();
+ for (int i = 0; i < pocketQueryList.size(); i++) {
+ items[i] = pocketQueryList.get(i).name;
}
-
- }
-
- public String getGuid() {
- return guid;
- }
-
- public int getMaxCaches() {
- return maxCaches;
- }
-
- public String getName() {
- return name;
+ new AlertDialog.Builder(activity)
+ .setTitle(activity.getString(R.string.search_pocket_select))
+ .setItems(items, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialogInterface, final int itemId) {
+ dialogInterface.dismiss();
+ runAfterwards.call(pocketQueryList.get(itemId));
+ }
+ }).create().show();
}
}
diff --git a/main/src/cgeo/geocaching/SearchActivity.java b/main/src/cgeo/geocaching/SearchActivity.java
index 4e1777d..8a89e5f 100644
--- a/main/src/cgeo/geocaching/SearchActivity.java
+++ b/main/src/cgeo/geocaching/SearchActivity.java
@@ -10,12 +10,17 @@ import cgeo.geocaching.connector.capability.ISearchByGeocode;
import cgeo.geocaching.connector.trackable.TrackableConnector;
import cgeo.geocaching.geopoint.Geopoint;
import cgeo.geocaching.geopoint.GeopointFormatter;
+import cgeo.geocaching.search.AutoCompleteAdapter;
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;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+import rx.functions.Func1;
import android.app.Activity;
import android.app.SearchManager;
@@ -26,10 +31,8 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
-import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
-import android.widget.EditText;
import java.util.Locale;
@@ -39,19 +42,19 @@ public class SearchActivity extends AbstractActivity {
@InjectView(R.id.buttonLongitude) protected Button buttonLongitude;
@InjectView(R.id.search_coordinates) protected Button buttonSearchCoords;
- @InjectView(R.id.address) protected EditText addressEditText;
+ @InjectView(R.id.address) protected AutoCompleteTextView addressEditText;
@InjectView(R.id.search_address) protected Button buttonSearchAddress;
@InjectView(R.id.geocode) protected AutoCompleteTextView geocodeEditText;
@InjectView(R.id.display_geocode) protected Button buttonSearchGeocode;
- @InjectView(R.id.keyword) protected EditText keywordEditText;
+ @InjectView(R.id.keyword) protected AutoCompleteTextView keywordEditText;
@InjectView(R.id.search_keyword) protected Button buttonSearchKeyword;
- @InjectView(R.id.finder) protected EditText finderNameEditText;
+ @InjectView(R.id.finder) protected AutoCompleteTextView finderNameEditText;
@InjectView(R.id.search_finder) protected Button buttonSearchFinder;
- @InjectView(R.id.owner) protected EditText ownerNameEditText;
+ @InjectView(R.id.owner) protected AutoCompleteTextView ownerNameEditText;
@InjectView(R.id.search_owner) protected Button buttonSearchOwner;
@InjectView(R.id.trackable) protected AutoCompleteTextView trackableEditText;
@@ -60,9 +63,23 @@ public class SearchActivity extends AbstractActivity {
@Override
public final void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ final Intent intent = getIntent();
+
+ // search suggestion for a cache
+ if (Intents.ACTION_GEOCACHE.equals(intent.getAction())) {
+ CacheDetailActivity.startActivity(this, intent.getStringExtra(SearchManager.QUERY));
+ finish();
+ return;
+ }
+
+ // search suggestion for a trackable
+ if (Intents.ACTION_TRACKABLE.equals(intent.getAction())) {
+ TrackableActivity.startActivity(this, null, intent.getStringExtra(SearchManager.QUERY), null);
+ finish();
+ return;
+ }
// search query
- final Intent intent = getIntent();
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
hideKeyboard();
final String query = intent.getStringExtra(SearchManager.QUERY);
@@ -173,7 +190,7 @@ public class SearchActivity extends AbstractActivity {
public void run() {
findByAddressFn();
}
- });
+ }, null);
setSearchAction(geocodeEditText, buttonSearchGeocode, new Runnable() {
@@ -181,8 +198,13 @@ public class SearchActivity extends AbstractActivity {
public void run() {
findByGeocodeFn();
}
+ }, new Func1<String, String[]>() {
+
+ @Override
+ public String[] call(final String input) {
+ return DataStore.getSuggestionsGeocode(input);
+ }
});
- addHistoryEntries(geocodeEditText, DataStore.getRecentGeocodesForSearch());
setSearchAction(keywordEditText, buttonSearchKeyword, new Runnable() {
@@ -190,6 +212,12 @@ public class SearchActivity extends AbstractActivity {
public void run() {
findByKeywordFn();
}
+ }, new Func1<String, String[]>() {
+
+ @Override
+ public String[] call(final String input) {
+ return DataStore.getSuggestionsKeyword(input);
+ }
});
setSearchAction(finderNameEditText, buttonSearchFinder, new Runnable() {
@@ -198,6 +226,12 @@ public class SearchActivity extends AbstractActivity {
public void run() {
findByFinderFn();
}
+ }, new Func1<String, String[]>() {
+
+ @Override
+ public String[] call(final String input) {
+ return DataStore.getSuggestionsFinderName(input);
+ }
});
setSearchAction(ownerNameEditText, buttonSearchOwner, new Runnable() {
@@ -206,6 +240,12 @@ public class SearchActivity extends AbstractActivity {
public void run() {
findByOwnerFn();
}
+ }, new Func1<String, String[]>() {
+
+ @Override
+ public String[] call(final String input) {
+ return DataStore.getSuggestionsOwnerName(input);
+ }
});
setSearchAction(trackableEditText, buttonSearchTrackable, new Runnable() {
@@ -214,12 +254,16 @@ public class SearchActivity extends AbstractActivity {
public void run() {
findTrackableFn();
}
+ }, new Func1<String, String[]>() {
+
+ @Override
+ public String[] call(final String input) {
+ return DataStore.getSuggestionsTrackableCode(input);
+ }
});
- addHistoryEntries(trackableEditText, DataStore.getTrackableCodes());
- disableSuggestions(trackableEditText);
}
- private static void setSearchAction(final EditText editText, final Button button, final Runnable runnable) {
+ private static void setSearchAction(final AutoCompleteTextView editText, final Button button, final @NonNull Runnable runnable, final @Nullable Func1<String, String[]> suggestionFunction) {
EditUtils.setActionListener(editText, runnable);
button.setOnClickListener(new View.OnClickListener() {
@Override
@@ -227,12 +271,8 @@ public class SearchActivity extends AbstractActivity {
runnable.run();
}
});
- }
-
- private void addHistoryEntries(final AutoCompleteTextView textView, final String[] entries) {
- if (entries != null) {
- final ArrayAdapter<String> historyAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, entries);
- textView.setAdapter(historyAdapter);
+ if (suggestionFunction != null) {
+ editText.setAdapter(new AutoCompleteAdapter(editText.getContext(), android.R.layout.simple_dropdown_item_1line, suggestionFunction));
}
}
@@ -305,7 +345,7 @@ public class SearchActivity extends AbstractActivity {
return;
}
- CacheListActivity.startActivityUserName(this, usernameText);
+ CacheListActivity.startActivityFinder(this, usernameText);
}
private void findByOwnerFn() {
@@ -331,7 +371,7 @@ public class SearchActivity extends AbstractActivity {
return;
}
- CacheDetailActivity.startActivity(this, geocodeText.toUpperCase());
+ CacheDetailActivity.startActivity(this, geocodeText.toUpperCase(Locale.US));
}
private void findTrackableFn() {
diff --git a/main/src/cgeo/geocaching/SearchResult.java b/main/src/cgeo/geocaching/SearchResult.java
index 131a01c..46ac38e 100644
--- a/main/src/cgeo/geocaching/SearchResult.java
+++ b/main/src/cgeo/geocaching/SearchResult.java
@@ -10,6 +10,7 @@ import cgeo.geocaching.gcvote.GCVote;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -228,6 +229,7 @@ public class SearchResult implements Parcelable {
return result;
}
+ @Nullable
public Geocache getFirstCacheFromResult(final EnumSet<LoadFlag> loadFlags) {
return CollectionUtils.isNotEmpty(geocodes) ? DataStore.loadCache(geocodes.iterator().next(), loadFlags) : null;
}
diff --git a/main/src/cgeo/geocaching/StaticMapsActivity.java b/main/src/cgeo/geocaching/StaticMapsActivity.java
index 7811da5..16fce37 100644
--- a/main/src/cgeo/geocaching/StaticMapsActivity.java
+++ b/main/src/cgeo/geocaching/StaticMapsActivity.java
@@ -4,11 +4,10 @@ import cgeo.geocaching.activity.AbstractActivity;
import cgeo.geocaching.enumerations.LoadFlags;
import cgeo.geocaching.utils.Log;
-import com.googlecode.androidannotations.annotations.EActivity;
-import com.googlecode.androidannotations.annotations.Extra;
-import com.googlecode.androidannotations.annotations.OptionsItem;
-import com.googlecode.androidannotations.annotations.OptionsMenu;
-
+import org.androidannotations.annotations.EActivity;
+import org.androidannotations.annotations.Extra;
+import org.androidannotations.annotations.OptionsItem;
+import org.androidannotations.annotations.OptionsMenu;
import org.apache.commons.collections4.CollectionUtils;
import android.app.ProgressDialog;
diff --git a/main/src/cgeo/geocaching/StaticMapsProvider.java b/main/src/cgeo/geocaching/StaticMapsProvider.java
index d5cbb13..eaab159 100644
--- a/main/src/cgeo/geocaching/StaticMapsProvider.java
+++ b/main/src/cgeo/geocaching/StaticMapsProvider.java
@@ -298,8 +298,8 @@ public final class StaticMapsProvider {
return true;
}
- public static Bitmap getPreviewMap(final String geocode) {
- return decodeFile(StaticMapsProvider.getMapFile(geocode, PREFIX_PREVIEW, false));
+ public static Bitmap getPreviewMap(final Geocache cache) {
+ return decodeFile(StaticMapsProvider.getMapFile(cache.getGeocode(), PREFIX_PREVIEW, false));
}
public static Bitmap getWaypointMap(final String geocode, final Waypoint waypoint, final int level) {
diff --git a/main/src/cgeo/geocaching/StatusFragment.java b/main/src/cgeo/geocaching/StatusFragment.java
index 4f70f0e..3672a3b 100644
--- a/main/src/cgeo/geocaching/StatusFragment.java
+++ b/main/src/cgeo/geocaching/StatusFragment.java
@@ -1,15 +1,17 @@
package cgeo.geocaching;
+import cgeo.geocaching.network.StatusUpdater;
import cgeo.geocaching.network.StatusUpdater.Status;
-import cgeo.geocaching.utils.IObserver;
import cgeo.geocaching.utils.Log;
+import rx.Subscription;
+import rx.android.observables.AndroidObservable;
+import rx.functions.Action1;
+
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
@@ -20,91 +22,68 @@ import android.widget.TextView;
public class StatusFragment extends Fragment {
- private ViewGroup status;
- private ImageView statusIcon;
- private TextView statusMessage;
-
- final private StatusHandler statusHandler = new StatusHandler();
+ private Subscription statusSubscription;
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
- status = (ViewGroup) inflater.inflate(R.layout.status, container, false);
- statusIcon = (ImageView) status.findViewById(R.id.status_icon);
- statusMessage = (TextView) status.findViewById(R.id.status_message);
- return status;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- CgeoApplication.getInstance().getStatusUpdater().addObserver(statusHandler);
- }
-
- @Override
- public void onPause() {
- CgeoApplication.getInstance().getStatusUpdater().deleteObserver(statusHandler);
- super.onPause();
- }
-
- private class StatusHandler extends Handler implements IObserver<Status> {
-
- @Override
- public void update(final Status data) {
- obtainMessage(0, data).sendToTarget();
- }
-
- @Override
- public void handleMessage(final Message msg) {
- final Status data = (Status) msg.obj;
- updateDisplay(data != null && data.message != null ? data : Status.defaultStatus());
- }
-
- private void updateDisplay(final Status data) {
-
- if (data == null) {
- status.setVisibility(View.INVISIBLE);
- return;
- }
-
- final Resources res = getResources();
- final String packageName = getActivity().getPackageName();
+ final ViewGroup statusGroup = (ViewGroup) inflater.inflate(R.layout.status, container, false);
+ final ImageView statusIcon = (ImageView) statusGroup.findViewById(R.id.status_icon);
+ final TextView statusMessage = (TextView) statusGroup.findViewById(R.id.status_message);
+ statusSubscription = AndroidObservable.fromFragment(this, StatusUpdater.latestStatus).subscribe(new Action1<Status>() {
+ @Override
+ public void call(final Status status) {
+ if (status == null) {
+ statusGroup.setVisibility(View.INVISIBLE);
+ return;
+ }
- if (data.icon != null) {
- final int iconId = res.getIdentifier(data.icon, "drawable", packageName);
- if (iconId != 0) {
- statusIcon.setImageResource(iconId);
- statusIcon.setVisibility(View.VISIBLE);
+ final Resources res = getResources();
+ final String packageName = getActivity().getPackageName();
+
+ if (status.icon != null) {
+ final int iconId = res.getIdentifier(status.icon, "drawable", packageName);
+ if (iconId != 0) {
+ statusIcon.setImageResource(iconId);
+ statusIcon.setVisibility(View.VISIBLE);
+ } else {
+ Log.w("StatusHandler: could not find icon corresponding to @drawable/" + status.icon);
+ statusIcon.setVisibility(View.GONE);
+ }
} else {
- Log.w("StatusHandler: could not find icon corresponding to @drawable/" + data.icon);
statusIcon.setVisibility(View.GONE);
}
- } else {
- statusIcon.setVisibility(View.GONE);
- }
- String message = data.message;
- if (data.messageId != null) {
- final int messageId = res.getIdentifier(data.messageId, "string", packageName);
- if (messageId != 0) {
- message = res.getString(messageId);
+ String message = status.message;
+ if (status.messageId != null) {
+ final int messageId = res.getIdentifier(status.messageId, "string", packageName);
+ if (messageId != 0) {
+ message = res.getString(messageId);
+ }
}
- }
- statusMessage.setText(message);
- status.setVisibility(View.VISIBLE);
+ statusMessage.setText(message);
+ statusGroup.setVisibility(View.VISIBLE);
- if (data.url != null) {
- status.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(data.url)));
- }
- });
- } else {
- status.setClickable(false);
+ if (status.url != null) {
+ statusGroup.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(status.url)));
+ }
+ });
+ } else {
+ statusGroup.setClickable(false);
+ }
}
- }
+ });
+ return statusGroup;
+ }
+ @Override
+ public void onDestroy() {
+ statusSubscription.unsubscribe();
+ super.onDestroy();
}
+
}
diff --git a/main/src/cgeo/geocaching/TrackableActivity.java b/main/src/cgeo/geocaching/TrackableActivity.java
index dcfd80a..948e668 100644
--- a/main/src/cgeo/geocaching/TrackableActivity.java
+++ b/main/src/cgeo/geocaching/TrackableActivity.java
@@ -34,6 +34,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.Html;
+import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@@ -109,6 +110,8 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
}
};
+ private CharSequence clickedItemText = null;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState, R.layout.viewpager_activity);
@@ -130,6 +133,8 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
// try to get data from URI
if (geocode == null && guid == null && id == null && uri != null) {
+ geocode = ConnectorFactory.getTrackableFromURL(uri.toString());
+
final String uriHost = uri.getHost().toLowerCase(Locale.US);
if (uriHost.contains("geocaching.com")) {
geocode = uri.getQueryParameter("tracker");
@@ -190,6 +195,36 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
}
@Override
+ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) {
+ super.onCreateContextMenu(menu, view, info);
+ final int viewId = view.getId();
+ assert view instanceof TextView;
+ clickedItemText = ((TextView) view).getText();
+ switch (viewId) {
+ case R.id.value: // name, TB-code, origin, released, distance
+ final String itemTitle = (String) ((TextView) ((View) view.getParent()).findViewById(R.id.name)).getText();
+ buildDetailsContextMenu(menu, clickedItemText, itemTitle, true);
+ break;
+ case R.id.goal:
+ buildDetailsContextMenu(menu, clickedItemText, res.getString(R.string.trackable_goal), false);
+ break;
+ case R.id.details:
+ buildDetailsContextMenu(menu, clickedItemText, res.getString(R.string.trackable_details), false);
+ break;
+ case R.id.log:
+ buildDetailsContextMenu(menu, clickedItemText, res.getString(R.string.cache_logs), false);
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ return onClipboardItemSelected(item, clickedItemText) || onOptionsItemSelected(item);
+ }
+
+ @Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.trackable_activity, menu);
return true;
@@ -360,7 +395,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
}
// trackable name
- details.add(R.string.trackable_name, StringUtils.isNotBlank(trackable.getName()) ? Html.fromHtml(trackable.getName()).toString() : res.getString(R.string.trackable_unknown));
+ registerForContextMenu(details.add(R.string.trackable_name, StringUtils.isNotBlank(trackable.getName()) ? Html.fromHtml(trackable.getName()).toString() : res.getString(R.string.trackable_unknown)));
// trackable type
String tbType;
@@ -372,7 +407,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
details.add(R.string.trackable_type, tbType);
// trackable geocode
- details.add(R.string.trackable_code, trackable.getGeocode());
+ registerForContextMenu(details.add(R.string.trackable_code, trackable.getGeocode()));
// trackable owner
final TextView owner = details.add(R.string.trackable_owner, res.getString(R.string.trackable_unknown));
@@ -441,16 +476,17 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
if (StringUtils.isNotBlank(trackable.getOrigin())) {
final TextView origin = details.add(R.string.trackable_origin, "");
origin.setText(Html.fromHtml(trackable.getOrigin()), TextView.BufferType.SPANNABLE);
+ registerForContextMenu(origin);
}
// trackable released
if (trackable.getReleased() != null) {
- details.add(R.string.trackable_released, Formatter.formatDate(trackable.getReleased().getTime()));
+ registerForContextMenu(details.add(R.string.trackable_released, Formatter.formatDate(trackable.getReleased().getTime())));
}
// trackable distance
if (trackable.getDistance() >= 0) {
- details.add(R.string.trackable_distance, Units.getDistanceFromKilometers(trackable.getDistance()));
+ registerForContextMenu(details.add(R.string.trackable_distance, Units.getDistanceFromKilometers(trackable.getDistance())));
}
// trackable goal
@@ -459,6 +495,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
goalTextView.setVisibility(View.VISIBLE);
goalTextView.setText(Html.fromHtml(trackable.getGoal(), new HtmlImage(geocode, true, 0, false), null), TextView.BufferType.SPANNABLE);
goalTextView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance());
+ registerForContextMenu(goalTextView);
}
// trackable details
@@ -467,6 +504,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
detailsTextView.setVisibility(View.VISIBLE);
detailsTextView.setText(Html.fromHtml(trackable.getDetails(), new HtmlImage(geocode, true, 0, false), new UnknownTagsHandler()), TextView.BufferType.SPANNABLE);
detailsTextView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance());
+ registerForContextMenu(detailsTextView);
}
// trackable image
diff --git a/main/src/cgeo/geocaching/UsefulAppsActivity.java b/main/src/cgeo/geocaching/UsefulAppsActivity.java
index c70143f..39c527d 100644
--- a/main/src/cgeo/geocaching/UsefulAppsActivity.java
+++ b/main/src/cgeo/geocaching/UsefulAppsActivity.java
@@ -62,6 +62,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_contacts_title, R.string.helper_contacts_description, R.drawable.cgeo, "cgeo.contacts"),
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"),
diff --git a/main/src/cgeo/geocaching/activity/AbstractActivity.java b/main/src/cgeo/geocaching/activity/AbstractActivity.java
index 36b6d01..7127de4 100644
--- a/main/src/cgeo/geocaching/activity/AbstractActivity.java
+++ b/main/src/cgeo/geocaching/activity/AbstractActivity.java
@@ -3,18 +3,27 @@ package cgeo.geocaching.activity;
import butterknife.ButterKnife;
import cgeo.geocaching.CgeoApplication;
+import cgeo.geocaching.R;
import cgeo.geocaching.compatibility.Compatibility;
import cgeo.geocaching.network.Cookies;
import cgeo.geocaching.settings.Settings;
+import cgeo.geocaching.utils.ClipboardUtils;
+import cgeo.geocaching.utils.HtmlUtils;
+import cgeo.geocaching.utils.TranslationUtils;
-import android.content.Context;
+import org.apache.commons.lang3.StringUtils;
+
+import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
+import android.view.ContextMenu;
+import android.view.MenuItem;
import android.view.View;
-import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
+import java.util.Locale;
+
public abstract class AbstractActivity extends FragmentActivity implements IAbstractActivity {
protected CgeoApplication app = null;
@@ -96,7 +105,7 @@ public abstract class AbstractActivity extends FragmentActivity implements IAbst
// create view variables
ButterKnife.inject(this);
}
-
+
private void initializeCommonFields() {
// initialize commonly used members
res = this.getResources();
@@ -116,6 +125,48 @@ public abstract class AbstractActivity extends FragmentActivity implements IAbst
}
protected void hideKeyboard() {
- ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
+ new Keyboard(this).hide();
+ }
+
+ public void showKeyboard(final View view) {
+ new Keyboard(this).show(view);
+ }
+
+ protected void buildDetailsContextMenu(final ContextMenu menu, final CharSequence clickedItemText, final String fieldTitle, final boolean copyOnly) {
+ menu.setHeaderTitle(fieldTitle);
+ getMenuInflater().inflate(R.menu.details_context, menu);
+ menu.findItem(R.id.menu_translate_to_sys_lang).setVisible(!copyOnly);
+ if (!copyOnly) {
+ if (clickedItemText.length() > TranslationUtils.TRANSLATION_TEXT_LENGTH_WARN) {
+ showToast(res.getString(R.string.translate_length_warning));
+ }
+ menu.findItem(R.id.menu_translate_to_sys_lang).setTitle(res.getString(R.string.translate_to_sys_lang, Locale.getDefault().getDisplayLanguage()));
+ }
+ final boolean localeIsEnglish = StringUtils.equals(Locale.getDefault().getLanguage(), Locale.ENGLISH.getLanguage());
+ menu.findItem(R.id.menu_translate_to_english).setVisible(!copyOnly && !localeIsEnglish);
+ }
+
+ protected boolean onClipboardItemSelected(final MenuItem item, final CharSequence clickedItemText) {
+ switch (item.getItemId()) {
+ // detail fields
+ case R.id.menu_copy:
+ ClipboardUtils.copyToClipboard(clickedItemText);
+ showToast(res.getString(R.string.clipboard_copy_ok));
+ return true;
+ case R.id.menu_translate_to_sys_lang:
+ TranslationUtils.startActivityTranslate(this, Locale.getDefault().getLanguage(), HtmlUtils.extractText(clickedItemText));
+ return true;
+ case R.id.menu_translate_to_english:
+ TranslationUtils.startActivityTranslate(this, Locale.ENGLISH.getLanguage(), HtmlUtils.extractText(clickedItemText));
+ return true;
+ case R.id.menu_cache_share_field:
+ final Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("text/plain");
+ intent.putExtra(Intent.EXTRA_TEXT, clickedItemText.toString());
+ startActivity(Intent.createChooser(intent, res.getText(R.string.cache_share_field)));
+ return true;
+ default:
+ return false;
+ }
}
}
diff --git a/main/src/cgeo/geocaching/activity/AbstractListActivity.java b/main/src/cgeo/geocaching/activity/AbstractListActivity.java
index 2adae7a..a5d5c14 100644
--- a/main/src/cgeo/geocaching/activity/AbstractListActivity.java
+++ b/main/src/cgeo/geocaching/activity/AbstractListActivity.java
@@ -11,7 +11,6 @@ public abstract class AbstractListActivity extends FragmentListActivity implemen
IAbstractActivity {
private boolean keepScreenOn = false;
- private boolean paused = true;
protected CgeoApplication app = null;
protected Resources res = null;
@@ -85,26 +84,4 @@ public abstract class AbstractListActivity extends FragmentListActivity implemen
// initialize action bar title with activity title
ActivityMixin.setTitle(this, getTitle());
}
-
- @Override
- public void onResume() {
- paused = false;
- super.onResume();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- paused = true;
- }
-
- /**
- * Check if the current activity is paused. This must be called and acted
- * upon only from the UI thread.
- *
- * @return <code>true</code> if the current activity is paused
- */
- protected boolean isPaused() {
- return paused;
- }
}
diff --git a/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java b/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java
index c7d4507..6e2900d 100644
--- a/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java
+++ b/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java
@@ -5,7 +5,6 @@ import cgeo.geocaching.utils.Log;
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;
diff --git a/main/src/cgeo/geocaching/activity/ActivityMixin.java b/main/src/cgeo/geocaching/activity/ActivityMixin.java
index c1a2678..bfd45da 100644
--- a/main/src/cgeo/geocaching/activity/ActivityMixin.java
+++ b/main/src/cgeo/geocaching/activity/ActivityMixin.java
@@ -125,6 +125,6 @@ public final class ActivityMixin {
editText.getText().replace(start, end, completeText);
int newCursor = moveCursor ? start + completeText.length() : start;
- editText.setSelection(newCursor, newCursor);
+ editText.setSelection(newCursor);
}
}
diff --git a/main/src/cgeo/geocaching/activity/Keyboard.java b/main/src/cgeo/geocaching/activity/Keyboard.java
new file mode 100644
index 0000000..9bae7be
--- /dev/null
+++ b/main/src/cgeo/geocaching/activity/Keyboard.java
@@ -0,0 +1,40 @@
+package cgeo.geocaching.activity;
+
+import org.eclipse.jdt.annotation.NonNull;
+
+import android.app.Activity;
+import android.content.Context;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+
+/**
+ * Class for hiding/showing the soft keyboard on Android.
+ *
+ */
+public class Keyboard {
+ private final Activity activity;
+
+ public Keyboard(final @NonNull Activity activity) {
+ this.activity = activity;
+ }
+
+ public void hide() {
+ ((InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE)).toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
+ }
+
+ public void show(final View view) {
+ view.requestFocus();
+ ((InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE)).showSoftInput(view, 0);
+ }
+
+ public void showDelayed(final View view) {
+ view.postDelayed(new Runnable() {
+
+ @Override
+ public void run() {
+ final InputMethodManager keyboard = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ keyboard.showSoftInput(view, 0);
+ }
+ }, 50);
+ }
+}
diff --git a/main/src/cgeo/geocaching/activity/Progress.java b/main/src/cgeo/geocaching/activity/Progress.java
index 2710023..8ee88a7 100644
--- a/main/src/cgeo/geocaching/activity/Progress.java
+++ b/main/src/cgeo/geocaching/activity/Progress.java
@@ -17,7 +17,7 @@ public class Progress {
private ProgressDialog dialog;
private int progress = 0;
private int progressDivider = 1;
- private boolean hideAbsolute = false;
+ final private boolean hideAbsolute;
public Progress(boolean hideAbsolute) {
this.hideAbsolute = hideAbsolute;
@@ -28,7 +28,7 @@ public class Progress {
}
public synchronized void dismiss() {
- if (dialog != null && dialog.isShowing()) {
+ if (isShowing()) {
try {
dialog.dismiss();
} catch (final Exception e) {
@@ -54,13 +54,8 @@ public class Progress {
}
}
- private void createProgressDialog(Context context, String title, String message, Message cancelMessage) {
- if (hideAbsolute) {
- dialog = new CustomProgressDialog(context);
- }
- else {
- dialog = new ProgressDialog(context);
- }
+ private void createProgressDialog(final Context context, final String title, final String message, final Message cancelMessage) {
+ dialog = hideAbsolute ? new CustomProgressDialog(context) : new ProgressDialog(context);
dialog.setTitle(title);
dialog.setMessage(message);
if (cancelMessage != null) {
@@ -87,7 +82,7 @@ public class Progress {
}
public synchronized void setMaxProgressAndReset(final int max) {
- if (dialog != null && dialog.isShowing()) {
+ if (isShowing()) {
final int modMax = max / this.progressDivider;
dialog.setMax(modMax);
dialog.setProgress(0);
@@ -97,7 +92,7 @@ public class Progress {
public synchronized void setProgress(final int progress) {
final int modProgress = progress / this.progressDivider;
- if (dialog != null && dialog.isShowing()) {
+ if (isShowing()) {
dialog.setProgress(modProgress);
}
this.progress = modProgress;
diff --git a/main/src/cgeo/geocaching/apps/AbstractLocusApp.java b/main/src/cgeo/geocaching/apps/AbstractLocusApp.java
index d6c2fe6..8e9181d 100644
--- a/main/src/cgeo/geocaching/apps/AbstractLocusApp.java
+++ b/main/src/cgeo/geocaching/apps/AbstractLocusApp.java
@@ -6,6 +6,7 @@ import cgeo.geocaching.Waypoint;
import cgeo.geocaching.enumerations.CacheSize;
import cgeo.geocaching.enumerations.CacheType;
import cgeo.geocaching.enumerations.WaypointType;
+import cgeo.geocaching.utils.SynchronizedDateFormat;
import menion.android.locus.addon.publiclib.DisplayData;
import menion.android.locus.addon.publiclib.LocusUtils;
@@ -14,8 +15,6 @@ import menion.android.locus.addon.publiclib.geoData.PointGeocachingData;
import menion.android.locus.addon.publiclib.geoData.PointGeocachingDataWaypoint;
import menion.android.locus.addon.publiclib.geoData.PointsData;
-import org.apache.commons.lang3.time.FastDateFormat;
-
import android.app.Activity;
import android.location.Location;
@@ -30,7 +29,7 @@ import java.util.Locale;
* @see <a href="http://forum.asamm.cz/viewtopic.php?f=29&t=767">Locus forum</a>
*/
public abstract class AbstractLocusApp extends AbstractApp {
- private static final FastDateFormat ISO8601DATE = FastDateFormat.getInstance("yyyy-MM-dd'T'", Locale.US);
+ private static final SynchronizedDateFormat ISO8601DATE = new SynchronizedDateFormat("yyyy-MM-dd'T'", Locale.US);
protected AbstractLocusApp(final String text, int id, final String intent) {
super(text, id, intent);
@@ -122,7 +121,7 @@ public abstract class AbstractLocusApp extends AbstractApp {
pg.placedBy = cache.getOwnerDisplayName();
final Date hiddenDate = cache.getHiddenDate();
if (hiddenDate != null) {
- pg.hidden = ISO8601DATE.format(hiddenDate.getTime());
+ pg.hidden = ISO8601DATE.format(hiddenDate);
}
int locusId = toLocusType(cache.getType());
if (locusId != NO_LOCUS_ID) {
diff --git a/main/src/cgeo/geocaching/apps/cache/navi/AbstractPointNavigationApp.java b/main/src/cgeo/geocaching/apps/cache/navi/AbstractPointNavigationApp.java
index a1c752c..ec9705c 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/AbstractPointNavigationApp.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/AbstractPointNavigationApp.java
@@ -8,6 +8,7 @@ import cgeo.geocaching.apps.AbstractApp;
import cgeo.geocaching.geopoint.Geopoint;
import android.app.Activity;
+import android.content.Intent;
/**
* navigation app for simple point navigation (no differentiation between cache/waypoint/point)
@@ -49,4 +50,17 @@ abstract class AbstractPointNavigationApp extends AbstractApp implements CacheNa
public boolean isEnabled(Waypoint waypoint) {
return waypoint.getCoords() != null;
}
+
+ protected static void addIntentExtras(final Intent intent, final Waypoint waypoint) {
+ intent.putExtra("name", waypoint.getName());
+ intent.putExtra("code", waypoint.getGeocode());
+ }
+
+ protected static void addIntentExtras(final Intent intent, final Geocache cache) {
+ intent.putExtra("difficulty", cache.getDifficulty());
+ intent.putExtra("terrain", cache.getTerrain());
+ intent.putExtra("name", cache.getName());
+ intent.putExtra("code", cache.getGeocode());
+ intent.putExtra("size", cache.getSize().getL10n());
+ }
}
diff --git a/main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java b/main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java
new file mode 100644
index 0000000..6c6ffda
--- /dev/null
+++ b/main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java
@@ -0,0 +1,45 @@
+package cgeo.geocaching.apps.cache.navi;
+
+import cgeo.geocaching.Geocache;
+import cgeo.geocaching.Waypoint;
+import cgeo.geocaching.geopoint.Geopoint;
+
+import android.app.Activity;
+import android.content.Intent;
+
+public abstract class AbstractRadarApp extends AbstractPointNavigationApp {
+
+ private final String intentAction;
+
+ protected AbstractRadarApp(final String name, final int id, final String intent, final String packageName) {
+ super(name, id, intent, packageName);
+ this.intentAction = intent;
+ }
+
+ private Intent createIntent(final Geopoint point) {
+ final Intent intent = new Intent(intentAction);
+ addCoordinates(intent, point);
+ return intent;
+ }
+
+ @Override
+ public void navigate(final Activity activity, final Geopoint point) {
+ activity.startActivity(createIntent(point));
+ }
+
+ @Override
+ public void navigate(final Activity activity, final Geocache cache) {
+ final Intent intent = createIntent(cache.getCoords());
+ addIntentExtras(intent, cache);
+ activity.startActivity(intent);
+ }
+
+ @Override
+ public void navigate(final Activity activity, final Waypoint waypoint) {
+ final Intent intent = createIntent(waypoint.getCoords());
+ addIntentExtras(intent, waypoint);
+ activity.startActivity(intent);
+ }
+
+ protected abstract void addCoordinates(final Intent intent, final Geopoint point);
+}
diff --git a/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsApp.java b/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsApp.java
index 60d6e31..819638c 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsApp.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsApp.java
@@ -1,6 +1,8 @@
package cgeo.geocaching.apps.cache.navi;
+import cgeo.geocaching.Geocache;
import cgeo.geocaching.R;
+import cgeo.geocaching.Waypoint;
import cgeo.geocaching.activity.ActivityMixin;
import cgeo.geocaching.geopoint.Geopoint;
import cgeo.geocaching.utils.Log;
@@ -22,10 +24,15 @@ class GoogleMapsApp extends AbstractPointNavigationApp {
@Override
public void navigate(Activity activity, Geopoint point) {
- // INFO: q parameter works with Google Maps, but breaks cooperation with all other apps
+ navigate(activity, point, activity.getString(R.string.waypoint));
+ }
+
+ private static void navigate(Activity activity, Geopoint point, String label) {
try {
- activity.startActivity(new Intent(Intent.ACTION_VIEW,
- Uri.parse("geo:" + point.getLatitude() + "," + point.getLongitude())));
+ final String geoLocation = "geo:" + point.getLatitude() + "," + point.getLongitude();
+ final String query = point.getLatitude() + "," + point.getLongitude() + "(" + label + ")";
+ final String uriString = geoLocation + "?q=" + Uri.encode(query);
+ activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(uriString)));
return;
} catch (RuntimeException e) {
// nothing
@@ -35,4 +42,13 @@ class GoogleMapsApp extends AbstractPointNavigationApp {
ActivityMixin.showToast(activity, getString(R.string.err_application_no));
}
+ @Override
+ public void navigate(Activity activity, Geocache cache) {
+ navigate(activity, cache.getCoords(), cache.getName());
+ }
+
+ @Override
+ public void navigate(Activity activity, Waypoint waypoint) {
+ navigate(activity, waypoint.getCoords(), waypoint.getName());
+ }
}
diff --git a/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java b/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java
index 8ba3bef..ac83085 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java
@@ -1,17 +1,15 @@
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 {
+class PebbleApp extends AbstractRadarApp {
private static final String INTENT = "com.webmajstr.pebble_gc.NAVIGATE_TO";
private static final String PACKAGE_NAME = "com.webmajstr.pebble_gc";
@@ -21,24 +19,8 @@ class PebbleApp extends AbstractPointNavigationApp {
}
@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);
+ protected void addCoordinates(final Intent intent, final Geopoint coords) {
+ intent.putExtra("latitude", coords.getLatitude());
+ intent.putExtra("longitude", coords.getLongitude());
}
-
- @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/apps/cache/navi/RadarApp.java b/main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java
index ffa6650..41cf2d8 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java
@@ -3,10 +3,9 @@ package cgeo.geocaching.apps.cache.navi;
import cgeo.geocaching.R;
import cgeo.geocaching.geopoint.Geopoint;
-import android.app.Activity;
import android.content.Intent;
-class RadarApp extends AbstractPointNavigationApp {
+class RadarApp extends AbstractRadarApp {
private static final String INTENT = "com.google.android.radar.SHOW_RADAR";
private static final String PACKAGE_NAME = "com.eclipsim.gpsstatus2";
@@ -16,10 +15,9 @@ class RadarApp extends AbstractPointNavigationApp {
}
@Override
- public void navigate(Activity activity, Geopoint point) {
- final Intent radarIntent = new Intent(INTENT);
- radarIntent.putExtra("latitude", (float) point.getLatitude());
- radarIntent.putExtra("longitude", (float) point.getLongitude());
- activity.startActivity(radarIntent);
+ protected void addCoordinates(final Intent intent, final Geopoint coords) {
+ intent.putExtra("latitude", (float) coords.getLatitude());
+ intent.putExtra("longitude", (float) coords.getLongitude());
}
+
} \ No newline at end of file
diff --git a/main/src/cgeo/geocaching/apps/cachelist/CacheListApp.java b/main/src/cgeo/geocaching/apps/cachelist/CacheListApp.java
index ac5809e..40c4d92 100644
--- a/main/src/cgeo/geocaching/apps/cachelist/CacheListApp.java
+++ b/main/src/cgeo/geocaching/apps/cachelist/CacheListApp.java
@@ -1,7 +1,7 @@
package cgeo.geocaching.apps.cachelist;
-import cgeo.geocaching.SearchResult;
import cgeo.geocaching.Geocache;
+import cgeo.geocaching.SearchResult;
import cgeo.geocaching.apps.App;
import android.app.Activity;
diff --git a/main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java b/main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java
index 76379de..0da198b 100644
--- a/main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java
+++ b/main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java
@@ -1,5 +1,7 @@
package cgeo.geocaching.concurrent;
+import org.eclipse.jdt.annotation.NonNull;
+
import java.util.concurrent.ThreadFactory;
/**
@@ -12,6 +14,7 @@ public class PriorityThreadFactory implements ThreadFactory {
this.priority = priority;
}
+ @NonNull
@Override
public Thread newThread(Runnable r) {
Thread result = new Thread(r);
diff --git a/main/src/cgeo/geocaching/connector/AbstractConnector.java b/main/src/cgeo/geocaching/connector/AbstractConnector.java
index 53a3bcb..6d8d79e 100644
--- a/main/src/cgeo/geocaching/connector/AbstractConnector.java
+++ b/main/src/cgeo/geocaching/connector/AbstractConnector.java
@@ -16,10 +16,10 @@ import cgeo.geocaching.connector.capability.ISearchByViewPort;
import cgeo.geocaching.enumerations.CacheType;
import cgeo.geocaching.enumerations.LogType;
import cgeo.geocaching.geopoint.Geopoint;
-import cgeo.geocaching.utils.RunnableWithArgument;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNull;
+import rx.functions.Action1;
import java.util.ArrayList;
import java.util.Collection;
@@ -255,21 +255,21 @@ public abstract class AbstractConnector implements IConnector {
List<UserAction> actions = getDefaultUserActions();
if (this instanceof ISearchByOwner) {
- actions.add(new UserAction(R.string.user_menu_view_hidden, new RunnableWithArgument<UserAction.Context>() {
+ actions.add(new UserAction(R.string.user_menu_view_hidden, new Action1<Context>() {
@Override
- public void run(Context context) {
+ public void call(Context context) {
CacheListActivity.startActivityOwner(context.activity, context.userName);
}
}));
}
if (this instanceof ISearchByFinder) {
- actions.add(new UserAction(R.string.user_menu_view_found, new RunnableWithArgument<UserAction.Context>() {
+ actions.add(new UserAction(R.string.user_menu_view_found, new Action1<UserAction.Context>() {
@Override
- public void run(Context context) {
- CacheListActivity.startActivityUserName(context.activity, context.userName);
+ public void call(Context context) {
+ CacheListActivity.startActivityFinder(context.activity, context.userName);
}
}));
}
@@ -283,10 +283,10 @@ public abstract class AbstractConnector implements IConnector {
public List<UserAction> getDefaultUserActions() {
final ArrayList<UserAction> actions = new ArrayList<UserAction>();
if (ContactsAddon.isAvailable()) {
- actions.add(new UserAction(R.string.user_menu_open_contact, new RunnableWithArgument<UserAction.Context>() {
+ actions.add(new UserAction(R.string.user_menu_open_contact, new Action1<UserAction.Context>() {
@Override
- public void run(Context context) {
+ public void call(Context context) {
ContactsAddon.openContactCard(context.activity, context.userName);
}
}));
diff --git a/main/src/cgeo/geocaching/connector/ConnectorFactory.java b/main/src/cgeo/geocaching/connector/ConnectorFactory.java
index 0081951..18344f5 100644
--- a/main/src/cgeo/geocaching/connector/ConnectorFactory.java
+++ b/main/src/cgeo/geocaching/connector/ConnectorFactory.java
@@ -28,6 +28,11 @@ import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
+import rx.Observable;
+import rx.schedulers.Schedulers;
+import rx.functions.Func1;
+import rx.functions.Func2;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -176,14 +181,30 @@ public final class ConnectorFactory {
}
/** @see ISearchByViewPort#searchByViewport */
- public static SearchResult searchByViewport(final @NonNull Viewport viewport, final MapTokens tokens) {
- final SearchResult result = new SearchResult();
- for (final ISearchByViewPort connector : searchByViewPortConns) {
- if (connector.isActive()) {
- result.addSearchResult(connector.searchByViewport(viewport, tokens));
+ public static Observable<SearchResult> searchByViewport(final @NonNull Viewport viewport, final MapTokens tokens) {
+ return Observable.from(searchByViewPortConns).filter(new Func1<ISearchByViewPort, Boolean>() {
+ @Override
+ public Boolean call(final ISearchByViewPort connector) {
+ return connector.isActive();
}
- }
- return result;
+ }).parallel(new Func1<Observable<ISearchByViewPort>, Observable<SearchResult>>() {
+ @Override
+ public Observable<SearchResult> call(final Observable<ISearchByViewPort> connector) {
+ return connector.map(new Func1<ISearchByViewPort, SearchResult>() {
+ @Override
+ public SearchResult call(final ISearchByViewPort connector) {
+ return connector.searchByViewport(viewport, tokens);
+ }
+ });
+ }
+ }, Schedulers.io()).reduce(new SearchResult(), new Func2<SearchResult, SearchResult, SearchResult>() {
+
+ @Override
+ public SearchResult call(final SearchResult result, final SearchResult searchResult) {
+ result.addSearchResult(searchResult);
+ return result;
+ }
+ });
}
public static String getGeocodeFromURL(final String url) {
@@ -200,6 +221,12 @@ public final class ConnectorFactory {
return TRACKABLE_CONNECTORS;
}
+ /**
+ * Get the geocode of a trackable from a URL.
+ *
+ * @param url
+ * @return {@code null} if the URL cannot be decoded
+ */
public static String getTrackableFromURL(final String url) {
if (url == null) {
return null;
diff --git a/main/src/cgeo/geocaching/connector/UserAction.java b/main/src/cgeo/geocaching/connector/UserAction.java
index d0c97bb..e9ee4a3 100644
--- a/main/src/cgeo/geocaching/connector/UserAction.java
+++ b/main/src/cgeo/geocaching/connector/UserAction.java
@@ -1,8 +1,7 @@
package cgeo.geocaching.connector;
-import cgeo.geocaching.utils.RunnableWithArgument;
-
import org.eclipse.jdt.annotation.NonNull;
+import rx.functions.Action1;
import android.app.Activity;
@@ -19,14 +18,15 @@ public class UserAction {
}
public final int displayResourceId;
- private final @NonNull RunnableWithArgument<Context> runnable;
+ private final @NonNull
+ Action1<Context> runnable;
- public UserAction(int displayResourceId, final @NonNull RunnableWithArgument<UserAction.Context> runnable) {
+ public UserAction(int displayResourceId, final @NonNull Action1<Context> runnable) {
this.displayResourceId = displayResourceId;
this.runnable = runnable;
}
public void run(Context context) {
- runnable.run(context);
+ runnable.call(context);
}
}
diff --git a/main/src/cgeo/geocaching/connector/capability/FieldNotesCapability.java b/main/src/cgeo/geocaching/connector/capability/FieldNotesCapability.java
new file mode 100644
index 0000000..4da9705
--- /dev/null
+++ b/main/src/cgeo/geocaching/connector/capability/FieldNotesCapability.java
@@ -0,0 +1,13 @@
+package cgeo.geocaching.connector.capability;
+
+import cgeo.geocaching.connector.IConnector;
+
+import java.io.File;
+
+/**
+ * Connector interface to implement an upload of (already exported) field notes
+ *
+ */
+public interface FieldNotesCapability extends IConnector {
+ public boolean uploadFieldNotes(final File exportFile);
+}
diff --git a/main/src/cgeo/geocaching/connector/ec/ECApi.java b/main/src/cgeo/geocaching/connector/ec/ECApi.java
index 03fce4d..702e557 100644
--- a/main/src/cgeo/geocaching/connector/ec/ECApi.java
+++ b/main/src/cgeo/geocaching/connector/ec/ECApi.java
@@ -15,12 +15,12 @@ import cgeo.geocaching.list.StoredList;
import cgeo.geocaching.network.Network;
import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.utils.Log;
+import cgeo.geocaching.utils.SynchronizedDateFormat;
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;
@@ -39,7 +39,7 @@ public class ECApi {
private static final String API_HOST = "https://extremcaching.com/exports/";
private static final ECLogin ecLogin = ECLogin.getInstance();
- private static final FastDateFormat LOG_DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSSZ", TimeZone.getTimeZone("UTC"), Locale.US);
+ private static final SynchronizedDateFormat LOG_DATE_FORMAT = new SynchronizedDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ", TimeZone.getTimeZone("UTC"), Locale.US);
public static String getIdFromGeocode(final String geocode) {
return StringUtils.removeStartIgnoreCase(geocode, "EC");
diff --git a/main/src/cgeo/geocaching/connector/ec/ECConnector.java b/main/src/cgeo/geocaching/connector/ec/ECConnector.java
index 6da076b..71716fe 100644
--- a/main/src/cgeo/geocaching/connector/ec/ECConnector.java
+++ b/main/src/cgeo/geocaching/connector/ec/ECConnector.java
@@ -140,7 +140,7 @@ public class ECConnector extends AbstractConnector implements ISearchByGeocode,
// invoke settings activity to insert login details
if (status == StatusCode.NO_LOGIN_INFO_STORED && fromActivity != null) {
- SettingsActivity.jumpToServicesPage(fromActivity);
+ SettingsActivity.openForScreen(R.string.preference_screen_ec, fromActivity);
}
}
return status == StatusCode.NO_ERROR;
diff --git a/main/src/cgeo/geocaching/connector/ec/ECLogin.java b/main/src/cgeo/geocaching/connector/ec/ECLogin.java
index 52bd423..012bdc9 100644
--- a/main/src/cgeo/geocaching/connector/ec/ECLogin.java
+++ b/main/src/cgeo/geocaching/connector/ec/ECLogin.java
@@ -10,7 +10,6 @@ import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.utils.Log;
import ch.boye.httpclientandroidlib.HttpResponse;
-
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.eclipse.jdt.annotation.Nullable;
diff --git a/main/src/cgeo/geocaching/connector/gc/GCConnector.java b/main/src/cgeo/geocaching/connector/gc/GCConnector.java
index e946748..925f6f0 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCConnector.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCConnector.java
@@ -10,6 +10,7 @@ import cgeo.geocaching.SearchResult;
import cgeo.geocaching.connector.AbstractConnector;
import cgeo.geocaching.connector.ILoggingManager;
import cgeo.geocaching.connector.UserAction;
+import cgeo.geocaching.connector.capability.FieldNotesCapability;
import cgeo.geocaching.connector.capability.ICredentials;
import cgeo.geocaching.connector.capability.ILogin;
import cgeo.geocaching.connector.capability.ISearchByCenter;
@@ -23,26 +24,29 @@ import cgeo.geocaching.geopoint.Geopoint;
import cgeo.geocaching.geopoint.Viewport;
import cgeo.geocaching.loaders.RecaptchaReceiver;
import cgeo.geocaching.network.Network;
+import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.settings.SettingsActivity;
import cgeo.geocaching.utils.CancellableHandler;
import cgeo.geocaching.utils.Log;
-import cgeo.geocaching.utils.RunnableWithArgument;
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 rx.functions.Action1;
+
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Handler;
+import java.io.File;
import java.util.List;
import java.util.regex.Pattern;
-public class GCConnector extends AbstractConnector implements ISearchByGeocode, ISearchByCenter, ISearchByViewPort, ISearchByKeyword, ILogin, ICredentials, ISearchByOwner, ISearchByFinder {
+public class GCConnector extends AbstractConnector implements ISearchByGeocode, ISearchByCenter, ISearchByViewPort, ISearchByKeyword, ILogin, ICredentials, ISearchByOwner, ISearchByFinder, FieldNotesCapability {
private static final String CACHE_URL_SHORT = "http://coord.info/";
// Double slash is used to force open in browser
@@ -91,7 +95,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode,
@Override
public boolean supportsPersonalNote() {
- return Settings.isPremiumMember();
+ return Settings.isGCPremiumMember();
}
@Override
@@ -285,7 +289,22 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode,
@Override
protected String getCacheUrlPrefix() {
- return CACHE_URL_SHORT;
+ return null; // UNUSED
+ }
+
+ @Override
+ public String getGeocodeFromUrl(String url) {
+ // coord.info URLs
+ String code = StringUtils.substringAfterLast(url, "coord.info/");
+ if (code != null && canHandle(code)) {
+ return code;
+ }
+ // expanded geocaching.com URLs
+ code = StringUtils.substringBetween(url, "/geocache/", "_");
+ if (code != null && canHandle(code)) {
+ return code;
+ }
+ return null;
}
@Override
@@ -316,7 +335,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode,
// invoke settings activity to insert login details
if (status == StatusCode.NO_LOGIN_INFO_STORED && fromActivity != null) {
- SettingsActivity.jumpToServicesPage(fromActivity);
+ SettingsActivity.openForScreen(R.string.preference_screen_gc, fromActivity);
}
}
return status == StatusCode.NO_ERROR;
@@ -379,17 +398,17 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode,
public @NonNull
List<UserAction> getUserActions() {
List<UserAction> actions = super.getUserActions();
- actions.add(new UserAction(R.string.user_menu_open_browser, new RunnableWithArgument<UserAction.Context>() {
+ actions.add(new UserAction(R.string.user_menu_open_browser, new Action1<UserAction.Context>() {
@Override
- public void run(cgeo.geocaching.connector.UserAction.Context context) {
+ public void call(cgeo.geocaching.connector.UserAction.Context context) {
context.activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/profile/?u=" + Network.encode(context.userName))));
}
}));
- actions.add(new UserAction(R.string.user_menu_send_message, new RunnableWithArgument<UserAction.Context>() {
+ actions.add(new UserAction(R.string.user_menu_send_message, new Action1<UserAction.Context>() {
@Override
- public void run(cgeo.geocaching.connector.UserAction.Context context) {
+ public void call(cgeo.geocaching.connector.UserAction.Context context) {
context.activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/email/?u=" + Network.encode(context.userName))));
}
}));
@@ -406,4 +425,40 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode,
return GCParser.searchByUsername(username, Settings.getCacheType(), Settings.isShowCaptcha(), recaptchaReceiver);
}
+ @Override
+ public boolean uploadFieldNotes(final File exportFile) {
+ if (!GCLogin.getInstance().isActualLoginStatus()) {
+ // no need to upload (possibly large file) if we're not logged in
+ 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 = GCLogin.getInstance().getRequestLogged(uri, null);
+
+ if (StringUtils.isBlank(page)) {
+ Log.e("FieldnoteExport.ExportTask get page: No data from server");
+ return false;
+ }
+
+ final String[] viewstates = GCLogin.getViewstates(page);
+
+ final Parameters uploadParams = new Parameters(
+ "__EVENTTARGET", "",
+ "__EVENTARGUMENT", "",
+ "ctl00$ContentBody$btnUpload", "Upload Field Note");
+
+ GCLogin.putViewstates(uploadParams, viewstates);
+
+ Network.getResponseData(Network.postRequest(uri, uploadParams, "ctl00$ContentBody$FieldNoteLoader", "text/plain", exportFile));
+
+ if (StringUtils.isBlank(page)) {
+ Log.e("FieldnoteExport.ExportTask upload: No data from server");
+ return false;
+ }
+ return true;
+ }
+
}
diff --git a/main/src/cgeo/geocaching/connector/gc/GCConstants.java b/main/src/cgeo/geocaching/connector/gc/GCConstants.java
index 3900a95..cfc369d 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCConstants.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCConstants.java
@@ -1,5 +1,7 @@
package cgeo.geocaching.connector.gc;
+import org.eclipse.jdt.annotation.NonNull;
+
import java.util.Locale;
import java.util.regex.Pattern;
@@ -15,13 +17,13 @@ public final class GCConstants {
static final String GC_URL = "http://www.geocaching.com/";
static final String GC_TILE_URL = "http://tiles.geocaching.com/";
/** Live Map */
- public final static String URL_LIVE_MAP = GC_URL + "map/default.aspx";
+ public final static @NonNull String URL_LIVE_MAP = GC_URL + "map/default.aspx";
/** Live Map pop-up */
- public final static String URL_LIVE_MAP_DETAILS = GC_TILE_URL + "map.details";
+ public final static @NonNull String URL_LIVE_MAP_DETAILS = GC_TILE_URL + "map.details";
/** Caches in a tile */
- public final static String URL_MAP_INFO = GC_TILE_URL + "map.info";
+ public final static @NonNull String URL_MAP_INFO = GC_TILE_URL + "map.info";
/** Tile itself */
- public final static String URL_MAP_TILE = GC_TILE_URL + "map.png";
+ public final static @NonNull String URL_MAP_TILE = GC_TILE_URL + "map.png";
/**
* Patterns for parsing the result of a (detailed) search
diff --git a/main/src/cgeo/geocaching/connector/gc/GCLogin.java b/main/src/cgeo/geocaching/connector/gc/GCLogin.java
index a7cf6cf..250b0b2 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCLogin.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCLogin.java
@@ -14,7 +14,6 @@ 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;
@@ -97,7 +96,7 @@ public class GCLogin extends AbstractLogin {
}
if (getLoginStatus(loginData)) {
- Log.i("Already logged in Geocaching.com as " + username + " (" + Settings.getMemberStatus() + ')');
+ Log.i("Already logged in Geocaching.com as " + username + " (" + Settings.getGCMemberStatus() + ')');
if (switchToEnglish(loginData) && retry) {
return login(false);
}
@@ -132,7 +131,7 @@ public class GCLogin extends AbstractLogin {
assert loginData != null; // Caught above
if (getLoginStatus(loginData)) {
- Log.i("Successfully logged in Geocaching.com as " + username + " (" + Settings.getMemberStatus() + ')');
+ Log.i("Successfully logged in Geocaching.com as " + username + " (" + Settings.getGCMemberStatus() + ')');
if (switchToEnglish(loginData) && retry) {
return login(false);
@@ -204,9 +203,9 @@ public class GCLogin extends AbstractLogin {
Log.e("getLoginStatus: bad cache count", e);
}
setActualCachesFound(cachesCount);
- Settings.setMemberStatus(TextUtils.getMatch(page, GCConstants.PATTERN_MEMBER_STATUS, true, null));
- if ( page.contains(GCConstants.MEMBER_STATUS_RENEW) ) {
- Settings.setMemberStatus(GCConstants.MEMBER_STATUS_PM);
+ Settings.setGCMemberStatus(TextUtils.getMatch(page, GCConstants.PATTERN_MEMBER_STATUS, true, null));
+ if (page.contains(GCConstants.MEMBER_STATUS_RENEW)) {
+ Settings.setGCMemberStatus(GCConstants.MEMBER_STATUS_PM);
}
return true;
}
@@ -259,9 +258,9 @@ public class GCLogin extends AbstractLogin {
final String responseData = StringUtils.defaultString(Network.getResponseData(Network.getRequest("http://www.geocaching.com/my/")));
final String profile = TextUtils.replaceWhitespace(responseData);
- Settings.setMemberStatus(TextUtils.getMatch(profile, GCConstants.PATTERN_MEMBER_STATUS, true, null));
+ Settings.setGCMemberStatus(TextUtils.getMatch(profile, GCConstants.PATTERN_MEMBER_STATUS, true, null));
if (profile.contains(GCConstants.MEMBER_STATUS_RENEW)) {
- Settings.setMemberStatus(GCConstants.MEMBER_STATUS_PM);
+ Settings.setGCMemberStatus(GCConstants.MEMBER_STATUS_PM);
}
setActualCachesFound(Integer.parseInt(TextUtils.getMatch(profile, GCConstants.PATTERN_CACHES_FOUND, true, "-1").replaceAll("[,.]", "")));
diff --git a/main/src/cgeo/geocaching/connector/gc/GCMap.java b/main/src/cgeo/geocaching/connector/gc/GCMap.java
index 6c94150..2782b64 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCMap.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCMap.java
@@ -368,7 +368,7 @@ public class GCMap {
}
}
- if (strategy.flags.contains(StrategyFlag.SEARCH_NEARBY) && Settings.isPremiumMember()) {
+ if (strategy.flags.contains(StrategyFlag.SEARCH_NEARBY) && Settings.isGCPremiumMember()) {
final Geopoint center = viewport.getCenter();
if ((lastSearchViewport == null) || !lastSearchViewport.contains(center)) {
//FIXME We don't have a RecaptchaReceiver!?
diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java
index 219adc8..8e59e5e 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCParser.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java
@@ -36,7 +36,6 @@ import cgeo.geocaching.utils.SynchronizedDateFormat;
import cgeo.geocaching.utils.TextUtils;
import ch.boye.httpclientandroidlib.HttpResponse;
-
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringEscapeUtils;
@@ -63,12 +62,13 @@ import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.Set;
+import java.util.regex.Pattern;
public abstract class GCParser {
private final static SynchronizedDateFormat dateTbIn1 = new SynchronizedDateFormat("EEEEE, dd MMMMM yyyy", Locale.ENGLISH); // Saturday, 28 March 2009
private final static SynchronizedDateFormat dateTbIn2 = new SynchronizedDateFormat("EEEEE, MMMMM dd, yyyy", Locale.ENGLISH); // Saturday, March 28, 2009
- private static SearchResult parseSearch(final String url, final String pageContent, final boolean showCaptcha, RecaptchaReceiver thread) {
+ private static SearchResult parseSearch(final String url, final String pageContent, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) {
if (StringUtils.isBlank(pageContent)) {
Log.e("GCParser.parseSearch: No page given");
return null;
@@ -86,12 +86,12 @@ public abstract class GCParser {
final String recaptchaJsParam = TextUtils.getMatch(page, GCConstants.PATTERN_SEARCH_RECAPTCHA, false, null);
if (recaptchaJsParam != null) {
- thread.setKey(recaptchaJsParam.trim());
+ recaptchaReceiver.setKey(recaptchaJsParam.trim());
- thread.fetchChallenge();
+ recaptchaReceiver.fetchChallenge();
}
- if (thread != null && StringUtils.isNotBlank(thread.getChallenge())) {
- thread.notifyNeed();
+ if (recaptchaReceiver != null && StringUtils.isNotBlank(recaptchaReceiver.getChallenge())) {
+ recaptchaReceiver.notifyNeed();
}
}
@@ -277,12 +277,12 @@ public abstract class GCParser {
}
String recaptchaText = null;
- if (thread != null && StringUtils.isNotBlank(thread.getChallenge())) {
- thread.waitForUser();
- recaptchaText = thread.getText();
+ if (recaptchaReceiver != null && StringUtils.isNotBlank(recaptchaReceiver.getChallenge())) {
+ recaptchaReceiver.waitForUser();
+ recaptchaText = recaptchaReceiver.getText();
}
- if (!cids.isEmpty() && (Settings.isPremiumMember() || showCaptcha) && ((thread == null || StringUtils.isBlank(thread.getChallenge())) || StringUtils.isNotBlank(recaptchaText))) {
+ if (!cids.isEmpty() && (Settings.isGCPremiumMember() || showCaptcha) && ((recaptchaReceiver == null || StringUtils.isBlank(recaptchaReceiver.getChallenge())) || StringUtils.isNotBlank(recaptchaText))) {
Log.i("Trying to get .loc for " + cids.size() + " caches");
try {
@@ -303,8 +303,8 @@ public abstract class GCParser {
params.put("CID", cid);
}
- if (thread != null && StringUtils.isNotBlank(thread.getChallenge()) && StringUtils.isNotBlank(recaptchaText)) {
- params.put("recaptcha_challenge_field", thread.getChallenge());
+ if (StringUtils.isNotBlank(recaptchaText) && recaptchaReceiver != null) {
+ params.put("recaptcha_challenge_field", recaptchaReceiver.getChallenge());
params.put("recaptcha_response_field", recaptchaText);
}
params.put("ctl00$ContentBody$uxDownloadLoc", "Download Waypoints");
@@ -347,6 +347,9 @@ public abstract class GCParser {
// attention: parseCacheFromText already stores implicitly through searchResult.addCache
if (searchResult != null && !searchResult.getGeocodes().isEmpty()) {
final Geocache cache = searchResult.getFirstCacheFromResult(LoadFlags.LOAD_CACHE_OR_DB);
+ if (cache == null) {
+ return null;
+ }
getExtraOnlineInfo(cache, page, handler);
// too late: it is already stored through parseCacheFromText
cache.setDetailedUpdatedNow();
@@ -741,7 +744,7 @@ public abstract class GCParser {
cache.parseWaypointsFromNote();
// logs
- cache.setLogs(loadLogsFromDetails(page, cache, false, true));
+ cache.setLogs(getLogsFromDetails(page, false));
// last check for necessary cache conditions
if (StringUtils.isBlank(cache.getGeocode())) {
@@ -1308,7 +1311,7 @@ public abstract class GCParser {
return false; // error
}
- final boolean guidOnPage = cache.isGuidContainedInPage(page);
+ final boolean guidOnPage = isGuidContainedInPage(cache, page);
if (guidOnPage) {
Log.i("GCParser.addToWatchlist: cache is on watchlist");
cache.setOnWatchlist(true);
@@ -1342,7 +1345,7 @@ public abstract class GCParser {
GCLogin.transferViewstates(page, params);
page = Network.getResponseData(Network.postRequest(uri, params));
- final boolean guidOnPage = cache.isGuidContainedInPage(page);
+ final boolean guidOnPage = isGuidContainedInPage(cache, page);
if (!guidOnPage) {
Log.i("GCParser.removeFromWatchlist: cache removed from watchlist");
cache.setOnWatchlist(false);
@@ -1352,6 +1355,21 @@ public abstract class GCParser {
return !guidOnPage; // on watch list (=error) / not on watch list
}
+ /**
+ * Checks if a page contains the guid of a cache
+ *
+ * @param cache the geocache
+ * @param page
+ * the page to search in, may be null
+ * @return true if the page contains the guid of the cache, false otherwise
+ */
+ private static boolean isGuidContainedInPage(final Geocache cache, final String page) {
+ if (StringUtils.isBlank(page) || StringUtils.isBlank(cache.getGuid())) {
+ return false;
+ }
+ return Pattern.compile(cache.getGuid(), Pattern.CASE_INSENSITIVE).matcher(page).find();
+ }
+
@Nullable
static String requestHtmlPage(@Nullable final String geocode, @Nullable final String guid, final String log, final String numlogs) {
final Parameters params = new Parameters("decrypt", "y");
@@ -1380,8 +1398,7 @@ public abstract class GCParser {
}
private static boolean changeFavorite(final Geocache cache, final boolean add) {
- final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0");
- final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, "");
+ final String userToken = getUserToken(cache);
if (StringUtils.isEmpty(userToken)) {
return false;
}
@@ -1400,6 +1417,11 @@ public abstract class GCParser {
return false;
}
+ private static String getUserToken(final Geocache cache) {
+ final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0");
+ return TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, "");
+ }
+
/**
* Removes the cache from the favorites.
*
@@ -1617,19 +1639,20 @@ public abstract class GCParser {
}
/**
- * Load logs from a cache details page.
+ * Extract logs from a cache details page.
*
* @param page
* the text of the details page
- * @param cache
- * the cache object to put the logs in
* @param friends
- * retrieve friend logs
+ * return friends logs only (will require a network request)
+ * @return a list of log entries or <code>null</code> if the logs could not be retrieved
+ *
*/
- private static List<LogEntry> loadLogsFromDetails(final String page, final Geocache cache, final boolean friends, final boolean getDataFromPage) {
+ @Nullable
+ private static List<LogEntry> getLogsFromDetails(final String page, final boolean friends) {
String rawResponse;
- if (!getDataFromPage) {
+ if (friends) {
final MatcherWrapper userTokenMatcher = new MatcherWrapper(GCConstants.PATTERN_USERTOKEN, page);
if (!userTokenMatcher.find()) {
Log.e("GCParser.loadLogsFromDetails: unable to extract userToken");
@@ -1716,7 +1739,7 @@ public abstract class GCParser {
final JSONArray images = entry.getJSONArray("Images");
for (int i = 0; i < images.length(); i++) {
final JSONObject image = images.getJSONObject(i);
- final String url = "http://img.geocaching.com/cache/log/large/" + image.getString("FileName");
+ final String url = "http://imgcdn.geocaching.com/cache/log/large/" + image.getString("FileName");
final String title = TextUtils.removeControlCharacters(image.getString("Name"));
final Image logImage = new Image(url, title);
logDone.addLogImage(logImage);
@@ -1829,7 +1852,7 @@ public abstract class GCParser {
if (Settings.isFriendLogsWanted()) {
CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs);
final List<LogEntry> allLogs = cache.getLogs();
- final List<LogEntry> friendLogs = loadLogsFromDetails(page, cache, true, false);
+ final List<LogEntry> friendLogs = getLogsFromDetails(page, true);
if (friendLogs != null) {
for (final LogEntry log : friendLogs) {
if (allLogs.contains(log)) {
@@ -1864,8 +1887,7 @@ public abstract class GCParser {
}
public static boolean editModifiedCoordinates(Geocache cache, Geopoint wpt) {
- final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0");
- final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, "");
+ final String userToken = getUserToken(cache);
if (StringUtils.isEmpty(userToken)) {
return false;
}
@@ -1900,8 +1922,7 @@ public abstract class GCParser {
}
public static boolean uploadPersonalNote(Geocache cache) {
- final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0");
- final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, "");
+ final String userToken = getUserToken(cache);
if (StringUtils.isEmpty(userToken)) {
return false;
}
diff --git a/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java b/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java
new file mode 100644
index 0000000..ff3f5ef
--- /dev/null
+++ b/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java
@@ -0,0 +1,114 @@
+package cgeo.geocaching.connector.gc;
+
+import cgeo.geocaching.R;
+import cgeo.geocaching.loaders.RecaptchaReceiver;
+import cgeo.geocaching.network.Network;
+import cgeo.geocaching.utils.Log;
+
+import org.apache.commons.io.IOUtils;
+
+import rx.Observable;
+import rx.android.observables.AndroidObservable;
+import rx.schedulers.Schedulers;
+import rx.functions.Action1;
+import rx.functions.Func0;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Handler;
+import android.os.Message;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+
+import java.io.InputStream;
+
+public class RecaptchaHandler extends Handler {
+ final public static int SHOW_CAPTCHA = 1;
+
+ final private Activity activity;
+ final private RecaptchaReceiver recaptchaReceiver;
+
+ public RecaptchaHandler(final Activity activity, final RecaptchaReceiver recaptchaReceiver) {
+ this.activity = activity;
+ this.recaptchaReceiver = recaptchaReceiver;
+ }
+
+ private void loadChallenge(final ImageView imageView, final View reloadButton) {
+ getCaptcha().subscribe(new Action1<Bitmap>() {
+ @Override
+ public void call(final Bitmap bitmap) {
+ imageView.setImageBitmap(bitmap);
+ }
+ }, new Action1<Throwable>() {
+ @Override
+ public void call(final Throwable throwable) {
+ // Do nothing
+ }
+ });
+ reloadButton.setEnabled(true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == SHOW_CAPTCHA) {
+ final AlertDialog.Builder dlg = new AlertDialog.Builder(activity);
+ final View view = activity.getLayoutInflater().inflate(R.layout.recaptcha_dialog, null);
+
+ final ImageView imageView = (ImageView) view.findViewById(R.id.image);
+
+ final ImageButton reloadButton = (ImageButton) view.findViewById(R.id.button_recaptcha_refresh);
+ reloadButton.setEnabled(false);
+ reloadButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ recaptchaReceiver.fetchChallenge();
+ loadChallenge(imageView, reloadButton);
+ }
+ });
+
+ loadChallenge(imageView, reloadButton);
+
+ dlg.setTitle(activity.getString(R.string.caches_recaptcha_title));
+ dlg.setView(view);
+ dlg.setNeutralButton(activity.getString(R.string.caches_recaptcha_continue), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ final String text = ((EditText) view.findViewById(R.id.text)).getText().toString();
+ recaptchaReceiver.setText(text);
+ dialog.cancel();
+ }
+ });
+
+ dlg.create().show();
+ }
+ }
+
+ private Observable<Bitmap> getCaptcha() {
+ return AndroidObservable.fromActivity(activity,
+ Observable.defer(new Func0<Observable<? extends Bitmap>>() {
+ @Override
+ public Observable<? extends Bitmap> call() {
+ final String url = "http://www.google.com/recaptcha/api/image?c=" + recaptchaReceiver.getChallenge();
+ final InputStream is = Network.getResponseStream(Network.getRequest(url));
+ if (is != null) {
+ try {
+ final Bitmap img = BitmapFactory.decodeStream(is);
+ return Observable.from(img);
+ } catch (final Exception e) {
+ Log.e("RecaptchaHandler.getCaptcha", e);
+ return Observable.error(e);
+ } finally {
+ IOUtils.closeQuietly(is);
+ }
+ }
+ return Observable.empty();
+ }
+ }).subscribeOn(Schedulers.io()));
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/connector/gc/SearchHandler.java b/main/src/cgeo/geocaching/connector/gc/SearchHandler.java
deleted file mode 100644
index 795ed2f..0000000
--- a/main/src/cgeo/geocaching/connector/gc/SearchHandler.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package cgeo.geocaching.connector.gc;
-
-import cgeo.geocaching.R;
-import cgeo.geocaching.loaders.RecaptchaReceiver;
-import cgeo.geocaching.utils.Log;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.DialogInterface;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.os.Handler;
-import android.os.Message;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.EditText;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-public class SearchHandler extends Handler {
- private Activity activity = null;
- private Resources res = null;
- private RecaptchaReceiver recaptchaThread = null;
- private ImageView imageView = null;
- private Bitmap img = null;
-
- private Handler imgHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- try {
- if (img != null && imageView != null) {
- imageView.setImageBitmap(img);
- }
- } catch (Exception e) {
- Log.e("Error setting reCAPTCHA image", e);
- }
- }
- };
-
- public SearchHandler(Activity activityIn, Resources resIn, RecaptchaReceiver recaptchaThreadIn) {
- activity = activityIn;
- res = resIn;
- recaptchaThread = recaptchaThreadIn;
- }
-
- @Override
- public void handleMessage(Message msg) {
- try {
- if (msg.what == 1) {
- final AlertDialog.Builder dlg = new AlertDialog.Builder(activity);
- final LayoutInflater inflater = activity.getLayoutInflater();
- final View view = inflater.inflate(R.layout.recaptcha_dialog, null);
-
- imageView = (ImageView) view.findViewById(R.id.image);
-
- ImageButton reloadButton = (ImageButton) view.findViewById(R.id.button_recaptcha_refresh);
- reloadButton.setOnClickListener(new View.OnClickListener() {
-
- @Override
- public void onClick(View v) {
- recaptchaThread.fetchChallenge();
- try {
- (new GetCaptchaThread(new URL("http://www.google.com/recaptcha/api/image?c=" + recaptchaThread.getChallenge()))).start();
- } catch (MalformedURLException e) {
- Log.e("Bad reCAPTCHA image url", e);
- }
- }
- });
-
- (new GetCaptchaThread(new URL("http://www.google.com/recaptcha/api/image?c=" + recaptchaThread.getChallenge()))).start();
-
- dlg.setTitle(res.getString(R.string.caches_recaptcha_title));
- dlg.setView(view);
- dlg.setNeutralButton(res.getString(R.string.caches_recaptcha_continue), new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- final String text = ((EditText) view.findViewById(R.id.text)).getText().toString();
-
- recaptchaThread.setText(text);
-
- dialog.cancel();
- }
- });
-
- dlg.create().show();
- }
- } catch (MalformedURLException e) {
- Log.e("Error in reCAPTCHA handler", e);
- }
- }
-
- private class GetCaptchaThread extends Thread {
- private URL uri = null;
-
- public GetCaptchaThread(URL uriIn) {
- uri = uriIn;
- }
-
- @Override
- public void run() {
- try {
- Log.d("Getting reCAPTCHA image from: " + uri.toString());
- HttpURLConnection connection = (HttpURLConnection) uri.openConnection();
- connection.setDoInput(true);
- connection.connect();
-
- InputStream is = connection.getInputStream();
-
- img = BitmapFactory.decodeStream(is);
-
- is.close();
-
- imgHandler.sendEmptyMessage(0);
- } catch (IOException e) {
- Log.e("Failed to download reCAPTCHA image", e);
- }
- }
- }
-}
diff --git a/main/src/cgeo/geocaching/connector/gc/Tile.java b/main/src/cgeo/geocaching/connector/gc/Tile.java
index 623730a..6a257cd 100644
--- a/main/src/cgeo/geocaching/connector/gc/Tile.java
+++ b/main/src/cgeo/geocaching/connector/gc/Tile.java
@@ -10,6 +10,8 @@ import cgeo.geocaching.utils.Log;
import ch.boye.httpclientandroidlib.HttpResponse;
+import org.eclipse.jdt.annotation.NonNull;
+
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -110,6 +112,7 @@ public class Tile {
* @see <a
* href="http://developers.cloudmade.com/projects/tiles/examples/convert-coordinates-to-tile-numbers">Cloudmade</a>
*/
+ @NonNull
public Geopoint getCoord(UTFGridPosition pos) {
double pixX = tileX * TILE_SIZE + pos.x * 4;
@@ -244,7 +247,7 @@ public class Tile {
return null;
}
- public boolean containsPoint(final ICoordinates point) {
+ public boolean containsPoint(final @NonNull ICoordinates point) {
return viewPort.contains(point);
}
diff --git a/main/src/cgeo/geocaching/connector/oc/IOCAuthParams.java b/main/src/cgeo/geocaching/connector/oc/IOCAuthParams.java
index dacb626..acf7b48 100644
--- a/main/src/cgeo/geocaching/connector/oc/IOCAuthParams.java
+++ b/main/src/cgeo/geocaching/connector/oc/IOCAuthParams.java
@@ -1,5 +1,7 @@
package cgeo.geocaching.connector.oc;
+import org.eclipse.jdt.annotation.NonNull;
+
public interface IOCAuthParams {
/**
@@ -7,6 +9,7 @@ public interface IOCAuthParams {
*
* @return
*/
+ @NonNull
String getSite();
/**
@@ -63,5 +66,6 @@ public interface IOCAuthParams {
*
* @return
*/
+ @NonNull
String getCallbackUri();
}
diff --git a/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java b/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java
index 46e4c96..284234e 100644
--- a/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java
+++ b/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java
@@ -90,4 +90,16 @@ public class OCApiConnector extends OCConnector implements ISearchByGeocode {
public int getTokenSecretPrefKeyId() {
return 0;
}
+
+ /**
+ * Checks if a search based on a user name targets the current user
+ *
+ * @param username
+ * Name of the user the query is searching after
+ * @return True - search target and current is same, False - current user not known or not the same as username
+ */
+ @SuppressWarnings("static-method")
+ public boolean isSearchForMyCaches(String username) {
+ return false;
+ }
}
diff --git a/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java b/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java
index 0b7493c..049c633 100644
--- a/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java
+++ b/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java
@@ -173,4 +173,10 @@ public class OCApiLiveConnector extends OCApiConnector implements ISearchByCente
final Geopoint currentPos = CgeoApplication.getInstance().currentGeo().getCoords();
return new SearchResult(OkapiClient.getCachesNamed(currentPos, name, this));
}
+
+ @Override
+ public boolean isSearchForMyCaches(String username) {
+ return StringUtils.equalsIgnoreCase(username, getUserName());
+ }
+
}
diff --git a/main/src/cgeo/geocaching/connector/oc/OCAuthorizationActivity.java b/main/src/cgeo/geocaching/connector/oc/OCAuthorizationActivity.java
index c082bac..19f4447 100644
--- a/main/src/cgeo/geocaching/connector/oc/OCAuthorizationActivity.java
+++ b/main/src/cgeo/geocaching/connector/oc/OCAuthorizationActivity.java
@@ -2,9 +2,13 @@ package cgeo.geocaching.connector.oc;
import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.R;
+import cgeo.geocaching.connector.oc.OkapiError.OkapiErrors;
import cgeo.geocaching.network.OAuthAuthorizationActivity;
import cgeo.geocaching.settings.Settings;
+import ch.boye.httpclientandroidlib.HttpResponse;
+
+import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.eclipse.jdt.annotation.Nullable;
@@ -52,4 +56,16 @@ public abstract class OCAuthorizationActivity extends OAuthAuthorizationActivity
return res.getString(R.string.auth_dialog_completed_oc, getAuthTitle());
}
+ /**
+ * Return an extended error in case of an invalid time stamp
+ */
+ @Override
+ protected String getExtendedErrorMsg(HttpResponse response) {
+ OkapiError error = OkapiClient.decodeErrorResponse(response);
+ if (error.getResult() == OkapiErrors.INVALID_TIMESTAMP) {
+ return res.getString(R.string.init_login_popup_invalid_timestamp);
+ }
+ return StringUtils.EMPTY;
+ }
+
}
diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java
index 2175935..712bb26 100644
--- a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java
+++ b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java
@@ -34,7 +34,6 @@ import cgeo.geocaching.utils.SynchronizedDateFormat;
import ch.boye.httpclientandroidlib.HttpResponse;
import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.time.FastDateFormat;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
@@ -61,7 +60,7 @@ final class OkapiClient {
private static final char SEPARATOR = '|';
private static final String SEPARATOR_STRING = Character.toString(SEPARATOR);
- private static final FastDateFormat LOG_DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSSZ", TimeZone.getTimeZone("UTC"), Locale.US);
+ private static final SynchronizedDateFormat LOG_DATE_FORMAT = new SynchronizedDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ", TimeZone.getTimeZone("UTC"), Locale.US);
private static final SynchronizedDateFormat ISO8601DATEFORMAT = new SynchronizedDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault());
private static final String CACHE_ATTRNAMES = "attrnames";
@@ -75,7 +74,8 @@ final class OkapiClient {
private static final String CACHE_STATUS_ARCHIVED = "Archived";
private static final String CACHE_STATUS_DISABLED = "Temporarily unavailable";
private static final String CACHE_IS_FOUND = "is_found";
- private static final String CACHE_SIZE = "size";
+ private static final String CACHE_SIZE_DEPRECATED = "size";
+ private static final String CACHE_SIZE2 = "size2";
private static final String CACHE_VOTES = "rating_votes";
private static final String CACHE_NOTFOUNDS = "notfounds";
private static final String CACHE_FOUNDS = "founds";
@@ -112,7 +112,7 @@ final class OkapiClient {
// the several realms of possible fields for cache retrieval:
// Core: for livemap requests (L3 - only with level 3 auth)
// Additional: additional fields for full cache (L3 - only for level 3 auth, current - only for connectors with current api)
- private static final String SERVICE_CACHE_CORE_FIELDS = "code|name|location|type|status|difficulty|terrain|size|date_hidden";
+ private static final String SERVICE_CACHE_CORE_FIELDS = "code|name|location|type|status|difficulty|terrain|size|size2|date_hidden";
private static final String SERVICE_CACHE_CORE_L3_FIELDS = "is_found";
private static final String SERVICE_CACHE_ADDITIONAL_FIELDS = "owner|founds|notfounds|rating|rating_votes|recommendations|description|hint|images|latest_logs|alt_wpts|attrnames|req_passwd";
private static final String SERVICE_CACHE_ADDITIONAL_CURRENT_FIELDS = "gc_code|attribution_note|attr_acodes";
@@ -148,23 +148,18 @@ final class OkapiClient {
valueMap.put("limit", "20");
valueMap.put("radius", "200");
- return requestCaches(connector, params, valueMap);
+ return requestCaches(connector, params, valueMap, false);
}
public static List<Geocache> getCachesByOwner(final String username, final OCApiConnector connector) {
- final Parameters params = new Parameters("search_method", METHOD_SEARCH_ALL);
- final Map<String, String> valueMap = new LinkedHashMap<String, String>();
- final @Nullable
- String uuid = getUserUUID(connector, username);
- if (StringUtils.isEmpty(uuid)) {
- return Collections.emptyList();
- }
- valueMap.put("owner_uuid", uuid);
-
- return requestCaches(connector, params, valueMap);
+ return getCachesByUser(username, connector, "owner_uuid");
}
public static List<Geocache> getCachesByFinder(final String username, final OCApiConnector connector) {
+ return getCachesByUser(username, connector, "found_by");
+ }
+
+ private static List<Geocache> getCachesByUser(final String username, final OCApiConnector connector, final String userRequestParam) {
final Parameters params = new Parameters("search_method", METHOD_SEARCH_ALL);
final Map<String, String> valueMap = new LinkedHashMap<String, String>();
final @Nullable
@@ -172,9 +167,9 @@ final class OkapiClient {
if (StringUtils.isEmpty(uuid)) {
return Collections.emptyList();
}
- valueMap.put("found_by", uuid);
+ valueMap.put(userRequestParam, uuid);
- return requestCaches(connector, params, valueMap);
+ return requestCaches(connector, params, valueMap, connector.isSearchForMyCaches(username));
}
public static List<Geocache> getCachesNamed(final Geopoint center, final String namePart, final OCApiConnector connector) {
@@ -195,16 +190,16 @@ final class OkapiClient {
// full wildcard search, maybe we need to change this after some testing and evaluation
valueMap.put("name", "*" + namePart + "*");
- return requestCaches(connector, params, valueMap);
+ return requestCaches(connector, params, valueMap, false);
}
- private static List<Geocache> requestCaches(final OCApiConnector connector, final Parameters params, final Map<String, String> valueMap) {
+ private static List<Geocache> requestCaches(final OCApiConnector connector, final Parameters params, final Map<String, String> valueMap, final boolean my) {
// if a global type filter is set, and OKAPI does not know that type, then return an empty list instead of all caches
if (Settings.getCacheType() != CacheType.ALL && StringUtils.isBlank(getFilterFromType())) {
return Collections.emptyList();
}
- addFilterParams(valueMap, connector);
+ addFilterParams(valueMap, connector, my);
params.add("search_params", new JSONObject(valueMap).toString());
addRetrieveParams(params, connector);
@@ -234,7 +229,7 @@ final class OkapiClient {
final Map<String, String> valueMap = new LinkedHashMap<String, String>();
valueMap.put("bbox", bboxString);
- return requestCaches(connector, params, valueMap);
+ return requestCaches(connector, params, valueMap, false);
}
public static boolean setWatchState(final Geocache cache, final boolean watched, final OCApiConnector connector) {
@@ -386,6 +381,7 @@ final class OkapiClient {
}
if (!response.isNull(CACHE_MY_NOTES)) {
cache.setPersonalNote(response.getString(CACHE_MY_NOTES));
+ cache.parseWaypointsFromNote();
}
cache.setLogPasswordRequired(response.getBoolean(CACHE_REQ_PASSWORD));
@@ -489,6 +485,30 @@ final class OkapiClient {
if ("Didn't find it".equalsIgnoreCase(logType)) {
return LogType.DIDNT_FIND_IT;
}
+ if ("Will attend".equalsIgnoreCase(logType)) {
+ return LogType.WILL_ATTEND;
+ }
+ if ("Attended".equalsIgnoreCase(logType)) {
+ return LogType.ATTENDED;
+ }
+ if ("Temporarily unavailable".equalsIgnoreCase(logType)) {
+ return LogType.TEMP_DISABLE_LISTING;
+ }
+ if ("Ready to search".equalsIgnoreCase(logType)) {
+ return LogType.ENABLE_LISTING;
+ }
+ if ("Archived".equalsIgnoreCase(logType)) {
+ return LogType.ARCHIVE;
+ }
+ if ("Needs maintenance".equalsIgnoreCase(logType)) {
+ return LogType.NEEDS_MAINTENANCE;
+ }
+ if ("Moved".equalsIgnoreCase(logType)) {
+ return LogType.UPDATE_COORDINATES;
+ }
+ if ("OC Team comment".equalsIgnoreCase(logType)) {
+ return LogType.POST_REVIEWER_NOTE;
+ }
return LogType.NOTE;
}
@@ -567,12 +587,25 @@ final class OkapiClient {
}
private static CacheSize getCacheSize(final JSONObject response) {
- if (response.isNull(CACHE_SIZE)) {
+ if (response.isNull(CACHE_SIZE2)) {
+ return getCacheSizeDeprecated(response);
+ }
+ try {
+ final String size = response.getString(CACHE_SIZE2);
+ return CacheSize.getById(size);
+ } catch (JSONException e) {
+ Log.e("OkapiClient.getCacheSize", e);
+ return getCacheSizeDeprecated(response);
+ }
+ }
+
+ private static CacheSize getCacheSizeDeprecated(final JSONObject response) {
+ if (response.isNull(CACHE_SIZE_DEPRECATED)) {
return CacheSize.NOT_CHOSEN;
}
double size = 0;
try {
- size = response.getDouble(CACHE_SIZE);
+ size = response.getDouble(CACHE_SIZE_DEPRECATED);
} catch (final JSONException e) {
Log.e("OkapiClient.getCacheSize", e);
}
@@ -586,7 +619,7 @@ final class OkapiClient {
case 4:
return CacheSize.LARGE;
case 5:
- return CacheSize.LARGE;
+ return CacheSize.VERY_LARGE;
default:
break;
}
@@ -687,11 +720,11 @@ final class OkapiClient {
return "en";
}
- private static void addFilterParams(final Map<String, String> valueMap, final OCApiConnector connector) {
+ private static void addFilterParams(final Map<String, String> valueMap, final OCApiConnector connector, final boolean my) {
if (!Settings.isExcludeDisabledCaches()) {
valueMap.put("status", "Available|Temporarily unavailable");
}
- if (Settings.isExcludeMyCaches() && connector.getSupportedAuthLevel() == OAuthLevel.Level3) {
+ if (!my && Settings.isExcludeMyCaches() && connector.getSupportedAuthLevel() == OAuthLevel.Level3) {
valueMap.put("exclude_my_own", "true");
valueMap.put("found_status", "notfound_only");
}
@@ -789,6 +822,21 @@ final class OkapiClient {
}
/**
+ * Retrieves error information from an unsuccessful Okapi-response
+ *
+ * @param response
+ * response containing an error object
+ * @return OkapiError object with detailed information
+ */
+ public static OkapiError decodeErrorResponse(HttpResponse response) {
+ final JSONResult result = new JSONResult(response);
+ if (!result.isSuccess) {
+ return new OkapiError(result.data);
+ }
+ return new OkapiError(new JSONObject());
+ }
+
+ /**
* Encapsulates response state and content of an HTTP-request that expects a JSON result. <code>isSuccess</code> is
* only true, if the response state was success and <code>data</code> is not null.
*/
diff --git a/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java b/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java
index f72b698..5f11a11 100644
--- a/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java
+++ b/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java
@@ -28,10 +28,10 @@ public class OXGPXParser extends GPX10Parser {
/**
* 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(), ","));
+ cache.setShortDescription(StringUtils.trim(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 2defc52..0137af4 100644
--- a/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java
+++ b/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java
@@ -11,7 +11,6 @@ import cgeo.geocaching.utils.CryptUtils;
import cgeo.geocaching.utils.Log;
import ch.boye.httpclientandroidlib.HttpResponse;
-
import org.apache.commons.collections4.CollectionUtils;
import org.eclipse.jdt.annotation.NonNull;
diff --git a/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java b/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java
index 69efddc..fb554b9 100644
--- a/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java
+++ b/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java
@@ -4,6 +4,7 @@ import cgeo.geocaching.connector.AbstractConnector;
import cgeo.geocaching.connector.UserAction;
import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
import java.util.List;
@@ -15,7 +16,8 @@ public abstract class AbstractTrackableConnector implements TrackableConnector {
}
@Override
- public String getTrackableCodeFromUrl(@NonNull String url) {
+ public @Nullable
+ String getTrackableCodeFromUrl(@NonNull String url) {
return null;
}
diff --git a/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java b/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java
index 8387076..03052f9 100644
--- a/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java
+++ b/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java
@@ -4,6 +4,10 @@ import cgeo.geocaching.Trackable;
import cgeo.geocaching.network.Network;
import cgeo.geocaching.utils.Log;
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
import java.util.regex.Pattern;
public class GeokretyConnector extends AbstractTrackableConnector {
@@ -29,7 +33,7 @@ public class GeokretyConnector extends AbstractTrackableConnector {
return GeokretyParser.parse(page);
}
- private static int getId(String geocode) {
+ protected static int getId(String geocode) {
try {
final String hex = geocode.substring(2);
return Integer.parseInt(hex, 16);
@@ -39,4 +43,25 @@ public class GeokretyConnector extends AbstractTrackableConnector {
return -1;
}
+ @Override
+ public @Nullable
+ String getTrackableCodeFromUrl(@NonNull String url) {
+ // http://geokrety.org/konkret.php?id=38545
+ String id = StringUtils.substringAfterLast(url, "konkret.php?id=");
+ if (StringUtils.isNumeric(id)) {
+ return geocode(Integer.parseInt(id));
+ }
+ return null;
+ }
+
+ /**
+ * Get geocode from geokrety id
+ *
+ * @param id
+ * @return
+ */
+ public static String geocode(final int id) {
+ return String.format("GK%04X", id);
+ }
+
}
diff --git a/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java b/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java
index ee8c8c0..0e64abd 100644
--- a/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java
+++ b/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java
@@ -37,8 +37,8 @@ public class GeokretyParser {
public void start(Attributes attributes) {
try {
final String kretyId = attributes.getValue("id");
- if (StringUtils.isNotBlank(kretyId)) {
- trackable.setGeocode(geocode(Integer.parseInt(kretyId)));
+ if (StringUtils.isNumeric(kretyId)) {
+ trackable.setGeocode(GeokretyConnector.geocode(Integer.parseInt(kretyId)));
}
final String distance = attributes.getValue("dist");
if (StringUtils.isNotBlank(distance)) {
@@ -88,8 +88,4 @@ public class GeokretyParser {
}
return null;
}
-
- protected static String geocode(final int id) {
- return String.format("GK%04X", id);
- }
}
diff --git a/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java b/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java
index 0990d96..6071b5f 100644
--- a/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java
+++ b/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java
@@ -4,6 +4,7 @@ import cgeo.geocaching.Trackable;
import cgeo.geocaching.connector.UserAction;
import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
import java.util.List;
@@ -21,7 +22,8 @@ public interface TrackableConnector {
public Trackable searchTrackable(String geocode, String guid, String id);
- public String getTrackableCodeFromUrl(final @NonNull String url);
+ public @Nullable
+ String getTrackableCodeFromUrl(final @NonNull String url);
public @NonNull
List<UserAction> getUserActions();
diff --git a/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java b/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java
index dad285c..77848d7 100644
--- a/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java
+++ b/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java
@@ -7,6 +7,7 @@ import cgeo.geocaching.connector.gc.GCParser;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
import java.util.List;
import java.util.regex.Pattern;
@@ -20,7 +21,7 @@ public class TravelBugConnector extends AbstractTrackableConnector {
@Override
public boolean canHandleTrackable(String geocode) {
- return TravelBugConnector.PATTERN_TB_CODE.matcher(geocode).matches();
+ return TravelBugConnector.PATTERN_TB_CODE.matcher(geocode).matches() && !StringUtils.startsWithIgnoreCase(geocode, "GC");
}
@Override
@@ -54,8 +55,18 @@ public class TravelBugConnector extends AbstractTrackableConnector {
}
@Override
- public String getTrackableCodeFromUrl(@NonNull String url) {
- return StringUtils.substringAfterLast(url, "?tracker=");
+ public @Nullable
+ String getTrackableCodeFromUrl(@NonNull String url) {
+ // coord.info URLs
+ String code = StringUtils.substringAfterLast(url, "coord.info/");
+ if (code != null && canHandleTrackable(code)) {
+ return code;
+ }
+ code = StringUtils.substringAfterLast(url, "?tracker=");
+ if (code != null && canHandleTrackable(code)) {
+ return code;
+ }
+ return null;
}
@Override
diff --git a/main/src/cgeo/geocaching/enumerations/CacheAttribute.java b/main/src/cgeo/geocaching/enumerations/CacheAttribute.java
index 472bad5..0703c3c 100644
--- a/main/src/cgeo/geocaching/enumerations/CacheAttribute.java
+++ b/main/src/cgeo/geocaching/enumerations/CacheAttribute.java
@@ -164,15 +164,11 @@ public enum CacheAttribute {
}
private final static Map<String, CacheAttribute> FIND_BY_GCRAWNAME;
- private final static SparseArray<CacheAttribute> FIND_BY_GCID = new SparseArray<CacheAttribute>();
private final static SparseArray<CacheAttribute> FIND_BY_OCACODE = new SparseArray<CacheAttribute>();
static {
final HashMap<String, CacheAttribute> mapGcRawNames = new HashMap<String, CacheAttribute>();
for (CacheAttribute attr : values()) {
mapGcRawNames.put(attr.rawName, attr);
- if (attr.gcid != NO_ID) {
- FIND_BY_GCID.put(attr.gcid, attr);
- }
if (attr.ocacode != NO_ID) {
FIND_BY_OCACODE.put(attr.ocacode, attr);
}
@@ -184,10 +180,6 @@ public enum CacheAttribute {
return rawName != null ? FIND_BY_GCRAWNAME.get(rawName) : null;
}
- public static CacheAttribute getByGcId(final int gcid) {
- return FIND_BY_GCID.get(gcid);
- }
-
public static CacheAttribute getByOcACode(final int ocAcode) {
return FIND_BY_OCACODE.get(ocAcode);
}
diff --git a/main/src/cgeo/geocaching/enumerations/CacheListType.java b/main/src/cgeo/geocaching/enumerations/CacheListType.java
index f482d5b..1fce282 100644
--- a/main/src/cgeo/geocaching/enumerations/CacheListType.java
+++ b/main/src/cgeo/geocaching/enumerations/CacheListType.java
@@ -1,23 +1,32 @@
package cgeo.geocaching.enumerations;
+import cgeo.geocaching.loaders.AbstractSearchLoader.CacheListLoaderType;
+
public enum CacheListType {
- OFFLINE(true),
- POCKET(false),
- HISTORY(true),
- NEAREST(false),
- COORDINATE(false),
- KEYWORD(false),
- ADDRESS(false),
- USERNAME(false),
- OWNER(false),
- MAP(false);
+ OFFLINE(true, CacheListLoaderType.OFFLINE),
+ POCKET(false, CacheListLoaderType.POCKET),
+ HISTORY(true, CacheListLoaderType.HISTORY),
+ NEAREST(false, CacheListLoaderType.NEAREST),
+ COORDINATE(false, CacheListLoaderType.COORDINATE),
+ KEYWORD(false, CacheListLoaderType.KEYWORD),
+ ADDRESS(false, CacheListLoaderType.ADDRESS),
+ FINDER(false, CacheListLoaderType.FINDER),
+ OWNER(false, CacheListLoaderType.OWNER),
+ MAP(false, CacheListLoaderType.MAP);
/**
* whether or not this list allows switching to another list
*/
public final boolean canSwitch;
- private CacheListType(final boolean canSwitch) {
+ public final CacheListLoaderType loaderType;
+
+ CacheListType(final boolean canSwitch, final CacheListLoaderType loaderType) {
this.canSwitch = canSwitch;
+ this.loaderType = loaderType;
+ }
+
+ public int getLoaderId() {
+ return loaderType.getLoaderId();
}
}
diff --git a/main/src/cgeo/geocaching/enumerations/CacheSize.java b/main/src/cgeo/geocaching/enumerations/CacheSize.java
index ee42c66..1255455 100644
--- a/main/src/cgeo/geocaching/enumerations/CacheSize.java
+++ b/main/src/cgeo/geocaching/enumerations/CacheSize.java
@@ -12,30 +12,38 @@ import java.util.Map;
* Enum listing cache sizes
*/
public enum CacheSize {
- MICRO("Micro", 1, R.string.cache_size_micro),
- SMALL("Small", 2, R.string.cache_size_small),
- REGULAR("Regular", 3, R.string.cache_size_regular),
- LARGE("Large", 4, R.string.cache_size_large),
- VIRTUAL("Virtual", 0, R.string.cache_size_virtual),
- NOT_CHOSEN("Not chosen", 0, R.string.cache_size_notchosen),
- OTHER("Other", 0, R.string.cache_size_other),
- UNKNOWN("Unknown", 0, R.string.cache_size_unknown); // CacheSize not init. yet
+ NANO("Nano", 0, R.string.cache_size_nano, "nano"), // used by OC only
+ MICRO("Micro", 1, R.string.cache_size_micro, "micro"),
+ SMALL("Small", 2, R.string.cache_size_small, "small"),
+ REGULAR("Regular", 3, R.string.cache_size_regular, "regular"),
+ LARGE("Large", 4, R.string.cache_size_large, "large"),
+ VERY_LARGE("Very large", 5, R.string.cache_size_very_large, "xlarge"), // used by OC only
+ NOT_CHOSEN("Not chosen", 6, R.string.cache_size_notchosen, ""),
+ VIRTUAL("Virtual", 7, R.string.cache_size_virtual, "none"),
+ OTHER("Other", 8, R.string.cache_size_other, "other"),
+ UNKNOWN("Unknown", -1, R.string.cache_size_unknown, ""); // CacheSize not init. yet
public final String id;
public final int comparable;
private final int stringId;
+ /**
+ * lookup for OC JSON requests (the numeric size is deprecated for OC)
+ */
+ private final String ocSize2;
- CacheSize(String id, int comparable, int stringId) {
+ CacheSize(final String id, final int comparable, final int stringId, final String ocSize2) {
this.id = id;
this.comparable = comparable;
this.stringId = stringId;
+ this.ocSize2 = ocSize2;
}
final private static Map<String, CacheSize> FIND_BY_ID;
static {
final HashMap<String, CacheSize> mapping = new HashMap<String, CacheSize>();
- for (CacheSize cs : values()) {
+ for (final CacheSize cs : values()) {
mapping.put(cs.id.toLowerCase(Locale.US), cs);
+ mapping.put(cs.ocSize2.toLowerCase(Locale.US), cs);
}
// add medium as additional string for Regular
mapping.put("medium", CacheSize.REGULAR);
@@ -61,21 +69,21 @@ public enum CacheSize {
/**
* 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);
+ final int numerical = Integer.parseInt(id);
if (numerical != 0) {
- for (CacheSize size : CacheSize.values()) {
+ for (final CacheSize size : CacheSize.values()) {
if (size.comparable == numerical) {
return size;
}
}
}
- } catch (NumberFormatException e) {
+ } catch (final NumberFormatException e) {
// ignore, as this might be a number or not
}
return UNKNOWN;
@@ -85,4 +93,3 @@ public enum CacheSize {
return CgeoApplication.getInstance().getBaseContext().getResources().getString(stringId);
}
}
-
diff --git a/main/src/cgeo/geocaching/enumerations/CacheType.java b/main/src/cgeo/geocaching/enumerations/CacheType.java
index c952ba0..35fe7a1 100644
--- a/main/src/cgeo/geocaching/enumerations/CacheType.java
+++ b/main/src/cgeo/geocaching/enumerations/CacheType.java
@@ -1,8 +1,8 @@
package cgeo.geocaching.enumerations;
+import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.ICache;
import cgeo.geocaching.R;
-import cgeo.geocaching.CgeoApplication;
import java.util.Collections;
import java.util.HashMap;
diff --git a/main/src/cgeo/geocaching/enumerations/LiveMapStrategy.java b/main/src/cgeo/geocaching/enumerations/LiveMapStrategy.java
index 710c3ba..5bcaf4a 100644
--- a/main/src/cgeo/geocaching/enumerations/LiveMapStrategy.java
+++ b/main/src/cgeo/geocaching/enumerations/LiveMapStrategy.java
@@ -1,7 +1,7 @@
package cgeo.geocaching.enumerations;
-import cgeo.geocaching.R;
import cgeo.geocaching.CgeoApplication;
+import cgeo.geocaching.R;
import java.util.EnumSet;
diff --git a/main/src/cgeo/geocaching/enumerations/LogType.java b/main/src/cgeo/geocaching/enumerations/LogType.java
index 543f83d..a63ef4e 100644
--- a/main/src/cgeo/geocaching/enumerations/LogType.java
+++ b/main/src/cgeo/geocaching/enumerations/LogType.java
@@ -19,19 +19,19 @@ public enum LogType {
DIDNT_FIND_IT(3, "3", "didn't find it", "Didn't find it", R.string.log_dnf, R.drawable.mark_red),
NOTE(4, "4", "write note", "Comment", R.string.log_note),
PUBLISH_LISTING(1003, "24", "publish listing", "", R.string.log_published, R.drawable.mark_green_more),
- ENABLE_LISTING(23, "23", "enable listing", "", R.string.log_enabled, R.drawable.mark_green_more),
- ARCHIVE(5, "5", "archive", "", R.string.log_archived, R.drawable.mark_red_more),
+ ENABLE_LISTING(23, "23", "enable listing", "Ready to search", R.string.log_enabled, R.drawable.mark_green_more),
+ ARCHIVE(5, "5", "archive", "Archived", R.string.log_archived, R.drawable.mark_red_more),
UNARCHIVE(12, "12", "unarchive", "", R.string.log_unarchived, R.drawable.mark_green_more),
- TEMP_DISABLE_LISTING(22, "22", "temporarily disable listing", "", R.string.log_disabled, R.drawable.mark_red_more),
+ TEMP_DISABLE_LISTING(22, "22", "temporarily disable listing", "Temporarily unavailable", R.string.log_disabled, R.drawable.mark_red_more),
NEEDS_ARCHIVE(7, "7", "needs archived", "", R.string.log_needs_archived, R.drawable.mark_red),
WILL_ATTEND(9, "9", "will attend", "Will attend", R.string.log_attend),
ATTENDED(10, "10", "attended", "Attended", R.string.log_attended, R.drawable.mark_green),
RETRIEVED_IT(13, "13", "retrieved it", "", R.string.log_retrieved, R.drawable.mark_green_more),
PLACED_IT(14, "14", "placed it", "", R.string.log_placed, R.drawable.mark_green_more),
GRABBED_IT(19, "19", "grabbed it", "", R.string.log_grabbed, R.drawable.mark_green_more),
- NEEDS_MAINTENANCE(45, "45", "needs maintenance", "Comment", R.string.log_maintenance_needed, R.drawable.mark_red),
+ NEEDS_MAINTENANCE(45, "45", "needs maintenance", "Needs maintenance", R.string.log_maintenance_needed, R.drawable.mark_red),
OWNER_MAINTENANCE(46, "46", "owner maintenance", "", R.string.log_maintained, R.drawable.mark_green_more),
- UPDATE_COORDINATES(47, "47", "update coordinates", "", R.string.log_update),
+ UPDATE_COORDINATES(47, "47", "update coordinates", "Moved", R.string.log_update),
DISCOVERED_IT(48, "48", "discovered it", "", R.string.log_discovered, R.drawable.mark_green),
POST_REVIEWER_NOTE(18, "68", "post reviewer note", "", R.string.log_reviewer),
VISIT(1001, "75", "visit", "", R.string.log_tb_visit, R.drawable.mark_green),
@@ -41,6 +41,7 @@ public enum LogType {
MOVE_INVENTORY(70, "70", "unused_inventory", "", R.string.log_moveinventory),
RETRACT(25, "25", "retract listing", "", R.string.log_retractlisting),
MARKED_MISSING(16, "16", "marked missing", "", R.string.log_marked_missing, R.drawable.mark_red),
+ OC_TEAM_COMMENT(83, null, "X1", "OC Team comment", R.string.log_oc_team_comment),
UNKNOWN(0, "unknown", "", "", R.string.err_unknown, R.drawable.mark_red); // LogType not init. yet
public final int id;
@@ -69,7 +70,9 @@ public enum LogType {
final HashMap<String, LogType> mappingPattern = new HashMap<String, LogType>();
final HashMap<String, LogType> mappingType = new HashMap<String, LogType>();
for (LogType lt : values()) {
- mappingPattern.put(lt.iconName, lt);
+ if (lt.iconName != null) {
+ mappingPattern.put(lt.iconName, lt);
+ }
mappingType.put(lt.type, lt);
}
FIND_BY_ICONNAME = Collections.unmodifiableMap(mappingPattern);
diff --git a/main/src/cgeo/geocaching/enumerations/LogTypeTrackable.java b/main/src/cgeo/geocaching/enumerations/LogTypeTrackable.java
index 68a17a5..e008294 100644
--- a/main/src/cgeo/geocaching/enumerations/LogTypeTrackable.java
+++ b/main/src/cgeo/geocaching/enumerations/LogTypeTrackable.java
@@ -1,29 +1,22 @@
package cgeo.geocaching.enumerations;
+import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.R;
public enum LogTypeTrackable {
- DO_NOTHING(0, "", R.string.log_tb_nothing),
- VISITED(1, "_Visited", R.string.log_tb_visit),
- DROPPED_OFF(2, "_DroppedOff", R.string.log_tb_drop);
+ DO_NOTHING("", R.string.log_tb_nothing),
+ VISITED("_Visited", R.string.log_tb_visit),
+ DROPPED_OFF("_DroppedOff", R.string.log_tb_drop);
- final public int id;
final public String action;
- final public int resourceId;
+ final private int resourceId;
- LogTypeTrackable(int id, String action, int resourceId) {
- this.id = id;
+ LogTypeTrackable(String action, int resourceId) {
this.action = action;
this.resourceId = resourceId;
}
- public static LogTypeTrackable findById(int id) {
- for (LogTypeTrackable logType : values()) {
- if (logType.id == id) {
- return logType;
- }
- }
- return DO_NOTHING;
+ public String getLabel() {
+ return CgeoApplication.getInstance().getString(resourceId);
}
-
}
diff --git a/main/src/cgeo/geocaching/enumerations/StatusCode.java b/main/src/cgeo/geocaching/enumerations/StatusCode.java
index e1a1aaa..8bda371 100644
--- a/main/src/cgeo/geocaching/enumerations/StatusCode.java
+++ b/main/src/cgeo/geocaching/enumerations/StatusCode.java
@@ -6,7 +6,6 @@ import android.content.res.Resources;
public enum StatusCode {
- COMMUNICATION_NOT_STARTED(R.string.err_start),
NO_ERROR(R.string.err_none),
LOG_SAVED(R.string.info_log_saved),
LOGIN_PARSE_ERROR(R.string.err_parse),
@@ -24,7 +23,6 @@ public enum StatusCode {
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),
LOGIMAGE_POST_ERROR(R.string.err_logimage_post_failed);
diff --git a/main/src/cgeo/geocaching/export/FieldNotes.java b/main/src/cgeo/geocaching/export/FieldNotes.java
new file mode 100644
index 0000000..11d725a
--- /dev/null
+++ b/main/src/cgeo/geocaching/export/FieldNotes.java
@@ -0,0 +1,65 @@
+package cgeo.geocaching.export;
+
+import cgeo.geocaching.Geocache;
+import cgeo.geocaching.LogEntry;
+import cgeo.geocaching.files.LocalStorage;
+import cgeo.geocaching.utils.FileUtils;
+import cgeo.geocaching.utils.SynchronizedDateFormat;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Field Notes are simple plain text files, but poorly documented. Syntax:<br>
+ * <code>GCxxxxx,yyyy-mm-ddThh:mm:ssZ,Found it,"logtext"</code>
+ */
+class FieldNotes {
+
+ private static final SynchronizedDateFormat FIELD_NOTE_DATE_FORMAT = new SynchronizedDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("UTC"), Locale.US);
+
+ private int size = 0;
+ private final StringBuilder buffer = new StringBuilder();
+
+ void add(final Geocache cache, final LogEntry log) {
+ size++;
+ buffer.append(cache.getGeocode())
+ .append(',')
+ .append(FIELD_NOTE_DATE_FORMAT.format(new Date(log.date)))
+ .append(',')
+ .append(StringUtils.capitalize(log.type.type))
+ .append(",\"")
+ .append(StringUtils.replaceChars(log.log, '"', '\''))
+ .append("\"\n");
+ }
+
+ public String getContent() {
+ return buffer.toString();
+ }
+
+ File writeToDirectory(File exportLocation) {
+ if (!LocalStorage.isExternalStorageAvailable()) {
+ return null;
+ }
+
+ FileUtils.mkdirs(exportLocation);
+
+ final SimpleDateFormat fileNameDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US);
+ final File exportFile = new File(exportLocation.toString() + '/' + fileNameDateFormat.format(new Date()) + ".txt");
+
+ if (!FileUtils.writeFileUTF16(exportFile, getContent())) {
+ return null;
+ }
+
+ return exportFile;
+ }
+
+ public int size() {
+ return size;
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/export/FieldnoteExport.java b/main/src/cgeo/geocaching/export/FieldnoteExport.java
index 4da480a..7d3e07e 100644
--- a/main/src/cgeo/geocaching/export/FieldnoteExport.java
+++ b/main/src/cgeo/geocaching/export/FieldnoteExport.java
@@ -5,21 +5,14 @@ import cgeo.geocaching.Geocache;
import cgeo.geocaching.LogEntry;
import cgeo.geocaching.R;
import cgeo.geocaching.activity.ActivityMixin;
-import cgeo.geocaching.connector.gc.GCLogin;
-import cgeo.geocaching.enumerations.StatusCode;
-import cgeo.geocaching.network.Network;
-import cgeo.geocaching.network.Parameters;
+import cgeo.geocaching.connector.ConnectorFactory;
+import cgeo.geocaching.connector.IConnector;
+import cgeo.geocaching.connector.capability.FieldNotesCapability;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.ui.Formatter;
import cgeo.geocaching.utils.AsyncTaskWithProgress;
-import cgeo.geocaching.utils.FileUtils;
import cgeo.geocaching.utils.Log;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang3.CharEncoding;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.time.FastDateFormat;
-
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -29,29 +22,16 @@ import android.view.ContextThemeWrapper;
import android.view.View;
import android.widget.CheckBox;
-import java.io.BufferedOutputStream;
import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.text.SimpleDateFormat;
-import java.util.Date;
import java.util.List;
-import java.util.Locale;
-import java.util.TimeZone;
/**
- * Exports offline logs in the Groundspeak Field Note format.<br>
- * <br>
+ * Exports offline logs in the Groundspeak Field Note format.
*
- * Field Notes are simple plain text files, but poorly documented. Syntax:<br>
- * <code>GCxxxxx,yyyy-mm-ddThh:mm:ssZ,Found it,"logtext"</code>
*/
class FieldnoteExport extends AbstractExport {
private static final File exportLocation = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/field-notes");
- private static final FastDateFormat fieldNoteDateFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("UTC"), Locale.US);
+ private static int fieldNotesCount = 0;
protected FieldnoteExport() {
super(getString(R.string.export_fieldnotes));
@@ -89,7 +69,7 @@ class FieldnoteExport extends AbstractExport {
builder.setPositiveButton(R.string.export, new DialogInterface.OnClickListener() {
@Override
- public void onClick(DialogInterface dialog, int which) {
+ public void onClick(final DialogInterface dialog, final int which) {
final boolean upload = uploadOption.isChecked();
final boolean onlyNew = onlyNewOption.isChecked();
Settings.setFieldNoteExportUpload(upload);
@@ -121,22 +101,32 @@ class FieldnoteExport extends AbstractExport {
* Upload/export only new logs since last export
*/
public ExportTask(final Activity activity, final boolean upload, final boolean onlyNew) {
- super(activity, getProgressTitle(), getString(R.string.export_fieldnotes_creating));
+ super(activity, getProgressTitle(), getString(R.string.export_fieldnotes_creating), true);
this.activity = activity;
this.upload = upload;
this.onlyNew = onlyNew;
}
@Override
- protected Boolean doInBackgroundInternal(Geocache[] caches) {
- final StringBuilder fieldNoteBuffer = new StringBuilder();
+ protected Boolean doInBackgroundInternal(final Geocache[] caches) {
+ // export field notes separately for each connector, so the file can be uploaded to the respective site afterwards
+ for (IConnector connector : ConnectorFactory.getConnectors()) {
+ if (connector instanceof FieldNotesCapability) {
+ exportFieldNotes((FieldNotesCapability) connector, caches);
+ }
+ }
+ return true;
+ }
+
+ private boolean exportFieldNotes(final FieldNotesCapability connector, Geocache[] caches) {
+ final FieldNotes fieldNotes = new FieldNotes();
try {
int i = 0;
for (final Geocache cache : caches) {
- if (cache.isLogOffline()) {
+ if (ConnectorFactory.getConnector(cache).equals(connector) && cache.isLogOffline()) {
final LogEntry log = DataStore.loadLogOffline(cache.getGeocode());
if (!onlyNew || log.date > Settings.getFieldnoteExportDate()) {
- appendFieldNote(fieldNoteBuffer, cache, log);
+ fieldNotes.add(cache, log);
}
}
publishProgress(++i);
@@ -145,65 +135,16 @@ class FieldnoteExport extends AbstractExport {
Log.e("FieldnoteExport.ExportTask generation", e);
return false;
}
+ fieldNotesCount += fieldNotes.size();
- fieldNoteBuffer.append('\n');
-
- if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- return false;
- }
-
- FileUtils.mkdirs(exportLocation);
-
- final SimpleDateFormat fileNameDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US);
- exportFile = new File(exportLocation.toString() + '/' + fileNameDateFormat.format(new Date()) + ".txt");
-
- Writer fileWriter = null;
- BufferedOutputStream buffer = null;
- try {
- final OutputStream os = new FileOutputStream(exportFile);
- buffer = new BufferedOutputStream(os);
- fileWriter = new OutputStreamWriter(buffer, CharEncoding.UTF_16);
- fileWriter.write(fieldNoteBuffer.toString());
- } catch (final IOException e) {
- Log.e("FieldnoteExport.ExportTask export", e);
+ exportFile = fieldNotes.writeToDirectory(exportLocation);
+ if (exportFile == null) {
return false;
- } finally {
- IOUtils.closeQuietly(fileWriter);
- IOUtils.closeQuietly(buffer);
}
if (upload) {
publishProgress(STATUS_UPLOAD);
-
- if (!GCLogin.getInstance().isActualLoginStatus()) {
- // no need to upload (possibly large file) if we're not logged in
- 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 = GCLogin.getInstance().getRequestLogged(uri, null);
-
- if (StringUtils.isBlank(page)) {
- Log.e("FieldnoteExport.ExportTask get page: No data from server");
- return false;
- }
-
- final String[] viewstates = GCLogin.getViewstates(page);
-
- final Parameters uploadParams = new Parameters(
- "__EVENTTARGET", "",
- "__EVENTARGUMENT", "",
- "ctl00$ContentBody$btnUpload", "Upload Field Note");
-
- GCLogin.putViewstates(uploadParams, viewstates);
-
- Network.getResponseData(Network.postRequest(uri, uploadParams, "ctl00$ContentBody$FieldNoteLoader", "text/plain", exportFile));
-
- if (StringUtils.isBlank(page)) {
- Log.e("FieldnoteExport.ExportTask upload: No data from server");
+ if (!connector.uploadFieldNotes(exportFile)) {
return false;
}
}
@@ -212,7 +153,7 @@ class FieldnoteExport extends AbstractExport {
}
@Override
- protected void onPostExecuteInternal(Boolean result) {
+ protected void onPostExecuteInternal(final Boolean result) {
if (null != activity) {
if (result) {
Settings.setFieldnoteExportDate(System.currentTimeMillis());
@@ -229,25 +170,11 @@ class FieldnoteExport extends AbstractExport {
}
@Override
- protected void onProgressUpdateInternal(int status) {
+ protected void onProgressUpdateInternal(final int status) {
if (null != activity) {
- if (STATUS_UPLOAD == status) {
- setMessage(getString(R.string.export_fieldnotes_uploading));
- } else {
- setMessage(getString(R.string.export_fieldnotes_creating) + " (" + status + ')');
- }
+ setMessage(getString(STATUS_UPLOAD == status ? R.string.export_fieldnotes_uploading : R.string.export_fieldnotes_creating) + " (" + fieldNotesCount + ')');
}
}
}
- static void appendFieldNote(final StringBuilder fieldNoteBuffer, final Geocache cache, final LogEntry log) {
- fieldNoteBuffer.append(cache.getGeocode())
- .append(',')
- .append(fieldNoteDateFormat.format(new Date(log.date)))
- .append(',')
- .append(StringUtils.capitalize(log.type.type))
- .append(",\"")
- .append(StringUtils.replaceChars(log.log, '"', '\''))
- .append("\"\n");
- }
}
diff --git a/main/src/cgeo/geocaching/export/GpxExport.java b/main/src/cgeo/geocaching/export/GpxExport.java
index a2e0f93..08fca0b 100644
--- a/main/src/cgeo/geocaching/export/GpxExport.java
+++ b/main/src/cgeo/geocaching/export/GpxExport.java
@@ -1,8 +1,8 @@
package cgeo.geocaching.export;
+import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.Geocache;
import cgeo.geocaching.R;
-import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.activity.ActivityMixin;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.utils.AsyncTaskWithProgress;
diff --git a/main/src/cgeo/geocaching/export/GpxSerializer.java b/main/src/cgeo/geocaching/export/GpxSerializer.java
index df07f17..962f0d3 100644
--- a/main/src/cgeo/geocaching/export/GpxSerializer.java
+++ b/main/src/cgeo/geocaching/export/GpxSerializer.java
@@ -8,6 +8,7 @@ import cgeo.geocaching.Waypoint;
import cgeo.geocaching.enumerations.CacheAttribute;
import cgeo.geocaching.enumerations.LoadFlags;
import cgeo.geocaching.geopoint.Geopoint;
+import cgeo.geocaching.utils.SynchronizedDateFormat;
import cgeo.geocaching.utils.TextUtils;
import cgeo.geocaching.utils.XmlUtils;
import cgeo.org.kxml2.io.KXmlSerializer;
@@ -15,7 +16,6 @@ import cgeo.org.kxml2.io.KXmlSerializer;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.time.FastDateFormat;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
@@ -29,7 +29,7 @@ import java.util.Set;
public final class GpxSerializer {
- private static final FastDateFormat dateFormatZ = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
+ private static final SynchronizedDateFormat dateFormatZ = new SynchronizedDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
public static final String PREFIX_XSI = "http://www.w3.org/2001/XMLSchema-instance";
public static final String PREFIX_GPX = "http://www.topografix.com/GPX/1/0";
public static final String PREFIX_GROUNDSPEAK = "http://www.groundspeak.com/cache/1/0";
diff --git a/main/src/cgeo/geocaching/files/GPXParser.java b/main/src/cgeo/geocaching/files/GPXParser.java
index 157ea9d..f5380be 100644
--- a/main/src/cgeo/geocaching/files/GPXParser.java
+++ b/main/src/cgeo/geocaching/files/GPXParser.java
@@ -500,52 +500,9 @@ public abstract class GPXParser extends FileParser {
// for GPX 1.1 from extensions node
final Element cacheParent = getCacheParent(waypoint);
- // GSAK extensions
- for (final String gsakNamespace : GSAK_NS) {
- final Element gsak = cacheParent.getChild(gsakNamespace, "wptExtension");
- gsak.getChild(gsakNamespace, "Watch").setEndTextElementListener(new EndTextElementListener() {
+ registerGsakExtensions(cacheParent);
- @Override
- public void end(String watchList) {
- cache.setOnWatchlist(Boolean.valueOf(watchList.trim()));
- }
- });
-
- gsak.getChild(gsakNamespace, "UserData").setEndTextElementListener(new UserDataListener(1));
-
- for (int i = 2; i <= 4; i++) {
- gsak.getChild(gsakNamespace, "User" + i).setEndTextElementListener(new UserDataListener(i));
- }
-
- gsak.getChild(gsakNamespace, "Parent").setEndTextElementListener(new EndTextElementListener() {
-
- @Override
- public void end(String body) {
- parentCacheCode = body;
- }
- });
- }
-
- // c:geo extensions
- final Element cgeoVisited = cacheParent.getChild(CGEO_NS, "visited");
-
- cgeoVisited.setEndTextElementListener(new EndTextElementListener() {
-
- @Override
- public void end(String visited) {
- wptVisited = Boolean.valueOf(visited.trim());
- }
- });
-
- final Element cgeoUserDefined = cacheParent.getChild(CGEO_NS, "userdefined");
-
- cgeoUserDefined.setEndTextElementListener(new EndTextElementListener() {
-
- @Override
- public void end(String userDefined) {
- wptUserDefined = Boolean.valueOf(userDefined.trim());
- }
- });
+ registerCgeoExtensions(cacheParent);
// 3 different versions of the GC schema
for (final String nsGC : GROUNDSPEAK_NAMESPACE) {
@@ -857,6 +814,94 @@ public abstract class GPXParser extends FileParser {
}
/**
+ * Add listeners for GSAK extensions
+ *
+ * @param cacheParent
+ */
+ private void registerGsakExtensions(final Element cacheParent) {
+ for (final String gsakNamespace : GSAK_NS) {
+ final Element gsak = cacheParent.getChild(gsakNamespace, "wptExtension");
+ gsak.getChild(gsakNamespace, "Watch").setEndTextElementListener(new EndTextElementListener() {
+
+ @Override
+ public void end(String watchList) {
+ cache.setOnWatchlist(Boolean.valueOf(watchList.trim()));
+ }
+ });
+
+ gsak.getChild(gsakNamespace, "UserData").setEndTextElementListener(new UserDataListener(1));
+
+ for (int i = 2; i <= 4; i++) {
+ gsak.getChild(gsakNamespace, "User" + i).setEndTextElementListener(new UserDataListener(i));
+ }
+
+ gsak.getChild(gsakNamespace, "Parent").setEndTextElementListener(new EndTextElementListener() {
+
+ @Override
+ public void end(String body) {
+ parentCacheCode = body;
+ }
+ });
+
+ gsak.getChild(gsakNamespace, "FavPoints").setEndTextElementListener(new EndTextElementListener() {
+
+ @Override
+ public void end(String favoritePoints) {
+ try {
+ cache.setFavoritePoints(Integer.parseInt(favoritePoints));
+ }
+ catch (final NumberFormatException e) {
+ Log.w("Failed to parse favorite points", e);
+ }
+ }
+ });
+
+ gsak.getChild(gsakNamespace, "GcNote").setEndTextElementListener(new EndTextElementListener() {
+
+ @Override
+ public void end(final String personalNote) {
+ cache.setPersonalNote(StringUtils.trim(personalNote));
+ }
+ });
+
+ gsak.getChild(gsakNamespace, "IsPremium").setEndTextElementListener(new EndTextElementListener() {
+
+ @Override
+ public void end(final String premium) {
+ cache.setPremiumMembersOnly(Boolean.parseBoolean(premium));
+ }
+ });
+ }
+ }
+
+ /**
+ * Add listeners for c:geo extensions
+ *
+ * @param cacheParent
+ */
+ private void registerCgeoExtensions(final Element cacheParent) {
+ final Element cgeoVisited = cacheParent.getChild(CGEO_NS, "visited");
+
+ cgeoVisited.setEndTextElementListener(new EndTextElementListener() {
+
+ @Override
+ public void end(String visited) {
+ wptVisited = Boolean.valueOf(visited.trim());
+ }
+ });
+
+ final Element cgeoUserDefined = cacheParent.getChild(CGEO_NS, "userdefined");
+
+ cgeoUserDefined.setEndTextElementListener(new EndTextElementListener() {
+
+ @Override
+ public void end(String userDefined) {
+ wptUserDefined = Boolean.valueOf(userDefined.trim());
+ }
+ });
+ }
+
+ /**
* 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.
*
diff --git a/main/src/cgeo/geocaching/files/LocalStorage.java b/main/src/cgeo/geocaching/files/LocalStorage.java
index d57c247..626f6e6 100644
--- a/main/src/cgeo/geocaching/files/LocalStorage.java
+++ b/main/src/cgeo/geocaching/files/LocalStorage.java
@@ -7,7 +7,6 @@ import cgeo.geocaching.utils.Log;
import ch.boye.httpclientandroidlib.Header;
import ch.boye.httpclientandroidlib.HttpResponse;
-
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.Nullable;
@@ -122,7 +121,7 @@ public final class LocalStorage {
* the geocode
* @return the cache directory
*/
- public static File getStorageDir(final String geocode) {
+ public static File getStorageDir(@Nullable final String geocode) {
return storageDir(getStorage(), geocode);
}
@@ -134,11 +133,11 @@ public final class LocalStorage {
* the geocode
* @return the cache directory
*/
- private static File getStorageSecDir(final String geocode) {
+ private static File getStorageSecDir(@Nullable final String geocode) {
return storageDir(getStorageSec(), geocode);
}
- private static File storageDir(final File base, final String geocode) {
+ private static File storageDir(final File base, @Nullable final String geocode) {
return new File(base, StringUtils.defaultIfEmpty(geocode, "_others"));
}
@@ -155,7 +154,7 @@ public final class LocalStorage {
* true if an url was given, false if a file name was given
* @return the file
*/
- public static File getStorageFile(final String geocode, final String fileNameOrUrl, final boolean isUrl, final boolean createDirs) {
+ public static File getStorageFile(@Nullable final String geocode, final String fileNameOrUrl, final boolean isUrl, final boolean createDirs) {
return buildFile(getStorageDir(geocode), fileNameOrUrl, isUrl, createDirs);
}
@@ -276,15 +275,18 @@ public final class LocalStorage {
return false;
}
+
try {
try {
- final FileOutputStream fos = new FileOutputStream(targetFile);
+ final File tempFile = File.createTempFile("download", null, targetFile.getParentFile());
+ final FileOutputStream fos = new FileOutputStream(tempFile);
final boolean written = copy(inputStream, fos);
fos.close();
- if (!written) {
- FileUtils.deleteIgnoringFailure(targetFile);
+ if (written) {
+ return tempFile.renameTo(targetFile);
}
- return written;
+ FileUtils.deleteIgnoringFailure(tempFile);
+ return false;
} finally {
IOUtils.closeQuietly(inputStream);
}
diff --git a/main/src/cgeo/geocaching/files/SimpleDirChooser.java b/main/src/cgeo/geocaching/files/SimpleDirChooser.java
index 67cbb34..1e1296a 100644
--- a/main/src/cgeo/geocaching/files/SimpleDirChooser.java
+++ b/main/src/cgeo/geocaching/files/SimpleDirChooser.java
@@ -88,9 +88,9 @@ public class SimpleDirChooser extends AbstractListActivity {
}
public void editPath() {
- AlertDialog.Builder builder = new AlertDialog.Builder(SimpleDirChooser.this);
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.simple_dir_chooser_current_path);
- final EditText input = new EditText(SimpleDirChooser.this);
+ final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setText(currentDir.getPath());
builder.setView(input);
diff --git a/main/src/cgeo/geocaching/filter/DistanceFilter.java b/main/src/cgeo/geocaching/filter/DistanceFilter.java
index 54225d2..cddcf72 100644
--- a/main/src/cgeo/geocaching/filter/DistanceFilter.java
+++ b/main/src/cgeo/geocaching/filter/DistanceFilter.java
@@ -1,9 +1,9 @@
package cgeo.geocaching.filter;
+import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.Geocache;
import cgeo.geocaching.IGeoData;
import cgeo.geocaching.R;
-import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.geopoint.Geopoint;
import java.util.ArrayList;
diff --git a/main/src/cgeo/geocaching/filter/FilterUserInterface.java b/main/src/cgeo/geocaching/filter/FilterUserInterface.java
index d77341b..b6eaa78 100644
--- a/main/src/cgeo/geocaching/filter/FilterUserInterface.java
+++ b/main/src/cgeo/geocaching/filter/FilterUserInterface.java
@@ -5,7 +5,8 @@ import cgeo.geocaching.R;
import cgeo.geocaching.enumerations.CacheType;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.utils.Log;
-import cgeo.geocaching.utils.RunnableWithArgument;
+
+import rx.functions.Action1;
import android.app.Activity;
import android.app.AlertDialog;
@@ -77,7 +78,7 @@ public final class FilterUserInterface {
registry.add(new FactoryEntry(res.getString(resourceId), factoryClass));
}
- public void selectFilter(final RunnableWithArgument<IFilter> runAfterwards) {
+ public void selectFilter(final Action1<IFilter> runAfterwards) {
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.caches_filter_title);
@@ -89,7 +90,7 @@ public final class FilterUserInterface {
FactoryEntry entry = adapter.getItem(itemIndex);
// reset?
if (entry.filterFactory == null) {
- runAfterwards.run(null);
+ runAfterwards.call(null);
}
else {
try {
@@ -105,10 +106,10 @@ public final class FilterUserInterface {
builder.create().show();
}
- private void selectFromFactory(final IFilterFactory factory, final String menuTitle, final RunnableWithArgument<IFilter> runAfterwards) {
+ private void selectFromFactory(final IFilterFactory factory, final String menuTitle, final Action1<IFilter> runAfterwards) {
final List<IFilter> filters = Collections.unmodifiableList(factory.getFilters());
if (filters.size() == 1) {
- runAfterwards.run(filters.get(0));
+ runAfterwards.call(filters.get(0));
return;
}
@@ -119,7 +120,7 @@ public final class FilterUserInterface {
builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int item) {
- runAfterwards.run(filters.get(item));
+ runAfterwards.call(filters.get(item));
}
});
diff --git a/main/src/cgeo/geocaching/filter/ModifiedFilter.java b/main/src/cgeo/geocaching/filter/ModifiedFilter.java
index d976b69..2ac088a 100644
--- a/main/src/cgeo/geocaching/filter/ModifiedFilter.java
+++ b/main/src/cgeo/geocaching/filter/ModifiedFilter.java
@@ -1,8 +1,8 @@
package cgeo.geocaching.filter;
+import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.Geocache;
import cgeo.geocaching.R;
-import cgeo.geocaching.CgeoApplication;
import java.util.Collections;
import java.util.List;
diff --git a/main/src/cgeo/geocaching/geopoint/Geopoint.java b/main/src/cgeo/geocaching/geopoint/Geopoint.java
index f21df01..e91a93d 100644
--- a/main/src/cgeo/geocaching/geopoint/Geopoint.java
+++ b/main/src/cgeo/geocaching/geopoint/Geopoint.java
@@ -4,6 +4,7 @@ import cgeo.geocaching.ICoordinates;
import cgeo.geocaching.R;
import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
import android.location.Location;
import android.os.Build;
@@ -11,10 +12,13 @@ import android.os.Parcel;
import android.os.Parcelable;
/**
- * Abstraction of geographic point.
+ * Abstraction of geographic point. This class is immutable.
*/
public final class Geopoint implements ICoordinates, Parcelable {
- public static final Geopoint ZERO = new Geopoint(0.0, 0.0);
+ /**
+ * Reusable default object
+ */
+ public static final @NonNull Geopoint ZERO = new Geopoint(0.0, 0.0);
private static final double DEG_TO_RAD = Math.PI / 180;
private static final double RAD_TO_DEG = 180 / Math.PI;
@@ -64,7 +68,6 @@ public final class Geopoint implements ICoordinates, Parcelable {
* longitude string to parse
* @throws Geopoint.ParseException
* if any argument string cannot be parsed
- * @see GeopointParser#parse(String, String)
*/
public Geopoint(final String latText, final String lonText) {
this(GeopointParser.parseLatitude(latText), GeopointParser.parseLongitude(lonText));
@@ -273,19 +276,6 @@ public final class Geopoint implements ICoordinates, Parcelable {
}
/**
- * Checks if given Geopoint is similar to this Geopoint with tolerance.
- *
- * @param gp
- * Geopoint to check
- * @param tolerance
- * tolerance in km
- * @return true if similar, false otherwise
- */
- public boolean isEqualTo(Geopoint gp, double tolerance) {
- return null != gp && distanceTo(gp) <= tolerance;
- }
-
- /**
* Returns formatted coordinates.
*
* @param format
@@ -300,7 +290,7 @@ public final class Geopoint implements ICoordinates, Parcelable {
/**
* Returns formatted coordinates with default format.
* Default format is decimalminutes, e.g. N 52° 36.123 E 010° 03.456
- *
+ *
* @return formatted coordinates
*/
@Override
@@ -365,7 +355,7 @@ public final class Geopoint implements ICoordinates, Parcelable {
/**
* Get longitude character (E or W).
- *
+ *
* @return
*/
public char getLonDir() {
diff --git a/main/src/cgeo/geocaching/geopoint/GeopointParser.java b/main/src/cgeo/geocaching/geopoint/GeopointParser.java
index c043d6f..120e02e 100644
--- a/main/src/cgeo/geocaching/geopoint/GeopointParser.java
+++ b/main/src/cgeo/geocaching/geopoint/GeopointParser.java
@@ -70,37 +70,11 @@ class GeopointParser {
}
/**
- * Parses a pair of coordinates (latitude and longitude) out of a String.
- * Accepts following formats and combinations of it:
- * X DD
- * X DD°
- * X DD° MM
- * X DD° MM.MMM
- * X DD° MM SS
- *
- * as well as:
- * DD.DDDDDDD
- *
- * Both . and , are accepted, also variable count of spaces (also 0)
- *
- * @param latitude
- * the latitude string to parse
- * @param longitude
- * the longitude string to parse
- * @return an Geopoint with parsed latitude and longitude
- * @throws Geopoint.ParseException
- * if lat or lon could not be parsed
- */
- public static Geopoint parse(final String latitude, final String longitude) {
- final double lat = parseLatitude(latitude);
- final double lon = parseLongitude(longitude);
-
- return new Geopoint(lat, lon);
- }
-
- /*
- * (non JavaDoc)
- * Helper for coordinates-parsing.
+ * Helper for coordinates-parsing
+ *
+ * @param text
+ * @param latlon
+ * @return
*/
private static ResultWrapper parseHelper(final String text, final LatLon latlon) {
diff --git a/main/src/cgeo/geocaching/geopoint/Viewport.java b/main/src/cgeo/geocaching/geopoint/Viewport.java
index 9d55f69..ba0e040 100644
--- a/main/src/cgeo/geocaching/geopoint/Viewport.java
+++ b/main/src/cgeo/geocaching/geopoint/Viewport.java
@@ -5,16 +5,15 @@ import cgeo.geocaching.ICoordinates;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
-import java.util.Set;
+import java.util.Collection;
-
-public class Viewport {
+public final class Viewport {
public final @NonNull Geopoint center;
public final @NonNull Geopoint bottomLeft;
public final @NonNull Geopoint topRight;
- public Viewport(final ICoordinates point1, final ICoordinates point2) {
+ public Viewport(final @NonNull ICoordinates point1, final @NonNull ICoordinates point2) {
final Geopoint gp1 = point1.getCoords();
final Geopoint gp2 = point2.getCoords();
this.bottomLeft = new Geopoint(Math.min(gp1.getLatitude(), gp2.getLatitude()),
@@ -25,7 +24,7 @@ public class Viewport {
(gp1.getLongitude() + gp2.getLongitude()) / 2);
}
- public Viewport(final ICoordinates center, final double latSpan, final double lonSpan) {
+ public Viewport(final @NonNull ICoordinates center, final double latSpan, final double lonSpan) {
this.center = center.getCoords();
final double centerLat = this.center.getLatitude();
final double centerLon = this.center.getLongitude();
@@ -71,7 +70,7 @@ public class Viewport {
* the coordinates to check
* @return true if the point is contained in this viewport, false otherwise or if the point contains no coordinates
*/
- public boolean contains(final ICoordinates point) {
+ public boolean contains(final @NonNull ICoordinates point) {
final Geopoint coords = point.getCoords();
return coords != null
&& coords.getLongitudeE6() >= bottomLeft.getLongitudeE6()
@@ -87,12 +86,12 @@ public class Viewport {
/**
* Check whether another viewport is fully included into the current one.
- *
+ *
* @param vp
* the other viewport
- * @return true if the vp is fully included into this one, false otherwise
+ * @return true if the viewport is fully included into this one, false otherwise
*/
- public boolean includes(final Viewport vp) {
+ public boolean includes(final @NonNull Viewport vp) {
return contains(vp.bottomLeft) && contains(vp.topRight);
}
@@ -124,46 +123,37 @@ public class Viewport {
}
/**
- * Return a viewport that contains the current viewport as well as another point.
- *
- * @param point
- * the point we want in the viewport
- * @return either the same or an expanded viewport
- */
- public Viewport expand(final ICoordinates point) {
- if (contains(point)) {
- return this;
- }
-
- final Geopoint coords = point.getCoords();
- final double latitude = coords.getLatitude();
- final double longitude = coords.getLongitude();
- final double latMin = Math.min(getLatitudeMin(), latitude);
- final double latMax = Math.max(getLatitudeMax(), latitude);
- final double lonMin = Math.min(getLongitudeMin(), longitude);
- final double lonMax = Math.max(getLongitudeMax(), longitude);
- return new Viewport(new Geopoint(latMin, lonMin), new Geopoint(latMax, lonMax));
- }
-
- /**
* Return the smallest viewport containing all the given points.
*
* @param points
* a set of points. Point with null coordinates (or null themselves) will be ignored
* @return the smallest viewport containing the non-null coordinates, or null if no coordinates are non-null
*/
- static public Viewport containing(final Set<? extends ICoordinates> points) {
- Viewport viewport = null;
+ static public @Nullable
+ Viewport containing(final Collection<? extends ICoordinates> points) {
+ boolean valid = false;
+ double latMin = Double.MAX_VALUE;
+ double latMax = -Double.MAX_VALUE;
+ double lonMin = Double.MAX_VALUE;
+ double lonMax = -Double.MAX_VALUE;
for (final ICoordinates point : points) {
- if (point != null && point.getCoords() != null) {
- if (viewport == null) {
- viewport = new Viewport(point, point);
- } else {
- viewport = viewport.expand(point);
+ if (point != null) {
+ final Geopoint coords = point.getCoords();
+ if (coords != null) {
+ valid = true;
+ final double latitude = coords.getLatitude();
+ final double longitude = coords.getLongitude();
+ latMin = Math.min(latMin, latitude);
+ latMax = Math.max(latMax, latitude);
+ lonMin = Math.min(lonMin, longitude);
+ lonMax = Math.max(lonMax, longitude);
}
}
}
- return viewport;
+ if (!valid) {
+ return null;
+ }
+ return new Viewport(new Geopoint(latMin, lonMin), new Geopoint(latMax, lonMax));
}
@Override
diff --git a/main/src/cgeo/geocaching/list/PseudoList.java b/main/src/cgeo/geocaching/list/PseudoList.java
index 365d6fd..f2cc7ed 100644
--- a/main/src/cgeo/geocaching/list/PseudoList.java
+++ b/main/src/cgeo/geocaching/list/PseudoList.java
@@ -17,6 +17,12 @@ public class PseudoList extends AbstractList {
*/
public static final AbstractList NEW_LIST = new PseudoList(NEW_LIST_ID, R.string.list_menu_create);
+ private static final int HISTORY_LIST_ID = 4;
+ /**
+ * list entry to create a new list
+ */
+ public static final AbstractList HISTORY_LIST = new PseudoList(HISTORY_LIST_ID, R.string.menu_history);
+
/**
* private constructor to have all instances as constants in the class
*/
diff --git a/main/src/cgeo/geocaching/list/StoredList.java b/main/src/cgeo/geocaching/list/StoredList.java
index 8106073..7e2a83c 100644
--- a/main/src/cgeo/geocaching/list/StoredList.java
+++ b/main/src/cgeo/geocaching/list/StoredList.java
@@ -5,11 +5,12 @@ import cgeo.geocaching.DataStore;
import cgeo.geocaching.R;
import cgeo.geocaching.activity.ActivityMixin;
import cgeo.geocaching.ui.dialog.Dialogs;
-import cgeo.geocaching.utils.RunnableWithArgument;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNull;
+import rx.functions.Action1;
+
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
@@ -67,15 +68,15 @@ public final class StoredList extends AbstractList {
res = app.getResources();
}
- public void promptForListSelection(final int titleId, @NonNull final RunnableWithArgument<Integer> runAfterwards) {
+ public void promptForListSelection(final int titleId, @NonNull final Action1<Integer> runAfterwards) {
promptForListSelection(titleId, runAfterwards, false, -1);
}
- public void promptForListSelection(final int titleId, @NonNull final RunnableWithArgument<Integer> runAfterwards, final boolean onlyConcreteLists, final int exceptListId) {
+ public void promptForListSelection(final int titleId, @NonNull final Action1<Integer> runAfterwards, final boolean onlyConcreteLists, final int exceptListId) {
promptForListSelection(titleId, runAfterwards, onlyConcreteLists, exceptListId, StringUtils.EMPTY);
}
- public void promptForListSelection(final int titleId, @NonNull final RunnableWithArgument<Integer> runAfterwards, final boolean onlyConcreteLists, final int exceptListId, final String newListName) {
+ public void promptForListSelection(final int titleId, @NonNull final Action1<Integer> runAfterwards, final boolean onlyConcreteLists, final int exceptListId, final String newListName) {
final List<AbstractList> lists = new ArrayList<AbstractList>();
lists.addAll(getSortedLists());
@@ -87,7 +88,12 @@ public final class StoredList extends AbstractList {
}
if (!onlyConcreteLists) {
- lists.add(PseudoList.ALL_LIST);
+ if (exceptListId != PseudoList.ALL_LIST.id) {
+ lists.add(PseudoList.ALL_LIST);
+ }
+ if (exceptListId != PseudoList.HISTORY_LIST.id) {
+ lists.add(PseudoList.HISTORY_LIST);
+ }
}
lists.add(PseudoList.NEW_LIST);
@@ -109,7 +115,7 @@ public final class StoredList extends AbstractList {
promptForListCreation(runAfterwards, newListName);
}
else {
- runAfterwards.run(lists.get(itemId).id);
+ runAfterwards.call(lists.get(itemId).id);
}
}
});
@@ -138,17 +144,17 @@ public final class StoredList extends AbstractList {
return lists;
}
- public void promptForListCreation(@NonNull final RunnableWithArgument<Integer> runAfterwards, String newListName) {
- handleListNameInput(newListName, R.string.list_dialog_create_title, R.string.list_dialog_create, new RunnableWithArgument<String>() {
+ public void promptForListCreation(@NonNull final Action1<Integer> runAfterwards, String newListName) {
+ handleListNameInput(newListName, R.string.list_dialog_create_title, R.string.list_dialog_create, new Action1<String>() {
@Override
- public void run(final String listName) {
+ public void call(final String listName) {
final int newId = DataStore.createList(listName);
new StoredList(newId, listName, 0);
if (newId >= DataStore.customListIdOffset) {
ActivityMixin.showToast(activity, res.getString(R.string.list_dialog_create_ok));
- runAfterwards.run(newId);
+ runAfterwards.call(newId);
} else {
ActivityMixin.showToast(activity, res.getString(R.string.list_dialog_create_err));
}
@@ -156,15 +162,15 @@ public final class StoredList extends AbstractList {
});
}
- private void handleListNameInput(final String defaultValue, int dialogTitle, int buttonTitle, final RunnableWithArgument<String> runnable) {
- Dialogs.input(activity, dialogTitle, defaultValue, buttonTitle, new RunnableWithArgument<String>() {
+ private void handleListNameInput(final String defaultValue, int dialogTitle, int buttonTitle, final Action1<String> runnable) {
+ Dialogs.input(activity, dialogTitle, defaultValue, buttonTitle, new Action1<String>() {
@Override
- public void run(final String input) {
+ public void call(final String input) {
// remove whitespaces added by autocompletion of Android keyboard
String listName = StringUtils.trim(input);
if (StringUtils.isNotBlank(listName)) {
- runnable.run(listName);
+ runnable.call(listName);
}
}
});
@@ -172,10 +178,10 @@ public final class StoredList extends AbstractList {
public void promptForListRename(final int listId, @NonNull final Runnable runAfterRename) {
final StoredList list = DataStore.getList(listId);
- handleListNameInput(list.title, R.string.list_dialog_rename_title, R.string.list_dialog_rename, new RunnableWithArgument<String>() {
+ handleListNameInput(list.title, R.string.list_dialog_rename_title, R.string.list_dialog_rename, new Action1<String>() {
@Override
- public void run(final String listName) {
+ public void call(final String listName) {
DataStore.renameList(listId, listName);
runAfterRename.run();
}
@@ -197,7 +203,7 @@ public final class StoredList extends AbstractList {
* Return the given list, if it is a concrete list. Return the default list otherwise.
*/
public static int getConcreteList(int listId) {
- if (listId == PseudoList.ALL_LIST.id || listId == TEMPORARY_LIST_ID) {
+ if (listId == PseudoList.ALL_LIST.id || listId == TEMPORARY_LIST_ID || listId == PseudoList.HISTORY_LIST.id) {
return STANDARD_LIST_ID;
}
return listId;
diff --git a/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java b/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java
index 7524b76..b2cb0b2 100644
--- a/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java
+++ b/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java
@@ -2,6 +2,7 @@ package cgeo.geocaching.loaders;
import cgeo.geocaching.SearchResult;
import cgeo.geocaching.connector.gc.GCConstants;
+import cgeo.geocaching.connector.gc.RecaptchaHandler;
import cgeo.geocaching.network.Network;
import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.utils.Log;
@@ -13,6 +14,8 @@ import android.content.Context;
import android.os.Handler;
import android.support.v4.content.AsyncTaskLoader;
+import java.util.concurrent.CountDownLatch;
+
public abstract class AbstractSearchLoader extends AsyncTaskLoader<SearchResult> implements RecaptchaReceiver {
public enum CacheListLoaderType {
@@ -28,6 +31,10 @@ public abstract class AbstractSearchLoader extends AsyncTaskLoader<SearchResult>
MAP,
REMOVE_FROM_HISTORY,
NEXT_PAGE;
+
+ public int getLoaderId() {
+ return ordinal();
+ }
}
private Handler recaptchaHandler = null;
@@ -36,6 +43,7 @@ public abstract class AbstractSearchLoader extends AsyncTaskLoader<SearchResult>
private String recaptchaText = null;
private SearchResult search;
private boolean loading;
+ private CountDownLatch latch = new CountDownLatch(1);
public AbstractSearchLoader(Context context) {
super(context);
@@ -72,23 +80,21 @@ public abstract class AbstractSearchLoader extends AsyncTaskLoader<SearchResult>
forceLoad();
}
- public void setRecaptchaHandler(Handler recaptchaHandlerIn) {
- recaptchaHandler = recaptchaHandlerIn;
+ public void setRecaptchaHandler(final Handler recaptchaHandler) {
+ this.recaptchaHandler = recaptchaHandler;
}
@Override
public void notifyNeed() {
if (recaptchaHandler != null) {
- recaptchaHandler.sendEmptyMessage(1);
+ recaptchaHandler.sendEmptyMessage(RecaptchaHandler.SHOW_CAPTCHA);
}
}
@Override
- public synchronized void waitForUser() {
+ public void waitForUser() {
try {
- while (getText() == null) {
- wait();
- }
+ latch.await();
} catch (InterruptedException e) {
Log.w("searchThread is not waiting for user…");
}
@@ -100,16 +106,11 @@ public abstract class AbstractSearchLoader extends AsyncTaskLoader<SearchResult>
}
@Override
- public String getKey() {
- return recaptchaKey;
- }
-
- @Override
public void fetchChallenge() {
recaptchaChallenge = null;
if (StringUtils.isNotEmpty(recaptchaKey)) {
- final Parameters params = new Parameters("k", getKey());
+ final Parameters params = new Parameters("k", recaptchaKey);
final String recaptchaJs = Network.getResponseData(Network.getRequest("http://www.google.com/recaptcha/api/challenge", params));
if (StringUtils.isNotBlank(recaptchaJs)) {
@@ -124,18 +125,16 @@ public abstract class AbstractSearchLoader extends AsyncTaskLoader<SearchResult>
}
@Override
- public synchronized void setText(String text) {
+ public void setText(String text) {
recaptchaText = text;
-
- notify();
+ latch.countDown();
}
@Override
- public synchronized String getText() {
+ public String getText() {
return recaptchaText;
}
-
@Override
public void reset() {
super.reset();
diff --git a/main/src/cgeo/geocaching/loaders/AddressGeocacheListLoader.java b/main/src/cgeo/geocaching/loaders/AddressGeocacheListLoader.java
index dd7c7a6..e1573c9 100644
--- a/main/src/cgeo/geocaching/loaders/AddressGeocacheListLoader.java
+++ b/main/src/cgeo/geocaching/loaders/AddressGeocacheListLoader.java
@@ -1,8 +1,8 @@
package cgeo.geocaching.loaders;
import cgeo.geocaching.SearchResult;
-import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.connector.gc.GCParser;
+import cgeo.geocaching.settings.Settings;
import android.content.Context;
diff --git a/main/src/cgeo/geocaching/loaders/HistoryGeocacheListLoader.java b/main/src/cgeo/geocaching/loaders/HistoryGeocacheListLoader.java
index 605f461..fdb35f2 100644
--- a/main/src/cgeo/geocaching/loaders/HistoryGeocacheListLoader.java
+++ b/main/src/cgeo/geocaching/loaders/HistoryGeocacheListLoader.java
@@ -2,9 +2,9 @@ package cgeo.geocaching.loaders;
import cgeo.geocaching.DataStore;
import cgeo.geocaching.SearchResult;
-import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.enumerations.CacheType;
import cgeo.geocaching.geopoint.Geopoint;
+import cgeo.geocaching.settings.Settings;
import android.content.Context;
diff --git a/main/src/cgeo/geocaching/loaders/NextPageGeocacheListLoader.java b/main/src/cgeo/geocaching/loaders/NextPageGeocacheListLoader.java
index 1104f83..05eac18 100644
--- a/main/src/cgeo/geocaching/loaders/NextPageGeocacheListLoader.java
+++ b/main/src/cgeo/geocaching/loaders/NextPageGeocacheListLoader.java
@@ -1,8 +1,8 @@
package cgeo.geocaching.loaders;
import cgeo.geocaching.SearchResult;
-import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.connector.gc.GCParser;
+import cgeo.geocaching.settings.Settings;
import android.content.Context;
diff --git a/main/src/cgeo/geocaching/loaders/OfflineGeocacheListLoader.java b/main/src/cgeo/geocaching/loaders/OfflineGeocacheListLoader.java
index 5088484..b80a1b8 100644
--- a/main/src/cgeo/geocaching/loaders/OfflineGeocacheListLoader.java
+++ b/main/src/cgeo/geocaching/loaders/OfflineGeocacheListLoader.java
@@ -1,9 +1,9 @@
package cgeo.geocaching.loaders;
-import cgeo.geocaching.SearchResult;
-import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.DataStore;
+import cgeo.geocaching.SearchResult;
import cgeo.geocaching.geopoint.Geopoint;
+import cgeo.geocaching.settings.Settings;
import android.content.Context;
diff --git a/main/src/cgeo/geocaching/loaders/RecaptchaReceiver.java b/main/src/cgeo/geocaching/loaders/RecaptchaReceiver.java
index fd5189c..881e048 100644
--- a/main/src/cgeo/geocaching/loaders/RecaptchaReceiver.java
+++ b/main/src/cgeo/geocaching/loaders/RecaptchaReceiver.java
@@ -10,8 +10,6 @@ public interface RecaptchaReceiver {
public void fetchChallenge();
- public String getKey();
-
public void setKey(String key);
public void notifyNeed();
diff --git a/main/src/cgeo/geocaching/maps/CGeoMap.java b/main/src/cgeo/geocaching/maps/CGeoMap.java
index c98ba72..6730ba9 100644
--- a/main/src/cgeo/geocaching/maps/CGeoMap.java
+++ b/main/src/cgeo/geocaching/maps/CGeoMap.java
@@ -38,12 +38,13 @@ import cgeo.geocaching.utils.CancellableHandler;
import cgeo.geocaching.utils.GeoDirHandler;
import cgeo.geocaching.utils.LeastRecentlyUsedSet;
import cgeo.geocaching.utils.Log;
-import cgeo.geocaching.utils.RunnableWithArgument;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.HashCodeBuilder;
+import rx.functions.Action1;
+
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
@@ -289,8 +290,6 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
waitDialog.dismiss();
waitDialog.setOnCancelListener(null);
}
-
- geoDirUpdate.startDir();
}
}
@@ -299,8 +298,6 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
if (loadDetailsThread != null) {
loadDetailsThread.stopIt();
}
-
- geoDirUpdate.startDir();
}
}
@@ -466,7 +463,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
prepareFilterBar();
- if (!app.isLiveMapHintShown() && !Settings.getHideLiveMapHint()) {
+ if (!app.isLiveMapHintShownInThisSession() && !Settings.getHideLiveMapHint() && Settings.getLiveMapHintShowCount() <= 3) {
LiveMapInfoDialogBuilder.create(activity).show();
}
}
@@ -495,7 +492,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
public void onResume() {
super.onResume();
- addGeoDirObservers();
+ geoDirUpdate.startGeoAndDir();
if (!CollectionUtils.isEmpty(dirtyCaches)) {
for (String geocode : dirtyCaches) {
@@ -515,18 +512,10 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
startTimer();
}
- private void addGeoDirObservers() {
- geoDirUpdate.startGeoAndDir();
- }
-
- private void deleteGeoDirObservers() {
- geoDirUpdate.stopGeoAndDir();
- }
-
@Override
public void onPause() {
stopTimer();
- deleteGeoDirObservers();
+ geoDirUpdate.stopGeoAndDir();
savePrefs();
if (mapView != null) {
@@ -661,9 +650,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
if (Settings.getChooseList()) {
// let user select list to store cache in
new StoredList.UserInterface(activity).promptForListSelection(R.string.list_title,
- new RunnableWithArgument<Integer>() {
+ new Action1<Integer>() {
@Override
- public void run(final Integer selectedListId) {
+ public void call(final Integer selectedListId) {
storeCaches(geocodes, selectedListId);
}
}, true, StoredList.TEMPORARY_LIST_ID);
@@ -800,7 +789,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
* @return true if a restart is needed, false otherwise
*/
private boolean changeMapSource(final MapSource mapSource) {
- final boolean restartRequired = !MapProviderFactory.isSameActivity(Settings.getMapSource(), mapSource);
+ final boolean restartRequired = !MapProviderFactory.isSameActivity(MapProviderFactory.getMapSource(currentSourceId), mapSource);
Settings.setMapSource(mapSource);
currentSourceId = mapSource.getNumericalId();
@@ -898,7 +887,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
private long timeLastPositionOverlayCalculation = 0;
@Override
- protected void updateGeoData(final IGeoData geo) {
+ public void updateGeoData(final IGeoData geo) {
if (geo.isPseudoLocation()) {
locationValid = false;
} else {
@@ -994,7 +983,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
public synchronized void startTimer() {
if (coordsIntent != null) {
// display just one point
- (new DisplayPointThread()).start();
+ displayPoint(coordsIntent);
} else {
// start timer
stopTimer();
@@ -1063,7 +1052,6 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
}
}
- yield();
} catch (Exception e) {
Log.w("CGeoMap.LoadTimer.run", e);
}
@@ -1182,7 +1170,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
}
}
}
- final SearchResult searchResult = ConnectorFactory.searchByViewport(viewport.resize(0.8), tokens);
+ final SearchResult searchResult = ConnectorFactory.searchByViewport(viewport.resize(0.8), tokens).toBlockingObservable().single();
downloaded = true;
Set<Geocache> result = searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB);
@@ -1271,32 +1259,16 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
}
}
- /**
- * Thread to display one point. Started on opening if in single mode.
- */
- private class DisplayPointThread extends Thread {
-
- @Override
- public void run() {
- if (mapView == null || caches == null) {
- return;
- }
-
- if (coordsIntent != null) {
- final Waypoint waypoint = new Waypoint("some place", waypointTypeIntent != null ? waypointTypeIntent : WaypointType.WAYPOINT, false);
- waypoint.setCoords(coordsIntent);
+ private void displayPoint(final Geopoint coords) {
+ final Waypoint waypoint = new Waypoint("some place", waypointTypeIntent != null ? waypointTypeIntent : WaypointType.WAYPOINT, false);
+ waypoint.setCoords(coords);
- final CachesOverlayItemImpl item = getWaypointItem(waypoint);
- overlayCaches.updateItems(item);
- displayHandler.sendEmptyMessage(INVALIDATE_MAP);
+ final CachesOverlayItemImpl item = getWaypointItem(waypoint);
+ overlayCaches.updateItems(item);
+ displayHandler.sendEmptyMessage(INVALIDATE_MAP);
+ displayHandler.sendEmptyMessage(UPDATE_TITLE);
- cachesCnt = 1;
- } else {
- cachesCnt = 0;
- }
-
- displayHandler.sendEmptyMessage(UPDATE_TITLE);
- }
+ cachesCnt = 1;
}
private static abstract class DoRunnable implements Runnable {
@@ -1343,8 +1315,6 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
if (loadDetailsThread != null) {
loadDetailsThread.stopIt();
}
-
- geoDirUpdate.startDir();
} catch (Exception e) {
Log.e("CGeoMap.storeCaches.onCancel", e);
}
@@ -1392,8 +1362,6 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
return;
}
- deleteGeoDirObservers();
-
for (final String geocode : geocodes) {
try {
if (handler.isCancelled()) {
@@ -1410,14 +1378,10 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
detailProgress++;
handler.sendEmptyMessage(UPDATE_PROGRESS);
}
-
- // FIXME: what does this yield() do here?
- yield();
}
// we're done
handler.sendEmptyMessage(FINISHED_LOADING_DETAILS);
- addGeoDirObservers();
}
}
@@ -1609,7 +1573,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
}
private CachesOverlayItemImpl getCacheItem(final Geocache cache) {
- final CachesOverlayItemImpl item = mapItemFactory.getCachesOverlayItem(cache, cache.getType().applyDistanceRule());
+ final CachesOverlayItemImpl item = mapItemFactory.getCachesOverlayItem(cache, cache.applyDistanceRule());
final int hashcode = new HashCodeBuilder()
.append(cache.isReliableLatLon())
diff --git a/main/src/cgeo/geocaching/maps/MapProviderFactory.java b/main/src/cgeo/geocaching/maps/MapProviderFactory.java
index 2e43e19..b928a1e 100644
--- a/main/src/cgeo/geocaching/maps/MapProviderFactory.java
+++ b/main/src/cgeo/geocaching/maps/MapProviderFactory.java
@@ -1,7 +1,7 @@
package cgeo.geocaching.maps;
-import cgeo.geocaching.R;
import cgeo.geocaching.CgeoApplication;
+import cgeo.geocaching.R;
import cgeo.geocaching.maps.google.GoogleMapProvider;
import cgeo.geocaching.maps.interfaces.MapProvider;
import cgeo.geocaching.maps.interfaces.MapSource;
diff --git a/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlay.java b/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlay.java
index 3339650..d14c687 100644
--- a/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlay.java
+++ b/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlay.java
@@ -77,11 +77,6 @@ public class GoogleCacheOverlay extends ItemizedOverlay<GoogleCacheOverlayItem>
}
@Override
- public Drawable superBoundCenter(Drawable markerIn) {
- return ItemizedOverlay.boundCenter(markerIn);
- }
-
- @Override
public Drawable superBoundCenterBottom(Drawable marker) {
return ItemizedOverlay.boundCenterBottom(marker);
}
diff --git a/main/src/cgeo/geocaching/maps/google/GoogleMapProvider.java b/main/src/cgeo/geocaching/maps/google/GoogleMapProvider.java
index cb95b2c..38d7d96 100644
--- a/main/src/cgeo/geocaching/maps/google/GoogleMapProvider.java
+++ b/main/src/cgeo/geocaching/maps/google/GoogleMapProvider.java
@@ -1,7 +1,7 @@
package cgeo.geocaching.maps.google;
-import cgeo.geocaching.R;
import cgeo.geocaching.CgeoApplication;
+import cgeo.geocaching.R;
import cgeo.geocaching.maps.AbstractMapProvider;
import cgeo.geocaching.maps.AbstractMapSource;
import cgeo.geocaching.maps.interfaces.MapItemFactory;
diff --git a/main/src/cgeo/geocaching/maps/google/GoogleMapView.java b/main/src/cgeo/geocaching/maps/google/GoogleMapView.java
index d02e3c2..610dbe1 100644
--- a/main/src/cgeo/geocaching/maps/google/GoogleMapView.java
+++ b/main/src/cgeo/geocaching/maps/google/GoogleMapView.java
@@ -11,15 +11,14 @@ import cgeo.geocaching.maps.interfaces.MapControllerImpl;
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.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 org.eclipse.jdt.annotation.NonNull;
import android.app.Activity;
import android.content.Context;
@@ -54,7 +53,7 @@ public class GoogleMapView extends MapView implements MapViewImpl {
}
@Override
- public void draw(Canvas canvas) {
+ public void draw(final Canvas canvas) {
try {
if (getMapZoomLevel() > 22) { // to avoid too close zoom level (mostly on Samsung Galaxy S series)
getController().setZoom(22);
@@ -91,6 +90,7 @@ public class GoogleMapView extends MapView implements MapViewImpl {
}
@Override
+ @NonNull
public GeoPointImpl getMapViewCenter() {
GeoPoint point = getMapCenter();
return new GoogleGeoPoint(point.getLatitudeE6(), point.getLongitudeE6());
@@ -102,11 +102,6 @@ public class GoogleMapView extends MapView implements MapViewImpl {
}
@Override
- public void addOverlay(OverlayImpl ovl) {
- getOverlays().add((Overlay) ovl);
- }
-
- @Override
public void clearOverlays() {
getOverlays().clear();
}
diff --git a/main/src/cgeo/geocaching/maps/interfaces/ItemizedOverlayImpl.java b/main/src/cgeo/geocaching/maps/interfaces/ItemizedOverlayImpl.java
index 90c5b31..ee61f12 100644
--- a/main/src/cgeo/geocaching/maps/interfaces/ItemizedOverlayImpl.java
+++ b/main/src/cgeo/geocaching/maps/interfaces/ItemizedOverlayImpl.java
@@ -18,8 +18,6 @@ public interface ItemizedOverlayImpl extends OverlayImpl {
void superSetLastFocusedItemIndex(int i);
- Drawable superBoundCenter(Drawable markerIn);
-
Drawable superBoundCenterBottom(Drawable marker);
boolean superOnTap(int index);
diff --git a/main/src/cgeo/geocaching/maps/interfaces/MapViewImpl.java b/main/src/cgeo/geocaching/maps/interfaces/MapViewImpl.java
index cb7ddc6..5ae8e15 100644
--- a/main/src/cgeo/geocaching/maps/interfaces/MapViewImpl.java
+++ b/main/src/cgeo/geocaching/maps/interfaces/MapViewImpl.java
@@ -4,6 +4,8 @@ import cgeo.geocaching.geopoint.Viewport;
import cgeo.geocaching.maps.CachesOverlay;
import cgeo.geocaching.maps.PositionAndScaleOverlay;
+import org.eclipse.jdt.annotation.NonNull;
+
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Drawable;
@@ -22,12 +24,11 @@ public interface MapViewImpl {
void clearOverlays();
- void addOverlay(OverlayImpl ovl);
-
MapControllerImpl getMapController();
void destroyDrawingCache();
+ @NonNull
GeoPointImpl getMapViewCenter();
int getLatitudeSpan();
@@ -75,7 +76,7 @@ public interface MapViewImpl {
/**
* Indicates if the current map view supports different themes
* for map rendering
- *
+ *
* @return true - supports custom themes, false - does not support custom themes
*/
boolean hasMapThemes();
diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeCacheOverlay.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeCacheOverlay.java
index 9e14e36..b9e40d7 100644
--- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeCacheOverlay.java
+++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeCacheOverlay.java
@@ -70,11 +70,6 @@ public class MapsforgeCacheOverlay extends ItemizedOverlay<MapsforgeCacheOverlay
}
@Override
- public Drawable superBoundCenter(Drawable markerIn) {
- return ItemizedOverlay.boundCenter(markerIn);
- }
-
- @Override
public Drawable superBoundCenterBottom(Drawable marker) {
return ItemizedOverlay.boundCenterBottom(marker);
}
diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java
index 78aa47d..e993548 100644
--- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java
+++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java
@@ -11,7 +11,6 @@ import cgeo.geocaching.maps.interfaces.MapProjectionImpl;
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.settings.Settings;
import cgeo.geocaching.utils.Log;
@@ -86,11 +85,6 @@ public class MapsforgeMapView extends MapView implements MapViewImpl {
}
@Override
- public void addOverlay(OverlayImpl ovl) {
- getOverlays().add((Overlay) ovl);
- }
-
- @Override
public void clearOverlays() {
getOverlays().clear();
}
@@ -229,7 +223,7 @@ public class MapsforgeMapView extends MapView implements MapViewImpl {
@Override
public void setMapTheme() {
String customRenderTheme = Settings.getCustomRenderThemeFilePath();
- if (!StringUtils.isEmpty(customRenderTheme)) {
+ if (StringUtils.isNotEmpty(customRenderTheme)) {
try {
setRenderTheme(new File(customRenderTheme));
} catch (FileNotFoundException e) {
diff --git a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeCacheOverlay.java b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeCacheOverlay.java
index 30355fd..a8111ed 100644
--- a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeCacheOverlay.java
+++ b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeCacheOverlay.java
@@ -70,11 +70,6 @@ public class MapsforgeCacheOverlay extends ItemizedOverlay<MapsforgeCacheOverlay
}
@Override
- public Drawable superBoundCenter(Drawable markerIn) {
- return ItemizedOverlay.boundCenter(markerIn);
- }
-
- @Override
public Drawable superBoundCenterBottom(Drawable marker) {
return ItemizedOverlay.boundCenterBottom(marker);
}
diff --git a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java
index c741a31..30caed5 100644
--- a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java
+++ b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java
@@ -10,10 +10,10 @@ import cgeo.geocaching.maps.interfaces.MapControllerImpl;
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.settings.Settings;
import cgeo.geocaching.utils.Log;
+import org.eclipse.jdt.annotation.NonNull;
import org.mapsforge.android.mapsold.GeoPoint;
import org.mapsforge.android.mapsold.MapDatabase;
import org.mapsforge.android.mapsold.MapView;
@@ -66,6 +66,7 @@ public class MapsforgeMapView024 extends MapView implements MapViewImpl {
}
@Override
+ @NonNull
public GeoPointImpl getMapViewCenter() {
GeoPoint point = getMapCenter();
return new MapsforgeGeoPoint(point.getLatitudeE6(), point.getLongitudeE6());
@@ -77,11 +78,6 @@ public class MapsforgeMapView024 extends MapView implements MapViewImpl {
}
@Override
- public void addOverlay(OverlayImpl ovl) {
- getOverlays().add((Overlay) ovl);
- }
-
- @Override
public void clearOverlays() {
getOverlays().clear();
}
diff --git a/main/src/cgeo/geocaching/network/HtmlImage.java b/main/src/cgeo/geocaching/network/HtmlImage.java
index 0daa588..524617c 100644
--- a/main/src/cgeo/geocaching/network/HtmlImage.java
+++ b/main/src/cgeo/geocaching/network/HtmlImage.java
@@ -6,14 +6,31 @@ import cgeo.geocaching.compatibility.Compatibility;
import cgeo.geocaching.connector.ConnectorFactory;
import cgeo.geocaching.files.LocalStorage;
import cgeo.geocaching.list.StoredList;
+import cgeo.geocaching.utils.CancellableHandler;
import cgeo.geocaching.utils.FileUtils;
import cgeo.geocaching.utils.ImageUtils;
import cgeo.geocaching.utils.Log;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.androidextra.Base64;
+
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+import rx.Observable;
+import rx.Observable.OnSubscribe;
+import rx.Scheduler;
+import rx.Scheduler.Inner;
+import rx.Subscriber;
+import rx.functions.Action1;
+import rx.functions.Func1;
+import rx.schedulers.Schedulers;
+import rx.subjects.PublishSubject;
+import rx.subscriptions.CompositeSubscription;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -31,9 +48,23 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
public class HtmlImage implements Html.ImageGetter {
+ // This class implements an all-purpose image getter that can also be used as a ImageGetter interface
+ // when displaying caches. An instance mainly has three possible use cases:
+ // - If onlySave is true, getDrawable() will return null immediately and will queue the image retrieval
+ // and saving in the loading subject. Downloads will start in parallel when the blocking
+ // waitForBackgroundLoading() method is called, and they can be cancelled through the given handler.
+ // - If onlySave is false and the instance is called through fetchDrawable(), then an observable for the
+ // given URL will be returned. This observable will emit the local copy of the image if it is present,
+ // regardless of its freshness, then if needed an updated fresher copy after retrieving it from the network.
+ // - If onlySave is false and the instance is used as an ImageGetter, only the final version of the
+ // image will be returned.
+
private static final String[] BLOCKED = new String[] {
"gccounter.de",
"gccounter.com",
@@ -59,105 +90,183 @@ public class HtmlImage implements Html.ImageGetter {
final private boolean returnErrorImage;
final private int listId;
final private boolean onlySave;
- final private BitmapFactory.Options bfOptions;
final private int maxWidth;
final private int maxHeight;
final private Resources resources;
+ // Background loading
+ final private PublishSubject<Observable<String>> loading = PublishSubject.create();
+ final Observable<String> waitForEnd = Observable.merge(loading).publish().refCount();
+ final CompositeSubscription subscription = new CompositeSubscription(waitForEnd.subscribe());
+ final private Scheduler downloadScheduler = Schedulers.executor(new ThreadPoolExecutor(10, 10, 5, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<Runnable>()));
+
public HtmlImage(final String geocode, final boolean returnErrorImage, final int listId, final boolean onlySave) {
this.geocode = geocode;
this.returnErrorImage = returnErrorImage;
this.listId = listId;
this.onlySave = onlySave;
- bfOptions = new BitmapFactory.Options();
- bfOptions.inTempStorage = new byte[16 * 1024];
- bfOptions.inPreferredConfig = Bitmap.Config.RGB_565;
-
Point displaySize = Compatibility.getDisplaySize();
this.maxWidth = displaySize.x - 25;
this.maxHeight = displaySize.y - 25;
this.resources = CgeoApplication.getInstance().getResources();
}
+ @Nullable
@Override
public BitmapDrawable getDrawable(final String url) {
- // Reject empty and counter images URL
+ final Observable<BitmapDrawable> drawable = fetchDrawable(url);
+ if (onlySave) {
+ loading.onNext(drawable.map(new Func1<BitmapDrawable, String>() {
+ @Override
+ public String call(final BitmapDrawable bitmapDrawable) {
+ return url;
+ }
+ }));
+ return null;
+ }
+ return drawable.toBlockingObservable().lastOrDefault(null);
+ }
+
+ // Caches are loaded from disk on Schedulers.computation() to avoid using more threads than processors
+ // on the phone while decoding the image. Downloads happen on downloadScheduler, in parallel with image
+ // decoding.
+ public Observable<BitmapDrawable> fetchDrawable(final String url) {
+
if (StringUtils.isBlank(url) || isCounter(url)) {
- return new BitmapDrawable(resources, getTransparent1x1Image());
+ return Observable.from(getTransparent1x1Image(resources));
}
final boolean shared = url.contains("/images/icons/icon_");
final String pseudoGeocode = shared ? SHARED : geocode;
- Bitmap imagePre = loadImageFromStorage(url, pseudoGeocode, shared);
-
- // Download image and save it to the cache
- if (imagePre == null) {
- final File file = LocalStorage.getStorageFile(pseudoGeocode, url, true, true);
- if (url.startsWith("data:image/")) {
- if (url.contains(";base64,")) {
- // TODO: when we use SDK level 8 or above, we can use the streaming version of the base64
- // Android utilities.
- byte[] decoded = Base64.decode(StringUtils.substringAfter(url, ";base64,"), Base64.DEFAULT);
- OutputStream out = null;
- try {
- out = new FileOutputStream(file);
- out.write(decoded);
- } catch (final IOException e) {
- Log.e("HtmlImage.getDrawable: cannot write file for decoded inline image", e);
- return null;
- } finally {
- IOUtils.closeQuietly(out);
+ return Observable.create(new OnSubscribe<BitmapDrawable>() {
+ @Override
+ public void call(final Subscriber<? super BitmapDrawable> subscriber) {
+ Schedulers.computation().schedule(new Action1<Inner>() {
+ @Override
+ public void call(final Inner inner) {
+ final Pair<BitmapDrawable, Boolean> loaded = loadFromDisk();
+ final BitmapDrawable bitmap = loaded.getLeft();
+ if (loaded.getRight()) {
+ subscriber.onNext(bitmap);
+ subscriber.onCompleted();
+ return;
+ }
+ if (bitmap != null && !onlySave) {
+ subscriber.onNext(bitmap);
+ }
+ downloadScheduler.schedule(new Action1<Inner>() {
+ @Override
+ public void call(final Inner inner) {
+ downloadAndSave(subscriber);
+ }
+ });
+ }
+ });
+ }
+
+ private Pair<BitmapDrawable, Boolean> loadFromDisk() {
+ final Pair<Bitmap, Boolean> loadResult = loadImageFromStorage(url, pseudoGeocode, shared);
+ final Bitmap bitmap = loadResult.getLeft();
+ return new ImmutablePair<BitmapDrawable, Boolean>(bitmap != null ?
+ ImageUtils.scaleBitmapToFitDisplay(bitmap) :
+ null,
+ loadResult.getRight());
+ }
+
+ private void downloadAndSave(final Subscriber<? super BitmapDrawable> subscriber) {
+ final File file = LocalStorage.getStorageFile(pseudoGeocode, url, true, true);
+ if (url.startsWith("data:image/")) {
+ if (url.contains(";base64,")) {
+ saveBase64ToFile(url, file);
+ } else {
+ Log.e("HtmlImage.getDrawable: unable to decode non-base64 inline image");
+ subscriber.onCompleted();
+ return;
}
} else {
- Log.e("HtmlImage.getDrawable: unable to decode non-base64 inline image");
- return null;
+ if (subscription.isUnsubscribed() || downloadOrRefreshCopy(url, file)) {
+ // The existing copy was fresh enough or we were unsubscribed earlier.
+ subscriber.onCompleted();
+ return;
+ }
}
- } else {
- final String absoluteURL = makeAbsoluteURL(url);
-
- if (absoluteURL != null) {
- try {
- final HttpResponse httpResponse = Network.getRequest(absoluteURL, null, file);
- if (httpResponse != null) {
- final int statusCode = httpResponse.getStatusLine().getStatusCode();
- if (statusCode == 200) {
- LocalStorage.saveEntityToFile(httpResponse, file);
- } else if (statusCode == 304) {
- if (!file.setLastModified(System.currentTimeMillis())) {
- makeFreshCopy(file);
- }
+ if (onlySave) {
+ subscriber.onCompleted();
+ } else {
+ Schedulers.computation().schedule(new Action1<Inner>() {
+ @Override
+ public void call(final Inner inner) {
+ final Pair<BitmapDrawable, Boolean> loaded = loadFromDisk();
+ final BitmapDrawable image = loaded.getLeft();
+ if (image != null) {
+ subscriber.onNext(image);
+ } else {
+ subscriber.onNext(returnErrorImage ?
+ new BitmapDrawable(resources, BitmapFactory.decodeResource(resources, R.drawable.image_not_loaded)) :
+ getTransparent1x1Image(resources));
}
+ subscriber.onCompleted();
}
- } catch (Exception e) {
- Log.e("HtmlImage.getDrawable (downloading from web)", e);
- }
+ });
}
}
- }
-
- if (onlySave) {
- return null;
- }
+ });
+ }
- // now load the newly downloaded image
- if (imagePre == null) {
- imagePre = loadImageFromStorage(url, pseudoGeocode, shared);
+ public void waitForBackgroundLoading(@Nullable final CancellableHandler handler) {
+ if (handler != null) {
+ handler.unsubscribeIfCancelled(subscription);
}
+ loading.onCompleted();
+ waitForEnd.toBlockingObservable().lastOrDefault(null);
+ }
- // get image and return
- if (imagePre == null) {
- Log.d("HtmlImage.getDrawable: Failed to obtain image");
+ /**
+ * Download or refresh the copy of <code>url</code> in <code>file</code>.
+ *
+ * @param url the url of the document
+ * @param file the file to save the document in
+ * @return <code>true</code> if the existing file was up-to-date, <code>false</code> otherwise
+ */
+ private boolean downloadOrRefreshCopy(final String url, final File file) {
+ final String absoluteURL = makeAbsoluteURL(url);
- if (returnErrorImage) {
- imagePre = BitmapFactory.decodeResource(resources, R.drawable.image_not_loaded);
- } else {
- imagePre = getTransparent1x1Image();
+ if (absoluteURL != null) {
+ try {
+ final HttpResponse httpResponse = Network.getRequest(absoluteURL, null, file);
+ if (httpResponse != null) {
+ final int statusCode = httpResponse.getStatusLine().getStatusCode();
+ if (statusCode == 200) {
+ LocalStorage.saveEntityToFile(httpResponse, file);
+ } else if (statusCode == 304) {
+ if (!file.setLastModified(System.currentTimeMillis())) {
+ makeFreshCopy(file);
+ }
+ return true;
+ }
+ }
+ } catch (Exception e) {
+ Log.e("HtmlImage.downloadOrRefreshCopy", e);
}
}
+ return false;
+ }
- return imagePre != null ? ImageUtils.scaleBitmapToFitDisplay(imagePre) : null;
+ private static void saveBase64ToFile(final String url, final File file) {
+ // TODO: when we use SDK level 8 or above, we can use the streaming version of the base64
+ // Android utilities.
+ OutputStream out = null;
+ try {
+ out = new FileOutputStream(file);
+ out.write(Base64.decode(StringUtils.substringAfter(url, ";base64,"), Base64.DEFAULT));
+ } catch (final IOException e) {
+ Log.e("HtmlImage.saveBase64ToFile: cannot write file for decoded inline image", e);
+ } finally {
+ IOUtils.closeQuietly(out);
+ }
}
/**
@@ -180,25 +289,35 @@ public class HtmlImage implements Html.ImageGetter {
}
}
- private Bitmap getTransparent1x1Image() {
- return BitmapFactory.decodeResource(resources, R.drawable.image_no_placement);
+ private BitmapDrawable getTransparent1x1Image(final Resources res) {
+ return new BitmapDrawable(res, BitmapFactory.decodeResource(resources, R.drawable.image_no_placement));
}
- private Bitmap loadImageFromStorage(final String url, final String pseudoGeocode, final boolean forceKeep) {
+ /**
+ * Load an image from primary or secondary storage.
+ *
+ * @param url the image URL
+ * @param pseudoGeocode the geocode or the shared name
+ * @param forceKeep keep the image if it is there, without checking its freshness
+ * @return <code>true</code> if the image was there and is fresh enough, <code>false</code> otherwise
+ */
+ @NonNull
+ private Pair<Bitmap, Boolean> loadImageFromStorage(final String url, final String pseudoGeocode, final boolean forceKeep) {
try {
final File file = LocalStorage.getStorageFile(pseudoGeocode, url, true, false);
- final Bitmap image = loadCachedImage(file, forceKeep);
- if (image != null) {
+ final Pair<Bitmap, Boolean> image = loadCachedImage(file, forceKeep);
+ if (image.getRight() || image.getLeft() != null) {
return image;
}
final File fileSec = LocalStorage.getStorageSecFile(pseudoGeocode, url, true);
return loadCachedImage(fileSec, forceKeep);
} catch (Exception e) {
- Log.w("HtmlImage.getDrawable (reading cache)", e);
+ Log.w("HtmlImage.loadImageFromStorage", e);
}
- return null;
+ return new ImmutablePair<Bitmap, Boolean>(null, false);
}
+ @Nullable
private String makeAbsoluteURL(final String url) {
// Check if uri is absolute or not, if not attach the connector hostname
// FIXME: that should also include the scheme
@@ -222,21 +341,39 @@ public class HtmlImage implements Html.ImageGetter {
return null;
}
- private Bitmap loadCachedImage(final File file, final boolean forceKeep) {
+ /**
+ * Load a previously saved image.
+ *
+ * @param file the file on disk
+ * @param forceKeep keep the image if it is there, without checking its freshness
+ * @return a pair with <code>true</code> if the image was there and is fresh enough or <code>false</code> otherwise,
+ * and the image (possibly <code>null</code> if the first component is <code>false</code> and the image
+ * could not be loaded, or if the first component is <code>true</code> and <code>onlySave</code> is also
+ * <code>true</code>)
+ */
+ @NonNull
+ private Pair<Bitmap, Boolean> loadCachedImage(final File file, final boolean forceKeep) {
if (file.exists()) {
- if (listId >= StoredList.STANDARD_LIST_ID || file.lastModified() > (new Date().getTime() - (24 * 60 * 60 * 1000)) || forceKeep) {
- setSampleSize(file);
- final Bitmap image = BitmapFactory.decodeFile(file.getPath(), bfOptions);
- if (image == null) {
- Log.e("Cannot decode bitmap from " + file.getPath());
- }
- return image;
+ final boolean freshEnough = listId >= StoredList.STANDARD_LIST_ID || file.lastModified() > (new Date().getTime() - (24 * 60 * 60 * 1000)) || forceKeep;
+ if (onlySave) {
+ return new ImmutablePair<Bitmap, Boolean>(null, true);
+ }
+ final BitmapFactory.Options bfOptions = new BitmapFactory.Options();
+ bfOptions.inTempStorage = new byte[16 * 1024];
+ bfOptions.inPreferredConfig = Bitmap.Config.RGB_565;
+ setSampleSize(file, bfOptions);
+ final Bitmap image = BitmapFactory.decodeFile(file.getPath(), bfOptions);
+ if (image == null) {
+ Log.e("Cannot decode bitmap from " + file.getPath());
+ return new ImmutablePair<Bitmap, Boolean>(null, false);
}
+ return new ImmutablePair<Bitmap, Boolean>(image,
+ freshEnough);
}
- return null;
+ return new ImmutablePair<Bitmap, Boolean>(null, false);
}
- private void setSampleSize(final File file) {
+ private void setSampleSize(final File file, final BitmapFactory.Options bfOptions) {
//Decode image size only
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
diff --git a/main/src/cgeo/geocaching/network/Network.java b/main/src/cgeo/geocaching/network/Network.java
index e891d3b..d8638db 100644
--- a/main/src/cgeo/geocaching/network/Network.java
+++ b/main/src/cgeo/geocaching/network/Network.java
@@ -5,18 +5,10 @@ import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.TextUtils;
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.HeaderElement;
import ch.boye.httpclientandroidlib.HttpEntity;
-import ch.boye.httpclientandroidlib.HttpException;
-import ch.boye.httpclientandroidlib.HttpRequest;
-import ch.boye.httpclientandroidlib.HttpRequestInterceptor;
import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.HttpResponseInterceptor;
import ch.boye.httpclientandroidlib.NameValuePair;
-import ch.boye.httpclientandroidlib.ProtocolException;
import ch.boye.httpclientandroidlib.client.HttpClient;
-import ch.boye.httpclientandroidlib.client.entity.GzipDecompressingEntity;
import ch.boye.httpclientandroidlib.client.entity.UrlEncodedFormEntity;
import ch.boye.httpclientandroidlib.client.methods.HttpGet;
import ch.boye.httpclientandroidlib.client.methods.HttpPost;
@@ -26,15 +18,14 @@ import ch.boye.httpclientandroidlib.entity.StringEntity;
import ch.boye.httpclientandroidlib.entity.mime.MultipartEntity;
import ch.boye.httpclientandroidlib.entity.mime.content.FileBody;
import ch.boye.httpclientandroidlib.entity.mime.content.StringBody;
+import ch.boye.httpclientandroidlib.impl.client.DecompressingHttpClient;
import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-import ch.boye.httpclientandroidlib.impl.client.DefaultRedirectStrategy;
+import ch.boye.httpclientandroidlib.impl.client.LaxRedirectStrategy;
import ch.boye.httpclientandroidlib.params.BasicHttpParams;
import ch.boye.httpclientandroidlib.params.CoreConnectionPNames;
import ch.boye.httpclientandroidlib.params.CoreProtocolPNames;
import ch.boye.httpclientandroidlib.params.HttpParams;
-import ch.boye.httpclientandroidlib.protocol.HttpContext;
import ch.boye.httpclientandroidlib.util.EntityUtils;
-
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.Nullable;
@@ -48,6 +39,7 @@ import android.net.Uri;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
@@ -86,60 +78,8 @@ public abstract class Network {
final DefaultHttpClient client = new DefaultHttpClient();
client.setCookieStore(Cookies.cookieStore);
client.setParams(clientParams);
-
- client.setRedirectStrategy(new DefaultRedirectStrategy() {
- @Override
- public boolean isRedirected(HttpRequest request, HttpResponse response, HttpContext context) {
- boolean isRedirect = false;
- try {
- isRedirect = super.isRedirected(request, response, context);
- } catch (final ProtocolException e) {
- Log.e("httpclient.isRedirected: unable to check for redirection", e);
- }
- if (!isRedirect) {
- final int responseCode = response.getStatusLine().getStatusCode();
- if (responseCode == 301 || responseCode == 302) {
- return true;
- }
- }
- return isRedirect;
- }
- });
-
- client.addRequestInterceptor(new HttpRequestInterceptor() {
-
- @Override
- public void process(
- final HttpRequest request,
- final HttpContext context) throws HttpException, IOException {
- if (!request.containsHeader("Accept-Encoding")) {
- request.addHeader("Accept-Encoding", "gzip");
- }
- }
- });
- client.addResponseInterceptor(new HttpResponseInterceptor() {
-
- @Override
- public void process(
- final HttpResponse response,
- final HttpContext context) throws HttpException, IOException {
- final HttpEntity entity = response.getEntity();
- if (entity != null) {
- final Header contentEncoding = entity.getContentEncoding();
- if (contentEncoding != null) {
- for (final HeaderElement codec : contentEncoding.getElements()) {
- if (codec.getName().equalsIgnoreCase("gzip")) {
- response.setEntity(new GzipDecompressingEntity(response.getEntity()));
- return;
- }
- }
- }
- }
- }
-
- });
-
- return client;
+ client.setRedirectStrategy(new LaxRedirectStrategy());
+ return new DecompressingHttpClient(client);
}
/**
@@ -426,6 +366,30 @@ public abstract class Network {
return null;
}
+ /**
+ * Get the input stream corresponding to a HTTP response if it exists.
+ *
+ * @param response a HTTP response, which can be null
+ * @return the input stream if the HTTP request is successful, <code>null</code> otherwise
+ */
+ @Nullable
+ public static InputStream getResponseStream(@Nullable final HttpResponse response) {
+ if (!isSuccess(response)) {
+ return null;
+ }
+ assert(response != null);
+ final HttpEntity entity = response.getEntity();
+ if (entity == null) {
+ return null;
+ }
+ try {
+ return entity.getContent();
+ } catch (final IOException e) {
+ Log.e("Network.getResponseStream", e);
+ return null;
+ }
+ }
+
@Nullable
private static String getResponseDataNoError(final HttpResponse response, boolean replaceWhitespace) {
try {
diff --git a/main/src/cgeo/geocaching/network/OAuth.java b/main/src/cgeo/geocaching/network/OAuth.java
index c033660..fa376af 100644
--- a/main/src/cgeo/geocaching/network/OAuth.java
+++ b/main/src/cgeo/geocaching/network/OAuth.java
@@ -37,13 +37,14 @@ public class OAuth {
}
final String keysPacked = consumerSecret + "&" + StringUtils.defaultString(tokenSecret); // both even if empty some of them!
- final String requestPacked = method + "&" + OAuth.percentEncode((https ? "https" : "http") + "://" + host + path) + "&" + OAuth.percentEncode(StringUtils.join(paramsEncoded.toArray(), '&'));
+ final @NonNull String joinedParams = StringUtils.join(paramsEncoded.toArray(), '&');
+ final String requestPacked = method + "&" + OAuth.percentEncode((https ? "https" : "http") + "://" + host + path) + "&" + OAuth.percentEncode(joinedParams);
params.put("oauth_signature", CryptUtils.base64Encode(CryptUtils.hashHmac(requestPacked, keysPacked)));
}
/**
* percent encode following http://tools.ietf.org/html/rfc5849#section-3.6
- *
+ *
* @param url
* @return
*/
diff --git a/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java b/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java
index 888cf77..a5a2383 100644
--- a/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java
+++ b/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java
@@ -7,6 +7,7 @@ import cgeo.geocaching.activity.AbstractActivity;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.MatcherWrapper;
+import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.ParseException;
import ch.boye.httpclientandroidlib.client.entity.UrlEncodedFormEntity;
import ch.boye.httpclientandroidlib.util.EntityUtils;
@@ -34,6 +35,10 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
public static final int NOT_AUTHENTICATED = 0;
public static final int AUTHENTICATED = 1;
+ private static final int STATUS_ERROR = 0;
+ private static final int STATUS_SUCCESS = 1;
+ private static final int STATUS_ERROR_EXT_MSG = 2;
+
@NonNull final private String host;
@NonNull final private String pathRequest;
@NonNull final private String pathAuthorize;
@@ -62,8 +67,13 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
startButton.setOnClickListener(new StartListener());
startButton.setEnabled(true);
- if (msg.what == 1) {
+ if (msg.what == STATUS_SUCCESS) {
startButton.setText(getAuthAgain());
+ } else if (msg.what == STATUS_ERROR_EXT_MSG) {
+ String errMsg = getErrAuthInitialize();
+ errMsg += msg.obj != null ? "\n" + msg.obj.toString() : "";
+ showToast(errMsg);
+ startButton.setText(getAuthStart());
} else {
showToast(getErrAuthInitialize());
startButton.setText(getAuthStart());
@@ -161,37 +171,49 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
params.put("oauth_callback", callback);
final String method = "GET";
OAuth.signOAuth(host, pathRequest, method, https, params, null, null, consumerKey, consumerSecret);
- final String line = Network.getResponseData(Network.getRequest(getUrlPrefix() + host + pathRequest, params));
+ final HttpResponse response = Network.getRequest(getUrlPrefix() + host + pathRequest, params);
- int status = 0;
- if (StringUtils.isNotBlank(line)) {
- assert line != null;
- final MatcherWrapper paramsMatcher1 = new MatcherWrapper(paramsPattern1, line);
- if (paramsMatcher1.find()) {
- OAtoken = paramsMatcher1.group(1);
- }
- final MatcherWrapper paramsMatcher2 = new MatcherWrapper(paramsPattern2, line);
- if (paramsMatcher2.find()) {
- OAtokenSecret = paramsMatcher2.group(1);
- }
+ if (Network.isSuccess(response)) {
+ final String line = Network.getResponseData(response);
- if (StringUtils.isNotBlank(OAtoken) && StringUtils.isNotBlank(OAtokenSecret)) {
- setTempTokens(OAtoken, OAtokenSecret);
- try {
- final Parameters paramsBrowser = new Parameters();
- paramsBrowser.put("oauth_token", OAtoken);
- final String encodedParams = EntityUtils.toString(new UrlEncodedFormEntity(paramsBrowser));
- startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getUrlPrefix() + host + pathAuthorize + "?" + encodedParams)));
- status = 1;
- } catch (ParseException e) {
- Log.e("OAuthAuthorizationActivity.requestToken", e);
- } catch (IOException e) {
- Log.e("OAuthAuthorizationActivity.requestToken", e);
+ int status = STATUS_ERROR;
+ if (StringUtils.isNotBlank(line)) {
+ assert line != null;
+ final MatcherWrapper paramsMatcher1 = new MatcherWrapper(paramsPattern1, line);
+ if (paramsMatcher1.find()) {
+ OAtoken = paramsMatcher1.group(1);
+ }
+ final MatcherWrapper paramsMatcher2 = new MatcherWrapper(paramsPattern2, line);
+ if (paramsMatcher2.find()) {
+ OAtokenSecret = paramsMatcher2.group(1);
+ }
+
+ if (StringUtils.isNotBlank(OAtoken) && StringUtils.isNotBlank(OAtokenSecret)) {
+ setTempTokens(OAtoken, OAtokenSecret);
+ try {
+ final Parameters paramsBrowser = new Parameters();
+ paramsBrowser.put("oauth_token", OAtoken);
+ final String encodedParams = EntityUtils.toString(new UrlEncodedFormEntity(paramsBrowser));
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getUrlPrefix() + host + pathAuthorize + "?" + encodedParams)));
+ status = STATUS_SUCCESS;
+ } catch (ParseException e) {
+ Log.e("OAuthAuthorizationActivity.requestToken", e);
+ } catch (IOException e) {
+ Log.e("OAuthAuthorizationActivity.requestToken", e);
+ }
}
}
- }
- requestTokenHandler.sendEmptyMessage(status);
+ requestTokenHandler.sendEmptyMessage(status);
+ } else {
+ final String extErrMsg = getExtendedErrorMsg(response);
+ if (StringUtils.isNotBlank(extErrMsg)) {
+ final Message msg = requestTokenHandler.obtainMessage(STATUS_ERROR_EXT_MSG, extErrMsg);
+ requestTokenHandler.sendMessage(msg);
+ } else {
+ requestTokenHandler.sendEmptyMessage(STATUS_ERROR);
+ }
+ }
}
private void changeToken(final String verifier) {
@@ -306,6 +328,18 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
return res.getString(R.string.err_auth_process);
}
+ /**
+ * Allows deriving classes to check the response for error messages specific to their OAuth implementation
+ *
+ * @param response
+ * The error response of the token request
+ * @return String with a more detailed error message (user-facing, localized), can be empty
+ */
+ @SuppressWarnings("static-method")
+ protected String getExtendedErrorMsg(HttpResponse response) {
+ return StringUtils.EMPTY;
+ }
+
protected String getAuthDialogWait() {
return res.getString(R.string.auth_dialog_waiting, getAuthTitle());
}
diff --git a/main/src/cgeo/geocaching/network/StatusUpdater.java b/main/src/cgeo/geocaching/network/StatusUpdater.java
index cb4c7f4..4055f01 100644
--- a/main/src/cgeo/geocaching/network/StatusUpdater.java
+++ b/main/src/cgeo/geocaching/network/StatusUpdater.java
@@ -1,21 +1,22 @@
package cgeo.geocaching.network;
import cgeo.geocaching.CgeoApplication;
-import cgeo.geocaching.utils.MemorySubject;
-import cgeo.geocaching.utils.PeriodicHandler;
-import cgeo.geocaching.utils.PeriodicHandler.PeriodicHandlerListener;
import cgeo.geocaching.utils.Version;
import org.json.JSONException;
import org.json.JSONObject;
+import rx.Scheduler;
+import rx.schedulers.Schedulers;
+import rx.subjects.BehaviorSubject;
+import rx.functions.Action1;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
-import android.os.Looper;
import java.util.Locale;
+import java.util.concurrent.TimeUnit;
-public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implements Runnable, PeriodicHandlerListener {
+public class StatusUpdater {
static public class Status {
final public String message;
@@ -30,24 +31,40 @@ public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implement
this.url = url;
}
+ Status(final JSONObject response) {
+ message = get(response, "message");
+ messageId = get(response, "message_id");
+ icon = get(response, "icon");
+ url = get(response, "url");
+ }
+
final static public Status closeoutStatus =
new Status("", "status_closeout_warning", "attribute_abandonedbuilding", "http://faq.cgeo.org/#7_69");
- final static public Status defaultStatus() {
+ final static public Status defaultStatus(final Status upToDate) {
+ if (upToDate != null && upToDate.message != null) {
+ return upToDate;
+ }
return VERSION.SDK_INT < VERSION_CODES.ECLAIR_MR1 ? closeoutStatus : null;
}
}
- @Override
- public void onPeriodic() {
- final JSONObject response =
- Network.requestJSON("http://status.cgeo.org/api/status.json",
- new Parameters("version_code", String.valueOf(Version.getVersionCode(CgeoApplication.getInstance())),
- "version_name", Version.getVersionName(CgeoApplication.getInstance()),
- "locale", Locale.getDefault().toString()));
- if (response != null) {
- notifyObservers(new Status(get(response, "message"), get(response, "message_id"), get(response, "icon"), get(response, "url")));
- }
+ final static public BehaviorSubject<Status> latestStatus = BehaviorSubject.create(Status.defaultStatus(null));
+
+ static {
+ Schedulers.io().schedulePeriodically(new Action1<Scheduler.Inner>() {
+ @Override
+ public void call(final Scheduler.Inner inner) {
+ final JSONObject response =
+ Network.requestJSON("http://status.cgeo.org/api/status.json",
+ new Parameters("version_code", String.valueOf(Version.getVersionCode(CgeoApplication.getInstance())),
+ "version_name", Version.getVersionName(CgeoApplication.getInstance()),
+ "locale", Locale.getDefault().toString()));
+ if (response != null) {
+ latestStatus.onNext(Status.defaultStatus(new Status(response)));
+ }
+ }
+ }, 0, 1800, TimeUnit.SECONDS);
}
private static String get(final JSONObject json, final String key) {
@@ -58,11 +75,4 @@ public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implement
}
}
- @Override
- public void run() {
- Looper.prepare();
- new PeriodicHandler(1800000L, this).start();
- Looper.loop();
- }
-
}
diff --git a/main/src/cgeo/geocaching/search/AutoCompleteAdapter.java b/main/src/cgeo/geocaching/search/AutoCompleteAdapter.java
new file mode 100644
index 0000000..15a45c6
--- /dev/null
+++ b/main/src/cgeo/geocaching/search/AutoCompleteAdapter.java
@@ -0,0 +1,71 @@
+package cgeo.geocaching.search;
+
+import org.apache.commons.lang3.StringUtils;
+
+import rx.functions.Func1;
+
+import android.content.Context;
+import android.widget.ArrayAdapter;
+import android.widget.Filter;
+
+/**
+ * The standard auto completion only matches user input at word boundaries. Therefore searching "est" will not match
+ * "test". This adapter matches everywhere.
+ *
+ */
+public class AutoCompleteAdapter extends ArrayAdapter<String> {
+
+ private String[] suggestions;
+ private final Func1<String, String[]> suggestionFunction;
+
+ public AutoCompleteAdapter(Context context, int textViewResourceId, final Func1<String, String[]> suggestionFunction) {
+ super(context, textViewResourceId);
+ this.suggestionFunction = suggestionFunction;
+ }
+
+ @Override
+ public int getCount() {
+ return suggestions.length;
+ }
+
+ @Override
+ public String getItem(int index) {
+ return suggestions[index];
+ }
+
+ @Override
+ public Filter getFilter() {
+ Filter filter = new Filter() {
+
+ @Override
+ protected FilterResults performFiltering(CharSequence constraint) {
+ FilterResults filterResults = new FilterResults();
+ if (constraint == null) {
+ return filterResults;
+ }
+ String trimmed = StringUtils.trim(constraint.toString());
+ if (StringUtils.length(trimmed) >= 2) {
+ String[] newResults = suggestionFunction.call(trimmed);
+
+ // Assign the data to the FilterResults, but do not yet store in the global member.
+ // Otherwise we might invalidate the adapter and cause an IllegalStateException.
+ filterResults.values = newResults;
+ filterResults.count = newResults.length;
+ }
+ return filterResults;
+ }
+
+ @Override
+ protected void publishResults(CharSequence constraint, FilterResults filterResults) {
+ if (filterResults != null && filterResults.count > 0) {
+ suggestions = (String[]) filterResults.values;
+ notifyDataSetChanged();
+ }
+ else {
+ notifyDataSetInvalidated();
+ }
+ }
+ };
+ return filter;
+ }
+} \ No newline at end of file
diff --git a/main/src/cgeo/geocaching/search/SuggestionProvider.java b/main/src/cgeo/geocaching/search/SuggestionProvider.java
new file mode 100644
index 0000000..c0a7728
--- /dev/null
+++ b/main/src/cgeo/geocaching/search/SuggestionProvider.java
@@ -0,0 +1,57 @@
+package cgeo.geocaching.search;
+
+import cgeo.geocaching.DataStore;
+
+import org.apache.commons.lang3.StringUtils;
+
+import android.app.SearchManager;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class SuggestionProvider extends ContentProvider {
+
+ private static Cursor lastCursor;
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public String getType(final Uri arg0) {
+ return SearchManager.SUGGEST_MIME_TYPE;
+ }
+
+ @Override
+ public Cursor query(final Uri uri, final String[] projection, final String selection, final String[] selectionArgs, final String sortOrder) {
+ final String searchTerm = uri.getLastPathSegment();
+ // can be empty when deleting the query
+ if (StringUtils.equals(searchTerm, SearchManager.SUGGEST_URI_PATH_QUERY)) {
+ return lastCursor;
+ }
+ return getSuggestions(searchTerm);
+ }
+
+ private static Cursor getSuggestions(final String searchTerm) {
+ lastCursor = DataStore.findSuggestions(searchTerm);
+ return lastCursor;
+ }
+
+ @Override
+ public int delete(final Uri uri, final String selection, final String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Uri insert(final Uri uri, final ContentValues values) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int update(final Uri uri, final ContentValues values, final String selection, final String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/settings/AbstractCheckCredentialsPreference.java b/main/src/cgeo/geocaching/settings/AbstractCheckCredentialsPreference.java
index d3aae5c..1efbc96 100644
--- a/main/src/cgeo/geocaching/settings/AbstractCheckCredentialsPreference.java
+++ b/main/src/cgeo/geocaching/settings/AbstractCheckCredentialsPreference.java
@@ -5,28 +5,23 @@ 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 rx.Observable;
+import rx.android.observables.AndroidObservable;
+import rx.functions.Action1;
+import rx.functions.Func0;
+import rx.schedulers.Schedulers;
-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 abstract class AbstractCheckCredentialsPreference extends AbstractClickablePreference {
public AbstractCheckCredentialsPreference(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -37,57 +32,25 @@ public abstract class AbstractCheckCredentialsPreference extends Preference {
}
@Override
- protected View onCreateView(ViewGroup parent) {
- setOnPreferenceClickListener(new LoginCheckClickListener());
- return super.onCreateView(parent);
+ protected OnPreferenceClickListener getOnPreferenceClickListener(final SettingsActivity activity) {
+ return new LoginCheckClickListener(activity);
}
protected abstract ImmutablePair<String, String> getCredentials();
- protected abstract Object login();
+ protected abstract ImmutablePair<StatusCode, Drawable> 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();
- }
+ final private SettingsActivity activity;
- 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();
- }
- }
- };
+ LoginCheckClickListener(final SettingsActivity activity) {
+ this.activity = activity;
+ }
@Override
public boolean onPreferenceClick(Preference preference) {
- this.activity = (SettingsActivity) AbstractCheckCredentialsPreference.this.getContext();
- this.res = activity.getResources();
-
- ImmutablePair<String, String> credentials = getCredentials();
+ final Resources res = activity.getResources();
+ final ImmutablePair<String, String> credentials = getCredentials();
// check credentials for validity
if (StringUtils.isBlank(credentials.getLeft())
@@ -96,19 +59,33 @@ public abstract class AbstractCheckCredentialsPreference extends Preference {
return false;
}
- loginDialog = ProgressDialog.show(activity,
+ final ProgressDialog 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() {
+ AndroidObservable.fromActivity(activity, Observable.defer(new Func0<Observable<ImmutablePair<StatusCode, Drawable>>>() {
@Override
- public void run() {
- Object payload = login();
- logInHandler.obtainMessage(0, payload).sendToTarget();
+ public Observable<ImmutablePair<StatusCode, Drawable>> call() {
+ return Observable.from(login());
+ }
+ }).subscribeOn(Schedulers.io())).subscribe(new Action1<ImmutablePair<StatusCode, Drawable>>() {
+ @Override
+ public void call(final ImmutablePair<StatusCode, Drawable> loginInfo) {
+ loginDialog.dismiss();
+ if (loginInfo.getLeft() == StatusCode.NO_ERROR) {
+ Dialogs.message(activity, R.string.init_login_popup, R.string.init_login_popup_ok, loginInfo.getRight());
+ } else {
+ Dialogs.message(activity, R.string.init_login_popup,
+ res.getString(R.string.init_login_popup_failed_reason)
+ + " "
+ + loginInfo.getLeft().getErrorString(res)
+ + ".");
+ }
+ activity.initBasicMemberPreferences();
}
- }).start();
+ });
return false; // no shared preference has to be changed
}
diff --git a/main/src/cgeo/geocaching/settings/AbstractClickablePreference.java b/main/src/cgeo/geocaching/settings/AbstractClickablePreference.java
new file mode 100644
index 0000000..f4080cd
--- /dev/null
+++ b/main/src/cgeo/geocaching/settings/AbstractClickablePreference.java
@@ -0,0 +1,30 @@
+package cgeo.geocaching.settings;
+
+import android.content.Context;
+import android.preference.Preference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+abstract class AbstractClickablePreference extends Preference {
+
+ final SettingsActivity activity;
+
+ public AbstractClickablePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ activity = (SettingsActivity) context;
+ }
+
+ public AbstractClickablePreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ activity = (SettingsActivity) context;
+ }
+
+ @Override
+ protected View onCreateView(ViewGroup parent) {
+ setOnPreferenceClickListener(getOnPreferenceClickListener(activity));
+ return super.onCreateView(parent);
+ }
+
+ abstract protected OnPreferenceClickListener getOnPreferenceClickListener(final SettingsActivity activity);
+}
diff --git a/main/src/cgeo/geocaching/settings/CheckECCredentialsPreference.java b/main/src/cgeo/geocaching/settings/CheckECCredentialsPreference.java
index 46a3661..c1cf740 100644
--- a/main/src/cgeo/geocaching/settings/CheckECCredentialsPreference.java
+++ b/main/src/cgeo/geocaching/settings/CheckECCredentialsPreference.java
@@ -7,14 +7,11 @@ import cgeo.geocaching.enumerations.StatusCode;
import org.apache.commons.lang3.tuple.ImmutablePair;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
public class CheckECCredentialsPreference extends AbstractCheckCredentialsPreference {
- public CheckECCredentialsPreference(Context context) {
- super(context);
- }
-
public CheckECCredentialsPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -29,12 +26,7 @@ public class CheckECCredentialsPreference extends AbstractCheckCredentialsPrefer
}
@Override
- protected Object login() {
- final StatusCode loginResult = ECLogin.getInstance().login();
- Object payload = loginResult;
- if (loginResult == StatusCode.NO_ERROR) {
- payload = null;
- }
- return payload;
+ protected ImmutablePair<StatusCode, Drawable> login() {
+ return new ImmutablePair<StatusCode, Drawable>(ECLogin.getInstance().login(), null);
}
}
diff --git a/main/src/cgeo/geocaching/settings/CheckGcCredentialsPreference.java b/main/src/cgeo/geocaching/settings/CheckGcCredentialsPreference.java
index 12c8b24..8257fdd 100644
--- a/main/src/cgeo/geocaching/settings/CheckGcCredentialsPreference.java
+++ b/main/src/cgeo/geocaching/settings/CheckGcCredentialsPreference.java
@@ -6,14 +6,11 @@ import cgeo.geocaching.enumerations.StatusCode;
import org.apache.commons.lang3.tuple.ImmutablePair;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
public class CheckGcCredentialsPreference extends AbstractCheckCredentialsPreference {
- public CheckGcCredentialsPreference(Context context) {
- super(context);
- }
-
public CheckGcCredentialsPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -28,13 +25,14 @@ public class CheckGcCredentialsPreference extends AbstractCheckCredentialsPrefer
}
@Override
- protected Object login() {
+ protected ImmutablePair<StatusCode, Drawable> login() {
final StatusCode loginResult = GCLogin.getInstance().login();
- Object payload = loginResult;
- if (loginResult == StatusCode.NO_ERROR) {
- GCLogin.detectGcCustomDate();
- payload = GCLogin.getInstance().downloadAvatarAndGetMemberStatus();
+ switch (loginResult) {
+ case NO_ERROR:
+ GCLogin.detectGcCustomDate();
+ return new ImmutablePair<StatusCode, Drawable>(StatusCode.NO_ERROR, GCLogin.getInstance().downloadAvatarAndGetMemberStatus());
+ default:
+ return new ImmutablePair<StatusCode, Drawable>(loginResult, null);
}
- return payload;
}
}
diff --git a/main/src/cgeo/geocaching/settings/OAuthPreference.java b/main/src/cgeo/geocaching/settings/OAuthPreference.java
index 3550947..df77197 100644
--- a/main/src/cgeo/geocaching/settings/OAuthPreference.java
+++ b/main/src/cgeo/geocaching/settings/OAuthPreference.java
@@ -10,10 +10,8 @@ import android.content.Context;
import android.content.Intent;
import android.preference.Preference;
import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-public class OAuthPreference extends Preference {
+public class OAuthPreference extends AbstractClickablePreference {
private static final int NO_KEY = -1;
@@ -23,8 +21,8 @@ public class OAuthPreference extends Preference {
OCPL(R.string.pref_fakekey_ocpl_authorization, OCPLAuthorizationActivity.class),
TWITTER(R.string.pref_fakekey_twitter_authorization, TwitterAuthorizationActivity.class);
- public int prefKeyId;
- public Class<?> authActivity;
+ public final int prefKeyId;
+ public final Class<?> authActivity;
OAuthActivityMapping(int prefKeyId, Class<?> clazz) {
this.prefKeyId = prefKeyId;
@@ -44,11 +42,6 @@ public class OAuthPreference extends Preference {
return OAuthActivityMapping.NONE;
}
- public OAuthPreference(Context context) {
- super(context);
- this.oAuthMapping = getAuthorization();
- }
-
public OAuthPreference(Context context, AttributeSet attrs) {
super(context, attrs);
this.oAuthMapping = getAuthorization();
@@ -60,10 +53,9 @@ public class OAuthPreference extends Preference {
}
@Override
- protected View onCreateView(ViewGroup parent) {
- final SettingsActivity activity = (SettingsActivity) getContext();
-
- setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ protected OnPreferenceClickListener getOnPreferenceClickListener(final SettingsActivity activity) {
+ activity.setOcAuthTitle(oAuthMapping.prefKeyId);
+ return new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
if (oAuthMapping.authActivity != null) {
@@ -74,9 +66,7 @@ public class OAuthPreference extends Preference {
}
return false; // no shared preference has to be changed
}
- });
+ };
- activity.setOcAuthTitle(oAuthMapping.prefKeyId);
- return super.onCreateView(parent);
}
}
diff --git a/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java b/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java
index 3e838ab..a1ab215 100644
--- a/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java
+++ b/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java
@@ -8,26 +8,19 @@ import cgeo.geocaching.ui.dialog.Dialogs;
import cgeo.geocaching.utils.Log;
import ch.boye.httpclientandroidlib.HttpResponse;
-
import org.apache.commons.lang3.StringUtils;
+import rx.Observable;
+import rx.android.observables.AndroidObservable;
+import rx.functions.Action1;
+import rx.functions.Func0;
+import rx.schedulers.Schedulers;
import android.app.ProgressDialog;
import android.content.Context;
-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 RegisterSend2CgeoPreference extends Preference {
- ProgressDialog progressDialog;
- SettingsActivity activity;
-
- public RegisterSend2CgeoPreference(Context context) {
- super(context);
- }
+public class RegisterSend2CgeoPreference extends AbstractClickablePreference {
public RegisterSend2CgeoPreference(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -37,42 +30,9 @@ public class RegisterSend2CgeoPreference extends Preference {
super(context, attrs, defStyle);
}
- private Handler webAuthHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- // satisfy static code analysis
- if (activity == null) {
- return;
- }
-
- try {
- if (progressDialog != null && progressDialog.isShowing()) {
- progressDialog.dismiss();
- }
-
- if (msg.what > 0) {
- Dialogs.message(activity, R.string.init_sendToCgeo,
- activity.getString(R.string.init_sendToCgeo_register_ok)
- .replace("####", String.valueOf(msg.what)));
- } else {
- 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);
- Log.e("SettingsActivity.webHandler", e);
- }
-
- if (progressDialog != null && progressDialog.isShowing()) {
- progressDialog.dismiss();
- }
- }
- };
-
@Override
- protected View onCreateView(ViewGroup parent) {
- activity = (SettingsActivity) getContext();
-
- setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ protected OnPreferenceClickListener getOnPreferenceClickListener(final SettingsActivity activity) {
+ return new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
// satisfy static code analysis
@@ -88,43 +48,51 @@ public class RegisterSend2CgeoPreference extends Preference {
return false;
}
- progressDialog = ProgressDialog.show(activity,
+ final ProgressDialog progressDialog = ProgressDialog.show(activity,
activity.getString(R.string.init_sendToCgeo),
activity.getString(R.string.init_sendToCgeo_registering), true);
progressDialog.setCancelable(false);
- (new Thread() {
-
- @Override
- public void run() {
- int pin = 0;
-
- final String nam = StringUtils.defaultString(deviceName);
- final String cod = StringUtils.defaultString(deviceCode);
-
- final Parameters params = new Parameters("name", nam, "code", cod);
- HttpResponse response = Network.getRequest("http://send2.cgeo.org/auth.html", params);
-
- if (response != null && response.getStatusLine().getStatusCode() == 200) {
- //response was OK
- String[] strings = StringUtils.split(Network.getResponseData(response), ',');
- try {
- pin = Integer.parseInt(strings[1].trim());
- } catch (Exception e) {
- Log.e("webDialog", e);
+ AndroidObservable.fromActivity(activity,
+ Observable.defer(new Func0<Observable<Integer>>() {
+ @Override
+ public Observable<Integer> call() {
+ final String nam = StringUtils.defaultString(deviceName);
+ final String cod = StringUtils.defaultString(deviceCode);
+
+ final Parameters params = new Parameters("name", nam, "code", cod);
+ HttpResponse response = Network.getRequest("http://send2.cgeo.org/auth.html", params);
+
+ if (response != null && response.getStatusLine().getStatusCode() == 200) {
+ //response was OK
+ final String[] strings = StringUtils.split(Network.getResponseData(response), ',');
+ Settings.setWebNameCode(nam, strings[0]);
+ try {
+ return Observable.from(Integer.parseInt(strings[1].trim()));
+ } catch (final Exception e) {
+ Log.e("RegisterSend2CgeoPreference", e);
+ }
+ }
+
+ return Observable.empty();
}
- String code = strings[0];
- Settings.setWebNameCode(nam, code);
+ }).firstOrDefault(0).subscribeOn(Schedulers.io())).subscribe(new Action1<Integer>() {
+ @Override
+ public void call(final Integer pin) {
+ progressDialog.dismiss();
+ if (pin > 0) {
+ Dialogs.message(activity, R.string.init_sendToCgeo,
+ activity.getString(R.string.init_sendToCgeo_register_ok)
+ .replace("####", String.valueOf(pin)));
+ } else {
+ Dialogs.message(activity, R.string.init_sendToCgeo, R.string.init_sendToCgeo_register_fail);
}
-
- webAuthHandler.sendEmptyMessage(pin);
}
- }).start();
+ });
return true;
}
- });
- return super.onCreateView(parent);
+ };
}
}
diff --git a/main/src/cgeo/geocaching/settings/Settings.java b/main/src/cgeo/geocaching/settings/Settings.java
index 0732866..6c3c984 100644
--- a/main/src/cgeo/geocaching/settings/Settings.java
+++ b/main/src/cgeo/geocaching/settings/Settings.java
@@ -54,8 +54,8 @@ public class Settings {
private final static int unitsMetric = 1;
// twitter api keys
- private final static String keyConsumerPublic = CryptUtils.rot13("ESnsCvAv3kEupF1GCR3jGj");
- private final static String keyConsumerSecret = CryptUtils.rot13("7vQWceACV9umEjJucmlpFe9FCMZSeqIqfkQ2BnhV9x");
+ private final static @NonNull String keyConsumerPublic = CryptUtils.rot13("ESnsCvAv3kEupF1GCR3jGj");
+ private final static @NonNull String keyConsumerSecret = CryptUtils.rot13("7vQWceACV9umEjJucmlpFe9FCMZSeqIqfkQ2BnhV9x");
public enum CoordInputFormatEnum {
Plain,
@@ -313,16 +313,16 @@ public class Settings {
return getBoolean(R.string.pref_connectorOXActive, false);
}
- public static boolean isPremiumMember() {
+ public static boolean isGCPremiumMember() {
// Basic Member, Premium Member, ???
- return GCConstants.MEMBER_STATUS_PM.equalsIgnoreCase(Settings.getMemberStatus());
+ return GCConstants.MEMBER_STATUS_PM.equalsIgnoreCase(Settings.getGCMemberStatus());
}
- public static String getMemberStatus() {
+ public static String getGCMemberStatus() {
return getString(R.string.pref_memberstatus, "");
}
- public static boolean setMemberStatus(final String memberStatus) {
+ public static boolean setGCMemberStatus(final String memberStatus) {
if (StringUtils.isBlank(memberStatus)) {
return remove(R.string.pref_memberstatus);
}
@@ -478,7 +478,7 @@ public class Settings {
}
public static boolean getLoadDirImg() {
- return !isPremiumMember() && getBoolean(R.string.pref_loaddirectionimg, true);
+ return !isGCPremiumMember() && getBoolean(R.string.pref_loaddirectionimg, true);
}
public static void setGcCustomDate(final String format) {
@@ -506,7 +506,7 @@ public class Settings {
}
public static boolean isShowCaptcha() {
- return !isPremiumMember() && getBoolean(R.string.pref_showcaptcha, false);
+ return !isGCPremiumMember() && getBoolean(R.string.pref_showcaptcha, false);
}
public static boolean isExcludeDisabledCaches() {
@@ -694,10 +694,12 @@ public class Settings {
return getBoolean(R.string.pref_skin, false);
}
+ @NonNull
public static String getKeyConsumerPublic() {
return keyConsumerPublic;
}
+ @NonNull
public static String getKeyConsumerSecret() {
return keyConsumerSecret;
}
diff --git a/main/src/cgeo/geocaching/settings/SettingsActivity.java b/main/src/cgeo/geocaching/settings/SettingsActivity.java
index 58acfc1..bcf6715 100644
--- a/main/src/cgeo/geocaching/settings/SettingsActivity.java
+++ b/main/src/cgeo/geocaching/settings/SettingsActivity.java
@@ -1,6 +1,7 @@
package cgeo.geocaching.settings;
import cgeo.geocaching.CgeoApplication;
+import cgeo.geocaching.DataStore;
import cgeo.geocaching.Intents;
import cgeo.geocaching.R;
import cgeo.geocaching.SelectMapfileActivity;
@@ -19,8 +20,10 @@ import cgeo.geocaching.utils.Log;
import org.apache.commons.lang3.StringUtils;
import org.openintents.intents.FileManagerIntents;
+import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -52,8 +55,7 @@ import java.util.Locale;
*/
public class SettingsActivity extends PreferenceActivity {
- private static final String INTENT_GOTO = "GOTO";
- private static final int INTENT_GOTO_SERVICES = 1;
+ private static final String INTENT_OPEN_SCREEN = "OPEN_SCREEN";
/**
* Enumeration for directory choosers. This is how we can retrieve information about the
@@ -89,18 +91,21 @@ public class SettingsActivity extends PreferenceActivity {
initPreferences();
Intent intent = getIntent();
- int gotoPage = intent.getIntExtra(INTENT_GOTO, 0);
- if (gotoPage == INTENT_GOTO_SERVICES) {
- // start with services screen
- PreferenceScreen main = (PreferenceScreen) getPreference(R.string.pref_fakekey_main_screen);
- try {
- if (main != null) {
- int index = getPreference(R.string.pref_fakekey_services_screen).getOrder();
- main.onItemClick(null, null, index, 0);
- }
- } catch (RuntimeException e) {
- Log.e("could not open services preferences", e);
- }
+ openInitialScreen(intent.getIntExtra(INTENT_OPEN_SCREEN, 0));
+ }
+
+ private void openInitialScreen(int initialScreen) {
+ if (initialScreen == 0) {
+ return;
+ }
+ PreferenceScreen screen = (PreferenceScreen) getPreference(initialScreen);
+ if (screen == null) {
+ return;
+ }
+ try {
+ setPreferenceScreen(screen);
+ } catch (RuntimeException e) {
+ Log.e("could not open preferences " + initialScreen, e);
}
}
@@ -121,6 +126,7 @@ public class SettingsActivity extends PreferenceActivity {
initSend2CgeoPreferences();
initServicePreferences();
initNavigationMenuPreferences();
+ initMaintenanceButtons();
for (int k : new int[] { R.string.pref_username, R.string.pref_password,
R.string.pref_pass_vote, R.string.pref_signature,
@@ -141,9 +147,9 @@ public class SettingsActivity extends PreferenceActivity {
getPreference(appEnum.preferenceKey).setEnabled(true);
}
}
- getPreference(R.string.pref_fakekey_basicmembers_screen)
- .setEnabled(!Settings.isPremiumMember());
- redrawScreen(R.string.pref_fakekey_navigation_menu_screen);
+ getPreference(R.string.preference_screen_basicmembers)
+ .setEnabled(!Settings.isGCPremiumMember());
+ redrawScreen(R.string.preference_screen_navigation_menu);
}
private void initServicePreferences() {
@@ -310,6 +316,35 @@ public class SettingsActivity extends PreferenceActivity {
});
}
+ public void initMaintenanceButtons() {
+ Preference dirMaintenance = getPreference(R.string.pref_fakekey_preference_maintenance_directories);
+ dirMaintenance.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(final Preference preference) {
+ // disable the button, as the cleanup runs in background and should not be invoked a second time
+ preference.setEnabled(false);
+
+ Resources res = getResources();
+ final SettingsActivity activity = SettingsActivity.this;
+ final ProgressDialog dialog = ProgressDialog.show(activity, res.getString(R.string.init_maintenance), res.getString(R.string.init_maintenance_directories), true, false);
+ new Thread() {
+ @Override
+ public void run() {
+ DataStore.removeObsoleteCacheDirectories();
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ dialog.dismiss();
+ }
+ });
+ }
+ }.start();
+
+ return true;
+ }
+ });
+ }
+
private void initDbLocationPreference() {
Preference p = getPreference(R.string.pref_dbonsdcard);
p.setPersistent(false);
@@ -336,21 +371,29 @@ public class SettingsActivity extends PreferenceActivity {
}
void initBasicMemberPreferences() {
- getPreference(R.string.pref_fakekey_basicmembers_screen)
- .setEnabled(!Settings.isPremiumMember());
+ getPreference(R.string.preference_screen_basicmembers)
+ .setEnabled(!Settings.isGCPremiumMember());
getPreference(R.string.pref_loaddirectionimg)
- .setEnabled(!Settings.isPremiumMember());
+ .setEnabled(!Settings.isGCPremiumMember());
getPreference(R.string.pref_showcaptcha)
- .setEnabled(!Settings.isPremiumMember());
+ .setEnabled(!Settings.isGCPremiumMember());
- redrawScreen(R.string.pref_fakekey_services_screen);
+ redrawScreen(R.string.preference_screen_services);
}
- void redrawScreen(int key) {
- PreferenceScreen screen = (PreferenceScreen) getPreference(key);
- if (screen == null) {
+ /**
+ * Refresh a preference screen. Has no effect when called for a preference, that is not actually a preference
+ * screen.
+ *
+ * @param key
+ * Key of a preference screen.
+ */
+ void redrawScreen(final int key) {
+ final Preference preference = getPreference(key);
+ if (!(preference instanceof PreferenceScreen)) {
return;
}
+ final PreferenceScreen screen = (PreferenceScreen) preference;
ListAdapter adapter = screen.getRootAdapter();
if (adapter instanceof BaseAdapter) {
((BaseAdapter) adapter).notifyDataSetChanged();
@@ -399,9 +442,9 @@ public class SettingsActivity extends PreferenceActivity {
: R.string.settings_authorize));
}
- public static void jumpToServicesPage(final Context fromActivity) {
+ public static void openForScreen(final int preferenceScreenKey, final Context fromActivity) {
final Intent intent = new Intent(fromActivity, SettingsActivity.class);
- intent.putExtra(INTENT_GOTO, INTENT_GOTO_SERVICES);
+ intent.putExtra(INTENT_OPEN_SCREEN, preferenceScreenKey);
fromActivity.startActivity(intent);
}
@@ -445,15 +488,15 @@ public class SettingsActivity extends PreferenceActivity {
break;
case R.string.pref_fakekey_ocde_authorization:
setOCDEAuthTitle();
- redrawScreen(R.string.pref_fakekey_services_screen);
+ redrawScreen(R.string.preference_screen_ocde);
break;
case R.string.pref_fakekey_ocpl_authorization:
setOCPLAuthTitle();
- redrawScreen(R.string.pref_fakekey_services_screen);
+ redrawScreen(R.string.preference_screen_ocpl);
break;
case R.string.pref_fakekey_twitter_authorization:
setTwitterAuthTitle();
- redrawScreen(R.string.pref_fakekey_services_screen);
+ redrawScreen(R.string.preference_screen_twitter);
break;
default:
throw new IllegalArgumentException();
@@ -582,6 +625,13 @@ public class SettingsActivity extends PreferenceActivity {
preferenceActivity.addPreferencesFromResource(preferencesResId);
}
+ @SuppressWarnings("deprecation")
+ @Override
+ public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
+ // TODO replace with fragment based code
+ super.setPreferenceScreen(preferenceScreen);
+ }
+
private static boolean isPreference(final Preference preference, int preferenceKeyId) {
return getKey(preferenceKeyId).equals(preference.getKey());
}
diff --git a/main/src/cgeo/geocaching/settings/TemplateTextPreference.java b/main/src/cgeo/geocaching/settings/TemplateTextPreference.java
index a703231..667b02b 100644
--- a/main/src/cgeo/geocaching/settings/TemplateTextPreference.java
+++ b/main/src/cgeo/geocaching/settings/TemplateTextPreference.java
@@ -2,6 +2,7 @@ package cgeo.geocaching.settings;
import cgeo.geocaching.R;
import cgeo.geocaching.activity.ActivityMixin;
+import cgeo.geocaching.ui.dialog.Dialogs;
import cgeo.geocaching.utils.LogTemplateProvider;
import cgeo.geocaching.utils.LogTemplateProvider.LogTemplate;
@@ -49,6 +50,7 @@ public class TemplateTextPreference extends DialogPreference {
editText = (EditText) view.findViewById(R.id.signature_dialog_text);
editText.setText(getPersistedString(initialValue != null ? initialValue : StringUtils.EMPTY));
+ Dialogs.moveCursorToEnd(editText);
Button button = (Button) view.findViewById(R.id.signature_templates);
button.setOnClickListener(new View.OnClickListener() {
diff --git a/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java b/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java
index a1c04a4..2b171b4 100644
--- a/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java
+++ b/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java
@@ -3,6 +3,7 @@ package cgeo.geocaching.sorting;
import cgeo.geocaching.Geocache;
import cgeo.geocaching.utils.Log;
+import org.apache.commons.lang3.StringUtils;
/**
* abstract super implementation for all cache comparators
@@ -13,25 +14,35 @@ public abstract class AbstractCacheComparator implements CacheComparator {
@Override
public final int compare(final Geocache cache1, final Geocache cache2) {
try {
- // first check that we have all necessary data for the comparison
- if (!canCompare(cache1, cache2)) {
- return 0;
+ final boolean canCompare1 = canCompare(cache1);
+ final boolean canCompare2 = canCompare(cache2);
+ if (!canCompare1) {
+ return canCompare2 ? 1 : fallbackToGeocode(cache1, cache2);
}
- return compareCaches(cache1, cache2);
- } catch (Exception e) {
+ return canCompare2 ? compareCaches(cache1, cache2) : -1;
+ } catch (final Exception e) {
Log.e("AbstractCacheComparator.compare", e);
+ // This may violate the Comparator interface if the exception is not systematic.
+ return fallbackToGeocode(cache1, cache2);
}
- return 0;
+ }
+
+ private static int fallbackToGeocode(final Geocache cache1, final Geocache cache2) {
+ return StringUtils.defaultString(cache1.getGeocode()).compareToIgnoreCase(StringUtils.defaultString(cache2.getGeocode()));
}
/**
- * Check necessary preconditions (like missing fields) before running the comparison itself
- *
- * @param cache1
- * @param cache2
- * @return
+ * Check necessary preconditions (like missing fields) before running the comparison itself.
+ * Caches not filling the conditions will be placed last, sorted by Geocode.
+ *
+ * The default returns <code>true</code> and can be overridden if needed in child classes.
+ *
+ * @param cache
+ * @return <code>true</code> if the cache holds the necessary data to be compared meaningfully
*/
- protected abstract boolean canCompare(final Geocache cache1, final Geocache cache2);
+ protected boolean canCompare(final Geocache cache) {
+ return true;
+ }
/**
* Compares two caches. Logging and exception handling is implemented outside this method already.
diff --git a/main/src/cgeo/geocaching/sorting/CacheComparator.java b/main/src/cgeo/geocaching/sorting/CacheComparator.java
index 7932729..b06a4b0 100644
--- a/main/src/cgeo/geocaching/sorting/CacheComparator.java
+++ b/main/src/cgeo/geocaching/sorting/CacheComparator.java
@@ -1,9 +1,9 @@
package cgeo.geocaching.sorting;
-import java.util.Comparator;
-
import cgeo.geocaching.Geocache;
+import java.util.Comparator;
+
public interface CacheComparator extends Comparator<Geocache> {
}
diff --git a/main/src/cgeo/geocaching/sorting/ComparatorUserInterface.java b/main/src/cgeo/geocaching/sorting/ComparatorUserInterface.java
index 99a535a..7f10353 100644
--- a/main/src/cgeo/geocaching/sorting/ComparatorUserInterface.java
+++ b/main/src/cgeo/geocaching/sorting/ComparatorUserInterface.java
@@ -2,7 +2,8 @@ package cgeo.geocaching.sorting;
import cgeo.geocaching.R;
import cgeo.geocaching.utils.Log;
-import cgeo.geocaching.utils.RunnableWithArgument;
+
+import rx.functions.Action1;
import android.app.Activity;
import android.app.AlertDialog;
@@ -70,7 +71,7 @@ public class ComparatorUserInterface {
registry.add(new ComparatorEntry(res.getString(resourceId), comparatorClass));
}
- public void selectComparator(final CacheComparator current, final RunnableWithArgument<CacheComparator> runAfterwards) {
+ public void selectComparator(final CacheComparator current, final Action1<CacheComparator> runAfterwards) {
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.caches_sort_title);
@@ -85,11 +86,11 @@ public class ComparatorUserInterface {
ComparatorEntry entry = registry.get(itemIndex);
try {
if (entry.cacheComparator == null) {
- runAfterwards.run(null);
+ runAfterwards.call(null);
}
else {
CacheComparator comparator = entry.cacheComparator.newInstance();
- runAfterwards.run(comparator);
+ runAfterwards.call(comparator);
}
} catch (InstantiationException e) {
Log.e("selectComparator", e);
diff --git a/main/src/cgeo/geocaching/sorting/DateComparator.java b/main/src/cgeo/geocaching/sorting/DateComparator.java
index 091f6a4..9df70f9 100644
--- a/main/src/cgeo/geocaching/sorting/DateComparator.java
+++ b/main/src/cgeo/geocaching/sorting/DateComparator.java
@@ -1,7 +1,7 @@
package cgeo.geocaching.sorting;
-import cgeo.geocaching.Geocache;
import cgeo.geocaching.CgeoApplication;
+import cgeo.geocaching.Geocache;
import java.util.ArrayList;
import java.util.Date;
@@ -12,11 +12,6 @@ import java.util.Date;
public class DateComparator extends AbstractCacheComparator {
@Override
- protected boolean canCompare(Geocache cache1, Geocache cache2) {
- return true;
- }
-
- @Override
protected int compareCaches(Geocache cache1, Geocache cache2) {
final Date date1 = cache1.getHiddenDate();
final Date date2 = cache2.getHiddenDate();
diff --git a/main/src/cgeo/geocaching/sorting/DifficultyComparator.java b/main/src/cgeo/geocaching/sorting/DifficultyComparator.java
index 73d12fa..459f38d 100644
--- a/main/src/cgeo/geocaching/sorting/DifficultyComparator.java
+++ b/main/src/cgeo/geocaching/sorting/DifficultyComparator.java
@@ -9,8 +9,8 @@ import cgeo.geocaching.Geocache;
public class DifficultyComparator extends AbstractCacheComparator {
@Override
- protected boolean canCompare(Geocache cache1, Geocache cache2) {
- return cache1.getDifficulty() != 0.0 && cache2.getDifficulty() != 0.0;
+ protected boolean canCompare(Geocache cache) {
+ return cache.getDifficulty() != 0.0;
}
@Override
diff --git a/main/src/cgeo/geocaching/sorting/DistanceComparator.java b/main/src/cgeo/geocaching/sorting/DistanceComparator.java
index 731e356..541ce48 100644
--- a/main/src/cgeo/geocaching/sorting/DistanceComparator.java
+++ b/main/src/cgeo/geocaching/sorting/DistanceComparator.java
@@ -36,11 +36,6 @@ public class DistanceComparator extends AbstractCacheComparator {
}
@Override
- protected boolean canCompare(Geocache cache1, Geocache cache2) {
- return true;
- }
-
- @Override
protected int compareCaches(final Geocache cache1, final Geocache cache2) {
calculateAllDistances();
final Float distance1 = cache1.getDistance();
diff --git a/main/src/cgeo/geocaching/sorting/FindsComparator.java b/main/src/cgeo/geocaching/sorting/FindsComparator.java
index c889776..7f2ef50 100644
--- a/main/src/cgeo/geocaching/sorting/FindsComparator.java
+++ b/main/src/cgeo/geocaching/sorting/FindsComparator.java
@@ -5,8 +5,8 @@ import cgeo.geocaching.Geocache;
public class FindsComparator extends AbstractCacheComparator {
@Override
- protected boolean canCompare(Geocache cache1, Geocache cache2) {
- return cache1.getLogCounts() != null && cache2.getLogCounts() != null;
+ protected boolean canCompare(Geocache cache) {
+ return cache.getLogCounts() != null;
}
@Override
diff --git a/main/src/cgeo/geocaching/sorting/GeocodeComparator.java b/main/src/cgeo/geocaching/sorting/GeocodeComparator.java
index fff26c6..e700f13 100644
--- a/main/src/cgeo/geocaching/sorting/GeocodeComparator.java
+++ b/main/src/cgeo/geocaching/sorting/GeocodeComparator.java
@@ -2,23 +2,20 @@ package cgeo.geocaching.sorting;
import cgeo.geocaching.Geocache;
-import org.apache.commons.lang3.StringUtils;
-
/**
* sorts caches by geo code, therefore effectively sorting by cache age
- *
+ *
*/
public class GeocodeComparator extends AbstractCacheComparator {
@Override
- protected boolean canCompare(Geocache cache1, Geocache cache2) {
- return StringUtils.isNotBlank(cache1.getGeocode())
- && StringUtils.isNotBlank(cache2.getGeocode());
+ protected boolean canCompare(final Geocache cache) {
+ // This will fall back to geocode comparisons.
+ return false;
}
@Override
protected int compareCaches(final Geocache cache1, final Geocache cache2) {
- final int lengthDiff = cache1.getGeocode().length() - cache2.getGeocode().length();
- return lengthDiff != 0 ? lengthDiff : cache1.getGeocode().compareToIgnoreCase(cache2.getGeocode());
+ throw new RuntimeException("should never be called");
}
}
diff --git a/main/src/cgeo/geocaching/sorting/InventoryComparator.java b/main/src/cgeo/geocaching/sorting/InventoryComparator.java
index 73ea2c5..9d19b64 100644
--- a/main/src/cgeo/geocaching/sorting/InventoryComparator.java
+++ b/main/src/cgeo/geocaching/sorting/InventoryComparator.java
@@ -8,11 +8,6 @@ import cgeo.geocaching.Geocache;
public class InventoryComparator extends AbstractCacheComparator {
@Override
- protected boolean canCompare(final Geocache cache1, final Geocache cache2) {
- return true;
- }
-
- @Override
protected int compareCaches(final Geocache cache1, final Geocache cache2) {
return cache2.getInventoryItems() - cache1.getInventoryItems();
}
diff --git a/main/src/cgeo/geocaching/sorting/NameComparator.java b/main/src/cgeo/geocaching/sorting/NameComparator.java
index b432ad0..2941b1c 100644
--- a/main/src/cgeo/geocaching/sorting/NameComparator.java
+++ b/main/src/cgeo/geocaching/sorting/NameComparator.java
@@ -11,8 +11,8 @@ import org.apache.commons.lang3.StringUtils;
public class NameComparator extends AbstractCacheComparator {
@Override
- protected boolean canCompare(Geocache cache1, Geocache cache2) {
- return StringUtils.isNotBlank(cache1.getName()) && StringUtils.isNotBlank(cache2.getName());
+ protected boolean canCompare(Geocache cache) {
+ return StringUtils.isNotBlank(cache.getName());
}
@Override
diff --git a/main/src/cgeo/geocaching/sorting/PopularityComparator.java b/main/src/cgeo/geocaching/sorting/PopularityComparator.java
index e256654..2dbee68 100644
--- a/main/src/cgeo/geocaching/sorting/PopularityComparator.java
+++ b/main/src/cgeo/geocaching/sorting/PopularityComparator.java
@@ -9,11 +9,6 @@ import cgeo.geocaching.Geocache;
public class PopularityComparator extends AbstractCacheComparator {
@Override
- protected boolean canCompare(final Geocache cache1, final Geocache cache2) {
- return true;
- }
-
- @Override
protected int compareCaches(final Geocache cache1, final Geocache cache2) {
return cache2.getFavoritePoints() - cache1.getFavoritePoints();
}
diff --git a/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java b/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java
index f438762..1ed8e68 100644
--- a/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java
+++ b/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java
@@ -11,11 +11,6 @@ import cgeo.geocaching.Geocache;
public class PopularityRatioComparator extends AbstractCacheComparator {
@Override
- protected boolean canCompare(final Geocache cache1, final Geocache cache2) {
- return true;
- }
-
- @Override
protected int compareCaches(final Geocache cache1, final Geocache cache2) {
float ratio1 = 0.0f;
diff --git a/main/src/cgeo/geocaching/sorting/RatingComparator.java b/main/src/cgeo/geocaching/sorting/RatingComparator.java
index 72cf6c8..6f2c615 100644
--- a/main/src/cgeo/geocaching/sorting/RatingComparator.java
+++ b/main/src/cgeo/geocaching/sorting/RatingComparator.java
@@ -9,11 +9,6 @@ import cgeo.geocaching.Geocache;
public class RatingComparator extends AbstractCacheComparator {
@Override
- protected boolean canCompare(final Geocache cache1, final Geocache cache2) {
- return true;
- }
-
- @Override
protected int compareCaches(final Geocache cache1, final Geocache cache2) {
final float rating1 = cache1.getRating();
final float rating2 = cache2.getRating();
diff --git a/main/src/cgeo/geocaching/sorting/SizeComparator.java b/main/src/cgeo/geocaching/sorting/SizeComparator.java
index d128822..c8de586 100644
--- a/main/src/cgeo/geocaching/sorting/SizeComparator.java
+++ b/main/src/cgeo/geocaching/sorting/SizeComparator.java
@@ -9,8 +9,8 @@ import cgeo.geocaching.Geocache;
public class SizeComparator extends AbstractCacheComparator {
@Override
- protected boolean canCompare(Geocache cache1, Geocache cache2) {
- return cache1.getSize() != null && cache2.getSize() != null;
+ protected boolean canCompare(Geocache cache) {
+ return cache.getSize() != null;
}
@Override
diff --git a/main/src/cgeo/geocaching/sorting/StateComparator.java b/main/src/cgeo/geocaching/sorting/StateComparator.java
index b99c3c0..9488bd9 100644
--- a/main/src/cgeo/geocaching/sorting/StateComparator.java
+++ b/main/src/cgeo/geocaching/sorting/StateComparator.java
@@ -9,11 +9,6 @@ import cgeo.geocaching.Geocache;
public class StateComparator extends AbstractCacheComparator {
@Override
- protected boolean canCompare(final Geocache cache1, final Geocache cache2) {
- return true;
- }
-
- @Override
protected int compareCaches(final Geocache cache1, final Geocache cache2) {
return getState(cache1) - getState(cache2);
}
diff --git a/main/src/cgeo/geocaching/sorting/StorageTimeComparator.java b/main/src/cgeo/geocaching/sorting/StorageTimeComparator.java
index 78ba742..b718d3b 100644
--- a/main/src/cgeo/geocaching/sorting/StorageTimeComparator.java
+++ b/main/src/cgeo/geocaching/sorting/StorageTimeComparator.java
@@ -5,11 +5,6 @@ import cgeo.geocaching.Geocache;
public class StorageTimeComparator extends AbstractCacheComparator {
@Override
- protected boolean canCompare(Geocache cache1, Geocache cache2) {
- return true;
- }
-
- @Override
protected int compareCaches(Geocache cache1, Geocache cache2) {
if (cache1.getUpdated() < cache2.getUpdated()) {
return -1;
diff --git a/main/src/cgeo/geocaching/sorting/TerrainComparator.java b/main/src/cgeo/geocaching/sorting/TerrainComparator.java
index be1e9bb..9bbb5f7 100644
--- a/main/src/cgeo/geocaching/sorting/TerrainComparator.java
+++ b/main/src/cgeo/geocaching/sorting/TerrainComparator.java
@@ -9,8 +9,8 @@ import cgeo.geocaching.Geocache;
public class TerrainComparator extends AbstractCacheComparator {
@Override
- protected boolean canCompare(final Geocache cache1, final Geocache cache2) {
- return cache1.getTerrain() != 0.0 && cache2.getTerrain() != 0.0;
+ protected boolean canCompare(final Geocache cache) {
+ return cache.getTerrain() != 0.0;
}
@Override
diff --git a/main/src/cgeo/geocaching/sorting/VisitComparator.java b/main/src/cgeo/geocaching/sorting/VisitComparator.java
index 27d3170..1589a4c 100644
--- a/main/src/cgeo/geocaching/sorting/VisitComparator.java
+++ b/main/src/cgeo/geocaching/sorting/VisitComparator.java
@@ -9,11 +9,6 @@ import cgeo.geocaching.Geocache;
public class VisitComparator extends AbstractCacheComparator {
@Override
- protected boolean canCompare(final Geocache cache1, final Geocache cache2) {
- return true;
- }
-
- @Override
protected int compareCaches(final Geocache cache1, final Geocache cache2) {
return Long.valueOf(cache2.getVisitedDate()).compareTo(cache1.getVisitedDate());
}
diff --git a/main/src/cgeo/geocaching/sorting/VoteComparator.java b/main/src/cgeo/geocaching/sorting/VoteComparator.java
index dc0304b..cd4ad7e 100644
--- a/main/src/cgeo/geocaching/sorting/VoteComparator.java
+++ b/main/src/cgeo/geocaching/sorting/VoteComparator.java
@@ -8,11 +8,6 @@ import cgeo.geocaching.Geocache;
public class VoteComparator extends AbstractCacheComparator {
@Override
- protected boolean canCompare(Geocache cache1, Geocache cache2) {
- return true;
- }
-
- @Override
protected int compareCaches(Geocache cache1, Geocache cache2) {
// if there is no vote available, put that cache at the end of the list
return Float.compare(cache2.getMyVote(), cache1.getMyVote());
diff --git a/main/src/cgeo/geocaching/speech/SpeechService.java b/main/src/cgeo/geocaching/speech/SpeechService.java
index 2a72bbf..8c650c3 100644
--- a/main/src/cgeo/geocaching/speech/SpeechService.java
+++ b/main/src/cgeo/geocaching/speech/SpeechService.java
@@ -47,7 +47,7 @@ public class SpeechService extends Service implements OnInitListener {
GeoDirHandler geoHandler = new GeoDirHandler() {
@Override
- protected void updateDirection(float newDirection) {
+ public void updateDirection(float newDirection) {
if (CgeoApplication.getInstance().currentGeo().getSpeed() <= 5) {
direction = DirectionProvider.getDirectionNow(startingActivity, newDirection);
directionInitialized = true;
@@ -56,7 +56,7 @@ public class SpeechService extends Service implements OnInitListener {
}
@Override
- protected void updateGeoData(cgeo.geocaching.IGeoData newGeo) {
+ public void updateGeoData(cgeo.geocaching.IGeoData newGeo) {
position = newGeo.getCoords();
positionInitialized = true;
if (!Settings.isUseCompass() || newGeo.getSpeed() > 5) {
diff --git a/main/src/cgeo/geocaching/speech/TextFactory.java b/main/src/cgeo/geocaching/speech/TextFactory.java
index 2a3b6d7..eb780c6 100644
--- a/main/src/cgeo/geocaching/speech/TextFactory.java
+++ b/main/src/cgeo/geocaching/speech/TextFactory.java
@@ -2,9 +2,9 @@ package cgeo.geocaching.speech;
import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.R;
-import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.geopoint.Geopoint;
import cgeo.geocaching.geopoint.IConversion;
+import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.utils.AngleUtils;
import java.util.Locale;
diff --git a/main/src/cgeo/geocaching/twitter/Twitter.java b/main/src/cgeo/geocaching/twitter/Twitter.java
index 51cf6e2..c89c0b6 100644
--- a/main/src/cgeo/geocaching/twitter/Twitter.java
+++ b/main/src/cgeo/geocaching/twitter/Twitter.java
@@ -17,7 +17,6 @@ import cgeo.geocaching.utils.LogTemplateProvider;
import cgeo.geocaching.utils.LogTemplateProvider.LogContext;
import ch.boye.httpclientandroidlib.HttpResponse;
-
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
diff --git a/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java b/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java
index ed5d182..0c67384 100644
--- a/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java
+++ b/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java
@@ -3,7 +3,6 @@ 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;
diff --git a/main/src/cgeo/geocaching/ui/AnchorAwareLinkMovementMethod.java b/main/src/cgeo/geocaching/ui/AnchorAwareLinkMovementMethod.java
index db82e5c..d4c2e10 100644
--- a/main/src/cgeo/geocaching/ui/AnchorAwareLinkMovementMethod.java
+++ b/main/src/cgeo/geocaching/ui/AnchorAwareLinkMovementMethod.java
@@ -8,7 +8,7 @@ import android.widget.TextView;
/**
* <code>LinkMovementMethod</code> with built-in suppression of errors for links, where the URL cannot be handled
* correctly by Android.
- *
+ *
*/
public class AnchorAwareLinkMovementMethod extends LinkMovementMethod {
diff --git a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java
index 7fe77c4..5d8ebef 100644
--- a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java
+++ b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java
@@ -39,6 +39,11 @@ public final class CacheDetailsCreator {
parentView.removeAllViews();
}
+ /**
+ * @param nameId
+ * @param value
+ * @return the view containing the displayed string (i.e. the right side one from the pair of "label": "value")
+ */
public TextView add(final int nameId, final CharSequence value) {
final RelativeLayout layout = (RelativeLayout) activity.getLayoutInflater().inflate(R.layout.cache_information_item, null);
final TextView nameView = (TextView) layout.findViewById(R.id.name);
@@ -188,14 +193,24 @@ public final class CacheDetailsCreator {
if (!cache.isEventCache()) {
return;
}
+ addHiddenDate(cache);
+ }
+
+ public TextView addHiddenDate(final @NonNull Geocache cache) {
final Date hiddenDate = cache.getHiddenDate();
if (hiddenDate == null) {
- return;
+ return null;
}
final long time = hiddenDate.getTime();
if (time > 0) {
- final String dateString = DateUtils.formatDateTime(CgeoApplication.getInstance().getBaseContext(), time, DateUtils.FORMAT_SHOW_WEEKDAY) + ", " + Formatter.formatFullDate(time);
- add(R.string.cache_event, dateString);
+ String dateString = Formatter.formatFullDate(time);
+ if (cache.isEventCache()) {
+ dateString = DateUtils.formatDateTime(CgeoApplication.getInstance().getBaseContext(), time, DateUtils.FORMAT_SHOW_WEEKDAY) + ", " + dateString;
+ }
+ final TextView view = add(cache.isEventCache() ? R.string.cache_event : R.string.cache_hidden, dateString);
+ view.setId(R.id.date);
+ return view;
}
+ return null;
}
}
diff --git a/main/src/cgeo/geocaching/ui/EditNoteDialog.java b/main/src/cgeo/geocaching/ui/EditNoteDialog.java
index 2af1cb8..63f06fc 100644
--- a/main/src/cgeo/geocaching/ui/EditNoteDialog.java
+++ b/main/src/cgeo/geocaching/ui/EditNoteDialog.java
@@ -1,12 +1,17 @@
package cgeo.geocaching.ui;
import cgeo.geocaching.R;
+import cgeo.geocaching.activity.Keyboard;
+import cgeo.geocaching.ui.dialog.Dialogs;
+
+import org.eclipse.jdt.annotation.NonNull;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentActivity;
import android.view.ContextThemeWrapper;
import android.view.View;
import android.widget.EditText;
@@ -35,15 +40,17 @@ public class EditNoteDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
- View view = View.inflate(new ContextThemeWrapper(getActivity(), R.style.dark), R.layout.fragment_edit_note, null);
+ final @NonNull FragmentActivity activity = getActivity();
+ View view = View.inflate(new ContextThemeWrapper(activity, R.style.dark), R.layout.fragment_edit_note, null);
mEditText = (EditText) view.findViewById(R.id.note);
String initialNote = getArguments().getString(ARGUMENT_INITIAL_NOTE);
if (initialNote != null) {
mEditText.setText(initialNote);
+ Dialogs.moveCursorToEnd(mEditText);
getArguments().remove(ARGUMENT_INITIAL_NOTE);
}
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.cache_personal_note);
builder.setView(view);
builder.setPositiveButton(android.R.string.ok,
@@ -61,6 +68,8 @@ public class EditNoteDialog extends DialogFragment {
dialog.dismiss();
}
});
- return builder.create();
+ final AlertDialog dialog = builder.create();
+ new Keyboard(activity).showDelayed(mEditText);
+ return dialog;
}
}
diff --git a/main/src/cgeo/geocaching/ui/ImagesList.java b/main/src/cgeo/geocaching/ui/ImagesList.java
index 4eaf06d..dcce969 100644
--- a/main/src/cgeo/geocaching/ui/ImagesList.java
+++ b/main/src/cgeo/geocaching/ui/ImagesList.java
@@ -9,6 +9,12 @@ import cgeo.geocaching.utils.Log;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
+import rx.Subscription;
+import rx.android.observables.AndroidObservable;
+import rx.functions.Action0;
+import rx.functions.Action1;
+import rx.subscriptions.CompositeSubscription;
+import rx.subscriptions.Subscriptions;
import android.app.Activity;
import android.content.Intent;
@@ -18,14 +24,12 @@ import android.graphics.Bitmap.CompressFormat;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
-import android.os.AsyncTask;
import android.text.Html;
import android.util.SparseArray;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
-import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -44,8 +48,7 @@ public class ImagesList {
public enum ImageType {
LogImages(R.string.cache_log_images_title),
- SpoilerImages(R.string.cache_spoiler_images_title),
- AllImages(R.string.cache_images_title);
+ SpoilerImages(R.string.cache_spoiler_images_title);
private final int titleResId;
@@ -75,12 +78,31 @@ public class ImagesList {
inflater = activity.getLayoutInflater();
}
- public void loadImages(final View parentView, final List<Image> images, final boolean offline) {
+ /**
+ * Load images into a view.
+ *
+ * @param parentView a view to load the images into
+ * @param images the images to load
+ * @param offline <tt>true</tt> if the images must be stored for offline use
+ * @return a subscription which, when unsubscribed, interrupts the loading and clears up resources
+ */
+ public Subscription loadImages(final View parentView, final List<Image> images, final boolean offline) {
+ // Start with a fresh subscription because of this method can be called several times if the
+ // englobing activity is stopped/restarted.
+ final CompositeSubscription subscriptions = new CompositeSubscription(Subscriptions.create(new Action0() {
+ @Override
+ public void call() {
+ removeAllViews();
+ }
+ }));
imagesView = (LinearLayout) parentView.findViewById(R.id.spoiler_list);
+ final HtmlImage imgGetter = new HtmlImage(geocode, true, offline ? StoredList.STANDARD_LIST_ID : StoredList.TEMPORARY_LIST_ID, false);
+
for (final Image img : images) {
- LinearLayout rowView = (LinearLayout) inflater.inflate(R.layout.cache_image_item, null);
+ final LinearLayout rowView = (LinearLayout) inflater.inflate(R.layout.cache_image_item, null);
+ assert(rowView != null);
if (StringUtils.isNotBlank(img.getTitle())) {
((TextView) rowView.findViewById(R.id.title)).setText(Html.fromHtml(img.getTitle()));
@@ -93,66 +115,58 @@ public class ImagesList {
descView.setVisibility(View.VISIBLE);
}
- new AsyncImgLoader(rowView, img, offline).execute();
+ final ImageView imageView = (ImageView) inflater.inflate(R.layout.image_item, null);
+ assert(imageView != null);
+ subscriptions.add(AndroidObservable.fromActivity(activity, imgGetter.fetchDrawable(img.getUrl()))
+ .subscribe(new Action1<BitmapDrawable>() {
+ @Override
+ public void call(final BitmapDrawable image) {
+ display(imageView, image, img, rowView);
+ }
+ }));
+ rowView.addView(imageView);
imagesView.addView(rowView);
}
+
+ return subscriptions;
}
- private class AsyncImgLoader extends AsyncTask<Void, Void, BitmapDrawable> {
+ private void display(final ImageView imageView, final BitmapDrawable image, final Image img, final LinearLayout view) {
+ if (image != null) {
+ bitmaps.add(image.getBitmap());
- final private LinearLayout view;
- final private Image img;
- final boolean offline;
+ final Rect bounds = image.getBounds();
- public AsyncImgLoader(final LinearLayout view, final Image img, final boolean offline) {
- this.view = view;
- this.img = img;
- this.offline = offline;
- }
+ imageView.setImageResource(R.drawable.image_not_loaded);
+ imageView.setClickable(true);
+ imageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ viewImageInStandardApp(image);
+ }
+ });
+ activity.registerForContextMenu(imageView);
+ imageView.setImageDrawable(image);
+ imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ imageView.setLayoutParams(new LinearLayout.LayoutParams(bounds.width(), bounds.height()));
- @Override
- protected BitmapDrawable doInBackground(Void... params) {
- final HtmlImage imgGetter = new HtmlImage(geocode, true, offline ? StoredList.STANDARD_LIST_ID : StoredList.TEMPORARY_LIST_ID, false);
- return imgGetter.getDrawable(img.getUrl());
- }
+ view.findViewById(R.id.progress_bar).setVisibility(View.GONE);
- @Override
- protected void onPostExecute(final BitmapDrawable image) {
- if (image != null) {
- bitmaps.add(image.getBitmap());
- final ImageView imageView = (ImageView) inflater.inflate(R.layout.image_item, null);
-
- final Rect bounds = image.getBounds();
-
- imageView.setImageResource(R.drawable.image_not_loaded);
- imageView.setClickable(true);
- imageView.setOnClickListener(new View.OnClickListener() {
-
- @Override
- public void onClick(View arg0) {
- viewImageInStandardApp(image);
- }
- });
- activity.registerForContextMenu(imageView);
- imageView.setImageDrawable(image);
- imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
- imageView.setLayoutParams(new LayoutParams(bounds.width(), bounds.height()));
-
- view.findViewById(R.id.progress_bar).setVisibility(View.GONE);
- view.addView(imageView);
-
- imageView.setId(image.hashCode());
- images.put(imageView.getId(), img);
- }
+ imageView.setId(image.hashCode());
+ images.put(imageView.getId(), img);
+
+ view.invalidate();
}
}
- public void removeAllViews() {
- imagesView.removeAllViews();
+ private void removeAllViews() {
for (final Bitmap b : bitmaps) {
b.recycle();
}
bitmaps.clear();
+ images.clear();
+
+ imagesView.removeAllViews();
}
public void onCreateContextMenu(ContextMenu menu, View v) {
diff --git a/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java b/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java
index 93f50e1..651ff6e 100644
--- a/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java
+++ b/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java
@@ -54,7 +54,7 @@ public class CoordinatesInputDialog extends NoTitleDialog {
} else if (geo != null && geo.getCoords() != null) {
this.gp = geo.getCoords();
} else {
- this.gp = new Geopoint(0.0, 0.0);
+ this.gp = Geopoint.ZERO;
}
}
@@ -396,7 +396,7 @@ public class CoordinatesInputDialog extends NoTitleDialog {
if (geo != null && geo.getCoords() != null) {
gp = geo.getCoords();
} else {
- gp = new Geopoint(0.0, 0.0);
+ gp = Geopoint.ZERO;
}
}
}
diff --git a/main/src/cgeo/geocaching/ui/dialog/Dialogs.java b/main/src/cgeo/geocaching/ui/dialog/Dialogs.java
index 865ba70..cb8926a 100644
--- a/main/src/cgeo/geocaching/ui/dialog/Dialogs.java
+++ b/main/src/cgeo/geocaching/ui/dialog/Dialogs.java
@@ -1,20 +1,24 @@
package cgeo.geocaching.ui.dialog;
import cgeo.geocaching.CgeoApplication;
-import cgeo.geocaching.utils.RunnableWithArgument;
+import cgeo.geocaching.R;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.Nullable;
+import rx.functions.Action1;
+
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
+import android.view.ContextThemeWrapper;
import android.view.WindowManager;
import android.widget.EditText;
@@ -298,7 +302,7 @@ public final class Dialogs {
/**
* Show a message dialog for input from the user. The okay button is only enabled on non empty input.
- *
+ *
* @param context
* activity owning the dialog
* @param title
@@ -310,19 +314,20 @@ public final class Dialogs {
* @param okayListener
* listener to be run on okay
*/
- public static void input(final Activity context, final int title, final String defaultValue, final int buttonTitle, final RunnableWithArgument<String> okayListener) {
- final EditText input = new EditText(context);
+ public static void input(final Activity context, final int title, final String defaultValue, final int buttonTitle, final Action1<String> okayListener) {
+ final Context themedContext = new ContextThemeWrapper(context, R.style.dark);
+ final EditText input = new EditText(themedContext);
input.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_CLASS_TEXT);
input.setText(defaultValue);
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ final AlertDialog.Builder builder = new AlertDialog.Builder(themedContext);
builder.setTitle(title);
builder.setView(input);
builder.setPositiveButton(buttonTitle, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- okayListener.run(input.getText().toString());
+ okayListener.call(input.getText().toString());
}
});
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@@ -357,8 +362,16 @@ public final class Dialogs {
dialog.show();
enableDialogButtonIfNotEmpty(dialog, defaultValue);
- // position cursor after text
- input.setSelection(input.getText().length());
+ moveCursorToEnd(input);
+ }
+
+ /**
+ * Move the cursor to the end of the input field.
+ *
+ * @param input
+ */
+ public static void moveCursorToEnd(final EditText input) {
+ input.setSelection(input.getText().length(), input.getText().length());
}
private static void enableDialogButtonIfNotEmpty(final AlertDialog dialog, final String input) {
diff --git a/main/src/cgeo/geocaching/ui/dialog/LiveMapInfoDialogBuilder.java b/main/src/cgeo/geocaching/ui/dialog/LiveMapInfoDialogBuilder.java
index 6ad59ec..c29f549 100644
--- a/main/src/cgeo/geocaching/ui/dialog/LiveMapInfoDialogBuilder.java
+++ b/main/src/cgeo/geocaching/ui/dialog/LiveMapInfoDialogBuilder.java
@@ -1,15 +1,14 @@
package cgeo.geocaching.ui.dialog;
+import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.R;
import cgeo.geocaching.settings.Settings;
-import cgeo.geocaching.CgeoApplication;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.view.ContextThemeWrapper;
import android.view.View;
-import android.widget.CheckBox;
public class LiveMapInfoDialogBuilder {
@@ -20,12 +19,7 @@ public class LiveMapInfoDialogBuilder {
final View layout = View.inflate(new ContextThemeWrapper(activity, R.style.dark), R.layout.livemapinfo, null);
builder.setView(layout);
- final CheckBox checkBoxHide = (CheckBox) layout.findViewById(R.id.live_map_hint_hide);
-
final int showCount = Settings.getLiveMapHintShowCount();
- if (showCount > 2) {
- checkBoxHide.setVisibility(View.VISIBLE);
- }
Settings.setLiveMapHintShowCount(showCount + 1);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@@ -33,10 +27,7 @@ public class LiveMapInfoDialogBuilder {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
- CgeoApplication.getInstance().setLiveMapHintShown();
- if (checkBoxHide.getVisibility() == View.VISIBLE && checkBoxHide.isChecked()) {
- Settings.setHideLiveHint(true);
- }
+ CgeoApplication.getInstance().setLiveMapHintShownInThisSession();
}
});
return builder.create();
diff --git a/main/src/cgeo/geocaching/utils/CancellableHandler.java b/main/src/cgeo/geocaching/utils/CancellableHandler.java
index cb4b9db..01fb568 100644
--- a/main/src/cgeo/geocaching/utils/CancellableHandler.java
+++ b/main/src/cgeo/geocaching/utils/CancellableHandler.java
@@ -2,6 +2,9 @@ package cgeo.geocaching.utils;
import cgeo.geocaching.CgeoApplication;
+import rx.Subscription;
+import rx.subscriptions.CompositeSubscription;
+
import android.os.Handler;
import android.os.Message;
@@ -13,6 +16,7 @@ public abstract class CancellableHandler extends Handler {
protected static final int UPDATE_LOAD_PROGRESS_DETAIL = 42186;
private volatile boolean cancelled = false;
+ private static CompositeSubscription subscriptions = new CompositeSubscription();
private static class CancelHolder {
final Object payload;
@@ -30,6 +34,7 @@ public abstract class CancellableHandler extends Handler {
if (message.obj instanceof CancelHolder) {
cancelled = true;
+ subscriptions.unsubscribe();
handleCancel(((CancelHolder) message.obj).payload);
} else {
handleRegularMessage(message);
@@ -37,6 +42,17 @@ public abstract class CancellableHandler extends Handler {
}
/**
+ * Add a subscription to the list of subscriptions to be subscribed at cancellation time.
+ */
+ final public void unsubscribeIfCancelled(final Subscription subscription) {
+ subscriptions.add(subscription);
+ if (cancelled) {
+ // Protect against race conditions
+ subscriptions.unsubscribe();
+ }
+ }
+
+ /**
* Handle a non-cancel message.<br>
* Subclasses must implement this to handle messages.
*
diff --git a/main/src/cgeo/geocaching/utils/CryptUtils.java b/main/src/cgeo/geocaching/utils/CryptUtils.java
index 5273fa5..80d841f 100644
--- a/main/src/cgeo/geocaching/utils/CryptUtils.java
+++ b/main/src/cgeo/geocaching/utils/CryptUtils.java
@@ -3,6 +3,7 @@ package cgeo.geocaching.utils;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
@@ -66,6 +67,8 @@ public final class CryptUtils {
}
}
+ @SuppressWarnings("null")
+ @NonNull
public static String rot13(String text) {
if (text == null) {
return StringUtils.EMPTY;
diff --git a/main/src/cgeo/geocaching/utils/FileUtils.java b/main/src/cgeo/geocaching/utils/FileUtils.java
index f650216..54553c2 100644
--- a/main/src/cgeo/geocaching/utils/FileUtils.java
+++ b/main/src/cgeo/geocaching/utils/FileUtils.java
@@ -1,12 +1,20 @@
package cgeo.geocaching.utils;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.StringUtils;
import android.os.Handler;
import android.os.Message;
+import java.io.BufferedOutputStream;
import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
import java.util.List;
/**
@@ -126,4 +134,23 @@ public final class FileUtils {
}
return success;
}
+
+ public static boolean writeFileUTF16(File file, String content) {
+ // TODO: replace by some apache.commons IOUtils or FileUtils code
+ Writer fileWriter = null;
+ BufferedOutputStream buffer = null;
+ try {
+ final OutputStream os = new FileOutputStream(file);
+ buffer = new BufferedOutputStream(os);
+ fileWriter = new OutputStreamWriter(buffer, CharEncoding.UTF_16);
+ fileWriter.write(content.toString());
+ } catch (final IOException e) {
+ Log.e("FieldnoteExport.ExportTask export", e);
+ return false;
+ } finally {
+ IOUtils.closeQuietly(fileWriter);
+ IOUtils.closeQuietly(buffer);
+ }
+ return true;
+ }
}
diff --git a/main/src/cgeo/geocaching/utils/GeoDirHandler.java b/main/src/cgeo/geocaching/utils/GeoDirHandler.java
index c85648b..7050e61 100644
--- a/main/src/cgeo/geocaching/utils/GeoDirHandler.java
+++ b/main/src/cgeo/geocaching/utils/GeoDirHandler.java
@@ -4,12 +4,16 @@ import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.IGeoData;
import cgeo.geocaching.settings.Settings;
-import android.os.Handler;
-import android.os.Message;
+import rx.Scheduler;
+import rx.Subscription;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.schedulers.Schedulers;
+import rx.functions.Action1;
+
+import java.util.concurrent.TimeUnit;
/**
- * GeoData and Direction handler. Manipulating geodata and direction information
- * through a GeoDirHandler ensures that all listeners are registered from a {@link android.os.Looper} thread.
+ * GeoData and Direction handler.
* <p>
* To use this class, override at least one of {@link #updateDirection(float)} or {@link #updateGeoData(IGeoData)}. You
* need to start the handler using one of
@@ -21,47 +25,11 @@ import android.os.Message;
* A good place might be the {@code onResume} method of the Activity. Stop the Handler accordingly in {@code onPause}.
* </p>
*/
-public abstract class GeoDirHandler extends Handler implements IObserver<Object> {
-
- private static final int OBSERVABLE = 1 << 1;
- private static final int START_GEO = 1 << 2;
- private static final int START_DIR = 1 << 3;
- private static final int STOP_GEO = 1 << 4;
- private static final int STOP_DIR = 1 << 5;
-
+public abstract class GeoDirHandler {
private static final CgeoApplication app = CgeoApplication.getInstance();
- @Override
- final public void handleMessage(final Message message) {
- if ((message.what & START_GEO) != 0) {
- app.addGeoObserver(this);
- }
-
- if ((message.what & START_DIR) != 0) {
- app.addDirectionObserver(this);
- }
-
- if ((message.what & STOP_GEO) != 0) {
- app.deleteGeoObserver(this);
- }
-
- if ((message.what & STOP_DIR) != 0) {
- app.deleteDirectionObserver(this);
- }
-
- if ((message.what & OBSERVABLE) != 0) {
- if (message.obj instanceof IGeoData) {
- updateGeoData((IGeoData) message.obj);
- } else {
- updateDirection((Float) message.obj);
- }
- }
- }
-
- @Override
- final public void update(final Object o) {
- obtainMessage(OBSERVABLE, o).sendToTarget();
- }
+ private Subscription dirSubscription = null;
+ private Subscription geoSubscription = null;
/**
* Update method called when new IGeoData is available.
@@ -69,7 +37,7 @@ public abstract class GeoDirHandler extends Handler implements IObserver<Object>
* @param data
* the new data
*/
- protected void updateGeoData(final IGeoData data) {
+ public void updateGeoData(final IGeoData data) {
// Override this in children
}
@@ -79,24 +47,38 @@ public abstract class GeoDirHandler extends Handler implements IObserver<Object>
* @param direction
* the new direction
*/
- protected void updateDirection(final float direction) {
+ public void updateDirection(final float direction) {
// Override this in children
}
/**
* Register the current GeoDirHandler for GeoData information.
*/
- public void startGeo() {
- sendEmptyMessage(START_GEO);
+ public synchronized void startGeo() {
+ geoSubscription = app.currentGeoObject()
+ .subscribeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Action1<IGeoData>() {
+ @Override
+ public void call(final IGeoData geoData) {
+ updateGeoData(geoData);
+ }
+ });
}
/**
* Register the current GeoDirHandler for direction information if the preferences
* allow it.
*/
- public void startDir() {
+ public synchronized void startDir() {
if (Settings.isUseCompass()) {
- sendEmptyMessage(START_DIR);
+ dirSubscription = app.currentDirObject()
+ .subscribeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Action1<Float>() {
+ @Override
+ public void call(final Float direction) {
+ updateDirection(direction);
+ }
+ });
}
}
@@ -105,27 +87,43 @@ public abstract class GeoDirHandler extends Handler implements IObserver<Object>
* preferences allow it).
*/
public void startGeoAndDir() {
- sendEmptyMessage(START_GEO | (Settings.isUseCompass() ? START_DIR : 0));
+ startGeo();
+ startDir();
}
/**
* Unregister the current GeoDirHandler for GeoData information.
*/
- public void stopGeo() {
- sendEmptyMessage(STOP_GEO);
+ public synchronized void stopGeo() {
+ // Delay the unsubscription by 2.5 seconds, so that another activity has
+ // the time to subscribe and the GPS receiver will not be turned down.
+ if (geoSubscription != null) {
+ final Subscription subscription = geoSubscription;
+ geoSubscription = null;
+ Schedulers.newThread().schedule(new Action1<Scheduler.Inner>() {
+ @Override
+ public void call(final Scheduler.Inner inner) {
+ subscription.unsubscribe();
+ }
+ }, 2500, TimeUnit.MILLISECONDS);
+ }
}
/**
* Unregister the current GeoDirHandler for direction information.
*/
- public void stopDir() {
- sendEmptyMessage(STOP_DIR);
+ public synchronized void stopDir() {
+ if (dirSubscription != null) {
+ dirSubscription.unsubscribe();
+ dirSubscription = null;
+ }
}
/**
* Unregister the current GeoDirHandler for GeoData and direction information.
*/
public void stopGeoAndDir() {
- sendEmptyMessage(STOP_GEO | STOP_DIR);
+ stopGeo();
+ stopDir();
}
}
diff --git a/main/src/cgeo/geocaching/utils/IObserver.java b/main/src/cgeo/geocaching/utils/IObserver.java
deleted file mode 100644
index bfcc798..0000000
--- a/main/src/cgeo/geocaching/utils/IObserver.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package cgeo.geocaching.utils;
-
-/**
- * Observer interface.
- * <p/>
- * An observer will receive updates about the observed object (implementing the {@link ISubject} interface) through its
- * {@link #update(Object)} method.
- *
- * @param <T>
- * the kind of data to observe
- */
-public interface IObserver<T> {
-
- /**
- * Called when an observed object has updated its data.
- *
- * @param data
- * the updated data
- */
- void update(final T data);
-
-}
diff --git a/main/src/cgeo/geocaching/utils/ISubject.java b/main/src/cgeo/geocaching/utils/ISubject.java
deleted file mode 100644
index c325db0..0000000
--- a/main/src/cgeo/geocaching/utils/ISubject.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package cgeo.geocaching.utils;
-
-/**
- * Interface for subjects objects. Those can be observed by objects implementing the {@link IObserver} interface.
- *
- * @param <T>
- * the kind of data to observe
- */
-
-public interface ISubject<T> {
-
- /**
- * Add an observer to the observers list.
- * <p/>
- * Observers will be notified with no particular order.
- *
- * @param observer
- * the observer to add
- * @return true if the observer has been added, false if it was present already
- */
- public boolean addObserver(final IObserver<? super T> observer);
-
- /**
- * Delete an observer from the observers list.
- *
- * @param observer
- * the observer to remove
- * @return true if the observer has been removed, false if it was not in the list of observers
- */
- public boolean deleteObserver(final IObserver<? super T> observer);
-
- /**
- * Number of observers currently observing the object.
- *
- * @return the number of observers
- */
- public int sizeObservers();
-
- /**
- * Notify all the observers that new data is available.
- * <p/>
- * The {@link IObserver#update(Object)} method of each observer will be called with no particular order.
- *
- * @param data
- * the updated data
- * @return true if at least one observer was notified, false if there were no observers
- */
- public boolean notifyObservers(final T data);
-
- /**
- * Clear the observers list.
- *
- * @return true if there were observers before calling this method, false if the observers list was empty
- */
- public boolean clearObservers();
-
-}
diff --git a/main/src/cgeo/geocaching/utils/ImageUtils.java b/main/src/cgeo/geocaching/utils/ImageUtils.java
index 73f322c..9f47ead 100644
--- a/main/src/cgeo/geocaching/utils/ImageUtils.java
+++ b/main/src/cgeo/geocaching/utils/ImageUtils.java
@@ -78,7 +78,7 @@ public final class ImageUtils {
* @return BitmapDrawable The scaled image
*/
@NonNull
- public static BitmapDrawable scaleBitmapTo(@NonNull final Bitmap image, final int maxWidth, final int maxHeight) {
+ private static BitmapDrawable scaleBitmapTo(@NonNull final Bitmap image, final int maxWidth, final int maxHeight) {
final CgeoApplication app = CgeoApplication.getInstance();
Bitmap result = image;
int width = image.getWidth();
diff --git a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java
index 708dff0..0c83076 100644
--- a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java
+++ b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java
@@ -1,5 +1,7 @@
package cgeo.geocaching.utils;
+import org.eclipse.jdt.annotation.NonNull;
+
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
@@ -42,6 +44,8 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E>
*
* @see HashSet
*/
+ @SuppressWarnings("null")
+ @NonNull
@Override
public Iterator<E> iterator() {
return map.keySet().iterator();
diff --git a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java
index 76fa0f7..5fa0982 100644
--- a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java
+++ b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java
@@ -47,10 +47,6 @@ public final class LogTemplateProvider {
this.logEntry = logEntry;
}
- public LogContext(final boolean offline) {
- this(null, null, offline);
- }
-
public LogContext(final Geocache cache, LogEntry logEntry, final boolean offline) {
this.cache = cache;
this.offline = offline;
diff --git a/main/src/cgeo/geocaching/utils/MemorySubject.java b/main/src/cgeo/geocaching/utils/MemorySubject.java
deleted file mode 100644
index c424528..0000000
--- a/main/src/cgeo/geocaching/utils/MemorySubject.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package cgeo.geocaching.utils;
-
-/**
- * Synchronized implementation of the {@link ISubject} interface with an added pull interface.
- *
- * @param <T>
- * the kind of data to observe
- */
-public class MemorySubject<T> extends Subject<T> {
-
- /**
- * The latest version of the observed data.
- * <p/>
- * A child class implementation may want to set this field from its constructors, in case early observers request
- * the data before it got a chance to get updated. Otherwise, <code>null</code> will be returned until updated
- * data is available.
- */
- private T memory;
-
- @Override
- public synchronized boolean addObserver(final IObserver<? super T> observer) {
- final boolean added = super.addObserver(observer);
- if (added && memory != null) {
- observer.update(memory);
- }
- return added;
- }
-
- @Override
- public synchronized boolean notifyObservers(final T data) {
- memory = data;
- return super.notifyObservers(data);
- }
-
- /**
- * Get the memorized version of the data.
- *
- * @return the initial data set by the subject (which may be <code>null</code>),
- * or the updated data if it is available
- */
- public synchronized T getMemory() {
- return memory;
- }
-
-}
diff --git a/main/src/cgeo/geocaching/utils/RunnableWithArgument.java b/main/src/cgeo/geocaching/utils/RunnableWithArgument.java
deleted file mode 100644
index 6137efd..0000000
--- a/main/src/cgeo/geocaching/utils/RunnableWithArgument.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package cgeo.geocaching.utils;
-
-public interface RunnableWithArgument<T> {
-
- abstract void run(final T argument);
-
-}
diff --git a/main/src/cgeo/geocaching/utils/Subject.java b/main/src/cgeo/geocaching/utils/Subject.java
deleted file mode 100644
index b1754cc..0000000
--- a/main/src/cgeo/geocaching/utils/Subject.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package cgeo.geocaching.utils;
-
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-/**
- * Synchronized implementation of the {@link ISubject} interface.
- *
- * @param <T>
- * the kind of data to observe
- */
-public class Subject<T> implements ISubject<T> {
-
- /**
- * Collection of observers.
- */
- protected final Set<IObserver<? super T>> observers = new LinkedHashSet<IObserver<? super T>>();
-
- @Override
- public synchronized boolean addObserver(final IObserver<? super T> observer) {
- final boolean added = observers.add(observer);
- if (added && observers.size() == 1) {
- onFirstObserver();
- }
- return added;
- }
-
- @Override
- public synchronized boolean deleteObserver(final IObserver<? super T> observer) {
- final boolean removed = observers.remove(observer);
- if (removed && observers.isEmpty()) {
- onLastObserver();
- }
- return removed;
- }
-
- @Override
- public synchronized boolean notifyObservers(final T arg) {
- final boolean nonEmpty = !observers.isEmpty();
- for (final IObserver<? super T> observer : observers) {
- observer.update(arg);
- }
- return nonEmpty;
- }
-
- @Override
- public synchronized int sizeObservers() {
- return observers.size();
- }
-
- @Override
- public synchronized boolean clearObservers() {
- final boolean nonEmpty = !observers.isEmpty();
- for (final IObserver<? super T> observer : observers) {
- deleteObserver(observer);
- }
- return nonEmpty;
- }
-
- /**
- * Method called when the collection of observers goes from empty to non-empty.
- * <p/>
- * The default implementation does nothing and may be overwritten by child classes.
- */
- protected void onFirstObserver() {
- }
-
- /**
- * Method called when the collection of observers goes from non-empty to empty.
- * <p/>
- * The default implementation does nothing and may be overwritten by child classes.
- */
- protected void onLastObserver() {
- }
-
-}
diff --git a/main/src/cgeo/geocaching/utils/SynchronizedDateFormat.java b/main/src/cgeo/geocaching/utils/SynchronizedDateFormat.java
index 2368469..7848d1a 100644
--- a/main/src/cgeo/geocaching/utils/SynchronizedDateFormat.java
+++ b/main/src/cgeo/geocaching/utils/SynchronizedDateFormat.java
@@ -4,6 +4,7 @@ import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
+import java.util.TimeZone;
public class SynchronizedDateFormat {
private final SimpleDateFormat format;
@@ -12,7 +13,17 @@ public class SynchronizedDateFormat {
format = new SimpleDateFormat(pattern, locale);
}
+ public SynchronizedDateFormat(String pattern, TimeZone timeZone, Locale locale) {
+ format = new SimpleDateFormat(pattern, locale);
+ format.setTimeZone(timeZone);
+ }
+
public synchronized Date parse(final String input) throws ParseException {
return format.parse(input);
}
+
+ public synchronized String format(final Date date) {
+ return format.format(date);
+ }
+
}
diff --git a/main/src/cgeo/geocaching/utils/TextUtils.java b/main/src/cgeo/geocaching/utils/TextUtils.java
index efbb2d7..c4e1128 100644
--- a/main/src/cgeo/geocaching/utils/TextUtils.java
+++ b/main/src/cgeo/geocaching/utils/TextUtils.java
@@ -4,7 +4,6 @@
package cgeo.geocaching.utils;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
import org.eclipse.jdt.annotation.Nullable;
import java.util.regex.Matcher;