mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +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
|
|
@ -39,9 +39,6 @@ dependencies {
|
||||||
implementation files('libs/simplemagic-1.9.jar')
|
implementation files('libs/simplemagic-1.9.jar')
|
||||||
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
|
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
|
||||||
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
|
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
|
||||||
implementation('com.github.esafirm.android-image-picker:imagepicker:1.13.1', {
|
|
||||||
exclude group: 'com.github.bumptech.glide', module: 'glide'
|
|
||||||
})
|
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation 'ch.acra:acra:4.9.2'
|
implementation 'ch.acra:acra:4.9.2'
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@
|
||||||
android:process=":acra" />
|
android:process=":acra" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="android.support.v4.content.FileProvider"
|
android:name=".filepicker.ExtendedFileProvider"
|
||||||
android:authorities="${applicationId}.provider"
|
android:authorities="${applicationId}.provider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:grantUriPermissions="true">
|
android:grantUriPermissions="true">
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,14 @@ import android.support.v4.view.ViewPager;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||||
|
import fr.free.nrw.commons.contributions.ContributionController;
|
||||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||||
|
|
||||||
|
|
@ -31,6 +34,9 @@ public class BookmarksActivity extends NavigationBaseActivity
|
||||||
@BindView(R.id.tabLayoutBookmarks)
|
@BindView(R.id.tabLayoutBookmarks)
|
||||||
TabLayout tabLayout;
|
TabLayout tabLayout;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ContributionController controller;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
@ -67,6 +73,12 @@ public class BookmarksActivity extends NavigationBaseActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
controller.handleActivityResult(this, requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called onClick of media inside category details (CategoryImageListFragment).
|
* This method is called onClick of media inside category details (CategoryImageListFragment).
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,6 @@ import android.widget.ProgressBar;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.esafirm.imagepicker.features.ImagePicker;
|
|
||||||
import com.esafirm.imagepicker.model.Image;
|
|
||||||
import com.pedrogomez.renderers.RVRendererAdapter;
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -32,8 +30,6 @@ import fr.free.nrw.commons.kvstore.BasicKvStore;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
import fr.free.nrw.commons.nearby.NearbyAdapterFactory;
|
import fr.free.nrw.commons.nearby.NearbyAdapterFactory;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.utils.ImageUtils;
|
|
||||||
import fr.free.nrw.commons.utils.IntentUtils;
|
|
||||||
|
|
||||||
public class BookmarkLocationsFragment extends DaggerFragment {
|
public class BookmarkLocationsFragment extends DaggerFragment {
|
||||||
|
|
||||||
|
|
@ -99,12 +95,6 @@ public class BookmarkLocationsFragment extends DaggerFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
if (IntentUtils.shouldBookmarksHandle(requestCode, resultCode, data)) {
|
contributionController.handleActivityResult(getActivity(), requestCode, resultCode, data);
|
||||||
List<Image> images = ImagePicker.getImages(data);
|
|
||||||
Intent shareIntent = contributionController.handleImagesPicked(ImageUtils.getUriListFromImages(images), requestCode);
|
|
||||||
startActivity(shareIntent);
|
|
||||||
} else {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,119 +4,142 @@ import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
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.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
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.BasicKvStore;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.upload.UploadActivity;
|
import fr.free.nrw.commons.upload.UploadActivity;
|
||||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
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_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.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.upload.UploadService.EXTRA_SOURCE;
|
||||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class ContributionController {
|
public class ContributionController {
|
||||||
|
|
||||||
//request codes
|
public static final String ACTION_INTERNAL_UPLOADS = "internalImageUploads";
|
||||||
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;
|
|
||||||
|
|
||||||
//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 BasicKvStore defaultKvStore;
|
||||||
private final JsonKvStore directKvStore;
|
private final JsonKvStore directKvStore;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ContributionController(Context context,
|
public ContributionController(@Named("default_preferences") BasicKvStore defaultKvStore,
|
||||||
@Named("default_preferences") BasicKvStore defaultKvStore,
|
|
||||||
@Named("direct_nearby_upload_prefs") JsonKvStore directKvStore) {
|
@Named("direct_nearby_upload_prefs") JsonKvStore directKvStore) {
|
||||||
this.context = context;
|
|
||||||
this.defaultKvStore = defaultKvStore;
|
this.defaultKvStore = defaultKvStore;
|
||||||
this.directKvStore = directKvStore;
|
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);
|
boolean useExtStorage = defaultKvStore.getBoolean("useExternalStorage", true);
|
||||||
if (!useExtStorage) {
|
if (!useExtStorage) {
|
||||||
initiateCameraUpload(activity, requestCode);
|
initiateCameraUpload(activity);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PermissionUtils.checkPermissionsAndPerformAction(activity,
|
PermissionUtils.checkPermissionsAndPerformAction(activity,
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
() -> initiateCameraUpload(activity, requestCode),
|
() -> initiateCameraUpload(activity),
|
||||||
R.string.storage_permission_title,
|
R.string.storage_permission_title,
|
||||||
R.string.write_storage_permission_rationale);
|
R.string.write_storage_permission_rationale);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initiateGalleryPick(Activity activity,
|
/**
|
||||||
int imageLimit,
|
* Check for permissions and initiate gallery picker
|
||||||
int requestCode) {
|
*/
|
||||||
|
public void initiateGalleryPick(Activity activity, boolean allowMultipleUploads) {
|
||||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
initiateGalleryUpload(activity, imageLimit, requestCode);
|
initiateGalleryUpload(activity, allowMultipleUploads);
|
||||||
} else {
|
} else {
|
||||||
PermissionUtils.checkPermissionsAndPerformAction(activity,
|
PermissionUtils.checkPermissionsAndPerformAction(activity,
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
() -> initiateGalleryUpload(activity, imageLimit, requestCode),
|
() -> initiateGalleryUpload(activity, allowMultipleUploads),
|
||||||
R.string.storage_permission_title,
|
R.string.storage_permission_title,
|
||||||
R.string.read_storage_permission_rationale);
|
R.string.read_storage_permission_rationale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initiateGalleryUpload(Activity activity,
|
/**
|
||||||
int imageLimit,
|
* Open chooser for gallery uploads
|
||||||
int requestCode) {
|
*/
|
||||||
ImagePicker imagePicker = ImagePicker.ImagePickerWithFragment
|
private void initiateGalleryUpload(Activity activity, boolean allowMultipleUploads) {
|
||||||
.create(activity)
|
setPickerConfiguration(activity, allowMultipleUploads);
|
||||||
.showCamera(false)
|
FilePicker.openGallery(activity, 0);
|
||||||
.folderMode(true)
|
|
||||||
.includeVideo(false)
|
|
||||||
.imageLoader(new FrescoImageLoader())
|
|
||||||
.enableLog(true);
|
|
||||||
|
|
||||||
if (imageLimit > 1) {
|
|
||||||
imagePicker.multi().limit(imageLimit).start(requestCode);
|
|
||||||
} else {
|
|
||||||
imagePicker.single().start(requestCode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initiateCameraUpload(Activity activity, int requestCode) {
|
/**
|
||||||
ImagePicker.cameraOnly()
|
* Sets configuration for file picker
|
||||||
.start(activity, requestCode);
|
*/
|
||||||
|
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);
|
Intent shareIntent = new Intent(context, UploadActivity.class);
|
||||||
shareIntent.setAction(ACTION_SEND_MULTIPLE);
|
shareIntent.setAction(ACTION_INTERNAL_UPLOADS);
|
||||||
shareIntent.putExtra(EXTRA_SOURCE, getSourceFromRequestCode(requestCode));
|
shareIntent.putExtra(EXTRA_SOURCE, source);
|
||||||
shareIntent.putExtra(EXTRA_STREAM, uriList);
|
shareIntent.putParcelableArrayListExtra(EXTRA_FILES, uploadableFiles);
|
||||||
shareIntent.setType("image/jpeg");
|
|
||||||
Place place = directKvStore.getJson(PLACE_OBJECT, Place.class);
|
Place place = directKvStore.getJson(PLACE_OBJECT, Place.class);
|
||||||
if (place != null) {
|
if (place != null) {
|
||||||
shareIntent.putExtra(PLACE_OBJECT, place);
|
shareIntent.putExtra(PLACE_OBJECT, place);
|
||||||
|
|
@ -125,18 +148,22 @@ public class ContributionController {
|
||||||
return shareIntent;
|
return shareIntent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getSourceFromRequestCode(int requestCode) {
|
@NonNull
|
||||||
switch (requestCode) {
|
private ArrayList<UploadableFile> getUploadableFiles(List<File> imagesFiles) {
|
||||||
case CAMERA_UPLOAD_REQUEST_CODE:
|
ArrayList<UploadableFile> uploadableFiles = new ArrayList<>();
|
||||||
case NEARBY_CAMERA_UPLOAD_REQUEST_CODE:
|
for (File file : imagesFiles) {
|
||||||
case BOOKMARK_CAMERA_UPLOAD_REQUEST_CODE:
|
uploadableFiles.add(new UploadableFile(file));
|
||||||
return SOURCE_CAMERA;
|
|
||||||
case GALLERY_UPLOAD_REQUEST_CODE:
|
|
||||||
case NEARBY_GALLERY_UPLOAD_REQUEST_CODE:
|
|
||||||
case BOOKMARK_GALLERY_UPLOAD_REQUEST_CODE:
|
|
||||||
return SOURCE_GALLERY;
|
|
||||||
}
|
}
|
||||||
|
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.GONE;
|
||||||
import static android.view.View.VISIBLE;
|
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.
|
* Created by root on 01.06.2018.
|
||||||
|
|
@ -95,8 +92,8 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
private void setListeners() {
|
private void setListeners() {
|
||||||
fabPlus.setOnClickListener(view -> animateFAB(isFabOpen));
|
fabPlus.setOnClickListener(view -> animateFAB(isFabOpen));
|
||||||
fabCamera.setOnClickListener(view -> controller.initiateCameraPick(getActivity(), CAMERA_UPLOAD_REQUEST_CODE));
|
fabCamera.setOnClickListener(view -> controller.initiateCameraPick(getActivity()));
|
||||||
fabGallery.setOnClickListener(view -> controller.initiateGalleryPick(getActivity(), MULTIPLE_UPLOAD_IMAGE_LIMIT, GALLERY_UPLOAD_REQUEST_CODE));
|
fabGallery.setOnClickListener(view -> controller.initiateGalleryPick(getActivity(), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void animateFAB(boolean isFabOpen) {
|
private void animateFAB(boolean isFabOpen) {
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,6 @@ import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.esafirm.imagepicker.features.ImagePicker;
|
|
||||||
import com.esafirm.imagepicker.model.Image;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
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.NotificationActivity;
|
||||||
import fr.free.nrw.commons.notification.NotificationController;
|
import fr.free.nrw.commons.notification.NotificationController;
|
||||||
import fr.free.nrw.commons.upload.UploadService;
|
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.Observable;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
@ -457,13 +452,8 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
if (IntentUtils.shouldContributionsHandle(requestCode, resultCode, data)) {
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
List<Image> images = ImagePicker.getImages(data);
|
controller.handleActivityResult(this, requestCode, resultCode, data);
|
||||||
Intent shareIntent = controller.handleImagesPicked(ImageUtils.getUriListFromImages(images), requestCode);
|
|
||||||
startActivity(shareIntent);
|
|
||||||
} else {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package fr.free.nrw.commons.filepicker;
|
||||||
|
|
||||||
|
public interface Constants {
|
||||||
|
String DEFAULT_FOLDER_NAME = "CommonsContributions";
|
||||||
|
|
||||||
|
interface RequestCodes {
|
||||||
|
int FILE_PICKER_IMAGE_IDENTIFICATOR = 0b1101101100; //876
|
||||||
|
int SOURCE_CHOOSER = 1 << 15;
|
||||||
|
|
||||||
|
int PICK_PICTURE_FROM_DOCUMENTS = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 11);
|
||||||
|
int PICK_PICTURE_FROM_GALLERY = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 12);
|
||||||
|
int TAKE_PICTURE = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 13);
|
||||||
|
int CAPTURE_VIDEO = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 14);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BundleKeys {
|
||||||
|
String FOLDER_NAME = "fr.free.nrw.commons.folder_name";
|
||||||
|
String ALLOW_MULTIPLE = "fr.free.nrw.commons.allow_multiple";
|
||||||
|
String COPY_TAKEN_PHOTOS = "fr.free.nrw.commons.copy_taken_photos";
|
||||||
|
String COPY_PICKED_IMAGES = "fr.free.nrw.commons.copy_picked_images";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package fr.free.nrw.commons.filepicker;
|
||||||
|
|
||||||
|
public abstract class DefaultCallback implements FilePicker.Callbacks {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onImagePickerError(Exception e, FilePicker.ImageSource source, int type) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCanceled(FilePicker.ImageSource source, int type) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package fr.free.nrw.commons.filepicker;
|
||||||
|
|
||||||
|
import android.support.v4.content.FileProvider;
|
||||||
|
|
||||||
|
public class ExtendedFileProvider extends FileProvider {
|
||||||
|
|
||||||
|
}
|
||||||
550
app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java
Normal file
550
app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java
Normal file
|
|
@ -0,0 +1,550 @@
|
||||||
|
package fr.free.nrw.commons.filepicker;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.provider.MediaStore;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.filepicker.PickedFiles.singleFileList;
|
||||||
|
|
||||||
|
@SuppressWarnings({"unused", "FieldCanBeLocal", "ResultOfMethodCallIgnored"})
|
||||||
|
public class FilePicker implements Constants {
|
||||||
|
|
||||||
|
private static final boolean SHOW_GALLERY_IN_CHOOSER = false;
|
||||||
|
private static final String KEY_PHOTO_URI = "photo_uri";
|
||||||
|
private static final String KEY_VIDEO_URI = "video_uri";
|
||||||
|
private static final String KEY_LAST_CAMERA_PHOTO = "last_photo";
|
||||||
|
private static final String KEY_LAST_CAMERA_VIDEO = "last_video";
|
||||||
|
private static final String KEY_TYPE = "type";
|
||||||
|
|
||||||
|
private static Uri createCameraPictureFile(@NonNull Context context) throws IOException {
|
||||||
|
File imagePath = PickedFiles.getCameraPicturesLocation(context);
|
||||||
|
Uri uri = PickedFiles.getUriToFile(context, imagePath);
|
||||||
|
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
||||||
|
editor.putString(KEY_PHOTO_URI, uri.toString());
|
||||||
|
editor.putString(KEY_LAST_CAMERA_PHOTO, imagePath.toString());
|
||||||
|
editor.apply();
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Uri createCameraVideoFile(@NonNull Context context) throws IOException {
|
||||||
|
File imagePath = PickedFiles.getCameraVideoLocation(context);
|
||||||
|
Uri uri = PickedFiles.getUriToFile(context, imagePath);
|
||||||
|
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
||||||
|
editor.putString(KEY_VIDEO_URI, uri.toString());
|
||||||
|
editor.putString(KEY_LAST_CAMERA_VIDEO, imagePath.toString());
|
||||||
|
editor.apply();
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Intent createDocumentsIntent(@NonNull Context context, int type) {
|
||||||
|
storeType(context, type);
|
||||||
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
intent.setType("image/*");
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Intent createGalleryIntent(@NonNull Context context, int type) {
|
||||||
|
storeType(context, type);
|
||||||
|
Intent intent = plainGalleryPickerIntent();
|
||||||
|
if (Build.VERSION.SDK_INT >= 18) {
|
||||||
|
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, configuration(context).allowsMultiplePickingInGallery());
|
||||||
|
}
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Intent createCameraForImageIntent(@NonNull Context context, int type) {
|
||||||
|
storeType(context, type);
|
||||||
|
|
||||||
|
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||||
|
try {
|
||||||
|
Uri capturedImageUri = createCameraPictureFile(context);
|
||||||
|
//We have to explicitly grant the write permission since Intent.setFlag works only on API Level >=20
|
||||||
|
grantWritePermission(context, intent, capturedImageUri);
|
||||||
|
intent.putExtra(MediaStore.EXTRA_OUTPUT, capturedImageUri);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Intent createCameraForVideoIntent(@NonNull Context context, int type) {
|
||||||
|
storeType(context, type);
|
||||||
|
|
||||||
|
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
|
||||||
|
try {
|
||||||
|
Uri capturedImageUri = createCameraVideoFile(context);
|
||||||
|
//We have to explicitly grant the write permission since Intent.setFlag works only on API Level >=20
|
||||||
|
grantWritePermission(context, intent, capturedImageUri);
|
||||||
|
intent.putExtra(MediaStore.EXTRA_OUTPUT, capturedImageUri);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void revokeWritePermission(@NonNull Context context, Uri uri) {
|
||||||
|
context.revokeUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void grantWritePermission(@NonNull Context context, Intent intent, Uri uri) {
|
||||||
|
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||||
|
for (ResolveInfo resolveInfo : resInfoList) {
|
||||||
|
String packageName = resolveInfo.activityInfo.packageName;
|
||||||
|
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Intent createChooserIntent(@NonNull Context context, @Nullable String chooserTitle, int type) throws IOException {
|
||||||
|
return createChooserIntent(context, chooserTitle, SHOW_GALLERY_IN_CHOOSER, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Intent createChooserIntent(@NonNull Context context, @Nullable String chooserTitle, boolean showGallery, int type) throws IOException {
|
||||||
|
storeType(context, type);
|
||||||
|
|
||||||
|
Uri outputFileUri = createCameraPictureFile(context);
|
||||||
|
List<Intent> cameraIntents = new ArrayList<>();
|
||||||
|
Intent captureIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
|
||||||
|
PackageManager packageManager = context.getPackageManager();
|
||||||
|
List<ResolveInfo> camList = packageManager.queryIntentActivities(captureIntent, 0);
|
||||||
|
for (ResolveInfo res : camList) {
|
||||||
|
final String packageName = res.activityInfo.packageName;
|
||||||
|
final Intent intent = new Intent(captureIntent);
|
||||||
|
intent.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));
|
||||||
|
intent.setPackage(packageName);
|
||||||
|
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
|
||||||
|
grantWritePermission(context, intent, outputFileUri);
|
||||||
|
cameraIntents.add(intent);
|
||||||
|
}
|
||||||
|
Intent galleryIntent;
|
||||||
|
|
||||||
|
if (showGallery) {
|
||||||
|
galleryIntent = createGalleryIntent(context, type);
|
||||||
|
} else {
|
||||||
|
galleryIntent = createDocumentsIntent(context, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent chooserIntent = Intent.createChooser(galleryIntent, chooserTitle);
|
||||||
|
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[cameraIntents.size()]));
|
||||||
|
|
||||||
|
return chooserIntent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void storeType(@NonNull Context context, int type) {
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context).edit().putInt(KEY_TYPE, type).commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int restoreType(@NonNull Context context) {
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(context).getInt(KEY_TYPE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openChooserWithDocuments(Activity activity, @Nullable String chooserTitle, int type) {
|
||||||
|
try {
|
||||||
|
Intent intent = createChooserIntent(activity, chooserTitle, type);
|
||||||
|
activity.startActivityForResult(intent, RequestCodes.SOURCE_CHOOSER | RequestCodes.PICK_PICTURE_FROM_DOCUMENTS);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openChooserWithDocuments(Fragment fragment, @Nullable String chooserTitle, int type) {
|
||||||
|
try {
|
||||||
|
Intent intent = createChooserIntent(fragment.getActivity(), chooserTitle, type);
|
||||||
|
fragment.startActivityForResult(intent, RequestCodes.SOURCE_CHOOSER | RequestCodes.PICK_PICTURE_FROM_DOCUMENTS);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openChooserWithDocuments(android.app.Fragment fragment, @Nullable String chooserTitle, int type) {
|
||||||
|
try {
|
||||||
|
Intent intent = createChooserIntent(fragment.getActivity(), chooserTitle, type);
|
||||||
|
fragment.startActivityForResult(intent, RequestCodes.SOURCE_CHOOSER | RequestCodes.PICK_PICTURE_FROM_DOCUMENTS);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openChooserWithGallery(Activity activity, @Nullable String chooserTitle, int type) {
|
||||||
|
try {
|
||||||
|
Intent intent = createChooserIntent(activity, chooserTitle, true, type);
|
||||||
|
activity.startActivityForResult(intent, RequestCodes.SOURCE_CHOOSER | RequestCodes.PICK_PICTURE_FROM_GALLERY);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openChooserWithGallery(Fragment fragment, @Nullable String chooserTitle, int type) {
|
||||||
|
try {
|
||||||
|
Intent intent = createChooserIntent(fragment.getActivity(), chooserTitle, true, type);
|
||||||
|
fragment.startActivityForResult(intent, RequestCodes.SOURCE_CHOOSER | RequestCodes.PICK_PICTURE_FROM_GALLERY);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openChooserWithGallery(android.app.Fragment fragment, @Nullable String chooserTitle, int type) {
|
||||||
|
try {
|
||||||
|
Intent intent = createChooserIntent(fragment.getActivity(), chooserTitle, true, type);
|
||||||
|
fragment.startActivityForResult(intent, RequestCodes.SOURCE_CHOOSER | RequestCodes.PICK_PICTURE_FROM_GALLERY);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openDocuments(Activity activity, int type) {
|
||||||
|
Intent intent = createDocumentsIntent(activity, type);
|
||||||
|
activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_DOCUMENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openDocuments(Fragment fragment, int type) {
|
||||||
|
Intent intent = createDocumentsIntent(fragment.getContext(), type);
|
||||||
|
fragment.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_DOCUMENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openDocuments(android.app.Fragment fragment, int type) {
|
||||||
|
Intent intent = createDocumentsIntent(fragment.getActivity(), type);
|
||||||
|
fragment.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_DOCUMENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens default galery or a available galleries picker if there is no default
|
||||||
|
*
|
||||||
|
* @param type Custom type of your choice, which will be returned with the images
|
||||||
|
*/
|
||||||
|
public static void openGallery(Activity activity, int type) {
|
||||||
|
Intent intent = createGalleryIntent(activity, type);
|
||||||
|
activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_GALLERY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens default galery or a available galleries picker if there is no default
|
||||||
|
*
|
||||||
|
* @param type Custom type of your choice, which will be returned with the images
|
||||||
|
*/
|
||||||
|
public static void openGallery(Fragment fragment, int type) {
|
||||||
|
Intent intent = createGalleryIntent(fragment.getContext(), type);
|
||||||
|
fragment.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_GALLERY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens default galery or a available galleries picker if there is no default
|
||||||
|
*
|
||||||
|
* @param type Custom type of your choice, which will be returned with the images
|
||||||
|
*/
|
||||||
|
public static void openGallery(android.app.Fragment fragment, int type) {
|
||||||
|
Intent intent = createGalleryIntent(fragment.getActivity(), type);
|
||||||
|
fragment.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_GALLERY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openCameraForImage(Activity activity, int type) {
|
||||||
|
Intent intent = createCameraForImageIntent(activity, type);
|
||||||
|
activity.startActivityForResult(intent, RequestCodes.TAKE_PICTURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openCameraForImage(Fragment fragment, int type) {
|
||||||
|
Intent intent = createCameraForImageIntent(fragment.getActivity(), type);
|
||||||
|
fragment.startActivityForResult(intent, RequestCodes.TAKE_PICTURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openCameraForImage(android.app.Fragment fragment, int type) {
|
||||||
|
Intent intent = createCameraForImageIntent(fragment.getActivity(), type);
|
||||||
|
fragment.startActivityForResult(intent, RequestCodes.TAKE_PICTURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openCameraForVideo(Activity activity, int type) {
|
||||||
|
Intent intent = createCameraForVideoIntent(activity, type);
|
||||||
|
activity.startActivityForResult(intent, RequestCodes.CAPTURE_VIDEO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openCameraForVideo(Fragment fragment, int type) {
|
||||||
|
Intent intent = createCameraForVideoIntent(fragment.getActivity(), type);
|
||||||
|
fragment.startActivityForResult(intent, RequestCodes.CAPTURE_VIDEO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openCameraForVideo(android.app.Fragment fragment, int type) {
|
||||||
|
Intent intent = createCameraForVideoIntent(fragment.getActivity(), type);
|
||||||
|
fragment.startActivityForResult(intent, RequestCodes.CAPTURE_VIDEO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static File takenCameraPicture(Context context) throws IOException, URISyntaxException {
|
||||||
|
String lastCameraPhoto = PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_LAST_CAMERA_PHOTO, null);
|
||||||
|
if (lastCameraPhoto != null) {
|
||||||
|
return new File(lastCameraPhoto);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static File takenCameraVideo(Context context) throws IOException, URISyntaxException {
|
||||||
|
String lastCameraPhoto = PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_LAST_CAMERA_VIDEO, null);
|
||||||
|
if (lastCameraPhoto != null) {
|
||||||
|
return new File(lastCameraPhoto);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void handleActivityResult(int requestCode, int resultCode, Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
|
||||||
|
boolean isHandledPickedFile = (requestCode & RequestCodes.FILE_PICKER_IMAGE_IDENTIFICATOR) > 0;
|
||||||
|
if (isHandledPickedFile) {
|
||||||
|
requestCode &= ~RequestCodes.SOURCE_CHOOSER;
|
||||||
|
if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY ||
|
||||||
|
requestCode == RequestCodes.TAKE_PICTURE ||
|
||||||
|
requestCode == RequestCodes.CAPTURE_VIDEO ||
|
||||||
|
requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS) {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
if (requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS && !isPhoto(data)) {
|
||||||
|
onPictureReturnedFromDocuments(data, activity, callbacks);
|
||||||
|
} else if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY && !isPhoto(data)) {
|
||||||
|
onPictureReturnedFromGallery(data, activity, callbacks);
|
||||||
|
} else if (requestCode == RequestCodes.TAKE_PICTURE) {
|
||||||
|
onPictureReturnedFromCamera(activity, callbacks);
|
||||||
|
} else if (requestCode == RequestCodes.CAPTURE_VIDEO) {
|
||||||
|
onVideoReturnedFromCamera(activity, callbacks);
|
||||||
|
} else if (isPhoto(data)) {
|
||||||
|
onPictureReturnedFromCamera(activity, callbacks);
|
||||||
|
} else {
|
||||||
|
onPictureReturnedFromDocuments(data, activity, callbacks);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS) {
|
||||||
|
callbacks.onCanceled(FilePicker.ImageSource.DOCUMENTS, restoreType(activity));
|
||||||
|
} else if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY) {
|
||||||
|
callbacks.onCanceled(FilePicker.ImageSource.GALLERY, restoreType(activity));
|
||||||
|
} else {
|
||||||
|
callbacks.onCanceled(FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<File> handleExternalImagesPicked(Intent data, Activity activity) {
|
||||||
|
try {
|
||||||
|
return getFilesFromGalleryPictures(data, activity);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPhoto(Intent data) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
|
return data == null || (data.getData() == null && data.getClipData() == null);
|
||||||
|
} else {
|
||||||
|
return data == null || (data.getData() == null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean willHandleActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (requestCode == RequestCodes.SOURCE_CHOOSER || requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY || requestCode == RequestCodes.TAKE_PICTURE || requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Intent plainGalleryPickerIntent() {
|
||||||
|
return new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean canDeviceHandleGallery(@NonNull Context context) {
|
||||||
|
return plainGalleryPickerIntent().resolveActivity(context.getPackageManager()) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param context context
|
||||||
|
* @return File containing lastly taken (using camera) photo. Returns null if there was no photo taken or it doesn't exist anymore.
|
||||||
|
*/
|
||||||
|
public static File lastlyTakenButCanceledPhoto(@NonNull Context context) {
|
||||||
|
String filePath = PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_LAST_CAMERA_PHOTO, null);
|
||||||
|
if (filePath == null) return null;
|
||||||
|
File file = new File(filePath);
|
||||||
|
if (file.exists()) {
|
||||||
|
return file;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File lastlyTakenButCanceledVideo(@NonNull Context context) {
|
||||||
|
String filePath = PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_LAST_CAMERA_VIDEO, null);
|
||||||
|
if (filePath == null) return null;
|
||||||
|
File file = new File(filePath);
|
||||||
|
if (file.exists()) {
|
||||||
|
return file;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onPictureReturnedFromDocuments(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
|
||||||
|
try {
|
||||||
|
Uri photoPath = data.getData();
|
||||||
|
File photoFile = PickedFiles.pickedExistingPicture(activity, photoPath);
|
||||||
|
callbacks.onImagesPicked(singleFileList(photoFile), FilePicker.ImageSource.DOCUMENTS, restoreType(activity));
|
||||||
|
|
||||||
|
if (configuration(activity).shouldCopyPickedImagesToPublicGalleryAppFolder()) {
|
||||||
|
PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
callbacks.onImagePickerError(e, FilePicker.ImageSource.DOCUMENTS, restoreType(activity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onPictureReturnedFromGallery(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
|
||||||
|
try {
|
||||||
|
List<File> files = getFilesFromGalleryPictures(data, activity);
|
||||||
|
callbacks.onImagesPicked(files, FilePicker.ImageSource.GALLERY, restoreType(activity));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
callbacks.onImagePickerError(e, FilePicker.ImageSource.GALLERY, restoreType(activity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<File> getFilesFromGalleryPictures(Intent data, Activity activity) throws IOException {
|
||||||
|
List<File> files = new ArrayList<>();
|
||||||
|
ClipData clipData = null;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
|
clipData = data.getClipData();
|
||||||
|
}
|
||||||
|
if (clipData == null) {
|
||||||
|
Uri uri = data.getData();
|
||||||
|
File file = PickedFiles.pickedExistingPicture(activity, uri);
|
||||||
|
files.add(file);
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < clipData.getItemCount(); i++) {
|
||||||
|
Uri uri = clipData.getItemAt(i).getUri();
|
||||||
|
File file = PickedFiles.pickedExistingPicture(activity, uri);
|
||||||
|
files.add(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration(activity).shouldCopyPickedImagesToPublicGalleryAppFolder()) {
|
||||||
|
PickedFiles.copyFilesInSeparateThread(activity, files);
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onPictureReturnedFromCamera(Activity activity, @NonNull FilePicker.Callbacks callbacks) {
|
||||||
|
try {
|
||||||
|
String lastImageUri = PreferenceManager.getDefaultSharedPreferences(activity).getString(KEY_PHOTO_URI, null);
|
||||||
|
if (!TextUtils.isEmpty(lastImageUri)) {
|
||||||
|
revokeWritePermission(activity, Uri.parse(lastImageUri));
|
||||||
|
}
|
||||||
|
|
||||||
|
File photoFile = FilePicker.takenCameraPicture(activity);
|
||||||
|
List<File> files = new ArrayList<>();
|
||||||
|
files.add(photoFile);
|
||||||
|
|
||||||
|
if (photoFile == null) {
|
||||||
|
Exception e = new IllegalStateException("Unable to get the picture returned from camera");
|
||||||
|
callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity));
|
||||||
|
} else {
|
||||||
|
if (configuration(activity).shouldCopyTakenPhotosToPublicGalleryAppFolder()) {
|
||||||
|
PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks.onImagesPicked(files, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity));
|
||||||
|
}
|
||||||
|
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
|
.edit()
|
||||||
|
.remove(KEY_LAST_CAMERA_PHOTO)
|
||||||
|
.remove(KEY_PHOTO_URI)
|
||||||
|
.apply();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onVideoReturnedFromCamera(Activity activity, @NonNull FilePicker.Callbacks callbacks) {
|
||||||
|
try {
|
||||||
|
String lastVideoUri = PreferenceManager.getDefaultSharedPreferences(activity).getString(KEY_VIDEO_URI, null);
|
||||||
|
if (!TextUtils.isEmpty(lastVideoUri)) {
|
||||||
|
revokeWritePermission(activity, Uri.parse(lastVideoUri));
|
||||||
|
}
|
||||||
|
|
||||||
|
File photoFile = FilePicker.takenCameraVideo(activity);
|
||||||
|
List<File> files = new ArrayList<>();
|
||||||
|
files.add(photoFile);
|
||||||
|
|
||||||
|
if (photoFile == null) {
|
||||||
|
Exception e = new IllegalStateException("Unable to get the video returned from camera");
|
||||||
|
callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_VIDEO, restoreType(activity));
|
||||||
|
} else {
|
||||||
|
if (configuration(activity).shouldCopyTakenPhotosToPublicGalleryAppFolder()) {
|
||||||
|
PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks.onImagesPicked(files, FilePicker.ImageSource.CAMERA_VIDEO, restoreType(activity));
|
||||||
|
}
|
||||||
|
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
|
.edit()
|
||||||
|
.remove(KEY_LAST_CAMERA_VIDEO)
|
||||||
|
.remove(KEY_VIDEO_URI)
|
||||||
|
.apply();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_VIDEO, restoreType(activity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to clear configuration. Would likely be used in onDestroy(), onDestroyView()...
|
||||||
|
*
|
||||||
|
* @param context context
|
||||||
|
*/
|
||||||
|
public static void clearConfiguration(@NonNull Context context) {
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||||
|
.remove(BundleKeys.FOLDER_NAME)
|
||||||
|
.remove(BundleKeys.ALLOW_MULTIPLE)
|
||||||
|
.remove(BundleKeys.COPY_TAKEN_PHOTOS)
|
||||||
|
.remove(BundleKeys.COPY_PICKED_IMAGES)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FilePickerConfiguration configuration(@NonNull Context context) {
|
||||||
|
return new FilePickerConfiguration(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public enum ImageSource {
|
||||||
|
GALLERY, DOCUMENTS, CAMERA_IMAGE, CAMERA_VIDEO
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Callbacks {
|
||||||
|
void onImagePickerError(Exception e, FilePicker.ImageSource source, int type);
|
||||||
|
|
||||||
|
void onImagesPicked(@NonNull List<File> imageFiles, FilePicker.ImageSource source, int type);
|
||||||
|
|
||||||
|
void onCanceled(FilePicker.ImageSource source, int type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package fr.free.nrw.commons.filepicker;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
|
public class FilePickerConfiguration implements Constants {
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
FilePickerConfiguration(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilePickerConfiguration setImagesFolderName(String folderName) {
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.edit().putString(BundleKeys.FOLDER_NAME, folderName)
|
||||||
|
.commit();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilePickerConfiguration setAllowMultiplePickInGallery(boolean allowMultiple) {
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||||
|
.putBoolean(BundleKeys.ALLOW_MULTIPLE, allowMultiple)
|
||||||
|
.commit();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilePickerConfiguration setCopyTakenPhotosToPublicGalleryAppFolder(boolean copy) {
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||||
|
.putBoolean(BundleKeys.COPY_TAKEN_PHOTOS, copy)
|
||||||
|
.commit();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilePickerConfiguration setCopyPickedImagesToPublicGalleryAppFolder(boolean copy) {
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||||
|
.putBoolean(BundleKeys.COPY_PICKED_IMAGES, copy)
|
||||||
|
.commit();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFolderName() {
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(context).getString(BundleKeys.FOLDER_NAME, DEFAULT_FOLDER_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean allowsMultiplePickingInGallery() {
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(BundleKeys.ALLOW_MULTIPLE, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldCopyTakenPhotosToPublicGalleryAppFolder() {
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(BundleKeys.COPY_TAKEN_PHOTOS, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldCopyPickedImagesToPublicGalleryAppFolder() {
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(BundleKeys.COPY_PICKED_IMAGES, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package fr.free.nrw.commons.filepicker;
|
||||||
|
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
import com.facebook.common.internal.ImmutableMap;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class MimeTypeMapWrapper {
|
||||||
|
|
||||||
|
private static final MimeTypeMap sMimeTypeMap = MimeTypeMap.getSingleton();
|
||||||
|
|
||||||
|
private static final Map<String, String> sMimeTypeToExtensionMap =
|
||||||
|
ImmutableMap.of(
|
||||||
|
"image/heif", "heif",
|
||||||
|
"image/heic", "heic");
|
||||||
|
|
||||||
|
private static final Map<String, String> sExtensionToMimeTypeMap =
|
||||||
|
ImmutableMap.of(
|
||||||
|
"heif", "image/heif",
|
||||||
|
"heic", "image/heic");
|
||||||
|
|
||||||
|
public static String getExtensionFromMimeType(String mimeType) {
|
||||||
|
String result = sMimeTypeToExtensionMap.get(mimeType);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return sMimeTypeMap.getExtensionFromMimeType(mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMimeTypeFromExtension(String extension) {
|
||||||
|
String result = sExtensionToMimeTypeMap.get(extension);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return sMimeTypeMap.getMimeTypeFromExtension(extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasExtension(String extension) {
|
||||||
|
return sExtensionToMimeTypeMap.containsKey(extension) || sMimeTypeMap.hasExtension(extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasMimeType(String mimeType) {
|
||||||
|
return sMimeTypeToExtensionMap.containsKey(mimeType) || sMimeTypeMap.hasMimeType(mimeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
package fr.free.nrw.commons.filepicker;
|
||||||
|
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.media.MediaScannerConnection;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.content.FileProvider;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
|
||||||
|
class PickedFiles implements Constants {
|
||||||
|
|
||||||
|
private static String getFolderName(@NonNull Context context) {
|
||||||
|
return FilePicker.configuration(context).getFolderName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File tempImageDirectory(@NonNull Context context) {
|
||||||
|
File privateTempDir = new File(context.getCacheDir(), DEFAULT_FOLDER_NAME);
|
||||||
|
if (!privateTempDir.exists()) privateTempDir.mkdirs();
|
||||||
|
return privateTempDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeToFile(InputStream in, File file) {
|
||||||
|
try {
|
||||||
|
OutputStream out = new FileOutputStream(file);
|
||||||
|
byte[] buf = new byte[1024];
|
||||||
|
int len;
|
||||||
|
while ((len = in.read(buf)) > 0) {
|
||||||
|
out.write(buf, 0, len);
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
|
in.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void copyFile(File src, File dst) throws IOException {
|
||||||
|
InputStream in = new FileInputStream(src);
|
||||||
|
writeToFile(in, dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void copyFilesInSeparateThread(final Context context, final List<File> filesToCopy) {
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
List<File> copiedFiles = new ArrayList<>();
|
||||||
|
int i = 1;
|
||||||
|
for (File fileToCopy : filesToCopy) {
|
||||||
|
File dstDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), getFolderName(context));
|
||||||
|
if (!dstDir.exists()) dstDir.mkdirs();
|
||||||
|
|
||||||
|
String[] filenameSplit = fileToCopy.getName().split("\\.");
|
||||||
|
String extension = "." + filenameSplit[filenameSplit.length - 1];
|
||||||
|
String filename = String.format("IMG_%s_%d.%s", new SimpleDateFormat("yyyyMMdd_HHmmss").format(Calendar.getInstance().getTime()), i, extension);
|
||||||
|
|
||||||
|
File dstFile = new File(dstDir, filename);
|
||||||
|
try {
|
||||||
|
dstFile.createNewFile();
|
||||||
|
copyFile(fileToCopy, dstFile);
|
||||||
|
copiedFiles.add(dstFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
scanCopiedImages(context, copiedFiles);
|
||||||
|
}
|
||||||
|
}).run();
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<File> singleFileList(File file) {
|
||||||
|
List<File> list = new ArrayList<>();
|
||||||
|
list.add(file);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void scanCopiedImages(Context context, List<File> copiedImages) {
|
||||||
|
String[] paths = new String[copiedImages.size()];
|
||||||
|
for (int i = 0; i < copiedImages.size(); i++) {
|
||||||
|
paths[i] = copiedImages.get(i).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaScannerConnection.scanFile(context,
|
||||||
|
paths, null,
|
||||||
|
new MediaScannerConnection.OnScanCompletedListener() {
|
||||||
|
public void onScanCompleted(String path, Uri uri) {
|
||||||
|
Log.d(getClass().getSimpleName(), "Scanned " + path + ":");
|
||||||
|
Log.d(getClass().getSimpleName(), "-> uri=" + uri);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static File pickedExistingPicture(@NonNull Context context, Uri photoUri) throws IOException {
|
||||||
|
InputStream pictureInputStream = context.getContentResolver().openInputStream(photoUri);
|
||||||
|
File directory = tempImageDirectory(context);
|
||||||
|
File photoFile = new File(directory, UUID.randomUUID().toString() + "." + getMimeType(context, photoUri));
|
||||||
|
photoFile.createNewFile();
|
||||||
|
writeToFile(pictureInputStream, photoFile);
|
||||||
|
return photoFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
static File getCameraPicturesLocation(@NonNull Context context) throws IOException {
|
||||||
|
File dir = tempImageDirectory(context);
|
||||||
|
return File.createTempFile(UUID.randomUUID().toString(), ".jpg", dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
static File getCameraVideoLocation(@NonNull Context context) throws IOException {
|
||||||
|
File dir = tempImageDirectory(context);
|
||||||
|
return File.createTempFile(UUID.randomUUID().toString(), ".mp4", dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To find out the extension of required object in given uri
|
||||||
|
* Solution by http://stackoverflow.com/a/36514823/1171484
|
||||||
|
*/
|
||||||
|
private static String getMimeType(@NonNull Context context, @NonNull Uri uri) {
|
||||||
|
String extension;
|
||||||
|
|
||||||
|
//Check uri format to avoid null
|
||||||
|
if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
|
||||||
|
//If scheme is a content
|
||||||
|
extension = MimeTypeMapWrapper.getExtensionFromMimeType(context.getContentResolver().getType(uri));
|
||||||
|
} else {
|
||||||
|
//If scheme is a File
|
||||||
|
//This will replace white spaces with %20 and also other special characters. This will avoid returning null values on file name with spaces and special characters.
|
||||||
|
extension = MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(new File(uri.getPath())).toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Uri getUriToFile(@NonNull Context context, @NonNull File file) {
|
||||||
|
String packageName = context.getApplicationContext().getPackageName();
|
||||||
|
String authority = packageName + ".provider";
|
||||||
|
return FileProvider.getUriForFile(context, authority, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
package fr.free.nrw.commons.media;
|
|
||||||
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import com.esafirm.imagepicker.features.imageloader.ImageLoader;
|
|
||||||
import com.esafirm.imagepicker.features.imageloader.ImageType;
|
|
||||||
import com.facebook.common.util.UriUtil;
|
|
||||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
|
||||||
import com.facebook.drawee.drawable.ProgressBarDrawable;
|
|
||||||
import com.facebook.drawee.generic.GenericDraweeHierarchy;
|
|
||||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
|
||||||
import com.facebook.drawee.interfaces.DraweeController;
|
|
||||||
import com.facebook.drawee.view.DraweeHolder;
|
|
||||||
import com.facebook.imagepipeline.common.ResizeOptions;
|
|
||||||
import com.facebook.imagepipeline.request.ImageRequest;
|
|
||||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
|
||||||
|
|
||||||
public class FrescoImageLoader implements ImageLoader {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadImage(String path, ImageView imageView, ImageType imageType) {
|
|
||||||
Drawable defaultDrawable = imageView.getDrawable();
|
|
||||||
Resources resources = imageView.getContext().getResources();
|
|
||||||
GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(resources)
|
|
||||||
.build();
|
|
||||||
final DraweeHolder<GenericDraweeHierarchy> draweeHolder = DraweeHolder.create(hierarchy, imageView.getContext());
|
|
||||||
draweeHolder.getHierarchy().setProgressBarImage(new ProgressBarDrawable());
|
|
||||||
Drawable drawable = draweeHolder.getHierarchy().getTopLevelDrawable();
|
|
||||||
if (drawable == null) {
|
|
||||||
imageView.setImageDrawable(defaultDrawable);
|
|
||||||
} else {
|
|
||||||
imageView.setImageDrawable(drawable);
|
|
||||||
}
|
|
||||||
|
|
||||||
Uri uri = new Uri.Builder()
|
|
||||||
.scheme(UriUtil.LOCAL_FILE_SCHEME)
|
|
||||||
.path(path)
|
|
||||||
.build();
|
|
||||||
ImageRequest imageRequest = ImageRequestBuilder
|
|
||||||
.newBuilderWithSource(uri)
|
|
||||||
.setResizeOptions(new ResizeOptions(200, 200))
|
|
||||||
.build();
|
|
||||||
DraweeController controller = Fresco.newDraweeControllerBuilder()
|
|
||||||
.setOldController(draweeHolder.getController())
|
|
||||||
.setImageRequest(imageRequest)
|
|
||||||
.build();
|
|
||||||
draweeHolder.setController(controller);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,7 +4,6 @@ import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
@ -22,7 +21,6 @@ import android.widget.LinearLayout;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -40,7 +38,6 @@ import fr.free.nrw.commons.location.LocationServiceManager;
|
||||||
import fr.free.nrw.commons.location.LocationUpdateListener;
|
import fr.free.nrw.commons.location.LocationUpdateListener;
|
||||||
import fr.free.nrw.commons.utils.FragmentUtils;
|
import fr.free.nrw.commons.utils.FragmentUtils;
|
||||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
import fr.free.nrw.commons.utils.UriSerializer;
|
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import fr.free.nrw.commons.wikidata.WikidataEditListener;
|
import fr.free.nrw.commons.wikidata.WikidataEditListener;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
|
|
@ -245,7 +242,7 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
|
||||||
@Override
|
@Override
|
||||||
public void onWikidataEditSuccessful() {
|
public void onWikidataEditSuccessful() {
|
||||||
// Do not refresh nearby map if we are checking other areas with search this area button
|
// Do not refresh nearby map if we are checking other areas with search this area button
|
||||||
if (!nearbyMapFragment.searchThisAreaModeOn) {
|
if (nearbyMapFragment != null && !nearbyMapFragment.searchThisAreaModeOn) {
|
||||||
refreshView(MAP_UPDATED);
|
refreshView(MAP_UPDATED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import android.animation.ObjectAnimator;
|
||||||
import android.animation.TypeEvaluator;
|
import android.animation.TypeEvaluator;
|
||||||
import android.animation.ValueAnimator;
|
import android.animation.ValueAnimator;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
@ -30,10 +29,8 @@ import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.mapbox.mapboxsdk.Mapbox;
|
import com.mapbox.mapboxsdk.Mapbox;
|
||||||
import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions;
|
|
||||||
import com.mapbox.mapboxsdk.annotations.Icon;
|
import com.mapbox.mapboxsdk.annotations.Icon;
|
||||||
import com.mapbox.mapboxsdk.annotations.IconFactory;
|
import com.mapbox.mapboxsdk.annotations.IconFactory;
|
||||||
import com.mapbox.mapboxsdk.annotations.Marker;
|
import com.mapbox.mapboxsdk.annotations.Marker;
|
||||||
|
|
@ -63,17 +60,11 @@ import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
|
||||||
import fr.free.nrw.commons.contributions.ContributionController;
|
import fr.free.nrw.commons.contributions.ContributionController;
|
||||||
import fr.free.nrw.commons.kvstore.BasicKvStore;
|
import fr.free.nrw.commons.kvstore.BasicKvStore;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
import fr.free.nrw.commons.utils.ImageUtils;
|
|
||||||
import fr.free.nrw.commons.utils.IntentUtils;
|
|
||||||
import fr.free.nrw.commons.utils.LocationUtils;
|
import fr.free.nrw.commons.utils.LocationUtils;
|
||||||
import fr.free.nrw.commons.utils.UiUtils;
|
import fr.free.nrw.commons.utils.UiUtils;
|
||||||
import fr.free.nrw.commons.utils.UriDeserializer;
|
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.contributions.ContributionController.NEARBY_CAMERA_UPLOAD_REQUEST_CODE;
|
|
||||||
import static fr.free.nrw.commons.contributions.ContributionController.NEARBY_GALLERY_UPLOAD_REQUEST_CODE;
|
|
||||||
import static fr.free.nrw.commons.contributions.ContributionController.NEARBY_UPLOAD_IMAGE_LIMIT;
|
|
||||||
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
|
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
|
||||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
||||||
|
|
||||||
|
|
@ -867,7 +858,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
if (fabCamera.isShown()) {
|
if (fabCamera.isShown()) {
|
||||||
Timber.d("Camera button tapped. Place: %s", place.toString());
|
Timber.d("Camera button tapped. Place: %s", place.toString());
|
||||||
storeSharedPrefs();
|
storeSharedPrefs();
|
||||||
controller.initiateCameraPick(getActivity(), NEARBY_CAMERA_UPLOAD_REQUEST_CODE);
|
controller.initiateCameraPick(getActivity());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -875,7 +866,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
if (fabGallery.isShown()) {
|
if (fabGallery.isShown()) {
|
||||||
Timber.d("Gallery button tapped. Place: %s", place.toString());
|
Timber.d("Gallery button tapped. Place: %s", place.toString());
|
||||||
storeSharedPrefs();
|
storeSharedPrefs();
|
||||||
controller.initiateGalleryPick(getActivity(), NEARBY_UPLOAD_IMAGE_LIMIT, NEARBY_GALLERY_UPLOAD_REQUEST_CODE);
|
controller.initiateGalleryPick(getActivity(), false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,17 +33,10 @@ import fr.free.nrw.commons.contributions.MainActivity;
|
||||||
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||||
import fr.free.nrw.commons.kvstore.BasicKvStore;
|
import fr.free.nrw.commons.kvstore.BasicKvStore;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
import fr.free.nrw.commons.utils.PlaceUtils;
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.contributions.ContributionController.NEARBY_CAMERA_UPLOAD_REQUEST_CODE;
|
|
||||||
import static fr.free.nrw.commons.contributions.ContributionController.NEARBY_GALLERY_UPLOAD_REQUEST_CODE;
|
|
||||||
import static fr.free.nrw.commons.contributions.ContributionController.NEARBY_UPLOAD_IMAGE_LIMIT;
|
|
||||||
import static fr.free.nrw.commons.theme.NavigationBaseActivity.startActivityWithFlags;
|
import static fr.free.nrw.commons.theme.NavigationBaseActivity.startActivityWithFlags;
|
||||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.IS_DIRECT_UPLOAD;
|
|
||||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
||||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ENTITY_ID_PREF;
|
|
||||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ITEM_LOCATION;
|
|
||||||
|
|
||||||
public class PlaceRenderer extends Renderer<Place> {
|
public class PlaceRenderer extends Renderer<Place> {
|
||||||
|
|
||||||
|
|
@ -146,7 +139,7 @@ public class PlaceRenderer extends Renderer<Place> {
|
||||||
} else {
|
} else {
|
||||||
Timber.d("Camera button tapped. Image title: " + place.getName() + "Image desc: " + place.getLongDescription());
|
Timber.d("Camera button tapped. Image title: " + place.getName() + "Image desc: " + place.getLongDescription());
|
||||||
storeSharedPrefs();
|
storeSharedPrefs();
|
||||||
controller.initiateCameraPick(fragment.getActivity(), NEARBY_CAMERA_UPLOAD_REQUEST_CODE);
|
controller.initiateCameraPick(fragment.getActivity());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -166,7 +159,7 @@ public class PlaceRenderer extends Renderer<Place> {
|
||||||
}else {
|
}else {
|
||||||
Timber.d("Gallery button tapped. Image title: " + place.getName() + "Image desc: " + place.getLongDescription());
|
Timber.d("Gallery button tapped. Image title: " + place.getName() + "Image desc: " + place.getLongDescription());
|
||||||
storeSharedPrefs();
|
storeSharedPrefs();
|
||||||
controller.initiateGalleryPick(fragment.getActivity(), NEARBY_UPLOAD_IMAGE_LIMIT, NEARBY_GALLERY_UPLOAD_REQUEST_CODE);
|
controller.initiateGalleryPick(fragment.getActivity(), false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import io.reactivex.schedulers.Schedulers;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processing of the image file that is about to be uploaded via ShareActivity is done here
|
* Processing of the image filePath that is about to be uploaded via ShareActivity is done here
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class FileProcessor implements SimilarImageDialogFragment.onResponse {
|
public class FileProcessor implements SimilarImageDialogFragment.onResponse {
|
||||||
|
|
@ -61,7 +61,7 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes file coordinates, either from EXIF data or user location
|
* Processes filePath coordinates, either from EXIF data or user location
|
||||||
*/
|
*/
|
||||||
GPSExtractor processFileCoordinates(SimilarImageInterface similarImageInterface) {
|
GPSExtractor processFileCoordinates(SimilarImageInterface similarImageInterface) {
|
||||||
Timber.d("Calling GPSExtractor");
|
Timber.d("Calling GPSExtractor");
|
||||||
|
|
@ -116,7 +116,7 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse {
|
||||||
Timber.d("not null fild EXIF" + tempImageObj.imageCoordsExists + " coords" + tempImageObj.getCoords());
|
Timber.d("not null fild EXIF" + tempImageObj.imageCoordsExists + " coords" + tempImageObj.getCoords());
|
||||||
if (tempImageObj.getCoords() != null && tempImageObj.imageCoordsExists) {
|
if (tempImageObj.getCoords() != null && tempImageObj.imageCoordsExists) {
|
||||||
// Current image has gps coordinates and it's not current gps locaiton
|
// Current image has gps coordinates and it's not current gps locaiton
|
||||||
Timber.d("This file has image coords:" + file.getAbsolutePath());
|
Timber.d("This filePath has image coords:" + file.getAbsolutePath());
|
||||||
similarImageInterface.showSimilarImageFragment(filePath, file.getAbsolutePath());
|
similarImageInterface.showSimilarImageFragment(filePath, file.getAbsolutePath());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,10 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.ContentUris;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.media.ExifInterface;
|
import android.media.ExifInterface;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.webkit.MimeTypeMap;
|
||||||
import android.os.Environment;
|
|
||||||
import android.os.ParcelFileDescriptor;
|
|
||||||
import android.provider.DocumentsContract;
|
|
||||||
import android.provider.MediaStore;
|
|
||||||
import android.provider.OpenableColumns;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
@ -32,7 +22,7 @@ import timber.log.Timber;
|
||||||
public class FileUtils {
|
public class FileUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get SHA1 of file from input stream
|
* Get SHA1 of filePath from input stream
|
||||||
*/
|
*/
|
||||||
static String getSHA1(InputStream is) {
|
static String getSHA1(InputStream is) {
|
||||||
|
|
||||||
|
|
@ -71,7 +61,7 @@ public class FileUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Geolocation of file from input file path
|
* Get Geolocation of filePath from input filePath path
|
||||||
*/
|
*/
|
||||||
static String getGeolocationOfFile(String filePath) {
|
static String getGeolocationOfFile(String filePath) {
|
||||||
|
|
||||||
|
|
@ -91,10 +81,10 @@ public class FileUtils {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read and return the content of a resource file as string.
|
* Read and return the content of a resource filePath as string.
|
||||||
*
|
*
|
||||||
* @param fileName asset file's path (e.g. "/queries/nearby_query.rq")
|
* @param fileName asset filePath's path (e.g. "/queries/nearby_query.rq")
|
||||||
* @return the content of the file
|
* @return the content of the filePath
|
||||||
*/
|
*/
|
||||||
public static String readFromResource(String fileName) throws IOException {
|
public static String readFromResource(String fileName) throws IOException {
|
||||||
StringBuilder buffer = new StringBuilder();
|
StringBuilder buffer = new StringBuilder();
|
||||||
|
|
@ -138,8 +128,22 @@ public class FileUtils {
|
||||||
return deletedAll;
|
return deletedAll;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getFileExt(String fileName) {
|
public static String getMimeType(Context context, Uri uri) {
|
||||||
//Default file extension
|
String mimeType;
|
||||||
|
if (uri.getScheme()!=null && uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
|
||||||
|
ContentResolver cr = context.getContentResolver();
|
||||||
|
mimeType = cr.getType(uri);
|
||||||
|
} else {
|
||||||
|
String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri
|
||||||
|
.toString());
|
||||||
|
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
|
||||||
|
fileExtension.toLowerCase());
|
||||||
|
}
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getFileExt(String fileName) {
|
||||||
|
//Default filePath extension
|
||||||
String extension = ".jpg";
|
String extension = ".jpg";
|
||||||
|
|
||||||
int i = fileName.lastIndexOf('.');
|
int i = fileName.lastIndexOf('.');
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,11 @@ import fr.free.nrw.commons.utils.ImageUtils;
|
||||||
import fr.free.nrw.commons.utils.ImageUtilsWrapper;
|
import fr.free.nrw.commons.utils.ImageUtilsWrapper;
|
||||||
import fr.free.nrw.commons.utils.StringUtils;
|
import fr.free.nrw.commons.utils.StringUtils;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.utils.ImageUtils.EMPTY_TITLE;
|
||||||
|
import static fr.free.nrw.commons.utils.ImageUtils.FILE_NAME_EXISTS;
|
||||||
|
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Methods for pre-processing images to be uploaded
|
* Methods for pre-processing images to be uploaded
|
||||||
|
|
@ -28,7 +33,6 @@ public class ImageProcessingService {
|
||||||
MediaWikiApi mwApi) {
|
MediaWikiApi mwApi) {
|
||||||
this.fileUtilsWrapper = fileUtilsWrapper;
|
this.fileUtilsWrapper = fileUtilsWrapper;
|
||||||
this.bitmapRegionDecoderWrapper = bitmapRegionDecoderWrapper;
|
this.bitmapRegionDecoderWrapper = bitmapRegionDecoderWrapper;
|
||||||
|
|
||||||
this.imageUtilsWrapper = imageUtilsWrapper;
|
this.imageUtilsWrapper = imageUtilsWrapper;
|
||||||
this.mwApi = mwApi;
|
this.mwApi = mwApi;
|
||||||
}
|
}
|
||||||
|
|
@ -37,23 +41,48 @@ public class ImageProcessingService {
|
||||||
* Check image quality before upload
|
* Check image quality before upload
|
||||||
* - checks duplicate image
|
* - checks duplicate image
|
||||||
* - checks dark image
|
* - checks dark image
|
||||||
|
* - checks geolocation for image
|
||||||
|
* - check for valid title
|
||||||
*/
|
*/
|
||||||
public Single<Integer> checkImageQuality(String filePath) {
|
Single<Integer> validateImage(UploadModel.UploadItem uploadItem, boolean checkTitle) {
|
||||||
return checkImageQuality(null, filePath);
|
int currentImageQuality = uploadItem.getImageQuality();
|
||||||
|
Timber.d("Current image quality is %d", currentImageQuality);
|
||||||
|
if (currentImageQuality == ImageUtils.IMAGE_KEEP) {
|
||||||
|
return Single.just(ImageUtils.IMAGE_OK);
|
||||||
|
}
|
||||||
|
Timber.d("Checking the validity of image");
|
||||||
|
String filePath = uploadItem.getMediaUri().getPath();
|
||||||
|
Single<Integer> duplicateImage = checkDuplicateImage(filePath);
|
||||||
|
Single<Integer> wrongGeoLocation = checkImageGeoLocation(uploadItem.getPlace(), filePath);
|
||||||
|
Single<Integer> darkImage = checkDarkImage(filePath);
|
||||||
|
Single<Integer> itemTitle = checkTitle ? validateItemTitle(uploadItem) : Single.just(ImageUtils.IMAGE_OK);
|
||||||
|
|
||||||
|
return Single.zip(duplicateImage, wrongGeoLocation, darkImage, itemTitle,
|
||||||
|
(duplicate, wrongGeo, dark, title) -> {
|
||||||
|
Timber.d("Result for duplicate: %d, geo: %d, dark: %d, title: %d", duplicate, wrongGeo, dark, title);
|
||||||
|
return duplicate | wrongGeo | dark | title;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check image quality before upload
|
* Checks item title
|
||||||
* - checks duplicate image
|
* - empty title
|
||||||
* - checks dark image
|
* - existing title
|
||||||
* - checks geolocation for image
|
* @param uploadItem
|
||||||
|
* @return
|
||||||
*/
|
*/
|
||||||
public Single<Integer> checkImageQuality(Place place, String filePath) {
|
private Single<Integer> validateItemTitle(UploadModel.UploadItem uploadItem) {
|
||||||
return Single.zip(
|
Timber.d("Checking for image title %s", uploadItem.getTitle());
|
||||||
checkDuplicateImage(filePath),
|
Title title = uploadItem.getTitle();
|
||||||
checkImageGeoLocation(place, filePath),
|
if (title.isEmpty()) {
|
||||||
checkDarkImage(filePath), //Returns IMAGE_DARK or IMAGE_OK
|
return Single.just(EMPTY_TITLE);
|
||||||
(dupe, wrongGeo, dark) -> dupe | wrongGeo | dark);
|
}
|
||||||
|
|
||||||
|
return Single.fromCallable(() -> mwApi.fileExistsWithName(uploadItem.getFileName()))
|
||||||
|
.map(doesFileExist -> {
|
||||||
|
Timber.d("Result for valid title is %s", doesFileExist);
|
||||||
|
return doesFileExist ? FILE_NAME_EXISTS : IMAGE_OK;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -62,11 +91,15 @@ public class ImageProcessingService {
|
||||||
* @return IMAGE_DUPLICATE or IMAGE_OK
|
* @return IMAGE_DUPLICATE or IMAGE_OK
|
||||||
*/
|
*/
|
||||||
private Single<Integer> checkDuplicateImage(String filePath) {
|
private Single<Integer> checkDuplicateImage(String filePath) {
|
||||||
|
Timber.d("Checking for duplicate image %s", filePath);
|
||||||
return Single.fromCallable(() ->
|
return Single.fromCallable(() ->
|
||||||
fileUtilsWrapper.getFileInputStream(filePath))
|
fileUtilsWrapper.getFileInputStream(filePath))
|
||||||
.map(fileUtilsWrapper::getSHA1)
|
.map(fileUtilsWrapper::getSHA1)
|
||||||
.map(mwApi::existingFile)
|
.map(mwApi::existingFile)
|
||||||
.map(b -> b ? ImageUtils.IMAGE_DUPLICATE : ImageUtils.IMAGE_OK);
|
.map(b -> {
|
||||||
|
Timber.d("Result for duplicate image %s", b);
|
||||||
|
return b ? ImageUtils.IMAGE_DUPLICATE : ImageUtils.IMAGE_OK;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -75,6 +108,7 @@ public class ImageProcessingService {
|
||||||
* @return IMAGE_DARK or IMAGE_OK
|
* @return IMAGE_DARK or IMAGE_OK
|
||||||
*/
|
*/
|
||||||
private Single<Integer> checkDarkImage(String filePath) {
|
private Single<Integer> checkDarkImage(String filePath) {
|
||||||
|
Timber.d("Checking for dark image %s", filePath);
|
||||||
return Single.fromCallable(() ->
|
return Single.fromCallable(() ->
|
||||||
fileUtilsWrapper.getFileInputStream(filePath))
|
fileUtilsWrapper.getFileInputStream(filePath))
|
||||||
.map(file -> bitmapRegionDecoderWrapper.newInstance(file, false))
|
.map(file -> bitmapRegionDecoderWrapper.newInstance(file, false))
|
||||||
|
|
@ -87,6 +121,7 @@ public class ImageProcessingService {
|
||||||
* @return IMAGE_GEOLOCATION_DIFFERENT or IMAGE_OK
|
* @return IMAGE_GEOLOCATION_DIFFERENT or IMAGE_OK
|
||||||
*/
|
*/
|
||||||
private Single<Integer> checkImageGeoLocation(Place place, String filePath) {
|
private Single<Integer> checkImageGeoLocation(Place place, String filePath) {
|
||||||
|
Timber.d("Checking for image geolocation %s", filePath);
|
||||||
if (place == null || StringUtils.isNullOrWhiteSpace(place.getWikiDataEntityId())) {
|
if (place == null || StringUtils.isNullOrWhiteSpace(place.getWikiDataEntityId())) {
|
||||||
return Single.just(ImageUtils.IMAGE_OK);
|
return Single.just(ImageUtils.IMAGE_OK);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import android.text.TextUtils;
|
||||||
import io.reactivex.subjects.BehaviorSubject;
|
import io.reactivex.subjects.BehaviorSubject;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
class Title{
|
public class Title{
|
||||||
|
|
||||||
private String titleText;
|
private String titleText;
|
||||||
private boolean set;
|
private boolean set;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
@ -52,6 +53,8 @@ import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.category.CategoriesModel;
|
import fr.free.nrw.commons.category.CategoriesModel;
|
||||||
import fr.free.nrw.commons.category.CategoryItem;
|
import fr.free.nrw.commons.category.CategoryItem;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
|
import fr.free.nrw.commons.contributions.ContributionController;
|
||||||
|
import fr.free.nrw.commons.contributions.UploadableFile;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
|
|
@ -67,12 +70,17 @@ import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_EXTERNAL;
|
||||||
|
import static fr.free.nrw.commons.contributions.ContributionController.ACTION_INTERNAL_UPLOADS;
|
||||||
|
import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES;
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.Result;
|
import static fr.free.nrw.commons.utils.ImageUtils.Result;
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult;
|
import static fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult;
|
||||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
||||||
|
|
||||||
public class UploadActivity extends BaseActivity implements UploadView, SimilarImageInterface {
|
public class UploadActivity extends BaseActivity implements UploadView, SimilarImageInterface {
|
||||||
@Inject MediaWikiApi mwApi;
|
@Inject MediaWikiApi mwApi;
|
||||||
|
@Inject
|
||||||
|
ContributionController contributionController;
|
||||||
@Inject @Named("direct_nearby_upload_prefs") JsonKvStore directKvStore;
|
@Inject @Named("direct_nearby_upload_prefs") JsonKvStore directKvStore;
|
||||||
@Inject UploadPresenter presenter;
|
@Inject UploadPresenter presenter;
|
||||||
@Inject CategoriesModel categoriesModel;
|
@Inject CategoriesModel categoriesModel;
|
||||||
|
|
@ -128,6 +136,7 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
|
||||||
private DescriptionsAdapter descriptionsAdapter;
|
private DescriptionsAdapter descriptionsAdapter;
|
||||||
private RVRendererAdapter<CategoryItem> categoriesAdapter;
|
private RVRendererAdapter<CategoryItem> categoriesAdapter;
|
||||||
private CompositeDisposable compositeDisposable;
|
private CompositeDisposable compositeDisposable;
|
||||||
|
private ProgressDialog progressDialog;
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
|
|
@ -241,7 +250,7 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
|
||||||
dismissKeyboard();
|
dismissKeyboard();
|
||||||
}
|
}
|
||||||
if(isShowingItem) {
|
if(isShowingItem) {
|
||||||
descriptionsAdapter.setItems(uploadItem.title, uploadItem.descriptions);
|
descriptionsAdapter.setItems(uploadItem.getTitle(), uploadItem.getDescriptions());
|
||||||
rvDescriptions.setAdapter(descriptionsAdapter);
|
rvDescriptions.setAdapter(descriptionsAdapter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -372,18 +381,10 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showBadPicturePopup(@Result int result) {
|
public void showBadPicturePopup(String errorMessage) {
|
||||||
if (result >= 8 ) { // If location of image and nearby does not match, then set shared preferences to disable wikidata edits
|
|
||||||
directKvStore.putBoolean("Picture_Has_Correct_Location", false);
|
|
||||||
}
|
|
||||||
String errorMessageForResult = getErrorMessageForResult(this, result);
|
|
||||||
if (StringUtils.isNullOrWhiteSpace(errorMessageForResult)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DialogUtil.showAlertDialog(this,
|
DialogUtil.showAlertDialog(this,
|
||||||
getString(R.string.warning),
|
getString(R.string.warning),
|
||||||
errorMessageForResult,
|
errorMessage,
|
||||||
() -> presenter.deletePicture(),
|
() -> presenter.deletePicture(),
|
||||||
() -> presenter.keepPicture());
|
() -> presenter.keepPicture());
|
||||||
}
|
}
|
||||||
|
|
@ -410,6 +411,22 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
|
||||||
() -> presenter.handleCategoryNext(categoriesModel, true));
|
() -> presenter.handleCategoryNext(categoriesModel, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showProgressDialog() {
|
||||||
|
if (progressDialog == null) {
|
||||||
|
progressDialog = new ProgressDialog(this);
|
||||||
|
}
|
||||||
|
progressDialog.setMessage(getString(R.string.please_wait));
|
||||||
|
progressDialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void hideProgressDialog() {
|
||||||
|
if (progressDialog != null && !isFinishing()) {
|
||||||
|
progressDialog.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launchMapActivity(String decCoords) {
|
public void launchMapActivity(String decCoords) {
|
||||||
Utils.handleGeoCoordinates(this, decCoords);
|
Utils.handleGeoCoordinates(this, decCoords);
|
||||||
|
|
@ -596,7 +613,26 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
|
||||||
|
|
||||||
private void receiveSharedItems() {
|
private void receiveSharedItems() {
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
String mimeType = intent.getType();
|
String action = intent.getAction();
|
||||||
|
if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
|
||||||
|
receiveExternalSharedItems();
|
||||||
|
} else if (ACTION_INTERNAL_UPLOADS.equals(action)) {
|
||||||
|
receiveInternalSharedItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void receiveExternalSharedItems() {
|
||||||
|
List<UploadableFile> uploadableFiles = contributionController.handleExternalImagesPicked(this, getIntent());
|
||||||
|
if (uploadableFiles.isEmpty()) {
|
||||||
|
handleNullMedia();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
presenter.receive(uploadableFiles, SOURCE_EXTERNAL, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void receiveInternalSharedItems() {
|
||||||
|
Intent intent = getIntent();
|
||||||
String source;
|
String source;
|
||||||
|
|
||||||
if (intent.hasExtra(UploadService.EXTRA_SOURCE)) {
|
if (intent.hasExtra(UploadService.EXTRA_SOURCE)) {
|
||||||
|
|
@ -605,31 +641,21 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
|
||||||
source = Contribution.SOURCE_EXTERNAL;
|
source = Contribution.SOURCE_EXTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.d("Received intent %s with action %s and mimeType %s from source %s",
|
Timber.d("Received intent %s with action %s and from source %s",
|
||||||
intent.toString(),
|
intent.toString(),
|
||||||
intent.getAction(),
|
intent.getAction(),
|
||||||
mimeType,
|
|
||||||
source);
|
source);
|
||||||
|
|
||||||
ArrayList<Uri> urisList = new ArrayList<>();
|
ArrayList<UploadableFile> uploadableFiles = intent.getParcelableArrayListExtra(EXTRA_FILES);
|
||||||
|
Timber.i("Received multiple upload %s", uploadableFiles.size());
|
||||||
|
|
||||||
if (Intent.ACTION_SEND.equals(intent.getAction())) {
|
if (uploadableFiles.isEmpty()) {
|
||||||
Uri mediaUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
|
||||||
if (mediaUri != null) {
|
|
||||||
urisList.add(mediaUri);
|
|
||||||
}
|
|
||||||
} else if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
|
|
||||||
urisList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
|
||||||
Timber.i("Received multiple upload %s", urisList.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (urisList.isEmpty()) {
|
|
||||||
handleNullMedia();
|
handleNullMedia();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Place place = intent.getParcelableExtra(PLACE_OBJECT);
|
Place place = intent.getParcelableExtra(PLACE_OBJECT);
|
||||||
presenter.receive(urisList, mimeType, source, place);
|
presenter.receive(uploadableFiles, source, place);
|
||||||
|
|
||||||
resetDirectPrefs();
|
resetDirectPrefs();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
@ -18,13 +16,17 @@ import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
|
import fr.free.nrw.commons.contributions.UploadableFile;
|
||||||
|
import fr.free.nrw.commons.filepicker.MimeTypeMapWrapper;
|
||||||
import fr.free.nrw.commons.kvstore.BasicKvStore;
|
import fr.free.nrw.commons.kvstore.BasicKvStore;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
import fr.free.nrw.commons.utils.ImageUtils;
|
import fr.free.nrw.commons.utils.ImageUtils;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.Single;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.functions.Consumer;
|
import io.reactivex.functions.Consumer;
|
||||||
|
|
@ -40,7 +42,6 @@ public class UploadModel {
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
GPSExtractor.DUMMY,
|
GPSExtractor.DUMMY,
|
||||||
"",
|
|
||||||
null,
|
null,
|
||||||
-1L) {
|
-1L) {
|
||||||
};
|
};
|
||||||
|
|
@ -54,11 +55,9 @@ public class UploadModel {
|
||||||
private boolean rightCardState = true;
|
private boolean rightCardState = true;
|
||||||
private int currentStepIndex = 0;
|
private int currentStepIndex = 0;
|
||||||
private Context context;
|
private Context context;
|
||||||
private ContentResolver contentResolver;
|
|
||||||
private Disposable badImageSubscription;
|
private Disposable badImageSubscription;
|
||||||
|
|
||||||
private SessionManager sessionManager;
|
private SessionManager sessionManager;
|
||||||
private FileUtilsWrapper fileUtilsWrapper;
|
|
||||||
private FileProcessor fileProcessor;
|
private FileProcessor fileProcessor;
|
||||||
private final ImageProcessingService imageProcessingService;
|
private final ImageProcessingService imageProcessingService;
|
||||||
|
|
||||||
|
|
@ -68,52 +67,41 @@ public class UploadModel {
|
||||||
@Named("licenses_by_name") Map<String, String> licensesByName,
|
@Named("licenses_by_name") Map<String, String> licensesByName,
|
||||||
Context context,
|
Context context,
|
||||||
SessionManager sessionManager,
|
SessionManager sessionManager,
|
||||||
FileUtilsWrapper fileUtilsWrapper,
|
FileProcessor fileProcessor,
|
||||||
FileProcessor fileProcessor, ImageProcessingService imageProcessingService) {
|
ImageProcessingService imageProcessingService) {
|
||||||
this.licenses = licenses;
|
this.licenses = licenses;
|
||||||
this.basicKvStore = basicKvStore;
|
this.basicKvStore = basicKvStore;
|
||||||
this.license = basicKvStore.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
|
this.license = basicKvStore.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
|
||||||
this.licensesByName = licensesByName;
|
this.licensesByName = licensesByName;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.contentResolver = context.getContentResolver();
|
|
||||||
this.sessionManager = sessionManager;
|
this.sessionManager = sessionManager;
|
||||||
this.fileUtilsWrapper = fileUtilsWrapper;
|
|
||||||
this.fileProcessor = fileProcessor;
|
this.fileProcessor = fileProcessor;
|
||||||
this.imageProcessingService = imageProcessingService;
|
this.imageProcessingService = imageProcessingService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
Observable<UploadItem> preProcessImages(List<Uri> mediaUris,
|
Observable<UploadItem> preProcessImages(List<UploadableFile> uploadableFiles,
|
||||||
String mimeType,
|
|
||||||
Place place,
|
Place place,
|
||||||
String source,
|
String source,
|
||||||
SimilarImageInterface similarImageInterface) {
|
SimilarImageInterface similarImageInterface) {
|
||||||
initDefaultValues();
|
initDefaultValues();
|
||||||
|
return Observable.fromIterable(uploadableFiles)
|
||||||
|
.map(uploadableFile -> getUploadItem(uploadableFile, place, source, similarImageInterface));
|
||||||
|
}
|
||||||
|
|
||||||
return Observable.fromIterable(mediaUris)
|
Single<Integer> getImageQuality(UploadItem uploadItem, boolean checkTitle) {
|
||||||
.map(mediaUri -> {
|
return imageProcessingService.validateImage(uploadItem, checkTitle);
|
||||||
UploadItem item = getUploadItem(mimeType, place, source, similarImageInterface, mediaUri);
|
|
||||||
imageProcessingService.checkImageQuality(place, mediaUri.getPath())
|
|
||||||
.subscribeOn(Schedulers.computation())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(item.imageQuality::onNext, Timber::e);
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private UploadItem getUploadItem(String mimeType,
|
private UploadItem getUploadItem(UploadableFile uploadableFile,
|
||||||
Place place,
|
Place place,
|
||||||
String source,
|
String source,
|
||||||
SimilarImageInterface similarImageInterface,
|
SimilarImageInterface similarImageInterface) {
|
||||||
Uri mediaUri) {
|
fileProcessor.initFileDetails(Objects.requireNonNull(uploadableFile.getFilePath()), context.getContentResolver());
|
||||||
fileProcessor
|
long fileCreatedDate = uploadableFile.getFileCreatedDate(context);
|
||||||
.initFileDetails(Objects.requireNonNull(mediaUri.getPath()), context.getContentResolver());
|
|
||||||
long fileCreatedDate = getFileCreatedDate(mediaUri);
|
|
||||||
String fileExt = fileUtilsWrapper.getFileExt(mediaUri.getPath());
|
|
||||||
GPSExtractor gpsExtractor = fileProcessor.processFileCoordinates(similarImageInterface);
|
GPSExtractor gpsExtractor = fileProcessor.processFileCoordinates(similarImageInterface);
|
||||||
return new UploadItem(mediaUri, mimeType, source, gpsExtractor,
|
return new UploadItem(Uri.parse(uploadableFile.getFilePath()), uploadableFile.getMimeType(context), source, gpsExtractor, place, fileCreatedDate);
|
||||||
fileExt, place, fileCreatedDate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onItemsProcessed(Place place, List<UploadItem> uploadItems) {
|
void onItemsProcessed(Place place, List<UploadItem> uploadItems) {
|
||||||
|
|
@ -121,9 +109,11 @@ public class UploadModel {
|
||||||
if (items.isEmpty()) {
|
if (items.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UploadItem uploadItem = items.get(0);
|
UploadItem uploadItem = items.get(0);
|
||||||
uploadItem.selected = true;
|
uploadItem.selected = true;
|
||||||
uploadItem.first = true;
|
uploadItem.first = true;
|
||||||
|
|
||||||
if (place != null) {
|
if (place != null) {
|
||||||
uploadItem.title.setTitleText(place.getName());
|
uploadItem.title.setTitleText(place.getName());
|
||||||
uploadItem.descriptions.get(0).setDescriptionText(place.getLongDescription());
|
uploadItem.descriptions.get(0).setDescriptionText(place.getLongDescription());
|
||||||
|
|
@ -140,34 +130,6 @@ public class UploadModel {
|
||||||
items = new ArrayList<>();
|
items = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get file creation date from uri from all possible content providers
|
|
||||||
*
|
|
||||||
* @param media
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private long getFileCreatedDate(Uri media) {
|
|
||||||
try {
|
|
||||||
Cursor cursor = contentResolver.query(media, 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isPreviousAvailable() {
|
boolean isPreviousAvailable() {
|
||||||
return currentStepIndex > 0;
|
return currentStepIndex > 0;
|
||||||
}
|
}
|
||||||
|
|
@ -226,11 +188,8 @@ public class UploadModel {
|
||||||
this.bottomCardState = bottomCardState;
|
this.bottomCardState = bottomCardState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
public void next() {
|
public void next() {
|
||||||
Timber.d("UploadModel:next; Handling next");
|
|
||||||
if (badImageSubscription != null)
|
|
||||||
badImageSubscription.dispose();
|
|
||||||
Timber.d("UploadModel:next; disposing badImageSubscription");
|
|
||||||
markCurrentUploadVisited();
|
markCurrentUploadVisited();
|
||||||
if (currentStepIndex < items.size() + 1) {
|
if (currentStepIndex < items.size() + 1) {
|
||||||
currentStepIndex++;
|
currentStepIndex++;
|
||||||
|
|
@ -312,7 +271,8 @@ public class UploadModel {
|
||||||
Observable<Contribution> buildContributions(List<String> categoryStringList) {
|
Observable<Contribution> buildContributions(List<String> categoryStringList) {
|
||||||
return Observable.fromIterable(items).map(item ->
|
return Observable.fromIterable(items).map(item ->
|
||||||
{
|
{
|
||||||
Contribution contribution = new Contribution(item.mediaUri, null, item.title + "." + item.fileExt,
|
Contribution contribution = new Contribution(item.mediaUri, null,
|
||||||
|
item.getFileName(),
|
||||||
Description.formatList(item.descriptions), -1,
|
Description.formatList(item.descriptions), -1,
|
||||||
null, null, sessionManager.getAuthorName(),
|
null, null, sessionManager.getAuthorName(),
|
||||||
CommonsApplication.DEFAULT_EDIT_SUMMARY, item.gpsCoords.getCoords());
|
CommonsApplication.DEFAULT_EDIT_SUMMARY, item.gpsCoords.getCoords());
|
||||||
|
|
@ -332,17 +292,21 @@ public class UploadModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
void keepPicture() {
|
void keepPicture() {
|
||||||
items.get(currentStepIndex).imageQuality.onNext(ImageUtils.IMAGE_KEEP);
|
items.get(currentStepIndex).setImageQuality(ImageUtils.IMAGE_KEEP);
|
||||||
}
|
}
|
||||||
|
|
||||||
void deletePicture() {
|
void deletePicture() {
|
||||||
badImageSubscription.dispose();
|
badImageSubscription.dispose();
|
||||||
items.remove(currentStepIndex).imageQuality.onComplete();
|
|
||||||
updateItemState();
|
updateItemState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void subscribeBadPicture(Consumer<Integer> consumer) {
|
void subscribeBadPicture(Consumer<Integer> consumer, boolean checkTitle) {
|
||||||
badImageSubscription = getCurrentItem().imageQuality.subscribe(consumer, Timber::e);
|
if (isShowingItem()) {
|
||||||
|
badImageSubscription = getImageQuality(getCurrentItem(), checkTitle)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(consumer, Timber::e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<UploadItem> getItems() {
|
public List<UploadItem> getItems() {
|
||||||
|
|
@ -351,24 +315,23 @@ public class UploadModel {
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
static class UploadItem {
|
static class UploadItem {
|
||||||
public final Uri mediaUri;
|
private final Uri mediaUri;
|
||||||
public final String mimeType;
|
private final String mimeType;
|
||||||
public final String source;
|
private final String source;
|
||||||
public final GPSExtractor gpsCoords;
|
private final GPSExtractor gpsCoords;
|
||||||
|
|
||||||
public boolean selected = false;
|
private boolean selected = false;
|
||||||
public boolean first = false;
|
private boolean first = false;
|
||||||
public String fileExt;
|
private Title title;
|
||||||
public BehaviorSubject<Integer> imageQuality;
|
private List<Description> descriptions;
|
||||||
Title title;
|
private Place place;
|
||||||
List<Description> descriptions;
|
private boolean visited;
|
||||||
public Place place;
|
private boolean error;
|
||||||
public boolean visited;
|
private long createdTimestamp;
|
||||||
public boolean error;
|
private BehaviorSubject<Integer> imageQuality;
|
||||||
public long createdTimestamp;
|
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
UploadItem(Uri mediaUri, String mimeType, String source, GPSExtractor gpsCoords, String fileExt, @Nullable Place place, long createdTimestamp) {
|
UploadItem(Uri mediaUri, String mimeType, String source, GPSExtractor gpsCoords, @Nullable Place place, long createdTimestamp) {
|
||||||
title = new Title();
|
title = new Title();
|
||||||
descriptions = new ArrayList<>();
|
descriptions = new ArrayList<>();
|
||||||
descriptions.add(new Description());
|
descriptions.add(new Description());
|
||||||
|
|
@ -377,9 +340,72 @@ public class UploadModel {
|
||||||
this.mimeType = mimeType;
|
this.mimeType = mimeType;
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.gpsCoords = gpsCoords;
|
this.gpsCoords = gpsCoords;
|
||||||
this.fileExt = fileExt;
|
|
||||||
imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT);
|
|
||||||
this.createdTimestamp = createdTimestamp;
|
this.createdTimestamp = createdTimestamp;
|
||||||
|
imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMimeType() {
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSource() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPSExtractor getGpsCoords() {
|
||||||
|
return gpsCoords;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSelected() {
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFirst() {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Description> getDescriptions() {
|
||||||
|
return descriptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVisited() {
|
||||||
|
return visited;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCreatedTimestamp() {
|
||||||
|
return createdTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Title getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri getMediaUri() {
|
||||||
|
return mediaUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getImageQuality() {
|
||||||
|
return this.imageQuality.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageQuality(int imageQuality) {
|
||||||
|
this.imageQuality.onNext(imageQuality);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFileExt() {
|
||||||
|
return MimeTypeMapWrapper.getExtensionFromMimeType(mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFileName() {
|
||||||
|
return Utils.fixExtension(title.toString(), getFileExt());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Place getPlace() {
|
||||||
|
return place;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.net.Uri;
|
import android.content.Context;
|
||||||
|
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
@ -14,11 +15,12 @@ import javax.inject.Singleton;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.category.CategoriesModel;
|
import fr.free.nrw.commons.category.CategoriesModel;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
|
import fr.free.nrw.commons.contributions.UploadableFile;
|
||||||
import fr.free.nrw.commons.kvstore.BasicKvStore;
|
import fr.free.nrw.commons.kvstore.BasicKvStore;
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
import fr.free.nrw.commons.utils.ImageUtils;
|
import fr.free.nrw.commons.utils.StringUtils;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
@ -29,6 +31,7 @@ import static fr.free.nrw.commons.utils.ImageUtils.EMPTY_TITLE;
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.FILE_NAME_EXISTS;
|
import static fr.free.nrw.commons.utils.ImageUtils.FILE_NAME_EXISTS;
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_KEEP;
|
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_KEEP;
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
||||||
|
import static fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The MVP pattern presenter of Upload GUI
|
* The MVP pattern presenter of Upload GUI
|
||||||
|
|
@ -36,10 +39,6 @@ import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
||||||
@Singleton
|
@Singleton
|
||||||
public class UploadPresenter {
|
public class UploadPresenter {
|
||||||
|
|
||||||
private final UploadModel uploadModel;
|
|
||||||
private final UploadController uploadController;
|
|
||||||
private final MediaWikiApi mediaWikiApi;
|
|
||||||
|
|
||||||
private static final UploadView DUMMY = (UploadView) Proxy.newProxyInstance(UploadView.class.getClassLoader(),
|
private static final UploadView DUMMY = (UploadView) Proxy.newProxyInstance(UploadView.class.getClassLoader(),
|
||||||
new Class[]{UploadView.class}, (proxy, method, methodArgs) -> null);
|
new Class[]{UploadView.class}, (proxy, method, methodArgs) -> null);
|
||||||
private UploadView view = DUMMY;
|
private UploadView view = DUMMY;
|
||||||
|
|
@ -51,31 +50,37 @@ public class UploadPresenter {
|
||||||
@UploadView.UploadPage
|
@UploadView.UploadPage
|
||||||
private int currentPage = UploadView.PLEASE_WAIT;
|
private int currentPage = UploadView.PLEASE_WAIT;
|
||||||
|
|
||||||
@Inject @Named("default_preferences") BasicKvStore defaultKvStore;
|
private final UploadModel uploadModel;
|
||||||
|
private final UploadController uploadController;
|
||||||
|
private final Context context;
|
||||||
|
private final BasicKvStore defaultKvStore;
|
||||||
|
private final JsonKvStore directKvStore;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UploadPresenter(UploadModel uploadModel,
|
UploadPresenter(UploadModel uploadModel,
|
||||||
UploadController uploadController,
|
UploadController uploadController,
|
||||||
MediaWikiApi mediaWikiApi) {
|
Context context,
|
||||||
|
@Named("default_preferences") BasicKvStore defaultKvStore,
|
||||||
|
@Named("direct_nearby_upload_prefs") JsonKvStore directKvStore) {
|
||||||
this.uploadModel = uploadModel;
|
this.uploadModel = uploadModel;
|
||||||
this.uploadController = uploadController;
|
this.uploadController = uploadController;
|
||||||
this.mediaWikiApi = mediaWikiApi;
|
this.context = context;
|
||||||
|
this.defaultKvStore = defaultKvStore;
|
||||||
|
this.directKvStore = directKvStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Passes the items received to {@link #uploadModel} and displays the items.
|
* Passes the items received to {@link #uploadModel} and displays the items.
|
||||||
*
|
*
|
||||||
* @param media The Uri's of the media being uploaded.
|
* @param media The Uri's of the media being uploaded.
|
||||||
* @param mimeType the mimeType of the files.
|
|
||||||
* @param source File source from {@link Contribution.FileSource}
|
* @param source File source from {@link Contribution.FileSource}
|
||||||
*/
|
*/
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
void receive(List<Uri> media,
|
void receive(List<UploadableFile> media,
|
||||||
String mimeType,
|
|
||||||
@Contribution.FileSource String source,
|
@Contribution.FileSource String source,
|
||||||
Place place) {
|
Place place) {
|
||||||
Observable<UploadItem> uploadItemObservable = uploadModel
|
Observable<UploadItem> uploadItemObservable = uploadModel
|
||||||
.preProcessImages(media, mimeType, place, source, similarImageInterface);
|
.preProcessImages(media, place, source, similarImageInterface);
|
||||||
|
|
||||||
uploadItemObservable
|
uploadItemObservable
|
||||||
.toList()
|
.toList()
|
||||||
|
|
@ -90,7 +95,7 @@ public class UploadPresenter {
|
||||||
updateCards();
|
updateCards();
|
||||||
updateLicenses();
|
updateLicenses();
|
||||||
updateContent();
|
updateContent();
|
||||||
uploadModel.subscribeBadPicture(this::handleBadPicture);
|
uploadModel.subscribeBadPicture(this::handleBadImage, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -112,13 +117,25 @@ public class UploadPresenter {
|
||||||
void handleNext(Title title,
|
void handleNext(Title title,
|
||||||
List<Description> descriptions) {
|
List<Description> descriptions) {
|
||||||
Timber.e("Inside handleNext");
|
Timber.e("Inside handleNext");
|
||||||
validateCurrentItemTitle()
|
view.showProgressDialog();
|
||||||
|
uploadModel.getImageQuality(uploadModel.getCurrentItem(), true)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(errorCode -> handleImage(errorCode, title, descriptions),
|
.subscribe(imageResult -> handleImage(title, descriptions, imageResult),
|
||||||
throwable -> Timber.e(throwable, "Error occurred while handling image"));
|
throwable -> Timber.e(throwable, "Error occurred while handling image"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleImage(Title title, List<Description> descriptions, Integer imageResult) {
|
||||||
|
view.hideProgressDialog();
|
||||||
|
if (imageResult == IMAGE_KEEP || imageResult == IMAGE_OK) {
|
||||||
|
Timber.d("Set title and desc; Show next uploaded item");
|
||||||
|
setTitleAndDescription(title, descriptions);
|
||||||
|
nextUploadedItem();
|
||||||
|
} else {
|
||||||
|
handleBadImage(imageResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by the next button in {@link UploadActivity}
|
* Called by the next button in {@link UploadActivity}
|
||||||
*/
|
*/
|
||||||
|
|
@ -132,28 +149,27 @@ public class UploadPresenter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleImage(Integer errorCode, Title title, List<Description> descriptions) {
|
private void handleBadImage(Integer errorCode) {
|
||||||
|
Timber.d("Handle bad picture with error code %d", errorCode);
|
||||||
|
if (errorCode >= 8) { // If location of image and nearby does not match, then set shared preferences to disable wikidata edits
|
||||||
|
directKvStore.putBoolean("Picture_Has_Correct_Location", false);
|
||||||
|
}
|
||||||
|
|
||||||
switch (errorCode) {
|
switch (errorCode) {
|
||||||
case EMPTY_TITLE:
|
case EMPTY_TITLE:
|
||||||
Timber.d("Title is empty. Showing toast");
|
Timber.d("Title is empty. Showing toast");
|
||||||
view.showErrorMessage(R.string.add_title_toast);
|
view.showErrorMessage(R.string.add_title_toast);
|
||||||
break;
|
break;
|
||||||
case FILE_NAME_EXISTS:
|
case FILE_NAME_EXISTS:
|
||||||
if(getCurrentItem().imageQuality.getValue().equals(IMAGE_KEEP)) {
|
Timber.d("Trying to show duplicate picture popup");
|
||||||
Timber.d("Set title and desc; Show next uploaded item");
|
view.showDuplicatePicturePopup();
|
||||||
setTitleAndDescription(title, descriptions);
|
|
||||||
nextUploadedItem();
|
|
||||||
} else {
|
|
||||||
Timber.d("Trying to show duplicate picture popup");
|
|
||||||
view.showDuplicatePicturePopup();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case IMAGE_OK:
|
|
||||||
Timber.d("Image is OK. Proceeding");
|
|
||||||
default:
|
default:
|
||||||
Timber.d("Default: Setting title and desc; Show next uploaded item");
|
String errorMessageForResult = getErrorMessageForResult(context, errorCode);
|
||||||
setTitleAndDescription(title, descriptions);
|
if (StringUtils.isNullOrWhiteSpace(errorMessageForResult)) {
|
||||||
nextUploadedItem();
|
return;
|
||||||
|
}
|
||||||
|
view.showBadPicturePopup(errorMessageForResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,10 +177,7 @@ public class UploadPresenter {
|
||||||
Timber.d("Trying to show next uploaded item");
|
Timber.d("Trying to show next uploaded item");
|
||||||
uploadModel.next();
|
uploadModel.next();
|
||||||
updateContent();
|
updateContent();
|
||||||
if (uploadModel.isShowingItem()) {
|
uploadModel.subscribeBadPicture(this::handleBadImage, false);
|
||||||
Timber.d("Is showing item is true");
|
|
||||||
uploadModel.subscribeBadPicture(this::handleBadPicture);
|
|
||||||
}
|
|
||||||
view.dismissKeyboard();
|
view.dismissKeyboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,31 +186,9 @@ public class UploadPresenter {
|
||||||
uploadModel.setCurrentTitleAndDescriptions(title, descriptions);
|
uploadModel.setCurrentTitleAndDescriptions(title, descriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Title getCurrentImageTitle() {
|
|
||||||
return getCurrentItem().title;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getCurrentImageFileName() {
|
String getCurrentImageFileName() {
|
||||||
UploadItem currentItem = getCurrentItem();
|
UploadItem currentItem = getCurrentItem();
|
||||||
return currentItem.title + "." + uploadModel.getCurrentItem().fileExt;
|
return currentItem.getFileName();
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
private Observable<Integer> validateCurrentItemTitle() {
|
|
||||||
Title title = getCurrentImageTitle();
|
|
||||||
if (title.isEmpty()) {
|
|
||||||
view.showErrorMessage(R.string.add_title_toast);
|
|
||||||
return Observable.just(EMPTY_TITLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Observable.fromCallable(() -> mediaWikiApi.fileExistsWithName(getCurrentImageFileName()))
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.map(doesFileExist -> {
|
|
||||||
if (doesFileExist) {
|
|
||||||
return FILE_NAME_EXISTS;
|
|
||||||
}
|
|
||||||
return IMAGE_OK;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -206,9 +197,7 @@ public class UploadPresenter {
|
||||||
void handlePrevious() {
|
void handlePrevious() {
|
||||||
uploadModel.previous();
|
uploadModel.previous();
|
||||||
updateContent();
|
updateContent();
|
||||||
if (uploadModel.isShowingItem()) {
|
uploadModel.subscribeBadPicture(this::handleBadImage, false);
|
||||||
uploadModel.subscribeBadPicture(this::handleBadPicture);
|
|
||||||
}
|
|
||||||
view.dismissKeyboard();
|
view.dismissKeyboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -235,22 +224,12 @@ public class UploadPresenter {
|
||||||
* Called by the map button on the right card in {@link UploadActivity}
|
* Called by the map button on the right card in {@link UploadActivity}
|
||||||
*/
|
*/
|
||||||
void openCoordinateMap() {
|
void openCoordinateMap() {
|
||||||
GPSExtractor gpsObj = uploadModel.getCurrentItem().gpsCoords;
|
GPSExtractor gpsObj = uploadModel.getCurrentItem().getGpsCoords();
|
||||||
if (gpsObj != null && gpsObj.imageCoordsExists) {
|
if (gpsObj != null && gpsObj.imageCoordsExists) {
|
||||||
view.launchMapActivity(gpsObj.getDecLatitude() + "," + gpsObj.getDecLongitude());
|
view.launchMapActivity(gpsObj.getDecLatitude() + "," + gpsObj.getDecLongitude());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by the image processors when a result is obtained.
|
|
||||||
*
|
|
||||||
* @param result the result returned by the image procesors.
|
|
||||||
*/
|
|
||||||
private void handleBadPicture(@ImageUtils.Result int result) {
|
|
||||||
view.showBadPicturePopup(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
void keepPicture() {
|
void keepPicture() {
|
||||||
uploadModel.keepPicture();
|
uploadModel.keepPicture();
|
||||||
}
|
}
|
||||||
|
|
@ -262,8 +241,7 @@ public class UploadPresenter {
|
||||||
uploadModel.deletePicture();
|
uploadModel.deletePicture();
|
||||||
updateCards();
|
updateCards();
|
||||||
updateContent();
|
updateContent();
|
||||||
if (uploadModel.isShowingItem())
|
uploadModel.subscribeBadPicture(this::handleBadImage, false);
|
||||||
uploadModel.subscribeBadPicture(this::handleBadPicture);
|
|
||||||
view.dismissKeyboard();
|
view.dismissKeyboard();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -367,7 +345,7 @@ public class UploadPresenter {
|
||||||
view.setPreviousEnabled(uploadModel.isPreviousAvailable());
|
view.setPreviousEnabled(uploadModel.isPreviousAvailable());
|
||||||
view.setSubmitEnabled(uploadModel.isSubmitAvailable());
|
view.setSubmitEnabled(uploadModel.isSubmitAvailable());
|
||||||
|
|
||||||
view.setBackground(uploadModel.getCurrentItem().mediaUri);
|
view.setBackground(uploadModel.getCurrentItem().getMediaUri());
|
||||||
|
|
||||||
view.updateBottomCardContent(uploadModel.getCurrentStep(),
|
view.updateBottomCardContent(uploadModel.getCurrentStep(),
|
||||||
uploadModel.getStepCount(),
|
uploadModel.getStepCount(),
|
||||||
|
|
@ -376,7 +354,7 @@ public class UploadPresenter {
|
||||||
|
|
||||||
view.updateTopCardContent();
|
view.updateTopCardContent();
|
||||||
|
|
||||||
GPSExtractor gpsObj = uploadModel.getCurrentItem().gpsCoords;
|
GPSExtractor gpsObj = uploadModel.getCurrentItem().getGpsCoords();
|
||||||
view.updateRightCardContent(gpsObj != null && gpsObj.imageCoordsExists);
|
view.updateRightCardContent(gpsObj != null && gpsObj.imageCoordsExists);
|
||||||
|
|
||||||
view.updateSubtitleVisibility(uploadModel.getCount());
|
view.updateSubtitleVisibility(uploadModel.getCount());
|
||||||
|
|
@ -421,8 +399,8 @@ public class UploadPresenter {
|
||||||
List<String> getImageTitleList() {
|
List<String> getImageTitleList() {
|
||||||
List<String> titleList = new ArrayList<>();
|
List<String> titleList = new ArrayList<>();
|
||||||
for (UploadItem item : uploadModel.getUploads()) {
|
for (UploadItem item : uploadModel.getUploads()) {
|
||||||
if (item.title.isSet()) {
|
if (item.getTitle().isSet()) {
|
||||||
titleList.add(item.title.toString());
|
titleList.add(item.getTitle().toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return titleList;
|
return titleList;
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,14 @@ import android.content.ContentResolver;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
import android.util.Log;
|
|
||||||
import android.webkit.MimeTypeMap;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.j256.simplemagic.ContentInfo;
|
|
||||||
import com.j256.simplemagic.ContentInfoUtil;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FilterInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
@ -33,12 +29,11 @@ import fr.free.nrw.commons.BuildConfig;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.HandlerService;
|
import fr.free.nrw.commons.HandlerService;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import fr.free.nrw.commons.contributions.ContributionDao;
|
import fr.free.nrw.commons.contributions.ContributionDao;
|
||||||
import fr.free.nrw.commons.contributions.MainActivity;
|
|
||||||
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||||
|
import fr.free.nrw.commons.contributions.MainActivity;
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import fr.free.nrw.commons.mwapi.UploadResult;
|
import fr.free.nrw.commons.mwapi.UploadResult;
|
||||||
import fr.free.nrw.commons.wikidata.WikidataEditService;
|
import fr.free.nrw.commons.wikidata.WikidataEditService;
|
||||||
|
|
@ -52,6 +47,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
|
|
||||||
public static final String ACTION_START_SERVICE = EXTRA_PREFIX + ".upload";
|
public static final String ACTION_START_SERVICE = EXTRA_PREFIX + ".upload";
|
||||||
public static final String EXTRA_SOURCE = EXTRA_PREFIX + ".source";
|
public static final String EXTRA_SOURCE = EXTRA_PREFIX + ".source";
|
||||||
|
public static final String EXTRA_FILES = EXTRA_PREFIX + ".files";
|
||||||
public static final String EXTRA_CAMPAIGN = EXTRA_PREFIX + ".campaign";
|
public static final String EXTRA_CAMPAIGN = EXTRA_PREFIX + ".campaign";
|
||||||
|
|
||||||
@Inject MediaWikiApi mwApi;
|
@Inject MediaWikiApi mwApi;
|
||||||
|
|
@ -64,7 +60,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
private int toUpload;
|
private int toUpload;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The file names of unfinished uploads, used to prevent overwriting
|
* The filePath names of unfinished uploads, used to prevent overwriting
|
||||||
*/
|
*/
|
||||||
private Set<String> unfinishedUploads = new HashSet<>();
|
private Set<String> unfinishedUploads = new HashSet<>();
|
||||||
|
|
||||||
|
|
@ -74,7 +70,6 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
public static final int NOTIFICATION_UPLOAD_IN_PROGRESS = 1;
|
public static final int NOTIFICATION_UPLOAD_IN_PROGRESS = 1;
|
||||||
public static final int NOTIFICATION_UPLOAD_COMPLETE = 2;
|
public static final int NOTIFICATION_UPLOAD_COMPLETE = 2;
|
||||||
public static final int NOTIFICATION_UPLOAD_FAILED = 3;
|
public static final int NOTIFICATION_UPLOAD_FAILED = 3;
|
||||||
private ContentInfoUtil contentInfoUtil;
|
|
||||||
|
|
||||||
public UploadService() {
|
public UploadService() {
|
||||||
super("UploadService");
|
super("UploadService");
|
||||||
|
|
@ -201,41 +196,22 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void uploadContribution(Contribution contribution) {
|
private void uploadContribution(Contribution contribution) {
|
||||||
InputStream fileInputStream = null;
|
InputStream fileInputStream;
|
||||||
InputStream tempFileInputStream = null;
|
Uri localUri = contribution.getLocalUri();
|
||||||
ContentInfo contentInfo = null;
|
if (localUri == null || localUri.getPath() == null) {
|
||||||
String notificationTag = contribution.getLocalUri().toString();
|
Timber.d("localUri/path is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String notificationTag = localUri.toString();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
File file1 = new File(contribution.getLocalUri().getPath());
|
File file1 = new File(localUri.getPath());
|
||||||
fileInputStream = new FileInputStream(file1);
|
fileInputStream = new FileInputStream(file1);
|
||||||
tempFileInputStream = new FileInputStream(file1);
|
|
||||||
if (contentInfoUtil == null) {
|
|
||||||
contentInfoUtil = new ContentInfoUtil();
|
|
||||||
}
|
|
||||||
contentInfo = contentInfoUtil.findMatch(tempFileInputStream);
|
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
Timber.d("File not found");
|
Timber.d("File not found");
|
||||||
Toast fileNotFound = Toast.makeText(this, R.string.upload_failed, Toast.LENGTH_LONG);
|
Toast fileNotFound = Toast.makeText(this, R.string.upload_failed, Toast.LENGTH_LONG);
|
||||||
fileNotFound.show();
|
fileNotFound.show();
|
||||||
return;
|
return;
|
||||||
} catch (IOException e) {
|
|
||||||
Timber.d("exception while fetching MIME type: "+e);
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
if (null != tempFileInputStream) {
|
|
||||||
tempFileInputStream.close();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Timber.d("File not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//As the fileInputStream is null there's no point in continuing the upload process
|
|
||||||
//mwapi.upload accepts a NonNull input stream
|
|
||||||
if (fileInputStream == null) {
|
|
||||||
Timber.d("File not found");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.d("Before execution!");
|
Timber.d("Before execution!");
|
||||||
|
|
@ -244,20 +220,8 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL);
|
CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL);
|
||||||
this.startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS, curProgressNotification.build());
|
this.startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS, curProgressNotification.build());
|
||||||
|
|
||||||
String filename = null;
|
String filename = contribution.getFilename();
|
||||||
try {
|
try {
|
||||||
//try to fetch the MIME type from contentInfo first and then use the tag to do it
|
|
||||||
//Note : the tag has not proven trustworthy in the past
|
|
||||||
String mimeType;
|
|
||||||
if (contentInfo == null || contentInfo.getMimeType() == null) {
|
|
||||||
mimeType = (String) contribution.getTag("mimeType");
|
|
||||||
} else {
|
|
||||||
mimeType = contentInfo.getMimeType();
|
|
||||||
}
|
|
||||||
filename = Utils.fixExtension(
|
|
||||||
contribution.getFilename(),
|
|
||||||
MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType));
|
|
||||||
|
|
||||||
synchronized (unfinishedUploads) {
|
synchronized (unfinishedUploads) {
|
||||||
Timber.d("making sure of uniqueness of name: %s", filename);
|
Timber.d("making sure of uniqueness of name: %s", filename);
|
||||||
filename = findUniqueFilename(filename);
|
filename = findUniqueFilename(filename);
|
||||||
|
|
@ -282,7 +246,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
contribution
|
contribution
|
||||||
);
|
);
|
||||||
UploadResult uploadResult = mwApi.uploadFile(filename, fileInputStream, contribution.getDataLength(),
|
UploadResult uploadResult = mwApi.uploadFile(filename, fileInputStream, contribution.getDataLength(),
|
||||||
contribution.getPageContents(getApplicationContext()), contribution.getEditSummary(), contribution.getLocalUri(), contribution.getContentProviderUri(), notificationUpdater);
|
contribution.getPageContents(getApplicationContext()), contribution.getEditSummary(), localUri, contribution.getContentProviderUri(), notificationUpdater);
|
||||||
|
|
||||||
Timber.d("Response is %s", uploadResult.toString());
|
Timber.d("Response is %s", uploadResult.toString());
|
||||||
|
|
||||||
|
|
@ -341,7 +305,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
sequenceFileName = fileName;
|
sequenceFileName = fileName;
|
||||||
} else {
|
} else {
|
||||||
if (fileName.indexOf('.') == -1) {
|
if (fileName.indexOf('.') == -1) {
|
||||||
// We really should have appended a file type suffix already.
|
// We really should have appended a filePath type suffix already.
|
||||||
// But... we might not.
|
// But... we might not.
|
||||||
sequenceFileName = fileName + " " + sequenceNumber;
|
sequenceFileName = fileName + " " + sequenceNumber;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -43,11 +43,11 @@ class UploadThumbnailRenderer extends Renderer<UploadModel.UploadItem> {
|
||||||
@Override
|
@Override
|
||||||
public void render() {
|
public void render() {
|
||||||
UploadModel.UploadItem content = getContent();
|
UploadModel.UploadItem content = getContent();
|
||||||
Uri uri = Uri.parse(content.mediaUri.toString());
|
Uri uri = Uri.parse(content.getMediaUri().toString());
|
||||||
background.setImageURI(Uri.fromFile(new File(String.valueOf(uri))));
|
background.setImageURI(Uri.fromFile(new File(String.valueOf(uri))));
|
||||||
background.setAlpha(content.selected ? 1.0f : 0.5f);
|
background.setAlpha(content.isSelected() ? 1.0f : 0.5f);
|
||||||
space.setVisibility(content.first ? View.VISIBLE : View.GONE);
|
space.setVisibility(content.isFirst() ? View.VISIBLE : View.GONE);
|
||||||
error.setVisibility(content.visited && content.error ? View.VISIBLE : View.GONE);
|
error.setVisibility(content.isVisited() && content.isError() ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ public interface UploadView {
|
||||||
|
|
||||||
void dismissKeyboard();
|
void dismissKeyboard();
|
||||||
|
|
||||||
void showBadPicturePopup(@ImageUtils.Result int errorMessage);
|
void showBadPicturePopup(String errorMessage);
|
||||||
|
|
||||||
void showDuplicatePicturePopup();
|
void showDuplicatePicturePopup();
|
||||||
|
|
||||||
|
|
@ -81,4 +81,8 @@ public interface UploadView {
|
||||||
void initDefaultCategories();
|
void initDefaultCategories();
|
||||||
|
|
||||||
void showNoCategorySelectedWarning();
|
void showNoCategorySelectedWarning();
|
||||||
|
|
||||||
|
void showProgressDialog();
|
||||||
|
|
||||||
|
void hideProgressDialog();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import android.net.Uri;
|
||||||
import android.support.annotation.IntDef;
|
import android.support.annotation.IntDef;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.esafirm.imagepicker.model.Image;
|
|
||||||
import com.facebook.common.executors.CallerThreadExecutor;
|
import com.facebook.common.executors.CallerThreadExecutor;
|
||||||
import com.facebook.common.references.CloseableReference;
|
import com.facebook.common.references.CloseableReference;
|
||||||
import com.facebook.datasource.DataSource;
|
import com.facebook.datasource.DataSource;
|
||||||
|
|
@ -24,8 +23,6 @@ import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
|
@ -37,9 +34,9 @@ import timber.log.Timber;
|
||||||
|
|
||||||
public class ImageUtils {
|
public class ImageUtils {
|
||||||
|
|
||||||
static final int IMAGE_DARK = 1;
|
public static final int IMAGE_DARK = 1;
|
||||||
static final int IMAGE_BLURRY = 1 << 1;
|
static final int IMAGE_BLURRY = 1 << 1;
|
||||||
public static final int IMAGE_DUPLICATE = 1 << 2;
|
public static final int IMAGE_DUPLICATE = 1 << 2; //4
|
||||||
public static final int IMAGE_GEOLOCATION_DIFFERENT = 1 << 3;
|
public static final int IMAGE_GEOLOCATION_DIFFERENT = 1 << 3;
|
||||||
public static final int IMAGE_OK = 0;
|
public static final int IMAGE_OK = 0;
|
||||||
public static final int IMAGE_KEEP = -1;
|
public static final int IMAGE_KEEP = -1;
|
||||||
|
|
@ -251,15 +248,4 @@ public class ImageUtils {
|
||||||
|
|
||||||
return errorMessage.toString();
|
return errorMessage.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArrayList<Uri> getUriListFromImages(List<Image> imageList) {
|
|
||||||
ArrayList<Uri> uriList = new ArrayList<>();
|
|
||||||
for (Image imagePath : imageList) {
|
|
||||||
if (!StringUtils.isNullOrWhiteSpace(imagePath.getPath())) {
|
|
||||||
uriList.add(Uri.parse(imagePath.getPath()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return uriList;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
package fr.free.nrw.commons.utils;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
import static fr.free.nrw.commons.contributions.ContributionController.BOOKMARK_CAMERA_UPLOAD_REQUEST_CODE;
|
|
||||||
import static fr.free.nrw.commons.contributions.ContributionController.BOOKMARK_GALLERY_UPLOAD_REQUEST_CODE;
|
|
||||||
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.NEARBY_CAMERA_UPLOAD_REQUEST_CODE;
|
|
||||||
import static fr.free.nrw.commons.contributions.ContributionController.NEARBY_GALLERY_UPLOAD_REQUEST_CODE;
|
|
||||||
|
|
||||||
public class IntentUtils {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the intent should be handled by contributions list fragment
|
|
||||||
*/
|
|
||||||
public static boolean shouldContributionsHandle(int requestCode, int resultCode, Intent data) {
|
|
||||||
return resultCode == Activity.RESULT_OK
|
|
||||||
&& (requestCode == GALLERY_UPLOAD_REQUEST_CODE || requestCode == CAMERA_UPLOAD_REQUEST_CODE
|
|
||||||
|| requestCode == NEARBY_CAMERA_UPLOAD_REQUEST_CODE || requestCode == NEARBY_GALLERY_UPLOAD_REQUEST_CODE)
|
|
||||||
&& data != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the intent should be handled by contributions list fragment
|
|
||||||
*/
|
|
||||||
public static boolean shouldBookmarksHandle(int requestCode, int resultCode, Intent data) {
|
|
||||||
return resultCode == Activity.RESULT_OK
|
|
||||||
&& (requestCode == BOOKMARK_CAMERA_UPLOAD_REQUEST_CODE || requestCode == BOOKMARK_GALLERY_UPLOAD_REQUEST_CODE)
|
|
||||||
&& data != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -92,7 +92,9 @@ public class WikidataEditService {
|
||||||
|
|
||||||
private void handleClaimResult(String wikidataEntityId, String revisionId) {
|
private void handleClaimResult(String wikidataEntityId, String revisionId) {
|
||||||
if (revisionId != null) {
|
if (revisionId != null) {
|
||||||
wikidataEditListener.onSuccessfulWikidataEdit();
|
if (wikidataEditListener != null) {
|
||||||
|
wikidataEditListener.onSuccessfulWikidataEdit();
|
||||||
|
}
|
||||||
showSuccessToast();
|
showSuccessToast();
|
||||||
logEdit(revisionId);
|
logEdit(revisionId);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,8 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:textSize="@dimen/subtitle_text"
|
android:textSize="@dimen/subtitle_text"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/bottom_card_title"
|
app:layout_constraintTop_toBottomOf="@id/bottom_card_title"
|
||||||
tools:text="1st image" />
|
tools:text="1st image" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -463,4 +463,8 @@ Upload your first media by touching the camera or gallery icon above.</string>
|
||||||
|
|
||||||
<string name="no_notification">You have no unread Notification</string>
|
<string name="no_notification">You have no unread Notification</string>
|
||||||
<string name="share_logs_using">Share logs using</string>
|
<string name="share_logs_using">Share logs using</string>
|
||||||
|
<string name="error_occurred_in_picking_images">Error occurred while picking images</string>
|
||||||
|
<string name="image_chooser_title">Choose Images to upload</string>
|
||||||
|
|
||||||
|
<string name="please_wait">Please wait…</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,5 @@
|
||||||
<paths>
|
<paths>
|
||||||
<cache-path name="images" path="images/" />
|
<cache-path name="images" path="images/" />
|
||||||
<external-path name="Download" path="./"/>
|
<external-path name="Download" path="./"/>
|
||||||
|
<root-path name="root" path="." />
|
||||||
</paths>
|
</paths>
|
||||||
|
|
@ -176,7 +176,7 @@ class ContributionDaoTest {
|
||||||
@Test
|
@Test
|
||||||
fun saveNewContribution_nullableImageUrlUsesFileAsBackup() {
|
fun saveNewContribution_nullableImageUrlUsesFileAsBackup() {
|
||||||
whenever(client.insert(isA(), isA())).thenReturn(contentUri)
|
whenever(client.insert(isA(), isA())).thenReturn(contentUri)
|
||||||
val contribution = createContribution(true, null, null, null, "file")
|
val contribution = createContribution(true, null, null, null, "filePath")
|
||||||
|
|
||||||
testObject.save(contribution)
|
testObject.save(contribution)
|
||||||
|
|
||||||
|
|
@ -186,7 +186,7 @@ class ContributionDaoTest {
|
||||||
// Nullable fields are absent if null
|
// Nullable fields are absent if null
|
||||||
assertFalse(it.containsKey(Table.COLUMN_LOCAL_URI))
|
assertFalse(it.containsKey(Table.COLUMN_LOCAL_URI))
|
||||||
assertFalse(it.containsKey(Table.COLUMN_UPLOADED))
|
assertFalse(it.containsKey(Table.COLUMN_UPLOADED))
|
||||||
assertEquals(Utils.makeThumbBaseUrl("file"), it.getAsString(Table.COLUMN_IMAGE_URL))
|
assertEquals(Utils.makeThumbBaseUrl("filePath"), it.getAsString(Table.COLUMN_IMAGE_URL))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,7 +285,7 @@ class ContributionDaoTest {
|
||||||
createCursor(created, uploaded, false, localUri).let { mc ->
|
createCursor(created, uploaded, false, localUri).let { mc ->
|
||||||
testObject.fromCursor(mc).let {
|
testObject.fromCursor(mc).let {
|
||||||
assertEquals(uriForId(111), it.contentUri)
|
assertEquals(uriForId(111), it.contentUri)
|
||||||
assertEquals("file", it.filename)
|
assertEquals("filePath", it.filename)
|
||||||
assertEquals(localUri, it.localUri.toString())
|
assertEquals(localUri, it.localUri.toString())
|
||||||
assertEquals("image", it.imageUrl)
|
assertEquals("image", it.imageUrl)
|
||||||
assertEquals(created, it.dateCreated.time)
|
assertEquals(created, it.dateCreated.time)
|
||||||
|
|
@ -335,7 +335,7 @@ class ContributionDaoTest {
|
||||||
|
|
||||||
private fun createCursor(created: Long, uploaded: Long, multiple: Boolean, localUri: String) =
|
private fun createCursor(created: Long, uploaded: Long, multiple: Boolean, localUri: String) =
|
||||||
MatrixCursor(Table.ALL_FIELDS, 1).apply {
|
MatrixCursor(Table.ALL_FIELDS, 1).apply {
|
||||||
addRow(listOf("111", "file", localUri, "image",
|
addRow(listOf("111", "filePath", localUri, "image",
|
||||||
created, STATE_QUEUED, 222L, uploaded, 88L, SOURCE_GALLERY, "desc",
|
created, STATE_QUEUED, 222L, uploaded, 88L, SOURCE_GALLERY, "desc",
|
||||||
"create", if (multiple) 1 else 0, 640, 480, "007", "Q1"))
|
"create", if (multiple) 1 else 0, 640, 480, "007", "Q1"))
|
||||||
moveToFirst()
|
moveToFirst()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
|
import android.graphics.BitmapRegionDecoder
|
||||||
|
import android.net.Uri
|
||||||
|
import fr.free.nrw.commons.location.LatLng
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi
|
||||||
|
import fr.free.nrw.commons.nearby.Place
|
||||||
|
import fr.free.nrw.commons.utils.BitmapRegionDecoderWrapper
|
||||||
|
import fr.free.nrw.commons.utils.ImageUtils
|
||||||
|
import fr.free.nrw.commons.utils.ImageUtilsWrapper
|
||||||
|
import io.reactivex.Single
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.ArgumentMatchers
|
||||||
|
import org.mockito.InjectMocks
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.*
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
import java.io.FileInputStream
|
||||||
|
|
||||||
|
class ImageProcessingServiceTest {
|
||||||
|
@Mock
|
||||||
|
internal var fileUtilsWrapper: FileUtilsWrapper? = null
|
||||||
|
@Mock
|
||||||
|
internal var bitmapRegionDecoderWrapper: BitmapRegionDecoderWrapper? = null
|
||||||
|
@Mock
|
||||||
|
internal var imageUtilsWrapper: ImageUtilsWrapper? = null
|
||||||
|
@Mock
|
||||||
|
internal var mwApi: MediaWikiApi? = null
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
var imageProcessingService: ImageProcessingService? = null
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
internal lateinit var uploadItem: UploadModel.UploadItem
|
||||||
|
|
||||||
|
@Before
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
val mediaUri = mock(Uri::class.java)
|
||||||
|
val mockPlace = mock(Place::class.java)
|
||||||
|
val mockTitle = mock(Title::class.java)
|
||||||
|
|
||||||
|
`when`(mockPlace.wikiDataEntityId).thenReturn("Q1")
|
||||||
|
`when`(mockPlace.getLocation()).thenReturn(mock(LatLng::class.java))
|
||||||
|
`when`(mediaUri.path).thenReturn("filePath")
|
||||||
|
`when`(mockTitle.isEmpty).thenReturn(false)
|
||||||
|
`when`(mockTitle.isSet).thenReturn(true)
|
||||||
|
|
||||||
|
`when`(uploadItem.mediaUri).thenReturn(mediaUri)
|
||||||
|
`when`(uploadItem.imageQuality).thenReturn(ImageUtils.IMAGE_WAIT)
|
||||||
|
|
||||||
|
`when`(uploadItem.title).thenReturn(mockTitle)
|
||||||
|
|
||||||
|
`when`(uploadItem.place).thenReturn(mockPlace)
|
||||||
|
|
||||||
|
`when`(fileUtilsWrapper!!.getFileInputStream(ArgumentMatchers.anyString()))
|
||||||
|
.thenReturn(mock(FileInputStream::class.java))
|
||||||
|
`when`(fileUtilsWrapper!!.getSHA1(any(FileInputStream::class.java)))
|
||||||
|
.thenReturn("fileSha")
|
||||||
|
|
||||||
|
`when`(fileUtilsWrapper!!.getGeolocationOfFile(ArgumentMatchers.anyString()))
|
||||||
|
.thenReturn("latLng")
|
||||||
|
|
||||||
|
`when`(bitmapRegionDecoderWrapper!!.newInstance(any(FileInputStream::class.java), anyBoolean()))
|
||||||
|
.thenReturn(mock(BitmapRegionDecoder::class.java))
|
||||||
|
`when`(imageUtilsWrapper!!.checkIfImageIsTooDark(any(BitmapRegionDecoder::class.java)))
|
||||||
|
.thenReturn(Single.just(ImageUtils.IMAGE_OK))
|
||||||
|
|
||||||
|
`when`(imageUtilsWrapper!!.checkImageGeolocationIsDifferent(ArgumentMatchers.anyString(), any(LatLng::class.java)))
|
||||||
|
.thenReturn(Single.just(ImageUtils.IMAGE_OK))
|
||||||
|
|
||||||
|
`when`(fileUtilsWrapper!!.getFileInputStream(ArgumentMatchers.anyString()))
|
||||||
|
.thenReturn(mock(FileInputStream::class.java))
|
||||||
|
`when`(fileUtilsWrapper!!.getSHA1(any(FileInputStream::class.java)))
|
||||||
|
.thenReturn("fileSha")
|
||||||
|
`when`(mwApi!!.existingFile(ArgumentMatchers.anyString()))
|
||||||
|
.thenReturn(false)
|
||||||
|
`when`(mwApi!!.fileExistsWithName(ArgumentMatchers.anyString()))
|
||||||
|
.thenReturn(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun validateImageForKeepImage() {
|
||||||
|
`when`(uploadItem.imageQuality).thenReturn(ImageUtils.IMAGE_KEEP)
|
||||||
|
val validateImage = imageProcessingService!!.validateImage(uploadItem, false)
|
||||||
|
assertEquals(ImageUtils.IMAGE_OK, validateImage.blockingGet())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun validateImageForDuplicateImage() {
|
||||||
|
`when`(mwApi!!.existingFile(ArgumentMatchers.anyString()))
|
||||||
|
.thenReturn(true)
|
||||||
|
val validateImage = imageProcessingService!!.validateImage(uploadItem, false)
|
||||||
|
assertEquals(ImageUtils.IMAGE_DUPLICATE, validateImage.blockingGet())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun validateImageForOkImage() {
|
||||||
|
val validateImage = imageProcessingService!!.validateImage(uploadItem, false)
|
||||||
|
assertEquals(ImageUtils.IMAGE_OK, validateImage.blockingGet())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun validateImageForDarkImage() {
|
||||||
|
`when`(imageUtilsWrapper!!.checkIfImageIsTooDark(any(BitmapRegionDecoder::class.java)))
|
||||||
|
.thenReturn(Single.just(ImageUtils.IMAGE_DARK))
|
||||||
|
val validateImage = imageProcessingService!!.validateImage(uploadItem, false)
|
||||||
|
assertEquals(ImageUtils.IMAGE_DARK, validateImage.blockingGet())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun validateImageForWrongGeoLocation() {
|
||||||
|
`when`(imageUtilsWrapper!!.checkImageGeolocationIsDifferent(ArgumentMatchers.anyString(), any(LatLng::class.java)))
|
||||||
|
.thenReturn(Single.just(ImageUtils.IMAGE_GEOLOCATION_DIFFERENT))
|
||||||
|
val validateImage = imageProcessingService!!.validateImage(uploadItem, false)
|
||||||
|
assertEquals(ImageUtils.IMAGE_GEOLOCATION_DIFFERENT, validateImage.blockingGet())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun validateImageForFileNameExistsWithCheckTitleOff() {
|
||||||
|
`when`(mwApi!!.fileExistsWithName(ArgumentMatchers.anyString()))
|
||||||
|
.thenReturn(true)
|
||||||
|
val validateImage = imageProcessingService!!.validateImage(uploadItem, false)
|
||||||
|
assertEquals(ImageUtils.IMAGE_OK, validateImage.blockingGet())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun validateImageForFileNameExistsWithCheckTitleOn() {
|
||||||
|
`when`(mwApi!!.fileExistsWithName(ArgumentMatchers.nullable(String::class.java)))
|
||||||
|
.thenReturn(true)
|
||||||
|
val validateImage = imageProcessingService!!.validateImage(uploadItem, true)
|
||||||
|
assertEquals(ImageUtils.FILE_NAME_EXISTS, validateImage.blockingGet())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,8 @@ package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
|
||||||
import fr.free.nrw.commons.auth.SessionManager
|
import fr.free.nrw.commons.auth.SessionManager
|
||||||
|
import fr.free.nrw.commons.contributions.UploadableFile
|
||||||
import fr.free.nrw.commons.kvstore.BasicKvStore
|
import fr.free.nrw.commons.kvstore.BasicKvStore
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi
|
import fr.free.nrw.commons.mwapi.MediaWikiApi
|
||||||
import fr.free.nrw.commons.nearby.Place
|
import fr.free.nrw.commons.nearby.Place
|
||||||
|
|
@ -14,8 +14,7 @@ import org.junit.Assert.assertFalse
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.ArgumentMatchers.any
|
import org.mockito.ArgumentMatchers.*
|
||||||
import org.mockito.ArgumentMatchers.anyString
|
|
||||||
import org.mockito.InjectMocks
|
import org.mockito.InjectMocks
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.`when`
|
import org.mockito.Mockito.`when`
|
||||||
|
|
@ -70,10 +69,8 @@ class UploadModelTest {
|
||||||
.thenReturn(mock(FileInputStream::class.java))
|
.thenReturn(mock(FileInputStream::class.java))
|
||||||
`when`(fileUtilsWrapper!!.getGeolocationOfFile(anyString()))
|
`when`(fileUtilsWrapper!!.getGeolocationOfFile(anyString()))
|
||||||
.thenReturn("")
|
.thenReturn("")
|
||||||
`when`(imageProcessingService!!.checkImageQuality(anyString()))
|
`when`(imageProcessingService!!.validateImage(any(UploadModel.UploadItem::class.java), anyBoolean()))
|
||||||
.thenReturn(Single.just(IMAGE_OK))
|
.thenReturn(Single.just(IMAGE_OK));
|
||||||
`when`(imageProcessingService!!.checkImageQuality(any(Place::class.java), anyString()))
|
|
||||||
.thenReturn(Single.just(IMAGE_OK))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,10 +81,7 @@ class UploadModelTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun receive() {
|
fun receive() {
|
||||||
val element = getElement()
|
val preProcessImages = uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
||||||
val element2 = getElement()
|
|
||||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
|
||||||
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
preProcessImages.doOnComplete {
|
preProcessImages.doOnComplete {
|
||||||
assertTrue(uploadModel!!.items.size == 2)
|
assertTrue(uploadModel!!.items.size == 2)
|
||||||
}
|
}
|
||||||
|
|
@ -95,46 +89,31 @@ class UploadModelTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun verifyPreviousNotAvailable() {
|
fun verifyPreviousNotAvailable() {
|
||||||
val element = getElement()
|
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
||||||
val element2 = getElement()
|
|
||||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
|
||||||
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
assertFalse(uploadModel!!.isPreviousAvailable)
|
assertFalse(uploadModel!!.isPreviousAvailable)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun verifyNextAvailable() {
|
fun verifyNextAvailable() {
|
||||||
val element = getElement()
|
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
||||||
val element2 = getElement()
|
|
||||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
|
||||||
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
assertTrue(uploadModel!!.isNextAvailable)
|
assertTrue(uploadModel!!.isNextAvailable)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun isSubmitAvailable() {
|
fun isSubmitAvailable() {
|
||||||
val element = getElement()
|
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
||||||
val element2 = getElement()
|
|
||||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
|
||||||
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
assertTrue(uploadModel!!.isNextAvailable)
|
assertTrue(uploadModel!!.isNextAvailable)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getCurrentStep() {
|
fun getCurrentStep() {
|
||||||
val element = getElement()
|
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
||||||
val element2 = getElement()
|
|
||||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
|
||||||
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
assertTrue(uploadModel!!.currentStep == 1)
|
assertTrue(uploadModel!!.currentStep == 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getStepCount() {
|
fun getStepCount() {
|
||||||
val element = getElement()
|
val preProcessImages = uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
||||||
val element2 = getElement()
|
|
||||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
|
||||||
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
preProcessImages.doOnComplete {
|
preProcessImages.doOnComplete {
|
||||||
assertTrue(uploadModel!!.stepCount == 4)
|
assertTrue(uploadModel!!.stepCount == 4)
|
||||||
}
|
}
|
||||||
|
|
@ -142,10 +121,7 @@ class UploadModelTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getCount() {
|
fun getCount() {
|
||||||
val element = getElement()
|
val preProcessImages = uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
||||||
val element2 = getElement()
|
|
||||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
|
||||||
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
preProcessImages.doOnComplete {
|
preProcessImages.doOnComplete {
|
||||||
assertTrue(uploadModel!!.count == 2)
|
assertTrue(uploadModel!!.count == 2)
|
||||||
}
|
}
|
||||||
|
|
@ -153,10 +129,7 @@ class UploadModelTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getUploads() {
|
fun getUploads() {
|
||||||
val element = getElement()
|
val preProcessImages = uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
||||||
val element2 = getElement()
|
|
||||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
|
||||||
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
preProcessImages.doOnComplete {
|
preProcessImages.doOnComplete {
|
||||||
assertTrue(uploadModel!!.uploads.size == 2)
|
assertTrue(uploadModel!!.uploads.size == 2)
|
||||||
}
|
}
|
||||||
|
|
@ -164,19 +137,13 @@ class UploadModelTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun isTopCardState() {
|
fun isTopCardState() {
|
||||||
val element = getElement()
|
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
||||||
val element2 = getElement()
|
|
||||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
|
||||||
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
assertTrue(uploadModel!!.isTopCardState)
|
assertTrue(uploadModel!!.isTopCardState)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun next() {
|
fun next() {
|
||||||
val element = getElement()
|
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
||||||
val element2 = getElement()
|
|
||||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
|
||||||
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
assertTrue(uploadModel!!.currentStep == 1)
|
assertTrue(uploadModel!!.currentStep == 1)
|
||||||
uploadModel!!.next()
|
uploadModel!!.next()
|
||||||
assertTrue(uploadModel!!.currentStep == 2)
|
assertTrue(uploadModel!!.currentStep == 2)
|
||||||
|
|
@ -184,10 +151,7 @@ class UploadModelTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun previous() {
|
fun previous() {
|
||||||
val element = getElement()
|
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
||||||
val element2 = getElement()
|
|
||||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
|
||||||
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
assertTrue(uploadModel!!.currentStep == 1)
|
assertTrue(uploadModel!!.currentStep == 1)
|
||||||
uploadModel!!.next()
|
uploadModel!!.next()
|
||||||
assertTrue(uploadModel!!.currentStep == 2)
|
assertTrue(uploadModel!!.currentStep == 2)
|
||||||
|
|
@ -197,18 +161,22 @@ class UploadModelTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun isShowingItem() {
|
fun isShowingItem() {
|
||||||
val element = getElement()
|
val preProcessImages = uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
||||||
val element2 = getElement()
|
|
||||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
|
||||||
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
preProcessImages.doOnComplete {
|
preProcessImages.doOnComplete {
|
||||||
assertTrue(uploadModel!!.isShowingItem)
|
assertTrue(uploadModel!!.isShowingItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getElement(): Uri {
|
private fun getMediaList(): List<UploadableFile> {
|
||||||
val mock = mock(Uri::class.java)
|
val element = getElement()
|
||||||
`when`(mock.path).thenReturn(UUID.randomUUID().toString() + "/file.jpg")
|
val element2 = getElement()
|
||||||
|
var uriList: List<UploadableFile> = mutableListOf(element, element2)
|
||||||
|
return uriList
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getElement(): UploadableFile {
|
||||||
|
val mock = mock(UploadableFile::class.java)
|
||||||
|
`when`(mock.filePath).thenReturn(UUID.randomUUID().toString() + "/filePath.jpg")
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package fr.free.nrw.commons.upload
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
import android.net.Uri
|
import fr.free.nrw.commons.contributions.UploadableFile
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi
|
import fr.free.nrw.commons.mwapi.MediaWikiApi
|
||||||
import fr.free.nrw.commons.nearby.Place
|
import fr.free.nrw.commons.nearby.Place
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
|
|
@ -26,8 +26,7 @@ class UploadPresenterTest {
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
`when`(uploadModel!!.preProcessImages(ArgumentMatchers.anyListOf(Uri::class.java),
|
`when`(uploadModel!!.preProcessImages(ArgumentMatchers.anyListOf(UploadableFile::class.java),
|
||||||
ArgumentMatchers.anyString(),
|
|
||||||
ArgumentMatchers.any(Place::class.java),
|
ArgumentMatchers.any(Place::class.java),
|
||||||
ArgumentMatchers.anyString(),
|
ArgumentMatchers.anyString(),
|
||||||
ArgumentMatchers.any(SimilarImageInterface::class.java)))
|
ArgumentMatchers.any(SimilarImageInterface::class.java)))
|
||||||
|
|
@ -36,9 +35,9 @@ class UploadPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun receiveMultipleItems() {
|
fun receiveMultipleItems() {
|
||||||
val element = Mockito.mock(Uri::class.java)
|
val element = Mockito.mock(UploadableFile::class.java)
|
||||||
val element2 = Mockito.mock(Uri::class.java)
|
val element2 = Mockito.mock(UploadableFile::class.java)
|
||||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
var uriList: List<UploadableFile> = mutableListOf<UploadableFile>(element, element2)
|
||||||
uploadPresenter!!.receive(uriList, "image/jpeg", "external", mock(Place::class.java))
|
uploadPresenter!!.receive(uriList, "external", mock(Place::class.java))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue