aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrsudev <rasch@munin-soft.de>2013-02-18 22:21:59 +0100
committerrsudev <rasch@munin-soft.de>2013-02-18 22:21:59 +0100
commit8449a5359f0e554ddd962167cff91e20e21cf4bb (patch)
tree82d6e8ef361e4a3065400f788b1eb6e64b6d1d64
parenta268af51bcf2ddec2ab4631c6d0aedbd51811ec2 (diff)
parentea13018cc48c97125e25668f5bea67135f5df9ee (diff)
downloadcgeo-8449a5359f0e554ddd962167cff91e20e21cf4bb.zip
cgeo-8449a5359f0e554ddd962167cff91e20e21cf4bb.tar.gz
cgeo-8449a5359f0e554ddd962167cff91e20e21cf4bb.tar.bz2
Merge remote-tracking branch 'campbeb/fix6a' into 6b
Conflicts: main/src/cgeo/geocaching/connector/gc/GCParser.java
-rw-r--r--main/AndroidManifest.xml5
-rw-r--r--main/res/layout/visit.xml3
-rw-r--r--main/res/layout/visit_image.xml124
-rw-r--r--main/res/values/strings.xml8
-rw-r--r--main/src/cgeo/geocaching/ImageSelectActivity.java274
-rw-r--r--main/src/cgeo/geocaching/VisitCacheActivity.java85
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCConstants.java2
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCParser.java119
-rw-r--r--main/src/cgeo/geocaching/network/Network.java4
-rw-r--r--main/src/cgeo/geocaching/utils/HtmlUtils.java25
10 files changed, 615 insertions, 34 deletions
diff --git a/main/AndroidManifest.xml b/main/AndroidManifest.xml
index f764462..f670723 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"
@@ -224,5 +225,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 54f4f4b..fde4cd3 100644
--- a/main/res/layout/visit.xml
+++ b/main/res/layout/visit.xml
@@ -76,6 +76,9 @@
android:text="@string/visit_tweet" />
</LinearLayout>
<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" />
<RelativeLayout style="@style/separator_horizontal_layout" >
diff --git a/main/res/layout/visit_image.xml b/main/res/layout/visit_image.xml
new file mode 100644
index 0000000..db4b45a
--- /dev/null
+++ b/main/res/layout/visit_image.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:background="?background_color"
+ android:orientation="vertical" >
+
+ <LinearLayout style="@style/action_bar" >
+
+ <ImageView
+ style="@style/action_bar_action"
+ android:onClick="goHome" />
+
+ <View style="@style/action_bar_separator" />
+
+ <TextView style="@style/action_bar_title" />
+
+ <View style="@style/action_bar_separator" />
+
+ <ProgressBar
+ style="@style/action_bar_progress"
+ android:visibility="gone" />
+
+ <ImageView
+ style="@style/action_bar_action"
+ android:onClick="goManual"
+ android:src="@drawable/actionbar_manual" />
+ </LinearLayout>
+
+ <ScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+ android:padding="4dip" >
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dip"
+ android:orientation="vertical" >
+
+ <RelativeLayout style="@style/separator_horizontal_layout" >
+
+ <View style="@style/separator_horizontal" />
+
+ <TextView
+ style="@style/separator_horizontal_headline"
+ android:text="@string/log_image" />
+ </RelativeLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dip"
+ android:orientation="horizontal" >
+
+ <Button
+ android:id="@+id/stored"
+ style="@style/button_full"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:text="@string/log_image_stored" />
+
+ <Button
+ android:id="@+id/camera"
+ style="@style/button_full"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:text="@string/log_image_camera" />
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/image_preview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="5dip"
+ android:layout_marginTop="5dip"
+ android:background="#000000"
+ android:padding="1dp"
+ android:visibility="gone" />
+
+ <EditText
+ android:id="@+id/caption"
+ style="@style/edittext_full"
+ android:layout_height="wrap_content"
+ android:hint="@string/log_image_caption"
+ android:inputType="textCapSentences"
+ android:maxLength="50"
+ android:minLines="1"
+ android:singleLine="false" />
+
+ <EditText
+ android:id="@+id/description"
+ style="@style/edittext_full"
+ android:layout_height="wrap_content"
+ android:hint="@string/log_image_description"
+ android:inputType="textMultiLine|textCapSentences"
+ android:maxLength="250"
+ android:minLines="5"
+ android:singleLine="false" />
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <Button
+ android:id="@+id/save"
+ style="@style/button_full"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:text="@android:string/yes" />
+
+ <Button
+ android:id="@+id/cancel"
+ style="@style/button_full"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:text="@android:string/no" />
+ </LinearLayout>
+ </LinearLayout>
+ </ScrollView>
+
+</LinearLayout> \ No newline at end of file
diff --git a/main/res/values/strings.xml b/main/res/values/strings.xml
index 5b273ca..3f70e44 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 &amp; 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/ImageSelectActivity.java b/main/src/cgeo/geocaching/ImageSelectActivity.java
new file mode 100644
index 0000000..572ac31
--- /dev/null
+++ b/main/src/cgeo/geocaching/ImageSelectActivity.java
@@ -0,0 +1,274 @@
+package cgeo.geocaching;
+
+import cgeo.geocaching.activity.AbstractActivity;
+import cgeo.geocaching.utils.Log;
+
+import org.apache.commons.lang3.StringUtils;
+
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public class ImageSelectActivity extends AbstractActivity {
+ static final String EXTRAS_CAPTION = "caption";
+ static final String EXTRAS_DESCRIPTION = "description";
+ static final String EXTRAS_URI_AS_STRING = "uri";
+
+ 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_NEW_IMAGE = 1;
+ private static final int SELECT_STORED_IMAGE = 2;
+
+ private EditText captionView;
+ private EditText descriptionView;
+
+ // Data to be saved while reconfiguring
+ private String imageCaption;
+ private String imageDescription;
+ private Uri imageUri;
+
+ public ImageSelectActivity() {
+ super("ImageSelectActivity");
+ }
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setTheme();
+ setContentView(R.layout.visit_image);
+ setTitle(res.getString(R.string.log_image));
+
+ imageCaption = "";
+ imageDescription = "";
+ imageUri = Uri.EMPTY;
+
+ // Get parameters from intent and basic cache information from database
+ final Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ imageCaption = extras.getString(EXTRAS_CAPTION);
+ imageDescription = extras.getString(EXTRAS_DESCRIPTION);
+ imageUri = Uri.parse(extras.getString(EXTRAS_URI_AS_STRING));
+ }
+
+ // Restore previous state
+ if (savedInstanceState != null) {
+ imageCaption = savedInstanceState.getString(SAVED_STATE_IMAGE_CAPTION);
+ imageDescription = savedInstanceState.getString(SAVED_STATE_IMAGE_DESCRIPTION);
+ imageUri = Uri.parse(savedInstanceState.getString(SAVED_STATE_IMAGE_URI));
+ }
+
+ final Button cameraButton = (Button) findViewById(R.id.camera);
+ cameraButton.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View view) {
+ selectImageFromCamera();
+ }
+ });
+
+ final Button storedButton = (Button) findViewById(R.id.stored);
+ storedButton.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View view) {
+ selectImageFromStorage();
+ }
+ });
+
+ captionView = (EditText) findViewById(R.id.caption);
+ if (StringUtils.isNotBlank(imageCaption)) {
+ captionView.setText(imageCaption);
+ }
+
+ descriptionView = (EditText) findViewById(R.id.description);
+ if (StringUtils.isNotBlank(imageDescription)) {
+ descriptionView.setText(imageDescription);
+ }
+
+ final Button saveButton = (Button) findViewById(R.id.save);
+ saveButton.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ saveImageInfo(true);
+ }
+ });
+
+ final Button clearButton = (Button) findViewById(R.id.cancel);
+ clearButton.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ saveImageInfo(false);
+ }
+ });
+
+ loadImagePreview();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ }
+
+ @Override
+ protected void onSaveInstanceState(final Bundle outState) {
+ super.onSaveInstanceState(outState);
+ currentEditTexts();
+ outState.putString(SAVED_STATE_IMAGE_CAPTION, imageCaption);
+ outState.putString(SAVED_STATE_IMAGE_DESCRIPTION, imageDescription);
+ outState.putString(SAVED_STATE_IMAGE_URI, imageUri != null ? imageUri.getPath() : StringUtils.EMPTY);
+ }
+
+ public void saveImageInfo(boolean saveInfo) {
+ if (saveInfo) {
+ Intent intent = new Intent();
+ currentEditTexts();
+ intent.putExtra(EXTRAS_CAPTION, imageCaption);
+ intent.putExtra(EXTRAS_DESCRIPTION, imageDescription);
+ intent.putExtra(EXTRAS_URI_AS_STRING, imageUri.toString());
+
+ setResult(RESULT_OK, intent);
+ } else {
+ setResult(RESULT_CANCELED);
+ }
+
+ finish();
+ }
+
+ private void currentEditTexts() {
+ imageCaption = captionView.getText().toString();
+ imageDescription = descriptionView.getText().toString();
+ }
+
+ private void selectImageFromCamera() {
+ // create Intent to take a picture and return control to the calling application
+ Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+
+ imageUri = getOutputImageFileUri(); // create a file to save the image
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); // set the image file name
+
+ // start the image capture Intent
+ startActivityForResult(intent, SELECT_NEW_IMAGE);
+ }
+
+ private void selectImageFromStorage() {
+ //Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+
+ //Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+ //intent.addCategory(Intent.CATEGORY_OPENABLE);
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("image/jpeg");
+
+ startActivityForResult(Intent.createChooser(intent, "Select Image"), SELECT_STORED_IMAGE);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode == RESULT_CANCELED) {
+ // User cancelled the image capture
+ showToast("Cancelled");
+ return;
+ }
+
+ if (resultCode == RESULT_OK) {
+ if (requestCode == SELECT_NEW_IMAGE) {
+
+ // Image captured and saved to fileUri specified in the Intent
+ //TODO: Some camera program ignore the URI passed in and store it in a location given in data
+ showToast("Image saved to:\n" + imageUri);//data.getData());
+ if (data != null) {
+ Log.d("SELECT_NEW_IMAGE data = " + data.toString());
+ } else {
+ Log.d("SELECT_NEW_IMAGE data is null");
+ }
+ } else if (requestCode == SELECT_STORED_IMAGE) {
+ Uri selectedImage = data.getData();
+ String[] filePathColumn = { MediaColumns.DATA };
+
+ Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);
+ cursor.moveToFirst();
+
+ int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
+ String filePath = cursor.getString(columnIndex);
+ imageUri = Uri.parse(filePath);
+ cursor.close();
+ if (data != null) {
+ Log.d("SELECT_STORED_IMAGE data = " + data.toString());
+ } else {
+ Log.d("SELECT_STORED_IMAGE data is null");
+ }
+ // imageUri = data.getData();
+ }
+ } else {
+ // Image capture failed, advise user
+ showToast("Unknown Error");
+ return;
+ }
+
+ loadImagePreview();
+ }
+
+ private void loadImagePreview()
+ {
+ if (!new File(imageUri.getPath()).exists()) {
+ Log.i("loading Image Preview with nonexistant file");
+ return;
+ }
+
+ final ImageView imagePreview = (ImageView) findViewById(R.id.image_preview);
+ BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
+ bitmapOptions.inSampleSize = 8;
+ final Bitmap bitmap = BitmapFactory.decodeFile(imageUri.getPath(), bitmapOptions);
+ imagePreview.setImageBitmap(bitmap);
+ imagePreview.setVisibility(View.VISIBLE);
+ }
+
+ private static Uri getOutputImageFileUri() {
+ return Uri.fromFile(getOutputImageFile());
+ }
+
+ /** Create a File for saving an image or video */
+ @SuppressLint("NewApi")
+ // TODO: Get rid of that suppress
+ private static File getOutputImageFile() {
+ // To be safe, you should check that the SDCard is mounted
+ // using Environment.getExternalStorageState() before doing this.
+
+ File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_PICTURES), "cgeo");
+ // This location works best if you want the created images to be shared
+ // between applications and persist after your app has been uninstalled.
+
+ // Create the storage directory if it does not exist
+ if (!mediaStorageDir.exists()) {
+ if (!mediaStorageDir.mkdirs()) {
+ Log.w("Failed to create directory");
+ return null;
+ }
+ }
+
+ // Create a media file name
+ String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
+ return new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg");
+ }
+}
diff --git a/main/src/cgeo/geocaching/VisitCacheActivity.java b/main/src/cgeo/geocaching/VisitCacheActivity.java
index d77be5f..b2c6625 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 Geocache 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().add(0, 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 8a4a741..8660ec4 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 5481b0c..23102c9 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCParser.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java
@@ -29,6 +29,7 @@ import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.ui.DirectionImage;
import cgeo.geocaching.utils.BaseUtils;
import cgeo.geocaching.utils.CancellableHandler;
+import cgeo.geocaching.utils.HtmlUtils;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.MatcherWrapper;
@@ -38,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;
@@ -45,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;
@@ -912,36 +915,20 @@ 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;
- }
-
- // fix log (non-Latin characters converted to HTML entities)
- final int logLen = log.length();
- final StringBuilder logUpdated = new StringBuilder();
-
- for (int i = 0; i < logLen; i++) {
- char c = log.charAt(i);
-
- if (c > 300) {
- logUpdated.append("&#");
- logUpdated.append(Integer.toString(c));
- logUpdated.append(';');
- } else {
- logUpdated.append(c);
- }
+ return new ImmutablePair<StatusCode, String>(StatusCode.NO_LOG_TEXT, "");
}
- final String logInfo = logUpdated.toString().replace("\n", "\r\n").trim(); // windows' eol and remove leading and trailing whitespaces
+ final String logInfo = HtmlUtils.convertNonLatinCharactersToHTML(log).replace("\n", "\r\n").trim(); // windows' eol and remove leading and trailing whitespaces
if (trackables != null) {
Log.i("Trying to post log for cache #" + cacheid + " - action: " + logType + "; date: " + year + "." + month + "." + day + ", log: " + logInfo + "; trackables: " + trackables.size());
@@ -981,7 +968,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
@@ -994,7 +981,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();
@@ -1048,16 +1035,98 @@ 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 caption
+ * of the image; max 50 chars
+ * @param description
+ * of the image; max 250 chars
+ * @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", HtmlUtils.convertNonLatinCharactersToHTML(caption),
+ "ctl00$ContentBody$ImageUploadControl1$uxFileDesc", HtmlUtils.convertNonLatinCharactersToHTML(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)) {
@@ -1072,7 +1141,7 @@ public abstract class GCParser {
Log.i("Trying to post log for trackable #" + trackingCode + " - action: " + logType + "; date: " + year + "." + month + "." + day + ", log: " + log);
- final String logInfo = log.replace("\n", "\r\n"); // windows' eol
+ final String logInfo = HtmlUtils.convertNonLatinCharactersToHTML(log).replace("\n", "\r\n"); // windows' eol
final Calendar currentDate = Calendar.getInstance();
final Parameters params = new Parameters(
diff --git a/main/src/cgeo/geocaching/network/Network.java b/main/src/cgeo/geocaching/network/Network.java
index 5c4148f..a4155be 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);
}
}
}
diff --git a/main/src/cgeo/geocaching/utils/HtmlUtils.java b/main/src/cgeo/geocaching/utils/HtmlUtils.java
index a54ba57..9b627ac 100644
--- a/main/src/cgeo/geocaching/utils/HtmlUtils.java
+++ b/main/src/cgeo/geocaching/utils/HtmlUtils.java
@@ -53,4 +53,29 @@ public class HtmlUtils {
return StringUtils.replace(result, "<br />", "\n").trim();
}
+ /**
+ * Convert any non-Latin characters into their HTML escaped equivalents
+ *
+ * @param input
+ * String
+ * @return output String
+ */
+ public static String convertNonLatinCharactersToHTML(final String input) {
+ final int inputLen = input.length();
+ final StringBuilder output = new StringBuilder();
+
+ for (int i = 0; i < inputLen; i++) {
+ char c = input.charAt(i);
+
+ if (c > 300) {
+ output.append("&#");
+ output.append(Integer.toString(c));
+ output.append(';');
+ } else {
+ output.append(c);
+ }
+ }
+
+ return output.toString();
+ }
}