diff options
Diffstat (limited to 'main/src')
43 files changed, 1226 insertions, 976 deletions
diff --git a/main/src/cgeo/geocaching/AbstractPopupActivity.java b/main/src/cgeo/geocaching/AbstractPopupActivity.java index f903d00..44acfc5 100644 --- a/main/src/cgeo/geocaching/AbstractPopupActivity.java +++ b/main/src/cgeo/geocaching/AbstractPopupActivity.java @@ -67,12 +67,22 @@ public abstract class AbstractPopupActivity extends AbstractActivity { cacheDistance.setText(Units.getDistanceFromKilometers(geo.getCoords().distanceTo(cache.getCoords()))); cacheDistance.bringToFront(); } + onUpdateGeoData(geo); } catch (Exception e) { Log.w("Failed to UpdateLocation location."); } } }; + /** + * Callback to run when new location information is available. + * This may be overriden by deriving classes. The default implementation does nothing. + * + * @param geo the new data + */ + public void onUpdateGeoData(final IGeoData geo) { + } + protected AbstractPopupActivity(String helpTopic, int layout) { super(helpTopic); this.layout = layout; diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java index 43ce65f..c4287ed 100644 --- a/main/src/cgeo/geocaching/CacheDetailActivity.java +++ b/main/src/cgeo/geocaching/CacheDetailActivity.java @@ -21,13 +21,14 @@ import cgeo.geocaching.network.Parameters; import cgeo.geocaching.ui.AbstractCachingPageViewCreator; 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; @@ -65,6 +66,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; @@ -112,7 +114,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; @@ -139,6 +142,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) { @@ -899,7 +904,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc return; } imagesList = new ImagesList(this, cache.getGeocode()); - imagesList.loadImages(imageView, cache.getImages(), ImageType.AllImages, false); + imagesList.loadImages(imageView, cache.getImages(), false); } public static void startActivity(final Context context, final String geocode) { @@ -1202,23 +1207,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // cache coordinates if (cache.getCoords() != null) { TextView valueView = details.add(R.string.cache_coordinates, cache.getCoords().toString()); - valueView.setOnClickListener(new View.OnClickListener() { - private int position = 0; - private GeopointFormatter.Format[] availableFormats = new GeopointFormatter.Format[] { - GeopointFormatter.Format.LAT_LON_DECMINUTE, - GeopointFormatter.Format.LAT_LON_DECSECOND, - GeopointFormatter.Format.LAT_LON_DECDEGREE - }; - - // rotate coordinate formats on click - @Override - public void onClick(View view) { - position = (position + 1) % availableFormats.length; - - final TextView valueView = (TextView) view.findViewById(R.id.value); - valueView.setText(cache.getCoords().format(availableFormats[position])); - } - }); + valueView.setOnClickListener(new CoordinatesFormatSwitcher(cache.getCoords())); registerForContextMenu(valueView); } @@ -1794,16 +1783,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 { @@ -1866,13 +1855,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); @@ -1884,6 +1870,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; @@ -2095,7 +2086,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 { @@ -2206,6 +2197,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // coordinates if (null != wpt.getCoords()) { final TextView coordinatesView = (TextView) waypointView.findViewById(R.id.coordinates); + coordinatesView.setOnClickListener(new CoordinatesFormatSwitcher(wpt.getCoords())); coordinatesView.setText(wpt.getCoords().toString()); coordinatesView.setVisibility(View.VISIBLE); } diff --git a/main/src/cgeo/geocaching/Geocache.java b/main/src/cgeo/geocaching/Geocache.java index 836cccb..000435e 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())) { @@ -1378,7 +1379,8 @@ public class Geocache implements ICache, IWaypoint { ((point.getLatitudeE6() % 1000) != 0 || (point.getLongitudeE6() % 1000) != 0) && !hasIdenticalWaypoint(point)) { 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++; @@ -1395,6 +1397,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; + } + private boolean hasIdenticalWaypoint(final Geopoint point) { for (final Waypoint waypoint: waypoints) { if (waypoint.getCoords().equals(point)) { diff --git a/main/src/cgeo/geocaching/ImagesActivity.java b/main/src/cgeo/geocaching/ImagesActivity.java index 24f699e..07ff734 100644 --- a/main/src/cgeo/geocaching/ImagesActivity.java +++ b/main/src/cgeo/geocaching/ImagesActivity.java @@ -19,10 +19,6 @@ import java.util.List; public class ImagesActivity extends AbstractActivity { - private static final String EXTRAS_IMAGES = "images"; - private static final String EXTRAS_TYPE = "type"; - private static final String EXTRAS_GEOCODE = "geocode"; - private boolean offline; private ArrayList<Image> imageNames; private ImagesList imagesList; @@ -37,8 +33,8 @@ public class ImagesActivity extends AbstractActivity { String geocode = null; if (extras != null) { - geocode = extras.getString(EXTRAS_GEOCODE); - imgType = (ImageType) extras.getSerializable(EXTRAS_TYPE); + geocode = extras.getString(Intents.EXTRA_GEOCODE); + imgType = (ImageType) extras.getSerializable(Intents.EXTRA_TYPE); } if (extras == null || geocode == null) { @@ -54,7 +50,7 @@ public class ImagesActivity extends AbstractActivity { imagesList = new ImagesList(this, geocode); - imageNames = extras.getParcelableArrayList(EXTRAS_IMAGES); + imageNames = extras.getParcelableArrayList(Intents.EXTRA_IMAGES); if (CollectionUtils.isEmpty(imageNames)) { showToast(res.getString(R.string.warn_load_images)); finish(); @@ -67,7 +63,7 @@ public class ImagesActivity extends AbstractActivity { @Override public void onStart() { super.onStart(); - imagesList.loadImages(findViewById(R.id.spoiler_list), imageNames, imgType, offline); + imagesList.loadImages(findViewById(R.id.spoiler_list), imageNames, offline); } @Override @@ -85,12 +81,12 @@ public class ImagesActivity extends AbstractActivity { final Intent logImgIntent = new Intent(fromActivity, ImagesActivity.class); // if resuming our app within this activity, finish it and return to the cache activity logImgIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) - .putExtra(EXTRAS_GEOCODE, geocode) - .putExtra(EXTRAS_TYPE, imageType); + .putExtra(Intents.EXTRA_GEOCODE, geocode) + .putExtra(Intents.EXTRA_TYPE, imageType); // avoid forcing the array list as parameter type final ArrayList<Image> arrayList = new ArrayList<Image>(logImages); - logImgIntent.putParcelableArrayListExtra(EXTRAS_IMAGES, arrayList); + logImgIntent.putParcelableArrayListExtra(Intents.EXTRA_IMAGES, arrayList); fromActivity.startActivity(logImgIntent); } diff --git a/main/src/cgeo/geocaching/Intents.java b/main/src/cgeo/geocaching/Intents.java index 7f0a004..a700451 100644 --- a/main/src/cgeo/geocaching/Intents.java +++ b/main/src/cgeo/geocaching/Intents.java @@ -9,10 +9,11 @@ public class Intents { private static final String PREFIX = "cgeo.geocaching.intent.extra."; public static final String EXTRA_ADDRESS = PREFIX + "address"; - public static final String EXTRAS_COORDS = PREFIX + "coords"; + public static final String EXTRA_COORDS = PREFIX + "coords"; public static final String EXTRA_COUNT = PREFIX + "count"; public static final String EXTRA_GEOCODE = PREFIX + "geocode"; public static final String EXTRA_GUID = PREFIX + "guid"; + public static final String EXTRA_IMAGES = PREFIX + "images"; public static final String EXTRA_ID = PREFIX + "id"; public static final String EXTRA_KEYWORD = PREFIX + "keyword"; public static final String EXTRA_KEYWORD_SEARCH = PREFIX + "keyword_search"; @@ -23,6 +24,7 @@ public class Intents { public static final String EXTRA_SEARCH = PREFIX + "search"; public static final String EXTRA_START_DIR = PREFIX + "start_dir"; public static final String EXTRA_TRACKING_CODE = PREFIX + "tracking_code"; + public static final String EXTRA_TYPE = PREFIX + "type"; public static final String EXTRA_USERNAME = PREFIX + "username"; public static final String EXTRA_WAYPOINT_ID = PREFIX + "waypoint_id"; public static final String EXTRA_CACHELIST = PREFIX + "cache_list"; 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 cd88071..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; @@ -81,10 +80,6 @@ public class StaticMapsProvider { } 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; } @@ -96,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); } @@ -167,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(); @@ -183,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, @@ -245,7 +231,7 @@ public class StaticMapsProvider { /** * Check if at least one map file exists for the given cache. - * + * * @param cache * @return <code>true</code> if at least one map file exists; <code>false</code> otherwise */ @@ -268,7 +254,7 @@ public class StaticMapsProvider { /** * Checks if at least one map file exists for the given geocode and waypoint ID. - * + * * @param geocode * @param waypoint * @return <code>true</code> if at least one map file exists; <code>false</code> otherwise @@ -287,7 +273,7 @@ public class StaticMapsProvider { /** * Checks if all map files exist for the given geocode and waypoint ID. - * + * * @param geocode * @param waypoint * @return <code>true</code> if all map files exist; <code>false</code> otherwise @@ -326,5 +312,4 @@ public class StaticMapsProvider { } return null; } - } diff --git a/main/src/cgeo/geocaching/VisitCacheActivity.java b/main/src/cgeo/geocaching/VisitCacheActivity.java index d95f6df..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,81 +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 class Poster extends AsyncTaskWithProgress<String, StatusCode> { - private final Handler handler; - private final String log; - - 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; - - try { - - final ImmutablePair<StatusCode, String> logResult = GCParser.postLog(geocode, cacheid, viewstates, typeSelected, - date.get(Calendar.YEAR), (date.get(Calendar.MONTH) + 1), date.get(Calendar.DATE), - log, trackables); - - result = logResult.left; - - if (logResult.left == StatusCode.NO_ERROR) { - final LogEntry logNow = new LogEntry(date, typeSelected, log); - - cache.getLogs().add(0, logNow); - - if (typeSelected == LogType.FOUND_IT || typeSelected == LogType.ATTENDED) { - cache.setFound(true); + 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); + + if (postResult.left == StatusCode.NO_ERROR) { + final LogEntry logNow = new LogEntry(date, typeSelected, log); + + cache.getLogs().add(0, logNow); + + 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 (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; + } } - cgData.saveChangedCache(cache); + return postResult.left; + } catch (Exception e) { + Log.e("cgeovisit.postLogFn", e); } - if (logResult.left == StatusCode.NO_ERROR) { - cgData.clearLogOffline(geocode); - } - - if (logResult.left == StatusCode.NO_ERROR && typeSelected == LogType.FOUND_IT && Settings.isUseTwitter() - && Settings.isTwitterLoginValid() - && tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE) { - Twitter.postTweetCache(geocode); - } - - if (logResult.left == StatusCode.NO_ERROR && typeSelected == LogType.FOUND_IT && Settings.isGCvoteLogin()) { - GCVote.setRating(cache, rating); - } + return StatusCode.LOG_POST_ERROR; + } - if (logResult.left == StatusCode.NO_ERROR && StringUtils.isNotBlank(imageUri.getPath())) { - result = GCParser.uploadLogImage(logResult.right, imageCaption, imageDescription, imageUri); + @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)); } - - return result; - } catch (Exception e) { - Log.e("cgeovisit.postLogFn", e); } - - return StatusCode.LOG_POST_ERROR; } 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/WaypointPopup.java b/main/src/cgeo/geocaching/WaypointPopup.java index 766d43d..43a758f 100644 --- a/main/src/cgeo/geocaching/WaypointPopup.java +++ b/main/src/cgeo/geocaching/WaypointPopup.java @@ -2,6 +2,7 @@ package cgeo.geocaching; import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.Units; import cgeo.geocaching.ui.CacheDetailsCreator; import cgeo.geocaching.utils.Log; @@ -19,6 +20,7 @@ import android.widget.TextView; public class WaypointPopup extends AbstractPopupActivity { private int waypointId = 0; private Waypoint waypoint = null; + private TextView waypointDistance = null; public WaypointPopup() { super("c:geo-waypoint-info", R.layout.waypoint_popup); @@ -35,6 +37,14 @@ public class WaypointPopup extends AbstractPopupActivity { } @Override + public void onUpdateGeoData(IGeoData geo) { + if (geo.getCoords() != null && waypoint != null && waypoint.getCoords() != null) { + waypointDistance.setText(Units.getDistanceFromKilometers(geo.getCoords().distanceTo(waypoint.getCoords()))); + waypointDistance.bringToFront(); + } + } + + @Override protected void init() { super.init(); waypoint = cgData.loadWaypoint(waypointId); @@ -53,6 +63,9 @@ public class WaypointPopup extends AbstractPopupActivity { //Waypoint geocode details.add(R.string.cache_geocode, waypoint.getPrefix() + waypoint.getGeocode().substring(2)); + details.addDistance(waypoint, waypointDistance); + waypointDistance = details.getValueView(); + details.add(R.string.waypoint_note, waypoint.getNote()); // Edit Button final Button buttonEdit = (Button) findViewById(R.id.edit); 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 61a32f1..30a18c5 100644 --- a/main/src/cgeo/geocaching/cgeocaches.java +++ b/main/src/cgeo/geocaching/cgeocaches.java @@ -39,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; @@ -74,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; @@ -373,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 @@ -428,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.EXTRA_COORDS); } else { extras = new Bundle(); @@ -794,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()) { @@ -804,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() { @@ -1144,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(); } @@ -1177,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(); } }); @@ -1196,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()])); } /** @@ -1383,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 { @@ -1445,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; } @@ -1544,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)); @@ -1667,7 +1661,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } final Intent cachesIntent = new Intent(context, cgeocaches.class); cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.NEAREST); - cachesIntent.putExtra(Intents.EXTRAS_COORDS, coordsNow); + cachesIntent.putExtra(Intents.EXTRA_COORDS, coordsNow); context.startActivity(cachesIntent); } @@ -1680,7 +1674,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity public static void startActivityAddress(final Context context, final Geopoint coords, final String address) { final Intent addressIntent = new Intent(context, cgeocaches.class); addressIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.ADDRESS); - addressIntent.putExtra(Intents.EXTRAS_COORDS, coords); + addressIntent.putExtra(Intents.EXTRA_COORDS, coords); addressIntent.putExtra(Intents.EXTRA_ADDRESS, address); context.startActivity(addressIntent); } @@ -1691,7 +1685,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } final Intent cachesIntent = new Intent(context, cgeocaches.class); cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.COORDINATE); - cachesIntent.putExtra(Intents.EXTRAS_COORDS, coords); + cachesIntent.putExtra(Intents.EXTRA_COORDS, coords); context.startActivity(cachesIntent); } @@ -1725,11 +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]; + AbstractSearchLoader loader = null; switch (enumType) { case OFFLINE: listId = Settings.getLastList(); 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 649ffa6..d566b2f 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,18 +1088,23 @@ 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); } + /** + * Post a log to GC.com. + * + * @return status code of the upload and ID of the log + */ public static StatusCode postLogTrackable(final String tbid, final String trackingCode, final String[] viewstates, final LogType logType, final int year, final int month, final int day, final String log) { if (Login.isEmpty(viewstates)) { 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| )*</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/AbstractExport.java b/main/src/cgeo/geocaching/export/AbstractExport.java index 72ea544..e4ba5f0 100644 --- a/main/src/cgeo/geocaching/export/AbstractExport.java +++ b/main/src/cgeo/geocaching/export/AbstractExport.java @@ -1,5 +1,6 @@ package cgeo.geocaching.export; +import cgeo.geocaching.R; import cgeo.geocaching.cgeoapplication; abstract class AbstractExport implements Export { @@ -27,7 +28,7 @@ abstract class AbstractExport implements Export { /** * Generates a localized string from a resource id. - * + * * @param resourceId * the resource id of the string * @param params @@ -43,4 +44,8 @@ abstract class AbstractExport implements Export { // used in the array adapter of the dialog showing the exports return getName(); } + + protected String getProgressTitle() { + return getString(R.string.export) + ": " + getName(); + } } diff --git a/main/src/cgeo/geocaching/export/FieldnoteExport.java b/main/src/cgeo/geocaching/export/FieldnoteExport.java index 5e1805a..2900781 100644 --- a/main/src/cgeo/geocaching/export/FieldnoteExport.java +++ b/main/src/cgeo/geocaching/export/FieldnoteExport.java @@ -5,11 +5,11 @@ import cgeo.geocaching.LogEntry; import cgeo.geocaching.R; import cgeo.geocaching.cgData; import cgeo.geocaching.activity.ActivityMixin; -import cgeo.geocaching.activity.Progress; import cgeo.geocaching.connector.gc.Login; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; +import cgeo.geocaching.utils.AsyncTaskWithProgress; import cgeo.geocaching.utils.IOUtils; import cgeo.geocaching.utils.Log; @@ -20,7 +20,6 @@ import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; -import android.os.AsyncTask; import android.os.Environment; import android.view.ContextThemeWrapper; import android.view.View; @@ -57,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 @@ -91,32 +91,27 @@ 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 AsyncTask<Void, Integer, 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; - private final Progress progress = new Progress(); private File exportFile; private static final int STATUS_UPLOAD = -1; /** - * Instantiates and configurates the task for exporting field notes. + * 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 @@ -124,22 +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) { - this.caches = caches; + public ExportTask(final Activity activity, final boolean upload, final boolean onlyNew) { + super(activity, getProgressTitle(), getString(R.string.export_fieldnotes_creating)); this.activity = activity; this.upload = upload; this.onlyNew = onlyNew; } @Override - protected void onPreExecute() { - if (null != activity) { - progress.show(activity, getString(R.string.export) + ": " + getName(), getString(R.string.export_fieldnotes_creating), true, null); - } - } - - @Override - protected Boolean doInBackground(Void... params) { + protected Boolean doInBackgroundInternal(Geocache[] caches) { final StringBuilder fieldNoteBuffer = new StringBuilder(); try { int i = 0; @@ -227,10 +215,8 @@ class FieldnoteExport extends AbstractExport { } @Override - protected void onPostExecute(Boolean result) { + protected void onPostExecuteInternal(Boolean result) { if (null != activity) { - progress.dismiss(); - if (result) { // if (onlyNew) { // // update last export time in settings when doing it ourself (currently we use the date check from gc.com) @@ -248,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]) { - progress.setMessage(getString(R.string.export_fieldnotes_uploading)); + if (STATUS_UPLOAD == status) { + setMessage(getString(R.string.export_fieldnotes_uploading)); } else { - progress.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 c2a58b7..3e58276 100644 --- a/main/src/cgeo/geocaching/export/GpxExport.java +++ b/main/src/cgeo/geocaching/export/GpxExport.java @@ -6,11 +6,12 @@ import cgeo.geocaching.R; import cgeo.geocaching.Settings; import cgeo.geocaching.Waypoint; import cgeo.geocaching.cgData; +import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.activity.ActivityMixin; -import cgeo.geocaching.activity.Progress; import cgeo.geocaching.enumerations.CacheAttribute; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.utils.AsyncTaskWithProgress; import cgeo.geocaching.utils.BaseUtils; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.XmlUtils; @@ -22,11 +23,9 @@ import org.xmlpull.v1.XmlSerializer; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; -import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; -import android.os.AsyncTask; import android.os.Environment; import android.view.ContextThemeWrapper; import android.view.View; @@ -38,9 +37,12 @@ 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.List; import java.util.Locale; +import java.util.Set; class GpxExport extends AbstractExport { private static final SimpleDateFormat dateFormatZ = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); @@ -48,24 +50,30 @@ class GpxExport extends AbstractExport { public static final String PREFIX_GPX = "http://www.topografix.com/GPX/1/0"; public static final String PREFIX_GROUNDSPEAK = "http://www.groundspeak.com/cache/1/0"; + /** + * During the export, only this number of geocaches is fully loaded into memory. + */ + public static final int CACHES_PER_BATCH = 100; + protected GpxExport() { super(getString(R.string.export_gpx)); } @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 @@ -91,46 +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(); } - protected class ExportTask extends AsyncTask<Void, Integer, File> { - private final List<Geocache> caches; + 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()]); + } + + protected class ExportTask extends AsyncTaskWithProgress<String, File> { private final Activity activity; - private final Progress progress = new Progress(); + 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) { - this.caches = caches; + public ExportTask(final Activity activity) { + super(activity, getProgressTitle()); this.activity = activity; } @Override - protected void onPreExecute() { - if (null != activity) { - progress.show(activity, null, getString(R.string.export) + ": " + getName(), ProgressDialog.STYLE_HORIZONTAL, null); - progress.setMaxProgressAndReset(caches.size()); - } - } - - @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; @@ -153,68 +162,17 @@ class GpxExport extends AbstractExport { PREFIX_GPX + " http://www.topografix.com/GPX/1/0/gpx.xsd " + PREFIX_GROUNDSPEAK + " http://www.groundspeak.com/cache/1/0/1/cache.xsd"); - for (int i = 0; i < caches.size(); i++) { - final Geocache cache = cgData.loadCache(caches.get(i).getGeocode(), LoadFlags.LOAD_ALL_DB_ONLY); - - gpx.startTag(PREFIX_GPX, "wpt"); - gpx.attribute("", "lat", Double.toString(cache.getCoords().getLatitude())); - gpx.attribute("", "lon", Double.toString(cache.getCoords().getLongitude())); - - final Date hiddenDate = cache.getHiddenDate(); - if (hiddenDate != null) { - XmlUtils.simpleText(gpx, PREFIX_GPX, "time", dateFormatZ.format(hiddenDate)); - } - - XmlUtils.multipleTexts(gpx, PREFIX_GPX, - "name", cache.getGeocode(), - "desc", cache.getName(), - "url", cache.getUrl(), - "urlname", cache.getName(), - "sym", cache.isFound() ? "Geocache Found" : "Geocache", - "type", "Geocache|" + cache.getType().pattern); - - gpx.startTag(PREFIX_GROUNDSPEAK, "cache"); - gpx.attribute("", "id", cache.getCacheId()); - gpx.attribute("", "available", !cache.isDisabled() ? "True" : "False"); - gpx.attribute("", "archives", cache.isArchived() ? "True" : "False"); - - XmlUtils.multipleTexts(gpx, PREFIX_GROUNDSPEAK, - "name", cache.getName(), - "placed_by", cache.getOwnerDisplayName(), - "owner", cache.getOwnerUserId(), - "type", cache.getType().pattern, - "container", cache.getSize().id, - "difficulty", Float.toString(cache.getDifficulty()), - "terrain", Float.toString(cache.getTerrain()), - "country", cache.getLocation(), - "state", "", - "encoded_hints", cache.getHint()); - - writeAttributes(gpx, cache); - - gpx.startTag(PREFIX_GROUNDSPEAK, "short_description"); - gpx.attribute("", "html", BaseUtils.containsHtml(cache.getShortDescription()) ? "True" : "False"); - gpx.text(cache.getShortDescription()); - gpx.endTag(PREFIX_GROUNDSPEAK, "short_description"); - - gpx.startTag(PREFIX_GROUNDSPEAK, "long_description"); - gpx.attribute("", "html", BaseUtils.containsHtml(cache.getDescription()) ? "True" : "False"); - gpx.text(cache.getDescription()); - gpx.endTag(PREFIX_GROUNDSPEAK, "long_description"); - - writeLogs(gpx, cache); - - gpx.endTag(PREFIX_GROUNDSPEAK, "cache"); - gpx.endTag(PREFIX_GPX, "wpt"); - - writeWaypoints(gpx, cache); - - publishProgress(i + 1); + // 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()) { + final List<String> batch = allGeocodes.subList(0, Math.min(CACHES_PER_BATCH, allGeocodes.size())); + exportBatch(gpx, batch); + batch.clear(); } gpx.endTag(PREFIX_GPX, "gpx"); gpx.endDocument(); - } catch (final IOException e) { + } catch (final Exception e) { Log.e("GpxExport.ExportTask export", e); if (writer != null) { @@ -235,6 +193,67 @@ class GpxExport extends AbstractExport { return exportFile; } + 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"); + gpx.attribute("", "lat", Double.toString(cache.getCoords().getLatitude())); + gpx.attribute("", "lon", Double.toString(cache.getCoords().getLongitude())); + + final Date hiddenDate = cache.getHiddenDate(); + if (hiddenDate != null) { + XmlUtils.simpleText(gpx, PREFIX_GPX, "time", dateFormatZ.format(hiddenDate)); + } + + XmlUtils.multipleTexts(gpx, PREFIX_GPX, + "name", cache.getGeocode(), + "desc", cache.getName(), + "url", cache.getUrl(), + "urlname", cache.getName(), + "sym", cache.isFound() ? "Geocache Found" : "Geocache", + "type", "Geocache|" + cache.getType().pattern); + + gpx.startTag(PREFIX_GROUNDSPEAK, "cache"); + gpx.attribute("", "id", cache.getCacheId()); + gpx.attribute("", "available", !cache.isDisabled() ? "True" : "False"); + gpx.attribute("", "archives", cache.isArchived() ? "True" : "False"); + + XmlUtils.multipleTexts(gpx, PREFIX_GROUNDSPEAK, + "name", cache.getName(), + "placed_by", cache.getOwnerDisplayName(), + "owner", cache.getOwnerUserId(), + "type", cache.getType().pattern, + "container", cache.getSize().id, + "difficulty", Float.toString(cache.getDifficulty()), + "terrain", Float.toString(cache.getTerrain()), + "country", cache.getLocation(), + "state", "", + "encoded_hints", cache.getHint()); + + writeAttributes(gpx, cache); + + gpx.startTag(PREFIX_GROUNDSPEAK, "short_description"); + gpx.attribute("", "html", BaseUtils.containsHtml(cache.getShortDescription()) ? "True" : "False"); + gpx.text(cache.getShortDescription()); + gpx.endTag(PREFIX_GROUNDSPEAK, "short_description"); + + gpx.startTag(PREFIX_GROUNDSPEAK, "long_description"); + gpx.attribute("", "html", BaseUtils.containsHtml(cache.getDescription()) ? "True" : "False"); + gpx.text(cache.getDescription()); + gpx.endTag(PREFIX_GROUNDSPEAK, "long_description"); + + writeLogs(gpx, cache); + + gpx.endTag(PREFIX_GROUNDSPEAK, "cache"); + gpx.endTag(PREFIX_GPX, "wpt"); + + writeWaypoints(gpx, cache); + + countExported++; + publishProgress(countExported); + } + } + private void writeWaypoints(final XmlSerializer gpx, final Geocache cache) throws IOException { List<Waypoint> waypoints = cache.getWaypoints(); List<Waypoint> ownWaypoints = new ArrayList<Waypoint>(waypoints.size()); @@ -345,9 +364,8 @@ class GpxExport extends AbstractExport { } @Override - protected void onPostExecute(final File exportFile) { + protected void onPostExecuteInternal(final File exportFile) { if (null != activity) { - progress.dismiss(); if (exportFile != null) { ActivityMixin.showToast(activity, getName() + ' ' + getString(R.string.export_exportedto) + ": " + exportFile.toString()); if (Settings.getShareAfterExport()) { @@ -363,11 +381,5 @@ class GpxExport extends AbstractExport { } } - @Override - protected void onProgressUpdate(Integer... status) { - if (null != activity) { - progress.setProgress(status[0]); - } - } } } 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/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..e18128d 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 { @@ -86,17 +87,41 @@ abstract class StateFilter extends AbstractFilter { } } + static class StateStoredFilter extends StateFilter { + public StateStoredFilter() { + super(res.getString(R.string.cache_status_stored)); + } + + @Override + public boolean accepts(Geocache cache) { + return cache.isOffline(); + } + } + + static class StateNotStoredFilter extends StateFilter { + public StateNotStoredFilter() { + super(res.getString(R.string.cache_status_not_stored)); + } + + @Override + public boolean accepts(Geocache cache) { + return !cache.isOffline(); + } + } + 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()); filters.add(new StatePremiumFilter()); filters.add(new StateNonPremiumFilter()); filters.add(new StateOfflineLogFilter()); + filters.add(new StateStoredFilter()); + filters.add(new StateNotStoredFilter()); Collections.sort(filters, new Comparator<StateFilter>() { @@ -106,7 +131,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/gcvote/GCVote.java b/main/src/cgeo/geocaching/gcvote/GCVote.java index a053f31..f6cfb84 100644 --- a/main/src/cgeo/geocaching/gcvote/GCVote.java +++ b/main/src/cgeo/geocaching/gcvote/GCVote.java @@ -173,12 +173,15 @@ public final class GCVote { /** * Transmit user vote to gcvote.com - * + * * @param cache * @param vote - * @return + * @return {@code true} if the rating was submitted successfully */ public static boolean setRating(Geocache cache, double vote) { + if (!Settings.isGCvoteLogin()) { + return false; + } if (!cache.supportsGCVote()) { return false; } 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/network/HtmlImage.java b/main/src/cgeo/geocaching/network/HtmlImage.java index a409750..b08d36d 100644 --- a/main/src/cgeo/geocaching/network/HtmlImage.java +++ b/main/src/cgeo/geocaching/network/HtmlImage.java @@ -193,7 +193,11 @@ public class HtmlImage implements Html.ImageGetter { if (file.exists()) { if (listId >= StoredList.STANDARD_LIST_ID || file.lastModified() > (new Date().getTime() - (24 * 60 * 60 * 1000)) || forceKeep) { setSampleSize(file); - return BitmapFactory.decodeFile(file.getPath(), bfOptions); + final Bitmap image = BitmapFactory.decodeFile(file.getPath(), bfOptions); + if (image == null) { + Log.e("Cannot decode bitmap from " + file.getPath()); + } + return image; } } return null; diff --git a/main/src/cgeo/geocaching/twitter/Twitter.java b/main/src/cgeo/geocaching/twitter/Twitter.java index f30830e..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,49 +50,56 @@ 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) { - 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; + if (!Settings.isUseTwitter()) { + return; } - 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) + '…'; - } - status = "I found " + name + " (" + url + ")"; - status = appendHashTag(status, "cgeo"); - status = appendHashTag(status, "geocaching"); + if (!Settings.isTwitterLoginValid()) { + return; } + final Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); + postTweet(cgeoapplication.getInstance(), getStatusMessage(cache), null); + } + + static String getStatusMessage(Geocache cache) { + String name = cache.getName(); + if (name.length() > 100) { + name = name.substring(0, 100) + '…'; + } + 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/CacheDetailsCreator.java b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java index e98bd77..80f01e2 100644 --- a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java +++ b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java @@ -2,6 +2,7 @@ package cgeo.geocaching.ui; import cgeo.geocaching.Geocache; import cgeo.geocaching.R; +import cgeo.geocaching.Waypoint; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Units; @@ -154,4 +155,24 @@ public final class CacheDetailsCreator { } add(R.string.cache_distance, text); } + + public void addDistance(final Waypoint wpt, final TextView waypointDistanceView) { + Float distance = null; + if (wpt.getCoords() != null) { + final Geopoint currentCoords = cgeoapplication.getInstance().currentGeo().getCoords(); + if (currentCoords != null) { + distance = currentCoords.distanceTo(wpt); + } + } + String text = "--"; + if (distance != null) { + text = Units.getDistanceFromKilometers(distance); + } + else if (waypointDistanceView != null) { + // if there is already a distance in waypointDistance, use it instead of resetting to default. + // this prevents displaying "--" while waiting for a new position update (See bug #1468) + text = waypointDistanceView.getText().toString(); + } + add(R.string.cache_distance, text); + } } diff --git a/main/src/cgeo/geocaching/ui/CoordinatesFormatSwitcher.java b/main/src/cgeo/geocaching/ui/CoordinatesFormatSwitcher.java new file mode 100644 index 0000000..afadb33 --- /dev/null +++ b/main/src/cgeo/geocaching/ui/CoordinatesFormatSwitcher.java @@ -0,0 +1,38 @@ +package cgeo.geocaching.ui; + +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.GeopointFormatter; + +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.TextView; + +/** + * view click listener to automatically switch different coordinate formats + * + */ +public class CoordinatesFormatSwitcher implements OnClickListener { + + private static GeopointFormatter.Format[] availableFormats = new GeopointFormatter.Format[] { + GeopointFormatter.Format.LAT_LON_DECMINUTE, + GeopointFormatter.Format.LAT_LON_DECSECOND, + GeopointFormatter.Format.LAT_LON_DECDEGREE + }; + + private int position = 0; + + private final Geopoint coordinates; + + public CoordinatesFormatSwitcher(final Geopoint coordinates) { + this.coordinates = coordinates; + } + + @Override + public void onClick(View view) { + position = (position + 1) % availableFormats.length; + TextView textView = (TextView) view; + // rotate coordinate formats on click + textView.setText(coordinates.format(availableFormats[position])); + } + +}
\ No newline at end of file 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/ImagesList.java b/main/src/cgeo/geocaching/ui/ImagesList.java index 9464114..b2c819c 100644 --- a/main/src/cgeo/geocaching/ui/ImagesList.java +++ b/main/src/cgeo/geocaching/ui/ImagesList.java @@ -11,7 +11,6 @@ import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; import android.app.Activity; -import android.app.ProgressDialog; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; @@ -46,16 +45,14 @@ public class ImagesList { private Image currentImage; public enum ImageType { - LogImages(R.string.cache_log_images_title, R.string.cache_log_images_loading), - SpoilerImages(R.string.cache_spoiler_images_title, R.string.cache_spoiler_images_loading), - AllImages(R.string.cache_images_title, R.string.cache_images_loading); + LogImages(R.string.cache_log_images_title), + SpoilerImages(R.string.cache_spoiler_images_title), + AllImages(R.string.cache_images_title); private final int titleResId; - private final int loadingResId; - ImageType(final int title, final int loading) { + ImageType(final int title) { this.titleResId = title; - this.loadingResId = loading; } public int getTitle() { @@ -64,9 +61,6 @@ public class ImagesList { } private LayoutInflater inflater = null; - private ProgressDialog progressDialog = null; - private int count = 0; - private int countDone = 0; private final Activity activity; // We could use a Set here, but we will insert no duplicates, so there is no need to check for uniqueness. private final Collection<Bitmap> bitmaps = new LinkedList<Bitmap>(); @@ -83,18 +77,10 @@ public class ImagesList { inflater = activity.getLayoutInflater(); } - public void loadImages(final View parentView, final List<Image> images, ImageType imageType, final boolean offline) { + public void loadImages(final View parentView, final List<Image> images, final boolean offline) { imagesView = (LinearLayout) parentView.findViewById(R.id.spoiler_list); - count = images.size(); - progressDialog = new ProgressDialog(activity); - progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - progressDialog.setMessage(activity.getString(imageType.loadingResId)); - progressDialog.setCancelable(true); - progressDialog.setMax(count); - progressDialog.show(); - for (final Image img : images) { LinearLayout rowView = (LinearLayout) inflater.inflate(R.layout.cache_image_item, null); @@ -154,19 +140,12 @@ public class ImagesList { imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); imageView.setLayoutParams(new LayoutParams(bounds.width(), bounds.height())); + view.findViewById(R.id.progress_bar).setVisibility(View.GONE); view.addView(imageView); imageView.setId(image.hashCode()); images.put(imageView.getId(), img); } - - synchronized (activity) { - countDone++; - progressDialog.setProgress(countDone); - if (progressDialog.getProgress() >= count) { - progressDialog.dismiss(); - } - } } } 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 new file mode 100644 index 0000000..7526d92 --- /dev/null +++ b/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java @@ -0,0 +1,139 @@ +package cgeo.geocaching.utils; + +import cgeo.geocaching.activity.Progress; + +import android.app.Activity; +import android.app.ProgressDialog; +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. 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> + */ +public abstract class AsyncTaskWithProgress<Params, Result> extends AsyncTask<Params, Integer, Result> { + + private final Progress progress = new Progress(); + private final Activity activity; + private final String progressTitle; + private final String progressMessage; + private boolean indeterminate = false; + + /** + * 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, 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.progressTitle = progressTitle; + this.progressMessage = progressMessage; + this.indeterminate = indeterminate; + } + + /** + * Creates an AsyncTask with progress dialog. + * + * @param activity + * @param progressTitle + */ + public AsyncTaskWithProgress(final Activity activity, final String progressTitle, boolean indeterminate) { + this(activity, progressTitle, null, indeterminate); + } + + @Override + protected final void onPreExecute() { + if (null != activity) { + if (indeterminate) { + progress.show(activity, progressTitle, progressMessage, true, null); + } + else { + progress.show(activity, progressTitle, progressMessage, ProgressDialog.STYLE_HORIZONTAL, null); + } + } + onPreExecuteInternal(); + } + + /** + * This method should typically be overridden by sub classes instead of {@link #onPreExecute()}. + */ + protected void onPreExecuteInternal() { + // empty by default + } + + @Override + 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 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); +} |
