aboutsummaryrefslogtreecommitdiffstats
path: root/main/src
diff options
context:
space:
mode:
Diffstat (limited to 'main/src')
-rw-r--r--main/src/cgeo/geocaching/AbstractLoggingActivity.java11
-rw-r--r--main/src/cgeo/geocaching/CacheDetailActivity.java86
-rw-r--r--main/src/cgeo/geocaching/CacheListActivity.java87
-rw-r--r--main/src/cgeo/geocaching/CgeoApplication.java61
-rw-r--r--main/src/cgeo/geocaching/CompassActivity.java86
-rw-r--r--main/src/cgeo/geocaching/CreateShortcutActivity.java110
-rw-r--r--main/src/cgeo/geocaching/DataStore.java124
-rw-r--r--main/src/cgeo/geocaching/EditWaypointActivity.java95
-rw-r--r--main/src/cgeo/geocaching/Geocache.java41
-rw-r--r--main/src/cgeo/geocaching/Image.java14
-rw-r--r--main/src/cgeo/geocaching/ImageSelectActivity.java51
-rw-r--r--main/src/cgeo/geocaching/Intents.java48
-rw-r--r--main/src/cgeo/geocaching/LogCacheActivity.java64
-rw-r--r--main/src/cgeo/geocaching/LogTrackableActivity.java7
-rw-r--r--main/src/cgeo/geocaching/MainActivity.java24
-rw-r--r--main/src/cgeo/geocaching/SearchActivity.java15
-rw-r--r--main/src/cgeo/geocaching/SearchResult.java2
-rw-r--r--main/src/cgeo/geocaching/StatusFragment.java3
-rw-r--r--main/src/cgeo/geocaching/Trackable.java15
-rw-r--r--main/src/cgeo/geocaching/TrackableActivity.java297
-rw-r--r--main/src/cgeo/geocaching/Waypoint.java21
-rw-r--r--main/src/cgeo/geocaching/activity/AbstractActivity.java39
-rw-r--r--main/src/cgeo/geocaching/activity/AbstractListActivity.java26
-rw-r--r--main/src/cgeo/geocaching/activity/ActivityMixin.java35
-rw-r--r--main/src/cgeo/geocaching/activity/IAbstractActivity.java14
-rw-r--r--main/src/cgeo/geocaching/activity/ShowcaseViewBuilder.java61
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java3
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java12
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java14
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java41
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/NavigationSelectionActionProvider.java2
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java6
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java4
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java4
-rw-r--r--main/src/cgeo/geocaching/connector/AbstractConnector.java2
-rw-r--r--main/src/cgeo/geocaching/connector/IConnector.java2
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCConnector.java15
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCConstants.java6
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCLogin.java6
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCParser.java86
-rw-r--r--main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java6
-rw-r--r--main/src/cgeo/geocaching/enumerations/CacheType.java4
-rw-r--r--main/src/cgeo/geocaching/enumerations/LogType.java16
-rw-r--r--main/src/cgeo/geocaching/files/GPXImporter.java40
-rw-r--r--main/src/cgeo/geocaching/files/GPXParser.java93
-rw-r--r--main/src/cgeo/geocaching/filter/PopularityRatioFilter.java8
-rw-r--r--main/src/cgeo/geocaching/gcvote/GCVote.java189
-rw-r--r--main/src/cgeo/geocaching/maps/CGeoMap.java123
-rw-r--r--main/src/cgeo/geocaching/maps/MapActivity.java17
-rw-r--r--main/src/cgeo/geocaching/network/HtmlImage.java12
-rw-r--r--main/src/cgeo/geocaching/network/Network.java2
-rw-r--r--main/src/cgeo/geocaching/network/SmileyImage.java16
-rw-r--r--main/src/cgeo/geocaching/playservices/LocationProvider.java140
-rw-r--r--main/src/cgeo/geocaching/sensors/GeoData.java62
-rw-r--r--main/src/cgeo/geocaching/sensors/GeoDataProvider.java129
-rw-r--r--main/src/cgeo/geocaching/sensors/GeoDirHandler.java5
-rw-r--r--main/src/cgeo/geocaching/sensors/GpsStatusProvider.java5
-rw-r--r--main/src/cgeo/geocaching/sensors/OrientationProvider.java (renamed from main/src/cgeo/geocaching/sensors/DirectionProvider.java)50
-rw-r--r--main/src/cgeo/geocaching/sensors/RotationProvider.java83
-rw-r--r--main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java2
-rw-r--r--main/src/cgeo/geocaching/settings/Settings.java161
-rw-r--r--main/src/cgeo/geocaching/settings/SettingsActivity.java38
-rw-r--r--main/src/cgeo/geocaching/sorting/DistanceComparator.java6
-rw-r--r--main/src/cgeo/geocaching/speech/SpeechService.java6
-rw-r--r--main/src/cgeo/geocaching/ui/CacheDetailsCreator.java16
-rw-r--r--main/src/cgeo/geocaching/ui/CacheListAdapter.java4
-rw-r--r--main/src/cgeo/geocaching/ui/CompassView.java16
-rw-r--r--main/src/cgeo/geocaching/ui/EditNoteDialog.java12
-rw-r--r--main/src/cgeo/geocaching/ui/dialog/Dialogs.java47
-rw-r--r--main/src/cgeo/geocaching/ui/logs/TrackableLogsViewCreator.java19
-rw-r--r--main/src/cgeo/geocaching/utils/AngleUtils.java43
-rw-r--r--main/src/cgeo/geocaching/utils/Formatter.java42
-rw-r--r--main/src/cgeo/geocaching/utils/HtmlUtils.java28
-rw-r--r--main/src/cgeo/geocaching/utils/ImageUtils.java87
-rw-r--r--main/src/cgeo/geocaching/utils/RxUtils.java34
75 files changed, 1943 insertions, 1258 deletions
diff --git a/main/src/cgeo/geocaching/AbstractLoggingActivity.java b/main/src/cgeo/geocaching/AbstractLoggingActivity.java
index 4641d3a..15e8848 100644
--- a/main/src/cgeo/geocaching/AbstractLoggingActivity.java
+++ b/main/src/cgeo/geocaching/AbstractLoggingActivity.java
@@ -19,11 +19,6 @@ import android.widget.EditText;
public abstract class AbstractLoggingActivity extends AbstractActionBarActivity {
- /**
- * sub classes can disable the send button
- */
- private boolean enableSend = true;
-
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.abstract_logging_activity, menu);
@@ -54,7 +49,6 @@ public abstract class AbstractLoggingActivity extends AbstractActionBarActivity
}
menu.findItem(R.id.menu_smilies).setVisible(smileyVisible);
- menu.findItem(R.id.menu_send).setVisible(enableSend);
return true;
}
@@ -85,11 +79,6 @@ public abstract class AbstractLoggingActivity extends AbstractActionBarActivity
ActivityMixin.insertAtPosition(log, newText, moveCursor);
}
- protected final void setLoggingEnabled(final boolean enabled) {
- enableSend = enabled;
- invalidateOptionsMenuCompatible();
- }
-
protected void requestKeyboardForLogging() {
new Keyboard(this).show(findViewById(R.id.log));
}
diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java
index dca0a43..a59d9b9 100644
--- a/main/src/cgeo/geocaching/CacheDetailActivity.java
+++ b/main/src/cgeo/geocaching/CacheDetailActivity.java
@@ -5,6 +5,7 @@ import butterknife.InjectView;
import cgeo.calendar.CalendarAddon;
import cgeo.geocaching.activity.AbstractActivity;
+import cgeo.geocaching.activity.AbstractActivity.ActivitySharingInterface;
import cgeo.geocaching.activity.AbstractViewPagerActivity;
import cgeo.geocaching.activity.INavigationSource;
import cgeo.geocaching.activity.Progress;
@@ -17,6 +18,7 @@ import cgeo.geocaching.connector.IConnector;
import cgeo.geocaching.connector.gc.GCConnector;
import cgeo.geocaching.connector.gc.GCConstants;
import cgeo.geocaching.enumerations.CacheAttribute;
+import cgeo.geocaching.enumerations.CacheType;
import cgeo.geocaching.enumerations.LoadFlags;
import cgeo.geocaching.enumerations.LoadFlags.SaveFlag;
import cgeo.geocaching.enumerations.WaypointType;
@@ -132,7 +134,8 @@ import java.util.regex.Pattern;
*
* e.g. details, description, logs, waypoints, inventory...
*/
-public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailActivity.Page> implements CacheMenuHandler.ActivityInterface, INavigationSource {
+public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailActivity.Page>
+ implements CacheMenuHandler.ActivityInterface, INavigationSource, ActivitySharingInterface, EditNoteDialogListener {
private static final int MESSAGE_FAILED = -1;
private static final int MESSAGE_SUCCEEDED = 1;
@@ -237,7 +240,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
finish();
return;
}
- } else if (uriHost.contains("opencaching.de")) {
+ } else if (uriHost.contains("opencaching.de") || uriHost.contains("opencaching.fr")) {
if (StringUtils.startsWith(uriPath, "/oc")) {
geocode = uriPath.substring(1).toUpperCase(Locale.US);
} else {
@@ -264,9 +267,8 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
return;
}
- // if we open this cache from a search, let's properly initialize the title bar, even if we don't have cache details
- cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_ONLY);
- updateTitleBar(geocode);
+ // If we open this cache from a search, let's properly initialize the title bar, even if we don't have cache details
+ updateTitleBar(geocode, name, null);
final LoadCacheHandler loadCacheHandler = new LoadCacheHandler(this, progress);
@@ -313,6 +315,14 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
});
locationUpdater = new CacheDetailsGeoDirHandler(this);
+
+ // If we have a newer Android device setup Android Beam for easy cache sharing
+ initializeAndroidBeam(this);
+ }
+
+ @Override
+ public String getAndroidBeamUri() {
+ return cache != null ? cache.getCgeoUrl() : null;
}
@Override
@@ -603,17 +613,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
// allow cache to notify CacheDetailActivity when it changes so it can be reloaded
cache.setChangeNotificationHandler(new ChangeNotificationHandler(this, progress));
- updateTitleBar(null);
-
- // if we have a newer Android device setup Android Beam for easy cache sharing
- initializeAndroidBeam(
- new ActivitySharingInterface() {
- @Override
- public String getUri() {
- return cache.getCgeoUrl();
- }
- }
- );
+ updateTitleBar(cache.getGeocode(), cache.getName(), cache.getType());
// reset imagesList so Images view page will be redrawn
imagesList = null;
@@ -626,19 +626,16 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
Settings.addCacheToHistory(cache.getGeocode());
}
- private void updateTitleBar(@Nullable final String geocode) {
- if (cache == null) {
- setTitle(StringUtils.isBlank(geocode) ? res.getString(R.string.cache) : geocode);
- // avoid showing the traditional cache icon from the standard action bar (it may later change to the actual type icon)
- getSupportActionBar().setIcon(android.R.color.transparent);
+ private void updateTitleBar(@Nullable final String geocode, @Nullable final String name, @Nullable final CacheType type) {
+ if (StringUtils.isNotBlank(name)) {
+ setTitle(StringUtils.isNotBlank(geocode) ? name + " (" + geocode + ")" : name);
+ } else {
+ setTitle(StringUtils.isNotBlank(geocode) ? geocode : res.getString(R.string.cache));
}
- else {
- if (StringUtils.isNotBlank(cache.getName())) {
- setTitle(cache.getName() + " (" + cache.getGeocode() + ')');
- } else {
- setTitle(cache.getGeocode());
- }
- getSupportActionBar().setIcon(getResources().getDrawable(cache.getType().markerId));
+ if (type != null) {
+ getSupportActionBar().setIcon(getResources().getDrawable(type.markerId));
+ } else {
+ getSupportActionBar().setIcon(android.R.color.transparent);
}
}
@@ -2248,7 +2245,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
if (UPDATE_LOAD_PROGRESS_DETAIL == msg.what && msg.obj instanceof String) {
updateStatusMsg(R.string.cache_dialog_offline_save_message, (String) msg.obj);
} else {
- notifyDatasetChanged(activityRef);
+ notifyDataSetChanged(activityRef);
}
}
}
@@ -2264,7 +2261,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
if (UPDATE_LOAD_PROGRESS_DETAIL == msg.what && msg.obj instanceof String) {
updateStatusMsg(R.string.cache_dialog_refresh_message, (String) msg.obj);
} else {
- notifyDatasetChanged(activityRef);
+ notifyDataSetChanged(activityRef);
}
}
}
@@ -2277,7 +2274,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
@Override
public void handleMessage(final Message msg) {
- notifyDatasetChanged(activityRef);
+ notifyDataSetChanged(activityRef);
}
}
@@ -2292,12 +2289,12 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
if (msg.what == MESSAGE_FAILED) {
super.handleMessage(msg);
} else {
- notifyDatasetChanged(activityRef);
+ notifyDataSetChanged(activityRef);
}
}
}
- private static void notifyDatasetChanged(final WeakReference<AbstractActivity> activityRef) {
+ private static void notifyDataSetChanged(final WeakReference<AbstractActivity> activityRef) {
final CacheDetailActivity activity = ((CacheDetailActivity) activityRef.get());
if (activity != null) {
activity.notifyDataSetChanged();
@@ -2316,23 +2313,22 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
public static void editPersonalNote(final Geocache cache, final CacheDetailActivity activity) {
if (cache.isOffline()) {
- final EditNoteDialogListener editNoteDialogListener = new EditNoteDialogListener() {
- @Override
- public void onFinishEditNoteDialog(final String note) {
- cache.setPersonalNote(note);
- cache.parseWaypointsFromNote();
- final TextView personalNoteView = ButterKnife.findById(activity, R.id.personalnote);
- setPersonalNote(personalNoteView, note);
- DataStore.saveCache(cache, EnumSet.of(SaveFlag.DB));
- activity.notifyDataSetChanged();
- }
- };
final FragmentManager fm = activity.getSupportFragmentManager();
- final EditNoteDialog dialog = EditNoteDialog.newInstance(cache.getPersonalNote(), editNoteDialogListener);
+ final EditNoteDialog dialog = EditNoteDialog.newInstance(cache.getPersonalNote());
dialog.show(fm, "fragment_edit_note");
}
}
+ @Override
+ public void onFinishEditNoteDialog(final String note) {
+ cache.setPersonalNote(note);
+ cache.parseWaypointsFromNote();
+ final TextView personalNoteView = ButterKnife.findById(this, R.id.personalnote);
+ setPersonalNote(personalNoteView, note);
+ DataStore.saveCache(cache, EnumSet.of(SaveFlag.DB));
+ notifyDataSetChanged();
+ }
+
private static void setPersonalNote(final TextView personalNoteView, final String personalNote) {
personalNoteView.setText(personalNote, TextView.BufferType.SPANNABLE);
if (StringUtils.isNotBlank(personalNote)) {
diff --git a/main/src/cgeo/geocaching/CacheListActivity.java b/main/src/cgeo/geocaching/CacheListActivity.java
index 82998cc..661755d 100644
--- a/main/src/cgeo/geocaching/CacheListActivity.java
+++ b/main/src/cgeo/geocaching/CacheListActivity.java
@@ -7,6 +7,7 @@ import cgeo.geocaching.activity.AbstractListActivity;
import cgeo.geocaching.activity.ActivityMixin;
import cgeo.geocaching.activity.FilteredActivity;
import cgeo.geocaching.activity.Progress;
+import cgeo.geocaching.activity.ShowcaseViewBuilder;
import cgeo.geocaching.apps.cache.navi.NavigationAppFactory;
import cgeo.geocaching.apps.cachelist.CacheListAppFactory;
import cgeo.geocaching.compatibility.Compatibility;
@@ -40,7 +41,6 @@ import cgeo.geocaching.maps.CGeoMap;
import cgeo.geocaching.network.Cookies;
import cgeo.geocaching.network.Network;
import cgeo.geocaching.network.Parameters;
-import cgeo.geocaching.sensors.DirectionProvider;
import cgeo.geocaching.sensors.GeoDirHandler;
import cgeo.geocaching.sensors.IGeoData;
import cgeo.geocaching.settings.Settings;
@@ -51,6 +51,7 @@ import cgeo.geocaching.ui.CacheListAdapter;
import cgeo.geocaching.ui.LoggingUI;
import cgeo.geocaching.ui.WeakReferenceHandler;
import cgeo.geocaching.ui.dialog.Dialogs;
+import cgeo.geocaching.utils.AngleUtils;
import cgeo.geocaching.utils.AsyncTaskWithProgress;
import cgeo.geocaching.utils.CancellableHandler;
import cgeo.geocaching.utils.DateUtils;
@@ -58,6 +59,9 @@ import cgeo.geocaching.utils.Log;
import ch.boye.httpclientandroidlib.HttpResponse;
+import com.github.amlcurran.showcaseview.targets.ActionViewTarget;
+import com.github.amlcurran.showcaseview.targets.ActionViewTarget.Type;
+
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
@@ -136,7 +140,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
@Override
public void updateDirection(final float direction) {
if (Settings.isLiveList()) {
- adapter.setActualHeading(DirectionProvider.getDirectionNow(direction));
+ adapter.setActualHeading(AngleUtils.getDirectionNow(direction));
}
}
@@ -388,8 +392,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
// get parameters
Bundle extras = getIntent().getExtras();
if (extras != null) {
- final Object typeObject = extras.get(Intents.EXTRA_LIST_TYPE);
- type = (typeObject instanceof CacheListType) ? (CacheListType) typeObject : CacheListType.OFFLINE;
+ type = Intents.getListType(getIntent());
coords = extras.getParcelable(Intents.EXTRA_COORDS);
}
else {
@@ -401,6 +404,9 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
coords = Geopoint.ZERO;
}
}
+ if (type == CacheListType.NEAREST) {
+ coords = CgeoApplication.getInstance().currentGeo().getCoords();
+ }
setTitle(title);
@@ -427,9 +433,9 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
if (isInvokedFromAttachment()) {
importGpxAttachement();
}
-
-
-
+ else {
+ presentShowcase();
+ }
}
/**
@@ -765,7 +771,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
return new SearchResult(geocodes);
}
- public void deletePastEvents() {
+ private void deletePastEvents() {
final List<Geocache> deletion = new ArrayList<>();
for (final Geocache cache : adapter.getCheckedOrAllCaches()) {
if (DateUtils.isPastEvent(cache)) {
@@ -775,9 +781,15 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
new DropDetailsTask().execute(deletion.toArray(new Geocache[deletion.size()]));
}
- public void clearOfflineLogs() {
- progress.show(this, null, res.getString(R.string.caches_clear_offlinelogs_progress), true, clearOfflineLogsHandler.cancelMessage());
- new ClearOfflineLogsThread(clearOfflineLogsHandler).start();
+ private void clearOfflineLogs() {
+ Dialogs.confirmYesNo(this, R.string.caches_clear_offlinelogs, R.string.caches_clear_offlinelogs_message, new OnClickListener() {
+
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ progress.show(CacheListActivity.this, null, res.getString(R.string.caches_clear_offlinelogs_progress), true, clearOfflineLogsHandler.cancelMessage());
+ new ClearOfflineLogsThread(clearOfflineLogsHandler).start();
+ }
+ });
}
/**
@@ -1121,7 +1133,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
});
}
- public void removeFromHistory() {
+ private void removeFromHistory() {
final List<Geocache> caches = adapter.getCheckedOrAllCaches();
final String[] geocodes = new String[caches.size()];
for (int i = 0; i < geocodes.length; i++) {
@@ -1132,7 +1144,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
getSupportLoaderManager().initLoader(CacheListLoaderType.REMOVE_FROM_HISTORY.getLoaderId(), b, this);
}
- public void importWeb() {
+ private void importWeb() {
// menu is also shown with no device connected
if (!Settings.isRegisteredForSend2cgeo()) {
Dialogs.confirm(this, R.string.web_import_title, R.string.init_sendToCgeo_description, new OnClickListener() {
@@ -1154,7 +1166,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
threadWeb.start();
}
- public void dropStored() {
+ private void dropStored() {
final int titleId = (adapter.getCheckedCount() > 0) ? R.string.caches_remove_selected : R.string.caches_remove_all;
final int messageId = (adapter.getCheckedCount() > 0) ? R.string.caches_remove_selected_confirm : R.string.caches_remove_all_confirm;
final String message = getString(messageId, adapter.getCheckedOrAllCount());
@@ -1350,13 +1362,13 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
};
}
- public void switchListById(final int id) {
+ private void switchListById(final int id) {
if (id < 0) {
return;
}
if (id == PseudoList.HISTORY_LIST.id) {
- CacheListActivity.startActivityHistory(this);
+ startActivity(CacheListActivity.getHistoryIntent(this));
finish();
return;
}
@@ -1381,6 +1393,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
showProgress(true);
showFooterLoadingCaches();
DataStore.moveToList(adapter.getCheckedCaches(), listId);
+ adapter.setSelectMode(false);
currentLoader = (OfflineGeocacheListLoader) getSupportLoaderManager().restartLoader(CacheListType.OFFLINE.getLoaderId(), OfflineGeocacheListLoader.getBundleForList(listId), this);
@@ -1446,7 +1459,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
public static void startActivityOffline(final Context context) {
final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.OFFLINE);
+ Intents.putListType(cachesIntent, CacheListType.OFFLINE);
context.startActivity(cachesIntent);
}
@@ -1455,7 +1468,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
return;
}
final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.OWNER);
+ Intents.putListType(cachesIntent, CacheListType.OWNER);
cachesIntent.putExtra(Intents.EXTRA_USERNAME, userName);
context.startActivity(cachesIntent);
}
@@ -1473,7 +1486,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
return;
}
final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.FINDER);
+ Intents.putListType(cachesIntent, CacheListType.FINDER);
cachesIntent.putExtra(Intents.EXTRA_USERNAME, userName);
context.startActivity(cachesIntent);
}
@@ -1494,25 +1507,17 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
}
}
- public static void startActivityNearest(final AbstractActivity context, final Geopoint coordsNow) {
- if (!isValidCoords(context, coordsNow)) {
- return;
- }
- final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.NEAREST);
- cachesIntent.putExtra(Intents.EXTRA_COORDS, coordsNow);
- context.startActivity(cachesIntent);
+ public static Intent getNearestIntent(final Activity context) {
+ return Intents.putListType(new Intent(context, CacheListActivity.class), CacheListType.NEAREST);
}
- public static void startActivityHistory(final Context context) {
- final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.HISTORY);
- context.startActivity(cachesIntent);
+ public static Intent getHistoryIntent(final Context context) {
+ return Intents.putListType(new Intent(context, CacheListActivity.class), CacheListType.HISTORY);
}
public static void startActivityAddress(final Context context, final Geopoint coords, final String address) {
final Intent addressIntent = new Intent(context, CacheListActivity.class);
- addressIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.ADDRESS);
+ Intents.putListType(addressIntent, CacheListType.ADDRESS);
addressIntent.putExtra(Intents.EXTRA_COORDS, coords);
addressIntent.putExtra(Intents.EXTRA_ADDRESS, address);
context.startActivity(addressIntent);
@@ -1523,7 +1528,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
return;
}
final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.COORDINATE);
+ Intents.putListType(cachesIntent, CacheListType.COORDINATE);
cachesIntent.putExtra(Intents.EXTRA_COORDS, coords);
context.startActivity(cachesIntent);
}
@@ -1542,15 +1547,15 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
return;
}
final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.KEYWORD);
+ Intents.putListType(cachesIntent, CacheListType.KEYWORD);
cachesIntent.putExtra(Intents.EXTRA_KEYWORD, keyword);
context.startActivity(cachesIntent);
}
public static void startActivityMap(final Context context, final SearchResult search) {
final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.MAP);
cachesIntent.putExtra(Intents.EXTRA_SEARCH, search);
+ Intents.putListType(cachesIntent, CacheListType.MAP);
context.startActivity(cachesIntent);
}
@@ -1561,7 +1566,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
return;
}
final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.POCKET);
+ Intents.putListType(cachesIntent, CacheListType.POCKET);
cachesIntent.putExtra(Intents.EXTRA_NAME, pq.getName());
cachesIntent.putExtra(Intents.EXTRA_POCKET_GUID, guid);
context.startActivity(cachesIntent);
@@ -1741,4 +1746,14 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
}
return numbers.isEmpty() ? null : StringUtils.join(numbers, '/');
}
+
+ @Override
+ public ShowcaseViewBuilder getShowcase() {
+ if (mCacheListSpinnerAdapter != null) {
+ return new ShowcaseViewBuilder(this)
+ .setTarget(new ActionViewTarget(this, Type.SPINNER))
+ .setContent(R.string.showcase_cachelist_title, R.string.showcase_cachelist_text);
+ }
+ return null;
+ }
}
diff --git a/main/src/cgeo/geocaching/CgeoApplication.java b/main/src/cgeo/geocaching/CgeoApplication.java
index ae5a565..34dab09 100644
--- a/main/src/cgeo/geocaching/CgeoApplication.java
+++ b/main/src/cgeo/geocaching/CgeoApplication.java
@@ -1,12 +1,13 @@
package cgeo.geocaching;
import cgeo.geocaching.playservices.LocationProvider;
-import cgeo.geocaching.sensors.DirectionProvider;
import cgeo.geocaching.sensors.GeoData;
import cgeo.geocaching.sensors.GeoDataProvider;
import cgeo.geocaching.sensors.GpsStatusProvider;
import cgeo.geocaching.sensors.GpsStatusProvider.Status;
import cgeo.geocaching.sensors.IGeoData;
+import cgeo.geocaching.sensors.OrientationProvider;
+import cgeo.geocaching.sensors.RotationProvider;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.OOMDumpingUncaughtExceptionHandler;
@@ -15,8 +16,11 @@ import cgeo.geocaching.utils.RxUtils;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
+import org.eclipse.jdt.annotation.NonNull;
+
import rx.Observable;
import rx.functions.Action1;
+import rx.functions.Func1;
import android.app.Application;
import android.view.ViewConfiguration;
@@ -33,13 +37,15 @@ public class CgeoApplication extends Application {
private Observable<IGeoData> geoDataObservableLowPower;
private Observable<Float> directionObservable;
private Observable<Status> gpsStatusObservable;
- private volatile IGeoData currentGeo = GeoData.dummyLocation();
+ @NonNull private volatile IGeoData currentGeo = GeoData.DUMMY_LOCATION;
+ private volatile boolean hasValidLocation = false;
private volatile float currentDirection = 0.0f;
private boolean isGooglePlayServicesAvailable = false;
- private final Action1<IGeoData> REMEMBER_GEODATA = new Action1<IGeoData>() {
+ private final Action1<IGeoData> rememberGeodataAction = new Action1<IGeoData>() {
@Override
public void call(final IGeoData geoData) {
currentGeo = geoData;
+ hasValidLocation = true;
}
};
@@ -78,38 +84,60 @@ public class CgeoApplication extends Application {
menuKeyField.setBoolean(config, false);
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException ignore) {
}
+
+ // Set language to English if the user decided so.
+ Settings.setLanguage(Settings.isUseEnglish());
+
// ensure initialization of lists
DataStore.getLists();
+
// Check if Google Play services is available
if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS) {
isGooglePlayServicesAvailable = true;
}
Log.i("Google Play services are " + (isGooglePlayServicesAvailable ? "" : "not ") + "available");
setupGeoDataObservables(Settings.useGooglePlayServices(), Settings.useLowPowerMode());
- geoDataObservableLowPower.subscribeOn(RxUtils.looperCallbacksScheduler).first().subscribe(REMEMBER_GEODATA);
- directionObservable = DirectionProvider.create(this).replay(1).refCount().doOnNext(new Action1<Float>() {
- @Override
- public void call(final Float direction) {
- currentDirection = direction;
- }
- });
- gpsStatusObservable = GpsStatusProvider.create(this).startWith(GpsStatusProvider.NO_GPS).share();
+ setupDirectionObservable(Settings.useLowPowerMode());
+ gpsStatusObservable = GpsStatusProvider.create(this).replay(1).refCount();
+
+ // Attempt to acquire an initial location before any real activity happens.
+ geoDataObservableLowPower.subscribeOn(RxUtils.looperCallbacksScheduler).first().subscribe(rememberGeodataAction);
}
public void setupGeoDataObservables(final boolean useGooglePlayServices, final boolean useLowPowerLocation) {
if (useGooglePlayServices) {
- geoDataObservable = LocationProvider.getMostPrecise(this, true).replay(1).refCount().doOnNext(REMEMBER_GEODATA);
+ geoDataObservable = LocationProvider.getMostPrecise(this).doOnNext(rememberGeodataAction);
if (useLowPowerLocation) {
- geoDataObservableLowPower = LocationProvider.getLowPower(this, true).replay(1).refCount().doOnNext(REMEMBER_GEODATA);
+ geoDataObservableLowPower = LocationProvider.getLowPower(this, true).doOnNext(rememberGeodataAction);
} else {
geoDataObservableLowPower = geoDataObservable;
}
} else {
- geoDataObservable = GeoDataProvider.create(this).replay(1).refCount().doOnNext(REMEMBER_GEODATA);
+ geoDataObservable = GeoDataProvider.create(this).replay(1).refCount().doOnNext(rememberGeodataAction);
geoDataObservableLowPower = geoDataObservable;
}
}
+ public void setupDirectionObservable(final boolean useLowPower) {
+ directionObservable = RotationProvider.create(this, useLowPower).onErrorResumeNext(new Func1<Throwable, Observable<? extends Float>>() {
+ @Override
+ public Observable<? extends Float> call(final Throwable throwable) {
+ return OrientationProvider.create(CgeoApplication.this);
+ }
+ }).onErrorResumeNext(new Func1<Throwable, Observable<? extends Float>>() {
+ @Override
+ public Observable<? extends Float> call(final Throwable throwable) {
+ Log.e("Device orientation will not be available as no suitable sensors were found");
+ return Observable.<Float>never().startWith(0.0f);
+ }
+ }).replay(1).refCount().doOnNext(new Action1<Float>() {
+ @Override
+ public void call(final Float direction) {
+ currentDirection = direction;
+ }
+ });
+ }
+
@Override
public void onLowMemory() {
Log.i("Cleaning applications cache.");
@@ -131,10 +159,15 @@ public class CgeoApplication extends Application {
return gpsStatusObservable;
}
+ @NonNull
public IGeoData currentGeo() {
return currentGeo;
}
+ public boolean hasValidLocation() {
+ return hasValidLocation;
+ }
+
public Float distanceNonBlocking(final ICoordinates target) {
if (target.getCoords() == null) {
return null;
diff --git a/main/src/cgeo/geocaching/CompassActivity.java b/main/src/cgeo/geocaching/CompassActivity.java
index 7b5ae35..00fa790 100644
--- a/main/src/cgeo/geocaching/CompassActivity.java
+++ b/main/src/cgeo/geocaching/CompassActivity.java
@@ -8,7 +8,6 @@ import cgeo.geocaching.enumerations.LoadFlags;
import cgeo.geocaching.geopoint.Geopoint;
import cgeo.geocaching.geopoint.Units;
import cgeo.geocaching.maps.CGeoMap;
-import cgeo.geocaching.sensors.DirectionProvider;
import cgeo.geocaching.sensors.GeoDirHandler;
import cgeo.geocaching.sensors.GpsStatusProvider.Status;
import cgeo.geocaching.sensors.IGeoData;
@@ -16,6 +15,7 @@ import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.speech.SpeechService;
import cgeo.geocaching.ui.CompassView;
import cgeo.geocaching.ui.LoggingUI;
+import cgeo.geocaching.utils.AngleUtils;
import cgeo.geocaching.utils.Formatter;
import cgeo.geocaching.utils.Log;
@@ -38,14 +38,10 @@ import android.view.SubMenu;
import android.view.View;
import android.widget.TextView;
-import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
public class CompassActivity extends AbstractActionBarActivity {
- private static final int COORDINATES_OFFSET = 10;
-
@InjectView(R.id.nav_type) protected TextView navType;
@InjectView(R.id.nav_accuracy) protected TextView navAccuracy;
@InjectView(R.id.nav_satellites) protected TextView navSatellites;
@@ -60,7 +56,6 @@ public class CompassActivity extends AbstractActionBarActivity {
private static final String EXTRAS_NAME = "name";
private static final String EXTRAS_GEOCODE = "geocode";
private static final String EXTRAS_CACHE_INFO = "cacheinfo";
- private static final List<IWaypoint> coordinates = new ArrayList<>();
/**
* Destination of the compass, or null (if the compass is used for a waypoint only).
@@ -124,6 +119,7 @@ public class CompassActivity extends AbstractActionBarActivity {
public void onResume() {
super.onResume(geoDirHandler.start(GeoDirHandler.UPDATE_GEODIR),
app.gpsStatusObservable().observeOn(AndroidSchedulers.mainThread()).subscribe(gpsStatusHandler));
+ forceRefresh();
}
@Override
@@ -143,34 +139,42 @@ public class CompassActivity extends AbstractActionBarActivity {
setTitle();
setDestCoords();
setCacheInfo();
+ forceRefresh();
+ }
+ private void forceRefresh() {
// Force a refresh of location and direction when data is available.
final CgeoApplication app = CgeoApplication.getInstance();
final IGeoData geo = app.currentGeo();
- if (geo != null) {
- geoDirHandler.updateGeoDir(geo, app.currentDirection());
- }
+ geoDirHandler.updateGeoDir(geo, app.currentDirection());
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.compass_activity_options, menu);
menu.findItem(R.id.menu_compass_sensor).setVisible(hasMagneticFieldSensor);
- final SubMenu subMenu = menu.findItem(R.id.menu_select_destination).getSubMenu();
- if (coordinates.size() > 1) {
- for (int i = 0; i < coordinates.size(); i++) {
- final IWaypoint coordinate = coordinates.get(i);
- subMenu.add(0, COORDINATES_OFFSET + i, 0, coordinate.getName() + " (" + coordinate.getCoordType() + ")");
- }
- } else {
- menu.findItem(R.id.menu_select_destination).setVisible(false);
- }
if (cache != null) {
LoggingUI.addMenuItems(this, menu, cache);
}
+ addWaypointItems(menu);
return true;
}
+ private void addWaypointItems(final Menu menu) {
+ if (cache != null) {
+ final List<Waypoint> waypoints = cache.getWaypoints();
+ boolean visible = false;
+ final SubMenu subMenu = menu.findItem(R.id.menu_select_destination).getSubMenu();
+ for (final Waypoint waypoint : waypoints) {
+ if (waypoint.getCoords() != null) {
+ subMenu.add(0, waypoint.getId(), 0, waypoint.getName());
+ visible = true;
+ }
+ }
+ menu.findItem(R.id.menu_select_destination).setVisible(visible);
+ }
+ }
+
@Override
public boolean onPrepareOptionsMenu(final Menu menu) {
super.onPrepareOptionsMenu(menu);
@@ -212,18 +216,19 @@ public class CompassActivity extends AbstractActionBarActivity {
if (LoggingUI.onMenuItemSelected(item, this, cache)) {
return true;
}
- final int coordinatesIndex = id - COORDINATES_OFFSET;
- if (coordinatesIndex >= 0 && coordinatesIndex < coordinates.size()) {
- final IWaypoint coordinate = coordinates.get(coordinatesIndex);
- title = coordinate.getName();
- dstCoords = coordinate.getCoords();
- setTitle();
- setDestCoords();
- setCacheInfo();
- updateDistanceInfo(app.currentGeo());
-
- Log.d("destination set: " + title + " (" + dstCoords + ")");
- return true;
+ if (cache != null) {
+ final Waypoint waypoint = cache.getWaypointById(id);
+ if (waypoint != null) {
+ title = waypoint.getName();
+ dstCoords = waypoint.getCoords();
+ setTitle();
+ setDestCoords();
+ setCacheInfo();
+ updateDistanceInfo(app.currentGeo());
+
+ Log.d("destination set: " + title + " (" + dstCoords + ")");
+ return true;
+ }
}
}
return super.onOptionsItemSelected(item);
@@ -297,7 +302,7 @@ public class CompassActivity extends AbstractActionBarActivity {
navLocation.setText(res.getString(R.string.loc_trying));
}
- updateNorthHeading(DirectionProvider.getDirectionNow(dir));
+ updateNorthHeading(AngleUtils.getDirectionNow(dir));
} catch (final RuntimeException e) {
Log.w("Failed to LocationUpdater location.");
}
@@ -310,17 +315,8 @@ public class CompassActivity extends AbstractActionBarActivity {
}
}
- public static void startActivity(final Context context, final String geocode, final String displayedName, final Geopoint coords, final Collection<IWaypoint> coordinatesWithType,
+ public static void startActivity(final Context context, final String geocode, final String displayedName, final Geopoint coords,
final String info) {
- coordinates.clear();
- if (coordinatesWithType != null) {
- for (final IWaypoint coordinate : coordinatesWithType) {
- if (coordinate != null) {
- coordinates.add(coordinate);
- }
- }
- }
-
final Intent navigateIntent = new Intent(context, CompassActivity.class);
navigateIntent.putExtra(EXTRAS_COORDS, coords);
navigateIntent.putExtra(EXTRAS_GEOCODE, geocode);
@@ -331,12 +327,12 @@ public class CompassActivity extends AbstractActionBarActivity {
context.startActivity(navigateIntent);
}
- public static void startActivity(final Context context, final String geocode, final String displayedName, final Geopoint coords, final Collection<IWaypoint> coordinatesWithType) {
- CompassActivity.startActivity(context, geocode, displayedName, coords, coordinatesWithType, null);
+ public static void startActivity(final Context context, final String geocode, final String displayedName, final Geopoint coords) {
+ startActivity(context, geocode, displayedName, coords, null);
}
- public static void startActivity(final Context context, final Geocache cache) {
- startActivity(context, cache.getGeocode(), cache.getName(), cache.getCoords(), null,
+ public static void startActivityCache(final Context context, final Geocache cache) {
+ startActivity(context, cache.getGeocode(), cache.getName(), cache.getCoords(),
Formatter.formatCacheInfoShort(cache));
}
diff --git a/main/src/cgeo/geocaching/CreateShortcutActivity.java b/main/src/cgeo/geocaching/CreateShortcutActivity.java
index ffcf81b..cf6d486 100644
--- a/main/src/cgeo/geocaching/CreateShortcutActivity.java
+++ b/main/src/cgeo/geocaching/CreateShortcutActivity.java
@@ -1,19 +1,54 @@
package cgeo.geocaching;
import cgeo.geocaching.activity.AbstractActionBarActivity;
-import cgeo.geocaching.list.PseudoList;
import cgeo.geocaching.list.StoredList;
+import cgeo.geocaching.maps.MapActivity;
+import cgeo.geocaching.ui.dialog.Dialogs;
+import cgeo.geocaching.ui.dialog.Dialogs.ItemWithIcon;
+import cgeo.geocaching.utils.ImageUtils;
import rx.functions.Action1;
import android.content.Intent;
import android.content.Intent.ShortcutIconResource;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
import android.os.Bundle;
+import java.util.ArrayList;
+import java.util.List;
+
public class CreateShortcutActivity extends AbstractActionBarActivity {
+ private static class Shortcut implements ItemWithIcon {
+
+ private final int titleResourceId;
+ private final int drawableResourceId;
+ private final Intent intent;
+
+ /**
+ * shortcut with a separate icon
+ */
+ public Shortcut(final int titleResourceId, final int drawableResourceId, final Intent intent) {
+ this.titleResourceId = titleResourceId;
+ this.drawableResourceId = drawableResourceId;
+ this.intent = intent;
+ }
+
+ @Override
+ public int getIcon() {
+ return drawableResourceId;
+ }
+
+ @Override
+ public String toString() {
+ return CgeoApplication.getInstance().getString(titleResourceId);
+ }
+ }
+
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// init
@@ -23,35 +58,82 @@ public class CreateShortcutActivity extends AbstractActionBarActivity {
}
private void promptForShortcut() {
+ final List<Shortcut> shortcuts = new ArrayList<>();
+
+ shortcuts.add(new Shortcut(R.string.live_map_button, R.drawable.main_live, new Intent(this, MapActivity.class)));
+ shortcuts.add(new Shortcut(R.string.caches_nearby_button, R.drawable.main_nearby, CacheListActivity.getNearestIntent(this)));
+
+ // TODO: make logging activities ask for cache/trackable when being invoked externally
+ // shortcuts.add(new Shortcut(R.string.cache_menu_visit, new Intent(this, LogCacheActivity.class)));
+ // shortcuts.add(new Shortcut(R.string.trackable_log_touch, new Intent(this, LogTrackableActivity.class)));
+
+ final Shortcut offlineShortcut = new Shortcut(R.string.stored_caches_button, R.drawable.main_stored, null);
+ shortcuts.add(offlineShortcut);
+ shortcuts.add(new Shortcut(R.string.advanced_search_button, R.drawable.main_search, new Intent(this, SearchActivity.class)));
+ shortcuts.add(new Shortcut(R.string.any_button, R.drawable.main_any, new Intent(this, NavigateAnyPointActivity.class)));
+ shortcuts.add(new Shortcut(R.string.menu_history, R.drawable.main_stored, CacheListActivity.getHistoryIntent(this)));
+
+ Dialogs.select(this, getString(R.string.create_shortcut), shortcuts, new Action1<Shortcut>() {
+
+ @Override
+ public void call(final Shortcut shortcut) {
+ if (shortcut == offlineShortcut) {
+ promptForListShortcut();
+ }
+ else {
+ createShortcutAndFinish(shortcut.toString(), shortcut.intent, shortcut.drawableResourceId);
+ }
+ }
+ });
+ }
+
+ protected void promptForListShortcut() {
new StoredList.UserInterface(this).promptForListSelection(R.string.create_shortcut, new Action1<Integer>() {
@Override
public void call(final Integer listId) {
- final Intent shortcut = createShortcut(listId);
- setResult(RESULT_OK, shortcut);
-
- // finish activity to return the shortcut
- finish();
+ createOfflineListShortcut(listId.intValue());
}
- }, false, PseudoList.HISTORY_LIST.id);
+ }, true, -1);
}
- protected Intent createShortcut(int listId) {
+ protected void createOfflineListShortcut(final int listId) {
final StoredList list = DataStore.getList(listId);
if (list == null) {
- return null;
+ return;
}
// target to be executed by the shortcut
final Intent targetIntent = new Intent(this, CacheListActivity.class);
targetIntent.putExtra(Intents.EXTRA_LIST_ID, list.id);
- final ShortcutIconResource iconResource = Intent.ShortcutIconResource.fromContext(this, R.drawable.cgeo);
// shortcut to be returned
+ createShortcutAndFinish(list.title, targetIntent, R.drawable.main_stored);
+ }
+
+ private void createShortcutAndFinish(final String title, final Intent targetIntent, final int iconResourceId) {
final Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, targetIntent);
- intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, list.title);
- intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
- return intent;
+ intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
+ if (iconResourceId == R.drawable.cgeo) {
+ final ShortcutIconResource iconResource = Intent.ShortcutIconResource.fromContext(this, iconResourceId);
+ intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
+ }
+ else {
+ intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, createOverlay(iconResourceId));
+ }
+
+ setResult(RESULT_OK, intent);
+
+ // finish activity to return the shortcut
+ finish();
+ }
+
+ private Bitmap createOverlay(final int drawableResourceId) {
+ final LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] {
+ res.getDrawable(drawableResourceId), res.getDrawable(R.drawable.cgeo) });
+ layerDrawable.setLayerInset(0, 0, 0, 10, 10);
+ layerDrawable.setLayerInset(1, 50, 50, 0, 0);
+ return ImageUtils.convertToBitmap(layerDrawable);
}
}
diff --git a/main/src/cgeo/geocaching/DataStore.java b/main/src/cgeo/geocaching/DataStore.java
index 4f4eaec..9ee588b 100644
--- a/main/src/cgeo/geocaching/DataStore.java
+++ b/main/src/cgeo/geocaching/DataStore.java
@@ -112,35 +112,32 @@ public class DataStore {
"cg_caches.direction," + // 16
"cg_caches.distance," + // 17
"cg_caches.terrain," + // 18
- "cg_caches.latlon," + // 19
- "cg_caches.location," + // 20
- "cg_caches.personal_note," + // 21
- "cg_caches.shortdesc," + // 22
- "cg_caches.favourite_cnt," + // 23
- "cg_caches.rating," + // 24
- "cg_caches.votes," + // 25
- "cg_caches.myvote," + // 26
- "cg_caches.disabled," + // 27
- "cg_caches.archived," + // 28
- "cg_caches.members," + // 29
- "cg_caches.found," + // 30
- "cg_caches.favourite," + // 31
- "cg_caches.inventoryunknown," + // 32
- "cg_caches.onWatchlist," + // 33
- "cg_caches.reliable_latlon," + // 34
- "cg_caches.coordsChanged," + // 35
- "cg_caches.latitude," + // 36
- "cg_caches.longitude," + // 37
- "cg_caches.finalDefined," + // 38
- "cg_caches._id," + // 39
- "cg_caches.inventorycoins," + // 40
- "cg_caches.inventorytags," + // 41
- "cg_caches.logPasswordRequired"; // 42
-
- //TODO: remove "latlon" field from cache table
+ "cg_caches.location," + // 19
+ "cg_caches.personal_note," + // 20
+ "cg_caches.shortdesc," + // 21
+ "cg_caches.favourite_cnt," + // 22
+ "cg_caches.rating," + // 23
+ "cg_caches.votes," + // 24
+ "cg_caches.myvote," + // 25
+ "cg_caches.disabled," + // 26
+ "cg_caches.archived," + // 27
+ "cg_caches.members," + // 28
+ "cg_caches.found," + // 29
+ "cg_caches.favourite," + // 30
+ "cg_caches.inventoryunknown," + // 31
+ "cg_caches.onWatchlist," + // 32
+ "cg_caches.reliable_latlon," + // 33
+ "cg_caches.coordsChanged," + // 34
+ "cg_caches.latitude," + // 35
+ "cg_caches.longitude," + // 36
+ "cg_caches.finalDefined," + // 37
+ "cg_caches._id," + // 38
+ "cg_caches.inventorycoins," + // 39
+ "cg_caches.inventorytags," + // 40
+ "cg_caches.logPasswordRequired"; // 41
/** The list of fields needed for mapping. */
- private static final String[] WAYPOINT_COLUMNS = new String[] { "_id", "geocode", "updated", "type", "prefix", "lookup", "name", "latlon", "latitude", "longitude", "note", "own", "visited" };
+ private static final String[] WAYPOINT_COLUMNS = new String[] { "_id", "geocode", "updated", "type", "prefix", "lookup", "name", "latitude", "longitude", "note", "own", "visited" };
/** Number of days (as ms) after temporarily saved caches are deleted */
private final static long DAYS_AFTER_CACHE_IS_DELETED = 3 * 24 * 60 * 60 * 1000;
@@ -184,7 +181,6 @@ public class DataStore {
+ "size text, "
+ "difficulty float, "
+ "terrain float, "
- + "latlon text, "
+ "location text, "
+ "direction double, "
+ "distance double, "
@@ -215,9 +211,7 @@ public class DataStore {
+ "create table " + dbTableLists + " ("
+ "_id integer primary key autoincrement, "
+ "title text not null, "
- + "updated long not null, "
- + "latitude double, "
- + "longitude double "
+ + "updated long not null"
+ "); ";
private static final String dbCreateAttributes = ""
+ "create table " + dbTableAttributes + " ("
@@ -236,7 +230,6 @@ public class DataStore {
+ "prefix text, "
+ "lookup text, "
+ "name text, "
- + "latlon text, "
+ "latitude double, "
+ "longitude double, "
+ "note text, "
@@ -639,7 +632,6 @@ public class DataStore {
+ "size text, "
+ "difficulty float, "
+ "terrain float, "
- + "latlon text, "
+ "location text, "
+ "direction double, "
+ "distance double, "
@@ -666,7 +658,7 @@ public class DataStore {
db.execSQL(dbCreateCachesTemp);
db.execSQL("insert into " + dbTableCachesTemp + " select _id,updated,detailed,detailedupdate,visiteddate,geocode,reason,cacheid,guid,type,name,own,owner,owner_real," +
- "hidden,hint,size,difficulty,terrain,latlon,location,direction,distance,latitude,longitude, 0," +
+ "hidden,hint,size,difficulty,terrain,location,direction,distance,latitude,longitude, 0," +
"personal_note,shortdesc,description,favourite_cnt,rating,votes,myvote,disabled,archived,members,found,favourite,inventorycoins," +
"inventorytags,inventoryunknown,onWatchlist from " + dbTableCaches);
db.execSQL("drop table " + dbTableCaches);
@@ -682,13 +674,12 @@ public class DataStore {
+ "prefix text, "
+ "lookup text, "
+ "name text, "
- + "latlon text, "
+ "latitude double, "
+ "longitude double, "
+ "note text "
+ "); ";
db.execSQL(dbCreateWaypointsTemp);
- db.execSQL("insert into " + dbTableWaypointsTemp + " select _id, geocode, updated, type, prefix, lookup, name, latlon, latitude, longitude, note from " + dbTableWaypoints);
+ db.execSQL("insert into " + dbTableWaypointsTemp + " select _id, geocode, updated, type, prefix, lookup, name, latitude, longitude, note from " + dbTableWaypoints);
db.execSQL("drop table " + dbTableWaypoints);
db.execSQL("alter table " + dbTableWaypointsTemp + " rename to " + dbTableWaypoints);
@@ -1274,7 +1265,6 @@ public class DataStore {
values.put("prefix", oneWaypoint.getPrefix());
values.put("lookup", oneWaypoint.getLookup());
values.put("name", oneWaypoint.getName());
- values.put("latlon", oneWaypoint.getLatlon());
putCoords(values, oneWaypoint.getCoords());
values.put("note", oneWaypoint.getNote());
values.put("own", oneWaypoint.isUserDefined() ? 1 : 0);
@@ -1353,7 +1343,6 @@ public class DataStore {
values.put("prefix", waypoint.getPrefix());
values.put("lookup", waypoint.getLookup());
values.put("name", waypoint.getName());
- values.put("latlon", waypoint.getLatlon());
putCoords(values, waypoint.getCoords());
values.put("note", waypoint.getNote());
values.put("own", waypoint.isUserDefined() ? 1 : 0);
@@ -1542,7 +1531,7 @@ public class DataStore {
return new HashSet<>();
}
- final Set<Geocache> result = new HashSet<>();
+ final Set<Geocache> result = new HashSet<>(geocodes.size());
final Set<String> remaining = new HashSet<>(geocodes);
if (loadFlags.contains(LoadFlag.CACHE_BEFORE)) {
@@ -1620,7 +1609,7 @@ public class DataStore {
int logIndex = -1;
while (cursor.moveToNext()) {
- final Geocache cache = DataStore.createCacheFromDatabaseContent(cursor);
+ final Geocache cache = createCacheFromDatabaseContent(cursor);
if (loadFlags.contains(LoadFlag.ATTRIBUTES)) {
cache.setAttributes(loadAttributes(cache.getGeocode()));
@@ -1730,25 +1719,25 @@ public class DataStore {
}
cache.setTerrain(cursor.getFloat(18));
// do not set cache.location
- cache.setCoords(getCoords(cursor, 36, 37));
- cache.setPersonalNote(cursor.getString(21));
+ cache.setPersonalNote(cursor.getString(20));
// do not set cache.shortdesc
// do not set cache.description
- cache.setFavoritePoints(cursor.getInt(23));
- cache.setRating(cursor.getFloat(24));
- cache.setVotes(cursor.getInt(25));
- cache.setMyVote(cursor.getFloat(26));
- cache.setDisabled(cursor.getInt(27) == 1);
- cache.setArchived(cursor.getInt(28) == 1);
- cache.setPremiumMembersOnly(cursor.getInt(29) == 1);
- cache.setFound(cursor.getInt(30) == 1);
- cache.setFavorite(cursor.getInt(31) == 1);
- cache.setInventoryItems(cursor.getInt(32));
- cache.setOnWatchlist(cursor.getInt(33) == 1);
- cache.setReliableLatLon(cursor.getInt(34) > 0);
- cache.setUserModifiedCoords(cursor.getInt(35) > 0);
- cache.setFinalDefined(cursor.getInt(38) > 0);
- cache.setLogPasswordRequired(cursor.getInt(42) > 0);
+ cache.setFavoritePoints(cursor.getInt(22));
+ cache.setRating(cursor.getFloat(23));
+ cache.setVotes(cursor.getInt(24));
+ cache.setMyVote(cursor.getFloat(25));
+ cache.setDisabled(cursor.getInt(26) == 1);
+ cache.setArchived(cursor.getInt(27) == 1);
+ cache.setPremiumMembersOnly(cursor.getInt(28) == 1);
+ cache.setFound(cursor.getInt(29) == 1);
+ cache.setFavorite(cursor.getInt(30) == 1);
+ cache.setInventoryItems(cursor.getInt(31));
+ cache.setOnWatchlist(cursor.getInt(32) == 1);
+ cache.setReliableLatLon(cursor.getInt(33) > 0);
+ cache.setUserModifiedCoords(cursor.getInt(34) > 0);
+ cache.setCoords(getCoords(cursor, 35, 36));
+ cache.setFinalDefined(cursor.getInt(37) > 0);
+ cache.setLogPasswordRequired(cursor.getInt(41) > 0);
Log.d("Loading " + cache.toString() + " (" + cache.getListId() + ") from DB");
@@ -1830,7 +1819,6 @@ public class DataStore {
waypoint.setGeocode(cursor.getString(cursor.getColumnIndex("geocode")));
waypoint.setPrefix(cursor.getString(cursor.getColumnIndex("prefix")));
waypoint.setLookup(cursor.getString(cursor.getColumnIndex("lookup")));
- waypoint.setLatlon(cursor.getString(cursor.getColumnIndex("latlon")));
waypoint.setCoords(getCoords(cursor, cursor.getColumnIndex("latitude"), cursor.getColumnIndex("longitude")));
waypoint.setNote(cursor.getString(cursor.getColumnIndex("note")));
@@ -2846,7 +2834,7 @@ public class DataStore {
final StringBuilder whereExpr = new StringBuilder("geocode in (");
final Iterator<String> iterator = geocodes.iterator();
while (true) {
- whereExpr.append(DatabaseUtils.sqlEscapeString(StringUtils.upperCase(iterator.next())));
+ DatabaseUtils.appendEscapedSQLString(whereExpr, StringUtils.upperCase(iterator.next()));
if (!iterator.hasNext()) {
break;
}
@@ -3112,18 +3100,24 @@ public class DataStore {
cursor.getString(1),
tbcode,
Intents.ACTION_TRACKABLE,
- tbcode
+ tbcode,
+ String.valueOf(R.drawable.trackable_all)
});
}
cursor.close();
}
public static String[] getSuggestions(final String table, final String column, final String input) {
- final Cursor cursor = database.rawQuery("SELECT DISTINCT " + column
- + " FROM " + table
- + " WHERE " + column + " LIKE ?"
- + " ORDER BY " + column + " COLLATE NOCASE ASC;", new String[] { getSuggestionArgument(input) });
- return cursorToColl(cursor, new LinkedList<String>(), GET_STRING_0).toArray(new String[cursor.getCount()]);
+ try {
+ final Cursor cursor = database.rawQuery("SELECT DISTINCT " + column
+ + " FROM " + table
+ + " WHERE " + column + " LIKE ?"
+ + " ORDER BY " + column + " COLLATE NOCASE ASC;", new String[] { getSuggestionArgument(input) });
+ return cursorToColl(cursor, new LinkedList<String>(), GET_STRING_0).toArray(new String[cursor.getCount()]);
+ } catch (final RuntimeException e) {
+ Log.e("cannot get suggestions from " + table + "->" + column + " for input '" + input + "'", e);
+ return new String[0];
+ }
}
public static String[] getSuggestionsOwnerName(final String input) {
diff --git a/main/src/cgeo/geocaching/EditWaypointActivity.java b/main/src/cgeo/geocaching/EditWaypointActivity.java
index 73cb477..5dccad8 100644
--- a/main/src/cgeo/geocaching/EditWaypointActivity.java
+++ b/main/src/cgeo/geocaching/EditWaypointActivity.java
@@ -89,10 +89,10 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
*/
private Geocache cache;
- private Handler loadWaypointHandler = new Handler() {
+ private final Handler loadWaypointHandler = new Handler() {
@Override
- public void handleMessage(Message msg) {
+ public void handleMessage(final Message msg) {
try {
if (waypoint == null) {
Log.d("No waypoint loaded to edit. id= " + id);
@@ -126,7 +126,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
if (own) {
initializeWaypointTypeSelector();
}
- } catch (RuntimeException e) {
+ } catch (final RuntimeException e) {
Log.e("EditWaypointActivity.loadWaypointHandler", e);
} finally {
if (waitDialog != null) {
@@ -138,7 +138,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
};
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState, R.layout.editwaypoint_activity);
if (StringUtils.isBlank(geocode) && id <= 0) {
@@ -159,11 +159,11 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
addWaypoint.setOnClickListener(new SaveWaypointListener());
- List<String> wayPointNames = new ArrayList<>();
- for (WaypointType wpt : WaypointType.ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL) {
+ final List<String> wayPointNames = new ArrayList<>();
+ for (final WaypointType wpt : WaypointType.ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL) {
wayPointNames.add(wpt.getL10n());
}
- ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, wayPointNames);
+ final ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, wayPointNames);
waypointName.setAdapter(adapter);
if (savedInstanceState != null) {
@@ -189,7 +189,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
disableSuggestions(distanceView);
}
- private void setCoordsModificationVisibility(IConnector con, Geocache cache) {
+ private void setCoordsModificationVisibility(final IConnector con, final Geocache cache) {
if (cache != null && (cache.getType() == CacheType.MYSTERY || cache.getType() == CacheType.MULTI)) {
coordinatesGroup.setVisibility(View.VISIBLE);
modifyBoth.setVisibility(con.supportsOwnCoordinates() ? View.VISIBLE : View.GONE);
@@ -205,18 +205,23 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
}
private void initializeWaypointTypeSelector() {
- ArrayAdapter<WaypointType> wpAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, POSSIBLE_WAYPOINT_TYPES.toArray(new WaypointType[POSSIBLE_WAYPOINT_TYPES.size()]));
+ final ArrayAdapter<WaypointType> wpAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, POSSIBLE_WAYPOINT_TYPES.toArray(new WaypointType[POSSIBLE_WAYPOINT_TYPES.size()]));
wpAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
waypointTypeSelector.setAdapter(wpAdapter);
waypointTypeSelector.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
- public void onItemSelected(AdapterView<?> parent, View v, int pos, long id) {
+ public void onItemSelected(final AdapterView<?> parent, final View v, final int pos, final long id) {
+ final String oldDefaultName = waypointTypeSelectorPosition >= 0 ? getDefaultWaypointName(POSSIBLE_WAYPOINT_TYPES.get(waypointTypeSelectorPosition)) : StringUtils.EMPTY;
waypointTypeSelectorPosition = pos;
+ final String currentName = waypointName.getText().toString().trim();
+ if (StringUtils.isBlank(currentName) || oldDefaultName.equals(currentName)) {
+ waypointName.setText(getDefaultWaypointName(getSelectedWaypointType()));
+ }
}
@Override
- public void onNothingSelected(AdapterView<?> parent) {
+ public void onNothingSelected(final AdapterView<?> parent) {
}
});
@@ -277,7 +282,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
waypoint = DataStore.loadWaypoint(id);
loadWaypointHandler.sendMessage(Message.obtain());
- } catch (Exception e) {
+ } catch (final Exception e) {
Log.e("EditWaypointActivity.loadWaypoint.run", e);
}
}
@@ -286,15 +291,15 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
private class CoordDialogListener implements View.OnClickListener {
@Override
- public void onClick(View arg0) {
+ public void onClick(final View arg0) {
Geopoint gp = null;
try {
gp = new Geopoint(buttonLat.getText().toString(), buttonLon.getText().toString());
- } catch (Geopoint.ParseException e) {
+ } catch (final Geopoint.ParseException e) {
// button text is blank when creating new waypoint
}
- Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS);
- CoordinatesInputDialog coordsDialog = CoordinatesInputDialog.getInstance(cache, gp, app.currentGeo());
+ final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS);
+ final CoordinatesInputDialog coordsDialog = CoordinatesInputDialog.getInstance(cache, gp, app.currentGeo());
coordsDialog.setCancelable(true);
coordsDialog.show(getSupportFragmentManager(),"wpeditdialog");
}
@@ -303,11 +308,43 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
}
@Override
- public void updateCoordinates(Geopoint gp) {
+ public void updateCoordinates(final Geopoint gp) {
buttonLat.setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE));
buttonLon.setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE));
}
+ /**
+ * Suffix the waypoint type with a running number to get a default name.
+ *
+ * @param type
+ * type to create a new default name for
+ *
+ * @return
+ */
+ private String getDefaultWaypointName(final WaypointType type) {
+ final ArrayList<String> wpNames = new ArrayList<>();
+ for (final Waypoint waypoint : cache.getWaypoints()) {
+ wpNames.add(waypoint.getName());
+ }
+ // try final and trailhead without index
+ if (type == WaypointType.FINAL || type == WaypointType.TRAILHEAD) {
+ if (!wpNames.contains(type.getL10n())) {
+ return type.getL10n();
+ }
+ }
+ // for other types add an index by default
+ int index = 1;
+ while (wpNames.contains(type.getL10n() + " " + index)) {
+ index++;
+ }
+ return type.getL10n() + " " + index;
+ }
+
+ private WaypointType getSelectedWaypointType() {
+ final int selectedTypeIndex = waypointTypeSelector.getSelectedItemPosition();
+ return selectedTypeIndex >= 0 ? POSSIBLE_WAYPOINT_TYPES.get(selectedTypeIndex) : waypoint.getWaypointType();
+ }
+
public static final int SUCCESS = 0;
public static final int UPLOAD_START = 1;
public static final int UPLOAD_ERROR = 2;
@@ -318,7 +355,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
private class SaveWaypointListener implements View.OnClickListener {
@Override
- public void onClick(View arg0) {
+ public void onClick(final View arg0) {
final String bearingText = bearing.getText().toString();
// combine distance from EditText and distanceUnit saved from Spinner
final String distanceText = distanceView.getText().toString() + distanceUnits.get(distanceUnitSelector.getSelectedItemPosition());
@@ -336,7 +373,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
if (StringUtils.isNotBlank(latText) && StringUtils.isNotBlank(lonText)) {
try {
coords = new Geopoint(latText, lonText);
- } catch (Geopoint.ParseException e) {
+ } catch (final Geopoint.ParseException e) {
showToast(res.getString(e.resource));
return;
}
@@ -354,7 +391,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
double bearing;
try {
bearing = Double.parseDouble(bearingText);
- } catch (NumberFormatException e) {
+ } catch (final NumberFormatException e) {
Dialogs.message(EditWaypointActivity.this, R.string.err_point_bear_and_dist_title, R.string.err_point_bear_and_dist);
return;
}
@@ -363,7 +400,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
try {
distance = DistanceParser.parseDistance(distanceText,
!Settings.isUseImperialUnits());
- } catch (NumberFormatException e) {
+ } catch (final NumberFormatException e) {
showToast(res.getString(R.string.err_parse_dist));
return;
}
@@ -371,19 +408,17 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
coords = coords.project(bearing, distance);
}
- // if no name is given, just give the waypoint its number as name
final String givenName = waypointName.getText().toString().trim();
- final String name = StringUtils.isNotEmpty(givenName) ? givenName : res.getString(R.string.waypoint) + " " + (wpCount + 1);
+ final String name = StringUtils.defaultIfBlank(givenName, getDefaultWaypointName(getSelectedWaypointType()));
final String noteText = note.getText().toString().trim();
final Geopoint coordsToSave = coords;
- final int selectedTypeIndex = waypointTypeSelector.getSelectedItemPosition();
- final WaypointType type = selectedTypeIndex >= 0 ? POSSIBLE_WAYPOINT_TYPES.get(selectedTypeIndex) : waypoint.getWaypointType();
+ final WaypointType type = getSelectedWaypointType();
final boolean visited = visitedCheckBox.isChecked();
final ProgressDialog progress = ProgressDialog.show(EditWaypointActivity.this, getString(R.string.cache), getString(R.string.waypoint_being_saved), true);
final Handler finishHandler = new Handler() {
@Override
- public void handleMessage(Message msg) {
+ public void handleMessage(final Message msg) {
// TODO: The order of showToast, progress.dismiss and finish is different in these cases. Why?
switch (msg.what) {
case UPLOAD_SUCCESS:
@@ -422,7 +457,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
class SaveWptTask extends AsyncTask<Void, Void, Void> {
@Override
- protected Void doInBackground(Void... params) {
+ protected Void doInBackground(final Void... params) {
final Waypoint waypoint = new Waypoint(name, type, own);
waypoint.setGeocode(geocode);
waypoint.setPrefix(prefix);
@@ -432,12 +467,12 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
waypoint.setVisited(visited);
waypoint.setId(id);
- Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS);
+ final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS);
if (cache == null) {
finishHandler.sendEmptyMessage(SAVE_ERROR);
return null;
}
- Waypoint oldWaypoint = cache.getWaypointById(id);
+ final Waypoint oldWaypoint = cache.getWaypointById(id);
if (cache.addOrChangeWaypoint(waypoint, true)) {
DataStore.saveCache(cache, EnumSet.of(SaveFlag.DB));
if (!StaticMapsProvider.hasAllStaticMapsForWaypoint(geocode, waypoint)) {
@@ -460,7 +495,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
finishHandler.sendEmptyMessage(UPLOAD_START);
if (cache.supportsOwnCoordinates()) {
- boolean result = uploadModifiedCoords(cache, waypoint.getCoords());
+ final boolean result = uploadModifiedCoords(cache, waypoint.getCoords());
finishHandler.sendEmptyMessage(result ? SUCCESS : UPLOAD_ERROR);
} else {
showToast(getString(R.string.waypoint_coordinates_couldnt_be_modified_on_website));
diff --git a/main/src/cgeo/geocaching/Geocache.java b/main/src/cgeo/geocaching/Geocache.java
index 19082d9..8bf64dc 100644
--- a/main/src/cgeo/geocaching/Geocache.java
+++ b/main/src/cgeo/geocaching/Geocache.java
@@ -52,14 +52,12 @@ import rx.functions.Action0;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.Parcelable;
import android.text.Html;
-import android.text.Html.ImageGetter;
import java.io.File;
import java.util.ArrayList;
@@ -70,7 +68,6 @@ import java.util.Date;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashSet;
-import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -161,10 +158,6 @@ public class Geocache implements ICache, IWaypoint {
private Handler changeNotificationHandler = null;
- // Images whose URL contains one of those patterns will not be available on the Images tab
- // for opening into an external application.
- private final static String[] NO_EXTERNAL = new String[]{"geocheck.org"};
-
/**
* Create a new cache. To be used everywhere except for the GPX parser
*/
@@ -453,11 +446,7 @@ public class Geocache implements ICache, IWaypoint {
ActivityMixin.showToast(fromActivity, fromActivity.getResources().getString(R.string.err_cannot_log_visit));
return;
}
- final Intent logVisitIntent = new Intent(fromActivity, LogCacheActivity.class);
- logVisitIntent.putExtra(LogCacheActivity.EXTRAS_ID, cacheId);
- logVisitIntent.putExtra(LogCacheActivity.EXTRAS_GEOCODE, geocode);
-
- fromActivity.startActivity(logVisitIntent);
+ fromActivity.startActivity(LogCacheActivity.getLogCacheIntent(fromActivity, cacheId, geocode));
}
public void logOffline(final Activity fromActivity, final LogType logType) {
@@ -534,7 +523,7 @@ public class Geocache implements ICache, IWaypoint {
}
public boolean supportsFavoritePoints() {
- return getConnector().supportsFavoritePoints();
+ return getConnector().supportsFavoritePoints(this);
}
public boolean supportsLogging() {
@@ -1521,7 +1510,6 @@ public class Geocache implements ICache, IWaypoint {
@Override
public void call() {
refreshSynchronous(handler);
- handler.sendEmptyMessage(CancellableHandler.DONE);
}
});
}
@@ -1612,7 +1600,7 @@ public class Geocache implements ICache, IWaypoint {
RxUtils.waitForCompletion(StaticMapsProvider.downloadMaps(cache), imgGetter.waitForEndObservable(handler));
if (handler != null) {
- handler.sendMessage(Message.obtain());
+ handler.sendEmptyMessage(CancellableHandler.DONE);
}
} catch (final Exception e) {
Log.e("Geocache.storeCache", e);
@@ -1702,23 +1690,6 @@ public class Geocache implements ICache, IWaypoint {
}
};
- private void addDescriptionImagesUrls(final Collection<Image> images) {
- final Set<String> urls = new LinkedHashSet<>();
- for (final Image image : images) {
- urls.add(image.getUrl());
- }
- Html.fromHtml(getDescription(), new ImageGetter() {
- @Override
- public Drawable getDrawable(final String source) {
- if (!urls.contains(source) && !ImageUtils.containsPattern(source, NO_EXTERNAL)) {
- images.add(new Image(source, geocode));
- urls.add(source);
- }
- return null;
- }
- }, null);
- }
-
public Collection<Image> getImages() {
final LinkedList<Image> result = new LinkedList<>();
result.addAll(getSpoilers());
@@ -1726,11 +1697,7 @@ public class Geocache implements ICache, IWaypoint {
for (final LogEntry log : getLogs()) {
result.addAll(log.getLogImages());
}
- final Set<String> urls = new HashSet<>(result.size());
- for (final Image image : result) {
- urls.add(image.getUrl());
- }
- addDescriptionImagesUrls(result);
+ ImageUtils.addImagesFromHtml(result, getDescription(), geocode);
return result;
}
diff --git a/main/src/cgeo/geocaching/Image.java b/main/src/cgeo/geocaching/Image.java
index 50ea80e..f592fc1 100644
--- a/main/src/cgeo/geocaching/Image.java
+++ b/main/src/cgeo/geocaching/Image.java
@@ -1,10 +1,13 @@
package cgeo.geocaching;
+import cgeo.geocaching.activity.ActivityMixin;
import cgeo.geocaching.utils.FileUtils;
+import cgeo.geocaching.utils.Log;
import org.apache.commons.lang3.StringUtils;
-import android.content.Context;
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Parcel;
@@ -73,12 +76,17 @@ public class Image implements Parcelable {
return description;
}
- public void openInBrowser(final Context fromActivity) {
+ public void openInBrowser(final Activity fromActivity) {
if (StringUtils.isBlank(url)) {
return;
}
final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
- fromActivity.startActivity(browserIntent);
+ try {
+ fromActivity.startActivity(browserIntent);
+ } catch (final ActivityNotFoundException e) {
+ Log.e("Cannot find suitable activity", e);
+ ActivityMixin.showToast(fromActivity, R.string.err_application_no);
+ }
}
@Override
diff --git a/main/src/cgeo/geocaching/ImageSelectActivity.java b/main/src/cgeo/geocaching/ImageSelectActivity.java
index a64b4cf..3b4039e 100644
--- a/main/src/cgeo/geocaching/ImageSelectActivity.java
+++ b/main/src/cgeo/geocaching/ImageSelectActivity.java
@@ -48,11 +48,6 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
@InjectView(R.id.cancel) protected Button clearButton;
@InjectView(R.id.image_preview) protected ImageView imagePreview;
- static final String EXTRAS_CAPTION = "caption";
- static final String EXTRAS_DESCRIPTION = "description";
- static final String EXTRAS_URI_AS_STRING = "uri";
- static final String EXTRAS_SCALE = "scale";
-
private static final String SAVED_STATE_IMAGE_CAPTION = "cgeo.geocaching.saved_state_image_caption";
private static final String SAVED_STATE_IMAGE_DESCRIPTION = "cgeo.geocaching.saved_state_image_description";
private static final String SAVED_STATE_IMAGE_URI = "cgeo.geocaching.saved_state_image_uri";
@@ -80,10 +75,10 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
// Get parameters from intent and basic cache information from database
final Bundle extras = getIntent().getExtras();
if (extras != null) {
- imageCaption = extras.getString(EXTRAS_CAPTION);
- imageDescription = extras.getString(EXTRAS_DESCRIPTION);
- imageUri = Uri.parse(extras.getString(EXTRAS_URI_AS_STRING));
- scaleChoiceIndex = extras.getInt(EXTRAS_SCALE, scaleChoiceIndex);
+ imageCaption = extras.getString(Intents.EXTRA_CAPTION);
+ imageDescription = extras.getString(Intents.EXTRA_DESCRIPTION);
+ imageUri = Uri.parse(extras.getString(Intents.EXTRA_URI_AS_STRING));
+ scaleChoiceIndex = extras.getInt(Intents.EXTRA_SCALE, scaleChoiceIndex);
}
// Restore previous state
@@ -97,7 +92,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
cameraButton.setOnClickListener(new View.OnClickListener() {
@Override
- public void onClick(View view) {
+ public void onClick(final View view) {
selectImageFromCamera();
}
});
@@ -105,7 +100,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
storedButton.setOnClickListener(new View.OnClickListener() {
@Override
- public void onClick(View view) {
+ public void onClick(final View view) {
selectImageFromStorage();
}
});
@@ -123,20 +118,20 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
scaleView.setSelection(scaleChoiceIndex);
scaleView.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
- public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
+ public void onItemSelected(final AdapterView<?> arg0, final View arg1, final int arg2, final long arg3) {
scaleChoiceIndex = scaleView.getSelectedItemPosition();
Settings.setLogImageScale(scaleChoiceIndex);
}
@Override
- public void onNothingSelected(AdapterView<?> arg0) {
+ public void onNothingSelected(final AdapterView<?> arg0) {
}
});
saveButton.setOnClickListener(new View.OnClickListener() {
@Override
- public void onClick(View v) {
+ public void onClick(final View v) {
saveImageInfo(true);
}
});
@@ -144,7 +139,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
clearButton.setOnClickListener(new View.OnClickListener() {
@Override
- public void onClick(View v) {
+ public void onClick(final View v) {
saveImageInfo(false);
}
});
@@ -162,17 +157,17 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
outState.putInt(SAVED_STATE_IMAGE_SCALE, scaleChoiceIndex);
}
- public void saveImageInfo(boolean saveInfo) {
+ public void saveImageInfo(final boolean saveInfo) {
if (saveInfo) {
final String filename = writeScaledImage(imageUri.getPath());
if (filename != null) {
imageUri = Uri.parse(filename);
final Intent intent = new Intent();
syncEditTexts();
- intent.putExtra(EXTRAS_CAPTION, imageCaption);
- intent.putExtra(EXTRAS_DESCRIPTION, imageDescription);
- intent.putExtra(EXTRAS_URI_AS_STRING, imageUri.toString());
- intent.putExtra(EXTRAS_SCALE, scaleChoiceIndex);
+ intent.putExtra(Intents.EXTRA_CAPTION, imageCaption);
+ intent.putExtra(Intents.EXTRA_DESCRIPTION, imageDescription);
+ intent.putExtra(Intents.EXTRA_URI_AS_STRING, imageUri.toString());
+ intent.putExtra(Intents.EXTRA_SCALE, scaleChoiceIndex);
setResult(RESULT_OK, intent);
} else {
showToast(res.getString(R.string.err_select_logimage_failed));
@@ -193,7 +188,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
private void selectImageFromCamera() {
// create Intent to take a picture and return control to the calling application
- Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ final Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
imageUri = ImageUtils.getOutputImageFileUri(); // create a file to save the image
if (imageUri == null) {
@@ -207,14 +202,14 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
}
private void selectImageFromStorage() {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/jpeg");
startActivityForResult(Intent.createChooser(intent, "Select Image"), SELECT_STORED_IMAGE);
}
@Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
if (resultCode == RESULT_CANCELED) {
// User cancelled the image capture
showToast(getResources().getString(R.string.info_select_logimage_cancelled));
@@ -232,7 +227,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
if (data != null) {
final Uri selectedImage = data.getData();
if (Build.VERSION.SDK_INT < VERSION_CODES.KITKAT) {
- String[] filePathColumn = { MediaColumns.DATA };
+ final String[] filePathColumn = { MediaColumns.DATA };
Cursor cursor = null;
try {
@@ -243,14 +238,14 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
}
cursor.moveToFirst();
- int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
- String filePath = cursor.getString(columnIndex);
+ final int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
+ final String filePath = cursor.getString(columnIndex);
if (StringUtils.isBlank(filePath)) {
showFailure();
return;
}
imageUri = Uri.parse(filePath);
- } catch (Exception e) {
+ } catch (final Exception e) {
Log.e("ImageSelectActivity.onActivityResult", e);
showFailure();
} finally {
@@ -269,7 +264,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
output = new FileOutputStream(outputFile);
LocalStorage.copy(input, output);
imageUri = Uri.fromFile(outputFile);
- } catch (FileNotFoundException e) {
+ } catch (final FileNotFoundException e) {
Log.e("ImageSelectActivity.onStartResult", e);
} finally {
IOUtils.closeQuietly(input);
diff --git a/main/src/cgeo/geocaching/Intents.java b/main/src/cgeo/geocaching/Intents.java
index a55c22a..e2b204e 100644
--- a/main/src/cgeo/geocaching/Intents.java
+++ b/main/src/cgeo/geocaching/Intents.java
@@ -1,5 +1,13 @@
package cgeo.geocaching;
+import cgeo.geocaching.enumerations.CacheListType;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+
+import android.content.Intent;
+import android.os.Bundle;
+
public class Intents {
private Intents() {
@@ -18,7 +26,22 @@ public class Intents {
public static final String EXTRA_KEYWORD = PREFIX + "keyword";
public static final String EXTRA_KEYWORD_SEARCH = PREFIX + "keyword_search";
public static final String EXTRA_LIST_ID = PREFIX + "list_id";
- public static final String EXTRA_LIST_TYPE = PREFIX + "list_type";
+ public static final String EXTRA_CAPTION = PREFIX + "caption";
+ public static final String EXTRA_DESCRIPTION = PREFIX + "description";
+ public static final String EXTRA_URI_AS_STRING = PREFIX + "uri";
+ public static final String EXTRA_SCALE = PREFIX + "scale";
+
+ public static final String EXTRA_WPTTYPE = PREFIX + "wpttype";
+ public static final String EXTRA_MAPSTATE = PREFIX + "mapstate";
+ public static final String EXTRA_MAP_TITLE = PREFIX + "mapTitle";
+ public static final String EXTRA_MAP_MODE = PREFIX + "mapMode";
+ public static final String EXTRA_LIVE_ENABLED = PREFIX + "liveEnabled";
+
+ /**
+ * list type to be used with the cache list activity. Be aware to use the String representation of the corresponding
+ * enum.
+ */
+ private static final String EXTRA_LIST_TYPE = PREFIX + "list_type";
public static final String EXTRA_MAP_FILE = PREFIX + "map_file";
public static final String EXTRA_NAME = PREFIX + "name";
public static final String EXTRA_SEARCH = PREFIX + "search";
@@ -49,4 +72,27 @@ public class Intents {
public static final String EXTRA_OAUTH_TEMP_TOKEN_SECRET_PREF = PREFIX_OAUTH + "tempSecretPref";
public static final String EXTRA_OAUTH_TOKEN_PUBLIC_KEY = PREFIX_OAUTH + "publicTokenPref";
public static final String EXTRA_OAUTH_TOKEN_SECRET_KEY = PREFIX_OAUTH + "secretTokenPref";
+
+ public static Intent putListType(final Intent intent, final @NonNull CacheListType listType) {
+ intent.putExtra(Intents.EXTRA_LIST_TYPE, listType.name());
+ return intent;
+ }
+
+ public static @NonNull CacheListType getListType(final Intent intent) {
+ final Bundle extras = intent.getExtras();
+ if (extras == null) {
+ return CacheListType.OFFLINE;
+ }
+ final String typeName = extras.getString(Intents.EXTRA_LIST_TYPE);
+ if (StringUtils.isBlank(typeName)) {
+ return CacheListType.OFFLINE;
+ }
+ CacheListType listType;
+ try {
+ listType = CacheListType.valueOf(typeName);
+ } catch (final IllegalArgumentException e) {
+ return CacheListType.OFFLINE;
+ }
+ return (listType != null) ? listType : CacheListType.OFFLINE;
+ }
}
diff --git a/main/src/cgeo/geocaching/LogCacheActivity.java b/main/src/cgeo/geocaching/LogCacheActivity.java
index 75d4c2c..3558b3c 100644
--- a/main/src/cgeo/geocaching/LogCacheActivity.java
+++ b/main/src/cgeo/geocaching/LogCacheActivity.java
@@ -2,6 +2,7 @@ package cgeo.geocaching;
import butterknife.ButterKnife;
+import cgeo.geocaching.activity.ShowcaseViewBuilder;
import cgeo.geocaching.connector.ILoggingManager;
import cgeo.geocaching.connector.ImageResult;
import cgeo.geocaching.connector.LogResult;
@@ -21,6 +22,8 @@ import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.LogTemplateProvider;
import cgeo.geocaching.utils.LogTemplateProvider.LogContext;
+import com.github.amlcurran.showcaseview.targets.ActionItemTarget;
+
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
@@ -51,8 +54,6 @@ import java.util.Date;
import java.util.List;
public class LogCacheActivity extends AbstractLoggingActivity implements DateDialog.DateDialogParent {
- static final String EXTRAS_GEOCODE = "geocode";
- static final String EXTRAS_ID = "id";
private static final String SAVED_STATE_RATING = "cgeo.geocaching.saved_state_rating";
private static final String SAVED_STATE_TYPE = "cgeo.geocaching.saved_state_type";
@@ -196,7 +197,6 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
private void enablePostButton(final boolean enabled) {
sendButtonEnabled = enabled;
- invalidateOptionsMenuCompatible();
}
@Override
@@ -206,9 +206,9 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
// Get parameters from intent and basic cache information from database
final Bundle extras = getIntent().getExtras();
if (extras != null) {
- geocode = extras.getString(EXTRAS_GEOCODE);
+ geocode = extras.getString(Intents.EXTRA_GEOCODE);
if (StringUtils.isBlank(geocode)) {
- final String cacheid = extras.getString(EXTRAS_ID);
+ final String cacheid = extras.getString(Intents.EXTRA_ID);
if (StringUtils.isNotBlank(cacheid)) {
geocode = DataStore.getGeocodeForGuid(cacheid);
}
@@ -422,7 +422,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
if (logResult.getPostLogResult() == StatusCode.NO_ERROR) {
// update geocache in DB
- if (typeSelected == LogType.FOUND_IT || typeSelected == LogType.ATTENDED || typeSelected == LogType.WEBCAM_PHOTO_TAKEN) {
+ if (typeSelected.isFoundLog()) {
cache.setFound(true);
cache.setVisitedDate(new Date().getTime());
}
@@ -577,9 +577,9 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
private void selectImage() {
final Intent selectImageIntent = new Intent(this, ImageSelectActivity.class);
- selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_CAPTION, imageCaption);
- selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_DESCRIPTION, imageDescription);
- selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_URI_AS_STRING, imageUri.toString());
+ selectImageIntent.putExtra(Intents.EXTRA_CAPTION, imageCaption);
+ selectImageIntent.putExtra(Intents.EXTRA_DESCRIPTION, imageDescription);
+ selectImageIntent.putExtra(Intents.EXTRA_URI_AS_STRING, imageUri.toString());
startActivityForResult(selectImageIntent, SELECT_IMAGE);
}
@@ -588,9 +588,9 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
if (requestCode == SELECT_IMAGE) {
if (resultCode == RESULT_OK) {
- imageCaption = data.getStringExtra(ImageSelectActivity.EXTRAS_CAPTION);
- imageDescription = data.getStringExtra(ImageSelectActivity.EXTRAS_DESCRIPTION);
- imageUri = Uri.parse(data.getStringExtra(ImageSelectActivity.EXTRAS_URI_AS_STRING));
+ imageCaption = data.getStringExtra(Intents.EXTRA_CAPTION);
+ imageDescription = data.getStringExtra(Intents.EXTRA_DESCRIPTION);
+ imageUri = Uri.parse(data.getStringExtra(Intents.EXTRA_URI_AS_STRING));
} else if (resultCode != RESULT_CANCELED) {
// Image capture failed, advise user
showToast(getResources().getString(R.string.err_select_logimage_failed));
@@ -602,7 +602,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_send:
- sendLog();
+ sendLogAndConfirm();
return true;
case R.id.menu_image:
selectImage();
@@ -621,25 +621,53 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
return super.onOptionsItemSelected(item);
}
- private void sendLog() {
+ private void sendLogAndConfirm() {
if (!sendButtonEnabled) {
Dialogs.message(this, R.string.log_post_not_possible);
+ return;
+ }
+ if (typeSelected.mustConfirmLog()) {
+ Dialogs.confirm(this, R.string.confirm_log_title, res.getString(R.string.confirm_log_message, typeSelected.getL10n()), new OnClickListener() {
+
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ sendLogInternal();
+ }
+ });
}
else {
- final String message = res.getString(StringUtils.isBlank(imageUri.getPath()) ?
- R.string.log_saving :
- R.string.log_saving_and_uploading);
- new Poster(this, message).execute(currentLogText(), currentLogPassword());
+ sendLogInternal();
}
}
+ private void sendLogInternal() {
+ final String message = res.getString(StringUtils.isBlank(imageUri.getPath()) ?
+ R.string.log_saving :
+ R.string.log_saving_and_uploading);
+ new Poster(this, message).execute(currentLogText(), currentLogPassword());
+ }
+
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
super.onCreateOptionsMenu(menu);
menu.findItem(R.id.menu_image).setVisible(cache.supportsLogImages());
menu.findItem(R.id.save).setVisible(true);
menu.findItem(R.id.clear).setVisible(true);
+ presentShowcase();
return true;
}
+ @Override
+ public ShowcaseViewBuilder getShowcase() {
+ return new ShowcaseViewBuilder(this)
+ .setTarget(new ActionItemTarget(this, R.id.menu_send))
+ .setContent(R.string.showcase_logcache_title, R.string.showcase_logcache_text);
+ }
+
+ public static Intent getLogCacheIntent(final Activity context, final String cacheId, final String geocode) {
+ final Intent logVisitIntent = new Intent(context, LogCacheActivity.class);
+ logVisitIntent.putExtra(Intents.EXTRA_ID, cacheId);
+ logVisitIntent.putExtra(Intents.EXTRA_GEOCODE, geocode);
+ return logVisitIntent;
+ }
}
diff --git a/main/src/cgeo/geocaching/LogTrackableActivity.java b/main/src/cgeo/geocaching/LogTrackableActivity.java
index 5970210..5c6d0f5 100644
--- a/main/src/cgeo/geocaching/LogTrackableActivity.java
+++ b/main/src/cgeo/geocaching/LogTrackableActivity.java
@@ -53,6 +53,9 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat
private String guid = null;
private String geocode = null;
private String[] viewstates = null;
+ /**
+ * As long as we still fetch the current state of the trackable from the Internet, the user cannot yet send a log.
+ */
private boolean gettingViewstate = true;
private Calendar date = Calendar.getInstance();
private LogType typeSelected = LogType.getById(Settings.getTrackableAction());
@@ -88,7 +91,6 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat
}
gettingViewstate = false; // we're done, user can post log
- setLoggingEnabled(true);
showProgress(false);
}
@@ -211,10 +213,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat
}
if (GCLogin.isEmpty(viewstates)) {
- setLoggingEnabled(false);
new LoadDataThread().start();
- } else {
- setLoggingEnabled(true);
}
disableSuggestions(trackingEditText);
}
diff --git a/main/src/cgeo/geocaching/MainActivity.java b/main/src/cgeo/geocaching/MainActivity.java
index 3723116..40504fb 100644
--- a/main/src/cgeo/geocaching/MainActivity.java
+++ b/main/src/cgeo/geocaching/MainActivity.java
@@ -4,6 +4,7 @@ import butterknife.ButterKnife;
import butterknife.InjectView;
import cgeo.geocaching.activity.AbstractActionBarActivity;
+import cgeo.geocaching.activity.ShowcaseViewBuilder;
import cgeo.geocaching.connector.ConnectorFactory;
import cgeo.geocaching.connector.capability.ILogin;
import cgeo.geocaching.enumerations.CacheType;
@@ -27,6 +28,7 @@ import cgeo.geocaching.utils.RxUtils;
import cgeo.geocaching.utils.TextUtils;
import cgeo.geocaching.utils.Version;
+import com.github.amlcurran.showcaseview.targets.ActionViewTarget;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
@@ -211,6 +213,9 @@ public class MainActivity extends AbstractActionBarActivity {
super.onResume(locationUpdater.start(GeoDirHandler.UPDATE_GEODATA | GeoDirHandler.LOW_POWER),
app.gpsStatusObservable().observeOn(AndroidSchedulers.mainThread()).subscribe(satellitesHandler));
updateUserInfoHandler.sendEmptyMessage(-1);
+ if (app.hasValidLocation()) {
+ locationUpdater.updateGeoData(app.currentGeo());
+ }
startBackgroundLogin();
init();
}
@@ -264,7 +269,7 @@ public class MainActivity extends AbstractActionBarActivity {
final MenuItem searchItem = menu.findItem(R.id.menu_gosearch);
final SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
-
+ presentShowcase();
return true;
}
@@ -293,7 +298,7 @@ public class MainActivity extends AbstractActionBarActivity {
startActivity(new Intent(this, SettingsActivity.class));
return true;
case R.id.menu_history:
- CacheListActivity.startActivityHistory(this);
+ startActivity(CacheListActivity.getHistoryIntent(this));
return true;
case R.id.menu_scan:
startScannerApplication();
@@ -356,8 +361,6 @@ public class MainActivity extends AbstractActionBarActivity {
initialized = true;
- Settings.setLanguage(Settings.isUseEnglish());
-
findOnMap.setClickable(true);
findOnMap.setOnClickListener(new OnClickListener() {
@Override
@@ -558,7 +561,7 @@ public class MainActivity extends AbstractActionBarActivity {
}
}
});
- AndroidObservable.bindActivity(MainActivity.this, address.onErrorResumeNext(Observable.from(geo.getCoords().toString())))
+ AndroidObservable.bindActivity(MainActivity.this, address.onErrorResumeNext(Observable.just(geo.getCoords().toString())))
.subscribeOn(RxUtils.networkScheduler)
.subscribe(new Action1<String>() {
@Override
@@ -579,7 +582,7 @@ public class MainActivity extends AbstractActionBarActivity {
*/
public void cgeoFindOnMap(final View v) {
findOnMap.setPressed(true);
- CGeoMap.startActivityLiveMap(this);
+ startActivity(CGeoMap.getLiveMapIntent(this));
}
/**
@@ -592,7 +595,7 @@ public class MainActivity extends AbstractActionBarActivity {
}
nearestView.setPressed(true);
- CacheListActivity.startActivityNearest(this, app.currentGeo().getCoords());
+ startActivity(CacheListActivity.getNearestIntent(this));
}
/**
@@ -735,4 +738,11 @@ public class MainActivity extends AbstractActionBarActivity {
public void showAbout(final View view) {
startActivity(new Intent(this, AboutActivity.class));
}
+
+ @Override
+ public ShowcaseViewBuilder getShowcase() {
+ return new ShowcaseViewBuilder(this)
+ .setTarget(new ActionViewTarget(this, ActionViewTarget.Type.OVERFLOW))
+ .setContent(R.string.showcase_main_title, R.string.showcase_main_text);
+ }
}
diff --git a/main/src/cgeo/geocaching/SearchActivity.java b/main/src/cgeo/geocaching/SearchActivity.java
index 81dec98..edd611a 100644
--- a/main/src/cgeo/geocaching/SearchActivity.java
+++ b/main/src/cgeo/geocaching/SearchActivity.java
@@ -4,6 +4,7 @@ import butterknife.ButterKnife;
import butterknife.InjectView;
import cgeo.geocaching.activity.AbstractActionBarActivity;
+import cgeo.geocaching.activity.ShowcaseViewBuilder;
import cgeo.geocaching.connector.ConnectorFactory;
import cgeo.geocaching.connector.IConnector;
import cgeo.geocaching.connector.capability.ISearchByGeocode;
@@ -177,14 +178,14 @@ public class SearchActivity extends AbstractActionBarActivity implements Coordin
buttonLatitude.setOnClickListener(new OnClickListener() {
@Override
- public void onClick(View v) {
+ public void onClick(final View v) {
updateCoordinates();
}
});
buttonLongitude.setOnClickListener(new OnClickListener() {
@Override
- public void onClick(View v) {
+ public void onClick(final View v) {
updateCoordinates();
}
});
@@ -398,6 +399,7 @@ public class SearchActivity extends AbstractActionBarActivity implements Coordin
@Override
public final boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.search_activity_options, menu);
+ presentShowcase();
return true;
}
@@ -417,4 +419,13 @@ public class SearchActivity extends AbstractActionBarActivity implements Coordin
putExtra(Intents.EXTRA_KEYWORD_SEARCH, false);
fromActivity.startActivityForResult(searchIntent, MainActivity.SEARCH_REQUEST_CODE);
}
+
+ @Override
+ public ShowcaseViewBuilder getShowcase() {
+ // The showcase doesn't work well with the search activity, because on searching a geocode (or
+ // selecting a cache from the search field) we immediately close the activity. That in turn confuses the delayed
+ // creation of the showcase bitmap. To avoid someone running into this issue again, this method explicitly overrides
+ // the parent method with the same implementation.
+ return null;
+ }
}
diff --git a/main/src/cgeo/geocaching/SearchResult.java b/main/src/cgeo/geocaching/SearchResult.java
index 74cc59d..6015872 100644
--- a/main/src/cgeo/geocaching/SearchResult.java
+++ b/main/src/cgeo/geocaching/SearchResult.java
@@ -320,7 +320,7 @@ public class SearchResult implements Parcelable {
return cObservable.flatMap(new Func1<C, Observable<? extends SearchResult>>() {
@Override
public Observable<? extends SearchResult> call(final C c) {
- return c.isActive() ? Observable.from(func.call(c)) : Observable.<SearchResult>empty();
+ return c.isActive() ? Observable.just(func.call(c)) : Observable.<SearchResult>empty();
}
});
}
diff --git a/main/src/cgeo/geocaching/StatusFragment.java b/main/src/cgeo/geocaching/StatusFragment.java
index a228363..3fd4434 100644
--- a/main/src/cgeo/geocaching/StatusFragment.java
+++ b/main/src/cgeo/geocaching/StatusFragment.java
@@ -9,7 +9,6 @@ import cgeo.geocaching.utils.Log;
import rx.Subscription;
import rx.android.observables.AndroidObservable;
import rx.functions.Action1;
-import rx.schedulers.Schedulers;
import rx.subscriptions.Subscriptions;
import android.content.Intent;
@@ -34,7 +33,7 @@ public class StatusFragment extends Fragment {
final ViewGroup statusGroup = (ViewGroup) inflater.inflate(R.layout.status, container, false);
final ImageView statusIcon = ButterKnife.findById(statusGroup, R.id.status_icon);
final TextView statusMessage = ButterKnife.findById(statusGroup, R.id.status_message);
- statusSubscription = AndroidObservable.bindFragment(this, StatusUpdater.latestStatus).subscribeOn(Schedulers.io())
+ statusSubscription = AndroidObservable.bindFragment(this, StatusUpdater.latestStatus)
.subscribe(new Action1<Status>() {
@Override
public void call(final Status status) {
diff --git a/main/src/cgeo/geocaching/Trackable.java b/main/src/cgeo/geocaching/Trackable.java
index 9c2b044..fe53109 100644
--- a/main/src/cgeo/geocaching/Trackable.java
+++ b/main/src/cgeo/geocaching/Trackable.java
@@ -3,13 +3,16 @@ package cgeo.geocaching;
import cgeo.geocaching.connector.ConnectorFactory;
import cgeo.geocaching.connector.trackable.TrackableConnector;
import cgeo.geocaching.enumerations.LogType;
+import cgeo.geocaching.utils.ImageUtils;
import org.apache.commons.lang3.StringUtils;
import android.text.Html;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Date;
+import java.util.LinkedList;
import java.util.List;
public class Trackable implements ILogable {
@@ -214,6 +217,18 @@ public class Trackable implements ILogable {
this.trackingcode = trackingcode;
}
+ public Collection<Image> getImages() {
+ final List<Image> images = new LinkedList<>();
+ if (StringUtils.isNotBlank(image)) {
+ images.add(new Image(image, StringUtils.defaultIfBlank(name, geocode)));
+ }
+ ImageUtils.addImagesFromHtml(images, getDetails(), geocode);
+ for (final LogEntry log : getLogs()) {
+ images.addAll(log.getLogImages());
+ }
+ return images;
+ }
+
static public List<LogType> getPossibleLogTypes() {
final List<LogType> logTypes = new ArrayList<>();
logTypes.add(LogType.RETRIEVED_IT);
diff --git a/main/src/cgeo/geocaching/TrackableActivity.java b/main/src/cgeo/geocaching/TrackableActivity.java
index dadc37f..81516c3 100644
--- a/main/src/cgeo/geocaching/TrackableActivity.java
+++ b/main/src/cgeo/geocaching/TrackableActivity.java
@@ -4,6 +4,7 @@ import butterknife.ButterKnife;
import butterknife.InjectView;
import cgeo.geocaching.activity.AbstractActivity;
+import cgeo.geocaching.activity.AbstractActivity.ActivitySharingInterface;
import cgeo.geocaching.activity.AbstractViewPagerActivity;
import cgeo.geocaching.connector.ConnectorFactory;
import cgeo.geocaching.connector.trackable.TrackableConnector;
@@ -14,29 +15,33 @@ import cgeo.geocaching.network.HtmlImage;
import cgeo.geocaching.ui.AbstractCachingPageViewCreator;
import cgeo.geocaching.ui.AnchorAwareLinkMovementMethod;
import cgeo.geocaching.ui.CacheDetailsCreator;
+import cgeo.geocaching.ui.ImagesList;
import cgeo.geocaching.ui.UserActionsClickListener;
import cgeo.geocaching.ui.UserNameClickListener;
import cgeo.geocaching.ui.logs.TrackableLogsViewCreator;
import cgeo.geocaching.utils.Formatter;
import cgeo.geocaching.utils.HtmlUtils;
import cgeo.geocaching.utils.Log;
+import cgeo.geocaching.utils.RxUtils;
import cgeo.geocaching.utils.UnknownTagsHandler;
+import org.apache.commons.collections4.CollectionUtils;
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.android.observables.AndroidObservable;
import rx.android.observables.ViewObservable;
import rx.functions.Action1;
+import rx.functions.Func0;
+import rx.subscriptions.CompositeSubscription;
import android.app.ProgressDialog;
import android.content.Intent;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
import android.support.v7.app.ActionBar;
import android.support.v7.view.ActionMode;
import android.text.Html;
@@ -56,11 +61,14 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
-public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivity.Page> {
+public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivity.Page> implements ActivitySharingInterface {
+
+ private CompositeSubscription createSubscriptions;
public enum Page {
DETAILS(R.string.detail),
- LOGS(R.string.cache_logs);
+ LOGS(R.string.cache_logs),
+ IMAGES(R.string.cache_images);
private final int resId;
@@ -76,59 +84,9 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
private String id = null;
private LayoutInflater inflater = null;
private ProgressDialog waitDialog = null;
- private final Handler loadTrackableHandler = new Handler() {
-
- @Override
- public void handleMessage(final Message msg) {
- if (trackable == null) {
- if (waitDialog != null) {
- waitDialog.dismiss();
- }
-
- if (StringUtils.isNotBlank(geocode)) {
- showToast(res.getString(R.string.err_tb_find) + " " + geocode + ".");
- } else {
- showToast(res.getString(R.string.err_tb_find_that));
- }
-
- finish();
- return;
- }
-
- try {
- inflater = getLayoutInflater();
- geocode = trackable.getGeocode();
-
- if (StringUtils.isNotBlank(trackable.getName())) {
- setTitle(Html.fromHtml(trackable.getName()).toString());
- } else {
- setTitle(trackable.getName());
- }
-
- invalidateOptionsMenuCompatible();
- reinitializeViewPager();
-
- } catch (final Exception e) {
- Log.e("TrackableActivity.loadTrackableHandler: ", e);
- }
-
- if (waitDialog != null) {
- waitDialog.dismiss();
- }
-
- // if we have a newer Android device setup Android Beam for easy cache sharing
- initializeAndroidBeam(
- new ActivitySharingInterface() {
- @Override
- public String getUri() {
- return trackable.getUrl();
- }
- }
- );
- }
- };
-
private CharSequence clickedItemText = null;
+ private ImagesList imagesList = null;
+
/**
* Action mode of the current contextual action bar (e.g. for copy and share actions).
*/
@@ -209,11 +167,33 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
} else {
message = res.getString(R.string.trackable);
}
+
+ // If we have a newer Android device setup Android Beam for easy cache sharing
+ initializeAndroidBeam(this);
+
+ createViewPager(0, new OnPageSelectedListener() {
+ @Override
+ public void onPageSelected(final int position) {
+ // Lazy loading of trackable images
+ if (getPage(position) == Page.IMAGES) {
+ loadTrackableImages();
+ }
+ }
+ });
waitDialog = ProgressDialog.show(this, message, res.getString(R.string.trackable_details_loading), true, true);
+ createSubscriptions = new CompositeSubscription();
+ createSubscriptions.add(AndroidObservable.bindActivity(this, loadTrackable(geocode, guid, id)).singleOrDefault(null).subscribe(new Action1<Trackable>() {
+ @Override
+ public void call(final Trackable trackable) {
+ TrackableActivity.this.trackable = trackable;
+ displayTrackable();
+ }
+ }));
+ }
- createViewPager(0, null);
- final LoadTrackableThread thread = new LoadTrackableThread(loadTrackableHandler, geocode, guid, id);
- thread.start();
+ @Override
+ public String getAndroidBeamUri() {
+ return trackable != null ? trackable.getUrl() : null;
}
@Override
@@ -244,87 +224,85 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
return super.onPrepareOptionsMenu(menu);
}
- private class LoadTrackableThread extends Thread {
- final private Handler handler;
- final private String geocode;
- final private String guid;
- final private String id;
-
- public LoadTrackableThread(final Handler handlerIn, final String geocodeIn, final String guidIn, final String idIn) {
- handler = handlerIn;
- geocode = geocodeIn;
- guid = guidIn;
- id = idIn;
- }
-
- @Override
- public void run() {
- if (StringUtils.isNotEmpty(geocode)) {
-
- // iterate over the connectors as some codes may be handled by multiple connectors
- for (final TrackableConnector trackableConnector : ConnectorFactory.getTrackableConnectors()) {
- if (trackableConnector.canHandleTrackable(geocode)) {
- trackable = trackableConnector.searchTrackable(geocode, guid, id);
- if (trackable != null) {
- break;
+ private static Observable<Trackable> loadTrackable(final String geocode, final String guid, final String id) {
+ return Observable.defer(new Func0<Observable<Trackable>>() {
+ @Override
+ public Observable<Trackable> call() {
+ if (StringUtils.isNotEmpty(geocode)) {
+ // iterate over the connectors as some codes may be handled by multiple connectors
+ for (final TrackableConnector trackableConnector : ConnectorFactory.getTrackableConnectors()) {
+ if (trackableConnector.canHandleTrackable(geocode)) {
+ final Trackable trackable = trackableConnector.searchTrackable(geocode, guid, id);
+ if (trackable != null) {
+ return Observable.just(trackable);
+ }
}
}
+ // Check local storage (offline case)
+ final Trackable trackable = DataStore.loadTrackable(geocode);
+ if (trackable != null) {
+ return Observable.just(trackable);
+ }
}
- // Check local storage (offline case)
- if (trackable == null) {
- trackable = DataStore.loadTrackable(geocode);
- }
- }
- // fall back to GC search by GUID
- if (trackable == null) {
- trackable = TravelBugConnector.getInstance().searchTrackable(geocode, guid, id);
+
+ // Fall back to GC search by GUID
+ final Trackable trackable = TravelBugConnector.getInstance().searchTrackable(geocode, guid, id);
+ return trackable != null ? Observable.just(trackable) : Observable.<Trackable>empty();
}
- handler.sendMessage(Message.obtain());
- }
+ }).subscribeOn(RxUtils.networkScheduler);
}
- private class TrackableIconThread extends Thread {
- final private String url;
- final private Handler handler;
+ public void displayTrackable() {
+ if (trackable == null) {
+ if (waitDialog != null) {
+ waitDialog.dismiss();
+ }
+
+ if (StringUtils.isNotBlank(geocode)) {
+ showToast(res.getString(R.string.err_tb_find) + " " + geocode + ".");
+ } else {
+ showToast(res.getString(R.string.err_tb_find_that));
+ }
- public TrackableIconThread(final String urlIn, final Handler handlerIn) {
- url = urlIn;
- handler = handlerIn;
+ finish();
+ return;
}
- @Override
- public void run() {
- if (url == null || handler == null) {
- return;
+ try {
+ inflater = getLayoutInflater();
+ geocode = trackable.getGeocode();
+
+ if (StringUtils.isNotBlank(trackable.getName())) {
+ setTitle(Html.fromHtml(trackable.getName()).toString());
+ } else {
+ setTitle(trackable.getName());
}
- try {
- final HtmlImage imgGetter = new HtmlImage(trackable.getGeocode(), false, 0, false);
+ invalidateOptionsMenuCompatible();
+ reinitializeViewPager();
- final BitmapDrawable image = imgGetter.getDrawable(url);
- final Message message = handler.obtainMessage(0, image);
- handler.sendMessage(message);
- } catch (final Exception e) {
- Log.e("TrackableActivity.TrackableIconThread.run: ", e);
- }
+ } catch (final Exception e) {
+ Log.e("TrackableActivity.loadTrackableHandler: ", e);
}
- }
-
- private static class TrackableIconHandler extends Handler {
- final private ActionBar view;
- public TrackableIconHandler(final ActionBar viewIn) {
- view = viewIn;
+ if (waitDialog != null) {
+ waitDialog.dismiss();
}
- @Override
- public void handleMessage(final Message message) {
- final BitmapDrawable image = (BitmapDrawable) message.obj;
- if (image != null && view != null) {
- image.setBounds(0, 0, view.getHeight(), view.getHeight());
- view.setIcon(image);
+ }
+
+ private void setupIcon(final ActionBar actionBar, final String url) {
+ final HtmlImage imgGetter = new HtmlImage(HtmlImage.SHARED, false, 0, false);
+ AndroidObservable.bindActivity(this, imgGetter.fetchDrawable(url)).subscribe(new Action1<BitmapDrawable>() {
+ @Override
+ public void call(final BitmapDrawable image) {
+ if (actionBar != null) {
+ final int height = actionBar.getHeight();
+ image.setBounds(0, 0, height, height);
+ actionBar.setIcon(image);
+ }
}
- }
+ });
}
public static void startActivity(final AbstractActivity fromContext,
@@ -343,10 +321,37 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
return new DetailsViewCreator();
case LOGS:
return new TrackableLogsViewCreator(this);
+ case IMAGES:
+ return new ImagesViewCreator();
}
throw new IllegalStateException(); // cannot happen as long as switch case is enum complete
}
+ private class ImagesViewCreator extends AbstractCachingPageViewCreator<View> {
+
+ @Override
+ public View getDispatchedView(final ViewGroup parentView) {
+ view = getLayoutInflater().inflate(R.layout.cachedetail_images_page, parentView, false);
+ return view;
+ }
+ }
+
+ private void loadTrackableImages() {
+ if (imagesList != null) {
+ return;
+ }
+ final PageViewCreator creator = getViewCreator(Page.IMAGES);
+ if (creator == null) {
+ return;
+ }
+ final View imageView = creator.getView(null);
+ if (imageView == null) {
+ return;
+ }
+ imagesList = new ImagesList(this, trackable.getGeocode());
+ createSubscriptions.add(imagesList.loadImages(imageView, trackable.getImages(), false));
+ }
+
@Override
protected String getTitle(final Page page) {
return res.getString(page.resId);
@@ -356,9 +361,12 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
protected Pair<List<? extends Page>, Integer> getOrderedPages() {
final List<Page> pages = new ArrayList<>();
pages.add(Page.DETAILS);
- if (!trackable.getLogs().isEmpty()) {
+ if (CollectionUtils.isNotEmpty(trackable.getLogs())) {
pages.add(Page.LOGS);
}
+ if (CollectionUtils.isNotEmpty(trackable.getImages())) {
+ pages.add(Page.IMAGES);
+ }
return new ImmutablePair<List<? extends Page>, Integer>(pages, 0);
}
@@ -381,9 +389,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
// action bar icon
if (StringUtils.isNotBlank(trackable.getIconUrl())) {
- final TrackableIconHandler iconHandler = new TrackableIconHandler(getSupportActionBar());
- final TrackableIconThread iconThread = new TrackableIconThread(trackable.getIconUrl(), iconHandler);
- iconThread.start();
+ setupIcon(getSupportActionBar(), trackable.getIconUrl());
}
// trackable name
@@ -432,8 +438,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
if (showTimeSpan && trackable.getLogs() != null) {
for (final LogEntry log : trackable.getLogs()) {
if (log.type == LogType.RETRIEVED_IT || log.type == LogType.GRABBED_IT || log.type == LogType.DISCOVERED_IT || log.type == LogType.PLACED_IT) {
- final int days = log.daysSinceLog();
- text.append(" (").append(res.getQuantityString(R.plurals.days_ago, days, days)).append(')');
+ text.append(" (").append(Formatter.formatDaysAgo(log.date)).append(')');
break;
}
}
@@ -442,21 +447,21 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
final TextView spotted = details.add(R.string.trackable_spotted, text.toString());
spotted.setClickable(true);
if (Trackable.SPOTTED_CACHE == trackable.getSpottedType()) {
- spotted.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View arg0) {
- final String cacheGeocode = DataStore.getGeocodeForGuid(trackable.getSpottedGuid());
- if (StringUtils.isNotBlank(cacheGeocode)) {
- CacheDetailActivity.startActivity(TrackableActivity.this, cacheGeocode, trackable.getSpottedName());
- } else {
- // for geokrety we only know the cache geocode
- final String cacheCode = trackable.getSpottedName();
- if (ConnectorFactory.canHandle(cacheCode)) {
- CacheDetailActivity.startActivity(TrackableActivity.this, cacheCode);
- }
+ spotted.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View arg0) {
+ if (StringUtils.isNotBlank(trackable.getSpottedGuid())) {
+ CacheDetailActivity.startActivityGuid(TrackableActivity.this, trackable.getSpottedGuid(), trackable.getSpottedName());
+ }
+ else {
+ // for geokrety we only know the cache geocode
+ final String cacheCode = trackable.getSpottedName();
+ if (ConnectorFactory.canHandle(cacheCode)) {
+ CacheDetailActivity.startActivity(TrackableActivity.this, cacheCode);
}
}
- });
+ }
+ });
} else if (Trackable.SPOTTED_USER == trackable.getSpottedType()) {
spotted.setOnClickListener(new UserNameClickListener(trackable, Html.fromHtml(trackable.getSpottedName()).toString()));
} else if (Trackable.SPOTTED_OWNER == trackable.getSpottedType()) {
@@ -559,7 +564,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
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();
+ final CharSequence itemTitle = ((TextView) ((View) view.getParent()).findViewById(R.id.name)).getText();
buildDetailsContextMenu(actionMode, menu, clickedItemText, itemTitle, true);
return true;
case R.id.goal:
@@ -605,6 +610,12 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
}
}
+ @Override
+ protected void onDestroy() {
+ createSubscriptions.unsubscribe();
+ super.onDestroy();
+ }
+
public Trackable getTrackable() {
return trackable;
}
diff --git a/main/src/cgeo/geocaching/Waypoint.java b/main/src/cgeo/geocaching/Waypoint.java
index 7381aab..b2c5305 100644
--- a/main/src/cgeo/geocaching/Waypoint.java
+++ b/main/src/cgeo/geocaching/Waypoint.java
@@ -21,13 +21,13 @@ public class Waypoint implements IWaypoint {
public static final String PREFIX_OWN = "OWN";
private static final int ORDER_UNDEFINED = -2;
+ private static final Pattern PATTERN_COORDS = Pattern.compile("\\b[nNsS]\\s*\\d");
private int id = -1;
private String geocode = "geocode";
private WaypointType waypointType = WaypointType.WAYPOINT;
private String prefix = "";
private String lookup = "";
private String name = "";
- private String latlon = "";
private Geopoint coords = null;
private String note = "";
private int cachedOrder = ORDER_UNDEFINED;
@@ -67,9 +67,6 @@ public class Waypoint implements IWaypoint {
if (StringUtils.isBlank(name)) {
setName(old.name);
}
- if (StringUtils.isBlank(latlon) || latlon.startsWith("?")) { // there are waypoints containing "???"
- latlon = old.latlon;
- }
if (coords == null) {
coords = old.coords;
}
@@ -204,14 +201,6 @@ public class Waypoint implements IWaypoint {
this.name = name;
}
- public String getLatlon() {
- return latlon;
- }
-
- public void setLatlon(final String latlon) {
- this.latlon = latlon;
- }
-
@Override
public Geopoint getCoords() {
return coords;
@@ -303,10 +292,9 @@ public class Waypoint implements IWaypoint {
*/
public static Collection<Waypoint> parseWaypointsFromNote(@NonNull final String initialNote) {
final List<Waypoint> waypoints = new LinkedList<>();
- final Pattern COORDPATTERN = Pattern.compile("\\b[nNsS]{1}\\s*\\d"); // begin of coordinates
String note = initialNote;
- MatcherWrapper matcher = new MatcherWrapper(COORDPATTERN, note);
+ MatcherWrapper matcher = new MatcherWrapper(PATTERN_COORDS, note);
int count = 1;
while (matcher.find()) {
try {
@@ -321,12 +309,11 @@ public class Waypoint implements IWaypoint {
waypoints.add(waypoint);
count++;
}
- } catch (final Geopoint.ParseException e) {
- // ignore
+ } catch (final Geopoint.ParseException ignore) {
}
note = note.substring(matcher.start() + 1);
- matcher = new MatcherWrapper(COORDPATTERN, note);
+ matcher = new MatcherWrapper(PATTERN_COORDS, note);
}
return waypoints;
}
diff --git a/main/src/cgeo/geocaching/activity/AbstractActivity.java b/main/src/cgeo/geocaching/activity/AbstractActivity.java
index a28fcfa..603211e 100644
--- a/main/src/cgeo/geocaching/activity/AbstractActivity.java
+++ b/main/src/cgeo/geocaching/activity/AbstractActivity.java
@@ -60,26 +60,36 @@ public abstract class AbstractActivity extends ActionBarActivity implements IAbs
}
@Override
- public final void showToast(String text) {
+ public final void showToast(final String text) {
ActivityMixin.showToast(this, text);
}
@Override
- public final void showShortToast(String text) {
+ public final void showShortToast(final String text) {
ActivityMixin.showShortToast(this, text);
}
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
initializeCommonFields();
+ }
+
+ @Override
+ public final void presentShowcase() {
+ ActivityMixin.presentShowcase(this);
+ }
+ @Override
+ public ShowcaseViewBuilder getShowcase() {
+ // do nothing by default
+ return null;
}
@Override
- public boolean onOptionsItemSelected(MenuItem item) {
+ public boolean onOptionsItemSelected(final MenuItem item) {
if (item.getItemId() == android.R.id.home) {
return ActivityMixin.navigateUp(this);
}
@@ -116,7 +126,6 @@ public abstract class AbstractActivity extends ActionBarActivity implements IAbs
}
protected void onCreate(final Bundle savedInstanceState, final int resourceLayoutID) {
-
super.onCreate(savedInstanceState);
initializeCommonFields();
@@ -137,11 +146,11 @@ public abstract class AbstractActivity extends ActionBarActivity implements IAbs
// only needed in some activities, but implemented in super class nonetheless
Cookies.restoreCookieStore(Settings.getCookieStore());
- ActivityMixin.keepScreenOn(this, keepScreenOn);
+ ActivityMixin.onCreate(this, keepScreenOn);
}
@Override
- public void setContentView(int layoutResID) {
+ public void setContentView(final int layoutResID) {
super.setContentView(layoutResID);
// initialize the action bar title with the activity title for single source
@@ -202,27 +211,27 @@ public abstract class AbstractActivity extends ActionBarActivity implements IAbs
// these are so few that we don't want to deal with the older (non Android Beam) API
public interface ActivitySharingInterface {
- /** Return an URL that represent the current activity for sharing */
- public String getUri();
+ /** Return an URL that represent the current activity for sharing or null for no sharing. */
+ public String getAndroidBeamUri();
}
- protected void initializeAndroidBeam(ActivitySharingInterface sharingInterface) {
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ protected void initializeAndroidBeam(final ActivitySharingInterface sharingInterface) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
initializeICSAndroidBeam(sharingInterface);
}
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
protected void initializeICSAndroidBeam(final ActivitySharingInterface sharingInterface) {
- NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+ final NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null) {
return;
}
nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
@Override
- public NdefMessage createNdefMessage(NfcEvent event) {
- NdefRecord record = NdefRecord.createUri(sharingInterface.getUri());
- return new NdefMessage(new NdefRecord[]{record});
+ public NdefMessage createNdefMessage(final NfcEvent event) {
+ final String uri = sharingInterface.getAndroidBeamUri();
+ return uri != null ? new NdefMessage(new NdefRecord[]{NdefRecord.createUri(uri)}) : null;
}
}, this);
diff --git a/main/src/cgeo/geocaching/activity/AbstractListActivity.java b/main/src/cgeo/geocaching/activity/AbstractListActivity.java
index eac191a..d7482c3 100644
--- a/main/src/cgeo/geocaching/activity/AbstractListActivity.java
+++ b/main/src/cgeo/geocaching/activity/AbstractListActivity.java
@@ -32,17 +32,17 @@ public abstract class AbstractListActivity extends ActionBarListActivity impleme
}
@Override
- public final void showToast(String text) {
+ public final void showToast(final String text) {
ActivityMixin.showToast(this, text);
}
@Override
- public final void showShortToast(String text) {
+ public final void showShortToast(final String text) {
ActivityMixin.showShortToast(this, text);
}
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
@@ -55,7 +55,7 @@ public abstract class AbstractListActivity extends ActionBarListActivity impleme
}
@Override
- public boolean onOptionsItemSelected(MenuItem item) {
+ public boolean onOptionsItemSelected(final MenuItem item) {
if (item.getItemId()== android.R.id.home) {
return ActivityMixin.navigateUp(this);
}
@@ -67,7 +67,7 @@ public abstract class AbstractListActivity extends ActionBarListActivity impleme
res = this.getResources();
app = (CgeoApplication) this.getApplication();
- ActivityMixin.keepScreenOn(this, keepScreenOn);
+ ActivityMixin.onCreate(this, keepScreenOn);
}
final protected void setTitle(final String title) {
@@ -79,7 +79,7 @@ public abstract class AbstractListActivity extends ActionBarListActivity impleme
ActivityMixin.invalidateOptionsMenu(this);
}
- public void onCreate(Bundle savedInstanceState, int resourceLayoutID) {
+ public void onCreate(final Bundle savedInstanceState, final int resourceLayoutID) {
super.onCreate(savedInstanceState);
initializeCommonFields();
@@ -88,10 +88,22 @@ public abstract class AbstractListActivity extends ActionBarListActivity impleme
}
@Override
- public void setContentView(int layoutResID) {
+ public void setContentView(final int layoutResID) {
super.setContentView(layoutResID);
// initialize action bar title with activity title
ActivityMixin.setTitle(this, getTitle());
}
+
+ @Override
+ public final void presentShowcase() {
+ ActivityMixin.presentShowcase(this);
+ }
+
+ @Override
+ public ShowcaseViewBuilder getShowcase() {
+ // do nothing by default
+ return null;
+ }
+
}
diff --git a/main/src/cgeo/geocaching/activity/ActivityMixin.java b/main/src/cgeo/geocaching/activity/ActivityMixin.java
index b58d3ae..28042b0 100644
--- a/main/src/cgeo/geocaching/activity/ActivityMixin.java
+++ b/main/src/cgeo/geocaching/activity/ActivityMixin.java
@@ -16,7 +16,9 @@ import android.support.v4.app.TaskStackBuilder;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.view.Gravity;
+import android.view.Window;
import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
import android.widget.EditText;
import android.widget.Toast;
@@ -103,13 +105,17 @@ public final class ActivityMixin {
postShowToast(activity, text, Toast.LENGTH_SHORT);
}
- public static void keepScreenOn(final Activity abstractActivity, boolean keepScreenOn) {
+ public static void onCreate(final Activity abstractActivity, final boolean keepScreenOn) {
+ final Window window = abstractActivity.getWindow();
if (keepScreenOn) {
- abstractActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ if (Settings.useHardwareAcceleration()) {
+ window.setFlags(LayoutParams.FLAG_HARDWARE_ACCELERATED, LayoutParams.FLAG_HARDWARE_ACCELERATED);
}
}
- public static void invalidateOptionsMenu(Activity activity) {
+ public static void invalidateOptionsMenu(final Activity activity) {
if (activity instanceof ActionBarActivity) {
((ActionBarActivity) activity).supportInvalidateOptionsMenu();
}
@@ -127,10 +133,10 @@ public final class ActivityMixin {
* place the cursor after the inserted text
*/
public static void insertAtPosition(final EditText editText, final String insertText, final boolean moveCursor) {
- int selectionStart = editText.getSelectionStart();
- int selectionEnd = editText.getSelectionEnd();
- int start = Math.min(selectionStart, selectionEnd);
- int end = Math.max(selectionStart, selectionEnd);
+ final int selectionStart = editText.getSelectionStart();
+ final int selectionEnd = editText.getSelectionEnd();
+ final int start = Math.min(selectionStart, selectionEnd);
+ final int end = Math.max(selectionStart, selectionEnd);
final String content = editText.getText().toString();
String completeText;
@@ -141,13 +147,13 @@ public final class ActivityMixin {
}
editText.getText().replace(start, end, completeText);
- int newCursor = moveCursor ? start + completeText.length() : start;
+ final int newCursor = moveCursor ? start + completeText.length() : start;
editText.setSelection(newCursor);
}
public static boolean navigateUp(@NonNull final Activity activity) {
// see http://developer.android.com/training/implementing-navigation/ancestral.html
- Intent upIntent = NavUtils.getParentActivityIntent(activity);
+ final Intent upIntent = NavUtils.getParentActivityIntent(activity);
if (upIntent == null) {
activity.finish();
return true;
@@ -167,4 +173,15 @@ public final class ActivityMixin {
}
return true;
}
+
+ public static void presentShowcase(final IAbstractActivity activity) {
+ if (VERSION.SDK_INT < 11) {
+ return;
+ }
+ final ShowcaseViewBuilder builder = activity.getShowcase();
+ if (builder != null) {
+ builder.setStyle(R.style.ShowcaseView);
+ builder.build();
+ }
+ }
}
diff --git a/main/src/cgeo/geocaching/activity/IAbstractActivity.java b/main/src/cgeo/geocaching/activity/IAbstractActivity.java
index 4fb6a2a..59aa284 100644
--- a/main/src/cgeo/geocaching/activity/IAbstractActivity.java
+++ b/main/src/cgeo/geocaching/activity/IAbstractActivity.java
@@ -8,4 +8,18 @@ public interface IAbstractActivity {
public void showShortToast(String text);
public void invalidateOptionsMenuCompatible();
+
+ /**
+ * Override this method to create a showcase view highlighting the most important UI element.
+ *
+ */
+ public ShowcaseViewBuilder getShowcase();
+
+ /**
+ * Call this method to actually present a showcase. The right time to invoke this method depends on the showcase
+ * target. I.e. if the showcase target is an action bar item, this method can only be invoked after that item has
+ * been created in onCreateOptionsMenu.
+ */
+ public void presentShowcase();
+
}
diff --git a/main/src/cgeo/geocaching/activity/ShowcaseViewBuilder.java b/main/src/cgeo/geocaching/activity/ShowcaseViewBuilder.java
new file mode 100644
index 0000000..6b00f0f
--- /dev/null
+++ b/main/src/cgeo/geocaching/activity/ShowcaseViewBuilder.java
@@ -0,0 +1,61 @@
+package cgeo.geocaching.activity;
+
+import com.github.amlcurran.showcaseview.ShowcaseView.Builder;
+import com.github.amlcurran.showcaseview.targets.Target;
+
+import android.app.Activity;
+
+/**
+ * TODO: replace by simple utility class embedding a builder instead of inheriting from it
+ */
+public class ShowcaseViewBuilder extends Builder {
+
+ private final Activity activity;
+
+ public ShowcaseViewBuilder(final Activity activity) {
+ super(activity);
+ this.activity = activity;
+ }
+
+ @Override
+ public ShowcaseViewBuilder setContentTitle(final int resId) {
+ setSingleshot(activity.getResources().getString(resId));
+ return (ShowcaseViewBuilder) super.setContentTitle(resId);
+ }
+
+ /**
+ * Use the hash of the title for the single shot remembering
+ *
+ * @param resId
+ */
+ private void setSingleshot(final CharSequence title) {
+ super.singleShot(title.hashCode());
+ }
+
+ @Override
+ public ShowcaseViewBuilder setContentText(final int resId) {
+ return (ShowcaseViewBuilder) super.setContentText(resId);
+ }
+
+ @Override
+ public ShowcaseViewBuilder setContentText(final CharSequence text) {
+ return (ShowcaseViewBuilder) super.setContentText(text);
+ }
+
+ @Override
+ public ShowcaseViewBuilder setContentTitle(final CharSequence title) {
+ setSingleshot(title);
+ return (ShowcaseViewBuilder) super.setContentTitle(title);
+ }
+
+ @Override
+ public ShowcaseViewBuilder setTarget(final Target target) {
+ return (ShowcaseViewBuilder) super.setTarget(target);
+ }
+
+ public ShowcaseViewBuilder setContent(final int titleId, final int textId) {
+ setContentTitle(titleId);
+ return setContentText(textId);
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java b/main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java
index 6c6ffda..f31d175 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java
@@ -9,6 +9,9 @@ import android.content.Intent;
public abstract class AbstractRadarApp extends AbstractPointNavigationApp {
+ protected static final String RADAR_EXTRA_LONGITUDE = "longitude";
+ protected static final String RADAR_EXTRA_LATITUDE = "latitude";
+
private final String intentAction;
protected AbstractRadarApp(final String name, final int id, final String intent, final String packageName) {
diff --git a/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java b/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java
index 03d2220..743ce1f 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java
@@ -20,19 +20,19 @@ class CompassApp extends AbstractPointNavigationApp {
}
@Override
- public void navigate(Activity activity, Geopoint coords) {
- CompassActivity.startActivity(activity, getString(R.string.navigation_direct_navigation), getString(R.string.navigation_target), coords, null);
+ public void navigate(final Activity activity, final Geopoint coords) {
+ CompassActivity.startActivity(activity, getString(R.string.navigation_direct_navigation), getString(R.string.navigation_target), coords);
}
@Override
- public void navigate(Activity activity, Waypoint waypoint) {
- CompassActivity.startActivity(activity, waypoint.getPrefix() + "/" + waypoint.getLookup(), waypoint.getName(), waypoint.getCoords(), null,
+ public void navigate(final Activity activity, final Waypoint waypoint) {
+ CompassActivity.startActivity(activity, waypoint.getPrefix() + "/" + waypoint.getLookup(), waypoint.getName(), waypoint.getCoords(),
waypoint.getWaypointType().getL10n());
}
@Override
- public void navigate(Activity activity, Geocache cache) {
- CompassActivity.startActivity(activity, cache);
+ public void navigate(final Activity activity, final Geocache cache) {
+ CompassActivity.startActivityCache(activity, cache);
}
} \ No newline at end of file
diff --git a/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java b/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java
index 4924786..f5ccef4 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java
@@ -1,10 +1,10 @@
package cgeo.geocaching.apps.cache.navi;
import cgeo.geocaching.CgeoApplication;
-import cgeo.geocaching.sensors.IGeoData;
import cgeo.geocaching.R;
import cgeo.geocaching.geopoint.Geopoint;
import cgeo.geocaching.maps.MapProviderFactory;
+import cgeo.geocaching.sensors.IGeoData;
import cgeo.geocaching.utils.Log;
import android.app.Activity;
@@ -23,15 +23,13 @@ public class GoogleMapsDirectionApp extends AbstractPointNavigationApp {
}
@Override
- public void navigate(Activity activity, Geopoint coords) {
+ public void navigate(final Activity activity, final Geopoint coords) {
try {
- IGeoData geo = CgeoApplication.getInstance().currentGeo();
- final Geopoint coordsNow = geo == null ? null : geo.getCoords();
-
- if (coordsNow != null) {
+ final IGeoData geo = CgeoApplication.getInstance().currentGeo();
+ if (geo.getCoords() != null) {
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri
.parse("http://maps.google.com/maps?f=d&saddr="
- + coordsNow.getLatitude() + "," + coordsNow.getLongitude() + "&daddr="
+ + geo.getCoords().getLatitude() + "," + geo.getCoords().getLongitude() + "&daddr="
+ coords.getLatitude() + "," + coords.getLongitude())));
} else {
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri
@@ -39,7 +37,7 @@ public class GoogleMapsDirectionApp extends AbstractPointNavigationApp {
+ coords.getLatitude() + "," + coords.getLongitude())));
}
- } catch (Exception e) {
+ } catch (final Exception e) {
Log.i("GoogleMapsDirectionApp: application not available.", e);
}
diff --git a/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java b/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java
index e24c055..c00723d 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java
@@ -142,23 +142,21 @@ public final class NavigationAppFactory extends AbstractAppFactory {
final boolean showInternalMap, final boolean showDefaultNavigation) {
final List<NavigationAppsEnum> items = new ArrayList<>();
final int defaultNavigationTool = Settings.getDefaultNavigationTool();
- for (final NavigationAppsEnum navApp : getInstalledNavigationApps()) {
+ for (final NavigationAppsEnum navApp : getActiveNavigationApps()) {
if ((showInternalMap || !(navApp.app instanceof InternalMap)) &&
(showDefaultNavigation || defaultNavigationTool != navApp.id)) {
- if (Settings.isUseNavigationApp(navApp)) {
- boolean add = false;
- if (cache != null && navApp.app instanceof CacheNavigationApp && navApp.app.isEnabled(cache)) {
- add = true;
- }
- if (waypoint != null && navApp.app instanceof WaypointNavigationApp && ((WaypointNavigationApp) navApp.app).isEnabled(waypoint)) {
- add = true;
- }
- if (destination != null && navApp.app instanceof GeopointNavigationApp) {
- add = true;
- }
- if (add) {
- items.add(navApp);
- }
+ boolean add = false;
+ if (cache != null && navApp.app instanceof CacheNavigationApp && navApp.app.isEnabled(cache)) {
+ add = true;
+ }
+ if (waypoint != null && navApp.app instanceof WaypointNavigationApp && ((WaypointNavigationApp) navApp.app).isEnabled(waypoint)) {
+ add = true;
+ }
+ if (destination != null && navApp.app instanceof GeopointNavigationApp) {
+ add = true;
+ }
+ if (add) {
+ items.add(navApp);
}
}
}
@@ -203,6 +201,19 @@ public final class NavigationAppFactory extends AbstractAppFactory {
}
/**
+ * @return all navigation apps, which are installed and activated in the settings
+ */
+ public static List<NavigationAppsEnum> getActiveNavigationApps() {
+ final List<NavigationAppsEnum> activeApps = new ArrayList<>();
+ for (final NavigationAppsEnum appEnum : getInstalledNavigationApps()) {
+ if (Settings.isUseNavigationApp(appEnum)) {
+ activeApps.add(appEnum);
+ }
+ }
+ return activeApps;
+ }
+
+ /**
* Returns all installed navigation apps for default navigation.
*
* @return
diff --git a/main/src/cgeo/geocaching/apps/cache/navi/NavigationSelectionActionProvider.java b/main/src/cgeo/geocaching/apps/cache/navi/NavigationSelectionActionProvider.java
index 9abc581..82883a2 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/NavigationSelectionActionProvider.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/NavigationSelectionActionProvider.java
@@ -47,7 +47,7 @@ public class NavigationSelectionActionProvider extends ActionProvider {
if (geocache == null) {
return;
}
- for (final NavigationAppsEnum app : NavigationAppFactory.getInstalledNavigationApps()) {
+ for (final NavigationAppsEnum app : NavigationAppFactory.getActiveNavigationApps()) {
if (app.app.isEnabled(geocache)) {
subMenu.add(Menu.NONE, app.id, Menu.NONE, app.app.getName()).setOnMenuItemClickListener(new OnMenuItemClickListener() {
diff --git a/main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java b/main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java
index 5d645f7..4dbfadd 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java
@@ -8,6 +8,8 @@ import android.content.Intent;
class OruxMapsApp extends AbstractPointNavigationApp {
+ private static final String ORUXMAPS_EXTRA_LONGITUDE = "longitude";
+ private static final String ORUXMAPS_EXTRA_LATITUDE = "latitude";
private static final String INTENT = "com.oruxmaps.VIEW_MAP_ONLINE";
OruxMapsApp() {
@@ -17,8 +19,8 @@ class OruxMapsApp extends AbstractPointNavigationApp {
@Override
public void navigate(Activity activity, Geopoint point) {
final Intent intent = new Intent(INTENT);
- intent.putExtra("latitude", point.getLatitude());//latitude, wgs84 datum
- intent.putExtra("longitude", point.getLongitude());//longitude, wgs84 datum
+ intent.putExtra(ORUXMAPS_EXTRA_LATITUDE, point.getLatitude());//latitude, wgs84 datum
+ intent.putExtra(ORUXMAPS_EXTRA_LONGITUDE, point.getLongitude());//longitude, wgs84 datum
activity.startActivity(intent);
}
diff --git a/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java b/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java
index 5012195..a12a38e 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java
@@ -20,7 +20,7 @@ class PebbleApp extends AbstractRadarApp {
@Override
protected void addCoordinates(final Intent intent, final Geopoint coords) {
- intent.putExtra("latitude", coords.getLatitude());
- intent.putExtra("longitude", coords.getLongitude());
+ intent.putExtra(RADAR_EXTRA_LATITUDE, coords.getLatitude());
+ intent.putExtra(RADAR_EXTRA_LONGITUDE, coords.getLongitude());
}
} \ 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 41cf2d8..0ee512b 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java
@@ -16,8 +16,8 @@ class RadarApp extends AbstractRadarApp {
@Override
protected void addCoordinates(final Intent intent, final Geopoint coords) {
- intent.putExtra("latitude", (float) coords.getLatitude());
- intent.putExtra("longitude", (float) coords.getLongitude());
+ intent.putExtra(RADAR_EXTRA_LATITUDE, (float) coords.getLatitude());
+ intent.putExtra(RADAR_EXTRA_LONGITUDE, (float) coords.getLongitude());
}
} \ No newline at end of file
diff --git a/main/src/cgeo/geocaching/connector/AbstractConnector.java b/main/src/cgeo/geocaching/connector/AbstractConnector.java
index 8138e96..a929e2b 100644
--- a/main/src/cgeo/geocaching/connector/AbstractConnector.java
+++ b/main/src/cgeo/geocaching/connector/AbstractConnector.java
@@ -76,7 +76,7 @@ public abstract class AbstractConnector implements IConnector {
}
@Override
- public boolean supportsFavoritePoints() {
+ public boolean supportsFavoritePoints(final Geocache cache) {
return false;
}
diff --git a/main/src/cgeo/geocaching/connector/IConnector.java b/main/src/cgeo/geocaching/connector/IConnector.java
index fbade5a..e6b6674 100644
--- a/main/src/cgeo/geocaching/connector/IConnector.java
+++ b/main/src/cgeo/geocaching/connector/IConnector.java
@@ -72,7 +72,7 @@ public interface IConnector {
*
* @return
*/
- public boolean supportsFavoritePoints();
+ public boolean supportsFavoritePoints(final Geocache cache);
/**
* enable/disable logging controls in cache details
diff --git a/main/src/cgeo/geocaching/connector/gc/GCConnector.java b/main/src/cgeo/geocaching/connector/gc/GCConnector.java
index ad00718..4512979 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCConnector.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCConnector.java
@@ -7,6 +7,7 @@ import cgeo.geocaching.ICache;
import cgeo.geocaching.LogCacheActivity;
import cgeo.geocaching.R;
import cgeo.geocaching.SearchResult;
+import cgeo.geocaching.activity.ActivityMixin;
import cgeo.geocaching.connector.AbstractConnector;
import cgeo.geocaching.connector.ILoggingManager;
import cgeo.geocaching.connector.UserAction;
@@ -37,6 +38,7 @@ import org.eclipse.jdt.annotation.Nullable;
import rx.functions.Action1;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@@ -143,7 +145,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode,
CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_loadpage);
- final String page = GCParser.requestHtmlPage(geocode, guid, "y", String.valueOf(GCConstants.NUMBER_OF_LOGS));
+ final String page = GCParser.requestHtmlPage(geocode, guid, "y");
if (StringUtils.isEmpty(page)) {
final SearchResult search = new SearchResult();
@@ -283,8 +285,8 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode,
}
@Override
- public boolean supportsFavoritePoints() {
- return true;
+ public boolean supportsFavoritePoints(final Geocache cache) {
+ return !cache.getType().isEvent();
}
@Override
@@ -414,7 +416,12 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode,
@Override
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))));
+ try {
+ context.activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/email/?u=" + Network.encode(context.userName))));
+ } catch (final ActivityNotFoundException e) {
+ Log.e("Cannot find suitable activity", e);
+ ActivityMixin.showToast(context.activity, R.string.err_application_no);
+ }
}
}));
return actions;
diff --git a/main/src/cgeo/geocaching/connector/gc/GCConstants.java b/main/src/cgeo/geocaching/connector/gc/GCConstants.java
index 8df7703..c2021bb 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCConstants.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCConstants.java
@@ -74,7 +74,7 @@ public final class GCConstants {
public static final Pattern PATTERN_CACHES_FOUND = Pattern.compile("<strong[^>]*>.*?([\\d,.]+) Caches? Found", Pattern.DOTALL);
public static final Pattern PATTERN_AVATAR_IMAGE_PROFILE_PAGE = Pattern.compile("src=\"(https?://(imgcdn\\.geocaching\\.com|[^>\"]+\\.cloudfront\\.net)/avatar/[0-9a-f-]+\\.jpg)\"[^>]*alt=\"");
public static final Pattern PATTERN_LOGIN_NAME_LOGIN_PAGE = Pattern.compile("ctl00_ContentBody_lbUsername\">.*<strong>(.*)</strong>");
- public static final Pattern PATTERN_CUSTOMDATE = Pattern.compile("<option selected=\"selected\" value=\"([ /Mdy-]+)\">");
+ public static final Pattern PATTERN_CUSTOMDATE = Pattern.compile("<option selected=\"selected\" value=\"([ /.Mdy-]+)\">");
public static final Pattern PATTERN_MAP_LOGGED_IN = Pattern.compile("<a href=\"https?://www.geocaching.com/my/\" class=\"CommonUsername\"");
/**
@@ -130,7 +130,7 @@ public final class GCConstants {
public final static Pattern PATTERN_SEARCH_TOTALCOUNT = Pattern.compile("<span>Total Records\\D*(\\d+)<");
public final static Pattern PATTERN_SEARCH_RECAPTCHA = Pattern.compile("<script[^>]*src=\"[^\"]*/recaptcha/api/challenge\\?k=([^\"]+)\"[^>]*>");
public final static Pattern PATTERN_SEARCH_RECAPTCHACHALLENGE = Pattern.compile("challenge : '([^']+)'");
- public final static Pattern PATTERN_SEARCH_HIDDEN_DATE = Pattern.compile("<td valign=\"top\"[^<]+<span class=\"small\">([^<]+)</span>");
+ public final static Pattern PATTERN_SEARCH_HIDDEN_DATE = Pattern.compile("<td style=\"width:70px\">[^<]+<span class=\"small\">([^<]+)</span>");
/**
* Patterns for waypoints
@@ -172,7 +172,7 @@ public final class GCConstants {
public final static String STRING_PREMIUMONLY_2 = "Sorry, the owner of this listing has made it viewable to Premium Members only.";
public final static String STRING_PREMIUMONLY_1 = "has chosen to make this cache listing visible to Premium Members only.";
public final static String STRING_UNPUBLISHED_OTHER = "you cannot view this cache listing until it has been published";
- public final static String STRING_UNPUBLISHED_FROM_SEARCH = "class=\"UnpublishedCacheSearchWidge\"";
+ public final static String STRING_UNPUBLISHED_FROM_SEARCH = "class=\"UnpublishedCacheSearchWidget"; // do not include closing brace as the CSS can contain additional styles
public final static String STRING_UNKNOWN_ERROR = "An Error Has Occurred";
public final static String STRING_DISABLED = "<li>This cache is temporarily unavailable.";
public final static String STRING_ARCHIVED = "<li>This cache has been archived,";
diff --git a/main/src/cgeo/geocaching/connector/gc/GCLogin.java b/main/src/cgeo/geocaching/connector/gc/GCLogin.java
index df537f5..16f20b8 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCLogin.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCLogin.java
@@ -47,7 +47,9 @@ public class GCLogin extends AbstractLogin {
DEFAULT_CUSTOM_DATE_FORMAT,
"yyyy-MM-dd",
"yyyy/MM/dd",
+ "dd.MM.yyyy",
"dd/MMM/yyyy",
+ "dd.MMM.yyyy",
"MMM/dd/yyyy",
"dd MMM yy",
"dd/MM/yyyy"
@@ -134,13 +136,11 @@ public class GCLogin extends AbstractLogin {
assert loginData != null; // Caught above
if (getLoginStatus(loginData)) {
- Log.i("Successfully logged in Geocaching.com as " + username + " (" + Settings.getGCMemberStatus() + ')');
-
if (switchToEnglish(loginData) && retry) {
return login(false);
}
+ Log.i("Successfully logged in Geocaching.com as " + username + " (" + Settings.getGCMemberStatus() + ')');
Settings.setCookieStore(Cookies.dumpCookieStore());
-
return StatusCode.NO_ERROR; // logged in
}
diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java
index d1c81fe..6919173 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCParser.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java
@@ -30,6 +30,7 @@ import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.ui.DirectionImage;
import cgeo.geocaching.utils.CancellableHandler;
+import cgeo.geocaching.utils.HtmlUtils;
import cgeo.geocaching.utils.JsonUtils;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.MatcherWrapper;
@@ -78,6 +79,7 @@ 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 final static ImmutablePair<StatusCode, Geocache> UNKNOWN_PARSE_ERROR = ImmutablePair.of(StatusCode.UNKNOWN_ERROR, null);
private static SearchResult parseSearch(final String url, final String pageContent, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) {
if (StringUtils.isBlank(pageContent)) {
@@ -374,7 +376,7 @@ public abstract class GCParser {
final SearchResult result = new SearchResult(parsed.left);
if (parsed.left == StatusCode.NO_ERROR) {
result.addAndPutInCache(Collections.singletonList(parsed.right));
- DataStore.saveLogsWithoutTransaction(parsed.right.getGeocode(), getLogsFromDetails(page).toBlocking().toIterable());
+ DataStore.saveLogsWithoutTransaction(parsed.right.getGeocode(), getLogs(page, Logs.ALL).toBlocking().toIterable());
}
return result;
}
@@ -390,12 +392,13 @@ public abstract class GCParser {
* @return a pair, with a {@link StatusCode} on the left, and a non-null cache object on the right
* iff the status code is {@link StatusCode.NO_ERROR}.
*/
+ @NonNull
static private ImmutablePair<StatusCode, Geocache> parseCacheFromText(final String pageIn, @Nullable final CancellableHandler handler) {
CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_details);
if (StringUtils.isBlank(pageIn)) {
Log.e("GCParser.parseCache: No page given");
- return null;
+ return UNKNOWN_PARSE_ERROR;
}
if (pageIn.contains(GCConstants.STRING_UNPUBLISHED_OTHER) || pageIn.contains(GCConstants.STRING_UNPUBLISHED_FROM_SEARCH)) {
@@ -408,7 +411,7 @@ public abstract class GCParser {
final String cacheName = Html.fromHtml(TextUtils.getMatch(pageIn, GCConstants.PATTERN_NAME, true, "")).toString();
if (GCConstants.STRING_UNKNOWN_ERROR.equalsIgnoreCase(cacheName)) {
- return ImmutablePair.of(StatusCode.UNKNOWN_ERROR, null);
+ return UNKNOWN_PARSE_ERROR;
}
// first handle the content with line breaks, then trim everything for easier matching and reduced memory consumption in parsed fields
@@ -451,7 +454,7 @@ public abstract class GCParser {
final int pos = tableInside.indexOf(GCConstants.STRING_CACHEDETAILS);
if (pos == -1) {
Log.e("GCParser.parseCache: ID \"cacheDetails\" not found on page");
- return null;
+ return UNKNOWN_PARSE_ERROR;
}
tableInside = tableInside.substring(pos);
@@ -590,7 +593,7 @@ public abstract class GCParser {
// cache spoilers
try {
if (CancellableHandler.isCancelled(handler)) {
- return null;
+ return UNKNOWN_PARSE_ERROR;
}
CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_spoilers);
@@ -691,7 +694,7 @@ public abstract class GCParser {
int wpBegin = page.indexOf("<table class=\"Table\" id=\"ctl00_ContentBody_Waypoints\">");
if (wpBegin != -1) { // parse waypoints
if (CancellableHandler.isCancelled(handler)) {
- return null;
+ return UNKNOWN_PARSE_ERROR;
}
CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_waypoints);
@@ -735,7 +738,6 @@ public abstract class GCParser {
// waypoint latitude and longitude
latlon = Html.fromHtml(TextUtils.getMatch(wp[7], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, false, 2, "", false)).toString().trim();
if (!StringUtils.startsWith(latlon, "???")) {
- waypoint.setLatlon(latlon);
waypoint.setCoords(new Geopoint(latlon));
}
@@ -756,7 +758,7 @@ public abstract class GCParser {
// last check for necessary cache conditions
if (StringUtils.isBlank(cache.getGeocode())) {
- return ImmutablePair.of(StatusCode.UNKNOWN_ERROR, null);
+ return UNKNOWN_PARSE_ERROR;
}
cache.setDetailedUpdatedNow();
@@ -1371,7 +1373,7 @@ public abstract class GCParser {
}
@Nullable
- static String requestHtmlPage(@Nullable final String geocode, @Nullable final String guid, final String log, final String numlogs) {
+ static String requestHtmlPage(@Nullable final String geocode, @Nullable final String guid, final String log) {
final Parameters params = new Parameters("decrypt", "y");
if (StringUtils.isNotBlank(geocode)) {
params.put("wp", geocode);
@@ -1379,7 +1381,7 @@ public abstract class GCParser {
params.put("guid", guid);
}
params.put("log", log);
- params.put("numlogs", numlogs);
+ params.put("numlogs", "0");
return GCLogin.getInstance().getRequestLogged("http://www.geocaching.com/seek/cache_details.aspx", params);
}
@@ -1418,7 +1420,7 @@ public abstract class GCParser {
}
private static String getUserToken(final Geocache cache) {
- final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0");
+ final String page = requestHtmlPage(cache.getGeocode(), null, "n");
return TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, "");
}
@@ -1546,7 +1548,7 @@ public abstract class GCParser {
}
// trackable goal
- trackable.setGoal(convertLinks(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GOAL, true, trackable.getGoal())));
+ trackable.setGoal(HtmlUtils.removeExtraParagraph(convertLinks(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GOAL, true, trackable.getGoal()))));
// trackable details & image
try {
@@ -1556,10 +1558,10 @@ public abstract class GCParser {
final String details = StringUtils.trim(matcherDetailsImage.group(4));
if (StringUtils.isNotEmpty(image)) {
- trackable.setImage(image);
+ trackable.setImage(StringUtils.replace(image, "/display/", "/large/"));
}
if (StringUtils.isNotEmpty(details) && !StringUtils.equals(details, "No additional details available.")) {
- trackable.setDetails(convertLinks(details));
+ trackable.setDetails(HtmlUtils.removeExtraParagraph(convertLinks(details)));
}
}
} catch (final RuntimeException e) {
@@ -1638,27 +1640,14 @@ public abstract class GCParser {
return StringUtils.replace(input, "../", GCConstants.GC_URL);
}
- /**
- * Extract logs from a cache details page.
- *
- * @param page
- * the text of the details page
- * @return a list of log entries which will be empty if the logs could not be retrieved
- *
- */
- @NonNull
- private static Observable<LogEntry> getLogsFromDetails(final String page) {
- // extract embedded JSON data from page
- return parseLogs(false, TextUtils.getMatch(page, GCConstants.PATTERN_LOGBOOK, ""));
- }
-
- private enum SpecialLogs {
+ private enum Logs {
+ ALL(null),
FRIENDS("sf"),
OWN("sp");
final String paramName;
- SpecialLogs(final String paramName) {
+ Logs(final String paramName) {
this.paramName = paramName;
}
@@ -1676,10 +1665,10 @@ public abstract class GCParser {
* The logType to request
* @return Observable<LogEntry> The logs
*/
- private static Observable<LogEntry> getSpecialLogs(final String page, final SpecialLogs logType) {
- return Observable.defer(new Func0<Observable<? extends LogEntry>>() {
+ private static Observable<LogEntry> getLogs(final String page, final Logs logType) {
+ return Observable.defer(new Func0<Observable<LogEntry>>() {
@Override
- public Observable<? extends LogEntry> call() {
+ public Observable<LogEntry> call() {
final MatcherWrapper userTokenMatcher = new MatcherWrapper(GCConstants.PATTERN_USERTOKEN, page);
if (!userTokenMatcher.find()) {
Log.e("GCParser.loadLogsFromDetails: unable to extract userToken");
@@ -1691,8 +1680,10 @@ public abstract class GCParser {
"tkn", userToken,
"idx", "1",
"num", String.valueOf(GCConstants.NUMBER_OF_LOGS),
- logType.getParamName(), Boolean.toString(Boolean.TRUE),
"decrypt", "true");
+ if (logType != Logs.ALL) {
+ params.add(logType.getParamName(), Boolean.toString(Boolean.TRUE));
+ }
final HttpResponse response = Network.getRequest("http://www.geocaching.com/seek/geocache.logbook", params);
if (response == null) {
Log.e("GCParser.loadLogsFromDetails: cannot log logs, response is null");
@@ -1708,7 +1699,7 @@ public abstract class GCParser {
Log.e("GCParser.loadLogsFromDetails: unable to read whole response");
return Observable.empty();
}
- return parseLogs(true, rawResponse);
+ return parseLogs(logType != Logs.ALL, rawResponse);
}
}).subscribeOn(RxUtils.networkScheduler);
}
@@ -1872,16 +1863,11 @@ public abstract class GCParser {
return;
}
- final Observable<LogEntry> logs = getLogsFromDetails(page).subscribeOn(RxUtils.computationScheduler);
- Observable<LogEntry> specialLogs;
- if (Settings.isFriendLogsWanted()) {
- CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs);
- specialLogs = Observable.merge(getSpecialLogs(page, SpecialLogs.FRIENDS),
- getSpecialLogs(page, SpecialLogs.OWN));
- } else {
- CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs);
- specialLogs = Observable.empty();
- }
+ CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs);
+ final Observable<LogEntry> logs = getLogs(page, Logs.ALL);
+ final Observable<LogEntry> ownLogs = getLogs(page, Logs.OWN).cache();
+ final Observable<LogEntry> specialLogs = Settings.isFriendLogsWanted() ?
+ Observable.merge(getLogs(page, Logs.FRIENDS), ownLogs) : Observable.<LogEntry>empty();
final Observable<List<LogEntry>> mergedLogs = Observable.zip(logs.toList(), specialLogs.toList(),
new Func2<List<LogEntry>, List<LogEntry>, List<LogEntry>>() {
@Override
@@ -1896,6 +1882,16 @@ public abstract class GCParser {
DataStore.saveLogsWithoutTransaction(cache.getGeocode(), logEntries);
}
});
+ if (cache.isFound() && cache.getVisitedDate() == 0) {
+ ownLogs.subscribe(new Action1<LogEntry>() {
+ @Override
+ public void call(final LogEntry logEntry) {
+ if (logEntry.type == LogType.FOUND_IT) {
+ cache.setVisitedDate(logEntry.date);
+ }
+ }
+ });
+ }
if (Settings.isRatingWanted() && !CancellableHandler.isCancelled(handler)) {
CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_gcvote);
diff --git a/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java b/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java
index 6095514..affeb7d 100644
--- a/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java
+++ b/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java
@@ -42,15 +42,15 @@ public class RecaptchaHandler extends Handler {
}
private void loadChallenge(final ImageView imageView, final View reloadButton) {
- final Observable<Bitmap> captcha = Observable.defer(new Func0<Observable<? extends Bitmap>>() {
+ final Observable<Bitmap> captcha = Observable.defer(new Func0<Observable<Bitmap>>() {
@Override
- public Observable<? extends Bitmap> call() {
+ public Observable<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);
+ return Observable.just(img);
} catch (final Exception e) {
Log.e("RecaptchaHandler.getCaptcha", e);
return Observable.error(e);
diff --git a/main/src/cgeo/geocaching/enumerations/CacheType.java b/main/src/cgeo/geocaching/enumerations/CacheType.java
index 16677da..1d190e4 100644
--- a/main/src/cgeo/geocaching/enumerations/CacheType.java
+++ b/main/src/cgeo/geocaching/enumerations/CacheType.java
@@ -68,8 +68,10 @@ public enum CacheType {
mappingPattern.put(ct.pattern.toLowerCase(Locale.US), ct);
mappingGuid.put(ct.guid, ct);
}
- // add old mystery type for GPX file compatibility
+ // Add old mystery type for GPX file compatibility.
mappingPattern.put("Mystery Cache".toLowerCase(Locale.US), MYSTERY);
+ // This pattern briefly appeared on gc.com in 2014-08.
+ mappingPattern.put("Traditional Geocache".toLowerCase(Locale.US), TRADITIONAL);
FIND_BY_ID = Collections.unmodifiableMap(mappingId);
FIND_BY_PATTERN = Collections.unmodifiableMap(mappingPattern);
diff --git a/main/src/cgeo/geocaching/enumerations/LogType.java b/main/src/cgeo/geocaching/enumerations/LogType.java
index 84ab7b9..5345611 100644
--- a/main/src/cgeo/geocaching/enumerations/LogType.java
+++ b/main/src/cgeo/geocaching/enumerations/LogType.java
@@ -52,7 +52,7 @@ public enum LogType {
private final int stringId;
public final int markerId;
- LogType(int id, String iconName, String type, String oc_type, int stringId, int markerId) {
+ LogType(final int id, final String iconName, final String type, final String oc_type, final int stringId, final int markerId) {
this.id = id;
this.iconName = iconName;
this.type = type;
@@ -61,7 +61,7 @@ public enum LogType {
this.markerId = markerId;
}
- LogType(int id, String iconName, String type, String oc_type, int stringId) {
+ LogType(final int id, final String iconName, final String type, final String oc_type, final int stringId) {
this(id, iconName, type, oc_type, stringId, R.drawable.mark_gray);
}
@@ -70,7 +70,7 @@ public enum LogType {
static {
final HashMap<String, LogType> mappingPattern = new HashMap<>();
final HashMap<String, LogType> mappingType = new HashMap<>();
- for (LogType lt : values()) {
+ for (final LogType lt : values()) {
if (lt.iconName != null) {
mappingPattern.put(lt.iconName, lt);
}
@@ -81,7 +81,7 @@ public enum LogType {
}
public static LogType getById(final int id) {
- for (LogType logType : values()) {
+ for (final LogType logType : values()) {
if (logType.id == id) {
return logType;
}
@@ -113,4 +113,12 @@ public enum LogType {
public final String getL10n() {
return CgeoApplication.getInstance().getBaseContext().getResources().getString(stringId);
}
+
+ public final boolean isFoundLog() {
+ return this == LogType.FOUND_IT || this == LogType.ATTENDED || this == LogType.WEBCAM_PHOTO_TAKEN;
+ }
+
+ public boolean mustConfirmLog() {
+ return this == ARCHIVE || this == NEEDS_ARCHIVE;
+ }
}
diff --git a/main/src/cgeo/geocaching/files/GPXImporter.java b/main/src/cgeo/geocaching/files/GPXImporter.java
index 85d12f7..4f1d391 100644
--- a/main/src/cgeo/geocaching/files/GPXImporter.java
+++ b/main/src/cgeo/geocaching/files/GPXImporter.java
@@ -113,7 +113,7 @@ public class GPXImporter {
fileType = getFileTypeFromMimeType(mimeType);
}
- ImportThread importer = getImporterFromFileType(uri, contentResolver,
+ final ImportThread importer = getImporterFromFileType(uri, contentResolver,
fileType);
if (importer != null) {
@@ -146,8 +146,8 @@ public class GPXImporter {
return FileType.UNKNOWN;
}
- private ImportThread getImporterFromFileType(Uri uri,
- ContentResolver contentResolver, FileType fileType) {
+ private ImportThread getImporterFromFileType(final Uri uri,
+ final ContentResolver contentResolver, final FileType fileType) {
switch (fileType) {
case ZIP:
return new ImportGpxZipAttachmentThread(uri, contentResolver,
@@ -179,7 +179,7 @@ public class GPXImporter {
final Handler importStepHandler;
final CancellableHandler progressHandler;
- protected ImportThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ protected ImportThread(final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
this.listId = listId;
this.importStepHandler = importStepHandler;
this.progressHandler = progressHandler;
@@ -245,7 +245,7 @@ public class GPXImporter {
static class ImportLocFileThread extends ImportThread {
private final File file;
- public ImportLocFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ public ImportLocFileThread(final File file, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
this.file = file;
}
@@ -263,7 +263,7 @@ public class GPXImporter {
private final Uri uri;
private final ContentResolver contentResolver;
- public ImportLocAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ public ImportLocAttachmentThread(final Uri uri, final ContentResolver contentResolver, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
this.uri = uri;
this.contentResolver = contentResolver;
@@ -285,7 +285,7 @@ public class GPXImporter {
static abstract class ImportGpxThread extends ImportThread {
- protected ImportGpxThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ protected ImportGpxThread(final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
}
@@ -306,13 +306,13 @@ public class GPXImporter {
static class ImportGpxFileThread extends ImportGpxThread {
private final File cacheFile;
- public ImportGpxFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ public ImportGpxFileThread(final File file, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
this.cacheFile = file;
}
@Override
- protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException {
+ protected Collection<Geocache> doImport(final GPXParser parser) throws IOException, ParserException {
Log.i("Import GPX file: " + cacheFile.getAbsolutePath());
importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) cacheFile.length()));
Collection<Geocache> caches = parser.parse(cacheFile, progressHandler);
@@ -334,17 +334,21 @@ public class GPXImporter {
private final Uri uri;
private final ContentResolver contentResolver;
- public ImportGpxAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ public ImportGpxAttachmentThread(final Uri uri, final ContentResolver contentResolver, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
this.uri = uri;
this.contentResolver = contentResolver;
}
@Override
- protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException {
+ protected Collection<Geocache> doImport(final GPXParser parser) throws IOException, ParserException {
Log.i("Import GPX from uri: " + uri);
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, -1));
final InputStream is = contentResolver.openInputStream(uri);
+ int streamSize = is.available();
+ if (streamSize == 0) {
+ streamSize = -1;
+ }
+ importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, streamSize));
try {
return parser.parse(is, progressHandler);
} finally {
@@ -355,12 +359,12 @@ public class GPXImporter {
static abstract class ImportGpxZipThread extends ImportGpxThread {
- protected ImportGpxZipThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ protected ImportGpxZipThread(final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
}
@Override
- protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException {
+ protected Collection<Geocache> doImport(final GPXParser parser) throws IOException, ParserException {
Collection<Geocache> caches = Collections.emptySet();
// can't assume that GPX file comes before waypoint file in zip -> so we need two passes
// 1. parse GPX files
@@ -404,7 +408,7 @@ public class GPXImporter {
static class ImportGpxZipFileThread extends ImportGpxZipThread {
private final File cacheFile;
- public ImportGpxZipFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ public ImportGpxZipFileThread(final File file, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
this.cacheFile = file;
Log.i("Import zipped GPX: " + file);
@@ -420,7 +424,7 @@ public class GPXImporter {
private final Uri uri;
private final ContentResolver contentResolver;
- public ImportGpxZipAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ public ImportGpxZipAttachmentThread(final Uri uri, final ContentResolver contentResolver, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
this.uri = uri;
this.contentResolver = contentResolver;
@@ -435,14 +439,14 @@ public class GPXImporter {
final private CancellableHandler progressHandler = new CancellableHandler() {
@Override
- public void handleRegularMessage(Message msg) {
+ public void handleRegularMessage(final Message msg) {
progress.setProgress(msg.arg1);
}
};
final private Handler importStepHandler = new Handler() {
@Override
- public void handleMessage(Message msg) {
+ public void handleMessage(final Message msg) {
switch (msg.what) {
case IMPORT_STEP_START:
final Message cancelMessage = importStepHandler.obtainMessage(IMPORT_STEP_CANCEL);
diff --git a/main/src/cgeo/geocaching/files/GPXParser.java b/main/src/cgeo/geocaching/files/GPXParser.java
index 370b8aa..ccc265e 100644
--- a/main/src/cgeo/geocaching/files/GPXParser.java
+++ b/main/src/cgeo/geocaching/files/GPXParser.java
@@ -8,6 +8,8 @@ import cgeo.geocaching.R;
import cgeo.geocaching.Trackable;
import cgeo.geocaching.Waypoint;
import cgeo.geocaching.connector.ConnectorFactory;
+import cgeo.geocaching.connector.IConnector;
+import cgeo.geocaching.connector.capability.ILogin;
import cgeo.geocaching.enumerations.CacheSize;
import cgeo.geocaching.enumerations.CacheType;
import cgeo.geocaching.enumerations.LoadFlags;
@@ -120,12 +122,12 @@ public abstract class GPXParser extends FileParser {
private final class UserDataListener implements EndTextElementListener {
private final int index;
- public UserDataListener(int index) {
+ public UserDataListener(final int index) {
this.index = index;
}
@Override
- public void end(String user) {
+ public void end(final String user) {
userData[index] = validate(user);
}
}
@@ -250,13 +252,13 @@ public abstract class GPXParser extends FileParser {
}
}
- protected GPXParser(int listIdIn, String namespaceIn, String versionIn) {
+ protected GPXParser(final int listIdIn, final String namespaceIn, final String versionIn) {
listId = listIdIn;
namespace = namespaceIn;
version = versionIn;
}
- static Date parseDate(String inputUntrimmed) throws ParseException {
+ static Date parseDate(final String inputUntrimmed) throws ParseException {
String input = inputUntrimmed.trim();
// remove milliseconds to reduce number of needed patterns
final MatcherWrapper matcher = new MatcherWrapper(PATTERN_MILLISECONDS, input);
@@ -280,7 +282,7 @@ public abstract class GPXParser extends FileParser {
root.getChild(namespace, "url").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
scriptUrl = body;
}
});
@@ -289,7 +291,7 @@ public abstract class GPXParser extends FileParser {
waypoint.setStartElementListener(new StartElementListener() {
@Override
- public void start(Attributes attrs) {
+ public void start(final Attributes attrs) {
try {
if (attrs.getIndex("lat") > -1 && attrs.getIndex("lon") > -1) {
final String latitude = attrs.getValue("lat");
@@ -399,7 +401,7 @@ public abstract class GPXParser extends FileParser {
waypoint.getChild(namespace, "time").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
try {
cache.setHidden(parseDate(body));
} catch (final Exception e) {
@@ -412,7 +414,7 @@ public abstract class GPXParser extends FileParser {
waypoint.getChild(namespace, "name").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
name = body;
String content = body.trim();
@@ -431,7 +433,7 @@ public abstract class GPXParser extends FileParser {
waypoint.getChild(namespace, "desc").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
desc = body;
cache.setShortDescription(validate(body));
@@ -442,7 +444,7 @@ public abstract class GPXParser extends FileParser {
waypoint.getChild(namespace, "cmt").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
cmt = body;
cache.setDescription(validate(body));
@@ -453,7 +455,7 @@ public abstract class GPXParser extends FileParser {
waypoint.getChild(namespace, "type").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
final String[] content = StringUtils.split(body, '|');
if (content.length > 0) {
type = content[0].toLowerCase(Locale.US).trim();
@@ -477,7 +479,7 @@ public abstract class GPXParser extends FileParser {
waypoint.getChild(namespace, "url").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String url) {
+ public void end(final String url) {
final MatcherWrapper matcher = new MatcherWrapper(PATTERN_GUID, url);
if (matcher.matches()) {
final String guid = matcher.group(1);
@@ -497,7 +499,7 @@ public abstract class GPXParser extends FileParser {
waypoint.getChild(namespace, "urlname").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String urlName) {
+ public void end(final String urlName) {
if (cache.getName().equals(cache.getGeocode()) && StringUtils.startsWith(cache.getGeocode(), "WM")) {
cache.setName(StringUtils.trim(urlName));
}
@@ -520,7 +522,7 @@ public abstract class GPXParser extends FileParser {
gcCache.setStartElementListener(new StartElementListener() {
@Override
- public void start(Attributes attrs) {
+ public void start(final Attributes attrs) {
try {
if (attrs.getIndex("id") > -1) {
cache.setCacheId(attrs.getValue("id"));
@@ -541,7 +543,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "name").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String cacheName) {
+ public void end(final String cacheName) {
cache.setName(validate(cacheName));
}
});
@@ -550,7 +552,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "owner").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String ownerUserId) {
+ public void end(final String ownerUserId) {
cache.setOwnerUserId(validate(ownerUserId));
}
});
@@ -559,7 +561,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "placed_by").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String ownerDisplayName) {
+ public void end(final String ownerDisplayName) {
cache.setOwnerDisplayName(validate(ownerDisplayName));
}
});
@@ -568,7 +570,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "type").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
cache.setType(CacheType.getByPattern(validate(body)));
}
});
@@ -577,7 +579,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "container").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
cache.setSize(CacheSize.getById(validate(body)));
}
});
@@ -597,7 +599,7 @@ public abstract class GPXParser extends FileParser {
gcAttribute.setStartElementListener(new StartElementListener() {
@Override
- public void start(Attributes attrs) {
+ public void start(final Attributes attrs) {
try {
if (attrs.getIndex("id") > -1 && attrs.getIndex("inc") > -1) {
final int attributeId = Integer.parseInt(attrs.getValue("id"));
@@ -617,7 +619,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "difficulty").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
try {
cache.setDifficulty(Float.parseFloat(body));
} catch (final NumberFormatException e) {
@@ -630,7 +632,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "terrain").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
try {
cache.setTerrain(Float.parseFloat(body));
} catch (final NumberFormatException e) {
@@ -643,7 +645,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "country").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String country) {
+ public void end(final String country) {
if (StringUtils.isBlank(cache.getLocation())) {
cache.setLocation(validate(country));
} else {
@@ -656,7 +658,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "state").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String state) {
+ public void end(final String state) {
final String trimmedState = state.trim();
if (StringUtils.isNotEmpty(trimmedState)) { // state can be completely empty
if (StringUtils.isBlank(cache.getLocation())) {
@@ -672,7 +674,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "encoded_hints").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String encoded) {
+ public void end(final String encoded) {
cache.setHint(validate(encoded));
}
});
@@ -680,7 +682,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "short_description").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String shortDesc) {
+ public void end(final String shortDesc) {
cache.setShortDescription(validate(shortDesc));
}
});
@@ -688,7 +690,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "long_description").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String desc) {
+ public void end(final String desc) {
cache.setDescription(validate(desc));
}
});
@@ -703,7 +705,7 @@ public abstract class GPXParser extends FileParser {
gcTB.setStartElementListener(new StartElementListener() {
@Override
- public void start(Attributes attrs) {
+ public void start(final Attributes attrs) {
trackable = new Trackable();
try {
@@ -733,7 +735,7 @@ public abstract class GPXParser extends FileParser {
gcTB.getChild(nsGC, "name").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String tbName) {
+ public void end(final String tbName) {
trackable.setName(validate(tbName));
}
});
@@ -747,7 +749,7 @@ public abstract class GPXParser extends FileParser {
gcLog.setStartElementListener(new StartElementListener() {
@Override
- public void start(Attributes attrs) {
+ public void start(final Attributes attrs) {
log = new LogEntry("", 0, LogType.UNKNOWN, "");
try {
@@ -765,6 +767,13 @@ public abstract class GPXParser extends FileParser {
@Override
public void end() {
if (log.type != LogType.UNKNOWN) {
+ if (log.type.isFoundLog() && StringUtils.isNotBlank(log.author)) {
+ final IConnector connector = ConnectorFactory.getConnector(cache);
+ if (connector instanceof ILogin && StringUtils.equals(log.author, ((ILogin) connector).getUserName())) {
+ cache.setFound(true);
+ cache.setVisitedDate(log.date);
+ }
+ }
logs.add(log);
}
}
@@ -774,7 +783,7 @@ public abstract class GPXParser extends FileParser {
gcLog.getChild(nsGC, "date").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
try {
log.date = parseDate(body).getTime();
} catch (final Exception e) {
@@ -787,7 +796,7 @@ public abstract class GPXParser extends FileParser {
gcLog.getChild(nsGC, "type").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
final String logType = validate(body);
log.type = LogType.getByType(logType);
}
@@ -797,7 +806,7 @@ public abstract class GPXParser extends FileParser {
gcLog.getChild(nsGC, "finder").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String finderName) {
+ public void end(final String finderName) {
log.author = validate(finderName);
}
});
@@ -806,7 +815,7 @@ public abstract class GPXParser extends FileParser {
gcLog.getChild(nsGC, "text").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String logText) {
+ public void end(final String logText) {
log.log = validate(logText);
}
});
@@ -814,7 +823,7 @@ public abstract class GPXParser extends FileParser {
try {
progressStream = new ProgressInputStream(stream);
- BufferedReader reader = new BufferedReader(new InputStreamReader(progressStream, CharEncoding.UTF_8));
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(progressStream, CharEncoding.UTF_8));
Xml.parse(new InvalidXMLCharacterFilterReader(reader), root.getContentHandler());
return DataStore.loadCaches(result, EnumSet.of(LoadFlag.DB_MINIMAL));
} catch (final SAXException e) {
@@ -833,7 +842,7 @@ public abstract class GPXParser extends FileParser {
gsak.getChild(gsakNamespace, "Watch").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String watchList) {
+ public void end(final String watchList) {
cache.setOnWatchlist(Boolean.valueOf(watchList.trim()));
}
});
@@ -847,7 +856,7 @@ public abstract class GPXParser extends FileParser {
gsak.getChild(gsakNamespace, "Parent").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
parentCacheCode = body;
}
});
@@ -855,7 +864,7 @@ public abstract class GPXParser extends FileParser {
gsak.getChild(gsakNamespace, "FavPoints").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String favoritePoints) {
+ public void end(final String favoritePoints) {
try {
cache.setFavoritePoints(Integer.parseInt(favoritePoints));
}
@@ -894,7 +903,7 @@ public abstract class GPXParser extends FileParser {
cgeoVisited.setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String visited) {
+ public void end(final String visited) {
wptVisited = Boolean.valueOf(visited.trim());
}
});
@@ -904,7 +913,7 @@ public abstract class GPXParser extends FileParser {
cgeoUserDefined.setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String userDefined) {
+ public void end(final String userDefined) {
wptUserDefined = Boolean.valueOf(userDefined.trim());
}
});
@@ -917,7 +926,7 @@ public abstract class GPXParser extends FileParser {
* @param cache
* currently imported cache
*/
- protected void afterParsing(Geocache cache) {
+ protected void afterParsing(final Geocache cache) {
// can be overridden by sub classes
}
@@ -930,7 +939,7 @@ public abstract class GPXParser extends FileParser {
*/
protected abstract Element getCacheParent(Element waypoint);
- protected static String validate(String input) {
+ protected static String validate(final String input) {
if ("nil".equalsIgnoreCase(input)) {
return "";
}
diff --git a/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java b/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java
index ed8c074..f7ac4db 100644
--- a/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java
+++ b/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java
@@ -16,7 +16,7 @@ class PopularityRatioFilter extends AbstractFilter {
private final int minRatio;
private final int maxRatio;
- public PopularityRatioFilter(String name, final int minRatio, final int maxRatio) {
+ public PopularityRatioFilter(final String name, final int minRatio, final int maxRatio) {
super(name);
this.minRatio = minRatio;
this.maxRatio = maxRatio;
@@ -35,11 +35,11 @@ class PopularityRatioFilter extends AbstractFilter {
return ratio > minRatio && ratio <= maxRatio;
}
- private static int getFindsCount(Geocache cache) {
+ private static int getFindsCount(final Geocache cache) {
if (cache.getLogCounts().isEmpty()) {
cache.setLogCounts(DataStore.loadLogCounts(cache.getGeocode()));
}
- Integer logged = cache.getLogCounts().get(LogType.FOUND_IT);
+ final Integer logged = cache.getLogCounts().get(LogType.FOUND_IT);
if (logged != null) {
return logged;
}
@@ -55,7 +55,7 @@ class PopularityRatioFilter extends AbstractFilter {
final List<IFilter> filters = new ArrayList<>(RATIOS.length);
for (final int minRange : RATIOS) {
final int maxRange = Integer.MAX_VALUE;
- final String name = "> " + minRange + " " + CgeoApplication.getInstance().getResources().getString(R.string.percent_favorite_points);
+ final String name = CgeoApplication.getInstance().getResources().getString(R.string.more_than_percent_favorite_points, minRange);
filters.add(new PopularityRatioFilter(name, minRange, maxRange));
}
return filters;
diff --git a/main/src/cgeo/geocaching/gcvote/GCVote.java b/main/src/cgeo/geocaching/gcvote/GCVote.java
index d42bb34..a5b31f0 100644
--- a/main/src/cgeo/geocaching/gcvote/GCVote.java
+++ b/main/src/cgeo/geocaching/gcvote/GCVote.java
@@ -8,30 +8,27 @@ import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.utils.LeastRecentlyUsedMap;
import cgeo.geocaching.utils.Log;
-import cgeo.geocaching.utils.MatcherWrapper;
+import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
+import org.apache.commons.io.Charsets;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.eclipse.jdt.annotation.NonNull;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserFactory;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.regex.Pattern;
public final class GCVote {
public static final float NO_RATING = 0;
- private static final Pattern PATTERN_LOG_IN = Pattern.compile("loggedIn='([^']+)'", Pattern.CASE_INSENSITIVE);
- private static final Pattern PATTERN_GUID = Pattern.compile("cacheId='([^']+)'", Pattern.CASE_INSENSITIVE);
- private static final Pattern PATTERN_WAYPOINT = Pattern.compile("waypoint='([^']+)'", Pattern.CASE_INSENSITIVE);
- private static final Pattern PATTERN_RATING = Pattern.compile("voteAvg='([0-9.]+)'", Pattern.CASE_INSENSITIVE);
- private static final Pattern PATTERN_VOTES = Pattern.compile("voteCnt='([0-9]+)'", Pattern.CASE_INSENSITIVE);
- private static final Pattern PATTERN_VOTE = Pattern.compile("voteUser='([0-9.]+)'", Pattern.CASE_INSENSITIVE);
- private static final Pattern PATTERN_VOTE_ELEMENT = Pattern.compile("<vote ([^>]+)>", Pattern.CASE_INSENSITIVE);
private static final int MAX_CACHED_RATINGS = 1000;
private static final LeastRecentlyUsedMap<String, GCVoteRating> RATINGS_CACHE = new LeastRecentlyUsedMap.LruCache<>(MAX_CACHED_RATINGS);
@@ -70,124 +67,68 @@ public final class GCVote {
* @param geocodes
* @return
*/
+ @NonNull
private static Map<String, GCVoteRating> getRating(final List<String> guids, final List<String> geocodes) {
if (guids == null && geocodes == null) {
- return null;
+ return Collections.emptyMap();
}
- final Map<String, GCVoteRating> ratings = new HashMap<>();
+ final Parameters params = new Parameters("version", "cgeo");
+ final ImmutablePair<String, String> login = Settings.getGCvoteLogin();
+ if (login != null) {
+ params.put("userName", login.left, "password", login.right);
+ }
+ // use guid or gccode for lookup
+ final boolean requestByGuids = CollectionUtils.isNotEmpty(guids);
+ if (requestByGuids) {
+ params.put("cacheIds", StringUtils.join(guids, ','));
+ } else {
+ params.put("waypoints", StringUtils.join(geocodes, ','));
+ }
+ final InputStream response = Network.getResponseStream(Network.getRequest("http://gcvote.com/getVotes.php", params));
+ if (response == null) {
+ return Collections.emptyMap();
+ }
try {
- final Parameters params = new Parameters();
- if (Settings.isLogin()) {
- final ImmutablePair<String, String> login = Settings.getGCvoteLogin();
- if (login != null) {
- params.put("userName", login.left, "password", login.right);
- }
- }
- // use guid or gccode for lookup
- boolean requestByGuids = true;
- if (guids != null && !guids.isEmpty()) {
- params.put("cacheIds", StringUtils.join(guids.toArray(), ','));
- } else {
- params.put("waypoints", StringUtils.join(geocodes.toArray(), ','));
- requestByGuids = false;
- }
- params.put("version", "cgeo");
- final String page = Network.getResponseData(Network.getRequest("http://gcvote.com/getVotes.php", params));
- if (page == null) {
- return null;
- }
-
- final MatcherWrapper matcherVoteElement = new MatcherWrapper(PATTERN_VOTE_ELEMENT, page);
- while (matcherVoteElement.find()) {
- String voteData = matcherVoteElement.group(1);
- if (voteData == null) {
- continue;
- }
-
- String id = null;
- String guid = null;
- final MatcherWrapper matcherGuid = new MatcherWrapper(PATTERN_GUID, voteData);
- if (matcherGuid.find()) {
- if (matcherGuid.groupCount() > 0) {
- guid = matcherGuid.group(1);
- if (requestByGuids) {
- id = guid;
- }
- }
- }
- if (!requestByGuids) {
- final MatcherWrapper matcherWp = new MatcherWrapper(PATTERN_WAYPOINT, voteData);
- if (matcherWp.find()) {
- if (matcherWp.groupCount() > 0) {
- id = matcherWp.group(1);
- }
- }
- }
- if (id == null) {
- continue;
- }
-
- boolean loggedIn = false;
- final MatcherWrapper matcherLoggedIn = new MatcherWrapper(PATTERN_LOG_IN, page);
- if (matcherLoggedIn.find()) {
- if (matcherLoggedIn.groupCount() > 0) {
- if (matcherLoggedIn.group(1).equalsIgnoreCase("true")) {
- loggedIn = true;
- }
- }
- }
-
- float rating = NO_RATING;
- try {
- final MatcherWrapper matcherRating = new MatcherWrapper(PATTERN_RATING, voteData);
- if (matcherRating.find()) {
- rating = Float.parseFloat(matcherRating.group(1));
- }
- } catch (NumberFormatException e) {
- Log.w("GCVote.getRating: Failed to parse rating");
- }
- if (!isValidRating(rating)) {
- continue;
- }
-
- int votes = -1;
- try {
- final MatcherWrapper matcherVotes = new MatcherWrapper(PATTERN_VOTES, voteData);
- if (matcherVotes.find()) {
- votes = Integer.parseInt(matcherVotes.group(1));
- }
- } catch (NumberFormatException e) {
- Log.w("GCVote.getRating: Failed to parse vote count");
- }
- if (votes < 0) {
- continue;
- }
+ return getRatingsFromXMLResponse(response, requestByGuids);
+ } finally {
+ IOUtils.closeQuietly(response);
+ }
+ }
- float myVote = NO_RATING;
- if (loggedIn) {
- try {
- final MatcherWrapper matcherVote = new MatcherWrapper(PATTERN_VOTE, voteData);
- if (matcherVote.find()) {
- myVote = Float.parseFloat(matcherVote.group(1));
- }
- } catch (NumberFormatException e) {
- Log.w("GCVote.getRating: Failed to parse user's vote");
+ static Map<String, GCVoteRating> getRatingsFromXMLResponse(@NonNull final InputStream response, final boolean requestByGuids) {
+ try {
+ final XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+ final XmlPullParser xpp = factory.newPullParser();
+ xpp.setInput(response, Charsets.UTF_8.name());
+ boolean loggedIn = false;
+ final Map<String, GCVoteRating> ratings = new HashMap<>();
+ int eventType = xpp.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ final String tagName = xpp.getName();
+ if (StringUtils.equals(tagName, "vote")) {
+ final String guid = xpp.getAttributeValue(null, "cacheId");
+ final String id = requestByGuids ? guid : xpp.getAttributeValue(null, "waypoint");
+ final float myVote = loggedIn ? Float.parseFloat(xpp.getAttributeValue(null, "voteUser")) : 0;
+ final GCVoteRating voteRating = new GCVoteRating(Float.parseFloat(xpp.getAttributeValue(null, "voteAvg")),
+ Integer.parseInt(xpp.getAttributeValue(null, "voteCnt")),
+ myVote);
+ ratings.put(id, voteRating);
+ } else if (StringUtils.equals(tagName, "votes")) {
+ loggedIn = StringUtils.equals(xpp.getAttributeValue(null, "loggedIn"), "true");
}
}
-
- if (StringUtils.isNotBlank(id)) {
- GCVoteRating gcvoteRating = new GCVoteRating(rating, votes, myVote);
- ratings.put(id, gcvoteRating);
- RATINGS_CACHE.put(guid, gcvoteRating);
- }
+ eventType = xpp.next();
}
- } catch (RuntimeException e) {
- Log.e("GCVote.getRating", e);
- }
+ RATINGS_CACHE.putAll(ratings);
+ return ratings;
+ } catch (final Exception e) {
+ Log.e("Cannot parse GC vote result", e);
+ return Collections.emptyMap();
- return ratings;
+ }
}
/**
@@ -235,16 +176,14 @@ public final class GCVote {
try {
final Map<String, GCVoteRating> ratings = GCVote.getRating(null, geocodes);
- if (MapUtils.isNotEmpty(ratings)) {
- // save found cache coordinates
- for (Geocache cache : caches) {
- if (ratings.containsKey(cache.getGeocode())) {
- GCVoteRating rating = ratings.get(cache.getGeocode());
+ // save found cache coordinates
+ for (Geocache cache : caches) {
+ if (ratings.containsKey(cache.getGeocode())) {
+ GCVoteRating rating = ratings.get(cache.getGeocode());
- cache.setRating(rating.getRating());
- cache.setVotes(rating.getVotes());
- cache.setMyVote(rating.getMyVote());
- }
+ cache.setRating(rating.getRating());
+ cache.setVotes(rating.getVotes());
+ cache.setMyVote(rating.getMyVote());
}
}
} catch (Exception e) {
diff --git a/main/src/cgeo/geocaching/maps/CGeoMap.java b/main/src/cgeo/geocaching/maps/CGeoMap.java
index b0e6464..d257cc6 100644
--- a/main/src/cgeo/geocaching/maps/CGeoMap.java
+++ b/main/src/cgeo/geocaching/maps/CGeoMap.java
@@ -6,6 +6,7 @@ import cgeo.geocaching.CacheListActivity;
import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.DataStore;
import cgeo.geocaching.Geocache;
+import cgeo.geocaching.Intents;
import cgeo.geocaching.R;
import cgeo.geocaching.SearchResult;
import cgeo.geocaching.Waypoint;
@@ -31,7 +32,6 @@ import cgeo.geocaching.maps.interfaces.MapProvider;
import cgeo.geocaching.maps.interfaces.MapSource;
import cgeo.geocaching.maps.interfaces.MapViewImpl;
import cgeo.geocaching.maps.interfaces.OnMapDragListener;
-import cgeo.geocaching.sensors.DirectionProvider;
import cgeo.geocaching.sensors.GeoDirHandler;
import cgeo.geocaching.sensors.IGeoData;
import cgeo.geocaching.settings.Settings;
@@ -125,16 +125,6 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
private static final int UPDATE_PROGRESS = 0;
private static final int FINISHED_LOADING_DETAILS = 1;
- //Menu
- private static final String EXTRAS_GEOCODE = "geocode";
- private static final String EXTRAS_COORDS = "coords";
- private static final String EXTRAS_WPTTYPE = "wpttype";
- private static final String EXTRAS_MAPSTATE = "mapstate";
- private static final String EXTRAS_SEARCH = "search";
- private static final String EXTRAS_MAP_TITLE = "mapTitle";
- private static final String EXTRAS_MAP_MODE = "mapMode";
- private static final String EXTRAS_LIVE_ENABLED = "liveEnabled";
-
private static final String BUNDLE_MAP_SOURCE = "mapSource";
private static final String BUNDLE_MAP_STATE = "mapState";
private static final String BUNDLE_LIVE_ENABLED = "liveEnabled";
@@ -269,13 +259,13 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
titleview.setText(title);
}
- if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)) {
- setTitleHoneyComb(title);
+ if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)) {
+ setTitleIceCreamSandwich(title);
}
}
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- private void setTitleHoneyComb(final String title) {
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ private void setTitleIceCreamSandwich(final String title) {
activity.getActionBar().setTitle(title);
}
/** Updates the progress. */
@@ -383,7 +373,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
outState.putParcelableArrayList(BUNDLE_TRAIL_HISTORY, overlayPositionAndScale.getHistory());
}
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -402,14 +392,14 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
// Get parameters from the intent
final Bundle extras = activity.getIntent().getExtras();
if (extras != null) {
- mapMode = (MapMode) extras.get(EXTRAS_MAP_MODE);
- isLiveEnabled = extras.getBoolean(EXTRAS_LIVE_ENABLED, false);
- searchIntent = extras.getParcelable(EXTRAS_SEARCH);
- geocodeIntent = extras.getString(EXTRAS_GEOCODE);
- coordsIntent = extras.getParcelable(EXTRAS_COORDS);
- waypointTypeIntent = WaypointType.findById(extras.getString(EXTRAS_WPTTYPE));
- mapStateIntent = extras.getIntArray(EXTRAS_MAPSTATE);
- mapTitle = extras.getString(EXTRAS_MAP_TITLE);
+ mapMode = (MapMode) extras.get(Intents.EXTRA_MAP_MODE);
+ isLiveEnabled = extras.getBoolean(Intents.EXTRA_LIVE_ENABLED, false);
+ searchIntent = extras.getParcelable(Intents.EXTRA_SEARCH);
+ geocodeIntent = extras.getString(Intents.EXTRA_GEOCODE);
+ coordsIntent = extras.getParcelable(Intents.EXTRA_COORDS);
+ waypointTypeIntent = WaypointType.findById(extras.getString(Intents.EXTRA_WPTTYPE));
+ mapStateIntent = extras.getIntArray(Intents.EXTRA_MAPSTATE);
+ mapTitle = extras.getString(Intents.EXTRA_MAP_TITLE);
}
else {
mapMode = MapMode.LIVE;
@@ -439,12 +429,12 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
// reset status
noMapTokenShowed = false;
- ActivityMixin.keepScreenOn(activity, true);
+ ActivityMixin.onCreate(activity, true);
// set layout
ActivityMixin.setTheme(activity);
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
activity.getActionBar().setDisplayHomeAsUpEnabled(true);
}
activity.setContentView(mapProvider.getMapLayoutId());
@@ -560,6 +550,13 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
super.onPause();
}
+ @Override
+ public void onStop() {
+ // Ensure that handlers will not try to update the dialog once the view is detached.
+ waitDialog = null;
+ super.onStop();
+ }
+
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
@@ -572,7 +569,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
subMenuStrategy.setHeaderTitle(res.getString(R.string.map_strategy_title));
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
/* if we have an Actionbar find the my position toggle */
final MenuItem item = menu.findItem(R.id.menu_toggle_mypos);
myLocSwitch = new CheckBox(activity);
@@ -606,6 +603,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
} else {
item.setTitle(res.getString(R.string.map_live_enable));
}
+ item.setVisible(coordsIntent == null);
item = menu.findItem(R.id.menu_mycaches_mode); // own & found caches
item.setChecked(Settings.isExcludeMyCaches());
@@ -849,19 +847,19 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
// prepare information to restart a similar view
final Intent mapIntent = new Intent(activity, Settings.getMapProvider().getMapClass());
- mapIntent.putExtra(EXTRAS_SEARCH, searchIntent);
- mapIntent.putExtra(EXTRAS_GEOCODE, geocodeIntent);
+ mapIntent.putExtra(Intents.EXTRA_SEARCH, searchIntent);
+ mapIntent.putExtra(Intents.EXTRA_GEOCODE, geocodeIntent);
if (coordsIntent != null) {
- mapIntent.putExtra(EXTRAS_COORDS, coordsIntent);
+ mapIntent.putExtra(Intents.EXTRA_COORDS, coordsIntent);
}
- mapIntent.putExtra(EXTRAS_WPTTYPE, waypointTypeIntent != null ? waypointTypeIntent.id : null);
- mapIntent.putExtra(EXTRAS_MAP_TITLE, mapTitle);
- mapIntent.putExtra(EXTRAS_MAP_MODE, mapMode);
- mapIntent.putExtra(EXTRAS_LIVE_ENABLED, isLiveEnabled);
+ mapIntent.putExtra(Intents.EXTRA_WPTTYPE, waypointTypeIntent != null ? waypointTypeIntent.id : null);
+ mapIntent.putExtra(Intents.EXTRA_MAP_TITLE, mapTitle);
+ mapIntent.putExtra(Intents.EXTRA_MAP_MODE, mapMode);
+ mapIntent.putExtra(Intents.EXTRA_LIVE_ENABLED, isLiveEnabled);
final int[] mapState = currentMapState();
if (mapState != null) {
- mapIntent.putExtra(EXTRAS_MAPSTATE, mapState);
+ mapIntent.putExtra(Intents.EXTRA_MAPSTATE, mapState);
}
// start the new map
@@ -874,6 +872,9 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
* @return the current map state as an array of int, or null if no map state is available
*/
private int[] currentMapState() {
+ if (mapView == null) {
+ return null;
+ }
final GeoPointImpl mapCenter = mapView.getMapViewCenter();
return new int[] {
mapCenter.getLatitudeE6(),
@@ -907,7 +908,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
// minimum change of location in fraction of map width/height (whatever is smaller) for position overlay update
private static final float MIN_LOCATION_DELTA = 0.01f;
- Location currentLocation = new Location("");
+ Location currentLocation = CgeoApplication.getInstance().currentGeo().getLocation();
float currentHeading;
private long timeLastPositionOverlayCalculation = 0;
@@ -923,7 +924,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
@Override
public void updateGeoDir(final IGeoData geo, final float dir) {
currentLocation = geo.getLocation();
- currentHeading = DirectionProvider.getDirectionNow(dir);
+ currentHeading = AngleUtils.getDirectionNow(dir);
repaintPositionOverlay();
}
@@ -1120,7 +1121,9 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
final boolean excludeMine = Settings.isExcludeMyCaches();
final boolean excludeDisabled = Settings.isExcludeDisabledCaches();
if (mapMode == MapMode.LIVE) {
- CGeoMap.filter(caches);
+ synchronized(caches) {
+ CGeoMap.filter(caches);
+ }
}
countVisibleCaches();
if (cachesCnt < Settings.getWayPointsThreshold() || geocodeIntent != null) {
@@ -1485,9 +1488,12 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
// switch My Location button image
private void switchMyLocationButton() {
- myLocSwitch.setChecked(followMyLocation);
- if (followMyLocation) {
- myLocationInMiddle(app.currentGeo());
+ // FIXME: temporary workaround for the absence of "follow my location" on Android 3.x (see issue #4289).
+ if (myLocSwitch != null) {
+ myLocSwitch.setChecked(followMyLocation);
+ if (followMyLocation) {
+ myLocationInMiddle(app.currentGeo());
+ }
}
}
@@ -1558,42 +1564,41 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
public static void startActivitySearch(final Activity fromActivity, final SearchResult search, final String title) {
final Intent mapIntent = newIntent(fromActivity);
- mapIntent.putExtra(EXTRAS_SEARCH, search);
- mapIntent.putExtra(EXTRAS_MAP_MODE, MapMode.LIST);
- mapIntent.putExtra(EXTRAS_LIVE_ENABLED, false);
+ mapIntent.putExtra(Intents.EXTRA_SEARCH, search);
+ mapIntent.putExtra(Intents.EXTRA_MAP_MODE, MapMode.LIST);
+ mapIntent.putExtra(Intents.EXTRA_LIVE_ENABLED, false);
if (StringUtils.isNotBlank(title)) {
- mapIntent.putExtra(CGeoMap.EXTRAS_MAP_TITLE, title);
+ mapIntent.putExtra(Intents.EXTRA_MAP_TITLE, title);
}
fromActivity.startActivity(mapIntent);
}
- public static void startActivityLiveMap(final Activity fromActivity) {
- final Intent mapIntent = newIntent(fromActivity);
- mapIntent.putExtra(EXTRAS_MAP_MODE, MapMode.LIVE);
- mapIntent.putExtra(EXTRAS_LIVE_ENABLED, Settings.isLiveMap());
- fromActivity.startActivity(mapIntent);
+ public static Intent getLiveMapIntent(final Activity fromActivity) {
+ return newIntent(fromActivity)
+ .putExtra(Intents.EXTRA_MAP_MODE, MapMode.LIVE)
+ .putExtra(Intents.EXTRA_LIVE_ENABLED, Settings.isLiveMap());
}
public static void startActivityCoords(final Activity fromActivity, final Geopoint coords, final WaypointType type, final String title) {
final Intent mapIntent = newIntent(fromActivity);
- mapIntent.putExtra(EXTRAS_MAP_MODE, MapMode.COORDS);
- mapIntent.putExtra(EXTRAS_LIVE_ENABLED, false);
- mapIntent.putExtra(EXTRAS_COORDS, coords);
+ mapIntent.putExtra(Intents.EXTRA_MAP_MODE, MapMode.COORDS);
+ mapIntent.putExtra(Intents.EXTRA_LIVE_ENABLED, false);
+ mapIntent.putExtra(Intents.EXTRA_COORDS, coords);
if (type != null) {
- mapIntent.putExtra(EXTRAS_WPTTYPE, type.id);
+ mapIntent.putExtra(Intents.EXTRA_WPTTYPE, type.id);
}
if (StringUtils.isNotBlank(title)) {
- mapIntent.putExtra(EXTRAS_MAP_TITLE, title);
+ mapIntent.putExtra(Intents.EXTRA_MAP_TITLE, title);
}
fromActivity.startActivity(mapIntent);
}
public static void startActivityGeoCode(final Activity fromActivity, final String geocode) {
final Intent mapIntent = newIntent(fromActivity);
- mapIntent.putExtra(EXTRAS_MAP_MODE, MapMode.SINGLE);
- mapIntent.putExtra(EXTRAS_LIVE_ENABLED, false);
- mapIntent.putExtra(EXTRAS_GEOCODE, geocode);
- mapIntent.putExtra(EXTRAS_MAP_TITLE, geocode);
+ mapIntent.putExtra(Intents.EXTRA_MAP_MODE, MapMode.SINGLE);
+ mapIntent.putExtra(Intents.EXTRA_LIVE_ENABLED, false);
+ mapIntent.putExtra(Intents.EXTRA_GEOCODE, geocode);
+ mapIntent.putExtra(Intents.EXTRA_MAP_TITLE, geocode);
fromActivity.startActivity(mapIntent);
}
diff --git a/main/src/cgeo/geocaching/maps/MapActivity.java b/main/src/cgeo/geocaching/maps/MapActivity.java
new file mode 100644
index 0000000..28668ca
--- /dev/null
+++ b/main/src/cgeo/geocaching/maps/MapActivity.java
@@ -0,0 +1,17 @@
+package cgeo.geocaching.maps;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * This activity provides an entry point for external intent calls, and then forwards to the currently used map activity
+ * implementation.
+ */
+public class MapActivity extends Activity {
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ startActivity(CGeoMap.getLiveMapIntent(this));
+ finish();
+ }
+}
diff --git a/main/src/cgeo/geocaching/network/HtmlImage.java b/main/src/cgeo/geocaching/network/HtmlImage.java
index 4a5c506..ab902d2 100644
--- a/main/src/cgeo/geocaching/network/HtmlImage.java
+++ b/main/src/cgeo/geocaching/network/HtmlImage.java
@@ -154,6 +154,10 @@ public class HtmlImage implements Html.ImageGetter {
if (view == null) {
return drawable.toBlocking().lastOrDefault(null);
}
+ return getContainerDrawable(drawable);
+ }
+
+ protected BitmapDrawable getContainerDrawable(final Observable<BitmapDrawable> drawable) {
return new ContainerDrawable(view, drawable);
}
@@ -162,17 +166,17 @@ public class HtmlImage implements Html.ImageGetter {
public Observable<BitmapDrawable> fetchDrawable(final String url) {
if (StringUtils.isBlank(url) || ImageUtils.containsPattern(url, BLOCKED)) {
- return Observable.from(ImageUtils.getTransparent1x1Drawable(resources));
+ return Observable.just(ImageUtils.getTransparent1x1Drawable(resources));
}
// Explicit local file URLs are loaded from the filesystem regardless of their age. The IO part is short
// enough to make the whole operation on the computation scheduler.
if (FileUtils.isFileUrl(url)) {
- return Observable.defer(new Func0<Observable<? extends BitmapDrawable>>() {
+ return Observable.defer(new Func0<Observable<BitmapDrawable>>() {
@Override
- public Observable<? extends BitmapDrawable> call() {
+ public Observable<BitmapDrawable> call() {
final Bitmap bitmap = loadCachedImage(FileUtils.urlToFile(url), true).getLeft();
- return bitmap != null ? Observable.from(ImageUtils.scaleBitmapToFitDisplay(bitmap)) : Observable.<BitmapDrawable>empty();
+ return bitmap != null ? Observable.just(ImageUtils.scaleBitmapToFitDisplay(bitmap)) : Observable.<BitmapDrawable>empty();
}
}).subscribeOn(RxUtils.computationScheduler);
}
diff --git a/main/src/cgeo/geocaching/network/Network.java b/main/src/cgeo/geocaching/network/Network.java
index 40f7f7e..e8c2b28 100644
--- a/main/src/cgeo/geocaching/network/Network.java
+++ b/main/src/cgeo/geocaching/network/Network.java
@@ -238,7 +238,7 @@ public abstract class Network {
Log.w(status + " [" + response.getStatusLine().getReasonPhrase() + "]" + formatTimeSpan(before) + reqLogStr);
}
return response;
- } catch (final IOException e) {
+ } catch (final Exception e) {
final String timeSpan = formatTimeSpan(before);
Log.w("Failure" + timeSpan + reqLogStr + " (" + e.toString() + ")");
}
diff --git a/main/src/cgeo/geocaching/network/SmileyImage.java b/main/src/cgeo/geocaching/network/SmileyImage.java
index ebac2bb..86baeaa 100644
--- a/main/src/cgeo/geocaching/network/SmileyImage.java
+++ b/main/src/cgeo/geocaching/network/SmileyImage.java
@@ -1,14 +1,21 @@
package cgeo.geocaching.network;
import cgeo.geocaching.list.StoredList;
+import cgeo.geocaching.utils.ImageUtils;
+import cgeo.geocaching.utils.ImageUtils.LineHeightContainerDrawable;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
+import rx.Observable;
+
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.widget.TextView;
+/**
+ * Specialized image class for fetching and displaying smileys in the log book.
+ */
public class SmileyImage extends HtmlImage {
public SmileyImage(final String geocode, final TextView view) {
@@ -20,10 +27,8 @@ public class SmileyImage extends HtmlImage {
final Bitmap bitmap = loadResult.getLeft();
BitmapDrawable drawable;
if (bitmap != null) {
- final int lineHeight = (int) (view.getLineHeight() * 0.8);
drawable = new BitmapDrawable(view.getResources(), bitmap);
- final int width = drawable.getIntrinsicWidth() * lineHeight / drawable.getIntrinsicHeight();
- drawable.setBounds(0, 0, width, lineHeight);
+ drawable.setBounds(ImageUtils.scaleImageToLineHeight(drawable, view));
}
else {
drawable = null;
@@ -31,4 +36,9 @@ public class SmileyImage extends HtmlImage {
return new ImmutablePair<>(drawable, loadResult.getRight());
}
+ @Override
+ protected BitmapDrawable getContainerDrawable(final Observable<BitmapDrawable> drawable) {
+ return new LineHeightContainerDrawable(view, drawable);
+ }
+
}
diff --git a/main/src/cgeo/geocaching/playservices/LocationProvider.java b/main/src/cgeo/geocaching/playservices/LocationProvider.java
index a1edb7a..f235a3b 100644
--- a/main/src/cgeo/geocaching/playservices/LocationProvider.java
+++ b/main/src/cgeo/geocaching/playservices/LocationProvider.java
@@ -2,8 +2,9 @@ package cgeo.geocaching.playservices;
import cgeo.geocaching.sensors.GeoData;
import cgeo.geocaching.sensors.IGeoData;
+import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.utils.Log;
-import cgeo.geocaching.utils.RxUtils.LooperCallbacks;
+import cgeo.geocaching.utils.RxUtils;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
@@ -13,45 +14,105 @@ import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import rx.Observable;
+import rx.Observable.OnSubscribe;
+import rx.Subscriber;
+import rx.functions.Action0;
+import rx.functions.Action1;
import rx.functions.Func1;
+import rx.subjects.ReplaySubject;
+import rx.subscriptions.Subscriptions;
import android.content.Context;
import android.location.Location;
import android.os.Bundle;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
-public class LocationProvider extends LooperCallbacks<IGeoData> implements ConnectionCallbacks, OnConnectionFailedListener, LocationListener {
+public class LocationProvider implements ConnectionCallbacks, OnConnectionFailedListener, LocationListener {
- private final LocationClient locationClient;
- private final boolean lowPower;
- private final boolean withInitialLocation;
- private boolean onlyInitialLocation;
private static final LocationRequest LOCATION_REQUEST =
LocationRequest.create().setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY).setInterval(2000).setFastestInterval(250);
private static final LocationRequest LOCATION_REQUEST_LOW_POWER =
LocationRequest.create().setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY).setInterval(10000).setFastestInterval(5000);
+ private static final AtomicInteger mostPreciseCount = new AtomicInteger(0);
+ private static final AtomicInteger lowPowerCount = new AtomicInteger(0);
+ private static LocationProvider instance = null;
+ private static ReplaySubject<IGeoData> subject = ReplaySubject.createWithSize(1);
+ private final LocationClient locationClient;
+
+ private static synchronized LocationProvider getInstance(final Context context) {
+ if (instance == null) {
+ instance = new LocationProvider(context);
+ }
+ return instance;
+ }
+
+ private synchronized void updateRequest() {
+ if (locationClient.isConnected()) {
+ if (mostPreciseCount.get() > 0) {
+ Log.d("LocationProvider: requesting most precise locations");
+ locationClient.requestLocationUpdates(LOCATION_REQUEST, this, RxUtils.looperCallbacksLooper);
+ } else if (lowPowerCount.get() > 0) {
+ Log.d("LocationProvider: requesting low-power locations");
+ locationClient.requestLocationUpdates(LOCATION_REQUEST_LOW_POWER, this, RxUtils.looperCallbacksLooper);
+ } else {
+ Log.d("LocationProvider: stopping location requests");
+ locationClient.removeLocationUpdates(this);
+ }
+ }
+ }
- public static Observable<IGeoData> getInitialLocation(final Context context, final boolean lowPower) {
- return Observable.create(new LocationProvider(context, lowPower, true, true));
+ private static Observable<IGeoData> get(final Context context, final AtomicInteger reference) {
+ final LocationProvider instance = getInstance(context);
+ return Observable.create(new OnSubscribe<IGeoData>() {
+ @Override
+ public void call(final Subscriber<? super IGeoData> subscriber) {
+ if (reference.incrementAndGet() == 1) {
+ instance.updateRequest();
+ }
+ subscriber.add(Subscriptions.create(new Action0() {
+ @Override
+ public void call() {
+ RxUtils.looperCallbacksWorker.schedule(new Action0() {
+ @Override
+ public void call() {
+ if (reference.decrementAndGet() == 0) {
+ instance.updateRequest();
+ }
+ }
+ }, 2500, TimeUnit.MILLISECONDS);
+ }
+ }));
+ subscriber.add(subject.subscribe(new Action1<IGeoData>() {
+ @Override
+ public void call(final IGeoData geoData) {
+ subscriber.onNext(geoData);
+ }
+ }));
+ }
+ });
}
- public static Observable<IGeoData> getMostPrecise(Context context, boolean withInitialLocation) {
- return Observable.create(new LocationProvider(context, false, withInitialLocation, false));
+ private static Observable<IGeoData> getInitialLocation(final Context context, final boolean lowPower) {
+ return get(context, lowPower ? lowPowerCount : mostPreciseCount).first();
+ }
+
+ public static Observable<IGeoData> getMostPrecise(final Context context) {
+ return get(context, mostPreciseCount);
}
public static Observable<IGeoData> getLowPower(Context context, boolean withInitialLocation) {
final Observable<IGeoData> initialLocationObservable = withInitialLocation ? getInitialLocation(context, true) : Observable.<IGeoData>empty();
- final Observable<IGeoData> lowPowerObservable = Observable.create(new LocationProvider(context, true, false, false));
- final Observable<IGeoData> lowPowerObservable2 = Observable.create(new LocationProvider(context, true, false, false));
- final Observable<IGeoData> gpsFixObservable = getMostPrecise(context, false).takeFirst(new Func1<IGeoData, Boolean>() {
+ final Observable<IGeoData> lowPowerObservable = get(context, lowPowerCount).skip(1);
+ final Observable<IGeoData> gpsFixObservable = get(context, mostPreciseCount).skip(1).lift(RxUtils.operatorTakeUntil(new Func1<IGeoData, Boolean>() {
@Override
public Boolean call(final IGeoData geoData) {
return geoData.getAccuracy() < 20;
}
- }).delaySubscription(2, TimeUnit.SECONDS);
- return initialLocationObservable.concatWith(lowPowerObservable.mergeWith(gpsFixObservable).first()
- .concatWith(lowPowerObservable2).timeout(60, TimeUnit.SECONDS).retry());
+ }));
+ return initialLocationObservable.concatWith(lowPowerObservable.ambWith(gpsFixObservable.delaySubscription(6, TimeUnit.SECONDS)).first()
+ .concatWith(lowPowerObservable).timeout(25, TimeUnit.SECONDS).retry());
}
/**
@@ -61,48 +122,19 @@ public class LocationProvider extends LooperCallbacks<IGeoData> implements Conne
* at will.
*
* @param context the context used to retrieve the system services
- * @param lowPower <tt>true</tt> if the GPS must not be used
- * @param withInitialLocation <tt>true</tt> if the initial location must be provided
- * @param onlyInitialLocation <tt>true</tt> if the observable must be closed after providing the initial location
*/
- protected LocationProvider(final Context context, final boolean lowPower, final boolean withInitialLocation, final boolean onlyInitialLocation) {
- super(lowPower ? 0 : 2500, TimeUnit.MILLISECONDS);
- if (onlyInitialLocation && !withInitialLocation) {
- throw new IllegalArgumentException("LocationProvider: cannot request only initial location without requesting it at all");
+ private LocationProvider(final Context context) {
+ final IGeoData initialLocation = GeoData.getInitialLocation(context);
+ if (initialLocation != null) {
+ subject.onNext(initialLocation);
}
locationClient = new LocationClient(context, this, this);
- this.withInitialLocation = withInitialLocation;
- this.lowPower = lowPower;
- this.onlyInitialLocation = onlyInitialLocation;
- }
-
- @Override
- public void onStart() {
- Log.d("LocationProvider: starting the location listener - lowPower: " + lowPower);
locationClient.connect();
}
@Override
- public void onStop() {
- Log.d("LocationProvider: stopping the location listener - lowPower: " + lowPower);
- if (!onlyInitialLocation) {
- locationClient.removeLocationUpdates(this);
- }
- locationClient.disconnect();
- }
-
- @Override
public void onConnected(final Bundle bundle) {
- if (withInitialLocation) {
- final Location initialLocation = locationClient.getLastLocation();
- Log.d("LocationProvider: starting with " + (initialLocation == null ? "dummy" : "previous") + " location");
- subscriber.onNext(initialLocation != null ? new GeoData(initialLocation) : GeoData.dummyLocation());
- }
- if (onlyInitialLocation) {
- subscriber.onCompleted();
- } else {
- locationClient.requestLocationUpdates(lowPower ? LOCATION_REQUEST_LOW_POWER : LOCATION_REQUEST, this);
- }
+ updateRequest();
}
@Override
@@ -112,12 +144,14 @@ public class LocationProvider extends LooperCallbacks<IGeoData> implements Conne
@Override
public void onConnectionFailed(final ConnectionResult connectionResult) {
Log.e("cannot connect to Google Play location service: " + connectionResult);
- subscriber.onError(new RuntimeException("Connection failed: " + connectionResult));
+ subject.onError(new RuntimeException("Connection failed: " + connectionResult));
}
@Override
public void onLocationChanged(final Location location) {
- location.setProvider(lowPower ? GeoData.LOW_POWER_PROVIDER : GeoData.FUSED_PROVIDER);
- subscriber.onNext(new GeoData(location));
+ if (Settings.useLowPowerMode()) {
+ location.setProvider(GeoData.LOW_POWER_PROVIDER);
+ }
+ subject.onNext(new GeoData(location));
}
}
diff --git a/main/src/cgeo/geocaching/sensors/GeoData.java b/main/src/cgeo/geocaching/sensors/GeoData.java
index 1291d3c..561c09f 100644
--- a/main/src/cgeo/geocaching/sensors/GeoData.java
+++ b/main/src/cgeo/geocaching/sensors/GeoData.java
@@ -2,20 +2,44 @@ package cgeo.geocaching.sensors;
import cgeo.geocaching.enumerations.LocationProviderType;
import cgeo.geocaching.geopoint.Geopoint;
+import cgeo.geocaching.utils.Log;
+import android.content.Context;
import android.location.Location;
import android.location.LocationManager;
+import javax.annotation.Nullable;
+
public class GeoData extends Location implements IGeoData {
public static final String INITIAL_PROVIDER = "initial";
public static final String FUSED_PROVIDER = "fused";
public static final String LOW_POWER_PROVIDER = "low-power";
+ // Some devices will not have the last position available (for example the emulator). In this case,
+ // rather than waiting forever for a position update which might never come, we emulate it by placing
+ // the user arbitrarly at Paris Notre-Dame, one of the most visited free tourist attractions in the world.
+ final public static GeoData DUMMY_LOCATION = new GeoData(new Location(INITIAL_PROVIDER));
+ static {
+ DUMMY_LOCATION.setLatitude(48.85308);
+ DUMMY_LOCATION.setLongitude(2.34962);
+ }
+
public GeoData(final Location location) {
super(location);
}
+ @Nullable
+ static Location best(@Nullable final Location gpsLocation, @Nullable final Location netLocation) {
+ if (isRecent(gpsLocation) || !(netLocation != null)) {
+ return gpsLocation;
+ }
+ if (!(gpsLocation != null)) {
+ return netLocation;
+ }
+ return gpsLocation.getTime() >= netLocation.getTime() ? gpsLocation : netLocation;
+ }
+
@Override
public Location getLocation() {
return this;
@@ -48,13 +72,35 @@ public class GeoData extends Location implements IGeoData {
return new Geopoint(this);
}
- // Some devices will not have the last position available (for example the emulator). In this case,
- // rather than waiting forever for a position update which might never come, we emulate it by placing
- // the user arbitrarly at Paris Notre-Dame, one of the most visited free tourist attractions in the world.
- public static GeoData dummyLocation() {
- final Location location = new Location(INITIAL_PROVIDER);
- location.setLatitude(48.85308);
- location.setLongitude(2.34962);
- return new GeoData(location);
+ @Nullable public static GeoData getInitialLocation(final Context context) {
+ final LocationManager geoManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+ if (geoManager == null) {
+ Log.w("No LocationManager available");
+ return null;
+ }
+ try {
+ // Try to find a sensible initial location from the last locations known to Android.
+ final Location lastGpsLocation = geoManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
+ final Location lastNetworkLocation = geoManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
+ final Location bestLocation = best(lastGpsLocation, lastNetworkLocation);
+ if (bestLocation != null) {
+ bestLocation.setProvider(INITIAL_PROVIDER);
+ return new GeoData(bestLocation);
+ }
+ Log.i("No last known location available");
+ return null;
+ } catch (final Exception e) {
+ // This error is non-fatal as its only consequence is that we will start with a dummy location
+ // instead of a previously known one.
+ Log.e("Error when retrieving last known location", e);
+ return null;
+ }
}
+
+
+
+ public static boolean isRecent(@Nullable final Location location) {
+ return location != null && System.currentTimeMillis() <= location.getTime() + 30000;
+ }
+
}
diff --git a/main/src/cgeo/geocaching/sensors/GeoDataProvider.java b/main/src/cgeo/geocaching/sensors/GeoDataProvider.java
index 19eef6a..faecbe3 100644
--- a/main/src/cgeo/geocaching/sensors/GeoDataProvider.java
+++ b/main/src/cgeo/geocaching/sensors/GeoDataProvider.java
@@ -3,6 +3,8 @@ package cgeo.geocaching.sensors;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.RxUtils.LooperCallbacks;
+import org.apache.commons.lang3.StringUtils;
+
import rx.Observable;
import android.content.Context;
@@ -15,29 +17,11 @@ import java.util.concurrent.TimeUnit;
public class GeoDataProvider extends LooperCallbacks<IGeoData> {
+ private final Context context;
private final LocationManager geoManager;
- 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 static class LocationData {
- public Location location;
- public long timestamp = 0;
-
- public void update(final Location location) {
- this.location = location;
- timestamp = System.currentTimeMillis();
- }
-
- public boolean isRecent() {
- return isValid() && System.currentTimeMillis() < timestamp + 30000;
- }
-
- public boolean isValid() {
- return location != null;
- }
- }
+ private Location latestGPSLocation = null;
+ private final Listener networkListener = new Listener();
+ private final Listener gpsListener = new Listener();
/**
* Build a new geo data provider object.
@@ -49,7 +33,8 @@ public class GeoDataProvider extends LooperCallbacks<IGeoData> {
*/
protected GeoDataProvider(final Context context) {
super(2500, TimeUnit.MILLISECONDS);
- geoManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+ this.context = context.getApplicationContext();
+ geoManager = (LocationManager) this.context.getSystemService(Context.LOCATION_SERVICE);
}
public static Observable<IGeoData> create(final Context context) {
@@ -58,14 +43,20 @@ public class GeoDataProvider extends LooperCallbacks<IGeoData> {
@Override
public void onStart() {
- subscriber.onNext(findInitialLocation());
+ final IGeoData initialLocation = GeoData.getInitialLocation(context);
+ if (initialLocation != null) {
+ subscriber.onNext(initialLocation);
+ }
Log.d("GeoDataProvider: starting the GPS and network listeners");
- 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, e);
- }
+ try {
+ geoManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, gpsListener);
+ } catch (final Exception e) {
+ Log.w("Unable to create GPS location provider: " + e.getMessage());
+ }
+ try {
+ geoManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, networkListener);
+ } catch (final Exception e) {
+ Log.w("Unable to create network location provider: " + e.getMessage());
}
}
@@ -76,50 +67,16 @@ public class GeoDataProvider extends LooperCallbacks<IGeoData> {
geoManager.removeUpdates(gpsListener);
}
- private IGeoData findInitialLocation() {
- final Location initialLocation = new Location(GeoData.INITIAL_PROVIDER);
- try {
- // Try to find a sensible initial location from the last locations known to Android.
- final Location lastGpsLocation = geoManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
- final Location lastNetworkLocation = geoManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
-
- // If both providers are non-null, take the most recent one
- if (lastGpsLocation != null && lastNetworkLocation != null) {
- if (lastGpsLocation.getTime() >= lastNetworkLocation.getTime()) {
- copyCoords(initialLocation, lastGpsLocation);
- } else {
- copyCoords(initialLocation, lastNetworkLocation);
- }
- } else if (lastGpsLocation != null) {
- copyCoords(initialLocation, lastGpsLocation);
- } else if (lastNetworkLocation != null) {
- copyCoords(initialLocation, lastNetworkLocation);
- } else {
- Log.i("GeoDataProvider: no last known location available");
- return GeoData.dummyLocation();
- }
- } catch (final Exception e) {
- // This error is non-fatal as its only consequence is that we will start with a dummy location
- // instead of a previously known one.
- Log.e("GeoDataProvider: error when retrieving last known location", e);
- }
- // Start with an historical GeoData just in case someone queries it before we get
- // a chance to get any information.
- return new GeoData(initialLocation);
- }
-
- private static void copyCoords(final Location target, final Location source) {
- target.setLatitude(source.getLatitude());
- target.setLongitude(source.getLongitude());
- }
-
private class Listener implements LocationListener {
- private final String locationProvider;
- private final LocationData locationData;
- Listener(final String locationProvider, final LocationData locationData) {
- this.locationProvider = locationProvider;
- this.locationData = locationData;
+ @Override
+ public void onLocationChanged(final Location location) {
+ if (StringUtils.equals(location.getProvider(), LocationManager.GPS_PROVIDER)) {
+ latestGPSLocation = location;
+ assign(latestGPSLocation);
+ } else {
+ assign(GeoData.best(latestGPSLocation, location));
+ }
}
@Override
@@ -136,35 +93,11 @@ public class GeoDataProvider extends LooperCallbacks<IGeoData> {
public void onProviderEnabled(final String provider) {
// nothing
}
-
- @Override
- public void onLocationChanged(final Location location) {
- locationData.update(location);
- selectBest();
- }
- }
-
- private LocationData best() {
- if (gpsLocation.isRecent() || !netLocation.isValid()) {
- return gpsLocation.isValid() ? gpsLocation : null;
- }
- if (!gpsLocation.isValid()) {
- return netLocation;
- }
- return gpsLocation.timestamp > netLocation.timestamp ? gpsLocation : netLocation;
}
- private void selectBest() {
- assign(best());
- }
-
- private void assign(final LocationData locationData) {
- if (locationData == null) {
- return;
- }
-
+ private void assign(final Location location) {
// We do not necessarily get signalled when satellites go to 0/0.
- final IGeoData current = new GeoData(locationData.location);
+ final IGeoData current = new GeoData(location);
subscriber.onNext(current);
}
diff --git a/main/src/cgeo/geocaching/sensors/GeoDirHandler.java b/main/src/cgeo/geocaching/sensors/GeoDirHandler.java
index 4a63c3d..d127784 100644
--- a/main/src/cgeo/geocaching/sensors/GeoDirHandler.java
+++ b/main/src/cgeo/geocaching/sensors/GeoDirHandler.java
@@ -2,6 +2,7 @@ package cgeo.geocaching.sensors;
import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.settings.Settings;
+import cgeo.geocaching.utils.AngleUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
@@ -21,7 +22,7 @@ import rx.subscriptions.CompositeSubscription;
* accordingly in {@code onPause}.
*
* The direction is always relative to the top of the device (natural direction), and that it must
- * be fixed using {@link DirectionProvider#getDirectionNow(float)}. When the direction is derived from the GPS,
+ * be fixed using {@link cgeo.geocaching.utils.AngleUtils#getDirectionNow(float)}. When the direction is derived from the GPS,
* it is altered so that the fix can still be applied as if the information came from the compass.
*/
public abstract class GeoDirHandler {
@@ -77,7 +78,7 @@ public abstract class GeoDirHandler {
private static float fixDirection(final IGeoData geoData, final float direction) {
final boolean useGPSBearing = !Settings.isUseCompass() || geoData.getSpeed() > 5;
- return useGPSBearing ? DirectionProvider.reverseDirectionNow(geoData.getBearing()) : direction;
+ return useGPSBearing ? AngleUtils.reverseDirectionNow(geoData.getBearing()) : direction;
}
/**
diff --git a/main/src/cgeo/geocaching/sensors/GpsStatusProvider.java b/main/src/cgeo/geocaching/sensors/GpsStatusProvider.java
index ec29a6a..5f12e99 100644
--- a/main/src/cgeo/geocaching/sensors/GpsStatusProvider.java
+++ b/main/src/cgeo/geocaching/sensors/GpsStatusProvider.java
@@ -13,8 +13,6 @@ import android.location.LocationManager;
public class GpsStatusProvider extends LooperCallbacks<Status> {
- public static final Status NO_GPS = new Status(false, 0, 0);
-
public static class Status {
final public boolean gpsEnabled;
final public int satellitesVisible;
@@ -31,6 +29,8 @@ public class GpsStatusProvider extends LooperCallbacks<Status> {
private final GpsStatus.Listener gpsStatusListener = new GpsStatusListener();
private Status latest = new Status(false, 0, 0);
+ private static final Status NO_GPS = new Status(false, 0, 0);
+
/**
* Build a new gps status provider object.
* <p/>
@@ -50,6 +50,7 @@ public class GpsStatusProvider extends LooperCallbacks<Status> {
@Override
protected void onStart() {
Log.d("GpsStatusProvider: starting the GPS status listener");
+ subscriber.onNext(NO_GPS);
geoManager.addGpsStatusListener(gpsStatusListener);
}
diff --git a/main/src/cgeo/geocaching/sensors/DirectionProvider.java b/main/src/cgeo/geocaching/sensors/OrientationProvider.java
index 907dc3d..83e0638 100644
--- a/main/src/cgeo/geocaching/sensors/DirectionProvider.java
+++ b/main/src/cgeo/geocaching/sensors/OrientationProvider.java
@@ -1,7 +1,6 @@
package cgeo.geocaching.sensors;
-import cgeo.geocaching.CgeoApplication;
-import cgeo.geocaching.utils.AngleUtils;
+import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.RxUtils.LooperCallbacks;
import rx.Observable;
@@ -11,20 +10,21 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
-import android.view.Surface;
-import android.view.WindowManager;
-public class DirectionProvider extends LooperCallbacks<Float> implements SensorEventListener {
-
- private static final WindowManager WINDOW_MANAGER = (WindowManager) CgeoApplication.getInstance().getSystemService(Context.WINDOW_SERVICE);
+public class OrientationProvider extends LooperCallbacks<Float> implements SensorEventListener {
private final SensorManager sensorManager;
private final Sensor orientationSensor;
@SuppressWarnings("deprecation")
- protected DirectionProvider(final Context context) {
+ protected OrientationProvider(final Context context) {
sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
orientationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
+ if (orientationSensor != null) {
+ Log.d("OrientationProvider: sensor found");
+ } else {
+ Log.w("OrientationProvider: no orientation sensor on this device");
+ }
}
@Override
@@ -45,47 +45,23 @@ public class DirectionProvider extends LooperCallbacks<Float> implements SensorE
@Override
public void onStart() {
if (orientationSensor != null) {
+ Log.d("OrientationProvider: starting the orientation provider");
sensorManager.registerListener(this, orientationSensor, SensorManager.SENSOR_DELAY_NORMAL);
+ } else {
+ subscriber.onError(new RuntimeException("orientation sensor is absent on this device"));
}
}
@Override
public void onStop() {
if (orientationSensor != null) {
+ Log.d("OrientationProvider: stopping the orientation provider");
sensorManager.unregisterListener(this);
}
}
public static Observable<Float> create(final Context context) {
- return Observable.create(new DirectionProvider(context));
- }
-
- /**
- * Take the phone rotation (through a given activity) in account and adjust the direction.
- *
- * @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 float direction) {
- return AngleUtils.normalize(direction + getRotationOffset());
- }
-
- static float reverseDirectionNow(final float direction) {
- return AngleUtils.normalize(direction - getRotationOffset());
- }
-
- private static int getRotationOffset() {
- switch (WINDOW_MANAGER.getDefaultDisplay().getRotation()) {
- case Surface.ROTATION_90:
- return 90;
- case Surface.ROTATION_180:
- return 180;
- case Surface.ROTATION_270:
- return 270;
- default:
- return 0;
- }
+ return Observable.create(new OrientationProvider(context));
}
}
diff --git a/main/src/cgeo/geocaching/sensors/RotationProvider.java b/main/src/cgeo/geocaching/sensors/RotationProvider.java
new file mode 100644
index 0000000..40e2c3c
--- /dev/null
+++ b/main/src/cgeo/geocaching/sensors/RotationProvider.java
@@ -0,0 +1,83 @@
+package cgeo.geocaching.sensors;
+
+import cgeo.geocaching.utils.Log;
+import cgeo.geocaching.utils.RxUtils.LooperCallbacks;
+
+import rx.Observable;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+
+public class RotationProvider extends LooperCallbacks<Float> implements SensorEventListener {
+
+ private final SensorManager sensorManager;
+ private final Sensor rotationSensor;
+ private final float[] rotationMatrix = new float[16];
+ private final float[] orientation = new float[4];
+ private final float[] values = new float[4];
+
+ @TargetApi(19)
+ protected RotationProvider(final Context context, final boolean lowPower) {
+ sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ // The geomagnetic rotation vector introduced in Android 4.4 (API 19) requires less power. Favour it
+ // even if it is more sensible to noise in low-power settings.
+ final Sensor sensor = lowPower ? sensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR) : null;
+ if (sensor != null) {
+ rotationSensor = sensor;
+ Log.d("RotationProvider: geomagnetic (low-power) sensor found");
+ } else {
+ rotationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
+ if (rotationSensor != null) {
+ Log.d("RotationProvider: sensor found");
+ } else {
+ Log.w("RotationProvider: no rotation sensor on this device");
+ }
+ }
+ }
+
+ @Override
+ public void onSensorChanged(final SensorEvent event) {
+ // On some Samsung devices, SensorManager#getRotationMatrixFromVector throws an exception if the rotation
+ // vector has more than 4 elements. Since only the four first elements are used, we can truncate the vector
+ // without losing precision.
+ if (event.values.length > 4) {
+ System.arraycopy(event.values, 0, values, 0, 4);
+ SensorManager.getRotationMatrixFromVector(rotationMatrix, values);
+ } else {
+ SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values);
+ }
+ SensorManager.getOrientation(rotationMatrix, orientation);
+ subscriber.onNext((float) (orientation[0] * 180 / Math.PI));
+ }
+
+ @Override
+ public void onAccuracyChanged(final Sensor sensor, final int accuracy) {
+ }
+
+ @Override
+ public void onStart() {
+ if (rotationSensor != null) {
+ Log.d("RotationProvider: starting the rotation provider");
+ sensorManager.registerListener(this, rotationSensor, SensorManager.SENSOR_DELAY_NORMAL);
+ } else {
+ subscriber.onError(new RuntimeException("rotation sensor is absent on this device"));
+ }
+ }
+
+ @Override
+ public void onStop() {
+ if (rotationSensor != null) {
+ Log.d("RotationProvider: stopping the rotation provider");
+ sensorManager.unregisterListener(this);
+ }
+ }
+
+ public static Observable<Float> create(final Context context, final boolean lowPower) {
+ return Observable.create(new RotationProvider(context, lowPower));
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java b/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java
index 84c343a..93480ee 100644
--- a/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java
+++ b/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java
@@ -69,7 +69,7 @@ public class RegisterSend2CgeoPreference extends AbstractClickablePreference {
final String[] strings = StringUtils.split(Network.getResponseData(response), ',');
Settings.setWebNameCode(nam, strings[0]);
try {
- return Observable.from(Integer.parseInt(strings[1].trim()));
+ return Observable.just(Integer.parseInt(strings[1].trim()));
} catch (final Exception e) {
Log.e("RegisterSend2CgeoPreference", e);
}
diff --git a/main/src/cgeo/geocaching/settings/Settings.java b/main/src/cgeo/geocaching/settings/Settings.java
index 6b9cbd5..4dd959b 100644
--- a/main/src/cgeo/geocaching/settings/Settings.java
+++ b/main/src/cgeo/geocaching/settings/Settings.java
@@ -34,6 +34,7 @@ import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.os.Build;
import android.os.Environment;
import android.preference.PreferenceManager;
@@ -53,6 +54,17 @@ public class Settings {
public static final int SHOW_WP_THRESHOLD_MAX = 50;
private static final int MAP_SOURCE_DEFAULT = GoogleMapProvider.GOOGLE_MAP_ID.hashCode();
+ public static final boolean HW_ACCEL_DISABLED_BY_DEFAULT =
+ Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 ||
+ StringUtils.equals(Build.MODEL, "HTC One X") || // HTC One X
+ StringUtils.equals(Build.MODEL, "HTC One S") || // HTC One S
+ StringUtils.equals(Build.MODEL, "GT-I8190") || // Samsung S3 mini
+ StringUtils.equals(Build.MODEL, "GT-S6310L") || // Samsung Galaxy Young
+ StringUtils.equals(Build.MODEL, "GT-P5210") || // Samsung Galaxy Tab 3
+ StringUtils.equals(Build.MODEL, "GT-S7580") || // Samsung Galaxy Trend Plus
+ StringUtils.equals(Build.MODEL, "ST25i") || // Sony Xperia U
+ StringUtils.equals(Build.MODEL, "bq Aquaris 5"); // bq Aquaris 5
+
private final static int unitsMetric = 1;
// twitter api keys
@@ -65,6 +77,8 @@ public class Settings {
Min,
Sec;
+ static int DEFAULT_INT_VALUE = Min.ordinal();
+
public static CoordInputFormatEnum fromInt(final int id) {
final CoordInputFormatEnum[] values = CoordInputFormatEnum.values();
if (id < 0 || id >= values.length) {
@@ -94,74 +108,89 @@ public class Settings {
}
private static void migrateSettings() {
- // migrate from non standard file location and integer based boolean types
- final int oldVersion = getInt(R.string.pref_settingsversion, 0);
- if (oldVersion < 1) {
- final String oldPreferencesName = "cgeo.pref";
- final SharedPreferences old = CgeoApplication.getInstance().getSharedPreferences(oldPreferencesName, Context.MODE_PRIVATE);
+ final int LATEST_PREFERENCES_VERSION = 2;
+ final int currentVersion = getInt(R.string.pref_settingsversion, 0);
+
+ // No need to migrate if we are up to date.
+ if (currentVersion == LATEST_PREFERENCES_VERSION) {
+ return;
+ }
+
+ // No need to migrate if we don't have older settings, defaults will be used instead.
+ final String preferencesNameV0 = "cgeo.pref";
+ final SharedPreferences prefsV0 = CgeoApplication.getInstance().getSharedPreferences(preferencesNameV0, Context.MODE_PRIVATE);
+ if (currentVersion == 0 && prefsV0.getAll().isEmpty()) {
+ final Editor e = sharedPrefs.edit();
+ e.putInt(getKey(R.string.pref_settingsversion), LATEST_PREFERENCES_VERSION);
+ e.commit();
+ return;
+ }
+
+ if (currentVersion < 1) {
+ // migrate from non standard file location and integer based boolean types
final Editor e = sharedPrefs.edit();
- e.putString(getKey(R.string.pref_temp_twitter_token_secret), old.getString(getKey(R.string.pref_temp_twitter_token_secret), null));
- e.putString(getKey(R.string.pref_temp_twitter_token_public), old.getString(getKey(R.string.pref_temp_twitter_token_public), null));
- e.putBoolean(getKey(R.string.pref_help_shown), old.getInt(getKey(R.string.pref_help_shown), 0) != 0);
- e.putFloat(getKey(R.string.pref_anylongitude), old.getFloat(getKey(R.string.pref_anylongitude), 0));
- e.putFloat(getKey(R.string.pref_anylatitude), old.getFloat(getKey(R.string.pref_anylatitude), 0));
- e.putBoolean(getKey(R.string.pref_offlinemaps), 0 != old.getInt(getKey(R.string.pref_offlinemaps), 1));
- e.putBoolean(getKey(R.string.pref_offlinewpmaps), 0 != old.getInt(getKey(R.string.pref_offlinewpmaps), 0));
- e.putString(getKey(R.string.pref_webDeviceCode), old.getString(getKey(R.string.pref_webDeviceCode), null));
- e.putString(getKey(R.string.pref_webDeviceName), old.getString(getKey(R.string.pref_webDeviceName), null));
- e.putBoolean(getKey(R.string.pref_maplive), old.getInt(getKey(R.string.pref_maplive), 1) != 0);
- e.putInt(getKey(R.string.pref_mapsource), old.getInt(getKey(R.string.pref_mapsource), MAP_SOURCE_DEFAULT));
- e.putBoolean(getKey(R.string.pref_twitter), 0 != old.getInt(getKey(R.string.pref_twitter), 0));
- e.putBoolean(getKey(R.string.pref_showaddress), 0 != old.getInt(getKey(R.string.pref_showaddress), 1));
- e.putBoolean(getKey(R.string.pref_showcaptcha), old.getBoolean(getKey(R.string.pref_showcaptcha), false));
- e.putBoolean(getKey(R.string.pref_maptrail), old.getInt(getKey(R.string.pref_maptrail), 1) != 0);
- e.putInt(getKey(R.string.pref_lastmapzoom), old.getInt(getKey(R.string.pref_lastmapzoom), 14));
- e.putBoolean(getKey(R.string.pref_livelist), 0 != old.getInt(getKey(R.string.pref_livelist), 1));
- e.putBoolean(getKey(R.string.pref_units), old.getInt(getKey(R.string.pref_units), unitsMetric) == unitsMetric);
- e.putBoolean(getKey(R.string.pref_skin), old.getInt(getKey(R.string.pref_skin), 0) != 0);
- e.putInt(getKey(R.string.pref_lastusedlist), old.getInt(getKey(R.string.pref_lastusedlist), StoredList.STANDARD_LIST_ID));
- e.putString(getKey(R.string.pref_cachetype), old.getString(getKey(R.string.pref_cachetype), CacheType.ALL.id));
- e.putString(getKey(R.string.pref_twitter_token_secret), old.getString(getKey(R.string.pref_twitter_token_secret), null));
- e.putString(getKey(R.string.pref_twitter_token_public), old.getString(getKey(R.string.pref_twitter_token_public), null));
- e.putInt(getKey(R.string.pref_version), old.getInt(getKey(R.string.pref_version), 0));
- e.putBoolean(getKey(R.string.pref_autoloaddesc), 0 != old.getInt(getKey(R.string.pref_autoloaddesc), 1));
- e.putBoolean(getKey(R.string.pref_ratingwanted), old.getBoolean(getKey(R.string.pref_ratingwanted), true));
- e.putBoolean(getKey(R.string.pref_friendlogswanted), old.getBoolean(getKey(R.string.pref_friendlogswanted), true));
- e.putBoolean(getKey(R.string.pref_useenglish), old.getBoolean(getKey(R.string.pref_useenglish), false));
- e.putBoolean(getKey(R.string.pref_usecompass), 0 != old.getInt(getKey(R.string.pref_usecompass), 1));
- e.putBoolean(getKey(R.string.pref_trackautovisit), old.getBoolean(getKey(R.string.pref_trackautovisit), false));
- e.putBoolean(getKey(R.string.pref_sigautoinsert), old.getBoolean(getKey(R.string.pref_sigautoinsert), false));
- e.putBoolean(getKey(R.string.pref_logimages), old.getBoolean(getKey(R.string.pref_logimages), false));
- e.putBoolean(getKey(R.string.pref_excludedisabled), 0 != old.getInt(getKey(R.string.pref_excludedisabled), 0));
- e.putBoolean(getKey(R.string.pref_excludemine), 0 != old.getInt(getKey(R.string.pref_excludemine), 0));
- e.putString(getKey(R.string.pref_mapfile), old.getString(getKey(R.string.pref_mapfile), null));
- e.putString(getKey(R.string.pref_signature), old.getString(getKey(R.string.pref_signature), null));
- e.putString(getKey(R.string.pref_pass_vote), old.getString(getKey(R.string.pref_pass_vote), null));
- e.putString(getKey(R.string.pref_password), old.getString(getKey(R.string.pref_password), null));
- e.putString(getKey(R.string.pref_username), old.getString(getKey(R.string.pref_username), null));
- e.putString(getKey(R.string.pref_memberstatus), old.getString(getKey(R.string.pref_memberstatus), ""));
- e.putInt(getKey(R.string.pref_coordinputformat), old.getInt(getKey(R.string.pref_coordinputformat), 0));
- e.putBoolean(getKey(R.string.pref_log_offline), old.getBoolean(getKey(R.string.pref_log_offline), false));
- e.putBoolean(getKey(R.string.pref_choose_list), old.getBoolean(getKey(R.string.pref_choose_list), true));
- e.putBoolean(getKey(R.string.pref_loaddirectionimg), old.getBoolean(getKey(R.string.pref_loaddirectionimg), true));
- e.putString(getKey(R.string.pref_gccustomdate), old.getString(getKey(R.string.pref_gccustomdate), null));
- e.putInt(getKey(R.string.pref_showwaypointsthreshold), old.getInt(getKey(R.string.pref_showwaypointsthreshold), SHOW_WP_THRESHOLD_DEFAULT));
- e.putString(getKey(R.string.pref_cookiestore), old.getString(getKey(R.string.pref_cookiestore), null));
- e.putBoolean(getKey(R.string.pref_opendetailslastpage), old.getBoolean(getKey(R.string.pref_opendetailslastpage), false));
- e.putInt(getKey(R.string.pref_lastdetailspage), old.getInt(getKey(R.string.pref_lastdetailspage), 1));
- e.putInt(getKey(R.string.pref_defaultNavigationTool), old.getInt(getKey(R.string.pref_defaultNavigationTool), NavigationAppsEnum.COMPASS.id));
- e.putInt(getKey(R.string.pref_defaultNavigationTool2), old.getInt(getKey(R.string.pref_defaultNavigationTool2), NavigationAppsEnum.INTERNAL_MAP.id));
- e.putInt(getKey(R.string.pref_livemapstrategy), old.getInt(getKey(R.string.pref_livemapstrategy), Strategy.AUTO.id));
- e.putBoolean(getKey(R.string.pref_debug), old.getBoolean(getKey(R.string.pref_debug), false));
- e.putInt(getKey(R.string.pref_livemaphintshowcount), old.getInt(getKey(R.string.pref_livemaphintshowcount), 0));
+ e.putString(getKey(R.string.pref_temp_twitter_token_secret), prefsV0.getString(getKey(R.string.pref_temp_twitter_token_secret), null));
+ e.putString(getKey(R.string.pref_temp_twitter_token_public), prefsV0.getString(getKey(R.string.pref_temp_twitter_token_public), null));
+ e.putBoolean(getKey(R.string.pref_help_shown), prefsV0.getInt(getKey(R.string.pref_help_shown), 0) != 0);
+ e.putFloat(getKey(R.string.pref_anylongitude), prefsV0.getFloat(getKey(R.string.pref_anylongitude), 0));
+ e.putFloat(getKey(R.string.pref_anylatitude), prefsV0.getFloat(getKey(R.string.pref_anylatitude), 0));
+ e.putBoolean(getKey(R.string.pref_offlinemaps), 0 != prefsV0.getInt(getKey(R.string.pref_offlinemaps), 1));
+ e.putBoolean(getKey(R.string.pref_offlinewpmaps), 0 != prefsV0.getInt(getKey(R.string.pref_offlinewpmaps), 0));
+ e.putString(getKey(R.string.pref_webDeviceCode), prefsV0.getString(getKey(R.string.pref_webDeviceCode), null));
+ e.putString(getKey(R.string.pref_webDeviceName), prefsV0.getString(getKey(R.string.pref_webDeviceName), null));
+ e.putBoolean(getKey(R.string.pref_maplive), prefsV0.getInt(getKey(R.string.pref_maplive), 1) != 0);
+ e.putInt(getKey(R.string.pref_mapsource), prefsV0.getInt(getKey(R.string.pref_mapsource), MAP_SOURCE_DEFAULT));
+ e.putBoolean(getKey(R.string.pref_twitter), 0 != prefsV0.getInt(getKey(R.string.pref_twitter), 0));
+ e.putBoolean(getKey(R.string.pref_showaddress), 0 != prefsV0.getInt(getKey(R.string.pref_showaddress), 1));
+ e.putBoolean(getKey(R.string.pref_showcaptcha), prefsV0.getBoolean(getKey(R.string.pref_showcaptcha), false));
+ e.putBoolean(getKey(R.string.pref_maptrail), prefsV0.getInt(getKey(R.string.pref_maptrail), 1) != 0);
+ e.putInt(getKey(R.string.pref_lastmapzoom), prefsV0.getInt(getKey(R.string.pref_lastmapzoom), 14));
+ e.putBoolean(getKey(R.string.pref_livelist), 0 != prefsV0.getInt(getKey(R.string.pref_livelist), 1));
+ e.putBoolean(getKey(R.string.pref_units), prefsV0.getInt(getKey(R.string.pref_units), unitsMetric) == unitsMetric);
+ e.putBoolean(getKey(R.string.pref_skin), prefsV0.getInt(getKey(R.string.pref_skin), 0) != 0);
+ e.putInt(getKey(R.string.pref_lastusedlist), prefsV0.getInt(getKey(R.string.pref_lastusedlist), StoredList.STANDARD_LIST_ID));
+ e.putString(getKey(R.string.pref_cachetype), prefsV0.getString(getKey(R.string.pref_cachetype), CacheType.ALL.id));
+ e.putString(getKey(R.string.pref_twitter_token_secret), prefsV0.getString(getKey(R.string.pref_twitter_token_secret), null));
+ e.putString(getKey(R.string.pref_twitter_token_public), prefsV0.getString(getKey(R.string.pref_twitter_token_public), null));
+ e.putInt(getKey(R.string.pref_version), prefsV0.getInt(getKey(R.string.pref_version), 0));
+ e.putBoolean(getKey(R.string.pref_autoloaddesc), 0 != prefsV0.getInt(getKey(R.string.pref_autoloaddesc), 1));
+ e.putBoolean(getKey(R.string.pref_ratingwanted), prefsV0.getBoolean(getKey(R.string.pref_ratingwanted), true));
+ e.putBoolean(getKey(R.string.pref_friendlogswanted), prefsV0.getBoolean(getKey(R.string.pref_friendlogswanted), true));
+ e.putBoolean(getKey(R.string.pref_useenglish), prefsV0.getBoolean(getKey(R.string.pref_useenglish), false));
+ e.putBoolean(getKey(R.string.pref_usecompass), 0 != prefsV0.getInt(getKey(R.string.pref_usecompass), 1));
+ e.putBoolean(getKey(R.string.pref_trackautovisit), prefsV0.getBoolean(getKey(R.string.pref_trackautovisit), false));
+ e.putBoolean(getKey(R.string.pref_sigautoinsert), prefsV0.getBoolean(getKey(R.string.pref_sigautoinsert), false));
+ e.putBoolean(getKey(R.string.pref_logimages), prefsV0.getBoolean(getKey(R.string.pref_logimages), false));
+ e.putBoolean(getKey(R.string.pref_excludedisabled), 0 != prefsV0.getInt(getKey(R.string.pref_excludedisabled), 0));
+ e.putBoolean(getKey(R.string.pref_excludemine), 0 != prefsV0.getInt(getKey(R.string.pref_excludemine), 0));
+ e.putString(getKey(R.string.pref_mapfile), prefsV0.getString(getKey(R.string.pref_mapfile), null));
+ e.putString(getKey(R.string.pref_signature), prefsV0.getString(getKey(R.string.pref_signature), null));
+ e.putString(getKey(R.string.pref_pass_vote), prefsV0.getString(getKey(R.string.pref_pass_vote), null));
+ e.putString(getKey(R.string.pref_password), prefsV0.getString(getKey(R.string.pref_password), null));
+ e.putString(getKey(R.string.pref_username), prefsV0.getString(getKey(R.string.pref_username), null));
+ e.putString(getKey(R.string.pref_memberstatus), prefsV0.getString(getKey(R.string.pref_memberstatus), ""));
+ e.putInt(getKey(R.string.pref_coordinputformat), prefsV0.getInt(getKey(R.string.pref_coordinputformat), CoordInputFormatEnum.DEFAULT_INT_VALUE));
+ e.putBoolean(getKey(R.string.pref_log_offline), prefsV0.getBoolean(getKey(R.string.pref_log_offline), false));
+ e.putBoolean(getKey(R.string.pref_choose_list), prefsV0.getBoolean(getKey(R.string.pref_choose_list), true));
+ e.putBoolean(getKey(R.string.pref_loaddirectionimg), prefsV0.getBoolean(getKey(R.string.pref_loaddirectionimg), true));
+ e.putString(getKey(R.string.pref_gccustomdate), prefsV0.getString(getKey(R.string.pref_gccustomdate), null));
+ e.putInt(getKey(R.string.pref_showwaypointsthreshold), prefsV0.getInt(getKey(R.string.pref_showwaypointsthreshold), SHOW_WP_THRESHOLD_DEFAULT));
+ e.putString(getKey(R.string.pref_cookiestore), prefsV0.getString(getKey(R.string.pref_cookiestore), null));
+ e.putBoolean(getKey(R.string.pref_opendetailslastpage), prefsV0.getBoolean(getKey(R.string.pref_opendetailslastpage), false));
+ e.putInt(getKey(R.string.pref_lastdetailspage), prefsV0.getInt(getKey(R.string.pref_lastdetailspage), 1));
+ e.putInt(getKey(R.string.pref_defaultNavigationTool), prefsV0.getInt(getKey(R.string.pref_defaultNavigationTool), NavigationAppsEnum.COMPASS.id));
+ e.putInt(getKey(R.string.pref_defaultNavigationTool2), prefsV0.getInt(getKey(R.string.pref_defaultNavigationTool2), NavigationAppsEnum.INTERNAL_MAP.id));
+ e.putInt(getKey(R.string.pref_livemapstrategy), prefsV0.getInt(getKey(R.string.pref_livemapstrategy), Strategy.AUTO.id));
+ e.putBoolean(getKey(R.string.pref_debug), prefsV0.getBoolean(getKey(R.string.pref_debug), false));
+ e.putInt(getKey(R.string.pref_livemaphintshowcount), prefsV0.getInt(getKey(R.string.pref_livemaphintshowcount), 0));
e.putInt(getKey(R.string.pref_settingsversion), 1); // mark migrated
e.commit();
}
// changes for new settings dialog
- if (oldVersion < 2) {
+ if (currentVersion < 2) {
final Editor e = sharedPrefs.edit();
e.putBoolean(getKey(R.string.pref_units), !isUseImperialUnits());
@@ -398,7 +427,7 @@ public class Settings {
}
public static boolean useLowPowerMode() {
- return useGooglePlayServices() && getBoolean(R.string.pref_lowpowermode, false);
+ return getBoolean(R.string.pref_lowpowermode, false);
}
/**
@@ -473,7 +502,7 @@ public class Settings {
}
public static CoordInputFormatEnum getCoordInputFormat() {
- return CoordInputFormatEnum.fromInt(getInt(R.string.pref_coordinputformat, CoordInputFormatEnum.Min.ordinal()));
+ return CoordInputFormatEnum.fromInt(getInt(R.string.pref_coordinputformat, CoordInputFormatEnum.DEFAULT_INT_VALUE));
}
public static void setCoordInputFormat(final CoordInputFormatEnum format) {
@@ -1033,4 +1062,12 @@ public class Settings {
history.add(0, geocode);
putString(R.string.pref_caches_history, StringUtils.join(history, HISTORY_SEPARATOR));
}
+
+ public static boolean useHardwareAcceleration() {
+ return getBoolean(R.string.pref_hardware_acceleration, !HW_ACCEL_DISABLED_BY_DEFAULT);
+ }
+
+ public static boolean setUseHardwareAcceleration(final boolean useHardwareAcceleration) {
+ return putBoolean(R.string.pref_hardware_acceleration, useHardwareAcceleration);
+ }
}
diff --git a/main/src/cgeo/geocaching/settings/SettingsActivity.java b/main/src/cgeo/geocaching/settings/SettingsActivity.java
index 5297857..b5d7b68 100644
--- a/main/src/cgeo/geocaching/settings/SettingsActivity.java
+++ b/main/src/cgeo/geocaching/settings/SettingsActivity.java
@@ -21,6 +21,7 @@ import org.openintents.intents.FileManagerIntents;
import android.app.ProgressDialog;
import android.app.backup.BackupManager;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -87,6 +88,7 @@ public class SettingsActivity extends PreferenceActivity {
setTheme(Settings.isLightSkin() && Build.VERSION.SDK_INT > 10 ? R.style.settings_light : R.style.settings);
super.onCreate(savedInstanceState);
+ initHardwareAccelerationPreferences();
SettingsActivity.addPreferencesFromResource(this, R.xml.preferences);
initPreferences();
@@ -122,7 +124,7 @@ public class SettingsActivity extends PreferenceActivity {
initDefaultNavigationPreferences();
initBackupButtons();
initDbLocationPreference();
- initGeolocationPreference();
+ initGeoDirPreferences();
initDebugPreference();
initBasicMemberPreferences();
initSend2CgeoPreferences();
@@ -185,7 +187,12 @@ public class SettingsActivity extends PreferenceActivity {
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(final Preference preference) {
- startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://" + host)));
+ try {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://" + host)));
+ } catch (final ActivityNotFoundException e) {
+ Log.e("Cannot find suitable activity", e);
+ ActivityMixin.showToast(SettingsActivity.this, R.string.err_application_no);
+ }
return true;
}
});
@@ -374,6 +381,12 @@ public class SettingsActivity extends PreferenceActivity {
});
}
+ public static void initHardwareAccelerationPreferences() {
+ // We have to ensure that the preference is initialized so that devices with hardware acceleration disabled
+ // get the appropriate value.
+ Settings.setUseHardwareAcceleration(Settings.useHardwareAcceleration());
+ }
+
private void initDbLocationPreference() {
final Preference p = getPreference(R.string.pref_dbonsdcard);
p.setPersistent(false);
@@ -400,27 +413,26 @@ public class SettingsActivity extends PreferenceActivity {
});
}
- private void initGeolocationPreference() {
- final Preference p = getPreference(R.string.pref_googleplayservices);
- final Preference p2 = getPreference(R.string.pref_lowpowermode);
- p.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ private void initGeoDirPreferences() {
+ final Preference playServices = getPreference(R.string.pref_googleplayservices);
+ playServices.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(final Preference preference, final Object newValue) {
- final boolean useGooglePlayServices = (Boolean) newValue;
- p2.setEnabled(useGooglePlayServices);
- CgeoApplication.getInstance().setupGeoDataObservables(useGooglePlayServices, Settings.useLowPowerMode());
+ CgeoApplication.getInstance().setupGeoDataObservables((Boolean) newValue, Settings.useLowPowerMode());
return true;
}
});
- p2.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ playServices.setEnabled(CgeoApplication.getInstance().isGooglePlayServicesAvailable());
+ getPreference(R.string.pref_lowpowermode).setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(final Preference preference, final Object newValue) {
- CgeoApplication.getInstance().setupGeoDataObservables(Settings.useGooglePlayServices(), (Boolean) newValue);
+ final CgeoApplication app = CgeoApplication.getInstance();
+ final Boolean useLowPower = (Boolean) newValue;
+ app.setupGeoDataObservables(Settings.useGooglePlayServices(), useLowPower);
+ app.setupDirectionObservable(useLowPower);
return true;
}
});
- p.setEnabled(CgeoApplication.getInstance().isGooglePlayServicesAvailable());
- p2.setEnabled(Settings.useGooglePlayServices());
}
void initBasicMemberPreferences() {
diff --git a/main/src/cgeo/geocaching/sorting/DistanceComparator.java b/main/src/cgeo/geocaching/sorting/DistanceComparator.java
index 541ce48..6812865 100644
--- a/main/src/cgeo/geocaching/sorting/DistanceComparator.java
+++ b/main/src/cgeo/geocaching/sorting/DistanceComparator.java
@@ -3,6 +3,7 @@ package cgeo.geocaching.sorting;
import cgeo.geocaching.Geocache;
import cgeo.geocaching.geopoint.Geopoint;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -15,9 +16,10 @@ public class DistanceComparator extends AbstractCacheComparator {
final private List<Geocache> list;
private boolean cachedDistances;
- public DistanceComparator(final Geopoint coords, List<Geocache> list) {
+ public DistanceComparator(final Geopoint coords, final List<Geocache> list) {
this.coords = coords;
- this.list = list;
+ // create new list so we can iterate over the list in parallel with the cache list adapter
+ this.list = new ArrayList<>(list);
}
/**
diff --git a/main/src/cgeo/geocaching/speech/SpeechService.java b/main/src/cgeo/geocaching/speech/SpeechService.java
index fbd2d7e..11e10c1 100644
--- a/main/src/cgeo/geocaching/speech/SpeechService.java
+++ b/main/src/cgeo/geocaching/speech/SpeechService.java
@@ -1,5 +1,6 @@
package cgeo.geocaching.speech;
+import cgeo.geocaching.Intents;
import cgeo.geocaching.R;
import cgeo.geocaching.activity.ActivityMixin;
import cgeo.geocaching.geopoint.Geopoint;
@@ -31,7 +32,6 @@ public class SpeechService extends Service implements OnInitListener {
private static final int SPEECH_MINPAUSE_SECONDS = 5;
private static final int SPEECH_MAXPAUSE_SECONDS = 30;
- private static final String EXTRA_TARGET_COORDS = "target";
private static Activity startingActivity;
private static boolean isRunning = false;
/**
@@ -152,7 +152,7 @@ public class SpeechService extends Service implements OnInitListener {
@Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
if (intent != null) {
- target = intent.getParcelableExtra(EXTRA_TARGET_COORDS);
+ target = intent.getParcelableExtra(Intents.EXTRA_COORDS);
}
return START_NOT_STICKY; // service can be stopped by system, if under memory pressure
}
@@ -168,7 +168,7 @@ public class SpeechService extends Service implements OnInitListener {
isRunning = true;
startingActivity = activity;
final Intent talkingService = new Intent(activity, SpeechService.class);
- talkingService.putExtra(EXTRA_TARGET_COORDS, dstCoords);
+ talkingService.putExtra(Intents.EXTRA_COORDS, dstCoords);
activity.startService(talkingService);
}
diff --git a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java
index 40cd726..d55d9c9 100644
--- a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java
+++ b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java
@@ -102,11 +102,14 @@ public final class CacheDetailsCreator {
public void addCacheState(final Geocache cache) {
if (cache.isLogOffline() || cache.isArchived() || cache.isDisabled() || cache.isPremiumMembersOnly() || cache.isFound()) {
final List<String> states = new ArrayList<>(5);
+ String date = getVisitedDate(cache);
if (cache.isLogOffline()) {
- states.add(res.getString(R.string.cache_status_offline_log));
+ states.add(res.getString(R.string.cache_status_offline_log) + date);
+ // reset the found date, to avoid showing it twice
+ date = "";
}
if (cache.isFound()) {
- states.add(res.getString(R.string.cache_status_found));
+ states.add(res.getString(R.string.cache_status_found) + date);
}
if (cache.isArchived()) {
states.add(res.getString(R.string.cache_status_archived));
@@ -121,12 +124,17 @@ public final class CacheDetailsCreator {
}
}
+ private static String getVisitedDate(final Geocache cache) {
+ final long visited = cache.getVisitedDate();
+ return visited != 0 ? " (" + Formatter.formatShortDate(visited) + ")" : "";
+ }
+
public void addRating(final Geocache cache) {
if (cache.getRating() > 0) {
final RelativeLayout itemLayout = addStars(R.string.cache_rating, cache.getRating());
if (cache.getVotes() > 0) {
final TextView itemAddition = ButterKnife.findById(itemLayout, R.id.addition);
- itemAddition.setText("(" + cache.getVotes() + ")");
+ itemAddition.setText(" (" + cache.getVotes() + ')');
itemAddition.setVisibility(View.VISIBLE);
}
}
@@ -170,7 +178,7 @@ public final class CacheDetailsCreator {
}
public void addDistance(final Waypoint wpt, final TextView waypointDistanceView) {
- Float distance = CgeoApplication.getInstance().distanceNonBlocking(wpt);
+ final Float distance = CgeoApplication.getInstance().distanceNonBlocking(wpt);
String text = "--";
if (distance != null) {
text = Units.getDistanceFromKilometers(distance);
diff --git a/main/src/cgeo/geocaching/ui/CacheListAdapter.java b/main/src/cgeo/geocaching/ui/CacheListAdapter.java
index b879e54..b0b30aa 100644
--- a/main/src/cgeo/geocaching/ui/CacheListAdapter.java
+++ b/main/src/cgeo/geocaching/ui/CacheListAdapter.java
@@ -120,9 +120,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> {
public CacheListAdapter(final Activity activity, final List<Geocache> list, final CacheListType cacheListType) {
super(activity, 0, list);
final IGeoData currentGeo = CgeoApplication.getInstance().currentGeo();
- if (currentGeo != null) {
- coords = currentGeo.getCoords();
- }
+ coords = currentGeo.getCoords();
this.res = activity.getResources();
this.list = list;
this.cacheListType = cacheListType;
diff --git a/main/src/cgeo/geocaching/ui/CompassView.java b/main/src/cgeo/geocaching/ui/CompassView.java
index 240afcf..a227770 100644
--- a/main/src/cgeo/geocaching/ui/CompassView.java
+++ b/main/src/cgeo/geocaching/ui/CompassView.java
@@ -81,8 +81,9 @@ public class CompassView extends View {
}
public void updateGraphics() {
- final float newAzimuthShown = smoothUpdate(northMeasured, azimuthShown);
- final float newCacheHeadingShown = smoothUpdate(cacheHeadingMeasured, cacheHeadingShown);
+ final float newAzimuthShown = initialDisplay ? northMeasured : smoothUpdate(northMeasured, azimuthShown);
+ final float newCacheHeadingShown = initialDisplay ? cacheHeadingMeasured : smoothUpdate(cacheHeadingMeasured, cacheHeadingShown);
+ initialDisplay = false;
if (Math.abs(AngleUtils.difference(azimuthShown, newAzimuthShown)) >= 2 ||
Math.abs(AngleUtils.difference(cacheHeadingShown, newCacheHeadingShown)) >= 2) {
azimuthShown = newAzimuthShown;
@@ -151,17 +152,6 @@ public class CompassView extends View {
* @param cacheHeading the cache direction (extra rotation of the needle)
*/
public void updateNorth(final float northHeading, final float cacheHeading) {
- if (initialDisplay) {
- // We will force the compass to move brutally if this is the first
- // update since it is visible.
- azimuthShown = northHeading;
- cacheHeadingShown = cacheHeading;
-
- // it may take some time to get an initial direction measurement for the device
- if (northHeading != 0.0) {
- initialDisplay = false;
- }
- }
northMeasured = northHeading;
cacheHeadingMeasured = cacheHeading;
}
diff --git a/main/src/cgeo/geocaching/ui/EditNoteDialog.java b/main/src/cgeo/geocaching/ui/EditNoteDialog.java
index 013fdff..00d1604 100644
--- a/main/src/cgeo/geocaching/ui/EditNoteDialog.java
+++ b/main/src/cgeo/geocaching/ui/EditNoteDialog.java
@@ -31,15 +31,19 @@ public class EditNoteDialog extends DialogFragment {
public static final String ARGUMENT_INITIAL_NOTE = "initialNote";
private EditText mEditText;
- private EditNoteDialogListener listener;
- public static EditNoteDialog newInstance(final String initialNote, final EditNoteDialogListener listener) {
+ /**
+ * Create a new dialog to edit a note.
+ * <em>This fragment must be inserted into an activity implementing the EditNoteDialogListener interface.</em>
+ *
+ * @param initialNote the initial note to insert in the edit dialog
+ */
+ public static EditNoteDialog newInstance(final String initialNote) {
final EditNoteDialog dialog = new EditNoteDialog();
final Bundle arguments = new Bundle();
arguments.putString(EditNoteDialog.ARGUMENT_INITIAL_NOTE, initialNote);
dialog.setArguments(arguments);
- dialog.listener = listener;
return dialog;
}
@@ -70,7 +74,7 @@ public class EditNoteDialog extends DialogFragment {
new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int whichButton) {
- listener.onFinishEditNoteDialog(mEditText.getText().toString());
+ ((EditNoteDialogListener) getActivity()).onFinishEditNoteDialog(mEditText.getText().toString());
dialog.dismiss();
}
});
diff --git a/main/src/cgeo/geocaching/ui/dialog/Dialogs.java b/main/src/cgeo/geocaching/ui/dialog/Dialogs.java
index 21e1a82..47ce6e1 100644
--- a/main/src/cgeo/geocaching/ui/dialog/Dialogs.java
+++ b/main/src/cgeo/geocaching/ui/dialog/Dialogs.java
@@ -25,8 +25,15 @@ import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.view.ContextThemeWrapper;
+import android.view.View;
+import android.view.ViewGroup;
import android.view.WindowManager;
+import android.widget.ArrayAdapter;
import android.widget.EditText;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import java.util.List;
/**
* Wrapper for {@link AlertDialog}. If you want to show a simple text, use one of the
@@ -410,4 +417,44 @@ public final class Dialogs {
private static void enableDialogButtonIfNotEmpty(final AlertDialog dialog, final String input) {
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(StringUtils.isNotBlank(input));
}
+
+ public static interface ItemWithIcon {
+ /**
+ * @return the drawable
+ */
+ int getIcon();
+ }
+
+ public static <T extends ItemWithIcon> void select(final Activity activity, final String title, final List<T> items, final Action1<T> listener) {
+ final ListAdapter adapter = new ArrayAdapter<T>(
+ activity,
+ android.R.layout.select_dialog_item,
+ android.R.id.text1,
+ items) {
+ @Override
+ public View getView(final int position, final View convertView, final ViewGroup parent) {
+ // standard list entry
+ final View v = super.getView(position, convertView, parent);
+
+ // add image
+ final TextView tv = (TextView) v.findViewById(android.R.id.text1);
+ tv.setCompoundDrawablesWithIntrinsicBounds(items.get(position).getIcon(), 0, 0, 0);
+
+ // Add margin between image and text
+ final int dp5 = (int) (5 * activity.getResources().getDisplayMetrics().density + 0.5f);
+ tv.setCompoundDrawablePadding(dp5);
+
+ return v;
+ }
+ };
+
+ new AlertDialog.Builder(activity)
+ .setTitle(title)
+ .setAdapter(adapter, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int item) {
+ listener.call(items.get(item));
+ }
+ }).show();
+ }
}
diff --git a/main/src/cgeo/geocaching/ui/logs/TrackableLogsViewCreator.java b/main/src/cgeo/geocaching/ui/logs/TrackableLogsViewCreator.java
index ecb6469..24c8871 100644
--- a/main/src/cgeo/geocaching/ui/logs/TrackableLogsViewCreator.java
+++ b/main/src/cgeo/geocaching/ui/logs/TrackableLogsViewCreator.java
@@ -1,7 +1,6 @@
package cgeo.geocaching.ui.logs;
import cgeo.geocaching.CacheDetailActivity;
-import cgeo.geocaching.DataStore;
import cgeo.geocaching.LogEntry;
import cgeo.geocaching.Trackable;
import cgeo.geocaching.TrackableActivity;
@@ -50,16 +49,14 @@ public class TrackableLogsViewCreator extends LogsViewCreator {
holder.countOrLocation.setVisibility(View.GONE);
} else {
holder.countOrLocation.setText(Html.fromHtml(log.cacheName));
- final String cacheCode = DataStore.getGeocodeForGuid(log.cacheGuid);
- if (cacheCode != null) {
- final String cacheName = log.cacheName;
- holder.countOrLocation.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View arg0) {
- CacheDetailActivity.startActivity(activity, cacheCode, Html.fromHtml(cacheName).toString());
- }
- });
- }
+ final String cacheGuid = log.cacheGuid;
+ final String cacheName = log.cacheName;
+ holder.countOrLocation.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View arg0) {
+ CacheDetailActivity.startActivityGuid(activity, cacheGuid, Html.fromHtml(cacheName).toString());
+ }
+ });
}
}
diff --git a/main/src/cgeo/geocaching/utils/AngleUtils.java b/main/src/cgeo/geocaching/utils/AngleUtils.java
index fdd9a9d..5ab2c75 100644
--- a/main/src/cgeo/geocaching/utils/AngleUtils.java
+++ b/main/src/cgeo/geocaching/utils/AngleUtils.java
@@ -1,7 +1,17 @@
package cgeo.geocaching.utils;
+import cgeo.geocaching.CgeoApplication;
+
+import android.content.Context;
+import android.view.Surface;
+import android.view.WindowManager;
+
public final class AngleUtils {
+ private static class WindowManagerHolder {
+ public static final WindowManager WINDOW_MANAGER = (WindowManager) CgeoApplication.getInstance().getSystemService(Context.WINDOW_SERVICE);
+ }
+
private AngleUtils() {
// Do not instantiate
}
@@ -27,4 +37,37 @@ public final class AngleUtils {
public static float normalize(final float angle) {
return (angle >= 0 ? angle : (360 - ((-angle) % 360))) % 360;
}
+
+ public static int getRotationOffset() {
+ switch (WindowManagerHolder.WINDOW_MANAGER.getDefaultDisplay().getRotation()) {
+ case Surface.ROTATION_90:
+ return 90;
+ case Surface.ROTATION_180:
+ return 180;
+ case Surface.ROTATION_270:
+ return 270;
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Take the phone rotation (through a given activity) in account and adjust the direction.
+ *
+ * @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 float direction) {
+ return normalize(direction + getRotationOffset());
+ }
+
+ /**
+ * Reverse the phone rotation (through a given activity) in account and adjust the direction.
+ *
+ * @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 reverseDirectionNow(final float direction) {
+ return normalize(direction - getRotationOffset());
+ }
}
diff --git a/main/src/cgeo/geocaching/utils/Formatter.java b/main/src/cgeo/geocaching/utils/Formatter.java
index 3068cd4..1b774f8 100644
--- a/main/src/cgeo/geocaching/utils/Formatter.java
+++ b/main/src/cgeo/geocaching/utils/Formatter.java
@@ -33,7 +33,7 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatTime(long date) {
+ public static String formatTime(final long date) {
return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_TIME);
}
@@ -45,7 +45,7 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatDate(long date) {
+ public static String formatDate(final long date) {
return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE);
}
@@ -58,7 +58,7 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatFullDate(long date) {
+ public static String formatFullDate(final long date) {
return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_SHOW_YEAR);
}
@@ -71,8 +71,8 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatShortDate(long date) {
- DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(context);
+ public static String formatShortDate(final long date) {
+ final DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(context);
return dateFormat.format(date);
}
@@ -84,8 +84,8 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatShortDateVerbally(long date) {
- int diff = cgeo.geocaching.utils.DateUtils.daysSince(date);
+ public static String formatShortDateVerbally(final long date) {
+ final int diff = cgeo.geocaching.utils.DateUtils.daysSince(date);
switch (diff) {
case 0:
return CgeoApplication.getInstance().getString(R.string.log_today);
@@ -104,7 +104,7 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatShortDateTime(long date) {
+ public static String formatShortDateTime(final long date) {
return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL);
}
@@ -116,11 +116,11 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatDateTime(long date) {
+ public static String formatDateTime(final long date) {
return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
}
- public static String formatCacheInfoLong(Geocache cache, CacheListType cacheListType) {
+ public static String formatCacheInfoLong(final Geocache cache, final CacheListType cacheListType) {
final ArrayList<String> infos = new ArrayList<>();
if (StringUtils.isNotBlank(cache.getGeocode())) {
infos.add(cache.getGeocode());
@@ -137,13 +137,13 @@ public abstract class Formatter {
return StringUtils.join(infos, Formatter.SEPARATOR);
}
- public static String formatCacheInfoShort(Geocache cache) {
+ public static String formatCacheInfoShort(final Geocache cache) {
final ArrayList<String> infos = new ArrayList<>();
addShortInfos(cache, infos);
return StringUtils.join(infos, Formatter.SEPARATOR);
}
- private static void addShortInfos(Geocache cache, final ArrayList<String> infos) {
+ private static void addShortInfos(final Geocache cache, final ArrayList<String> infos) {
if (cache.hasDifficulty()) {
infos.add("D " + String.format("%.1f", cache.getDifficulty()));
}
@@ -162,7 +162,7 @@ public abstract class Formatter {
}
}
- public static String formatCacheInfoHistory(Geocache cache) {
+ public static String formatCacheInfoHistory(final Geocache cache) {
final ArrayList<String> infos = new ArrayList<>(3);
infos.add(StringUtils.upperCase(cache.getGeocode()));
infos.add(Formatter.formatDate(cache.getVisitedDate()));
@@ -170,9 +170,9 @@ public abstract class Formatter {
return StringUtils.join(infos, Formatter.SEPARATOR);
}
- public static String formatWaypointInfo(Waypoint waypoint) {
+ public static String formatWaypointInfo(final Waypoint waypoint) {
final List<String> infos = new ArrayList<>(3);
- WaypointType waypointType = waypoint.getWaypointType();
+ final WaypointType waypointType = waypoint.getWaypointType();
if (waypointType != WaypointType.OWN && waypointType != null) {
infos.add(waypointType.getL10n());
}
@@ -188,4 +188,16 @@ public abstract class Formatter {
}
return StringUtils.join(infos, Formatter.SEPARATOR);
}
+
+ public static String formatDaysAgo(final long date) {
+ final int days = cgeo.geocaching.utils.DateUtils.daysSince(date);
+ switch (days) {
+ case 0:
+ return CgeoApplication.getInstance().getString(R.string.log_today);
+ case 1:
+ return CgeoApplication.getInstance().getString(R.string.log_yesterday);
+ default:
+ return CgeoApplication.getInstance().getResources().getQuantityString(R.plurals.days_ago, days, days);
+ }
+ }
}
diff --git a/main/src/cgeo/geocaching/utils/HtmlUtils.java b/main/src/cgeo/geocaching/utils/HtmlUtils.java
index 51c4d6e..e90b70d 100644
--- a/main/src/cgeo/geocaching/utils/HtmlUtils.java
+++ b/main/src/cgeo/geocaching/utils/HtmlUtils.java
@@ -24,7 +24,7 @@ public final class HtmlUtils {
* @param html
* @return
*/
- public static String extractText(CharSequence html) {
+ public static String extractText(final CharSequence html) {
if (StringUtils.isBlank(html)) {
return StringUtils.EMPTY;
}
@@ -32,13 +32,13 @@ public final class HtmlUtils {
// recognize images in textview HTML contents
if (html instanceof Spanned) {
- Spanned text = (Spanned) html;
- Object[] styles = text.getSpans(0, text.length(), Object.class);
- ArrayList<Pair<Integer, Integer>> removals = new ArrayList<>();
- for (Object style : styles) {
+ final Spanned text = (Spanned) html;
+ final Object[] styles = text.getSpans(0, text.length(), Object.class);
+ final ArrayList<Pair<Integer, Integer>> removals = new ArrayList<>();
+ for (final Object style : styles) {
if (style instanceof ImageSpan) {
- int start = text.getSpanStart(style);
- int end = text.getSpanEnd(style);
+ final int start = text.getSpanStart(style);
+ final int end = text.getSpanEnd(style);
removals.add(Pair.of(start, end));
}
}
@@ -47,12 +47,12 @@ public final class HtmlUtils {
Collections.sort(removals, new Comparator<Pair<Integer, Integer>>() {
@Override
- public int compare(Pair<Integer, Integer> lhs, Pair<Integer, Integer> rhs) {
+ public int compare(final Pair<Integer, Integer> lhs, final Pair<Integer, Integer> rhs) {
return rhs.getRight().compareTo(lhs.getRight());
}
});
result = text.toString();
- for (Pair<Integer, Integer> removal : removals) {
+ for (final Pair<Integer, Integer> removal : removals) {
result = result.substring(0, removal.getLeft()) + result.substring(removal.getRight());
}
}
@@ -60,4 +60,14 @@ public final class HtmlUtils {
// now that images are gone, do a normal html to text conversion
return Html.fromHtml(result).toString().trim();
}
+
+ public static String removeExtraParagraph(final String html) {
+ if (StringUtils.startsWith(html, "<p>") && StringUtils.endsWith(html, "</p>")) {
+ final String paragraph = StringUtils.substring(html, "<p>".length(), html.length() - "</p>".length()).trim();
+ if (extractText(paragraph).equals(paragraph)) {
+ return paragraph;
+ }
+ }
+ return html;
+ }
}
diff --git a/main/src/cgeo/geocaching/utils/ImageUtils.java b/main/src/cgeo/geocaching/utils/ImageUtils.java
index d2edfc6..c2b7327 100644
--- a/main/src/cgeo/geocaching/utils/ImageUtils.java
+++ b/main/src/cgeo/geocaching/utils/ImageUtils.java
@@ -1,6 +1,7 @@
package cgeo.geocaching.utils;
import cgeo.geocaching.CgeoApplication;
+import cgeo.geocaching.Image;
import cgeo.geocaching.R;
import cgeo.geocaching.compatibility.Compatibility;
@@ -25,6 +26,8 @@ import android.graphics.drawable.Drawable;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Environment;
+import android.text.Html;
+import android.text.Html.ImageGetter;
import android.util.Base64;
import android.util.Base64InputStream;
import android.widget.TextView;
@@ -36,8 +39,11 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
+import java.util.Collection;
import java.util.Date;
+import java.util.LinkedHashSet;
import java.util.Locale;
+import java.util.Set;
public final class ImageUtils {
private static final int[] ORIENTATIONS = new int[] {
@@ -49,6 +55,10 @@ public final class ImageUtils {
private static final int[] ROTATION = new int[] { 90, 180, 270 };
private static final int MAX_DISPLAY_IMAGE_XY = 800;
+ // Images whose URL contains one of those patterns will not be available on the Images tab
+ // for opening into an external application.
+ private final static String[] NO_EXTERNAL = new String[] { "geocheck.org" };
+
private ImageUtils() {
// Do not let this class be instantiated, this is a utility class.
}
@@ -303,11 +313,35 @@ public final class ImageUtils {
}
/**
+ * Add images present in the HTML description to the existing collection.
+ *
+ * @param images a collection of images
+ * @param htmlText the HTML description to be parsed
+ * @param geocode the common title for images in the description
+ */
+ public static void addImagesFromHtml(final Collection<Image> images, final String htmlText, final String geocode) {
+ final Set<String> urls = new LinkedHashSet<>();
+ for (final Image image : images) {
+ urls.add(image.getUrl());
+ }
+ Html.fromHtml(StringUtils.defaultString(htmlText), new ImageGetter() {
+ @Override
+ public Drawable getDrawable(final String source) {
+ if (!urls.contains(source) && canBeOpenedExternally(source)) {
+ images.add(new Image(source, StringUtils.defaultString(geocode)));
+ urls.add(source);
+ }
+ return null;
+ }
+ }, null);
+ }
+
+ /**
* Container which can hold a drawable (initially an empty one) and get a newer version when it
* becomes available. It also invalidates the view the container belongs to, so that it is
* redrawn properly.
*/
- public final static class ContainerDrawable extends BitmapDrawable implements Action1<Drawable> {
+ public static class ContainerDrawable extends BitmapDrawable implements Action1<Drawable> {
private Drawable drawable;
final private TextView view;
@@ -324,7 +358,7 @@ public final class ImageUtils {
}
@Override
- public void draw(final Canvas canvas) {
+ public final void draw(final Canvas canvas) {
if (drawable != null) {
drawable.draw(canvas);
}
@@ -337,8 +371,55 @@ public final class ImageUtils {
view.setText(view.getText());
}
- public void updateFrom(final Observable<? extends Drawable> drawableObservable) {
+ public final void updateFrom(final Observable<? extends Drawable> drawableObservable) {
drawableObservable.observeOn(AndroidSchedulers.mainThread()).subscribe(this);
}
}
+
+ /**
+ * Image that automatically scales to fit a line of text in the containing {@link TextView}.
+ */
+ public final static class LineHeightContainerDrawable extends ContainerDrawable {
+ private final TextView view;
+
+ public LineHeightContainerDrawable(@NonNull final TextView view, final Observable<? extends Drawable> drawableObservable) {
+ super(view, drawableObservable);
+ this.view = view;
+ }
+
+ @Override
+ public void call(final Drawable newDrawable) {
+ super.call(newDrawable);
+ setBounds(ImageUtils.scaleImageToLineHeight(newDrawable, view));
+ }
+ }
+
+ public static boolean canBeOpenedExternally(final String source) {
+ return !containsPattern(source, NO_EXTERNAL);
+ }
+
+ public static Rect scaleImageToLineHeight(final Drawable drawable, final TextView view) {
+ final int lineHeight = (int) (view.getLineHeight() * 0.8);
+ final int width = drawable.getIntrinsicWidth() * lineHeight / drawable.getIntrinsicHeight();
+ return new Rect(0, 0, width, lineHeight);
+ }
+
+ public static Bitmap convertToBitmap(final Drawable drawable) {
+ if (drawable instanceof BitmapDrawable) {
+ return ((BitmapDrawable) drawable).getBitmap();
+ }
+
+ // handle solid colors, which have no width
+ int width = drawable.getIntrinsicWidth();
+ width = width > 0 ? width : 1;
+ int height = drawable.getIntrinsicHeight();
+ height = height > 0 ? height : 1;
+
+ final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+
+ return bitmap;
+ }
}
diff --git a/main/src/cgeo/geocaching/utils/RxUtils.java b/main/src/cgeo/geocaching/utils/RxUtils.java
index 3af98cc..ef79f93 100644
--- a/main/src/cgeo/geocaching/utils/RxUtils.java
+++ b/main/src/cgeo/geocaching/utils/RxUtils.java
@@ -2,17 +2,21 @@ package cgeo.geocaching.utils;
import rx.Observable;
import rx.Observable.OnSubscribe;
+import rx.Observable.Operator;
import rx.Scheduler;
import rx.Scheduler.Worker;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action0;
+import rx.functions.Func1;
+import rx.internal.operators.OperatorTakeWhile;
import rx.observables.BlockingObservable;
import rx.schedulers.Schedulers;
import rx.subscriptions.Subscriptions;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.Looper;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
@@ -21,8 +25,9 @@ import java.util.concurrent.atomic.AtomicInteger;
public class RxUtils {
- // Utility class, not to be instanciated
- private RxUtils() {}
+ private RxUtils() {
+ // Utility class, not to be instantiated
+ }
public final static Scheduler computationScheduler = Schedulers.computation();
@@ -30,10 +35,13 @@ public class RxUtils {
private static final HandlerThread looperCallbacksThread =
new HandlerThread("Looper callbacks thread", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+
static {
looperCallbacksThread.start();
}
- public static final Scheduler looperCallbacksScheduler = AndroidSchedulers.handlerThread(new Handler(looperCallbacksThread.getLooper()));
+
+ public static final Looper looperCallbacksLooper = looperCallbacksThread.getLooper();
+ public static final Scheduler looperCallbacksScheduler = AndroidSchedulers.handlerThread(new Handler(looperCallbacksLooper));
public static final Worker looperCallbacksWorker = looperCallbacksScheduler.createWorker();
public static <T> void waitForCompletion(final BlockingObservable<T> observable) {
@@ -47,7 +55,8 @@ public class RxUtils {
/**
* Subscribe function whose subscription and unsubscription take place on a looper thread.
*
- * @param <T> the type of the observable
+ * @param <T>
+ * the type of the observable
*/
public static abstract class LooperCallbacks<T> implements OnSubscribe<T> {
@@ -92,6 +101,23 @@ public class RxUtils {
}
abstract protected void onStart();
+
abstract protected void onStop();
}
+
+ public static <T> Operator<T, T> operatorTakeUntil(final Func1<? super T, Boolean> predicate) {
+ return new OperatorTakeWhile<>(new Func1<T, Boolean>() {
+ private boolean quitting = false;
+
+ @Override
+ public Boolean call(final T item) {
+ if (quitting) {
+ return false;
+ }
+ quitting |= predicate.call(item);
+ return true;
+ }
+ });
+ }
+
}