mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-28 21:33:53 +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
|
||||
*/
|
||||
fun buildContributions(): Observable<Contribution>? {
|
||||
fun buildContributions(): Observable<Contribution> {
|
||||
return uploadModel.buildContributions()
|
||||
}
|
||||
|
||||
|
|
@ -177,7 +177,7 @@ class UploadRepository @Inject constructor(
|
|||
place: Place?,
|
||||
similarImageInterface: SimilarImageInterface?,
|
||||
inAppPictureLocation: LatLng?
|
||||
): Observable<UploadItem>? {
|
||||
): Observable<UploadItem> {
|
||||
return uploadModel.preProcessImage(
|
||||
uploadableFile,
|
||||
place,
|
||||
|
|
@ -193,7 +193,7 @@ class UploadRepository @Inject constructor(
|
|||
* @param location Location of the image
|
||||
* @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)
|
||||
}
|
||||
|
||||
|
|
@ -213,7 +213,7 @@ class UploadRepository @Inject constructor(
|
|||
* @param uploadItem UploadItem whose caption is to be checked
|
||||
* @return Quality of caption of the UploadItem
|
||||
*/
|
||||
fun getCaptionQuality(uploadItem: UploadItem): Single<Int>? {
|
||||
fun getCaptionQuality(uploadItem: UploadItem): Single<Int> {
|
||||
return uploadModel.getCaptionQuality(uploadItem)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -175,11 +175,11 @@ class UploadModel @Inject internal constructor(
|
|||
Timber.d(
|
||||
"Created timestamp while building contribution is %s, %s",
|
||||
item.createdTimestamp,
|
||||
Date(item.createdTimestamp!!)
|
||||
item.createdTimestamp?.let { Date(it) }
|
||||
)
|
||||
|
||||
if (item.createdTimestamp != -1L) {
|
||||
contribution.dateCreated = Date(item.createdTimestamp)
|
||||
contribution.dateCreated = item.createdTimestamp?.let { Date(it) }
|
||||
contribution.dateCreatedSource = item.createdTimestampSource
|
||||
//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
|
||||
|
||||
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.R
|
||||
import fr.free.nrw.commons.contributions.Contribution
|
||||
|
|
@ -69,8 +68,7 @@ class UploadPresenter @Inject internal constructor(
|
|||
private fun processContributionsForSubmission() {
|
||||
if (view.isLoggedIn()) {
|
||||
view.showProgress(true)
|
||||
repository.buildContributions()
|
||||
?.observeOn(Schedulers.io())
|
||||
repository.buildContributions().observeOn(Schedulers.io())
|
||||
?.subscribe(object : Observer<Contribution> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
view.showProgress(false)
|
||||
|
|
@ -133,8 +131,9 @@ class UploadPresenter @Inject internal constructor(
|
|||
* @param uploadItemIndex Index of next image, whose quality is to be checked
|
||||
*/
|
||||
override fun checkImageQuality(uploadItemIndex: Int) {
|
||||
val uploadItem = repository.getUploadItem(uploadItemIndex)
|
||||
presenter.checkImageQuality(uploadItem, uploadItemIndex)
|
||||
repository.getUploadItem(uploadItemIndex)?.let {
|
||||
presenter.checkImageQuality(it, uploadItemIndex)
|
||||
}
|
||||
}
|
||||
|
||||
override fun deletePictureAtIndex(index: Int) {
|
||||
|
|
@ -156,8 +155,9 @@ class UploadPresenter @Inject internal constructor(
|
|||
view.onUploadMediaDeleted(index)
|
||||
if (index != uploadableFiles.size && index != 0) {
|
||||
// if the deleted image was not the last item to be uploaded, check quality of next
|
||||
val uploadItem = repository.getUploadItem(index)
|
||||
presenter.checkImageQuality(uploadItem, index)
|
||||
repository.getUploadItem(index)?.let {
|
||||
presenter.checkImageQuality(it, index)
|
||||
}
|
||||
}
|
||||
|
||||
if (uploadableFiles.size < 2) {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ import java.util.Locale;
|
|||
import java.util.Objects;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||
|
|
@ -154,7 +155,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
|
||||
public void setCallback(UploadMediaDetailFragmentCallback callback) {
|
||||
this.callback = callback;
|
||||
UploadMediaPresenter.presenterCallback = callback;
|
||||
UploadMediaPresenter.Companion.setPresenterCallback(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -190,12 +191,12 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
activity = getActivity();
|
||||
activity = requireActivity();
|
||||
basicKvStore = new BasicKvStore(activity, "CurrentUploadImageQualities");
|
||||
|
||||
if (callback != null) {
|
||||
indexOfFragment = callback.getIndexInViewFlipper(this);
|
||||
init();
|
||||
initializeFragment();
|
||||
}
|
||||
|
||||
if(savedInstanceState!=null){
|
||||
|
|
@ -207,7 +208,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
}
|
||||
|
||||
try {
|
||||
if(!presenter.getImageQuality(indexOfFragment, inAppPictureLocation, getActivity())) {
|
||||
if(!presenter.getImageQuality(indexOfFragment, inAppPictureLocation, requireActivity())) {
|
||||
ActivityUtils.startActivityWithFlags(
|
||||
getActivity(), MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_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) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -373,7 +374,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onImageProcessed(UploadItem uploadItem, Place place) {
|
||||
public void onImageProcessed(@NotNull UploadItem uploadItem) {
|
||||
if (binding == null) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -386,7 +387,8 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
* @param place
|
||||
*/
|
||||
@Override
|
||||
public void onNearbyPlaceFound(UploadItem uploadItem, Place place) {
|
||||
public void onNearbyPlaceFound(
|
||||
@NotNull UploadItem uploadItem, @org.jetbrains.annotations.Nullable Place place) {
|
||||
nearbyPlace = place;
|
||||
this.uploadItem = uploadItem;
|
||||
showNearbyFound = true;
|
||||
|
|
@ -506,7 +508,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public void showDuplicatePicturePopup(UploadItem uploadItem) {
|
||||
public void showDuplicatePicturePopup(@NotNull UploadItem uploadItem) {
|
||||
if (defaultKvStore.getBoolean("showDuplicatePicturePopup", true)) {
|
||||
String uploadTitleFormat = getString(R.string.upload_title_duplicate);
|
||||
View checkBoxView = View
|
||||
|
|
@ -517,7 +519,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
defaultKvStore.putBoolean("showDuplicatePicturePopup", false);
|
||||
}
|
||||
});
|
||||
DialogUtil.showAlertDialog(getActivity(),
|
||||
DialogUtil.showAlertDialog(requireActivity(),
|
||||
getString(R.string.duplicate_file_name),
|
||||
String.format(Locale.getDefault(),
|
||||
uploadTitleFormat,
|
||||
|
|
@ -597,7 +599,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public void showExternalMap(final UploadItem uploadItem) {
|
||||
public void showExternalMap(@NotNull final UploadItem uploadItem) {
|
||||
goToLocationPickerActivity(uploadItem);
|
||||
}
|
||||
|
||||
|
|
@ -612,7 +614,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
* is started using resultLauncher that handles the result in respective callback.
|
||||
*/
|
||||
@Override
|
||||
public void showEditActivity(UploadItem uploadItem) {
|
||||
public void showEditActivity(@NotNull UploadItem uploadItem) {
|
||||
editableUploadItem = uploadItem;
|
||||
Intent intent = new Intent(getContext(), EditActivity.class);
|
||||
intent.putExtra("image", uploadableFile.getFilePath().toString());
|
||||
|
|
@ -789,7 +791,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public void updateMediaDetails(List<UploadMediaDetail> uploadMediaDetails) {
|
||||
public void updateMediaDetails(@NotNull List<UploadMediaDetail> uploadMediaDetails) {
|
||||
uploadMediaDetailAdapter.setItems(uploadMediaDetails);
|
||||
showNearbyFound =
|
||||
showNearbyFound && (
|
||||
|
|
@ -823,7 +825,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
* @param onSkipClicked proceed for verifying image quality
|
||||
*/
|
||||
@Override
|
||||
public void displayAddLocationDialog(final Runnable onSkipClicked) {
|
||||
public void displayAddLocationDialog(@NotNull final Runnable onSkipClicked) {
|
||||
isMissingLocationDialog = true;
|
||||
DialogUtil.showAlertDialog(requireActivity(),
|
||||
getString(R.string.no_location_found_title),
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ import fr.free.nrw.commons.upload.UploadMediaDetail
|
|||
*/
|
||||
interface UploadMediaDetailsContract {
|
||||
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)
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ interface UploadMediaDetailsContract {
|
|||
|
||||
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
|
||||
|
|
@ -42,13 +42,13 @@ interface UploadMediaDetailsContract {
|
|||
*/
|
||||
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?> {
|
||||
|
|
@ -59,7 +59,7 @@ interface UploadMediaDetailsContract {
|
|||
)
|
||||
|
||||
fun setUploadMediaDetails(
|
||||
uploadMediaDetails: List<UploadMediaDetail?>?,
|
||||
uploadMediaDetails: List<UploadMediaDetail>,
|
||||
uploadItemIndex: Int
|
||||
)
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ interface UploadMediaDetailsContract {
|
|||
fun getImageQuality(
|
||||
uploadItemIndex: Int,
|
||||
inAppPictureLocation: LatLng?,
|
||||
activity: Activity?
|
||||
activity: Activity
|
||||
): Boolean
|
||||
|
||||
/**
|
||||
|
|
@ -87,7 +87,8 @@ interface UploadMediaDetailsContract {
|
|||
* @param hasUserRemovedLocation True if user has removed location from the image
|
||||
*/
|
||||
fun displayLocDialog(
|
||||
uploadItemIndex: Int, inAppPictureLocation: LatLng?,
|
||||
uploadItemIndex: Int,
|
||||
inAppPictureLocation: LatLng?,
|
||||
hasUserRemovedLocation: Boolean
|
||||
)
|
||||
|
||||
|
|
@ -97,7 +98,7 @@ interface UploadMediaDetailsContract {
|
|||
* @param uploadItem 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
|
||||
|
|
@ -111,7 +112,7 @@ interface UploadMediaDetailsContract {
|
|||
|
||||
fun fetchTitleAndDescription(indexInViewFlipper: Int)
|
||||
|
||||
fun useSimilarPictureCoordinates(imageCoordinates: ImageCoordinates?, uploadItemIndex: Int)
|
||||
fun useSimilarPictureCoordinates(imageCoordinates: ImageCoordinates, uploadItemIndex: 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
|
||||
|
||||
import android.net.Uri
|
||||
import com.nhaarman.mockitokotlin2.argumentCaptor
|
||||
import com.nhaarman.mockitokotlin2.isA
|
||||
import com.nhaarman.mockitokotlin2.mock
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import fr.free.nrw.commons.R
|
||||
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.nearby.Place
|
||||
import fr.free.nrw.commons.repository.UploadRepository
|
||||
|
|
@ -24,6 +26,7 @@ import org.junit.Test
|
|||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockedStatic
|
||||
import org.mockito.Mockito
|
||||
|
|
@ -55,7 +58,7 @@ class UploadMediaPresenterTest {
|
|||
private lateinit var place: Place
|
||||
|
||||
@Mock
|
||||
private var location: LatLng? = null
|
||||
private lateinit var location: LatLng
|
||||
|
||||
@Mock
|
||||
private lateinit var uploadItem: UploadItem
|
||||
|
|
@ -63,18 +66,12 @@ class UploadMediaPresenterTest {
|
|||
@Mock
|
||||
private lateinit var imageCoordinates: ImageCoordinates
|
||||
|
||||
@Mock
|
||||
private lateinit var uploadMediaDetails: List<UploadMediaDetail>
|
||||
|
||||
private lateinit var testObservableUploadItem: Observable<UploadItem>
|
||||
private lateinit var testSingleImageResult: Single<Int>
|
||||
|
||||
private lateinit var testScheduler: TestScheduler
|
||||
private lateinit var mockedCountry: MockedStatic<Coordinates2Country>
|
||||
|
||||
@Mock
|
||||
private lateinit var jsonKvStore: JsonKvStore
|
||||
|
||||
@Mock
|
||||
lateinit var mockActivity: UploadActivity
|
||||
|
||||
|
|
@ -91,7 +88,6 @@ class UploadMediaPresenterTest {
|
|||
uploadMediaPresenter =
|
||||
UploadMediaPresenter(
|
||||
repository,
|
||||
jsonKvStore,
|
||||
testScheduler,
|
||||
testScheduler,
|
||||
)
|
||||
|
|
@ -120,10 +116,7 @@ class UploadMediaPresenterTest {
|
|||
uploadMediaPresenter.receiveImage(uploadableFile, place, location)
|
||||
verify(view).showProgress(true)
|
||||
testScheduler.triggerActions()
|
||||
verify(view).onImageProcessed(
|
||||
ArgumentMatchers.any(UploadItem::class.java),
|
||||
ArgumentMatchers.any(Place::class.java),
|
||||
)
|
||||
verify(view).onImageProcessed(isA())
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -167,7 +160,7 @@ class UploadMediaPresenterTest {
|
|||
@Test
|
||||
fun emptyFileNameTest() {
|
||||
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
|
||||
fun fetchImageAndTitleTest() {
|
||||
whenever(repository.getUploads()).thenReturn(listOf(uploadItem))
|
||||
whenever(repository.getUploadItem(ArgumentMatchers.anyInt()))
|
||||
.thenReturn(uploadItem)
|
||||
whenever(repository.getUploadItem(ArgumentMatchers.anyInt())).thenReturn(uploadItem)
|
||||
whenever(uploadItem.uploadMediaDetails).thenReturn(mutableListOf())
|
||||
|
||||
uploadMediaPresenter.fetchTitleAndDescription(0)
|
||||
verify(view).updateMediaDetails(ArgumentMatchers.any())
|
||||
verify(view).updateMediaDetails(isA())
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -273,12 +265,9 @@ class UploadMediaPresenterTest {
|
|||
verify(view).showProgress(true)
|
||||
testScheduler.triggerActions()
|
||||
|
||||
val captor: ArgumentCaptor<UploadItem> = ArgumentCaptor.forClass(UploadItem::class.java)
|
||||
verify(view).onImageProcessed(
|
||||
captor.capture(),
|
||||
ArgumentMatchers.any(Place::class.java),
|
||||
)
|
||||
val captor = argumentCaptor<UploadItem>()
|
||||
verify(view).onImageProcessed(captor.capture())
|
||||
|
||||
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
|
||||
|
||||
@Mock
|
||||
private var location: fr.free.nrw.commons.location.LatLng? = null
|
||||
private lateinit var location: LatLng
|
||||
|
||||
@Mock
|
||||
private lateinit var defaultKvStore: JsonKvStore
|
||||
|
|
@ -194,7 +194,7 @@ class UploadMediaDetailFragmentUnitTest {
|
|||
Whitebox.setInternalState(fragment, "presenter", presenter)
|
||||
val method: Method =
|
||||
UploadMediaDetailFragment::class.java.getDeclaredMethod(
|
||||
"init",
|
||||
"initializeFragment",
|
||||
)
|
||||
method.isAccessible = true
|
||||
method.invoke(fragment)
|
||||
|
|
@ -209,7 +209,7 @@ class UploadMediaDetailFragmentUnitTest {
|
|||
`when`(callback.totalNumberOfSteps).thenReturn(5)
|
||||
val method: Method =
|
||||
UploadMediaDetailFragment::class.java.getDeclaredMethod(
|
||||
"init",
|
||||
"initializeFragment",
|
||||
)
|
||||
method.isAccessible = true
|
||||
method.invoke(fragment)
|
||||
|
|
@ -258,7 +258,7 @@ class UploadMediaDetailFragmentUnitTest {
|
|||
fun testOnImageProcessed() {
|
||||
Shadows.shadowOf(Looper.getMainLooper()).idle()
|
||||
`when`(uploadItem.mediaUri).thenReturn(mediaUri)
|
||||
fragment.onImageProcessed(uploadItem, place)
|
||||
fragment.onImageProcessed(uploadItem)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -407,7 +407,7 @@ class UploadMediaDetailFragmentUnitTest {
|
|||
@Throws(Exception::class)
|
||||
fun testUpdateMediaDetails() {
|
||||
Shadows.shadowOf(Looper.getMainLooper()).idle()
|
||||
fragment.updateMediaDetails(null)
|
||||
fragment.updateMediaDetails(mock())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue