mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Merge remote-tracking branch 'origin/master' into macgills/3760-categories-pagination
This commit is contained in:
commit
be075c8013
34 changed files with 1397 additions and 928 deletions
|
|
@ -42,6 +42,11 @@ dependencies {
|
|||
implementation 'com.dinuscxj:circleprogressbar:1.1.1'
|
||||
implementation 'com.karumi:dexter:5.0.0'
|
||||
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
|
||||
|
||||
//paging
|
||||
implementation "androidx.paging:paging-runtime-ktx:2.1.2"
|
||||
implementation "androidx.paging:paging-rxjava2-ktx:2.1.2"
|
||||
|
||||
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
|
||||
implementation "com.hannesdorfmann:adapterdelegates4-kotlin-dsl-layoutcontainer:$ADAPTER_DELEGATES_VERSION"
|
||||
implementation "com.hannesdorfmann:adapterdelegates4-pagination:$ADAPTER_DELEGATES_VERSION"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.os.Parcelable;
|
|||
import androidx.annotation.NonNull;
|
||||
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;
|
||||
|
|
@ -15,6 +16,8 @@ import java.util.ArrayList;
|
|||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
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;
|
||||
|
|
@ -50,6 +53,8 @@ public class Media implements Parcelable {
|
|||
/**
|
||||
* Wikibase Identifier associated with media files
|
||||
*/
|
||||
@PrimaryKey
|
||||
@NonNull
|
||||
private String pageId;
|
||||
private List<String> categories; // as loaded at runtime?
|
||||
/**
|
||||
|
|
@ -64,14 +69,28 @@ public class Media implements Parcelable {
|
|||
* Provides local constructor
|
||||
*/
|
||||
public Media() {
|
||||
pageId = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
public static final Creator<Media> CREATOR = new Creator<Media>() {
|
||||
@Override
|
||||
public Media createFromParcel(final Parcel source) {
|
||||
return new Media(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Media[] newArray(final int size) {
|
||||
return new Media[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides a minimal constructor
|
||||
*
|
||||
* @param filename Media filename
|
||||
*/
|
||||
public Media(String filename) {
|
||||
public Media(final String filename) {
|
||||
this();
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
|
|
@ -86,11 +105,13 @@ public class Media implements Parcelable {
|
|||
* @param dateUploaded Media date uploaded
|
||||
* @param creator Media creator
|
||||
*/
|
||||
public Media(Uri localUri, String imageUrl, String filename,
|
||||
String description,
|
||||
long dataLength, Date dateCreated, Date dateUploaded, String 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;
|
||||
this.thumbUrl = imageUrl;
|
||||
thumbUrl = imageUrl;
|
||||
this.imageUrl = imageUrl;
|
||||
this.filename = filename;
|
||||
this.description = description;
|
||||
|
|
@ -100,17 +121,80 @@ public class Media implements Parcelable {
|
|||
this.creator = creator;
|
||||
}
|
||||
|
||||
public Media(Uri localUri, String filename,
|
||||
String description, String creator, List<String> categories) {
|
||||
/**
|
||||
* 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(String title, Date date, String user) {
|
||||
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,
|
||||
|
|
@ -120,14 +204,14 @@ public class Media implements Parcelable {
|
|||
* @return Media object
|
||||
*/
|
||||
@Nullable
|
||||
public static Media from(MwQueryPage page) {
|
||||
ImageInfo imageInfo = page.imageInfo();
|
||||
public static Media from(final MwQueryPage page) {
|
||||
final ImageInfo imageInfo = page.imageInfo();
|
||||
if (imageInfo == null) {
|
||||
return new Media(); // null is not allowed
|
||||
}
|
||||
ExtMetadata metadata = imageInfo.getMetadata();
|
||||
final ExtMetadata metadata = imageInfo.getMetadata();
|
||||
if (metadata == null) {
|
||||
Media media = new Media(null, imageInfo.getOriginalUrl(),
|
||||
final Media media = new Media(null, imageInfo.getOriginalUrl(),
|
||||
page.title(), "", 0, null, null, null);
|
||||
if (!StringUtils.isBlank(imageInfo.getThumbUrl())) {
|
||||
media.setThumbUrl(imageInfo.getThumbUrl());
|
||||
|
|
@ -135,7 +219,7 @@ public class Media implements Parcelable {
|
|||
return media;
|
||||
}
|
||||
|
||||
Media media = new Media(null,
|
||||
final Media media = new Media(null,
|
||||
imageInfo.getOriginalUrl(),
|
||||
page.title(),
|
||||
"",
|
||||
|
|
@ -158,11 +242,12 @@ public class Media implements Parcelable {
|
|||
|
||||
media.setDescription(metadata.imageDescription());
|
||||
media.setCategories(MediaDataExtractorUtil.extractCategoriesFromList(metadata.getCategories()));
|
||||
String latitude = metadata.getGpsLatitude();
|
||||
String longitude = metadata.getGpsLongitude();
|
||||
final String latitude = metadata.getGpsLatitude();
|
||||
final String longitude = metadata.getGpsLongitude();
|
||||
|
||||
if (!StringUtils.isBlank(latitude) && !StringUtils.isBlank(longitude)) {
|
||||
LatLng latLng = new LatLng(Double.parseDouble(latitude), Double.parseDouble(longitude), 0);
|
||||
final LatLng latLng = new LatLng(Double.parseDouble(latitude),
|
||||
Double.parseDouble(longitude), 0);
|
||||
media.setCoordinates(latLng);
|
||||
}
|
||||
|
||||
|
|
@ -175,29 +260,17 @@ public class Media implements Parcelable {
|
|||
* @param metadata
|
||||
* @return
|
||||
*/
|
||||
private static String getArtist(ExtMetadata metadata) {
|
||||
private static String getArtist(final ExtMetadata metadata) {
|
||||
try {
|
||||
String artistHtml = metadata.artist();
|
||||
final String artistHtml = metadata.artist();
|
||||
return artistHtml.substring(artistHtml.indexOf("title=\""), artistHtml.indexOf("\">"))
|
||||
.replace("title=\"User:", "");
|
||||
} catch (Exception ex) {
|
||||
} catch (final Exception ex) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return pageId for the current media object*/
|
||||
public String getPageId() {
|
||||
return pageId;
|
||||
}
|
||||
|
||||
/**
|
||||
*sets pageId for the current media object
|
||||
*/
|
||||
public void setPageId(String pageId) {
|
||||
this.pageId = pageId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getThumbUrl() {
|
||||
return thumbUrl;
|
||||
}
|
||||
|
|
@ -210,11 +283,13 @@ public class Media implements Parcelable {
|
|||
return filename != null ? getPageTitle().getDisplayTextWithoutNamespace().replaceFirst("[.][^.]+$", "") : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Caption(if available) as the thumbnail title of the image
|
||||
*/
|
||||
public void setThumbnailTitle(String title) {
|
||||
this.thumbnailTitle = title;
|
||||
@Nullable
|
||||
private static Date safeParseDate(final String dateStr) {
|
||||
try {
|
||||
return CommonsDateUtil.getMediaSimpleDateFormat().parse(dateStr);
|
||||
} catch (final ParseException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -260,19 +335,17 @@ public class Media implements Parcelable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the file.
|
||||
* @param filename the new name of the file
|
||||
*/
|
||||
public void setFilename(String filename) {
|
||||
this.filename = filename;
|
||||
* @return pageId for the current media object*/
|
||||
@NonNull
|
||||
public String getPageId() {
|
||||
return pageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the discussion of the file.
|
||||
* @param discussion
|
||||
*sets pageId for the current media object
|
||||
*/
|
||||
public void setDiscussion(String discussion) {
|
||||
this.discussion = discussion;
|
||||
public void setPageId(final String pageId) {
|
||||
this.pageId = pageId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -310,11 +383,11 @@ public class Media implements Parcelable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the file description.
|
||||
* @param description the new description of the file
|
||||
* Sets the name of the file.
|
||||
* @param filename the new name of the file
|
||||
*/
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
public void setFilename(final String filename) {
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -326,11 +399,11 @@ public class Media implements Parcelable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the dataLength of the file.
|
||||
* @param dataLength as a long
|
||||
* Sets the discussion of the file.
|
||||
* @param discussion
|
||||
*/
|
||||
public void setDataLength(long dataLength) {
|
||||
this.dataLength = dataLength;
|
||||
public void setDiscussion(final String discussion) {
|
||||
this.discussion = discussion;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -342,11 +415,11 @@ public class Media implements Parcelable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the creation date of the file.
|
||||
* @param date creation date as a Date
|
||||
* Sets the file description.
|
||||
* @param description the new description of the file
|
||||
*/
|
||||
public void setDateCreated(Date date) {
|
||||
this.dateCreated = date;
|
||||
public void setDescription(final String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -368,11 +441,11 @@ public class Media implements Parcelable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the creator name of the file.
|
||||
* @param creator creator name as a string
|
||||
* Sets the dataLength of the file.
|
||||
* @param dataLength as a long
|
||||
*/
|
||||
public void setCreator(String creator) {
|
||||
this.creator = creator;
|
||||
public void setDataLength(final long dataLength) {
|
||||
this.dataLength = dataLength;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -383,8 +456,11 @@ public class Media implements Parcelable {
|
|||
return license;
|
||||
}
|
||||
|
||||
public void setThumbUrl(String thumbUrl) {
|
||||
this.thumbUrl = thumbUrl;
|
||||
/**
|
||||
* Set Caption(if available) as the thumbnail title of the image
|
||||
*/
|
||||
public void setThumbnailTitle(final String title) {
|
||||
thumbnailTitle = title;
|
||||
}
|
||||
|
||||
public String getLicenseUrl() {
|
||||
|
|
@ -392,16 +468,11 @@ public class Media implements Parcelable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the license name of the file.
|
||||
* @param license license name as a String
|
||||
* Sets the creator name of the file.
|
||||
* @param creator creator name as a string
|
||||
*/
|
||||
public void setLicenseInformation(String license, String licenseUrl) {
|
||||
this.license = license;
|
||||
|
||||
if (!licenseUrl.startsWith("http://") && !licenseUrl.startsWith("https://")) {
|
||||
licenseUrl = "https://" + licenseUrl;
|
||||
}
|
||||
this.licenseUrl = licenseUrl;
|
||||
public void setCreator(final String creator) {
|
||||
this.creator = creator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -413,12 +484,8 @@ public class Media implements Parcelable {
|
|||
return coordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the coordinates of where the file was created.
|
||||
* @param coordinates file coordinates as a LatLng
|
||||
*/
|
||||
public void setCoordinates(@Nullable LatLng coordinates) {
|
||||
this.coordinates = coordinates;
|
||||
public void setThumbUrl(final String thumbUrl) {
|
||||
this.thumbUrl = thumbUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -430,6 +497,27 @@ public class Media implements Parcelable {
|
|||
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
|
||||
*/
|
||||
public void setCoordinates(@Nullable final LatLng coordinates) {
|
||||
this.coordinates = coordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the categories the file falls under.
|
||||
* </p>
|
||||
|
|
@ -437,26 +525,10 @@ public class Media implements Parcelable {
|
|||
* and then add the specified ones.
|
||||
* @param categories file categories as a list of Strings
|
||||
*/
|
||||
public void setCategories(List<String> categories) {
|
||||
public void setCategories(final List<String> categories) {
|
||||
this.categories = categories;
|
||||
}
|
||||
|
||||
@Nullable private static Date safeParseDate(String dateStr) {
|
||||
try {
|
||||
return CommonsDateUtil.getIso8601DateFormatShort().parse(dateStr);
|
||||
} catch (ParseException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set requested deletion to true
|
||||
* @param requestedDeletion
|
||||
*/
|
||||
public void setRequestedDeletion(boolean requestedDeletion){
|
||||
this.requestedDeletion = requestedDeletion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of requested deletion
|
||||
* @return boolean requestedDeletion
|
||||
|
|
@ -465,12 +537,20 @@ public class Media implements Parcelable {
|
|||
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.
|
||||
*
|
||||
* @param license license name as a String
|
||||
*/
|
||||
public void setLicense(String license) {
|
||||
public void setLicense(final String license) {
|
||||
this.license = license;
|
||||
}
|
||||
|
||||
|
|
@ -482,15 +562,10 @@ public class Media implements Parcelable {
|
|||
* This function sets captions
|
||||
* @param caption
|
||||
*/
|
||||
public void setCaption(String caption) {
|
||||
public void setCaption(final String caption) {
|
||||
this.caption = caption;
|
||||
}
|
||||
|
||||
/* Sets depictions for the current media obtained fro Wikibase API*/
|
||||
public void setDepictions(Depictions depictions) {
|
||||
this.depictions = depictions;
|
||||
}
|
||||
|
||||
public void setLocalUri(@Nullable final Uri localUri) {
|
||||
this.localUri = localUri;
|
||||
}
|
||||
|
|
@ -516,6 +591,19 @@ public class Media implements Parcelable {
|
|||
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.
|
||||
|
|
@ -523,63 +611,70 @@ public class Media implements Parcelable {
|
|||
* @param flags Parcel flag
|
||||
*/
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeParcelable(this.localUri, flags);
|
||||
dest.writeString(this.thumbUrl);
|
||||
dest.writeString(this.imageUrl);
|
||||
dest.writeString(this.filename);
|
||||
dest.writeString(this.thumbnailTitle);
|
||||
dest.writeString(this.caption);
|
||||
dest.writeString(this.description);
|
||||
dest.writeString(this.discussion);
|
||||
dest.writeLong(this.dataLength);
|
||||
dest.writeLong(this.dateCreated != null ? this.dateCreated.getTime() : -1);
|
||||
dest.writeLong(this.dateUploaded != null ? this.dateUploaded.getTime() : -1);
|
||||
dest.writeString(this.license);
|
||||
dest.writeString(this.licenseUrl);
|
||||
dest.writeString(this.creator);
|
||||
dest.writeString(this.pageId);
|
||||
dest.writeStringList(this.categories);
|
||||
dest.writeParcelable(this.depictions, flags);
|
||||
dest.writeByte(this.requestedDeletion ? (byte) 1 : (byte) 0);
|
||||
dest.writeParcelable(this.coordinates, flags);
|
||||
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.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);
|
||||
}
|
||||
|
||||
protected Media(Parcel in) {
|
||||
this.localUri = in.readParcelable(Uri.class.getClassLoader());
|
||||
this.thumbUrl = in.readString();
|
||||
this.imageUrl = in.readString();
|
||||
this.filename = in.readString();
|
||||
this.thumbnailTitle = in.readString();
|
||||
this.caption = in.readString();
|
||||
this.description = in.readString();
|
||||
this.discussion = in.readString();
|
||||
this.dataLength = in.readLong();
|
||||
long tmpDateCreated = in.readLong();
|
||||
this.dateCreated = tmpDateCreated == -1 ? null : new Date(tmpDateCreated);
|
||||
long tmpDateUploaded = in.readLong();
|
||||
this.dateUploaded = tmpDateUploaded == -1 ? null : new Date(tmpDateUploaded);
|
||||
this.license = in.readString();
|
||||
this.licenseUrl = in.readString();
|
||||
this.creator = in.readString();
|
||||
this.pageId = in.readString();
|
||||
final ArrayList<String> list = new ArrayList<>();
|
||||
in.readStringList(list);
|
||||
this.categories=list;
|
||||
in.readParcelable(Depictions.class.getClassLoader());
|
||||
this.requestedDeletion = in.readByte() != 0;
|
||||
this.coordinates = in.readParcelable(LatLng.class.getClassLoader());
|
||||
/**
|
||||
* 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)) {
|
||||
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());
|
||||
}
|
||||
|
||||
public static final Creator<Media> CREATOR = new Creator<Media>() {
|
||||
@Override
|
||||
public Media createFromParcel(Parcel source) {
|
||||
return new Media(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Media[] newArray(int size) {
|
||||
return new Media[size];
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ public final class OkHttpConnectionFactory {
|
|||
@NonNull private static final Cache NET_CACHE = new Cache(new File(CommonsApplication.getInstance().getCacheDir(),
|
||||
CACHE_DIR_NAME), NET_CACHE_SIZE);
|
||||
|
||||
@NonNull private static final OkHttpClient CLIENT = createClient();
|
||||
@NonNull
|
||||
private static final OkHttpClient CLIENT = createClient();
|
||||
|
||||
@NonNull public static OkHttpClient getClient() {
|
||||
return CLIENT;
|
||||
|
|
@ -40,7 +41,7 @@ public final class OkHttpConnectionFactory {
|
|||
|
||||
private static HttpLoggingInterceptor getLoggingInterceptor() {
|
||||
final HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor()
|
||||
.setLevel(Level.BASIC);
|
||||
.setLevel(Level.BASIC);
|
||||
|
||||
httpLoggingInterceptor.redactHeader("Authorization");
|
||||
httpLoggingInterceptor.redactHeader("Cookie");
|
||||
|
|
@ -49,7 +50,10 @@ public final class OkHttpConnectionFactory {
|
|||
}
|
||||
|
||||
private static class CommonHeaderRequestInterceptor implements Interceptor {
|
||||
@Override @NonNull public Response intercept(@NonNull final Chain chain) throws IOException {
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Response intercept(@NonNull final Chain chain) throws IOException {
|
||||
final Request request = chain.request().newBuilder()
|
||||
.header("User-Agent", CommonsApplication.getInstance().getUserAgent())
|
||||
.build();
|
||||
|
|
@ -61,16 +65,18 @@ public final class OkHttpConnectionFactory {
|
|||
|
||||
private static final String ERRORS_PREFIX = "{\"error";
|
||||
|
||||
@Override @NonNull public Response intercept(@NonNull final Chain chain) throws IOException {
|
||||
@Override
|
||||
@NonNull
|
||||
public Response intercept(@NonNull final Chain chain) throws IOException {
|
||||
final Response rsp = chain.proceed(chain.request());
|
||||
if (rsp.isSuccessful()) {
|
||||
try (final ResponseBody responseBody = rsp.peekBody(ERRORS_PREFIX.length())) {
|
||||
if (ERRORS_PREFIX.equals(responseBody.string())){
|
||||
if (ERRORS_PREFIX.equals(responseBody.string())) {
|
||||
try (final ResponseBody body = rsp.body()) {
|
||||
throw new IOException(body.string());
|
||||
}
|
||||
}
|
||||
}catch (final IOException e){
|
||||
} catch (final IOException e) {
|
||||
Timber.e(e);
|
||||
}
|
||||
return rsp;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package fr.free.nrw.commons.contributions;
|
|||
|
||||
import android.os.Parcel;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.upload.UploadMediaDetail;
|
||||
|
|
@ -13,7 +12,7 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryLogEvent;
|
||||
import java.util.Objects;
|
||||
|
||||
@Entity(tableName = "contribution")
|
||||
public class Contribution extends Media {
|
||||
|
|
@ -24,24 +23,21 @@ public class Contribution extends Media {
|
|||
public static final int STATE_QUEUED = 2;
|
||||
public static final int STATE_IN_PROGRESS = 3;
|
||||
|
||||
@PrimaryKey (autoGenerate = true)
|
||||
private long _id;
|
||||
private int state;
|
||||
private long transferred;
|
||||
private String decimalCoords;
|
||||
private String dateCreatedSource;
|
||||
private WikidataPlace wikidataPlace;
|
||||
/**
|
||||
* Each depiction loaded in depictions activity is associated with a wikidata entity id,
|
||||
* this Id is in turn used to upload depictions to wikibase
|
||||
* Each depiction loaded in depictions activity is associated with a wikidata entity id, this Id
|
||||
* is in turn used to upload depictions to wikibase
|
||||
*/
|
||||
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>"
|
||||
* 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 Map<String, String> captions = new HashMap<>();
|
||||
|
||||
|
|
@ -55,20 +51,13 @@ public class Contribution extends Media {
|
|||
UploadMediaDetail.formatList(item.getUploadMediaDetails()),
|
||||
sessionManager.getAuthorName(),
|
||||
categories);
|
||||
captions = UploadMediaDetail.formatCaptions(item.getUploadMediaDetails());
|
||||
captions = UploadMediaDetail.formatCaptions(item.getUploadMediaDetails());
|
||||
decimalCoords = item.getGpsCoords().getDecimalCoords();
|
||||
dateCreatedSource = "";
|
||||
this.depictedItems = depictedItems;
|
||||
wikidataPlace = WikidataPlace.from(item.getPlace());
|
||||
}
|
||||
|
||||
public Contribution(final MwQueryLogEvent queryLogEvent, final String user) {
|
||||
super(queryLogEvent.title(),queryLogEvent.date(), user);
|
||||
decimalCoords = "";
|
||||
dateCreatedSource = "";
|
||||
state = STATE_COMPLETED;
|
||||
}
|
||||
|
||||
public void setDateCreatedSource(final String dateCreatedSource) {
|
||||
this.dateCreatedSource = dateCreatedSource;
|
||||
}
|
||||
|
|
@ -108,14 +97,6 @@ public class Contribution extends Media {
|
|||
return wikidataPlace;
|
||||
}
|
||||
|
||||
public long get_id() {
|
||||
return _id;
|
||||
}
|
||||
|
||||
public void set_id(final long _id) {
|
||||
this._id = _id;
|
||||
}
|
||||
|
||||
public String getDecimalCoords() {
|
||||
return decimalCoords;
|
||||
}
|
||||
|
|
@ -128,29 +109,30 @@ public class Contribution extends Media {
|
|||
this.depictedItems = depictedItems;
|
||||
}
|
||||
|
||||
public void setMimeType(String mimeType) {
|
||||
this.mimeType = mimeType;
|
||||
public String getMimeType() {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
public String getMimeType() {
|
||||
return mimeType;
|
||||
public void setMimeType(final String mimeType) {
|
||||
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
|
||||
*
|
||||
* 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 Map<String, String> getCaptions() {
|
||||
return captions;
|
||||
return captions;
|
||||
}
|
||||
|
||||
public void setCaptions(Map<String, String> captions) {
|
||||
this.captions = captions;
|
||||
this.captions = captions;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -161,7 +143,6 @@ public class Contribution extends Media {
|
|||
@Override
|
||||
public void writeToParcel(final Parcel dest, final int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeLong(_id);
|
||||
dest.writeInt(state);
|
||||
dest.writeLong(transferred);
|
||||
dest.writeString(decimalCoords);
|
||||
|
|
@ -169,9 +150,24 @@ public class Contribution extends Media {
|
|||
dest.writeSerializable((HashMap) captions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that takes Media object and state as parameters and builds a new Contribution object
|
||||
* @param 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());
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
protected Contribution(final Parcel in) {
|
||||
super(in);
|
||||
_id = in.readLong();
|
||||
state = in.readInt();
|
||||
transferred = in.readLong();
|
||||
decimalCoords = in.readString();
|
||||
|
|
@ -190,4 +186,35 @@ public class Contribution extends Media {
|
|||
return new Contribution[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Equals implementation of Contributions that compares all parameters for checking equality
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof Contribution)) {
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
package fr.free.nrw.commons.contributions
|
||||
|
||||
import androidx.paging.PagedList.BoundaryCallback
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.di.CommonsApplicationModule
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import io.reactivex.Scheduler
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
/**
|
||||
* Class that extends PagedList.BoundaryCallback for contributions list It defines the action that
|
||||
* is triggered for various boundary conditions in the list
|
||||
*/
|
||||
class ContributionBoundaryCallback @Inject constructor(
|
||||
private val repository: ContributionsRepository,
|
||||
private val sessionManager: SessionManager,
|
||||
private val mediaClient: MediaClient,
|
||||
@param:Named(CommonsApplicationModule.IO_THREAD) private val ioThreadScheduler: Scheduler
|
||||
) : BoundaryCallback<Contribution>() {
|
||||
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
|
||||
|
||||
/**
|
||||
* It is triggered when the list has no items User's Contributions are then fetched from the
|
||||
* network
|
||||
*/
|
||||
override fun onZeroItemsLoaded() {
|
||||
fetchContributions()
|
||||
}
|
||||
|
||||
/**
|
||||
* It is triggered when the user scrolls to the top of the list User's Contributions are then
|
||||
* fetched from the network
|
||||
* */
|
||||
override fun onItemAtFrontLoaded(itemAtFront: Contribution) {
|
||||
fetchContributions()
|
||||
}
|
||||
|
||||
/**
|
||||
* It is triggered when the user scrolls to the end of the list. User's Contributions are then
|
||||
* fetched from the network
|
||||
*/
|
||||
override fun onItemAtEndLoaded(itemAtEnd: Contribution) {
|
||||
fetchContributions()
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches contributions using the MediaWiki API
|
||||
*/
|
||||
fun fetchContributions() {
|
||||
if (mediaClient.doesMediaListForUserHaveMorePages(sessionManager.userName).not()) {
|
||||
return
|
||||
}
|
||||
compositeDisposable.add(
|
||||
mediaClient.getMediaListForUser(sessionManager.userName)
|
||||
.map { mediaList: List<Media?> ->
|
||||
mediaList.map {
|
||||
Contribution(it, Contribution.STATE_COMPLETED)
|
||||
}
|
||||
}
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe(
|
||||
::saveContributionsToDB
|
||||
) { error: Throwable ->
|
||||
Timber.e(
|
||||
"Failed to fetch contributions: %s",
|
||||
error.message
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the contributions the the local DB
|
||||
*/
|
||||
private fun saveContributionsToDB(contributions: List<Contribution>) {
|
||||
compositeDisposable.add(
|
||||
repository.save(contributions)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe { longs: List<Long?>? ->
|
||||
repository["last_fetch_timestamp"] = System.currentTimeMillis()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.paging.DataSource;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
|
|
@ -15,40 +15,55 @@ import java.util.List;
|
|||
@Dao
|
||||
public abstract class ContributionDao {
|
||||
|
||||
@Query("SELECT * FROM contribution order by dateUploaded DESC")
|
||||
abstract LiveData<List<Contribution>> fetchContributions();
|
||||
@Query("SELECT * FROM contribution order by dateUploaded DESC")
|
||||
abstract DataSource.Factory<Integer, Contribution> fetchContributions();
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
public abstract Single<Long> save(Contribution contribution);
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
public abstract void saveSynchronous(Contribution contribution);
|
||||
|
||||
public Completable deleteAllAndSave(List<Contribution> contributions){
|
||||
return Completable.fromAction(() -> deleteAllAndSaveTransaction(contributions));
|
||||
}
|
||||
public Completable save(final Contribution contribution) {
|
||||
return Completable
|
||||
.fromAction(() -> saveSynchronous(contribution));
|
||||
}
|
||||
|
||||
@Transaction
|
||||
public void deleteAllAndSaveTransaction(List<Contribution> contributions){
|
||||
deleteAll(Contribution.STATE_COMPLETED);
|
||||
save(contributions);
|
||||
}
|
||||
@Transaction
|
||||
public void deleteAndSaveContribution(final Contribution oldContribution,
|
||||
final Contribution newContribution) {
|
||||
deleteSynchronous(oldContribution);
|
||||
saveSynchronous(newContribution);
|
||||
}
|
||||
|
||||
@Insert
|
||||
public abstract void save(List<Contribution> contribution);
|
||||
public Completable saveAndDelete(final Contribution oldContribution,
|
||||
final Contribution newContribution) {
|
||||
return Completable
|
||||
.fromAction(() -> deleteAndSaveContribution(oldContribution, newContribution));
|
||||
}
|
||||
|
||||
@Delete
|
||||
public abstract Single<Integer> delete(Contribution contribution);
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
public abstract Single<List<Long>> save(List<Contribution> contribution);
|
||||
|
||||
@Query("SELECT * from contribution WHERE filename=:fileName")
|
||||
public abstract List<Contribution> getContributionWithTitle(String fileName);
|
||||
@Delete
|
||||
public abstract void deleteSynchronous(Contribution contribution);
|
||||
|
||||
@Query("UPDATE contribution SET state=:state WHERE state in (:toUpdateStates)")
|
||||
public abstract Single<Integer> updateStates(int state, int[] toUpdateStates);
|
||||
public Completable delete(final Contribution contribution) {
|
||||
return Completable
|
||||
.fromAction(() -> deleteSynchronous(contribution));
|
||||
}
|
||||
|
||||
@Query("Delete FROM contribution")
|
||||
public abstract void deleteAll();
|
||||
@Query("SELECT * from contribution WHERE filename=:fileName")
|
||||
public abstract List<Contribution> getContributionWithTitle(String fileName);
|
||||
|
||||
@Query("Delete FROM contribution WHERE state = :state")
|
||||
public abstract void deleteAll(int state);
|
||||
@Query("UPDATE contribution SET state=:state WHERE state in (:toUpdateStates)")
|
||||
public abstract Single<Integer> updateStates(int state, int[] toUpdateStates);
|
||||
|
||||
@Update
|
||||
public abstract Single<Integer> update(Contribution contribution);
|
||||
@Query("Delete FROM contribution")
|
||||
public abstract void deleteAll();
|
||||
|
||||
@Update
|
||||
public abstract void updateSynchronous(Contribution contribution);
|
||||
|
||||
public Completable update(final Contribution contribution) {
|
||||
return Completable
|
||||
.fromAction(() -> updateSynchronous(contribution));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,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 java.util.Random;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
||||
|
|
@ -39,23 +38,22 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
|||
|
||||
private int position;
|
||||
private Contribution contribution;
|
||||
private Random random = new Random();
|
||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
private final MediaClient mediaClient;
|
||||
|
||||
ContributionViewHolder(View parent, Callback callback,
|
||||
MediaClient mediaClient) {
|
||||
ContributionViewHolder(final View parent, final Callback callback,
|
||||
final MediaClient mediaClient) {
|
||||
super(parent);
|
||||
this.mediaClient = mediaClient;
|
||||
ButterKnife.bind(this, parent);
|
||||
this.callback=callback;
|
||||
}
|
||||
|
||||
public void init(int position, Contribution contribution) {
|
||||
public void init(final int position, final Contribution contribution) {
|
||||
this.contribution = contribution;
|
||||
fetchAndDisplayCaption(contribution);
|
||||
this.position = position;
|
||||
String imageSource = chooseImageSource(contribution.getThumbUrl(), contribution.getLocalUri());
|
||||
final String imageSource = chooseImageSource(contribution.getThumbUrl(), contribution.getLocalUri());
|
||||
if (!TextUtils.isEmpty(imageSource)) {
|
||||
final ImageRequest imageRequest =
|
||||
ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource))
|
||||
|
|
@ -84,8 +82,8 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
|||
stateView.setVisibility(View.GONE);
|
||||
progressView.setVisibility(View.VISIBLE);
|
||||
failedImageOptions.setVisibility(View.GONE);
|
||||
long total = contribution.getDataLength();
|
||||
long transferred = contribution.getTransferred();
|
||||
final long total = contribution.getDataLength();
|
||||
final long transferred = contribution.getTransferred();
|
||||
if (transferred == 0 || transferred >= total) {
|
||||
progressView.setIndeterminate(true);
|
||||
} else {
|
||||
|
|
@ -107,14 +105,14 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
|||
*
|
||||
* @param contribution
|
||||
*/
|
||||
private void fetchAndDisplayCaption(Contribution 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());
|
||||
String wikibaseMediaId = PAGE_ID_PREFIX
|
||||
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())
|
||||
|
|
@ -141,7 +139,7 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
|||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
private String chooseImageSource(String thumbUrl, Uri localUri) {
|
||||
private String chooseImageSource(final String thumbUrl, final Uri localUri) {
|
||||
return !TextUtils.isEmpty(thumbUrl) ? thumbUrl :
|
||||
localUri != null ? localUri.toString() :
|
||||
null;
|
||||
|
|
|
|||
|
|
@ -12,16 +12,6 @@ public class ContributionsContract {
|
|||
|
||||
public interface View {
|
||||
|
||||
void showWelcomeTip(boolean numberOfUploads);
|
||||
|
||||
void showProgress(boolean shouldShow);
|
||||
|
||||
void showNoContributionsUI(boolean shouldShow);
|
||||
|
||||
void setUploadCount(int count);
|
||||
|
||||
void showContributions(List<Contribution> contributionList);
|
||||
|
||||
void showMessage(String localizedMessage);
|
||||
}
|
||||
|
||||
|
|
@ -31,8 +21,6 @@ public class ContributionsContract {
|
|||
|
||||
void deleteUpload(Contribution contribution);
|
||||
|
||||
Media getItemAtPosition(int i);
|
||||
|
||||
void updateContribution(Contribution contribution);
|
||||
|
||||
void fetchMediaDetails(Contribution contribution);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import android.widget.Toast;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import butterknife.BindView;
|
||||
|
|
@ -30,8 +29,7 @@ import fr.free.nrw.commons.campaigns.Campaign;
|
|||
import fr.free.nrw.commons.campaigns.CampaignView;
|
||||
import fr.free.nrw.commons.campaigns.CampaignsPresenter;
|
||||
import fr.free.nrw.commons.campaigns.ICampaignsView;
|
||||
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
|
||||
import fr.free.nrw.commons.contributions.ContributionsListFragment.SourceRefresher;
|
||||
import fr.free.nrw.commons.contributions.ContributionsListFragment.Callback;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
|
|
@ -54,7 +52,6 @@ import io.reactivex.Observable;
|
|||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import timber.log.Timber;
|
||||
|
|
@ -62,11 +59,10 @@ import timber.log.Timber;
|
|||
public class ContributionsFragment
|
||||
extends CommonsDaggerSupportFragment
|
||||
implements
|
||||
MediaDetailProvider,
|
||||
OnBackStackChangedListener,
|
||||
SourceRefresher,
|
||||
LocationUpdateListener,
|
||||
ICampaignsView, ContributionsContract.View {
|
||||
MediaDetailProvider,
|
||||
ICampaignsView, ContributionsContract.View, Callback {
|
||||
@Inject @Named("default_preferences") JsonKvStore store;
|
||||
@Inject NearbyController nearbyController;
|
||||
@Inject OkHttpJsonApiClient okHttpJsonApiClient;
|
||||
|
|
@ -78,8 +74,8 @@ public class ContributionsFragment
|
|||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
|
||||
private ContributionsListFragment contributionsListFragment;
|
||||
private MediaDetailPagerFragment mediaDetailPagerFragment;
|
||||
private static final String CONTRIBUTION_LIST_FRAGMENT_TAG = "ContributionListFragmentTag";
|
||||
private MediaDetailPagerFragment mediaDetailPagerFragment;
|
||||
static final String MEDIA_DETAIL_PAGER_FRAGMENT_TAG = "MediaDetailFragmentTag";
|
||||
|
||||
@BindView(R.id.card_view_nearby) public NearbyNotificationCardView nearbyNotificationCardView;
|
||||
|
|
@ -113,7 +109,6 @@ public class ContributionsFragment
|
|||
}
|
||||
};
|
||||
private boolean shouldShowMediaDetailsFragment;
|
||||
private int numberOfContributions;
|
||||
private boolean isAuthCookieAcquired;
|
||||
|
||||
@Override
|
||||
|
|
@ -128,7 +123,6 @@ public class ContributionsFragment
|
|||
ButterKnife.bind(this, view);
|
||||
presenter.onAttachView(this);
|
||||
contributionsPresenter.onAttachView(this);
|
||||
contributionsPresenter.setLifeCycleOwner(this.getViewLifecycleOwner());
|
||||
campaignView.setVisibility(View.GONE);
|
||||
checkBoxView = View.inflate(getActivity(), R.layout.nearby_permission_dialog, null);
|
||||
checkBox = (CheckBox) checkBoxView.findViewById(R.id.never_ask_again);
|
||||
|
|
@ -141,103 +135,21 @@ public class ContributionsFragment
|
|||
|
||||
if (savedInstanceState != null) {
|
||||
mediaDetailPagerFragment = (MediaDetailPagerFragment) getChildFragmentManager()
|
||||
.findFragmentByTag(MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
|
||||
.findFragmentByTag(MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
|
||||
contributionsListFragment = (ContributionsListFragment) getChildFragmentManager()
|
||||
.findFragmentByTag(CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
.findFragmentByTag(CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
shouldShowMediaDetailsFragment = savedInstanceState.getBoolean("mediaDetailsVisible");
|
||||
}
|
||||
|
||||
initFragments();
|
||||
|
||||
if(shouldShowMediaDetailsFragment){
|
||||
showMediaDetailPagerFragment();
|
||||
}else{
|
||||
showContributionsListFragment();
|
||||
}
|
||||
|
||||
if (!ConfigUtils.isBetaFlavour()) {
|
||||
setUploadCount();
|
||||
}
|
||||
|
||||
getChildFragmentManager().registerFragmentLifecycleCallbacks(
|
||||
new FragmentManager.FragmentLifecycleCallbacks() {
|
||||
@Override public void onFragmentResumed(FragmentManager fm, Fragment f) {
|
||||
super.onFragmentResumed(fm, f);
|
||||
//If media detail pager fragment is visible, hide the campaigns view [might not be the best way to do, this but yeah, this proves to work for now]
|
||||
Timber.e("onFragmentResumed %s", f.getClass().getName());
|
||||
if (f instanceof MediaDetailPagerFragment) {
|
||||
campaignView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void onFragmentDetached(FragmentManager fm, Fragment f) {
|
||||
super.onFragmentDetached(fm, f);
|
||||
Timber.e("onFragmentDetached %s", f.getClass().getName());
|
||||
//If media detail pager fragment is detached, ContributionsList fragment is gonna be visible, [becomes tightly coupled though]
|
||||
if (f instanceof MediaDetailPagerFragment) {
|
||||
fetchCampaigns();
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialose the ContributionsListFragment and MediaDetailPagerFragment fragment
|
||||
*/
|
||||
private void initFragments() {
|
||||
if (null == contributionsListFragment) {
|
||||
contributionsListFragment = new ContributionsListFragment();
|
||||
}
|
||||
|
||||
contributionsListFragment.setCallback(new Callback() {
|
||||
@Override
|
||||
public void retryUpload(Contribution contribution) {
|
||||
ContributionsFragment.this.retryUpload(contribution);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteUpload(Contribution contribution) {
|
||||
contributionsPresenter.deleteUpload(contribution);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openMediaDetail(int position) {
|
||||
showDetail(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contribution getContributionForPosition(int position) {
|
||||
return (Contribution) contributionsPresenter.getItemAtPosition(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchMediaUriFor(Contribution contribution) {
|
||||
Timber.d("Fetching thumbnail for %s", contribution.getFilename());
|
||||
contributionsPresenter.fetchMediaDetails(contribution);
|
||||
}
|
||||
});
|
||||
|
||||
if(null==mediaDetailPagerFragment){
|
||||
mediaDetailPagerFragment=new MediaDetailPagerFragment();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replaces the root frame layout with the given fragment
|
||||
* @param fragment
|
||||
* @param tag
|
||||
*/
|
||||
private void showFragment(Fragment fragment, String tag) {
|
||||
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
|
||||
transaction.replace(R.id.root_frame, fragment, tag);
|
||||
transaction.addToBackStack(CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
transaction.commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
|
|
@ -265,7 +177,7 @@ public class ContributionsFragment
|
|||
if (nearbyNotificationCardView != null) {
|
||||
if (store.getBoolean("displayNearbyCardView", true)) {
|
||||
if (nearbyNotificationCardView.cardViewVisibilityState
|
||||
== NearbyNotificationCardView.CardViewVisibilityState.READY) {
|
||||
== NearbyNotificationCardView.CardViewVisibilityState.READY) {
|
||||
nearbyNotificationCardView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -275,20 +187,22 @@ public class ContributionsFragment
|
|||
showFragment(contributionsListFragment, CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace FrameLayout with MediaDetailPagerFragment, user will see details of selected media.
|
||||
* Creates new one if null.
|
||||
*/
|
||||
private void showMediaDetailPagerFragment() {
|
||||
// hide tabs on media detail view is visible
|
||||
((MainActivity)getActivity()).hideTabs();
|
||||
((MainActivity) getActivity()).hideTabs();
|
||||
// hide nearby card view on media detail is visible
|
||||
nearbyNotificationCardView.setVisibility(View.GONE);
|
||||
|
||||
showFragment(mediaDetailPagerFragment,MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
|
||||
showFragment(mediaDetailPagerFragment, MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
|
||||
|
||||
}
|
||||
|
||||
private void setupViewForMediaDetails() {
|
||||
campaignView.setVisibility(View.GONE);
|
||||
nearbyNotificationCardView.setVisibility(View.GONE);
|
||||
((MainActivity)getActivity()).hideTabs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
((MainActivity)getActivity()).initBackButton();
|
||||
|
|
@ -307,43 +221,42 @@ public class ContributionsFragment
|
|||
|
||||
}
|
||||
|
||||
private void initFragments() {
|
||||
if (null == contributionsListFragment) {
|
||||
contributionsListFragment = new ContributionsListFragment(this);
|
||||
}
|
||||
|
||||
if (shouldShowMediaDetailsFragment) {
|
||||
showMediaDetailPagerFragment();
|
||||
} else {
|
||||
showContributionsListFragment();
|
||||
}
|
||||
|
||||
showFragment(contributionsListFragment, CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the root frame layout with the given fragment
|
||||
*
|
||||
* @param fragment
|
||||
* @param tag
|
||||
*/
|
||||
private void showFragment(Fragment fragment, String tag) {
|
||||
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
|
||||
transaction.replace(R.id.root_frame, fragment, tag);
|
||||
transaction.addToBackStack(CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
transaction.commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
public Intent getUploadServiceIntent(){
|
||||
Intent intent = new Intent(getActivity(), UploadService.class);
|
||||
intent.setAction(UploadService.ACTION_START_SERVICE);
|
||||
return intent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace whatever is in the current contributionsFragmentContainer view with
|
||||
* mediaDetailPagerFragment, and preserve previous state in back stack.
|
||||
* Called when user selects a contribution.
|
||||
*/
|
||||
private void showDetail(int i) {
|
||||
if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) {
|
||||
mediaDetailPagerFragment = new MediaDetailPagerFragment();
|
||||
showMediaDetailPagerFragment();
|
||||
}
|
||||
mediaDetailPagerFragment.showImage(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshSource() {
|
||||
contributionsPresenter.fetchContributions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Media getMediaAtPosition(int i) {
|
||||
return contributionsPresenter.getItemAtPosition(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalMediaCount() {
|
||||
return numberOfContributions;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private void setUploadCount() {
|
||||
|
||||
compositeDisposable.add(okHttpJsonApiClient
|
||||
.getUploadCount(((MainActivity)getActivity()).sessionManager.getCurrentAccount().name)
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
|
@ -373,8 +286,6 @@ public class ContributionsFragment
|
|||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
boolean mediaDetailsVisible = mediaDetailPagerFragment != null && mediaDetailPagerFragment.isVisible();
|
||||
outState.putBoolean("mediaDetailsVisible", mediaDetailsVisible);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -384,13 +295,6 @@ public class ContributionsFragment
|
|||
firstLocationUpdate = true;
|
||||
locationManager.addLocationListener(this);
|
||||
|
||||
boolean isSettingsChanged = store.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
|
||||
store.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
|
||||
if (isSettingsChanged) {
|
||||
refreshSource();
|
||||
}
|
||||
|
||||
|
||||
if (store.getBoolean("displayNearbyCardView", true)) {
|
||||
checkPermissionsAndShowNearbyCardView();
|
||||
if (nearbyNotificationCardView.cardViewVisibilityState == NearbyNotificationCardView.CardViewVisibilityState.READY) {
|
||||
|
|
@ -403,10 +307,6 @@ public class ContributionsFragment
|
|||
}
|
||||
|
||||
fetchCampaigns();
|
||||
if(isAuthCookieAcquired){
|
||||
contributionsPresenter.fetchContributions();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void checkPermissionsAndShowNearbyCardView() {
|
||||
|
|
@ -463,17 +363,11 @@ public class ContributionsFragment
|
|||
}
|
||||
|
||||
private void updateNearbyNotification(@Nullable NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
|
||||
|
||||
if (nearbyPlacesInfo != null && nearbyPlacesInfo.placeList != null && nearbyPlacesInfo.placeList.size() > 0) {
|
||||
Place closestNearbyPlace = nearbyPlacesInfo.placeList.get(0);
|
||||
String distance = formatDistanceBetween(curLatLng, closestNearbyPlace.location);
|
||||
closestNearbyPlace.setDistance(distance);
|
||||
nearbyNotificationCardView.updateContent(closestNearbyPlace);
|
||||
if (mediaDetailPagerFragment != null && mediaDetailPagerFragment.isVisible()) {
|
||||
nearbyNotificationCardView.setVisibility(View.GONE);
|
||||
}else {
|
||||
nearbyNotificationCardView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
// Means that no close nearby place is found
|
||||
nearbyNotificationCardView.setVisibility(View.GONE);
|
||||
|
|
@ -553,37 +447,13 @@ public class ContributionsFragment
|
|||
presenter.onDetachView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showWelcomeTip(boolean shouldShow) {
|
||||
contributionsListFragment.showWelcomeTip(shouldShow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showProgress(boolean shouldShow) {
|
||||
contributionsListFragment.showProgress(shouldShow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showNoContributionsUI(boolean shouldShow) {
|
||||
contributionsListFragment.showNoContributionsUI(shouldShow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUploadCount(int count) {
|
||||
this.numberOfContributions=count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showContributions(List<Contribution> contributionList) {
|
||||
contributionsListFragment.setContributions(contributionList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry upload when it is failed
|
||||
*
|
||||
* @param contribution contribution to be retried
|
||||
*/
|
||||
private void retryUpload(Contribution contribution) {
|
||||
@Override
|
||||
public void retryUpload(Contribution contribution) {
|
||||
if (NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
if (contribution.getState() == STATE_FAILED && null != uploadService) {
|
||||
uploadService.queue(contribution);
|
||||
|
|
@ -596,5 +466,29 @@ public class ContributionsFragment
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace whatever is in the current contributionsFragmentContainer view with
|
||||
* mediaDetailPagerFragment, and preserve previous state in back stack. Called when user selects a
|
||||
* contribution.
|
||||
*/
|
||||
@Override
|
||||
public void showDetail(int position) {
|
||||
if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) {
|
||||
mediaDetailPagerFragment = new MediaDetailPagerFragment();
|
||||
showMediaDetailPagerFragment();
|
||||
}
|
||||
mediaDetailPagerFragment.showImage(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Media getMediaAtPosition(int i) {
|
||||
return contributionsListFragment.getMediaAtPosition(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalMediaCount() {
|
||||
return contributionsListFragment.getTotalMediaCount();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,68 +1,71 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.paging.PagedListAdapter;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.media.MediaClient;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents The View Adapter for the List of Contributions
|
||||
*/
|
||||
public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionViewHolder> {
|
||||
public class ContributionsListAdapter extends
|
||||
PagedListAdapter<Contribution, ContributionViewHolder> {
|
||||
|
||||
private Callback callback;
|
||||
private final Callback callback;
|
||||
private final MediaClient mediaClient;
|
||||
private List<Contribution> contributions;
|
||||
|
||||
public ContributionsListAdapter(Callback callback,
|
||||
MediaClient mediaClient) {
|
||||
ContributionsListAdapter(final Callback callback,
|
||||
final MediaClient mediaClient) {
|
||||
super(DIFF_CALLBACK);
|
||||
this.callback = callback;
|
||||
this.mediaClient = mediaClient;
|
||||
contributions = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the new View Holder which will be used to display items(contributions)
|
||||
* using the onBindViewHolder(viewHolder,position)
|
||||
* Uses DiffUtil to calculate the changes in the list
|
||||
* It has methods that check ID and the content of the items to determine if its a new item
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public ContributionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
ContributionViewHolder viewHolder = new ContributionViewHolder(
|
||||
LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.layout_contribution, parent, false), callback, mediaClient);
|
||||
return viewHolder;
|
||||
}
|
||||
private static final DiffUtil.ItemCallback<Contribution> DIFF_CALLBACK =
|
||||
new DiffUtil.ItemCallback<Contribution>() {
|
||||
@Override
|
||||
public boolean areItemsTheSame(final Contribution oldContribution, final Contribution newContribution) {
|
||||
return oldContribution.getPageId().equals(newContribution.getPageId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ContributionViewHolder holder, int position) {
|
||||
final Contribution contribution = contributions.get(position);
|
||||
if (TextUtils.isEmpty(contribution.getThumbUrl())
|
||||
&& contribution.getState() == Contribution.STATE_COMPLETED) {
|
||||
callback.fetchMediaUriFor(contribution);
|
||||
}
|
||||
@Override
|
||||
public boolean areContentsTheSame(final Contribution oldContribution, final Contribution newContribution) {
|
||||
return oldContribution.equals(newContribution);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return contributions.size();
|
||||
}
|
||||
|
||||
public void setContributions(@NonNull List<Contribution> contributionList) {
|
||||
contributions = contributionList;
|
||||
notifyDataSetChanged();
|
||||
Contribution getContributionForPosition(final int position) {
|
||||
return getItem(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the new View Holder which will be used to display items(contributions) using the
|
||||
* onBindViewHolder(viewHolder,position)
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return contributions.get(position).get_id();
|
||||
public ContributionViewHolder onCreateViewHolder(@NonNull final ViewGroup parent,
|
||||
final int viewType) {
|
||||
final ContributionViewHolder viewHolder = new ContributionViewHolder(
|
||||
LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.layout_contribution, parent, false), callback, mediaClient);
|
||||
return viewHolder;
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
|
|
@ -72,9 +75,5 @@ public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionV
|
|||
void deleteUpload(Contribution contribution);
|
||||
|
||||
void openMediaDetail(int contribution);
|
||||
|
||||
Contribution getContributionForPosition(int position);
|
||||
|
||||
void fetchMediaUriFor(Contribution contribution);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import fr.free.nrw.commons.BasePresenter;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The contract for Contributions list View & Presenter
|
||||
*/
|
||||
public class ContributionsListContract {
|
||||
|
||||
public interface View {
|
||||
|
||||
void showWelcomeTip(boolean numberOfUploads);
|
||||
|
||||
void showProgress(boolean shouldShow);
|
||||
|
||||
void showNoContributionsUI(boolean shouldShow);
|
||||
}
|
||||
|
||||
public interface UserActionListener extends BasePresenter<View> {
|
||||
|
||||
void deleteUpload(Contribution contribution);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import static android.view.View.VISIBLE;
|
|||
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
|
@ -16,218 +17,217 @@ import android.widget.TextView;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.RecyclerView.LayoutManager;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.media.MediaClient;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
/**
|
||||
* Created by root on 01.06.2018.
|
||||
*/
|
||||
|
||||
public class ContributionsListFragment extends CommonsDaggerSupportFragment {
|
||||
public class ContributionsListFragment extends CommonsDaggerSupportFragment implements
|
||||
ContributionsListContract.View, ContributionsListAdapter.Callback {
|
||||
|
||||
private static final String VISIBLE_ITEM_ID = "visible_item_id";
|
||||
@BindView(R.id.contributionsList)
|
||||
RecyclerView rvContributionsList;
|
||||
@BindView(R.id.loadingContributionsProgressBar)
|
||||
ProgressBar progressBar;
|
||||
@BindView(R.id.fab_plus)
|
||||
FloatingActionButton fabPlus;
|
||||
@BindView(R.id.fab_camera)
|
||||
FloatingActionButton fabCamera;
|
||||
@BindView(R.id.fab_gallery)
|
||||
FloatingActionButton fabGallery;
|
||||
@BindView(R.id.noContributionsYet)
|
||||
TextView noContributionsYet;
|
||||
@BindView(R.id.fab_layout)
|
||||
LinearLayout fab_layout;
|
||||
private static final String RV_STATE = "rv_scroll_state";
|
||||
|
||||
@Inject @Named("default_preferences") JsonKvStore kvStore;
|
||||
@Inject ContributionController controller;
|
||||
@Inject MediaClient mediaClient;
|
||||
@BindView(R.id.contributionsList)
|
||||
RecyclerView rvContributionsList;
|
||||
@BindView(R.id.loadingContributionsProgressBar)
|
||||
ProgressBar progressBar;
|
||||
@BindView(R.id.fab_plus)
|
||||
FloatingActionButton fabPlus;
|
||||
@BindView(R.id.fab_camera)
|
||||
FloatingActionButton fabCamera;
|
||||
@BindView(R.id.fab_gallery)
|
||||
FloatingActionButton fabGallery;
|
||||
@BindView(R.id.noContributionsYet)
|
||||
TextView noContributionsYet;
|
||||
@BindView(R.id.fab_layout)
|
||||
LinearLayout fab_layout;
|
||||
|
||||
private Animation fab_close;
|
||||
private Animation fab_open;
|
||||
private Animation rotate_forward;
|
||||
private Animation rotate_backward;
|
||||
@Inject
|
||||
ContributionController controller;
|
||||
@Inject
|
||||
MediaClient mediaClient;
|
||||
|
||||
@Inject
|
||||
ContributionsListPresenter contributionsListPresenter;
|
||||
|
||||
private Animation fab_close;
|
||||
private Animation fab_open;
|
||||
private Animation rotate_forward;
|
||||
private Animation rotate_backward;
|
||||
|
||||
|
||||
private boolean isFabOpen = false;
|
||||
private boolean isFabOpen;
|
||||
|
||||
private ContributionsListAdapter adapter;
|
||||
private ContributionsListAdapter adapter;
|
||||
|
||||
private Callback callback;
|
||||
private String lastVisibleItemID;
|
||||
private final Callback callback;
|
||||
|
||||
private int SPAN_COUNT=3;
|
||||
private List<Contribution> contributions=new ArrayList<>();
|
||||
private final int SPAN_COUNT_LANDSCAPE = 3;
|
||||
private final int SPAN_COUNT_PORTRAIT = 1;
|
||||
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_contributions_list, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
initAdapter();
|
||||
return view;
|
||||
ContributionsListFragment(final Callback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public View onCreateView(
|
||||
final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
final View view = inflater.inflate(R.layout.fragment_contributions_list, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
contributionsListPresenter.onAttachView(this);
|
||||
initAdapter();
|
||||
return view;
|
||||
}
|
||||
|
||||
private void initAdapter() {
|
||||
adapter = new ContributionsListAdapter(this, mediaClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(final View view, @Nullable final Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
initRecyclerView();
|
||||
initializeAnimations();
|
||||
setListeners();
|
||||
}
|
||||
|
||||
private void initRecyclerView() {
|
||||
final GridLayoutManager layoutManager = new GridLayoutManager(getContext(),
|
||||
getSpanCount(getResources().getConfiguration().orientation));
|
||||
rvContributionsList.setLayoutManager(layoutManager);
|
||||
contributionsListPresenter.setup();
|
||||
contributionsListPresenter.contributionList.observe(this, adapter::submitList);
|
||||
rvContributionsList.setAdapter(adapter);
|
||||
}
|
||||
|
||||
private int getSpanCount(final int orientation) {
|
||||
return orientation == Configuration.ORIENTATION_LANDSCAPE ?
|
||||
SPAN_COUNT_LANDSCAPE : SPAN_COUNT_PORTRAIT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(final Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
// check orientation
|
||||
fab_layout.setOrientation(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ?
|
||||
LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
|
||||
rvContributionsList
|
||||
.setLayoutManager(new GridLayoutManager(getContext(), getSpanCount(newConfig.orientation)));
|
||||
}
|
||||
|
||||
private void initializeAnimations() {
|
||||
fab_open = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_open);
|
||||
fab_close = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_close);
|
||||
rotate_forward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_forward);
|
||||
rotate_backward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_backward);
|
||||
}
|
||||
|
||||
private void setListeners() {
|
||||
fabPlus.setOnClickListener(view -> animateFAB(isFabOpen));
|
||||
fabCamera.setOnClickListener(view -> {
|
||||
controller.initiateCameraPick(getActivity());
|
||||
animateFAB(isFabOpen);
|
||||
});
|
||||
fabGallery.setOnClickListener(view -> {
|
||||
controller.initiateGalleryPick(getActivity(), true);
|
||||
animateFAB(isFabOpen);
|
||||
});
|
||||
}
|
||||
|
||||
private void animateFAB(final boolean isFabOpen) {
|
||||
this.isFabOpen = !isFabOpen;
|
||||
if (fabPlus.isShown()) {
|
||||
if (isFabOpen) {
|
||||
fabPlus.startAnimation(rotate_backward);
|
||||
fabCamera.startAnimation(fab_close);
|
||||
fabGallery.startAnimation(fab_close);
|
||||
fabCamera.hide();
|
||||
fabGallery.hide();
|
||||
} else {
|
||||
fabPlus.startAnimation(rotate_forward);
|
||||
fabCamera.startAnimation(fab_open);
|
||||
fabGallery.startAnimation(fab_open);
|
||||
fabCamera.show();
|
||||
fabGallery.show();
|
||||
}
|
||||
this.isFabOpen = !isFabOpen;
|
||||
}
|
||||
}
|
||||
|
||||
public void setCallback(Callback callback) {
|
||||
this.callback = callback;
|
||||
/**
|
||||
* Shows welcome message if user has no contributions yet i.e. new user.
|
||||
*/
|
||||
public void showWelcomeTip(final boolean shouldShow) {
|
||||
noContributionsYet.setVisibility(shouldShow ? VISIBLE : GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible to set progress bar invisible and visible
|
||||
*
|
||||
* @param shouldShow True when contributions list should be hidden.
|
||||
*/
|
||||
public void showProgress(final boolean shouldShow) {
|
||||
progressBar.setVisibility(shouldShow ? VISIBLE : GONE);
|
||||
}
|
||||
|
||||
public void showNoContributionsUI(final boolean shouldShow) {
|
||||
noContributionsYet.setVisibility(shouldShow ? VISIBLE : GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
final GridLayoutManager layoutManager = (GridLayoutManager) rvContributionsList.getLayoutManager();
|
||||
outState.putParcelable(RV_STATE, layoutManager.onSaveInstanceState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
|
||||
super.onViewStateRestored(savedInstanceState);
|
||||
if (null != savedInstanceState) {
|
||||
final Parcelable savedRecyclerLayoutState = savedInstanceState.getParcelable(RV_STATE);
|
||||
rvContributionsList.getLayoutManager().onRestoreInstanceState(savedRecyclerLayoutState);
|
||||
}
|
||||
}
|
||||
|
||||
private void initAdapter() {
|
||||
adapter = new ContributionsListAdapter(callback, mediaClient);
|
||||
adapter.setHasStableIds(true);
|
||||
}
|
||||
@Override
|
||||
public void retryUpload(final Contribution contribution) {
|
||||
callback.retryUpload(contribution);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
initRecyclerView();
|
||||
initializeAnimations();
|
||||
setListeners();
|
||||
}
|
||||
@Override
|
||||
public void deleteUpload(final Contribution contribution) {
|
||||
contributionsListPresenter.deleteUpload(contribution);
|
||||
}
|
||||
|
||||
private void initRecyclerView() {
|
||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
rvContributionsList.setLayoutManager(new GridLayoutManager(getContext(),SPAN_COUNT));
|
||||
} else {
|
||||
rvContributionsList.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
}
|
||||
@Override
|
||||
public void openMediaDetail(final int position) {
|
||||
callback.showDetail(position);
|
||||
}
|
||||
|
||||
rvContributionsList.setAdapter(adapter);
|
||||
adapter.setContributions(contributions);
|
||||
}
|
||||
public Media getMediaAtPosition(final int i) {
|
||||
return adapter.getContributionForPosition(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
// check orientation
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
fab_layout.setOrientation(LinearLayout.HORIZONTAL);
|
||||
rvContributionsList.setLayoutManager(new GridLayoutManager(getContext(),SPAN_COUNT));
|
||||
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
fab_layout.setOrientation(LinearLayout.VERTICAL);
|
||||
rvContributionsList.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
}
|
||||
}
|
||||
public int getTotalMediaCount() {
|
||||
return adapter.getItemCount();
|
||||
}
|
||||
|
||||
private void initializeAnimations() {
|
||||
fab_open = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_open);
|
||||
fab_close = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_close);
|
||||
rotate_forward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_forward);
|
||||
rotate_backward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_backward);
|
||||
}
|
||||
public interface Callback {
|
||||
|
||||
private void setListeners() {
|
||||
fabPlus.setOnClickListener(view -> animateFAB(isFabOpen));
|
||||
fabCamera.setOnClickListener(view -> {
|
||||
controller.initiateCameraPick(getActivity());
|
||||
animateFAB(isFabOpen);
|
||||
});
|
||||
fabGallery.setOnClickListener(view -> {
|
||||
controller.initiateGalleryPick(getActivity(), true);
|
||||
animateFAB(isFabOpen);
|
||||
});
|
||||
}
|
||||
|
||||
private void animateFAB(boolean isFabOpen) {
|
||||
this.isFabOpen = !isFabOpen;
|
||||
if (fabPlus.isShown()){
|
||||
if (isFabOpen) {
|
||||
fabPlus.startAnimation(rotate_backward);
|
||||
fabCamera.startAnimation(fab_close);
|
||||
fabGallery.startAnimation(fab_close);
|
||||
fabCamera.hide();
|
||||
fabGallery.hide();
|
||||
} else {
|
||||
fabPlus.startAnimation(rotate_forward);
|
||||
fabCamera.startAnimation(fab_open);
|
||||
fabGallery.startAnimation(fab_open);
|
||||
fabCamera.show();
|
||||
fabGallery.show();
|
||||
}
|
||||
this.isFabOpen=!isFabOpen;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows welcome message if user has no contributions yet i.e. new user.
|
||||
*/
|
||||
public void showWelcomeTip(boolean shouldShow) {
|
||||
noContributionsYet.setVisibility(shouldShow ? VISIBLE : GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible to set progress bar invisible and visible
|
||||
*
|
||||
* @param shouldShow True when contributions list should be hidden.
|
||||
*/
|
||||
public void showProgress(boolean shouldShow) {
|
||||
progressBar.setVisibility(shouldShow ? VISIBLE : GONE);
|
||||
}
|
||||
|
||||
public void showNoContributionsUI(boolean shouldShow) {
|
||||
noContributionsYet.setVisibility(shouldShow ? VISIBLE : GONE);
|
||||
}
|
||||
|
||||
public void setContributions(List<Contribution> contributionList) {
|
||||
this.contributions.clear();
|
||||
this.contributions.addAll(contributionList);
|
||||
adapter.setContributions(contributions);
|
||||
}
|
||||
|
||||
public interface SourceRefresher {
|
||||
void refreshSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
LayoutManager layoutManager = rvContributionsList.getLayoutManager();
|
||||
int lastVisibleItemPosition=0;
|
||||
if(layoutManager instanceof LinearLayoutManager){
|
||||
lastVisibleItemPosition= ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition();
|
||||
}else if(layoutManager instanceof GridLayoutManager){
|
||||
lastVisibleItemPosition=((GridLayoutManager)layoutManager).findLastCompletelyVisibleItemPosition();
|
||||
}
|
||||
String idOfItemWithPosition = findIdOfItemWithPosition(lastVisibleItemPosition);
|
||||
if (null != idOfItemWithPosition) {
|
||||
outState.putString(VISIBLE_ITEM_ID, idOfItemWithPosition);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
|
||||
super.onViewStateRestored(savedInstanceState);
|
||||
if(null!=savedInstanceState){
|
||||
lastVisibleItemID =savedInstanceState.getString(VISIBLE_ITEM_ID, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the id of the contribution from the db
|
||||
* @param position
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
private String findIdOfItemWithPosition(int position) {
|
||||
Contribution contributionForPosition = callback.getContributionForPosition(position);
|
||||
if (null != contributionForPosition) {
|
||||
return contributionForPosition.getFilename();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
void retryUpload(Contribution contribution);
|
||||
|
||||
void showDetail(int position);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.paging.LivePagedListBuilder;
|
||||
import androidx.paging.PagedList;
|
||||
import fr.free.nrw.commons.contributions.ContributionsListContract.UserActionListener;
|
||||
import fr.free.nrw.commons.di.CommonsApplicationModule;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
/**
|
||||
* The presenter class for Contributions
|
||||
*/
|
||||
public class ContributionsListPresenter implements UserActionListener {
|
||||
|
||||
private final ContributionBoundaryCallback contributionBoundaryCallback;
|
||||
private final ContributionsRepository repository;
|
||||
private final Scheduler ioThreadScheduler;
|
||||
|
||||
private final CompositeDisposable compositeDisposable;
|
||||
|
||||
LiveData<PagedList<Contribution>> contributionList;
|
||||
|
||||
@Inject
|
||||
ContributionsListPresenter(
|
||||
final ContributionBoundaryCallback contributionBoundaryCallback,
|
||||
final ContributionsRepository repository,
|
||||
@Named(CommonsApplicationModule.IO_THREAD) final Scheduler ioThreadScheduler) {
|
||||
this.contributionBoundaryCallback = contributionBoundaryCallback;
|
||||
this.repository = repository;
|
||||
this.ioThreadScheduler = ioThreadScheduler;
|
||||
compositeDisposable = new CompositeDisposable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachView(final ContributionsListContract.View view) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the paged list. This method sets the configuration for paged list and ties it up with the
|
||||
* live data object. This method can be tweaked to update the lazy loading behavior of the
|
||||
* contributions list
|
||||
*/
|
||||
void setup() {
|
||||
final PagedList.Config pagedListConfig =
|
||||
(new PagedList.Config.Builder())
|
||||
.setPrefetchDistance(50)
|
||||
.setPageSize(10).build();
|
||||
contributionList = (new LivePagedListBuilder(repository.fetchContributions(), pagedListConfig)
|
||||
.setBoundaryCallback(contributionBoundaryCallback)).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachView() {
|
||||
compositeDisposable.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a failed contribution from the local db
|
||||
*/
|
||||
@Override
|
||||
public void deleteUpload(final Contribution contribution) {
|
||||
compositeDisposable.add(repository
|
||||
.deleteContributionFromDB(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import androidx.paging.DataSource.Factory;
|
||||
import io.reactivex.Completable;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Single;
|
||||
|
||||
/**
|
||||
|
|
@ -59,23 +58,23 @@ class ContributionsLocalDataSource {
|
|||
* @param contribution
|
||||
* @return
|
||||
*/
|
||||
public Single<Integer> deleteContribution(Contribution contribution) {
|
||||
public Completable deleteContribution(Contribution contribution) {
|
||||
return contributionDao.delete(contribution);
|
||||
}
|
||||
|
||||
public LiveData<List<Contribution>> getContributions() {
|
||||
public Factory<Integer, Contribution> getContributions() {
|
||||
return contributionDao.fetchContributions();
|
||||
}
|
||||
|
||||
public Completable saveContributions(List<Contribution> contributions) {
|
||||
return contributionDao.deleteAllAndSave(contributions);
|
||||
public Single<List<Long>> saveContributions(List<Contribution> contributions) {
|
||||
return contributionDao.save(contributions);
|
||||
}
|
||||
|
||||
public void set(String key, long value) {
|
||||
defaultKVStore.putLong(key,value);
|
||||
}
|
||||
|
||||
public Single<Integer> updateContribution(Contribution contribution) {
|
||||
public Completable updateContribution(Contribution contribution) {
|
||||
return contributionDao.update(contribution);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ 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.db.AppDatabase;
|
||||
import fr.free.nrw.commons.di.CommonsApplicationModule;
|
||||
import fr.free.nrw.commons.mwapi.UserClient;
|
||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||
|
|
@ -25,6 +25,9 @@ import javax.inject.Inject;
|
|||
import javax.inject.Named;
|
||||
import timber.log.Timber;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
/**
|
||||
* The presenter class for Contributions
|
||||
*/
|
||||
|
|
@ -35,25 +38,10 @@ public class ContributionsPresenter implements UserActionListener {
|
|||
private final Scheduler ioThreadScheduler;
|
||||
private CompositeDisposable compositeDisposable;
|
||||
private ContributionsContract.View view;
|
||||
private List<Contribution> contributionList=new ArrayList<>();
|
||||
|
||||
@Inject
|
||||
Context context;
|
||||
|
||||
@Inject
|
||||
UserClient userClient;
|
||||
|
||||
@Inject
|
||||
AppDatabase appDatabase;
|
||||
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
|
||||
@Inject
|
||||
MediaDataExtractor mediaDataExtractor;
|
||||
|
||||
private LifecycleOwner lifeCycleOwner;
|
||||
|
||||
@Inject
|
||||
ContributionsPresenter(ContributionsRepository repository, @Named(CommonsApplicationModule.MAIN_THREAD) Scheduler mainThreadScheduler,@Named(CommonsApplicationModule.IO_THREAD) Scheduler ioThreadScheduler) {
|
||||
this.repository = repository;
|
||||
|
|
@ -61,74 +49,12 @@ public class ContributionsPresenter implements UserActionListener {
|
|||
this.ioThreadScheduler=ioThreadScheduler;
|
||||
}
|
||||
|
||||
private String user;
|
||||
|
||||
@Override
|
||||
public void onAttachView(ContributionsContract.View view) {
|
||||
this.view = view;
|
||||
compositeDisposable=new CompositeDisposable();
|
||||
}
|
||||
|
||||
public void setLifeCycleOwner(LifecycleOwner lifeCycleOwner){
|
||||
this.lifeCycleOwner=lifeCycleOwner;
|
||||
}
|
||||
|
||||
public void fetchContributions() {
|
||||
Timber.d("fetch Contributions");
|
||||
LiveData<List<Contribution>> liveDataContributions = repository.fetchContributions();
|
||||
if(null!=lifeCycleOwner) {
|
||||
liveDataContributions.observe(lifeCycleOwner, this::showContributions);
|
||||
}
|
||||
|
||||
if (NetworkUtils.isInternetConnectionEstablished(CommonsApplication.getInstance()) && shouldFetchContributions()) {
|
||||
Timber.d("fetching contributions: ");
|
||||
view.showProgress(true);
|
||||
this.user = sessionManager.getUserName();
|
||||
view.showContributions(Collections.emptyList());
|
||||
compositeDisposable.add(userClient.logEvents(user)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.observeOn(mainThreadScheduler)
|
||||
.doOnNext(mwQueryLogEvent -> Timber.d("Received image %s", mwQueryLogEvent.title()))
|
||||
.filter(mwQueryLogEvent -> !mwQueryLogEvent.isDeleted()).doOnNext(mwQueryLogEvent -> Timber.d("Image %s passed filters", mwQueryLogEvent.title()))
|
||||
.map(image -> new Contribution(image, user))
|
||||
.toList()
|
||||
.subscribe(this::saveContributionsToDB, error -> {
|
||||
Timber.e("Failed to fetch contributions: %s", error.getMessage());
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private void showContributions(@NonNull List<Contribution> contributions) {
|
||||
view.showProgress(false);
|
||||
if (contributions.isEmpty()) {
|
||||
view.showWelcomeTip(true);
|
||||
view.showNoContributionsUI(true);
|
||||
} else {
|
||||
view.showWelcomeTip(false);
|
||||
view.showNoContributionsUI(false);
|
||||
view.setUploadCount(contributions.size());
|
||||
view.showContributions(contributions);
|
||||
this.contributionList.clear();
|
||||
this.contributionList.addAll(contributions);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveContributionsToDB(List<Contribution> contributions) {
|
||||
Timber.e("Fetched: "+contributions.size()+" contributions "+" saving to db");
|
||||
repository.save(contributions).subscribeOn(ioThreadScheduler).subscribe();
|
||||
repository.set("last_fetch_timestamp",System.currentTimeMillis());
|
||||
}
|
||||
|
||||
private boolean shouldFetchContributions() {
|
||||
long lastFetchTimestamp = repository.getLong("last_fetch_timestamp");
|
||||
Timber.d("last fetch timestamp: %s", lastFetchTimestamp);
|
||||
if(lastFetchTimestamp!=0){
|
||||
return System.currentTimeMillis()-lastFetchTimestamp>15*60*100;
|
||||
}
|
||||
Timber.d("should fetch contributions: %s", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachView() {
|
||||
this.view = null;
|
||||
|
|
@ -146,24 +72,10 @@ public class ContributionsPresenter implements UserActionListener {
|
|||
*/
|
||||
@Override
|
||||
public void deleteUpload(Contribution contribution) {
|
||||
compositeDisposable.add(repository.deleteContributionFromDB(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a contribution at the specified cursor position
|
||||
*
|
||||
* @param i
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Media getItemAtPosition(int i) {
|
||||
if (i == -1 || contributionList.size() < i+1) {
|
||||
return null;
|
||||
}
|
||||
return contributionList.get(i);
|
||||
compositeDisposable.add(repository
|
||||
.deleteContributionFromDB(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import androidx.paging.DataSource.Factory;
|
||||
import io.reactivex.Completable;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Single;
|
||||
|
||||
/**
|
||||
|
|
@ -33,7 +32,7 @@ public class ContributionsRepository {
|
|||
* @param contribution
|
||||
* @return
|
||||
*/
|
||||
public Single<Integer> deleteContributionFromDB(Contribution contribution) {
|
||||
public Completable deleteContributionFromDB(Contribution contribution) {
|
||||
return localDataSource.deleteContribution(contribution);
|
||||
}
|
||||
|
||||
|
|
@ -46,11 +45,11 @@ public class ContributionsRepository {
|
|||
return localDataSource.getContributionWithFileName(fileName);
|
||||
}
|
||||
|
||||
public LiveData<List<Contribution>> fetchContributions() {
|
||||
public Factory<Integer, Contribution> fetchContributions() {
|
||||
return localDataSource.getContributions();
|
||||
}
|
||||
|
||||
public Completable save(List<Contribution> contributions) {
|
||||
public Single<List<Long>> save(List<Contribution> contributions) {
|
||||
return localDataSource.saveContributions(contributions);
|
||||
}
|
||||
|
||||
|
|
@ -58,11 +57,7 @@ public class ContributionsRepository {
|
|||
localDataSource.set(key,value);
|
||||
}
|
||||
|
||||
public long getLong(String key) {
|
||||
return localDataSource.getLong(key);
|
||||
}
|
||||
|
||||
public Single<Integer> updateContribution(Contribution contribution) {
|
||||
public Completable updateContribution(Contribution contribution) {
|
||||
return localDataSource.updateContribution(contribution);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package fr.free.nrw.commons.contributions;
|
|||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
|
|
@ -12,7 +11,6 @@ import android.view.MenuItem;
|
|||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.GravityCompat;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
|
|
@ -20,16 +18,9 @@ import androidx.fragment.app.Fragment;
|
|||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||
|
|
@ -44,10 +35,10 @@ import fr.free.nrw.commons.upload.UploadService;
|
|||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static android.content.ContentResolver.requestSync;
|
||||
|
||||
public class MainActivity extends NavigationBaseActivity implements FragmentManager.OnBackStackChangedListener {
|
||||
|
||||
@BindView(R.id.tab_layout)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import fr.free.nrw.commons.contributions.ContributionDao
|
|||
* The database for accessing the respective DAOs
|
||||
*
|
||||
*/
|
||||
@Database(entities = [Contribution::class], version = 1, exportSchema = false)
|
||||
@Database(entities = [Contribution::class], version = 2, exportSchema = false)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun contributionDao(): ContributionDao
|
||||
|
|
|
|||
|
|
@ -224,7 +224,9 @@ public class CommonsApplicationModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
public AppDatabase provideAppDataBase() {
|
||||
return Room.databaseBuilder(applicationContext, AppDatabase.class, "commons_room.db").build();
|
||||
return Room.databaseBuilder(applicationContext, AppDatabase.class, "commons_room.db")
|
||||
.fallbackToDestructiveMigration()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ public class MediaClient {
|
|||
|
||||
//OkHttpJsonApiClient used JsonKvStore for this. I don't know why.
|
||||
private Map<String, Map<String, String>> continuationStore;
|
||||
private Map<String, Boolean> continuationExists;
|
||||
public static final String NO_CAPTION = "No caption";
|
||||
private static final String NO_DEPICTION = "No depiction";
|
||||
|
||||
|
|
@ -40,6 +41,7 @@ public class MediaClient {
|
|||
this.mediaInterface = mediaInterface;
|
||||
this.mediaDetailInterface = mediaDetailInterface;
|
||||
this.continuationStore = new HashMap<>();
|
||||
this.continuationExists = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -83,6 +85,36 @@ public class MediaClient {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes the userName as input and returns a list of Media objects filtered using
|
||||
* allimages query It uses the allimages query API to get the images contributed by the userName,
|
||||
* 10 at a time.
|
||||
*
|
||||
* @param userName the username
|
||||
* @return
|
||||
*/
|
||||
public Single<List<Media>> getMediaListForUser(String userName) {
|
||||
Map<String, String> continuation =
|
||||
continuationStore.containsKey("user_" + userName)
|
||||
? continuationStore.get("user_" + userName)
|
||||
: Collections.emptyMap();
|
||||
return responseToMediaList(mediaInterface
|
||||
.getMediaListForUser(userName, 10, continuation), "user_" + userName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if media for user has reached the end of the list.
|
||||
* @param userName
|
||||
* @return
|
||||
*/
|
||||
public boolean doesMediaListForUserHaveMorePages(String userName) {
|
||||
final String key = "user_" + userName;
|
||||
if(continuationExists.containsKey(key)) {
|
||||
return continuationExists.get(key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes a keyword as input and returns a list of Media objects filtered using image generator query
|
||||
* It uses the generator query API to get the images searched using a query, 10 at a time.
|
||||
|
|
@ -106,7 +138,12 @@ public class MediaClient {
|
|||
|| null == mwQueryResponse.query().pages()) {
|
||||
return Observable.empty();
|
||||
}
|
||||
continuationStore.put(key, mwQueryResponse.continuation());
|
||||
if(mwQueryResponse.continuation() != null) {
|
||||
continuationStore.put(key, mwQueryResponse.continuation());
|
||||
continuationExists.put(key, true);
|
||||
} else {
|
||||
continuationExists.put(key, false);
|
||||
}
|
||||
return Observable.fromIterable(mwQueryResponse.query().pages());
|
||||
})
|
||||
.map(Media::from)
|
||||
|
|
|
|||
|
|
@ -23,26 +23,21 @@ import butterknife.ButterKnife;
|
|||
import com.google.android.material.snackbar.Snackbar;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.bookmarks.Bookmark;
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider;
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
|
||||
import fr.free.nrw.commons.category.CategoryImagesCallback;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.utils.DownloadUtils;
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment implements ViewPager.OnPageChangeListener {
|
||||
|
||||
@Inject SessionManager sessionManager;
|
||||
@Inject @Named("default_preferences") JsonKvStore store;
|
||||
@Inject BookmarkPicturesDao bookmarkDao;
|
||||
|
||||
@BindView(R.id.mediaDetailsPager) ViewPager pager;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
package fr.free.nrw.commons.media;
|
||||
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import fr.free.nrw.commons.depictions.models.DepictionResponse;
|
||||
import io.reactivex.Observable;
|
||||
import java.util.Map;
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
import retrofit2.http.QueryMap;
|
||||
|
|
@ -17,6 +15,7 @@ public interface MediaInterface {
|
|||
String MEDIA_PARAMS="&prop=imageinfo&iiprop=url|extmetadata&iiurlwidth=640" +
|
||||
"&iiextmetadatafilter=DateTime|Categories|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal" +
|
||||
"|Artist|LicenseShortName|LicenseUrl";
|
||||
|
||||
/**
|
||||
* Checks if a page exists or not.
|
||||
*
|
||||
|
|
@ -48,6 +47,19 @@ public interface MediaInterface {
|
|||
MEDIA_PARAMS)
|
||||
Observable<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
|
||||
*
|
||||
* @param username user's Wikimedia Commons username.
|
||||
* @param itemLimit how many images are returned
|
||||
* @param continuation the continuation string from the previous query or empty map
|
||||
* @return
|
||||
*/
|
||||
@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,
|
||||
@Query("gailimit") int itemLimit, @QueryMap(encoded = true) Map<String, String> continuation);
|
||||
|
||||
/**
|
||||
* This method retrieves a list of Media objects filtered using image generator query
|
||||
*
|
||||
|
|
@ -86,21 +98,15 @@ public interface MediaInterface {
|
|||
Observable<MwParseResponse> getPageHtml(@Query("page") String title);
|
||||
|
||||
/**
|
||||
* 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")
|
||||
Observable<MwQueryResponse> fetchCaptionByFilename(@Query("language") String language, @Query("titles") String filename);
|
||||
* 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 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&list=search&format=json&srnamespace=6")
|
||||
Observable<DepictionResponse> fetchImagesForDepictedItem(@Query("srsearch") String query, @Query("sroffset") String sroffset);
|
||||
@GET("w/api.php?action=query&list=search&format=json&srnamespace=6")
|
||||
Observable<DepictionResponse> fetchImagesForDepictedItem(@Query("srsearch") String query,
|
||||
@Query("sroffset") String sroffset);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ public class Prefs {
|
|||
public static String TRACKING_ENABLED = "eventLogging";
|
||||
public static final String DEFAULT_LICENSE = "defaultLicense";
|
||||
public static final String UPLOADS_SHOWING = "uploadsshowing";
|
||||
public static final String IS_CONTRIBUTION_COUNT_CHANGED = "ccontributionCountChanged";
|
||||
public static final String MANAGED_EXIF_TAGS = "managed_exif_tags";
|
||||
public static final String KEY_LANGUAGE_VALUE = "languageDescription";
|
||||
public static final String KEY_THEME_VALUE = "appThemePref";
|
||||
|
|
|
|||
|
|
@ -65,43 +65,6 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||
});
|
||||
}
|
||||
|
||||
final EditTextPreference uploadLimit = findPreference("uploads");
|
||||
int currentUploadLimit = defaultKvStore.getInt(Prefs.UPLOADS_SHOWING, 100);
|
||||
uploadLimit.setText(String.valueOf(currentUploadLimit));
|
||||
|
||||
uploadLimit.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
|
||||
if (newValue.toString().length() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int value = Integer.parseInt(newValue.toString());
|
||||
if (value > 500) {
|
||||
Snackbar error = Snackbar.make(getView(), R.string.maximum_limit_alert, Snackbar.LENGTH_LONG);
|
||||
error.show();
|
||||
return false;
|
||||
} else if (value == 0) {
|
||||
Snackbar error = Snackbar.make(getView(), R.string.cannot_be_zero, Snackbar.LENGTH_LONG);
|
||||
error.show();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
uploadLimit.setOnBindEditTextListener(editText -> {
|
||||
|
||||
editText.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
editText.selectAll();
|
||||
int maxLength = 3; // set maxLength to 3
|
||||
editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)});
|
||||
|
||||
int value = Integer.parseInt(editText.getText().toString());
|
||||
|
||||
defaultKvStore.putInt(Prefs.UPLOADS_SHOWING, value);
|
||||
defaultKvStore.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, true);
|
||||
uploadLimit.setText(Integer.toString(value));
|
||||
});
|
||||
|
||||
langListPreference = findPreference("descriptionDefaultLanguagePref");
|
||||
prepareLanguages();
|
||||
Preference betaTesterPreference = findPreference("becomeBetaTester");
|
||||
|
|
@ -121,7 +84,6 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||
findPreference("displayNearbyCardView").setEnabled(false);
|
||||
findPreference("displayLocationPermissionForCardView").setEnabled(false);
|
||||
findPreference("displayCampaignsCardView").setEnabled(false);
|
||||
uploadLimit.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,9 +23,14 @@ import fr.free.nrw.commons.di.CommonsDaggerService;
|
|||
import fr.free.nrw.commons.media.MediaClient;
|
||||
import fr.free.nrw.commons.utils.CommonsDateUtil;
|
||||
import fr.free.nrw.commons.wikidata.WikidataEditService;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.functions.Action;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.processors.PublishProcessor;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.io.File;
|
||||
|
|
@ -33,6 +38,7 @@ import java.io.IOException;
|
|||
import java.text.ParseException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.inject.Inject;
|
||||
|
|
@ -106,10 +112,10 @@ public class UploadService extends CommonsDaggerService {
|
|||
notificationManager.notify(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build());
|
||||
|
||||
contribution.setTransferred(transferred);
|
||||
compositeDisposable.add(contributionDao.
|
||||
save(contribution).subscribeOn(ioThreadScheduler)
|
||||
.observeOn(mainThreadScheduler)
|
||||
.subscribe());
|
||||
|
||||
compositeDisposable.add(contributionDao.update(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -156,14 +162,11 @@ public class UploadService extends CommonsDaggerService {
|
|||
Timber.d("%d uploads left", toUpload);
|
||||
notificationManager.notify(contribution.getLocalUri().toString(), NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build());
|
||||
}
|
||||
|
||||
compositeDisposable.add(contributionDao
|
||||
.save(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.observeOn(mainThreadScheduler)
|
||||
.subscribe(aLong->{
|
||||
contribution.set_id(aLong);
|
||||
uploadContribution(contribution);
|
||||
}, Throwable::printStackTrace));
|
||||
.subscribe(() -> uploadContribution(contribution)));
|
||||
}
|
||||
|
||||
private boolean freshStart = true;
|
||||
|
|
@ -269,7 +272,7 @@ public class UploadService extends CommonsDaggerService {
|
|||
}
|
||||
|
||||
private void onUpload(Contribution contribution, String notificationTag,
|
||||
UploadResult uploadResult) throws ParseException {
|
||||
UploadResult uploadResult) {
|
||||
Timber.d("Stash upload response 2 is %s", uploadResult.toString());
|
||||
|
||||
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
|
||||
|
|
@ -282,8 +285,7 @@ public class UploadService extends CommonsDaggerService {
|
|||
}
|
||||
}
|
||||
|
||||
private void onSuccessfulUpload(Contribution contribution, UploadResult uploadResult)
|
||||
throws ParseException {
|
||||
private void onSuccessfulUpload(Contribution contribution, UploadResult uploadResult) {
|
||||
compositeDisposable
|
||||
.add(wikidataEditService.addDepictionsAndCaptions(uploadResult, contribution));
|
||||
WikidataPlace wikidataPlace = contribution.getWikidataPlace();
|
||||
|
|
@ -293,17 +295,11 @@ public class UploadService extends CommonsDaggerService {
|
|||
saveCompletedContribution(contribution, uploadResult);
|
||||
}
|
||||
|
||||
private void saveCompletedContribution(Contribution contribution, UploadResult uploadResult) throws ParseException {
|
||||
contribution.setFilename(uploadResult.createCanonicalFileName());
|
||||
contribution.setImageUrl(uploadResult.getImageinfo().getOriginalUrl());
|
||||
contribution.setState(Contribution.STATE_COMPLETED);
|
||||
contribution.setDateUploaded(CommonsDateUtil.getIso8601DateFormatTimestamp()
|
||||
.parse(uploadResult.getImageinfo().getTimestamp()));
|
||||
compositeDisposable.add(contributionDao
|
||||
.save(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.observeOn(mainThreadScheduler)
|
||||
.subscribe());
|
||||
private void saveCompletedContribution(Contribution contribution, UploadResult uploadResult) {
|
||||
compositeDisposable.add(mediaClient.getMedia("File:" + uploadResult.getFilename())
|
||||
.map(media -> new Contribution(media, Contribution.STATE_COMPLETED))
|
||||
.flatMapCompletable(newContribution -> contributionDao.saveAndDelete(contribution, newContribution))
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
|
|
@ -317,10 +313,11 @@ public class UploadService extends CommonsDaggerService {
|
|||
notificationManager.notify(contribution.getLocalUri().toString(), NOTIFICATION_UPLOAD_FAILED, curNotification.build());
|
||||
|
||||
contribution.setState(Contribution.STATE_FAILED);
|
||||
compositeDisposable.add(contributionDao.save(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.observeOn(mainThreadScheduler)
|
||||
.subscribe());
|
||||
|
||||
compositeDisposable.add(contributionDao
|
||||
.update(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
private String findUniqueFilename(String fileName) throws IOException {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,16 @@ public class CommonsDateUtil {
|
|||
return simpleDateFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets SimpleDateFormat for date pattern returned by Media object
|
||||
* @return simpledateformat
|
||||
*/
|
||||
public static SimpleDateFormat getMediaSimpleDateFormat() {
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ROOT);
|
||||
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
return simpleDateFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the timestamp pattern for a date
|
||||
* @return timestamp
|
||||
|
|
|
|||
|
|
@ -26,13 +26,6 @@
|
|||
android:summary="@string/use_external_storage_summary"
|
||||
android:title="@string/use_external_storage" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="100"
|
||||
android:key="uploads"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:singleLineTitle="false"
|
||||
android:title="@string/set_limit" />
|
||||
|
||||
<ListPreference
|
||||
android:key="descriptionDefaultLanguagePref"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
package fr.free.nrw.commons.contributions
|
||||
|
||||
import android.content.Context
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import fr.free.nrw.commons.utils.NetworkUtilsTest
|
||||
import fr.free.nrw.commons.utils.createMockDataSourceFactory
|
||||
import io.reactivex.Scheduler
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import io.reactivex.schedulers.TestScheduler
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers.*
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
import java.lang.RuntimeException
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* The unit test class for ContributionBoundaryCallbackTest
|
||||
*/
|
||||
class ContributionBoundaryCallbackTest {
|
||||
@Mock
|
||||
internal lateinit var repository: ContributionsRepository
|
||||
|
||||
@Mock
|
||||
internal lateinit var sessionManager: SessionManager
|
||||
|
||||
@Mock
|
||||
internal lateinit var mediaClient: MediaClient
|
||||
|
||||
private lateinit var contributionBoundaryCallback: ContributionBoundaryCallback
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
var instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
lateinit var scheduler: Scheduler
|
||||
|
||||
/**
|
||||
* initial setup
|
||||
*/
|
||||
@Before
|
||||
@Throws(Exception::class)
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
scheduler = Schedulers.trampoline()
|
||||
contributionBoundaryCallback =
|
||||
ContributionBoundaryCallback(repository, sessionManager, mediaClient, scheduler);
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnZeroItemsLoaded() {
|
||||
whenever(repository.save(anyList<Contribution>()))
|
||||
.thenReturn(Single.just(listOf(1L, 2L)))
|
||||
whenever(sessionManager.userName).thenReturn("Test")
|
||||
whenever(mediaClient.getMediaListForUser(anyString())).thenReturn(
|
||||
Single.just(listOf(mock(Media::class.java)))
|
||||
)
|
||||
whenever(mediaClient.doesMediaListForUserHaveMorePages(anyString()))
|
||||
.thenReturn(true)
|
||||
contributionBoundaryCallback.onZeroItemsLoaded()
|
||||
verify(repository).save(anyList<Contribution>());
|
||||
verify(mediaClient).getMediaListForUser(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLastItemLoaded() {
|
||||
whenever(repository.save(anyList<Contribution>()))
|
||||
.thenReturn(Single.just(listOf(1L, 2L)))
|
||||
whenever(sessionManager.userName).thenReturn("Test")
|
||||
whenever(mediaClient.getMediaListForUser(anyString())).thenReturn(
|
||||
Single.just(listOf(mock(Media::class.java)))
|
||||
)
|
||||
whenever(mediaClient.doesMediaListForUserHaveMorePages(anyString()))
|
||||
.thenReturn(true)
|
||||
contributionBoundaryCallback.onItemAtEndLoaded(mock(Contribution::class.java))
|
||||
verify(repository).save(anyList());
|
||||
verify(mediaClient).getMediaListForUser(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnFrontItemLoaded() {
|
||||
whenever(repository.save(anyList<Contribution>()))
|
||||
.thenReturn(Single.just(listOf(1L, 2L)))
|
||||
whenever(sessionManager.userName).thenReturn("Test")
|
||||
whenever(mediaClient.getMediaListForUser(anyString())).thenReturn(
|
||||
Single.just(listOf(mock(Media::class.java)))
|
||||
)
|
||||
whenever(mediaClient.doesMediaListForUserHaveMorePages(anyString()))
|
||||
.thenReturn(true)
|
||||
contributionBoundaryCallback.onItemAtFrontLoaded(mock(Contribution::class.java))
|
||||
verify(repository).save(anyList());
|
||||
verify(mediaClient).getMediaListForUser(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFetchContributions() {
|
||||
whenever(repository.save(anyList<Contribution>()))
|
||||
.thenReturn(Single.just(listOf(1L, 2L)))
|
||||
whenever(sessionManager.userName).thenReturn("Test")
|
||||
whenever(mediaClient.getMediaListForUser(anyString())).thenReturn(
|
||||
Single.just(listOf(mock(Media::class.java)))
|
||||
)
|
||||
whenever(mediaClient.doesMediaListForUserHaveMorePages(anyString()))
|
||||
.thenReturn(true)
|
||||
contributionBoundaryCallback.fetchContributions()
|
||||
verify(repository).save(anyList());
|
||||
verify(mediaClient).getMediaListForUser(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFetchContributionsForEndOfList() {
|
||||
whenever(sessionManager.userName).thenReturn("Test")
|
||||
whenever(mediaClient.doesMediaListForUserHaveMorePages(anyString()))
|
||||
.thenReturn(false)
|
||||
contributionBoundaryCallback.fetchContributions()
|
||||
verify(mediaClient, times(0)).getMediaListForUser(anyString())
|
||||
verifyNoMoreInteractions(repository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFetchContributionsFailed() {
|
||||
whenever(sessionManager.userName).thenReturn("Test")
|
||||
whenever(mediaClient.doesMediaListForUserHaveMorePages(anyString()))
|
||||
.thenReturn(true)
|
||||
whenever(mediaClient.getMediaListForUser(anyString())).thenReturn(Single.error(Exception("Error")))
|
||||
contributionBoundaryCallback.fetchContributions()
|
||||
verifyZeroInteractions(repository);
|
||||
verify(mediaClient).getMediaListForUser(anyString());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package fr.free.nrw.commons.contributions
|
||||
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.nhaarman.mockitokotlin2.times
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Scheduler
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
/**
|
||||
* The unit test class for ContributionsListPresenterTest
|
||||
*/
|
||||
class ContributionsListPresenterTest {
|
||||
@Mock
|
||||
internal lateinit var contributionBoundaryCallback: ContributionBoundaryCallback
|
||||
|
||||
@Mock
|
||||
internal lateinit var repository: ContributionsRepository
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
var instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
lateinit var scheduler: Scheduler
|
||||
|
||||
lateinit var contributionsListPresenter: ContributionsListPresenter
|
||||
|
||||
/**
|
||||
* initial setup
|
||||
*/
|
||||
@Before
|
||||
@Throws(Exception::class)
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
scheduler = Schedulers.trampoline()
|
||||
contributionsListPresenter =
|
||||
ContributionsListPresenter(contributionBoundaryCallback, repository, scheduler);
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeleteUpload() {
|
||||
whenever(repository.deleteContributionFromDB(any<Contribution>()))
|
||||
.thenReturn(Completable.complete())
|
||||
contributionsListPresenter.deleteUpload(mock(Contribution::class.java))
|
||||
verify(repository, times(1))
|
||||
.deleteContributionFromDB(ArgumentMatchers.any(Contribution::class.java));
|
||||
}
|
||||
}
|
||||
|
|
@ -7,9 +7,11 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.loader.content.CursorLoader
|
||||
import androidx.loader.content.Loader
|
||||
import com.nhaarman.mockitokotlin2.any
|
||||
import com.nhaarman.mockitokotlin2.mock
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Scheduler
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.schedulers.TestScheduler
|
||||
|
|
@ -17,9 +19,11 @@ import org.junit.Before
|
|||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.ArgumentMatchers.*
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.MockitoAnnotations
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* The unit test class for ContributionsPresenter
|
||||
|
|
@ -42,7 +46,7 @@ class ContributionsPresenterTest {
|
|||
|
||||
@Rule @JvmField var instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
lateinit var scheduler : Scheduler
|
||||
lateinit var scheduler : TestScheduler
|
||||
|
||||
/**
|
||||
* initial setup
|
||||
|
|
@ -60,23 +64,13 @@ class ContributionsPresenterTest {
|
|||
liveData=MutableLiveData()
|
||||
}
|
||||
|
||||
/**
|
||||
* Test fetch contributions
|
||||
*/
|
||||
@Test
|
||||
fun testFetchContributions(){
|
||||
whenever(repository.getString(ArgumentMatchers.anyString())).thenReturn("10")
|
||||
whenever(repository.fetchContributions()).thenReturn(liveData)
|
||||
contributionsPresenter.fetchContributions()
|
||||
verify(repository).fetchContributions()
|
||||
}
|
||||
|
||||
/**
|
||||
* Test presenter actions onDeleteContribution
|
||||
*/
|
||||
@Test
|
||||
fun testDeleteContribution() {
|
||||
whenever(repository.deleteContributionFromDB(ArgumentMatchers.any(Contribution::class.java))).thenReturn(Single.just(1))
|
||||
whenever(repository.deleteContributionFromDB(ArgumentMatchers.any<Contribution>()))
|
||||
.thenReturn(Completable.complete())
|
||||
contributionsPresenter.deleteUpload(contribution)
|
||||
verify(repository).deleteContributionFromDB(contribution)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
package fr.free.nrw.commons.contributions
|
||||
|
||||
import com.nhaarman.mockitokotlin2.times
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import fr.free.nrw.commons.utils.createMockDataSourceFactory
|
||||
import io.reactivex.Scheduler
|
||||
import io.reactivex.Single
|
||||
import junit.framework.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.*
|
||||
import org.mockito.Mockito.any
|
||||
import org.mockito.Mockito.mock
|
||||
|
||||
/**
|
||||
* The unit test class for ContributionsRepositoryTest
|
||||
*/
|
||||
class ContributionsRepositoryTest {
|
||||
@Mock
|
||||
internal lateinit var localDataSource: ContributionsLocalDataSource
|
||||
|
||||
@InjectMocks
|
||||
private lateinit var contributionsRepository: ContributionsRepository
|
||||
|
||||
lateinit var scheduler: Scheduler
|
||||
|
||||
/**
|
||||
* initial setup
|
||||
*/
|
||||
@Before
|
||||
@Throws(Exception::class)
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFetchContributions() {
|
||||
val contribution = mock(Contribution::class.java)
|
||||
whenever(localDataSource.getContributions())
|
||||
.thenReturn(createMockDataSourceFactory(listOf(contribution)))
|
||||
val contributionsFactory = contributionsRepository.fetchContributions()
|
||||
verify(localDataSource, times(1)).getContributions();
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSaveContribution() {
|
||||
val contributions = listOf(mock(Contribution::class.java))
|
||||
whenever(localDataSource.saveContributions(ArgumentMatchers.anyList()))
|
||||
.thenReturn(Single.just(listOf(1L)))
|
||||
val save = contributionsRepository.save(contributions).test().assertValueAt(0) {
|
||||
it.size == 1 && it.get(0) == 1L
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package fr.free.nrw.commons.media
|
||||
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.utils.CommonsDateUtil
|
||||
import io.reactivex.Observable
|
||||
|
|
@ -7,18 +8,16 @@ import junit.framework.Assert.*
|
|||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.*
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.mock
|
||||
import org.wikipedia.dataclient.mwapi.ImageDetails
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryPage
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResult
|
||||
import org.wikipedia.gallery.ImageInfo
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatchers.*
|
||||
import java.util.*
|
||||
import org.mockito.Captor
|
||||
|
||||
|
||||
import org.mockito.Mockito.*
|
||||
|
||||
|
||||
class MediaClientTest {
|
||||
|
|
@ -46,9 +45,10 @@ class MediaClientTest {
|
|||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
|
||||
`when`(mediaInterface!!.checkPageExistsUsingTitle(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
|
||||
val checkPageExistsUsingTitle = mediaClient!!.checkPageExistsUsingTitle("File:Test.jpg").blockingGet()
|
||||
val checkPageExistsUsingTitle =
|
||||
mediaClient!!.checkPageExistsUsingTitle("File:Test.jpg").blockingGet()
|
||||
assertTrue(checkPageExistsUsingTitle)
|
||||
}
|
||||
|
||||
|
|
@ -63,9 +63,10 @@ class MediaClientTest {
|
|||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
|
||||
`when`(mediaInterface!!.checkPageExistsUsingTitle(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
|
||||
val checkPageExistsUsingTitle = mediaClient!!.checkPageExistsUsingTitle("File:Test.jpg").blockingGet()
|
||||
val checkPageExistsUsingTitle =
|
||||
mediaClient!!.checkPageExistsUsingTitle("File:Test.jpg").blockingGet()
|
||||
assertFalse(checkPageExistsUsingTitle)
|
||||
}
|
||||
|
||||
|
|
@ -80,7 +81,7 @@ class MediaClientTest {
|
|||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
|
||||
`when`(mediaInterface!!.checkFileExistsUsingSha(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
|
||||
val checkFileExistsUsingSha = mediaClient!!.checkFileExistsUsingSha("abcde").blockingGet()
|
||||
assertTrue(checkFileExistsUsingSha)
|
||||
|
|
@ -97,7 +98,7 @@ class MediaClientTest {
|
|||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
|
||||
`when`(mediaInterface!!.checkFileExistsUsingSha(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
|
||||
val checkFileExistsUsingSha = mediaClient!!.checkFileExistsUsingSha("abcde").blockingGet()
|
||||
assertFalse(checkFileExistsUsingSha)
|
||||
|
|
@ -117,7 +118,7 @@ class MediaClientTest {
|
|||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
|
||||
`when`(mediaInterface!!.getMedia(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
|
||||
assertEquals("Test", mediaClient!!.getMedia("abcde").blockingGet().filename)
|
||||
}
|
||||
|
|
@ -136,10 +137,11 @@ class MediaClientTest {
|
|||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
|
||||
`when`(mediaInterface!!.getMedia(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
|
||||
assertEquals(Media.EMPTY, mediaClient!!.getMedia("abcde").blockingGet())
|
||||
}
|
||||
|
||||
@Captor
|
||||
private val filenameCaptor: ArgumentCaptor<String>? = null
|
||||
|
||||
|
|
@ -159,7 +161,7 @@ class MediaClientTest {
|
|||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
|
||||
`when`(mediaInterface!!.getMediaWithGenerator(filenameCaptor!!.capture()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
|
||||
assertEquals("Test", mediaClient!!.getPictureOfTheDay().blockingGet().filename)
|
||||
assertEquals(template, filenameCaptor.value);
|
||||
|
|
@ -170,7 +172,7 @@ class MediaClientTest {
|
|||
|
||||
@Test
|
||||
fun getMediaListFromCategoryTwice() {
|
||||
val mockContinuation= mapOf(Pair("gcmcontinue", "test"))
|
||||
val mockContinuation = mapOf(Pair("gcmcontinue", "test"))
|
||||
val imageInfo = ImageInfo()
|
||||
|
||||
val mwQueryPage = mock(MwQueryPage::class.java)
|
||||
|
|
@ -184,9 +186,13 @@ class MediaClientTest {
|
|||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
`when`(mockResponse.continuation()).thenReturn(mockContinuation)
|
||||
|
||||
`when`(mediaInterface!!.getMediaListFromCategory(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt(),
|
||||
continuationCaptor!!.capture()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
`when`(
|
||||
mediaInterface!!.getMediaListFromCategory(
|
||||
ArgumentMatchers.anyString(), ArgumentMatchers.anyInt(),
|
||||
continuationCaptor!!.capture()
|
||||
)
|
||||
)
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
val media1 = mediaClient!!.getMediaListFromCategory("abcde").blockingGet().get(0)
|
||||
val media2 = mediaClient!!.getMediaListFromCategory("abcde").blockingGet().get(0)
|
||||
|
||||
|
|
@ -197,6 +203,38 @@ class MediaClientTest {
|
|||
assertEquals(media2.filename, "Test")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getMediaListForUser() {
|
||||
val mockContinuation = mapOf("gcmcontinue" to "test")
|
||||
val imageInfo = ImageInfo()
|
||||
|
||||
val mwQueryPage = mock(MwQueryPage::class.java)
|
||||
whenever(mwQueryPage.title()).thenReturn("Test")
|
||||
whenever(mwQueryPage.imageInfo()).thenReturn(imageInfo)
|
||||
|
||||
val mwQueryResult = mock(MwQueryResult::class.java)
|
||||
whenever(mwQueryResult.pages()).thenReturn(listOf(mwQueryPage))
|
||||
|
||||
val mockResponse = mock(MwQueryResponse::class.java)
|
||||
whenever(mockResponse.query()).thenReturn(mwQueryResult)
|
||||
whenever(mockResponse.continuation()).thenReturn(mockContinuation)
|
||||
|
||||
whenever(
|
||||
mediaInterface!!.getMediaListForUser(
|
||||
ArgumentMatchers.anyString(), ArgumentMatchers.anyInt(),
|
||||
continuationCaptor!!.capture()
|
||||
)
|
||||
)
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
val media1 = mediaClient!!.getMediaListForUser("Test").blockingGet().get(0)
|
||||
val media2 = mediaClient!!.getMediaListForUser("Test").blockingGet().get(0)
|
||||
|
||||
verify(mediaInterface, times(2))?.getMediaListForUser(
|
||||
ArgumentMatchers.anyString(),
|
||||
ArgumentMatchers.anyInt(), ArgumentMatchers.anyMap<String, String>()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getPageHtmlTest() {
|
||||
val mwParseResult = mock(MwParseResult::class.java)
|
||||
|
|
@ -207,7 +245,7 @@ class MediaClientTest {
|
|||
mockResponse.setParse(mwParseResult)
|
||||
|
||||
`when`(mediaInterface!!.getPageHtml(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
|
||||
assertEquals("Test", mediaClient!!.getPageHtml("abcde").blockingGet())
|
||||
}
|
||||
|
|
@ -218,7 +256,7 @@ class MediaClientTest {
|
|||
mockResponse.setParse(null)
|
||||
|
||||
`when`(mediaInterface!!.getPageHtml(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
.thenReturn(Observable.just(mockResponse))
|
||||
|
||||
assertEquals("", mediaClient!!.getPageHtml("abcde").blockingGet())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.net.ConnectivityManager;
|
|||
import android.net.NetworkInfo;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
|
|
@ -28,34 +29,30 @@ public class NetworkUtilsTest {
|
|||
|
||||
@Test
|
||||
public void testInternetConnectionEstablished() {
|
||||
Context mockContext = mock(Context.class);
|
||||
Application mockApplication = mock(Application.class);
|
||||
ConnectivityManager mockConnectivityManager = mock(ConnectivityManager.class);
|
||||
NetworkInfo mockNetworkInfo = mock(NetworkInfo.class);
|
||||
when(mockNetworkInfo.isConnectedOrConnecting())
|
||||
.thenReturn(true);
|
||||
when(mockConnectivityManager.getActiveNetworkInfo())
|
||||
.thenReturn(mockNetworkInfo);
|
||||
when(mockApplication.getSystemService(Context.CONNECTIVITY_SERVICE))
|
||||
.thenReturn(mockConnectivityManager);
|
||||
when(mockContext.getApplicationContext()).thenReturn(mockApplication);
|
||||
Context mockContext = getContext(true);
|
||||
boolean internetConnectionEstablished = NetworkUtils.isInternetConnectionEstablished(mockContext);
|
||||
assertTrue(internetConnectionEstablished);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInternetConnectionNotEstablished() {
|
||||
@NotNull
|
||||
public static Context getContext(boolean connectionEstablished) {
|
||||
Context mockContext = mock(Context.class);
|
||||
Application mockApplication = mock(Application.class);
|
||||
ConnectivityManager mockConnectivityManager = mock(ConnectivityManager.class);
|
||||
NetworkInfo mockNetworkInfo = mock(NetworkInfo.class);
|
||||
when(mockNetworkInfo.isConnectedOrConnecting())
|
||||
.thenReturn(false);
|
||||
.thenReturn(connectionEstablished);
|
||||
when(mockConnectivityManager.getActiveNetworkInfo())
|
||||
.thenReturn(mockNetworkInfo);
|
||||
.thenReturn(mockNetworkInfo);
|
||||
when(mockApplication.getSystemService(Context.CONNECTIVITY_SERVICE))
|
||||
.thenReturn(mockConnectivityManager);
|
||||
.thenReturn(mockConnectivityManager);
|
||||
when(mockContext.getApplicationContext()).thenReturn(mockApplication);
|
||||
return mockContext;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInternetConnectionNotEstablished() {
|
||||
Context mockContext = getContext(false);
|
||||
boolean internetConnectionEstablished = NetworkUtils.isInternetConnectionEstablished(mockContext);
|
||||
assertFalse(internetConnectionEstablished);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
package fr.free.nrw.commons.utils
|
||||
|
||||
|
||||
import android.database.Cursor
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.DataSource
|
||||
import androidx.paging.LivePagedListBuilder
|
||||
import androidx.paging.PagedList
|
||||
import androidx.room.InvalidationTracker
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.RoomSQLiteQuery
|
||||
import androidx.room.paging.LimitOffsetDataSource
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import org.mockito.Mockito.mock
|
||||
|
||||
fun <T> List<T>.asPagedList(config: PagedList.Config? = null): LiveData<PagedList<T>> {
|
||||
val defaultConfig = PagedList.Config.Builder()
|
||||
.setEnablePlaceholders(false)
|
||||
.setPageSize(size)
|
||||
.setMaxSize(size + 2)
|
||||
.setPrefetchDistance(1)
|
||||
.build()
|
||||
return LivePagedListBuilder<Int, T>(
|
||||
createMockDataSourceFactory(this),
|
||||
config ?: defaultConfig
|
||||
).build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a mocked instance of the data source factory
|
||||
*/
|
||||
fun <T> createMockDataSourceFactory(itemList: List<T>): DataSource.Factory<Int, T> =
|
||||
object : DataSource.Factory<Int, T>() {
|
||||
override fun create(): DataSource<Int, T> = MockLimitDataSource(itemList)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a mocked Room SQL query
|
||||
*/
|
||||
private fun mockQuery(): RoomSQLiteQuery? {
|
||||
val query = mock(RoomSQLiteQuery::class.java);
|
||||
whenever(query.sql).thenReturn("");
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a mocked Room DB
|
||||
*/
|
||||
private fun mockDb(): RoomDatabase? {
|
||||
val roomDatabase = mock(RoomDatabase::class.java);
|
||||
val invalidationTracker = mock(InvalidationTracker::class.java)
|
||||
whenever(roomDatabase.invalidationTracker).thenReturn(invalidationTracker);
|
||||
return roomDatabase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that defines the mocked data source
|
||||
*/
|
||||
class MockLimitDataSource<T>(private val itemList: List<T>) :
|
||||
LimitOffsetDataSource<T>(mockDb(), mockQuery(), false, null) {
|
||||
override fun convertRows(cursor: Cursor?): MutableList<T> = itemList.toMutableList()
|
||||
override fun countItems(): Int = itemList.count()
|
||||
override fun isInvalid(): Boolean = false
|
||||
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>) {
|
||||
}
|
||||
|
||||
override fun loadRange(startPosition: Int, loadCount: Int): MutableList<T> {
|
||||
return itemList.subList(startPosition, startPosition + loadCount).toMutableList()
|
||||
}
|
||||
|
||||
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>) {
|
||||
callback.onResult(itemList, 0)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue