diff options
Diffstat (limited to 'main')
19 files changed, 383 insertions, 219 deletions
diff --git a/main/res/values-cs/strings.xml b/main/res/values-cs/strings.xml index 5ea6fdd..caf0a17 100644 --- a/main/res/values-cs/strings.xml +++ b/main/res/values-cs/strings.xml @@ -409,7 +409,6 @@ <string name="init_offline_wp">Ukládat statické mapy k bodům trasy pro offline použití</string> <string name="init_save_log_img">Ukládat obrázky z logů</string> <string name="init_units">Používat imperiální jednotky vzdálenosti</string> - <string name="init_nav">Používat Google Navigaci</string> <string name="init_log_offline">Povolit Offline logování\n(Při logování nezobrazovat online logovací obrazovku a neodesílat Log na server)</string> <string name="init_choose_list">Ptát se na seznam pro uložení keše</string> <string name="init_livelist">Zobrazovat směr v seznamu keší</string> @@ -1062,7 +1061,6 @@ <string name="facebook">Facebook: <a href="http://www.facebook.com/pages/cgeo/297269860090">Stránka c:geo</a></string> <string name="twitter">Twitter: <a href="http://twitter.com/android_gc">@android_GC</a></string> <string name="nutshellmanual">Návod: <a href="http://manual.cgeo.org/">c:geo v Nutshell</a></string> - <string name="about_go4cache">Služba <b>Go 4 Cache</b> zobrazuje ostatní geokačery na mapě (v <b>c:geo</b> nebo v prohlížeči) v reálném čase. Může zobrazovat - na příklad - jakou keš zrovna hledají. Připojením se k <b>Go 4 Cache</b> získá aplikace <b>c:geo</b> povolení zveřejňovat tvou polohu při geocachingu (pouze když je <b>c:geo</b> spuštěno).</string> <string name="about_twitter">Má <b>c:geo</b> publikovat nový status na Twitteru vždy, když zaloguješ keš?</string> <string name="about_auth_1">Následující proces dovoluje aplikaci <b>c:geo</b> přístup na Twitter - pokud budeš souhlasit.</string> <string name="about_auth_2">Klepnutím na tlačítko \"Zahájit autorizaci\" bude proces zahájen. Tento proces otevře webový prohlížeč s Twitterem. Přihlaš se na této stránce a povol <b>c:geu</b> přístup k tvému účtu. Pokud je to povoleno, Twitter ti ukáže číselný PIN kód. Tento PIN kód musíš zadat do <b>c:geo</b> a potvrdit. To je vše.</string> diff --git a/main/res/values/strings_not_translatable.xml b/main/res/values/strings_not_translatable.xml index 553c16a..8de1483 100644 --- a/main/res/values/strings_not_translatable.xml +++ b/main/res/values/strings_not_translatable.xml @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="waypoint_coordinate_formats" translatable="false">
<item>@string/waypoint_coordinate_formats_plain</item>
@@ -80,40 +80,17 @@ <!-- changelog -->
<string name="changelog" translatable="false">\n
- <b>2013.04.03</b>\n\n
+ <b>Next release</b>\n\n
<b>New Features/Functions:</b>\n
- · Support of attaching pictures to logs\n
- · Support of opencaching.de online API in live map and nearby search\n
- · Support for searching opencaching.nl caches\n
- · Alphabetical sorting of OSM:Offline maps in map selection\n
- · Color markers also in trackable logbook\n
- · Edit cache type filter by clicking on filter bar\n
- · Inverse sorting of cache lists (hit the same menu again)\n
- · View pager for trackable activity\n
- · Menu item to delete past events\n
- · Settings: Option to ask for list to store caches\n
- · Change list in cache details\n
- · Ability to select \"All\" list from main screen\n
- · Default log type for event caches will be \"Attended\" if \"Will attend\" was logged before\n
- · Improved and extended cache type detection on live map\n
- · Waypoints can be marked as visited\n
- · Possibility to delete offline logs in lists\n
- · Support of language specific characters in log text equally to the website\n
+ · ...\n
\n
<b>Bugfixing:</b>\n
- · Final flag icon lost when updating cache with self defined final\n
- · Bad selection in directory chooser\n
- · Log type \"Retract Listing\" now parsed correctly\n
- · Active cache detail page now remembered when rotating device\n
- · Replaced the term \"GC-Code\" by \"Geocode\"\n
- · Improvements for light theme\n
- · Share function uses short URL again\n
- · Offline log marker now shown after autosave of log\n
- · GPX export no longer exports waypoints without coordinates\n
- · Corrections for light scheme on Adnroid 2.x devices\n
- · Avoid crash if logging page is opened while not connected correctly\n
+ · Do not apply encryption/decryption when clicking on a link in a log\n
+ · Also show a picture attached to a log locally after sending the log\n
+ · Improved display of log text for OC-caches\n
+ · Improved performance of GPX exports\n
\n
- <a href="https://github.com/cgeo/c-geo-opensource/issues?milestone=9&state=closed">Detailed list of all changes</a>\n
+ <a href="https://github.com/cgeo/c-geo-opensource/issues?milestone=17&state=closed">Detailed list of all changes</a>\n
\n
<b>Known Limitations/Bugs:</b>\n
· Live map:\n
@@ -123,7 +100,6 @@ On low zoom owned/found caches may not be hidden anymore\n
· Other:\n
Log images with huge size cause a long loading time\n
- After uploading log images they are only shown in the logview after refreshing the cache\n
The personal note added to a cache is not synced to geocaching.com but will be overruled by personal notes on geocaching.com\n
On devices with HD display resolution OSM maps might not work. Please use Google maps in this case.\n
A huge amount of pictures on the image tab of a cache might cause a crash\n
diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java index a521ab9..9347db9 100644 --- a/main/src/cgeo/geocaching/CacheDetailActivity.java +++ b/main/src/cgeo/geocaching/CacheDetailActivity.java @@ -21,6 +21,7 @@ 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; @@ -1207,23 +1208,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); } @@ -2213,6 +2198,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 9a8325d..96bb2fe 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; @@ -1360,6 +1361,9 @@ public class Geocache implements ICache, IWaypoint { return null; } + /** + * Detect coordinates in the personal note and convert them to user defined waypoints. Works by rule of thumb. + */ public void parseWaypointsFromNote() { try { if (StringUtils.isBlank(getPersonalNote())) { @@ -1375,7 +1379,8 @@ public class Geocache implements ICache, IWaypoint { // coords must have non zero latitude and longitude and at least one part shall have fractional degrees if (point.getLatitudeE6() != 0 && point.getLongitudeE6() != 0 && ((point.getLatitudeE6() % 1000) != 0 || (point.getLongitudeE6() % 1000) != 0)) { final String name = cgeoapplication.getInstance().getString(R.string.cache_personal_note) + " " + count; - final Waypoint waypoint = new Waypoint(name, WaypointType.WAYPOINT, false); + final String potentialWaypointType = note.substring(Math.max(0, matcher.start() - 15)); + final Waypoint waypoint = new Waypoint(name, parseWaypointType(potentialWaypointType), false); waypoint.setCoords(point); addOrChangeWaypoint(waypoint, false); count++; @@ -1392,6 +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; + } + /* * For working in the debugger * (non-Javadoc) diff --git a/main/src/cgeo/geocaching/Settings.java b/main/src/cgeo/geocaching/Settings.java index 0c157e1..93bfa9b 100644 --- a/main/src/cgeo/geocaching/Settings.java +++ b/main/src/cgeo/geocaching/Settings.java @@ -149,6 +149,7 @@ public final class Settings { // maps private static MapProvider mapProvider = null; + private static String cacheTwitterMessage = "I found [NAME] ([URL])"; private Settings() { // this class is not to be instantiated; @@ -1424,4 +1425,18 @@ public final class Settings { } }); } + + public static String getCacheTwitterMessage() { + // TODO make customizable from UI + return cacheTwitterMessage; + } + + public static String getTrackableTwitterMessage() { + // TODO make customizable from UI + return "I touched [NAME] ([URL])!"; + } + + public static void setCacheTwitterMessage(final String message) { + cacheTwitterMessage = message; + } } diff --git a/main/src/cgeo/geocaching/StaticMapsProvider.java b/main/src/cgeo/geocaching/StaticMapsProvider.java index cd88071..69f5cc4 100644 --- a/main/src/cgeo/geocaching/StaticMapsProvider.java +++ b/main/src/cgeo/geocaching/StaticMapsProvider.java @@ -76,8 +76,11 @@ public class StaticMapsProvider { final long fileSize = file.length(); if (fileSize < MIN_MAP_IMAGE_BYTES) { file.delete(); + return; } + return; } + return; } public static void downloadMaps(Geocache cache) { @@ -245,7 +248,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 +271,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 +290,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 +329,4 @@ public class StaticMapsProvider { } return null; } - } diff --git a/main/src/cgeo/geocaching/VisitCacheActivity.java b/main/src/cgeo/geocaching/VisitCacheActivity.java index d95f6df..b4eb82a 100644 --- a/main/src/cgeo/geocaching/VisitCacheActivity.java +++ b/main/src/cgeo/geocaching/VisitCacheActivity.java @@ -571,18 +571,16 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD } public StatusCode postLogFn(String log) { - StatusCode result = StatusCode.LOG_POST_ERROR; try { - final ImmutablePair<StatusCode, String> logResult = GCParser.postLog(geocode, cacheid, viewstates, typeSelected, + final ImmutablePair<StatusCode, String> postResult = GCParser.postLog(geocode, cacheid, viewstates, typeSelected, date.get(Calendar.YEAR), (date.get(Calendar.MONTH) + 1), date.get(Calendar.DATE), log, trackables); + result = postResult.left; - result = logResult.left; - - if (logResult.left == StatusCode.NO_ERROR) { + if (result == StatusCode.NO_ERROR) { final LogEntry logNow = new LogEntry(date, typeSelected, log); cache.getLogs().add(0, logNow); @@ -592,24 +590,24 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD } cgData.saveChangedCache(cache); - } - - 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); - } + if (typeSelected == LogType.FOUND_IT) { + if (tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE) { + Twitter.postTweetCache(geocode); + } + GCVote.setRating(cache, rating); + } - if (logResult.left == StatusCode.NO_ERROR && StringUtils.isNotBlank(imageUri.getPath())) { - result = GCParser.uploadLogImage(logResult.right, imageCaption, imageDescription, imageUri); + 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); + } + result = imageResult.left; + } } return result; 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 9af9953..4b5d7b9 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCParser.java +++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java @@ -1059,7 +1059,7 @@ public abstract class GCParser { * the URI for the image to be uploaded * @return status code to indicate success or failure */ - public static StatusCode uploadLogImage(final String logId, final String caption, final String description, final Uri imageUri) { + public static ImmutablePair<StatusCode, String> uploadLogImage(final String logId, final String caption, final String description, final Uri imageUri) { final String uri = new Uri.Builder().scheme("http").authority("www.geocaching.com").path("/seek/upload.aspx").encodedQuery("LID=" + logId).build().toString(); String page = Network.getResponseData(Network.getRequest(uri)); @@ -1071,7 +1071,7 @@ public abstract class GCParser { page = Network.getResponseData(Network.getRequest(uri)); } else { Log.e("Image upload: No login (error: " + loginState + ')'); - return StatusCode.NOT_LOGGED_IN; + return ImmutablePair.of(StatusCode.NOT_LOGGED_IN, null); } } @@ -1088,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..eadcb77 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; @@ -102,18 +101,17 @@ class FieldnoteExport extends AbstractExport { return builder.create(); } - private class ExportTask extends AsyncTask<Void, Integer, Boolean> { + private class ExportTask extends AsyncTaskWithProgress<Void, Boolean> { private final List<Geocache> caches; 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 @@ -125,6 +123,7 @@ class FieldnoteExport extends AbstractExport { * Upload/export only new logs since last export */ public ExportTask(final List<Geocache> caches, final Activity activity, final boolean upload, final boolean onlyNew) { + super(activity, getProgressTitle(), getString(R.string.export_fieldnotes_creating)); this.caches = caches; this.activity = activity; this.upload = upload; @@ -132,13 +131,6 @@ class FieldnoteExport extends AbstractExport { } @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) { final StringBuilder fieldNoteBuffer = new StringBuilder(); try { @@ -228,9 +220,8 @@ class FieldnoteExport extends AbstractExport { @Override protected void onPostExecute(Boolean result) { + super.onPostExecute(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) @@ -251,9 +242,9 @@ class FieldnoteExport extends AbstractExport { protected void onProgressUpdate(Integer... status) { if (null != activity) { if (STATUS_UPLOAD == status[0]) { - progress.setMessage(getString(R.string.export_fieldnotes_uploading)); + 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[0] + ')'); } } } diff --git a/main/src/cgeo/geocaching/export/GpxExport.java b/main/src/cgeo/geocaching/export/GpxExport.java index 28ca646..19b8518 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; @@ -21,11 +22,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.util.Xml; import android.view.ContextThemeWrapper; @@ -39,8 +38,11 @@ import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; 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,6 +50,11 @@ 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)); } @@ -98,10 +105,10 @@ class GpxExport extends AbstractExport { return builder.create(); } - private class ExportTask extends AsyncTask<Void, Integer, File> { - private final List<Geocache> caches; + private class ExportTask extends AsyncTaskWithProgress<Void, File> { + private final Set<String> allGeocodes = new HashSet<String>(); private final Activity activity; - private final Progress progress = new Progress(); + private int countExported = 0; /** * Instantiates and configures the task for exporting field notes. @@ -112,16 +119,13 @@ class GpxExport extends AbstractExport { * optional: Show a progress bar and toasts */ public ExportTask(final List<Geocache> caches, final Activity activity) { - this.caches = caches; - this.activity = activity; - } + super(activity, caches.size(), getProgressTitle(), cgeoapplication.getInstance().getResources().getQuantityString(R.plurals.cache_counts, caches.size(), caches.size())); - @Override - protected void onPreExecute() { - if (null != activity) { - progress.show(activity, null, getString(R.string.export) + ": " + getName(), ProgressDialog.STYLE_HORIZONTAL, null); - progress.setMaxProgressAndReset(caches.size()); + // get rid of the (half loaded) caches, we will reload them as full caches during the export + for (Geocache geocache : caches) { + allGeocodes.add(geocache.getGeocode()); } + this.activity = activity; } @Override @@ -153,68 +157,24 @@ 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())); + // 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). - final Date hiddenDate = cache.getHiddenDate(); - if (hiddenDate != null) { - XmlUtils.simpleText(gpx, PREFIX_GPX, "time", dateFormatZ.format(hiddenDate)); + while (!allGeocodes.isEmpty()) { + int cachesInBatch = 0; + HashSet<String> geocodesOfBatch = new HashSet<String>(CACHES_PER_BATCH); + for (Iterator<String> iterator = allGeocodes.iterator(); iterator.hasNext() && cachesInBatch < CACHES_PER_BATCH;) { + final String geocode = iterator.next(); + geocodesOfBatch.add(geocode); + cachesInBatch++; } - - 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); + allGeocodes.removeAll(geocodesOfBatch); + exportBatch(gpx, geocodesOfBatch); } 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 +195,67 @@ class GpxExport extends AbstractExport { return exportFile; } + private void exportBatch(final XmlSerializer gpx, HashSet<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()); @@ -346,8 +367,8 @@ class GpxExport extends AbstractExport { @Override protected void onPostExecute(final File exportFile) { + super.onPostExecute(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 +384,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/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/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/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/utils/AsyncTaskWithProgress.java b/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java new file mode 100644 index 0000000..b23fa9d --- /dev/null +++ b/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java @@ -0,0 +1,80 @@ +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. + * + * @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 int maxProgress; + private final String progressTitle; + private final String progressMessage; + + /** + * Creates an AsyncTask with progress dialog, where the maximum is set to the given maxProgress. + * + * @param activity + * @param maxProgress + * @param progressTitle + * @param progressMessage + */ + public AsyncTaskWithProgress(final Activity activity, final int maxProgress, final String progressTitle, final String progressMessage) { + this.activity = activity; + this.maxProgress = maxProgress; + this.progressTitle = progressTitle; + this.progressMessage = progressMessage; + } + + /** + * Creates an AsyncTask with progress dialog, where the maximum is set to indeterminate. + * + * @param activity + * @param progressTitle + * @param progressMessage + */ + public AsyncTaskWithProgress(final Activity activity, final String progressTitle, final String progressMessage) { + this(activity, 0, progressTitle, progressMessage); + } + + @Override + protected void onPreExecute() { + if (null != activity) { + if (maxProgress <= 0) { + progress.show(activity, progressTitle, progressMessage, true, null); + } + else { + progress.show(activity, progressTitle, progressMessage, ProgressDialog.STYLE_HORIZONTAL, null); + } + progress.setMaxProgressAndReset(maxProgress); + } + } + + @Override + protected void onPostExecute(Result result) { + if (null != activity) { + progress.dismiss(); + } + } + + @Override + protected void onProgressUpdate(Integer... status) { + if (null != activity) { + progress.setProgress(status[0]); + } + } + + protected void setMessage(final String message) { + progress.setMessage(message); + } +} |
