#3699 Multiple upload: Only one file's caption is used for all files, others ignored - share model data with the UI - inline and remove Remote/LocalDataSource (#3707)

This commit is contained in:
Seán Mac Gillicuddy 2020-05-08 11:21:17 +01:00 committed by GitHub
parent 11ff5fb055
commit 4443810823
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 341 additions and 704 deletions

View file

@ -6,7 +6,7 @@ import androidx.room.PrimaryKey;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.upload.UploadMediaDetail; import fr.free.nrw.commons.upload.UploadMediaDetail;
import fr.free.nrw.commons.upload.UploadModel.UploadItem; import fr.free.nrw.commons.upload.UploadItem;
import fr.free.nrw.commons.upload.WikidataPlace; import fr.free.nrw.commons.upload.WikidataPlace;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import java.util.ArrayList; import java.util.ArrayList;

View file

@ -1,151 +0,0 @@
package fr.free.nrw.commons.repository;
import androidx.annotation.Nullable;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.upload.UploadModel;
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
/**
* The Local Data Source for UploadRepository, fetches and returns data from local db/shared prefernces
*/
@Singleton
public class UploadLocalDataSource {
private final UploadModel uploadModel;
private JsonKvStore defaultKVStore;
@Inject
public UploadLocalDataSource(
@Named("default_preferences") JsonKvStore defaultKVStore,
UploadModel uploadModel) {
this.defaultKVStore = defaultKVStore;
this.uploadModel = uploadModel;
}
/**
* Fetches and returns the string list of valid licenses
*
* @return
*/
public List<String> getLicenses() {
return uploadModel.getLicenses();
}
/**
* Returns the number of Upload Items
*
* @return
*/
public int getCount() {
return uploadModel.getCount();
}
/**
* Fetches and return the selected license for the current upload
*
* @return
*/
public String getSelectedLicense() {
return uploadModel.getSelectedLicense();
}
/**
* Set selected license for the current upload
*
* @param licenseName
*/
public void setSelectedLicense(String licenseName) {
uploadModel.setSelectedLicense(licenseName);
}
/**
* Updates the current upload item
*
* @param index
* @param uploadItem
*/
public void updateUploadItem(int index, UploadItem uploadItem) {
uploadModel.updateUploadItem(index, uploadItem);
}
/**
* upload is halted, cleanup the acquired resources
*/
public void cleanUp() {
uploadModel.cleanUp();
}
/**
* Deletes the upload item at the current index
*
* @param filePath
*/
public void deletePicture(String filePath) {
uploadModel.deletePicture(filePath);
}
/**
* Fethces and returns the previous upload item, if any, returns null otherwise
*
* @param index
* @return
*/
@Nullable
public UploadItem getPreviousUploadItem(int index) {
if (index - 1 >= 0) {
return uploadModel.getItems().get(index - 1);
}
return null; //There is no previous item to copy details
}
/**
* saves boolean value in default store
*
* @param key
* @param value
*/
public void saveValue(String key, boolean value) {
defaultKVStore.putBoolean(key, value);
}
/**
* saves string value in default store
*
* @param key
* @param value
*/
public void saveValue(String key, String value) {
defaultKVStore.putString(key, value);
}
/**
* Fetches and returns string value from the default store
*
* @param key
* @param defaultValue
* @return
*/
public String getValue(String key, String defaultValue) {
return defaultKVStore.getString(key, defaultValue);
}
/**
* Fetches and returns boolean value from the default store
*
* @param key
* @param defaultValue
* @return
*/
public boolean getValue(String key, boolean defaultValue) {
return defaultKVStore.getBoolean(key, defaultValue);
}
}

View file

@ -1,230 +0,0 @@
package fr.free.nrw.commons.repository;
import fr.free.nrw.commons.category.CategoriesModel;
import fr.free.nrw.commons.category.CategoryItem;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.filepicker.UploadableFile;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.nearby.NearbyPlaces;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.upload.ImageCoordinates;
import fr.free.nrw.commons.upload.SimilarImageInterface;
import fr.free.nrw.commons.upload.UploadController;
import fr.free.nrw.commons.upload.UploadModel;
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
import fr.free.nrw.commons.upload.structure.depictions.DepictModel;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import io.reactivex.Flowable;
import io.reactivex.Observable;
import io.reactivex.Single;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* This class would act as the data source for remote operations for UploadActivity
*/
@Singleton
public class UploadRemoteDataSource {
private static final double NEARBY_RADIUS_IN_KILO_METERS = 0.1; //100 meters
private UploadModel uploadModel;
private UploadController uploadController;
private CategoriesModel categoriesModel;
private DepictModel depictModel;
private NearbyPlaces nearbyPlaces;
@Inject
public UploadRemoteDataSource(UploadModel uploadModel, UploadController uploadController,
CategoriesModel categoriesModel, NearbyPlaces nearbyPlaces, DepictModel depictModel) {
this.uploadModel = uploadModel;
this.uploadController = uploadController;
this.categoriesModel = categoriesModel;
this.nearbyPlaces = nearbyPlaces;
this.depictModel = depictModel;
}
/**
* asks the UploadModel to build the contributions
*
* @return
*/
public Observable<Contribution> buildContributions() {
return uploadModel.buildContributions();
}
/**
* asks the UploadService to star the uplaod for
*
* @param contribution
*/
public void startUpload(Contribution contribution) {
uploadController.startUpload(contribution);
}
/**
* returns the list of UploadItem from the UploadModel
*
* @return
*/
public List<UploadItem> getUploads() {
return uploadModel.getUploads();
}
/**
* Prepare the UploadService for the upload
*/
public void prepareService() {
uploadController.prepareService();
}
/**
* Clean up the selected categories
*/
public void cleanUp(){
//This needs further refactoring, this should not be here, right now the structure wont suppoort rhis
categoriesModel.cleanUp();
depictModel.cleanUp();
}
/**
* returnt the list of selected categories
*
* @return
*/
public List<CategoryItem> getSelectedCategories() {
return categoriesModel.getSelectedCategories();
}
/**
* all categories from MWApi
*
* @param query
* @param imageTitleList
* @return
*/
public Observable<CategoryItem> searchAll(String query, List<String> imageTitleList) {
return categoriesModel.searchAll(query, imageTitleList);
}
/**
* returns the string list of categories
*
* @return
*/
public List<String> getCategoryStringList() {
return categoriesModel.getCategoryStringList();
}
/**
* sets the selected categories in the UploadModel
*
* @param categoryStringList
*/
public void setSelectedCategories(List<String> categoryStringList) {
uploadModel.setSelectedCategories(categoryStringList);
}
/**
* handles category selection/unselection
*
* @param categoryItem
*/
public void onCategoryClicked(CategoryItem categoryItem) {
categoriesModel.onCategoryItemClicked(categoryItem);
}
/**
* returns category sorted based on similarity with query
*
* @param query
* @return
*/
public Comparator<CategoryItem> sortBySimilarity(String query) {
return categoriesModel.sortBySimilarity(query);
}
/**
* prunes the category list for irrelevant categories see #750
*
* @param name
* @return
*/
public boolean containsYear(String name) {
return categoriesModel.containsYear(name);
}
/**
* pre process the UploadableFile
*
* @param uploadableFile
* @param place
* @param similarImageInterface
* @return
*/
public Observable<UploadItem> preProcessImage(UploadableFile uploadableFile, Place place,
SimilarImageInterface similarImageInterface) {
return uploadModel.preProcessImage(uploadableFile, place, similarImageInterface);
}
/**
* ask the UplaodModel for the image quality of the UploadItem
*
* @param uploadItem
* @return
*/
public Single<Integer> getImageQuality(UploadItem uploadItem) {
return uploadModel.getImageQuality(uploadItem);
}
/**
* gets nearby places matching with upload item's GPS location
*
* @param latitude
* @param longitude
* @return
*/
public Place getNearbyPlaces(double latitude, double longitude) throws IOException {
List<Place> fromWikidataQuery = nearbyPlaces
.getFromWikidataQuery(new LatLng(latitude, longitude, 0.0f),
Locale.getDefault().getLanguage(),
NEARBY_RADIUS_IN_KILO_METERS);
return fromWikidataQuery.size() > 0 ? fromWikidataQuery.get(0) : null;
}
/**
* handles category selection/unselection
* @param depictedItem
*/
public void onDepictedItemClicked(DepictedItem depictedItem) {
uploadModel.onDepictItemClicked(depictedItem);
}
/**
* returns the list of selected depictions
* @return
*/
public List<DepictedItem> getSelectedDepictions() {
return uploadModel.getSelectedDepictions();
}
/**
* get all depictions
* @return
*/
public Flowable<List<DepictedItem>> searchAllEntities(String query) {
return depictModel.searchAllEntities(query);
}
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
uploadModel.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex);
}
}

