fr.free.nrw.commons.upload.UploadPresenter.java Source code

Java tutorial

Introduction

Here is the source code for fr.free.nrw.commons.upload.UploadPresenter.java

Source

package fr.free.nrw.commons.upload;

import android.annotation.SuppressLint;
import android.content.Context;
import android.text.TextUtils;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.category.CategoriesModel;
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.location.LatLng;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.utils.CustomProxy;
import fr.free.nrw.commons.utils.CustomProxy;
import fr.free.nrw.commons.utils.StringSortingUtils;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import timber.log.Timber;

import static fr.free.nrw.commons.upload.UploadModel.UploadItem;
import static fr.free.nrw.commons.utils.ImageUtils.EMPTY_TITLE;
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;

/**
 * The MVP pattern presenter of Upload GUI
 */
@Singleton
public class UploadPresenter {

    private static final UploadView DUMMY = (UploadView) CustomProxy.newInstance(UploadView.class.getClassLoader(),
            new Class[] { UploadView.class });

    private UploadView view = DUMMY;

    private static final SimilarImageInterface SIMILAR_IMAGE = (SimilarImageInterface) CustomProxy
            .newInstance(SimilarImageInterface.class.getClassLoader(), new Class[] { SimilarImageInterface.class });
    private SimilarImageInterface similarImageInterface = SIMILAR_IMAGE;

    @UploadView.UploadPage
    private int currentPage = UploadView.PLEASE_WAIT;

    private final UploadModel uploadModel;
    private final UploadController uploadController;
    private final Context context;
    private final JsonKvStore directKvStore;
    private CompositeDisposable compositeDisposable = new CompositeDisposable();

    @Inject
    UploadPresenter(UploadModel uploadModel, UploadController uploadController, Context context,
            @Named("default_preferences") JsonKvStore directKvStore) {
        this.uploadModel = uploadModel;
        this.uploadController = uploadController;
        this.context = context;
        this.directKvStore = directKvStore;
    }

    /**
      * Passes the items received to {@link #uploadModel} and displays the items.
      *
      * @param media    The Uri's of the media being uploaded.
      * @param source   File source from {@link Contribution.FileSource}
      */
    @SuppressLint("CheckResult")
    void receive(List<UploadableFile> media, @Contribution.FileSource String source, Place place) {
        Observable<UploadItem> uploadItemObservable = uploadModel.preProcessImages(media, place, source,
                similarImageInterface);

        compositeDisposable.add(
                uploadItemObservable.toList().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                        .subscribe(uploadItems -> onImagesProcessed(uploadItems, place),
                                throwable -> Timber.e(throwable, "Error occurred in processing images")));
    }

    private void onImagesProcessed(List<UploadItem> uploadItems, Place place) {
        uploadModel.onItemsProcessed(place, uploadItems);
        updateCards();
        updateLicenses();
        updateContent();
        uploadModel.subscribeBadPicture(this::handleBadImage, false);
    }

    /**
     * Sets the license to parameter and updates {@link UploadActivity}
     *
     * @param licenseName license name
     */
    void selectLicense(String licenseName) {
        uploadModel.setSelectedLicense(licenseName);
        view.updateLicenseSummary(uploadModel.getSelectedLicense(), uploadModel.getCount());
    }

    //region Wizard step management

    /**
     * Called by the next button in {@link UploadActivity}
     */
    @SuppressLint("CheckResult")
    void handleNext(Title title, List<Description> descriptions) {
        Timber.e("Inside handleNext");
        view.showProgressDialog();
        setTitleAndDescription(title, descriptions);
        compositeDisposable.add(uploadModel.getImageQuality(uploadModel.getCurrentItem(), true)
                .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                .subscribe(imageResult -> handleImage(title, descriptions, imageResult),
                        throwable -> Timber.e(throwable, "Error occurred while handling image")));
    }

    private void handleImage(Title title, List<Description> descriptions, Integer imageResult) {
        view.hideProgressDialog();
        if (imageResult == IMAGE_KEEP || imageResult == IMAGE_OK) {
            Timber.d("Set title and desc; Show next uploaded item");
            setTitleAndDescription(title, descriptions);
            directKvStore.putBoolean("Picture_Has_Correct_Location", true);
            nextUploadedItem();
        } else {
            handleBadImage(imageResult);
        }
    }

    /**
     * Called by the next button in {@link UploadActivity}
     */
    @SuppressLint("CheckResult")
    void handleCategoryNext(CategoriesModel categoriesModel, boolean noCategoryWarningShown) {
        if (categoriesModel.selectedCategoriesCount() < 1 && !noCategoryWarningShown) {
            view.showNoCategorySelectedWarning();
        } else {
            nextUploadedItem();
        }
    }

    private void handleBadImage(Integer errorCode) {
        Timber.d("Handle bad picture with error code %d", errorCode);
        if (errorCode >= 8) { // If location of image and nearby does not match, then set shared preferences to disable wikidata edits
            directKvStore.putBoolean("Picture_Has_Correct_Location", false);
        }

        switch (errorCode) {
        case EMPTY_TITLE:
            Timber.d("Title is empty. Showing toast");
            view.showErrorMessage(R.string.add_title_toast);
            break;
        case FILE_NAME_EXISTS:
            Timber.d("Trying to show duplicate picture popup");
            view.showDuplicatePicturePopup();
            break;
        default:
            String errorMessageForResult = getErrorMessageForResult(context, errorCode);
            if (TextUtils.isEmpty(errorMessageForResult)) {
                return;
            }
            view.showBadPicturePopup(errorMessageForResult);
        }
    }

    private void nextUploadedItem() {
        Timber.d("Trying to show next uploaded item");
        uploadModel.next();
        updateContent();
        uploadModel.subscribeBadPicture(this::handleBadImage, false);
        view.dismissKeyboard();
    }

    private void setTitleAndDescription(Title title, List<Description> descriptions) {
        Timber.d("setTitleAndDescription: Setting title and desc");
        uploadModel.setCurrentTitleAndDescriptions(title, descriptions);
    }

    String getCurrentImageFileName() {
        UploadItem currentItem = getCurrentItem();
        return currentItem.getFileName();
    }

    /**
     * Called by the previous button in {@link UploadActivity}
     */
    void handlePrevious() {
        uploadModel.previous();
        updateContent();
        uploadModel.subscribeBadPicture(this::handleBadImage, false);
        view.dismissKeyboard();
    }

    /**
     * Called when one of the pictures on the top card is clicked on in {@link UploadActivity}
     */
    void thumbnailClicked(UploadItem item) {
        uploadModel.jumpTo(item);
        updateContent();
    }

    /**
     * Called by the submit button in {@link UploadActivity}
     */
    @SuppressLint("CheckResult")
    void handleSubmit(CategoriesModel categoriesModel) {
        if (view.checkIfLoggedIn())
            compositeDisposable.add(uploadModel.buildContributions(categoriesModel.getCategoryStringList())
                    .observeOn(Schedulers.io()).subscribe(uploadController::startUpload));
    }

    /**
     * Called by the map button on the right card in {@link UploadActivity}
     */
    void openCoordinateMap() {
        GPSExtractor gpsObj = uploadModel.getCurrentItem().getGpsCoords();
        if (gpsObj != null && gpsObj.imageCoordsExists) {
            view.launchMapActivity(new LatLng(gpsObj.getDecLatitude(), gpsObj.getDecLongitude(), 0.0f));
        }
    }

    void keepPicture() {
        uploadModel.keepPicture();
    }

    void deletePicture() {
        if (uploadModel.getCount() == 1)
            view.finish();
        else {
            uploadModel.deletePicture();
            updateCards();
            updateContent();
            uploadModel.subscribeBadPicture(this::handleBadImage, false);
            view.dismissKeyboard();
        }
    }
    //endregion

    //region Top Bottom and Right card state management

    /**
     * Toggles the top card's state between open and closed.
     */
    void toggleTopCardState() {
        uploadModel.setTopCardState(!uploadModel.isTopCardState());
        view.setTopCardState(uploadModel.isTopCardState());
    }

    /**
     * Toggles the bottom card's state between open and closed.
     */
    void toggleBottomCardState() {
        uploadModel.setBottomCardState(!uploadModel.isBottomCardState());
        view.setBottomCardState(uploadModel.isBottomCardState());
    }

    /**
     * Sets all the cards' states to closed.
     */
    void closeAllCards() {
        if (uploadModel.isTopCardState()) {
            uploadModel.setTopCardState(false);
            view.setTopCardState(false);
        }
        if (uploadModel.isRightCardState()) {
            uploadModel.setRightCardState(false);
        }
        if (uploadModel.isBottomCardState()) {
            uploadModel.setBottomCardState(false);
            view.setBottomCardState(false);
        }
    }
    //endregion

    //region View / Lifecycle management
    public void init() {
        uploadController.prepareService();
    }

    void cleanup() {
        compositeDisposable.clear();
        uploadModel.cleanup();
        uploadController.cleanup();
    }

    void removeView() {
        this.view = DUMMY;
    }

    void addView(UploadView view) {
        this.view = view;

        updateCards();
        updateLicenses();
        updateContent();
    }

    /**
     * Updates the cards for when there is a change to the amount of items being uploaded.
     */
    private void updateCards() {
        Timber.i("uploadModel.getCount():" + uploadModel.getCount());
        view.updateThumbnails(uploadModel.getUploads());
        view.setTopCardVisibility(uploadModel.getCount() > 1);
        view.setBottomCardVisibility(uploadModel.getCount() > 0);
        view.setTopCardState(uploadModel.isTopCardState());
        view.setBottomCardState(uploadModel.isBottomCardState());
    }

    /**
     * Sets the list of licences and the default license.
     */
    private void updateLicenses() {
        String selectedLicense = directKvStore.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_4);//CC_BY_SA_4 is the default one used by the commons web app
        try {//I have to make sure that the stored default license was not one of the deprecated one's
            Utils.licenseNameFor(selectedLicense);
        } catch (IllegalStateException exception) {
            Timber.e(exception.getMessage());
            selectedLicense = Prefs.Licenses.CC_BY_SA_4;
            directKvStore.putString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_4);
        }
        view.updateLicenses(uploadModel.getLicenses(), selectedLicense);
        view.updateLicenseSummary(selectedLicense, uploadModel.getCount());
    }

    /**
     * Updates the cards and the background when a new currentPage is selected.
     */
    private void updateContent() {
        Timber.i("Updating content for currentPage" + uploadModel.getCurrentStep());
        view.setNextEnabled(uploadModel.isNextAvailable());
        view.setPreviousEnabled(uploadModel.isPreviousAvailable());
        view.setSubmitEnabled(uploadModel.isSubmitAvailable());

        view.setBackground(uploadModel.getCurrentItem().getMediaUri());

        view.updateBottomCardContent(uploadModel.getCurrentStep(), uploadModel.getStepCount(),
                uploadModel.getCurrentItem(), uploadModel.isShowingItem());

        view.updateTopCardContent();

        GPSExtractor gpsObj = uploadModel.getCurrentItem().getGpsCoords();
        view.updateRightCardContent(gpsObj != null && gpsObj.imageCoordsExists);

        view.updateSubtitleVisibility(uploadModel.getCount());

        showCorrectCards(uploadModel.getCurrentStep(), uploadModel.getCount());
    }

    /**
     * Updates the layout to show the correct bottom card.
     *
     * @param currentStep the current step
     * @param uploadCount how many items are being uploaded
     */
    private void showCorrectCards(int currentStep, int uploadCount) {
        if (uploadCount == 0) {
            currentPage = UploadView.PLEASE_WAIT;
        } else if (currentStep <= uploadCount) {
            currentPage = UploadView.TITLE_CARD;
            view.setTopCardVisibility(uploadModel.getCount() > 1);
        } else if (currentStep == uploadCount + 1) {
            currentPage = UploadView.CATEGORIES;
            view.setTopCardVisibility(false);
            view.setRightCardVisibility(false);
            view.initDefaultCategories();
        } else {
            currentPage = UploadView.LICENSE;
            view.setTopCardVisibility(false);
            view.setRightCardVisibility(false);
        }
        view.setBottomCardVisibility(currentPage, uploadCount);
    }

    //endregion

    /**
     * @return the item currently being displayed
     */
    private UploadItem getCurrentItem() {
        return uploadModel.getCurrentItem();
    }

    List<String> getImageTitleList() {
        List<String> titleList = new ArrayList<>();
        for (UploadItem item : uploadModel.getUploads()) {
            if (item.getTitle().isSet()) {
                titleList.add(item.getTitle().toString());
            }
        }
        return titleList;
    }

}