diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.java deleted file mode 100644 index 093412c25..000000000 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.java +++ /dev/null @@ -1,206 +0,0 @@ -package fr.free.nrw.commons.upload; - -import android.annotation.SuppressLint; -import fr.free.nrw.commons.CommonsApplication; -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.contributions.Contribution; -import fr.free.nrw.commons.filepicker.UploadableFile; -import fr.free.nrw.commons.kvstore.JsonKvStore; -import fr.free.nrw.commons.repository.UploadRepository; -import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract; -import io.reactivex.Observer; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; -import java.lang.reflect.Proxy; -import java.util.List; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; -import timber.log.Timber; - -/** - * The MVP pattern presenter of Upload GUI - */ -@Singleton -public class UploadPresenter implements UploadContract.UserActionListener { - - private static final UploadContract.View DUMMY = (UploadContract.View) Proxy.newProxyInstance( - UploadContract.View.class.getClassLoader(), - new Class[]{UploadContract.View.class}, (proxy, method, methodArgs) -> null); - private final UploadRepository repository; - private final JsonKvStore defaultKvStore; - private UploadContract.View view = DUMMY; - @Inject - UploadMediaDetailsContract.UserActionListener presenter; - - private CompositeDisposable compositeDisposable; - public static final String COUNTER_OF_CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES - = "number_of_consecutive_uploads_without_coordinates"; - - public static final int CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES_REMINDER_THRESHOLD = 10; - - - @Inject - UploadPresenter(UploadRepository uploadRepository, - @Named("default_preferences") JsonKvStore defaultKvStore) { - this.repository = uploadRepository; - this.defaultKvStore = defaultKvStore; - compositeDisposable = new CompositeDisposable(); - } - - - /** - * Called by the submit button in {@link UploadActivity} - */ - @SuppressLint("CheckResult") - @Override - public void handleSubmit() { - boolean hasLocationProvidedForNewUploads = false; - for (UploadItem item : repository.getUploads()) { - if (item.getGpsCoords().getImageCoordsExists()) { - hasLocationProvidedForNewUploads = true; - } - } - boolean hasManyConsecutiveUploadsWithoutLocation = defaultKvStore.getInt( - COUNTER_OF_CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES, 0) >= - CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES_REMINDER_THRESHOLD; - - if (hasManyConsecutiveUploadsWithoutLocation && !hasLocationProvidedForNewUploads) { - defaultKvStore.putInt(COUNTER_OF_CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES, 0); - view.showAlertDialog( - R.string.location_message, - () -> {defaultKvStore.putInt( - COUNTER_OF_CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES, - 0); - processContributionsForSubmission(); - }); - } else { - processContributionsForSubmission(); - } - } - - private void processContributionsForSubmission() { - if (view.isLoggedIn()) { - view.showProgress(true); - repository.buildContributions() - .observeOn(Schedulers.io()) - .subscribe(new Observer() { - @Override - public void onSubscribe(Disposable d) { - view.showProgress(false); - if (defaultKvStore - .getBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED, - false)) { - view.showMessage(R.string.uploading_queued); - } else { - view.showMessage(R.string.uploading_started); - } - - compositeDisposable.add(d); - } - - @Override - public void onNext(Contribution contribution) { - if (contribution.getDecimalCoords() == null) { - final int recentCount = defaultKvStore.getInt( - COUNTER_OF_CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES, 0); - defaultKvStore.putInt( - COUNTER_OF_CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES, recentCount + 1); - } else { - defaultKvStore.putInt( - COUNTER_OF_CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES, 0); - } - repository.prepareMedia(contribution); - contribution.setState(Contribution.STATE_QUEUED); - repository.saveContribution(contribution); - } - - @Override - public void onError(Throwable e) { - view.showMessage(R.string.upload_failed); - repository.cleanup(); - view.returnToMainActivity(); - compositeDisposable.clear(); - Timber.e("failed to upload: " + e.getMessage()); - - //is submission error, not need to go to the uploadActivity - //not start the uploading progress - } - - @Override - public void onComplete() { - view.makeUploadRequest(); - repository.cleanup(); - view.returnToMainActivity(); - compositeDisposable.clear(); - - //after finish the uploadActivity, if successful, - //directly go to the upload progress activity - view.goToUploadProgressActivity(); - } - }); - } else { - view.askUserToLogIn(); - } - } - - /** - * Calls checkImageQuality of UploadMediaPresenter to check image quality of next image - * - * @param uploadItemIndex Index of next image, whose quality is to be checked - */ - @Override - public void checkImageQuality(int uploadItemIndex) { - UploadItem uploadItem = repository.getUploadItem(uploadItemIndex); - presenter.checkImageQuality(uploadItem, uploadItemIndex); - } - - - @Override - public void deletePictureAtIndex(int index) { - List uploadableFiles = view.getUploadableFiles(); - if (index == uploadableFiles.size() - 1) { - // If the next fragment to be shown is not one of the MediaDetailsFragment - // lets hide the top card so that it doesn't appear on the other fragments - view.showHideTopCard(false); - } - view.setImageCancelled(true); - repository.deletePicture(uploadableFiles.get(index).getFilePath()); - if (uploadableFiles.size() == 1) { - view.showMessage(R.string.upload_cancelled); - view.finish(); - return; - } else { - if (presenter != null) { - presenter.updateImageQualitiesJSON(uploadableFiles.size(), index); - } - 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 - UploadItem uploadItem = repository.getUploadItem(index); - presenter.checkImageQuality(uploadItem, index); - } - } - if (uploadableFiles.size() < 2) { - view.showHideTopCard(false); - } - - //In case lets update the number of uploadable media - view.updateTopCardTitle(); - - } - - @Override - public void onAttachView(UploadContract.View view) { - this.view = view; - } - - @Override - public void onDetachView() { - this.view = DUMMY; - compositeDisposable.clear(); - repository.cleanup(); - } - -} 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 new file mode 100644 index 000000000..67158c015 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.kt @@ -0,0 +1,192 @@ +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 +import fr.free.nrw.commons.kvstore.JsonKvStore +import fr.free.nrw.commons.repository.UploadRepository +import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract +import io.reactivex.Observer +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import timber.log.Timber +import java.lang.reflect.Method +import java.lang.reflect.Proxy +import javax.inject.Inject +import javax.inject.Named +import javax.inject.Singleton + +/** + * The MVP pattern presenter of Upload GUI + */ +@Singleton +class UploadPresenter @Inject internal constructor( + private val repository: UploadRepository, + @param:Named("default_preferences") private val defaultKvStore: JsonKvStore +) : UploadContract.UserActionListener { + private var view = DUMMY + + @Inject + lateinit var presenter: UploadMediaDetailsContract.UserActionListener + + private val compositeDisposable = CompositeDisposable() + + /** + * Called by the submit button in [UploadActivity] + */ + @SuppressLint("CheckResult") + override fun handleSubmit() { + var hasLocationProvidedForNewUploads = false + for (item in repository.getUploads()) { + if (item.gpsCoords.imageCoordsExists) { + hasLocationProvidedForNewUploads = true + } + } + val hasManyConsecutiveUploadsWithoutLocation = defaultKvStore.getInt( + COUNTER_OF_CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES, 0 + ) >= + CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES_REMINDER_THRESHOLD + + if (hasManyConsecutiveUploadsWithoutLocation && !hasLocationProvidedForNewUploads) { + defaultKvStore.putInt(COUNTER_OF_CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES, 0) + view.showAlertDialog( + R.string.location_message + ) { + defaultKvStore.putInt( + COUNTER_OF_CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES, + 0 + ) + processContributionsForSubmission() + } + } else { + processContributionsForSubmission() + } + } + + private fun processContributionsForSubmission() { + if (view.isLoggedIn()) { + view.showProgress(true) + repository.buildContributions() + ?.observeOn(Schedulers.io()) + ?.subscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + view.showProgress(false) + if (defaultKvStore.getBoolean(IS_LIMITED_CONNECTION_MODE_ENABLED, false)) { + view.showMessage(R.string.uploading_queued) + } else { + view.showMessage(R.string.uploading_started) + } + compositeDisposable.add(d) + } + + override fun onNext(contribution: Contribution) { + if (contribution.decimalCoords == null) { + val recentCount = defaultKvStore.getInt( + COUNTER_OF_CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES, 0 + ) + defaultKvStore.putInt( + COUNTER_OF_CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES, recentCount + 1 + ) + } else { + defaultKvStore.putInt( + COUNTER_OF_CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES, 0 + ) + } + repository.prepareMedia(contribution) + contribution.state = Contribution.STATE_QUEUED + repository.saveContribution(contribution) + } + + override fun onError(e: Throwable) { + view.showMessage(R.string.upload_failed) + repository.cleanup() + view.returnToMainActivity() + compositeDisposable.clear() + Timber.e(e, "failed to upload") + + //is submission error, not need to go to the uploadActivity + //not start the uploading progress + } + + override fun onComplete() { + view.makeUploadRequest() + repository.cleanup() + view.returnToMainActivity() + compositeDisposable.clear() + + //after finish the uploadActivity, if successful, + //directly go to the upload progress activity + view.goToUploadProgressActivity() + } + }) + } else { + view.askUserToLogIn() + } + } + + /** + * Calls checkImageQuality of UploadMediaPresenter to check image quality of next image + * + * @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) + } + + override fun deletePictureAtIndex(index: Int) { + val uploadableFiles = view.getUploadableFiles() + if (index == uploadableFiles!!.size - 1) { + // If the next fragment to be shown is not one of the MediaDetailsFragment + // lets hide the top card so that it doesn't appear on the other fragments + view.showHideTopCard(false) + } + view.setImageCancelled(true) + repository.deletePicture(uploadableFiles[index].getFilePath()) + if (uploadableFiles.size == 1) { + view.showMessage(R.string.upload_cancelled) + view.finish() + return + } + + presenter.updateImageQualitiesJSON(uploadableFiles.size, index) + 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) + } + + if (uploadableFiles.size < 2) { + view.showHideTopCard(false) + } + + //In case lets update the number of uploadable media + view.updateTopCardTitle() + } + + override fun onAttachView(view: UploadContract.View) { + this.view = view + } + + override fun onDetachView() { + view = DUMMY + compositeDisposable.clear() + repository.cleanup() + } + + companion object { + private val DUMMY = Proxy.newProxyInstance( + UploadContract.View::class.java.classLoader, + arrayOf>(UploadContract.View::class.java) + ) { _: Any?, _: Method?, _: Array? -> null } as UploadContract.View + + const val COUNTER_OF_CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES: String = + "number_of_consecutive_uploads_without_coordinates" + + const val CONSECUTIVE_UPLOADS_WITHOUT_COORDINATES_REMINDER_THRESHOLD: Int = 10 + } +}