mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-10-31 14:53:59 +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("/") | ||||
|     } | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Seán Mac Gillicuddy
						Seán Mac Gillicuddy