mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-30 06:13:54 +01:00
Convert UploadModel to kotlin
This commit is contained in:
parent
75ba0f5065
commit
a6c4731f74
11 changed files with 282 additions and 341 deletions
|
|
@ -101,7 +101,7 @@ data class Contribution constructor(
|
||||||
*/
|
*/
|
||||||
fun formatCaptions(uploadMediaDetails: List<UploadMediaDetail>) =
|
fun formatCaptions(uploadMediaDetails: List<UploadMediaDetail>) =
|
||||||
uploadMediaDetails
|
uploadMediaDetails
|
||||||
.associate { it.languageCode!! to it.captionText }
|
.associate { it.languageCode!! to it.captionText!! }
|
||||||
.filter { it.value.isNotBlank() }
|
.filter { it.value.isNotBlank() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -112,7 +112,7 @@ data class Contribution constructor(
|
||||||
*/
|
*/
|
||||||
fun formatDescriptions(descriptions: List<UploadMediaDetail>) =
|
fun formatDescriptions(descriptions: List<UploadMediaDetail>) =
|
||||||
descriptions
|
descriptions
|
||||||
.filter { it.descriptionText.isNotEmpty() }
|
.filter { it.descriptionText!!.isNotEmpty() }
|
||||||
.joinToString(separator = "") { "{{${it.languageCode}|1=${it.descriptionText}}}" }
|
.joinToString(separator = "") { "{{${it.languageCode}|1=${it.descriptionText}}}" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -272,11 +272,11 @@ class DescriptionEditActivity :
|
||||||
applicationContext,
|
applicationContext,
|
||||||
media,
|
media,
|
||||||
mediaDetail.languageCode!!,
|
mediaDetail.languageCode!!,
|
||||||
mediaDetail.captionText,
|
mediaDetail.captionText!!,
|
||||||
).subscribeOn(Schedulers.io())
|
).subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe { s: Boolean? ->
|
.subscribe { s: Boolean? ->
|
||||||
updatedCaptions[mediaDetail.languageCode!!] = mediaDetail.captionText
|
updatedCaptions[mediaDetail.languageCode!!] = mediaDetail.captionText!!
|
||||||
media.captions = updatedCaptions
|
media.captions = updatedCaptions
|
||||||
Timber.d("Caption is added.")
|
Timber.d("Caption is added.")
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1569,7 +1569,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
|
||||||
mediaDetail: UploadMediaDetail,
|
mediaDetail: UploadMediaDetail,
|
||||||
updatedCaptions: MutableMap<String, String>
|
updatedCaptions: MutableMap<String, String>
|
||||||
) {
|
) {
|
||||||
updatedCaptions[mediaDetail.languageCode!!] = mediaDetail.captionText
|
updatedCaptions[mediaDetail.languageCode!!] = mediaDetail.captionText!!
|
||||||
media!!.captions = updatedCaptions
|
media!!.captions = updatedCaptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ class UploadRepository @Inject constructor(
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
fun getUploads(): List<UploadItem> {
|
fun getUploads(): List<UploadItem> {
|
||||||
return uploadModel.getUploads()
|
return uploadModel.uploads
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -275,7 +275,7 @@ class UploadRepository @Inject constructor(
|
||||||
* @param selectedExistingDepictions existing depicts
|
* @param selectedExistingDepictions existing depicts
|
||||||
*/
|
*/
|
||||||
fun setSelectedExistingDepictions(selectedExistingDepictions: List<String>) {
|
fun setSelectedExistingDepictions(selectedExistingDepictions: List<String>) {
|
||||||
uploadModel.selectedExistingDepictions = selectedExistingDepictions
|
uploadModel.selectedExistingDepictions = selectedExistingDepictions.toMutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ class FileProcessor
|
||||||
* Processes filePath coordinates, either from EXIF data or user location
|
* Processes filePath coordinates, either from EXIF data or user location
|
||||||
*/
|
*/
|
||||||
fun processFileCoordinates(
|
fun processFileCoordinates(
|
||||||
similarImageInterface: SimilarImageInterface,
|
similarImageInterface: SimilarImageInterface?,
|
||||||
filePath: String?,
|
filePath: String?,
|
||||||
inAppPictureLocation: LatLng?,
|
inAppPictureLocation: LatLng?,
|
||||||
): ImageCoordinates {
|
): ImageCoordinates {
|
||||||
|
|
@ -146,7 +146,7 @@ class FileProcessor
|
||||||
*/
|
*/
|
||||||
private fun findOtherImages(
|
private fun findOtherImages(
|
||||||
fileBeingProcessed: File,
|
fileBeingProcessed: File,
|
||||||
similarImageInterface: SimilarImageInterface,
|
similarImageInterface: SimilarImageInterface?,
|
||||||
) {
|
) {
|
||||||
val oneHundredAndTwentySeconds = 120 * 1000L
|
val oneHundredAndTwentySeconds = 120 * 1000L
|
||||||
// Time when the original image was created
|
// Time when the original image was created
|
||||||
|
|
@ -161,7 +161,7 @@ class FileProcessor
|
||||||
.map { Pair(it, readImageCoordinates(it)) }
|
.map { Pair(it, readImageCoordinates(it)) }
|
||||||
.firstOrNull { it.second?.decimalCoords != null }
|
.firstOrNull { it.second?.decimalCoords != null }
|
||||||
?.let { fileCoordinatesPair ->
|
?.let { fileCoordinatesPair ->
|
||||||
similarImageInterface.showSimilarImageFragment(
|
similarImageInterface?.showSimilarImageFragment(
|
||||||
fileBeingProcessed.path,
|
fileBeingProcessed.path,
|
||||||
fileCoordinatesPair.first.absolutePath,
|
fileCoordinatesPair.first.absolutePath,
|
||||||
fileCoordinatesPair.second,
|
fileCoordinatesPair.second,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import kotlinx.parcelize.Parcelize
|
||||||
* Holds a description of an item being uploaded by [UploadActivity]
|
* Holds a description of an item being uploaded by [UploadActivity]
|
||||||
*/
|
*/
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class UploadMediaDetail constructor(
|
data class UploadMediaDetail(
|
||||||
/**
|
/**
|
||||||
* The language code ie. "en" or "fr".
|
* The language code ie. "en" or "fr".
|
||||||
* @param languageCode The language code ie. "en" or "fr".
|
* @param languageCode The language code ie. "en" or "fr".
|
||||||
|
|
@ -18,19 +18,19 @@ data class UploadMediaDetail constructor(
|
||||||
* The description text for the item being uploaded.
|
* The description text for the item being uploaded.
|
||||||
* @param descriptionText The description text.
|
* @param descriptionText The description text.
|
||||||
*/
|
*/
|
||||||
var descriptionText: String = "",
|
var descriptionText: String? = "",
|
||||||
/**
|
/**
|
||||||
* The caption text for the item being uploaded.
|
* The caption text for the item being uploaded.
|
||||||
* @param captionText The caption text.
|
* @param captionText The caption text.
|
||||||
*/
|
*/
|
||||||
var captionText: String = "",
|
var captionText: String? = "",
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
fun javaCopy() = copy()
|
fun javaCopy() = copy()
|
||||||
|
|
||||||
constructor(place: Place) : this(
|
constructor(place: Place?) : this(
|
||||||
place.language,
|
place?.language,
|
||||||
place.longDescription,
|
place?.longDescription,
|
||||||
place.name,
|
place?.name,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,297 +0,0 @@
|
||||||
package fr.free.nrw.commons.upload;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.net.Uri;
|
|
||||||
import fr.free.nrw.commons.Media;
|
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
|
||||||
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.upload.structure.depictions.DepictedItem;
|
|
||||||
import io.reactivex.Observable;
|
|
||||||
import io.reactivex.Single;
|
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class UploadModel {
|
|
||||||
|
|
||||||
private final JsonKvStore store;
|
|
||||||
private final List<String> licenses;
|
|
||||||
private final Context context;
|
|
||||||
private String license;
|
|
||||||
private final Map<String, String> licensesByName;
|
|
||||||
private final List<UploadItem> items = new ArrayList<>();
|
|
||||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
|
||||||
|
|
||||||
private final SessionManager sessionManager;
|
|
||||||
private final FileProcessor fileProcessor;
|
|
||||||
private final ImageProcessingService imageProcessingService;
|
|
||||||
private final List<String> selectedCategories = new ArrayList<>();
|
|
||||||
private final List<DepictedItem> selectedDepictions = new ArrayList<>();
|
|
||||||
/**
|
|
||||||
* Existing depicts which are selected
|
|
||||||
*/
|
|
||||||
private List<String> selectedExistingDepictions = new ArrayList<>();
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
UploadModel(@Named("licenses") final List<String> licenses,
|
|
||||||
@Named("default_preferences") final JsonKvStore store,
|
|
||||||
@Named("licenses_by_name") final Map<String, String> licensesByName,
|
|
||||||
final Context context,
|
|
||||||
final SessionManager sessionManager,
|
|
||||||
final FileProcessor fileProcessor,
|
|
||||||
final ImageProcessingService imageProcessingService) {
|
|
||||||
this.licenses = licenses;
|
|
||||||
this.store = store;
|
|
||||||
this.license = store.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
|
|
||||||
this.licensesByName = licensesByName;
|
|
||||||
this.context = context;
|
|
||||||
this.sessionManager = sessionManager;
|
|
||||||
this.fileProcessor = fileProcessor;
|
|
||||||
this.imageProcessingService = imageProcessingService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* cleanup the resources, I am Singleton, preparing for fresh upload
|
|
||||||
*/
|
|
||||||
public void cleanUp() {
|
|
||||||
compositeDisposable.clear();
|
|
||||||
fileProcessor.cleanup();
|
|
||||||
items.clear();
|
|
||||||
selectedCategories.clear();
|
|
||||||
selectedDepictions.clear();
|
|
||||||
selectedExistingDepictions.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelectedCategories(List<String> selectedCategories) {
|
|
||||||
this.selectedCategories.clear();
|
|
||||||
this.selectedCategories.addAll(selectedCategories);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* pre process a one item at a time
|
|
||||||
*/
|
|
||||||
public Observable<UploadItem> preProcessImage(final UploadableFile uploadableFile,
|
|
||||||
final Place place,
|
|
||||||
final SimilarImageInterface similarImageInterface,
|
|
||||||
LatLng inAppPictureLocation) {
|
|
||||||
return Observable.just(
|
|
||||||
createAndAddUploadItem(uploadableFile, place, similarImageInterface, inAppPictureLocation));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls validateImage() of ImageProcessingService to check quality of image
|
|
||||||
*
|
|
||||||
* @param uploadItem UploadItem whose quality is to be checked
|
|
||||||
* @param inAppPictureLocation In app picture location (if any)
|
|
||||||
* @return Quality of UploadItem
|
|
||||||
*/
|
|
||||||
public Single<Integer> getImageQuality(final UploadItem uploadItem, LatLng inAppPictureLocation) {
|
|
||||||
return imageProcessingService.validateImage(uploadItem, inAppPictureLocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls checkDuplicateImage() of ImageProcessingService to check if image is duplicate
|
|
||||||
*
|
|
||||||
* @param filePath file to be checked
|
|
||||||
* @return IMAGE_DUPLICATE or IMAGE_OK
|
|
||||||
*/
|
|
||||||
public Single<Integer> checkDuplicateImage(String filePath){
|
|
||||||
return imageProcessingService.checkDuplicateImage(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls validateCaption() of ImageProcessingService to check caption of image
|
|
||||||
*
|
|
||||||
* @param uploadItem UploadItem whose caption is to be checked
|
|
||||||
* @return Quality of caption of the UploadItem
|
|
||||||
*/
|
|
||||||
public Single<Integer> getCaptionQuality(final UploadItem uploadItem) {
|
|
||||||
return imageProcessingService.validateCaption(uploadItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
private UploadItem createAndAddUploadItem(final UploadableFile uploadableFile,
|
|
||||||
final Place place,
|
|
||||||
final SimilarImageInterface similarImageInterface,
|
|
||||||
LatLng inAppPictureLocation) {
|
|
||||||
final UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile
|
|
||||||
.getFileCreatedDate(context);
|
|
||||||
long fileCreatedDate = -1;
|
|
||||||
String createdTimestampSource = "";
|
|
||||||
String fileCreatedDateString = "";
|
|
||||||
if (dateTimeWithSource != null) {
|
|
||||||
fileCreatedDate = dateTimeWithSource.getEpochDate();
|
|
||||||
fileCreatedDateString = dateTimeWithSource.getDateString();
|
|
||||||
createdTimestampSource = dateTimeWithSource.getSource();
|
|
||||||
}
|
|
||||||
Timber.d("File created date is %d", fileCreatedDate);
|
|
||||||
final ImageCoordinates imageCoordinates = fileProcessor
|
|
||||||
.processFileCoordinates(similarImageInterface, uploadableFile.getFilePath(),
|
|
||||||
inAppPictureLocation);
|
|
||||||
final UploadItem uploadItem = new UploadItem(
|
|
||||||
Uri.parse(uploadableFile.getFilePath()),
|
|
||||||
uploadableFile.getMimeType(context), imageCoordinates, place, fileCreatedDate,
|
|
||||||
createdTimestampSource,
|
|
||||||
uploadableFile.getContentUri(),
|
|
||||||
fileCreatedDateString);
|
|
||||||
|
|
||||||
// If an uploadItem of the same uploadableFile has been created before, we return that.
|
|
||||||
// This is to avoid multiple instances of uploadItem of same file passed around.
|
|
||||||
if (items.contains(uploadItem)) {
|
|
||||||
return items.get(items.indexOf(uploadItem));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (place != null) {
|
|
||||||
uploadItem.getUploadMediaDetails().set(0, new UploadMediaDetail(place));
|
|
||||||
}
|
|
||||||
if (!items.contains(uploadItem)) {
|
|
||||||
items.add(uploadItem);
|
|
||||||
}
|
|
||||||
return uploadItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCount() {
|
|
||||||
return items.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<UploadItem> getUploads() {
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getLicenses() {
|
|
||||||
return licenses;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSelectedLicense() {
|
|
||||||
return license;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelectedLicense(final String licenseName) {
|
|
||||||
this.license = licensesByName.get(licenseName);
|
|
||||||
store.putString(Prefs.DEFAULT_LICENSE, license);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Observable<Contribution> buildContributions() {
|
|
||||||
return Observable.fromIterable(items).map(item ->
|
|
||||||
{
|
|
||||||
String imageSHA1 = FileUtils.INSTANCE.getSHA1(context.getContentResolver().openInputStream(item.getContentUri()));
|
|
||||||
|
|
||||||
final Contribution contribution = new Contribution(
|
|
||||||
item, sessionManager, newListOf(selectedDepictions), newListOf(selectedCategories), imageSHA1);
|
|
||||||
|
|
||||||
contribution.setHasInvalidLocation(item.hasInvalidLocation());
|
|
||||||
|
|
||||||
Timber.d("Created timestamp while building contribution is %s, %s",
|
|
||||||
item.getCreatedTimestamp(),
|
|
||||||
new Date(item.getCreatedTimestamp()));
|
|
||||||
|
|
||||||
if (item.getCreatedTimestamp() != -1L) {
|
|
||||||
contribution.setDateCreated(new Date(item.getCreatedTimestamp()));
|
|
||||||
contribution.setDateCreatedSource(item.getCreatedTimestampSource());
|
|
||||||
//Set the date only if you have it, else the upload service is gonna try it the other way
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contribution.getWikidataPlace() != null) {
|
|
||||||
if (item.isWLMUpload()) {
|
|
||||||
contribution.getWikidataPlace().setMonumentUpload(true);
|
|
||||||
} else {
|
|
||||||
contribution.getWikidataPlace().setMonumentUpload(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
contribution.setCountryCode(item.getCountryCode());
|
|
||||||
return contribution;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deletePicture(final String filePath) {
|
|
||||||
final Iterator<UploadItem> iterator = items.iterator();
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
if (iterator.next().getMediaUri().toString().contains(filePath)) {
|
|
||||||
iterator.remove();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (items.isEmpty()) {
|
|
||||||
cleanUp();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<UploadItem> getItems() {
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onDepictItemClicked(DepictedItem depictedItem, Media media) {
|
|
||||||
if (media == null) {
|
|
||||||
if (depictedItem.isSelected()) {
|
|
||||||
selectedDepictions.add(depictedItem);
|
|
||||||
} else {
|
|
||||||
selectedDepictions.remove(depictedItem);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (depictedItem.isSelected()) {
|
|
||||||
if (media.getDepictionIds().contains(depictedItem.getId())) {
|
|
||||||
selectedExistingDepictions.add(depictedItem.getId());
|
|
||||||
} else {
|
|
||||||
selectedDepictions.add(depictedItem);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (media.getDepictionIds().contains(depictedItem.getId())) {
|
|
||||||
selectedExistingDepictions.remove(depictedItem.getId());
|
|
||||||
if (!media.getDepictionIds().contains(depictedItem.getId())) {
|
|
||||||
final List<String> depictsList = new ArrayList<>();
|
|
||||||
depictsList.add(depictedItem.getId());
|
|
||||||
depictsList.addAll(media.getDepictionIds());
|
|
||||||
media.setDepictionIds(depictsList);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
selectedDepictions.remove(depictedItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private <T> List<T> newListOf(final List<T> items) {
|
|
||||||
return items != null ? new ArrayList<>(items) : new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void useSimilarPictureCoordinates(final ImageCoordinates imageCoordinates, final int uploadItemIndex) {
|
|
||||||
fileProcessor.prePopulateCategoriesAndDepictionsBy(imageCoordinates);
|
|
||||||
items.get(uploadItemIndex).setGpsCoords(imageCoordinates);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<DepictedItem> getSelectedDepictions() {
|
|
||||||
return selectedDepictions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides selected existing depicts
|
|
||||||
*
|
|
||||||
* @return selected existing depicts
|
|
||||||
*/
|
|
||||||
public List<String> getSelectedExistingDepictions() {
|
|
||||||
return selectedExistingDepictions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize existing depicts
|
|
||||||
*
|
|
||||||
* @param selectedExistingDepictions existing depicts
|
|
||||||
*/
|
|
||||||
public void setSelectedExistingDepictions(final List<String> selectedExistingDepictions) {
|
|
||||||
this.selectedExistingDepictions = selectedExistingDepictions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
242
app/src/main/java/fr/free/nrw/commons/upload/UploadModel.kt
Normal file
242
app/src/main/java/fr/free/nrw/commons/upload/UploadModel.kt
Normal file
|
|
@ -0,0 +1,242 @@
|
||||||
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import fr.free.nrw.commons.Media
|
||||||
|
import fr.free.nrw.commons.auth.SessionManager
|
||||||
|
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.upload.FileUtils.getSHA1
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.Single
|
||||||
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.Date
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Named
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class UploadModel @Inject internal constructor(
|
||||||
|
@param:Named("licenses") val licenses: List<String>,
|
||||||
|
@param:Named("default_preferences") val store: JsonKvStore,
|
||||||
|
@param:Named("licenses_by_name") val licensesByName: Map<String, String>,
|
||||||
|
val context: Context,
|
||||||
|
val sessionManager: SessionManager,
|
||||||
|
val fileProcessor: FileProcessor,
|
||||||
|
val imageProcessingService: ImageProcessingService
|
||||||
|
) {
|
||||||
|
var license: String? = store.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3)
|
||||||
|
val items: MutableList<UploadItem> = mutableListOf()
|
||||||
|
val compositeDisposable: CompositeDisposable = CompositeDisposable()
|
||||||
|
val selectedCategories: MutableList<String> = mutableListOf()
|
||||||
|
val selectedDepictions: MutableList<DepictedItem> = mutableListOf()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Existing depicts which are selected
|
||||||
|
*/
|
||||||
|
var selectedExistingDepictions: MutableList<String> = mutableListOf()
|
||||||
|
val count: Int
|
||||||
|
get() = items.size
|
||||||
|
|
||||||
|
val uploads: List<UploadItem>
|
||||||
|
get() = items
|
||||||
|
|
||||||
|
var selectedLicense: String?
|
||||||
|
get() = license
|
||||||
|
set(licenseName) {
|
||||||
|
license = licensesByName[licenseName]
|
||||||
|
if (license == null) {
|
||||||
|
store.remove(Prefs.DEFAULT_LICENSE)
|
||||||
|
} else {
|
||||||
|
store.putString(Prefs.DEFAULT_LICENSE, license!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cleanup the resources, I am Singleton, preparing for fresh upload
|
||||||
|
*/
|
||||||
|
fun cleanUp() {
|
||||||
|
compositeDisposable.clear()
|
||||||
|
fileProcessor.cleanup()
|
||||||
|
items.clear()
|
||||||
|
selectedCategories.clear()
|
||||||
|
selectedDepictions.clear()
|
||||||
|
selectedExistingDepictions.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSelectedCategories(categories: List<String>) {
|
||||||
|
selectedCategories.clear()
|
||||||
|
selectedCategories.addAll(categories)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pre process a one item at a time
|
||||||
|
*/
|
||||||
|
fun preProcessImage(
|
||||||
|
uploadableFile: UploadableFile?,
|
||||||
|
place: Place?,
|
||||||
|
similarImageInterface: SimilarImageInterface?,
|
||||||
|
inAppPictureLocation: LatLng?
|
||||||
|
): Observable<UploadItem> = Observable.just(
|
||||||
|
createAndAddUploadItem(uploadableFile, place, similarImageInterface, inAppPictureLocation)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls validateImage() of ImageProcessingService to check quality of image
|
||||||
|
*
|
||||||
|
* @param uploadItem UploadItem whose quality is to be checked
|
||||||
|
* @param inAppPictureLocation In app picture location (if any)
|
||||||
|
* @return Quality of UploadItem
|
||||||
|
*/
|
||||||
|
fun getImageQuality(uploadItem: UploadItem, inAppPictureLocation: LatLng?): Single<Int> =
|
||||||
|
imageProcessingService.validateImage(uploadItem, inAppPictureLocation)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls checkDuplicateImage() of ImageProcessingService to check if image is duplicate
|
||||||
|
*
|
||||||
|
* @param filePath file to be checked
|
||||||
|
* @return IMAGE_DUPLICATE or IMAGE_OK
|
||||||
|
*/
|
||||||
|
fun checkDuplicateImage(filePath: String?): Single<Int> =
|
||||||
|
imageProcessingService.checkDuplicateImage(filePath)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls validateCaption() of ImageProcessingService to check caption of image
|
||||||
|
*
|
||||||
|
* @param uploadItem UploadItem whose caption is to be checked
|
||||||
|
* @return Quality of caption of the UploadItem
|
||||||
|
*/
|
||||||
|
fun getCaptionQuality(uploadItem: UploadItem): Single<Int> =
|
||||||
|
imageProcessingService.validateCaption(uploadItem)
|
||||||
|
|
||||||
|
private fun createAndAddUploadItem(
|
||||||
|
uploadableFile: UploadableFile?,
|
||||||
|
place: Place?,
|
||||||
|
similarImageInterface: SimilarImageInterface?,
|
||||||
|
inAppPictureLocation: LatLng?
|
||||||
|
): UploadItem {
|
||||||
|
val dateTimeWithSource = uploadableFile?.getFileCreatedDate(context)
|
||||||
|
var fileCreatedDate: Long = -1
|
||||||
|
var createdTimestampSource = ""
|
||||||
|
var fileCreatedDateString: String? = ""
|
||||||
|
if (dateTimeWithSource != null) {
|
||||||
|
fileCreatedDate = dateTimeWithSource.epochDate
|
||||||
|
fileCreatedDateString = dateTimeWithSource.dateString
|
||||||
|
createdTimestampSource = dateTimeWithSource.source
|
||||||
|
}
|
||||||
|
Timber.d("File created date is %d", fileCreatedDate)
|
||||||
|
val imageCoordinates = fileProcessor
|
||||||
|
.processFileCoordinates(
|
||||||
|
similarImageInterface, uploadableFile?.getFilePath(),
|
||||||
|
inAppPictureLocation
|
||||||
|
)
|
||||||
|
val uploadItem = UploadItem(
|
||||||
|
Uri.parse(uploadableFile?.getFilePath()),
|
||||||
|
uploadableFile?.getMimeType(context), imageCoordinates, place, fileCreatedDate,
|
||||||
|
createdTimestampSource,
|
||||||
|
uploadableFile?.contentUri,
|
||||||
|
fileCreatedDateString
|
||||||
|
)
|
||||||
|
|
||||||
|
// If an uploadItem of the same uploadableFile has been created before, we return that.
|
||||||
|
// This is to avoid multiple instances of uploadItem of same file passed around.
|
||||||
|
if (items.contains(uploadItem)) {
|
||||||
|
return items[items.indexOf(uploadItem)]
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadItem.uploadMediaDetails[0] = UploadMediaDetail(place)
|
||||||
|
if (!items.contains(uploadItem)) {
|
||||||
|
items.add(uploadItem)
|
||||||
|
}
|
||||||
|
return uploadItem
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildContributions(): Observable<Contribution> {
|
||||||
|
return Observable.fromIterable(items).map { item: UploadItem ->
|
||||||
|
val imageSHA1 = getSHA1(
|
||||||
|
context.contentResolver.openInputStream(item.contentUri!!)!!
|
||||||
|
)
|
||||||
|
val contribution = Contribution(
|
||||||
|
item,
|
||||||
|
sessionManager,
|
||||||
|
buildList { addAll(selectedDepictions) },
|
||||||
|
buildList { addAll(selectedCategories) },
|
||||||
|
imageSHA1
|
||||||
|
)
|
||||||
|
|
||||||
|
contribution.setHasInvalidLocation(item.hasInvalidLocation())
|
||||||
|
|
||||||
|
Timber.d(
|
||||||
|
"Created timestamp while building contribution is %s, %s",
|
||||||
|
item.createdTimestamp,
|
||||||
|
Date(item.createdTimestamp)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (item.createdTimestamp != -1L) {
|
||||||
|
contribution.dateCreated = Date(item.createdTimestamp)
|
||||||
|
contribution.dateCreatedSource = item.createdTimestampSource
|
||||||
|
//Set the date only if you have it, else the upload service is gonna try it the other way
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contribution.wikidataPlace != null) {
|
||||||
|
contribution.wikidataPlace!!.isMonumentUpload = item.isWLMUpload
|
||||||
|
}
|
||||||
|
contribution.countryCode = item.countryCode
|
||||||
|
contribution
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deletePicture(filePath: String) {
|
||||||
|
val iterator = items.iterator()
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
if (iterator.next().mediaUri.toString().contains(filePath)) {
|
||||||
|
iterator.remove()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (items.isEmpty()) {
|
||||||
|
cleanUp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDepictItemClicked(depictedItem: DepictedItem, media: Media?) {
|
||||||
|
if (media == null) {
|
||||||
|
if (depictedItem.isSelected) {
|
||||||
|
selectedDepictions.add(depictedItem)
|
||||||
|
} else {
|
||||||
|
selectedDepictions.remove(depictedItem)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (depictedItem.isSelected) {
|
||||||
|
if (media.depictionIds.contains(depictedItem.id)) {
|
||||||
|
selectedExistingDepictions.add(depictedItem.id)
|
||||||
|
} else {
|
||||||
|
selectedDepictions.add(depictedItem)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (media.depictionIds.contains(depictedItem.id)) {
|
||||||
|
selectedExistingDepictions.remove(depictedItem.id)
|
||||||
|
if (!media.depictionIds.contains(depictedItem.id)) {
|
||||||
|
media.depictionIds = mutableListOf<String>().apply {
|
||||||
|
add(depictedItem.id)
|
||||||
|
addAll(media.depictionIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedDepictions.remove(depictedItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun useSimilarPictureCoordinates(imageCoordinates: ImageCoordinates, uploadItemIndex: Int) {
|
||||||
|
fileProcessor.prePopulateCategoriesAndDepictionsBy(imageCoordinates)
|
||||||
|
items[uploadItemIndex].gpsCoords = imageCoordinates
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -140,7 +140,7 @@ class CategoriesPresenter
|
||||||
*/
|
*/
|
||||||
private fun getImageTitleList(): List<String> =
|
private fun getImageTitleList(): List<String> =
|
||||||
repository.getUploads()
|
repository.getUploads()
|
||||||
.map { it.uploadMediaDetails[0].captionText }
|
.map { it.uploadMediaDetails[0].captionText!! }
|
||||||
.filterNot { TextUtils.isEmpty(it) }
|
.filterNot { TextUtils.isEmpty(it) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,6 @@ class UploadModelUnitTest {
|
||||||
@Ignore
|
@Ignore
|
||||||
@Test
|
@Test
|
||||||
fun testSetSelectedExistingDepictions() {
|
fun testSetSelectedExistingDepictions() {
|
||||||
uploadModel.selectedExistingDepictions = listOf("")
|
uploadModel.selectedExistingDepictions = mutableListOf("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package fr.free.nrw.commons.upload
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
|
import com.nhaarman.mockitokotlin2.any
|
||||||
import com.nhaarman.mockitokotlin2.mock
|
import com.nhaarman.mockitokotlin2.mock
|
||||||
import com.nhaarman.mockitokotlin2.verify
|
import com.nhaarman.mockitokotlin2.verify
|
||||||
|
import com.nhaarman.mockitokotlin2.whenever
|
||||||
import fr.free.nrw.commons.Media
|
import fr.free.nrw.commons.Media
|
||||||
import fr.free.nrw.commons.category.CategoriesModel
|
import fr.free.nrw.commons.category.CategoriesModel
|
||||||
import fr.free.nrw.commons.category.CategoryItem
|
import fr.free.nrw.commons.category.CategoryItem
|
||||||
|
|
@ -17,6 +19,7 @@ import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
import io.reactivex.Completable
|
import io.reactivex.Completable
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
import org.junit.Assert.assertSame
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
|
@ -118,7 +121,9 @@ class UploadRepositoryUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGetUploads() {
|
fun testGetUploads() {
|
||||||
assertEquals(repository.getUploads(), uploadModel.uploads)
|
val result = listOf(uploadItem)
|
||||||
|
whenever(uploadModel.uploads).thenReturn(result)
|
||||||
|
assertSame(result, repository.getUploads())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -136,10 +141,10 @@ class UploadRepositoryUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSearchAll() {
|
fun testSearchAll() {
|
||||||
assertEquals(
|
val empty = Observable.empty<List<CategoryItem>>()
|
||||||
repository.searchAll("", listOf(), listOf()),
|
whenever(categoriesModel.searchAll(any(), any(), any())).thenReturn(empty)
|
||||||
categoriesModel.searchAll("", listOf(), listOf()),
|
assertSame(empty, repository.searchAll("", listOf(), listOf()))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -164,7 +169,9 @@ class UploadRepositoryUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGetLicenses() {
|
fun testGetLicenses() {
|
||||||
assertEquals(repository.getLicenses(), uploadModel.licenses)
|
whenever(uploadModel.licenses).thenReturn(listOf())
|
||||||
|
repository.getLicenses()
|
||||||
|
verify(uploadModel).licenses
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -208,10 +215,10 @@ class UploadRepositoryUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGetUploadItemCaseNonNull() {
|
fun testGetUploadItemCaseNonNull() {
|
||||||
`when`(uploadModel.items).thenReturn(listOf(uploadItem))
|
`when`(uploadModel.items).thenReturn(mutableListOf(uploadItem))
|
||||||
assertEquals(
|
assertEquals(
|
||||||
repository.getUploadItem(0),
|
repository.getUploadItem(0),
|
||||||
uploadModel.items[0],
|
uploadItem,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -220,19 +227,6 @@ class UploadRepositoryUnitTest {
|
||||||
assertEquals(repository.getUploadItem(-1), null)
|
assertEquals(repository.getUploadItem(-1), null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSetSelectedLicense() {
|
|
||||||
assertEquals(repository.setSelectedLicense(""), uploadModel.setSelectedLicense(""))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSetSelectedExistingDepictions() {
|
|
||||||
assertEquals(
|
|
||||||
repository.setSelectedExistingDepictions(listOf("")),
|
|
||||||
uploadModel.setSelectedExistingDepictions(listOf("")),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testOnDepictItemClicked() {
|
fun testOnDepictItemClicked() {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
|
@ -243,12 +237,14 @@ class UploadRepositoryUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGetSelectedDepictions() {
|
fun testGetSelectedDepictions() {
|
||||||
assertEquals(repository.getSelectedDepictions(), uploadModel.selectedDepictions)
|
repository.getSelectedDepictions()
|
||||||
|
verify(uploadModel).selectedDepictions
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGetSelectedExistingDepictions() {
|
fun testGetSelectedExistingDepictions() {
|
||||||
assertEquals(repository.getSelectedExistingDepictions(), uploadModel.selectedExistingDepictions)
|
repository.getSelectedExistingDepictions()
|
||||||
|
verify(uploadModel).selectedExistingDepictions
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -324,8 +320,8 @@ class UploadRepositoryUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testIsWMLSupportedForThisPlace() {
|
fun testIsWMLSupportedForThisPlace() {
|
||||||
`when`(uploadModel.items).thenReturn(listOf(uploadItem))
|
whenever(uploadModel.items).thenReturn(mutableListOf(uploadItem))
|
||||||
`when`(uploadItem.isWLMUpload).thenReturn(true)
|
whenever(uploadItem.isWLMUpload).thenReturn(true)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
repository.isWMLSupportedForThisPlace(),
|
repository.isWMLSupportedForThisPlace(),
|
||||||
true,
|
true,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue