diff options
author | campbeb <bpcampbell@gmail.com> | 2013-01-16 21:04:35 -1000 |
---|---|---|
committer | campbeb <bpcampbell@gmail.com> | 2013-01-16 21:04:35 -1000 |
commit | cf0ed3e3a1449115705bd4443990df7e4aa152d4 (patch) | |
tree | 80ca3f723c5bcd3adb0e1982c8e3b6d877f7dfad | |
parent | 884a60814d1a72ca30e9e9b0e31d59e895b2aca0 (diff) | |
download | cgeo-cf0ed3e3a1449115705bd4443990df7e4aa152d4.zip cgeo-cf0ed3e3a1449115705bd4443990df7e4aa152d4.tar.gz cgeo-cf0ed3e3a1449115705bd4443990df7e4aa152d4.tar.bz2 |
Fix #6 - Support uploading images with cache logs
-rw-r--r-- | main/AndroidManifest.xml | 5 | ||||
-rw-r--r-- | main/res/layout/visit.xml | 12 | ||||
-rw-r--r-- | main/res/values/strings.xml | 8 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/VisitCacheActivity.java | 85 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/connector/gc/GCConstants.java | 2 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/connector/gc/GCParser.java | 96 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/network/Network.java | 4 |
7 files changed, 195 insertions, 17 deletions
diff --git a/main/AndroidManifest.xml b/main/AndroidManifest.xml index d1a1e47..43c9ab2 100644 --- a/main/AndroidManifest.xml +++ b/main/AndroidManifest.xml @@ -10,6 +10,7 @@ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> + <uses-feature android:name="android.hardware.camera" android:required="false"/> <supports-screens android:largeScreens="true" android:normalScreens="true" @@ -232,5 +233,9 @@ <activity android:name=".files.SimpleDirChooser" android:label="@string/app_name"> </activity> + <activity + android:name=".ImageSelectActivity" + android:label="@string/app_name"> + </activity> </application> </manifest> diff --git a/main/res/layout/visit.xml b/main/res/layout/visit.xml index e88c21b..33c1af6 100644 --- a/main/res/layout/visit.xml +++ b/main/res/layout/visit.xml @@ -75,6 +75,18 @@ android:textColor="?text_color" android:text="@string/visit_tweet" /> </LinearLayout> + <ImageView + android:id="@+id/image_preview" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="5dip" + android:layout_marginBottom="5dip" + android:background="#000000" + android:padding="1dp" + android:visibility="gone"/> + <Button style="@style/button_full" + android:id="@+id/image_btn" + android:text="@string/log_image_attach" /> <Button style="@style/button_full" android:id="@+id/post" android:text="@string/log_post" /> diff --git a/main/res/values/strings.xml b/main/res/values/strings.xml index 9831059..601f874 100644 --- a/main/res/values/strings.xml +++ b/main/res/values/strings.xml @@ -85,6 +85,7 @@ <string name="log_tb_changeall">Change All</string> <string name="log_save">Save</string> <string name="log_saving">Sending log…</string> + <string name="log_saving_and_uploading">Sending log and uploading image…</string> <string name="log_clear">Clear</string> <string name="log_post">Submit Log</string> <string name="log_post_rate">Submit Log & Rate</string> @@ -118,6 +119,13 @@ <string name="log_today">Today</string> <string name="log_yesterday">Yesterday</string> <string name="log_smilies">Smilies</string> + <string name="log_image">Image</string> + <string name="log_image_attach">Attach Image</string> + <string name="log_image_edit">Edit Image</string> + <string name="log_image_stored">Existing</string> + <string name="log_image_camera">New</string> + <string name="log_image_caption">Caption</string> + <string name="log_image_description">Description</string> <!-- translation --> <string name="translate_to_sys_lang">Translate to %s</string> diff --git a/main/src/cgeo/geocaching/VisitCacheActivity.java b/main/src/cgeo/geocaching/VisitCacheActivity.java index a29256c..760d992 100644 --- a/main/src/cgeo/geocaching/VisitCacheActivity.java +++ b/main/src/cgeo/geocaching/VisitCacheActivity.java @@ -18,6 +18,7 @@ import cgeo.geocaching.utils.LogTemplateProvider.LogContext; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; import android.app.AlertDialog; import android.app.AlertDialog.Builder; @@ -26,6 +27,7 @@ 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; @@ -57,6 +59,11 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD private static final String SAVED_STATE_RATING = "cgeo.geocaching.saved_state_rating"; private static final String SAVED_STATE_TYPE = "cgeo.geocaching.saved_state_type"; private static final String SAVED_STATE_DATE = "cgeo.geocaching.saved_state_date"; + private static final String SAVED_STATE_IMAGE_CAPTION = "cgeo.geocaching.saved_state_image_caption"; + private static final String SAVED_STATE_IMAGE_DESCRIPTION = "cgeo.geocaching.saved_state_image_description"; + private static final String SAVED_STATE_IMAGE_URI = "cgeo.geocaching.saved_state_image_uri"; + + private static final int SELECT_IMAGE = 101; private LayoutInflater inflater = null; private cgCache cache = null; @@ -77,6 +84,9 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD private double rating; private LogType typeSelected; private Calendar date; + private String imageCaption; + private String imageDescription; + private Uri imageUri; @Override public Loader<String> onCreateLoader(final int id, final Bundle args) { @@ -297,6 +307,9 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD rating = savedInstanceState.getDouble(SAVED_STATE_RATING); typeSelected = LogType.getById(savedInstanceState.getInt(SAVED_STATE_TYPE)); date.setTimeInMillis(savedInstanceState.getLong(SAVED_STATE_DATE)); + imageCaption = savedInstanceState.getString(SAVED_STATE_IMAGE_CAPTION); + imageDescription = savedInstanceState.getString(SAVED_STATE_IMAGE_DESCRIPTION); + imageUri = Uri.parse(savedInstanceState.getString(SAVED_STATE_IMAGE_URI)); } else { // If log had been previously saved, load it now, otherwise initialize signature as needed final LogEntry log = cgData.loadLogOffline(geocode); @@ -334,6 +347,15 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD tweetCheck.setChecked(true); + final Button imageButton = (Button) findViewById(R.id.image_btn); + imageButton.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View view) { + selectImage(); + } + }); + final Button saveButton = (Button) findViewById(R.id.save); saveButton.setOnClickListener(new View.OnClickListener() { @@ -374,6 +396,9 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD } } text = null; + imageCaption = ""; + imageDescription = ""; + imageUri = Uri.EMPTY; } private void clearLog() { @@ -454,6 +479,8 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD outState.putDouble(SAVED_STATE_RATING, rating); outState.putInt(SAVED_STATE_TYPE, typeSelected.id); outState.putLong(SAVED_STATE_DATE, date.getTimeInMillis()); + outState.putString(SAVED_STATE_IMAGE_URI, imageUri.getPath()); + Log.d("saved state"); } @Override @@ -497,7 +524,8 @@ 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(R.string.log_saving), true); + 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()); @@ -525,11 +553,19 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD public StatusCode postLogFn(String log) { try { - final StatusCode status = GCParser.postLog(geocode, cacheid, viewstates, typeSelected, + // test call only + // if (imageUri != null) { + // final StatusCode status = GCParser.uploadLogImage("289163155", imageUri); + // if (status == StatusCode.LOG_POST_ERROR) { + // return status; + // } + // } + + 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); - if (status == StatusCode.NO_ERROR) { + if (logResult.left == StatusCode.NO_ERROR) { final LogEntry logNow = new LogEntry(date, typeSelected, log); cache.getLogs().prepend(logNow); @@ -541,21 +577,25 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD cgData.saveChangedCache(cache); } - if (status == StatusCode.NO_ERROR) { + if (logResult.left == StatusCode.NO_ERROR) { cgData.clearLogOffline(geocode); } - if (status == StatusCode.NO_ERROR && typeSelected == LogType.FOUND_IT && Settings.isUseTwitter() + if (logResult.left == StatusCode.NO_ERROR && typeSelected == LogType.FOUND_IT && Settings.isUseTwitter() && Settings.isTwitterLoginValid() && tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE) { Twitter.postTweetCache(geocode); } - if (status == StatusCode.NO_ERROR && typeSelected == LogType.FOUND_IT && Settings.isGCvoteLogin()) { + if (logResult.left == StatusCode.NO_ERROR && typeSelected == LogType.FOUND_IT && Settings.isGCvoteLogin()) { GCVote.setRating(cache, rating); } - return status; + if (StringUtils.isNotBlank(imageUri.getPath())) { + final StatusCode status = GCParser.uploadLogImage(logResult.right, imageCaption, imageDescription, imageUri); + } + + return logResult.left; } catch (Exception e) { Log.e("cgeovisit.postLogFn", e); } @@ -651,4 +691,35 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD alert.create().show(); } + private void selectImage() { + Intent selectImageIntent = new Intent(this, ImageSelectActivity.class); + selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_CAPTION, imageCaption); + selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_DESCRIPTION, imageDescription); + selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_URI_AS_STRING, imageUri.toString()); + + startActivityForResult(selectImageIntent, SELECT_IMAGE); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == SELECT_IMAGE) { + if (resultCode == RESULT_OK) { + imageCaption = data.getStringExtra(ImageSelectActivity.EXTRAS_CAPTION); + imageDescription = data.getStringExtra(ImageSelectActivity.EXTRAS_DESCRIPTION); + imageUri = Uri.parse(data.getStringExtra(ImageSelectActivity.EXTRAS_URI_AS_STRING)); + // Image captured and saved to fileUri specified in the Intent + showToast("Image saved to:\n" + imageUri); + } else if (resultCode == RESULT_CANCELED) { + // User cancelled the image capture + showToast("Cancelled"); + } else { + // Image capture failed, advise user + showToast("Unknown Error"); + } + final Button imageButton = (Button) findViewById(R.id.image_btn); + imageButton.setText(StringUtils.isNotBlank(imageUri.getPath()) ? + res.getString(R.string.log_image_edit) : res.getString(R.string.log_image_attach)); + + } + } } diff --git a/main/src/cgeo/geocaching/connector/gc/GCConstants.java b/main/src/cgeo/geocaching/connector/gc/GCConstants.java index 282c88c..e67f6a8 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConstants.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConstants.java @@ -163,6 +163,8 @@ public final class GCConstants { public final static Pattern PATTERN_USERSESSION = Pattern.compile("UserSession\\('([^']+)'"); public final static Pattern PATTERN_SESSIONTOKEN = Pattern.compile("sessionToken:'([^']+)'"); + public final static Pattern PATTERN_LOG_IMAGE_UPLOAD = Pattern.compile("/seek/upload\\.aspx\\?LID=(\\d+)", Pattern.CASE_INSENSITIVE); + public final static String STRING_PREMIUMONLY_2 = "Sorry, the owner of this listing has made it viewable to Premium Members only."; public final static String STRING_PREMIUMONLY_1 = "has chosen to make this cache listing visible to Premium Members only."; public final static String STRING_UNPUBLISHED_OWNER = "cache has not been published yet"; diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java index 274ba0d..908c21a 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCParser.java +++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java @@ -1,5 +1,6 @@ package cgeo.geocaching.connector.gc; +import cgeo.geocaching.Image; import cgeo.geocaching.LogEntry; import cgeo.geocaching.R; import cgeo.geocaching.SearchResult; @@ -9,7 +10,6 @@ import cgeo.geocaching.TrackableLog; import cgeo.geocaching.Waypoint; import cgeo.geocaching.cgCache; import cgeo.geocaching.cgData; -import cgeo.geocaching.Image; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.CacheType; @@ -39,6 +39,7 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -46,6 +47,7 @@ import org.json.JSONObject; import android.net.Uri; import android.text.Html; +import java.io.File; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -918,17 +920,17 @@ public abstract class GCParser { return trackable; } - public static StatusCode postLog(final String geocode, final String cacheid, final String[] viewstates, + public static ImmutablePair<StatusCode, String> postLog(final String geocode, final String cacheid, final String[] viewstates, final LogType logType, final int year, final int month, final int day, final String log, final List<TrackableLog> trackables) { if (Login.isEmpty(viewstates)) { Log.e("GCParser.postLog: No viewstate given"); - return StatusCode.LOG_POST_ERROR; + return new ImmutablePair<StatusCode, String>(StatusCode.LOG_POST_ERROR, ""); } if (StringUtils.isBlank(log)) { Log.e("GCParser.postLog: No log text given"); - return StatusCode.NO_LOG_TEXT; + return new ImmutablePair<StatusCode, String>(StatusCode.NO_LOG_TEXT, ""); } // fix log (non-Latin characters converted to HTML entities) @@ -987,7 +989,7 @@ public abstract class GCParser { String page = Login.postRequestLogged(uri, params); if (!Login.getLoginStatus(page)) { Log.e("GCParser.postLogTrackable: Can not log in geocaching"); - return StatusCode.NOT_LOGGED_IN; + return new ImmutablePair<StatusCode, String>(StatusCode.NOT_LOGGED_IN, ""); } // maintenance, archived needs to be confirmed @@ -1000,7 +1002,7 @@ public abstract class GCParser { if (Login.isEmpty(viewstatesConfirm)) { Log.e("GCParser.postLog: No viewstate for confirm log"); - return StatusCode.LOG_POST_ERROR; + return new ImmutablePair<StatusCode, String>(StatusCode.LOG_POST_ERROR, ""); } params.clear(); @@ -1054,16 +1056,94 @@ public abstract class GCParser { if (Login.getActualCachesFound() >= 0) { Login.setActualCachesFound(Login.getActualCachesFound() + 1); } - return StatusCode.NO_ERROR; + + final String logID = BaseUtils.getMatch(page, GCConstants.PATTERN_LOG_IMAGE_UPLOAD, ""); + + return new ImmutablePair<StatusCode, String>(StatusCode.NO_ERROR, logID); } } catch (Exception e) { Log.e("GCParser.postLog.check", e); } Log.e("GCParser.postLog: Failed to post log because of unknown error"); - return StatusCode.LOG_POST_ERROR; + return new ImmutablePair<StatusCode, String>(StatusCode.LOG_POST_ERROR, ""); } + /** + * Upload an image to a log that has already been posted + * + * @param logId + * the ID of the log to upload the image to. Found on page returned when log is uploaded + * @param imageUri + * 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) + { + 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)); + + if (!Login.getLoginStatus(page)) { + // Login.isActualLoginStatus() was wrong, we are not logged in + final StatusCode loginState = Login.login(); + if (loginState == StatusCode.NO_ERROR) { + page = Network.getResponseData(Network.getRequest(uri)); + } else { + Log.e("xxx upload: No login (error: " + loginState + ')'); + return StatusCode.NOT_LOGGED_IN; + } + } + + final String[] viewstates = Login.getViewstates(page); + + final Parameters uploadParams = new Parameters( + "__EVENTTARGET", "", + "__EVENTARGUMENT", "", + "ctl00$ContentBody$ImageUploadControl1$uxFileCaption", caption, + "ctl00$ContentBody$ImageUploadControl1$uxFileDesc", description, + "ctl00$ContentBody$ImageUploadControl1$uxUpload", "Upload"); + Login.putViewstates(uploadParams, viewstates); + + final File image = new File(imageUri.getPath()); + final String response = Network.getResponseData(Network.postRequest(uri, uploadParams, "ctl00$ContentBody$ImageUploadControl1$uxFileUpload", "image/jpeg", image)); + + //TODO: check response and return correct error codes + + /* + * String page = Login.postRequestLogged(uri, params); + * if (!Login.getLoginStatus(page)) { + * Log.e("GCParser.postLogTrackable: Can not log in geocaching"); + * return StatusCode.NOT_LOGGED_IN; + * } + * + * try { + * + * final MatcherWrapper matcherOk = new MatcherWrapper(GCConstants.PATTERN_OK1, page); + * if (matcherOk.find()) { + * Log.i("Log successfully posted to cache #" + cacheid); + * + * if (geocode != null) { + * cgData.saveVisitDate(geocode); + * } + * + * Login.getLoginStatus(page); + * // the log-successful-page contains still the old value + * if (Login.getActualCachesFound() >= 0) { + * Login.setActualCachesFound(Login.getActualCachesFound() + 1); + * } + * + * final String logID = BaseUtils.getMatch(page, GCConstants.PATTERN_LOG_IMAGE_UPLOAD, ""); + * + * return StatusCode.NO_ERROR; + * } + * } catch (Exception e) { + * Log.e("GCParser.postLog.check", e); + * } + */ + Log.e("GCParser.postLog: Failed to post log because of unknown error"); + return StatusCode.LOG_POST_ERROR; + } 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/network/Network.java b/main/src/cgeo/geocaching/network/Network.java index 2affab4..2545f4d 100644 --- a/main/src/cgeo/geocaching/network/Network.java +++ b/main/src/cgeo/geocaching/network/Network.java @@ -291,9 +291,9 @@ public abstract class Network { final String timeSpan = Network.formatTimeSpan(before); final String tries = (i + 1) + "/" + (Network.NB_DOWNLOAD_RETRIES + 1); if (i == Network.NB_DOWNLOAD_RETRIES) { - Log.e("Failure " + tries + timeSpan + reqLogStr, e); + Log.w("Failure " + tries + timeSpan + reqLogStr + " (" + e.toString() + ")"); } else { - Log.e("Failure " + tries + " (" + e.toString() + ")" + timeSpan + "- retrying " + reqLogStr); + Log.w("Failure " + tries + " (" + e.toString() + ")" + timeSpan + "- retrying " + reqLogStr); } } } |