diff --git a/app/src/main/java/fr/free/nrw/commons/repository/UploadRepository.kt b/app/src/main/java/fr/free/nrw/commons/repository/UploadRepository.kt index 4bf06eb61d..9acdde32c5 100644 --- a/app/src/main/java/fr/free/nrw/commons/repository/UploadRepository.kt +++ b/app/src/main/java/fr/free/nrw/commons/repository/UploadRepository.kt @@ -46,7 +46,7 @@ class UploadRepository @Inject constructor( * * @return */ - fun buildContributions(): Observable? { + fun buildContributions(): Observable { return uploadModel.buildContributions() } @@ -177,7 +177,7 @@ class UploadRepository @Inject constructor( place: Place?, similarImageInterface: SimilarImageInterface?, inAppPictureLocation: LatLng? - ): Observable? { + ): Observable { 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? { + fun getImageQuality(uploadItem: UploadItem, location: LatLng?): Single { 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? { + fun getCaptionQuality(uploadItem: UploadItem): Single { return uploadModel.getCaptionQuality(uploadItem) } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.kt index dab89e4277..c7ffe06f70 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.kt @@ -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 } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.kt index 361ac1cee2..a2f10ddcde 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.kt @@ -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 { 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) { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java index 9da781ce65..90e721302c 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java @@ -56,6 +56,7 @@ 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 View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c 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 void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat } 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 void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat } - private void init() { + private void initializeFragment() { if (binding == null) { return; } @@ -373,7 +374,7 @@ public void onNegativeResponse() { } @Override - public void onImageProcessed(UploadItem uploadItem, Place place) { + public void onImageProcessed(@NotNull UploadItem uploadItem) { if (binding == null) { return; } @@ -386,7 +387,8 @@ public void onImageProcessed(UploadItem uploadItem, Place place) { * @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 void showMessage(String message, int colorResourceId) { } @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 void showDuplicatePicturePopup(UploadItem uploadItem) { 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 void showConnectionErrorPopup() { } @Override - public void showExternalMap(final UploadItem uploadItem) { + public void showExternalMap(@NotNull final UploadItem uploadItem) { goToLocationPickerActivity(uploadItem); } @@ -612,7 +614,7 @@ public void showExternalMap(final UploadItem uploadItem) { * 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 void editLocation(final String latitude, final String longitude, final do } @Override - public void updateMediaDetails(List uploadMediaDetails) { + public void updateMediaDetails(@NotNull List uploadMediaDetails) { uploadMediaDetailAdapter.setItems(uploadMediaDetails); showNearbyFound = showNearbyFound && ( @@ -823,7 +825,7 @@ private boolean listContainsEmptyDetails(List uploadMediaDeta * @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), diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt index 88dad8b939..7bdd6b9df4 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt @@ -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?) + fun updateMediaDetails(uploadMediaDetails: List) - fun displayAddLocationDialog(runnable: Runnable?) + fun displayAddLocationDialog(runnable: Runnable) } interface UserActionListener : BasePresenter { @@ -59,7 +59,7 @@ interface UploadMediaDetailsContract { ) fun setUploadMediaDetails( - uploadMediaDetails: List?, + uploadMediaDetails: List, 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) diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java deleted file mode 100644 index 35d281201f..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java +++ /dev/null @@ -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 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 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 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 Locale.getISOCountries() - * 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 getCountryNamesAndCodes(){ - final Map 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 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 deepCopy(final List uploadMediaDetails) { - final ArrayList 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 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 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 - ); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt new file mode 100644 index 0000000000..47e2544e8a --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt @@ -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 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, + 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) = + 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>(UploadMediaDetailsContract.View::class.java) + ) { _: Any?, _: Method?, _: Array? -> 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 + } +} diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaPresenterTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaPresenterTest.kt index 5793d6e79e..da14438342 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaPresenterTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaPresenterTest.kt @@ -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 - private lateinit var testObservableUploadItem: Observable private lateinit var testSingleImageResult: Single private lateinit var testScheduler: TestScheduler private lateinit var mockedCountry: MockedStatic - @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 = ArgumentCaptor.forClass(UploadItem::class.java) - verify(view).onImageProcessed( - captor.capture(), - ArgumentMatchers.any(Place::class.java), - ) + val captor = argumentCaptor() + verify(view).onImageProcessed(captor.capture()) - assertEquals("Exptected contry code", "de", captor.value.countryCode) + assertEquals("Exptected contry code", "de", captor.firstValue.countryCode) } } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt index cbd1f8ca73..a969b34486 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt @@ -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