View file

@ -1,24 +1,28 @@
package fr.free.nrw.commons.repository; package fr.free.nrw.commons.repository;
import fr.free.nrw.commons.upload.ImageCoordinates; import fr.free.nrw.commons.category.CategoriesModel;
import io.reactivex.Flowable;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import fr.free.nrw.commons.category.CategoryItem; import fr.free.nrw.commons.category.CategoryItem;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.filepicker.UploadableFile; import fr.free.nrw.commons.filepicker.UploadableFile;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.nearby.NearbyPlaces;
import fr.free.nrw.commons.nearby.Place; import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.upload.ImageCoordinates;
import fr.free.nrw.commons.upload.SimilarImageInterface; import fr.free.nrw.commons.upload.SimilarImageInterface;
import fr.free.nrw.commons.upload.UploadModel.UploadItem; import fr.free.nrw.commons.upload.UploadController;
import fr.free.nrw.commons.upload.UploadItem;
import fr.free.nrw.commons.upload.UploadModel;
import fr.free.nrw.commons.upload.structure.depictions.DepictModel;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import io.reactivex.Flowable;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.Single; import io.reactivex.Single;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
import javax.inject.Singleton;
/** /**
* The repository class for UploadActivity * The repository class for UploadActivity
@ -26,14 +30,25 @@ import io.reactivex.Single;
@Singleton @Singleton
public class UploadRepository { public class UploadRepository {
private UploadLocalDataSource localDataSource; private final UploadModel uploadModel;
private UploadRemoteDataSource remoteDataSource; private final UploadController uploadController;
private final CategoriesModel categoriesModel;
private final NearbyPlaces nearbyPlaces;
private final DepictModel depictModel;
private static final double NEARBY_RADIUS_IN_KILO_METERS = 0.1; //100 meters
@Inject @Inject
public UploadRepository(UploadLocalDataSource localDataSource, public UploadRepository(UploadModel uploadModel,
UploadRemoteDataSource remoteDataSource) { UploadController uploadController,
this.localDataSource = localDataSource; CategoriesModel categoriesModel,
this.remoteDataSource = remoteDataSource; NearbyPlaces nearbyPlaces,
DepictModel depictModel) {
this.uploadModel = uploadModel;
this.uploadController = uploadController;
this.categoriesModel = categoriesModel;
this.nearbyPlaces = nearbyPlaces;
this.depictModel = depictModel;
} }
/** /**
@ -42,7 +57,7 @@ public class UploadRepository {
* @return * @return
*/ */
public Observable<Contribution> buildContributions() { public Observable<Contribution> buildContributions() {
return remoteDataSource.buildContributions(); return uploadModel.buildContributions();
} }
/** /**
@ -51,7 +66,7 @@ public class UploadRepository {
* @param contribution * @param contribution
*/ */
public void startUpload(Contribution contribution) { public void startUpload(Contribution contribution) {
remoteDataSource.startUpload(contribution); uploadController.startUpload(contribution);
} }
/** /**
@ -60,22 +75,24 @@ public class UploadRepository {
* @return * @return
*/ */
public List<UploadItem> getUploads() { public List<UploadItem> getUploads() {
return remoteDataSource.getUploads(); return uploadModel.getUploads();
} }
/** /**
* asks the RemoteDataSource to prepare the Upload Service * asks the RemoteDataSource to prepare the Upload Service
*/ */
public void prepareService() { public void prepareService() {
remoteDataSource.prepareService(); uploadController.prepareService();
} }
/** /**
*Prepare for a fresh upload *Prepare for a fresh upload
*/ */
public void cleanup() { public void cleanup() {
localDataSource.cleanUp(); uploadModel.cleanUp();
remoteDataSource.cleanUp(); //This needs further refactoring, this should not be here, right now the structure wont suppoort rhis
categoriesModel.cleanUp();
depictModel.cleanUp();
} }
/** /**
@ -84,7 +101,7 @@ public class UploadRepository {
* @return * @return
*/ */
public List<CategoryItem> getSelectedCategories() { public List<CategoryItem> getSelectedCategories() {
return remoteDataSource.getSelectedCategories(); return categoriesModel.getSelectedCategories();
} }
/** /**
@ -95,7 +112,7 @@ public class UploadRepository {
* @return * @return
*/ */
public Observable<CategoryItem> searchAll(String query, List<String> imageTitleList) { public Observable<CategoryItem> searchAll(String query, List<String> imageTitleList) {
return remoteDataSource.searchAll(query, imageTitleList); return categoriesModel.searchAll(query, imageTitleList);
} }
/** /**
@ -105,7 +122,7 @@ public class UploadRepository {
*/ */
public List<String> getCategoryStringList() { public List<String> getCategoryStringList() {
return remoteDataSource.getCategoryStringList(); return categoriesModel.getCategoryStringList();
} }
/** /**
@ -114,7 +131,7 @@ public class UploadRepository {
* @param categoryStringList * @param categoryStringList
*/ */
public void setSelectedCategories(List<String> categoryStringList) { public void setSelectedCategories(List<String> categoryStringList) {
remoteDataSource.setSelectedCategories(categoryStringList); uploadModel.setSelectedCategories(categoryStringList);
} }
/** /**
@ -123,7 +140,7 @@ public class UploadRepository {
* @param categoryItem * @param categoryItem
*/ */
public void onCategoryClicked(CategoryItem categoryItem) { public void onCategoryClicked(CategoryItem categoryItem) {
remoteDataSource.onCategoryClicked(categoryItem); categoriesModel.onCategoryItemClicked(categoryItem);
} }
/** /**
@ -133,7 +150,7 @@ public class UploadRepository {
* @return * @return
*/ */
public Comparator<? super CategoryItem> sortBySimilarity(String query) { public Comparator<? super CategoryItem> sortBySimilarity(String query) {
return remoteDataSource.sortBySimilarity(query); return categoriesModel.sortBySimilarity(query);
} }
/** /**
@ -143,7 +160,7 @@ public class UploadRepository {
* @return * @return
*/ */
public boolean containsYear(String name) { public boolean containsYear(String name) {
return remoteDataSource.containsYear(name); return categoriesModel.containsYear(name);
} }
/** /**
@ -152,7 +169,7 @@ public class UploadRepository {
* @return * @return
*/ */
public List<String> getLicenses() { public List<String> getLicenses() {
return localDataSource.getLicenses(); return uploadModel.getLicenses();
} }
/** /**
@ -161,7 +178,7 @@ public class UploadRepository {
* @return * @return
*/ */
public String getSelectedLicense() { public String getSelectedLicense() {
return localDataSource.getSelectedLicense(); return uploadModel.getSelectedLicense();
} }
/** /**
@ -170,7 +187,7 @@ public class UploadRepository {
* @return * @return
*/ */
public int getCount() { public int getCount() {
return localDataSource.getCount(); return uploadModel.getCount();
} }
/** /**
@ -183,7 +200,8 @@ public class UploadRepository {
*/ */
public Observable<UploadItem> preProcessImage(UploadableFile uploadableFile, Place place, public Observable<UploadItem> preProcessImage(UploadableFile uploadableFile, Place place,
SimilarImageInterface similarImageInterface) { SimilarImageInterface similarImageInterface) {
return remoteDataSource.preProcessImage(uploadableFile, place, similarImageInterface); return uploadModel.preProcessImage(uploadableFile, place,
similarImageInterface);
} }
/** /**
@ -193,17 +211,7 @@ public class UploadRepository {
* @return * @return
*/ */
public Single<Integer> getImageQuality(UploadItem uploadItem) { public Single<Integer> getImageQuality(UploadItem uploadItem) {
return remoteDataSource.getImageQuality(uploadItem); return uploadModel.getImageQuality(uploadItem);
}
/**
* asks the LocalDataSource to update the Upload Item
*
* @param index
* @param uploadItem
*/
public void updateUploadItem(int index, UploadItem uploadItem) {
localDataSource.updateUploadItem(index, uploadItem);
} }
/** /**
@ -212,7 +220,7 @@ public class UploadRepository {
* @param filePath * @param filePath
*/ */
public void deletePicture(String filePath) { public void deletePicture(String filePath) {
localDataSource.deletePicture(filePath); uploadModel.deletePicture(filePath);
} }
/** /**
@ -222,38 +230,10 @@ public class UploadRepository {
* @return * @return
*/ */
public UploadItem getPreviousUploadItem(int index) { public UploadItem getPreviousUploadItem(int index) {
return localDataSource.getPreviousUploadItem(index); if (index - 1 >= 0) {
} return uploadModel.getItems().get(index - 1);
}
/** return null; //There is no previous item to copy details
* Save boolean value locally
*
* @param key
* @param value
*/
public void saveValue(String key, boolean value) {
localDataSource.saveValue(key, value);
}
/**
* save string value locally
*
* @param key
* @param value
*/
public void saveValue(String key, String value) {
localDataSource.saveValue(key, value);
}
/**
* fetch the string value for the associated key
*
* @param key
* @param value
* @return
*/
public String getValue(String key, String value) {
return localDataSource.getValue(key, value);
} }
/** /**
@ -262,11 +242,11 @@ public class UploadRepository {
* @param licenseName * @param licenseName
*/ */
public void setSelectedLicense(String licenseName) { public void setSelectedLicense(String licenseName) {
localDataSource.setSelectedLicense(licenseName); uploadModel.setSelectedLicense(licenseName);
} }
public void onDepictItemClicked(DepictedItem depictedItem) { public void onDepictItemClicked(DepictedItem depictedItem) {
remoteDataSource.onDepictedItemClicked(depictedItem); uploadModel.onDepictItemClicked(depictedItem);
} }
/** /**
@ -276,7 +256,7 @@ public class UploadRepository {
*/ */
public List<DepictedItem> getSelectedDepictions() { public List<DepictedItem> getSelectedDepictions() {
return remoteDataSource.getSelectedDepictions(); return uploadModel.getSelectedDepictions();
} }
/** /**
@ -287,7 +267,7 @@ public class UploadRepository {
*/ */
public Flowable<List<DepictedItem>> searchAllEntities(String query) { public Flowable<List<DepictedItem>> searchAllEntities(String query) {
return remoteDataSource.searchAllEntities(query); return depictModel.searchAllEntities(query);
} }
/** /**
@ -296,11 +276,19 @@ public class UploadRepository {
* @param decLongitude * @param decLongitude
* @return * @return
*/ */
public Place checkNearbyPlaces(double decLatitude, double decLongitude) throws IOException { public Place checkNearbyPlaces(double decLatitude, double decLongitude) {
return remoteDataSource.getNearbyPlaces(decLatitude, decLongitude); try {
List<Place> fromWikidataQuery = nearbyPlaces.getFromWikidataQuery(new LatLng(
decLatitude, decLongitude, 0.0f),
Locale.getDefault().getLanguage(),
NEARBY_RADIUS_IN_KILO_METERS);
return fromWikidataQuery.size() > 0 ? fromWikidataQuery.get(0) : null;
} catch (IOException e) {
return null;
}
} }
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) { public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
remoteDataSource.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex); uploadModel.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex);
} }
} }

