aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--main/res/layout/fragment_edit_note.xml15
-rw-r--r--main/res/layout/spoilers.xml1
-rw-r--r--main/res/values/strings_not_translatable.xml33
-rw-r--r--main/src/cgeo/geocaching/CacheDetailActivity.java39
-rw-r--r--main/src/cgeo/geocaching/Geocache.java31
-rw-r--r--main/src/cgeo/geocaching/Settings.java15
-rw-r--r--main/src/cgeo/geocaching/StaticMapsProvider.java23
-rw-r--r--main/src/cgeo/geocaching/VisitCacheActivity.java134
-rw-r--r--main/src/cgeo/geocaching/Waypoint.java2
-rw-r--r--main/src/cgeo/geocaching/cgData.java3
-rw-r--r--main/src/cgeo/geocaching/cgeo.java3
-rw-r--r--main/src/cgeo/geocaching/cgeocaches.java96
-rw-r--r--main/src/cgeo/geocaching/cgeonavigate.java2
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCConstants.java2
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCParser.java14
-rw-r--r--main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java26
-rw-r--r--main/src/cgeo/geocaching/export/FieldnoteExport.java29
-rw-r--r--main/src/cgeo/geocaching/export/GpxExport.java59
-rw-r--r--main/src/cgeo/geocaching/files/GPXImporter.java920
-rw-r--r--main/src/cgeo/geocaching/files/GPXParser.java3
-rw-r--r--main/src/cgeo/geocaching/filter/AttributeFilter.java23
-rw-r--r--main/src/cgeo/geocaching/filter/DifficultyFilter.java5
-rw-r--r--main/src/cgeo/geocaching/filter/FilterUserInterface.java9
-rw-r--r--main/src/cgeo/geocaching/filter/IFilterFactory.java4
-rw-r--r--main/src/cgeo/geocaching/filter/ModifiedFilter.java7
-rw-r--r--main/src/cgeo/geocaching/filter/OriginFilter.java5
-rw-r--r--main/src/cgeo/geocaching/filter/SizeFilter.java9
-rw-r--r--main/src/cgeo/geocaching/filter/StateFilter.java7
-rw-r--r--main/src/cgeo/geocaching/filter/TerrainFilter.java8
-rw-r--r--main/src/cgeo/geocaching/filter/TrackablesFilter.java9
-rw-r--r--main/src/cgeo/geocaching/filter/TypeFilter.java9
-rw-r--r--main/src/cgeo/geocaching/geopoint/Geopoint.java10
-rw-r--r--main/src/cgeo/geocaching/geopoint/GeopointFormatter.java45
-rw-r--r--main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java3
-rw-r--r--main/src/cgeo/geocaching/maps/CGeoMap.java8
-rw-r--r--main/src/cgeo/geocaching/twitter/Twitter.java62
-rw-r--r--main/src/cgeo/geocaching/ui/DecryptTextClickListener.java6
-rw-r--r--main/src/cgeo/geocaching/ui/EditNoteDialog.java65
-rw-r--r--main/src/cgeo/geocaching/ui/dialog/EditorDialog.java60
-rw-r--r--main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java99
-rw-r--r--tests/src/cgeo/geocaching/connector/gc/GCParserTest.java18
-rw-r--r--tests/src/cgeo/geocaching/connector/oc/OCXMLTest.java16
-rw-r--r--tests/src/cgeo/geocaching/filter/DifficultyFilterTest.java2
-rw-r--r--tests/src/cgeo/geocaching/filter/SizeFilterTest.java2
-rw-r--r--tests/src/cgeo/geocaching/filter/TerrainFilterTest.java2
-rw-r--r--tests/src/cgeo/geocaching/filter/TypeFilterTest.java2
-rw-r--r--tests/src/cgeo/geocaching/geopoint/GeoPointFormatterTest.java11
-rw-r--r--tests/src/cgeo/geocaching/twitter/TwitterTest.java37
48 files changed, 1082 insertions, 911 deletions
diff --git a/main/res/layout/fragment_edit_note.xml b/main/res/layout/fragment_edit_note.xml
new file mode 100644
index 0000000..68e2b2c
--- /dev/null
+++ b/main/res/layout/fragment_edit_note.xml
@@ -0,0 +1,15 @@
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/edit_note"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="vertical">
+
+ <EditText
+ android:id="@+id/note"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:inputType="text"
+ android:imeOptions="actionDone"/>
+</LinearLayout>
diff --git a/main/res/layout/spoilers.xml b/main/res/layout/spoilers.xml
index 5513934..233972a 100644
--- a/main/res/layout/spoilers.xml
+++ b/main/res/layout/spoilers.xml
@@ -2,6 +2,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
+ android:background="?background_color"
android:orientation="vertical" >
<LinearLayout style="@style/action_bar" >
diff --git a/main/res/values/strings_not_translatable.xml b/main/res/values/strings_not_translatable.xml
index b082e39..8de1483 100644
--- a/main/res/values/strings_not_translatable.xml
+++ b/main/res/values/strings_not_translatable.xml
@@ -82,36 +82,15 @@
<string name="changelog" translatable="false">\n
<b>Next release</b>\n\n
<b>New Features/Functions:</b>\n
- · Support of attaching pictures to logs\n
- · Support of opencaching.de online API in live map and nearby search\n
- · Support for searching opencaching.nl caches\n
- · Alphabetical sorting of OSM:Offline maps in map selection\n
- · Color markers also in trackable logbook\n
- · Edit cache type filter by clicking on filter bar\n
- · Inverse sorting of cache lists (hit the same menu again)\n
- · View pager for trackable activity\n
- · Menu item to delete past events\n
- · Settings: Option to ask for list to store caches\n
- · Change list in cache details\n
- · Ability to select \"All\" list from main screen\n
- · Default log type for event caches will be \"Attended\" if \"Will attend\" was logged before\n
- · Improved and extended cache type detection on live map\n
- · Waypoints can be marked as visited\n
- · Possibility to delete offline logs in lists\n
- · Support of language specific characters in log text equally to the website\n
+ · ...\n
\n
<b>Bugfixing:</b>\n
- · Final flag icon lost when updating cache with self defined final\n
- · Bad selection in directory chooser\n
- · Log type \"Retract Listing\" now parsed correctly\n
- · Active cache detail page now remembered when rotating device\n
- · Replaced the term \"GC-Code\" by \"Geocode\"\n
- · Improvements for light theme\n
- · Share function uses short URL again\n
- · Offline log marker now shown after autosave of log\n
- · GPX export no longer exports waypoints without coordinates\n
+ · Do not apply encryption/decryption when clicking on a link in a log\n
+ · Also show a picture attached to a log locally after sending the log\n
+ · Improved display of log text for OC-caches\n
+ · Improved performance of GPX exports\n
\n
- <a href="https://github.com/cgeo/c-geo-opensource/issues?milestone=9&amp;state=closed">Detailed list of all changes</a>\n
+ <a href="https://github.com/cgeo/c-geo-opensource/issues?milestone=17&amp;state=closed">Detailed list of all changes</a>\n
\n
<b>Known Limitations/Bugs:</b>\n
· Live map:\n
diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java
index ea8794f..daeb14e 100644
--- a/main/src/cgeo/geocaching/CacheDetailActivity.java
+++ b/main/src/cgeo/geocaching/CacheDetailActivity.java
@@ -23,12 +23,13 @@ import cgeo.geocaching.ui.AnchorAwareLinkMovementMethod;
import cgeo.geocaching.ui.CacheDetailsCreator;
import cgeo.geocaching.ui.CoordinatesFormatSwitcher;
import cgeo.geocaching.ui.DecryptTextClickListener;
+import cgeo.geocaching.ui.EditNoteDialog;
+import cgeo.geocaching.ui.EditNoteDialog.EditNoteDialogListener;
import cgeo.geocaching.ui.Formatter;
import cgeo.geocaching.ui.ImagesList;
import cgeo.geocaching.ui.ImagesList.ImageType;
import cgeo.geocaching.ui.LoggingUI;
import cgeo.geocaching.ui.WeakReferenceHandler;
-import cgeo.geocaching.ui.dialog.EditorDialog;
import cgeo.geocaching.utils.BaseUtils;
import cgeo.geocaching.utils.CancellableHandler;
import cgeo.geocaching.utils.ClipboardUtils;
@@ -66,6 +67,7 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.support.v4.app.FragmentManager;
import android.text.Editable;
import android.text.Html;
import android.text.Spannable;
@@ -113,7 +115,8 @@ import java.util.regex.Pattern;
*
* e.g. details, description, logs, waypoints, inventory...
*/
-public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailActivity.Page> {
+public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailActivity.Page>
+ implements EditNoteDialogListener {
private static final int MENU_FIELD_COPY = 1;
private static final int MENU_FIELD_TRANSLATE = 2;
@@ -140,6 +143,8 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
private final Progress progress = new Progress();
private SearchResult search;
+ private EditNoteDialogListener editNoteDialogListener;
+
private final GeoDirHandler locationUpdater = new GeoDirHandler() {
@Override
public void updateGeoData(final IGeoData geo) {
@@ -1779,16 +1784,16 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
personalNoteEdit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- EditorDialog editor = new EditorDialog(CacheDetailActivity.this, personalNoteView.getText());
- editor.setOnEditorUpdate(new EditorDialog.EditorUpdate() {
+ editNoteDialogListener = new EditNoteDialogListener() {
@Override
- public void update(CharSequence editorText) {
- cache.setPersonalNote(editorText.toString());
+ public void onFinishEditNoteDialog(final String note) {
+ cache.setPersonalNote(note);
setPersonalNote(personalNoteView);
- cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB));
- }
- });
- editor.show();
+ cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); }
+ };
+ final FragmentManager fm = getSupportFragmentManager();
+ final EditNoteDialog dialog = new EditNoteDialog(cache.getPersonalNote());
+ dialog.show(fm, "fragment_edit_note");
}
});
} else {
@@ -1851,13 +1856,10 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
personalNoteView.setText(personalNote, TextView.BufferType.SPANNABLE);
if (StringUtils.isNotBlank(personalNote)) {
personalNoteView.setVisibility(View.VISIBLE);
- }
- else {
+ } else {
personalNoteView.setVisibility(View.GONE);
}
- }
-
- private void loadLongDescription() {
+ }private void loadLongDescription() {
Button showDesc = (Button) view.findViewById(R.id.show_description);
showDesc.setVisibility(View.GONE);
showDesc.setOnClickListener(null);
@@ -1869,6 +1871,11 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
}
+ @Override
+ public void onFinishEditNoteDialog(final String note) {
+ editNoteDialogListener.onFinishEditNoteDialog(note);
+ }
+
private static class HtmlImageCounter implements Html.ImageGetter {
private int imageCount = 0;
@@ -2080,7 +2087,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
if (imageCounter.getImageCount() > 0) {
// Complete view: parse again with loading images - if necessary ! If there are any images causing problems the user can see at least the preview
LogImageLoader loader = new LogImageLoader(holder);
- loader.execute(new String[] { logText });
+ loader.execute(logText);
}
}
else {
diff --git a/main/src/cgeo/geocaching/Geocache.java b/main/src/cgeo/geocaching/Geocache.java
index 9a8325d..4b78e4d 100644
--- a/main/src/cgeo/geocaching/Geocache.java
+++ b/main/src/cgeo/geocaching/Geocache.java
@@ -49,6 +49,7 @@ import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;
@@ -710,10 +711,7 @@ public class Geocache implements ICache, IWaypoint {
public String getPersonalNote() {
// non premium members have no personal notes, premium members have an empty string by default.
// map both to null, so other code doesn't need to differentiate
- if (StringUtils.isBlank(personalNote)) {
- return null;
- }
- return personalNote;
+ return StringUtils.defaultIfBlank(personalNote, null);
}
public boolean supportsUserActions() {
@@ -1360,6 +1358,9 @@ public class Geocache implements ICache, IWaypoint {
return null;
}
+ /**
+ * Detect coordinates in the personal note and convert them to user defined waypoints. Works by rule of thumb.
+ */
public void parseWaypointsFromNote() {
try {
if (StringUtils.isBlank(getPersonalNote())) {
@@ -1375,7 +1376,8 @@ public class Geocache implements ICache, IWaypoint {
// coords must have non zero latitude and longitude and at least one part shall have fractional degrees
if (point.getLatitudeE6() != 0 && point.getLongitudeE6() != 0 && ((point.getLatitudeE6() % 1000) != 0 || (point.getLongitudeE6() % 1000) != 0)) {
final String name = cgeoapplication.getInstance().getString(R.string.cache_personal_note) + " " + count;
- final Waypoint waypoint = new Waypoint(name, WaypointType.WAYPOINT, false);
+ final String potentialWaypointType = note.substring(Math.max(0, matcher.start() - 15));
+ final Waypoint waypoint = new Waypoint(name, parseWaypointType(potentialWaypointType), false);
waypoint.setCoords(point);
addOrChangeWaypoint(waypoint, false);
count++;
@@ -1392,6 +1394,25 @@ public class Geocache implements ICache, IWaypoint {
}
}
+ /**
+ * Detect waypoint types in the personal note text. It works by rule of thumb only.
+ */
+ private static WaypointType parseWaypointType(final String input) {
+ final String lowerInput = StringUtils.substring(input, 0, 20).toLowerCase(Locale.getDefault());
+ for (WaypointType wpType : WaypointType.values()) {
+ if (lowerInput.contains(wpType.getL10n().toLowerCase(Locale.getDefault()))) {
+ return wpType;
+ }
+ if (lowerInput.contains(wpType.id)) {
+ return wpType;
+ }
+ if (lowerInput.contains(wpType.name().toLowerCase(Locale.US))) {
+ return wpType;
+ }
+ }
+ return WaypointType.WAYPOINT;
+ }
+
/*
* For working in the debugger
* (non-Javadoc)
diff --git a/main/src/cgeo/geocaching/Settings.java b/main/src/cgeo/geocaching/Settings.java
index 0c157e1..93bfa9b 100644
--- a/main/src/cgeo/geocaching/Settings.java
+++ b/main/src/cgeo/geocaching/Settings.java
@@ -149,6 +149,7 @@ public final class Settings {
// maps
private static MapProvider mapProvider = null;
+ private static String cacheTwitterMessage = "I found [NAME] ([URL])";
private Settings() {
// this class is not to be instantiated;
@@ -1424,4 +1425,18 @@ public final class Settings {
}
});
}
+
+ public static String getCacheTwitterMessage() {
+ // TODO make customizable from UI
+ return cacheTwitterMessage;
+ }
+
+ public static String getTrackableTwitterMessage() {
+ // TODO make customizable from UI
+ return "I touched [NAME] ([URL])!";
+ }
+
+ public static void setCacheTwitterMessage(final String message) {
+ cacheTwitterMessage = message;
+ }
}
diff --git a/main/src/cgeo/geocaching/StaticMapsProvider.java b/main/src/cgeo/geocaching/StaticMapsProvider.java
index 69f5cc4..9a4c00b 100644
--- a/main/src/cgeo/geocaching/StaticMapsProvider.java
+++ b/main/src/cgeo/geocaching/StaticMapsProvider.java
@@ -10,7 +10,6 @@ import cgeo.geocaching.utils.Log;
import ch.boye.httpclientandroidlib.HttpResponse;
-import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import android.content.Context;
@@ -76,18 +75,11 @@ public class StaticMapsProvider {
final long fileSize = file.length();
if (fileSize < MIN_MAP_IMAGE_BYTES) {
file.delete();
- return;
}
- return;
}
- return;
}
public static void downloadMaps(Geocache cache) {
- if (cache == null) {
- Log.e("downloadMaps - missing input parameter cache");
- return;
- }
if ((!Settings.isStoreOfflineMaps() && !Settings.isStoreOfflineWpMaps()) || StringUtils.isBlank(cache.getGeocode())) {
return;
}
@@ -99,8 +91,8 @@ public class StaticMapsProvider {
}
// clean old and download static maps for waypoints if one is missing
- if (Settings.isStoreOfflineWpMaps() && CollectionUtils.isNotEmpty(cache.getWaypoints())) {
- for (Waypoint waypoint : cache.getWaypoints()) {
+ if (Settings.isStoreOfflineWpMaps()) {
+ for (final Waypoint waypoint : cache.getWaypoints()) {
if (!hasAllStaticMapsForWaypoint(cache.getGeocode(), waypoint)) {
refreshAllWpStaticMaps(cache, edge);
}
@@ -170,10 +162,6 @@ public class StaticMapsProvider {
}
public static void storeCachePreviewMap(final Geocache cache) {
- if (cache == null) {
- Log.e("storeCachePreviewMap - missing input parameter cache");
- return;
- }
final String latlonMap = cache.getCoords().format(Format.LAT_LON_DECDEGREE_COMMA);
final Display display = ((WindowManager) cgeoapplication.getInstance().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
@@ -186,12 +174,7 @@ public class StaticMapsProvider {
private static int guessMaxDisplaySide() {
Point displaySize = Compatibility.getDisplaySize();
- final int maxWidth = displaySize.x - 25;
- final int maxHeight = displaySize.y - 25;
- if (maxWidth > maxHeight) {
- return maxWidth;
- }
- return maxHeight;
+ return Math.max(displaySize.x, displaySize.y) - 25;
}
private static void downloadMaps(final String geocode, final String markerUrl, final String prefix, final String latlonMap, final int edge,
diff --git a/main/src/cgeo/geocaching/VisitCacheActivity.java b/main/src/cgeo/geocaching/VisitCacheActivity.java
index 8f47606..74890f4 100644
--- a/main/src/cgeo/geocaching/VisitCacheActivity.java
+++ b/main/src/cgeo/geocaching/VisitCacheActivity.java
@@ -12,6 +12,7 @@ import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.twitter.Twitter;
import cgeo.geocaching.ui.Formatter;
import cgeo.geocaching.ui.dialog.DateDialog;
+import cgeo.geocaching.utils.AsyncTaskWithProgress;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.LogTemplateProvider;
import cgeo.geocaching.utils.LogTemplateProvider.LogContext;
@@ -20,17 +21,15 @@ import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
+import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
-import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.util.SparseArray;
@@ -67,7 +66,6 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD
private LayoutInflater inflater = null;
private Geocache cache = null;
- private ProgressDialog waitDialog = null;
private String cacheid = null;
private String geocode = null;
private String text = null;
@@ -240,34 +238,6 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD
return res.getString(R.string.log_post_rate) + " " + ratingTextValue(rating) + "*";
}
- private final Handler postLogHandler = new Handler() {
-
- @Override
- public void handleMessage(final Message msg) {
- if (waitDialog != null) {
- waitDialog.dismiss();
- }
-
- final StatusCode error = (StatusCode) msg.obj;
- if (error == StatusCode.NO_ERROR) {
- showToast(res.getString(R.string.info_log_posted));
- // No need to save the log when quitting if it has been posted.
- text = currentLogText();
- finish();
- } else if (error == StatusCode.LOG_SAVED) {
- showToast(res.getString(R.string.info_log_saved));
-
- if (waitDialog != null) {
- waitDialog.dismiss();
- }
-
- finish();
- } else {
- showToast(error.getErrorString(res));
- }
- }
- };
-
public VisitCacheActivity() {
super("c:geo-log");
}
@@ -543,73 +513,79 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD
private class PostListener implements View.OnClickListener {
@Override
public void onClick(View arg0) {
- waitDialog = ProgressDialog.show(VisitCacheActivity.this, null,
- res.getString(StringUtils.isBlank(imageUri.getPath()) ? R.string.log_saving : R.string.log_saving_and_uploading), true);
- waitDialog.setCancelable(true);
-
- final Thread thread = new PostLogThread(postLogHandler, currentLogText());
- thread.start();
+ final String message = res.getString(StringUtils.isBlank(imageUri.getPath()) ?
+ R.string.log_saving :
+ R.string.log_saving_and_uploading);
+ new Poster(VisitCacheActivity.this, message).execute(currentLogText());
}
}
- private class PostLogThread extends Thread {
-
- private final Handler handler;
- private final String log;
+ private class Poster extends AsyncTaskWithProgress<String, StatusCode> {
- public PostLogThread(Handler handlerIn, String logIn) {
- super("Post log");
- handler = handlerIn;
- log = logIn;
+ public Poster(final Activity activity, final String progressMessage) {
+ super(activity, null, progressMessage, true);
}
@Override
- public void run() {
- final StatusCode status = postLogFn(log);
- handler.sendMessage(handler.obtainMessage(0, status));
- }
- }
-
- public StatusCode postLogFn(String log) {
- StatusCode result = StatusCode.LOG_POST_ERROR;
+ protected StatusCode doInBackgroundInternal(final String[] logTexts) {
+ final String log = logTexts[0];
+ try {
+ final ImmutablePair<StatusCode, String> postResult = GCParser.postLog(geocode, cacheid, viewstates, typeSelected,
+ date.get(Calendar.YEAR), (date.get(Calendar.MONTH) + 1), date.get(Calendar.DATE),
+ log, trackables);
- try {
+ if (postResult.left == StatusCode.NO_ERROR) {
+ final LogEntry logNow = new LogEntry(date, typeSelected, log);
- final ImmutablePair<StatusCode, String> postResult = GCParser.postLog(geocode, cacheid, viewstates, typeSelected,
- date.get(Calendar.YEAR), (date.get(Calendar.MONTH) + 1), date.get(Calendar.DATE),
- log, trackables);
- result = postResult.left;
+ cache.getLogs().add(0, logNow);
- if (result == StatusCode.NO_ERROR) {
- final LogEntry logNow = new LogEntry(date, typeSelected, log);
+ if (typeSelected == LogType.FOUND_IT || typeSelected == LogType.ATTENDED) {
+ cache.setFound(true);
+ }
- cache.getLogs().add(0, logNow);
+ cgData.saveChangedCache(cache);
+ cgData.clearLogOffline(geocode);
- if (typeSelected == LogType.FOUND_IT || typeSelected == LogType.ATTENDED) {
- cache.setFound(true);
- }
-
- cgData.saveChangedCache(cache);
- cgData.clearLogOffline(geocode);
+ if (typeSelected == LogType.FOUND_IT) {
+ if (tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE) {
+ Twitter.postTweetCache(geocode);
+ }
+ GCVote.setRating(cache, rating);
+ }
- if (typeSelected == LogType.FOUND_IT) {
- if (tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE) {
- Twitter.postTweetCache(geocode);
+ if (StringUtils.isNotBlank(imageUri.getPath())) {
+ ImmutablePair<StatusCode, String> imageResult = GCParser.uploadLogImage(postResult.right, imageCaption, imageDescription, imageUri);
+ final String uploadedImageUrl = imageResult.right;
+ if (StringUtils.isNotEmpty(uploadedImageUrl)) {
+ logNow.addLogImage(new Image(uploadedImageUrl, imageCaption, imageDescription));
+ cgData.saveChangedCache(cache);
+ }
+ return imageResult.left;
}
- GCVote.setRating(cache, rating);
}
- if (StringUtils.isNotBlank(imageUri.getPath())) {
- result = GCParser.uploadLogImage(postResult.right, imageCaption, imageDescription, imageUri);
- }
+ return postResult.left;
+ } catch (Exception e) {
+ Log.e("cgeovisit.postLogFn", e);
}
- return result;
- } catch (Exception e) {
- Log.e("cgeovisit.postLogFn", e);
+ return StatusCode.LOG_POST_ERROR;
}
- return StatusCode.LOG_POST_ERROR;
+ @Override
+ protected void onPostExecuteInternal(final StatusCode status) {
+ if (status == StatusCode.NO_ERROR) {
+ showToast(res.getString(R.string.info_log_posted));
+ // No need to save the log when quitting if it has been posted.
+ text = currentLogText();
+ finish();
+ } else if (status == StatusCode.LOG_SAVED) {
+ showToast(res.getString(R.string.info_log_saved));
+ finish();
+ } else {
+ showToast(status.getErrorString(res));
+ }
+ }
}
private void saveLog(final boolean force) {
diff --git a/main/src/cgeo/geocaching/Waypoint.java b/main/src/cgeo/geocaching/Waypoint.java
index 48c9bc5..6112986 100644
--- a/main/src/cgeo/geocaching/Waypoint.java
+++ b/main/src/cgeo/geocaching/Waypoint.java
@@ -275,7 +275,7 @@ public class Waypoint implements IWaypoint, Comparable<Waypoint> {
if (coords != null) {
hash = coords.hashCode();
}
- hash = hash ^ waypointType.markerId;
+ hash ^= waypointType.markerId;
return (int) hash;
}
}
diff --git a/main/src/cgeo/geocaching/cgData.java b/main/src/cgeo/geocaching/cgData.java
index 28485a5..86821fb 100644
--- a/main/src/cgeo/geocaching/cgData.java
+++ b/main/src/cgeo/geocaching/cgData.java
@@ -33,6 +33,7 @@ import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
@@ -1396,7 +1397,7 @@ public class cgData {
* @param geocodes
* @return Set of loaded caches. Never null.
*/
- public static Set<Geocache> loadCaches(final Set<String> geocodes, final EnumSet<LoadFlag> loadFlags) {
+ public static Set<Geocache> loadCaches(final Collection<String> geocodes, final EnumSet<LoadFlag> loadFlags) {
if (CollectionUtils.isEmpty(geocodes)) {
return new HashSet<Geocache>();
}
diff --git a/main/src/cgeo/geocaching/cgeo.java b/main/src/cgeo/geocaching/cgeo.java
index 5680ff3..0140c3a 100644
--- a/main/src/cgeo/geocaching/cgeo.java
+++ b/main/src/cgeo/geocaching/cgeo.java
@@ -311,6 +311,7 @@ public class cgeo extends AbstractActivity {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == SCAN_REQUEST_CODE) {
+ // Only handle positive results, don't do anything if cancelled.
if (resultCode == RESULT_OK) {
String scan = intent.getStringExtra("SCAN_RESULT");
if (StringUtils.isBlank(scan)) {
@@ -318,8 +319,6 @@ public class cgeo extends AbstractActivity {
}
SearchActivity.startActivityScan(scan, this);
- } else if (resultCode == RESULT_CANCELED) {
- // do nothing
}
} else if (requestCode == SEARCH_REQUEST_CODE) {
// SearchActivity activity returned without making a search
diff --git a/main/src/cgeo/geocaching/cgeocaches.java b/main/src/cgeo/geocaching/cgeocaches.java
index df09936..bcd9531 100644
--- a/main/src/cgeo/geocaching/cgeocaches.java
+++ b/main/src/cgeo/geocaching/cgeocaches.java
@@ -23,6 +23,7 @@ import cgeo.geocaching.loaders.AddressGeocacheListLoader;
import cgeo.geocaching.loaders.CoordsGeocacheListLoader;
import cgeo.geocaching.loaders.HistoryGeocacheListLoader;
import cgeo.geocaching.loaders.KeywordGeocacheListLoader;
+import cgeo.geocaching.loaders.NextPageGeocacheListLoader;
import cgeo.geocaching.loaders.OfflineGeocacheListLoader;
import cgeo.geocaching.loaders.OwnerGeocacheListLoader;
import cgeo.geocaching.loaders.RemoveFromHistoryLoader;
@@ -38,6 +39,7 @@ import cgeo.geocaching.sorting.VisitComparator;
import cgeo.geocaching.ui.CacheListAdapter;
import cgeo.geocaching.ui.LoggingUI;
import cgeo.geocaching.ui.WeakReferenceHandler;
+import cgeo.geocaching.utils.AsyncTaskWithProgress;
import cgeo.geocaching.utils.DateUtils;
import cgeo.geocaching.utils.GeoDirHandler;
import cgeo.geocaching.utils.Log;
@@ -73,6 +75,7 @@ import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
@@ -372,21 +375,6 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
}
}
};
- private Handler dropDetailsHandler = new Handler() {
-
- @Override
- public void handleMessage(Message msg) {
- if (msg.what != MSG_CANCEL) {
- adapter.setSelectMode(false);
-
- refreshCurrentList();
-
- replaceCacheListFromSearch();
-
- progress.dismiss();
- }
- }
- };
private Handler clearOfflineLogsHandler = new Handler() {
@Override
@@ -427,7 +415,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
if (extras != null) {
Object typeObject = extras.get(Intents.EXTRA_LIST_TYPE);
type = (typeObject instanceof CacheListType) ? (CacheListType) typeObject : CacheListType.OFFLINE;
- coords = (Geopoint) extras.getParcelable(Intents.EXTRAS_COORDS);
+ coords = extras.getParcelable(Intents.EXTRAS_COORDS);
}
else {
extras = new Bundle();
@@ -464,7 +452,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- if (currentLoader.isLoading()) {
+ if (currentLoader != null && currentLoader.isLoading()) {
showFooterLoadingCaches();
}
}
@@ -793,7 +781,6 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
}
public void deletePastEvents() {
- progress.show(this, null, res.getString(R.string.caches_drop_progress), true, dropDetailsHandler.obtainMessage(MSG_CANCEL));
final List<Geocache> deletion = new ArrayList<Geocache>();
for (Geocache cache : adapter.getCheckedOrAllCaches()) {
if (cache.isEventCache()) {
@@ -803,7 +790,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
}
}
}
- new DropDetailsThread(dropDetailsHandler, deletion).start();
+ new DropDetailsTask(false).execute(deletion.toArray(new Geocache[deletion.size()]));
}
public void clearOfflineLogs() {
@@ -1143,7 +1130,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
public void removeFromHistory() {
final List<Geocache> caches = adapter.getCheckedOrAllCaches();
- final String geocodes[] = new String[caches.size()];
+ final String[] geocodes = new String[caches.size()];
for (int i = 0; i < geocodes.length; i++) {
geocodes[i] = caches.get(i).getGeocode();
}
@@ -1176,10 +1163,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
@Override
public void onClick(DialogInterface dialog, int id) {
- dropSelected();
- if (removeListAfterwards) {
- removeList(false);
- }
+ dropSelected(removeListAfterwards);
dialog.cancel();
}
});
@@ -1195,9 +1179,9 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
alert.show();
}
- public void dropSelected() {
- progress.show(this, null, res.getString(R.string.caches_drop_progress), true, dropDetailsHandler.obtainMessage(MSG_CANCEL));
- new DropDetailsThread(dropDetailsHandler, adapter.getCheckedOrAllCaches()).start();
+ public void dropSelected(boolean removeListAfterwards) {
+ final List<Geocache> selected = adapter.getCheckedOrAllCaches();
+ new DropDetailsTask(removeListAfterwards).execute(selected.toArray(new Geocache[selected.size()]));
}
/**
@@ -1382,24 +1366,35 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
}
}
- private class DropDetailsThread extends Thread {
+ private class DropDetailsTask extends AsyncTaskWithProgress<Geocache, Void> {
- final private Handler handler;
- final private List<Geocache> selected;
+ private final boolean removeListAfterwards;
- public DropDetailsThread(Handler handlerIn, List<Geocache> selectedIn) {
- handler = handlerIn;
- selected = selectedIn;
+ public DropDetailsTask(boolean removeListAfterwards) {
+ super(cgeocaches.this, null, res.getString(R.string.caches_drop_progress), true);
+ this.removeListAfterwards = removeListAfterwards;
}
@Override
- public void run() {
+ protected Void doInBackgroundInternal(Geocache[] caches) {
removeGeoAndDir();
- cgData.markDropped(selected);
- handler.sendEmptyMessage(MSG_DONE);
-
+ cgData.markDropped(Arrays.asList(caches));
startGeoAndDir();
+ return null;
+ }
+
+ @Override
+ protected void onPostExecuteInternal(Void result) {
+ // remove list in UI because of toast
+ if (removeListAfterwards) {
+ removeList(false);
+ }
+
+ adapter.setSelectMode(false);
+ refreshCurrentList();
+ replaceCacheListFromSearch();
}
+
}
private class ClearOfflineLogsThread extends Thread {
@@ -1427,7 +1422,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
showFooterLoadingCaches();
listFooter.setOnClickListener(null);
- currentLoader.startLoading();
+ getSupportLoaderManager().restartLoader(CacheListLoaderType.NEXT_PAGE.ordinal(), null, cgeocaches.this);
}
}
@@ -1444,7 +1439,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
* @param view
* unused here but needed since this method is referenced from XML layout
*/
- public void selectList(View view) {
+ public void selectList(@SuppressWarnings("unused") View view) {
if (type != CacheListType.OFFLINE) {
return;
}
@@ -1543,7 +1538,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
* @param view
* unused here but needed since this method is referenced from XML layout
*/
- public void goMap(View view) {
+ public void goMap(@SuppressWarnings("unused") View view) {
if (search == null || CollectionUtils.isEmpty(cacheList)) {
showToast(res.getString(R.string.warn_no_cache_coord));
@@ -1724,9 +1719,11 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
@Override
public Loader<SearchResult> onCreateLoader(int type, Bundle extras) {
- AbstractSearchLoader loader = null;
+ if (type >= CacheListLoaderType.values().length) {
+ throw new IllegalArgumentException("invalid loader type " + type);
+ }
CacheListLoaderType enumType = CacheListLoaderType.values()[type];
- final String username = extras.getString(Intents.EXTRA_USERNAME);
+ AbstractSearchLoader loader = null;
switch (enumType) {
case OFFLINE:
listId = Settings.getLastList();
@@ -1775,12 +1772,14 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
}
break;
case USERNAME:
+ final String username = extras.getString(Intents.EXTRA_USERNAME);
title = username;
loader = new UsernameGeocacheListLoader(app, username);
break;
case OWNER:
- title = username;
- loader = new OwnerGeocacheListLoader(app, username);
+ final String ownerName = extras.getString(Intents.EXTRA_USERNAME);
+ title = ownerName;
+ loader = new OwnerGeocacheListLoader(app, ownerName);
break;
case MAP:
//TODO Build Nullloader
@@ -1793,12 +1792,9 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
title = res.getString(R.string.caches_history);
loader = new RemoveFromHistoryLoader(app, extras.getStringArray(Intents.EXTRA_CACHELIST), coords);
break;
-
- default:
- title = "caches";
- setTitle(title);
- Log.e("cgeocaches.onCreate: No action or unknown action specified");
- return null;
+ case NEXT_PAGE:
+ loader = new NextPageGeocacheListLoader(app, search);
+ break;
}
setTitle(title);
showProgress(true);
diff --git a/main/src/cgeo/geocaching/cgeonavigate.java b/main/src/cgeo/geocaching/cgeonavigate.java
index 17c2e20..929f0a3 100644
--- a/main/src/cgeo/geocaching/cgeonavigate.java
+++ b/main/src/cgeo/geocaching/cgeonavigate.java
@@ -63,7 +63,7 @@ public class cgeonavigate extends AbstractActivity {
if (extras != null) {
title = extras.getString(EXTRAS_GEOCODE);
final String name = extras.getString(EXTRAS_NAME);
- dstCoords = (Geopoint) extras.getParcelable(EXTRAS_COORDS);
+ dstCoords = extras.getParcelable(EXTRAS_COORDS);
info = extras.getString(EXTRAS_CACHE_INFO);
if (StringUtils.isNotBlank(name)) {
diff --git a/main/src/cgeo/geocaching/connector/gc/GCConstants.java b/main/src/cgeo/geocaching/connector/gc/GCConstants.java
index 4d27617..d8711cf 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCConstants.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCConstants.java
@@ -149,7 +149,7 @@ public final class GCConstants {
public final static Pattern PATTERN_MAINTENANCE = Pattern.compile("<span id=\"ctl00_ContentBody_LogBookPanel1_lbConfirm\"[^>]*>([^<]*<font[^>]*>)?([^<]+)(</font>[^<]*)?</span>", Pattern.CASE_INSENSITIVE);
public final static Pattern PATTERN_OK1 = Pattern.compile("<h2[^>]*>[^<]*<span id=\"ctl00_ContentBody_lbHeading\"[^>]*>[^<]*</span>[^<]*</h2>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
public final static Pattern PATTERN_OK2 = Pattern.compile("<div id=[\"|']ctl00_ContentBody_LogBookPanel1_ViewLogPanel[\"|']>", Pattern.CASE_INSENSITIVE);
- public final static Pattern PATTERN_OK_IMAGEUPLOAD = Pattern.compile("<div id=[\"|']ctl00_ContentBody_ImageUploadControl1_uxUploadDonePanel[\"|']>", Pattern.CASE_INSENSITIVE);
+ public final static Pattern PATTERN_IMAGE_UPLOAD_URL = Pattern.compile("title=\"Click for Larger Image\"\\s*src=\"(.*?)\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
public final static Pattern PATTERN_VIEWSTATEFIELDCOUNT = Pattern.compile("id=\"__VIEWSTATEFIELDCOUNT\"[^(value)]+value=\"(\\d+)\"[^>]+>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
public final static Pattern PATTERN_VIEWSTATES = Pattern.compile("id=\"__VIEWSTATE(\\d*)\"[^(value)]+value=\"([^\"]+)\"[^>]+>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
public final static Pattern PATTERN_USERTOKEN = Pattern.compile("userToken\\s*=\\s*'([^']+)'");
diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java
index 76351c1..4b5d7b9 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCParser.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java
@@ -1059,7 +1059,7 @@ public abstract class GCParser {
* the URI for the image to be uploaded
* @return status code to indicate success or failure
*/
- public static StatusCode uploadLogImage(final String logId, final String caption, final String description, final Uri imageUri) {
+ public static ImmutablePair<StatusCode, String> uploadLogImage(final String logId, final String caption, final String description, final Uri imageUri) {
final String uri = new Uri.Builder().scheme("http").authority("www.geocaching.com").path("/seek/upload.aspx").encodedQuery("LID=" + logId).build().toString();
String page = Network.getResponseData(Network.getRequest(uri));
@@ -1071,7 +1071,7 @@ public abstract class GCParser {
page = Network.getResponseData(Network.getRequest(uri));
} else {
Log.e("Image upload: No login (error: " + loginState + ')');
- return StatusCode.NOT_LOGGED_IN;
+ return ImmutablePair.of(StatusCode.NOT_LOGGED_IN, null);
}
}
@@ -1088,16 +1088,16 @@ public abstract class GCParser {
final File image = new File(imageUri.getPath());
final String response = Network.getResponseData(Network.postRequest(uri, uploadParams, "ctl00$ContentBody$ImageUploadControl1$uxFileUpload", "image/jpeg", image));
- MatcherWrapper matcherOK = new MatcherWrapper(GCConstants.PATTERN_OK_IMAGEUPLOAD, response);
+ MatcherWrapper matcherUrl = new MatcherWrapper(GCConstants.PATTERN_IMAGE_UPLOAD_URL, response);
- if (matcherOK.find()) {
+ if (matcherUrl.find()) {
Log.i("Logimage successfully uploaded.");
-
- return StatusCode.NO_ERROR;
+ final String uploadedImageUrl = matcherUrl.group(1);
+ return ImmutablePair.of(StatusCode.NO_ERROR, uploadedImageUrl);
}
Log.e("GCParser.uploadLogIMage: Failed to upload image because of unknown error");
- return StatusCode.LOGIMAGE_POST_ERROR;
+ return ImmutablePair.of(StatusCode.LOGIMAGE_POST_ERROR, null);
}
/**
diff --git a/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java b/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java
index 621032f..d03062f 100644
--- a/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java
+++ b/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java
@@ -48,6 +48,8 @@ public class OC11XMLParser {
private static Pattern LOCAL_URL = Pattern.compile("href=\"(.*)\"");
private static final int CACHE_PARSE_LIMIT = 250;
private static final Resources res = cgeoapplication.getInstance().getResources();
+ private static final Pattern WHITESPACE = Pattern.compile("<p>(\\s|&nbsp;)*</p>");
+
private static ImageHolder imageHolder = null;
@@ -513,7 +515,7 @@ public class OC11XMLParser {
@Override
public void end(String body) {
final String content = body.trim();
- descHolder.shortDesc = linkify(stripMarkup(content));
+ descHolder.shortDesc = linkify(stripEmptyText(content));
}
});
@@ -523,7 +525,7 @@ public class OC11XMLParser {
@Override
public void end(String body) {
final String content = body.trim();
- descHolder.desc = linkify(stripMarkup(content));
+ descHolder.desc = linkify(stripEmptyText(content));
}
});
@@ -626,7 +628,7 @@ public class OC11XMLParser {
@Override
public void end(String logText) {
- logHolder.logEntry.log = stripMarkup(logText);
+ logHolder.logEntry.log = stripEmptyText(logText);
}
});
@@ -728,14 +730,20 @@ public class OC11XMLParser {
}
/**
- * Removes unneeded markup. Log texts are typically encapsulated in paragraph tags which lead to more empty space on
- * rendering.
+ * Removes some unneeded markup and whitespace. Log texts are typically encapsulated in paragraph tags which lead to
+ * more empty space on rendering.
*/
- protected static String stripMarkup(String input) {
- if (!StringUtils.startsWith(input, "<")) {
- return input;
+ protected static String stripEmptyText(String input) {
+ final Matcher matcher = WHITESPACE.matcher(input);
+ String result = matcher.replaceAll("").trim();
+ if (!StringUtils.startsWith(result, "<")) {
+ return result;
}
- String result = input.trim();
+ return stripMarkup(result);
+ }
+
+ private static String stripMarkup(final String input) {
+ String result = input;
for (String tagName : MARKUP) {
final String startTag = "<" + tagName + ">";
if (StringUtils.startsWith(result, startTag)) {
diff --git a/main/src/cgeo/geocaching/export/FieldnoteExport.java b/main/src/cgeo/geocaching/export/FieldnoteExport.java
index eadcb77..2900781 100644
--- a/main/src/cgeo/geocaching/export/FieldnoteExport.java
+++ b/main/src/cgeo/geocaching/export/FieldnoteExport.java
@@ -56,18 +56,19 @@ class FieldnoteExport extends AbstractExport {
}
@Override
- public void export(final List<Geocache> caches, final Activity activity) {
+ public void export(final List<Geocache> cachesList, final Activity activity) {
+ final Geocache[] caches = cachesList.toArray(new Geocache[cachesList.size()]);
if (null == activity) {
// No activity given, so no user interaction possible.
// Start export with default parameters.
- new ExportTask(caches, null, false, false).execute((Void) null);
+ new ExportTask(null, false, false).execute(caches);
} else {
// Show configuration dialog
getExportOptionsDialog(caches, activity).show();
}
}
- private Dialog getExportOptionsDialog(final List<Geocache> caches, final Activity activity) {
+ private Dialog getExportOptionsDialog(final Geocache[] caches, final Activity activity) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
// AlertDialog has always dark style, so we have to apply it as well always
@@ -90,19 +91,17 @@ class FieldnoteExport extends AbstractExport {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
new ExportTask(
- caches,
activity,
uploadOption.isChecked(),
onlyNewOption.isChecked())
- .execute((Void) null);
+ .execute(caches);
}
});
return builder.create();
}
- private class ExportTask extends AsyncTaskWithProgress<Void, Boolean> {
- private final List<Geocache> caches;
+ private class ExportTask extends AsyncTaskWithProgress<Geocache, Boolean> {
private final Activity activity;
private final boolean upload;
private final boolean onlyNew;
@@ -113,8 +112,6 @@ class FieldnoteExport extends AbstractExport {
/**
* Instantiates and configures the task for exporting field notes.
*
- * @param caches
- * The {@link List} of {@link cgeo.geocaching.Geocache} to be exported
* @param activity
* optional: Show a progress bar and toasts
* @param upload
@@ -122,16 +119,15 @@ class FieldnoteExport extends AbstractExport {
* @param onlyNew
* Upload/export only new logs since last export
*/
- public ExportTask(final List<Geocache> caches, final Activity activity, final boolean upload, final boolean onlyNew) {
+ public ExportTask(final Activity activity, final boolean upload, final boolean onlyNew) {
super(activity, getProgressTitle(), getString(R.string.export_fieldnotes_creating));
- this.caches = caches;
this.activity = activity;
this.upload = upload;
this.onlyNew = onlyNew;
}
@Override
- protected Boolean doInBackground(Void... params) {
+ protected Boolean doInBackgroundInternal(Geocache[] caches) {
final StringBuilder fieldNoteBuffer = new StringBuilder();
try {
int i = 0;
@@ -219,8 +215,7 @@ class FieldnoteExport extends AbstractExport {
}
@Override
- protected void onPostExecute(Boolean result) {
- super.onPostExecute(result);
+ protected void onPostExecuteInternal(Boolean result) {
if (null != activity) {
if (result) {
// if (onlyNew) {
@@ -239,12 +234,12 @@ class FieldnoteExport extends AbstractExport {
}
@Override
- protected void onProgressUpdate(Integer... status) {
+ protected void onProgressUpdateInternal(int status) {
if (null != activity) {
- if (STATUS_UPLOAD == status[0]) {
+ if (STATUS_UPLOAD == status) {
setMessage(getString(R.string.export_fieldnotes_uploading));
} else {
- setMessage(getString(R.string.export_fieldnotes_creating) + " (" + status[0] + ')');
+ setMessage(getString(R.string.export_fieldnotes_creating) + " (" + status + ')');
}
}
}
diff --git a/main/src/cgeo/geocaching/export/GpxExport.java b/main/src/cgeo/geocaching/export/GpxExport.java
index 19b8518..c6f72fd 100644
--- a/main/src/cgeo/geocaching/export/GpxExport.java
+++ b/main/src/cgeo/geocaching/export/GpxExport.java
@@ -37,9 +37,9 @@ import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Date;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -61,18 +61,19 @@ class GpxExport extends AbstractExport {
@Override
public void export(final List<Geocache> caches, final Activity activity) {
+ String[] geocodes = getGeocodes(caches);
if (null == activity) {
// No activity given, so no user interaction possible.
// Start export with default parameters.
- new ExportTask(caches, null).execute((Void) null);
+ new ExportTask(null).execute(geocodes);
} else {
// Show configuration dialog
- getExportDialog(caches, activity).show();
+ getExportDialog(geocodes, activity).show();
}
}
- private Dialog getExportDialog(final List<Geocache> caches, final Activity activity) {
+ private Dialog getExportDialog(final String[] geocodes, final Activity activity) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
// AlertDialog has always dark style, so we have to apply it as well always
@@ -98,43 +99,47 @@ class GpxExport extends AbstractExport {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
- new ExportTask(caches, activity).execute((Void) null);
+ new ExportTask(activity).execute(geocodes);
}
});
return builder.create();
}
- private class ExportTask extends AsyncTaskWithProgress<Void, File> {
- private final Set<String> allGeocodes = new HashSet<String>();
+ private static String[] getGeocodes(final List<Geocache> caches) {
+ ArrayList<String> allGeocodes = new ArrayList<String>(caches.size());
+ for (final Geocache geocache : caches) {
+ allGeocodes.add(geocache.getGeocode());
+ }
+ return allGeocodes.toArray(new String[allGeocodes.size()]);
+ }
+
+ private class ExportTask extends AsyncTaskWithProgress<String, File> {
private final Activity activity;
private int countExported = 0;
/**
* Instantiates and configures the task for exporting field notes.
*
- * @param caches
- * The {@link List} of {@link cgeo.geocaching.Geocache} to be exported
* @param activity
* optional: Show a progress bar and toasts
*/
- public ExportTask(final List<Geocache> caches, final Activity activity) {
- super(activity, caches.size(), getProgressTitle(), cgeoapplication.getInstance().getResources().getQuantityString(R.plurals.cache_counts, caches.size(), caches.size()));
-
- // get rid of the (half loaded) caches, we will reload them as full caches during the export
- for (Geocache geocache : caches) {
- allGeocodes.add(geocache.getGeocode());
- }
+ public ExportTask(final Activity activity) {
+ super(activity, getProgressTitle());
this.activity = activity;
}
@Override
- protected File doInBackground(Void... params) {
+ protected File doInBackgroundInternal(String[] geocodes) {
// quick check for being able to write the GPX file
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
return null;
}
+ List<String> allGeocodes = new ArrayList<String>(Arrays.asList(geocodes));
+
+ setMessage(cgeoapplication.getInstance().getResources().getQuantityString(R.plurals.cache_counts, allGeocodes.size(), allGeocodes.size()));
+
final SimpleDateFormat fileNameDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US);
final File exportFile = new File(Settings.getGpxExportDir() + File.separatorChar + "export_" + fileNameDateFormat.format(new Date()) + ".gpx");
FileWriter writer = null;
@@ -159,17 +164,10 @@ class GpxExport extends AbstractExport {
// Split the overall set of geocodes into small chunks. That is a compromise between memory efficiency (because
// we don't load all caches fully into memory) and speed (because we don't query each cache separately).
-
while (!allGeocodes.isEmpty()) {
- int cachesInBatch = 0;
- HashSet<String> geocodesOfBatch = new HashSet<String>(CACHES_PER_BATCH);
- for (Iterator<String> iterator = allGeocodes.iterator(); iterator.hasNext() && cachesInBatch < CACHES_PER_BATCH;) {
- final String geocode = iterator.next();
- geocodesOfBatch.add(geocode);
- cachesInBatch++;
- }
- allGeocodes.removeAll(geocodesOfBatch);
- exportBatch(gpx, geocodesOfBatch);
+ final List<String> batch = allGeocodes.subList(0, Math.min(CACHES_PER_BATCH, allGeocodes.size()));
+ exportBatch(gpx, batch);
+ batch.clear();
}
gpx.endTag(PREFIX_GPX, "gpx");
@@ -195,7 +193,7 @@ class GpxExport extends AbstractExport {
return exportFile;
}
- private void exportBatch(final XmlSerializer gpx, HashSet<String> geocodesOfBatch) throws IOException {
+ private void exportBatch(final XmlSerializer gpx, Collection<String> geocodesOfBatch) throws IOException {
Set<Geocache> caches = cgData.loadCaches(geocodesOfBatch, LoadFlags.LOAD_ALL_DB_ONLY);
for (Geocache cache : caches) {
gpx.startTag(PREFIX_GPX, "wpt");
@@ -366,8 +364,7 @@ class GpxExport extends AbstractExport {
}
@Override
- protected void onPostExecute(final File exportFile) {
- super.onPostExecute(exportFile);
+ protected void onPostExecuteInternal(final File exportFile) {
if (null != activity) {
if (exportFile != null) {
ActivityMixin.showToast(activity, getName() + ' ' + getString(R.string.export_exportedto) + ": " + exportFile.toString());
diff --git a/main/src/cgeo/geocaching/files/GPXImporter.java b/main/src/cgeo/geocaching/files/GPXImporter.java
index b8dcbb3..ff81fe1 100644
--- a/main/src/cgeo/geocaching/files/GPXImporter.java
+++ b/main/src/cgeo/geocaching/files/GPXImporter.java
@@ -1,458 +1,462 @@
-package cgeo.geocaching.files;
-
-import cgeo.geocaching.Geocache;
-import cgeo.geocaching.R;
-import cgeo.geocaching.SearchResult;
-import cgeo.geocaching.Settings;
-import cgeo.geocaching.StaticMapsProvider;
-import cgeo.geocaching.cgData;
-import cgeo.geocaching.activity.IAbstractActivity;
-import cgeo.geocaching.activity.Progress;
-import cgeo.geocaching.enumerations.LoadFlags;
-import cgeo.geocaching.utils.CancellableHandler;
-import cgeo.geocaching.utils.Log;
-
-import org.apache.commons.lang3.StringUtils;
-
-import android.app.Activity;
-import android.app.ProgressDialog;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Message;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-public class GPXImporter {
- static final int IMPORT_STEP_START = 0;
- static final int IMPORT_STEP_READ_FILE = 1;
- static final int IMPORT_STEP_READ_WPT_FILE = 2;
- static final int IMPORT_STEP_STORE_STATIC_MAPS = 4;
- static final int IMPORT_STEP_FINISHED = 5;
- static final int IMPORT_STEP_FINISHED_WITH_ERROR = 6;
- static final int IMPORT_STEP_CANCEL = 7;
- static final int IMPORT_STEP_CANCELED = 8;
- static final int IMPORT_STEP_STATIC_MAPS_SKIPPED = 9;
-
- public static final String GPX_FILE_EXTENSION = ".gpx";
- public static final String ZIP_FILE_EXTENSION = ".zip";
- public static final String WAYPOINTS_FILE_SUFFIX = "-wpts";
- public static final String WAYPOINTS_FILE_SUFFIX_AND_EXTENSION = WAYPOINTS_FILE_SUFFIX + GPX_FILE_EXTENSION;
-
- private static final List<String> GPX_MIME_TYPES = Arrays.asList("text/xml", "application/xml");
- private static final List<String> ZIP_MIME_TYPES = Arrays.asList("application/zip", "application/x-compressed", "application/x-zip-compressed", "application/x-zip", "application/octet-stream");
-
- private Progress progress = new Progress(true);
-
- private Resources res;
- private int listId;
- private IAbstractActivity fromActivity;
- private Handler importFinishedHandler;
-
- public GPXImporter(final IAbstractActivity fromActivity, final int listId, final Handler importFinishedHandler) {
- this.listId = listId;
- this.fromActivity = fromActivity;
- res = ((Activity) fromActivity).getResources();
- this.importFinishedHandler = importFinishedHandler;
- }
-
- /**
- * Import GPX file. Currently supports *.gpx, *.zip (containing gpx files, e.g. PQ queries) or *.loc files.
- *
- * @param file
- * the file to import
- */
- public void importGPX(final File file) {
- if (StringUtils.endsWithIgnoreCase(file.getName(), GPX_FILE_EXTENSION)) {
- new ImportGpxFileThread(file, listId, importStepHandler, progressHandler).start();
- } else if (StringUtils.endsWithIgnoreCase(file.getName(), ZIP_FILE_EXTENSION)) {
- new ImportGpxZipFileThread(file, listId, importStepHandler, progressHandler).start();
- } else {
- new ImportLocFileThread(file, listId, importStepHandler, progressHandler).start();
- }
- }
-
- /**
- * Import GPX provided via intent of activity that instantiated this GPXImporter.
- */
- public void importGPX() {
- final ContentResolver contentResolver = ((Activity) fromActivity).getContentResolver();
- final Intent intent = ((Activity) fromActivity).getIntent();
- final Uri uri = intent.getData();
-
- String mimeType = intent.getType();
- // if mimetype can't be determined (e.g. for emulators email app), derive it from uri file extension
- // contentResolver.getType(uri) doesn't help but throws exception for emulators email app
- // Permission Denial: reading com.android.email.provider.EmailProvider uri
- // Google search says: there is no solution for this problem
- // Gmail doesn't work at all, see #967
- if (mimeType == null) {
- if (StringUtils.endsWithIgnoreCase(uri.getPath(), GPX_FILE_EXTENSION)) {
- mimeType = "application/xml";
- } else {
- // if we can't determine a better type, default to zip import
- // emulator email sends e.g. content://com.android.email.attachmentprovider/1/1/RAW, mimetype=null
- mimeType = "application/zip";
- }
- }
-
- Log.i("importGPX: " + uri + ", mimetype=" + mimeType);
- if (GPX_MIME_TYPES.contains(mimeType)) {
- new ImportGpxAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start();
- } else if (ZIP_MIME_TYPES.contains(mimeType)) {
- new ImportGpxZipAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start();
- } else {
- importFinished();
- }
- }
-
- static abstract class ImportThread extends Thread {
- final int listId;
- final Handler importStepHandler;
- final CancellableHandler progressHandler;
-
- protected ImportThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- this.listId = listId;
- this.importStepHandler = importStepHandler;
- this.progressHandler = progressHandler;
- }
-
- @Override
- public void run() {
- try {
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_START));
- final Collection<Geocache> caches = doImport();
- Log.i("Imported successfully " + caches.size() + " caches.");
-
- final SearchResult search = new SearchResult();
- for (Geocache cache : caches) {
- search.addCache(cache);
- }
-
- if (Settings.isStoreOfflineMaps() || Settings.isStoreOfflineWpMaps()) {
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_STORE_STATIC_MAPS, R.string.gpx_import_store_static_maps, search.getCount()));
- boolean finishedWithoutCancel = importStaticMaps(search);
- // Skip last message if static maps where canceled
- if (!finishedWithoutCancel) {
- return;
- }
- }
-
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED, search.getCount(), 0, search));
- } catch (IOException e) {
- Log.i("Importing caches failed - error reading data: " + e.getMessage());
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_io, 0, e.getLocalizedMessage()));
- } catch (ParserException e) {
- Log.i("Importing caches failed - data format error" + e.getMessage());
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_parser, 0, e.getLocalizedMessage()));
- } catch (CancellationException e) {
- Log.i("Importing caches canceled");
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_CANCELED));
- } catch (Exception e) {
- Log.e("Importing caches failed - unknown error: ", e);
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_unexpected, 0, e.getLocalizedMessage()));
- }
- }
-
- protected abstract Collection<Geocache> doImport() throws IOException, ParserException;
-
- private boolean importStaticMaps(final SearchResult importedCaches) {
- int storedCacheMaps = 0;
- for (String geocode : importedCaches.getGeocodes()) {
- Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS);
- Log.d("GPXImporter.ImportThread.importStaticMaps start downloadMaps for cache " + geocode);
- StaticMapsProvider.downloadMaps(cache);
- storedCacheMaps++;
- if (progressHandler.isCancelled()) {
- return false;
- }
- progressHandler.sendMessage(progressHandler.obtainMessage(0, storedCacheMaps, 0));
- }
- return true;
- }
- }
-
- static class ImportLocFileThread extends ImportThread {
- private final File file;
-
- public ImportLocFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- this.file = file;
- }
-
- @Override
- protected Collection<Geocache> doImport() throws IOException, ParserException {
- Log.i("Import LOC file: " + file.getAbsolutePath());
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) file.length()));
- LocParser parser = new LocParser(listId);
- return parser.parse(file, progressHandler);
- }
- }
-
- static abstract class ImportGpxThread extends ImportThread {
-
- protected ImportGpxThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- }
-
- @Override
- protected Collection<Geocache> doImport() throws IOException, ParserException {
- try {
- // try to parse cache file as GPX 10
- return doImport(new GPX10Parser(listId));
- } catch (ParserException pe) {
- // didn't work -> lets try GPX11
- return doImport(new GPX11Parser(listId));
- }
- }
-
- protected abstract Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException;
- }
-
- static class ImportGpxFileThread extends ImportGpxThread {
- private final File cacheFile;
-
- public ImportGpxFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- this.cacheFile = file;
- }
-
- @Override
- protected Collection<Geocache> doImport(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);
-
- final String wptsFilename = getWaypointsFileNameForGpxFile(cacheFile);
- if (wptsFilename != null) {
- final File wptsFile = new File(cacheFile.getParentFile(), wptsFilename);
- if (wptsFile.canRead()) {
- Log.i("Import GPX waypoint file: " + wptsFile.getAbsolutePath());
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_WPT_FILE, R.string.gpx_import_loading_waypoints, (int) wptsFile.length()));
- caches = parser.parse(wptsFile, progressHandler);
- }
- }
- return caches;
- }
- }
-
- static class ImportGpxAttachmentThread extends ImportGpxThread {
- private final Uri uri;
- private ContentResolver contentResolver;
-
- public ImportGpxAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- this.uri = uri;
- this.contentResolver = contentResolver;
- }
-
- @Override
- protected Collection<Geocache> doImport(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));
- InputStream is = contentResolver.openInputStream(uri);
- try {
- return parser.parse(is, progressHandler);
- } finally {
- is.close();
- }
- }
- }
-
- static abstract class ImportGpxZipThread extends ImportGpxThread {
-
- protected ImportGpxZipThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- }
-
- @Override
- protected Collection<Geocache> doImport(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
- ZipInputStream zis = new ZipInputStream(getInputStream());
- try {
- for (ZipEntry zipEntry = zis.getNextEntry(); zipEntry != null; zipEntry = zis.getNextEntry()) {
- if (StringUtils.endsWithIgnoreCase(zipEntry.getName(), GPX_FILE_EXTENSION)) {
- if (!StringUtils.endsWithIgnoreCase(zipEntry.getName(), WAYPOINTS_FILE_SUFFIX_AND_EXTENSION)) {
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) zipEntry.getSize()));
- caches = parser.parse(new NoCloseInputStream(zis), progressHandler);
- }
- } else {
- throw new ParserException("Imported zip is not a GPX zip file.");
- }
- zis.closeEntry();
- }
- } finally {
- zis.close();
- }
-
- // 2. parse waypoint files
- zis = new ZipInputStream(getInputStream());
- try {
- for (ZipEntry zipEntry = zis.getNextEntry(); zipEntry != null; zipEntry = zis.getNextEntry()) {
- if (StringUtils.endsWithIgnoreCase(zipEntry.getName(), WAYPOINTS_FILE_SUFFIX_AND_EXTENSION)) {
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_WPT_FILE, R.string.gpx_import_loading_waypoints, (int) zipEntry.getSize()));
- caches = parser.parse(new NoCloseInputStream(zis), progressHandler);
- }
- zis.closeEntry();
- }
- } finally {
- zis.close();
- }
-
- return caches;
- }
-
- protected abstract InputStream getInputStream() throws IOException;
- }
-
- static class ImportGpxZipFileThread extends ImportGpxZipThread {
- private final File cacheFile;
-
- public ImportGpxZipFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- this.cacheFile = file;
- Log.i("Import zipped GPX: " + file);
- }
-
- @Override
- protected InputStream getInputStream() throws IOException {
- return new FileInputStream(cacheFile);
- }
- }
-
- static class ImportGpxZipAttachmentThread extends ImportGpxZipThread {
- private final Uri uri;
- private ContentResolver contentResolver;
-
- public ImportGpxZipAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- this.uri = uri;
- this.contentResolver = contentResolver;
- Log.i("Import zipped GPX from uri: " + uri);
- }
-
- @Override
- protected InputStream getInputStream() throws IOException {
- return contentResolver.openInputStream(uri);
- }
- }
-
- final private CancellableHandler progressHandler = new CancellableHandler() {
- @Override
- public void handleRegularMessage(Message msg) {
- progress.setProgress(msg.arg1);
- }
- };
-
- final private Handler importStepHandler = new Handler() {
- private boolean showProgressAfterCancel = false;
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case IMPORT_STEP_START:
- Message cancelMessage = importStepHandler.obtainMessage(IMPORT_STEP_CANCEL);
- progress.show((Context) fromActivity, res.getString(R.string.gpx_import_title_reading_file), res.getString(R.string.gpx_import_loading_caches), ProgressDialog.STYLE_HORIZONTAL, cancelMessage);
- break;
-
- case IMPORT_STEP_READ_FILE:
- case IMPORT_STEP_READ_WPT_FILE:
- progress.setMessage(res.getString(msg.arg1));
- progress.setMaxProgressAndReset(msg.arg2);
- break;
-
- case IMPORT_STEP_STORE_STATIC_MAPS:
- progress.dismiss();
- Message skipMessage = importStepHandler.obtainMessage(IMPORT_STEP_STATIC_MAPS_SKIPPED, msg.arg2, 0);
- progress.show((Context) fromActivity, res.getString(R.string.gpx_import_title_static_maps), res.getString(R.string.gpx_import_store_static_maps), ProgressDialog.STYLE_HORIZONTAL, skipMessage);
- progress.setMaxProgressAndReset(msg.arg2);
- break;
-
- case IMPORT_STEP_STATIC_MAPS_SKIPPED:
- progress.dismiss();
- progressHandler.cancel();
- StringBuilder bufferSkipped = new StringBuilder(20);
- bufferSkipped.append(res.getString(R.string.gpx_import_static_maps_skipped)).append(", ").append(msg.arg1).append(' ').append(res.getString(R.string.gpx_import_caches_imported));
- fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_imported), bufferSkipped.toString());
- importFinished();
- break;
-
- case IMPORT_STEP_FINISHED:
- progress.dismiss();
- fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_imported), msg.arg1 + " " + res.getString(R.string.gpx_import_caches_imported));
- importFinished();
- break;
-
- case IMPORT_STEP_FINISHED_WITH_ERROR:
- progress.dismiss();
- fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_import_failed), res.getString(msg.arg1) + "\n\n" + msg.obj);
- importFinished();
- break;
-
- case IMPORT_STEP_CANCEL:
- progress.dismiss();
- progressHandler.cancel();
- break;
-
- case IMPORT_STEP_CANCELED:
- StringBuilder bufferCanceled = new StringBuilder(20);
- bufferCanceled.append(res.getString(R.string.gpx_import_canceled));
- if (showProgressAfterCancel) {
- bufferCanceled.append(", ").append(progress.getProgress()).append(' ').append(res.getString(R.string.gpx_import_caches_imported));
- }
- fromActivity.showShortToast(bufferCanceled.toString());
- importFinished();
- break;
-
- default:
- break;
- }
- }
- };
-
- /**
- * @param gpxfile
- * the gpx file
- * @return the expected file name of the waypoints file
- */
- static String getWaypointsFileNameForGpxFile(final File gpxfile) {
- if (gpxfile == null || !gpxfile.canRead()) {
- return null;
- }
- final String gpxFileName = gpxfile.getName();
- File dir = gpxfile.getParentFile();
- String[] filenameList = dir.list();
- for (String filename : filenameList) {
- if (!StringUtils.containsIgnoreCase(filename, WAYPOINTS_FILE_SUFFIX)) {
- continue;
- }
- String expectedGpxFileName = StringUtils.substringBeforeLast(filename, WAYPOINTS_FILE_SUFFIX)
- + StringUtils.substringAfterLast(filename, WAYPOINTS_FILE_SUFFIX);
- if (gpxFileName.equals(expectedGpxFileName)) {
- return filename;
- }
- }
- return null;
- }
-
- protected void importFinished() {
- if (importFinishedHandler != null) {
- importFinishedHandler.sendEmptyMessage(0);
- }
- }
-}
+package cgeo.geocaching.files;
+
+import cgeo.geocaching.Geocache;
+import cgeo.geocaching.R;
+import cgeo.geocaching.SearchResult;
+import cgeo.geocaching.Settings;
+import cgeo.geocaching.StaticMapsProvider;
+import cgeo.geocaching.cgData;
+import cgeo.geocaching.activity.IAbstractActivity;
+import cgeo.geocaching.activity.Progress;
+import cgeo.geocaching.enumerations.LoadFlags;
+import cgeo.geocaching.utils.CancellableHandler;
+import cgeo.geocaching.utils.Log;
+
+import org.apache.commons.lang3.StringUtils;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class GPXImporter {
+ static final int IMPORT_STEP_START = 0;
+ static final int IMPORT_STEP_READ_FILE = 1;
+ static final int IMPORT_STEP_READ_WPT_FILE = 2;
+ static final int IMPORT_STEP_STORE_STATIC_MAPS = 4;
+ static final int IMPORT_STEP_FINISHED = 5;
+ static final int IMPORT_STEP_FINISHED_WITH_ERROR = 6;
+ static final int IMPORT_STEP_CANCEL = 7;
+ static final int IMPORT_STEP_CANCELED = 8;
+ static final int IMPORT_STEP_STATIC_MAPS_SKIPPED = 9;
+
+ public static final String GPX_FILE_EXTENSION = ".gpx";
+ public static final String ZIP_FILE_EXTENSION = ".zip";
+ public static final String WAYPOINTS_FILE_SUFFIX = "-wpts";
+ public static final String WAYPOINTS_FILE_SUFFIX_AND_EXTENSION = WAYPOINTS_FILE_SUFFIX + GPX_FILE_EXTENSION;
+
+ private static final List<String> GPX_MIME_TYPES = Arrays.asList("text/xml", "application/xml");
+ private static final List<String> ZIP_MIME_TYPES = Arrays.asList("application/zip", "application/x-compressed", "application/x-zip-compressed", "application/x-zip", "application/octet-stream");
+
+ private Progress progress = new Progress(true);
+
+ private Resources res;
+ private int listId;
+ private IAbstractActivity fromActivity;
+ private Handler importFinishedHandler;
+
+ public GPXImporter(final IAbstractActivity fromActivity, final int listId, final Handler importFinishedHandler) {
+ this.listId = listId;
+ this.fromActivity = fromActivity;
+ res = ((Activity) fromActivity).getResources();
+ this.importFinishedHandler = importFinishedHandler;
+ }
+
+ /**
+ * Import GPX file. Currently supports *.gpx, *.zip (containing gpx files, e.g. PQ queries) or *.loc files.
+ *
+ * @param file
+ * the file to import
+ */
+ public void importGPX(final File file) {
+ if (StringUtils.endsWithIgnoreCase(file.getName(), GPX_FILE_EXTENSION)) {
+ new ImportGpxFileThread(file, listId, importStepHandler, progressHandler).start();
+ } else if (StringUtils.endsWithIgnoreCase(file.getName(), ZIP_FILE_EXTENSION)) {
+ new ImportGpxZipFileThread(file, listId, importStepHandler, progressHandler).start();
+ } else {
+ new ImportLocFileThread(file, listId, importStepHandler, progressHandler).start();
+ }
+ }
+
+ /**
+ * Import GPX provided via intent of activity that instantiated this GPXImporter.
+ */
+ public void importGPX() {
+ final ContentResolver contentResolver = ((Activity) fromActivity).getContentResolver();
+ final Intent intent = ((Activity) fromActivity).getIntent();
+ final Uri uri = intent.getData();
+
+ String mimeType = intent.getType();
+ // if mimetype can't be determined (e.g. for emulators email app), derive it from uri file extension
+ // contentResolver.getType(uri) doesn't help but throws exception for emulators email app
+ // Permission Denial: reading com.android.email.provider.EmailProvider uri
+ // Google search says: there is no solution for this problem
+ // Gmail doesn't work at all, see #967
+ if (mimeType == null) {
+ if (StringUtils.endsWithIgnoreCase(uri.getPath(), GPX_FILE_EXTENSION)) {
+ mimeType = "application/xml";
+ } else {
+ // if we can't determine a better type, default to zip import
+ // emulator email sends e.g. content://com.android.email.attachmentprovider/1/1/RAW, mimetype=null
+ mimeType = "application/zip";
+ }
+ }
+
+ Log.i("importGPX: " + uri + ", mimetype=" + mimeType);
+ if (GPX_MIME_TYPES.contains(mimeType)) {
+ new ImportGpxAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start();
+ } else if (ZIP_MIME_TYPES.contains(mimeType)) {
+ new ImportGpxZipAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start();
+ } else {
+ importFinished();
+ }
+ }
+
+ static abstract class ImportThread extends Thread {
+ final int listId;
+ final Handler importStepHandler;
+ final CancellableHandler progressHandler;
+
+ protected ImportThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ this.listId = listId;
+ this.importStepHandler = importStepHandler;
+ this.progressHandler = progressHandler;
+ }
+
+ @Override
+ public void run() {
+ try {
+ importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_START));
+ final Collection<Geocache> caches = doImport();
+ Log.i("Imported successfully " + caches.size() + " caches.");
+
+ final SearchResult search = new SearchResult();
+ for (Geocache cache : caches) {
+ search.addCache(cache);
+ }
+
+ if (Settings.isStoreOfflineMaps() || Settings.isStoreOfflineWpMaps()) {
+ importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_STORE_STATIC_MAPS, R.string.gpx_import_store_static_maps, search.getCount()));
+ boolean finishedWithoutCancel = importStaticMaps(search);
+ // Skip last message if static maps where canceled
+ if (!finishedWithoutCancel) {
+ return;
+ }
+ }
+
+ importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED, search.getCount(), 0, search));
+ } catch (IOException e) {
+ Log.i("Importing caches failed - error reading data: " + e.getMessage());
+ importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_io, 0, e.getLocalizedMessage()));
+ } catch (ParserException e) {
+ Log.i("Importing caches failed - data format error" + e.getMessage());
+ importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_parser, 0, e.getLocalizedMessage()));
+ } catch (CancellationException e) {
+ Log.i("Importing caches canceled");
+ importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_CANCELED));
+ } catch (Exception e) {
+ Log.e("Importing caches failed - unknown error: ", e);
+ importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_unexpected, 0, e.getLocalizedMessage()));
+ }
+ }
+
+ protected abstract Collection<Geocache> doImport() throws IOException, ParserException;
+
+ private boolean importStaticMaps(final SearchResult importedCaches) {
+ int storedCacheMaps = 0;
+ for (final String geocode : importedCaches.getGeocodes()) {
+ final Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS);
+ if (cache != null) {
+ Log.d("GPXImporter.ImportThread.importStaticMaps start downloadMaps for cache " + geocode);
+ StaticMapsProvider.downloadMaps(cache);
+ } else {
+ Log.d("GPXImporter.ImportThread.importStaticMaps: no data found for " + geocode);
+ }
+ storedCacheMaps++;
+ if (progressHandler.isCancelled()) {
+ return false;
+ }
+ progressHandler.sendMessage(progressHandler.obtainMessage(0, storedCacheMaps, 0));
+ }
+ return true;
+ }
+ }
+
+ static class ImportLocFileThread extends ImportThread {
+ private final File file;
+
+ public ImportLocFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ super(listId, importStepHandler, progressHandler);
+ this.file = file;
+ }
+
+ @Override
+ protected Collection<Geocache> doImport() throws IOException, ParserException {
+ Log.i("Import LOC file: " + file.getAbsolutePath());
+ importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) file.length()));
+ LocParser parser = new LocParser(listId);
+ return parser.parse(file, progressHandler);
+ }
+ }
+
+ static abstract class ImportGpxThread extends ImportThread {
+
+ protected ImportGpxThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ super(listId, importStepHandler, progressHandler);
+ }
+
+ @Override
+ protected Collection<Geocache> doImport() throws IOException, ParserException {
+ try {
+ // try to parse cache file as GPX 10
+ return doImport(new GPX10Parser(listId));
+ } catch (ParserException pe) {
+ // didn't work -> lets try GPX11
+ return doImport(new GPX11Parser(listId));
+ }
+ }
+
+ protected abstract Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException;
+ }
+
+ static class ImportGpxFileThread extends ImportGpxThread {
+ private final File cacheFile;
+
+ public ImportGpxFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ super(listId, importStepHandler, progressHandler);
+ this.cacheFile = file;
+ }
+
+ @Override
+ protected Collection<Geocache> doImport(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);
+
+ final String wptsFilename = getWaypointsFileNameForGpxFile(cacheFile);
+ if (wptsFilename != null) {
+ final File wptsFile = new File(cacheFile.getParentFile(), wptsFilename);
+ if (wptsFile.canRead()) {
+ Log.i("Import GPX waypoint file: " + wptsFile.getAbsolutePath());
+ importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_WPT_FILE, R.string.gpx_import_loading_waypoints, (int) wptsFile.length()));
+ caches = parser.parse(wptsFile, progressHandler);
+ }
+ }
+ return caches;
+ }
+ }
+
+ static class ImportGpxAttachmentThread extends ImportGpxThread {
+ private final Uri uri;
+ private ContentResolver contentResolver;
+
+ public ImportGpxAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ super(listId, importStepHandler, progressHandler);
+ this.uri = uri;
+ this.contentResolver = contentResolver;
+ }
+
+ @Override
+ protected Collection<Geocache> doImport(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));
+ InputStream is = contentResolver.openInputStream(uri);
+ try {
+ return parser.parse(is, progressHandler);
+ } finally {
+ is.close();
+ }
+ }
+ }
+
+ static abstract class ImportGpxZipThread extends ImportGpxThread {
+
+ protected ImportGpxZipThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ super(listId, importStepHandler, progressHandler);
+ }
+
+ @Override
+ protected Collection<Geocache> doImport(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
+ ZipInputStream zis = new ZipInputStream(getInputStream());
+ try {
+ for (ZipEntry zipEntry = zis.getNextEntry(); zipEntry != null; zipEntry = zis.getNextEntry()) {
+ if (StringUtils.endsWithIgnoreCase(zipEntry.getName(), GPX_FILE_EXTENSION)) {
+ if (!StringUtils.endsWithIgnoreCase(zipEntry.getName(), WAYPOINTS_FILE_SUFFIX_AND_EXTENSION)) {
+ importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) zipEntry.getSize()));
+ caches = parser.parse(new NoCloseInputStream(zis), progressHandler);
+ }
+ } else {
+ throw new ParserException("Imported zip is not a GPX zip file.");
+ }
+ zis.closeEntry();
+ }
+ } finally {
+ zis.close();
+ }
+
+ // 2. parse waypoint files
+ zis = new ZipInputStream(getInputStream());
+ try {
+ for (ZipEntry zipEntry = zis.getNextEntry(); zipEntry != null; zipEntry = zis.getNextEntry()) {
+ if (StringUtils.endsWithIgnoreCase(zipEntry.getName(), WAYPOINTS_FILE_SUFFIX_AND_EXTENSION)) {
+ importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_WPT_FILE, R.string.gpx_import_loading_waypoints, (int) zipEntry.getSize()));
+ caches = parser.parse(new NoCloseInputStream(zis), progressHandler);
+ }
+ zis.closeEntry();
+ }
+ } finally {
+ zis.close();
+ }
+
+ return caches;
+ }
+
+ protected abstract InputStream getInputStream() throws IOException;
+ }
+
+ static class ImportGpxZipFileThread extends ImportGpxZipThread {
+ private final File cacheFile;
+
+ public ImportGpxZipFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ super(listId, importStepHandler, progressHandler);
+ this.cacheFile = file;
+ Log.i("Import zipped GPX: " + file);
+ }
+
+ @Override
+ protected InputStream getInputStream() throws IOException {
+ return new FileInputStream(cacheFile);
+ }
+ }
+
+ static class ImportGpxZipAttachmentThread extends ImportGpxZipThread {
+ private final Uri uri;
+ private ContentResolver contentResolver;
+
+ public ImportGpxZipAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ super(listId, importStepHandler, progressHandler);
+ this.uri = uri;
+ this.contentResolver = contentResolver;
+ Log.i("Import zipped GPX from uri: " + uri);
+ }
+
+ @Override
+ protected InputStream getInputStream() throws IOException {
+ return contentResolver.openInputStream(uri);
+ }
+ }
+
+ final private CancellableHandler progressHandler = new CancellableHandler() {
+ @Override
+ public void handleRegularMessage(Message msg) {
+ progress.setProgress(msg.arg1);
+ }
+ };
+
+ final private Handler importStepHandler = new Handler() {
+ private boolean showProgressAfterCancel = false;
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case IMPORT_STEP_START:
+ Message cancelMessage = importStepHandler.obtainMessage(IMPORT_STEP_CANCEL);
+ progress.show((Context) fromActivity, res.getString(R.string.gpx_import_title_reading_file), res.getString(R.string.gpx_import_loading_caches), ProgressDialog.STYLE_HORIZONTAL, cancelMessage);
+ break;
+
+ case IMPORT_STEP_READ_FILE:
+ case IMPORT_STEP_READ_WPT_FILE:
+ progress.setMessage(res.getString(msg.arg1));
+ progress.setMaxProgressAndReset(msg.arg2);
+ break;
+
+ case IMPORT_STEP_STORE_STATIC_MAPS:
+ progress.dismiss();
+ Message skipMessage = importStepHandler.obtainMessage(IMPORT_STEP_STATIC_MAPS_SKIPPED, msg.arg2, 0);
+ progress.show((Context) fromActivity, res.getString(R.string.gpx_import_title_static_maps), res.getString(R.string.gpx_import_store_static_maps), ProgressDialog.STYLE_HORIZONTAL, skipMessage);
+ progress.setMaxProgressAndReset(msg.arg2);
+ break;
+
+ case IMPORT_STEP_STATIC_MAPS_SKIPPED:
+ progress.dismiss();
+ progressHandler.cancel();
+ StringBuilder bufferSkipped = new StringBuilder(20);
+ bufferSkipped.append(res.getString(R.string.gpx_import_static_maps_skipped)).append(", ").append(msg.arg1).append(' ').append(res.getString(R.string.gpx_import_caches_imported));
+ fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_imported), bufferSkipped.toString());
+ importFinished();
+ break;
+
+ case IMPORT_STEP_FINISHED:
+ progress.dismiss();
+ fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_imported), msg.arg1 + " " + res.getString(R.string.gpx_import_caches_imported));
+ importFinished();
+ break;
+
+ case IMPORT_STEP_FINISHED_WITH_ERROR:
+ progress.dismiss();
+ fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_import_failed), res.getString(msg.arg1) + "\n\n" + msg.obj);
+ importFinished();
+ break;
+
+ case IMPORT_STEP_CANCEL:
+ progress.dismiss();
+ progressHandler.cancel();
+ break;
+
+ case IMPORT_STEP_CANCELED:
+ StringBuilder bufferCanceled = new StringBuilder(20);
+ bufferCanceled.append(res.getString(R.string.gpx_import_canceled));
+ if (showProgressAfterCancel) {
+ bufferCanceled.append(", ").append(progress.getProgress()).append(' ').append(res.getString(R.string.gpx_import_caches_imported));
+ }
+ fromActivity.showShortToast(bufferCanceled.toString());
+ importFinished();
+ break;
+
+ default:
+ break;
+ }
+ }
+ };
+
+ /**
+ * @param gpxfile
+ * the gpx file
+ * @return the expected file name of the waypoints file
+ */
+ static String getWaypointsFileNameForGpxFile(final File gpxfile) {
+ if (gpxfile == null || !gpxfile.canRead()) {
+ return null;
+ }
+ final String gpxFileName = gpxfile.getName();
+ File dir = gpxfile.getParentFile();
+ String[] filenameList = dir.list();
+ for (String filename : filenameList) {
+ if (!StringUtils.containsIgnoreCase(filename, WAYPOINTS_FILE_SUFFIX)) {
+ continue;
+ }
+ String expectedGpxFileName = StringUtils.substringBeforeLast(filename, WAYPOINTS_FILE_SUFFIX)
+ + StringUtils.substringAfterLast(filename, WAYPOINTS_FILE_SUFFIX);
+ if (gpxFileName.equals(expectedGpxFileName)) {
+ return filename;
+ }
+ }
+ return null;
+ }
+
+ protected void importFinished() {
+ if (importFinishedHandler != null) {
+ importFinishedHandler.sendEmptyMessage(0);
+ }
+ }
+}
diff --git a/main/src/cgeo/geocaching/files/GPXParser.java b/main/src/cgeo/geocaching/files/GPXParser.java
index 5647d14..96c90cc 100644
--- a/main/src/cgeo/geocaching/files/GPXParser.java
+++ b/main/src/cgeo/geocaching/files/GPXParser.java
@@ -190,6 +190,9 @@ public abstract class GPXParser extends FileParser {
// map GPX-Attribute-Id to baseName
public static String getBaseName(final int id) {
+ if (id < 0) {
+ return null;
+ }
// get String out of array
if (CACHE_ATTRIBUTES.length <= id) {
return null;
diff --git a/main/src/cgeo/geocaching/filter/AttributeFilter.java b/main/src/cgeo/geocaching/filter/AttributeFilter.java
index 4b6f382..cadcf49 100644
--- a/main/src/cgeo/geocaching/filter/AttributeFilter.java
+++ b/main/src/cgeo/geocaching/filter/AttributeFilter.java
@@ -1,16 +1,16 @@
package cgeo.geocaching.filter;
-import cgeo.geocaching.R;
import cgeo.geocaching.Geocache;
+import cgeo.geocaching.R;
import cgeo.geocaching.cgData;
import cgeo.geocaching.cgeoapplication;
import cgeo.geocaching.enumerations.LoadFlags.LoadFlag;
-import org.apache.commons.lang3.StringUtils;
-
import android.content.res.Resources;
import java.util.EnumSet;
+import java.util.LinkedList;
+import java.util.List;
class AttributeFilter extends AbstractFilter {
@@ -24,13 +24,7 @@ class AttributeFilter extends AbstractFilter {
private static String getName(final String attribute, final Resources res, final String packageName) {
// dynamically search for a translation of the attribute
final int id = res.getIdentifier(attribute, "string", packageName);
- if (id > 0) {
- final String translated = res.getString(id);
- if (StringUtils.isNotBlank(translated)) {
- return translated;
- }
- }
- return attribute;
+ return id > 0 ? res.getString(id) : attribute;
}
@Override
@@ -45,14 +39,13 @@ class AttributeFilter extends AbstractFilter {
public static class Factory implements IFilterFactory {
@Override
- public IFilter[] getFilters() {
+ public List<IFilter> getFilters() {
final String packageName = cgeoapplication.getInstance().getBaseContext().getPackageName();
final Resources res = cgeoapplication.getInstance().getResources();
- final String[] ids = res.getStringArray(R.array.attribute_ids);
- final IFilter[] filters = new IFilter[ids.length];
- for (int i = 0; i < ids.length; i++) {
- filters[i] = new AttributeFilter(getName("attribute_" + ids[i], res, packageName), ids[i]);
+ final List<IFilter> filters = new LinkedList<IFilter>();
+ for (final String id: res.getStringArray(R.array.attribute_ids)) {
+ filters.add(new AttributeFilter(getName("attribute_" + id, res, packageName), id));
}
return filters;
}
diff --git a/main/src/cgeo/geocaching/filter/DifficultyFilter.java b/main/src/cgeo/geocaching/filter/DifficultyFilter.java
index c0ec61a..8099a51 100644
--- a/main/src/cgeo/geocaching/filter/DifficultyFilter.java
+++ b/main/src/cgeo/geocaching/filter/DifficultyFilter.java
@@ -4,6 +4,7 @@ import cgeo.geocaching.Geocache;
import cgeo.geocaching.R;
import java.util.ArrayList;
+import java.util.List;
class DifficultyFilter extends AbstractRangeFilter {
@@ -19,12 +20,12 @@ class DifficultyFilter extends AbstractRangeFilter {
public static class Factory implements IFilterFactory {
@Override
- public IFilter[] getFilters() {
+ public List<IFilter> getFilters() {
final ArrayList<IFilter> filters = new ArrayList<IFilter>(5);
for (int difficulty = 1; difficulty <= 5; difficulty++) {
filters.add(new DifficultyFilter(difficulty));
}
- return filters.toArray(new IFilter[filters.size()]);
+ return filters;
}
}
diff --git a/main/src/cgeo/geocaching/filter/FilterUserInterface.java b/main/src/cgeo/geocaching/filter/FilterUserInterface.java
index be63a08..a1d42cc 100644
--- a/main/src/cgeo/geocaching/filter/FilterUserInterface.java
+++ b/main/src/cgeo/geocaching/filter/FilterUserInterface.java
@@ -16,6 +16,7 @@ import android.widget.ArrayAdapter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.List;
public final class FilterUserInterface {
@@ -101,9 +102,9 @@ public final class FilterUserInterface {
}
private void selectFromFactory(final IFilterFactory factory, final String menuTitle, final RunnableWithArgument<IFilter> runAfterwards) {
- final IFilter[] filters = factory.getFilters();
- if (filters.length == 1) {
- runAfterwards.run(filters[0]);
+ final List<IFilter> filters = Collections.unmodifiableList(factory.getFilters());
+ if (filters.size() == 1) {
+ runAfterwards.run(filters.get(0));
return;
}
@@ -114,7 +115,7 @@ public final class FilterUserInterface {
builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int item) {
- runAfterwards.run(filters[item]);
+ runAfterwards.run(filters.get(item));
}
});
diff --git a/main/src/cgeo/geocaching/filter/IFilterFactory.java b/main/src/cgeo/geocaching/filter/IFilterFactory.java
index 3491fd7..e750639 100644
--- a/main/src/cgeo/geocaching/filter/IFilterFactory.java
+++ b/main/src/cgeo/geocaching/filter/IFilterFactory.java
@@ -1,5 +1,7 @@
package cgeo.geocaching.filter;
+import java.util.List;
+
interface IFilterFactory {
- public IFilter[] getFilters();
+ public List<? extends IFilter> getFilters();
}
diff --git a/main/src/cgeo/geocaching/filter/ModifiedFilter.java b/main/src/cgeo/geocaching/filter/ModifiedFilter.java
index f3e57de..74befda 100644
--- a/main/src/cgeo/geocaching/filter/ModifiedFilter.java
+++ b/main/src/cgeo/geocaching/filter/ModifiedFilter.java
@@ -4,6 +4,9 @@ import cgeo.geocaching.Geocache;
import cgeo.geocaching.R;
import cgeo.geocaching.cgeoapplication;
+import java.util.Collections;
+import java.util.List;
+
class ModifiedFilter extends AbstractFilter implements IFilterFactory {
public ModifiedFilter() {
@@ -17,7 +20,7 @@ class ModifiedFilter extends AbstractFilter implements IFilterFactory {
}
@Override
- public IFilter[] getFilters() {
- return new IFilter[] { this };
+ public List<ModifiedFilter> getFilters() {
+ return Collections.singletonList(this);
}
}
diff --git a/main/src/cgeo/geocaching/filter/OriginFilter.java b/main/src/cgeo/geocaching/filter/OriginFilter.java
index a880092..bd4e41e 100644
--- a/main/src/cgeo/geocaching/filter/OriginFilter.java
+++ b/main/src/cgeo/geocaching/filter/OriginFilter.java
@@ -7,6 +7,7 @@ import cgeo.geocaching.connector.IConnector;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.List;
public class OriginFilter extends AbstractFilter {
@@ -25,7 +26,7 @@ public class OriginFilter extends AbstractFilter {
public static final class Factory implements IFilterFactory {
@Override
- public IFilter[] getFilters() {
+ public List<OriginFilter> getFilters() {
final ArrayList<OriginFilter> filters = new ArrayList<OriginFilter>();
for (IConnector connector : ConnectorFactory.getConnectors()) {
filters.add(new OriginFilter(connector));
@@ -40,7 +41,7 @@ public class OriginFilter extends AbstractFilter {
}
});
- return filters.toArray(new OriginFilter[filters.size()]);
+ return filters;
}
}
diff --git a/main/src/cgeo/geocaching/filter/SizeFilter.java b/main/src/cgeo/geocaching/filter/SizeFilter.java
index 7a34c83..8ddc475 100644
--- a/main/src/cgeo/geocaching/filter/SizeFilter.java
+++ b/main/src/cgeo/geocaching/filter/SizeFilter.java
@@ -3,7 +3,8 @@ package cgeo.geocaching.filter;
import cgeo.geocaching.Geocache;
import cgeo.geocaching.enumerations.CacheSize;
-import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
class SizeFilter extends AbstractFilter {
private final CacheSize cacheSize;
@@ -26,15 +27,15 @@ class SizeFilter extends AbstractFilter {
public static class Factory implements IFilterFactory {
@Override
- public IFilter[] getFilters() {
+ public List<IFilter> getFilters() {
final CacheSize[] cacheSizes = CacheSize.values();
- final ArrayList<SizeFilter> filters = new ArrayList<SizeFilter>();
+ final List<IFilter> filters = new LinkedList<IFilter>();
for (CacheSize cacheSize : cacheSizes) {
if (cacheSize != CacheSize.UNKNOWN) {
filters.add(new SizeFilter(cacheSize));
}
}
- return filters.toArray(new SizeFilter[filters.size()]);
+ return filters;
}
}
diff --git a/main/src/cgeo/geocaching/filter/StateFilter.java b/main/src/cgeo/geocaching/filter/StateFilter.java
index 0df47c1..ae3fac2 100644
--- a/main/src/cgeo/geocaching/filter/StateFilter.java
+++ b/main/src/cgeo/geocaching/filter/StateFilter.java
@@ -9,6 +9,7 @@ import android.content.res.Resources;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.List;
abstract class StateFilter extends AbstractFilter {
@@ -89,8 +90,8 @@ abstract class StateFilter extends AbstractFilter {
public static class Factory implements IFilterFactory {
@Override
- public IFilter[] getFilters() {
- final ArrayList<StateFilter> filters = new ArrayList<StateFilter>();
+ public List<StateFilter> getFilters() {
+ final List<StateFilter> filters = new ArrayList<StateFilter>(6);
filters.add(new StateFoundFilter());
filters.add(new StateArchivedFilter());
filters.add(new StateDisabledFilter());
@@ -106,7 +107,7 @@ abstract class StateFilter extends AbstractFilter {
}
});
- return filters.toArray(new StateFilter[filters.size()]);
+ return filters;
}
}
diff --git a/main/src/cgeo/geocaching/filter/TerrainFilter.java b/main/src/cgeo/geocaching/filter/TerrainFilter.java
index f7703d5..87372c6 100644
--- a/main/src/cgeo/geocaching/filter/TerrainFilter.java
+++ b/main/src/cgeo/geocaching/filter/TerrainFilter.java
@@ -1,10 +1,10 @@
package cgeo.geocaching.filter;
-
-import cgeo.geocaching.R;
import cgeo.geocaching.Geocache;
+import cgeo.geocaching.R;
import java.util.ArrayList;
+import java.util.List;
class TerrainFilter extends AbstractRangeFilter {
@@ -19,12 +19,12 @@ class TerrainFilter extends AbstractRangeFilter {
public static class Factory implements IFilterFactory {
@Override
- public IFilter[] getFilters() {
+ public List<IFilter> getFilters() {
final ArrayList<IFilter> filters = new ArrayList<IFilter>(5);
for (int terrain = 1; terrain <= 5; terrain++) {
filters.add(new TerrainFilter(terrain));
}
- return filters.toArray(new IFilter[filters.size()]);
+ return filters;
}
}
diff --git a/main/src/cgeo/geocaching/filter/TrackablesFilter.java b/main/src/cgeo/geocaching/filter/TrackablesFilter.java
index 3225daa..5eff8a7 100644
--- a/main/src/cgeo/geocaching/filter/TrackablesFilter.java
+++ b/main/src/cgeo/geocaching/filter/TrackablesFilter.java
@@ -1,9 +1,12 @@
package cgeo.geocaching.filter;
-import cgeo.geocaching.R;
import cgeo.geocaching.Geocache;
+import cgeo.geocaching.R;
import cgeo.geocaching.cgeoapplication;
+import java.util.Collections;
+import java.util.List;
+
class TrackablesFilter extends AbstractFilter implements IFilterFactory {
public TrackablesFilter() {
super(cgeoapplication.getInstance().getString(R.string.caches_filter_track));
@@ -15,8 +18,8 @@ class TrackablesFilter extends AbstractFilter implements IFilterFactory {
}
@Override
- public IFilter[] getFilters() {
- return new IFilter[] { this };
+ public List<TrackablesFilter> getFilters() {
+ return Collections.singletonList(this);
}
}
diff --git a/main/src/cgeo/geocaching/filter/TypeFilter.java b/main/src/cgeo/geocaching/filter/TypeFilter.java
index eeab552..ea0ccff 100644
--- a/main/src/cgeo/geocaching/filter/TypeFilter.java
+++ b/main/src/cgeo/geocaching/filter/TypeFilter.java
@@ -3,7 +3,8 @@ package cgeo.geocaching.filter;
import cgeo.geocaching.Geocache;
import cgeo.geocaching.enumerations.CacheType;
-import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
class TypeFilter extends AbstractFilter {
private final CacheType cacheType;
@@ -26,15 +27,15 @@ class TypeFilter extends AbstractFilter {
public static class Factory implements IFilterFactory {
@Override
- public IFilter[] getFilters() {
+ public List<IFilter> getFilters() {
final CacheType[] types = CacheType.values();
- final ArrayList<IFilter> filters = new ArrayList<IFilter>(types.length);
+ final List<IFilter> filters = new LinkedList<IFilter>();
for (CacheType cacheType : types) {
if (cacheType != CacheType.ALL) {
filters.add(new TypeFilter(cacheType));
}
}
- return filters.toArray(new IFilter[filters.size()]);
+ return filters;
}
}
diff --git a/main/src/cgeo/geocaching/geopoint/Geopoint.java b/main/src/cgeo/geocaching/geopoint/Geopoint.java
index c03a2bc..dbe0aa4 100644
--- a/main/src/cgeo/geocaching/geopoint/Geopoint.java
+++ b/main/src/cgeo/geocaching/geopoint/Geopoint.java
@@ -173,6 +173,16 @@ public final class Geopoint implements ICoordinates, Parcelable {
return longitude;
}
+ /*
+ * Return a waypoint which is the copy of this one rounded to the given limit.
+ * For example, to get a waypoint adapter to a display with 3 digits after the
+ * seconds decimal point, a rounding factor of 3600*1000 would be appropriate.
+ */
+ Geopoint roundedAt(final long factor) {
+ return new Geopoint(((double) Math.round(latitude * factor)) / factor,
+ ((double) Math.round(longitude * factor)) / factor);
+ }
+
/**
* Get longitude in microdegree.
*
diff --git a/main/src/cgeo/geocaching/geopoint/GeopointFormatter.java b/main/src/cgeo/geocaching/geopoint/GeopointFormatter.java
index ba0a4d5..67f5ebb 100644
--- a/main/src/cgeo/geocaching/geopoint/GeopointFormatter.java
+++ b/main/src/cgeo/geocaching/geopoint/GeopointFormatter.java
@@ -64,36 +64,51 @@ public class GeopointFormatter {
case LAT_LON_DECDEGREE_COMMA:
return String.format((Locale) null, "%.6f,%.6f", latSigned, lonSigned);
- case LAT_LON_DECMINUTE:
+ case LAT_LON_DECMINUTE: {
+ final Geopoint rgp = gp.roundedAt(60 * 1000);
return String.format(Locale.getDefault(), "%c %02d° %06.3f · %c %03d° %06.3f",
- gp.getLatDir(), gp.getLatDeg(), gp.getLatMinRaw(), gp.getLonDir(), gp.getLonDeg(), gp.getLonMinRaw());
+ rgp.getLatDir(), rgp.getLatDeg(), rgp.getLatMinRaw(), rgp.getLonDir(), rgp.getLonDeg(), rgp.getLonMinRaw());
+ }
- case LAT_LON_DECMINUTE_RAW:
+ case LAT_LON_DECMINUTE_RAW: {
+ final Geopoint rgp = gp.roundedAt(60 * 1000);
return String.format((Locale) null, "%c %02d° %06.3f %c %03d° %06.3f",
- gp.getLatDir(), gp.getLatDeg(), gp.getLatMinRaw(), gp.getLonDir(), gp.getLonDeg(), gp.getLonMinRaw());
+ rgp.getLatDir(), rgp.getLatDeg(), rgp.getLatMinRaw(), rgp.getLonDir(), rgp.getLonDeg(), rgp.getLonMinRaw());
+ }
- case LAT_LON_DECSECOND:
+ case LAT_LON_DECSECOND: {
+ final Geopoint rgp = gp.roundedAt(3600 * 1000);
return String.format(Locale.getDefault(), "%c %02d° %02d' %06.3f\" · %c %03d° %02d' %06.3f\"",
- gp.getLatDir(), gp.getLatDeg(), gp.getLatMin(), gp.getLatSecRaw(),
- gp.getLonDir(), gp.getLonDeg(), gp.getLonMin(), gp.getLonSecRaw());
+ rgp.getLatDir(), rgp.getLatDeg(), rgp.getLatMin(), rgp.getLatSecRaw(),
+ rgp.getLonDir(), rgp.getLonDeg(), rgp.getLonMin(), rgp.getLonSecRaw());
+ }
case LAT_DECDEGREE_RAW:
return String.format((Locale) null, "%.6f", latSigned);
- case LAT_DECMINUTE:
- return String.format(Locale.getDefault(), "%c %02d° %06.3f", gp.getLatDir(), gp.getLatDeg(), gp.getLatMinRaw());
+ case LAT_DECMINUTE: {
+ final Geopoint rgp = gp.roundedAt(60 * 1000);
+ return String.format(Locale.getDefault(), "%c %02d° %06.3f", rgp.getLatDir(), rgp.getLatDeg(), rgp.getLatMinRaw());
+ }
- case LAT_DECMINUTE_RAW:
- return String.format(Locale.getDefault(), "%c %02d %06.3f", gp.getLatDir(), gp.getLatDeg(), gp.getLatMinRaw());
+ case LAT_DECMINUTE_RAW: {
+ final Geopoint rgp = gp.roundedAt(60 * 1000);
+ return String.format(Locale.getDefault(), "%c %02d %06.3f", rgp.getLatDir(), rgp.getLatDeg(), rgp.getLatMinRaw());
+ }
case LON_DECDEGREE_RAW:
return String.format((Locale) null, "%.6f", lonSigned);
- case LON_DECMINUTE:
- return String.format(Locale.getDefault(), "%c %03d° %06.3f", gp.getLonDir(), gp.getLonDeg(), gp.getLonMinRaw());
+ case LON_DECMINUTE: {
+ final Geopoint rgp = gp.roundedAt(60 * 1000);
+ return String.format(Locale.getDefault(), "%c %03d° %06.3f", rgp.getLonDir(), rgp.getLonDeg(), rgp.getLonMinRaw());
+ }
+
+ case LON_DECMINUTE_RAW: {
+ final Geopoint rgp = gp.roundedAt(60 * 1000);
+ return String.format(Locale.getDefault(), "%c %03d %06.3f", rgp.getLonDir(), rgp.getLonDeg(), rgp.getLonMinRaw());
+ }
- case LON_DECMINUTE_RAW:
- return String.format(Locale.getDefault(), "%c %03d %06.3f", gp.getLonDir(), gp.getLonDeg(), gp.getLonMinRaw());
default:
throw new IllegalArgumentException();
}
diff --git a/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java b/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java
index 46912c5..ece5c2f 100644
--- a/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java
+++ b/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java
@@ -19,7 +19,8 @@ public abstract class AbstractSearchLoader extends AsyncTaskLoader<SearchResult>
USERNAME,
OWNER,
MAP,
- REMOVE_FROM_HISTORY;
+ REMOVE_FROM_HISTORY,
+ NEXT_PAGE;
}
private Handler recaptchaHandler = null;
diff --git a/main/src/cgeo/geocaching/maps/CGeoMap.java b/main/src/cgeo/geocaching/maps/CGeoMap.java
index f2e455c..21c4984 100644
--- a/main/src/cgeo/geocaching/maps/CGeoMap.java
+++ b/main/src/cgeo/geocaching/maps/CGeoMap.java
@@ -382,9 +382,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
if (extras != null) {
mapMode = (MapMode) extras.get(EXTRAS_MAP_MODE);
isLiveEnabled = extras.getBoolean(EXTRAS_LIVE_ENABLED, false);
- searchIntent = (SearchResult) extras.getParcelable(EXTRAS_SEARCH);
+ searchIntent = extras.getParcelable(EXTRAS_SEARCH);
geocodeIntent = extras.getString(EXTRAS_GEOCODE);
- coordsIntent = (Geopoint) extras.getParcelable(EXTRAS_COORDS);
+ coordsIntent = extras.getParcelable(EXTRAS_COORDS);
waypointTypeIntent = WaypointType.findById(extras.getString(EXTRAS_WPTTYPE));
mapStateIntent = extras.getIntArray(EXTRAS_MAPSTATE);
mapTitle = extras.getString(EXTRAS_MAP_TITLE);
@@ -750,9 +750,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto
@Override
public void onClick(DialogInterface dialog, int newItem) {
- if (newItem == selectedItem) {
- // no change
- } else {
+ if (newItem != selectedItem) {
// Adjust index because of <default> selection
if (newItem > 0) {
Settings.setCustomRenderThemeFile(themeFiles[newItem - 1].getPath());
diff --git a/main/src/cgeo/geocaching/twitter/Twitter.java b/main/src/cgeo/geocaching/twitter/Twitter.java
index d3b2718..e3d3f77 100644
--- a/main/src/cgeo/geocaching/twitter/Twitter.java
+++ b/main/src/cgeo/geocaching/twitter/Twitter.java
@@ -15,7 +15,10 @@ import cgeo.geocaching.utils.Log;
import ch.boye.httpclientandroidlib.HttpResponse;
+import org.apache.commons.lang3.StringUtils;
+
public final class Twitter {
+ private static final String HASH_PREFIX_WITH_BLANK = " #";
public static final int MAX_TWEET_SIZE = 140;
public static void postTweet(final cgeoapplication app, final String status, final Geopoint coords) {
@@ -47,12 +50,13 @@ public final class Twitter {
}
}
- public static String appendHashTag(final String status, final String tag) {
- String result = status;
- if (result.length() + 2 + tag.length() <= 140) {
- result += " #" + tag;
+ public static void appendHashTag(final StringBuilder status, final String tag) {
+ if (status.length() + HASH_PREFIX_WITH_BLANK.length() + tag.length() <= MAX_TWEET_SIZE) {
+ final String tagWithPrefix = HASH_PREFIX_WITH_BLANK + tag;
+ if (status.indexOf(tagWithPrefix, 0) == -1) {
+ status.append(tagWithPrefix);
+ }
}
- return result;
}
public static void postTweetCache(String geocode) {
@@ -63,39 +67,39 @@ public final class Twitter {
return;
}
final Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB);
- String status;
- final String url = cache.getUrl();
- if (url.length() >= 100) {
- status = "I found " + url;
- }
- else {
- String name = cache.getName();
- status = "I found " + name + " (" + url + ")";
- if (status.length() > MAX_TWEET_SIZE) {
- name = name.substring(0, name.length() - (status.length() - MAX_TWEET_SIZE) - 1) + '…';
+ postTweet(cgeoapplication.getInstance(), getStatusMessage(cache), null);
+ }
+
+ static String getStatusMessage(Geocache cache) {
+ String name = cache.getName();
+ if (name.length() > 100) {
+ name = name.substring(0, 100) + '…';
}
- status = "I found " + name + " (" + url + ")";
- status = appendHashTag(status, "cgeo");
- status = appendHashTag(status, "geocaching");
- }
+ final String url = StringUtils.defaultString(cache.getUrl());
+ return fillTemplate(Settings.getCacheTwitterMessage(), name, url);
+ }
- postTweet(cgeoapplication.getInstance(), status, null);
+ private static String fillTemplate(String template, String name, final String url) {
+ String result = StringUtils.replace(template, "[NAME]", name);
+ result = StringUtils.replace(result, "[URL]", url);
+ StringBuilder builder = new StringBuilder(result);
+ appendHashTag(builder, "cgeo");
+ appendHashTag(builder, "geocaching");
+ return builder.toString();
}
public static void postTweetTrackable(String geocode) {
final Trackable trackable = cgData.loadTrackable(geocode);
+ postTweet(cgeoapplication.getInstance(), getStatusMessage(trackable), null);
+ }
+
+ static String getStatusMessage(Trackable trackable) {
String name = trackable.getName();
if (name.length() > 82) {
name = name.substring(0, 81) + '…';
}
- StringBuilder builder = new StringBuilder("I touched ");
- builder.append(name);
- if (trackable.getUrl() != null) {
- builder.append(" (").append(trackable.getUrl()).append(')');
- }
- builder.append('!');
- String status = appendHashTag(builder.toString(), "cgeo");
- status = appendHashTag(status, "geocaching");
- postTweet(cgeoapplication.getInstance(), status, null);
+ String url = StringUtils.defaultString(trackable.getUrl());
+ String status = Settings.getTrackableTwitterMessage();
+ return fillTemplate(status, name, url);
}
}
diff --git a/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java b/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java
index 4ba88ae..f10e13a 100644
--- a/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java
+++ b/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java
@@ -16,6 +16,12 @@ public class DecryptTextClickListener implements View.OnClickListener {
try {
final TextView logView = (TextView) view;
+
+ // do not run the click listener if a link was clicked
+ if (logView.getSelectionStart() != -1 || logView.getSelectionEnd() != -1) {
+ return;
+ }
+
CharSequence text = logView.getText();
if (text instanceof Spannable) {
Spannable span = (Spannable) text;
diff --git a/main/src/cgeo/geocaching/ui/EditNoteDialog.java b/main/src/cgeo/geocaching/ui/EditNoteDialog.java
new file mode 100644
index 0000000..23e57f2
--- /dev/null
+++ b/main/src/cgeo/geocaching/ui/EditNoteDialog.java
@@ -0,0 +1,65 @@
+package cgeo.geocaching.ui;
+
+import cgeo.geocaching.R;
+import cgeo.geocaching.R.string;
+
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.EditorInfo;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+public class EditNoteDialog extends DialogFragment implements OnEditorActionListener {
+
+ public interface EditNoteDialogListener {
+ void onFinishEditNoteDialog(final String inputText);
+ }
+
+ private EditText mEditText;
+ private String initialNote;
+
+ public EditNoteDialog() {
+ // Empty constructor required for DialogFragment
+ }
+
+ public EditNoteDialog(final String initialNote) {
+ this.initialNote = initialNote;
+ }
+
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_edit_note, container);
+ mEditText = (EditText) view.findViewById(R.id.note);
+ if (initialNote != null) {
+ mEditText.setText(initialNote);
+ initialNote = null;
+ }
+ getDialog().setTitle(string.cache_personal_note);
+ mEditText.requestFocus();
+ getDialog().getWindow().setSoftInputMode(
+ LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+ mEditText.setOnEditorActionListener(this);
+
+ return view;
+ }
+
+ @Override
+ public boolean onEditorAction(final TextView v, final int actionId, final KeyEvent event) {
+ if (EditorInfo.IME_ACTION_DONE == actionId) {
+ final EditNoteDialogListener activity = (EditNoteDialogListener) getActivity();
+ activity.onFinishEditNoteDialog(mEditText.getText().toString());
+ dismiss();
+ return true;
+ }
+ return false;
+ }
+
+
+}
diff --git a/main/src/cgeo/geocaching/ui/dialog/EditorDialog.java b/main/src/cgeo/geocaching/ui/dialog/EditorDialog.java
deleted file mode 100644
index 4db69e5..0000000
--- a/main/src/cgeo/geocaching/ui/dialog/EditorDialog.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package cgeo.geocaching.ui.dialog;
-
-import cgeo.geocaching.CacheDetailActivity;
-import cgeo.geocaching.R;
-import cgeo.geocaching.activity.ActivityMixin;
-
-import android.app.Dialog;
-import android.os.Bundle;
-import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.view.Window;
-import android.widget.Button;
-import android.widget.EditText;
-
-public class EditorDialog extends Dialog {
-
- private CharSequence editorText;
- private EditorUpdate editorUpdate;
-
- public EditorDialog(CacheDetailActivity cacheDetailActivity, CharSequence editable) {
- super(cacheDetailActivity, ActivityMixin.getTheme());
- this.editorText = editable;
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- setContentView(R.layout.editor);
-
- final EditText editText = (EditText) findViewById(R.id.editorEditText);
- editText.setText(editorText);
-
- final Button buttonSave = (Button) findViewById(R.id.editorSave);
- buttonSave.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- editorUpdate.update(editText.getEditableText());
- EditorDialog.this.hide();
- }
- });
- }
-
- public interface EditorUpdate {
- public void update(CharSequence editorText);
- }
-
- public void setOnEditorUpdate(EditorUpdate editorUpdate) {
- this.editorUpdate = editorUpdate;
-
- }
-
- @Override
- public void show() {
- super.show();
- getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
- }
-
-}
diff --git a/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java b/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java
index b23fa9d..7526d92 100644
--- a/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java
+++ b/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java
@@ -8,8 +8,12 @@ import android.os.AsyncTask;
/**
* AsyncTask which automatically shows a progress dialog. Use it like the {@code AsyncTask} class, but leave away the
- * middle template parameter.
- *
+ * middle template parameter. Override {@link #doInBackgroundInternal(Object[])} and related methods.
+ * <p>
+ * If no style is given, the progress dialog uses "determinate" style with known maximum. The progress maximum is
+ * automatically derived from the number of {@code Params} given to the task in {@link #execute(Object...)}.
+ * </p>
+ *
* @param <Params>
* @param <Result>
*/
@@ -17,64 +21,119 @@ public abstract class AsyncTaskWithProgress<Params, Result> extends AsyncTask<Pa
private final Progress progress = new Progress();
private final Activity activity;
- private final int maxProgress;
private final String progressTitle;
private final String progressMessage;
+ private boolean indeterminate = false;
/**
- * Creates an AsyncTask with progress dialog, where the maximum is set to the given maxProgress.
- *
+ * Creates an AsyncTask with progress dialog.
+ *
* @param activity
- * @param maxProgress
* @param progressTitle
* @param progressMessage
*/
- public AsyncTaskWithProgress(final Activity activity, final int maxProgress, final String progressTitle, final String progressMessage) {
+ public AsyncTaskWithProgress(final Activity activity, final String progressTitle, final String progressMessage) {
+ this(activity, progressTitle, progressMessage, false);
+ }
+
+ /**
+ * Creates an AsyncTask with progress dialog.
+ *
+ * @param activity
+ * @param progressTitle
+ */
+ public AsyncTaskWithProgress(final Activity activity, final String progressTitle) {
+ this(activity, progressTitle, null);
+ }
+
+ /**
+ * Creates an AsyncTask with progress dialog.
+ *
+ * @param activity
+ * @param progressTitle
+ * @param progressMessage
+ */
+ public AsyncTaskWithProgress(final Activity activity, final String progressTitle, final String progressMessage, boolean indeterminate) {
this.activity = activity;
- this.maxProgress = maxProgress;
this.progressTitle = progressTitle;
this.progressMessage = progressMessage;
+ this.indeterminate = indeterminate;
}
/**
- * Creates an AsyncTask with progress dialog, where the maximum is set to indeterminate.
- *
+ * Creates an AsyncTask with progress dialog.
+ *
* @param activity
* @param progressTitle
- * @param progressMessage
*/
- public AsyncTaskWithProgress(final Activity activity, final String progressTitle, final String progressMessage) {
- this(activity, 0, progressTitle, progressMessage);
+ public AsyncTaskWithProgress(final Activity activity, final String progressTitle, boolean indeterminate) {
+ this(activity, progressTitle, null, indeterminate);
}
@Override
- protected void onPreExecute() {
+ protected final void onPreExecute() {
if (null != activity) {
- if (maxProgress <= 0) {
+ if (indeterminate) {
progress.show(activity, progressTitle, progressMessage, true, null);
}
else {
progress.show(activity, progressTitle, progressMessage, ProgressDialog.STYLE_HORIZONTAL, null);
}
- progress.setMaxProgressAndReset(maxProgress);
}
+ onPreExecuteInternal();
+ }
+
+ /**
+ * This method should typically be overridden by sub classes instead of {@link #onPreExecute()}.
+ */
+ protected void onPreExecuteInternal() {
+ // empty by default
}
@Override
- protected void onPostExecute(Result result) {
+ protected final void onPostExecute(Result result) {
+ onPostExecuteInternal(result);
if (null != activity) {
progress.dismiss();
}
}
+ /**
+ * This method should typically be overridden by sub classes instead of {@link #onPostExecute(Object)}.
+ *
+ * @param result
+ */
+ protected void onPostExecuteInternal(Result result) {
+ // empty by default
+ }
+
@Override
- protected void onProgressUpdate(Integer... status) {
- if (null != activity) {
- progress.setProgress(status[0]);
+ protected final void onProgressUpdate(Integer... status) {
+ final int progressValue = status[0];
+ if (null != activity && progressValue >= 0) {
+ progress.setProgress(progressValue);
}
+ onProgressUpdateInternal(progressValue);
+ }
+
+ /**
+ * This method should by overridden by sub classes instead of {@link #onProgressUpdate(Integer...)}.
+ */
+ protected void onProgressUpdateInternal(@SuppressWarnings("unused") int progress) {
+ // empty by default
}
protected void setMessage(final String message) {
progress.setMessage(message);
}
+
+ @Override
+ protected final Result doInBackground(Params... params) {
+ if (params != null) {
+ progress.setMaxProgressAndReset(params.length);
+ }
+ return doInBackgroundInternal(params);
+ }
+
+ protected abstract Result doInBackgroundInternal(Params[] params);
}
diff --git a/tests/src/cgeo/geocaching/connector/gc/GCParserTest.java b/tests/src/cgeo/geocaching/connector/gc/GCParserTest.java
index cf1df46..ff7ddff 100644
--- a/tests/src/cgeo/geocaching/connector/gc/GCParserTest.java
+++ b/tests/src/cgeo/geocaching/connector/gc/GCParserTest.java
@@ -7,6 +7,7 @@ import cgeo.geocaching.Settings;
import cgeo.geocaching.Waypoint;
import cgeo.geocaching.enumerations.LoadFlags;
import cgeo.geocaching.enumerations.StatusCode;
+import cgeo.geocaching.enumerations.WaypointType;
import cgeo.geocaching.geopoint.Geopoint;
import cgeo.geocaching.test.AbstractResourceInstrumentationTestCase;
import cgeo.geocaching.test.R;
@@ -22,6 +23,7 @@ import android.os.Handler;
import android.test.suitebuilder.annotation.MediumTest;
import java.util.ArrayList;
+import java.util.List;
public class GCParserTest extends AbstractResourceInstrumentationTestCase {
@@ -187,6 +189,22 @@ public class GCParserTest extends AbstractResourceInstrumentationTestCase {
assertEquals(13, cache.getWaypoints().size());
}
+ public static void testNoteParsingWaypointTypes() {
+ final Geocache cache = new Geocache();
+ cache.setWaypoints(new ArrayList<Waypoint>(), false);
+ cache.setPersonalNote("\"Parking area at PARKING=N 50° 40.666E 006° 58.222\n" +
+ "My calculated final coordinates: FINAL=N 50° 40.777E 006° 58.111\n" +
+ "Get some ice cream at N 50° 40.555E 006° 58.000\"");
+
+ cache.parseWaypointsFromNote();
+ final List<Waypoint> waypoints = cache.getWaypoints();
+
+ assertEquals(3, waypoints.size());
+ assertEquals(WaypointType.PARKING, waypoints.get(0).getWaypointType());
+ assertEquals(WaypointType.FINAL, waypoints.get(1).getWaypointType());
+ assertEquals(WaypointType.WAYPOINT, waypoints.get(2).getWaypointType());
+ }
+
private Geocache parseCache(int resourceId) {
final String page = getFileContent(resourceId);
final SearchResult result = GCParser.parseCacheFromText(page, null);
diff --git a/tests/src/cgeo/geocaching/connector/oc/OCXMLTest.java b/tests/src/cgeo/geocaching/connector/oc/OCXMLTest.java
index b12823a..3563991 100644
--- a/tests/src/cgeo/geocaching/connector/oc/OCXMLTest.java
+++ b/tests/src/cgeo/geocaching/connector/oc/OCXMLTest.java
@@ -111,13 +111,13 @@ public class OCXMLTest extends CGeoTestCase {
}
public static void testRemoveMarkup() {
- assertEquals("", OC11XMLParser.stripMarkup(""));
- assertEquals("Test", OC11XMLParser.stripMarkup("Test"));
- assertEquals("<b>bold and others not removed</b>", OC11XMLParser.stripMarkup("<b>bold and others not removed</b>"));
- assertEquals("unnecessary paragraph", OC11XMLParser.stripMarkup("<p>unnecessary paragraph</p>"));
- assertEquals("unnecessary span", OC11XMLParser.stripMarkup("<span>unnecessary span</span>"));
- assertEquals("nested", OC11XMLParser.stripMarkup("<span><span>nested</span></span>"));
- assertEquals("mixed", OC11XMLParser.stripMarkup("<span> <p> mixed </p> </span>"));
- assertEquals("<p>not</p><p>removable</p>", OC11XMLParser.stripMarkup("<p>not</p><p>removable</p>"));
+ assertEquals("", OC11XMLParser.stripEmptyText(""));
+ assertEquals("Test", OC11XMLParser.stripEmptyText("Test"));
+ assertEquals("<b>bold and others not removed</b>", OC11XMLParser.stripEmptyText("<b>bold and others not removed</b>"));
+ assertEquals("unnecessary paragraph", OC11XMLParser.stripEmptyText("<p>unnecessary paragraph</p>"));
+ assertEquals("unnecessary span", OC11XMLParser.stripEmptyText("<span>unnecessary span</span>"));
+ assertEquals("nested", OC11XMLParser.stripEmptyText("<span><span>nested</span></span>"));
+ assertEquals("mixed", OC11XMLParser.stripEmptyText("<span> <p> mixed </p> </span>"));
+ assertEquals("<p>not</p><p>removable</p>", OC11XMLParser.stripEmptyText("<p>not</p><p>removable</p>"));
}
}
diff --git a/tests/src/cgeo/geocaching/filter/DifficultyFilterTest.java b/tests/src/cgeo/geocaching/filter/DifficultyFilterTest.java
index b3ff056..ebe4cf6 100644
--- a/tests/src/cgeo/geocaching/filter/DifficultyFilterTest.java
+++ b/tests/src/cgeo/geocaching/filter/DifficultyFilterTest.java
@@ -19,6 +19,6 @@ public class DifficultyFilterTest extends CGeoTestCase {
}
public static void testAllFilters() {
- assertTrue(new DifficultyFilter.Factory().getFilters().length == 5); // difficulty ranges from 1 to 5
+ assertTrue(new DifficultyFilter.Factory().getFilters().size() == 5); // difficulty ranges from 1 to 5
}
}
diff --git a/tests/src/cgeo/geocaching/filter/SizeFilterTest.java b/tests/src/cgeo/geocaching/filter/SizeFilterTest.java
index 2c6552b..8b909f3 100644
--- a/tests/src/cgeo/geocaching/filter/SizeFilterTest.java
+++ b/tests/src/cgeo/geocaching/filter/SizeFilterTest.java
@@ -33,7 +33,7 @@ public class SizeFilterTest extends CGeoTestCase {
public static void testGetAllFilters() {
final int expectedSizes = CacheSize.values().length - 1; // hide "UNKNOWN"
- assertEquals(expectedSizes, new SizeFilter.Factory().getFilters().length);
+ assertEquals(expectedSizes, new SizeFilter.Factory().getFilters().size());
}
public void testFilter() {
diff --git a/tests/src/cgeo/geocaching/filter/TerrainFilterTest.java b/tests/src/cgeo/geocaching/filter/TerrainFilterTest.java
index d64f4d9..234af7e 100644
--- a/tests/src/cgeo/geocaching/filter/TerrainFilterTest.java
+++ b/tests/src/cgeo/geocaching/filter/TerrainFilterTest.java
@@ -19,6 +19,6 @@ public class TerrainFilterTest extends CGeoTestCase {
}
public static void testAllFilters() {
- assertTrue(new TerrainFilter.Factory().getFilters().length == 5); // terrain ranges from 1 to 5
+ assertTrue(new TerrainFilter.Factory().getFilters().size() == 5); // terrain ranges from 1 to 5
}
}
diff --git a/tests/src/cgeo/geocaching/filter/TypeFilterTest.java b/tests/src/cgeo/geocaching/filter/TypeFilterTest.java
index 3a43b33..e813052 100644
--- a/tests/src/cgeo/geocaching/filter/TypeFilterTest.java
+++ b/tests/src/cgeo/geocaching/filter/TypeFilterTest.java
@@ -46,7 +46,7 @@ public class TypeFilterTest extends CGeoTestCase {
public static void testGetAllFilters() {
final int expectedEntries = CacheType.values().length - 1; // hide "all"
- assertEquals(expectedEntries, new TypeFilter.Factory().getFilters().length);
+ assertEquals(expectedEntries, new TypeFilter.Factory().getFilters().size());
}
}
diff --git a/tests/src/cgeo/geocaching/geopoint/GeoPointFormatterTest.java b/tests/src/cgeo/geocaching/geopoint/GeoPointFormatterTest.java
index c0cea01..a1be25d 100644
--- a/tests/src/cgeo/geocaching/geopoint/GeoPointFormatterTest.java
+++ b/tests/src/cgeo/geocaching/geopoint/GeoPointFormatterTest.java
@@ -6,6 +6,17 @@ import android.test.AndroidTestCase;
public class GeoPointFormatterTest extends AndroidTestCase {
+ public static void testConfluence() {
+ // From issue #2624: coordinate is wrong near to a confluence point
+ final Geopoint point = new Geopoint(49.9999999999999, 5.0);
+ final String format = GeopointFormatter.format(GeopointFormatter.Format.LAT_LON_DECDEGREE_COMMA, point);
+ assertEquals("50.000000,5.000000", format);
+ final String formatMinute = GeopointFormatter.format(GeopointFormatter.Format.LAT_LON_DECMINUTE_RAW, point);
+ assertEquals("N 50° 00.000 E 005° 00.000", formatMinute);
+ final String formatSecond = GeopointFormatter.format(GeopointFormatter.Format.LAT_LON_DECSECOND, point).replaceAll(",", ".");
+ assertEquals(formatSecond, "N 50° 00' 00.000\"" + Formatter.SEPARATOR + "E 005° 00' 00.000\"", formatSecond);
+ }
+
public static void testFormat() {
// taken from GC30R6G
final Geopoint point = new Geopoint("N 51° 21.104 E 010° 15.369");
diff --git a/tests/src/cgeo/geocaching/twitter/TwitterTest.java b/tests/src/cgeo/geocaching/twitter/TwitterTest.java
new file mode 100644
index 0000000..a27e57a
--- /dev/null
+++ b/tests/src/cgeo/geocaching/twitter/TwitterTest.java
@@ -0,0 +1,37 @@
+package cgeo.geocaching.twitter;
+
+import cgeo.geocaching.Geocache;
+import cgeo.geocaching.Settings;
+import cgeo.geocaching.Trackable;
+
+import junit.framework.TestCase;
+
+public class TwitterTest extends TestCase {
+
+ public static void testTrackableMessage() {
+ Trackable tb = new Trackable();
+ tb.setName("Travel bug");
+ tb.setGeocode("TB1234");
+ assertEquals("I touched Travel bug (http://www.geocaching.com//track/details.aspx?tracker=TB1234)! #cgeo #geocaching", Twitter.getStatusMessage(tb));
+ }
+
+ public static void testCacheMessage() {
+ Geocache cache = new Geocache();
+ cache.setGeocode("GC1234");
+ cache.setName("TwitterTest");
+ assertEquals("I found TwitterTest (http://coord.info/GC1234) #cgeo #geocaching", Twitter.getStatusMessage(cache));
+ }
+
+ public static void testAvoidDuplicateTags() {
+ String oldMessage = Settings.getCacheTwitterMessage();
+ try {
+ Geocache cache = new Geocache();
+ cache.setGeocode("GC1234");
+ cache.setName("TwitterTest");
+ Settings.setCacheTwitterMessage("[NAME] #cgeo");
+ assertEquals("TwitterTest #cgeo #geocaching", Twitter.getStatusMessage(cache));
+ } finally {
+ Settings.setCacheTwitterMessage(oldMessage);
+ }
+ }
+}