From f3d905c817c29798755fca863775a90ce0b511e1 Mon Sep 17 00:00:00 2001 From: Alex Ning Date: Sun, 4 Jul 2021 22:46:15 +0800 Subject: [PATCH] Save image name and image url for uploaded images when uploading images. Uploading captured images is available. --- .../UploadImageEnabledActivity.java | 7 + .../infinityforreddit/UploadedImage.java | 42 +++++ .../activities/CommentActivity.java | 148 ++++++++++++++---- .../UploadedImagesRecyclerViewAdapter.java | 61 ++++++++ .../UploadedImagesBottomSheetFragment.java | 67 ++++++++ .../fragment_uploaded_images_bottom_sheet.xml | 53 +++++++ .../main/res/layout/item_uploaded_image.xml | 28 ++++ app/src/main/res/values/strings.xml | 3 + 8 files changed, 379 insertions(+), 30 deletions(-) create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/UploadImageEnabledActivity.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/UploadedImage.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/adapters/UploadedImagesRecyclerViewAdapter.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/UploadedImagesBottomSheetFragment.java create mode 100644 app/src/main/res/layout/fragment_uploaded_images_bottom_sheet.xml create mode 100644 app/src/main/res/layout/item_uploaded_image.xml diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/UploadImageEnabledActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/UploadImageEnabledActivity.java new file mode 100644 index 00000000..aecde88f --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/UploadImageEnabledActivity.java @@ -0,0 +1,7 @@ +package ml.docilealligator.infinityforreddit; + +public interface UploadImageEnabledActivity { + void uploadImage(); + void captureImage(); + void insertImageUrl(UploadedImage uploadedImage); +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/UploadedImage.java b/app/src/main/java/ml/docilealligator/infinityforreddit/UploadedImage.java new file mode 100644 index 00000000..e3d28734 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/UploadedImage.java @@ -0,0 +1,42 @@ +package ml.docilealligator.infinityforreddit; + +import android.os.Parcel; +import android.os.Parcelable; + +public class UploadedImage implements Parcelable { + public String imageName; + public String imageUrl; + + public UploadedImage(String imageName, String imageUrl) { + this.imageName = imageName; + this.imageUrl = imageUrl; + } + + protected UploadedImage(Parcel in) { + imageName = in.readString(); + imageUrl = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public UploadedImage createFromParcel(Parcel in) { + return new UploadedImage(in); + } + + @Override + public UploadedImage[] newArray(int size) { + return new UploadedImage[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeString(imageName); + parcel.writeString(imageUrl); + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/CommentActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/CommentActivity.java index 9262eea2..93e4e2a8 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/CommentActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/CommentActivity.java @@ -1,13 +1,19 @@ package ml.docilealligator.infinityforreddit.activities; +import android.content.ActivityNotFoundException; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Environment; import android.os.Handler; +import android.provider.MediaStore; +import android.provider.OpenableColumns; import android.text.Spanned; import android.text.util.Linkify; import android.view.Menu; @@ -22,6 +28,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.content.FileProvider; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -36,7 +43,9 @@ import org.greenrobot.eventbus.Subscribe; import org.json.JSONException; import org.xmlpull.v1.XmlPullParserException; +import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -57,8 +66,11 @@ import io.noties.markwon.recycler.table.TableEntry; import io.noties.markwon.recycler.table.TableEntryPlugin; import ml.docilealligator.infinityforreddit.Infinity; import ml.docilealligator.infinityforreddit.R; +import ml.docilealligator.infinityforreddit.UploadImageEnabledActivity; +import ml.docilealligator.infinityforreddit.UploadedImage; import ml.docilealligator.infinityforreddit.adapters.MarkdownBottomBarRecyclerViewAdapter; import ml.docilealligator.infinityforreddit.bottomsheetfragments.CopyTextBottomSheetFragment; +import ml.docilealligator.infinityforreddit.bottomsheetfragments.UploadedImagesBottomSheetFragment; import ml.docilealligator.infinityforreddit.comment.Comment; import ml.docilealligator.infinityforreddit.comment.SendComment; import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; @@ -68,7 +80,7 @@ import ml.docilealligator.infinityforreddit.utils.UploadImageUtils; import ml.docilealligator.infinityforreddit.utils.Utils; import retrofit2.Retrofit; -public class CommentActivity extends BaseActivity { +public class CommentActivity extends BaseActivity implements UploadImageEnabledActivity { public static final String EXTRA_COMMENT_PARENT_TEXT_KEY = "ECPTK"; public static final String EXTRA_COMMENT_PARENT_TEXT_MARKDOWN_KEY = "ECPTMK"; @@ -81,6 +93,8 @@ public class CommentActivity extends BaseActivity { public static final String RETURN_EXTRA_COMMENT_DATA_KEY = "RECDK"; public static final int WRITE_COMMENT_REQUEST_CODE = 1; private static final int PICK_IMAGE_REQUEST_CODE = 100; + private static final int CAPTURE_IMAGE_REQUEST_CODE = 200; + private static final String UPLOADED_IMAGES_STATE = "UIS"; @BindView(R.id.coordinator_layout_comment_activity) CoordinatorLayout coordinatorLayout; @@ -121,6 +135,8 @@ public class CommentActivity extends BaseActivity { private boolean isSubmitting = false; private boolean isReplying; private int markdownColor; + private Uri capturedImageUri; + private ArrayList uploadedImages = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { @@ -247,6 +263,10 @@ public class CommentActivity extends BaseActivity { setSupportActionBar(toolbar); + if (savedInstanceState != null) { + uploadedImages = savedInstanceState.getParcelableArrayList(UPLOADED_IMAGES_STATE); + } + MarkdownBottomBarRecyclerViewAdapter adapter = new MarkdownBottomBarRecyclerViewAdapter( mCustomThemeWrapper, new MarkdownBottomBarRecyclerViewAdapter.ItemClickListener() { @Override @@ -257,11 +277,13 @@ public class CommentActivity extends BaseActivity { @Override public void onUploadImage() { - Intent intent = new Intent(); - intent.setType("image/*"); - intent.setAction(Intent.ACTION_GET_CONTENT); - startActivityForResult(Intent.createChooser(intent, - getResources().getString(R.string.select_from_gallery)), PICK_IMAGE_REQUEST_CODE); + Utils.hideKeyboard(CommentActivity.this); + UploadedImagesBottomSheetFragment fragment = new UploadedImagesBottomSheetFragment(); + Bundle arguments = new Bundle(); + arguments.putParcelableArrayList(UploadedImagesBottomSheetFragment.EXTRA_UPLOADED_IMAGES, + uploadedImages); + fragment.setArguments(arguments); + fragment.show(getSupportFragmentManager(), fragment.getTag()); } }); @@ -276,6 +298,12 @@ public class CommentActivity extends BaseActivity { } } + @Override + protected void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelableArrayList(UPLOADED_IMAGES_STATE, uploadedImages); + } + @Override public SharedPreferences getDefaultSharedPreferences() { return mSharedPreferences; @@ -397,34 +425,46 @@ public class CommentActivity extends BaseActivity { Toast.makeText(CommentActivity.this, R.string.error_getting_image, Toast.LENGTH_LONG).show(); return; } - Handler handler = new Handler(); - mExecutor.execute(() -> { - try { - Bitmap bitmap = Glide.with(CommentActivity.this).asBitmap().load(data.getData()).submit().get(); - String imageUrlOrError = UploadImageUtils.uploadImage(mOauthRetrofit, mUploadMediaRetrofit, mAccessToken, bitmap); - handler.post(() -> { - if (imageUrlOrError != null && !imageUrlOrError.startsWith("Error: ")) { - int start = Math.max(commentEditText.getSelectionStart(), 0); - int end = Math.max(commentEditText.getSelectionEnd(), 0); - commentEditText.getText().replace(Math.min(start, end), Math.max(start, end), - "[" + imageUrlOrError + "](" + imageUrlOrError + ")", - 0, "[]()".length() + imageUrlOrError.length() + imageUrlOrError.length()); - } else { - Toast.makeText(CommentActivity.this, R.string.upload_image_failed, Toast.LENGTH_LONG).show(); - } - }); - } catch (ExecutionException | InterruptedException e) { - e.printStackTrace(); - handler.post(() -> Toast.makeText(CommentActivity.this, R.string.get_image_bitmap_failed, Toast.LENGTH_LONG).show()); - } catch (XmlPullParserException | JSONException | IOException e) { - e.printStackTrace(); - handler.post(() -> Toast.makeText(CommentActivity.this, R.string.error_processing_image, Toast.LENGTH_LONG).show()); - } - }); + uploadImageToReddit(data.getData()); + } else if (requestCode == CAPTURE_IMAGE_REQUEST_CODE) { + uploadImageToReddit(capturedImageUri); } } } + private void uploadImageToReddit(Uri imageUri) { + Handler handler = new Handler(); + mExecutor.execute(() -> { + try { + Bitmap bitmap = Glide.with(CommentActivity.this).asBitmap().load(imageUri).submit().get(); + String imageUrlOrError = UploadImageUtils.uploadImage(mOauthRetrofit, mUploadMediaRetrofit, mAccessToken, bitmap); + handler.post(() -> { + if (imageUrlOrError != null && !imageUrlOrError.startsWith("Error: ")) { + String fileName = getFileName(imageUri); + if (fileName == null) { + fileName = imageUrlOrError; + } + uploadedImages.add(new UploadedImage(fileName, imageUrlOrError)); + + int start = Math.max(commentEditText.getSelectionStart(), 0); + int end = Math.max(commentEditText.getSelectionEnd(), 0); + commentEditText.getText().replace(Math.min(start, end), Math.max(start, end), + "[" + imageUrlOrError + "](" + imageUrlOrError + ")", + 0, "[]()".length() + imageUrlOrError.length() + imageUrlOrError.length()); + } else { + Toast.makeText(CommentActivity.this, R.string.upload_image_failed, Toast.LENGTH_LONG).show(); + } + }); + } catch (ExecutionException | InterruptedException e) { + e.printStackTrace(); + handler.post(() -> Toast.makeText(CommentActivity.this, R.string.get_image_bitmap_failed, Toast.LENGTH_LONG).show()); + } catch (XmlPullParserException | JSONException | IOException e) { + e.printStackTrace(); + handler.post(() -> Toast.makeText(CommentActivity.this, R.string.error_processing_image, Toast.LENGTH_LONG).show()); + } + }); + } + @Override public void onBackPressed() { if (isSubmitting) { @@ -448,4 +488,52 @@ public class CommentActivity extends BaseActivity { public void onAccountSwitchEvent(SwitchAccountEvent event) { finish(); } + + @Nullable + private String getFileName(Uri uri) { + ContentResolver contentResolver = getContentResolver(); + if (contentResolver != null) { + Cursor cursor = contentResolver.query(uri, null, null, null, null); + if (cursor != null) { + int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + cursor.moveToFirst(); + return cursor.getString(nameIndex); + } + } + + return null; + } + + @Override + public void uploadImage() { + Intent intent = new Intent(); + intent.setType("image/*"); + intent.setAction(Intent.ACTION_GET_CONTENT); + startActivityForResult(Intent.createChooser(intent, + getResources().getString(R.string.select_from_gallery)), PICK_IMAGE_REQUEST_CODE); + } + + @Override + public void captureImage() { + Intent pictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + try { + capturedImageUri = FileProvider.getUriForFile(this, "ml.docilealligator.infinityforreddit.provider", + File.createTempFile("captured_image", ".jpg", getExternalFilesDir(Environment.DIRECTORY_PICTURES))); + pictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, capturedImageUri); + startActivityForResult(pictureIntent, CAPTURE_IMAGE_REQUEST_CODE); + } catch (IOException ex) { + Toast.makeText(this, R.string.error_creating_temp_file, Toast.LENGTH_SHORT).show(); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.no_camera_available, Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void insertImageUrl(UploadedImage uploadedImage) { + int start = Math.max(commentEditText.getSelectionStart(), 0); + int end = Math.max(commentEditText.getSelectionEnd(), 0); + commentEditText.getText().replace(Math.min(start, end), Math.max(start, end), + "[" + uploadedImage.imageName + "](" + uploadedImage.imageUrl + ")", + 0, "[]()".length() + uploadedImage.imageName.length() + uploadedImage.imageUrl.length()); + } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/UploadedImagesRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/UploadedImagesRecyclerViewAdapter.java new file mode 100644 index 00000000..eebd6482 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/UploadedImagesRecyclerViewAdapter.java @@ -0,0 +1,61 @@ +package ml.docilealligator.infinityforreddit.adapters; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; + +import ml.docilealligator.infinityforreddit.R; +import ml.docilealligator.infinityforreddit.UploadedImage; + +public class UploadedImagesRecyclerViewAdapter extends RecyclerView.Adapter { + private ArrayList uploadedImages; + private ItemClickListener itemClickListener; + + public UploadedImagesRecyclerViewAdapter(ArrayList uploadedImages, ItemClickListener itemClickListener) { + this.uploadedImages = uploadedImages; + this.itemClickListener = itemClickListener; + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new UploadedImageViewHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_uploaded_image, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + ((UploadedImageViewHolder) holder).imageNameTextView.setText(uploadedImages.get(position).imageName); + ((UploadedImageViewHolder) holder).imageUrlTextView.setText(uploadedImages.get(position).imageUrl); + } + + @Override + public int getItemCount() { + return uploadedImages == null ? 0 : uploadedImages.size(); + } + + private class UploadedImageViewHolder extends RecyclerView.ViewHolder { + TextView imageNameTextView; + TextView imageUrlTextView; + + public UploadedImageViewHolder(@NonNull View itemView) { + super(itemView); + imageNameTextView = itemView.findViewById(R.id.image_name_item_uploaded_image); + imageUrlTextView = itemView.findViewById(R.id.image_url_item_uploaded_image); + + itemView.setOnClickListener(view -> { + itemClickListener.onClick(uploadedImages.get(getBindingAdapterPosition())); + }); + } + } + + public interface ItemClickListener { + void onClick(UploadedImage uploadedImage); + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/UploadedImagesBottomSheetFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/UploadedImagesBottomSheetFragment.java new file mode 100644 index 00000000..cc100c79 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/UploadedImagesBottomSheetFragment.java @@ -0,0 +1,67 @@ +package ml.docilealligator.infinityforreddit.bottomsheetfragments; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.deishelon.roundedbottomsheet.RoundedBottomSheetDialogFragment; +import com.google.android.material.button.MaterialButton; + +import ml.docilealligator.infinityforreddit.R; +import ml.docilealligator.infinityforreddit.UploadImageEnabledActivity; +import ml.docilealligator.infinityforreddit.adapters.UploadedImagesRecyclerViewAdapter; + +public class UploadedImagesBottomSheetFragment extends RoundedBottomSheetDialogFragment { + + public static final String EXTRA_UPLOADED_IMAGES = "EUI"; + + private MaterialButton uploadButton; + private MaterialButton captureButton; + private RecyclerView uploadedImagesRecyclerView; + private UploadedImagesRecyclerViewAdapter adapter; + private UploadImageEnabledActivity activity; + + public UploadedImagesBottomSheetFragment() { + // Required empty public constructor + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View rootView = inflater.inflate(R.layout.fragment_uploaded_images_bottom_sheet, container, false); + uploadButton = rootView.findViewById(R.id.upload_button_uploaded_images_bottom_sheet_fragment); + captureButton = rootView.findViewById(R.id.capture_button_uploaded_images_bottom_sheet_fragment); + + uploadButton.setOnClickListener(view -> { + activity.uploadImage(); + dismiss(); + }); + + captureButton.setOnClickListener(view -> { + activity.captureImage(); + dismiss(); + }); + + uploadedImagesRecyclerView = rootView.findViewById(R.id.recycler_view_uploaded_images_bottom_sheet); + adapter = new UploadedImagesRecyclerViewAdapter( + getArguments().getParcelableArrayList(EXTRA_UPLOADED_IMAGES), uploadedImage -> { + activity.insertImageUrl(uploadedImage); + dismiss(); + }); + uploadedImagesRecyclerView.setAdapter(adapter); + + return rootView; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + this.activity = (UploadImageEnabledActivity) context; + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_uploaded_images_bottom_sheet.xml b/app/src/main/res/layout/fragment_uploaded_images_bottom_sheet.xml new file mode 100644 index 00000000..4eb8005c --- /dev/null +++ b/app/src/main/res/layout/fragment_uploaded_images_bottom_sheet.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_uploaded_image.xml b/app/src/main/res/layout/item_uploaded_image.xml new file mode 100644 index 00000000..6fb4b589 --- /dev/null +++ b/app/src/main/res/layout/item_uploaded_image.xml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cf4d8871..468d7d6d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1118,6 +1118,9 @@ Reply + Uploaded Images + Upload + Capture Unable to get the bitmap of the image Unable to upload the image