View file

@ -45,7 +45,7 @@ public class ImageProcessingService {
* Check image quality before upload - checks duplicate image - checks dark image - checks * Check image quality before upload - checks duplicate image - checks dark image - checks
* geolocation for image - check for valid title * geolocation for image - check for valid title
*/ */
Single<Integer> validateImage(UploadModel.UploadItem uploadItem) { Single<Integer> validateImage(UploadItem uploadItem) {
int currentImageQuality = uploadItem.getImageQuality(); int currentImageQuality = uploadItem.getImageQuality();
Timber.d("Current image quality is %d", currentImageQuality); Timber.d("Current image quality is %d", currentImageQuality);
if (currentImageQuality == ImageUtils.IMAGE_KEEP) { if (currentImageQuality == ImageUtils.IMAGE_KEEP) {
@ -97,7 +97,7 @@ public class ImageProcessingService {
* @param uploadItem * @param uploadItem
* @return * @return
*/ */
private Single<Integer> validateItemTitle(UploadModel.UploadItem uploadItem) { private Single<Integer> validateItemTitle(UploadItem uploadItem) {
Timber.d("Checking for image title %s", uploadItem.getUploadMediaDetails()); Timber.d("Checking for image title %s", uploadItem.getUploadMediaDetails());
List<UploadMediaDetail> captions = uploadItem.getUploadMediaDetails(); List<UploadMediaDetail> captions = uploadItem.getUploadMediaDetails();
if (captions.isEmpty()) { if (captions.isEmpty()) {

View file

@ -0,0 +1,106 @@
package fr.free.nrw.commons.upload;
import android.annotation.SuppressLint;
import android.net.Uri;
import androidx.annotation.Nullable;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.filepicker.MimeTypeMapWrapper;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.utils.ImageUtils;
import io.reactivex.subjects.BehaviorSubject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class UploadItem {
private final Uri mediaUri;
private final String mimeType;
private ImageCoordinates gpsCoords;
private List<UploadMediaDetail> uploadMediaDetails;
private final Place place;
private final long createdTimestamp;
private final String createdTimestampSource;
private final BehaviorSubject<Integer> imageQuality;
@SuppressLint("CheckResult")
UploadItem(final Uri mediaUri,
final String mimeType,
final ImageCoordinates gpsCoords,
final Place place,
final long createdTimestamp,
final String createdTimestampSource) {
this.createdTimestampSource = createdTimestampSource;
uploadMediaDetails = new ArrayList<>(Collections.singletonList(new UploadMediaDetail()));
this.place = place;
this.mediaUri = mediaUri;
this.mimeType = mimeType;
this.gpsCoords = gpsCoords;
this.createdTimestamp = createdTimestamp;
imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT);
}
public String getCreatedTimestampSource() {
return createdTimestampSource;
}
public ImageCoordinates getGpsCoords() {
return gpsCoords;
}
public List<UploadMediaDetail> getUploadMediaDetails() {
return uploadMediaDetails;
}
public long getCreatedTimestamp() {
return createdTimestamp;
}
public Uri getMediaUri() {
return mediaUri;
}
public int getImageQuality() {
return imageQuality.getValue();
}
public void setImageQuality(final int imageQuality) {
this.imageQuality.onNext(imageQuality);
}
public Place getPlace() {
return place;
}
public void setMediaDetails(final List<UploadMediaDetail> uploadMediaDetails) {
this.uploadMediaDetails = uploadMediaDetails;
}
@Override
public boolean equals(@Nullable final Object obj) {
if (!(obj instanceof UploadItem)) {
return false;
}
return mediaUri.toString().contains(((UploadItem) (obj)).mediaUri.toString());
}
@Override
public int hashCode() {
return mediaUri.hashCode();
}
/**
* Choose a filename for the media. Currently, the caption is used as a filename. If several
* languages have been entered, the first language is used.
*/
public String getFileName() {
return Utils.fixExtension(uploadMediaDetails.get(0).getCaptionText(),
MimeTypeMapWrapper.getExtensionFromMimeType(mimeType));
}
public void setGpsCoords(final ImageCoordinates gpsCoords) {
this.gpsCoords = gpsCoords;
}
}

View file

@ -17,6 +17,8 @@ data class UploadMediaDetail constructor(
var descriptionText: String = "", var descriptionText: String = "",
var captionText: String = "" var captionText: String = ""
) { ) {
fun javaCopy() = copy()
constructor(place: Place) : this( constructor(place: Place) : this(
Locale.getDefault().language, Locale.getDefault().language,
place.longDescription, place.longDescription,

View file

@ -1,25 +1,18 @@
package fr.free.nrw.commons.upload; package fr.free.nrw.commons.upload;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.Nullable;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.filepicker.MimeTypeMapWrapper;
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.kvstore.JsonKvStore;
import fr.free.nrw.commons.nearby.Place; import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import fr.free.nrw.commons.utils.ImageUtils;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.Single; import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.subjects.BehaviorSubject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -44,8 +37,8 @@ public class UploadModel {
private final SessionManager sessionManager; private final SessionManager sessionManager;
private final FileProcessor fileProcessor; private final FileProcessor fileProcessor;
private final ImageProcessingService imageProcessingService; private final ImageProcessingService imageProcessingService;
private List<String> selectedCategories = new ArrayList<>(); private final List<String> selectedCategories = new ArrayList<>();
private List<DepictedItem> selectedDepictions = new ArrayList<>(); private final List<DepictedItem> selectedDepictions = new ArrayList<>();
@Inject @Inject
UploadModel(@Named("licenses") final List<String> licenses, UploadModel(@Named("licenses") final List<String> licenses,
@ -77,7 +70,8 @@ public class UploadModel {
} }
public void setSelectedCategories(List<String> selectedCategories) { public void setSelectedCategories(List<String> selectedCategories) {
this.selectedCategories = selectedCategories; this.selectedCategories.clear();
this.selectedCategories.addAll(selectedCategories);
} }
/** /**
@ -150,7 +144,7 @@ public class UploadModel {
Timber.d("Created timestamp while building contribution is %s, %s", Timber.d("Created timestamp while building contribution is %s, %s",
item.getCreatedTimestamp(), item.getCreatedTimestamp(),
new Date(item.getCreatedTimestamp())); new Date(item.getCreatedTimestamp()));
if (item.createdTimestamp != -1L) { if (item.getCreatedTimestamp() != -1L) {
contribution.setDateCreated(new Date(item.getCreatedTimestamp())); contribution.setDateCreated(new Date(item.getCreatedTimestamp()));
contribution.setDateCreatedSource(item.getCreatedTimestampSource()); contribution.setDateCreatedSource(item.getCreatedTimestampSource());
//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
@ -162,7 +156,7 @@ public class UploadModel {
public void deletePicture(final String filePath) { public void deletePicture(final String filePath) {
final Iterator<UploadItem> iterator = items.iterator(); final Iterator<UploadItem> iterator = items.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
if (iterator.next().mediaUri.toString().contains(filePath)) { if (iterator.next().getMediaUri().toString().contains(filePath)) {
iterator.remove(); iterator.remove();
break; break;
} }
@ -176,11 +170,6 @@ public class UploadModel {
return items; return items;
} }
public void updateUploadItem(final int index, final UploadItem uploadItem) {
final UploadItem uploadItem1 = items.get(index);
uploadItem1.setMediaDetails(uploadItem.uploadMediaDetails);
}
public void onDepictItemClicked(DepictedItem depictedItem) { public void onDepictItemClicked(DepictedItem depictedItem) {
if (depictedItem.isSelected()) { if (depictedItem.isSelected()) {
selectedDepictions.add(depictedItem); selectedDepictions.add(depictedItem);
@ -202,97 +191,4 @@ public class UploadModel {
public List<DepictedItem> getSelectedDepictions() { public List<DepictedItem> getSelectedDepictions() {
return selectedDepictions; return selectedDepictions;
} }
@SuppressWarnings("WeakerAccess")
public static class UploadItem {
private final Uri mediaUri;
private final String mimeType;
private ImageCoordinates gpsCoords;
private List<UploadMediaDetail> uploadMediaDetails;
private final Place place;
private final long createdTimestamp;
private final String createdTimestampSource;
private final BehaviorSubject<Integer> imageQuality;
@SuppressLint("CheckResult")
UploadItem(final Uri mediaUri,
final String mimeType,
final ImageCoordinates gpsCoords,
final Place place,
final long createdTimestamp,
final String createdTimestampSource) {
this.createdTimestampSource = createdTimestampSource;
uploadMediaDetails = new ArrayList<>(Arrays.asList(new UploadMediaDetail()));
this.place = place;
this.mediaUri = mediaUri;
this.mimeType = mimeType;
this.gpsCoords = gpsCoords;
this.createdTimestamp = createdTimestamp;
imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT);
}
public String getCreatedTimestampSource() {
return createdTimestampSource;
}
public ImageCoordinates getGpsCoords() {
return gpsCoords;
}
public List<UploadMediaDetail> getUploadMediaDetails() {
return uploadMediaDetails;
}
public long getCreatedTimestamp() {
return createdTimestamp;
}
public Uri getMediaUri() {
return mediaUri;
}
public int getImageQuality() {
return this.imageQuality.getValue();
}
public void setImageQuality(final int imageQuality) {
this.imageQuality.onNext(imageQuality);
}
public Place getPlace() {
return place;
}
public void setMediaDetails(final List<UploadMediaDetail> uploadMediaDetails) {
this.uploadMediaDetails = uploadMediaDetails;
}
@Override
public boolean equals(@Nullable final Object obj) {
if (!(obj instanceof UploadItem)) {
return false;
}
return this.mediaUri.toString().contains(((UploadItem) (obj)).mediaUri.toString());
}
@Override
public int hashCode() {
return mediaUri.hashCode();
}
/**
* Choose a filename for the media.
* Currently, the caption is used as a filename. If several languages have been entered, the first language is used.
*/
public String getFileName() {
return Utils.fixExtension(uploadMediaDetails.get(0).getCaptionText(),
MimeTypeMapWrapper.getExtensionFromMimeType(mimeType));
}
public void setGpsCoords(final ImageCoordinates gpsCoords) {
this.gpsCoords = gpsCoords;
}
}
} }

View file

@ -30,7 +30,7 @@ public interface UploadView {
boolean checkIfLoggedIn(); boolean checkIfLoggedIn();
void updateThumbnails(List<UploadModel.UploadItem> uploads); void updateThumbnails(List<UploadItem> uploads);
void setNextEnabled(boolean available); void setNextEnabled(boolean available);
@ -54,7 +54,7 @@ public interface UploadView {
void updateRightCardContent(boolean gpsPresent); void updateRightCardContent(boolean gpsPresent);
void updateBottomCardContent(int currentStep, int stepCount, UploadModel.UploadItem uploadItem, boolean isShowingItem); void updateBottomCardContent(int currentStep, int stepCount, UploadItem uploadItem, boolean isShowingItem);
void updateLicenses(List<String> licenses, String selectedLicense); void updateLicenses(List<String> licenses, String selectedLicense);

View file

@ -7,7 +7,7 @@ import android.text.TextUtils;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.category.CategoryItem; import fr.free.nrw.commons.category.CategoryItem;
import fr.free.nrw.commons.repository.UploadRepository; import fr.free.nrw.commons.repository.UploadRepository;
import fr.free.nrw.commons.upload.UploadModel.UploadItem; import fr.free.nrw.commons.upload.UploadItem;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.Scheduler; import io.reactivex.Scheduler;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;

View file

@ -1,14 +1,14 @@
package fr.free.nrw.commons.upload.license; package fr.free.nrw.commons.upload.license;
import java.lang.reflect.Proxy;
import java.util.List;
import javax.inject.Inject;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.repository.UploadRepository; import fr.free.nrw.commons.repository.UploadRepository;
import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.license.MediaLicenseContract.View; import fr.free.nrw.commons.upload.license.MediaLicenseContract.View;
import java.lang.reflect.Proxy;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber; import timber.log.Timber;
/** /**
@ -23,11 +23,14 @@ public class MediaLicensePresenter implements MediaLicenseContract.UserActionLis
(proxy, method, methodArgs) -> null); (proxy, method, methodArgs) -> null);
private final UploadRepository repository; private final UploadRepository repository;
private final JsonKvStore defaultKVStore;
private MediaLicenseContract.View view = DUMMY; private MediaLicenseContract.View view = DUMMY;
@Inject @Inject
public MediaLicensePresenter(UploadRepository uploadRepository) { public MediaLicensePresenter(UploadRepository uploadRepository,
@Named("default_preferences") JsonKvStore defaultKVStore) {
this.repository = uploadRepository; this.repository = uploadRepository;
this.defaultKVStore = defaultKVStore;
} }
@Override @Override
@ -48,14 +51,14 @@ public class MediaLicensePresenter implements MediaLicenseContract.UserActionLis
List<String> licenses = repository.getLicenses(); List<String> licenses = repository.getLicenses();
view.setLicenses(licenses); view.setLicenses(licenses);
String selectedLicense = repository.getValue(Prefs.DEFAULT_LICENSE, String selectedLicense = defaultKVStore.getString(Prefs.DEFAULT_LICENSE,
Prefs.Licenses.CC_BY_SA_4);//CC_BY_SA_4 is the default one used by the commons web app 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 try {//I have to make sure that the stored default license was not one of the deprecated one's
Utils.licenseNameFor(selectedLicense); Utils.licenseNameFor(selectedLicense);
} catch (IllegalStateException exception) { } catch (IllegalStateException exception) {
Timber.e(exception.getMessage()); Timber.e(exception.getMessage());
selectedLicense = Prefs.Licenses.CC_BY_SA_4; selectedLicense = Prefs.Licenses.CC_BY_SA_4;
repository.saveValue(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_4); defaultKVStore.putString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_4);
} }
view.setSelectedLicense(selectedLicense); view.setSelectedLicense(selectedLicense);

View file

@ -31,8 +31,7 @@ import fr.free.nrw.commons.upload.SimilarImageDialogFragment;
import fr.free.nrw.commons.upload.UploadBaseFragment; import fr.free.nrw.commons.upload.UploadBaseFragment;
import fr.free.nrw.commons.upload.UploadMediaDetail; import fr.free.nrw.commons.upload.UploadMediaDetail;
import fr.free.nrw.commons.upload.UploadMediaDetailAdapter; import fr.free.nrw.commons.upload.UploadMediaDetailAdapter;
import fr.free.nrw.commons.upload.UploadModel; import fr.free.nrw.commons.upload.UploadItem;
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
import fr.free.nrw.commons.utils.DialogUtil; import fr.free.nrw.commons.utils.DialogUtil;
import fr.free.nrw.commons.utils.ImageUtils; import fr.free.nrw.commons.utils.ImageUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
@ -45,8 +44,6 @@ import javax.inject.Named;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import timber.log.Timber; import timber.log.Timber;
//import fr.free.nrw.commons.upload.DescriptionsAdapter;
public class UploadMediaDetailFragment extends UploadBaseFragment implements public class UploadMediaDetailFragment extends UploadBaseFragment implements
UploadMediaDetailsContract.View, UploadMediaDetailAdapter.EventListener { UploadMediaDetailsContract.View, UploadMediaDetailAdapter.EventListener {
@ -70,9 +67,6 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
@BindView(R.id.btn_copy_prev_title_desc) @BindView(R.id.btn_copy_prev_title_desc)
AppCompatButton btnCopyPreviousTitleDesc; AppCompatButton btnCopyPreviousTitleDesc;
private UploadModel.UploadItem uploadItem;
private List<UploadMediaDetail> descriptions;
@Inject @Inject
UploadMediaDetailsContract.UserActionListener presenter; UploadMediaDetailsContract.UserActionListener presenter;
@ -181,8 +175,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
@OnClick(R.id.btn_next) @OnClick(R.id.btn_next)
public void onNextButtonClicked() { public void onNextButtonClicked() {
uploadItem.setMediaDetails(uploadMediaDetailAdapter.getUploadMediaDetails()); presenter.verifyImageQuality(callback.getIndexInViewFlipper(this));
presenter.verifyImageQuality(uploadItem);
} }
@OnClick(R.id.btn_previous) @OnClick(R.id.btn_previous)
@ -222,10 +215,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
@Override @Override
public void onImageProcessed(UploadItem uploadItem, Place place) { public void onImageProcessed(UploadItem uploadItem, Place place) {
this.uploadItem = uploadItem;
descriptions = uploadItem.getUploadMediaDetails();
photoViewBackgroundImage.setImageURI(uploadItem.getMediaUri()); photoViewBackgroundImage.setImageURI(uploadItem.getMediaUri());
setDescriptionsInAdapter(descriptions);
} }
/** /**
@ -242,8 +232,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
getString(R.string.upload_nearby_place_found_description), getString(R.string.upload_nearby_place_found_description),
place.getName()), place.getName()),
() -> { () -> {
descriptions = new ArrayList<>(Arrays.asList(new UploadMediaDetail(place))); presenter.onUserConfirmedUploadIsOfPlace(place, callback.getIndexInViewFlipper(this));
setDescriptionsInAdapter(descriptions);
}, },
() -> { () -> {
@ -257,7 +246,6 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
@Override @Override
public void onImageValidationSuccess() { public void onImageValidationSuccess() {
presenter.setUploadItem(callback.getIndexInViewFlipper(this), uploadItem);
callback.onNextButtonClicked(callback.getIndexInViewFlipper(this)); callback.onNextButtonClicked(callback.getIndexInViewFlipper(this));
} }
@ -272,7 +260,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
} }
@Override @Override
public void showDuplicatePicturePopup() { public void showDuplicatePicturePopup(UploadItem uploadItem) {
String uploadTitleFormat = getString(R.string.upload_title_duplicate); String uploadTitleFormat = getString(R.string.upload_title_duplicate);
DialogUtil.showAlertDialog(getActivity(), DialogUtil.showAlertDialog(getActivity(),
getString(R.string.duplicate_image_found), getString(R.string.duplicate_image_found),
@ -289,7 +277,8 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
} }
@Override @Override
public void showBadImagePopup(Integer errorCode) { public void showBadImagePopup(Integer errorCode,
UploadItem uploadItem) {
String errorMessageForResult = getErrorMessageForResult(getContext(), errorCode); String errorMessageForResult = getErrorMessageForResult(getContext(), errorCode);
if (!StringUtils.isBlank(errorMessageForResult)) { if (!StringUtils.isBlank(errorMessageForResult)) {
DialogUtil.showAlertDialog(getActivity(), DialogUtil.showAlertDialog(getActivity(),
@ -312,8 +301,15 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
} }
@Override @Override
public void setCaptionsAndDescriptions(List<UploadMediaDetail> uploadMediaDetails) { public void showExternalMap(UploadItem uploadItem) {
setDescriptionsInAdapter(uploadMediaDetails); Utils.handleGeoCoordinates(getContext(),
new LatLng(uploadItem.getGpsCoords().getDecLatitude(),
uploadItem.getGpsCoords().getDecLongitude(), 0.0f));
}
@Override
public void updateMediaDetails(List<UploadMediaDetail> uploadMediaDetails) {
uploadMediaDetailAdapter.setItems(uploadMediaDetails);
} }
private void deleteThisPicture() { private void deleteThisPicture() {
@ -342,9 +338,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
} }
@OnClick(R.id.ib_map) public void onIbMapClicked() { @OnClick(R.id.ib_map) public void onIbMapClicked() {
Utils.handleGeoCoordinates(getContext(), presenter.onMapIconClicked(callback.getIndexInViewFlipper(this));
new LatLng(uploadItem.getGpsCoords().getDecLatitude(),
uploadItem.getGpsCoords().getDecLongitude(), 0.0f));
} }
@Override @Override
@ -354,7 +348,6 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
btnNext.setAlpha(isNotEmpty ? 1.0f: 0.5f); btnNext.setAlpha(isNotEmpty ? 1.0f: 0.5f);
} }
public interface UploadMediaDetailFragmentCallback extends Callback { public interface UploadMediaDetailFragmentCallback extends Callback {
void deletePictureAtIndex(int index); void deletePictureAtIndex(int index);
@ -366,7 +359,4 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
presenter.fetchPreviousTitleAndDescription(callback.getIndexInViewFlipper(this)); presenter.fetchPreviousTitleAndDescription(callback.getIndexInViewFlipper(this));
} }
private void setDescriptionsInAdapter(List<UploadMediaDetail> uploadMediaDetails){
uploadMediaDetailAdapter.setItems(uploadMediaDetails);
}
} }

View file

@ -6,7 +6,7 @@ import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.upload.ImageCoordinates; import fr.free.nrw.commons.upload.ImageCoordinates;
import fr.free.nrw.commons.upload.SimilarImageInterface; import fr.free.nrw.commons.upload.SimilarImageInterface;
import fr.free.nrw.commons.upload.UploadMediaDetail; import fr.free.nrw.commons.upload.UploadMediaDetail;
import fr.free.nrw.commons.upload.UploadModel.UploadItem; import fr.free.nrw.commons.upload.UploadItem;
import java.util.List; import java.util.List;
/** /**
@ -28,27 +28,30 @@ public interface UploadMediaDetailsContract {
void showMessage(String message, int colorResourceId); void showMessage(String message, int colorResourceId);
void showDuplicatePicturePopup(); void showDuplicatePicturePopup(UploadItem uploadItem);
void showBadImagePopup(Integer errorCode); void showBadImagePopup(Integer errorCode, UploadItem uploadItem);
void showMapWithImageCoordinates(boolean shouldShow); void showMapWithImageCoordinates(boolean shouldShow);
void setCaptionsAndDescriptions(List<UploadMediaDetail> uploadMediaDetails); void showExternalMap(UploadItem uploadItem);
void updateMediaDetails(List<UploadMediaDetail> uploadMediaDetails);
} }
interface UserActionListener extends BasePresenter<View> { interface UserActionListener extends BasePresenter<View> {
void receiveImage(UploadableFile uploadableFile, Place place); void receiveImage(UploadableFile uploadableFile, Place place);
void verifyImageQuality(UploadItem uploadItem); void verifyImageQuality(int uploadItemIndex);
void setUploadItem(int index, UploadItem uploadItem);
void fetchPreviousTitleAndDescription(int indexInViewFlipper); void fetchPreviousTitleAndDescription(int indexInViewFlipper);
void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex); void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex);
void onMapIconClicked(int indexInViewFlipper);
void onUserConfirmedUploadIsOfPlace(Place place, int uploadItemPosition);
} }
} }

View file

@ -9,11 +9,13 @@ import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
import fr.free.nrw.commons.R; 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.nearby.Place; import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.repository.UploadRepository; import fr.free.nrw.commons.repository.UploadRepository;
import fr.free.nrw.commons.upload.ImageCoordinates; import fr.free.nrw.commons.upload.ImageCoordinates;
import fr.free.nrw.commons.upload.SimilarImageInterface; import fr.free.nrw.commons.upload.SimilarImageInterface;
import fr.free.nrw.commons.upload.UploadModel.UploadItem; import fr.free.nrw.commons.upload.UploadItem;
import fr.free.nrw.commons.upload.UploadMediaDetail;
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract.UserActionListener; import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract.UserActionListener;
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract.View; import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract.View;
import io.reactivex.Maybe; import io.reactivex.Maybe;
@ -21,8 +23,11 @@ import io.reactivex.Scheduler;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
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 UploadMediaPresenter implements UserActionListener, SimilarImageInterface { public class UploadMediaPresenter implements UserActionListener, SimilarImageInterface {
@ -38,14 +43,17 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
private CompositeDisposable compositeDisposable; private CompositeDisposable compositeDisposable;
private final JsonKvStore defaultKVStore;
private Scheduler ioScheduler; private Scheduler ioScheduler;
private Scheduler mainThreadScheduler; private Scheduler mainThreadScheduler;
@Inject @Inject
public UploadMediaPresenter(UploadRepository uploadRepository, public UploadMediaPresenter(UploadRepository uploadRepository,
@Named("default_preferences") JsonKvStore defaultKVStore,
@Named(IO_THREAD) Scheduler ioScheduler, @Named(IO_THREAD) Scheduler ioScheduler,
@Named(MAIN_THREAD) Scheduler mainThreadScheduler) { @Named(MAIN_THREAD) Scheduler mainThreadScheduler) {
this.repository = uploadRepository; this.repository = uploadRepository;
this.defaultKVStore = defaultKVStore;
this.ioScheduler = ioScheduler; this.ioScheduler = ioScheduler;
this.mainThreadScheduler = mainThreadScheduler; this.mainThreadScheduler = mainThreadScheduler;
compositeDisposable = new CompositeDisposable(); compositeDisposable = new CompositeDisposable();
@ -70,22 +78,25 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
@Override @Override
public void receiveImage(UploadableFile uploadableFile, Place place) { public void receiveImage(UploadableFile uploadableFile, Place place) {
view.showProgress(true); view.showProgress(true);
Disposable uploadItemDisposable = repository compositeDisposable.add(
repository
.preProcessImage(uploadableFile, place, this) .preProcessImage(uploadableFile, place, this)
.subscribeOn(ioScheduler) .subscribeOn(ioScheduler)
.observeOn(mainThreadScheduler) .observeOn(mainThreadScheduler)
.subscribe(uploadItem -> .subscribe(uploadItem ->
{ {
view.onImageProcessed(uploadItem, place); view.onImageProcessed(uploadItem, place);
ImageCoordinates gpsCoords = uploadItem.getGpsCoords(); view.updateMediaDetails(uploadItem.getUploadMediaDetails());
view.showMapWithImageCoordinates(gpsCoords != null && gpsCoords.getImageCoordsExists()); ImageCoordinates gpsCoords = uploadItem.getGpsCoords();
view.showProgress(false); final boolean hasImageCoordinates =
if (gpsCoords != null && gpsCoords.getImageCoordsExists()) { gpsCoords != null && gpsCoords.getImageCoordsExists();
checkNearbyPlaces(uploadItem); view.showMapWithImageCoordinates(hasImageCoordinates);
} view.showProgress(false);
}, if (hasImageCoordinates) {
throwable -> Timber.e(throwable, "Error occurred in pre-processing images")); checkNearbyPlaces(uploadItem);
compositeDisposable.add(uploadItemDisposable); }
},
throwable -> Timber.e(throwable, "Error occurred in processing images")));
} }
/** /**
@ -110,19 +121,20 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
/** /**
* asks the repository to verify image quality * asks the repository to verify image quality
* *
* @param uploadItem * @param uploadItemIndex
*/ */
@Override @Override
public void verifyImageQuality(UploadItem uploadItem) { public void verifyImageQuality(int uploadItemIndex) {
view.showProgress(true); view.showProgress(true);
compositeDisposable.add( final UploadItem uploadItem = repository.getUploads().get(uploadItemIndex);
compositeDisposable.add(
repository repository
.getImageQuality(uploadItem) .getImageQuality(uploadItem)
.observeOn(mainThreadScheduler) .observeOn(mainThreadScheduler)
.subscribe(imageResult -> { .subscribe(imageResult -> {
view.showProgress(false); view.showProgress(false);
handleImageResult(imageResult); handleImageResult(imageResult, uploadItem);
}, },
throwable -> { throwable -> {
view.showProgress(false); view.showProgress(false);
@ -133,16 +145,6 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
); );
} }
/**
* Adds the corresponding upload item to the repository
*
* @param index
* @param uploadItem
*/
@Override
public void setUploadItem(int index, UploadItem uploadItem) {
repository.updateUploadItem(index, uploadItem);
}
/** /**
* Fetches and sets the caption and desctiption of the previous item * Fetches and sets the caption and desctiption of the previous item
@ -152,28 +154,55 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
@Override @Override
public void fetchPreviousTitleAndDescription(int indexInViewFlipper) { public void fetchPreviousTitleAndDescription(int indexInViewFlipper) {
UploadItem previousUploadItem = repository.getPreviousUploadItem(indexInViewFlipper); UploadItem previousUploadItem = repository.getPreviousUploadItem(indexInViewFlipper);
if (null != previousUploadItem) { if (null != previousUploadItem) {
view.setCaptionsAndDescriptions(previousUploadItem.getUploadMediaDetails()); final UploadItem currentUploadItem = repository.getUploads().get(indexInViewFlipper);
currentUploadItem.setMediaDetails(deepCopy(previousUploadItem.getUploadMediaDetails()));
view.updateMediaDetails(currentUploadItem.getUploadMediaDetails());
} else { } else {
view.showMessage(R.string.previous_image_title_description_not_found, R.color.color_error); view.showMessage(R.string.previous_image_title_description_not_found, R.color.color_error);
} }
} }
@NotNull
private List<UploadMediaDetail> deepCopy(List<UploadMediaDetail> uploadMediaDetails) {
final ArrayList<UploadMediaDetail> newList = new ArrayList<>();
for (UploadMediaDetail uploadMediaDetail : uploadMediaDetails) {
newList.add(uploadMediaDetail.javaCopy());
}
return newList;
}
@Override @Override
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) { public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
repository.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex); repository.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex);
} }
@Override
public void onMapIconClicked(int indexInViewFlipper) {
view.showExternalMap(repository.getUploads().get(indexInViewFlipper));
}
@Override
public void onUserConfirmedUploadIsOfPlace(Place place, int uploadItemPosition) {
final List<UploadMediaDetail> uploadMediaDetails = repository.getUploads()
.get(uploadItemPosition)
.getUploadMediaDetails();
uploadMediaDetails.set(0, new UploadMediaDetail(place));
view.updateMediaDetails(uploadMediaDetails);
}
/** /**
* handles image quality verifications * handles image quality verifications
* *
* @param imageResult * @param imageResult
*/ * @param uploadItem
public void handleImageResult(Integer imageResult) { */
public void handleImageResult(Integer imageResult,
UploadItem uploadItem) {
if (imageResult == IMAGE_KEEP || imageResult == IMAGE_OK) { if (imageResult == IMAGE_KEEP || imageResult == IMAGE_OK) {
view.onImageValidationSuccess(); view.onImageValidationSuccess();
} else { } else {
handleBadImage(imageResult); handleBadImage(imageResult, uploadItem);
} }
} }
@ -181,12 +210,13 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
* Handle images, say empty caption, duplicate file name, bad picture(in all other cases) * Handle images, say empty caption, duplicate file name, bad picture(in all other cases)
* *
* @param errorCode * @param errorCode
* @param uploadItem
*/ */
public void handleBadImage(Integer errorCode) { public void handleBadImage(Integer errorCode, UploadItem uploadItem) {
Timber.d("Handle bad picture with error code %d", errorCode); Timber.d("Handle bad picture with error code %d", errorCode);
if (errorCode // If location of image and nearby does not match, then set shared preferences to disable wikidata edits
>= 8) { // If location of image and nearby does not match, then set shared preferences to disable wikidata edits if (errorCode >= 8) {
repository.saveValue("Picture_Has_Correct_Location", false); defaultKVStore.putBoolean("Picture_Has_Correct_Location", false);
} }
switch (errorCode) { switch (errorCode) {
@ -196,10 +226,10 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
break; break;
case FILE_NAME_EXISTS: case FILE_NAME_EXISTS:
Timber.d("Trying to show duplicate picture popup"); Timber.d("Trying to show duplicate picture popup");
view.showDuplicatePicturePopup(); view.showDuplicatePicturePopup(uploadItem);
break; break;
default: default:
view.showBadImagePopup(errorCode); view.showBadImagePopup(errorCode, uploadItem);
} }
} }

View file

@ -33,7 +33,7 @@ class u {
var imageProcessingService: ImageProcessingService? = null var imageProcessingService: ImageProcessingService? = null
@Mock @Mock
internal lateinit var uploadItem: UploadModel.UploadItem internal lateinit var uploadItem: UploadItem
@Before @Before
@Throws(Exception::class) @Throws(Exception::class)

View file

@ -2,6 +2,7 @@ package fr.free.nrw.commons.upload
import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verify
import fr.free.nrw.commons.Utils import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.repository.UploadRepository import fr.free.nrw.commons.repository.UploadRepository
import fr.free.nrw.commons.upload.license.MediaLicenseContract import fr.free.nrw.commons.upload.license.MediaLicenseContract
import fr.free.nrw.commons.upload.license.MediaLicensePresenter import fr.free.nrw.commons.upload.license.MediaLicensePresenter
@ -24,13 +25,16 @@ import org.powermock.modules.junit4.PowerMockRunner
@PrepareForTest(Utils::class) @PrepareForTest(Utils::class)
class MediaLicensePresenterTest { class MediaLicensePresenterTest {
@Mock @Mock
internal var repository: UploadRepository? = null internal lateinit var repository: UploadRepository
@Mock @Mock
internal var view: MediaLicenseContract.View? = null internal lateinit var defaultKvStore: JsonKvStore
@Mock
internal lateinit var view: MediaLicenseContract.View
@InjectMocks @InjectMocks
var mediaLicensePresenter: MediaLicensePresenter? = null lateinit var mediaLicensePresenter: MediaLicensePresenter
/** /**
* initial setup test environemnt * initial setup test environemnt
@ -39,7 +43,7 @@ class MediaLicensePresenterTest {
@Throws(Exception::class) @Throws(Exception::class)
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
mediaLicensePresenter?.onAttachView(view) mediaLicensePresenter.onAttachView(view)
PowerMockito.mockStatic(Utils::class.java) PowerMockito.mockStatic(Utils::class.java)
PowerMockito.`when`(Utils.licenseNameFor(ArgumentMatchers.anyString())).thenReturn(1) PowerMockito.`when`(Utils.licenseNameFor(ArgumentMatchers.anyString())).thenReturn(1)
} }
@ -50,9 +54,9 @@ class MediaLicensePresenterTest {
*/ */
@Test @Test
fun getLicenseTest() { fun getLicenseTest() {
mediaLicensePresenter?.getLicenses() mediaLicensePresenter.getLicenses()
verify(view)?.setLicenses(ArgumentMatchers.anyList()) verify(view).setLicenses(ArgumentMatchers.anyList())
verify(view)?.setSelectedLicense(ArgumentMatchers.any()) verify(view).setSelectedLicense(ArgumentMatchers.any())
} }
/** /**
@ -60,7 +64,7 @@ class MediaLicensePresenterTest {
*/ */
@Test @Test
fun selectLicenseTest() { fun selectLicenseTest() {
mediaLicensePresenter?.selectLicense(ArgumentMatchers.anyString()) mediaLicensePresenter.selectLicense(ArgumentMatchers.anyString())
verify(view)?.updateLicenseSummary(ArgumentMatchers.any(), ArgumentMatchers.anyInt()) verify(view).updateLicenseSummary(ArgumentMatchers.any(), ArgumentMatchers.anyInt())
} }
} }

View file

@ -3,6 +3,7 @@ package fr.free.nrw.commons.upload
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.filepicker.UploadableFile import fr.free.nrw.commons.filepicker.UploadableFile
import fr.free.nrw.commons.kvstore.JsonKvStore
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
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract
@ -41,16 +42,19 @@ class UploadMediaPresenterTest {
private lateinit var place: Place private lateinit var place: Place
@Mock @Mock
private lateinit var uploadItem: UploadModel.UploadItem private lateinit var uploadItem: UploadItem
@Mock @Mock
private lateinit var uploadMediaDetails: List<UploadMediaDetail> private lateinit var uploadMediaDetails: List<UploadMediaDetail>
private lateinit var testObservableUploadItem: Observable<UploadModel.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
@Mock
private lateinit var jsonKvStore: JsonKvStore
/** /**
* initial setup unit test environment * initial setup unit test environment
*/ */
@ -61,7 +65,8 @@ class UploadMediaPresenterTest {
testObservableUploadItem = Observable.just(uploadItem) testObservableUploadItem = Observable.just(uploadItem)
testSingleImageResult = Single.just(1) testSingleImageResult = Single.just(1)
testScheduler = TestScheduler() testScheduler = TestScheduler()
uploadMediaPresenter = UploadMediaPresenter(repository, testScheduler, testScheduler) uploadMediaPresenter = UploadMediaPresenter(repository,
jsonKvStore,testScheduler, testScheduler)
uploadMediaPresenter.onAttachView(view) uploadMediaPresenter.onAttachView(view)
} }
@ -81,7 +86,7 @@ class UploadMediaPresenterTest {
verify(view).showProgress(true) verify(view).showProgress(true)
testScheduler.triggerActions() testScheduler.triggerActions()
verify(view).onImageProcessed( verify(view).onImageProcessed(
ArgumentMatchers.any(UploadModel.UploadItem::class.java), ArgumentMatchers.any(UploadItem::class.java),
ArgumentMatchers.any(Place::class.java) ArgumentMatchers.any(Place::class.java)
) )
verify(view).showProgress(false) verify(view).showProgress(false)
@ -92,10 +97,11 @@ class UploadMediaPresenterTest {
*/ */
@Test @Test
fun verifyImageQualityTest() { fun verifyImageQualityTest() {
whenever(repository.getImageQuality(ArgumentMatchers.any(UploadModel.UploadItem::class.java))) whenever(repository.uploads).thenReturn(listOf(uploadItem))
whenever(repository.getImageQuality(uploadItem))
.thenReturn(testSingleImageResult) .thenReturn(testSingleImageResult)
whenever(uploadItem.imageQuality).thenReturn(ArgumentMatchers.anyInt()) whenever(uploadItem.imageQuality).thenReturn(ArgumentMatchers.anyInt())
uploadMediaPresenter.verifyImageQuality(uploadItem) uploadMediaPresenter.verifyImageQuality(0)
verify(view).showProgress(true) verify(view).showProgress(true)
testScheduler.triggerActions() testScheduler.triggerActions()
verify(view).showProgress(false) verify(view).showProgress(false)
@ -107,21 +113,21 @@ class UploadMediaPresenterTest {
@Test @Test
fun handleImageResult() { fun handleImageResult() {
//Positive case test //Positive case test
uploadMediaPresenter.handleImageResult(IMAGE_KEEP) uploadMediaPresenter.handleImageResult(IMAGE_KEEP, uploadItem)
verify(view).onImageValidationSuccess() verify(view).onImageValidationSuccess()
//Duplicate file name //Duplicate file name
uploadMediaPresenter.handleImageResult(FILE_NAME_EXISTS) uploadMediaPresenter.handleImageResult(FILE_NAME_EXISTS, uploadItem)
verify(view).showDuplicatePicturePopup() verify(view).showDuplicatePicturePopup(uploadItem)
//Empty Caption test //Empty Caption test
uploadMediaPresenter.handleImageResult(EMPTY_CAPTION) uploadMediaPresenter.handleImageResult(EMPTY_CAPTION, uploadItem)
verify(view).showMessage(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()) verify(view).showMessage(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
//Bad Picture test //Bad Picture test
//Empty Caption test //Empty Caption test
uploadMediaPresenter.handleImageResult(-7) uploadMediaPresenter.handleImageResult(-7, uploadItem)
verify(view)?.showBadImagePopup(ArgumentMatchers.anyInt()) verify(view)?.showBadImagePopup(ArgumentMatchers.anyInt(), eq(uploadItem))
} }
@ -138,7 +144,6 @@ class UploadMediaPresenterTest {
testScheduler.triggerActions() testScheduler.triggerActions()
verify(view).showProgress(true) verify(view).showProgress(true)
verify(view).onImageValidationSuccess() verify(view).onImageValidationSuccess()
uploadMediaPresenter.setUploadItem(0, uploadItem)
} }
} }
@ -155,7 +160,6 @@ class UploadMediaPresenterTest {
testScheduler.triggerActions() testScheduler.triggerActions()
verify(view).showProgress(true) verify(view).showProgress(true)
verify(view).onImageValidationSuccess() verify(view).onImageValidationSuccess()
uploadMediaPresenter.setUploadItem(0, uploadItem)
} }
} }
@ -164,12 +168,13 @@ class UploadMediaPresenterTest {
*/ */
@Test @Test
fun fetchPreviousImageAndTitleTestPositive() { fun fetchPreviousImageAndTitleTestPositive() {
whenever(repository.uploads).thenReturn(listOf(uploadItem))
whenever(repository.getPreviousUploadItem(ArgumentMatchers.anyInt())) whenever(repository.getPreviousUploadItem(ArgumentMatchers.anyInt()))
.thenReturn(uploadItem) .thenReturn(uploadItem)
whenever(uploadItem.uploadMediaDetails).thenReturn(uploadMediaDetails) whenever(uploadItem.uploadMediaDetails).thenReturn(listOf())
uploadMediaPresenter.fetchPreviousTitleAndDescription(0) uploadMediaPresenter.fetchPreviousTitleAndDescription(0)
verify(view).setCaptionsAndDescriptions(ArgumentMatchers.any()) verify(view).updateMediaDetails(ArgumentMatchers.any())
} }
/** /**
@ -188,9 +193,9 @@ class UploadMediaPresenterTest {
*/ */
@Test @Test
fun handleBadImageBaseTestInvalidLocation() { fun handleBadImageBaseTestInvalidLocation() {
uploadMediaPresenter.handleBadImage(8) uploadMediaPresenter.handleBadImage(8, uploadItem)
verify(repository).saveValue(ArgumentMatchers.anyString(), eq(false)) verify(jsonKvStore).putBoolean(ArgumentMatchers.anyString(), eq(false))
verify(view).showBadImagePopup(8) verify(view).showBadImagePopup(8, uploadItem)
} }
/** /**
@ -198,7 +203,7 @@ class UploadMediaPresenterTest {
*/ */
@Test @Test
fun handleBadImageBaseTestEmptyTitle() { fun handleBadImageBaseTestEmptyTitle() {
uploadMediaPresenter.handleBadImage(-3) uploadMediaPresenter.handleBadImage(-3, uploadItem)
verify(view).showMessage(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()) verify(view).showMessage(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
} }
@ -207,8 +212,8 @@ class UploadMediaPresenterTest {
*/ */
@Test @Test
fun handleBadImageBaseTestFileNameExists() { fun handleBadImageBaseTestFileNameExists() {
uploadMediaPresenter.handleBadImage(-4) uploadMediaPresenter.handleBadImage(-4, uploadItem)
verify(view).showDuplicatePicturePopup() verify(view).showDuplicatePicturePopup(uploadItem)
} }
@ -222,13 +227,4 @@ class UploadMediaPresenterTest {
verify(view).showSimilarImageFragment("original", "possible", similar) verify(view).showSimilarImageFragment("original", "possible", similar)
} }
/**
* Test set upload item
*/
@Test
fun setUploadItemTest() {
uploadMediaPresenter.setUploadItem(0, uploadItem)
verify(repository).updateUploadItem(0, uploadItem)
}
} }