package com.kenny.openimgur.fragments; import android.Manifest; import android.app.Activity; import android.content.ClipData; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityOptionsCompat; import android.support.v4.content.FileProvider; import android.support.v7.app.AlertDialog; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import com.kenny.openimgur.R; import com.kenny.openimgur.activities.UploadEditActivity; import com.kenny.openimgur.classes.OpengurApp; import com.kenny.openimgur.classes.Upload; import com.kenny.openimgur.classes.UploadListener; import com.kenny.openimgur.ui.adapters.UploadPhotoAdapter; import com.kenny.openimgur.util.FileUtil; import com.kenny.openimgur.util.LogUtil; import com.kenny.openimgur.util.PermissionUtils; import com.kenny.openimgur.util.RequestCodes; import com.kenny.openimgur.util.ViewUtils; import com.kennyc.view.MultiStateView; import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import butterknife.BindView; public class UploadFragment extends BaseFragment implements View.OnClickListener { private static final String KEY_PASSED_FILE = "passed_file"; private static final String KEY_PASSED_LINK = "passed_link"; private static final String KEY_PASSED_URIS = "passed_uri_list"; private static final String KEY_SAVED_ITEMS = "saved_items"; @BindView(R.id.multiView) MultiStateView mMultiView; @BindView(R.id.list) RecyclerView mRecyclerView; UploadPhotoAdapter mAdapter; File mTempFile; List<Uri> mPhotoUris = null; UploadListener mListener; public static UploadFragment newInstance(@Nullable Bundle args) { UploadFragment fragment = new UploadFragment(); fragment.setArguments(args); return fragment; } @Nullable public static Bundle createArguments(String path, boolean isLink, ArrayList<Uri> photoUris) { Bundle args = null; if (!TextUtils.isEmpty(path)) { args = new Bundle(1); if (!isLink) { args.putString(KEY_PASSED_FILE, path); } else { args.putString(KEY_PASSED_LINK, path); } return args; } else if (photoUris != null && !photoUris.isEmpty()) { args = new Bundle(1); args.putParcelableArrayList(KEY_PASSED_URIS, photoUris); } return args; } @Override public void onAttach(Activity activity) { super.onAttach(activity); if (activity instanceof UploadListener) mListener = (UploadListener) activity; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); } @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_upload, container, false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); Resources res = getResources(); ViewUtils.setRecyclerViewGridDefaults(getActivity(), mRecyclerView, res.getInteger(R.integer.upload_num_columns), res.getDimensionPixelSize(R.dimen.grid_padding)); ViewUtils.setEmptyText(mMultiView, R.id.emptyMessage, R.string.upload_empty_message); new ItemTouchHelper(mSimpleItemTouchCallback).attachToRecyclerView(mRecyclerView); if (savedInstanceState != null && savedInstanceState.containsKey(KEY_SAVED_ITEMS)) { List<Upload> uploads = savedInstanceState.getParcelableArrayList(KEY_SAVED_ITEMS); mAdapter = new UploadPhotoAdapter(getActivity(), uploads, this); mRecyclerView.setAdapter(mAdapter); mMultiView.setViewState(MultiStateView.VIEW_STATE_CONTENT); if (mListener != null) mListener.onPhotoAdded(); } else { handleArgs(getArguments()); } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.upload, menu); super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.camera: if (checkWritePermissions()) startCamera(); return true; case R.id.gallery: Intent intent = new Intent(); intent.setType("image/*"); // Allow multiple selection for API 18+ if (isApiLevel(Build.VERSION_CODES.JELLY_BEAN_MR2)) intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); intent.setAction(Intent.ACTION_GET_CONTENT); if (intent.resolveActivity(getActivity().getPackageManager()) != null) { startActivityForResult(intent, RequestCodes.SELECT_PHOTO); } else { // TODO? } return true; case R.id.link: getChildFragmentManager().beginTransaction() .add(UploadLinkDialogFragment.newInstance(null), UploadLinkDialogFragment.TAG) .commit(); return true; } return super.onOptionsItemSelected(item); } private void handleArgs(@Nullable Bundle args) { if (args == null) { mMultiView.setViewState(MultiStateView.VIEW_STATE_EMPTY); return; } List<Upload> uploads = null; if (args.containsKey(KEY_PASSED_FILE)) { LogUtil.v(TAG, "Received file from bundle"); uploads = new ArrayList<>(1); uploads.add(new Upload(args.getString(KEY_PASSED_FILE))); } else if (args.containsKey(KEY_PASSED_LINK)) { mMultiView.setViewState(MultiStateView.VIEW_STATE_EMPTY); String link = args.getString(KEY_PASSED_LINK); getChildFragmentManager().beginTransaction() .add(UploadLinkDialogFragment.newInstance(link), UploadLinkDialogFragment.TAG) .commit(); } else if (args.containsKey(KEY_PASSED_URIS)) { ArrayList<Uri> photoUris = args.getParcelableArrayList(KEY_PASSED_URIS); if (photoUris != null && !photoUris.isEmpty()) { LogUtil.v(TAG, "Received " + photoUris.size() + " images via Share intent"); new DecodeImagesTask(this).execute(photoUris); mMultiView.setViewState(MultiStateView.VIEW_STATE_LOADING); } } if (uploads != null && !uploads.isEmpty()) { mAdapter = new UploadPhotoAdapter(getActivity(), uploads, this); mRecyclerView.setAdapter(mAdapter); mMultiView.setViewState(MultiStateView.VIEW_STATE_CONTENT); } } void onUrisDecoded(@Nullable List<Upload> uploads) { if (uploads == null || uploads.isEmpty()) { boolean isEmpty = mAdapter == null || mAdapter.isEmpty(); mMultiView.setViewState(isEmpty ? MultiStateView.VIEW_STATE_EMPTY : MultiStateView.VIEW_STATE_CONTENT); } else { if (mAdapter == null) { mAdapter = new UploadPhotoAdapter(getActivity(), uploads, this); mRecyclerView.setAdapter(mAdapter); } else { mAdapter.addItems(uploads); } mMultiView.setViewState(MultiStateView.VIEW_STATE_CONTENT); } mPhotoUris = null; if (mListener != null) { if (mAdapter == null || mAdapter.isEmpty()) { mListener.onPhotoRemoved(0); } else { mListener.onPhotoAdded(); } } } void onNeedsReadPermission(List<Uri> photoUris) { mMultiView.setViewState(mAdapter != null && !mAdapter.isEmpty() ? MultiStateView.VIEW_STATE_CONTENT : MultiStateView.VIEW_STATE_EMPTY); mPhotoUris = photoUris; int level = PermissionUtils.getPermissionLevel(this, Manifest.permission.READ_EXTERNAL_STORAGE); switch (level) { // Should never have gotten here if its available case PermissionUtils.PERMISSION_AVAILABLE: new DecodeImagesTask(this).doInBackground(mPhotoUris); break; case PermissionUtils.PERMISSION_DENIED: int uriSize = photoUris != null ? photoUris.size() : 0; new AlertDialog.Builder(getActivity(), theme.getAlertDialogTheme()) .setTitle(R.string.permission_title) .setMessage(getResources().getQuantityString(R.plurals.permission_rational_file_access, uriSize, uriSize)) .setNegativeButton(R.string.cancel, null) .setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, RequestCodes.REQUEST_PERMISSION_READ); } }) .show(); break; case PermissionUtils.PERMISSION_NEVER_ASKED: ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, RequestCodes.REQUEST_PERMISSION_READ); break; } } /** * Checks if the needed permissions are available * * @return If the permissions are available. If false is returned, the necessary prompts will be shown */ private boolean checkWritePermissions() { @PermissionUtils.PermissionLevel int permissionLevel = PermissionUtils.getPermissionLevel(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); switch (permissionLevel) { case PermissionUtils.PERMISSION_AVAILABLE: LogUtil.v(TAG, "Permissions available"); return true; case PermissionUtils.PERMISSION_DENIED: new AlertDialog.Builder(getActivity(), app.getImgurTheme().getAlertDialogTheme()) .setTitle(R.string.permission_title) .setMessage(R.string.permission_rationale_upload) .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { Snackbar.make(mMultiView, R.string.permission_denied, Snackbar.LENGTH_LONG).show(); } }).setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, RequestCodes.REQUEST_PERMISSION_WRITE); } }).setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialogInterface) { Snackbar.make(mMultiView, R.string.permission_denied, Snackbar.LENGTH_LONG).show(); } }).show(); break; case PermissionUtils.PERMISSION_NEVER_ASKED: default: LogUtil.v(TAG, "Prompting for permissions"); ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, RequestCodes.REQUEST_PERMISSION_WRITE); break; } return false; } private void startCamera() { mTempFile = FileUtil.createFile(FileUtil.EXTENSION_JPEG); if (FileUtil.isFileValid(mTempFile)) { Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(getActivity(), OpengurApp.AUTHORITY, mTempFile)); if (intent.resolveActivity(getActivity().getPackageManager()) != null) { startActivityForResult(intent, RequestCodes.TAKE_PHOTO); } else { Snackbar.make(mMultiView, R.string.cant_launch_intent, Snackbar.LENGTH_LONG).show(); } } } @Nullable public ArrayList<Upload> getPhotosForUpload() { if (mAdapter != null && !mAdapter.isEmpty()) { return mAdapter.retainItems(); } return null; } public boolean hasPhotosForUpload() { return getPhotosForUpload() != null; } @Override public void onDestroyView() { if (mAdapter != null) mAdapter.onDestroy(); super.onDestroyView(); } public void onClick(View view) { int position = mRecyclerView.getChildAdapterPosition(view); if (position != RecyclerView.NO_POSITION) { Upload upload = mAdapter.getItem(position); Intent editIntent = UploadEditActivity.createIntent(getActivity(), upload); if (isApiLevel(Build.VERSION_CODES.LOLLIPOP)) { View v = view.findViewById(R.id.image); ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), v, getString(R.string.transition_upload_photo)); startActivityForResult(editIntent, RequestCodes.UPLOAD_EDIT, options.toBundle()); } else { startActivityForResult(editIntent, RequestCodes.UPLOAD_EDIT); } } } public void onLinkAdded(String link) { Upload upload = new Upload(link, true); if (mAdapter == null) { mAdapter = new UploadPhotoAdapter(getActivity(), upload, this); mRecyclerView.setAdapter(mAdapter); } else { mAdapter.addItem(upload); } mMultiView.setViewState(MultiStateView.VIEW_STATE_CONTENT); if (mListener != null) mListener.onPhotoAdded(); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case RequestCodes.REQUEST_PERMISSION_WRITE: if (PermissionUtils.verifyPermissions(grantResults)) { startCamera(); } else { Snackbar.make(mMultiView, R.string.permission_denied, Snackbar.LENGTH_LONG).show(); } break; case RequestCodes.REQUEST_PERMISSION_READ: if (PermissionUtils.verifyPermissions(grantResults)) { if (mPhotoUris != null) new DecodeImagesTask(this).execute(mPhotoUris); } else { Snackbar.make(mMultiView, R.string.permission_denied, Snackbar.LENGTH_LONG).show(); } break; } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case RequestCodes.TAKE_PHOTO: if (resultCode == Activity.RESULT_OK) { if (!FileUtil.isFileValid(mTempFile)) { Snackbar.make(mMultiView, R.string.upload_camera_error, Snackbar.LENGTH_LONG).show(); return; } FileUtil.scanFile(Uri.fromFile(mTempFile), getActivity()); String fileLocation = mTempFile.getAbsolutePath(); Upload upload = new Upload(fileLocation); if (mAdapter == null) { mAdapter = new UploadPhotoAdapter(getActivity(), upload, this); mRecyclerView.setAdapter(mAdapter); } else { mAdapter.addItem(upload); } mMultiView.setViewState(MultiStateView.VIEW_STATE_CONTENT); mTempFile = null; } break; case RequestCodes.SELECT_PHOTO: if (resultCode == Activity.RESULT_OK && data != null) { ClipData clipData = data.getClipData(); List<Uri> uris = null; // Check if we have multiple images if (clipData != null) { int size = clipData.getItemCount(); uris = new ArrayList<>(size); for (int i = 0; i < size; i++) { uris.add(clipData.getItemAt(i).getUri()); } } else if (data.getData() != null) { // If not multiple images, then only one was selected uris = new ArrayList<>(1); uris.add(data.getData()); } if (uris != null && !uris.isEmpty()) { mMultiView.setViewState(MultiStateView.VIEW_STATE_LOADING); new DecodeImagesTask(this).execute(uris); } else { Snackbar.make(mMultiView, R.string.error_generic, Snackbar.LENGTH_LONG).show(); } } break; case RequestCodes.UPLOAD_EDIT: if (resultCode == Activity.RESULT_OK && data != null) { final Upload upload; if (data.hasExtra(UploadEditActivity.KEY_UPDATED_UPLOAD)) { upload = data.getParcelableExtra(UploadEditActivity.KEY_UPDATED_UPLOAD); if (upload != null) { mAdapter.updateItem(upload); } } else if (data.hasExtra(UploadEditActivity.KEY_UPDATED_DELETED)) { upload = data.getParcelableExtra(UploadEditActivity.KEY_UPDATED_DELETED); if (upload != null) { final int itemIndex = mAdapter.indexOf(upload); if (itemIndex > -1) { mAdapter.removeItem(itemIndex); if (mListener != null) mListener.onPhotoRemoved(mAdapter.getItemCount()); if (mAdapter.isEmpty()) mMultiView.setViewState(MultiStateView.VIEW_STATE_EMPTY); Snackbar.make(mMultiView, R.string.upload_photo_removed, Snackbar.LENGTH_LONG) .setAction(R.string.undo, new View.OnClickListener() { @Override public void onClick(View v) { mMultiView.setViewState(MultiStateView.VIEW_STATE_CONTENT); mAdapter.addItem(upload, itemIndex); if (mListener != null) mListener.onPhotoAdded(); } }).show(); } } } } break; } super.onActivityResult(requestCode, resultCode, data); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (mAdapter != null && !mAdapter.isEmpty()) { outState.putParcelableArrayList(KEY_SAVED_ITEMS, mAdapter.retainItems()); } } private ItemTouchHelper.SimpleCallback mSimpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT, 0) { @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { return mAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition()); } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { //NOOP } }; private static class DecodeImagesTask extends AsyncTask<List<Uri>, Void, List<Upload>> { WeakReference<UploadFragment> mFragment; boolean needsPermission = false; List<Uri> photoUris = null; public DecodeImagesTask(@NonNull UploadFragment fragment) { mFragment = new WeakReference<>(fragment); } @Override protected List<Upload> doInBackground(List<Uri>... params) { if (params != null && params[0] != null && !params[0].isEmpty()) { Activity activity = mFragment.get().getActivity(); photoUris = params[0]; List<Upload> uploads = new ArrayList<>(photoUris.size()); boolean hasReadPermission = PermissionUtils.hasPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE); for (Uri uri : photoUris) { try { // Check if the URI is a file as we will need read permissions for it if (!hasReadPermission && "file".equals(uri.getScheme())) { LogUtil.v("DecodeImageTask", "Received a File URI and don't have permissions"); needsPermission = true; return null; } File file = FileUtil.createFile(uri, activity); if (FileUtil.isFileValid(file)) uploads.add(new Upload(file.getAbsolutePath())); } catch (Throwable ex) { LogUtil.e("DecodeImageTask", "Unable to decode image", ex); } } return uploads; } return null; } @Override protected void onPostExecute(List<Upload> uploads) { if (mFragment != null && mFragment.get() != null) { UploadFragment fragment = mFragment.get(); if (!fragment.isAdded()) { mFragment.clear(); mFragment = null; return; } if (needsPermission) { fragment.onNeedsReadPermission(photoUris); } else { fragment.onUrisDecoded(uploads); } mFragment.clear(); mFragment = null; } } } }