mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
* #3468 Switch from RvRenderer to AdapterDelegates - replace SearchDepictionsRenderer * #3468 Switch from RvRenderer to AdapterDelegates - replace UploadCategoryDepictionsRenderer * #3468 Switch from RvRenderer to AdapterDelegates - update BaseAdapter to be easier to use * #3468 Switch from RvRenderer to AdapterDelegates - replace SearchImagesRenderer * #3468 Switch from RvRenderer to AdapterDelegates - replace SearchCategoriesRenderer * #3468 Switch from RvRenderer to AdapterDelegates - replace NotificationRenderer * #3468 Switch from RvRenderer to AdapterDelegates - replace UploadDepictsRenderer * #3468 Switch from RvRenderer to AdapterDelegates - replace PlaceRenderer * #3756 Convert SearchDepictionsFragment to use Pagination - convert SearchDepictionsFragment * #3756 Convert SearchDepictionsFragment to use Pagination - fix presenter unit tests now that view is not nullable - fix Category prefix imports * #3756 Convert SearchDepictionsFragment to use Pagination - test DataSource related classes * #3756 Convert SearchDepictionsFragment to use Pagination - reset rx scheduler - ignore failing test * #3760 Convert SearchCategoriesFragment to use Pagination - extract functionality of pagination to base classes - add category pagination * #3772 Convert SearchImagesFragment to use Pagination - convert SearchImagesFragment - tidy up showing the empty view - make search fragments show snackbar with appropriate text * #3772 Convert SearchImagesFragment to use Pagination - allow viewpager to load more data * #3760 remove test that got re-added by merge * #3760 remove duplicate dependency * #3772 fix compilation * #3780 Create media using a combination of Entities & MwQueryResult - construct media with an entity - move fields from media down to contribution - move dynamic fields outside of media - remove unused constructors - remove all unnecessary fetching of captions/descriptions - bump database version * #3808 Construct media objects that depict an item id correctly - use generator to construct media for DepictedImages * #3780 Create media using a combination of Entities & MwQueryResult - update wikicode to align with expected behaviour * #3780 Create media using a combination of Entities & MwQueryResult - replace old site of thumbnail title with most relevant caption
This commit is contained in:
parent
bf4b7e2efc
commit
4b22583b60
46 changed files with 803 additions and 1532 deletions
|
|
@ -1,6 +1,5 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import androidx.annotation.NonNull;
|
||||
|
|
@ -8,44 +7,25 @@ import androidx.annotation.Nullable;
|
|||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.media.Depictions;
|
||||
import fr.free.nrw.commons.utils.CommonsDateUtil;
|
||||
import fr.free.nrw.commons.utils.MediaDataExtractorUtil;
|
||||
import java.text.ParseException;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryPage;
|
||||
import org.wikipedia.gallery.ExtMetadata;
|
||||
import org.wikipedia.gallery.ImageInfo;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.wikipedia.page.PageTitle;
|
||||
|
||||
@Entity
|
||||
public class Media implements Parcelable {
|
||||
|
||||
public static final Media EMPTY = new Media("");
|
||||
|
||||
// Primary metadata fields
|
||||
@Nullable
|
||||
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;
|
||||
private String description; // monolingual description on input...
|
||||
private String discussion;
|
||||
private long dataLength;
|
||||
private Date dateCreated;
|
||||
private String fallbackDescription; // monolingual description on input...
|
||||
@Nullable private Date dateUploaded;
|
||||
private String license;
|
||||
private String licenseUrl;
|
||||
|
|
@ -57,13 +37,14 @@ public class Media implements Parcelable {
|
|||
@NonNull
|
||||
private String pageId;
|
||||
private List<String> 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
|
||||
*/
|
||||
private Depictions depictions;
|
||||
private boolean requestedDeletion;
|
||||
@Nullable private LatLng coordinates;
|
||||
@NotNull
|
||||
private Map<String, String> captions = Collections.emptyMap();
|
||||
@NotNull
|
||||
private Map<String, String> descriptions = Collections.emptyMap();
|
||||
|
||||
@NotNull
|
||||
private List<String> depictionIds = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* Provides local constructor
|
||||
|
|
@ -72,6 +53,82 @@ public class Media implements Parcelable {
|
|||
pageId = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with all parameters
|
||||
*/
|
||||
public Media(final String thumbUrl,
|
||||
final String imageUrl,
|
||||
final String filename,
|
||||
final String fallbackDescription,
|
||||
@Nullable final Date dateUploaded,
|
||||
final String license,
|
||||
final String licenseUrl,
|
||||
final String creator,
|
||||
@NonNull final String pageId,
|
||||
final List<String> categories,
|
||||
@Nullable final LatLng coordinates,
|
||||
@NotNull final Map<String, String> captions,
|
||||
@NotNull final Map<String, String> descriptions,
|
||||
@NotNull final List<String> depictionIds) {
|
||||
this.thumbUrl = thumbUrl;
|
||||
this.imageUrl = imageUrl;
|
||||
this.filename = filename;
|
||||
this.fallbackDescription = fallbackDescription;
|
||||
this.dateUploaded = dateUploaded;
|
||||
this.license = license;
|
||||
this.licenseUrl = licenseUrl;
|
||||
this.creator = creator;
|
||||
this.pageId = pageId;
|
||||
this.categories = categories;
|
||||
this.coordinates = coordinates;
|
||||
this.captions = captions;
|
||||
this.descriptions = descriptions;
|
||||
this.depictionIds = depictionIds;
|
||||
}
|
||||
|
||||
public Media(Media media) {
|
||||
this(media.getThumbUrl(), media.getImageUrl(), media.getFilename(),
|
||||
media.getFallbackDescription(), media.getDateUploaded(), media.getLicense(),
|
||||
media.getLicenseUrl(), media.getCreator(), media.getPageId(), media.getCategories(),
|
||||
media.getCoordinates(), media.getCaptions(), media.getDescriptions(),
|
||||
media.getDepictionIds());
|
||||
}
|
||||
|
||||
public Media(final String filename,
|
||||
Map<String, String> captions, final String fallbackDescription,
|
||||
final String creator, final List<String> categories) {
|
||||
this();
|
||||
thumbUrl = null;
|
||||
this.imageUrl = null;
|
||||
this.filename = filename;
|
||||
this.fallbackDescription = fallbackDescription;
|
||||
this.dateUploaded = new Date();
|
||||
this.creator = creator;
|
||||
this.categories = categories;
|
||||
this.captions=captions;
|
||||
}
|
||||
|
||||
protected Media(final Parcel in) {
|
||||
this(in.readString(), in.readString(), in.readString(),
|
||||
in.readString(), readDateUploaded(in), in.readString(),
|
||||
in.readString(), in.readString(), in.readString(), readList(in),
|
||||
in.readParcelable(LatLng.class.getClassLoader()),
|
||||
((Map<String, String>) in.readSerializable()),
|
||||
((Map<String, String>) in.readSerializable()),
|
||||
readList(in));
|
||||
}
|
||||
|
||||
private static List<String> readList(Parcel in) {
|
||||
final List<String> list = new ArrayList<>();
|
||||
in.readStringList(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
private static Date readDateUploaded(Parcel in) {
|
||||
final long tmpDateUploaded = in.readLong();
|
||||
return tmpDateUploaded == -1 ? null : new Date(tmpDateUploaded);
|
||||
}
|
||||
|
||||
public static final Creator<Media> CREATOR = new Creator<Media>() {
|
||||
@Override
|
||||
public Media createFromParcel(final Parcel source) {
|
||||
|
|
@ -84,192 +141,6 @@ public class Media implements Parcelable {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides a minimal constructor
|
||||
*
|
||||
* @param filename Media filename
|
||||
*/
|
||||
public Media(final String filename) {
|
||||
this();
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide Media constructor
|
||||
* @param localUri Media URI
|
||||
* @param imageUrl Media image URL
|
||||
* @param filename Media filename
|
||||
* @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(final Uri localUri, final String imageUrl, final String filename,
|
||||
final String description,
|
||||
final long dataLength, final Date dateCreated, final Date dateUploaded,
|
||||
final String creator) {
|
||||
this();
|
||||
this.localUri = localUri;
|
||||
thumbUrl = imageUrl;
|
||||
this.imageUrl = imageUrl;
|
||||
this.filename = filename;
|
||||
this.description = description;
|
||||
this.dataLength = dataLength;
|
||||
this.dateCreated = dateCreated;
|
||||
this.dateUploaded = dateUploaded;
|
||||
this.creator = creator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with all parameters
|
||||
*/
|
||||
public Media(final String pageId,
|
||||
final Uri localUri,
|
||||
final String thumbUrl,
|
||||
final String imageUrl,
|
||||
final String filename,
|
||||
final String description,
|
||||
final String discussion,
|
||||
final long dataLength,
|
||||
final Date dateCreated,
|
||||
final Date dateUploaded,
|
||||
final String license,
|
||||
final String licenseUrl,
|
||||
final String creator,
|
||||
final List<String> categories,
|
||||
final boolean requestedDeletion,
|
||||
final LatLng coordinates) {
|
||||
this.pageId = pageId;
|
||||
this.localUri = localUri;
|
||||
this.thumbUrl = thumbUrl;
|
||||
this.imageUrl = imageUrl;
|
||||
this.filename = filename;
|
||||
this.description = description;
|
||||
this.discussion = discussion;
|
||||
this.dataLength = dataLength;
|
||||
this.dateCreated = dateCreated;
|
||||
this.dateUploaded = dateUploaded;
|
||||
this.license = license;
|
||||
this.licenseUrl = licenseUrl;
|
||||
this.creator = creator;
|
||||
this.categories = categories;
|
||||
this.requestedDeletion = requestedDeletion;
|
||||
this.coordinates = coordinates;
|
||||
}
|
||||
|
||||
public Media(final Uri localUri, final String filename,
|
||||
final String description, final String creator, final List<String> categories) {
|
||||
this(localUri,null, filename,
|
||||
description, -1, null, new Date(), creator);
|
||||
this.categories = categories;
|
||||
}
|
||||
|
||||
public Media(final String title, final Date date, final String user) {
|
||||
this(null, null, title, "", -1, date, date, user);
|
||||
}
|
||||
|
||||
protected Media(final Parcel in) {
|
||||
localUri = in.readParcelable(Uri.class.getClassLoader());
|
||||
thumbUrl = in.readString();
|
||||
imageUrl = in.readString();
|
||||
filename = in.readString();
|
||||
thumbnailTitle = in.readString();
|
||||
caption = in.readString();
|
||||
description = in.readString();
|
||||
discussion = in.readString();
|
||||
dataLength = in.readLong();
|
||||
final long tmpDateCreated = in.readLong();
|
||||
dateCreated = tmpDateCreated == -1 ? null : new Date(tmpDateCreated);
|
||||
final long tmpDateUploaded = in.readLong();
|
||||
dateUploaded = tmpDateUploaded == -1 ? null : new Date(tmpDateUploaded);
|
||||
license = in.readString();
|
||||
licenseUrl = in.readString();
|
||||
creator = in.readString();
|
||||
pageId = in.readString();
|
||||
final ArrayList<String> list = new ArrayList<>();
|
||||
in.readStringList(list);
|
||||
categories = list;
|
||||
in.readParcelable(Depictions.class.getClassLoader());
|
||||
requestedDeletion = in.readByte() != 0;
|
||||
coordinates = in.readParcelable(LatLng.class.getClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creating Media object from MWQueryPage.
|
||||
* Earlier only basic details were set for the media object but going forward,
|
||||
* a full media object(with categories, descriptions, coordinates etc) can be constructed using this method
|
||||
*
|
||||
* @param page response from the API
|
||||
* @return Media object
|
||||
*/
|
||||
@NonNull
|
||||
public static Media from(final MwQueryPage page) {
|
||||
final ImageInfo imageInfo = page.imageInfo();
|
||||
if (imageInfo == null) {
|
||||
return new Media(); // null is not allowed
|
||||
}
|
||||
final ExtMetadata metadata = imageInfo.getMetadata();
|
||||
if (metadata == null) {
|
||||
final Media media = new Media(null, imageInfo.getOriginalUrl(),
|
||||
page.title(), "", 0, null, null, null);
|
||||
if (!StringUtils.isBlank(imageInfo.getThumbUrl())) {
|
||||
media.setThumbUrl(imageInfo.getThumbUrl());
|
||||
}
|
||||
return media;
|
||||
}
|
||||
|
||||
final Media media = new Media(null,
|
||||
imageInfo.getOriginalUrl(),
|
||||
page.title(),
|
||||
"",
|
||||
0,
|
||||
safeParseDate(metadata.dateTime()),
|
||||
safeParseDate(metadata.dateTime()),
|
||||
getArtist(metadata)
|
||||
);
|
||||
|
||||
if (!StringUtils.isBlank(imageInfo.getThumbUrl())) {
|
||||
media.setThumbUrl(imageInfo.getThumbUrl());
|
||||
}
|
||||
|
||||
media.setPageId(String.valueOf(page.pageId()));
|
||||
|
||||
String language = Locale.getDefault().getLanguage();
|
||||
if (StringUtils.isBlank(language)) {
|
||||
language = "default";
|
||||
}
|
||||
|
||||
media.setDescription(metadata.imageDescription());
|
||||
media.setCategories(MediaDataExtractorUtil.extractCategoriesFromList(metadata.getCategories()));
|
||||
final String latitude = metadata.getGpsLatitude();
|
||||
final String longitude = metadata.getGpsLongitude();
|
||||
|
||||
if (!StringUtils.isBlank(latitude) && !StringUtils.isBlank(longitude)) {
|
||||
final LatLng latLng = new LatLng(Double.parseDouble(latitude),
|
||||
Double.parseDouble(longitude), 0);
|
||||
media.setCoordinates(latLng);
|
||||
}
|
||||
|
||||
media.setLicenseInformation(metadata.licenseShortName(), metadata.licenseUrl());
|
||||
return media;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method extracts the Commons Username from the artist HTML information
|
||||
* @param metadata
|
||||
* @return
|
||||
*/
|
||||
private static String getArtist(final ExtMetadata metadata) {
|
||||
try {
|
||||
final String artistHtml = metadata.artist();
|
||||
return artistHtml.substring(artistHtml.indexOf("title=\""), artistHtml.indexOf("\">"))
|
||||
.replace("title=\"User:", "");
|
||||
} catch (final Exception ex) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getThumbUrl() {
|
||||
return thumbUrl;
|
||||
|
|
@ -283,23 +154,6 @@ public class Media implements Parcelable {
|
|||
return filename != null ? getPageTitle().getDisplayTextWithoutNamespace().replaceFirst("[.][^.]+$", "") : "";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Date safeParseDate(final String dateStr) {
|
||||
try {
|
||||
return CommonsDateUtil.getMediaSimpleDateFormat().parse(dateStr);
|
||||
} catch (final ParseException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return title to be shown on image thumbnail
|
||||
* If caption is available for the image then it returns caption else filename
|
||||
*/
|
||||
public String getThumbnailTitle() {
|
||||
return thumbnailTitle != null? thumbnailTitle : getDisplayTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets file page title
|
||||
* @return New media page title
|
||||
|
|
@ -308,13 +162,6 @@ public class Media implements Parcelable {
|
|||
return Utils.getPageTitle(getFilename());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets local URI
|
||||
* @return Media local URI
|
||||
*/
|
||||
public Uri getLocalUri() {
|
||||
return localUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets image URL
|
||||
|
|
@ -348,38 +195,12 @@ public class Media implements Parcelable {
|
|||
this.pageId = pageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file discussion as a string.
|
||||
* @return file discussion as a string
|
||||
*/
|
||||
public String getDiscussion() {
|
||||
return discussion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file description.
|
||||
* @return file description as a string
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return caption
|
||||
*/
|
||||
public String getCaption() {
|
||||
return caption;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return depictions associated with the current media
|
||||
*/
|
||||
public Depictions getDepiction() {
|
||||
return depictions;
|
||||
public String getFallbackDescription() {
|
||||
return fallbackDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -390,36 +211,12 @@ public class Media implements Parcelable {
|
|||
this.filename = filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dataLength of the file.
|
||||
* @return file dataLength as a long
|
||||
*/
|
||||
public long getDataLength() {
|
||||
return dataLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the discussion of the file.
|
||||
* @param discussion
|
||||
*/
|
||||
public void setDiscussion(final String discussion) {
|
||||
this.discussion = discussion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the creation date of the file.
|
||||
* @return creation date as a Date
|
||||
*/
|
||||
public Date getDateCreated() {
|
||||
return dateCreated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file description.
|
||||
* @param description the new description of the file
|
||||
* @param fallbackDescription the new description of the file
|
||||
*/
|
||||
public void setDescription(final String description) {
|
||||
this.description = description;
|
||||
public void setFallbackDescription(final String fallbackDescription) {
|
||||
this.fallbackDescription = fallbackDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -440,14 +237,6 @@ public class Media implements Parcelable {
|
|||
return creator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dataLength of the file.
|
||||
* @param dataLength as a long
|
||||
*/
|
||||
public void setDataLength(final long dataLength) {
|
||||
this.dataLength = dataLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the license name of the file.
|
||||
* @return license as a String
|
||||
|
|
@ -456,13 +245,6 @@ public class Media implements Parcelable {
|
|||
return license;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Caption(if available) as the thumbnail title of the image
|
||||
*/
|
||||
public void setThumbnailTitle(final String title) {
|
||||
thumbnailTitle = title;
|
||||
}
|
||||
|
||||
public String getLicenseUrl() {
|
||||
return licenseUrl;
|
||||
}
|
||||
|
|
@ -492,24 +274,10 @@ public class Media implements Parcelable {
|
|||
* Gets the categories the file falls under.
|
||||
* @return file categories as an ArrayList of Strings
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<String> getCategories() {
|
||||
return categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the license name of the file.
|
||||
* @param license license name as a String
|
||||
*/
|
||||
public void setLicenseInformation(final String license, String licenseUrl) {
|
||||
this.license = license;
|
||||
|
||||
if (!licenseUrl.startsWith("http://") && !licenseUrl.startsWith("https://")) {
|
||||
licenseUrl = "https://" + licenseUrl;
|
||||
}
|
||||
this.licenseUrl = licenseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the coordinates of where the file was created.
|
||||
* @param coordinates file coordinates as a LatLng
|
||||
|
|
@ -523,7 +291,18 @@ public class Media implements Parcelable {
|
|||
* @return
|
||||
*/
|
||||
public String getWikiCode() {
|
||||
return String.format("[[%s|thumb|%s]]", filename, thumbnailTitle);
|
||||
return String.format("[[%s|thumb|%s]]", filename, getMostRelevantCaption());
|
||||
}
|
||||
|
||||
public String getMostRelevantCaption() {
|
||||
final String languageAppropriateCaption = captions.get(Locale.getDefault().getLanguage());
|
||||
if (languageAppropriateCaption != null) {
|
||||
return languageAppropriateCaption;
|
||||
}
|
||||
for (String firstCaption : captions.values()) {
|
||||
return firstCaption;
|
||||
}
|
||||
return getDisplayTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -537,21 +316,6 @@ public class Media implements Parcelable {
|
|||
this.categories = categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of requested deletion
|
||||
* @return boolean requestedDeletion
|
||||
*/
|
||||
public boolean isRequestedDeletion(){
|
||||
return requestedDeletion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set requested deletion to true
|
||||
* @param requestedDeletion
|
||||
*/
|
||||
public void setRequestedDeletion(final boolean requestedDeletion) {
|
||||
this.requestedDeletion = requestedDeletion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the license name of the file.
|
||||
|
|
@ -562,22 +326,6 @@ public class Media implements Parcelable {
|
|||
this.license = license;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* This function sets captions
|
||||
* @param caption
|
||||
*/
|
||||
public void setCaption(final String caption) {
|
||||
this.caption = caption;
|
||||
}
|
||||
|
||||
public void setLocalUri(@Nullable final Uri localUri) {
|
||||
this.localUri = localUri;
|
||||
}
|
||||
|
||||
public void setImageUrl(final String imageUrl) {
|
||||
this.imageUrl = imageUrl;
|
||||
}
|
||||
|
|
@ -590,28 +338,11 @@ public class Media implements Parcelable {
|
|||
this.licenseUrl = licenseUrl;
|
||||
}
|
||||
|
||||
public Depictions getDepictions() {
|
||||
return depictions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Sets depictions for the current media obtained fro Wikibase API*/
|
||||
public void setDepictions(final Depictions depictions) {
|
||||
this.depictions = depictions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the creation date of the file.
|
||||
* @param date creation date as a Date
|
||||
*/
|
||||
public void setDateCreated(final Date date) {
|
||||
dateCreated = date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a way to transfer information between two or more
|
||||
* activities.
|
||||
|
|
@ -620,69 +351,76 @@ public class Media implements Parcelable {
|
|||
*/
|
||||
@Override
|
||||
public void writeToParcel(final Parcel dest, final int flags) {
|
||||
dest.writeParcelable(localUri, flags);
|
||||
dest.writeString(thumbUrl);
|
||||
dest.writeString(imageUrl);
|
||||
dest.writeString(filename);
|
||||
dest.writeString(thumbnailTitle);
|
||||
dest.writeString(caption);
|
||||
dest.writeString(description);
|
||||
dest.writeString(discussion);
|
||||
dest.writeLong(dataLength);
|
||||
dest.writeLong(dateCreated != null ? dateCreated.getTime() : -1);
|
||||
dest.writeString(fallbackDescription);
|
||||
dest.writeLong(dateUploaded != null ? dateUploaded.getTime() : -1);
|
||||
dest.writeString(license);
|
||||
dest.writeString(licenseUrl);
|
||||
dest.writeString(creator);
|
||||
dest.writeString(pageId);
|
||||
dest.writeStringList(categories);
|
||||
dest.writeParcelable(depictions, flags);
|
||||
dest.writeByte(requestedDeletion ? (byte) 1 : (byte) 0);
|
||||
dest.writeParcelable(coordinates, flags);
|
||||
dest.writeSerializable((Serializable) captions);
|
||||
dest.writeSerializable((Serializable) descriptions);
|
||||
dest.writeList(depictionIds);
|
||||
}
|
||||
|
||||
public Map<String, String> getCaptions() {
|
||||
return captions;
|
||||
}
|
||||
|
||||
public void setCaptions(Map<String, String> captions) {
|
||||
this.captions = captions;
|
||||
}
|
||||
|
||||
public Map<String, String> getDescriptions() {
|
||||
return descriptions;
|
||||
}
|
||||
|
||||
public void setDescriptions(Map<String, String> descriptions) {
|
||||
this.descriptions = descriptions;
|
||||
}
|
||||
|
||||
public List<String> getDepictionIds() {
|
||||
return depictionIds;
|
||||
}
|
||||
|
||||
public void setDepictionIds(final List<String> depictionIds) {
|
||||
this.depictionIds = depictionIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equals implementation that matches all parameters for equality check
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof Media)) {
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final Media media = (Media) o;
|
||||
return getDataLength() == media.getDataLength() &&
|
||||
isRequestedDeletion() == media.isRequestedDeletion() &&
|
||||
Objects.equals(getLocalUri(), media.getLocalUri()) &&
|
||||
Objects.equals(getThumbUrl(), media.getThumbUrl()) &&
|
||||
Objects.equals(getImageUrl(), media.getImageUrl()) &&
|
||||
Objects.equals(getFilename(), media.getFilename()) &&
|
||||
Objects.equals(getThumbnailTitle(), media.getThumbnailTitle()) &&
|
||||
Objects.equals(getCaption(), media.getCaption()) &&
|
||||
Objects.equals(getDescription(), media.getDescription()) &&
|
||||
Objects.equals(getDiscussion(), media.getDiscussion()) &&
|
||||
Objects.equals(getDateCreated(), media.getDateCreated()) &&
|
||||
Objects.equals(getDateUploaded(), media.getDateUploaded()) &&
|
||||
Objects.equals(getLicense(), media.getLicense()) &&
|
||||
Objects.equals(getLicenseUrl(), media.getLicenseUrl()) &&
|
||||
Objects.equals(getCreator(), media.getCreator()) &&
|
||||
getPageId().equals(media.getPageId()) &&
|
||||
Objects.equals(getCategories(), media.getCategories()) &&
|
||||
Objects.equals(getDepictions(), media.getDepictions()) &&
|
||||
Objects.equals(getCoordinates(), media.getCoordinates());
|
||||
return Objects.equals(thumbUrl, media.thumbUrl) &&
|
||||
Objects.equals(imageUrl, media.imageUrl) &&
|
||||
Objects.equals(filename, media.filename) &&
|
||||
Objects.equals(fallbackDescription, media.fallbackDescription) &&
|
||||
Objects.equals(dateUploaded, media.dateUploaded) &&
|
||||
Objects.equals(license, media.license) &&
|
||||
Objects.equals(licenseUrl, media.licenseUrl) &&
|
||||
Objects.equals(creator, media.creator) &&
|
||||
pageId.equals(media.pageId) &&
|
||||
Objects.equals(categories, media.categories) &&
|
||||
Objects.equals(coordinates, media.coordinates) &&
|
||||
captions.equals(media.captions) &&
|
||||
descriptions.equals(media.descriptions) &&
|
||||
depictionIds.equals(media.depictionIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashcode implementation that uses all parameters for calculating hash
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects
|
||||
.hash(getLocalUri(), getThumbUrl(), getImageUrl(), getFilename(), getThumbnailTitle(),
|
||||
getCaption(), getDescription(), getDiscussion(), getDataLength(), getDateCreated(),
|
||||
getDateUploaded(), getLicense(), getLicenseUrl(), getCreator(), getPageId(),
|
||||
getCategories(), getDepictions(), isRequestedDeletion(), getCoordinates());
|
||||
.hash(thumbUrl, imageUrl, filename, fallbackDescription, dateUploaded, license,
|
||||
licenseUrl,
|
||||
creator, pageId, categories, coordinates, captions, descriptions, depictionIds);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
package fr.free.nrw.commons
|
||||
|
||||
import androidx.core.text.HtmlCompat
|
||||
import fr.free.nrw.commons.depictions.Media.DepictedImagesFragment
|
||||
import fr.free.nrw.commons.media.Depictions
|
||||
import fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX
|
||||
import fr.free.nrw.commons.media.IdAndCaptions
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.functions.Function5
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
|
@ -13,107 +12,40 @@ import javax.inject.Singleton
|
|||
/**
|
||||
* Fetch additional media data from the network that we don't store locally.
|
||||
*
|
||||
* This includes things like category lists and multilingual descriptions,
|
||||
* which are not intrinsic to the media and may change due to editing.
|
||||
*
|
||||
* This includes things like category lists and multilingual descriptions, which are not intrinsic
|
||||
* to the media and may change due to editing.
|
||||
*/
|
||||
@Singleton
|
||||
class MediaDataExtractor @Inject constructor(private val mediaClient: MediaClient) {
|
||||
|
||||
/**
|
||||
* Simplified method to extract all details required to show media details.
|
||||
* It fetches media object, deletion status, talk page and captions for the filename
|
||||
* @param filename for which the details are to be fetched
|
||||
* @return full Media object with all details including deletion status and talk page
|
||||
*/
|
||||
fun fetchMediaDetails(filename: String, pageId: String?): Single<Media> {
|
||||
return Single.zip(
|
||||
getMediaFromFileName(filename),
|
||||
mediaClient.checkPageExistsUsingTitle("Commons:Deletion_requests/$filename"),
|
||||
getDiscussion(filename),
|
||||
if (pageId != null)
|
||||
getCaption(DepictedImagesFragment.PAGE_ID_PREFIX + pageId)
|
||||
else Single.just(MediaClient.NO_CAPTION),
|
||||
getDepictions(filename),
|
||||
Function5 { media: Media, deletionStatus: Boolean, discussion: String, caption: String, depictions: Depictions ->
|
||||
combineToMedia(
|
||||
media,
|
||||
deletionStatus,
|
||||
discussion,
|
||||
caption,
|
||||
depictions
|
||||
)
|
||||
fun fetchDepictionIdsAndLabels(media: Media) =
|
||||
mediaClient.getEntities(media.depictionIds)
|
||||
.map {
|
||||
it.entities()
|
||||
.mapValues { entry -> entry.value.labels().mapValues { it.value.value() } }
|
||||
}
|
||||
)
|
||||
}
|
||||
.map { it.map { (key, value) -> IdAndCaptions(key, value) } }
|
||||
.onErrorReturn { emptyList() }
|
||||
|
||||
private fun combineToMedia(
|
||||
media: Media,
|
||||
deletionStatus: Boolean,
|
||||
discussion: String,
|
||||
caption: String,
|
||||
depictions: Depictions
|
||||
): Media {
|
||||
media.discussion = discussion
|
||||
media.caption = caption
|
||||
media.depictions = depictions
|
||||
if (deletionStatus) {
|
||||
media.isRequestedDeletion = true
|
||||
}
|
||||
return media
|
||||
}
|
||||
fun checkDeletionRequestExists(media: Media) =
|
||||
mediaClient.checkPageExistsUsingTitle("Commons:Deletion_requests/" + media.filename)
|
||||
|
||||
/**
|
||||
* Obtains captions using filename
|
||||
* @param wikibaseIdentifier
|
||||
*
|
||||
* @return caption for the image in user's locale
|
||||
* Ex: "a nice painting" (english locale) and "No Caption" in case the caption is not available for the image
|
||||
*/
|
||||
private fun getCaption(wikibaseIdentifier: String): Single<String> {
|
||||
return mediaClient.getCaptionByWikibaseIdentifier(wikibaseIdentifier)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch depictions from the MediaWiki API
|
||||
* @param filename the filename we will return the caption for
|
||||
* @return Depictions
|
||||
*/
|
||||
private fun getDepictions(filename: String): Single<Depictions> {
|
||||
return mediaClient.getDepictions(filename)
|
||||
.doOnError { throwable: Throwable? ->
|
||||
Timber.e(
|
||||
throwable,
|
||||
"error while fetching depictions"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method can be used to fetch media for a given filename
|
||||
* @param filename Eg. File:Test.jpg
|
||||
* @return return data rich Media object
|
||||
*/
|
||||
fun getMediaFromFileName(filename: String?): Single<Media> {
|
||||
return mediaClient.getMedia(filename)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch talk page from the MediaWiki API
|
||||
* @param filename
|
||||
* @return
|
||||
*/
|
||||
private fun getDiscussion(filename: String): Single<String> {
|
||||
return mediaClient.getPageHtml(filename.replace("File", "File talk"))
|
||||
.map { discussion: String? ->
|
||||
HtmlCompat.fromHtml(
|
||||
discussion!!,
|
||||
HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||
).toString()
|
||||
}
|
||||
.onErrorReturn { throwable: Throwable? ->
|
||||
Timber.e(throwable, "Error occurred while fetching discussion")
|
||||
fun fetchDiscussion(media: Media) =
|
||||
mediaClient.getPageHtml(media.filename.replace("File", "File talk"))
|
||||
.map { HtmlCompat.fromHtml(it, HtmlCompat.FROM_HTML_MODE_LEGACY).toString() }
|
||||
.onErrorReturn {
|
||||
Timber.d("Error occurred while fetching discussion")
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
fun refresh(media: Media): Single<Media> {
|
||||
return Single.ambArray(
|
||||
mediaClient.getMediaById(PAGE_ID_PREFIX + media.pageId)
|
||||
.onErrorResumeNext { Single.never() },
|
||||
mediaClient.getMedia(media.filename)
|
||||
.onErrorResumeNext { Single.never() }
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,5 @@
|
|||
package fr.free.nrw.commons.bookmarks.pictures;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.bookmarks.Bookmark;
|
||||
import fr.free.nrw.commons.media.MediaClient;
|
||||
|
|
@ -15,6 +7,10 @@ import io.reactivex.Observable;
|
|||
import io.reactivex.ObservableSource;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.functions.Function;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
public class BookmarkPicturesController {
|
||||
|
|
@ -40,16 +36,13 @@ public class BookmarkPicturesController {
|
|||
currentBookmarks = bookmarks;
|
||||
return Observable.fromIterable(bookmarks)
|
||||
.flatMap((Function<Bookmark, ObservableSource<Media>>) this::getMediaFromBookmark)
|
||||
.filter(media -> media != null && !StringUtils.isBlank(media.getFilename()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private Observable<Media> getMediaFromBookmark(Bookmark bookmark) {
|
||||
Media dummyMedia = new Media("");
|
||||
return mediaClient.getMedia(bookmark.getMediaName())
|
||||
.map(media -> media == null ? dummyMedia : media)
|
||||
.onErrorReturn(throwable -> dummyMedia)
|
||||
.toObservable();
|
||||
.toObservable()
|
||||
.onErrorResumeNext(Observable.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package fr.free.nrw.commons.category;
|
|||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Bundle;
|
||||
|
|
@ -253,37 +252,6 @@ public class CategoryImagesListFragment extends DaggerFragment {
|
|||
progressBar.setVisibility(GONE);
|
||||
isLoading = false;
|
||||
statusTextView.setVisibility(GONE);
|
||||
for (Media m : collection) {
|
||||
final String pageId = m.getPageId();
|
||||
if (pageId != null) {
|
||||
replaceTitlesWithCaptions(PAGE_ID_PREFIX + pageId, mediaSize++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch captions for the image using filename and replace title of on the image thumbnail(if captions are available)
|
||||
* else show filename
|
||||
*/
|
||||
public void replaceTitlesWithCaptions(String wikibaseIdentifier, int i) {
|
||||
compositeDisposable.add(mediaClient.getCaptionByWikibaseIdentifier(wikibaseIdentifier)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(subscriber -> {
|
||||
handleLabelforImage(subscriber, i);
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* If caption is available for the image, then modify grid adapter
|
||||
* to show captions
|
||||
*/
|
||||
private void handleLabelforImage(String s, int position) {
|
||||
if (!s.trim().equals(getString(R.string.detail_caption_empty))) {
|
||||
gridAdapter.getItem(position).setThumbnailTitle(s);
|
||||
gridAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ public class GridViewAdapter extends ArrayAdapter {
|
|||
SimpleDraweeView imageView = convertView.findViewById(R.id.categoryImageView);
|
||||
TextView fileName = convertView.findViewById(R.id.categoryImageTitle);
|
||||
TextView author = convertView.findViewById(R.id.categoryImageAuthor);
|
||||
fileName.setText(item.getThumbnailTitle());
|
||||
fileName.setText(item.getMostRelevantCaption());
|
||||
setAuthorView(item, author);
|
||||
imageView.setImageURI(item.getThumbUrl());
|
||||
return convertView;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.room.Entity;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.upload.UploadMediaDetail;
|
||||
import fr.free.nrw.commons.upload.UploadItem;
|
||||
import fr.free.nrw.commons.upload.UploadMediaDetail;
|
||||
import fr.free.nrw.commons.upload.WikidataPlace;
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@Entity(tableName = "contribution")
|
||||
|
|
@ -34,24 +35,23 @@ public class Contribution extends Media {
|
|||
*/
|
||||
private List<DepictedItem> 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: "<caption in
|
||||
* short in English>" key = "de" , value: "<caption in german>"
|
||||
*/
|
||||
private HashMap<String, String> captions = new HashMap<>();
|
||||
@Nullable
|
||||
private Uri localUri;
|
||||
private long dataLength;
|
||||
private Date dateCreated;
|
||||
|
||||
public Contribution() {
|
||||
}
|
||||
|
||||
public Contribution(final UploadItem item, final SessionManager sessionManager,
|
||||
final List<DepictedItem> depictedItems, final List<String> categories) {
|
||||
super(item.getMediaUri(),
|
||||
super(
|
||||
item.getFileName(),
|
||||
UploadMediaDetail.formatList(item.getUploadMediaDetails()),
|
||||
UploadMediaDetail.formatCaptions(item.getUploadMediaDetails()),
|
||||
UploadMediaDetail.formatDescriptions(item.getUploadMediaDetails()),
|
||||
sessionManager.getAuthorName(),
|
||||
categories);
|
||||
captions = new HashMap<>(UploadMediaDetail.formatCaptions(item.getUploadMediaDetails()));
|
||||
localUri = item.getMediaUri();
|
||||
decimalCoords = item.getGpsCoords().getDecimalCoords();
|
||||
dateCreatedSource = "";
|
||||
this.depictedItems = depictedItems;
|
||||
|
|
@ -117,24 +117,6 @@ public class Contribution extends Media {
|
|||
this.mimeType = 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
|
||||
* <p>
|
||||
* key of the HashMap is the language and value is the caption in the corresponding language
|
||||
* <p>
|
||||
* returns list of captions stored in hashmap
|
||||
*/
|
||||
public HashMap<String, String> getCaptions() {
|
||||
return captions;
|
||||
}
|
||||
|
||||
public void setCaptions(HashMap<String, String> captions) {
|
||||
this.captions = captions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
|
|
@ -147,7 +129,6 @@ public class Contribution extends Media {
|
|||
dest.writeLong(transferred);
|
||||
dest.writeString(decimalCoords);
|
||||
dest.writeString(dateCreatedSource);
|
||||
dest.writeSerializable(captions);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -156,13 +137,7 @@ public class Contribution extends Media {
|
|||
* @param state
|
||||
*/
|
||||
public Contribution(Media media, int state) {
|
||||
super(media.getPageId(),
|
||||
media.getLocalUri(), media.getThumbUrl(), media.getImageUrl(), media.getFilename(),
|
||||
media.getDescription(),
|
||||
media.getDiscussion(),
|
||||
media.getDataLength(), media.getDateCreated(), media.getDateUploaded(),
|
||||
media.getLicense(), media.getLicenseUrl(), media.getCreator(), media.getCategories(),
|
||||
media.isRequestedDeletion(), media.getCoordinates());
|
||||
super(media);
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
|
|
@ -172,7 +147,6 @@ public class Contribution extends Media {
|
|||
transferred = in.readLong();
|
||||
decimalCoords = in.readString();
|
||||
dateCreatedSource = in.readString();
|
||||
captions = (HashMap<String, String>) in.readSerializable();
|
||||
}
|
||||
|
||||
public static final Creator<Contribution> CREATOR = new Creator<Contribution>() {
|
||||
|
|
@ -187,34 +161,60 @@ public class Contribution extends Media {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Equals implementation of Contributions that compares all parameters for checking equality
|
||||
*/
|
||||
@Nullable
|
||||
public Uri getLocalUri() {
|
||||
return localUri;
|
||||
}
|
||||
|
||||
public void setLocalUri(@Nullable Uri localUri) {
|
||||
this.localUri = localUri;
|
||||
}
|
||||
|
||||
public long getDataLength() {
|
||||
return dataLength;
|
||||
}
|
||||
|
||||
public void setDataLength(long dataLength) {
|
||||
this.dataLength = dataLength;
|
||||
}
|
||||
|
||||
public Date getDateCreated() {
|
||||
return dateCreated;
|
||||
}
|
||||
|
||||
public void setDateCreated(Date dateCreated) {
|
||||
this.dateCreated = dateCreated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof Contribution)) {
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if (!super.equals(o)) {
|
||||
return false;
|
||||
}
|
||||
final Contribution that = (Contribution) o;
|
||||
return getState() == that.getState() && getTransferred() == that.getTransferred() && Objects
|
||||
.equals(getDecimalCoords(), that.getDecimalCoords()) && Objects
|
||||
.equals(getDateCreatedSource(), that.getDateCreatedSource()) && Objects
|
||||
.equals(getWikidataPlace(), that.getWikidataPlace()) && Objects
|
||||
.equals(getDepictedItems(), that.getDepictedItems()) && Objects
|
||||
.equals(getMimeType(), that.getMimeType()) && Objects
|
||||
.equals(getCaptions(), that.getCaptions());
|
||||
return state == that.state &&
|
||||
transferred == that.transferred &&
|
||||
dataLength == that.dataLength &&
|
||||
Objects.equals(decimalCoords, that.decimalCoords) &&
|
||||
Objects.equals(dateCreatedSource, that.dateCreatedSource) &&
|
||||
Objects.equals(wikidataPlace, that.wikidataPlace) &&
|
||||
Objects.equals(depictedItems, that.depictedItems) &&
|
||||
Objects.equals(mimeType, that.mimeType) &&
|
||||
Objects.equals(localUri, that.localUri) &&
|
||||
Objects.equals(dateCreated, that.dateCreated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash code implementation of contributions that considers all parameters for calculating hash.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects
|
||||
.hash(getState(), getTransferred(), getDecimalCoords(), getDateCreatedSource(),
|
||||
getWikidataPlace(), getDepictedItems(), getMimeType(), getCaptions());
|
||||
.hash(super.hashCode(), state, transferred, decimalCoords, dateCreatedSource,
|
||||
wikidataPlace,
|
||||
depictedItems, mimeType, localUri, dataLength, dateCreated);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,9 +62,7 @@ class ContributionBoundaryCallback @Inject constructor(
|
|||
}
|
||||
}
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe(
|
||||
::saveContributionsToDB
|
||||
) { error: Throwable ->
|
||||
.subscribe(::saveContributionsToDB) { error: Throwable ->
|
||||
Timber.e(
|
||||
"Failed to fetch contributions: %s",
|
||||
error.message
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
|
@ -24,8 +21,6 @@ import fr.free.nrw.commons.media.MediaClient;
|
|||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.wikipedia.dataclient.WikiSite;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
|
|
@ -65,8 +60,8 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
|||
|
||||
public void init(final int position, final Contribution contribution) {
|
||||
this.contribution = contribution;
|
||||
fetchAndDisplayCaption(contribution);
|
||||
this.position = position;
|
||||
titleView.setText(contribution.getMostRelevantCaption());
|
||||
final String imageSource = chooseImageSource(contribution.getThumbUrl(),
|
||||
contribution.getLocalUri());
|
||||
if (!TextUtils.isEmpty(imageSource)) {
|
||||
|
|
@ -116,37 +111,6 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In contributions first we show the title for the image stored in cache, then we fetch captions
|
||||
* associated with the image and replace title on the thumbnail with caption
|
||||
*
|
||||
* @param contribution
|
||||
*/
|
||||
private void fetchAndDisplayCaption(final Contribution contribution) {
|
||||
if ((contribution.getState() != Contribution.STATE_COMPLETED)) {
|
||||
titleView.setText(contribution.getDisplayTitle());
|
||||
} else {
|
||||
final String pageId = contribution.getPageId();
|
||||
if (pageId != null) {
|
||||
Timber.d("Fetching caption for %s", contribution.getFilename());
|
||||
final String wikibaseMediaId = PAGE_ID_PREFIX
|
||||
+ pageId; // Create Wikibase media id from the page id. Example media id: M80618155 for https://commons.wikimedia.org/wiki/File:Tantanmen.jpeg with has the pageid 80618155
|
||||
compositeDisposable.add(mediaClient.getCaptionByWikibaseIdentifier(wikibaseMediaId)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(subscriber -> {
|
||||
if (!subscriber.trim().equals(MediaClient.NO_CAPTION)) {
|
||||
titleView.setText(subscriber);
|
||||
} else {
|
||||
titleView.setText(contribution.getDisplayTitle());
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
titleView.setText(contribution.getDisplayTitle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a media exists on the corresponding Wikipedia article Currently the check is made for
|
||||
* the device's current language Wikipedia
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.BasePresenter;
|
||||
import fr.free.nrw.commons.Media;
|
||||
|
||||
/**
|
||||
* The contract for Contributions View & Presenter
|
||||
|
|
@ -21,8 +18,5 @@ public class ContributionsContract {
|
|||
|
||||
void deleteUpload(Contribution contribution);
|
||||
|
||||
void updateContribution(Contribution contribution);
|
||||
|
||||
void fetchMediaDetails(Contribution contribution);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,9 +46,8 @@ public class ContributionsListAdapter extends
|
|||
* Initializes the view holder with contribution data
|
||||
*/
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ContributionViewHolder holder, final int position) {
|
||||
final Contribution contribution = getItem(position);
|
||||
holder.init(position, contribution);
|
||||
public void onBindViewHolder(@NonNull ContributionViewHolder holder, int position) {
|
||||
holder.init(position, getItem(position));
|
||||
}
|
||||
|
||||
Contribution getContributionForPosition(final int position) {
|
||||
|
|
|
|||
|
|
@ -1,30 +1,10 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.MediaDataExtractor;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.MediaDataExtractor;
|
||||
import fr.free.nrw.commons.contributions.ContributionsContract.UserActionListener;
|
||||
import fr.free.nrw.commons.di.CommonsApplicationModule;
|
||||
import fr.free.nrw.commons.mwapi.UserClient;
|
||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||
import io.reactivex.Scheduler;
|
||||
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 javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
|
|
@ -77,24 +57,4 @@ public class ContributionsPresenter implements UserActionListener {
|
|||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateContribution(Contribution contribution) {
|
||||
compositeDisposable.add(repository
|
||||
.updateContribution(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchMediaDetails(Contribution contribution) {
|
||||
compositeDisposable.add(mediaDataExtractor
|
||||
.getMediaFromFileName(contribution.getFilename())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(media -> {
|
||||
contribution.setThumbUrl(media.getThumbUrl());
|
||||
updateContribution(contribution);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import fr.free.nrw.commons.contributions.ContributionDao
|
|||
* The database for accessing the respective DAOs
|
||||
*
|
||||
*/
|
||||
@Database(entities = [Contribution::class], version = 2, exportSchema = false)
|
||||
@Database(entities = [Contribution::class], version = 3, exportSchema = false)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun contributionDao(): ContributionDao
|
||||
|
|
|
|||
|
|
@ -7,11 +7,9 @@ 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 fr.free.nrw.commons.media.Depictions;
|
||||
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;
|
||||
|
||||
|
|
@ -55,13 +53,13 @@ public class Converters {
|
|||
}
|
||||
|
||||
@TypeConverter
|
||||
public static String mapObjectToString(HashMap<String,String> objectList) {
|
||||
public static String mapObjectToString(Map<String,String> objectList) {
|
||||
return writeObjectToString(objectList);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static HashMap<String,String> stringToMap(String objectList) {
|
||||
return readObjectWithTypeToken(objectList, new TypeToken<HashMap<String,String>>(){});
|
||||
public static Map<String,String> stringToMap(String objectList) {
|
||||
return readObjectWithTypeToken(objectList, new TypeToken<Map<String,String>>(){});
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
|
|
@ -94,16 +92,6 @@ public class Converters {
|
|||
return readObjectWithTypeToken(depictedItems, new TypeToken<List<DepictedItem>>() {});
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static String depictionsToString(Depictions depictedItems) {
|
||||
return writeObjectToString(depictedItems);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static Depictions stringToDepictions(String depictedItems) {
|
||||
return readObjectFromString(depictedItems, Depictions.class);
|
||||
}
|
||||
|
||||
private static String writeObjectToString(Object object) {
|
||||
return object == null ? null : getGson().toJson(object);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ public class GridViewAdapter extends ArrayAdapter {
|
|||
SimpleDraweeView imageView = convertView.findViewById(R.id.depict_image_view);
|
||||
TextView fileName = convertView.findViewById(R.id.depict_image_title);
|
||||
TextView author = convertView.findViewById(R.id.depict_image_author);
|
||||
fileName.setText(item.getThumbnailTitle());
|
||||
fileName.setText(item.getDisplayTitle());
|
||||
setAuthorView(item, author);
|
||||
imageView.setImageURI(item.getThumbUrl());
|
||||
return convertView;
|
||||
|
|
|
|||
|
|
@ -31,10 +31,6 @@ public interface DepictedImagesContract {
|
|||
*/
|
||||
void setAdapter(List<Media> mediaList);
|
||||
|
||||
/**
|
||||
* Seat caption to the image at the given position
|
||||
*/
|
||||
void handleLabelforImage(String caption, int position);
|
||||
|
||||
/**
|
||||
* Display snackbar
|
||||
|
|
@ -94,12 +90,6 @@ public interface DepictedImagesContract {
|
|||
*/
|
||||
void fetchMoreImages(String entityId);
|
||||
|
||||
/**
|
||||
* fetch captions for the image using filename and replace title of on the image thumbnail(if captions are available)
|
||||
* else show filename
|
||||
*/
|
||||
void replaceTitlesWithCaptions(String title, int position);
|
||||
|
||||
/**
|
||||
* add items to query list
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@ public class DepictedImagesFragment extends DaggerFragment implements DepictedIm
|
|||
private String entityId = null;
|
||||
private boolean isLastPage;
|
||||
private boolean isLoading = true;
|
||||
private int mediaSize = 0;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
|
|
@ -146,17 +145,6 @@ public class DepictedImagesFragment extends DaggerFragment implements DepictedIm
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Seat caption to the image at the given position
|
||||
*/
|
||||
@Override
|
||||
public void handleLabelforImage(String caption, int position) {
|
||||
if (!caption.trim().equals(getString(R.string.detail_caption_empty))) {
|
||||
gridAdapter.getItem(position).setThumbnailTitle(caption);
|
||||
gridAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display snackbar
|
||||
*/
|
||||
|
|
@ -257,11 +245,5 @@ public class DepictedImagesFragment extends DaggerFragment implements DepictedIm
|
|||
progressBar.setVisibility(GONE);
|
||||
isLoading = false;
|
||||
statusTextView.setVisibility(GONE);
|
||||
for (Media media : collection) {
|
||||
final String pageId = media.getPageId();
|
||||
if (pageId != null) {
|
||||
presenter.replaceTitlesWithCaptions(PAGE_ID_PREFIX + pageId, mediaSize++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
|||
|
||||
import android.annotation.SuppressLint;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.media.MediaClient;
|
||||
import io.reactivex.Scheduler;
|
||||
|
|
@ -27,7 +26,6 @@ public class DepictedImagesPresenter implements DepictedImagesContract.UserActio
|
|||
DepictedImagesContract.View.class.getClassLoader(),
|
||||
new Class[]{DepictedImagesContract.View.class},
|
||||
(proxy, method, methodArgs) -> null);
|
||||
DepictsClient depictsClient;
|
||||
MediaClient mediaClient;
|
||||
@Named("default_preferences")
|
||||
JsonKvStore depictionKvStore;
|
||||
|
|
@ -40,13 +38,13 @@ public class DepictedImagesPresenter implements DepictedImagesContract.UserActio
|
|||
* Ex: Q9394
|
||||
*/
|
||||
private List<Media> queryList = new ArrayList<>();
|
||||
private String entityId;
|
||||
|
||||
@Inject
|
||||
public DepictedImagesPresenter(@Named("default_preferences") JsonKvStore depictionKvStore, DepictsClient depictsClient, MediaClient mediaClient, @Named(IO_THREAD) Scheduler ioScheduler,
|
||||
@Named(MAIN_THREAD) Scheduler mainThreadScheduler) {
|
||||
public DepictedImagesPresenter(@Named("default_preferences") JsonKvStore depictionKvStore,
|
||||
MediaClient mediaClient,
|
||||
@Named(IO_THREAD) Scheduler ioScheduler,
|
||||
@Named(MAIN_THREAD) Scheduler mainThreadScheduler) {
|
||||
this.depictionKvStore = depictionKvStore;
|
||||
this.depictsClient = depictsClient;
|
||||
this.ioScheduler = ioScheduler;
|
||||
this.mainThreadScheduler = mainThreadScheduler;
|
||||
this.mediaClient = mediaClient;
|
||||
|
|
@ -68,11 +66,10 @@ public class DepictedImagesPresenter implements DepictedImagesContract.UserActio
|
|||
@SuppressLint("CheckResult")
|
||||
@Override
|
||||
public void initList(String entityId) {
|
||||
this.entityId = entityId;
|
||||
view.setLoadingStatus(true);
|
||||
view.progressBarVisible(true);
|
||||
view.setIsLastPage(false);
|
||||
compositeDisposable.add(depictsClient.fetchImagesForDepictedItem(entityId, 0)
|
||||
compositeDisposable.add(mediaClient.fetchImagesForDepictedItem(entityId, 0)
|
||||
.subscribeOn(ioScheduler)
|
||||
.observeOn(mainThreadScheduler)
|
||||
.subscribe(this::handleSuccess, this::handleError));
|
||||
|
|
@ -86,7 +83,7 @@ public class DepictedImagesPresenter implements DepictedImagesContract.UserActio
|
|||
@Override
|
||||
public void fetchMoreImages(String entityId) {
|
||||
view.progressBarVisible(true);
|
||||
compositeDisposable.add(depictsClient.fetchImagesForDepictedItem(entityId, queryList.size())
|
||||
compositeDisposable.add(mediaClient.fetchImagesForDepictedItem(entityId, queryList.size())
|
||||
.subscribeOn(ioScheduler)
|
||||
.observeOn(mainThreadScheduler)
|
||||
.subscribe(this::handlePaginationSuccess, this::handleError));
|
||||
|
|
@ -136,20 +133,6 @@ public class DepictedImagesPresenter implements DepictedImagesContract.UserActio
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch captions for the image using filename and replace title of on the image thumbnail(if captions are available)
|
||||
* else show filename
|
||||
*/
|
||||
@Override
|
||||
public void replaceTitlesWithCaptions(String wikibaseIdentifier, int position) {
|
||||
compositeDisposable.add(mediaClient.getCaptionByWikibaseIdentifier(wikibaseIdentifier)
|
||||
.subscribeOn(ioScheduler)
|
||||
.observeOn(mainThreadScheduler)
|
||||
.subscribe(caption -> {
|
||||
view.handleLabelforImage(caption, position);
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* add items to query list
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
package fr.free.nrw.commons.depictions.models;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Model class for list of depicted images obtained by fetching using depiction entity
|
||||
*/
|
||||
public class DepictionResponse {
|
||||
|
||||
@SerializedName("batchcomplete")
|
||||
@Expose
|
||||
private String batchcomplete;
|
||||
@SerializedName("continue")
|
||||
@Expose
|
||||
private Continue _continue;
|
||||
@SerializedName("query")
|
||||
@Expose
|
||||
private Query query;
|
||||
|
||||
/**
|
||||
* No args constructor for use in serialization
|
||||
*
|
||||
*/
|
||||
public DepictionResponse() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param query
|
||||
* @param batchcomplete
|
||||
* @param _continue
|
||||
*/
|
||||
public DepictionResponse(String batchcomplete, Continue _continue, Query query) {
|
||||
super();
|
||||
this.batchcomplete = batchcomplete;
|
||||
this._continue = _continue;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns batchcomplete string from DepictionResponse object
|
||||
*/
|
||||
public String getBatchcomplete() {
|
||||
return batchcomplete;
|
||||
}
|
||||
|
||||
public void setBatchcomplete(String batchcomplete) {
|
||||
this.batchcomplete = batchcomplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns continue object from DepictionResponse object
|
||||
*/
|
||||
public Continue getContinue() {
|
||||
return _continue;
|
||||
}
|
||||
|
||||
public void setContinue(Continue _continue) {
|
||||
this._continue = _continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns query object from DepictionResponse object
|
||||
*/
|
||||
public Query getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public void setQuery(Query query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -9,18 +9,12 @@ import android.view.MenuItem;
|
|||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.category.CategoryImagesCallback;
|
||||
|
|
@ -29,6 +23,8 @@ import fr.free.nrw.commons.explore.SearchActivity;
|
|||
import fr.free.nrw.commons.explore.ViewPagerAdapter;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This activity displays featured images and images uploaded via mobile
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.depictions.models.DepictionResponse
|
||||
import fr.free.nrw.commons.depictions.subClass.models.SparqlResponse
|
||||
import fr.free.nrw.commons.media.MediaInterface
|
||||
import fr.free.nrw.commons.upload.depicts.DepictsInterface
|
||||
|
|
@ -43,33 +40,6 @@ class DepictsClient @Inject constructor(
|
|||
.map { it.entities().values.map(::DepictedItem) }
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of images for a particular depict entity
|
||||
*/
|
||||
fun fetchImagesForDepictedItem(query: String, sroffset: Int): Observable<List<Media>> {
|
||||
return mediaInterface.fetchImagesForDepictedItem(
|
||||
"haswbstatement:" + BuildConfig.DEPICTS_PROPERTY + "=" + query,
|
||||
sroffset.toString()
|
||||
)
|
||||
.map { mwQueryResponse: DepictionResponse ->
|
||||
mwQueryResponse.query
|
||||
.search
|
||||
.map {
|
||||
Media(
|
||||
null,
|
||||
getUrl(it.title),
|
||||
it.title,
|
||||
"",
|
||||
0,
|
||||
safeParseDate(it.timestamp),
|
||||
safeParseDate(it.timestamp),
|
||||
""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getUrl(title: String): String {
|
||||
return getImageUrl(title, LARGE_IMAGE_SIZE)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package fr.free.nrw.commons.explore.depictions
|
|||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity
|
||||
import fr.free.nrw.commons.explore.BaseSearchFragment
|
||||
import fr.free.nrw.commons.explore.SearchFragmentContract
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,93 @@
|
|||
package fr.free.nrw.commons.explore.media
|
||||
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.location.LatLng
|
||||
import fr.free.nrw.commons.upload.structure.depictions.get
|
||||
import fr.free.nrw.commons.utils.CommonsDateUtil
|
||||
import fr.free.nrw.commons.utils.MediaDataExtractorUtil
|
||||
import fr.free.nrw.commons.wikidata.WikidataProperties
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryPage
|
||||
import org.wikipedia.gallery.ExtMetadata
|
||||
import org.wikipedia.wikidata.DataValue
|
||||
import org.wikipedia.wikidata.Entities
|
||||
import java.text.ParseException
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class MediaConverter @Inject constructor() {
|
||||
fun convert(mwQueryPage: MwQueryPage): Media = Media.from(mwQueryPage)
|
||||
fun convert(page: MwQueryPage, entity: Entities.Entity): Media {
|
||||
val imageInfo = page.imageInfo()
|
||||
requireNotNull(imageInfo) { "No image info" }
|
||||
val metadata = imageInfo.metadata
|
||||
requireNotNull(metadata) { "No metadata" }
|
||||
return Media(
|
||||
imageInfo.thumbUrl.takeIf { it.isNotBlank() } ?: imageInfo.originalUrl,
|
||||
imageInfo.originalUrl,
|
||||
page.title(),
|
||||
metadata.imageDescription(),
|
||||
safeParseDate(metadata.dateTime()),
|
||||
metadata.licenseShortName(),
|
||||
metadata.prefixedLicenseUrl,
|
||||
getArtist(metadata),
|
||||
page.pageId().toString(),
|
||||
MediaDataExtractorUtil.extractCategoriesFromList(metadata.categories),
|
||||
metadata.latLng,
|
||||
entity.labels().mapValues { it.value.value() },
|
||||
entity.descriptions().mapValues { it.value.value() },
|
||||
entity.depictionIds()
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creating Media object from MWQueryPage.
|
||||
* Earlier only basic details were set for the media object but going forward,
|
||||
* a full media object(with categories, descriptions, coordinates etc) can be constructed using this method
|
||||
*
|
||||
* @param page response from the API
|
||||
* @return Media object
|
||||
*/
|
||||
|
||||
private fun safeParseDate(dateStr: String): Date? {
|
||||
return try {
|
||||
CommonsDateUtil.getMediaSimpleDateFormat().parse(dateStr)
|
||||
} catch (e: ParseException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method extracts the Commons Username from the artist HTML information
|
||||
* @param metadata
|
||||
* @return
|
||||
*/
|
||||
private fun getArtist(metadata: ExtMetadata): String? {
|
||||
return try {
|
||||
val artistHtml = metadata.artist()
|
||||
artistHtml.substring(artistHtml.indexOf("title=\""), artistHtml.indexOf("\">"))
|
||||
.replace("title=\"User:", "")
|
||||
} catch (ex: java.lang.Exception) {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Entities.Entity.depictionIds() =
|
||||
this[WikidataProperties.DEPICTS]?.mapNotNull { (it.mainSnak.dataValue as? DataValue.EntityId)?.value?.id }
|
||||
?: emptyList()
|
||||
|
||||
private val ExtMetadata.prefixedLicenseUrl: String
|
||||
get() = licenseUrl().let {
|
||||
if (!it.startsWith("http://") && !it.startsWith("https://"))
|
||||
"https://$it"
|
||||
else
|
||||
it
|
||||
}
|
||||
|
||||
private val ExtMetadata.latLng: LatLng?
|
||||
get() = if (!StringUtils.isBlank(gpsLatitude) && !StringUtils.isBlank(gpsLongitude))
|
||||
LatLng(gpsLatitude.toDouble(), gpsLongitude.toDouble(), 0.0f)
|
||||
else
|
||||
null
|
||||
|
|
|
|||
|
|
@ -1,34 +1,17 @@
|
|||
package fr.free.nrw.commons.explore.media
|
||||
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX
|
||||
import fr.free.nrw.commons.explore.LiveDataConverter
|
||||
import fr.free.nrw.commons.explore.PageableDataSource
|
||||
import fr.free.nrw.commons.explore.depictions.LoadFunction
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import fr.free.nrw.commons.media.MediaClient.Companion.NO_CAPTION
|
||||
import javax.inject.Inject
|
||||
|
||||
class PageableMediaDataSource @Inject constructor(
|
||||
liveDataConverter: LiveDataConverter,
|
||||
private val mediaConverter: MediaConverter,
|
||||
private val mediaClient: MediaClient
|
||||
) : PageableDataSource<Media>(liveDataConverter) {
|
||||
override val loadFunction: LoadFunction<Media> = { loadSize: Int, startPosition: Int ->
|
||||
mediaClient.getMediaListFromSearch(query, loadSize, startPosition)
|
||||
.map { it.query()?.pages()?.map(mediaConverter::convert) ?: emptyList() }
|
||||
.map { it.zip(getCaptions(it)) }
|
||||
.map { it.map { (media, caption) -> media.also { it.caption = caption } } }
|
||||
.blockingGet()
|
||||
mediaClient.getMediaListFromSearch(query, loadSize, startPosition).blockingGet()
|
||||
}
|
||||
|
||||
private fun getCaptions(it: List<Media>) =
|
||||
mediaClient.getEntities(it.joinToString("|") { PAGE_ID_PREFIX + it.pageId })
|
||||
.map {
|
||||
it.entities().values.map { entity ->
|
||||
entity.labels().values.firstOrNull()?.value() ?: NO_CAPTION
|
||||
}
|
||||
}
|
||||
.blockingGet()
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class SearchImagesViewHolder(containerView: View, val onImageClicked: (Int) -> U
|
|||
override fun bind(item: Pair<Media, Int>) {
|
||||
val media = item.first
|
||||
categoryImageView.setOnClickListener { onImageClicked(item.second) }
|
||||
categoryImageTitle.text = media.thumbnailTitle
|
||||
categoryImageTitle.text = media.mostRelevantCaption
|
||||
categoryImageView.setImageURI(media.thumbUrl)
|
||||
if (media.creator?.isNotEmpty() == true) {
|
||||
categoryImageAuthor.visibility = View.VISIBLE
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
package fr.free.nrw.commons.media
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.WorkerThread
|
||||
import fr.free.nrw.commons.wikidata.WikidataProperties.DEPICTS
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import org.wikipedia.wikidata.DataValue.EntityId
|
||||
import org.wikipedia.wikidata.Entities
|
||||
import java.util.*
|
||||
|
||||
@Parcelize
|
||||
data class Depictions(val depictions: List<IdAndLabel>) : Parcelable {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@WorkerThread
|
||||
fun from(entities: Entities, mediaClient: MediaClient) =
|
||||
Depictions(
|
||||
entities.first?.statements
|
||||
?.getOrElse(DEPICTS.propertyName, { emptyList() })
|
||||
?.map { statement ->
|
||||
(statement.mainSnak.dataValue as EntityId).value.id
|
||||
}
|
||||
?.map { id -> IdAndLabel(id, fetchLabel(mediaClient, id)) }
|
||||
?: emptyList()
|
||||
)
|
||||
|
||||
private fun fetchLabel(mediaClient: MediaClient, id: String) =
|
||||
mediaClient.getLabelForDepiction(id, Locale.getDefault().language).blockingGet()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package fr.free.nrw.commons.media
|
||||
|
||||
data class IdAndCaptions(val id: String, val captions: Map<String, String>)
|
||||
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package fr.free.nrw.commons.media
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import org.wikipedia.wikidata.Entities
|
||||
|
||||
@Parcelize
|
||||
data class IdAndLabel(val entityId: String, val entityLabel: String) : Parcelable {
|
||||
constructor(entityId: String, entities: MutableMap<String, Entities.Entity>) : this(
|
||||
entityId,
|
||||
entities.values.first().labels().values.first().value()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1,18 +1,17 @@
|
|||
package fr.free.nrw.commons.media
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.media.Depictions.Companion.from
|
||||
import fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX
|
||||
import fr.free.nrw.commons.explore.media.MediaConverter
|
||||
import fr.free.nrw.commons.utils.CommonsDateUtil
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryPage
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
||||
import org.wikipedia.wikidata.Entities
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
/**
|
||||
* Media Client to handle custom calls to Commons MediaWiki APIs
|
||||
|
|
@ -21,12 +20,16 @@ import kotlin.collections.ArrayList
|
|||
class MediaClient @Inject constructor(
|
||||
private val mediaInterface: MediaInterface,
|
||||
private val pageMediaInterface: PageMediaInterface,
|
||||
private val mediaDetailInterface: MediaDetailInterface
|
||||
private val mediaDetailInterface: MediaDetailInterface,
|
||||
private val mediaConverter: MediaConverter
|
||||
) {
|
||||
|
||||
fun getMediaById(id: String) =
|
||||
responseToMediaList(mediaInterface.getMediaById(id)).map { it.first() }
|
||||
|
||||
//OkHttpJsonApiClient used JsonKvStore for this. I don't know why.
|
||||
private val continuationStore: MutableMap<String, Map<String, String>?>
|
||||
private val continuationExists: MutableMap<String, Boolean>
|
||||
private val continuationStore: MutableMap<String, Map<String, String>?> = mutableMapOf()
|
||||
private val continuationExists: MutableMap<String, Boolean> = mutableMapOf()
|
||||
|
||||
/**
|
||||
* Checks if a page exists on Commons
|
||||
|
|
@ -36,11 +39,7 @@ class MediaClient @Inject constructor(
|
|||
*/
|
||||
fun checkPageExistsUsingTitle(title: String?): Single<Boolean> {
|
||||
return mediaInterface.checkPageExistsUsingTitle(title)
|
||||
.map { mwQueryResponse: MwQueryResponse ->
|
||||
mwQueryResponse
|
||||
.query()!!.firstPage()!!.pageId() > 0
|
||||
}
|
||||
.singleOrError()
|
||||
.map { it.query()!!.firstPage()!!.pageId() > 0 }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -50,11 +49,7 @@ class MediaClient @Inject constructor(
|
|||
*/
|
||||
fun checkFileExistsUsingSha(fileSha: String?): Single<Boolean> {
|
||||
return mediaInterface.checkFileExistsUsingSha(fileSha)
|
||||
.map { mwQueryResponse: MwQueryResponse ->
|
||||
mwQueryResponse
|
||||
.query()!!.allImages().size > 0
|
||||
}
|
||||
.singleOrError()
|
||||
.map { it.query()!!.allImages().size > 0 }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -66,18 +61,13 @@ class MediaClient @Inject constructor(
|
|||
*/
|
||||
fun getMediaListFromCategory(category: String): Single<List<Media>> {
|
||||
return responseToMediaList(
|
||||
if (continuationStore.containsKey("category_$category")) mediaInterface.getMediaListFromCategory(
|
||||
mediaInterface.getMediaListFromCategory(
|
||||
category,
|
||||
10,
|
||||
continuationStore["category_$category"]
|
||||
) else //if true
|
||||
mediaInterface.getMediaListFromCategory(
|
||||
category,
|
||||
10,
|
||||
emptyMap()
|
||||
),
|
||||
continuationStore["category_$category"] ?: emptyMap()
|
||||
),
|
||||
"category_$category"
|
||||
) //if false
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -89,25 +79,16 @@ class MediaClient @Inject constructor(
|
|||
* @return
|
||||
*/
|
||||
fun getMediaListForUser(userName: String): Single<List<Media>> {
|
||||
val continuation =
|
||||
if (continuationStore.containsKey("user_$userName")) continuationStore["user_$userName"] else emptyMap()
|
||||
return responseToMediaList(
|
||||
mediaInterface
|
||||
.getMediaListForUser(userName, 10, continuation), "user_$userName"
|
||||
mediaInterface.getMediaListForUser(
|
||||
userName,
|
||||
10,
|
||||
continuationStore["user_$userName"] ?: Collections.emptyMap()
|
||||
),
|
||||
"user_$userName"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if media for user has reached the end of the list.
|
||||
* @param userName
|
||||
* @return
|
||||
*/
|
||||
fun doesMediaListForUserHaveMorePages(userName: String): Boolean {
|
||||
val key = "user_$userName"
|
||||
return if (continuationExists.containsKey(key)) {
|
||||
continuationExists[key]!!
|
||||
} else true
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes a keyword as input and returns a list of Media objects filtered using image generator query
|
||||
|
|
@ -118,40 +99,45 @@ class MediaClient @Inject constructor(
|
|||
* @param offset
|
||||
* @return
|
||||
*/
|
||||
fun getMediaListFromSearch(
|
||||
keyword: String?,
|
||||
limit: Int,
|
||||
offset: Int
|
||||
): Single<MwQueryResponse> {
|
||||
return mediaInterface.getMediaListFromSearch(keyword, limit, offset)
|
||||
fun getMediaListFromSearch(keyword: String?, limit: Int, offset: Int) =
|
||||
responseToMediaList(mediaInterface.getMediaListFromSearch(keyword, limit, offset))
|
||||
|
||||
/**
|
||||
* @return list of images for a particular depict entity
|
||||
*/
|
||||
fun fetchImagesForDepictedItem(query: String, sroffset: Int): Single<List<Media>> {
|
||||
return responseToMediaList(
|
||||
mediaInterface.fetchImagesForDepictedItem(
|
||||
"haswbstatement:" + BuildConfig.DEPICTS_PROPERTY + "=" + query,
|
||||
sroffset.toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private fun responseToMediaList(
|
||||
response: Observable<MwQueryResponse>,
|
||||
key: String
|
||||
response: Single<MwQueryResponse>,
|
||||
key: String? = null
|
||||
): Single<List<Media>> {
|
||||
return response.flatMap { mwQueryResponse: MwQueryResponse? ->
|
||||
if (null == mwQueryResponse || null == mwQueryResponse.query() || null == mwQueryResponse.query()!!
|
||||
.pages()
|
||||
) {
|
||||
return@flatMap Observable.empty<MwQueryPage>()
|
||||
return response.map {
|
||||
if (key != null) {
|
||||
continuationExists[key] =
|
||||
it.continuation()?.let { continuation ->
|
||||
continuationStore[key] = continuation
|
||||
true
|
||||
} ?: false
|
||||
}
|
||||
if (mwQueryResponse.continuation() != null) {
|
||||
continuationStore[key] = mwQueryResponse.continuation()
|
||||
continuationExists[key] = true
|
||||
} else {
|
||||
continuationExists[key] = false
|
||||
it.query()?.pages() ?: emptyList()
|
||||
}.flatMap(::mediaFromPageAndEntity)
|
||||
|
||||
}
|
||||
|
||||
private fun mediaFromPageAndEntity(pages: List<MwQueryPage>): Single<List<Media>> {
|
||||
return getEntities(pages.map { "$PAGE_ID_PREFIX${it.pageId()}" })
|
||||
.map {
|
||||
pages.zip(it.entities().values)
|
||||
.map { (page, entity) -> mediaConverter.convert(page, entity) }
|
||||
}
|
||||
Observable.fromIterable(mwQueryResponse.query()!!.pages())
|
||||
}
|
||||
.map { page: MwQueryPage? -> Media.from(page) }
|
||||
.collect(
|
||||
{ ArrayList() }
|
||||
) { obj: MutableList<Media>, e: Media ->
|
||||
obj.add(
|
||||
e
|
||||
)
|
||||
}.map { it.toList() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -161,17 +147,8 @@ class MediaClient @Inject constructor(
|
|||
* @return
|
||||
*/
|
||||
fun getMedia(titles: String?): Single<Media> {
|
||||
return mediaInterface.getMedia(titles)
|
||||
.flatMap { mwQueryResponse: MwQueryResponse? ->
|
||||
if (null == mwQueryResponse || null == mwQueryResponse.query() || null == mwQueryResponse.query()!!
|
||||
.firstPage()
|
||||
) {
|
||||
return@flatMap Observable.empty<MwQueryPage>()
|
||||
}
|
||||
Observable.just(mwQueryResponse.query()!!.firstPage())
|
||||
}
|
||||
.map { page: MwQueryPage? -> Media.from(page) }
|
||||
.single(Media.EMPTY)
|
||||
return responseToMediaList(mediaInterface.getMedia(titles))
|
||||
.map { it.first() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -179,122 +156,37 @@ class MediaClient @Inject constructor(
|
|||
*
|
||||
* @return Media object corresponding to the picture of the day
|
||||
*/
|
||||
val pictureOfTheDay: Single<Media>
|
||||
get() {
|
||||
val date =
|
||||
CommonsDateUtil.getIso8601DateFormatShort().format(Date())
|
||||
Timber.d("Current date is %s", date)
|
||||
val template = "Template:Potd/$date"
|
||||
return mediaInterface.getMediaWithGenerator(template)
|
||||
.flatMap { mwQueryResponse: MwQueryResponse? ->
|
||||
if (null == mwQueryResponse || null == mwQueryResponse.query() || null == mwQueryResponse.query()!!
|
||||
.firstPage()
|
||||
) {
|
||||
return@flatMap Observable.empty<MwQueryPage>()
|
||||
}
|
||||
Observable.just(mwQueryResponse.query()!!.firstPage())
|
||||
}
|
||||
.map { page: MwQueryPage? -> Media.from(page) }
|
||||
.single(Media.EMPTY)
|
||||
}
|
||||
fun getPictureOfTheDay(): Single<Media> {
|
||||
val date = CommonsDateUtil.getIso8601DateFormatShort().format(Date())
|
||||
return responseToMediaList(mediaInterface.getMediaWithGenerator("Template:Potd/$date")).map { it.first() }
|
||||
|
||||
}
|
||||
|
||||
fun getPageHtml(title: String?): Single<String> {
|
||||
return mediaInterface.getPageHtml(title)
|
||||
.filter { obj: MwParseResponse -> obj.success() }
|
||||
.map { obj: MwParseResponse -> obj.parse() }
|
||||
.map { obj: MwParseResult? -> obj!!.text() }
|
||||
.first("")
|
||||
.map { obj: MwParseResponse -> obj.parse()?.text() ?: "" }
|
||||
}
|
||||
|
||||
fun getEntities(entityIds: List<String>): Single<Entities> {
|
||||
return if (entityIds.isEmpty())
|
||||
Single.error(Exception("empty list passed for ids"))
|
||||
else
|
||||
mediaDetailInterface.getEntity(entityIds.joinToString("|"))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return caption for image using wikibaseIdentifier
|
||||
* Check if media for user has reached the end of the list.
|
||||
* @param userName
|
||||
* @return
|
||||
*/
|
||||
fun getCaptionByWikibaseIdentifier(wikibaseIdentifier: String?): Single<String> {
|
||||
return mediaDetailInterface.getEntityForImage(
|
||||
Locale.getDefault().language,
|
||||
wikibaseIdentifier
|
||||
)
|
||||
.map { mediaDetailResponse: Entities ->
|
||||
if (isSuccess(mediaDetailResponse)) {
|
||||
for (wikibaseItem in mediaDetailResponse.entities().values) {
|
||||
for (label in wikibaseItem.labels().values) {
|
||||
return@map label.value()
|
||||
}
|
||||
}
|
||||
}
|
||||
NO_CAPTION
|
||||
}
|
||||
.singleOrError()
|
||||
fun doesMediaListForUserHaveMorePages(userName: String): Boolean {
|
||||
val key = "user_$userName"
|
||||
return if (continuationExists.containsKey(key)) continuationExists[key]!! else true
|
||||
}
|
||||
|
||||
fun doesPageContainMedia(title: String?): Single<Boolean> {
|
||||
return pageMediaInterface.getMediaList(title)
|
||||
.map { it.items.isNotEmpty() }
|
||||
}
|
||||
|
||||
private fun isSuccess(response: Entities?): Boolean {
|
||||
return response != null && response.success == 1 && response.entities() != null
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches Structured data from API
|
||||
*
|
||||
* @param filename
|
||||
* @return a map containing caption and depictions (empty string in the map if no caption/depictions)
|
||||
*/
|
||||
fun getDepictions(filename: String?): Single<Depictions> {
|
||||
return mediaDetailInterface.fetchEntitiesByFileName(
|
||||
Locale.getDefault().language, filename
|
||||
)
|
||||
.map { entities: Entities? ->
|
||||
from(
|
||||
entities!!,
|
||||
this
|
||||
)
|
||||
}
|
||||
.singleOrError()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets labels for Depictions using Entity Id from MediaWikiAPI
|
||||
*
|
||||
* @param entityId EntityId (Ex: Q81566) of the depict entity
|
||||
* @return label
|
||||
*/
|
||||
fun getLabelForDepiction(
|
||||
entityId: String?,
|
||||
language: String
|
||||
): Single<String> {
|
||||
return mediaDetailInterface.getEntity(entityId)
|
||||
.map { entities: Entities ->
|
||||
if (isSuccess(entities)) {
|
||||
for (entity in entities.entities().values) {
|
||||
val languageToLabelMap =
|
||||
entity.labels()
|
||||
if (languageToLabelMap.containsKey(language)) {
|
||||
return@map languageToLabelMap[language]!!.value()
|
||||
}
|
||||
for (label in languageToLabelMap.values) {
|
||||
return@map label.value()
|
||||
}
|
||||
}
|
||||
}
|
||||
throw RuntimeException("failed getEntities")
|
||||
}
|
||||
}
|
||||
|
||||
fun getEntities(entityId: String?): Single<Entities> {
|
||||
return mediaDetailInterface.getEntity(entityId)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NO_CAPTION = "No caption"
|
||||
private const val NO_DEPICTION = "No depiction"
|
||||
}
|
||||
|
||||
init {
|
||||
continuationStore =
|
||||
HashMap()
|
||||
continuationExists = HashMap()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,11 +54,12 @@ import fr.free.nrw.commons.ui.widget.HtmlTextView;
|
|||
import fr.free.nrw.commons.utils.ViewUtilWrapper;
|
||||
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.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.wikipedia.util.DateUtil;
|
||||
|
|
@ -70,7 +71,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
private boolean isCategoryImage;
|
||||
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
|
||||
private int index;
|
||||
private Locale locale;
|
||||
private boolean isDeleted = false;
|
||||
|
||||
|
||||
|
|
@ -141,7 +141,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
@BindView(R.id.mediaDetailScrollView)
|
||||
ScrollView scrollView;
|
||||
|
||||
private ArrayList<String> categoryNames;
|
||||
/**
|
||||
* 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
|
||||
|
|
@ -150,10 +149,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
private ImageInfo imageInfoCache;
|
||||
private int oldWidthOfImageView;
|
||||
private int newWidthOfImageView;
|
||||
private Depictions depictions;
|
||||
private boolean categoriesLoaded = false;
|
||||
private boolean categoriesPresent = false;
|
||||
private boolean depictionLoaded = false;
|
||||
private boolean heightVerifyingBoolean = true; // helps in maintaining aspect ratio
|
||||
private ViewTreeObserver.OnGlobalLayoutListener layoutListener; // for layout stuff, only used once!
|
||||
|
||||
|
|
@ -203,9 +198,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
reasonList.add(getString(R.string.deletion_reason_no_longer_want_public));
|
||||
reasonList.add(getString(R.string.deletion_reason_bad_for_my_privacy));
|
||||
|
||||
categoryNames = new ArrayList<>();
|
||||
categoryNames.add(getString(R.string.detail_panel_cats_loading));
|
||||
|
||||
final View view = inflater.inflate(R.layout.fragment_media_detail, container, false);
|
||||
|
||||
ButterKnife.bind(this,view);
|
||||
|
|
@ -217,7 +209,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
authorLayout.setVisibility(GONE);
|
||||
}
|
||||
|
||||
locale = getResources().getConfiguration().locale;
|
||||
return view;
|
||||
}
|
||||
|
||||
|
|
@ -291,19 +282,55 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
}
|
||||
|
||||
private void displayMediaDetails() {
|
||||
//Always load image from Internet to allow viewing the desc, license, and cats
|
||||
setupImageView();
|
||||
title.setText(media.getDisplayTitle());
|
||||
desc.setHtmlText(media.getDescription());
|
||||
license.setText(media.getLicense());
|
||||
|
||||
Disposable disposable = mediaDataExtractor.fetchMediaDetails(media.getFilename(), media.getPageId())
|
||||
setTextFields(media);
|
||||
compositeDisposable.addAll(
|
||||
mediaDataExtractor.fetchDepictionIdsAndLabels(media)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::setTextFields);
|
||||
compositeDisposable.add(disposable);
|
||||
.subscribe(this::onDepictionsLoaded, Timber::e),
|
||||
mediaDataExtractor.checkDeletionRequestExists(media)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::onDeletionPageExists, Timber::e),
|
||||
mediaDataExtractor.fetchDiscussion(media)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::onDiscussionLoaded, Timber::e),
|
||||
mediaDataExtractor.refresh(media)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::onMediaRefreshed, Timber::e)
|
||||
);
|
||||
}
|
||||
|
||||
private void onMediaRefreshed(Media media) {
|
||||
setTextFields(media);
|
||||
compositeDisposable.addAll(
|
||||
mediaDataExtractor.fetchDepictionIdsAndLabels(media)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::onDepictionsLoaded, Timber::e)
|
||||
);
|
||||
}
|
||||
|
||||
private void onDiscussionLoaded(String discussion) {
|
||||
mediaDiscussion.setText(prettyDiscussion(discussion.trim()));
|
||||
}
|
||||
|
||||
private void onDeletionPageExists(Boolean deletionPageExists) {
|
||||
if (deletionPageExists){
|
||||
delete.setVisibility(GONE);
|
||||
nominatedForDeletion.setVisibility(VISIBLE);
|
||||
} else if (!isCategoryImage) {
|
||||
delete.setVisibility(VISIBLE);
|
||||
nominatedForDeletion.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDepictionsLoaded(List<IdAndCaptions> idAndCaptions){
|
||||
depictsLayout.setVisibility(idAndCaptions.isEmpty() ? GONE : VISIBLE);
|
||||
buildDepictionList(idAndCaptions);
|
||||
}
|
||||
/**
|
||||
* The imageSpacer is Basically a transparent overlay for the SimpleDraweeView
|
||||
* which holds the image to be displayed( moreover this image is out of
|
||||
|
|
@ -370,58 +397,45 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
}
|
||||
|
||||
private void setTextFields(Media media) {
|
||||
this.media = media;
|
||||
setupImageView();
|
||||
title.setText(media.getDisplayTitle());
|
||||
desc.setHtmlText(prettyDescription(media));
|
||||
license.setText(prettyLicense(media));
|
||||
coordinates.setText(prettyCoordinates(media));
|
||||
uploadedDate.setText(prettyUploadedDate(media));
|
||||
mediaDiscussion.setText(prettyDiscussion(media));
|
||||
if (prettyCaption(media).equals(getContext().getString(R.string.detail_caption_empty))) {
|
||||
captionLayout.setVisibility(GONE);
|
||||
} else mediaCaption.setText(prettyCaption(media));
|
||||
} else {
|
||||
mediaCaption.setText(prettyCaption(media));
|
||||
}
|
||||
|
||||
|
||||
categoryNames.clear();
|
||||
categoryNames.addAll(media.getCategories());
|
||||
|
||||
depictions=media.getDepiction();
|
||||
|
||||
depictionLoaded = true;
|
||||
|
||||
categoriesLoaded = true;
|
||||
categoriesPresent = (categoryNames.size() > 0);
|
||||
if (!categoriesPresent) {
|
||||
final List<String> categories = media.getCategories();
|
||||
if (categories.isEmpty()) {
|
||||
// Stick in a filler element.
|
||||
categoryNames.add(getString(R.string.detail_panel_cats_none));
|
||||
categories.add(getString(R.string.detail_panel_cats_none));
|
||||
}
|
||||
|
||||
rebuildCatList();
|
||||
rebuildCatList(categories);
|
||||
|
||||
|
||||
if(depictions != null) {
|
||||
rebuildDepictionList();
|
||||
}
|
||||
else depictsLayout.setVisibility(GONE);
|
||||
|
||||
if (media.getCreator() == null || media.getCreator().equals("")) {
|
||||
authorLayout.setVisibility(GONE);
|
||||
} else {
|
||||
author.setText(media.getCreator());
|
||||
}
|
||||
|
||||
checkDeletion(media);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates media details fragment with depiction list
|
||||
* @param idAndCaptions
|
||||
*/
|
||||
private void rebuildDepictionList() {
|
||||
private void buildDepictionList(List<IdAndCaptions> idAndCaptions) {
|
||||
depictionContainer.removeAllViews();
|
||||
for (IdAndLabel depiction : depictions.getDepictions()) {
|
||||
depictionContainer.addView(
|
||||
buildDepictLabel(
|
||||
depiction.getEntityLabel(),
|
||||
depiction.getEntityId(),
|
||||
for (IdAndCaptions idAndCaption : idAndCaptions) {
|
||||
depictionContainer.addView(buildDepictLabel(
|
||||
idAndCaption.getCaptions().values().iterator().next(),
|
||||
idAndCaption.getId(),
|
||||
depictionContainer
|
||||
));
|
||||
}
|
||||
|
|
@ -446,7 +460,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
|
||||
@OnClick(R.id.copyWikicode)
|
||||
public void onCopyWikicodeClicked(){
|
||||
String data = "[[" + media.getFilename() + "|thumb|" + media.getDescription() + "]]";
|
||||
String data = "[[" + media.getFilename() + "|thumb|" + media.getFallbackDescription() + "]]";
|
||||
Utils.copy("wikiCode",data,getContext());
|
||||
Timber.d("Generated wikidata copy code: %s", data);
|
||||
|
||||
|
|
@ -573,42 +587,37 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
}
|
||||
}
|
||||
|
||||
private void rebuildCatList() {
|
||||
private void rebuildCatList(List<String> categories) {
|
||||
categoryContainer.removeAllViews();
|
||||
// @fixme add the category items
|
||||
|
||||
//As per issue #1826(see https://github.com/commons-app/apps-android-commons/issues/1826), some categories come suffixed with strings prefixed with |. As per the discussion
|
||||
//that was meant for alphabetical sorting of the categories and can be safely removed.
|
||||
for (int i = 0; i < categoryNames.size(); i++) {
|
||||
String categoryName = categoryNames.get(i);
|
||||
//Removed everything after '|'
|
||||
int indexOfPipe = categoryName.indexOf('|');
|
||||
if (indexOfPipe != -1) {
|
||||
categoryName = categoryName.substring(0, indexOfPipe);
|
||||
//Set the updated category to the list as well
|
||||
categoryNames.set(i, categoryName);
|
||||
}
|
||||
View catLabel = buildCatLabel(categoryName, categoryContainer);
|
||||
categoryContainer.addView(catLabel);
|
||||
for (String category : categories) {
|
||||
categoryContainer.addView(buildCatLabel(sanitise(category), categoryContainer));
|
||||
}
|
||||
}
|
||||
|
||||
//As per issue #1826(see https://github.com/commons-app/apps-android-commons/issues/1826), some categories come suffixed with strings prefixed with |. As per the discussion
|
||||
//that was meant for alphabetical sorting of the categories and can be safely removed.
|
||||
private String sanitise(String category) {
|
||||
int indexOfPipe = category.indexOf('|');
|
||||
if (indexOfPipe != -1) {
|
||||
//Removed everything after '|'
|
||||
return category.substring(0, indexOfPipe);
|
||||
}
|
||||
return category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add view to depictions obtained also tapping on depictions should open the url
|
||||
*/
|
||||
private View buildDepictLabel(String depictionName, String entityId, LinearLayout depictionContainer) {
|
||||
final View item = LayoutInflater.from(getContext()).inflate(R.layout.detail_category_item, depictionContainer, false);
|
||||
final View item = LayoutInflater.from(getContext()).inflate(R.layout.detail_category_item, depictionContainer,false);
|
||||
final TextView textView = item.findViewById(R.id.mediaDetailCategoryItemText);
|
||||
|
||||
textView.setText(depictionName);
|
||||
if (depictionLoaded) {
|
||||
item.setOnClickListener(view -> {
|
||||
Intent intent = new Intent(getContext(), WikidataItemDetailsActivity.class);
|
||||
intent.putExtra("wikidataItemName", depictionName);
|
||||
intent.putExtra("entityId", entityId);
|
||||
getContext().startActivity(intent);
|
||||
});
|
||||
}
|
||||
item.setOnClickListener(view -> {
|
||||
Intent intent = new Intent(getContext(), WikidataItemDetailsActivity.class);
|
||||
intent.putExtra("wikidataItemName", depictionName);
|
||||
intent.putExtra("entityId", entityId);
|
||||
getContext().startActivity(intent);
|
||||
});
|
||||
return item;
|
||||
}
|
||||
|
||||
|
|
@ -617,7 +626,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
final TextView textView = item.findViewById(R.id.mediaDetailCategoryItemText);
|
||||
|
||||
textView.setText(catName);
|
||||
if (categoriesLoaded && categoriesPresent) {
|
||||
if(!getString(R.string.detail_panel_cats_none).equals(catName)) {
|
||||
textView.setOnClickListener(view -> {
|
||||
// Open Category Details page
|
||||
String selectedCategoryTitle = CATEGORY_PREFIX + catName;
|
||||
|
|
@ -636,30 +645,36 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
* @return caption as string
|
||||
*/
|
||||
private String prettyCaption(Media media) {
|
||||
String caption = media.getCaption().trim();
|
||||
if (caption.equals("")) {
|
||||
return getString(R.string.detail_caption_empty);
|
||||
} else {
|
||||
return caption;
|
||||
for (String caption : media.getCaptions().values()) {
|
||||
if (caption.equals("")) {
|
||||
return getString(R.string.detail_caption_empty);
|
||||
} else {
|
||||
return caption;
|
||||
}
|
||||
}
|
||||
return getString(R.string.detail_caption_empty);
|
||||
}
|
||||
|
||||
private String prettyDescription(Media media) {
|
||||
// @todo use UI language when multilingual descs are available
|
||||
String desc = media.getDescription();
|
||||
if (desc.equals("")) {
|
||||
return getString(R.string.detail_description_empty);
|
||||
} else {
|
||||
return desc;
|
||||
}
|
||||
final String description = chooseDescription(media);
|
||||
return description.isEmpty() ? getString(R.string.detail_description_empty)
|
||||
: description;
|
||||
}
|
||||
private String prettyDiscussion(Media media) {
|
||||
String disc = media.getDiscussion().trim();
|
||||
if (disc.equals("")) {
|
||||
return getString(R.string.detail_discussion_empty);
|
||||
} else {
|
||||
return disc;
|
||||
|
||||
private String chooseDescription(Media media) {
|
||||
final Map<String, String> descriptions = media.getDescriptions();
|
||||
final String multilingualDesc = descriptions.get(Locale.getDefault().getLanguage());
|
||||
if (multilingualDesc != null) {
|
||||
return multilingualDesc;
|
||||
}
|
||||
for (String description : descriptions.values()) {
|
||||
return description;
|
||||
}
|
||||
return media.getFallbackDescription();
|
||||
}
|
||||
|
||||
private String prettyDiscussion(String discussion) {
|
||||
return discussion.isEmpty() ? getString(R.string.detail_discussion_empty) : discussion;
|
||||
}
|
||||
|
||||
private String prettyLicense(Media media) {
|
||||
|
|
@ -691,14 +706,4 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
return media.getCoordinates().getPrettyCoordinateString();
|
||||
}
|
||||
|
||||
private void checkDeletion(Media media){
|
||||
if (media.isRequestedDeletion()){
|
||||
delete.setVisibility(GONE);
|
||||
nominatedForDeletion.setVisibility(VISIBLE);
|
||||
} else if (!isCategoryImage) {
|
||||
delete.setVisibility(VISIBLE);
|
||||
nominatedForDeletion.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package fr.free.nrw.commons.media;
|
||||
|
||||
import fr.free.nrw.commons.depictions.models.DepictionResponse;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
import java.util.Map;
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||
|
|
@ -24,7 +22,7 @@ public interface MediaInterface {
|
|||
* @return
|
||||
*/
|
||||
@GET("w/api.php?action=query&format=json&formatversion=2")
|
||||
Observable<MwQueryResponse> checkPageExistsUsingTitle(@Query("titles") String title);
|
||||
Single<MwQueryResponse> checkPageExistsUsingTitle(@Query("titles") String title);
|
||||
|
||||
/**
|
||||
* Check if file exists
|
||||
|
|
@ -33,7 +31,7 @@ public interface MediaInterface {
|
|||
* @return
|
||||
*/
|
||||
@GET("w/api.php?action=query&format=json&formatversion=2&list=allimages")
|
||||
Observable<MwQueryResponse> checkFileExistsUsingSha(@Query("aisha1") String aisha1);
|
||||
Single<MwQueryResponse> checkFileExistsUsingSha(@Query("aisha1") String aisha1);
|
||||
|
||||
/**
|
||||
* This method retrieves a list of Media objects filtered using image generator query
|
||||
|
|
@ -46,7 +44,8 @@ public interface MediaInterface {
|
|||
@GET("w/api.php?action=query&format=json&formatversion=2" + //Basic parameters
|
||||
"&generator=categorymembers&gcmtype=file&gcmsort=timestamp&gcmdir=desc" + //Category parameters
|
||||
MEDIA_PARAMS)
|
||||
Observable<MwQueryResponse> getMediaListFromCategory(@Query("gcmtitle") String category, @Query("gcmlimit") int itemLimit, @QueryMap Map<String, String> continuation);
|
||||
Single<MwQueryResponse> getMediaListFromCategory(@Query("gcmtitle") String category, @Query("gcmlimit") int itemLimit, @QueryMap Map<String, String> continuation);
|
||||
|
||||
|
||||
/**
|
||||
* This method retrieves a list of Media objects for a given user name
|
||||
|
|
@ -58,7 +57,7 @@ public interface MediaInterface {
|
|||
*/
|
||||
@GET("w/api.php?action=query&format=json&formatversion=2" + //Basic parameters
|
||||
"&generator=allimages&gaisort=timestamp&gaidir=older" + MEDIA_PARAMS)
|
||||
Observable<MwQueryResponse> getMediaListForUser(@Query("gaiuser") String username,
|
||||
Single<MwQueryResponse> getMediaListForUser(@Query("gaiuser") String username,
|
||||
@Query("gailimit") int itemLimit, @QueryMap(encoded = true) Map<String, String> continuation);
|
||||
|
||||
/**
|
||||
|
|
@ -82,7 +81,17 @@ public interface MediaInterface {
|
|||
*/
|
||||
@GET("w/api.php?action=query&format=json&formatversion=2" +
|
||||
MEDIA_PARAMS)
|
||||
Observable<MwQueryResponse> getMedia(@Query("titles") String title);
|
||||
Single<MwQueryResponse> getMedia(@Query("titles") String title);
|
||||
|
||||
/**
|
||||
* Fetches Media object from the imageInfo API
|
||||
*
|
||||
* @param pageIds the ids to be searched for
|
||||
* @return
|
||||
*/
|
||||
@GET("w/api.php?action=query&format=json&formatversion=2" +
|
||||
MEDIA_PARAMS)
|
||||
Single<MwQueryResponse> getMediaById(@Query("pageids") String pageIds);
|
||||
|
||||
/**
|
||||
* Fetches Media object from the imageInfo API
|
||||
|
|
@ -93,21 +102,29 @@ public interface MediaInterface {
|
|||
*/
|
||||
@GET("w/api.php?action=query&format=json&formatversion=2&generator=images" +
|
||||
MEDIA_PARAMS)
|
||||
Observable<MwQueryResponse> getMediaWithGenerator(@Query("titles") String title);
|
||||
Single<MwQueryResponse> getMediaWithGenerator(@Query("titles") String title);
|
||||
|
||||
@GET("w/api.php?format=json&action=parse&prop=text")
|
||||
Observable<MwParseResponse> getPageHtml(@Query("page") String title);
|
||||
Single<MwParseResponse> getPageHtml(@Query("page") String title);
|
||||
|
||||
/**
|
||||
* Fetches list of images from a depiction entity
|
||||
*
|
||||
* @param query depictionEntityId
|
||||
* @param sroffset number od depictions already fetched, this is useful in implementing
|
||||
* pagination
|
||||
*/
|
||||
* Fetches caption using file name
|
||||
*
|
||||
* @param filename name of the file to be used for fetching captions
|
||||
* */
|
||||
@GET("w/api.php?action=wbgetentities&props=labels&format=json&languagefallback=1")
|
||||
Single<MwQueryResponse> fetchCaptionByFilename(@Query("language") String language, @Query("titles") String filename);
|
||||
|
||||
@GET("w/api.php?action=query&list=search&format=json&srnamespace=6")
|
||||
Observable<DepictionResponse> fetchImagesForDepictedItem(@Query("srsearch") String query,
|
||||
@Query("sroffset") String sroffset);
|
||||
/**
|
||||
* Fetches list of images from a depiction entity
|
||||
*
|
||||
* @param query depictionEntityId
|
||||
* @param sroffset number od depictions already fetched, this is useful in implementing pagination
|
||||
*/
|
||||
|
||||
@GET("w/api.php?action=query&format=json&formatversion=2" + //Basic parameters
|
||||
"&generator=search&gsrnamespace=6" + //Search parameters
|
||||
MEDIA_PARAMS)
|
||||
Single<MwQueryResponse> fetchImagesForDepictedItem(@Query("gsrsearch") String query, @Query("gsroffset") String sroffset);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,18 +14,13 @@ import android.view.View;
|
|||
import android.widget.Button;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
import com.viewpagerindicator.CirclePageIndicator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.delete.DeleteHelper;
|
||||
|
|
@ -35,6 +30,7 @@ import fr.free.nrw.commons.utils.ViewUtil;
|
|||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class ReviewActivity extends NavigationBaseActivity {
|
||||
|
||||
|
|
@ -144,10 +140,8 @@ public class ReviewActivity extends NavigationBaseActivity {
|
|||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(media -> {
|
||||
if (media != null) {
|
||||
reviewPagerAdapter.disableButtons();
|
||||
updateImage(media);
|
||||
}
|
||||
reviewPagerAdapter.disableButtons();
|
||||
updateImage(media);
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,19 @@
|
|||
package fr.free.nrw.commons.review;
|
||||
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryPage;
|
||||
import org.wikipedia.dataclient.mwapi.RecentChange;
|
||||
import org.wikipedia.util.DateUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.media.MediaClient;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryPage;
|
||||
import org.wikipedia.dataclient.mwapi.RecentChange;
|
||||
import org.wikipedia.util.DateUtil;
|
||||
|
||||
@Singleton
|
||||
public class ReviewHelper {
|
||||
|
|
@ -69,7 +66,6 @@ public class ReviewHelper {
|
|||
public Single<Media> getRandomMedia() {
|
||||
return getRecentChanges()
|
||||
.flatMapSingle(change -> getRandomMediaFromRecentChange(change))
|
||||
.onExceptionResumeNext(Observable.just(new Media("")))
|
||||
.filter(media -> !StringUtils.isBlank(media.getFilename()))
|
||||
.firstOrError();
|
||||
}
|
||||
|
|
@ -86,7 +82,7 @@ public class ReviewHelper {
|
|||
.flatMap(change -> mediaClient.checkPageExistsUsingTitle("Commons:Deletion_requests/" + change.getTitle()))
|
||||
.flatMap(isDeleted -> {
|
||||
if (isDeleted) {
|
||||
return Single.just(new Media(""));
|
||||
return Single.error(new Exception(recentChange.getTitle() + " is deleted"));
|
||||
}
|
||||
return mediaClient.getMedia(recentChange.getTitle());
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class PageContentsCreator {
|
|||
buffer
|
||||
.append("== {{int:filedesc}} ==\n")
|
||||
.append("{{Information\n")
|
||||
.append("|description=").append(contribution.getDescription()).append("\n")
|
||||
.append("|description=").append(contribution.getFallbackDescription()).append("\n")
|
||||
.append("|source=").append("{{own}}\n")
|
||||
.append("|author=[[User:").append(contribution.getCreator()).append("|")
|
||||
.append(contribution.getCreator()).append("]]\n");
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import android.net.Uri;
|
|||
import android.os.IBinder;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.TextUtils;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
|
|
@ -110,8 +109,8 @@ public class UploadController {
|
|||
contribution.setCreator(sessionManager.getAuthorName());
|
||||
}
|
||||
|
||||
if (contribution.getDescription() == null) {
|
||||
contribution.setDescription("");
|
||||
if (contribution.getFallbackDescription() == null) {
|
||||
contribution.setFallbackDescription("");
|
||||
}
|
||||
|
||||
final String license = store.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
|
||||
|
|
@ -164,7 +163,7 @@ public class UploadController {
|
|||
return mimeType;
|
||||
}
|
||||
|
||||
private long resolveDataLength(final ContentResolver contentResolver, final Media contribution) {
|
||||
private long resolveDataLength(final ContentResolver contentResolver, final Contribution contribution) {
|
||||
try {
|
||||
if (contribution.getDataLength() <= 0) {
|
||||
Timber.d("UploadController/doInBackground, contribution.getLocalUri():%s", contribution.getLocalUri());
|
||||
|
|
@ -182,7 +181,7 @@ public class UploadController {
|
|||
return contribution.getDataLength();
|
||||
}
|
||||
|
||||
private Date resolveDateTakenOrNow(final ContentResolver contentResolver, final Media contribution) {
|
||||
private Date resolveDateTakenOrNow(final ContentResolver contentResolver, final Contribution contribution) {
|
||||
Timber.d("local uri %s", contribution.getLocalUri());
|
||||
try(final Cursor cursor = dateTakenCursor(contentResolver, contribution)) {
|
||||
if (cursor != null && cursor.getCount() != 0 && cursor.getColumnCount() != 0) {
|
||||
|
|
@ -196,7 +195,7 @@ public class UploadController {
|
|||
}
|
||||
}
|
||||
|
||||
private Cursor dateTakenCursor(final ContentResolver contentResolver, final Media contribution) {
|
||||
private Cursor dateTakenCursor(final ContentResolver contentResolver, final Contribution contribution) {
|
||||
return contentResolver.query(contribution.getLocalUri(),
|
||||
new String[]{MediaStore.Images.ImageColumns.DATE_TAKEN}, null, null, null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ data class UploadMediaDetail constructor(
|
|||
* @return a string with the pattern of {{en|1=descriptionText}}
|
||||
*/
|
||||
@JvmStatic
|
||||
fun formatList(descriptions: List<UploadMediaDetail>) =
|
||||
fun formatDescriptions(descriptions: List<UploadMediaDetail>) =
|
||||
descriptions.filter { it.descriptionText.isNotEmpty() }
|
||||
.joinToString { "{{${it.languageCode}|1=${it.descriptionText}}}" }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,9 +141,6 @@ public class UploadModel {
|
|||
{
|
||||
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()));
|
||||
if (item.getCreatedTimestamp() != -1L) {
|
||||
contribution.setDateCreated(new Date(item.getCreatedTimestamp()));
|
||||
contribution.setDateCreatedSource(item.getCreatedTimestampSource());
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ internal data class WikidataPlace(
|
|||
place.wikiDataEntityId!!,
|
||||
place.name,
|
||||
place.pic.takeIf { it.isNotBlank() },
|
||||
if (place.siteLinks.wikipediaLink == null) "" else place.siteLinks.wikipediaLink.toString()
|
||||
place.siteLinks.wikipediaLink?.toString() ?: ""
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
|
@ -27,10 +27,6 @@ internal data class WikidataPlace(
|
|||
}
|
||||
|
||||
fun getWikipediaPageTitle(): String? {
|
||||
if (wikipediaArticle == null) {
|
||||
return null
|
||||
}
|
||||
val split: Array<String> = wikipediaArticle.split("/".toRegex()).toTypedArray()
|
||||
return split[split.size - 1]
|
||||
return wikipediaArticle?.substringAfterLast("/")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import com.nhaarman.mockitokotlin2.mock
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.category.CategoryItem
|
||||
import fr.free.nrw.commons.location.LatLng
|
||||
import fr.free.nrw.commons.nearby.Label
|
||||
|
|
@ -7,6 +8,7 @@ import fr.free.nrw.commons.nearby.Place
|
|||
import fr.free.nrw.commons.nearby.Sitelinks
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import org.wikipedia.wikidata.*
|
||||
import java.util.*
|
||||
|
||||
fun depictedItem(
|
||||
name: String = "label",
|
||||
|
|
@ -29,6 +31,38 @@ fun depictedItem(
|
|||
fun categoryItem(name: String = "name", selected: Boolean = false) =
|
||||
CategoryItem(name, selected)
|
||||
|
||||
fun media(
|
||||
thumbUrl: String? = "thumbUrl",
|
||||
imageUrl: String? = "imageUrl",
|
||||
filename: String? = "filename",
|
||||
fallbackDescription: String? = "fallbackDescription",
|
||||
dateUploaded: Date? = Date(),
|
||||
license: String? = "license",
|
||||
licenseUrl: String? = "licenseUrl",
|
||||
creator: String? = "creator",
|
||||
pageId: String = "pageId",
|
||||
categories: List<String>? = listOf("categories"),
|
||||
coordinates: LatLng? = LatLng(0.0, 0.0, 0.0f),
|
||||
captions: Map<String?, String?> = mapOf("en" to "caption"),
|
||||
descriptions: Map<String?, String?> = mapOf("en" to "description"),
|
||||
depictionIds: List<String> = listOf("depictionId")
|
||||
) = Media(
|
||||
thumbUrl,
|
||||
imageUrl,
|
||||
filename,
|
||||
fallbackDescription,
|
||||
dateUploaded,
|
||||
license,
|
||||
licenseUrl,
|
||||
creator,
|
||||
pageId,
|
||||
categories,
|
||||
coordinates,
|
||||
captions,
|
||||
descriptions,
|
||||
depictionIds
|
||||
)
|
||||
|
||||
fun place(
|
||||
name: String = "name",
|
||||
label: Label? = null,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package fr.free.nrw.commons
|
||||
|
||||
import media
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
|
@ -11,13 +12,15 @@ import org.robolectric.annotation.Config
|
|||
class MediaTest {
|
||||
@Test
|
||||
fun displayTitleShouldStripExtension() {
|
||||
val m = Media("File:Example.jpg")
|
||||
val m = media(filename = "File:Example.jpg")
|
||||
assertEquals("Example", m.displayTitle)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun displayTitleShouldUseSpaceForUnderscore() {
|
||||
val m = Media("File:Example 1_2.jpg")
|
||||
val m = media(filename = "File:Example 1_2.jpg")
|
||||
assertEquals("Example 1 2", m.displayTitle)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,108 +0,0 @@
|
|||
package fr.free.nrw.commons.bookmarks.pictures;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.bookmarks.Bookmark;
|
||||
import fr.free.nrw.commons.media.MediaClient;
|
||||
import io.reactivex.Single;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for bookmark pictures controller
|
||||
*/
|
||||
public class BookmarkPicturesControllerTest {
|
||||
|
||||
@Mock
|
||||
MediaClient mediaClient;
|
||||
@Mock
|
||||
BookmarkPicturesDao bookmarkDao;
|
||||
|
||||
@InjectMocks
|
||||
BookmarkPicturesController bookmarkPicturesController;
|
||||
|
||||
|
||||
/**
|
||||
* Init mocks
|
||||
*/
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
Media mockMedia = getMockMedia();
|
||||
when(bookmarkDao.getAllBookmarks())
|
||||
.thenReturn(getMockBookmarkList());
|
||||
when(mediaClient.getMedia(anyString()))
|
||||
.thenReturn(Single.just(mockMedia));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mock bookmark list
|
||||
* @return
|
||||
*/
|
||||
private List<Bookmark> getMockBookmarkList() {
|
||||
ArrayList<Bookmark> list = new ArrayList<>();
|
||||
list.add(new Bookmark("File:Test1.jpg", "Maskaravivek", Uri.EMPTY));
|
||||
list.add(new Bookmark("File:Test2.jpg", "Maskaravivek", Uri.EMPTY));
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case where all bookmark pictures are fetched and media is found against it
|
||||
*/
|
||||
@Test
|
||||
public void loadBookmarkedPictures() {
|
||||
List<Media> bookmarkedPictures = bookmarkPicturesController.loadBookmarkedPictures().blockingGet();
|
||||
assertEquals(2, bookmarkedPictures.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case where all bookmark pictures are fetched and only one media is found
|
||||
*/
|
||||
@Test
|
||||
public void loadBookmarkedPicturesForNullMedia() {
|
||||
when(mediaClient.getMedia("File:Test1.jpg"))
|
||||
.thenReturn(Single.error(new NullPointerException("Error occurred")));
|
||||
when(mediaClient.getMedia("File:Test2.jpg"))
|
||||
.thenReturn(Single.just(getMockMedia()));
|
||||
List<Media> bookmarkedPictures = bookmarkPicturesController.loadBookmarkedPictures().blockingGet();
|
||||
assertEquals(1, bookmarkedPictures.size());
|
||||
}
|
||||
|
||||
private Media getMockMedia() {
|
||||
return new Media("File:Test.jpg");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case where current bookmarks don't match the bookmarks in DB
|
||||
*/
|
||||
@Test
|
||||
public void needRefreshBookmarkedPictures() {
|
||||
boolean needRefreshBookmarkedPictures = bookmarkPicturesController.needRefreshBookmarkedPictures();
|
||||
assertTrue(needRefreshBookmarkedPictures);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case where the DB is up to date with the bookmarks loaded in the list
|
||||
*/
|
||||
@Test
|
||||
public void doNotNeedRefreshBookmarkedPictures() {
|
||||
List<Media> bookmarkedPictures = bookmarkPicturesController.loadBookmarkedPictures().blockingGet();
|
||||
assertEquals(2, bookmarkedPictures.size());
|
||||
boolean needRefreshBookmarkedPictures = bookmarkPicturesController.needRefreshBookmarkedPictures();
|
||||
assertFalse(needRefreshBookmarkedPictures);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
package fr.free.nrw.commons.bookmarks.pictures
|
||||
|
||||
import android.net.Uri
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.bookmarks.Bookmark
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import io.reactivex.Single
|
||||
import media
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Tests for bookmark pictures controller
|
||||
*/
|
||||
class BookmarkPicturesControllerTest {
|
||||
@Mock
|
||||
var mediaClient: MediaClient? = null
|
||||
|
||||
@Mock
|
||||
var bookmarkDao: BookmarkPicturesDao? = null
|
||||
|
||||
@InjectMocks
|
||||
var bookmarkPicturesController: BookmarkPicturesController? = null
|
||||
|
||||
/**
|
||||
* Init mocks
|
||||
*/
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
val mockMedia = mockMedia
|
||||
whenever(bookmarkDao!!.allBookmarks)
|
||||
.thenReturn(mockBookmarkList)
|
||||
whenever(
|
||||
mediaClient!!.getMedia(
|
||||
ArgumentMatchers.anyString()
|
||||
)
|
||||
)
|
||||
.thenReturn(Single.just(mockMedia))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mock bookmark list
|
||||
* @return
|
||||
*/
|
||||
private val mockBookmarkList: List<Bookmark>
|
||||
private get() {
|
||||
val list = ArrayList<Bookmark>()
|
||||
list.add(Bookmark("File:Test1.jpg", "Maskaravivek", Uri.EMPTY))
|
||||
list.add(Bookmark("File:Test2.jpg", "Maskaravivek", Uri.EMPTY))
|
||||
return list
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case where all bookmark pictures are fetched and media is found against it
|
||||
*/
|
||||
@Test
|
||||
fun loadBookmarkedPictures() {
|
||||
val bookmarkedPictures =
|
||||
bookmarkPicturesController!!.loadBookmarkedPictures().blockingGet()
|
||||
Assert.assertEquals(2, bookmarkedPictures.size.toLong())
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case where all bookmark pictures are fetched and only one media is found
|
||||
*/
|
||||
@Test
|
||||
fun loadBookmarkedPicturesForNullMedia() {
|
||||
whenever(mediaClient!!.getMedia("File:Test1.jpg"))
|
||||
.thenReturn(Single.error(NullPointerException("Error occurred")))
|
||||
whenever(mediaClient!!.getMedia("File:Test2.jpg"))
|
||||
.thenReturn(Single.just(mockMedia))
|
||||
val bookmarkedPictures =
|
||||
bookmarkPicturesController!!.loadBookmarkedPictures().blockingGet()
|
||||
Assert.assertEquals(1, bookmarkedPictures.size.toLong())
|
||||
}
|
||||
|
||||
private val mockMedia: Media
|
||||
private get() = media(filename="File:Test.jpg")
|
||||
|
||||
/**
|
||||
* Test case where current bookmarks don't match the bookmarks in DB
|
||||
*/
|
||||
@Test
|
||||
fun needRefreshBookmarkedPictures() {
|
||||
val needRefreshBookmarkedPictures =
|
||||
bookmarkPicturesController!!.needRefreshBookmarkedPictures()
|
||||
Assert.assertTrue(needRefreshBookmarkedPictures)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case where the DB is up to date with the bookmarks loaded in the list
|
||||
*/
|
||||
@Test
|
||||
fun doNotNeedRefreshBookmarkedPictures() {
|
||||
val bookmarkedPictures =
|
||||
bookmarkPicturesController!!.loadBookmarkedPictures().blockingGet()
|
||||
Assert.assertEquals(2, bookmarkedPictures.size.toLong())
|
||||
val needRefreshBookmarkedPictures =
|
||||
bookmarkPicturesController!!.needRefreshBookmarkedPictures()
|
||||
Assert.assertFalse(needRefreshBookmarkedPictures)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,12 +8,12 @@ import fr.free.nrw.commons.auth.SessionManager
|
|||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||
import fr.free.nrw.commons.utils.ViewUtilWrapper
|
||||
import io.reactivex.Single
|
||||
import media
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.*
|
||||
import org.mockito.MockitoAnnotations
|
||||
import java.util.*
|
||||
|
|
@ -55,11 +55,10 @@ class ReasonBuilderTest {
|
|||
`when`(okHttpJsonApiClient!!.getAchievements(anyString()))
|
||||
.thenReturn(Single.just(mock(FeedbackResponse::class.java)))
|
||||
|
||||
val media = Media("test_file")
|
||||
media.dateUploaded=Date()
|
||||
val media = media(filename="test_file", dateUploaded = Date())
|
||||
|
||||
reasonBuilder!!.getReason(media, "test")
|
||||
verify(sessionManager, times(0))!!.forceLogin(any(Context::class.java))
|
||||
verify(okHttpJsonApiClient, times(1))!!.getAchievements(anyString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,8 @@ package fr.free.nrw.commons.depictions
|
|||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.depictions.Media.DepictedImagesFragment
|
||||
import fr.free.nrw.commons.depictions.Media.DepictedImagesPresenter
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.schedulers.TestScheduler
|
||||
import org.junit.Before
|
||||
|
|
@ -27,9 +25,6 @@ class DepictedImagesPresenterTest {
|
|||
@Mock
|
||||
lateinit var jsonKvStore: JsonKvStore
|
||||
|
||||
@Mock
|
||||
lateinit var depictsClient: DepictsClient
|
||||
|
||||
@Mock
|
||||
lateinit var mediaClient: MediaClient
|
||||
|
||||
|
|
@ -40,7 +35,7 @@ class DepictedImagesPresenterTest {
|
|||
@Mock
|
||||
lateinit var mediaItem: Media
|
||||
|
||||
var testObservable: Observable<List<Media>>? = null
|
||||
var testSingle: Single<List<Media>>? = null
|
||||
|
||||
|
||||
@Before
|
||||
|
|
@ -49,28 +44,20 @@ class DepictedImagesPresenterTest {
|
|||
MockitoAnnotations.initMocks(this)
|
||||
testScheduler = TestScheduler()
|
||||
mediaList.add(mediaItem)
|
||||
testObservable = Observable.just(mediaList)
|
||||
depictedImagesPresenter = DepictedImagesPresenter(jsonKvStore, depictsClient, mediaClient, testScheduler, testScheduler)
|
||||
testSingle = Single.just(mediaList)
|
||||
depictedImagesPresenter = DepictedImagesPresenter(jsonKvStore,
|
||||
mediaClient, testScheduler, testScheduler)
|
||||
depictedImagesPresenter.onAttachView(view)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initList() {
|
||||
Mockito.`when`(
|
||||
depictsClient.fetchImagesForDepictedItem(ArgumentMatchers.anyString(),
|
||||
mediaClient.fetchImagesForDepictedItem(ArgumentMatchers.anyString(),
|
||||
ArgumentMatchers.anyInt())
|
||||
).thenReturn(testObservable)
|
||||
).thenReturn(testSingle)
|
||||
depictedImagesPresenter.initList("rabbit")
|
||||
depictedImagesPresenter.handleSuccess(mediaList)
|
||||
verify(view)?.handleSuccess(mediaList)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun replaceTitlesWithCaptions() {
|
||||
var stringObservable: Single<String>? = Single.just(String())
|
||||
Mockito.`when`(mediaClient.getCaptionByWikibaseIdentifier(ArgumentMatchers.anyString()))?.thenReturn(stringObservable)
|
||||
depictedImagesPresenter.replaceTitlesWithCaptions("File:rabbit.jpg", 0)
|
||||
testScheduler.triggerActions()
|
||||
verify(view)?.handleLabelforImage("", 0)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
import com.jraska.livedata.test
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import com.nhaarman.mockitokotlin2.mock
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import io.reactivex.schedulers.TestScheduler
|
||||
import org.junit.Before
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
package fr.free.nrw.commons.explore.media
|
||||
|
||||
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.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import io.reactivex.Single
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
|
|
@ -13,10 +10,6 @@ import org.junit.Before
|
|||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryPage
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResult
|
||||
import org.wikipedia.wikidata.Entities
|
||||
|
||||
class PageableMediaDataSourceTest {
|
||||
@Mock
|
||||
|
|
@ -31,41 +24,10 @@ class PageableMediaDataSourceTest {
|
|||
|
||||
@Test
|
||||
fun `loadFunction invokes mediaClient and has Label`() {
|
||||
val (media, entity: Entities.Entity) = expectMediaAndEntity()
|
||||
val label: Entities.Label = mock()
|
||||
whenever(entity.labels()).thenReturn(mapOf(" " to label))
|
||||
whenever(label.value()).thenReturn("label")
|
||||
val pageableMediaDataSource = PageableMediaDataSource(mock(), mediaConverter, mediaClient)
|
||||
pageableMediaDataSource.onQueryUpdated("test")
|
||||
assertThat(pageableMediaDataSource.loadFunction(0,1), `is`(listOf(media)))
|
||||
verify(media).caption = "label"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loadFunction invokes mediaClient and does not have Label`() {
|
||||
val (media, entity: Entities.Entity) = expectMediaAndEntity()
|
||||
whenever(entity.labels()).thenReturn(mapOf())
|
||||
val pageableMediaDataSource = PageableMediaDataSource(mock(), mediaConverter, mediaClient)
|
||||
pageableMediaDataSource.onQueryUpdated("test")
|
||||
assertThat(pageableMediaDataSource.loadFunction(0,1), `is`(listOf(media)))
|
||||
verify(media).caption = MediaClient.NO_CAPTION
|
||||
}
|
||||
|
||||
private fun expectMediaAndEntity(): Pair<Media, Entities.Entity> {
|
||||
val queryResponse: MwQueryResponse = mock()
|
||||
whenever(mediaClient.getMediaListFromSearch("test", 0, 1))
|
||||
.thenReturn(Single.just(queryResponse))
|
||||
val queryResult: MwQueryResult = mock()
|
||||
whenever(queryResponse.query()).thenReturn(queryResult)
|
||||
val queryPage: MwQueryPage = mock()
|
||||
whenever(queryResult.pages()).thenReturn(listOf(queryPage))
|
||||
val media = mock<Media>()
|
||||
whenever(mediaConverter.convert(queryPage)).thenReturn(media)
|
||||
whenever(media.pageId).thenReturn("1")
|
||||
val entities: Entities = mock()
|
||||
whenever(mediaClient.getEntities("${PAGE_ID_PREFIX}1")).thenReturn(Single.just(entities))
|
||||
val entity: Entities.Entity = mock()
|
||||
whenever(entities.entities()).thenReturn(mapOf("" to entity))
|
||||
return Pair(media, entity)
|
||||
.thenReturn(Single.just(emptyList()))
|
||||
val pageableMediaDataSource = PageableMediaDataSource(mock(), mediaClient)
|
||||
pageableMediaDataSource.onQueryUpdated("test")
|
||||
assertThat(pageableMediaDataSource.loadFunction(0,1), `is`(emptyList()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
package fr.free.nrw.commons.media
|
||||
|
||||
import com.nhaarman.mockitokotlin2.mock
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.explore.media.MediaConverter
|
||||
import fr.free.nrw.commons.media.model.PageMediaListItem
|
||||
import fr.free.nrw.commons.media.model.PageMediaListResponse
|
||||
import fr.free.nrw.commons.utils.CommonsDateUtil
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import junit.framework.Assert.*
|
||||
import org.junit.Before
|
||||
|
|
@ -17,6 +18,7 @@ import org.wikipedia.dataclient.mwapi.MwQueryPage
|
|||
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResult
|
||||
import org.wikipedia.gallery.ImageInfo
|
||||
import org.wikipedia.wikidata.Entities
|
||||
import java.util.*
|
||||
|
||||
|
||||
|
|
@ -24,12 +26,14 @@ class MediaClientTest {
|
|||
|
||||
@Mock
|
||||
internal var mediaInterface: MediaInterface? = null
|
||||
@Mock
|
||||
internal var mediaConverter: MediaConverter? = null
|
||||
@Mock
|
||||
internal var mediaDetailInterface: MediaDetailInterface? = null
|
||||
|
||||
@Mock
|
||||
internal var pageMediaInterface: PageMediaInterface? = null
|
||||
|
||||
@Mock
|
||||
internal var mediaDetailInterface: MediaDetailInterface? = null
|
||||
|
||||
@InjectMocks
|
||||
var mediaClient: MediaClient? = null
|
||||
|
|
@ -51,7 +55,7 @@ class MediaClientTest {
|
|||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
|
||||
`when`(mediaInterface!!.checkPageExistsUsingTitle(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Single.just(mockResponse))
|
||||
|
||||
val checkPageExistsUsingTitle =
|
||||
mediaClient!!.checkPageExistsUsingTitle("File:Test.jpg").blockingGet()
|
||||
|
|
@ -69,7 +73,7 @@ class MediaClientTest {
|
|||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
|
||||
`when`(mediaInterface!!.checkPageExistsUsingTitle(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Single.just(mockResponse))
|
||||
|
||||
val checkPageExistsUsingTitle =
|
||||
mediaClient!!.checkPageExistsUsingTitle("File:Test.jpg").blockingGet()
|
||||
|
|
@ -87,7 +91,7 @@ class MediaClientTest {
|
|||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
|
||||
`when`(mediaInterface!!.checkFileExistsUsingSha(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Single.just(mockResponse))
|
||||
|
||||
val checkFileExistsUsingSha = mediaClient!!.checkFileExistsUsingSha("abcde").blockingGet()
|
||||
assertTrue(checkFileExistsUsingSha)
|
||||
|
|
@ -104,7 +108,7 @@ class MediaClientTest {
|
|||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
|
||||
`when`(mediaInterface!!.checkFileExistsUsingSha(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Single.just(mockResponse))
|
||||
|
||||
val checkFileExistsUsingSha = mediaClient!!.checkFileExistsUsingSha("abcde").blockingGet()
|
||||
assertFalse(checkFileExistsUsingSha)
|
||||
|
|
@ -112,21 +116,12 @@ class MediaClientTest {
|
|||
|
||||
@Test
|
||||
fun getMedia() {
|
||||
val imageInfo = ImageInfo()
|
||||
|
||||
val mwQueryPage = mock(MwQueryPage::class.java)
|
||||
`when`(mwQueryPage.title()).thenReturn("Test")
|
||||
`when`(mwQueryPage.imageInfo()).thenReturn(imageInfo)
|
||||
|
||||
val mwQueryResult = mock(MwQueryResult::class.java)
|
||||
`when`(mwQueryResult.firstPage()).thenReturn(mwQueryPage)
|
||||
val mockResponse = mock(MwQueryResponse::class.java)
|
||||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
val (mockResponse, media: Media) = expectGetEntitiesAndMediaConversion()
|
||||
|
||||
`when`(mediaInterface!!.getMedia(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Single.just(mockResponse))
|
||||
|
||||
assertEquals("Test", mediaClient!!.getMedia("abcde").blockingGet().filename)
|
||||
mediaClient!!.getMedia("abcde").test().assertValue(media)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -143,34 +138,34 @@ class MediaClientTest {
|
|||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
|
||||
`when`(mediaInterface!!.getMedia(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
|
||||
assertEquals(Media.EMPTY, mediaClient!!.getMedia("abcde").blockingGet())
|
||||
.thenReturn(Single.just(mockResponse))
|
||||
mediaClient!!.getMedia("abcde").test().assertErrorMessage("empty list passed for ids")
|
||||
}
|
||||
|
||||
@Captor
|
||||
private val filenameCaptor: ArgumentCaptor<String>? = null
|
||||
|
||||
@Test
|
||||
fun getPictureOfTheDay() {
|
||||
val template = "Template:Potd/" + CommonsDateUtil.getIso8601DateFormatShort().format(Date())
|
||||
|
||||
val imageInfo = ImageInfo()
|
||||
val (mockResponse, media: Media) = expectGetEntitiesAndMediaConversion()
|
||||
`when`(mediaInterface!!.getMediaWithGenerator(template))
|
||||
.thenReturn(Single.just(mockResponse))
|
||||
mediaClient!!.getPictureOfTheDay().test().assertValue(media)
|
||||
}
|
||||
|
||||
val mwQueryPage = mock(MwQueryPage::class.java)
|
||||
`when`(mwQueryPage.title()).thenReturn("Test")
|
||||
`when`(mwQueryPage.imageInfo()).thenReturn(imageInfo)
|
||||
|
||||
val mwQueryResult = mock(MwQueryResult::class.java)
|
||||
`when`(mwQueryResult.firstPage()).thenReturn(mwQueryPage)
|
||||
private fun expectGetEntitiesAndMediaConversion(): Pair<MwQueryResponse, Media> {
|
||||
val mockResponse = mock(MwQueryResponse::class.java)
|
||||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
|
||||
`when`(mediaInterface!!.getMediaWithGenerator(filenameCaptor!!.capture()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
|
||||
assertEquals("Test", mediaClient!!.pictureOfTheDay.blockingGet().filename)
|
||||
assertEquals(template, filenameCaptor.value);
|
||||
val queryResult: MwQueryResult = mock()
|
||||
whenever(mockResponse.query()).thenReturn(queryResult)
|
||||
val queryPage: MwQueryPage = mock()
|
||||
whenever(queryResult.pages()).thenReturn(listOf(queryPage))
|
||||
whenever(queryPage.pageId()).thenReturn(0)
|
||||
val entities: Entities = mock()
|
||||
whenever(mediaDetailInterface!!.getEntity("M0")).thenReturn(Single.just(entities))
|
||||
val entity: Entities.Entity = mock()
|
||||
whenever(entities.entities()).thenReturn(mapOf("id" to entity))
|
||||
val media: Media = mock()
|
||||
whenever(mediaConverter!!.convert(queryPage, entity)).thenReturn(media)
|
||||
return Pair(mockResponse, media)
|
||||
}
|
||||
|
||||
@Captor
|
||||
|
|
@ -179,17 +174,8 @@ class MediaClientTest {
|
|||
@Test
|
||||
fun getMediaListFromCategoryTwice() {
|
||||
val mockContinuation = mapOf(Pair("gcmcontinue", "test"))
|
||||
val imageInfo = ImageInfo()
|
||||
|
||||
val mwQueryPage = mock(MwQueryPage::class.java)
|
||||
`when`(mwQueryPage.title()).thenReturn("Test")
|
||||
`when`(mwQueryPage.imageInfo()).thenReturn(imageInfo)
|
||||
|
||||
val mwQueryResult = mock(MwQueryResult::class.java)
|
||||
`when`(mwQueryResult.pages()).thenReturn(listOf(mwQueryPage))
|
||||
|
||||
val mockResponse = mock(MwQueryResponse::class.java)
|
||||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
val (mockResponse, media: Media) = expectGetEntitiesAndMediaConversion()
|
||||
`when`(mockResponse.continuation()).thenReturn(mockContinuation)
|
||||
|
||||
`when`(
|
||||
|
|
@ -198,31 +184,23 @@ class MediaClientTest {
|
|||
continuationCaptor!!.capture()
|
||||
)
|
||||
)
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Single.just(mockResponse))
|
||||
|
||||
val media1 = mediaClient!!.getMediaListFromCategory("abcde").blockingGet().get(0)
|
||||
val media2 = mediaClient!!.getMediaListFromCategory("abcde").blockingGet().get(0)
|
||||
|
||||
assertEquals(continuationCaptor.allValues[0], emptyMap<String, String>())
|
||||
assertEquals(continuationCaptor.allValues[1], mockContinuation)
|
||||
|
||||
assertEquals(media1.filename, "Test")
|
||||
assertEquals(media2.filename, "Test")
|
||||
assertEquals(media1, media)
|
||||
assertEquals(media2, media)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getMediaListForUser() {
|
||||
val mockContinuation = mapOf("gcmcontinue" to "test")
|
||||
val imageInfo = ImageInfo()
|
||||
|
||||
val mwQueryPage = mock(MwQueryPage::class.java)
|
||||
whenever(mwQueryPage.title()).thenReturn("Test")
|
||||
whenever(mwQueryPage.imageInfo()).thenReturn(imageInfo)
|
||||
|
||||
val mwQueryResult = mock(MwQueryResult::class.java)
|
||||
whenever(mwQueryResult.pages()).thenReturn(listOf(mwQueryPage))
|
||||
|
||||
val mockResponse = mock(MwQueryResponse::class.java)
|
||||
whenever(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
val (mockResponse, media: Media) = expectGetEntitiesAndMediaConversion()
|
||||
whenever(mockResponse.continuation()).thenReturn(mockContinuation)
|
||||
|
||||
whenever(
|
||||
|
|
@ -231,7 +209,7 @@ class MediaClientTest {
|
|||
continuationCaptor!!.capture()
|
||||
)
|
||||
)
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Single.just(mockResponse))
|
||||
val media1 = mediaClient!!.getMediaListForUser("Test").blockingGet().get(0)
|
||||
val media2 = mediaClient!!.getMediaListForUser("Test").blockingGet().get(0)
|
||||
|
||||
|
|
@ -251,7 +229,7 @@ class MediaClientTest {
|
|||
mockResponse.setParse(mwParseResult)
|
||||
|
||||
`when`(mediaInterface!!.getPageHtml(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Single.just(mockResponse))
|
||||
|
||||
assertEquals("Test", mediaClient!!.getPageHtml("abcde").blockingGet())
|
||||
}
|
||||
|
|
@ -282,7 +260,7 @@ class MediaClientTest {
|
|||
mockResponse.setParse(null)
|
||||
|
||||
`when`(mediaInterface!!.getPageHtml(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Single.just(mockResponse))
|
||||
|
||||
assertEquals("", mediaClient!!.getPageHtml("abcde").blockingGet())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue