mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-30 06:13:54 +01:00
Add module for file picker for camera and gallery uploads (#2375)
* Use easy image for image picker * Do not use harcoded mime type * Use uploadable file for image uploads * Add picker files in filepicker module * Remove redundant checks for file * Make usage of file extensions consistent * Add javadocs * Fix tests * Enable image upload using bookmark activity * Fix multiple uploads * Fix external image uploads * Fix chooser intents * Fix image quality checks * Segregate internal and external upload intents * Invoke all error messages from one place * Minor changes * Fix tests * Add image processing service tests
This commit is contained in:
parent
fb5a40bba5
commit
52ab39381e
39 changed files with 1553 additions and 574 deletions
|
|
@ -4,119 +4,142 @@ import android.Manifest;
|
|||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.esafirm.imagepicker.features.ImagePicker;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.media.FrescoImageLoader;
|
||||
import fr.free.nrw.commons.filepicker.DefaultCallback;
|
||||
import fr.free.nrw.commons.filepicker.FilePicker;
|
||||
import fr.free.nrw.commons.kvstore.BasicKvStore;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import fr.free.nrw.commons.upload.UploadActivity;
|
||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
|
||||
import static android.content.Intent.ACTION_SEND_MULTIPLE;
|
||||
import static android.content.Intent.EXTRA_STREAM;
|
||||
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_CAMERA;
|
||||
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_EXTERNAL;
|
||||
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_GALLERY;
|
||||
import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES;
|
||||
import static fr.free.nrw.commons.upload.UploadService.EXTRA_SOURCE;
|
||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
||||
|
||||
@Singleton
|
||||
public class ContributionController {
|
||||
|
||||
//request codes
|
||||
public static final int CAMERA_UPLOAD_REQUEST_CODE = 10011;
|
||||
public static final int GALLERY_UPLOAD_REQUEST_CODE = 10012;
|
||||
public static final int NEARBY_CAMERA_UPLOAD_REQUEST_CODE = 10013;
|
||||
public static final int NEARBY_GALLERY_UPLOAD_REQUEST_CODE = 10014;
|
||||
public static final int BOOKMARK_CAMERA_UPLOAD_REQUEST_CODE = 10015;
|
||||
public static final int BOOKMARK_GALLERY_UPLOAD_REQUEST_CODE = 10016;
|
||||
public static final String ACTION_INTERNAL_UPLOADS = "internalImageUploads";
|
||||
|
||||
//upload limits
|
||||
public static final int MULTIPLE_UPLOAD_IMAGE_LIMIT = 5;
|
||||
public static final int NEARBY_UPLOAD_IMAGE_LIMIT = 1;
|
||||
|
||||
private final Context context;
|
||||
private final BasicKvStore defaultKvStore;
|
||||
private final JsonKvStore directKvStore;
|
||||
|
||||
@Inject
|
||||
public ContributionController(Context context,
|
||||
@Named("default_preferences") BasicKvStore defaultKvStore,
|
||||
public ContributionController(@Named("default_preferences") BasicKvStore defaultKvStore,
|
||||
@Named("direct_nearby_upload_prefs") JsonKvStore directKvStore) {
|
||||
this.context = context;
|
||||
this.defaultKvStore = defaultKvStore;
|
||||
this.directKvStore = directKvStore;
|
||||
}
|
||||
|
||||
public void initiateCameraPick(Activity activity,
|
||||
int requestCode) {
|
||||
/**
|
||||
* Check for permissions and initiate camera click
|
||||
*/
|
||||
public void initiateCameraPick(Activity activity) {
|
||||
boolean useExtStorage = defaultKvStore.getBoolean("useExternalStorage", true);
|
||||
if (!useExtStorage) {
|
||||
initiateCameraUpload(activity, requestCode);
|
||||
initiateCameraUpload(activity);
|
||||
return;
|
||||
}
|
||||
|
||||
PermissionUtils.checkPermissionsAndPerformAction(activity,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
() -> initiateCameraUpload(activity, requestCode),
|
||||
() -> initiateCameraUpload(activity),
|
||||
R.string.storage_permission_title,
|
||||
R.string.write_storage_permission_rationale);
|
||||
}
|
||||
|
||||
public void initiateGalleryPick(Activity activity,
|
||||
int imageLimit,
|
||||
int requestCode) {
|
||||
/**
|
||||
* Check for permissions and initiate gallery picker
|
||||
*/
|
||||
public void initiateGalleryPick(Activity activity, boolean allowMultipleUploads) {
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
initiateGalleryUpload(activity, imageLimit, requestCode);
|
||||
initiateGalleryUpload(activity, allowMultipleUploads);
|
||||
} else {
|
||||
PermissionUtils.checkPermissionsAndPerformAction(activity,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
() -> initiateGalleryUpload(activity, imageLimit, requestCode),
|
||||
() -> initiateGalleryUpload(activity, allowMultipleUploads),
|
||||
R.string.storage_permission_title,
|
||||
R.string.read_storage_permission_rationale);
|
||||
}
|
||||
}
|
||||
|
||||
private void initiateGalleryUpload(Activity activity,
|
||||
int imageLimit,
|
||||
int requestCode) {
|
||||
ImagePicker imagePicker = ImagePicker.ImagePickerWithFragment
|
||||
.create(activity)
|
||||
.showCamera(false)
|
||||
.folderMode(true)
|
||||
.includeVideo(false)
|
||||
.imageLoader(new FrescoImageLoader())
|
||||
.enableLog(true);
|
||||
|
||||
if (imageLimit > 1) {
|
||||
imagePicker.multi().limit(imageLimit).start(requestCode);
|
||||
} else {
|
||||
imagePicker.single().start(requestCode);
|
||||
}
|
||||
/**
|
||||
* Open chooser for gallery uploads
|
||||
*/
|
||||
private void initiateGalleryUpload(Activity activity, boolean allowMultipleUploads) {
|
||||
setPickerConfiguration(activity, allowMultipleUploads);
|
||||
FilePicker.openGallery(activity, 0);
|
||||
}
|
||||
|
||||
private void initiateCameraUpload(Activity activity, int requestCode) {
|
||||
ImagePicker.cameraOnly()
|
||||
.start(activity, requestCode);
|
||||
/**
|
||||
* Sets configuration for file picker
|
||||
*/
|
||||
private void setPickerConfiguration(Activity activity,
|
||||
boolean allowMultipleUploads) {
|
||||
boolean copyToExternalStorage = defaultKvStore.getBoolean("useExternalStorage", true);
|
||||
FilePicker.configuration(activity)
|
||||
.setCopyTakenPhotosToPublicGalleryAppFolder(copyToExternalStorage)
|
||||
.setAllowMultiplePickInGallery(allowMultipleUploads);
|
||||
}
|
||||
|
||||
public Intent handleImagesPicked(ArrayList<Uri> uriList, int requestCode) {
|
||||
/**
|
||||
* Initiate camera upload by opening camera
|
||||
*/
|
||||
private void initiateCameraUpload(Activity activity) {
|
||||
setPickerConfiguration(activity, false);
|
||||
FilePicker.openCameraForImage(activity, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches callback for file picker.
|
||||
*/
|
||||
public void handleActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
|
||||
FilePicker.handleActivityResult(requestCode, resultCode, data, activity, new DefaultCallback() {
|
||||
@Override
|
||||
public void onImagePickerError(Exception e, FilePicker.ImageSource source, int type) {
|
||||
ViewUtil.showShortToast(activity, R.string.error_occurred_in_picking_images);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImagesPicked(@NonNull List<File> imagesFiles, FilePicker.ImageSource source, int type) {
|
||||
Intent intent = handleImagesPicked(activity, imagesFiles, getSourceFromImageSource(source));
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public List<UploadableFile> handleExternalImagesPicked(Activity activity,
|
||||
Intent data) {
|
||||
return getUploadableFiles(FilePicker.handleExternalImagesPicked(data, activity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns intent to be passed to upload activity
|
||||
* Attaches place object for nearby uploads
|
||||
*/
|
||||
private Intent handleImagesPicked(Context context,
|
||||
List<File> imagesFiles,
|
||||
String source) {
|
||||
ArrayList<UploadableFile> uploadableFiles = getUploadableFiles(imagesFiles);
|
||||
Intent shareIntent = new Intent(context, UploadActivity.class);
|
||||
shareIntent.setAction(ACTION_SEND_MULTIPLE);
|
||||
shareIntent.putExtra(EXTRA_SOURCE, getSourceFromRequestCode(requestCode));
|
||||
shareIntent.putExtra(EXTRA_STREAM, uriList);
|
||||
shareIntent.setType("image/jpeg");
|
||||
shareIntent.setAction(ACTION_INTERNAL_UPLOADS);
|
||||
shareIntent.putExtra(EXTRA_SOURCE, source);
|
||||
shareIntent.putParcelableArrayListExtra(EXTRA_FILES, uploadableFiles);
|
||||
Place place = directKvStore.getJson(PLACE_OBJECT, Place.class);
|
||||
if (place != null) {
|
||||
shareIntent.putExtra(PLACE_OBJECT, place);
|
||||
|
|
@ -125,18 +148,22 @@ public class ContributionController {
|
|||
return shareIntent;
|
||||
}
|
||||
|
||||
private String getSourceFromRequestCode(int requestCode) {
|
||||
switch (requestCode) {
|
||||
case CAMERA_UPLOAD_REQUEST_CODE:
|
||||
case NEARBY_CAMERA_UPLOAD_REQUEST_CODE:
|
||||
case BOOKMARK_CAMERA_UPLOAD_REQUEST_CODE:
|
||||
return SOURCE_CAMERA;
|
||||
case GALLERY_UPLOAD_REQUEST_CODE:
|
||||
case NEARBY_GALLERY_UPLOAD_REQUEST_CODE:
|
||||
case BOOKMARK_GALLERY_UPLOAD_REQUEST_CODE:
|
||||
return SOURCE_GALLERY;
|
||||
@NonNull
|
||||
private ArrayList<UploadableFile> getUploadableFiles(List<File> imagesFiles) {
|
||||
ArrayList<UploadableFile> uploadableFiles = new ArrayList<>();
|
||||
for (File file : imagesFiles) {
|
||||
uploadableFiles.add(new UploadableFile(file));
|
||||
}
|
||||
return uploadableFiles;
|
||||
}
|
||||
|
||||
return SOURCE_EXTERNAL;
|
||||
/**
|
||||
* Get image upload source
|
||||
*/
|
||||
private String getSourceFromImageSource(FilePicker.ImageSource source) {
|
||||
if (source.equals(FilePicker.ImageSource.CAMERA_IMAGE)) {
|
||||
return SOURCE_CAMERA;
|
||||
}
|
||||
return SOURCE_GALLERY;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,6 @@ import fr.free.nrw.commons.utils.ConfigUtils;
|
|||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static fr.free.nrw.commons.contributions.ContributionController.CAMERA_UPLOAD_REQUEST_CODE;
|
||||
import static fr.free.nrw.commons.contributions.ContributionController.GALLERY_UPLOAD_REQUEST_CODE;
|
||||
import static fr.free.nrw.commons.contributions.ContributionController.MULTIPLE_UPLOAD_IMAGE_LIMIT;
|
||||
|
||||
/**
|
||||
* Created by root on 01.06.2018.
|
||||
|
|
@ -95,8 +92,8 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
|
|||
|
||||
private void setListeners() {
|
||||
fabPlus.setOnClickListener(view -> animateFAB(isFabOpen));
|
||||
fabCamera.setOnClickListener(view -> controller.initiateCameraPick(getActivity(), CAMERA_UPLOAD_REQUEST_CODE));
|
||||
fabGallery.setOnClickListener(view -> controller.initiateGalleryPick(getActivity(), MULTIPLE_UPLOAD_IMAGE_LIMIT, GALLERY_UPLOAD_REQUEST_CODE));
|
||||
fabCamera.setOnClickListener(view -> controller.initiateCameraPick(getActivity()));
|
||||
fabGallery.setOnClickListener(view -> controller.initiateGalleryPick(getActivity(), true));
|
||||
}
|
||||
|
||||
private void animateFAB(boolean isFabOpen) {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,6 @@ import android.view.View;
|
|||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.esafirm.imagepicker.features.ImagePicker;
|
||||
import com.esafirm.imagepicker.model.Image;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
|
@ -40,8 +37,6 @@ import fr.free.nrw.commons.notification.Notification;
|
|||
import fr.free.nrw.commons.notification.NotificationActivity;
|
||||
import fr.free.nrw.commons.notification.NotificationController;
|
||||
import fr.free.nrw.commons.upload.UploadService;
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import fr.free.nrw.commons.utils.IntentUtils;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
|
@ -457,13 +452,8 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag
|
|||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (IntentUtils.shouldContributionsHandle(requestCode, resultCode, data)) {
|
||||
List<Image> images = ImagePicker.getImages(data);
|
||||
Intent shareIntent = controller.handleImagesPicked(ImageUtils.getUriListFromImages(images), requestCode);
|
||||
startActivity(shareIntent);
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
controller.handleActivityResult(this, requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import fr.free.nrw.commons.upload.FileUtils;
|
||||
|
||||
public class UploadableFile implements Parcelable {
|
||||
public static final Creator<UploadableFile> CREATOR = new Creator<UploadableFile>() {
|
||||
@Override
|
||||
public UploadableFile createFromParcel(Parcel in) {
|
||||
return new UploadableFile(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UploadableFile[] newArray(int size) {
|
||||
return new UploadableFile[size];
|
||||
}
|
||||
};
|
||||
private final File file;
|
||||
|
||||
public UploadableFile(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public UploadableFile(Parcel in) {
|
||||
file = (File) in.readSerializable();
|
||||
}
|
||||
|
||||
public String getFilePath() {
|
||||
return file.getPath();
|
||||
}
|
||||
|
||||
public Uri getMediaUri() {
|
||||
return Uri.parse(getFilePath());
|
||||
}
|
||||
|
||||
public String getMimeType(Context context) {
|
||||
return FileUtils.getMimeType(context, getMediaUri());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int i) {
|
||||
parcel.writeSerializable(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filePath creation date from uri from all possible content providers
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public long getFileCreatedDate(Context context) {
|
||||
try {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
Cursor cursor = contentResolver.query(getMediaUri(), null, null, null, null);
|
||||
if (cursor == null) {
|
||||
return -1;//Could not fetch last_modified
|
||||
}
|
||||
//Content provider contracts for opening gallery from the app and that by sharing from gallery from outside are different and we need to handle both the cases
|
||||
int lastModifiedColumnIndex = cursor.getColumnIndex("last_modified");//If gallery is opened from in app
|
||||
if (lastModifiedColumnIndex == -1) {
|
||||
lastModifiedColumnIndex = cursor.getColumnIndex("datetaken");
|
||||
}
|
||||
//If both the content providers do not give the data, lets leave it to Jesus
|
||||
if (lastModifiedColumnIndex == -1) {
|
||||
return -1L;
|
||||
}
|
||||
cursor.moveToFirst();
|
||||
return cursor.getLong(lastModifiedColumnIndex);
|
||||
} catch (Exception e) {
|
||||
return -1;////Could not fetch last_modified
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue