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