From 628a6056e071f8c29464a1d219ebb5e12136b0dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=C3=A1n=20Mac=20Gillicuddy?= Date: Thu, 9 Apr 2020 16:38:40 +0100 Subject: [PATCH] #3529 Captions/depictions are not saved to Commons (#3588) * #3529 Captions/depictions are not saved to Commons - update flow to update appropriate data * #3529 Captions/depictions are not saved to Commons - fix invoking of setlabel * #3529 Captions/depictions are not saved to Commons - fix unit tests * #3529 Captions/depictions are not saved to Commons - use constant for @Named * #3529 Captions/depictions are not saved to Commons - remove captions interface * #3529 Captions/depictions are not saved to Commons - delete unused Contribution fields - enforce Single Responsibility by using PageContentsCreator * #3529 Captions/depictions are not saved to Commons - prefix id with M - remove language from url and only add from Field * #3529 Captions/depictions are not saved to Commons - make edits of depictions and captions sequential * #3529 Captions/depictions are not saved to Commons - remove unused model fields * #3529 Captions/depictions are not saved to Commons - weaken type of categories - copy list on Contribution creation * #3529 Captions/depictions are not saved to Commons - mark Media fields private - weaken types - remove partly implemented fields * #3529 Captions/depictions are not saved to Commons - add semi colon * #3529 Captions/depictions are not saved to Commons - fix test --- .../main/java/fr/free/nrw/commons/Media.java | 254 +++-------- .../free/nrw/commons/MediaDataExtractor.java | 4 +- .../commons/contributions/Contribution.java | 385 +++++------------ .../contributions/ContributionController.java | 38 +- .../contributions/ContributionDao.java | 8 +- .../contributions/ContributionViewHolder.java | 4 +- .../contributions/ContributionsFragment.java | 25 +- .../ContributionsListAdapter.java | 2 +- .../contributions/ContributionsPresenter.java | 39 +- .../fr/free/nrw/commons/db/Converters.java | 62 ++- .../free/nrw/commons/delete/DeleteHelper.java | 26 +- .../SubClass/SubDepictionListPresenter.java | 2 +- .../WikidataItemDetailsActivity.java | 4 +- .../free/nrw/commons/di/NetworkingModule.java | 7 - .../explore/depictions/DepictsClient.java | 4 +- .../SearchDepictionsFragmentPresenter.java | 2 +- .../depictions/SearchDepictionsRenderer.java | 2 +- .../commons/media/MediaDetailFragment.java | 52 +-- .../repository/UploadRemoteDataSource.java | 17 +- .../commons/repository/UploadRepository.java | 14 +- .../commons/upload/PageContentsCreator.java | 109 +++++ .../nrw/commons/upload/UploadActivity.java | 39 +- .../free/nrw/commons/upload/UploadClient.java | 29 +- .../nrw/commons/upload/UploadController.java | 8 +- .../commons/upload/UploadDepictsRenderer.java | 4 +- .../nrw/commons/upload/UploadMediaDetail.kt | 10 +- .../free/nrw/commons/upload/UploadModel.java | 91 ++-- .../free/nrw/commons/upload/UploadResult.kt | 14 +- .../nrw/commons/upload/UploadService.java | 105 ++--- .../nrw/commons/upload/WikiBaseInterface.java | 24 +- .../free/nrw/commons/upload/WikidataItem.kt | 6 + .../free/nrw/commons/upload/WikidataPlace.kt | 21 + .../upload/depicts/DepictsPresenter.java | 1 - .../upload/mediaDetails/CaptionInterface.java | 31 -- .../UploadMediaDetailFragment.java | 6 +- .../UploadMediaDetailsContract.java | 11 +- .../mediaDetails/UploadMediaPresenter.java | 8 +- .../structure/depictions/DepictModel.kt | 21 +- .../structure/depictions/DepictedItem.kt | 11 +- .../depictions/DepictionRenderer.java | 2 +- .../nrw/commons/wikidata/WikiBaseClient.java | 73 ++-- .../nrw/commons/wikidata/WikidataClient.java | 10 +- .../commons/wikidata/WikidataEditService.java | 403 +++++++----------- .../commons/wikidata/WikidataProperties.java | 18 + .../wikidata/model/DepictSearchItem.kt | 19 +- .../nrw/commons/delete/DeleteHelperTest.kt | 102 ++--- .../upload/UploadMediaPresenterTest.kt | 3 +- .../commons/wikidata/WikidataClientTest.kt | 32 +- .../wikidata/WikidataEditServiceTest.kt | 34 +- 49 files changed, 925 insertions(+), 1271 deletions(-) create mode 100644 app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.java create mode 100644 app/src/main/java/fr/free/nrw/commons/upload/WikidataItem.kt create mode 100644 app/src/main/java/fr/free/nrw/commons/upload/WikidataPlace.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/CaptionInterface.java create mode 100644 app/src/main/java/fr/free/nrw/commons/wikidata/WikidataProperties.java diff --git a/app/src/main/java/fr/free/nrw/commons/Media.java b/app/src/main/java/fr/free/nrw/commons/Media.java index f28dcdb6b..3ca86ed27 100644 --- a/app/src/main/java/fr/free/nrw/commons/Media.java +++ b/app/src/main/java/fr/free/nrw/commons/Media.java @@ -11,9 +11,7 @@ import fr.free.nrw.commons.utils.CommonsDateUtil; import fr.free.nrw.commons.utils.MediaDataExtractorUtil; import java.text.ParseException; import java.util.ArrayList; -import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -30,62 +28,42 @@ public class Media implements Parcelable { // Primary metadata fields @Nullable - public Uri localUri; - public String thumbUrl; - public String imageUrl; - public String filename; - public String thumbnailTitle; - /** + private Uri localUri; + private String thumbUrl; + private String imageUrl; + private String filename; + private String thumbnailTitle; + /* * Captions are a feature part of Structured data. They are meant to store short, multilingual descriptions about files * This is a replacement of the previously used titles for images (titles were not multilingual) * Also now captions replace the previous convention of using title for filename */ private String caption; - public String description; // monolingual description on input... - public String discussion; - long dataLength; - public Date dateCreated; - @Nullable public Date dateUploaded; - public int width; - public int height; - public String license; - public String licenseUrl; - public String creator; + private String description; // monolingual description on input... + private String discussion; + private long dataLength; + private Date dateCreated; + @Nullable private Date dateUploaded; + private String license; + private String licenseUrl; + private String creator; /** * Wikibase Identifier associated with media files */ - public String pageId; - public ArrayList categories; // as loaded at runtime? + private String pageId; + private List categories; // as loaded at runtime? /** * Depicts is a feature part of Structured data. Multiple Depictions can be added for an image just like categories. * However unlike categories depictions is multi-lingual */ - public ArrayList> depictionList; - /** - * The above hashmap is fetched from API and to diplay in Explore - * However this list of depictions is for storing and retrieving depictions from local storage or cache - */ - public ArrayList depictions; - public boolean requestedDeletion; - public HashMap descriptions; // multilingual descriptions as loaded - /** - * This hasmap stores the list of multilingual captions, where - * key of the HashMap is the language and value is the caption in the corresponding language - * Ex: key = "en", value: "" - * key = "de" , value: "" - */ - public Map captions; - public HashMap tags = new HashMap<>(); - @Nullable public LatLng coordinates; + private List> depictionList= new ArrayList<>(); + private boolean requestedDeletion; + @Nullable private LatLng coordinates; /** * Provides local constructor */ - protected Media() { - this.categories = new ArrayList<>(); - this.depictions = new ArrayList<>(); - this.descriptions = new HashMap<>(); - this.captions = new HashMap<>(); + public Media() { } /** @@ -94,7 +72,6 @@ public class Media implements Parcelable { * @param filename Media filename */ public Media(String filename) { - this(); this.filename = filename; } @@ -103,29 +80,35 @@ public class Media implements Parcelable { * @param localUri Media URI * @param imageUrl Media image URL * @param filename Media filename - * @param captions Media captions * @param description Media description * @param dataLength Media date length * @param dateCreated Media creation date * @param dateUploaded Media date uploaded * @param creator Media creator */ - public Media(Uri localUri, String imageUrl, String filename, Map captions, String description, - long dataLength, Date dateCreated, Date dateUploaded, String creator) { - this(); + public Media(Uri localUri, String imageUrl, String filename, + String description, + long dataLength, Date dateCreated, Date dateUploaded, String creator) { this.localUri = localUri; this.thumbUrl = imageUrl; this.imageUrl = imageUrl; this.filename = filename; - this.captions = captions; this.description = description; this.dataLength = dataLength; this.dateCreated = dateCreated; this.dateUploaded = dateUploaded; this.creator = creator; - this.categories = new ArrayList<>(); - this.depictions = new ArrayList<>(); - this.descriptions = new HashMap<>(); + } + + public Media(Uri localUri, String filename, + String description, String creator, List categories) { + this(localUri,null, filename, + description, -1, null, new Date(), creator); + this.categories = categories; + } + + public Media(String title, Date date, String user) { + this(null, null, title, "", -1, date, date, user); } /** @@ -145,7 +128,7 @@ public class Media implements Parcelable { ExtMetadata metadata = imageInfo.getMetadata(); if (metadata == null) { Media media = new Media(null, imageInfo.getOriginalUrl(), - page.title(), new HashMap<>() , "", 0, null, null, null); + page.title(), "", 0, null, null, null); if (!StringUtils.isBlank(imageInfo.getThumbUrl())) { media.setThumbUrl(imageInfo.getThumbUrl()); } @@ -155,8 +138,7 @@ public class Media implements Parcelable { Media media = new Media(null, imageInfo.getOriginalUrl(), page.title(), - new HashMap<>(), - "", + "", 0, safeParseDate(metadata.dateTime()), safeParseDate(metadata.dateTime()), @@ -174,7 +156,7 @@ public class Media implements Parcelable { language = "default"; } - media.setDescriptions(Collections.singletonMap(language, metadata.imageDescription())); + media.setDescription(metadata.imageDescription()); media.setCategories(MediaDataExtractorUtil.extractCategoriesFromList(metadata.getCategories())); String latitude = metadata.getGpsLatitude(); String longitude = metadata.getGpsLongitude(); @@ -212,31 +194,14 @@ public class Media implements Parcelable { /** *sets pageId for the current media object */ - private void setPageId(String pageId) { + public void setPageId(String pageId) { this.pageId = pageId; } + public String getThumbUrl() { return thumbUrl; } - /** - * Gets tag of media - * @param key Media key - * @return Media tag - */ - public Object getTag(String key) { - return tags.get(key); - } - - /** - * Modifies( or creates a) tag of media - * @param key Media key - * @param value Media value - */ - public void setTag(String key, String value) { - tags.put(key, value); - } - /** * Gets media display title * @return Media title @@ -340,22 +305,11 @@ public class Media implements Parcelable { /** * @return depictions associated with the current media */ - public ArrayList> getDepiction() { + public List> getDepiction() { return depictionList; } - /** - * Captions are a feature part of Structured data. They are meant to store short, multilingual descriptions about files - * This is a replacement of the previously used titles for images (titles were not multilingual) - * Also now captions replace the previous convention of using title for filename - * - * key of the HashMap is the language and value is the caption in the corresponding language - * - * returns list of captions stored in hashmap - */ - public Map getCaptions() { - return captions; - } + /** * Sets the file description. @@ -423,38 +377,6 @@ public class Media implements Parcelable { this.creator = creator; } - /** - * Gets the width of the media. - * @return file width as an int - */ - public int getWidth() { - return width; - } - - /** - * Sets the width of the media. - * @param width file width as an int - */ - public void setWidth(int width) { - this.width = width; - } - - /** - * Gets the height of the media. - * @return file height as an int - */ - public int getHeight() { - return height; - } - - /** - * Sets the height of the media. - * @param height file height as an int - */ - public void setHeight(int height) { - this.height = height; - } - /** * Gets the license name of the file. * @return license as a String @@ -506,15 +428,8 @@ public class Media implements Parcelable { * @return file categories as an ArrayList of Strings */ @SuppressWarnings("unchecked") - public ArrayList getCategories() { - return (ArrayList) categories.clone(); // feels dirty - } - - /** - * @return array list of depictions associated with the current media - */ - public ArrayList getDepictions() { - return (ArrayList) depictions.clone(); + public List getCategories() { + return categories; } /** @@ -525,43 +440,7 @@ public class Media implements Parcelable { * @param categories file categories as a list of Strings */ public void setCategories(List categories) { - this.categories.clear(); - this.categories.addAll(categories); - } - - public void setDepictions(List depictions) { - this.depictions.clear(); - this.depictions.addAll(depictions); - } - - /** - * Modifies (or sets) media descriptions - * @param descriptions Media descriptions - */ - void setDescriptions(Map descriptions) { - this.descriptions.clear(); - this.descriptions.putAll(descriptions); - } - - /** - * Gets media description in preferred language - * @param preferredLanguage Language preferred - * @return Description in preferred language - */ - public String getDescription(String preferredLanguage) { - if (descriptions.containsKey(preferredLanguage)) { - // See if the requested language is there. - return descriptions.get(preferredLanguage); - } else if (descriptions.containsKey("en")) { - // Ah, English. Language of the world, until the Chinese crush us. - return descriptions.get("en"); - } else if (descriptions.containsKey("default")) { - // No languages marked... - return descriptions.get("default"); - } else { - // FIXME: return the first available non-English description? - return ""; - } + this.categories = categories; } @Nullable private static Date safeParseDate(String dateStr) { @@ -572,20 +451,19 @@ public class Media implements Parcelable { } } - - /** * Set requested deletion to true + * @param requestedDeletion */ - public void setRequestedDeletion(){ - requestedDeletion = true; + public void setRequestedDeletion(boolean requestedDeletion){ + this.requestedDeletion = requestedDeletion; } /** * Get the value of requested deletion * @return boolean requestedDeletion */ - public boolean getRequestedDeletion(){ + public boolean isRequestedDeletion(){ return requestedDeletion; } @@ -610,15 +488,29 @@ public class Media implements Parcelable { this.caption = caption; } - public void setCaptions(HashMap captions) { - this.captions = captions; + /* Sets depictions for the current media obtained fro Wikibase API*/ + public void setDepictionList(List> depictions) { + this.depictionList = depictions; } - /** - * Sets depictions for the current media obtained fro Wikibase API - */ - public void setDepiction(ArrayList> depictions) { - this.depictionList = depictions; + public void setLocalUri(@Nullable final Uri localUri) { + this.localUri = localUri; + } + + public void setImageUrl(final String imageUrl) { + this.imageUrl = imageUrl; + } + + public void setDateUploaded(@Nullable final Date dateUploaded) { + this.dateUploaded = dateUploaded; + } + + public void setLicenseUrl(final String licenseUrl) { + this.licenseUrl = licenseUrl; + } + + public List> getDepictionList() { + return depictionList; } @Override @@ -645,8 +537,6 @@ public class Media implements Parcelable { dest.writeLong(this.dataLength); dest.writeLong(this.dateCreated != null ? this.dateCreated.getTime() : -1); dest.writeLong(this.dateUploaded != null ? this.dateUploaded.getTime() : -1); - dest.writeInt(this.width); - dest.writeInt(this.height); dest.writeString(this.license); dest.writeString(this.licenseUrl); dest.writeString(this.creator); @@ -654,8 +544,6 @@ public class Media implements Parcelable { dest.writeStringList(this.categories); dest.writeList(this.depictionList); dest.writeByte(this.requestedDeletion ? (byte) 1 : (byte) 0); - dest.writeSerializable(this.descriptions); - dest.writeSerializable(this.tags); dest.writeParcelable(this.coordinates, flags); } @@ -673,8 +561,6 @@ public class Media implements Parcelable { this.dateCreated = tmpDateCreated == -1 ? null : new Date(tmpDateCreated); long tmpDateUploaded = in.readLong(); this.dateUploaded = tmpDateUploaded == -1 ? null : new Date(tmpDateUploaded); - this.width = in.readInt(); - this.height = in.readInt(); this.license = in.readString(); this.licenseUrl = in.readString(); this.creator = in.readString(); @@ -684,8 +570,6 @@ public class Media implements Parcelable { this.categories=list; in.readList(depictionList,null); this.requestedDeletion = in.readByte() != 0; - this.descriptions = (HashMap) in.readSerializable(); - this.tags = (HashMap) in.readSerializable(); this.coordinates = in.readParcelable(LatLng.class.getClassLoader()); } diff --git a/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java b/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java index 96d8c30da..11db9a66e 100644 --- a/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java +++ b/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java @@ -55,9 +55,9 @@ public class MediaDataExtractor { final String caption, final JsonObject depiction) { media.setDiscussion(discussion); media.setCaption(caption); - media.setDepiction(formatDepictions(depiction)); + media.setDepictionList(formatDepictions(depiction)); if (deletionStatus) { - media.setRequestedDeletion(); + media.setRequestedDeletion(true); } return media; } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java index f97898414..80c378137 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java @@ -1,34 +1,22 @@ package fr.free.nrw.commons.contributions; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.content.Context; -import android.net.Uri; import android.os.Parcel; -import androidx.annotation.NonNull; -import androidx.annotation.StringDef; import androidx.room.Entity; import androidx.room.PrimaryKey; -import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.Media; -import fr.free.nrw.commons.filepicker.UploadableFile; -import fr.free.nrw.commons.settings.Prefs; -import fr.free.nrw.commons.utils.ConfigUtils; -import java.lang.annotation.Retention; +import fr.free.nrw.commons.auth.SessionManager; +import fr.free.nrw.commons.upload.UploadMediaDetail; +import fr.free.nrw.commons.upload.UploadModel.UploadItem; +import fr.free.nrw.commons.upload.WikidataPlace; +import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; import java.util.ArrayList; -import java.util.Date; -import java.util.Locale; +import java.util.HashMap; +import java.util.List; import java.util.Map; -import org.apache.commons.lang3.StringUtils; +import org.wikipedia.dataclient.mwapi.MwQueryLogEvent; @Entity(tableName = "contribution") -public class Contribution extends Media { - - //{{According to Exif data|2009-01-09}} - private static final String TEMPLATE_DATE_ACC_TO_EXIF = "{{According to Exif data|%s}}"; - - //2009-01-09 → 9 January 2009 - private static final String TEMPLATE_DATA_OTHER_SOURCE = "%s"; +public class Contribution extends Media { // No need to be bitwise - they're mutually exclusive public static final int STATE_COMPLETED = -1; @@ -36,256 +24,133 @@ public class Contribution extends Media { public static final int STATE_QUEUED = 2; public static final int STATE_IN_PROGRESS = 3; - @Retention(SOURCE) - @StringDef({SOURCE_CAMERA, SOURCE_GALLERY, SOURCE_EXTERNAL}) - public @interface FileSource {} - - public static final String SOURCE_CAMERA = "camera"; - public static final String SOURCE_GALLERY = "gallery"; - public static final String SOURCE_EXTERNAL = "external"; @PrimaryKey (autoGenerate = true) - @NonNull - public long _id; - public Uri contentUri; - public String source; - public String editSummary; - public int state; - public long transferred; - public String decimalCoords; - public boolean isMultiple; - public String wikiDataEntityId; - public String wikiItemName; - private String p18Value; - public Uri contentProviderUri; - public String dateCreatedSource; - + private long _id; + private int state; + private long transferred; + private String decimalCoords; + private String dateCreatedSource; + private WikidataPlace wikidataPlace; /** * Each depiction loaded in depictions activity is associated with a wikidata entity id, * this Id is in turn used to upload depictions to wikibase */ - public ArrayList depictionsEntityIds; + private List depictedItems = new ArrayList<>(); + private String mimeType; + /** + * This hasmap stores the list of multilingual captions, where + * key of the HashMap is the language and value is the caption in the corresponding language + * Ex: key = "en", value: "" + * key = "de" , value: "" + */ + private Map captions = new HashMap<>(); - public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date dateCreated, - int state, long dataLength, Date dateUploaded, long transferred, - String source, Map captions, String description, String creator, boolean isMultiple, - int width, int height, String license) { - super(localUri, imageUrl, filename, captions, description, dataLength, dateCreated, dateUploaded, creator); - this.contentUri = contentUri; - this.state = state; - this.transferred = transferred; - this.source = source; - this.isMultiple = isMultiple; - this.width = width; - this.height = height; - this.license = license; - this.dateCreatedSource = ""; + public Contribution() { } - public Contribution(Uri localUri, String imageUrl, String filename, Map captions, String description, long dataLength, - Date dateCreated, Date dateUploaded, String creator, String editSummary, ArrayList depictionsEntityIds, String decimalCoords) { - super(localUri, imageUrl, filename, captions, description, dataLength, dateCreated, dateUploaded, creator); - this.decimalCoords = decimalCoords; - this.editSummary = editSummary; - this.dateCreatedSource = ""; - this.depictionsEntityIds = depictionsEntityIds; + public Contribution(final UploadItem item, final SessionManager sessionManager, + final List depictedItems, final List categories) { + super(item.getMediaUri(), + item.getFileName(), + UploadMediaDetail.formatList(item.getUploadMediaDetails()), + sessionManager.getAuthorName(), + categories); + captions = UploadMediaDetail.formatCaptions(item.getUploadMediaDetails()); + decimalCoords = item.getGpsCoords().getDecimalCoords(); + dateCreatedSource = ""; + this.depictedItems = depictedItems; + wikidataPlace = WikidataPlace.from(item.getPlace()); } - public Contribution(Uri localUri, String imageUrl, String filename, Map captions, String description, long dataLength, - Date dateCreated, Date dateUploaded, String creator, String editSummary, String decimalCoords, int state) { - super(localUri, imageUrl, filename, captions, description, dataLength, dateCreated, dateUploaded, creator); - this.decimalCoords = decimalCoords; - this.editSummary = editSummary; - this.dateCreatedSource = ""; - this.state=state; + public Contribution(final MwQueryLogEvent queryLogEvent, final String user) { + super(queryLogEvent.title(),queryLogEvent.date(), user); + decimalCoords = ""; + dateCreatedSource = ""; + state = STATE_COMPLETED; } - - - public void setDateCreatedSource(String dateCreatedSource) { + public void setDateCreatedSource(final String dateCreatedSource) { this.dateCreatedSource = dateCreatedSource; } - public boolean getMultiple() { - return isMultiple; - } - - public void setMultiple(boolean multiple) { - isMultiple = multiple; + public String getDateCreatedSource() { + return dateCreatedSource; } public long getTransferred() { return transferred; } - public void setTransferred(long transferred) { + public void setTransferred(final long transferred) { this.transferred = transferred; } - public String getEditSummary() { - return editSummary != null ? editSummary : CommonsApplication.DEFAULT_EDIT_SUMMARY; - } - - public Uri getContentUri() { - return contentUri; - } - - public void setContentUri(Uri contentUri) { - this.contentUri = contentUri; - } - public int getState() { return state; } - public void setState(int state) { + public void setState(final int state) { this.state = state; } - public void setDateUploaded(Date date) { - this.dateUploaded = date; - } - - /** - * sets depiction entity ids for the given contribution - */ - public void setDepictions(ArrayList depictionsEntityIds) { - this.depictionsEntityIds = depictionsEntityIds; - } - - public String getPageContents(Context applicationContext) { - StringBuilder buffer = new StringBuilder(); - buffer - .append("== {{int:filedesc}} ==\n") - .append("{{Information\n") - .append("|description=").append(getDescription()).append("\n") - .append("|source=").append("{{own}}\n") - .append("|author=[[User:").append(creator).append("|").append(creator).append("]]\n"); - - String templatizedCreatedDate = getTemplatizedCreatedDate(); - if (!StringUtils.isBlank(templatizedCreatedDate)) { - buffer.append("|date=").append(templatizedCreatedDate); - } - - buffer.append("}}").append("\n"); - - //Only add Location template (e.g. {{Location|37.51136|-77.602615}} ) if coords is not null - if (decimalCoords != null) { - buffer.append("{{Location|").append(decimalCoords).append("}}").append("\n"); - } - - buffer.append("== {{int:license-header}} ==\n") - .append(licenseTemplateFor(getLicense())).append("\n\n") - .append("{{Uploaded from Mobile|platform=Android|version=") - .append(ConfigUtils.getVersionNameWithSha(applicationContext)).append("}}\n"); - if(categories!=null&&categories.size()!=0) { - for (int i = 0; i < categories.size(); i++) { - String category = categories.get(i); - buffer.append("\n[[Category:").append(category).append("]]"); - } - } - else - buffer.append("{{subst:unc}}"); - return buffer.toString(); - } - - /** - * Returns upload date in either TEMPLATE_DATE_ACC_TO_EXIF or TEMPLATE_DATA_OTHER_SOURCE - * @return - */ - private String getTemplatizedCreatedDate() { - if (dateCreated != null) { - java.text.SimpleDateFormat dateFormat = new java.text.SimpleDateFormat("yyyy-MM-dd"); - if (UploadableFile.DateTimeWithSource.EXIF_SOURCE.equals(dateCreatedSource)) { - return String.format(Locale.ENGLISH, TEMPLATE_DATE_ACC_TO_EXIF, dateFormat.format(dateCreated)) + "\n"; - } else { - return String.format(Locale.ENGLISH, TEMPLATE_DATA_OTHER_SOURCE, dateFormat.format(dateCreated)) + "\n"; - } - } - return ""; - } - - @Override - public void setFilename(String filename) { - this.filename = filename; - } - - public void setImageUrl(String imageUrl) { - this.imageUrl = imageUrl; - } - - public Contribution() { - - } - - public String getSource() { - return source; - } - - public void setSource(String source) { - this.source = source; - } - - @NonNull - private String licenseTemplateFor(String license) { - switch (license) { - case Prefs.Licenses.CC_BY_3: - return "{{self|cc-by-3.0}}"; - case Prefs.Licenses.CC_BY_4: - return "{{self|cc-by-4.0}}"; - case Prefs.Licenses.CC_BY_SA_3: - return "{{self|cc-by-sa-3.0}}"; - case Prefs.Licenses.CC_BY_SA_4: - return "{{self|cc-by-sa-4.0}}"; - case Prefs.Licenses.CC0: - return "{{self|cc-zero}}"; - } - - throw new RuntimeException("Unrecognized license value: " + license); - } - - public String getWikiDataEntityId() { - return wikiDataEntityId; - } - - public String getWikiItemName() { - return wikiItemName; - } - - /** - * When the corresponding wikidata entity is known as in case of nearby uploads, it can be set - * using the setter method - * @param wikiDataEntityId wikiDataEntityId - */ - public void setWikiDataEntityId(String wikiDataEntityId) { - this.wikiDataEntityId = wikiDataEntityId; - } - - public void setWikiItemName(String wikiItemName) { - this.wikiItemName = wikiItemName; - } - - public String getP18Value() { - return p18Value; - } - - /** - * When the corresponding image property of wiki entity is known as in case of nearby uploads, - * it can be set using the setter method - * @param p18Value p18 value, image property of the wikidata item - */ - public void setP18Value(String p18Value) { - this.p18Value = p18Value; - } - - public void setContentProviderUri(Uri contentProviderUri) { - this.contentProviderUri = contentProviderUri; - } - /** * @return array list of entityids for the depictions */ - public ArrayList getDepictionsEntityIds() { - return depictionsEntityIds; + public List getDepictedItems() { + return depictedItems; + } + + public void setWikidataPlace(final WikidataPlace wikidataPlace) { + this.wikidataPlace = wikidataPlace; + } + + public WikidataPlace getWikidataPlace() { + return wikidataPlace; + } + + public long get_id() { + return _id; + } + + public void set_id(final long _id) { + this._id = _id; + } + + public String getDecimalCoords() { + return decimalCoords; + } + + public void setDecimalCoords(final String decimalCoords) { + this.decimalCoords = decimalCoords; + } + + public void setDepictedItems(final List depictedItems) { + this.depictedItems = depictedItems; + } + + public void setMimeType(String mimeType) { + this.mimeType = mimeType; + } + + public String getMimeType() { + return mimeType; + } + + /** + * Captions are a feature part of Structured data. They are meant to store short, multilingual descriptions about files + * This is a replacement of the previously used titles for images (titles were not multilingual) + * Also now captions replace the previous convention of using title for filename + * + * key of the HashMap is the language and value is the caption in the corresponding language + * + * returns list of captions stored in hashmap + */ + public Map getCaptions() { + return captions; + } + + public void setCaptions(Map captions) { + this.captions = captions; } @Override @@ -294,48 +159,34 @@ public class Contribution extends Media { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(final Parcel dest, final int flags) { super.writeToParcel(dest, flags); - dest.writeLong(this._id); - dest.writeParcelable(this.contentUri, flags); - dest.writeString(this.source); - dest.writeString(this.editSummary); - dest.writeInt(this.state); - dest.writeLong(this.transferred); - dest.writeString(this.decimalCoords); - dest.writeByte(this.isMultiple ? (byte) 1 : (byte) 0); - dest.writeString(this.wikiDataEntityId); - dest.writeString(this.wikiItemName); - dest.writeString(this.p18Value); - dest.writeParcelable(this.contentProviderUri, flags); - dest.writeString(this.dateCreatedSource); + dest.writeLong(_id); + dest.writeInt(state); + dest.writeLong(transferred); + dest.writeString(decimalCoords); + dest.writeString(dateCreatedSource); + dest.writeSerializable((HashMap) captions); } - protected Contribution(Parcel in) { + protected Contribution(final Parcel in) { super(in); - this._id = in.readLong(); - this.contentUri = in.readParcelable(Uri.class.getClassLoader()); - this.source = in.readString(); - this.editSummary = in.readString(); - this.state = in.readInt(); - this.transferred = in.readLong(); - this.decimalCoords = in.readString(); - this.isMultiple = in.readByte() != 0; - this.wikiDataEntityId = in.readString(); - this.wikiItemName = in.readString(); - this.p18Value = in.readString(); - this.contentProviderUri = in.readParcelable(Uri.class.getClassLoader()); - this.dateCreatedSource = in.readString(); + _id = in.readLong(); + state = in.readInt(); + transferred = in.readLong(); + decimalCoords = in.readString(); + dateCreatedSource = in.readString(); + captions = (HashMap) in.readSerializable(); } public static final Creator CREATOR = new Creator() { @Override - public Contribution createFromParcel(Parcel source) { + public Contribution createFromParcel(final Parcel source) { return new Contribution(source); } @Override - public Contribution[] newArray(int size) { + public Contribution[] newArray(final int size) { return new Contribution[size]; } }; diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java index eb4d5711b..9e1de9736 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java @@ -1,19 +1,13 @@ package fr.free.nrw.commons.contributions; +import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES; +import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT; + import android.Manifest; import android.app.Activity; import android.content.Context; import android.content.Intent; - import androidx.annotation.NonNull; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - import fr.free.nrw.commons.R; import fr.free.nrw.commons.filepicker.DefaultCallback; import fr.free.nrw.commons.filepicker.FilePicker; @@ -23,12 +17,11 @@ import fr.free.nrw.commons.nearby.Place; import fr.free.nrw.commons.upload.UploadActivity; import fr.free.nrw.commons.utils.PermissionUtils; import fr.free.nrw.commons.utils.ViewUtil; - -import static fr.free.nrw.commons.contributions.Contribution.SOURCE_CAMERA; -import static fr.free.nrw.commons.contributions.Contribution.SOURCE_GALLERY; -import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES; -import static fr.free.nrw.commons.upload.UploadService.EXTRA_SOURCE; -import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT; +import java.util.ArrayList; +import java.util.List; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; @Singleton public class ContributionController { @@ -109,7 +102,7 @@ public class ContributionController { @Override public void onImagesPicked(@NonNull List imagesFiles, FilePicker.ImageSource source, int type) { - Intent intent = handleImagesPicked(activity, imagesFiles, getSourceFromImageSource(source)); + Intent intent = handleImagesPicked(activity, imagesFiles); activity.startActivity(intent); } }); @@ -125,11 +118,9 @@ public class ContributionController { * Attaches place object for nearby uploads */ private Intent handleImagesPicked(Context context, - List imagesFiles, - String source) { + List imagesFiles) { Intent shareIntent = new Intent(context, UploadActivity.class); shareIntent.setAction(ACTION_INTERNAL_UPLOADS); - shareIntent.putExtra(EXTRA_SOURCE, source); shareIntent.putParcelableArrayListExtra(EXTRA_FILES, new ArrayList<>(imagesFiles)); Place place = defaultKvStore.getJson(PLACE_OBJECT, Place.class); if (place != null) { @@ -139,13 +130,4 @@ public class ContributionController { return shareIntent; } - /** - * Get image upload source - */ - private String getSourceFromImageSource(FilePicker.ImageSource source) { - if (source.equals(FilePicker.ImageSource.CAMERA_IMAGE)) { - return SOURCE_CAMERA; - } - return SOURCE_GALLERY; - } } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java index 261784b52..3c0ac925f 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java @@ -7,13 +7,10 @@ import androidx.room.Insert; import androidx.room.OnConflictStrategy; import androidx.room.Query; import androidx.room.Transaction; - import androidx.room.Update; -import io.reactivex.disposables.Disposable; -import java.util.List; - import io.reactivex.Completable; import io.reactivex.Single; +import java.util.List; @Dao public abstract class ContributionDao { @@ -40,9 +37,6 @@ public abstract class ContributionDao { @Delete public abstract Single delete(Contribution contribution); - @Query("SELECT * from contribution WHERE contentProviderUri=:uri") - public abstract List getContributionWithUri(String uri); - @Query("SELECT * from contribution WHERE filename=:fileName") public abstract List getContributionWithTitle(String fileName); diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java index c05213378..17182d323 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java @@ -2,8 +2,6 @@ package fr.free.nrw.commons.contributions; import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.text.TextUtils; import android.view.View; @@ -60,7 +58,7 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder { this.contribution = contribution; fetchAndDisplayCaption(contribution); this.position = position; - String imageSource = chooseImageSource(contribution.thumbUrl, contribution.getLocalUri()); + String imageSource = chooseImageSource(contribution.getThumbUrl(), contribution.getLocalUri()); if (!TextUtils.isEmpty(imageSource)) { final ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource)) diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java index c3da13a7d..64b836c39 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java @@ -1,5 +1,9 @@ package fr.free.nrw.commons.contributions; +import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED; +import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION; +import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween; + import android.Manifest; import android.content.ComponentName; import android.content.Context; @@ -12,21 +16,12 @@ import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager.OnBackStackChangedListener; import androidx.fragment.app.FragmentTransaction; - -import fr.free.nrw.commons.MediaDataExtractor; -import io.reactivex.disposables.Disposable; -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Named; - import butterknife.BindView; import butterknife.ButterKnife; import fr.free.nrw.commons.HandlerService; @@ -43,7 +38,6 @@ import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LocationServiceManager; import fr.free.nrw.commons.location.LocationUpdateListener; -import fr.free.nrw.commons.media.MediaClient; import fr.free.nrw.commons.media.MediaDetailPagerFragment; import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider; import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; @@ -61,14 +55,11 @@ import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.schedulers.Schedulers; -import java.util.ArrayList; - +import java.util.List; +import javax.inject.Inject; +import javax.inject.Named; import timber.log.Timber; -import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED; -import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION; -import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween; - public class ContributionsFragment extends CommonsDaggerSupportFragment implements @@ -224,7 +215,7 @@ public class ContributionsFragment @Override public void fetchMediaUriFor(Contribution contribution) { - Timber.d("Fetching thumbnail for %s", contribution.filename); + Timber.d("Fetching thumbnail for %s", contribution.getFilename()); contributionsPresenter.fetchMediaDetails(contribution); } }); diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java index a26b86205..82daf97f4 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java @@ -62,7 +62,7 @@ public class ContributionsListAdapter extends RecyclerView.Adapter Timber.d("Received image %s", mwQueryLogEvent.title())) .filter(mwQueryLogEvent -> !mwQueryLogEvent.isDeleted()).doOnNext(mwQueryLogEvent -> Timber.d("Image %s passed filters", mwQueryLogEvent.title())) - .map(image -> { - Contribution contribution = new Contribution(null, null, image.title(), - new HashMap<>(), "", -1, image.date(), image.date(), user, - "", "", STATE_COMPLETED); - return contribution; - }) + .map(image -> new Contribution(image, user)) .toList() .subscribe(this::saveContributionsToDB, error -> { Timber.e("Failed to fetch contributions: %s", error.getMessage()); @@ -198,11 +177,11 @@ public class ContributionsPresenter implements UserActionListener { @Override public void fetchMediaDetails(Contribution contribution) { compositeDisposable.add(mediaDataExtractor - .getMediaFromFileName(contribution.filename) + .getMediaFromFileName(contribution.getFilename()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(media -> { - contribution.thumbUrl=media.thumbUrl; + contribution.setThumbUrl(media.getThumbUrl()); updateContribution(contribution); })); } diff --git a/app/src/main/java/fr/free/nrw/commons/db/Converters.java b/app/src/main/java/fr/free/nrw/commons/db/Converters.java index 7a824922c..20b41b794 100644 --- a/app/src/main/java/fr/free/nrw/commons/db/Converters.java +++ b/app/src/main/java/fr/free/nrw/commons/db/Converters.java @@ -7,9 +7,10 @@ import com.google.gson.reflect.TypeToken; import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.di.ApplicationlessInjection; import fr.free.nrw.commons.location.LatLng; -import java.util.ArrayList; +import fr.free.nrw.commons.upload.WikidataPlace; +import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; import java.util.Date; -import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -42,53 +43,74 @@ public class Converters { } @TypeConverter - public static String listObjectToString(ArrayList objectList) { - return objectList == null ? null : getGson().toJson(objectList); + public static String listObjectToString(List objectList) { + return writeObjectToString(objectList); } @TypeConverter - public static ArrayList stringToArrayListObject(String objectList) { - return objectList == null ? null : getGson().fromJson(objectList,new TypeToken>(){}.getType()); + public static List stringToListObject(String objectList) { + return readObjectWithTypeToken(objectList, new TypeToken>() {}); } @TypeConverter - public static String mapObjectToString(HashMap objectList) { - return objectList == null ? null : getGson().toJson(objectList); + public static String mapObjectToString(Map objectList) { + return writeObjectToString(objectList); } @TypeConverter - public static HashMap stringToHashMap(String objectList) { - return objectList == null ? null : getGson().fromJson(objectList,new TypeToken>(){}.getType()); + public static Map stringToMap(String objectList) { + return readObjectWithTypeToken(objectList, new TypeToken>(){}); } @TypeConverter public static String latlngObjectToString(LatLng latlng) { - return latlng == null ? null : getGson().toJson(latlng); + return writeObjectToString(latlng); } @TypeConverter public static LatLng stringToLatLng(String objectList) { - return objectList == null ? null : getGson().fromJson(objectList,LatLng.class); + return readObjectFromString(objectList,LatLng.class); } @TypeConverter - public static String listOfMapToString(ArrayList> listOfMaps) { - return listOfMaps == null ? null : getGson().toJson(listOfMaps); + public static String listOfMapToString(List> listOfMaps) { + return writeObjectToString(listOfMaps); } @TypeConverter - public static ArrayList> stringToListOfMap(String listOfMaps) { - return listOfMaps == null ? null :getGson().fromJson(listOfMaps,new TypeToken>>(){}.getType()); + public static List> stringToListOfMap(String listOfMaps) { + return readObjectWithTypeToken(listOfMaps, new TypeToken>>() {}); } @TypeConverter - public static String mapToString(Map map) { - return map == null ? null : getGson().toJson(map); + public static String wikidataPlaceToString(WikidataPlace wikidataPlace) { + return writeObjectToString(wikidataPlace); } @TypeConverter - public static Map stringToMap(String map) { - return map == null ? null :getGson().fromJson(map,new TypeToken>(){}.getType()); + public static WikidataPlace stringToWikidataPlace(String wikidataPlace) { + return readObjectFromString(wikidataPlace, WikidataPlace.class); } + @TypeConverter + public static String depictionListToString(List depictedItems) { + return writeObjectToString(depictedItems); + } + + @TypeConverter + public static List stringToList(String depictedItems) { + return readObjectWithTypeToken(depictedItems, new TypeToken>() {}); + } + + private static String writeObjectToString(Object object) { + return object == null ? null : getGson().toJson(object); + } + + private static T readObjectFromString(String objectAsString, Class clazz) { + return objectAsString == null ? null : getGson().fromJson(objectAsString, clazz); + } + + private static T readObjectWithTypeToken(String objectList, TypeToken typeToken) { + return objectList == null ? null : getGson().fromJson(objectList, typeToken.getType()); + } } diff --git a/app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.java b/app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.java index 5253f0dbd..3eac4908e 100644 --- a/app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.java +++ b/app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.java @@ -1,22 +1,12 @@ package fr.free.nrw.commons.delete; +import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_DELETE; + import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.net.Uri; - import androidx.appcompat.app.AlertDialog; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Locale; -import java.util.concurrent.Callable; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.Media; import fr.free.nrw.commons.R; @@ -29,10 +19,16 @@ import io.reactivex.Single; import io.reactivex.SingleSource; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Locale; +import java.util.concurrent.Callable; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; import timber.log.Timber; -import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_DELETE; - /** * Refactored async task to Rx */ @@ -104,7 +100,7 @@ public class DeleteHelper { } String creatorName = creator.replace(" (page does not exist)", ""); - return pageEditClient.prependEdit(media.filename, fileDeleteString + "\n", summary) + return pageEditClient.prependEdit(media.getFilename(), fileDeleteString + "\n", summary) .flatMap(result -> { if (result) { return pageEditClient.edit("Commons:Deletion_requests/" + media.getFilename(), subpageString + "\n", summary); diff --git a/app/src/main/java/fr/free/nrw/commons/depictions/SubClass/SubDepictionListPresenter.java b/app/src/main/java/fr/free/nrw/commons/depictions/SubClass/SubDepictionListPresenter.java index 9781a7f46..a3b75151e 100644 --- a/app/src/main/java/fr/free/nrw/commons/depictions/SubClass/SubDepictionListPresenter.java +++ b/app/src/main/java/fr/free/nrw/commons/depictions/SubClass/SubDepictionListPresenter.java @@ -141,7 +141,7 @@ public class SubDepictionListPresenter implements SubDepictionListContract.UserA this.queryList.addAll(mediaList); view.onSuccess(mediaList); for (DepictedItem m : mediaList) { - fetchThumbnailForEntityId(m.getEntityId(), size++); + fetchThumbnailForEntityId(m.getId(), size++); } } } diff --git a/app/src/main/java/fr/free/nrw/commons/depictions/WikidataItemDetailsActivity.java b/app/src/main/java/fr/free/nrw/commons/depictions/WikidataItemDetailsActivity.java index e9b858763..86a428406 100644 --- a/app/src/main/java/fr/free/nrw/commons/depictions/WikidataItemDetailsActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/depictions/WikidataItemDetailsActivity.java @@ -197,8 +197,8 @@ public class WikidataItemDetailsActivity extends NavigationBaseActivity implemen public static void startYourself(Context context, DepictedItem depictedItem) { Intent intent = new Intent(context, WikidataItemDetailsActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP); - intent.putExtra("wikidataItemName", depictedItem.getDepictsLabel()); - intent.putExtra("entityId", depictedItem.getEntityId()); + intent.putExtra("wikidataItemName", depictedItem.getName()); + intent.putExtra("entityId", depictedItem.getId()); context.startActivity(intent); } } diff --git a/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java b/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java index d6e6109c6..09e74442d 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java +++ b/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java @@ -18,7 +18,6 @@ import fr.free.nrw.commons.review.ReviewInterface; import fr.free.nrw.commons.upload.UploadInterface; import fr.free.nrw.commons.upload.WikiBaseInterface; import fr.free.nrw.commons.upload.depicts.DepictsInterface; -import fr.free.nrw.commons.upload.mediaDetails.CaptionInterface; import fr.free.nrw.commons.wikidata.WikidataInterface; import java.io.File; import java.util.concurrent.TimeUnit; @@ -163,12 +162,6 @@ public class NetworkingModule { return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, ReviewInterface.class); } - @Provides - @Singleton - public CaptionInterface provideCaptionInterface(@Named(NAMED_WIKI_DATA_WIKI_SITE) WikiSite wikidataWikiSite) { - return ServiceFactory.get(wikidataWikiSite, BuildConfig.WIKIDATA_URL, CaptionInterface.class); - } - @Provides @Singleton public DepictsInterface provideDepictsInterface(@Named(NAMED_WIKI_DATA_WIKI_SITE) WikiSite wikidataWikiSite) { diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/DepictsClient.java b/app/src/main/java/fr/free/nrw/commons/explore/depictions/DepictsClient.java index e4d7f86c7..3090069cc 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/DepictsClient.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/DepictsClient.java @@ -12,7 +12,6 @@ import java.security.NoSuchAlgorithmException; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; -import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -113,8 +112,7 @@ public class DepictsClient { Media media = new Media(null, getUrl(s.getTitle()), s.getTitle(), - new HashMap<>(), - "", + "", 0, safeParseDate(s.getTimestamp()), safeParseDate(s.getTimestamp()), diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragmentPresenter.java b/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragmentPresenter.java index 805e3f44f..84bdf1129 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragmentPresenter.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragmentPresenter.java @@ -158,7 +158,7 @@ public class SearchDepictionsFragmentPresenter extends CommonsDaggerSupportFragm view.onSuccess(mediaList); offset=queryList.size(); for (DepictedItem m : mediaList) { - fetchThumbnailForEntityId(m.getEntityId(), size++); + fetchThumbnailForEntityId(m.getId(), size++); } } } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsRenderer.java b/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsRenderer.java index 7b07aa7b8..581858ef9 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsRenderer.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsRenderer.java @@ -78,7 +78,7 @@ public class SearchDepictionsRenderer extends Renderer { @Override public void render() { DepictedItem item = getContent(); - tvDepictionLabel.setText(item.getDepictsLabel()); + tvDepictionLabel.setText(item.getName()); tvDepictionDesc.setText(item.getDescription()); imageView.setImageDrawable(getContext().getResources().getDrawable(R.drawable.ic_wikidata_logo_24dp)); diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java index 58caa3144..b9e5fa134 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java @@ -1,10 +1,13 @@ package fr.free.nrw.commons.media; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + import android.annotation.SuppressLint; -import android.graphics.drawable.Animatable; import android.app.AlertDialog; -import android.content.Intent; import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Animatable; import android.net.Uri; import android.os.Bundle; import android.text.Editable; @@ -22,28 +25,17 @@ import android.widget.ScrollView; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; - -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.drawee.interfaces.DraweeController; -import com.facebook.drawee.controller.BaseControllerListener; -import com.facebook.drawee.controller.ControllerListener; -import com.facebook.drawee.view.SimpleDraweeView; -import com.facebook.imagepipeline.image.ImageInfo; -import com.facebook.imagepipeline.request.ImageRequest; - -import org.apache.commons.lang3.StringUtils; -import org.wikipedia.util.DateUtil; - -import java.util.ArrayList; -import java.util.Date; -import java.util.Locale; - -import javax.inject.Inject; - +import androidx.annotation.Nullable; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; -import androidx.annotation.Nullable; +import com.facebook.drawee.backends.pipeline.Fresco; +import com.facebook.drawee.controller.BaseControllerListener; +import com.facebook.drawee.controller.ControllerListener; +import com.facebook.drawee.interfaces.DraweeController; +import com.facebook.drawee.view.SimpleDraweeView; +import com.facebook.imagepipeline.image.ImageInfo; +import com.facebook.imagepipeline.request.ImageRequest; import fr.free.nrw.commons.Media; import fr.free.nrw.commons.MediaDataExtractor; import fr.free.nrw.commons.R; @@ -63,13 +55,15 @@ import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import java.util.ArrayList; +import java.util.Date; +import java.util.Locale; import java.util.Map; - +import javax.inject.Inject; +import org.apache.commons.lang3.StringUtils; +import org.wikipedia.util.DateUtil; import timber.log.Timber; -import static android.view.View.GONE; -import static android.view.View.VISIBLE; - public class MediaDetailFragment extends CommonsDaggerSupportFragment { private boolean editable; @@ -555,8 +549,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { item.setOnClickListener(view -> { DepictedItem depictedItem = new DepictedItem(depictionName, "", "", false, entityId); Intent intent = new Intent(getContext(), WikidataItemDetailsActivity.class); - intent.putExtra("wikidataItemName", depictedItem.getDepictsLabel()); - intent.putExtra("entityId", depictedItem.getEntityId()); + intent.putExtra("wikidataItemName", depictedItem.getName()); + intent.putExtra("entityId", depictedItem.getId()); getContext().startActivity(intent); }); } @@ -609,7 +603,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { private String prettyDescription(Media media) { // @todo use UI language when multilingual descs are available - String desc = media.getDescription(locale.getLanguage()).trim(); + String desc = media.getDescription(); if (desc.equals("")) { return getString(R.string.detail_description_empty); } else { @@ -655,7 +649,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { } private void checkDeletion(Media media){ - if (media.getRequestedDeletion()){ + if (media.isRequestedDeletion()){ delete.setVisibility(GONE); nominatedForDeletion.setVisibility(VISIBLE); } else if (!isCategoryImage) { diff --git a/app/src/main/java/fr/free/nrw/commons/repository/UploadRemoteDataSource.java b/app/src/main/java/fr/free/nrw/commons/repository/UploadRemoteDataSource.java index e4b6247bd..bd9ca57c7 100644 --- a/app/src/main/java/fr/free/nrw/commons/repository/UploadRemoteDataSource.java +++ b/app/src/main/java/fr/free/nrw/commons/repository/UploadRemoteDataSource.java @@ -170,13 +170,12 @@ public class UploadRemoteDataSource { * * @param uploadableFile * @param place - * @param source * @param similarImageInterface * @return */ public Observable preProcessImage(UploadableFile uploadableFile, Place place, - String source, SimilarImageInterface similarImageInterface) { - return uploadModel.preProcessImage(uploadableFile, place, source, similarImageInterface); + SimilarImageInterface similarImageInterface) { + return uploadModel.preProcessImage(uploadableFile, place, similarImageInterface); } /** @@ -213,7 +212,7 @@ public class UploadRemoteDataSource { */ public void onDepictedItemClicked(DepictedItem depictedItem) { - depictModel.onDepictItemClicked(depictedItem); + uploadModel.onDepictItemClicked(depictedItem); } /** @@ -222,7 +221,7 @@ public class UploadRemoteDataSource { */ public List getSelectedDepictions() { - return depictModel.getSelectedDepictions(); + return uploadModel.getSelectedDepictions(); } /** @@ -233,14 +232,6 @@ public class UploadRemoteDataSource { return depictModel.searchAllEntities(query); } - public void setSelectedDepictions(List selectedDepictions) { - uploadModel.setSelectedDepictions(selectedDepictions); - } - - public List depictionsEntityIdList() { - return depictModel.depictionsEntityIdList(); - } - public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) { uploadModel.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex); } diff --git a/app/src/main/java/fr/free/nrw/commons/repository/UploadRepository.java b/app/src/main/java/fr/free/nrw/commons/repository/UploadRepository.java index f48aa79bd..bc7a5fbd4 100644 --- a/app/src/main/java/fr/free/nrw/commons/repository/UploadRepository.java +++ b/app/src/main/java/fr/free/nrw/commons/repository/UploadRepository.java @@ -115,10 +115,6 @@ public class UploadRepository { remoteDataSource.setSelectedCategories(categoryStringList); } - public void setSelectedDepictions(List selectedDepictions) { - remoteDataSource.setSelectedDepictions(selectedDepictions); - } - /** * handles the category selection/deselection * @@ -180,14 +176,12 @@ public class UploadRepository { * * @param uploadableFile * @param place - * @param source * @param similarImageInterface * @return */ public Observable preProcessImage(UploadableFile uploadableFile, Place place, - String source, SimilarImageInterface similarImageInterface) { - return remoteDataSource - .preProcessImage(uploadableFile, place, source, similarImageInterface); + SimilarImageInterface similarImageInterface) { + return remoteDataSource.preProcessImage(uploadableFile, place, similarImageInterface); } /** @@ -294,10 +288,6 @@ public class UploadRepository { return remoteDataSource.searchAllEntities(query); } - public List getDepictionsEntityIdList() { - return remoteDataSource.depictionsEntityIdList(); - } - /** * Returns nearest place matching the passed latitude and longitude * @param decLatitude diff --git a/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.java b/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.java new file mode 100644 index 000000000..6079ddb11 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/upload/PageContentsCreator.java @@ -0,0 +1,109 @@ +package fr.free.nrw.commons.upload; + +import android.content.Context; +import androidx.annotation.NonNull; +import fr.free.nrw.commons.contributions.Contribution; +import fr.free.nrw.commons.filepicker.UploadableFile.DateTimeWithSource; +import fr.free.nrw.commons.settings.Prefs.Licenses; +import fr.free.nrw.commons.utils.ConfigUtils; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import javax.inject.Inject; +import org.apache.commons.lang3.StringUtils; + +class PageContentsCreator { + + //{{According to Exif data|2009-01-09}} + private static final String TEMPLATE_DATE_ACC_TO_EXIF = "{{According to Exif data|%s}}"; + + //2009-01-09 → 9 January 2009 + private static final String TEMPLATE_DATA_OTHER_SOURCE = "%s"; + + private final Context context; + + @Inject + public PageContentsCreator(Context context) { + this.context = context; + } + + public String createFrom(Contribution contribution) { + StringBuilder buffer = new StringBuilder(); + buffer + .append("== {{int:filedesc}} ==\n") + .append("{{Information\n") + .append("|description=").append(contribution.getDescription()).append("\n") + .append("|source=").append("{{own}}\n") + .append("|author=[[User:").append(contribution.getCreator()).append("|") + .append(contribution.getCreator()).append("]]\n"); + + String templatizedCreatedDate = getTemplatizedCreatedDate( + contribution.getDateCreated(), contribution.getDateCreatedSource()); + if (!StringUtils.isBlank(templatizedCreatedDate)) { + buffer.append("|date=").append(templatizedCreatedDate); + } + + buffer.append("}}").append("\n"); + + //Only add Location template (e.g. {{Location|37.51136|-77.602615}} ) if coords is not null + final String decimalCoords = contribution.getDecimalCoords(); + if (decimalCoords != null) { + buffer.append("{{Location|").append(decimalCoords).append("}}").append("\n"); + } + + buffer.append("== {{int:license-header}} ==\n") + .append(licenseTemplateFor(contribution.getLicense())).append("\n\n") + .append("{{Uploaded from Mobile|platform=Android|version=") + .append(ConfigUtils.getVersionNameWithSha(context)).append("}}\n"); + final List categories = contribution.getCategories(); + if (categories != null && categories.size() != 0) { + for (int i = 0; i < categories.size(); i++) { + buffer.append("\n[[Category:").append(categories.get(i)).append("]]"); + } + } else { + buffer.append("{{subst:unc}}"); + } + return buffer.toString(); + } + + /** + * Returns upload date in either TEMPLATE_DATE_ACC_TO_EXIF or TEMPLATE_DATA_OTHER_SOURCE + * + * @param dateCreated + * @param dateCreatedSource + * @return + */ + private String getTemplatizedCreatedDate(Date dateCreated, String dateCreatedSource) { + if (dateCreated != null) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + return String.format(Locale.ENGLISH, + isExif(dateCreatedSource) ? TEMPLATE_DATE_ACC_TO_EXIF : TEMPLATE_DATA_OTHER_SOURCE, + dateFormat.format(dateCreated) + ) + "\n"; + } + return ""; + } + + private boolean isExif(String dateCreatedSource) { + return DateTimeWithSource.EXIF_SOURCE.equals(dateCreatedSource); + } + + @NonNull + private String licenseTemplateFor(String license) { + switch (license) { + case Licenses.CC_BY_3: + return "{{self|cc-by-3.0}}"; + case Licenses.CC_BY_4: + return "{{self|cc-by-4.0}}"; + case Licenses.CC_BY_SA_3: + return "{{self|cc-by-sa-3.0}}"; + case Licenses.CC_BY_SA_4: + return "{{self|cc-by-sa-4.0}}"; + case Licenses.CC0: + return "{{self|cc-zero}}"; + } + + throw new RuntimeException("Unrecognized license value: " + license); + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java index 5c85346d4..6114b76f9 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java @@ -1,5 +1,9 @@ package fr.free.nrw.commons.upload; +import static fr.free.nrw.commons.contributions.ContributionController.ACTION_INTERNAL_UPLOADS; +import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES; +import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT; + import android.Manifest; import android.annotation.SuppressLint; import android.app.ProgressDialog; @@ -10,7 +14,6 @@ import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; - import androidx.appcompat.app.AlertDialog; import androidx.cardview.widget.CardView; import androidx.fragment.app.Fragment; @@ -20,14 +23,6 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Named; - import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; @@ -36,7 +31,6 @@ import fr.free.nrw.commons.R; import fr.free.nrw.commons.auth.LoginActivity; import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.category.CategoriesModel; -import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.ContributionController; import fr.free.nrw.commons.filepicker.UploadableFile; import fr.free.nrw.commons.kvstore.JsonKvStore; @@ -48,18 +42,18 @@ import fr.free.nrw.commons.upload.depicts.DepictsFragment; import fr.free.nrw.commons.upload.license.MediaLicenseFragment; import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment; import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.UploadMediaDetailFragmentCallback; -import fr.free.nrw.commons.upload.structure.depictions.DepictModel; import fr.free.nrw.commons.utils.PermissionUtils; import fr.free.nrw.commons.utils.ViewUtil; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.schedulers.Schedulers; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.inject.Inject; +import javax.inject.Named; import timber.log.Timber; -import static fr.free.nrw.commons.contributions.ContributionController.ACTION_INTERNAL_UPLOADS; -import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES; -import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT; - public class UploadActivity extends BaseActivity implements UploadContract.View, UploadBaseFragment.Callback { @Inject ContributionController contributionController; @@ -108,8 +102,6 @@ public class UploadActivity extends BaseActivity implements UploadContract.View, private MediaLicenseFragment mediaLicenseFragment; private ThumbnailsAdapter thumbnailsAdapter; - - private String source; private Place place; private List uploadableFiles = Collections.emptyList(); private int currentSelectedPosition = 0; @@ -325,7 +317,7 @@ public class UploadActivity extends BaseActivity implements UploadContract.View, fragments = new ArrayList<>(); for (UploadableFile uploadableFile : uploadableFiles) { UploadMediaDetailFragment uploadMediaDetailFragment = new UploadMediaDetailFragment(); - uploadMediaDetailFragment.setImageTobeUploaded(uploadableFile, source, place); + uploadMediaDetailFragment.setImageTobeUploaded(uploadableFile, place); uploadMediaDetailFragment.setCallback(new UploadMediaDetailFragmentCallback() { @Override public void deletePictureAtIndex(int index) { @@ -387,16 +379,7 @@ public class UploadActivity extends BaseActivity implements UploadContract.View, private void receiveInternalSharedItems() { Intent intent = getIntent(); - if (intent.hasExtra(UploadService.EXTRA_SOURCE)) { - source = intent.getStringExtra(UploadService.EXTRA_SOURCE); - } else { - source = Contribution.SOURCE_EXTERNAL; - } - - Timber.d("Received intent %s with action %s and from source %s", - intent.toString(), - intent.getAction(), - source); + Timber.d("Received intent %s with action %s", intent.toString(), intent.getAction()); uploadableFiles = intent.getParcelableArrayListExtra(EXTRA_FILES); Timber.i("Received multiple upload %s", uploadableFiles.size()); diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.java index 936e27305..39c42cc1d 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadClient.java @@ -1,35 +1,36 @@ package fr.free.nrw.commons.upload; +import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF; + import android.content.Context; import android.net.Uri; - -import org.wikipedia.csrf.CsrfTokenClient; - -import java.io.File; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - +import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.upload.UploadService.NotificationUpdateProgressListener; import io.reactivex.Observable; +import java.io.File; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.RequestBody; - -import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF; +import org.wikipedia.csrf.CsrfTokenClient; @Singleton public class UploadClient { private final UploadInterface uploadInterface; private final CsrfTokenClient csrfTokenClient; + private final PageContentsCreator pageContentsCreator; @Inject - public UploadClient(UploadInterface uploadInterface, @Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient) { + public UploadClient(UploadInterface uploadInterface, + @Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient, + PageContentsCreator pageContentsCreator) { this.uploadInterface = uploadInterface; this.csrfTokenClient = csrfTokenClient; + this.pageContentsCreator = pageContentsCreator; } Observable uploadFileToStash(Context context, String filename, File file, @@ -61,8 +62,8 @@ public class UploadClient { try { return uploadInterface .uploadFileFromStash(csrfTokenClient.getTokenBlocking(), - contribution.getPageContents(context), - contribution.getEditSummary(), + pageContentsCreator.createFrom(contribution), + CommonsApplication.DEFAULT_EDIT_SUMMARY, uniqueFileName, fileKey).map(uploadResponse -> uploadResponse.getUpload()); } catch (Throwable throwable) { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadController.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadController.java index 1a5f5ae81..e28978ff8 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadController.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadController.java @@ -115,10 +115,6 @@ public class UploadController { contribution.setDescription(""); } - if (contribution.getCaption() == null) { - contribution.setCaption(""); - } - final String license = store.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3); contribution.setLicense(license); @@ -152,7 +148,7 @@ public class UploadController { if (mimeType != null) { Timber.d("MimeType is: %s", mimeType); - contribution.setTag("mimeType", mimeType); + contribution.setMimeType(mimeType); if(mimeType.startsWith("image/") && contribution.getDateCreated() == null){ contribution.setDateCreated(resolveDateTakenOrNow(contentResolver, contribution)); } @@ -162,7 +158,7 @@ public class UploadController { } private String resolveMimeType(final ContentResolver contentResolver, final Contribution contribution) { - final String mimeType = (String) contribution.getTag("mimeType"); + final String mimeType = contribution.getMimeType(); if (mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) { return contentResolver.getType(contribution.getLocalUri()); } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadDepictsRenderer.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadDepictsRenderer.java index 748a41c46..cb5b8986c 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadDepictsRenderer.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadDepictsRenderer.java @@ -89,13 +89,13 @@ public class UploadDepictsRenderer extends Renderer { public void render() { DepictedItem item = getContent(); checkedView.setChecked(item.isSelected()); - depictsLabel.setText(item.getDepictsLabel()); + depictsLabel.setText(item.getName()); description.setText(item.getDescription()); if (!TextUtils.isEmpty(item.getImageUrl())) { if (!item.getImageUrl().equals(NO_IMAGE_FOR_DEPICTION)) setImageView(Uri.parse(item.getImageUrl()), imageView); }else{ - listener.fetchThumbnailUrlForEntity(item.getEntityId(),item.getPosition()); + listener.fetchThumbnailUrlForEntity(item.getId(),item.getPosition()); } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetail.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetail.kt index 08ad2b841..2617ff903 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetail.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetail.kt @@ -46,7 +46,7 @@ data class UploadMediaDetail constructor( */ @JvmStatic fun formatCaptions(uploadMediaDetails: List) = - uploadMediaDetails.associate { it.languageCode to it.captionText } + uploadMediaDetails.associate { it.languageCode to it.captionText }.filter { it.value.isNotBlank() } /** * Formats the list of descriptions into the format Commons requires for uploads. @@ -56,11 +56,7 @@ data class UploadMediaDetail constructor( */ @JvmStatic fun formatList(descriptions: List) = - descriptions.joinToString { - if (it.descriptionText.isNotEmpty()) - "{{${it.languageCode}|1=${it.descriptionText}}}" - else - "" - } + descriptions.filter { it.descriptionText.isNotEmpty() } + .joinToString { "{{${it.languageCode}|1=${it.descriptionText}}}" } } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java index 6a8271126..34780b80f 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java @@ -4,13 +4,13 @@ import android.annotation.SuppressLint; import android.content.Context; import android.net.Uri; import androidx.annotation.Nullable; -import fr.free.nrw.commons.CommonsApplication; 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.nearby.Place; import fr.free.nrw.commons.settings.Prefs; +import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; import fr.free.nrw.commons.utils.ImageUtils; import io.reactivex.Observable; import io.reactivex.Single; @@ -18,9 +18,7 @@ import io.reactivex.disposables.CompositeDisposable; import io.reactivex.subjects.BehaviorSubject; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -44,8 +42,8 @@ public class UploadModel { private final SessionManager sessionManager; private final FileProcessor fileProcessor; private final ImageProcessingService imageProcessingService; - private List selectedCategories; - private ArrayList selectedDepictions; + private List selectedCategories = new ArrayList<>(); + private List selectedDepictions = new ArrayList<>(); @Inject UploadModel(@Named("licenses") final List licenses, @@ -71,28 +69,23 @@ public class UploadModel { public void cleanUp() { compositeDisposable.clear(); fileProcessor.cleanup(); - this.items.clear(); - if (this.selectedCategories != null) { - this.selectedCategories.clear(); - } - if (this.selectedDepictions != null) { - this.selectedDepictions.clear(); - } + items.clear(); + selectedCategories.clear(); + selectedDepictions.clear(); } public void setSelectedCategories(List selectedCategories) { - this.selectedCategories = newListOf(selectedCategories); + this.selectedCategories = selectedCategories; } /** * pre process a one item at a time */ public Observable preProcessImage(final UploadableFile uploadableFile, - final Place place, - final String source, - final SimilarImageInterface similarImageInterface) { + final Place place, + final SimilarImageInterface similarImageInterface) { return Observable.just( - createAndAddUploadItem(uploadableFile, place, source, similarImageInterface)); + createAndAddUploadItem(uploadableFile, place, similarImageInterface)); } public Single getImageQuality(final UploadItem uploadItem) { @@ -100,9 +93,8 @@ public class UploadModel { } private UploadItem createAndAddUploadItem(final UploadableFile uploadableFile, - final Place place, - final String source, - final SimilarImageInterface similarImageInterface) { + final Place place, + final SimilarImageInterface similarImageInterface) { final UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile .getFileCreatedDate(context); long fileCreatedDate = -1; @@ -116,7 +108,7 @@ public class UploadModel { .processFileCoordinates(similarImageInterface, uploadableFile.getFilePath()); final UploadItem uploadItem = new UploadItem(uploadableFile.getContentUri(), Uri.parse(uploadableFile.getFilePath()), - uploadableFile.getMimeType(context), source, imageCoordinates, place, fileCreatedDate, + uploadableFile.getMimeType(context), imageCoordinates, place, fileCreatedDate, createdTimestampSource); if (place != null) { uploadItem.getUploadMediaDetails().set(0, new UploadMediaDetail(place)); @@ -151,29 +143,8 @@ public class UploadModel { public Observable buildContributions() { return Observable.fromIterable(items).map(item -> { - final Contribution contribution = new Contribution(item.mediaUri, null, - item.getFileName(), item.uploadMediaDetails.size()!=0? UploadMediaDetail.formatCaptions(item.uploadMediaDetails):new HashMap<>(), - UploadMediaDetail.formatList(item.uploadMediaDetails), -1, - null, null, sessionManager.getAuthorName(), - CommonsApplication.DEFAULT_EDIT_SUMMARY, new ArrayList<>(selectedDepictions), item.gpsCoords.getDecimalCoords()); - if (item.place != null) { - contribution.setWikiDataEntityId(item.place.getWikiDataEntityId()); - contribution.setWikiItemName(item.place.getName()); - // If item already has an image, we need to know it. We don't want to override existing image later - contribution.setP18Value(item.place.pic); - } - if (null == selectedCategories) {//Just a fail safe, this should never be null - selectedCategories = new ArrayList<>(); - } - if (selectedDepictions == null) { - selectedDepictions = new ArrayList<>(); - } - contribution.setCategories(selectedCategories); - contribution.setTag("mimeType", item.mimeType); - contribution.setSource(item.source); - contribution.setContentProviderUri(item.mediaUri); - contribution.setDateUploaded(new Date()); - + final Contribution contribution = new Contribution( + item, sessionManager, newListOf(selectedDepictions), newListOf(selectedCategories)); Timber.d("Created timestamp while building contribution is %s, %s", item.getCreatedTimestamp(), new Date(item.getCreatedTimestamp())); @@ -208,12 +179,16 @@ public class UploadModel { uploadItem1.setMediaDetails(uploadItem.uploadMediaDetails); } - public void setSelectedDepictions(final List selectedDepictions) { - this.selectedDepictions = newListOf(selectedDepictions); + public void onDepictItemClicked(DepictedItem depictedItem) { + if (depictedItem.isSelected()) { + selectedDepictions.add(depictedItem); + } else { + selectedDepictions.remove(depictedItem); + } } @NotNull - private ArrayList newListOf(final List items) { + private List newListOf(final List items) { return items != null ? new ArrayList<>(items) : new ArrayList<>(); } @@ -222,13 +197,16 @@ public class UploadModel { items.get(uploadItemIndex).setGpsCoords(imageCoordinates); } + public List getSelectedDepictions() { + return selectedDepictions; + } + @SuppressWarnings("WeakerAccess") public static class UploadItem { private final Uri originalContentUri; private final Uri mediaUri; private final String mimeType; - private final String source; private ImageCoordinates gpsCoords; private List uploadMediaDetails; private final Place place; @@ -237,17 +215,17 @@ public class UploadModel { private final BehaviorSubject imageQuality; @SuppressLint("CheckResult") UploadItem(final Uri originalContentUri, - final Uri mediaUri, final String mimeType, final String source, final ImageCoordinates gpsCoords, - final Place place, - final long createdTimestamp, - final String createdTimestampSource) { + final Uri mediaUri, final String mimeType, + final ImageCoordinates gpsCoords, + final Place place, + final long createdTimestamp, + final String createdTimestampSource) { this.originalContentUri = originalContentUri; this.createdTimestampSource = createdTimestampSource; uploadMediaDetails = new ArrayList<>(Arrays.asList(new UploadMediaDetail())); this.place = place; this.mediaUri = mediaUri; this.mimeType = mimeType; - this.source = source; this.gpsCoords = gpsCoords; this.createdTimestamp = createdTimestamp; imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT); @@ -257,10 +235,6 @@ public class UploadModel { return createdTimestampSource; } - public String getSource() { - return source; - } - public ImageCoordinates getGpsCoords() { return gpsCoords; } @@ -323,5 +297,8 @@ public class UploadModel { this.gpsCoords = gpsCoords; } + public String getMimeType() { + return mimeType; + } } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadResult.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadResult.kt index ab6940193..d3ec0cc64 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadResult.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadResult.kt @@ -2,4 +2,16 @@ package fr.free.nrw.commons.upload import org.wikipedia.gallery.ImageInfo -class UploadResult(val result: String, val filekey: String, val filename: String, val sessionkey: String, val imageinfo: ImageInfo) \ No newline at end of file +private const val RESULT_SUCCESS = "Success" + +data class UploadResult( + val result: String, + val filekey: String, + val filename: String, + val sessionkey: String, + val imageinfo: ImageInfo +) { + fun isSuccessful(): Boolean = result == RESULT_SUCCESS + + fun createCanonicalFileName() = "File:$filename" +} diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java index 9af064086..2dd9b6f1d 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java @@ -7,21 +7,8 @@ import android.content.Intent; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Bundle; - import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; - -import java.io.File; -import java.io.IOException; -import java.text.ParseException; -import java.util.HashSet; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.inject.Inject; -import javax.inject.Named; - import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.HandlerService; @@ -38,6 +25,15 @@ import io.reactivex.Observable; import io.reactivex.Scheduler; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.schedulers.Schedulers; +import java.io.File; +import java.io.IOException; +import java.text.ParseException; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.inject.Inject; +import javax.inject.Named; import timber.log.Timber; public class UploadService extends HandlerService { @@ -47,7 +43,6 @@ public class UploadService extends HandlerService { public static final int ACTION_UPLOAD_FILE = 1; public static final String ACTION_START_SERVICE = EXTRA_PREFIX + ".upload"; - public static final String EXTRA_SOURCE = EXTRA_PREFIX + ".source"; public static final String EXTRA_FILES = EXTRA_PREFIX + ".files"; @Inject WikidataEditService wikidataEditService; @Inject SessionManager sessionManager; @@ -151,7 +146,6 @@ public class UploadService extends HandlerService { @Override public void queue(int what, Contribution contribution) { - Timber.d("Upload service queue has contribution with wiki data entity id as %s", contribution.getWikiDataEntityId()); switch (what) { case ACTION_UPLOAD_FILE: @@ -168,7 +162,7 @@ public class UploadService extends HandlerService { .subscribeOn(ioThreadScheduler) .observeOn(mainThreadScheduler) .subscribe(aLong->{ - contribution._id = aLong; + contribution.set_id(aLong); UploadService.super.queue(what, contribution); }, Throwable::printStackTrace)); break; @@ -251,20 +245,19 @@ public class UploadService extends HandlerService { Timber.d("Stash upload response 1 is %s", uploadStash.toString()); - String resultStatus = uploadStash.getResult(); - if (!resultStatus.equals("Success")) { - Timber.d("Contribution upload failed. Wikidata entity won't be edited"); - showFailedNotification(contribution); - return Observable.never(); - } else { + if (uploadStash.isSuccessful()) { Timber.d("making sure of uniqueness of name: %s", filename); String uniqueFilename = findUniqueFilename(filename); unfinishedUploads.add(uniqueFilename); return uploadClient.uploadFileFromStash( - getApplicationContext(), - contribution, - uniqueFilename, - uploadStash.getFilekey()); + getApplicationContext(), + contribution, + uniqueFilename, + uploadStash.getFilekey()); + } else { + Timber.d("Contribution upload failed. Wikidata entity won't be edited"); + showFailedNotification(contribution); + return Observable.never(); } }) .subscribe( @@ -282,44 +275,38 @@ public class UploadService extends HandlerService { notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS); - String resultStatus = uploadResult.getResult(); - if (!resultStatus.equals("Success")) { + if (uploadResult.isSuccessful()) { + onSuccessfulUpload(contribution, uploadResult); + } else { Timber.d("Contribution upload failed. Wikidata entity won't be edited"); showFailedNotification(contribution); - } else { - String canonicalFilename = "File:" + uploadResult.getFilename(); - final String wikiDataEntityId = contribution.getWikiDataEntityId(); - Timber.d("Contribution upload success. Initiating Wikidata edit for entity id %s", - wikiDataEntityId); - // to perform upload of depictions we pass on depiction entityId of the selected depictions to the wikidataEditService - final String p18Value = contribution.getP18Value(); - final String wikiItemName = contribution.getWikiItemName(); - if (contribution.getDepictionsEntityIds() != null) { - for (String depictionEntityId : contribution.getDepictionsEntityIds()) { - wikidataEditService.createClaimWithLogging(depictionEntityId, - wikiItemName, canonicalFilename, p18Value); - } - } - Timber.d("Contribution upload success. Initiating Wikidata edit for" - + " entity id %s if necessary (if P18 is null). P18 value is %s", - wikiDataEntityId, p18Value); - wikidataEditService.createClaimWithLogging( - wikiDataEntityId, wikiItemName, canonicalFilename,p18Value); - - wikidataEditService.createLabelforWikidataEntity(canonicalFilename, contribution.getCaptions()); - contribution.setFilename(canonicalFilename); - contribution.setImageUrl(uploadResult.getImageinfo().getOriginalUrl()); - contribution.setState(Contribution.STATE_COMPLETED); - contribution.setDateUploaded(CommonsDateUtil.getIso8601DateFormatTimestamp() - .parse(uploadResult.getImageinfo().getTimestamp())); - compositeDisposable.add(contributionDao - .save(contribution) - .subscribeOn(ioThreadScheduler) - .observeOn(mainThreadScheduler) - .subscribe()); } } + private void onSuccessfulUpload(Contribution contribution, UploadResult uploadResult) + throws ParseException { + compositeDisposable + .add(wikidataEditService.addDepictionsAndCaptions(uploadResult, contribution)); + WikidataPlace wikidataPlace = contribution.getWikidataPlace(); + if (wikidataPlace != null && wikidataPlace.getImageValue() == null) { + wikidataEditService.createImageClaim(wikidataPlace, uploadResult); + } + saveCompletedContribution(contribution, uploadResult); + } + + private void saveCompletedContribution(Contribution contribution, UploadResult uploadResult) throws ParseException { + contribution.setFilename(uploadResult.createCanonicalFileName()); + contribution.setImageUrl(uploadResult.getImageinfo().getOriginalUrl()); + contribution.setState(Contribution.STATE_COMPLETED); + contribution.setDateUploaded(CommonsDateUtil.getIso8601DateFormatTimestamp() + .parse(uploadResult.getImageinfo().getTimestamp())); + compositeDisposable.add(contributionDao + .save(contribution) + .subscribeOn(ioThreadScheduler) + .observeOn(mainThreadScheduler) + .subscribe()); + } + @SuppressLint("StringFormatInvalid") @SuppressWarnings("deprecation") private void showFailedNotification(Contribution contribution) { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/WikiBaseInterface.java b/app/src/main/java/fr/free/nrw/commons/upload/WikiBaseInterface.java index 45492749e..79ba57bc7 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/WikiBaseInterface.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/WikiBaseInterface.java @@ -1,11 +1,11 @@ package fr.free.nrw.commons.upload; -import androidx.annotation.NonNull; +import static org.wikipedia.dataclient.Service.MW_API_PREFIX; +import androidx.annotation.NonNull; +import io.reactivex.Observable; import org.wikipedia.dataclient.mwapi.MwPostResponse; import org.wikipedia.dataclient.mwapi.MwQueryResponse; - -import io.reactivex.Observable; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; @@ -13,8 +13,6 @@ import retrofit2.http.Headers; import retrofit2.http.POST; import retrofit2.http.Query; -import static org.wikipedia.dataclient.Service.MW_API_PREFIX; - /** * Retrofit calls for managing responses network calls of entity ids required for uploading depictions */ @@ -31,4 +29,18 @@ public interface WikiBaseInterface { @GET(MW_API_PREFIX + "action=query&prop=info") Observable getFileEntityId(@Query("titles") String fileName); -} \ No newline at end of file + /** + * Upload Captions for the image when upload is successful + * + * @param fileEntityId enityId for the uploaded file + * @param editToken editToken for the file + * @param captionValue value of the caption to be uploaded + */ + @FormUrlEncoded + @POST(MW_API_PREFIX + "action=wbsetlabel") + Observable addLabelstoWikidata(@Field("id") String fileEntityId, + @Field("token") String editToken, + @Field("language") String language, + @Field("value") String captionValue); + +} diff --git a/app/src/main/java/fr/free/nrw/commons/upload/WikidataItem.kt b/app/src/main/java/fr/free/nrw/commons/upload/WikidataItem.kt new file mode 100644 index 000000000..5c8a639df --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/upload/WikidataItem.kt @@ -0,0 +1,6 @@ +package fr.free.nrw.commons.upload + +interface WikidataItem { +val id:String +val name:String +} diff --git a/app/src/main/java/fr/free/nrw/commons/upload/WikidataPlace.kt b/app/src/main/java/fr/free/nrw/commons/upload/WikidataPlace.kt new file mode 100644 index 000000000..4f11d0000 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/upload/WikidataPlace.kt @@ -0,0 +1,21 @@ +package fr.free.nrw.commons.upload + +import android.os.Parcelable +import fr.free.nrw.commons.nearby.Place +import kotlinx.android.parcel.Parcelize + +@Parcelize +internal data class WikidataPlace(override val id: String, override val name: String, val imageValue: String?) : + WikidataItem,Parcelable { + constructor(place: Place) : this( + place.wikiDataEntityId!!, + place.name, + place.pic.takeIf { it.isNotBlank() }) + + companion object { + @JvmStatic + fun from(place: Place?): WikidataPlace? { + return place?.let { WikidataPlace(it) } + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsPresenter.java b/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsPresenter.java index 42d3196c3..3aabeb561 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsPresenter.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsPresenter.java @@ -126,7 +126,6 @@ public class DepictsPresenter implements DepictsContract.UserActionListener { public void verifyDepictions() { List selectedDepictions = repository.getSelectedDepictions(); if (selectedDepictions != null && !selectedDepictions.isEmpty()) { - repository.setSelectedDepictions(repository.getDepictionsEntityIdList()); view.goToNextScreen(); } else { view.noDepictionSelected(); diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/CaptionInterface.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/CaptionInterface.java deleted file mode 100644 index 4bd5c5d48..000000000 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/CaptionInterface.java +++ /dev/null @@ -1,31 +0,0 @@ -package fr.free.nrw.commons.upload.mediaDetails; - - -import org.wikipedia.dataclient.mwapi.MwPostResponse; - -import java.util.Map; - -import io.reactivex.Observable; -import retrofit2.http.Field; -import retrofit2.http.FormUrlEncoded; -import retrofit2.http.POST; - -import static org.wikipedia.dataclient.Service.MW_API_PREFIX; - -public interface CaptionInterface { - - /** - * Upload Captions for the image when upload is successful - * - * @param FileEntityId enityId for the uploaded file - * @param editToken editToken for the file - * @param captionValue value of the caption to be uploaded - * @param caption additional data associated with caption - */ - @FormUrlEncoded - @POST(MW_API_PREFIX + "action=wbsetlabel&language=en") - Observable addLabelstoWikidata(@Field("id") String FileEntityId, - @Field("token") String editToken, - @Field("value") String captionValue, - @Field("data") Map caption); -} diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java index 444f83836..a2c3d99d2 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java @@ -81,7 +81,6 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements JsonKvStore defaultKvStore; private UploadableFile uploadableFile; - private String source; private Place place; private boolean isExpanded = true; @@ -97,9 +96,8 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements super.onCreate(savedInstanceState); } - public void setImageTobeUploaded(UploadableFile uploadableFile, String source, Place place) { + public void setImageTobeUploaded(UploadableFile uploadableFile, Place place) { this.uploadableFile = uploadableFile; - this.source = source; this.place = place; } @@ -122,7 +120,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements callback.getTotalNumberOfSteps())); initRecyclerView(); initPresenter(); - presenter.receiveImage(uploadableFile, source, place); + presenter.receiveImage(uploadableFile, place); if (callback.getIndexInViewFlipper(this) == 0) { btnPrevious.setEnabled(false); diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.java index d8d4bdec0..47a807b7e 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.java @@ -1,15 +1,13 @@ package fr.free.nrw.commons.upload.mediaDetails; -import fr.free.nrw.commons.upload.ImageCoordinates; -import java.util.List; - import fr.free.nrw.commons.BasePresenter; -import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.filepicker.UploadableFile; import fr.free.nrw.commons.nearby.Place; -import fr.free.nrw.commons.upload.UploadMediaDetail; +import fr.free.nrw.commons.upload.ImageCoordinates; import fr.free.nrw.commons.upload.SimilarImageInterface; +import fr.free.nrw.commons.upload.UploadMediaDetail; import fr.free.nrw.commons.upload.UploadModel.UploadItem; +import java.util.List; /** * The contract with with UploadMediaDetails and its presenter would talk to each other @@ -41,8 +39,7 @@ public interface UploadMediaDetailsContract { interface UserActionListener extends BasePresenter { - void receiveImage(UploadableFile uploadableFile, @Contribution.FileSource String source, - Place place); + void receiveImage(UploadableFile uploadableFile, Place place); void verifyImageQuality(UploadItem uploadItem); diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java index d21c2e9c8..59cff4c8a 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java @@ -64,16 +64,14 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt /** * Receives the corresponding uploadable file, processes it and return the view with and uplaod item - * - * @param uploadableFile - * @param source + * @param uploadableFile * @param place */ @Override - public void receiveImage(UploadableFile uploadableFile, String source, Place place) { + public void receiveImage(UploadableFile uploadableFile, Place place) { view.showProgress(true); Disposable uploadItemDisposable = repository - .preProcessImage(uploadableFile, place, source, this) + .preProcessImage(uploadableFile, place, this) .subscribeOn(ioScheduler) .observeOn(mainThreadScheduler) .subscribe(uploadItem -> diff --git a/app/src/main/java/fr/free/nrw/commons/upload/structure/depictions/DepictModel.kt b/app/src/main/java/fr/free/nrw/commons/upload/structure/depictions/DepictModel.kt index b5540e3e9..593a2f1a3 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/structure/depictions/DepictModel.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/structure/depictions/DepictModel.kt @@ -2,7 +2,7 @@ package fr.free.nrw.commons.upload.structure.depictions import fr.free.nrw.commons.upload.depicts.DepictsInterface import io.reactivex.Observable -import java.util.Locale +import java.util.* import javax.inject.Inject /** @@ -14,24 +14,6 @@ class DepictModel @Inject constructor(private val depictsInterface: DepictsInter private const val SEARCH_DEPICTS_LIMIT = 25 } - val selectedDepictions = mutableListOf() - - fun onDepictItemClicked(depictedItem: DepictedItem) { - if (depictedItem.isSelected) { - selectDepictItem(depictedItem) - } else { - unselectDepiction(depictedItem) - } - } - - private fun unselectDepiction(depictedItem: DepictedItem) { - selectedDepictions.remove(depictedItem) - } - - private fun selectDepictItem(depictedItem: DepictedItem) { - selectedDepictions.add(depictedItem) - } - /** * Search for depictions */ @@ -44,5 +26,4 @@ class DepictModel @Inject constructor(private val depictsInterface: DepictsInter .map(::DepictedItem) } - fun depictionsEntityIdList() = selectedDepictions.map { it.entityId } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/structure/depictions/DepictedItem.kt b/app/src/main/java/fr/free/nrw/commons/upload/structure/depictions/DepictedItem.kt index 494dc66e5..b561c97d0 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/structure/depictions/DepictedItem.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/structure/depictions/DepictedItem.kt @@ -1,17 +1,18 @@ package fr.free.nrw.commons.upload.structure.depictions +import fr.free.nrw.commons.upload.WikidataItem import fr.free.nrw.commons.wikidata.model.DepictSearchItem /** * Model class for Depicted Item in Upload and Explore */ data class DepictedItem constructor( - val depictsLabel: String?, + override val name: String, val description: String?, var imageUrl: String, var isSelected: Boolean, - val entityId: String -) { + override val id: String +):WikidataItem { constructor(depictSearchItem: DepictSearchItem) : this( depictSearchItem.label, depictSearchItem.description, @@ -24,12 +25,12 @@ data class DepictedItem constructor( override fun equals(o: Any?) = when { this === o -> true - o is DepictedItem -> depictsLabel == o.depictsLabel + o is DepictedItem -> name == o.name else -> false } override fun hashCode(): Int { - return depictsLabel?.hashCode() ?: 0 + return name?.hashCode() ?: 0 } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/structure/depictions/DepictionRenderer.java b/app/src/main/java/fr/free/nrw/commons/upload/structure/depictions/DepictionRenderer.java index b5aa9dc45..42e99eef4 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/structure/depictions/DepictionRenderer.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/structure/depictions/DepictionRenderer.java @@ -50,7 +50,7 @@ public class DepictionRenderer extends Renderer { public void render() { DepictedItem item = getContent(); checkedView.setChecked(item.isSelected()); - depictsLabel.setText(item.getDepictsLabel()); + depictsLabel.setText(item.getName()); description.setText(item.getDescription()); } } diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikiBaseClient.java b/app/src/main/java/fr/free/nrw/commons/wikidata/WikiBaseClient.java index 3bc542c6a..739981c8a 100644 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/WikiBaseClient.java +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/WikiBaseClient.java @@ -1,16 +1,17 @@ package fr.free.nrw.commons.wikidata; -import org.wikipedia.csrf.CsrfTokenClient; +import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX; +import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF; +import fr.free.nrw.commons.upload.UploadResult; +import fr.free.nrw.commons.upload.WikiBaseInterface; +import io.reactivex.Observable; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; - -import fr.free.nrw.commons.upload.WikiBaseInterface; -import fr.free.nrw.commons.utils.ConfigUtils; -import io.reactivex.Observable; - -import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF; +import org.wikipedia.csrf.CsrfTokenClient; +import org.wikipedia.dataclient.mwapi.MwPostResponse; +import timber.log.Timber; /** * Wikibase Client for calling WikiBase APIs @@ -18,27 +19,43 @@ import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF; @Singleton public class WikiBaseClient { - private final WikiBaseInterface wikiBaseInterface; - private final CsrfTokenClient csrfTokenClient; + private final WikiBaseInterface wikiBaseInterface; + private final CsrfTokenClient csrfTokenClient; - @Inject - public WikiBaseClient(WikiBaseInterface wikiBaseInterface, - @Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient) { - this.wikiBaseInterface = wikiBaseInterface; - this.csrfTokenClient = csrfTokenClient; - } + @Inject + public WikiBaseClient(WikiBaseInterface wikiBaseInterface, + @Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient) { + this.wikiBaseInterface = wikiBaseInterface; + this.csrfTokenClient = csrfTokenClient; + } - public Observable postEditEntity(String fileEntityId, String data) { - try { - return wikiBaseInterface.postEditEntity(fileEntityId, csrfTokenClient.getTokenBlocking(), data) - .map(response -> (response.getSuccessVal() == 1)); - } catch (Throwable throwable) { - return Observable.just(false); - } - } + public Observable postEditEntity(String fileEntityId, String data) { + return csrfToken() + .switchMap(editToken -> wikiBaseInterface.postEditEntity(fileEntityId, editToken, data) + .map(response -> (response.getSuccessVal() == 1))); + } - public Observable getFileEntityId(String fileName) { - return wikiBaseInterface.getFileEntityId(fileName) - .map(response -> (long) (response.query().pages().get(0).pageId())); - } -} \ No newline at end of file + public Observable getFileEntityId(UploadResult uploadResult) { + return wikiBaseInterface.getFileEntityId(uploadResult.createCanonicalFileName()) + .map(response -> (long) (response.query().pages().get(0).pageId())); + } + + public Observable addLabelstoWikidata(long fileEntityId, + String languageCode, String captionValue) { + return csrfToken() + .switchMap(editToken -> wikiBaseInterface + .addLabelstoWikidata(PAGE_ID_PREFIX + fileEntityId, editToken, languageCode, captionValue)); + + } + + private Observable csrfToken() { + return Observable.fromCallable(() -> { + try { + return csrfTokenClient.getTokenBlocking(); + } catch (Throwable throwable) { + Timber.e(throwable); + return ""; + } + }); + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.java b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.java index 542eb58ed..24f518b52 100644 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.java +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.java @@ -1,5 +1,6 @@ package fr.free.nrw.commons.wikidata; +import fr.free.nrw.commons.upload.WikidataItem; import org.jetbrains.annotations.NotNull; import javax.inject.Inject; @@ -24,15 +25,16 @@ public class WikidataClient { /** * Create wikidata claim to add P18 value - * @param entityId wikidata entity ID + * @param entity wikidata entity ID * @param value value of the P18 edit * @return revisionID of the edit */ - Observable createClaim(String entityId, String value) { + Observable createImageClaim(WikidataItem entity, String value) { return getCsrfToken() - .flatMap(csrfToken -> wikidataInterface.postCreateClaim(toRequestBody(entityId), + .flatMap(csrfToken -> wikidataInterface.postCreateClaim( + toRequestBody(entity.getId()), toRequestBody("value"), - toRequestBody("P18"), + toRequestBody(WikidataProperties.IMAGE.getPropertyName()), toRequestBody(value), toRequestBody("en"), toRequestBody(csrfToken))) diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.java b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.java index bd1a3e995..fc8699851 100644 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.java +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.java @@ -4,27 +4,28 @@ import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_I import android.annotation.SuppressLint; import android.content.Context; -import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.R; +import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.kvstore.JsonKvStore; -import fr.free.nrw.commons.upload.mediaDetails.CaptionInterface; +import fr.free.nrw.commons.upload.UploadResult; +import fr.free.nrw.commons.upload.WikidataItem; +import fr.free.nrw.commons.upload.WikidataPlace; import fr.free.nrw.commons.utils.ConfigUtils; import fr.free.nrw.commons.utils.ViewUtil; import io.reactivex.Observable; -import io.reactivex.ObservableSource; import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import java.util.HashMap; +import java.util.ArrayList; import java.util.Locale; -import java.util.Map; -import java.util.concurrent.Callable; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; -import org.wikipedia.csrf.CsrfTokenClient; +import org.jetbrains.annotations.NotNull; +import org.wikipedia.dataclient.mwapi.MwPostResponse; import timber.log.Timber; /** @@ -35,279 +36,193 @@ import timber.log.Timber; @Singleton public class WikidataEditService { - private final static String COMMONS_APP_TAG = "wikimedia-commons-app"; - private final static String COMMONS_APP_EDIT_REASON = "Add tag for edits made using Android Commons app"; + private static final String COMMONS_APP_TAG = "wikimedia-commons-app"; + private static final String COMMONS_APP_EDIT_REASON = "Add tag for edits made using Android Commons app"; private final Context context; private final WikidataEditListener wikidataEditListener; private final JsonKvStore directKvStore; - private final CaptionInterface captionInterface; - private final WikiBaseClient wikiBaseClient; + private final WikiBaseClient wikiBaseClient; private final WikidataClient wikidataClient; - private final CsrfTokenClient csrfTokenClient; - @Inject + @Inject public WikidataEditService(final Context context, final WikidataEditListener wikidataEditListener, @Named("default_preferences") final JsonKvStore directKvStore, final WikiBaseClient wikiBaseClient, - final CaptionInterface captionInterface, - final WikidataClient wikidataClient, - @Named("commons-csrf") final CsrfTokenClient csrfTokenClient) { + final WikidataClient wikidataClient) { this.context = context; this.wikidataEditListener = wikidataEditListener; this.directKvStore = directKvStore; - this.captionInterface = captionInterface; this.wikiBaseClient = wikiBaseClient; this.wikidataClient = wikidataClient; - this.csrfTokenClient = csrfTokenClient; } - /** - * Create a P18 claim and log the edit with custom tag - * - * @param wikidataEntityId a unique id of each Wikidata items - * @param fileName name of the file we will upload - * @param p18Value pic attribute of Wikidata item - */ - public void createClaimWithLogging(String wikidataEntityId, String wikiItemName, String fileName, String p18Value) { - if (wikidataEntityId == null) { - Timber.d("Skipping creation of claim as Wikidata entity ID is null"); - return; - } + /** + * Edits the wikibase entity by adding DEPICTS property. + * Adding DEPICTS property requires call to the wikibase API to set tag against the entity. + */ + @SuppressLint("CheckResult") + private Observable addDepictsProperty(final String fileEntityId, + final WikidataItem depictedItem) { + // Wikipedia:Sandbox (Q10) + final String data = depictionJson(ConfigUtils.isBetaFlavour() ? "Q10" : depictedItem.getId()); - if (fileName == null) { - Timber.d("Skipping creation of claim as fileName entity ID is null"); - return; - } - - if (!(directKvStore.getBoolean("Picture_Has_Correct_Location", true))) { - Timber.d("Image location and nearby place location mismatched, so Wikidata item won't be edited"); - return; - } - - if (p18Value != null && !p18Value.trim().isEmpty()) { - Timber.d("Skipping creation of claim as p18Value is not empty, we won't override existing image"); - return; - } - - editWikidataProperty(wikidataEntityId, wikiItemName, fileName);; - editWikiBaseDepictsProperty(wikidataEntityId, fileName); + return wikiBaseClient.postEditEntity(PAGE_ID_PREFIX + fileEntityId, data) + .doOnNext(success -> { + if (success) { + Timber.d("DEPICTS property was set successfully for %s", fileEntityId); + } else { + Timber.d("Unable to set DEPICTS property for %s", fileEntityId); + } + }) + .doOnError( throwable -> { + Timber.e(throwable, "Error occurred while setting DEPICTS property"); + ViewUtil.showLongToast(context, throwable.toString()); + }) + .subscribeOn(Schedulers.io()); } + @NotNull + private String depictionJson(final String entityId) { + final JsonObject value = new JsonObject(); + value.addProperty("entity-type", "item"); + value.addProperty("numeric-id", entityId.replace("Q", "")); + value.addProperty("id", entityId); + final JsonObject dataValue = new JsonObject(); + dataValue.add("value", value); + dataValue.addProperty("type", "wikibase-entityid"); - /** - * Edits the wikidata entity by adding the P18 property to it. - * Adding the P18 edit requires calling the wikidata API to create a claim against the entity - * - * @param wikidataEntityId - * @param fileName - */ - @SuppressLint("CheckResult") - private void editWikidataProperty(String wikidataEntityId, String wikiItemName, String fileName) { - Timber.d("Upload successful with wiki data entity id as %s", wikidataEntityId); - Timber.d("Attempting to edit Wikidata property %s", wikidataEntityId); + final JsonObject mainSnak = new JsonObject(); + mainSnak.addProperty("snaktype", "value"); + mainSnak.addProperty("property", WikidataProperties.DEPICTS.getPropertyName()); + mainSnak.add("datavalue", dataValue); - final String propertyValue = getFileName(fileName); + final JsonObject claim = new JsonObject(); + claim.add("mainsnak", mainSnak); + claim.addProperty("type", "statement"); + claim.addProperty("rank", "preferred"); - Timber.d("Entity id is %s and property value is %s", wikidataEntityId, propertyValue); - wikidataClient.createClaim(wikidataEntityId, propertyValue) - .flatMap(revisionId -> { - if (revisionId != -1) { - return wikidataClient.addEditTag(revisionId, COMMONS_APP_TAG, COMMONS_APP_EDIT_REASON); - } - throw new RuntimeException("Unable to edit wikidata item"); - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(revisionId -> handleClaimResult(wikidataEntityId, wikiItemName, String.valueOf(revisionId)), throwable -> { - Timber.e(throwable, "Error occurred while making claim"); - ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure)); - }); - } + final JsonArray claims = new JsonArray(); + claims.add(claim); - /** - * Edits the wikibase entity by adding DEPICTS property. - * Adding DEPICTS property requires call to the wikibase API to set tag against the entity. - * - * @param wikidataEntityId - * @param fileName - */ - @SuppressLint("CheckResult") - private void editWikiBaseDepictsProperty(final String wikidataEntityId, final String fileName) { - wikiBaseClient.getFileEntityId(fileName) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(fileEntityId -> { - if (fileEntityId != null) { - Timber.d("EntityId for image was received successfully: %s", fileEntityId); - addDepictsProperty(wikidataEntityId, fileEntityId.toString()); - } else { - Timber.d("Error acquiring EntityId for image: %s", fileName); - } - }, throwable -> { - Timber.e(throwable, "Error occurred while getting EntityID to set DEPICTS property"); - ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure)); - }); - } + final JsonObject jsonData = new JsonObject(); + jsonData.add("claims", claims); - @SuppressLint("CheckResult") - private void addDepictsProperty(String entityId, final String fileEntityId) { - if (ConfigUtils.isBetaFlavour()) { - entityId = "Q10"; // Wikipedia:Sandbox (Q10) - } + return jsonData.toString(); + } - final JsonObject value = new JsonObject(); - value.addProperty("entity-type", "item"); - value.addProperty("numeric-id", entityId.replace("Q", "")); - value.addProperty("id", entityId); - - final JsonObject dataValue = new JsonObject(); - dataValue.add("value", value); - dataValue.addProperty("type", "wikibase-entityid"); - - final JsonObject mainSnak = new JsonObject(); - mainSnak.addProperty("snaktype", "value"); - mainSnak.addProperty("property", BuildConfig.DEPICTS_PROPERTY); - mainSnak.add("datavalue", dataValue); - - final JsonObject claim = new JsonObject(); - claim.add("mainsnak", mainSnak); - claim.addProperty("type", "statement"); - claim.addProperty("rank", "preferred"); - - final JsonArray claims = new JsonArray(); - claims.add(claim); - - final JsonObject jsonData = new JsonObject(); - jsonData.add("claims", claims); - - final String data = jsonData.toString(); - - Observable.defer((Callable>) () -> - wikiBaseClient.postEditEntity(PAGE_ID_PREFIX + fileEntityId, data)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(success -> { - if (success) - Timber.d("DEPICTS property was set successfully for %s", fileEntityId); - else - Timber.d("Unable to set DEPICTS property for %s", fileEntityId); - }, - throwable -> { - Timber.e(throwable, "Error occurred while setting DEPICTS property"); - ViewUtil.showLongToast(context, throwable.toString()); - }); - } - - private void handleClaimResult(String wikidataEntityId, String wikiItemName, String revisionId) { - if (revisionId != null) { - if (wikidataEditListener != null) { - wikidataEditListener.onSuccessfulWikidataEdit(); - } - showSuccessToast(wikiItemName); - } else { - Timber.d("Unable to make wiki data edit for entity %s", wikidataEntityId); - ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure)); - } - } - - - /** + /** * Show a success toast when the edit is made successfully */ - private void showSuccessToast(String wikiItemName) { - String successStringTemplate = context.getString(R.string.successful_wikidata_edit); - String successMessage = String.format(Locale.getDefault(), successStringTemplate, wikiItemName); + private void showSuccessToast(final String wikiItemName) { + final String successStringTemplate = context.getString(R.string.successful_wikidata_edit); + final String successMessage = String.format(Locale.getDefault(), successStringTemplate, wikiItemName); ViewUtil.showLongToast(context, successMessage); } - /** - * Formats and returns the filename as accepted by the wiki base API - * https://www.mediawiki.org/wiki/Wikibase/API#wbcreateclaim - * - * @param fileName - * @return - */ - private String getFileName(String fileName) { - fileName = String.format("\"%s\"", fileName.replace("File:", "")); - Timber.d("Wikidata property name is %s", fileName); - return fileName; - } - - /** - * Adding captions as labels after image is successfully uploaded - */ - @SuppressLint("CheckResult") - public void createLabelforWikidataEntity(final String fileName, - final Map captions) { - Observable.fromCallable(() -> wikiBaseClient.getFileEntityId(fileName)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(fileEntityId -> { - if (fileEntityId != null) { - for (final Map.Entry entry : captions.entrySet()) { - final Map caption = new HashMap<>(); - caption.put(entry.getKey(), entry.getValue()); - try { - wikidataAddLabels(fileEntityId.toString(), caption); - } catch (final Throwable throwable) { - throwable.printStackTrace(); - } - } - } else { - Timber.d("Error acquiring EntityId for image"); - } - }, throwable -> { - Timber.e(throwable, "Error occurred while getting EntityID for the file"); - ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure)); - }); - } - - /** + /** * Adds label to Wikidata using the fileEntityId and the edit token, obtained from csrfTokenClient * * @param fileEntityId - * @param caption + * @return */ @SuppressLint("CheckResult") - private void wikidataAddLabels(final String fileEntityId, final Map caption) { - Observable.fromCallable(() -> { - try { - return csrfTokenClient.getTokenBlocking(); - } catch (Throwable throwable) { - throwable.printStackTrace(); - return null; - } - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(editToken -> { - if (editToken != null) { - Observable.fromCallable(() -> captionInterface.addLabelstoWikidata(fileEntityId, editToken, caption.get(0), caption)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(revisionId -> - { - if (revisionId != null) { - Timber.d("Caption successfully set, revision id = %s", revisionId); - } else { - Timber.d("Error occurred while setting Captions, fileEntityId = %s", fileEntityId); - } - - }, - throwable -> { - Timber.e(throwable, "Error occurred while setting Captions"); - ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure)); - }); - }else { - Timber.d("Error acquiring EntityId for image"); - } - }, throwable -> { - Timber.e(throwable, "Error occurred while getting EntityID for the File"); - ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure)); - }); + private Observable addCaption(final long fileEntityId, final String languageCode, + final String captionValue) { + return wikiBaseClient.addLabelstoWikidata(fileEntityId, languageCode, captionValue) + .doOnNext(mwPostResponse -> onAddCaptionResponse(fileEntityId, mwPostResponse) ) + .doOnError(throwable -> { + Timber.e(throwable, "Error occurred while setting Captions"); + ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure)); + }) + .map(mwPostResponse -> mwPostResponse != null); } + + private void onAddCaptionResponse(Long fileEntityId, MwPostResponse response) { + if (response != null) { + Timber.d("Caption successfully set, revision id = %s", response); + } else { + Timber.d("Error occurred while setting Captions, fileEntityId = %s", fileEntityId); + } + } + + public void createImageClaim(@Nullable final WikidataPlace wikidataPlace, final UploadResult imageUpload) { + if (!(directKvStore.getBoolean("Picture_Has_Correct_Location", true))) { + Timber.d("Image location and nearby place location mismatched, so Wikidata item won't be edited"); + return; + } + editWikidataImageProperty(wikidataPlace, imageUpload); + } + + @SuppressLint("CheckResult") + private void editWikidataImageProperty(final WikidataItem wikidataItem, final UploadResult imageUpload) { + wikidataClient.createImageClaim(wikidataItem, String.format("\"%s\"", imageUpload.getFilename())) + .flatMap(revisionId -> { + if (revisionId != -1) { + return wikidataClient.addEditTag(revisionId, COMMONS_APP_TAG, COMMONS_APP_EDIT_REASON); + } + throw new RuntimeException("Unable to edit wikidata item"); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(revisionId -> handleImageClaimResult(wikidataItem, String.valueOf(revisionId)), throwable -> { + Timber.e(throwable, "Error occurred while making claim"); + ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure)); + }); + } + + private void handleImageClaimResult(final WikidataItem wikidataItem, final String revisionId) { + if (revisionId != null) { + if (wikidataEditListener != null) { + wikidataEditListener.onSuccessfulWikidataEdit(); + } + showSuccessToast(wikidataItem.getName()); + } else { + Timber.d("Unable to make wiki data edit for entity %s", wikidataItem); + ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure)); + } + } + + public Disposable addDepictionsAndCaptions(UploadResult uploadResult, Contribution contribution) { + return wikiBaseClient.getFileEntityId(uploadResult) + .doOnError(throwable -> { + Timber.e(throwable, "Error occurred while getting EntityID to set DEPICTS property"); + ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure)); + }) + .subscribeOn(Schedulers.io()) + .switchMap(fileEntityId -> { + if (fileEntityId != null) { + Timber.d("EntityId for image was received successfully: %s", fileEntityId); + return Observable.concat( + depictionEdits(contribution, fileEntityId), + captionEdits(contribution, fileEntityId) + ); + } else { + Timber.d("Error acquiring EntityId for image: %s", uploadResult); + return Observable.empty(); + } + } + ).subscribe( + success -> Timber.d("edit response: %s", success), + throwable -> Timber.e(throwable, "posting edits failed") + ); + } + + private Observable captionEdits(Contribution contribution, Long fileEntityId) { + return Observable.fromIterable(contribution.getCaptions().entrySet()) + .concatMap(entry -> addCaption(fileEntityId, entry.getKey(), entry.getValue())); + } + + private Observable depictionEdits(Contribution contribution, Long fileEntityId) { + final ArrayList depictedItems = new ArrayList<>(contribution.getDepictedItems()); + final WikidataPlace wikidataPlace = contribution.getWikidataPlace(); + if (wikidataPlace != null) { + depictedItems.add(wikidataPlace); + } + return Observable.fromIterable(depictedItems) + .concatMap( wikidataItem -> addDepictsProperty(fileEntityId.toString(), wikidataItem)); + } } diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataProperties.java b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataProperties.java new file mode 100644 index 000000000..de584dd53 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataProperties.java @@ -0,0 +1,18 @@ +package fr.free.nrw.commons.wikidata; + +import fr.free.nrw.commons.BuildConfig; + +enum WikidataProperties { + IMAGE("P18"), + DEPICTS(BuildConfig.DEPICTS_PROPERTY); + + private final String propertyName; + + WikidataProperties(final String propertyName) { + this.propertyName = propertyName; + } + + public String getPropertyName() { + return propertyName; + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/model/DepictSearchItem.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/model/DepictSearchItem.kt index 2147e1353..596426f50 100644 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/model/DepictSearchItem.kt +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/model/DepictSearchItem.kt @@ -2,11 +2,26 @@ package fr.free.nrw.commons.wikidata.model /** * Model class for Depiction item returned from API after calling searchForDepicts - */ +"search": [ +{ +"repository": "local", +"id": "Q23444", +"concepturi": "http://www.wikidata.org/entity/Q23444", +"title": "Q23444", +"pageid": 26835, +"url": "//www.wikidata.org/wiki/Q23444", +"label": "white", +"description": "color", +"match": { +"type": "label", +"language": "en", +"text": "white" +} +}*/ class DepictSearchItem( val id: String, val pageid: String, val url: String, - val label: String?, + val label: String, val description: String? ) diff --git a/app/src/test/kotlin/fr/free/nrw/commons/delete/DeleteHelperTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/delete/DeleteHelperTest.kt index 5fd56601f..6009e0289 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/delete/DeleteHelperTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/delete/DeleteHelperTest.kt @@ -2,22 +2,19 @@ package fr.free.nrw.commons.delete import android.content.Context import com.nhaarman.mockitokotlin2.eq +import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.whenever import fr.free.nrw.commons.Media import fr.free.nrw.commons.actions.PageEditClient -import fr.free.nrw.commons.notification.NotificationHelper -import fr.free.nrw.commons.utils.ViewUtilWrapper import io.reactivex.Observable -import org.junit.Assert.* +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.mockito.ArgumentMatchers -import org.mockito.InjectMocks import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -import javax.inject.Inject -import javax.inject.Named /** * Tests for delete helper @@ -25,23 +22,15 @@ import javax.inject.Named class DeleteHelperTest { @Mock - @field:[Inject Named("commons-page-edit")] - internal var pageEditClient: PageEditClient? = null + internal lateinit var pageEditClient: PageEditClient @Mock - internal var context: Context? = null + internal lateinit var context: Context @Mock - internal var notificationHelper: NotificationHelper? = null + internal lateinit var media: Media - @Mock - internal var viewUtil: ViewUtilWrapper? = null - - @Mock - internal var media: Media? = null - - @InjectMocks - var deleteHelper: DeleteHelper? = null + lateinit var deleteHelper: DeleteHelper /** * Init mocks for test @@ -49,6 +38,7 @@ class DeleteHelperTest { @Before fun setup() { MockitoAnnotations.initMocks(this) + deleteHelper = DeleteHelper(mock(), pageEditClient, mock(), "") } /** @@ -56,23 +46,23 @@ class DeleteHelperTest { */ @Test fun makeDeletion() { - `when`(pageEditClient?.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) + whenever(pageEditClient.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) .thenReturn(Observable.just(true)) - `when`(pageEditClient?.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) + whenever(pageEditClient.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) .thenReturn(Observable.just(true)) - `when`(pageEditClient?.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) + whenever(pageEditClient.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) .thenReturn(Observable.just(true)) - `when`(media?.displayTitle).thenReturn("Test file") - media?.filename="Test file.jpg" + whenever(media.displayTitle).thenReturn("Test file") val creatorName = "Creator" - `when`(media?.getCreator()).thenReturn("$creatorName (page does not exist)") + whenever(media.creator).thenReturn("$creatorName (page does not exist)") + whenever(media.filename).thenReturn("Test file.jpg") - val makeDeletion = deleteHelper?.makeDeletion(context, media, "Test reason")?.blockingGet() + val makeDeletion = deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet() assertNotNull(makeDeletion) assertTrue(makeDeletion!!) - verify(pageEditClient)?.appendEdit(eq("User_Talk:$creatorName"), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()) + verify(pageEditClient).appendEdit(eq("User_Talk:$creatorName"), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()) } /** @@ -80,63 +70,63 @@ class DeleteHelperTest { */ @Test(expected = RuntimeException::class) fun makeDeletionForPrependEditFailure() { - `when`(pageEditClient?.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) + whenever(pageEditClient.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) .thenReturn(Observable.just(false)) - `when`(pageEditClient?.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) + whenever(pageEditClient.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) .thenReturn(Observable.just(true)) - `when`(pageEditClient?.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) + whenever(pageEditClient.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) .thenReturn(Observable.just(true)) - `when`(media?.displayTitle).thenReturn("Test file") - `when`(media?.filename).thenReturn("Test file.jpg") - `when`(media?.creator).thenReturn("Creator (page does not exist)") + whenever(media.displayTitle).thenReturn("Test file") + whenever(media.filename).thenReturn("Test file.jpg") + whenever(media.creator).thenReturn("Creator (page does not exist)") - deleteHelper?.makeDeletion(context, media, "Test reason")?.blockingGet() + deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet() } @Test(expected = RuntimeException::class) fun makeDeletionForEditFailure() { - `when`(pageEditClient?.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) + whenever(pageEditClient.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) .thenReturn(Observable.just(true)) - `when`(pageEditClient?.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) + whenever(pageEditClient.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) .thenReturn(Observable.just(true)) - `when`(pageEditClient?.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) + whenever(pageEditClient.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) .thenReturn(Observable.just(false)) - `when`(media?.displayTitle).thenReturn("Test file") - `when`(media?.filename).thenReturn("Test file.jpg") - `when`(media?.creator).thenReturn("Creator (page does not exist)") + whenever(media.displayTitle).thenReturn("Test file") + whenever(media.filename).thenReturn("Test file.jpg") + whenever(media.creator).thenReturn("Creator (page does not exist)") - deleteHelper?.makeDeletion(context, media, "Test reason")?.blockingGet() + deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet() } @Test(expected = RuntimeException::class) fun makeDeletionForAppendEditFailure() { - `when`(pageEditClient?.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) + whenever(pageEditClient.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) .thenReturn(Observable.just(true)) - `when`(pageEditClient?.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) + whenever(pageEditClient.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) .thenReturn(Observable.just(false)) - `when`(pageEditClient?.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) + whenever(pageEditClient.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) .thenReturn(Observable.just(true)) - `when`(media?.displayTitle).thenReturn("Test file") - `when`(media?.filename).thenReturn("Test file.jpg") - `when`(media?.creator).thenReturn("Creator (page does not exist)") + whenever(media.displayTitle).thenReturn("Test file") + whenever(media.filename).thenReturn("Test file.jpg") + whenever(media.creator).thenReturn("Creator (page does not exist)") - deleteHelper?.makeDeletion(context, media, "Test reason")?.blockingGet() + deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet() } @Test(expected = RuntimeException::class) fun makeDeletionForEmptyCreatorName() { - `when`(pageEditClient?.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) + whenever(pageEditClient.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) .thenReturn(Observable.just(true)) - `when`(pageEditClient?.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) + whenever(pageEditClient.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) .thenReturn(Observable.just(true)) - `when`(pageEditClient?.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) + whenever(pageEditClient.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) .thenReturn(Observable.just(true)) - `when`(media?.displayTitle).thenReturn("Test file") - media?.filename="Test file.jpg" + whenever(media.displayTitle).thenReturn("Test file") + media.filename ="Test file.jpg" - `when`(media?.getCreator()).thenReturn(null) + whenever(media.creator).thenReturn(null) - deleteHelper?.makeDeletion(context, media, "Test reason")?.blockingGet() + deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet() } -} \ No newline at end of file +} diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaPresenterTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaPresenterTest.kt index adc48e919..b16021ced 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaPresenterTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaPresenterTest.kt @@ -74,11 +74,10 @@ class UploadMediaPresenterTest { repository.preProcessImage( ArgumentMatchers.any(UploadableFile::class.java), ArgumentMatchers.any(Place::class.java), - ArgumentMatchers.anyString(), ArgumentMatchers.any(UploadMediaPresenter::class.java) ) ).thenReturn(testObservableUploadItem) - uploadMediaPresenter.receiveImage(uploadableFile, ArgumentMatchers.anyString(), place) + uploadMediaPresenter.receiveImage(uploadableFile, place) verify(view).showProgress(true) testScheduler.triggerActions() verify(view).onImageProcessed( diff --git a/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataClientTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataClientTest.kt index 9d8a0aaed..ad51203de 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataClientTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataClientTest.kt @@ -1,12 +1,12 @@ package fr.free.nrw.commons.wikidata +import com.nhaarman.mockitokotlin2.mock import fr.free.nrw.commons.wikidata.model.AddEditTagResponse -import fr.free.nrw.commons.wikidata.model.WbCreateClaimResponse import io.reactivex.Observable -import okhttp3.RequestBody import org.junit.Before import org.junit.Test -import org.mockito.ArgumentMatchers.* +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyString import org.mockito.InjectMocks import org.mockito.Mock import org.mockito.Mockito.`when` @@ -32,25 +32,29 @@ class WikidataClientTest { `when`(mwQueryResult!!.csrfToken()).thenReturn("test_token") `when`(mwQueryResponse.query()).thenReturn(mwQueryResult) `when`(wikidataInterface!!.getCsrfToken()) - .thenReturn(Observable.just(mwQueryResponse)) + .thenReturn(Observable.just(mwQueryResponse)) } @Test fun createClaim() { - `when`(wikidataInterface!!.postCreateClaim(any(RequestBody::class.java), - any(RequestBody::class.java), - any(RequestBody::class.java), - any(RequestBody::class.java), - any(RequestBody::class.java), - any(RequestBody::class.java))) - .thenReturn(Observable.just(mock(WbCreateClaimResponse::class.java))) - wikidataClient!!.createClaim("Q1", "test.jpg") + `when`( + wikidataInterface!!.postCreateClaim( + any(), + any(), + any(), + any(), + any(), + any() + ) + ) + .thenReturn(Observable.just(mock())) + wikidataClient!!.createImageClaim(mock(), "test.jpg") } @Test fun addEditTag() { `when`(wikidataInterface!!.addEditTag(anyString(), anyString(), anyString(), anyString())) - .thenReturn(Observable.just(mock(AddEditTagResponse::class.java))) + .thenReturn(Observable.just(mock(AddEditTagResponse::class.java))) wikidataClient!!.addEditTag(1L, "test", "test") } -} \ No newline at end of file +} diff --git a/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataEditServiceTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataEditServiceTest.kt index 25e83c0cd..cc41f756c 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataEditServiceTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataEditServiceTest.kt @@ -1,9 +1,12 @@ package fr.free.nrw.commons.wikidata import android.content.Context +import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.verifyZeroInteractions import com.nhaarman.mockitokotlin2.whenever import fr.free.nrw.commons.kvstore.JsonKvStore +import fr.free.nrw.commons.upload.UploadResult +import fr.free.nrw.commons.upload.WikidataPlace import fr.free.nrw.commons.wikidata.model.AddEditTagResponse import io.reactivex.Observable import org.junit.Before @@ -37,42 +40,27 @@ class WikidataEditServiceTest { MockitoAnnotations.initMocks(this) } - @Test - fun noClaimsWhenEntityIdIsNull() { - wikidataEditService.createClaimWithLogging(null, null,"Test.jpg","") - verifyZeroInteractions(wikidataClient) - } - - @Test - fun noClaimsWhenFileNameIsNull() { - wikidataEditService.createClaimWithLogging("Q1", "Test", null,"") - verifyZeroInteractions(wikidataClient) - } - - @Test - fun noClaimsWhenP18IsNotEmpty() { - wikidataEditService.createClaimWithLogging("Q1", "Test","Test.jpg","Previous.jpg") - verifyZeroInteractions(wikidataClient) - } - @Test fun noClaimsWhenLocationIsNotCorrect() { whenever(directKvStore.getBoolean("Picture_Has_Correct_Location", true)) .thenReturn(false) - wikidataEditService.createClaimWithLogging("Q1", "", "Test.jpg", "") + wikidataEditService.createImageClaim(mock(), mock()) verifyZeroInteractions(wikidataClient) } @Test - fun createClaimWithLogging() { + fun createImageClaim() { whenever(directKvStore.getBoolean("Picture_Has_Correct_Location", true)) .thenReturn(true) - whenever(wikidataClient.createClaim(anyString(), anyString())) + whenever(wikidataClient.createImageClaim(any(), any())) .thenReturn(Observable.just(1L)) whenever(wikidataClient.addEditTag(anyLong(), anyString(), anyString())) .thenReturn(Observable.just(mock(AddEditTagResponse::class.java))) whenever(wikibaseClient.getFileEntityId(any())).thenReturn(Observable.just(1L)) - wikidataEditService.createClaimWithLogging("Q1", "", "Test.jpg", "") - verify(wikidataClient, times(1)).createClaim(anyString(), anyString()) + val wikidataPlace:WikidataPlace = mock() + val uploadResult = mock() + whenever(uploadResult.filename).thenReturn("file") + wikidataEditService.createImageClaim(wikidataPlace, uploadResult) + verify(wikidataClient, times(1)).createImageClaim(wikidataPlace, """"file"""") } }