mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-30 22:34:02 +01:00
Convert UploadMediaPresenter to kotlin
This commit is contained in:
parent
e4b4ceb39d
commit
69f804438e
9 changed files with 530 additions and 613 deletions
|
|
@ -46,7 +46,7 @@ class UploadRepository @Inject constructor(
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
fun buildContributions(): Observable<Contribution>? {
|
fun buildContributions(): Observable<Contribution> {
|
||||||
return uploadModel.buildContributions()
|
return uploadModel.buildContributions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,7 +177,7 @@ class UploadRepository @Inject constructor(
|
||||||
place: Place?,
|
place: Place?,
|
||||||
similarImageInterface: SimilarImageInterface?,
|
similarImageInterface: SimilarImageInterface?,
|
||||||
inAppPictureLocation: LatLng?
|
inAppPictureLocation: LatLng?
|
||||||
): Observable<UploadItem>? {
|
): Observable<UploadItem> {
|
||||||
return uploadModel.preProcessImage(
|
return uploadModel.preProcessImage(
|
||||||
uploadableFile,
|
uploadableFile,
|
||||||
place,
|
place,
|
||||||
|
|
@ -193,7 +193,7 @@ class UploadRepository @Inject constructor(
|
||||||
* @param location Location of the image
|
* @param location Location of the image
|
||||||
* @return Quality of UploadItem
|
* @return Quality of UploadItem
|
||||||
*/
|
*/
|
||||||
fun getImageQuality(uploadItem: UploadItem, location: LatLng?): Single<Int>? {
|
fun getImageQuality(uploadItem: UploadItem, location: LatLng?): Single<Int> {
|
||||||
return uploadModel.getImageQuality(uploadItem, location)
|
return uploadModel.getImageQuality(uploadItem, location)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,7 +213,7 @@ class UploadRepository @Inject constructor(
|
||||||
* @param uploadItem UploadItem whose caption is to be checked
|
* @param uploadItem UploadItem whose caption is to be checked
|
||||||
* @return Quality of caption of the UploadItem
|
* @return Quality of caption of the UploadItem
|
||||||
*/
|
*/
|
||||||
fun getCaptionQuality(uploadItem: UploadItem): Single<Int>? {
|
fun getCaptionQuality(uploadItem: UploadItem): Single<Int> {
|
||||||
return uploadModel.getCaptionQuality(uploadItem)
|
return uploadModel.getCaptionQuality(uploadItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -175,11 +175,11 @@ class UploadModel @Inject internal constructor(
|
||||||
Timber.d(
|
Timber.d(
|
||||||
"Created timestamp while building contribution is %s, %s",
|
"Created timestamp while building contribution is %s, %s",
|
||||||
item.createdTimestamp,
|
item.createdTimestamp,
|
||||||
Date(item.createdTimestamp!!)
|
item.createdTimestamp?.let { Date(it) }
|
||||||
)
|
)
|
||||||
|
|
||||||
if (item.createdTimestamp != -1L) {
|
if (item.createdTimestamp != -1L) {
|
||||||
contribution.dateCreated = Date(item.createdTimestamp)
|
contribution.dateCreated = item.createdTimestamp?.let { Date(it) }
|
||||||
contribution.dateCreatedSource = item.createdTimestampSource
|
contribution.dateCreatedSource = item.createdTimestampSource
|
||||||
//Set the date only if you have it, else the upload service is gonna try it the other way
|
//Set the date only if you have it, else the upload service is gonna try it the other way
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package fr.free.nrw.commons.upload
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import fr.free.nrw.commons.CommonsApplication
|
|
||||||
import fr.free.nrw.commons.CommonsApplication.Companion.IS_LIMITED_CONNECTION_MODE_ENABLED
|
import fr.free.nrw.commons.CommonsApplication.Companion.IS_LIMITED_CONNECTION_MODE_ENABLED
|
||||||
import fr.free.nrw.commons.R
|
import fr.free.nrw.commons.R
|
||||||
import fr.free.nrw.commons.contributions.Contribution
|
import fr.free.nrw.commons.contributions.Contribution
|
||||||
|
|
@ -69,8 +68,7 @@ class UploadPresenter @Inject internal constructor(
|
||||||
private fun processContributionsForSubmission() {
|
private fun processContributionsForSubmission() {
|
||||||
if (view.isLoggedIn()) {
|
if (view.isLoggedIn()) {
|
||||||
view.showProgress(true)
|
view.showProgress(true)
|
||||||
repository.buildContributions()
|
repository.buildContributions().observeOn(Schedulers.io())
|
||||||
?.observeOn(Schedulers.io())
|
|
||||||
?.subscribe(object : Observer<Contribution> {
|
?.subscribe(object : Observer<Contribution> {
|
||||||
override fun onSubscribe(d: Disposable) {
|
override fun onSubscribe(d: Disposable) {
|
||||||
view.showProgress(false)
|
view.showProgress(false)
|
||||||
|
|
@ -133,8 +131,9 @@ class UploadPresenter @Inject internal constructor(
|
||||||
* @param uploadItemIndex Index of next image, whose quality is to be checked
|
* @param uploadItemIndex Index of next image, whose quality is to be checked
|
||||||
*/
|
*/
|
||||||
override fun checkImageQuality(uploadItemIndex: Int) {
|
override fun checkImageQuality(uploadItemIndex: Int) {
|
||||||
val uploadItem = repository.getUploadItem(uploadItemIndex)
|
repository.getUploadItem(uploadItemIndex)?.let {
|
||||||
presenter.checkImageQuality(uploadItem, uploadItemIndex)
|
presenter.checkImageQuality(it, uploadItemIndex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deletePictureAtIndex(index: Int) {
|
override fun deletePictureAtIndex(index: Int) {
|
||||||
|
|
@ -156,8 +155,9 @@ class UploadPresenter @Inject internal constructor(
|
||||||
view.onUploadMediaDeleted(index)
|
view.onUploadMediaDeleted(index)
|
||||||
if (index != uploadableFiles.size && index != 0) {
|
if (index != uploadableFiles.size && index != 0) {
|
||||||
// if the deleted image was not the last item to be uploaded, check quality of next
|
// if the deleted image was not the last item to be uploaded, check quality of next
|
||||||
val uploadItem = repository.getUploadItem(index)
|
repository.getUploadItem(index)?.let {
|
||||||
presenter.checkImageQuality(uploadItem, index)
|
presenter.checkImageQuality(it, index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uploadableFiles.size < 2) {
|
if (uploadableFiles.size < 2) {
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
|
|
@ -154,7 +155,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
|
|
||||||
public void setCallback(UploadMediaDetailFragmentCallback callback) {
|
public void setCallback(UploadMediaDetailFragmentCallback callback) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
UploadMediaPresenter.presenterCallback = callback;
|
UploadMediaPresenter.Companion.setPresenterCallback(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -190,12 +191,12 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
activity = getActivity();
|
activity = requireActivity();
|
||||||
basicKvStore = new BasicKvStore(activity, "CurrentUploadImageQualities");
|
basicKvStore = new BasicKvStore(activity, "CurrentUploadImageQualities");
|
||||||
|
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
indexOfFragment = callback.getIndexInViewFlipper(this);
|
indexOfFragment = callback.getIndexInViewFlipper(this);
|
||||||
init();
|
initializeFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(savedInstanceState!=null){
|
if(savedInstanceState!=null){
|
||||||
|
|
@ -207,7 +208,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if(!presenter.getImageQuality(indexOfFragment, inAppPictureLocation, getActivity())) {
|
if(!presenter.getImageQuality(indexOfFragment, inAppPictureLocation, requireActivity())) {
|
||||||
ActivityUtils.startActivityWithFlags(
|
ActivityUtils.startActivityWithFlags(
|
||||||
getActivity(), MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
|
getActivity(), MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
|
||||||
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
|
@ -217,7 +218,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
private void initializeFragment() {
|
||||||
if (binding == null) {
|
if (binding == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -373,7 +374,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onImageProcessed(UploadItem uploadItem, Place place) {
|
public void onImageProcessed(@NotNull UploadItem uploadItem) {
|
||||||
if (binding == null) {
|
if (binding == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -386,7 +387,8 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
* @param place
|
* @param place
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onNearbyPlaceFound(UploadItem uploadItem, Place place) {
|
public void onNearbyPlaceFound(
|
||||||
|
@NotNull UploadItem uploadItem, @org.jetbrains.annotations.Nullable Place place) {
|
||||||
nearbyPlace = place;
|
nearbyPlace = place;
|
||||||
this.uploadItem = uploadItem;
|
this.uploadItem = uploadItem;
|
||||||
showNearbyFound = true;
|
showNearbyFound = true;
|
||||||
|
|
@ -506,7 +508,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showDuplicatePicturePopup(UploadItem uploadItem) {
|
public void showDuplicatePicturePopup(@NotNull UploadItem uploadItem) {
|
||||||
if (defaultKvStore.getBoolean("showDuplicatePicturePopup", true)) {
|
if (defaultKvStore.getBoolean("showDuplicatePicturePopup", true)) {
|
||||||
String uploadTitleFormat = getString(R.string.upload_title_duplicate);
|
String uploadTitleFormat = getString(R.string.upload_title_duplicate);
|
||||||
View checkBoxView = View
|
View checkBoxView = View
|
||||||
|
|
@ -517,7 +519,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
defaultKvStore.putBoolean("showDuplicatePicturePopup", false);
|
defaultKvStore.putBoolean("showDuplicatePicturePopup", false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
DialogUtil.showAlertDialog(getActivity(),
|
DialogUtil.showAlertDialog(requireActivity(),
|
||||||
getString(R.string.duplicate_file_name),
|
getString(R.string.duplicate_file_name),
|
||||||
String.format(Locale.getDefault(),
|
String.format(Locale.getDefault(),
|
||||||
uploadTitleFormat,
|
uploadTitleFormat,
|
||||||
|
|
@ -597,7 +599,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showExternalMap(final UploadItem uploadItem) {
|
public void showExternalMap(@NotNull final UploadItem uploadItem) {
|
||||||
goToLocationPickerActivity(uploadItem);
|
goToLocationPickerActivity(uploadItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -612,7 +614,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
* is started using resultLauncher that handles the result in respective callback.
|
* is started using resultLauncher that handles the result in respective callback.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void showEditActivity(UploadItem uploadItem) {
|
public void showEditActivity(@NotNull UploadItem uploadItem) {
|
||||||
editableUploadItem = uploadItem;
|
editableUploadItem = uploadItem;
|
||||||
Intent intent = new Intent(getContext(), EditActivity.class);
|
Intent intent = new Intent(getContext(), EditActivity.class);
|
||||||
intent.putExtra("image", uploadableFile.getFilePath().toString());
|
intent.putExtra("image", uploadableFile.getFilePath().toString());
|
||||||
|
|
@ -789,7 +791,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateMediaDetails(List<UploadMediaDetail> uploadMediaDetails) {
|
public void updateMediaDetails(@NotNull List<UploadMediaDetail> uploadMediaDetails) {
|
||||||
uploadMediaDetailAdapter.setItems(uploadMediaDetails);
|
uploadMediaDetailAdapter.setItems(uploadMediaDetails);
|
||||||
showNearbyFound =
|
showNearbyFound =
|
||||||
showNearbyFound && (
|
showNearbyFound && (
|
||||||
|
|
@ -823,7 +825,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
* @param onSkipClicked proceed for verifying image quality
|
* @param onSkipClicked proceed for verifying image quality
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void displayAddLocationDialog(final Runnable onSkipClicked) {
|
public void displayAddLocationDialog(@NotNull final Runnable onSkipClicked) {
|
||||||
isMissingLocationDialog = true;
|
isMissingLocationDialog = true;
|
||||||
DialogUtil.showAlertDialog(requireActivity(),
|
DialogUtil.showAlertDialog(requireActivity(),
|
||||||
getString(R.string.no_location_found_title),
|
getString(R.string.no_location_found_title),
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,9 @@ import fr.free.nrw.commons.upload.UploadMediaDetail
|
||||||
*/
|
*/
|
||||||
interface UploadMediaDetailsContract {
|
interface UploadMediaDetailsContract {
|
||||||
interface View : SimilarImageInterface {
|
interface View : SimilarImageInterface {
|
||||||
fun onImageProcessed(uploadItem: UploadItem?, place: Place?)
|
fun onImageProcessed(uploadItem: UploadItem)
|
||||||
|
|
||||||
fun onNearbyPlaceFound(uploadItem: UploadItem?, place: Place?)
|
fun onNearbyPlaceFound(uploadItem: UploadItem, place: Place?)
|
||||||
|
|
||||||
fun showProgress(shouldShow: Boolean)
|
fun showProgress(shouldShow: Boolean)
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ interface UploadMediaDetailsContract {
|
||||||
|
|
||||||
fun showMessage(message: String?, colorResourceId: Int)
|
fun showMessage(message: String?, colorResourceId: Int)
|
||||||
|
|
||||||
fun showDuplicatePicturePopup(uploadItem: UploadItem?)
|
fun showDuplicatePicturePopup(uploadItem: UploadItem)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a dialog alerting the user that internet connection is required for upload process
|
* Shows a dialog alerting the user that internet connection is required for upload process
|
||||||
|
|
@ -42,13 +42,13 @@ interface UploadMediaDetailsContract {
|
||||||
*/
|
*/
|
||||||
fun showConnectionErrorPopupForCaptionCheck()
|
fun showConnectionErrorPopupForCaptionCheck()
|
||||||
|
|
||||||
fun showExternalMap(uploadItem: UploadItem?)
|
fun showExternalMap(uploadItem: UploadItem)
|
||||||
|
|
||||||
fun showEditActivity(uploadItem: UploadItem?)
|
fun showEditActivity(uploadItem: UploadItem)
|
||||||
|
|
||||||
fun updateMediaDetails(uploadMediaDetails: List<UploadMediaDetail?>?)
|
fun updateMediaDetails(uploadMediaDetails: List<UploadMediaDetail>)
|
||||||
|
|
||||||
fun displayAddLocationDialog(runnable: Runnable?)
|
fun displayAddLocationDialog(runnable: Runnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserActionListener : BasePresenter<View?> {
|
interface UserActionListener : BasePresenter<View?> {
|
||||||
|
|
@ -59,7 +59,7 @@ interface UploadMediaDetailsContract {
|
||||||
)
|
)
|
||||||
|
|
||||||
fun setUploadMediaDetails(
|
fun setUploadMediaDetails(
|
||||||
uploadMediaDetails: List<UploadMediaDetail?>?,
|
uploadMediaDetails: List<UploadMediaDetail>,
|
||||||
uploadItemIndex: Int
|
uploadItemIndex: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -74,7 +74,7 @@ interface UploadMediaDetailsContract {
|
||||||
fun getImageQuality(
|
fun getImageQuality(
|
||||||
uploadItemIndex: Int,
|
uploadItemIndex: Int,
|
||||||
inAppPictureLocation: LatLng?,
|
inAppPictureLocation: LatLng?,
|
||||||
activity: Activity?
|
activity: Activity
|
||||||
): Boolean
|
): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -87,7 +87,8 @@ interface UploadMediaDetailsContract {
|
||||||
* @param hasUserRemovedLocation True if user has removed location from the image
|
* @param hasUserRemovedLocation True if user has removed location from the image
|
||||||
*/
|
*/
|
||||||
fun displayLocDialog(
|
fun displayLocDialog(
|
||||||
uploadItemIndex: Int, inAppPictureLocation: LatLng?,
|
uploadItemIndex: Int,
|
||||||
|
inAppPictureLocation: LatLng?,
|
||||||
hasUserRemovedLocation: Boolean
|
hasUserRemovedLocation: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -97,7 +98,7 @@ interface UploadMediaDetailsContract {
|
||||||
* @param uploadItem UploadItem whose quality is to be checked
|
* @param uploadItem UploadItem whose quality is to be checked
|
||||||
* @param index Index of the UploadItem whose quality is to be checked
|
* @param index Index of the UploadItem whose quality is to be checked
|
||||||
*/
|
*/
|
||||||
fun checkImageQuality(uploadItem: UploadItem?, index: Int)
|
fun checkImageQuality(uploadItem: UploadItem, index: Int)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the image qualities stored in JSON, whenever an image is deleted
|
* Updates the image qualities stored in JSON, whenever an image is deleted
|
||||||
|
|
@ -111,7 +112,7 @@ interface UploadMediaDetailsContract {
|
||||||
|
|
||||||
fun fetchTitleAndDescription(indexInViewFlipper: Int)
|
fun fetchTitleAndDescription(indexInViewFlipper: Int)
|
||||||
|
|
||||||
fun useSimilarPictureCoordinates(imageCoordinates: ImageCoordinates?, uploadItemIndex: Int)
|
fun useSimilarPictureCoordinates(imageCoordinates: ImageCoordinates, uploadItemIndex: Int)
|
||||||
|
|
||||||
fun onMapIconClicked(indexInViewFlipper: Int)
|
fun onMapIconClicked(indexInViewFlipper: Int)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,547 +0,0 @@
|
||||||
package fr.free.nrw.commons.upload.mediaDetails;
|
|
||||||
|
|
||||||
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
|
|
||||||
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
|
||||||
import static fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.activity;
|
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.EMPTY_CAPTION;
|
|
||||||
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_OK;
|
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import fr.free.nrw.commons.R;
|
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
|
||||||
import fr.free.nrw.commons.kvstore.BasicKvStore;
|
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
|
||||||
import fr.free.nrw.commons.repository.UploadRepository;
|
|
||||||
import fr.free.nrw.commons.upload.ImageCoordinates;
|
|
||||||
import fr.free.nrw.commons.upload.SimilarImageInterface;
|
|
||||||
import fr.free.nrw.commons.upload.UploadActivity;
|
|
||||||
import fr.free.nrw.commons.upload.UploadItem;
|
|
||||||
import fr.free.nrw.commons.upload.UploadMediaDetail;
|
|
||||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.UploadMediaDetailFragmentCallback;
|
|
||||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract.UserActionListener;
|
|
||||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract.View;
|
|
||||||
import fr.free.nrw.commons.utils.DialogUtil;
|
|
||||||
import io.github.coordinates2country.Coordinates2Country;
|
|
||||||
import io.reactivex.Maybe;
|
|
||||||
import io.reactivex.Scheduler;
|
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
|
||||||
import io.reactivex.disposables.Disposable;
|
|
||||||
import java.lang.reflect.Proxy;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class UploadMediaPresenter implements UserActionListener, SimilarImageInterface {
|
|
||||||
|
|
||||||
private static final UploadMediaDetailsContract.View DUMMY = (UploadMediaDetailsContract.View) Proxy
|
|
||||||
.newProxyInstance(
|
|
||||||
UploadMediaDetailsContract.View.class.getClassLoader(),
|
|
||||||
new Class[]{UploadMediaDetailsContract.View.class},
|
|
||||||
(proxy, method, methodArgs) -> null);
|
|
||||||
|
|
||||||
private final UploadRepository repository;
|
|
||||||
private UploadMediaDetailsContract.View view = DUMMY;
|
|
||||||
|
|
||||||
private CompositeDisposable compositeDisposable;
|
|
||||||
|
|
||||||
private final JsonKvStore defaultKVStore;
|
|
||||||
private Scheduler ioScheduler;
|
|
||||||
private Scheduler mainThreadScheduler;
|
|
||||||
|
|
||||||
public static UploadMediaDetailFragmentCallback presenterCallback ;
|
|
||||||
|
|
||||||
private final List<String> WLM_SUPPORTED_COUNTRIES= Arrays.asList("am","at","az","br","hr","sv","fi","fr","de","gh","in","ie","il","mk","my","mt","pk","pe","pl","ru","rw","si","es","se","tw","ug","ua","us");
|
|
||||||
private Map<String, String> countryNamesAndCodes = null;
|
|
||||||
|
|
||||||
private final String keyForCurrentUploadImageQualities = "UploadedImagesQualities";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Variable used to determine if the battery-optimisation dialog is being shown or not
|
|
||||||
*/
|
|
||||||
public static boolean isBatteryDialogShowing;
|
|
||||||
|
|
||||||
public static boolean isCategoriesDialogShowing;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public UploadMediaPresenter(final UploadRepository uploadRepository,
|
|
||||||
@Named("default_preferences") final JsonKvStore defaultKVStore,
|
|
||||||
@Named(IO_THREAD) final Scheduler ioScheduler,
|
|
||||||
@Named(MAIN_THREAD) final Scheduler mainThreadScheduler) {
|
|
||||||
this.repository = uploadRepository;
|
|
||||||
this.defaultKVStore = defaultKVStore;
|
|
||||||
this.ioScheduler = ioScheduler;
|
|
||||||
this.mainThreadScheduler = mainThreadScheduler;
|
|
||||||
compositeDisposable = new CompositeDisposable();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttachView(final View view) {
|
|
||||||
this.view = view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDetachView() {
|
|
||||||
this.view = DUMMY;
|
|
||||||
compositeDisposable.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the Upload Media Details for the corresponding upload item
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void setUploadMediaDetails(final List<UploadMediaDetail> uploadMediaDetails, final int uploadItemIndex) {
|
|
||||||
repository.getUploads().get(uploadItemIndex).setUploadMediaDetails(uploadMediaDetails);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receives the corresponding uploadable file, processes it and return the view with and uplaod item
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void receiveImage(final UploadableFile uploadableFile, final Place place,
|
|
||||||
final LatLng inAppPictureLocation) {
|
|
||||||
view.showProgress(true);
|
|
||||||
compositeDisposable.add(
|
|
||||||
repository
|
|
||||||
.preProcessImage(uploadableFile, place, this, inAppPictureLocation)
|
|
||||||
.map(uploadItem -> {
|
|
||||||
if(place!=null && place.isMonument()){
|
|
||||||
if (place.location != null) {
|
|
||||||
final String countryCode = reverseGeoCode(place.location);
|
|
||||||
if (countryCode != null && WLM_SUPPORTED_COUNTRIES
|
|
||||||
.contains(countryCode.toLowerCase(Locale.ROOT))) {
|
|
||||||
uploadItem.setWLMUpload(true);
|
|
||||||
uploadItem.setCountryCode(countryCode.toLowerCase(Locale.ROOT));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return uploadItem;
|
|
||||||
})
|
|
||||||
.subscribeOn(ioScheduler)
|
|
||||||
.observeOn(mainThreadScheduler)
|
|
||||||
.subscribe(uploadItem ->
|
|
||||||
{
|
|
||||||
view.onImageProcessed(uploadItem, place);
|
|
||||||
view.updateMediaDetails(uploadItem.getUploadMediaDetails());
|
|
||||||
view.showProgress(false);
|
|
||||||
final ImageCoordinates gpsCoords = uploadItem.getGpsCoords();
|
|
||||||
final boolean hasImageCoordinates =
|
|
||||||
gpsCoords != null && gpsCoords.getImageCoordsExists();
|
|
||||||
if (hasImageCoordinates && place == null) {
|
|
||||||
checkNearbyPlaces(uploadItem);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
throwable -> Timber.e(throwable, "Error occurred in processing images")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private String reverseGeoCode(final LatLng latLng){
|
|
||||||
if(countryNamesAndCodes == null){
|
|
||||||
countryNamesAndCodes = getCountryNamesAndCodes();
|
|
||||||
}
|
|
||||||
return countryNamesAndCodes.get(Coordinates2Country.country(latLng.getLatitude(), latLng.getLongitude()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates HashMap containing all ISO countries 2-letter codes provided by <code>Locale.getISOCountries()</code>
|
|
||||||
* and their english names
|
|
||||||
*
|
|
||||||
* @return HashMap where Key is country english name and Value is 2-letter country code
|
|
||||||
* e.g. ["Germany":"DE", ...]
|
|
||||||
*/
|
|
||||||
private Map<String, String> getCountryNamesAndCodes(){
|
|
||||||
final Map<String, String> result = new HashMap<>();
|
|
||||||
|
|
||||||
final String[] isoCountries = Locale.getISOCountries();
|
|
||||||
|
|
||||||
for (final String isoCountry : isoCountries) {
|
|
||||||
result.put(
|
|
||||||
new Locale("en", isoCountry).getDisplayCountry(Locale.ENGLISH),
|
|
||||||
isoCountry
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method checks for the nearest location that needs images and suggests it to the user.
|
|
||||||
*/
|
|
||||||
private void checkNearbyPlaces(final UploadItem uploadItem) {
|
|
||||||
final Disposable checkNearbyPlaces = Maybe.fromCallable(() -> repository
|
|
||||||
.checkNearbyPlaces(uploadItem.getGpsCoords().getDecLatitude(),
|
|
||||||
uploadItem.getGpsCoords().getDecLongitude()))
|
|
||||||
.subscribeOn(ioScheduler)
|
|
||||||
.observeOn(mainThreadScheduler)
|
|
||||||
.subscribe(place -> {
|
|
||||||
if (place != null) {
|
|
||||||
view.onNearbyPlaceFound(uploadItem, place);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
throwable -> Timber.e(throwable, "Error occurred in processing images"));
|
|
||||||
compositeDisposable.add(checkNearbyPlaces);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the image has a location. Displays a dialog alerting user that no
|
|
||||||
* location has been to added to the image and asking them to add one, if location was not
|
|
||||||
* removed by the user
|
|
||||||
*
|
|
||||||
* @param uploadItemIndex Index of the uploadItem which has no location
|
|
||||||
* @param inAppPictureLocation In app picture location (if any)
|
|
||||||
* @param hasUserRemovedLocation True if user has removed location from the image
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void displayLocDialog(final int uploadItemIndex, final LatLng inAppPictureLocation,
|
|
||||||
final boolean hasUserRemovedLocation) {
|
|
||||||
final List<UploadItem> uploadItems = repository.getUploads();
|
|
||||||
final UploadItem uploadItem = uploadItems.get(uploadItemIndex);
|
|
||||||
if (uploadItem.getGpsCoords().getDecimalCoords() == null && inAppPictureLocation == null
|
|
||||||
&& !hasUserRemovedLocation) {
|
|
||||||
final Runnable onSkipClicked = () -> {
|
|
||||||
verifyCaptionQuality(uploadItem);
|
|
||||||
};
|
|
||||||
view.displayAddLocationDialog(onSkipClicked);
|
|
||||||
} else {
|
|
||||||
verifyCaptionQuality(uploadItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies the image's caption and calls function to handle the result
|
|
||||||
*
|
|
||||||
* @param uploadItem UploadItem whose caption is checked
|
|
||||||
*/
|
|
||||||
private void verifyCaptionQuality(final UploadItem uploadItem) {
|
|
||||||
view.showProgress(true);
|
|
||||||
compositeDisposable.add(
|
|
||||||
repository
|
|
||||||
.getCaptionQuality(uploadItem)
|
|
||||||
.observeOn(mainThreadScheduler)
|
|
||||||
.subscribe(capResult -> {
|
|
||||||
view.showProgress(false);
|
|
||||||
handleCaptionResult(capResult, uploadItem);
|
|
||||||
},
|
|
||||||
throwable -> {
|
|
||||||
view.showProgress(false);
|
|
||||||
if (throwable instanceof UnknownHostException) {
|
|
||||||
view.showConnectionErrorPopupForCaptionCheck();
|
|
||||||
} else {
|
|
||||||
view.showMessage("" + throwable.getLocalizedMessage(),
|
|
||||||
R.color.color_error);
|
|
||||||
}
|
|
||||||
Timber.e(throwable, "Error occurred while handling image");
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles image's caption results and shows dialog if necessary
|
|
||||||
*
|
|
||||||
* @param errorCode Error code of the UploadItem
|
|
||||||
* @param uploadItem UploadItem whose caption is checked
|
|
||||||
*/
|
|
||||||
public void handleCaptionResult(final Integer errorCode, final UploadItem uploadItem) {
|
|
||||||
// If errorCode is empty caption show message
|
|
||||||
if (errorCode == EMPTY_CAPTION) {
|
|
||||||
Timber.d("Captions are empty. Showing toast");
|
|
||||||
view.showMessage(R.string.add_caption_toast, R.color.color_error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If image with same file name exists check the bit in errorCode is set or not
|
|
||||||
if ((errorCode & FILE_NAME_EXISTS) != 0) {
|
|
||||||
Timber.d("Trying to show duplicate picture popup");
|
|
||||||
view.showDuplicatePicturePopup(uploadItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If caption is not duplicate or user still wants to upload it
|
|
||||||
if (errorCode == IMAGE_OK) {
|
|
||||||
Timber.d("Image captions are okay or user still wants to upload it");
|
|
||||||
view.onImageValidationSuccess();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies the caption and description of the current item to the subsequent media
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void copyTitleAndDescriptionToSubsequentMedia(final int indexInViewFlipper) {
|
|
||||||
for(int i = indexInViewFlipper+1; i < repository.getCount(); i++){
|
|
||||||
final UploadItem subsequentUploadItem = repository.getUploads().get(i);
|
|
||||||
subsequentUploadItem.setUploadMediaDetails(deepCopy(repository.getUploads().get(indexInViewFlipper).getUploadMediaDetails()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches and set the caption and description of the item
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void fetchTitleAndDescription(final int indexInViewFlipper) {
|
|
||||||
final UploadItem currentUploadItem = repository.getUploads().get(indexInViewFlipper);
|
|
||||||
view.updateMediaDetails(currentUploadItem.getUploadMediaDetails());
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private List<UploadMediaDetail> deepCopy(final List<UploadMediaDetail> uploadMediaDetails) {
|
|
||||||
final ArrayList<UploadMediaDetail> newList = new ArrayList<>();
|
|
||||||
for (final UploadMediaDetail uploadMediaDetail : uploadMediaDetails) {
|
|
||||||
newList.add(uploadMediaDetail.javaCopy());
|
|
||||||
}
|
|
||||||
return newList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void useSimilarPictureCoordinates(final ImageCoordinates imageCoordinates, final int uploadItemIndex) {
|
|
||||||
repository.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMapIconClicked(final int indexInViewFlipper) {
|
|
||||||
view.showExternalMap(repository.getUploads().get(indexInViewFlipper));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEditButtonClicked(final int indexInViewFlipper){
|
|
||||||
view.showEditActivity(repository.getUploads().get(indexInViewFlipper));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the information regarding the specified place for the specified upload item
|
|
||||||
* when the user confirms the suggested nearby place.
|
|
||||||
*
|
|
||||||
* @param place The place to be associated with the uploads.
|
|
||||||
* @param uploadItemIndex Index of the uploadItem whose detected place has been confirmed
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onUserConfirmedUploadIsOfPlace(final Place place, final int uploadItemIndex) {
|
|
||||||
final UploadItem uploadItem = repository.getUploads().get(uploadItemIndex);
|
|
||||||
|
|
||||||
uploadItem.setPlace(place);
|
|
||||||
final List<UploadMediaDetail> uploadMediaDetails = uploadItem.getUploadMediaDetails();
|
|
||||||
// Update UploadMediaDetail object for this UploadItem
|
|
||||||
uploadMediaDetails.set(0, new UploadMediaDetail(place));
|
|
||||||
|
|
||||||
// Now that the UploadItem and its associated UploadMediaDetail objects have been updated,
|
|
||||||
// update the view with the modified media details of the first upload item
|
|
||||||
view.updateMediaDetails(uploadMediaDetails);
|
|
||||||
UploadActivity.setUploadIsOfAPlace(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the image quality
|
|
||||||
*
|
|
||||||
* @param uploadItemIndex Index of the UploadItem whose quality is to be checked
|
|
||||||
* @param inAppPictureLocation In app picture location (if any)
|
|
||||||
* @param activity Context reference
|
|
||||||
* @return true if no internal error occurs, else returns false
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean getImageQuality(final int uploadItemIndex, final LatLng inAppPictureLocation,
|
|
||||||
final Activity activity) {
|
|
||||||
final List<UploadItem> uploadItems = repository.getUploads();
|
|
||||||
view.showProgress(true);
|
|
||||||
if (uploadItems.isEmpty()) {
|
|
||||||
view.showProgress(false);
|
|
||||||
// No internationalization required for this error message because it's an internal error.
|
|
||||||
view.showMessage(
|
|
||||||
"Internal error: Zero upload items received by the Upload Media Detail Fragment. Sorry, please upload again.",
|
|
||||||
R.color.color_error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final UploadItem uploadItem = uploadItems.get(uploadItemIndex);
|
|
||||||
compositeDisposable.add(
|
|
||||||
repository
|
|
||||||
.getImageQuality(uploadItem, inAppPictureLocation)
|
|
||||||
.observeOn(mainThreadScheduler)
|
|
||||||
.subscribe(imageResult -> {
|
|
||||||
storeImageQuality(imageResult, uploadItemIndex, activity, uploadItem);
|
|
||||||
},
|
|
||||||
throwable -> {
|
|
||||||
if (throwable instanceof UnknownHostException) {
|
|
||||||
view.showProgress(false);
|
|
||||||
view.showConnectionErrorPopup();
|
|
||||||
} else {
|
|
||||||
view.showMessage("" + throwable.getLocalizedMessage(),
|
|
||||||
R.color.color_error);
|
|
||||||
}
|
|
||||||
Timber.e(throwable, "Error occurred while handling image");
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores the image quality in JSON format in SharedPrefs
|
|
||||||
*
|
|
||||||
* @param imageResult Image quality
|
|
||||||
* @param uploadItemIndex Index of the UploadItem whose quality is calculated
|
|
||||||
* @param activity Context reference
|
|
||||||
* @param uploadItem UploadItem whose quality is to be checked
|
|
||||||
*/
|
|
||||||
private void storeImageQuality(final Integer imageResult, final int uploadItemIndex, final Activity activity,
|
|
||||||
final UploadItem uploadItem) {
|
|
||||||
final BasicKvStore store = new BasicKvStore(activity,
|
|
||||||
UploadActivity.storeNameForCurrentUploadImagesSize);
|
|
||||||
final String value = store.getString(keyForCurrentUploadImageQualities, null);
|
|
||||||
final JSONObject jsonObject;
|
|
||||||
try {
|
|
||||||
if (value != null) {
|
|
||||||
jsonObject = new JSONObject(value);
|
|
||||||
} else {
|
|
||||||
jsonObject = new JSONObject();
|
|
||||||
}
|
|
||||||
jsonObject.put("UploadItem" + uploadItemIndex, imageResult);
|
|
||||||
store.putString(keyForCurrentUploadImageQualities, jsonObject.toString());
|
|
||||||
} catch (final Exception e) {
|
|
||||||
Timber.e(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uploadItemIndex == 0) {
|
|
||||||
if (!isBatteryDialogShowing && !isCategoriesDialogShowing) {
|
|
||||||
// if battery-optimisation dialog is not being shown, call checkImageQuality
|
|
||||||
checkImageQuality(uploadItem, uploadItemIndex);
|
|
||||||
} else {
|
|
||||||
view.showProgress(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to check image quality from stored qualities and display dialogs
|
|
||||||
*
|
|
||||||
* @param uploadItem UploadItem whose quality is to be checked
|
|
||||||
* @param index Index of the UploadItem whose quality is to be checked
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void checkImageQuality(final UploadItem uploadItem, final int index) {
|
|
||||||
if ((uploadItem.getImageQuality() != IMAGE_OK) && (uploadItem.getImageQuality()
|
|
||||||
!= IMAGE_KEEP)) {
|
|
||||||
final BasicKvStore store = new BasicKvStore(activity,
|
|
||||||
UploadActivity.storeNameForCurrentUploadImagesSize);
|
|
||||||
final String value = store.getString(keyForCurrentUploadImageQualities, null);
|
|
||||||
final JSONObject jsonObject;
|
|
||||||
try {
|
|
||||||
if (value != null) {
|
|
||||||
jsonObject = new JSONObject(value);
|
|
||||||
} else {
|
|
||||||
jsonObject = new JSONObject();
|
|
||||||
}
|
|
||||||
final Integer imageQuality = (int) jsonObject.get("UploadItem" + index);
|
|
||||||
view.showProgress(false);
|
|
||||||
if (imageQuality == IMAGE_OK) {
|
|
||||||
uploadItem.setHasInvalidLocation(false);
|
|
||||||
uploadItem.setImageQuality(imageQuality);
|
|
||||||
} else {
|
|
||||||
handleBadImage(imageQuality, uploadItem, index);
|
|
||||||
}
|
|
||||||
} catch (final Exception e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the image qualities stored in JSON, whenever an image is deleted
|
|
||||||
*
|
|
||||||
* @param size Size of uploadableFiles
|
|
||||||
* @param index Index of the UploadItem which was deleted
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void updateImageQualitiesJSON(final int size, final int index) {
|
|
||||||
final BasicKvStore store = new BasicKvStore(activity,
|
|
||||||
UploadActivity.storeNameForCurrentUploadImagesSize);
|
|
||||||
final String value = store.getString(keyForCurrentUploadImageQualities, null);
|
|
||||||
final JSONObject jsonObject;
|
|
||||||
try {
|
|
||||||
if (value != null) {
|
|
||||||
jsonObject = new JSONObject(value);
|
|
||||||
} else {
|
|
||||||
jsonObject = new JSONObject();
|
|
||||||
}
|
|
||||||
for (int i = index; i < (size - 1); i++) {
|
|
||||||
jsonObject.put("UploadItem" + i, jsonObject.get("UploadItem" + (i + 1)));
|
|
||||||
}
|
|
||||||
jsonObject.remove("UploadItem" + (size - 1));
|
|
||||||
store.putString(keyForCurrentUploadImageQualities, jsonObject.toString());
|
|
||||||
} catch (final Exception e) {
|
|
||||||
Timber.e(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles bad pictures, like too dark, already on wikimedia, downloaded from internet
|
|
||||||
*
|
|
||||||
* @param errorCode Error code of the bad image quality
|
|
||||||
* @param uploadItem UploadItem whose quality is bad
|
|
||||||
* @param index Index of item whose quality is bad
|
|
||||||
*/
|
|
||||||
public void handleBadImage(final Integer errorCode,
|
|
||||||
final UploadItem uploadItem, final int index) {
|
|
||||||
Timber.d("Handle bad picture with error code %d", errorCode);
|
|
||||||
if (errorCode >= 8) { // If location of image and nearby does not match
|
|
||||||
uploadItem.setHasInvalidLocation(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If image has some other problems, show popup accordingly
|
|
||||||
if (errorCode != EMPTY_CAPTION && errorCode != FILE_NAME_EXISTS) {
|
|
||||||
showBadImagePopup(errorCode, index, activity, uploadItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a dialog describing the potential problems in the current image
|
|
||||||
*
|
|
||||||
* @param errorCode Has the potential problems in the current image
|
|
||||||
* @param index Index of the UploadItem which has problems
|
|
||||||
* @param activity Context reference
|
|
||||||
* @param uploadItem UploadItem which has problems
|
|
||||||
*/
|
|
||||||
public void showBadImagePopup(final Integer errorCode,
|
|
||||||
final int index, final Activity activity, final UploadItem uploadItem) {
|
|
||||||
final String errorMessageForResult = getErrorMessageForResult(activity, errorCode);
|
|
||||||
if (!StringUtils.isBlank(errorMessageForResult)) {
|
|
||||||
DialogUtil.showAlertDialog(activity,
|
|
||||||
activity.getString(R.string.upload_problem_image),
|
|
||||||
errorMessageForResult,
|
|
||||||
activity.getString(R.string.upload),
|
|
||||||
activity.getString(R.string.cancel),
|
|
||||||
() -> {
|
|
||||||
view.showProgress(false);
|
|
||||||
uploadItem.setImageQuality(IMAGE_OK);
|
|
||||||
},
|
|
||||||
() -> {
|
|
||||||
presenterCallback.deletePictureAtIndex(index);
|
|
||||||
}
|
|
||||||
).setCancelable(false);
|
|
||||||
}
|
|
||||||
//If the error message is null, we will probably not show anything
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* notifies the user that a similar image exists
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void showSimilarImageFragment(final String originalFilePath, final String possibleFilePath,
|
|
||||||
final ImageCoordinates similarImageCoordinates) {
|
|
||||||
view.showSimilarImageFragment(originalFilePath, possibleFilePath,
|
|
||||||
similarImageCoordinates
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,472 @@
|
||||||
|
package fr.free.nrw.commons.upload.mediaDetails
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import fr.free.nrw.commons.R
|
||||||
|
import fr.free.nrw.commons.di.CommonsApplicationModule.Companion.IO_THREAD
|
||||||
|
import fr.free.nrw.commons.di.CommonsApplicationModule.Companion.MAIN_THREAD
|
||||||
|
import fr.free.nrw.commons.filepicker.UploadableFile
|
||||||
|
import fr.free.nrw.commons.kvstore.BasicKvStore
|
||||||
|
import fr.free.nrw.commons.location.LatLng
|
||||||
|
import fr.free.nrw.commons.nearby.Place
|
||||||
|
import fr.free.nrw.commons.repository.UploadRepository
|
||||||
|
import fr.free.nrw.commons.upload.ImageCoordinates
|
||||||
|
import fr.free.nrw.commons.upload.SimilarImageInterface
|
||||||
|
import fr.free.nrw.commons.upload.UploadActivity
|
||||||
|
import fr.free.nrw.commons.upload.UploadActivity.Companion.setUploadIsOfAPlace
|
||||||
|
import fr.free.nrw.commons.upload.UploadItem
|
||||||
|
import fr.free.nrw.commons.upload.UploadMediaDetail
|
||||||
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.UploadMediaDetailFragmentCallback
|
||||||
|
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
|
||||||
|
import fr.free.nrw.commons.utils.ImageUtils.EMPTY_CAPTION
|
||||||
|
import fr.free.nrw.commons.utils.ImageUtils.FILE_NAME_EXISTS
|
||||||
|
import fr.free.nrw.commons.utils.ImageUtils.IMAGE_KEEP
|
||||||
|
import fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK
|
||||||
|
import fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult
|
||||||
|
import io.github.coordinates2country.Coordinates2Country
|
||||||
|
import io.reactivex.Maybe
|
||||||
|
import io.reactivex.Scheduler
|
||||||
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
|
import org.json.JSONObject
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
import java.lang.reflect.Proxy
|
||||||
|
import java.net.UnknownHostException
|
||||||
|
import java.util.Locale
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Named
|
||||||
|
|
||||||
|
class UploadMediaPresenter @Inject constructor(
|
||||||
|
private val repository: UploadRepository,
|
||||||
|
@param:Named(IO_THREAD) private val ioScheduler: Scheduler,
|
||||||
|
@param:Named(MAIN_THREAD) private val mainThreadScheduler: Scheduler
|
||||||
|
) : UploadMediaDetailsContract.UserActionListener, SimilarImageInterface {
|
||||||
|
private var view = DUMMY
|
||||||
|
|
||||||
|
private val compositeDisposable = CompositeDisposable()
|
||||||
|
|
||||||
|
private val countryNamesAndCodes: Map<String, String> by lazy {
|
||||||
|
// Create a map containing all ISO countries 2-letter codes provided by
|
||||||
|
// `Locale.getISOCountries()` and their english names
|
||||||
|
buildMap {
|
||||||
|
Locale.getISOCountries().forEach {
|
||||||
|
put(Locale("en", it).getDisplayCountry(Locale.ENGLISH), it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttachView(view: UploadMediaDetailsContract.View) {
|
||||||
|
this.view = view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachView() {
|
||||||
|
view = DUMMY
|
||||||
|
compositeDisposable.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Upload Media Details for the corresponding upload item
|
||||||
|
*/
|
||||||
|
override fun setUploadMediaDetails(
|
||||||
|
uploadMediaDetails: List<UploadMediaDetail>,
|
||||||
|
uploadItemIndex: Int
|
||||||
|
) {
|
||||||
|
repository.getUploads()[uploadItemIndex].uploadMediaDetails = uploadMediaDetails.toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives the corresponding uploadable file, processes it and return the view with and uplaod item
|
||||||
|
*/
|
||||||
|
override fun receiveImage(
|
||||||
|
uploadableFile: UploadableFile?,
|
||||||
|
place: Place?,
|
||||||
|
inAppPictureLocation: LatLng?
|
||||||
|
) {
|
||||||
|
view.showProgress(true)
|
||||||
|
compositeDisposable.add(
|
||||||
|
repository.preProcessImage(
|
||||||
|
uploadableFile, place, this, inAppPictureLocation
|
||||||
|
).map { uploadItem: UploadItem ->
|
||||||
|
if (place != null && place.isMonument && place.location != null) {
|
||||||
|
val countryCode = countryNamesAndCodes[Coordinates2Country.country(
|
||||||
|
place.location.latitude,
|
||||||
|
place.location.longitude
|
||||||
|
)]
|
||||||
|
if (countryCode != null && WLM_SUPPORTED_COUNTRIES.contains(countryCode.lowercase())) {
|
||||||
|
uploadItem.isWLMUpload = true
|
||||||
|
uploadItem.countryCode = countryCode.lowercase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uploadItem
|
||||||
|
}.subscribeOn(ioScheduler).observeOn(mainThreadScheduler)
|
||||||
|
.subscribe({ uploadItem: UploadItem ->
|
||||||
|
view.onImageProcessed(uploadItem)
|
||||||
|
view.updateMediaDetails(uploadItem.uploadMediaDetails)
|
||||||
|
view.showProgress(false)
|
||||||
|
val gpsCoords = uploadItem.gpsCoords
|
||||||
|
val hasImageCoordinates = gpsCoords != null && gpsCoords.imageCoordsExists
|
||||||
|
if (hasImageCoordinates && place == null) {
|
||||||
|
checkNearbyPlaces(uploadItem)
|
||||||
|
}
|
||||||
|
}, { throwable: Throwable? ->
|
||||||
|
Timber.e(throwable, "Error occurred in processing images")
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method checks for the nearest location that needs images and suggests it to the user.
|
||||||
|
*/
|
||||||
|
private fun checkNearbyPlaces(uploadItem: UploadItem) {
|
||||||
|
compositeDisposable.add(Maybe.fromCallable {
|
||||||
|
repository.checkNearbyPlaces(
|
||||||
|
uploadItem.gpsCoords!!.decLatitude, uploadItem.gpsCoords!!.decLongitude
|
||||||
|
)
|
||||||
|
}.subscribeOn(ioScheduler).observeOn(mainThreadScheduler).subscribe({
|
||||||
|
view.onNearbyPlaceFound(uploadItem, it)
|
||||||
|
}, { throwable: Throwable? ->
|
||||||
|
Timber.e(throwable, "Error occurred in processing images")
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the image has a location. Displays a dialog alerting user that no
|
||||||
|
* location has been to added to the image and asking them to add one, if location was not
|
||||||
|
* removed by the user
|
||||||
|
*
|
||||||
|
* @param uploadItemIndex Index of the uploadItem which has no location
|
||||||
|
* @param inAppPictureLocation In app picture location (if any)
|
||||||
|
* @param hasUserRemovedLocation True if user has removed location from the image
|
||||||
|
*/
|
||||||
|
override fun displayLocDialog(
|
||||||
|
uploadItemIndex: Int, inAppPictureLocation: LatLng?,
|
||||||
|
hasUserRemovedLocation: Boolean
|
||||||
|
) {
|
||||||
|
val uploadItem = repository.getUploads()[uploadItemIndex]
|
||||||
|
if (uploadItem.gpsCoords!!.decimalCoords == null && inAppPictureLocation == null && !hasUserRemovedLocation) {
|
||||||
|
view.displayAddLocationDialog { verifyCaptionQuality(uploadItem) }
|
||||||
|
} else {
|
||||||
|
verifyCaptionQuality(uploadItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the image's caption and calls function to handle the result
|
||||||
|
*
|
||||||
|
* @param uploadItem UploadItem whose caption is checked
|
||||||
|
*/
|
||||||
|
private fun verifyCaptionQuality(uploadItem: UploadItem) {
|
||||||
|
view.showProgress(true)
|
||||||
|
compositeDisposable.add(repository.getCaptionQuality(uploadItem)
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.subscribe({ capResult: Int ->
|
||||||
|
view.showProgress(false)
|
||||||
|
handleCaptionResult(capResult, uploadItem)
|
||||||
|
}, { throwable: Throwable ->
|
||||||
|
view.showProgress(false)
|
||||||
|
if (throwable is UnknownHostException) {
|
||||||
|
view.showConnectionErrorPopupForCaptionCheck()
|
||||||
|
} else {
|
||||||
|
view.showMessage(throwable.localizedMessage, R.color.color_error)
|
||||||
|
}
|
||||||
|
Timber.e(throwable, "Error occurred while handling image")
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles image's caption results and shows dialog if necessary
|
||||||
|
*
|
||||||
|
* @param errorCode Error code of the UploadItem
|
||||||
|
* @param uploadItem UploadItem whose caption is checked
|
||||||
|
*/
|
||||||
|
fun handleCaptionResult(errorCode: Int, uploadItem: UploadItem) {
|
||||||
|
// If errorCode is empty caption show message
|
||||||
|
if (errorCode == EMPTY_CAPTION) {
|
||||||
|
Timber.d("Captions are empty. Showing toast")
|
||||||
|
view.showMessage(R.string.add_caption_toast, R.color.color_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If image with same file name exists check the bit in errorCode is set or not
|
||||||
|
if ((errorCode and FILE_NAME_EXISTS) != 0) {
|
||||||
|
Timber.d("Trying to show duplicate picture popup")
|
||||||
|
view.showDuplicatePicturePopup(uploadItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If caption is not duplicate or user still wants to upload it
|
||||||
|
if (errorCode == IMAGE_OK) {
|
||||||
|
Timber.d("Image captions are okay or user still wants to upload it")
|
||||||
|
view.onImageValidationSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the caption and description of the current item to the subsequent media
|
||||||
|
*/
|
||||||
|
override fun copyTitleAndDescriptionToSubsequentMedia(indexInViewFlipper: Int) {
|
||||||
|
for (i in indexInViewFlipper + 1 until repository.getCount()) {
|
||||||
|
val subsequentUploadItem = repository.getUploads()[i]
|
||||||
|
subsequentUploadItem.uploadMediaDetails = deepCopy(
|
||||||
|
repository.getUploads()[indexInViewFlipper].uploadMediaDetails
|
||||||
|
).toMutableList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and set the caption and description of the item
|
||||||
|
*/
|
||||||
|
override fun fetchTitleAndDescription(indexInViewFlipper: Int) =
|
||||||
|
view.updateMediaDetails(repository.getUploads()[indexInViewFlipper].uploadMediaDetails)
|
||||||
|
|
||||||
|
private fun deepCopy(uploadMediaDetails: List<UploadMediaDetail>) =
|
||||||
|
uploadMediaDetails.map(UploadMediaDetail::javaCopy)
|
||||||
|
|
||||||
|
override fun useSimilarPictureCoordinates(
|
||||||
|
imageCoordinates: ImageCoordinates, uploadItemIndex: Int
|
||||||
|
) = repository.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex)
|
||||||
|
|
||||||
|
override fun onMapIconClicked(indexInViewFlipper: Int) =
|
||||||
|
view.showExternalMap(repository.getUploads()[indexInViewFlipper])
|
||||||
|
|
||||||
|
override fun onEditButtonClicked(indexInViewFlipper: Int) =
|
||||||
|
view.showEditActivity(repository.getUploads()[indexInViewFlipper])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the information regarding the specified place for the specified upload item
|
||||||
|
* when the user confirms the suggested nearby place.
|
||||||
|
*
|
||||||
|
* @param place The place to be associated with the uploads.
|
||||||
|
* @param uploadItemIndex Index of the uploadItem whose detected place has been confirmed
|
||||||
|
*/
|
||||||
|
override fun onUserConfirmedUploadIsOfPlace(place: Place?, uploadItemIndex: Int) {
|
||||||
|
val uploadItem = repository.getUploads()[uploadItemIndex]
|
||||||
|
|
||||||
|
uploadItem.place = place
|
||||||
|
val uploadMediaDetails = uploadItem.uploadMediaDetails
|
||||||
|
// Update UploadMediaDetail object for this UploadItem
|
||||||
|
uploadMediaDetails[0] = UploadMediaDetail(place)
|
||||||
|
|
||||||
|
// Now that the UploadItem and its associated UploadMediaDetail objects have been updated,
|
||||||
|
// update the view with the modified media details of the first upload item
|
||||||
|
view.updateMediaDetails(uploadMediaDetails)
|
||||||
|
setUploadIsOfAPlace(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the image quality
|
||||||
|
*
|
||||||
|
* @param uploadItemIndex Index of the UploadItem whose quality is to be checked
|
||||||
|
* @param inAppPictureLocation In app picture location (if any)
|
||||||
|
* @param activity Context reference
|
||||||
|
* @return true if no internal error occurs, else returns false
|
||||||
|
*/
|
||||||
|
override fun getImageQuality(
|
||||||
|
uploadItemIndex: Int,
|
||||||
|
inAppPictureLocation: LatLng?,
|
||||||
|
activity: Activity
|
||||||
|
): Boolean {
|
||||||
|
val uploadItems = repository.getUploads()
|
||||||
|
view.showProgress(true)
|
||||||
|
if (uploadItems.isEmpty()) {
|
||||||
|
view.showProgress(false)
|
||||||
|
// No internationalization required for this error message because it's an internal error.
|
||||||
|
view.showMessage(
|
||||||
|
"Internal error: Zero upload items received by the Upload Media Detail Fragment. Sorry, please upload again.",
|
||||||
|
R.color.color_error
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val uploadItem = uploadItems[uploadItemIndex]
|
||||||
|
compositeDisposable.add(repository.getImageQuality(uploadItem, inAppPictureLocation)
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.subscribe({ imageResult: Int ->
|
||||||
|
storeImageQuality(imageResult, uploadItemIndex, activity, uploadItem)
|
||||||
|
}, { throwable: Throwable ->
|
||||||
|
if (throwable is UnknownHostException) {
|
||||||
|
view.showProgress(false)
|
||||||
|
view.showConnectionErrorPopup()
|
||||||
|
} else {
|
||||||
|
view.showMessage(throwable.localizedMessage, R.color.color_error)
|
||||||
|
}
|
||||||
|
Timber.e(throwable, "Error occurred while handling image")
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the image quality in JSON format in SharedPrefs
|
||||||
|
*
|
||||||
|
* @param imageResult Image quality
|
||||||
|
* @param uploadItemIndex Index of the UploadItem whose quality is calculated
|
||||||
|
* @param activity Context reference
|
||||||
|
* @param uploadItem UploadItem whose quality is to be checked
|
||||||
|
*/
|
||||||
|
private fun storeImageQuality(
|
||||||
|
imageResult: Int, uploadItemIndex: Int, activity: Activity, uploadItem: UploadItem
|
||||||
|
) {
|
||||||
|
val store = BasicKvStore(activity, UploadActivity.storeNameForCurrentUploadImagesSize)
|
||||||
|
val value = store.getString(UPLOAD_QUALITIES_KEY, null)
|
||||||
|
try {
|
||||||
|
val jsonObject = value.asJsonObject().apply {
|
||||||
|
put("UploadItem$uploadItemIndex", imageResult)
|
||||||
|
}
|
||||||
|
store.putString(UPLOAD_QUALITIES_KEY, jsonObject.toString())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uploadItemIndex == 0) {
|
||||||
|
if (!isBatteryDialogShowing && !isCategoriesDialogShowing) {
|
||||||
|
// if battery-optimisation dialog is not being shown, call checkImageQuality
|
||||||
|
checkImageQuality(uploadItem, uploadItemIndex)
|
||||||
|
} else {
|
||||||
|
view.showProgress(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to check image quality from stored qualities and display dialogs
|
||||||
|
*
|
||||||
|
* @param uploadItem UploadItem whose quality is to be checked
|
||||||
|
* @param index Index of the UploadItem whose quality is to be checked
|
||||||
|
*/
|
||||||
|
override fun checkImageQuality(uploadItem: UploadItem, index: Int) {
|
||||||
|
if ((uploadItem.imageQuality != IMAGE_OK) && (uploadItem.imageQuality != IMAGE_KEEP)) {
|
||||||
|
val store = BasicKvStore(
|
||||||
|
UploadMediaDetailFragment.activity,
|
||||||
|
UploadActivity.storeNameForCurrentUploadImagesSize
|
||||||
|
)
|
||||||
|
val value = store.getString(UPLOAD_QUALITIES_KEY, null)
|
||||||
|
try {
|
||||||
|
val imageQuality = value.asJsonObject()["UploadItem$index"] as Int
|
||||||
|
view.showProgress(false)
|
||||||
|
if (imageQuality == IMAGE_OK) {
|
||||||
|
uploadItem.hasInvalidLocation = false
|
||||||
|
uploadItem.imageQuality = imageQuality
|
||||||
|
} else {
|
||||||
|
handleBadImage(imageQuality, uploadItem, index)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the image qualities stored in JSON, whenever an image is deleted
|
||||||
|
*
|
||||||
|
* @param size Size of uploadableFiles
|
||||||
|
* @param index Index of the UploadItem which was deleted
|
||||||
|
*/
|
||||||
|
override fun updateImageQualitiesJSON(size: Int, index: Int) {
|
||||||
|
val store = BasicKvStore(
|
||||||
|
UploadMediaDetailFragment.activity,
|
||||||
|
UploadActivity.storeNameForCurrentUploadImagesSize
|
||||||
|
)
|
||||||
|
val value = store.getString(UPLOAD_QUALITIES_KEY, null)
|
||||||
|
try {
|
||||||
|
val jsonObject = value.asJsonObject().apply {
|
||||||
|
for (i in index until (size - 1)) {
|
||||||
|
put("UploadItem$i", this["UploadItem" + (i + 1)])
|
||||||
|
}
|
||||||
|
remove("UploadItem" + (size - 1))
|
||||||
|
}
|
||||||
|
store.putString(UPLOAD_QUALITIES_KEY, jsonObject.toString())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles bad pictures, like too dark, already on wikimedia, downloaded from internet
|
||||||
|
*
|
||||||
|
* @param errorCode Error code of the bad image quality
|
||||||
|
* @param uploadItem UploadItem whose quality is bad
|
||||||
|
* @param index Index of item whose quality is bad
|
||||||
|
*/
|
||||||
|
private fun handleBadImage(
|
||||||
|
errorCode: Int,
|
||||||
|
uploadItem: UploadItem, index: Int
|
||||||
|
) {
|
||||||
|
Timber.d("Handle bad picture with error code %d", errorCode)
|
||||||
|
if (errorCode >= 8) { // If location of image and nearby does not match
|
||||||
|
uploadItem.hasInvalidLocation = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If image has some other problems, show popup accordingly
|
||||||
|
if (errorCode != EMPTY_CAPTION && errorCode != FILE_NAME_EXISTS) {
|
||||||
|
showBadImagePopup(errorCode, index, UploadMediaDetailFragment.activity, uploadItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a dialog describing the potential problems in the current image
|
||||||
|
*
|
||||||
|
* @param errorCode Has the potential problems in the current image
|
||||||
|
* @param index Index of the UploadItem which has problems
|
||||||
|
* @param activity Context reference
|
||||||
|
* @param uploadItem UploadItem which has problems
|
||||||
|
*/
|
||||||
|
private fun showBadImagePopup(
|
||||||
|
errorCode: Int, index: Int, activity: Activity, uploadItem: UploadItem
|
||||||
|
) {
|
||||||
|
//If the error message is null, we will probably not show anything
|
||||||
|
val errorMessageForResult = getErrorMessageForResult(activity, errorCode)
|
||||||
|
if (errorMessageForResult.isNotEmpty()) {
|
||||||
|
showAlertDialog(activity,
|
||||||
|
activity.getString(R.string.upload_problem_image),
|
||||||
|
errorMessageForResult,
|
||||||
|
activity.getString(R.string.upload),
|
||||||
|
activity.getString(R.string.cancel),
|
||||||
|
{
|
||||||
|
view.showProgress(false)
|
||||||
|
uploadItem.imageQuality = IMAGE_OK
|
||||||
|
}, {
|
||||||
|
presenterCallback!!.deletePictureAtIndex(index)
|
||||||
|
}
|
||||||
|
)?.setCancelable(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* notifies the user that a similar image exists
|
||||||
|
*/
|
||||||
|
override fun showSimilarImageFragment(
|
||||||
|
originalFilePath: String?,
|
||||||
|
possibleFilePath: String?,
|
||||||
|
similarImageCoordinates: ImageCoordinates?
|
||||||
|
) = view.showSimilarImageFragment(originalFilePath, possibleFilePath, similarImageCoordinates)
|
||||||
|
|
||||||
|
private fun String?.asJsonObject() = if (this != null) {
|
||||||
|
JSONObject(this)
|
||||||
|
} else {
|
||||||
|
JSONObject()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val UPLOAD_QUALITIES_KEY = "UploadedImagesQualities"
|
||||||
|
private val WLM_SUPPORTED_COUNTRIES = listOf(
|
||||||
|
"am", "at", "az", "br", "hr", "sv", "fi", "fr", "de", "gh",
|
||||||
|
"in", "ie", "il", "mk", "my", "mt", "pk", "pe", "pl", "ru",
|
||||||
|
"rw", "si", "es", "se", "tw", "ug", "ua", "us"
|
||||||
|
)
|
||||||
|
|
||||||
|
private val DUMMY = Proxy.newProxyInstance(
|
||||||
|
UploadMediaDetailsContract.View::class.java.classLoader,
|
||||||
|
arrayOf<Class<*>>(UploadMediaDetailsContract.View::class.java)
|
||||||
|
) { _: Any?, _: Method?, _: Array<Any?>? -> null } as UploadMediaDetailsContract.View
|
||||||
|
|
||||||
|
var presenterCallback: UploadMediaDetailFragmentCallback? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable used to determine if the battery-optimisation dialog is being shown or not
|
||||||
|
*/
|
||||||
|
var isBatteryDialogShowing: Boolean = false
|
||||||
|
|
||||||
|
var isCategoriesDialogShowing: Boolean = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package fr.free.nrw.commons.upload
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import com.nhaarman.mockitokotlin2.argumentCaptor
|
||||||
|
import com.nhaarman.mockitokotlin2.isA
|
||||||
import com.nhaarman.mockitokotlin2.mock
|
import com.nhaarman.mockitokotlin2.mock
|
||||||
import com.nhaarman.mockitokotlin2.whenever
|
import com.nhaarman.mockitokotlin2.whenever
|
||||||
|
import fr.free.nrw.commons.R
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile
|
import fr.free.nrw.commons.filepicker.UploadableFile
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
|
||||||
import fr.free.nrw.commons.location.LatLng
|
import fr.free.nrw.commons.location.LatLng
|
||||||
import fr.free.nrw.commons.nearby.Place
|
import fr.free.nrw.commons.nearby.Place
|
||||||
import fr.free.nrw.commons.repository.UploadRepository
|
import fr.free.nrw.commons.repository.UploadRepository
|
||||||
|
|
@ -24,6 +26,7 @@ import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.ArgumentCaptor
|
import org.mockito.ArgumentCaptor
|
||||||
import org.mockito.ArgumentMatchers
|
import org.mockito.ArgumentMatchers
|
||||||
|
import org.mockito.ArgumentMatchers.anyInt
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.MockedStatic
|
import org.mockito.MockedStatic
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
|
|
@ -55,7 +58,7 @@ class UploadMediaPresenterTest {
|
||||||
private lateinit var place: Place
|
private lateinit var place: Place
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private var location: LatLng? = null
|
private lateinit var location: LatLng
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var uploadItem: UploadItem
|
private lateinit var uploadItem: UploadItem
|
||||||
|
|
@ -63,18 +66,12 @@ class UploadMediaPresenterTest {
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var imageCoordinates: ImageCoordinates
|
private lateinit var imageCoordinates: ImageCoordinates
|
||||||
|
|
||||||
@Mock
|
|
||||||
private lateinit var uploadMediaDetails: List<UploadMediaDetail>
|
|
||||||
|
|
||||||
private lateinit var testObservableUploadItem: Observable<UploadItem>
|
private lateinit var testObservableUploadItem: Observable<UploadItem>
|
||||||
private lateinit var testSingleImageResult: Single<Int>
|
private lateinit var testSingleImageResult: Single<Int>
|
||||||
|
|
||||||
private lateinit var testScheduler: TestScheduler
|
private lateinit var testScheduler: TestScheduler
|
||||||
private lateinit var mockedCountry: MockedStatic<Coordinates2Country>
|
private lateinit var mockedCountry: MockedStatic<Coordinates2Country>
|
||||||
|
|
||||||
@Mock
|
|
||||||
private lateinit var jsonKvStore: JsonKvStore
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
lateinit var mockActivity: UploadActivity
|
lateinit var mockActivity: UploadActivity
|
||||||
|
|
||||||
|
|
@ -91,7 +88,6 @@ class UploadMediaPresenterTest {
|
||||||
uploadMediaPresenter =
|
uploadMediaPresenter =
|
||||||
UploadMediaPresenter(
|
UploadMediaPresenter(
|
||||||
repository,
|
repository,
|
||||||
jsonKvStore,
|
|
||||||
testScheduler,
|
testScheduler,
|
||||||
testScheduler,
|
testScheduler,
|
||||||
)
|
)
|
||||||
|
|
@ -120,10 +116,7 @@ class UploadMediaPresenterTest {
|
||||||
uploadMediaPresenter.receiveImage(uploadableFile, place, location)
|
uploadMediaPresenter.receiveImage(uploadableFile, place, location)
|
||||||
verify(view).showProgress(true)
|
verify(view).showProgress(true)
|
||||||
testScheduler.triggerActions()
|
testScheduler.triggerActions()
|
||||||
verify(view).onImageProcessed(
|
verify(view).onImageProcessed(isA())
|
||||||
ArgumentMatchers.any(UploadItem::class.java),
|
|
||||||
ArgumentMatchers.any(Place::class.java),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -167,7 +160,7 @@ class UploadMediaPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun emptyFileNameTest() {
|
fun emptyFileNameTest() {
|
||||||
uploadMediaPresenter.handleCaptionResult(EMPTY_CAPTION, uploadItem)
|
uploadMediaPresenter.handleCaptionResult(EMPTY_CAPTION, uploadItem)
|
||||||
verify(view).showMessage(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
|
verify(view).showMessage(R.string.add_caption_toast, R.color.color_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -226,12 +219,11 @@ class UploadMediaPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun fetchImageAndTitleTest() {
|
fun fetchImageAndTitleTest() {
|
||||||
whenever(repository.getUploads()).thenReturn(listOf(uploadItem))
|
whenever(repository.getUploads()).thenReturn(listOf(uploadItem))
|
||||||
whenever(repository.getUploadItem(ArgumentMatchers.anyInt()))
|
whenever(repository.getUploadItem(ArgumentMatchers.anyInt())).thenReturn(uploadItem)
|
||||||
.thenReturn(uploadItem)
|
|
||||||
whenever(uploadItem.uploadMediaDetails).thenReturn(mutableListOf())
|
whenever(uploadItem.uploadMediaDetails).thenReturn(mutableListOf())
|
||||||
|
|
||||||
uploadMediaPresenter.fetchTitleAndDescription(0)
|
uploadMediaPresenter.fetchTitleAndDescription(0)
|
||||||
verify(view).updateMediaDetails(ArgumentMatchers.any())
|
verify(view).updateMediaDetails(isA())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -273,12 +265,9 @@ class UploadMediaPresenterTest {
|
||||||
verify(view).showProgress(true)
|
verify(view).showProgress(true)
|
||||||
testScheduler.triggerActions()
|
testScheduler.triggerActions()
|
||||||
|
|
||||||
val captor: ArgumentCaptor<UploadItem> = ArgumentCaptor.forClass(UploadItem::class.java)
|
val captor = argumentCaptor<UploadItem>()
|
||||||
verify(view).onImageProcessed(
|
verify(view).onImageProcessed(captor.capture())
|
||||||
captor.capture(),
|
|
||||||
ArgumentMatchers.any(Place::class.java),
|
|
||||||
)
|
|
||||||
|
|
||||||
assertEquals("Exptected contry code", "de", captor.value.countryCode)
|
assertEquals("Exptected contry code", "de", captor.firstValue.countryCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ class UploadMediaDetailFragmentUnitTest {
|
||||||
private lateinit var place: Place
|
private lateinit var place: Place
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private var location: fr.free.nrw.commons.location.LatLng? = null
|
private lateinit var location: LatLng
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var defaultKvStore: JsonKvStore
|
private lateinit var defaultKvStore: JsonKvStore
|
||||||
|
|
@ -194,7 +194,7 @@ class UploadMediaDetailFragmentUnitTest {
|
||||||
Whitebox.setInternalState(fragment, "presenter", presenter)
|
Whitebox.setInternalState(fragment, "presenter", presenter)
|
||||||
val method: Method =
|
val method: Method =
|
||||||
UploadMediaDetailFragment::class.java.getDeclaredMethod(
|
UploadMediaDetailFragment::class.java.getDeclaredMethod(
|
||||||
"init",
|
"initializeFragment",
|
||||||
)
|
)
|
||||||
method.isAccessible = true
|
method.isAccessible = true
|
||||||
method.invoke(fragment)
|
method.invoke(fragment)
|
||||||
|
|
@ -209,7 +209,7 @@ class UploadMediaDetailFragmentUnitTest {
|
||||||
`when`(callback.totalNumberOfSteps).thenReturn(5)
|
`when`(callback.totalNumberOfSteps).thenReturn(5)
|
||||||
val method: Method =
|
val method: Method =
|
||||||
UploadMediaDetailFragment::class.java.getDeclaredMethod(
|
UploadMediaDetailFragment::class.java.getDeclaredMethod(
|
||||||
"init",
|
"initializeFragment",
|
||||||
)
|
)
|
||||||
method.isAccessible = true
|
method.isAccessible = true
|
||||||
method.invoke(fragment)
|
method.invoke(fragment)
|
||||||
|
|
@ -258,7 +258,7 @@ class UploadMediaDetailFragmentUnitTest {
|
||||||
fun testOnImageProcessed() {
|
fun testOnImageProcessed() {
|
||||||
Shadows.shadowOf(Looper.getMainLooper()).idle()
|
Shadows.shadowOf(Looper.getMainLooper()).idle()
|
||||||
`when`(uploadItem.mediaUri).thenReturn(mediaUri)
|
`when`(uploadItem.mediaUri).thenReturn(mediaUri)
|
||||||
fragment.onImageProcessed(uploadItem, place)
|
fragment.onImageProcessed(uploadItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -407,7 +407,7 @@ class UploadMediaDetailFragmentUnitTest {
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun testUpdateMediaDetails() {
|
fun testUpdateMediaDetails() {
|
||||||
Shadows.shadowOf(Looper.getMainLooper()).idle()
|
Shadows.shadowOf(Looper.getMainLooper()).idle()
|
||||||
fragment.updateMediaDetails(null)
|
fragment.updateMediaDetails(mock())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue