diff options
| author | rsudev <rasch@munin-soft.de> | 2013-02-23 03:26:57 -0800 |
|---|---|---|
| committer | rsudev <rasch@munin-soft.de> | 2013-02-23 03:26:57 -0800 |
| commit | 9ca04d070303afbad0154b2f5d8575a93c942123 (patch) | |
| tree | 4c7adec4cc1f6f7515d40d6d418bc277abeee486 /main | |
| parent | 6cdda77f8bb78440f706dcc637b20b1d3a1a7685 (diff) | |
| parent | deb10abf3c288b7c453626e5126ebc912a24bc03 (diff) | |
| download | cgeo-9ca04d070303afbad0154b2f5d8575a93c942123.zip cgeo-9ca04d070303afbad0154b2f5d8575a93c942123.tar.gz cgeo-9ca04d070303afbad0154b2f5d8575a93c942123.tar.bz2 | |
Merge pull request #2494 from rsudev/6b
Image upload, implements #6
Diffstat (limited to 'main')
| -rw-r--r-- | main/AndroidManifest.xml | 5 | ||||
| -rw-r--r-- | main/res/layout/visit.xml | 3 | ||||
| -rw-r--r-- | main/res/layout/visit_image.xml | 123 | ||||
| -rw-r--r-- | main/res/values/strings.xml | 14 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/ImageSelectActivity.java | 253 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/VisitCacheActivity.java | 88 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/compatibility/AndroidLevel8.java | 9 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/compatibility/AndroidLevel8Emulation.java | 9 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/compatibility/AndroidLevel8Interface.java | 4 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/compatibility/Compatibility.java | 6 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/connector/gc/GCConstants.java | 3 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/connector/gc/GCParser.java | 95 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/enumerations/StatusCode.java | 3 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/network/Network.java | 4 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/utils/HtmlUtils.java | 25 |
15 files changed, 607 insertions, 37 deletions
diff --git a/main/AndroidManifest.xml b/main/AndroidManifest.xml index aa1fd3a..35cac8a 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" @@ -226,5 +227,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..0b18f8f --- /dev/null +++ b/main/res/layout/visit_image.xml @@ -0,0 +1,123 @@ +<?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: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 56c2a7f..0382071 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> @@ -166,6 +174,8 @@ <string name="err_location_unknown">c:geo doesn\'t know location of cache.</string> <string name="err_missing_device_name">Please enter a device name before registering.</string> <string name="err_favorite_failed">Changing favorite status failed.</string> + <string name="err_select_logimage_failed">Selecting an image for the log failed.</string> + <string name="err_aquire_image_failed">Acquiring an image failed.</string> <string name="err_tb_display">c:geo can\'t display trackable you want. Is it really a trackable?</string> <string name="err_tb_details_open">c:geo can\'t open trackable details.</string> @@ -188,6 +198,7 @@ <string name="err_log_load_data_still">c:geo is still loading data required to post log. Please wait a little while longer.</string> <string name="err_log_failed_server">c:geo failed to post log because server is not responding.</string> <string name="err_log_post_failed">It seems that your log was not posted. Please check it on Geocaching.com.</string> + <string name="err_logimage_post_failed">It seems that your log image was not uploaded. Please check it on Geocaching.com.</string> <string name="err_search_address_forgot">c:geo forgot the address you tried to find.</string> <string name="err_parse_lat">c:geo can\'t parse latitude.</string> @@ -213,11 +224,12 @@ <string name="warn_nonexistant_mapfile">The selected map file does not exist.\nOffline maps are not available.</string> <string name="warn_rendertheme_missing">Map theme not found.</string> - <string name="info_log_posted">c:geo successfully submitted the log.</string> <string name="info_log_saved">c:geo successfully saved the log.</string> <string name="info_log_cleared">Log was cleared.</string> <string name="info_log_type_changed">Type of log has been changed!</string> + <string name="info_select_logimage_cancelled">Image selection or capture was cancelled.</string> + <string name="info_stored_image">New image saved to:</string> <string name="info_storing_static_maps">Trying to store static maps</string> diff --git a/main/src/cgeo/geocaching/ImageSelectActivity.java b/main/src/cgeo/geocaching/ImageSelectActivity.java new file mode 100644 index 0000000..9b29c38 --- /dev/null +++ b/main/src/cgeo/geocaching/ImageSelectActivity.java @@ -0,0 +1,253 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.compatibility.Compatibility; +import cgeo.geocaching.utils.Log; + +import org.apache.commons.lang3.StringUtils; + +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.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("c:geo-selectimage"); + } + + @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 + protected void onSaveInstanceState(final Bundle outState) { + super.onSaveInstanceState(outState); + syncEditTexts(); + 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(); + syncEditTexts(); + 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 syncEditTexts() { + 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.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(getResources().getString(R.string.info_select_logimage_cancelled)); + return; + } + + if (resultCode == RESULT_OK) { + if (data != null) { + 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(); + + Log.d("SELECT IMAGE data = " + data.toString()); + } else { + Log.d("SELECT IMAGE data is null"); + } + + if (requestCode == SELECT_NEW_IMAGE) { + showToast(getResources().getString(R.string.info_stored_image) + "\n" + imageUri); + } + } else { + // Image capture failed, advise user + showToast(getResources().getString(R.string.err_aquire_image_failed)); + return; + } + + loadImagePreview(); + } + + private void loadImagePreview() + { + if (!new File(imageUri.getPath()).exists()) { + Log.i("Image does not exist"); + 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 */ + 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(Compatibility.getExternalPictureDir(), "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..43c439b 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); @@ -311,6 +324,7 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD } } updatePostButtonText(); + setImageButtonText(); enablePostButton(false); final Button typeButton = (Button) findViewById(R.id.type); @@ -334,6 +348,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 +397,9 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD } } text = null; + imageCaption = ""; + imageDescription = ""; + imageUri = Uri.EMPTY; } private void clearLog() { @@ -387,6 +413,8 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD final EditText logView = (EditText) findViewById(R.id.log); logView.setText(StringUtils.EMPTY); + setImageButtonText(); + showToast(res.getString(R.string.info_log_cleared)); } @@ -454,6 +482,9 @@ 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()); + outState.putString(SAVED_STATE_IMAGE_CAPTION, imageCaption); + outState.putString(SAVED_STATE_IMAGE_DESCRIPTION, imageDescription); } @Override @@ -497,7 +528,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()); @@ -524,38 +556,48 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD } public StatusCode postLogFn(String log) { + + StatusCode result = StatusCode.LOG_POST_ERROR; + try { - final StatusCode status = GCParser.postLog(geocode, cacheid, viewstates, typeSelected, + + 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) { + result = logResult.left; + + if (logResult.left == StatusCode.NO_ERROR) { final LogEntry logNow = new LogEntry(date, typeSelected, log); cache.getLogs().add(0, logNow); - if (typeSelected == LogType.FOUND_IT) { + if (typeSelected == LogType.FOUND_IT || typeSelected == LogType.ATTENDED) { cache.setFound(true); } 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 (logResult.left == StatusCode.NO_ERROR && StringUtils.isNotBlank(imageUri.getPath())) { + result = GCParser.uploadLogImage(logResult.right, imageCaption, imageDescription, imageUri); + } + + return result; } catch (Exception e) { Log.e("cgeovisit.postLogFn", e); } @@ -651,4 +693,34 @@ 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)); + } else if (resultCode != RESULT_CANCELED) { + // Image capture failed, advise user + showToast(getResources().getString(R.string.err_select_logimage_failed)); + } + setImageButtonText(); + + } + } + + private void setImageButtonText() { + 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/compatibility/AndroidLevel8.java b/main/src/cgeo/geocaching/compatibility/AndroidLevel8.java index a388adb..e250934 100644 --- a/main/src/cgeo/geocaching/compatibility/AndroidLevel8.java +++ b/main/src/cgeo/geocaching/compatibility/AndroidLevel8.java @@ -5,9 +5,12 @@ import cgeo.geocaching.utils.Log; import android.annotation.TargetApi; import android.app.Activity; import android.app.backup.BackupManager; +import android.os.Environment; import android.view.Display; import android.view.Surface; +import java.io.File; + @TargetApi(8) public class AndroidLevel8 implements AndroidLevel8Interface { @@ -41,4 +44,10 @@ public class AndroidLevel8 implements AndroidLevel8Interface { return 0; } + + @Override + public File getExternalPictureDir() { + return Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES); + } } diff --git a/main/src/cgeo/geocaching/compatibility/AndroidLevel8Emulation.java b/main/src/cgeo/geocaching/compatibility/AndroidLevel8Emulation.java index 197993d..996c527 100644 --- a/main/src/cgeo/geocaching/compatibility/AndroidLevel8Emulation.java +++ b/main/src/cgeo/geocaching/compatibility/AndroidLevel8Emulation.java @@ -3,8 +3,11 @@ package cgeo.geocaching.compatibility; import android.annotation.TargetApi; import android.app.Activity; import android.content.res.Configuration; +import android.os.Environment; import android.view.Display; +import java.io.File; + @TargetApi(value = 7) public class AndroidLevel8Emulation implements AndroidLevel8Interface { @@ -27,4 +30,10 @@ public class AndroidLevel8Emulation implements AndroidLevel8Interface { } return 0; } + + @Override + public File getExternalPictureDir() { + // Use externalStorage/Pictures as default + return new File(Environment.getExternalStorageDirectory(), "Pictures"); + } } diff --git a/main/src/cgeo/geocaching/compatibility/AndroidLevel8Interface.java b/main/src/cgeo/geocaching/compatibility/AndroidLevel8Interface.java index 761c23a..75998aa 100644 --- a/main/src/cgeo/geocaching/compatibility/AndroidLevel8Interface.java +++ b/main/src/cgeo/geocaching/compatibility/AndroidLevel8Interface.java @@ -2,9 +2,13 @@ package cgeo.geocaching.compatibility; import android.app.Activity; +import java.io.File; + public interface AndroidLevel8Interface { public int getRotation(final Activity activity); public void dataChanged(final String name); public int getRotationOffset(final Activity activity); + + public File getExternalPictureDir(); }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/compatibility/Compatibility.java b/main/src/cgeo/geocaching/compatibility/Compatibility.java index 05a3331..d846bda 100644 --- a/main/src/cgeo/geocaching/compatibility/Compatibility.java +++ b/main/src/cgeo/geocaching/compatibility/Compatibility.java @@ -13,6 +13,8 @@ import android.os.Build; import android.text.InputType; import android.widget.EditText; +import java.io.File; + public final class Compatibility { private final static int sdkVersion = Build.VERSION.SDK_INT; @@ -108,4 +110,8 @@ public final class Compatibility { return level13.getDisplaySize(); } + public static File getExternalPictureDir() { + return level8.getExternalPictureDir(); + } + } diff --git a/main/src/cgeo/geocaching/connector/gc/GCConstants.java b/main/src/cgeo/geocaching/connector/gc/GCConstants.java index 8d58332..f678797 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConstants.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConstants.java @@ -149,6 +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_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*'([^']+)'"); @@ -163,6 +164,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..494e040 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; + return new ImmutablePair<StatusCode, String>(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); - } - } - - 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,14 +1035,72 @@ 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("Image 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)); + + MatcherWrapper matcherOK = new MatcherWrapper(GCConstants.PATTERN_OK_IMAGEUPLOAD, response); + + if (matcherOK.find()) { + Log.i("Logimage successfully uploaded."); + + return StatusCode.NO_ERROR; + } + Log.e("GCParser.uploadLogIMage: Failed to upload image because of unknown error"); + + return StatusCode.LOGIMAGE_POST_ERROR; } public static StatusCode postLogTrackable(final String tbid, final String trackingCode, final String[] viewstates, @@ -1072,7 +1117,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/enumerations/StatusCode.java b/main/src/cgeo/geocaching/enumerations/StatusCode.java index dc62225..102b9e9 100644 --- a/main/src/cgeo/geocaching/enumerations/StatusCode.java +++ b/main/src/cgeo/geocaching/enumerations/StatusCode.java @@ -23,7 +23,8 @@ public enum StatusCode { LOG_POST_ERROR(R.string.err_log_post_failed), NO_LOG_TEXT(R.string.warn_log_text_fill), NO_DATA_FROM_SERVER(R.string.err_log_failed_server), - NOT_LOGGED_IN(R.string.init_login_popup_failed); + NOT_LOGGED_IN(R.string.init_login_popup_failed), + LOGIMAGE_POST_ERROR(R.string.err_logimage_post_failed); final private int error_string; 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..30aa19b 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 HTML unicode entities + * + * @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(); + } } |
