mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
Merge branch 'macgills/3760-categories-pagination' into macgills/3772-images-pagination
# Conflicts: # app/src/main/java/fr/free/nrw/commons/Media.java # app/src/main/java/fr/free/nrw/commons/media/MediaClient.java # app/src/main/java/fr/free/nrw/commons/media/MediaDetailInterface.java # app/src/main/java/fr/free/nrw/commons/media/MediaInterface.java
This commit is contained in:
commit
350b19e62f
91 changed files with 2281 additions and 1185 deletions
14
README.md
14
README.md
|
|
@ -13,7 +13,7 @@ Initially started by the Wikimedia Foundation, this app is now maintained by gra
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
We try to have an extensive documentation at [our wiki here at Github][4]:
|
We try to have an extensive documentation at our [documentation repository][4]:
|
||||||
|
|
||||||
* [User Documentation][5]
|
* [User Documentation][5]
|
||||||
* [Contributor Documentation][6]
|
* [Contributor Documentation][6]
|
||||||
|
|
@ -45,11 +45,11 @@ This software is open source, licensed under the [Apache License 2.0][10].
|
||||||
[2]: https://commons-app.github.io/
|
[2]: https://commons-app.github.io/
|
||||||
[3]: https://github.com/commons-app/apps-android-commons/issues
|
[3]: https://github.com/commons-app/apps-android-commons/issues
|
||||||
|
|
||||||
[4]: https://github.com/commons-app/apps-android-commons/wiki
|
[4]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#-android-documentation
|
||||||
[5]: https://github.com/commons-app/apps-android-commons/wiki#user-documentation
|
[5]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#-user-documentation
|
||||||
[6]: https://github.com/commons-app/apps-android-commons/wiki#contributor-documentation
|
[6]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#️-contributor-documentation
|
||||||
[7]: https://github.com/commons-app/apps-android-commons/wiki/Volunteers-welcome%21
|
[7]: https://github.com/commons-app/commons-app-documentation/blob/master/android/Volunteers-welcome!.md#volunteers-welcome
|
||||||
[8]: https://github.com/commons-app/apps-android-commons/wiki#developer-documentation
|
[8]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#-developer-documentation
|
||||||
[9]: https://github.com/commons-app/apps-android-commons/wiki/Libraries-used
|
[9]: https://github.com/commons-app/commons-app-documentation/blob/master/android/Libraries-used.md#libraries-used
|
||||||
|
|
||||||
[10]: https://www.apache.org/licenses/LICENSE-2.0
|
[10]: https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ dependencies {
|
||||||
implementation 'com.dinuscxj:circleprogressbar:1.1.1'
|
implementation 'com.dinuscxj:circleprogressbar:1.1.1'
|
||||||
implementation 'com.karumi:dexter:5.0.0'
|
implementation 'com.karumi:dexter:5.0.0'
|
||||||
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
|
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
|
||||||
|
|
||||||
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
|
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
|
||||||
implementation "com.hannesdorfmann:adapterdelegates4-kotlin-dsl-layoutcontainer:$ADAPTER_DELEGATES_VERSION"
|
implementation "com.hannesdorfmann:adapterdelegates4-kotlin-dsl-layoutcontainer:$ADAPTER_DELEGATES_VERSION"
|
||||||
implementation "com.hannesdorfmann:adapterdelegates4-pagination:$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.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.room.Entity;
|
import androidx.room.Entity;
|
||||||
|
import androidx.room.PrimaryKey;
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
import fr.free.nrw.commons.media.Depictions;
|
import fr.free.nrw.commons.media.Depictions;
|
||||||
import fr.free.nrw.commons.utils.CommonsDateUtil;
|
import fr.free.nrw.commons.utils.CommonsDateUtil;
|
||||||
|
|
@ -15,6 +16,8 @@ import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryPage;
|
import org.wikipedia.dataclient.mwapi.MwQueryPage;
|
||||||
import org.wikipedia.gallery.ExtMetadata;
|
import org.wikipedia.gallery.ExtMetadata;
|
||||||
|
|
@ -50,6 +53,8 @@ public class Media implements Parcelable {
|
||||||
/**
|
/**
|
||||||
* Wikibase Identifier associated with media files
|
* Wikibase Identifier associated with media files
|
||||||
*/
|
*/
|
||||||
|
@PrimaryKey
|
||||||
|
@NonNull
|
||||||
private String pageId;
|
private String pageId;
|
||||||
private List<String> categories; // as loaded at runtime?
|
private List<String> categories; // as loaded at runtime?
|
||||||
/**
|
/**
|
||||||
|
|
@ -64,14 +69,28 @@ public class Media implements Parcelable {
|
||||||
* Provides local constructor
|
* Provides local constructor
|
||||||
*/
|
*/
|
||||||
public Media() {
|
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
|
* Provides a minimal constructor
|
||||||
*
|
*
|
||||||
* @param filename Media filename
|
* @param filename Media filename
|
||||||
*/
|
*/
|
||||||
public Media(String filename) {
|
public Media(final String filename) {
|
||||||
|
this();
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,11 +105,13 @@ public class Media implements Parcelable {
|
||||||
* @param dateUploaded Media date uploaded
|
* @param dateUploaded Media date uploaded
|
||||||
* @param creator Media creator
|
* @param creator Media creator
|
||||||
*/
|
*/
|
||||||
public Media(Uri localUri, String imageUrl, String filename,
|
public Media(final Uri localUri, final String imageUrl, final String filename,
|
||||||
String description,
|
final String description,
|
||||||
long dataLength, Date dateCreated, Date dateUploaded, String creator) {
|
final long dataLength, final Date dateCreated, final Date dateUploaded,
|
||||||
|
final String creator) {
|
||||||
|
this();
|
||||||
this.localUri = localUri;
|
this.localUri = localUri;
|
||||||
this.thumbUrl = imageUrl;
|
thumbUrl = imageUrl;
|
||||||
this.imageUrl = imageUrl;
|
this.imageUrl = imageUrl;
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
|
@ -100,17 +121,80 @@ public class Media implements Parcelable {
|
||||||
this.creator = creator;
|
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,
|
this(localUri,null, filename,
|
||||||
description, -1, null, new Date(), creator);
|
description, -1, null, new Date(), creator);
|
||||||
this.categories = categories;
|
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);
|
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.
|
* Creating Media object from MWQueryPage.
|
||||||
* Earlier only basic details were set for the media object but going forward,
|
* 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
|
* @return Media object
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static Media from(MwQueryPage page) {
|
public static Media from(final MwQueryPage page) {
|
||||||
ImageInfo imageInfo = page.imageInfo();
|
final ImageInfo imageInfo = page.imageInfo();
|
||||||
if (imageInfo == null) {
|
if (imageInfo == null) {
|
||||||
return new Media(); // null is not allowed
|
return new Media(); // null is not allowed
|
||||||
}
|
}
|
||||||
ExtMetadata metadata = imageInfo.getMetadata();
|
final ExtMetadata metadata = imageInfo.getMetadata();
|
||||||
if (metadata == null) {
|
if (metadata == null) {
|
||||||
Media media = new Media(null, imageInfo.getOriginalUrl(),
|
final Media media = new Media(null, imageInfo.getOriginalUrl(),
|
||||||
page.title(), "", 0, null, null, null);
|
page.title(), "", 0, null, null, null);
|
||||||
if (!StringUtils.isBlank(imageInfo.getThumbUrl())) {
|
if (!StringUtils.isBlank(imageInfo.getThumbUrl())) {
|
||||||
media.setThumbUrl(imageInfo.getThumbUrl());
|
media.setThumbUrl(imageInfo.getThumbUrl());
|
||||||
|
|
@ -135,7 +219,7 @@ public class Media implements Parcelable {
|
||||||
return media;
|
return media;
|
||||||
}
|
}
|
||||||
|
|
||||||
Media media = new Media(null,
|
final Media media = new Media(null,
|
||||||
imageInfo.getOriginalUrl(),
|
imageInfo.getOriginalUrl(),
|
||||||
page.title(),
|
page.title(),
|
||||||
"",
|
"",
|
||||||
|
|
@ -158,11 +242,12 @@ public class Media implements Parcelable {
|
||||||
|
|
||||||
media.setDescription(metadata.imageDescription());
|
media.setDescription(metadata.imageDescription());
|
||||||
media.setCategories(MediaDataExtractorUtil.extractCategoriesFromList(metadata.getCategories()));
|
media.setCategories(MediaDataExtractorUtil.extractCategoriesFromList(metadata.getCategories()));
|
||||||
String latitude = metadata.getGpsLatitude();
|
final String latitude = metadata.getGpsLatitude();
|
||||||
String longitude = metadata.getGpsLongitude();
|
final String longitude = metadata.getGpsLongitude();
|
||||||
|
|
||||||
if (!StringUtils.isBlank(latitude) && !StringUtils.isBlank(longitude)) {
|
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);
|
media.setCoordinates(latLng);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,29 +260,17 @@ public class Media implements Parcelable {
|
||||||
* @param metadata
|
* @param metadata
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private static String getArtist(ExtMetadata metadata) {
|
private static String getArtist(final ExtMetadata metadata) {
|
||||||
try {
|
try {
|
||||||
String artistHtml = metadata.artist();
|
final String artistHtml = metadata.artist();
|
||||||
return artistHtml.substring(artistHtml.indexOf("title=\""), artistHtml.indexOf("\">"))
|
return artistHtml.substring(artistHtml.indexOf("title=\""), artistHtml.indexOf("\">"))
|
||||||
.replace("title=\"User:", "");
|
.replace("title=\"User:", "");
|
||||||
} catch (Exception ex) {
|
} catch (final Exception ex) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Nullable
|
||||||
* @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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getThumbUrl() {
|
public String getThumbUrl() {
|
||||||
return thumbUrl;
|
return thumbUrl;
|
||||||
}
|
}
|
||||||
|
|
@ -210,11 +283,13 @@ public class Media implements Parcelable {
|
||||||
return filename != null ? getPageTitle().getDisplayTextWithoutNamespace().replaceFirst("[.][^.]+$", "") : "";
|
return filename != null ? getPageTitle().getDisplayTextWithoutNamespace().replaceFirst("[.][^.]+$", "") : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Nullable
|
||||||
* Set Caption(if available) as the thumbnail title of the image
|
private static Date safeParseDate(final String dateStr) {
|
||||||
*/
|
try {
|
||||||
public void setThumbnailTitle(String title) {
|
return CommonsDateUtil.getMediaSimpleDateFormat().parse(dateStr);
|
||||||
this.thumbnailTitle = title;
|
} catch (final ParseException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -260,19 +335,17 @@ public class Media implements Parcelable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the name of the file.
|
* @return pageId for the current media object*/
|
||||||
* @param filename the new name of the file
|
@NonNull
|
||||||
*/
|
public String getPageId() {
|
||||||
public void setFilename(String filename) {
|
return pageId;
|
||||||
this.filename = filename;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the discussion of the file.
|
*sets pageId for the current media object
|
||||||
* @param discussion
|
|
||||||
*/
|
*/
|
||||||
public void setDiscussion(String discussion) {
|
public void setPageId(final String pageId) {
|
||||||
this.discussion = discussion;
|
this.pageId = pageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -310,11 +383,11 @@ public class Media implements Parcelable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the file description.
|
* Sets the name of the file.
|
||||||
* @param description the new description of the file
|
* @param filename the new name of the file
|
||||||
*/
|
*/
|
||||||
public void setDescription(String description) {
|
public void setFilename(final String filename) {
|
||||||
this.description = description;
|
this.filename = filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -326,11 +399,11 @@ public class Media implements Parcelable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the dataLength of the file.
|
* Sets the discussion of the file.
|
||||||
* @param dataLength as a long
|
* @param discussion
|
||||||
*/
|
*/
|
||||||
public void setDataLength(long dataLength) {
|
public void setDiscussion(final String discussion) {
|
||||||
this.dataLength = dataLength;
|
this.discussion = discussion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -342,11 +415,11 @@ public class Media implements Parcelable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the creation date of the file.
|
* Sets the file description.
|
||||||
* @param date creation date as a Date
|
* @param description the new description of the file
|
||||||
*/
|
*/
|
||||||
public void setDateCreated(Date date) {
|
public void setDescription(final String description) {
|
||||||
this.dateCreated = date;
|
this.description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -368,11 +441,11 @@ public class Media implements Parcelable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the creator name of the file.
|
* Sets the dataLength of the file.
|
||||||
* @param creator creator name as a string
|
* @param dataLength as a long
|
||||||
*/
|
*/
|
||||||
public void setCreator(String creator) {
|
public void setDataLength(final long dataLength) {
|
||||||
this.creator = creator;
|
this.dataLength = dataLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -383,8 +456,11 @@ public class Media implements Parcelable {
|
||||||
return license;
|
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() {
|
public String getLicenseUrl() {
|
||||||
|
|
@ -392,16 +468,11 @@ public class Media implements Parcelable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the license name of the file.
|
* Sets the creator name of the file.
|
||||||
* @param license license name as a String
|
* @param creator creator name as a string
|
||||||
*/
|
*/
|
||||||
public void setLicenseInformation(String license, String licenseUrl) {
|
public void setCreator(final String creator) {
|
||||||
this.license = license;
|
this.creator = creator;
|
||||||
|
|
||||||
if (!licenseUrl.startsWith("http://") && !licenseUrl.startsWith("https://")) {
|
|
||||||
licenseUrl = "https://" + licenseUrl;
|
|
||||||
}
|
|
||||||
this.licenseUrl = licenseUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -413,12 +484,8 @@ public class Media implements Parcelable {
|
||||||
return coordinates;
|
return coordinates;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void setThumbUrl(final String thumbUrl) {
|
||||||
* Sets the coordinates of where the file was created.
|
this.thumbUrl = thumbUrl;
|
||||||
* @param coordinates file coordinates as a LatLng
|
|
||||||
*/
|
|
||||||
public void setCoordinates(@Nullable LatLng coordinates) {
|
|
||||||
this.coordinates = coordinates;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -430,6 +497,27 @@ public class Media implements Parcelable {
|
||||||
return categories;
|
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.
|
* Sets the categories the file falls under.
|
||||||
* </p>
|
* </p>
|
||||||
|
|
@ -437,26 +525,10 @@ public class Media implements Parcelable {
|
||||||
* and then add the specified ones.
|
* and then add the specified ones.
|
||||||
* @param categories file categories as a list of Strings
|
* @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;
|
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
|
* Get the value of requested deletion
|
||||||
* @return boolean requestedDeletion
|
* @return boolean requestedDeletion
|
||||||
|
|
@ -465,12 +537,20 @@ public class Media implements Parcelable {
|
||||||
return requestedDeletion;
|
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.
|
* Sets the license name of the file.
|
||||||
*
|
*
|
||||||
* @param license license name as a String
|
* @param license license name as a String
|
||||||
*/
|
*/
|
||||||
public void setLicense(String license) {
|
public void setLicense(final String license) {
|
||||||
this.license = license;
|
this.license = license;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -482,15 +562,10 @@ public class Media implements Parcelable {
|
||||||
* This function sets captions
|
* This function sets captions
|
||||||
* @param caption
|
* @param caption
|
||||||
*/
|
*/
|
||||||
public void setCaption(String caption) {
|
public void setCaption(final String caption) {
|
||||||
this.caption = 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) {
|
public void setLocalUri(@Nullable final Uri localUri) {
|
||||||
this.localUri = localUri;
|
this.localUri = localUri;
|
||||||
}
|
}
|
||||||
|
|
@ -516,6 +591,19 @@ public class Media implements Parcelable {
|
||||||
return 0;
|
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
|
* Creates a way to transfer information between two or more
|
||||||
* activities.
|
* activities.
|
||||||
|
|
@ -523,63 +611,70 @@ public class Media implements Parcelable {
|
||||||
* @param flags Parcel flag
|
* @param flags Parcel flag
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(final Parcel dest, final int flags) {
|
||||||
dest.writeParcelable(this.localUri, flags);
|
dest.writeParcelable(localUri, flags);
|
||||||
dest.writeString(this.thumbUrl);
|
dest.writeString(thumbUrl);
|
||||||
dest.writeString(this.imageUrl);
|
dest.writeString(imageUrl);
|
||||||
dest.writeString(this.filename);
|
dest.writeString(filename);
|
||||||
dest.writeString(this.thumbnailTitle);
|
dest.writeString(thumbnailTitle);
|
||||||
dest.writeString(this.caption);
|
dest.writeString(caption);
|
||||||
dest.writeString(this.description);
|
dest.writeString(description);
|
||||||
dest.writeString(this.discussion);
|
dest.writeString(discussion);
|
||||||
dest.writeLong(this.dataLength);
|
dest.writeLong(dataLength);
|
||||||
dest.writeLong(this.dateCreated != null ? this.dateCreated.getTime() : -1);
|
dest.writeLong(dateCreated != null ? dateCreated.getTime() : -1);
|
||||||
dest.writeLong(this.dateUploaded != null ? this.dateUploaded.getTime() : -1);
|
dest.writeLong(dateUploaded != null ? dateUploaded.getTime() : -1);
|
||||||
dest.writeString(this.license);
|
dest.writeString(license);
|
||||||
dest.writeString(this.licenseUrl);
|
dest.writeString(licenseUrl);
|
||||||
dest.writeString(this.creator);
|
dest.writeString(creator);
|
||||||
dest.writeString(this.pageId);
|
dest.writeString(pageId);
|
||||||
dest.writeStringList(this.categories);
|
dest.writeStringList(categories);
|
||||||
dest.writeParcelable(this.depictions, flags);
|
dest.writeParcelable(depictions, flags);
|
||||||
dest.writeByte(this.requestedDeletion ? (byte) 1 : (byte) 0);
|
dest.writeByte(requestedDeletion ? (byte) 1 : (byte) 0);
|
||||||
dest.writeParcelable(this.coordinates, flags);
|
dest.writeParcelable(coordinates, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Media(Parcel in) {
|
/**
|
||||||
this.localUri = in.readParcelable(Uri.class.getClassLoader());
|
* Equals implementation that matches all parameters for equality check
|
||||||
this.thumbUrl = in.readString();
|
*/
|
||||||
this.imageUrl = in.readString();
|
@Override
|
||||||
this.filename = in.readString();
|
public boolean equals(final Object o) {
|
||||||
this.thumbnailTitle = in.readString();
|
if (this == o) {
|
||||||
this.caption = in.readString();
|
return true;
|
||||||
this.description = in.readString();
|
}
|
||||||
this.discussion = in.readString();
|
if (!(o instanceof Media)) {
|
||||||
this.dataLength = in.readLong();
|
return false;
|
||||||
long tmpDateCreated = in.readLong();
|
}
|
||||||
this.dateCreated = tmpDateCreated == -1 ? null : new Date(tmpDateCreated);
|
final Media media = (Media) o;
|
||||||
long tmpDateUploaded = in.readLong();
|
return getDataLength() == media.getDataLength() &&
|
||||||
this.dateUploaded = tmpDateUploaded == -1 ? null : new Date(tmpDateUploaded);
|
isRequestedDeletion() == media.isRequestedDeletion() &&
|
||||||
this.license = in.readString();
|
Objects.equals(getLocalUri(), media.getLocalUri()) &&
|
||||||
this.licenseUrl = in.readString();
|
Objects.equals(getThumbUrl(), media.getThumbUrl()) &&
|
||||||
this.creator = in.readString();
|
Objects.equals(getImageUrl(), media.getImageUrl()) &&
|
||||||
this.pageId = in.readString();
|
Objects.equals(getFilename(), media.getFilename()) &&
|
||||||
final ArrayList<String> list = new ArrayList<>();
|
Objects.equals(getThumbnailTitle(), media.getThumbnailTitle()) &&
|
||||||
in.readStringList(list);
|
Objects.equals(getCaption(), media.getCaption()) &&
|
||||||
this.categories=list;
|
Objects.equals(getDescription(), media.getDescription()) &&
|
||||||
in.readParcelable(Depictions.class.getClassLoader());
|
Objects.equals(getDiscussion(), media.getDiscussion()) &&
|
||||||
this.requestedDeletion = in.readByte() != 0;
|
Objects.equals(getDateCreated(), media.getDateCreated()) &&
|
||||||
this.coordinates = in.readParcelable(LatLng.class.getClassLoader());
|
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
|
* Hashcode implementation that uses all parameters for calculating hash
|
||||||
public Media createFromParcel(Parcel source) {
|
*/
|
||||||
return new Media(source);
|
@Override
|
||||||
}
|
public int hashCode() {
|
||||||
|
return Objects
|
||||||
@Override
|
.hash(getLocalUri(), getThumbUrl(), getImageUrl(), getFilename(), getThumbnailTitle(),
|
||||||
public Media[] newArray(int size) {
|
getCaption(), getDescription(), getDiscussion(), getDataLength(), getDateCreated(),
|
||||||
return new Media[size];
|
getDateUploaded(), getLicense(), getLicenseUrl(), getCreator(), getPageId(),
|
||||||
}
|
getCategories(), getDepictions(), isRequestedDeletion(), getCoordinates());
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,6 @@
|
||||||
package fr.free.nrw.commons;
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import okhttp3.logging.HttpLoggingInterceptor.Level;
|
|
||||||
import org.wikipedia.dataclient.SharedPreferenceCookieManager;
|
|
||||||
import org.wikipedia.dataclient.okhttp.HttpStatusException;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import okhttp3.Cache;
|
import okhttp3.Cache;
|
||||||
|
|
@ -26,7 +21,8 @@ public final class OkHttpConnectionFactory {
|
||||||
@NonNull private static final Cache NET_CACHE = new Cache(new File(CommonsApplication.getInstance().getCacheDir(),
|
@NonNull private static final Cache NET_CACHE = new Cache(new File(CommonsApplication.getInstance().getCacheDir(),
|
||||||
CACHE_DIR_NAME), NET_CACHE_SIZE);
|
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() {
|
@NonNull public static OkHttpClient getClient() {
|
||||||
return CLIENT;
|
return CLIENT;
|
||||||
|
|
@ -45,7 +41,7 @@ public final class OkHttpConnectionFactory {
|
||||||
|
|
||||||
private static HttpLoggingInterceptor getLoggingInterceptor() {
|
private static HttpLoggingInterceptor getLoggingInterceptor() {
|
||||||
final HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor()
|
final HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor()
|
||||||
.setLevel(Level.BASIC);
|
.setLevel(Level.BASIC);
|
||||||
|
|
||||||
httpLoggingInterceptor.redactHeader("Authorization");
|
httpLoggingInterceptor.redactHeader("Authorization");
|
||||||
httpLoggingInterceptor.redactHeader("Cookie");
|
httpLoggingInterceptor.redactHeader("Cookie");
|
||||||
|
|
@ -54,7 +50,10 @@ public final class OkHttpConnectionFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class CommonHeaderRequestInterceptor implements Interceptor {
|
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()
|
final Request request = chain.request().newBuilder()
|
||||||
.header("User-Agent", CommonsApplication.getInstance().getUserAgent())
|
.header("User-Agent", CommonsApplication.getInstance().getUserAgent())
|
||||||
.build();
|
.build();
|
||||||
|
|
@ -66,16 +65,18 @@ public final class OkHttpConnectionFactory {
|
||||||
|
|
||||||
private static final String ERRORS_PREFIX = "{\"error";
|
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());
|
final Response rsp = chain.proceed(chain.request());
|
||||||
if (rsp.isSuccessful()) {
|
if (rsp.isSuccessful()) {
|
||||||
try (final ResponseBody responseBody = rsp.peekBody(ERRORS_PREFIX.length())) {
|
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()) {
|
try (final ResponseBody body = rsp.body()) {
|
||||||
throw new IOException(body.string());
|
throw new IOException(body.string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}catch (final IOException e){
|
} catch (final IOException e) {
|
||||||
Timber.e(e);
|
Timber.e(e);
|
||||||
}
|
}
|
||||||
return rsp;
|
return rsp;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import android.os.RemoteException;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.nearby.NearbyController;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -74,8 +75,10 @@ public class BookmarkLocationsDao {
|
||||||
boolean bookmarkExists = findBookmarkLocation(bookmarkLocation);
|
boolean bookmarkExists = findBookmarkLocation(bookmarkLocation);
|
||||||
if (bookmarkExists) {
|
if (bookmarkExists) {
|
||||||
deleteBookmarkLocation(bookmarkLocation);
|
deleteBookmarkLocation(bookmarkLocation);
|
||||||
|
NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, false);
|
||||||
} else {
|
} else {
|
||||||
addBookmarkLocation(bookmarkLocation);
|
addBookmarkLocation(bookmarkLocation);
|
||||||
|
NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, true);
|
||||||
}
|
}
|
||||||
return !bookmarkExists;
|
return !bookmarkExists;
|
||||||
}
|
}
|
||||||
|
|
@ -160,10 +163,9 @@ public class BookmarkLocationsDao {
|
||||||
location,
|
location,
|
||||||
cursor.getString(cursor.getColumnIndex(Table.COLUMN_CATEGORY)),
|
cursor.getString(cursor.getColumnIndex(Table.COLUMN_CATEGORY)),
|
||||||
builder.build(),
|
builder.build(),
|
||||||
null,
|
cursor.getString(cursor.getColumnIndex(Table.COLUMN_PIC)),
|
||||||
null
|
cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESTROYED))
|
||||||
);
|
);
|
||||||
// TODO: add pic and destroyed to bookmark location dao
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContentValues toContentValues(Place bookmarkLocation) {
|
private ContentValues toContentValues(Place bookmarkLocation) {
|
||||||
|
|
@ -179,6 +181,7 @@ public class BookmarkLocationsDao {
|
||||||
cv.put(BookmarkLocationsDao.Table.COLUMN_LAT, bookmarkLocation.location.getLatitude());
|
cv.put(BookmarkLocationsDao.Table.COLUMN_LAT, bookmarkLocation.location.getLatitude());
|
||||||
cv.put(BookmarkLocationsDao.Table.COLUMN_LONG, bookmarkLocation.location.getLongitude());
|
cv.put(BookmarkLocationsDao.Table.COLUMN_LONG, bookmarkLocation.location.getLongitude());
|
||||||
cv.put(BookmarkLocationsDao.Table.COLUMN_PIC, bookmarkLocation.pic);
|
cv.put(BookmarkLocationsDao.Table.COLUMN_PIC, bookmarkLocation.pic);
|
||||||
|
cv.put(BookmarkLocationsDao.Table.COLUMN_DESTROYED, bookmarkLocation.destroyed);
|
||||||
return cv;
|
return cv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,6 +200,7 @@ public class BookmarkLocationsDao {
|
||||||
static final String COLUMN_WIKIDATA_LINK = "location_wikidata_link";
|
static final String COLUMN_WIKIDATA_LINK = "location_wikidata_link";
|
||||||
static final String COLUMN_COMMONS_LINK = "location_commons_link";
|
static final String COLUMN_COMMONS_LINK = "location_commons_link";
|
||||||
static final String COLUMN_PIC = "location_pic";
|
static final String COLUMN_PIC = "location_pic";
|
||||||
|
static final String COLUMN_DESTROYED = "location_destroyed";
|
||||||
|
|
||||||
// NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
|
// NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
|
||||||
public static final String[] ALL_FIELDS = {
|
public static final String[] ALL_FIELDS = {
|
||||||
|
|
@ -211,7 +215,8 @@ public class BookmarkLocationsDao {
|
||||||
COLUMN_WIKIPEDIA_LINK,
|
COLUMN_WIKIPEDIA_LINK,
|
||||||
COLUMN_WIKIDATA_LINK,
|
COLUMN_WIKIDATA_LINK,
|
||||||
COLUMN_COMMONS_LINK,
|
COLUMN_COMMONS_LINK,
|
||||||
COLUMN_PIC
|
COLUMN_PIC,
|
||||||
|
COLUMN_DESTROYED
|
||||||
};
|
};
|
||||||
|
|
||||||
static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
|
static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
|
||||||
|
|
@ -228,7 +233,8 @@ public class BookmarkLocationsDao {
|
||||||
+ COLUMN_WIKIPEDIA_LINK + " STRING,"
|
+ COLUMN_WIKIPEDIA_LINK + " STRING,"
|
||||||
+ COLUMN_WIKIDATA_LINK + " STRING,"
|
+ COLUMN_WIKIDATA_LINK + " STRING,"
|
||||||
+ COLUMN_COMMONS_LINK + " STRING,"
|
+ COLUMN_COMMONS_LINK + " STRING,"
|
||||||
+ COLUMN_PIC + " STRING"
|
+ COLUMN_PIC + " STRING,"
|
||||||
|
+ COLUMN_DESTROYED + " STRING"
|
||||||
+ ");";
|
+ ");";
|
||||||
|
|
||||||
public static void onCreate(SQLiteDatabase db) {
|
public static void onCreate(SQLiteDatabase db) {
|
||||||
|
|
@ -242,37 +248,24 @@ public class BookmarkLocationsDao {
|
||||||
|
|
||||||
public static void onUpdate(SQLiteDatabase db, int from, int to) {
|
public static void onUpdate(SQLiteDatabase db, int from, int to) {
|
||||||
Timber.d("bookmarksLocations db is updated from:"+from+", to:"+to);
|
Timber.d("bookmarksLocations db is updated from:"+from+", to:"+to);
|
||||||
if (from == to) {
|
switch (from) {
|
||||||
return;
|
case 7: onCreate(db);
|
||||||
}
|
case 8: // No change
|
||||||
if (from < 7) {
|
case 9: // No change
|
||||||
// doesn't exist yet
|
case 10:
|
||||||
from++;
|
try {
|
||||||
onUpdate(db, from, to);
|
db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_pic STRING;");
|
||||||
return;
|
} catch (SQLiteException exception){
|
||||||
}
|
Timber.e(exception);
|
||||||
if (from == 7) {
|
}
|
||||||
// table added in version 8
|
case 11: // No change
|
||||||
onCreate(db);
|
case 12:
|
||||||
from++;
|
try {
|
||||||
onUpdate(db, from, to);
|
db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_destroyed STRING;");
|
||||||
return;
|
}catch (SQLiteException exception){
|
||||||
}
|
Timber.e(exception);
|
||||||
if (from == 8) {
|
}
|
||||||
from++;
|
break;
|
||||||
onUpdate(db, from, to);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (from == 10 && to == 11) {
|
|
||||||
from++;
|
|
||||||
//This is safe, and can be called clean, as we/I do not remember the appropriate version for this
|
|
||||||
//We are anyways switching to room, these things won't be nescessary then
|
|
||||||
try {
|
|
||||||
db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_pic STRING;");
|
|
||||||
}catch (SQLiteException exception){
|
|
||||||
Timber.e(exception);//
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ package fr.free.nrw.commons.category
|
||||||
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import fr.free.nrw.commons.upload.GpsCategoryModel
|
import fr.free.nrw.commons.upload.GpsCategoryModel
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
import fr.free.nrw.commons.utils.StringSortingUtils
|
import fr.free.nrw.commons.utils.StringSortingUtils
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.functions.Function3
|
import io.reactivex.functions.Function4
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -67,30 +68,42 @@ class CategoriesModel @Inject constructor(
|
||||||
* @param imageTitleList
|
* @param imageTitleList
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
fun searchAll(term: String, imageTitleList: List<String>): Observable<List<CategoryItem>> {
|
fun searchAll(
|
||||||
return suggestionsOrSearch(term, imageTitleList)
|
term: String,
|
||||||
|
imageTitleList: List<String>,
|
||||||
|
selectedDepictions: List<DepictedItem>
|
||||||
|
): Observable<List<CategoryItem>> {
|
||||||
|
return suggestionsOrSearch(term, imageTitleList, selectedDepictions)
|
||||||
.map { it.map { CategoryItem(it, false) } }
|
.map { it.map { CategoryItem(it, false) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun suggestionsOrSearch(term: String, imageTitleList: List<String>):
|
private fun suggestionsOrSearch(
|
||||||
Observable<List<String>> {
|
term: String,
|
||||||
|
imageTitleList: List<String>,
|
||||||
|
selectedDepictions: List<DepictedItem>
|
||||||
|
): Observable<List<String>> {
|
||||||
return if (TextUtils.isEmpty(term))
|
return if (TextUtils.isEmpty(term))
|
||||||
Observable.combineLatest(
|
Observable.combineLatest(
|
||||||
|
categoriesFromDepiction(selectedDepictions),
|
||||||
gpsCategoryModel.categoriesFromLocation,
|
gpsCategoryModel.categoriesFromLocation,
|
||||||
titleCategories(imageTitleList),
|
titleCategories(imageTitleList),
|
||||||
Observable.just(categoryDao.recentCategories(SEARCH_CATS_LIMIT)),
|
Observable.just(categoryDao.recentCategories(SEARCH_CATS_LIMIT)),
|
||||||
Function3(::combine)
|
Function4(::combine)
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
categoryClient.searchCategoriesForPrefix(term.toLowerCase(), SEARCH_CATS_LIMIT)
|
categoryClient.searchCategoriesForPrefix(term.toLowerCase(), SEARCH_CATS_LIMIT)
|
||||||
.map { it.sortedWith(StringSortingUtils.sortBySimilarity(term)) }
|
.map { it.sortedWith(StringSortingUtils.sortBySimilarity(term)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun categoriesFromDepiction(selectedDepictions: List<DepictedItem>) =
|
||||||
|
Observable.just(selectedDepictions.map { it.commonsCategories }.flatten())
|
||||||
|
|
||||||
private fun combine(
|
private fun combine(
|
||||||
|
depictionCategories: List<String>,
|
||||||
locationCategories: List<String>,
|
locationCategories: List<String>,
|
||||||
titles: List<String>,
|
titles: List<String>,
|
||||||
recents: List<String>
|
recents: List<String>
|
||||||
) = locationCategories + titles + recents
|
) = depictionCategories + locationCategories + titles + recents
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -98,14 +111,13 @@ class CategoriesModel @Inject constructor(
|
||||||
* @param titleList
|
* @param titleList
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private fun titleCategories(titleList: List<String>): Observable<List<String>> {
|
private fun titleCategories(titleList: List<String>) =
|
||||||
return if (titleList.isNotEmpty())
|
if (titleList.isNotEmpty())
|
||||||
Observable.combineLatest(titleList.map { getTitleCategories(it) }) { searchResults ->
|
Observable.combineLatest(titleList.map { getTitleCategories(it) }) { searchResults ->
|
||||||
searchResults.map { it as List<String> }.flatten()
|
searchResults.map { it as List<String> }.flatten()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
Observable.just(emptyList())
|
Observable.just(emptyList())
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return category for single title
|
* Return category for single title
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package fr.free.nrw.commons.contributions;
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import androidx.room.Entity;
|
import androidx.room.Entity;
|
||||||
import androidx.room.PrimaryKey;
|
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.upload.UploadMediaDetail;
|
import fr.free.nrw.commons.upload.UploadMediaDetail;
|
||||||
|
|
@ -13,7 +12,7 @@ import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryLogEvent;
|
import java.util.Objects;
|
||||||
|
|
||||||
@Entity(tableName = "contribution")
|
@Entity(tableName = "contribution")
|
||||||
public class Contribution extends Media {
|
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_QUEUED = 2;
|
||||||
public static final int STATE_IN_PROGRESS = 3;
|
public static final int STATE_IN_PROGRESS = 3;
|
||||||
|
|
||||||
@PrimaryKey (autoGenerate = true)
|
|
||||||
private long _id;
|
|
||||||
private int state;
|
private int state;
|
||||||
private long transferred;
|
private long transferred;
|
||||||
private String decimalCoords;
|
private String decimalCoords;
|
||||||
private String dateCreatedSource;
|
private String dateCreatedSource;
|
||||||
private WikidataPlace wikidataPlace;
|
private WikidataPlace wikidataPlace;
|
||||||
/**
|
/**
|
||||||
* Each depiction loaded in depictions activity is associated with a wikidata entity id,
|
* Each depiction loaded in depictions activity is associated with a wikidata entity id, this Id
|
||||||
* this Id is in turn used to upload depictions to wikibase
|
* is in turn used to upload depictions to wikibase
|
||||||
*/
|
*/
|
||||||
private List<DepictedItem> depictedItems = new ArrayList<>();
|
private List<DepictedItem> depictedItems = new ArrayList<>();
|
||||||
private String mimeType;
|
private String mimeType;
|
||||||
/**
|
/**
|
||||||
* This hasmap stores the list of multilingual captions, where
|
* This hasmap stores the list of multilingual captions, where key of the HashMap is the language
|
||||||
* key of the HashMap is the language and value is the caption in the corresponding language
|
* and value is the caption in the corresponding language Ex: key = "en", value: "<caption in
|
||||||
* Ex: key = "en", value: "<caption in short in English>"
|
* short in English>" key = "de" , value: "<caption in german>"
|
||||||
* key = "de" , value: "<caption in german>"
|
|
||||||
*/
|
*/
|
||||||
private Map<String, String> captions = new HashMap<>();
|
private Map<String, String> captions = new HashMap<>();
|
||||||
|
|
||||||
|
|
@ -55,20 +51,13 @@ public class Contribution extends Media {
|
||||||
UploadMediaDetail.formatList(item.getUploadMediaDetails()),
|
UploadMediaDetail.formatList(item.getUploadMediaDetails()),
|
||||||
sessionManager.getAuthorName(),
|
sessionManager.getAuthorName(),
|
||||||
categories);
|
categories);
|
||||||
captions = UploadMediaDetail.formatCaptions(item.getUploadMediaDetails());
|
captions = UploadMediaDetail.formatCaptions(item.getUploadMediaDetails());
|
||||||
decimalCoords = item.getGpsCoords().getDecimalCoords();
|
decimalCoords = item.getGpsCoords().getDecimalCoords();
|
||||||
dateCreatedSource = "";
|
dateCreatedSource = "";
|
||||||
this.depictedItems = depictedItems;
|
this.depictedItems = depictedItems;
|
||||||
wikidataPlace = WikidataPlace.from(item.getPlace());
|
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) {
|
public void setDateCreatedSource(final String dateCreatedSource) {
|
||||||
this.dateCreatedSource = dateCreatedSource;
|
this.dateCreatedSource = dateCreatedSource;
|
||||||
}
|
}
|
||||||
|
|
@ -108,14 +97,6 @@ public class Contribution extends Media {
|
||||||
return wikidataPlace;
|
return wikidataPlace;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long get_id() {
|
|
||||||
return _id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_id(final long _id) {
|
|
||||||
this._id = _id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDecimalCoords() {
|
public String getDecimalCoords() {
|
||||||
return decimalCoords;
|
return decimalCoords;
|
||||||
}
|
}
|
||||||
|
|
@ -128,29 +109,30 @@ public class Contribution extends Media {
|
||||||
this.depictedItems = depictedItems;
|
this.depictedItems = depictedItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMimeType(String mimeType) {
|
public String getMimeType() {
|
||||||
this.mimeType = mimeType;
|
return mimeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMimeType() {
|
public void setMimeType(final String mimeType) {
|
||||||
return mimeType;
|
this.mimeType = mimeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Captions are a feature part of Structured data. They are meant to store short, multilingual descriptions about files
|
* Captions are a feature part of Structured data. They are meant to store short, multilingual
|
||||||
* This is a replacement of the previously used titles for images (titles were not multilingual)
|
* descriptions about files This is a replacement of the previously used titles for images (titles
|
||||||
* Also now captions replace the previous convention of using title for filename
|
* 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
|
* key of the HashMap is the language and value is the caption in the corresponding language
|
||||||
*
|
* <p>
|
||||||
* returns list of captions stored in hashmap
|
* returns list of captions stored in hashmap
|
||||||
*/
|
*/
|
||||||
public Map<String, String> getCaptions() {
|
public Map<String, String> getCaptions() {
|
||||||
return captions;
|
return captions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCaptions(Map<String, String> captions) {
|
public void setCaptions(Map<String, String> captions) {
|
||||||
this.captions = captions;
|
this.captions = captions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -161,7 +143,6 @@ public class Contribution extends Media {
|
||||||
@Override
|
@Override
|
||||||
public void writeToParcel(final Parcel dest, final int flags) {
|
public void writeToParcel(final Parcel dest, final int flags) {
|
||||||
super.writeToParcel(dest, flags);
|
super.writeToParcel(dest, flags);
|
||||||
dest.writeLong(_id);
|
|
||||||
dest.writeInt(state);
|
dest.writeInt(state);
|
||||||
dest.writeLong(transferred);
|
dest.writeLong(transferred);
|
||||||
dest.writeString(decimalCoords);
|
dest.writeString(decimalCoords);
|
||||||
|
|
@ -169,9 +150,24 @@ public class Contribution extends Media {
|
||||||
dest.writeSerializable((HashMap) captions);
|
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) {
|
protected Contribution(final Parcel in) {
|
||||||
super(in);
|
super(in);
|
||||||
_id = in.readLong();
|
|
||||||
state = in.readInt();
|
state = in.readInt();
|
||||||
transferred = in.readLong();
|
transferred = in.readLong();
|
||||||
decimalCoords = in.readString();
|
decimalCoords = in.readString();
|
||||||
|
|
@ -190,4 +186,35 @@ public class Contribution extends Media {
|
||||||
return new Contribution[size];
|
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;
|
package fr.free.nrw.commons.contributions;
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.paging.DataSource;
|
||||||
import androidx.room.Dao;
|
import androidx.room.Dao;
|
||||||
import androidx.room.Delete;
|
import androidx.room.Delete;
|
||||||
import androidx.room.Insert;
|
import androidx.room.Insert;
|
||||||
|
|
@ -15,40 +15,55 @@ import java.util.List;
|
||||||
@Dao
|
@Dao
|
||||||
public abstract class ContributionDao {
|
public abstract class ContributionDao {
|
||||||
|
|
||||||
@Query("SELECT * FROM contribution order by dateUploaded DESC")
|
@Query("SELECT * FROM contribution order by dateUploaded DESC")
|
||||||
abstract LiveData<List<Contribution>> fetchContributions();
|
abstract DataSource.Factory<Integer, Contribution> fetchContributions();
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
public abstract Single<Long> save(Contribution contribution);
|
public abstract void saveSynchronous(Contribution contribution);
|
||||||
|
|
||||||
public Completable deleteAllAndSave(List<Contribution> contributions){
|
public Completable save(final Contribution contribution) {
|
||||||
return Completable.fromAction(() -> deleteAllAndSaveTransaction(contributions));
|
return Completable
|
||||||
}
|
.fromAction(() -> saveSynchronous(contribution));
|
||||||
|
}
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
public void deleteAllAndSaveTransaction(List<Contribution> contributions){
|
public void deleteAndSaveContribution(final Contribution oldContribution,
|
||||||
deleteAll(Contribution.STATE_COMPLETED);
|
final Contribution newContribution) {
|
||||||
save(contributions);
|
deleteSynchronous(oldContribution);
|
||||||
}
|
saveSynchronous(newContribution);
|
||||||
|
}
|
||||||
|
|
||||||
@Insert
|
public Completable saveAndDelete(final Contribution oldContribution,
|
||||||
public abstract void save(List<Contribution> contribution);
|
final Contribution newContribution) {
|
||||||
|
return Completable
|
||||||
|
.fromAction(() -> deleteAndSaveContribution(oldContribution, newContribution));
|
||||||
|
}
|
||||||
|
|
||||||
@Delete
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
public abstract Single<Integer> delete(Contribution contribution);
|
public abstract Single<List<Long>> save(List<Contribution> contribution);
|
||||||
|
|
||||||
@Query("SELECT * from contribution WHERE filename=:fileName")
|
@Delete
|
||||||
public abstract List<Contribution> getContributionWithTitle(String fileName);
|
public abstract void deleteSynchronous(Contribution contribution);
|
||||||
|
|
||||||
@Query("UPDATE contribution SET state=:state WHERE state in (:toUpdateStates)")
|
public Completable delete(final Contribution contribution) {
|
||||||
public abstract Single<Integer> updateStates(int state, int[] toUpdateStates);
|
return Completable
|
||||||
|
.fromAction(() -> deleteSynchronous(contribution));
|
||||||
|
}
|
||||||
|
|
||||||
@Query("Delete FROM contribution")
|
@Query("SELECT * from contribution WHERE filename=:fileName")
|
||||||
public abstract void deleteAll();
|
public abstract List<Contribution> getContributionWithTitle(String fileName);
|
||||||
|
|
||||||
@Query("Delete FROM contribution WHERE state = :state")
|
@Query("UPDATE contribution SET state=:state WHERE state in (:toUpdateStates)")
|
||||||
public abstract void deleteAll(int state);
|
public abstract Single<Integer> updateStates(int state, int[] toUpdateStates);
|
||||||
|
|
||||||
@Update
|
@Query("Delete FROM contribution")
|
||||||
public abstract Single<Integer> update(Contribution 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.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
import java.util.Random;
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
@ -39,23 +38,22 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
private int position;
|
private int position;
|
||||||
private Contribution contribution;
|
private Contribution contribution;
|
||||||
private Random random = new Random();
|
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
|
||||||
private final MediaClient mediaClient;
|
private final MediaClient mediaClient;
|
||||||
|
|
||||||
ContributionViewHolder(View parent, Callback callback,
|
ContributionViewHolder(final View parent, final Callback callback,
|
||||||
MediaClient mediaClient) {
|
final MediaClient mediaClient) {
|
||||||
super(parent);
|
super(parent);
|
||||||
this.mediaClient = mediaClient;
|
this.mediaClient = mediaClient;
|
||||||
ButterKnife.bind(this, parent);
|
ButterKnife.bind(this, parent);
|
||||||
this.callback=callback;
|
this.callback=callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(int position, Contribution contribution) {
|
public void init(final int position, final Contribution contribution) {
|
||||||
this.contribution = contribution;
|
this.contribution = contribution;
|
||||||
fetchAndDisplayCaption(contribution);
|
fetchAndDisplayCaption(contribution);
|
||||||
this.position = position;
|
this.position = position;
|
||||||
String imageSource = chooseImageSource(contribution.getThumbUrl(), contribution.getLocalUri());
|
final String imageSource = chooseImageSource(contribution.getThumbUrl(), contribution.getLocalUri());
|
||||||
if (!TextUtils.isEmpty(imageSource)) {
|
if (!TextUtils.isEmpty(imageSource)) {
|
||||||
final ImageRequest imageRequest =
|
final ImageRequest imageRequest =
|
||||||
ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource))
|
ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource))
|
||||||
|
|
@ -84,8 +82,8 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
||||||
stateView.setVisibility(View.GONE);
|
stateView.setVisibility(View.GONE);
|
||||||
progressView.setVisibility(View.VISIBLE);
|
progressView.setVisibility(View.VISIBLE);
|
||||||
failedImageOptions.setVisibility(View.GONE);
|
failedImageOptions.setVisibility(View.GONE);
|
||||||
long total = contribution.getDataLength();
|
final long total = contribution.getDataLength();
|
||||||
long transferred = contribution.getTransferred();
|
final long transferred = contribution.getTransferred();
|
||||||
if (transferred == 0 || transferred >= total) {
|
if (transferred == 0 || transferred >= total) {
|
||||||
progressView.setIndeterminate(true);
|
progressView.setIndeterminate(true);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -107,14 +105,14 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
||||||
*
|
*
|
||||||
* @param contribution
|
* @param contribution
|
||||||
*/
|
*/
|
||||||
private void fetchAndDisplayCaption(Contribution contribution) {
|
private void fetchAndDisplayCaption(final Contribution contribution) {
|
||||||
if ((contribution.getState() != Contribution.STATE_COMPLETED)) {
|
if ((contribution.getState() != Contribution.STATE_COMPLETED)) {
|
||||||
titleView.setText(contribution.getDisplayTitle());
|
titleView.setText(contribution.getDisplayTitle());
|
||||||
} else {
|
} else {
|
||||||
final String pageId = contribution.getPageId();
|
final String pageId = contribution.getPageId();
|
||||||
if (pageId != null) {
|
if (pageId != null) {
|
||||||
Timber.d("Fetching caption for %s", contribution.getFilename());
|
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
|
+ 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)
|
compositeDisposable.add(mediaClient.getCaptionByWikibaseIdentifier(wikibaseMediaId)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
|
|
@ -141,7 +139,7 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private String chooseImageSource(String thumbUrl, Uri localUri) {
|
private String chooseImageSource(final String thumbUrl, final Uri localUri) {
|
||||||
return !TextUtils.isEmpty(thumbUrl) ? thumbUrl :
|
return !TextUtils.isEmpty(thumbUrl) ? thumbUrl :
|
||||||
localUri != null ? localUri.toString() :
|
localUri != null ? localUri.toString() :
|
||||||
null;
|
null;
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,6 @@ public class ContributionsContract {
|
||||||
|
|
||||||
public interface View {
|
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);
|
void showMessage(String localizedMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,8 +21,6 @@ public class ContributionsContract {
|
||||||
|
|
||||||
void deleteUpload(Contribution contribution);
|
void deleteUpload(Contribution contribution);
|
||||||
|
|
||||||
Media getItemAtPosition(int i);
|
|
||||||
|
|
||||||
void updateContribution(Contribution contribution);
|
void updateContribution(Contribution contribution);
|
||||||
|
|
||||||
void fetchMediaDetails(Contribution contribution);
|
void fetchMediaDetails(Contribution contribution);
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import android.widget.Toast;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
|
import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
import butterknife.BindView;
|
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.CampaignView;
|
||||||
import fr.free.nrw.commons.campaigns.CampaignsPresenter;
|
import fr.free.nrw.commons.campaigns.CampaignsPresenter;
|
||||||
import fr.free.nrw.commons.campaigns.ICampaignsView;
|
import fr.free.nrw.commons.campaigns.ICampaignsView;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
|
import fr.free.nrw.commons.contributions.ContributionsListFragment.Callback;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsListFragment.SourceRefresher;
|
|
||||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
|
@ -54,7 +52,6 @@ import io.reactivex.Observable;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
import java.util.List;
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
@ -62,11 +59,10 @@ import timber.log.Timber;
|
||||||
public class ContributionsFragment
|
public class ContributionsFragment
|
||||||
extends CommonsDaggerSupportFragment
|
extends CommonsDaggerSupportFragment
|
||||||
implements
|
implements
|
||||||
MediaDetailProvider,
|
|
||||||
OnBackStackChangedListener,
|
OnBackStackChangedListener,
|
||||||
SourceRefresher,
|
|
||||||
LocationUpdateListener,
|
LocationUpdateListener,
|
||||||
ICampaignsView, ContributionsContract.View {
|
MediaDetailProvider,
|
||||||
|
ICampaignsView, ContributionsContract.View, Callback {
|
||||||
@Inject @Named("default_preferences") JsonKvStore store;
|
@Inject @Named("default_preferences") JsonKvStore store;
|
||||||
@Inject NearbyController nearbyController;
|
@Inject NearbyController nearbyController;
|
||||||
@Inject OkHttpJsonApiClient okHttpJsonApiClient;
|
@Inject OkHttpJsonApiClient okHttpJsonApiClient;
|
||||||
|
|
@ -78,8 +74,8 @@ public class ContributionsFragment
|
||||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||||
|
|
||||||
private ContributionsListFragment contributionsListFragment;
|
private ContributionsListFragment contributionsListFragment;
|
||||||
private MediaDetailPagerFragment mediaDetailPagerFragment;
|
|
||||||
private static final String CONTRIBUTION_LIST_FRAGMENT_TAG = "ContributionListFragmentTag";
|
private static final String CONTRIBUTION_LIST_FRAGMENT_TAG = "ContributionListFragmentTag";
|
||||||
|
private MediaDetailPagerFragment mediaDetailPagerFragment;
|
||||||
static final String MEDIA_DETAIL_PAGER_FRAGMENT_TAG = "MediaDetailFragmentTag";
|
static final String MEDIA_DETAIL_PAGER_FRAGMENT_TAG = "MediaDetailFragmentTag";
|
||||||
|
|
||||||
@BindView(R.id.card_view_nearby) public NearbyNotificationCardView nearbyNotificationCardView;
|
@BindView(R.id.card_view_nearby) public NearbyNotificationCardView nearbyNotificationCardView;
|
||||||
|
|
@ -113,7 +109,6 @@ public class ContributionsFragment
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private boolean shouldShowMediaDetailsFragment;
|
private boolean shouldShowMediaDetailsFragment;
|
||||||
private int numberOfContributions;
|
|
||||||
private boolean isAuthCookieAcquired;
|
private boolean isAuthCookieAcquired;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -128,7 +123,6 @@ public class ContributionsFragment
|
||||||
ButterKnife.bind(this, view);
|
ButterKnife.bind(this, view);
|
||||||
presenter.onAttachView(this);
|
presenter.onAttachView(this);
|
||||||
contributionsPresenter.onAttachView(this);
|
contributionsPresenter.onAttachView(this);
|
||||||
contributionsPresenter.setLifeCycleOwner(this.getViewLifecycleOwner());
|
|
||||||
campaignView.setVisibility(View.GONE);
|
campaignView.setVisibility(View.GONE);
|
||||||
checkBoxView = View.inflate(getActivity(), R.layout.nearby_permission_dialog, null);
|
checkBoxView = View.inflate(getActivity(), R.layout.nearby_permission_dialog, null);
|
||||||
checkBox = (CheckBox) checkBoxView.findViewById(R.id.never_ask_again);
|
checkBox = (CheckBox) checkBoxView.findViewById(R.id.never_ask_again);
|
||||||
|
|
@ -141,103 +135,21 @@ public class ContributionsFragment
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
mediaDetailPagerFragment = (MediaDetailPagerFragment) getChildFragmentManager()
|
mediaDetailPagerFragment = (MediaDetailPagerFragment) getChildFragmentManager()
|
||||||
.findFragmentByTag(MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
|
.findFragmentByTag(MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
|
||||||
contributionsListFragment = (ContributionsListFragment) getChildFragmentManager()
|
contributionsListFragment = (ContributionsListFragment) getChildFragmentManager()
|
||||||
.findFragmentByTag(CONTRIBUTION_LIST_FRAGMENT_TAG);
|
.findFragmentByTag(CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||||
shouldShowMediaDetailsFragment = savedInstanceState.getBoolean("mediaDetailsVisible");
|
shouldShowMediaDetailsFragment = savedInstanceState.getBoolean("mediaDetailsVisible");
|
||||||
}
|
}
|
||||||
|
|
||||||
initFragments();
|
initFragments();
|
||||||
|
|
||||||
if(shouldShowMediaDetailsFragment){
|
|
||||||
showMediaDetailPagerFragment();
|
|
||||||
}else{
|
|
||||||
showContributionsListFragment();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ConfigUtils.isBetaFlavour()) {
|
if (!ConfigUtils.isBetaFlavour()) {
|
||||||
setUploadCount();
|
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;
|
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
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
|
|
@ -265,7 +177,7 @@ public class ContributionsFragment
|
||||||
if (nearbyNotificationCardView != null) {
|
if (nearbyNotificationCardView != null) {
|
||||||
if (store.getBoolean("displayNearbyCardView", true)) {
|
if (store.getBoolean("displayNearbyCardView", true)) {
|
||||||
if (nearbyNotificationCardView.cardViewVisibilityState
|
if (nearbyNotificationCardView.cardViewVisibilityState
|
||||||
== NearbyNotificationCardView.CardViewVisibilityState.READY) {
|
== NearbyNotificationCardView.CardViewVisibilityState.READY) {
|
||||||
nearbyNotificationCardView.setVisibility(View.VISIBLE);
|
nearbyNotificationCardView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -275,20 +187,22 @@ public class ContributionsFragment
|
||||||
showFragment(contributionsListFragment, CONTRIBUTION_LIST_FRAGMENT_TAG);
|
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() {
|
private void showMediaDetailPagerFragment() {
|
||||||
// hide tabs on media detail view is visible
|
// hide tabs on media detail view is visible
|
||||||
((MainActivity)getActivity()).hideTabs();
|
((MainActivity) getActivity()).hideTabs();
|
||||||
// hide nearby card view on media detail is visible
|
// hide nearby card view on media detail is visible
|
||||||
nearbyNotificationCardView.setVisibility(View.GONE);
|
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
|
@Override
|
||||||
public void onBackStackChanged() {
|
public void onBackStackChanged() {
|
||||||
((MainActivity)getActivity()).initBackButton();
|
((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(){
|
public Intent getUploadServiceIntent(){
|
||||||
Intent intent = new Intent(getActivity(), UploadService.class);
|
Intent intent = new Intent(getActivity(), UploadService.class);
|
||||||
intent.setAction(UploadService.ACTION_START_SERVICE);
|
intent.setAction(UploadService.ACTION_START_SERVICE);
|
||||||
return intent;
|
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")
|
@SuppressWarnings("ConstantConditions")
|
||||||
private void setUploadCount() {
|
private void setUploadCount() {
|
||||||
|
|
||||||
compositeDisposable.add(okHttpJsonApiClient
|
compositeDisposable.add(okHttpJsonApiClient
|
||||||
.getUploadCount(((MainActivity)getActivity()).sessionManager.getCurrentAccount().name)
|
.getUploadCount(((MainActivity)getActivity()).sessionManager.getCurrentAccount().name)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
|
|
@ -373,8 +286,6 @@ public class ContributionsFragment
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
boolean mediaDetailsVisible = mediaDetailPagerFragment != null && mediaDetailPagerFragment.isVisible();
|
|
||||||
outState.putBoolean("mediaDetailsVisible", mediaDetailsVisible);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -384,13 +295,6 @@ public class ContributionsFragment
|
||||||
firstLocationUpdate = true;
|
firstLocationUpdate = true;
|
||||||
locationManager.addLocationListener(this);
|
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)) {
|
if (store.getBoolean("displayNearbyCardView", true)) {
|
||||||
checkPermissionsAndShowNearbyCardView();
|
checkPermissionsAndShowNearbyCardView();
|
||||||
if (nearbyNotificationCardView.cardViewVisibilityState == NearbyNotificationCardView.CardViewVisibilityState.READY) {
|
if (nearbyNotificationCardView.cardViewVisibilityState == NearbyNotificationCardView.CardViewVisibilityState.READY) {
|
||||||
|
|
@ -403,10 +307,6 @@ public class ContributionsFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchCampaigns();
|
fetchCampaigns();
|
||||||
if(isAuthCookieAcquired){
|
|
||||||
contributionsPresenter.fetchContributions();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkPermissionsAndShowNearbyCardView() {
|
private void checkPermissionsAndShowNearbyCardView() {
|
||||||
|
|
@ -463,17 +363,11 @@ public class ContributionsFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateNearbyNotification(@Nullable NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
|
private void updateNearbyNotification(@Nullable NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
|
||||||
|
|
||||||
if (nearbyPlacesInfo != null && nearbyPlacesInfo.placeList != null && nearbyPlacesInfo.placeList.size() > 0) {
|
if (nearbyPlacesInfo != null && nearbyPlacesInfo.placeList != null && nearbyPlacesInfo.placeList.size() > 0) {
|
||||||
Place closestNearbyPlace = nearbyPlacesInfo.placeList.get(0);
|
Place closestNearbyPlace = nearbyPlacesInfo.placeList.get(0);
|
||||||
String distance = formatDistanceBetween(curLatLng, closestNearbyPlace.location);
|
String distance = formatDistanceBetween(curLatLng, closestNearbyPlace.location);
|
||||||
closestNearbyPlace.setDistance(distance);
|
closestNearbyPlace.setDistance(distance);
|
||||||
nearbyNotificationCardView.updateContent(closestNearbyPlace);
|
nearbyNotificationCardView.updateContent(closestNearbyPlace);
|
||||||
if (mediaDetailPagerFragment != null && mediaDetailPagerFragment.isVisible()) {
|
|
||||||
nearbyNotificationCardView.setVisibility(View.GONE);
|
|
||||||
}else {
|
|
||||||
nearbyNotificationCardView.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Means that no close nearby place is found
|
// Means that no close nearby place is found
|
||||||
nearbyNotificationCardView.setVisibility(View.GONE);
|
nearbyNotificationCardView.setVisibility(View.GONE);
|
||||||
|
|
@ -553,37 +447,13 @@ public class ContributionsFragment
|
||||||
presenter.onDetachView();
|
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
|
* Retry upload when it is failed
|
||||||
*
|
*
|
||||||
* @param contribution contribution to be retried
|
* @param contribution contribution to be retried
|
||||||
*/
|
*/
|
||||||
private void retryUpload(Contribution contribution) {
|
@Override
|
||||||
|
public void retryUpload(Contribution contribution) {
|
||||||
if (NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
if (NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||||
if (contribution.getState() == STATE_FAILED && null != uploadService) {
|
if (contribution.getState() == STATE_FAILED && null != uploadService) {
|
||||||
uploadService.queue(contribution);
|
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.LayoutInflater;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import androidx.annotation.NonNull;
|
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.R;
|
||||||
import fr.free.nrw.commons.media.MediaClient;
|
import fr.free.nrw.commons.media.MediaClient;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents The View Adapter for the List of Contributions
|
* 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 final MediaClient mediaClient;
|
||||||
private List<Contribution> contributions;
|
|
||||||
|
|
||||||
public ContributionsListAdapter(Callback callback,
|
ContributionsListAdapter(final Callback callback,
|
||||||
MediaClient mediaClient) {
|
final MediaClient mediaClient) {
|
||||||
|
super(DIFF_CALLBACK);
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.mediaClient = mediaClient;
|
this.mediaClient = mediaClient;
|
||||||
contributions = new ArrayList<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the new View Holder which will be used to display items(contributions)
|
* Uses DiffUtil to calculate the changes in the list
|
||||||
* using the onBindViewHolder(viewHolder,position)
|
* It has methods that check ID and the content of the items to determine if its a new item
|
||||||
*/
|
*/
|
||||||
@NonNull
|
private static final DiffUtil.ItemCallback<Contribution> DIFF_CALLBACK =
|
||||||
@Override
|
new DiffUtil.ItemCallback<Contribution>() {
|
||||||
public ContributionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
@Override
|
||||||
ContributionViewHolder viewHolder = new ContributionViewHolder(
|
public boolean areItemsTheSame(final Contribution oldContribution, final Contribution newContribution) {
|
||||||
LayoutInflater.from(parent.getContext())
|
return oldContribution.getPageId().equals(newContribution.getPageId());
|
||||||
.inflate(R.layout.layout_contribution, parent, false), callback, mediaClient);
|
}
|
||||||
return viewHolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull ContributionViewHolder holder, int position) {
|
public boolean areContentsTheSame(final Contribution oldContribution, final Contribution newContribution) {
|
||||||
final Contribution contribution = contributions.get(position);
|
return oldContribution.equals(newContribution);
|
||||||
if (TextUtils.isEmpty(contribution.getThumbUrl())
|
}
|
||||||
&& contribution.getState() == Contribution.STATE_COMPLETED) {
|
};
|
||||||
callback.fetchMediaUriFor(contribution);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
holder.init(position, contribution);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
Contribution getContributionForPosition(final int position) {
|
||||||
public int getItemCount() {
|
return getItem(position);
|
||||||
return contributions.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setContributions(@NonNull List<Contribution> contributionList) {
|
|
||||||
contributions = contributionList;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the new View Holder which will be used to display items(contributions) using the
|
||||||
|
* onBindViewHolder(viewHolder,position)
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public long getItemId(int position) {
|
public ContributionViewHolder onCreateViewHolder(@NonNull final ViewGroup parent,
|
||||||
return contributions.get(position).get_id();
|
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 {
|
public interface Callback {
|
||||||
|
|
@ -72,9 +75,5 @@ public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionV
|
||||||
void deleteUpload(Contribution contribution);
|
void deleteUpload(Contribution contribution);
|
||||||
|
|
||||||
void openMediaDetail(int 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.content.res.Configuration;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Parcelable;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
@ -16,218 +17,217 @@ import android.widget.TextView;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.recyclerview.widget.RecyclerView.LayoutManager;
|
import androidx.recyclerview.widget.RecyclerView.LayoutManager;
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.R;
|
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.di.CommonsDaggerSupportFragment;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
|
||||||
import fr.free.nrw.commons.media.MediaClient;
|
import fr.free.nrw.commons.media.MediaClient;
|
||||||
import java.util.ArrayList;
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
import java.util.List;
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by root on 01.06.2018.
|
* 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";
|
private static final String RV_STATE = "rv_scroll_state";
|
||||||
@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;
|
|
||||||
|
|
||||||
@Inject @Named("default_preferences") JsonKvStore kvStore;
|
@BindView(R.id.contributionsList)
|
||||||
@Inject ContributionController controller;
|
RecyclerView rvContributionsList;
|
||||||
@Inject MediaClient mediaClient;
|
@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;
|
@Inject
|
||||||
private Animation fab_open;
|
ContributionController controller;
|
||||||
private Animation rotate_forward;
|
@Inject
|
||||||
private Animation rotate_backward;
|
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 final Callback callback;
|
||||||
private String lastVisibleItemID;
|
|
||||||
|
|
||||||
private int SPAN_COUNT=3;
|
private final int SPAN_COUNT_LANDSCAPE = 3;
|
||||||
private List<Contribution> contributions=new ArrayList<>();
|
private final int SPAN_COUNT_PORTRAIT = 1;
|
||||||
|
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
ContributionsListFragment(final Callback callback) {
|
||||||
View view = inflater.inflate(R.layout.fragment_contributions_list, container, false);
|
this.callback = callback;
|
||||||
ButterKnife.bind(this, view);
|
}
|
||||||
initAdapter();
|
|
||||||
return view;
|
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() {
|
@Override
|
||||||
adapter = new ContributionsListAdapter(callback, mediaClient);
|
public void retryUpload(final Contribution contribution) {
|
||||||
adapter.setHasStableIds(true);
|
callback.retryUpload(contribution);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
public void deleteUpload(final Contribution contribution) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
contributionsListPresenter.deleteUpload(contribution);
|
||||||
initRecyclerView();
|
}
|
||||||
initializeAnimations();
|
|
||||||
setListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initRecyclerView() {
|
@Override
|
||||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
public void openMediaDetail(final int position) {
|
||||||
rvContributionsList.setLayoutManager(new GridLayoutManager(getContext(),SPAN_COUNT));
|
callback.showDetail(position);
|
||||||
} else {
|
}
|
||||||
rvContributionsList.setLayoutManager(new LinearLayoutManager(getContext()));
|
|
||||||
}
|
|
||||||
|
|
||||||
rvContributionsList.setAdapter(adapter);
|
public Media getMediaAtPosition(final int i) {
|
||||||
adapter.setContributions(contributions);
|
return adapter.getContributionForPosition(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public int getTotalMediaCount() {
|
||||||
public void onConfigurationChanged(Configuration newConfig) {
|
return adapter.getItemCount();
|
||||||
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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeAnimations() {
|
public interface Callback {
|
||||||
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() {
|
void retryUpload(Contribution contribution);
|
||||||
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 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;
|
package fr.free.nrw.commons.contributions;
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.paging.DataSource.Factory;
|
||||||
|
import io.reactivex.Completable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
import io.reactivex.Completable;
|
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -59,23 +58,23 @@ class ContributionsLocalDataSource {
|
||||||
* @param contribution
|
* @param contribution
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Single<Integer> deleteContribution(Contribution contribution) {
|
public Completable deleteContribution(Contribution contribution) {
|
||||||
return contributionDao.delete(contribution);
|
return contributionDao.delete(contribution);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<List<Contribution>> getContributions() {
|
public Factory<Integer, Contribution> getContributions() {
|
||||||
return contributionDao.fetchContributions();
|
return contributionDao.fetchContributions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Completable saveContributions(List<Contribution> contributions) {
|
public Single<List<Long>> saveContributions(List<Contribution> contributions) {
|
||||||
return contributionDao.deleteAllAndSave(contributions);
|
return contributionDao.save(contributions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set(String key, long value) {
|
public void set(String key, long value) {
|
||||||
defaultKVStore.putLong(key,value);
|
defaultKVStore.putLong(key,value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Single<Integer> updateContribution(Contribution contribution) {
|
public Completable updateContribution(Contribution contribution) {
|
||||||
return contributionDao.update(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.Media;
|
||||||
import fr.free.nrw.commons.MediaDataExtractor;
|
import fr.free.nrw.commons.MediaDataExtractor;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
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.contributions.ContributionsContract.UserActionListener;
|
||||||
import fr.free.nrw.commons.db.AppDatabase;
|
|
||||||
import fr.free.nrw.commons.di.CommonsApplicationModule;
|
import fr.free.nrw.commons.di.CommonsApplicationModule;
|
||||||
import fr.free.nrw.commons.mwapi.UserClient;
|
import fr.free.nrw.commons.mwapi.UserClient;
|
||||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
|
|
@ -25,6 +25,9 @@ import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The presenter class for Contributions
|
* The presenter class for Contributions
|
||||||
*/
|
*/
|
||||||
|
|
@ -35,25 +38,10 @@ public class ContributionsPresenter implements UserActionListener {
|
||||||
private final Scheduler ioThreadScheduler;
|
private final Scheduler ioThreadScheduler;
|
||||||
private CompositeDisposable compositeDisposable;
|
private CompositeDisposable compositeDisposable;
|
||||||
private ContributionsContract.View view;
|
private ContributionsContract.View view;
|
||||||
private List<Contribution> contributionList=new ArrayList<>();
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
Context context;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
UserClient userClient;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
AppDatabase appDatabase;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
SessionManager sessionManager;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MediaDataExtractor mediaDataExtractor;
|
MediaDataExtractor mediaDataExtractor;
|
||||||
|
|
||||||
private LifecycleOwner lifeCycleOwner;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ContributionsPresenter(ContributionsRepository repository, @Named(CommonsApplicationModule.MAIN_THREAD) Scheduler mainThreadScheduler,@Named(CommonsApplicationModule.IO_THREAD) Scheduler ioThreadScheduler) {
|
ContributionsPresenter(ContributionsRepository repository, @Named(CommonsApplicationModule.MAIN_THREAD) Scheduler mainThreadScheduler,@Named(CommonsApplicationModule.IO_THREAD) Scheduler ioThreadScheduler) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
|
|
@ -61,74 +49,12 @@ public class ContributionsPresenter implements UserActionListener {
|
||||||
this.ioThreadScheduler=ioThreadScheduler;
|
this.ioThreadScheduler=ioThreadScheduler;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String user;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttachView(ContributionsContract.View view) {
|
public void onAttachView(ContributionsContract.View view) {
|
||||||
this.view = view;
|
this.view = view;
|
||||||
compositeDisposable=new CompositeDisposable();
|
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
|
@Override
|
||||||
public void onDetachView() {
|
public void onDetachView() {
|
||||||
this.view = null;
|
this.view = null;
|
||||||
|
|
@ -146,24 +72,10 @@ public class ContributionsPresenter implements UserActionListener {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void deleteUpload(Contribution contribution) {
|
public void deleteUpload(Contribution contribution) {
|
||||||
compositeDisposable.add(repository.deleteContributionFromDB(contribution)
|
compositeDisposable.add(repository
|
||||||
.subscribeOn(ioThreadScheduler)
|
.deleteContributionFromDB(contribution)
|
||||||
.subscribe());
|
.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
package fr.free.nrw.commons.contributions;
|
package fr.free.nrw.commons.contributions;
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.paging.DataSource.Factory;
|
||||||
|
import io.reactivex.Completable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import io.reactivex.Completable;
|
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -33,7 +32,7 @@ public class ContributionsRepository {
|
||||||
* @param contribution
|
* @param contribution
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Single<Integer> deleteContributionFromDB(Contribution contribution) {
|
public Completable deleteContributionFromDB(Contribution contribution) {
|
||||||
return localDataSource.deleteContribution(contribution);
|
return localDataSource.deleteContribution(contribution);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,11 +45,11 @@ public class ContributionsRepository {
|
||||||
return localDataSource.getContributionWithFileName(fileName);
|
return localDataSource.getContributionWithFileName(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<List<Contribution>> fetchContributions() {
|
public Factory<Integer, Contribution> fetchContributions() {
|
||||||
return localDataSource.getContributions();
|
return localDataSource.getContributions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Completable save(List<Contribution> contributions) {
|
public Single<List<Long>> save(List<Contribution> contributions) {
|
||||||
return localDataSource.saveContributions(contributions);
|
return localDataSource.saveContributions(contributions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,11 +57,7 @@ public class ContributionsRepository {
|
||||||
localDataSource.set(key,value);
|
localDataSource.set(key,value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getLong(String key) {
|
public Completable updateContribution(Contribution contribution) {
|
||||||
return localDataSource.getLong(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Single<Integer> updateContribution(Contribution contribution) {
|
|
||||||
return localDataSource.updateContribution(contribution);
|
return localDataSource.updateContribution(contribution);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package fr.free.nrw.commons.contributions;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
|
@ -12,7 +11,6 @@ import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.view.GravityCompat;
|
import androidx.core.view.GravityCompat;
|
||||||
import androidx.drawerlayout.widget.DrawerLayout;
|
import androidx.drawerlayout.widget.DrawerLayout;
|
||||||
|
|
@ -20,16 +18,9 @@ import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentPagerAdapter;
|
import androidx.fragment.app.FragmentPagerAdapter;
|
||||||
import androidx.viewpager.widget.ViewPager;
|
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.BindView;
|
||||||
import butterknife.ButterKnife;
|
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.R;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
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 fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.inject.Inject;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static android.content.ContentResolver.requestSync;
|
|
||||||
|
|
||||||
public class MainActivity extends NavigationBaseActivity implements FragmentManager.OnBackStackChangedListener {
|
public class MainActivity extends NavigationBaseActivity implements FragmentManager.OnBackStackChangedListener {
|
||||||
|
|
||||||
@BindView(R.id.tab_layout)
|
@BindView(R.id.tab_layout)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
|
||||||
public class DBOpenHelper extends SQLiteOpenHelper {
|
public class DBOpenHelper extends SQLiteOpenHelper {
|
||||||
|
|
||||||
private static final String DATABASE_NAME = "commons.db";
|
private static final String DATABASE_NAME = "commons.db";
|
||||||
private static final int DATABASE_VERSION = 12;
|
private static final int DATABASE_VERSION = 13;
|
||||||
public static final String CONTRIBUTIONS_TABLE = "contributions";
|
public static final String CONTRIBUTIONS_TABLE = "contributions";
|
||||||
private final String DROP_TABLE_STATEMENT="DROP TABLE IF EXISTS %s";
|
private final String DROP_TABLE_STATEMENT="DROP TABLE IF EXISTS %s";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import fr.free.nrw.commons.contributions.ContributionDao
|
||||||
* The database for accessing the respective DAOs
|
* 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)
|
@TypeConverters(Converters::class)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
abstract fun contributionDao(): ContributionDao
|
abstract fun contributionDao(): ContributionDao
|
||||||
|
|
|
||||||
|
|
@ -90,8 +90,9 @@ public interface DepictedImagesContract {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches more images for the item and adds it to the grid view adapter
|
* Fetches more images for the item and adds it to the grid view adapter
|
||||||
|
* @param entityId
|
||||||
*/
|
*/
|
||||||
void fetchMoreImages();
|
void fetchMoreImages(String entityId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fetch captions for the image using filename and replace title of on the image thumbnail(if captions are available)
|
* fetch captions for the image using filename and replace title of on the image thumbnail(if captions are available)
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ public class DepictedImagesFragment extends DaggerFragment implements DepictedIm
|
||||||
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||||
handleNoInternet();
|
handleNoInternet();
|
||||||
} else {
|
} else {
|
||||||
presenter.fetchMoreImages();
|
presenter.fetchMoreImages(entityId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isLastPage) {
|
if (isLastPage) {
|
||||||
|
|
|
||||||
|
|
@ -80,10 +80,11 @@ public class DepictedImagesPresenter implements DepictedImagesContract.UserActio
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches more images for the item and adds it to the grid view adapter
|
* Fetches more images for the item and adds it to the grid view adapter
|
||||||
|
* @param entityId
|
||||||
*/
|
*/
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
@Override
|
@Override
|
||||||
public void fetchMoreImages() {
|
public void fetchMoreImages(String entityId) {
|
||||||
view.progressBarVisible(true);
|
view.progressBarVisible(true);
|
||||||
compositeDisposable.add(depictsClient.fetchImagesForDepictedItem(entityId, queryList.size())
|
compositeDisposable.add(depictsClient.fetchImagesForDepictedItem(entityId, queryList.size())
|
||||||
.subscribeOn(ioScheduler)
|
.subscribeOn(ioScheduler)
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,9 @@ public class CommonsApplicationModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
public AppDatabase provideAppDataBase() {
|
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
|
@Provides
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.paging.PagedListAdapter
|
import androidx.paging.PagedListAdapter
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import fr.free.nrw.commons.R
|
import fr.free.nrw.commons.R
|
||||||
import fr.free.nrw.commons.explore.BaseViewHolder
|
|
||||||
import fr.free.nrw.commons.explore.inflate
|
import fr.free.nrw.commons.explore.inflate
|
||||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
|
import kotlinx.android.extensions.LayoutContainer
|
||||||
import kotlinx.android.synthetic.main.item_depictions.*
|
import kotlinx.android.synthetic.main.item_depictions.*
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -22,18 +23,17 @@ class DepictionAdapter(val onDepictionClicked: (DepictedItem) -> Unit) :
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DepictedItemViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DepictedItemViewHolder {
|
||||||
return DepictedItemViewHolder(parent.inflate(R.layout.item_depictions), onDepictionClicked)
|
return DepictedItemViewHolder(parent.inflate(R.layout.item_depictions))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: DepictedItemViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: DepictedItemViewHolder, position: Int) {
|
||||||
holder.bind(getItem(position)!!)
|
holder.bind(getItem(position)!!, onDepictionClicked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DepictedItemViewHolder(containerView: View, val onDepictionClicked: (DepictedItem) -> Unit) :
|
class DepictedItemViewHolder(override val containerView: View) :
|
||||||
BaseViewHolder<DepictedItem>(containerView) {
|
RecyclerView.ViewHolder(containerView), LayoutContainer {
|
||||||
|
fun bind(item: DepictedItem, onDepictionClicked: (DepictedItem) -> Unit) {
|
||||||
override fun bind(item: DepictedItem) {
|
|
||||||
containerView.setOnClickListener { onDepictionClicked(item) }
|
containerView.setOnClickListener { onDepictionClicked(item) }
|
||||||
depicts_label.text = item.name
|
depicts_label.text = item.name
|
||||||
description.text = item.description
|
description.text = item.description
|
||||||
|
|
@ -44,7 +44,3 @@ class DepictedItemViewHolder(containerView: View, val onDepictionClicked: (Depic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ class DepictsClient @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getEntities(ids: String): Single<Entities> {
|
fun getEntities(ids: String): Single<Entities> {
|
||||||
return depictsInterface.getEntities(ids, Locale.getDefault().language)
|
return depictsInterface.getEntities(ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toDepictions(sparqlResponse: Observable<SparqlResponse>): Observable<List<DepictedItem>> {
|
fun toDepictions(sparqlResponse: Observable<SparqlResponse>): Observable<List<DepictedItem>> {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package fr.free.nrw.commons.explore.depictions
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import fr.free.nrw.commons.R
|
||||||
|
import kotlinx.android.extensions.LayoutContainer
|
||||||
|
import kotlinx.android.synthetic.main.list_item_load_more.*
|
||||||
|
|
||||||
|
class FooterAdapter(private val onRefreshClicked: () -> Unit) :
|
||||||
|
ListAdapter<FooterItem, FooterViewHolder>(object :
|
||||||
|
DiffUtil.ItemCallback<FooterItem>() {
|
||||||
|
override fun areItemsTheSame(oldItem: FooterItem, newItem: FooterItem) = oldItem == newItem
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: FooterItem, newItem: FooterItem) =
|
||||||
|
oldItem == newItem
|
||||||
|
}) {
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
return getItem(position).ordinal
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
|
when (FooterItem.values()[viewType]) {
|
||||||
|
FooterItem.LoadingItem -> LoadingViewHolder(parent.inflate(R.layout.list_item_progress))
|
||||||
|
FooterItem.RefreshItem -> RefreshViewHolder(
|
||||||
|
parent.inflate(R.layout.list_item_load_more),
|
||||||
|
onRefreshClicked
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: FooterViewHolder, position: Int) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
open class FooterViewHolder(override val containerView: View) :
|
||||||
|
RecyclerView.ViewHolder(containerView),
|
||||||
|
LayoutContainer
|
||||||
|
|
||||||
|
class LoadingViewHolder(containerView: View) : FooterViewHolder(containerView)
|
||||||
|
class RefreshViewHolder(containerView: View, onRefreshClicked: () -> Unit) :
|
||||||
|
FooterViewHolder(containerView) {
|
||||||
|
init {
|
||||||
|
listItemLoadMoreButton.setOnClickListener { onRefreshClicked() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class FooterItem { LoadingItem, RefreshItem }
|
||||||
|
|
||||||
|
fun ViewGroup.inflate(@LayoutRes layoutId: Int, attachToRoot: Boolean = false): View =
|
||||||
|
LayoutInflater.from(context).inflate(layoutId, this, attachToRoot)
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package fr.free.nrw.commons.explore.depictions
|
||||||
|
|
||||||
|
import androidx.paging.PositionalDataSource
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
|
import io.reactivex.Completable
|
||||||
|
import io.reactivex.processors.PublishProcessor
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
|
||||||
|
data class SearchDepictionsDataSource constructor(
|
||||||
|
private val depictsClient: DepictsClient,
|
||||||
|
private val loadingStates: PublishProcessor<LoadingState>,
|
||||||
|
private val query: String
|
||||||
|
) : PositionalDataSource<DepictedItem>() {
|
||||||
|
|
||||||
|
private var lastExecutedRequest: (() -> Boolean)? = null
|
||||||
|
|
||||||
|
override fun loadInitial(
|
||||||
|
params: LoadInitialParams,
|
||||||
|
callback: LoadInitialCallback<DepictedItem>
|
||||||
|
) {
|
||||||
|
storeAndExecute {
|
||||||
|
loadingStates.offer(LoadingState.InitialLoad)
|
||||||
|
performWithTryCatch {
|
||||||
|
callback.onResult(
|
||||||
|
getItems(query, params.requestedLoadSize, params.requestedStartPosition),
|
||||||
|
params.requestedStartPosition
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<DepictedItem>) {
|
||||||
|
storeAndExecute {
|
||||||
|
loadingStates.offer(LoadingState.Loading)
|
||||||
|
performWithTryCatch {
|
||||||
|
callback.onResult(getItems(query, params.loadSize, params.startPosition))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun retryFailedRequest() {
|
||||||
|
Completable.fromAction { lastExecutedRequest?.invoke() }
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getItems(query: String, limit: Int, offset: Int) =
|
||||||
|
depictsClient.searchForDepictions(query, limit, offset).blockingGet()
|
||||||
|
|
||||||
|
private fun storeAndExecute(function: () -> Boolean) {
|
||||||
|
function.also { lastExecutedRequest = it }.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun performWithTryCatch(function: () -> Unit) = try {
|
||||||
|
function.invoke()
|
||||||
|
loadingStates.offer(LoadingState.Complete)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
loadingStates.offer(LoadingState.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package fr.free.nrw.commons.explore.depictions
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.paging.Config
|
||||||
|
import androidx.paging.DataSource
|
||||||
|
import androidx.paging.PagedList
|
||||||
|
import androidx.paging.toLiveData
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
|
import io.reactivex.Flowable
|
||||||
|
import io.reactivex.processors.PublishProcessor
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val PAGE_SIZE = 50
|
||||||
|
private const val INITIAL_LOAD_SIZE = 50
|
||||||
|
|
||||||
|
class SearchableDepictionsDataSourceFactory @Inject constructor(
|
||||||
|
val searchDepictionsDataSourceFactoryFactory: SearchDepictionsDataSourceFactoryFactory,
|
||||||
|
val liveDataConverter: LiveDataConverter
|
||||||
|
) {
|
||||||
|
private val _loadingStates = PublishProcessor.create<LoadingState>()
|
||||||
|
val loadingStates: Flowable<LoadingState> = _loadingStates
|
||||||
|
private val _searchResults = PublishProcessor.create<LiveData<PagedList<DepictedItem>>>()
|
||||||
|
val searchResults: Flowable<LiveData<PagedList<DepictedItem>>> = _searchResults
|
||||||
|
private val _noItemsLoadedEvent = PublishProcessor.create<Unit>()
|
||||||
|
val noItemsLoadedEvent: Flowable<Unit> = _noItemsLoadedEvent
|
||||||
|
|
||||||
|
private var currentFactory: SearchDepictionsDataSourceFactory? = null
|
||||||
|
|
||||||
|
fun onQueryUpdated(query: String) {
|
||||||
|
_searchResults.offer(
|
||||||
|
liveDataConverter.convert(
|
||||||
|
searchDepictionsDataSourceFactoryFactory.create(query, _loadingStates)
|
||||||
|
.also { currentFactory = it }
|
||||||
|
) { _noItemsLoadedEvent.offer(Unit) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun retryFailedRequest() {
|
||||||
|
currentFactory?.retryFailedRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LiveDataConverter @Inject constructor() {
|
||||||
|
fun convert(
|
||||||
|
dataSourceFactory: SearchDepictionsDataSourceFactory,
|
||||||
|
zeroItemsLoadedFunction: () -> Unit
|
||||||
|
): LiveData<PagedList<DepictedItem>> {
|
||||||
|
return dataSourceFactory.toLiveData(
|
||||||
|
Config(
|
||||||
|
pageSize = PAGE_SIZE,
|
||||||
|
initialLoadSizeHint = INITIAL_LOAD_SIZE,
|
||||||
|
enablePlaceholders = false
|
||||||
|
),
|
||||||
|
boundaryCallback = object : PagedList.BoundaryCallback<DepictedItem>() {
|
||||||
|
override fun onZeroItemsLoaded() {
|
||||||
|
zeroItemsLoadedFunction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SearchDepictionsDataSourceFactoryFactory {
|
||||||
|
fun create(query: String, loadingStates: PublishProcessor<LoadingState>)
|
||||||
|
: SearchDepictionsDataSourceFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchDepictionsDataSourceFactory constructor(
|
||||||
|
private val depictsClient: DepictsClient,
|
||||||
|
private val query: String,
|
||||||
|
private val loadingStates: PublishProcessor<LoadingState>
|
||||||
|
) : DataSource.Factory<Int, DepictedItem>() {
|
||||||
|
private var currentDataSource: SearchDepictionsDataSource? = null
|
||||||
|
override fun create() = SearchDepictionsDataSource(depictsClient, loadingStates, query)
|
||||||
|
.also { currentDataSource = it }
|
||||||
|
|
||||||
|
fun retryFailedRequest() {
|
||||||
|
currentDataSource?.retryFailedRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class LoadingState {
|
||||||
|
object InitialLoad : LoadingState()
|
||||||
|
object Loading : LoadingState()
|
||||||
|
object Complete : LoadingState()
|
||||||
|
object Error : LoadingState()
|
||||||
|
}
|
||||||
|
|
@ -32,6 +32,7 @@ public class MediaClient {
|
||||||
|
|
||||||
//OkHttpJsonApiClient used JsonKvStore for this. I don't know why.
|
//OkHttpJsonApiClient used JsonKvStore for this. I don't know why.
|
||||||
private Map<String, Map<String, String>> continuationStore;
|
private Map<String, Map<String, String>> continuationStore;
|
||||||
|
private Map<String, Boolean> continuationExists;
|
||||||
public static final String NO_CAPTION = "No caption";
|
public static final String NO_CAPTION = "No caption";
|
||||||
private static final String NO_DEPICTION = "No depiction";
|
private static final String NO_DEPICTION = "No depiction";
|
||||||
|
|
||||||
|
|
@ -40,6 +41,7 @@ public class MediaClient {
|
||||||
this.mediaInterface = mediaInterface;
|
this.mediaInterface = mediaInterface;
|
||||||
this.mediaDetailInterface = mediaDetailInterface;
|
this.mediaDetailInterface = mediaDetailInterface;
|
||||||
this.continuationStore = new HashMap<>();
|
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
|
* 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.
|
* It uses the generator query API to get the images searched using a query, 10 at a time.
|
||||||
|
|
@ -104,7 +136,12 @@ public class MediaClient {
|
||||||
|| null == mwQueryResponse.query().pages()) {
|
|| null == mwQueryResponse.query().pages()) {
|
||||||
return Observable.empty();
|
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());
|
return Observable.fromIterable(mwQueryResponse.query().pages());
|
||||||
})
|
})
|
||||||
.map(Media::from)
|
.map(Media::from)
|
||||||
|
|
@ -169,7 +206,7 @@ public class MediaClient {
|
||||||
* @return caption for image using wikibaseIdentifier
|
* @return caption for image using wikibaseIdentifier
|
||||||
*/
|
*/
|
||||||
public Single<String> getCaptionByWikibaseIdentifier(String wikibaseIdentifier) {
|
public Single<String> getCaptionByWikibaseIdentifier(String wikibaseIdentifier) {
|
||||||
return mediaDetailInterface.getCaptionForImage(Locale.getDefault().getLanguage(), wikibaseIdentifier)
|
return mediaDetailInterface.getEntityForImage(Locale.getDefault().getLanguage(), wikibaseIdentifier)
|
||||||
.map(mediaDetailResponse -> {
|
.map(mediaDetailResponse -> {
|
||||||
if (isSuccess(mediaDetailResponse)) {
|
if (isSuccess(mediaDetailResponse)) {
|
||||||
for (Entity wikibaseItem : mediaDetailResponse.entities().values()) {
|
for (Entity wikibaseItem : mediaDetailResponse.entities().values()) {
|
||||||
|
|
@ -205,12 +242,16 @@ public class MediaClient {
|
||||||
* @param entityId EntityId (Ex: Q81566) of the depict entity
|
* @param entityId EntityId (Ex: Q81566) of the depict entity
|
||||||
* @return label
|
* @return label
|
||||||
*/
|
*/
|
||||||
public Single<String> getLabelForDepiction(String entityId) {
|
public Single<String> getLabelForDepiction(String entityId, String language) {
|
||||||
return getEntities(entityId)
|
return mediaDetailInterface.getEntity(entityId)
|
||||||
.map(entities -> {
|
.map(entities -> {
|
||||||
if (isSuccess(entities)) {
|
if (isSuccess(entities)) {
|
||||||
for (Entity entity : entities.entities().values()) {
|
for (Entity entity : entities.entities().values()) {
|
||||||
for (Label label : entity.labels().values()) {
|
final Map<String, Label> languageToLabelMap = entity.labels();
|
||||||
|
if (languageToLabelMap.containsKey(language)) {
|
||||||
|
return languageToLabelMap.get(language).value();
|
||||||
|
}
|
||||||
|
for (Label label : languageToLabelMap.values()) {
|
||||||
return label.value();
|
return label.value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -220,6 +261,6 @@ public class MediaClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Single<Entities> getEntities(String entityId) {
|
public Single<Entities> getEntities(String entityId) {
|
||||||
return mediaDetailInterface.getEntity(entityId, Locale.getDefault().getLanguage());
|
return mediaDetailInterface.getEntity(entityId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@ import fr.free.nrw.commons.delete.ReasonBuilder;
|
||||||
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity;
|
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity;
|
||||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
import fr.free.nrw.commons.ui.widget.HtmlTextView;
|
import fr.free.nrw.commons.ui.widget.HtmlTextView;
|
||||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
|
||||||
import fr.free.nrw.commons.utils.ViewUtilWrapper;
|
import fr.free.nrw.commons.utils.ViewUtilWrapper;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,11 @@ public interface MediaDetailInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets labels for Depictions using Entity Id from MediaWikiAPI
|
* Gets labels for Depictions using Entity Id from MediaWikiAPI
|
||||||
|
* @param entityId EntityId (Ex: Q81566) of the depict entity
|
||||||
*
|
*
|
||||||
* @param entityId EntityId (Ex: Q81566) of the depict entity
|
|
||||||
* @param language user's locale
|
|
||||||
*/
|
*/
|
||||||
@GET("/w/api.php?format=json&action=wbgetentities&props=labels&languagefallback=1")
|
@GET("/w/api.php?format=json&action=wbgetentities&props=labels&languagefallback=1")
|
||||||
Single<Entities> getEntity(@Query("ids") String entityId, @Query("languages") String language);
|
Single<Entities> getEntity(@Query("ids") String entityId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches caption using wikibaseIdentifier
|
* Fetches caption using wikibaseIdentifier
|
||||||
|
|
@ -34,5 +33,5 @@ public interface MediaDetailInterface {
|
||||||
* @param wikibaseIdentifier pageId for the media
|
* @param wikibaseIdentifier pageId for the media
|
||||||
*/
|
*/
|
||||||
@GET("/w/api.php?action=wbgetentities&props=labels&format=json&languagefallback=1&sites=commonswiki")
|
@GET("/w/api.php?action=wbgetentities&props=labels&format=json&languagefallback=1&sites=commonswiki")
|
||||||
Observable<Entities> getCaptionForImage(@Query("languages") String language, @Query("ids") String wikibaseIdentifier);
|
Observable<Entities> getEntityForImage(@Query("languages") String language, @Query("ids") String wikibaseIdentifier);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,26 +23,21 @@ import butterknife.ButterKnife;
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.R;
|
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.Bookmark;
|
||||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider;
|
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider;
|
||||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
|
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
|
||||||
import fr.free.nrw.commons.category.CategoryImagesCallback;
|
import fr.free.nrw.commons.category.CategoryImagesCallback;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
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.DownloadUtils;
|
||||||
import fr.free.nrw.commons.utils.ImageUtils;
|
import fr.free.nrw.commons.utils.ImageUtils;
|
||||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment implements ViewPager.OnPageChangeListener {
|
public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment implements ViewPager.OnPageChangeListener {
|
||||||
|
|
||||||
@Inject SessionManager sessionManager;
|
|
||||||
@Inject @Named("default_preferences") JsonKvStore store;
|
|
||||||
@Inject BookmarkPicturesDao bookmarkDao;
|
@Inject BookmarkPicturesDao bookmarkDao;
|
||||||
|
|
||||||
@BindView(R.id.mediaDetailsPager) ViewPager pager;
|
@BindView(R.id.mediaDetailsPager) ViewPager pager;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ public interface MediaInterface {
|
||||||
String MEDIA_PARAMS="&prop=imageinfo&iiprop=url|extmetadata&iiurlwidth=640" +
|
String MEDIA_PARAMS="&prop=imageinfo&iiprop=url|extmetadata&iiurlwidth=640" +
|
||||||
"&iiextmetadatafilter=DateTime|Categories|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal" +
|
"&iiextmetadatafilter=DateTime|Categories|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal" +
|
||||||
"|Artist|LicenseShortName|LicenseUrl";
|
"|Artist|LicenseShortName|LicenseUrl";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a page exists or not.
|
* Checks if a page exists or not.
|
||||||
*
|
*
|
||||||
|
|
@ -47,6 +48,19 @@ public interface MediaInterface {
|
||||||
MEDIA_PARAMS)
|
MEDIA_PARAMS)
|
||||||
Observable<MwQueryResponse> getMediaListFromCategory(@Query("gcmtitle") String category, @Query("gcmlimit") int itemLimit, @QueryMap Map<String, String> continuation);
|
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
|
* This method retrieves a list of Media objects filtered using image generator query
|
||||||
*
|
*
|
||||||
|
|
@ -85,21 +99,15 @@ public interface MediaInterface {
|
||||||
Observable<MwParseResponse> getPageHtml(@Query("page") String title);
|
Observable<MwParseResponse> getPageHtml(@Query("page") String title);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches caption using file name
|
* Fetches list of images from a depiction entity
|
||||||
*
|
*
|
||||||
* @param filename name of the file to be used for fetching captions
|
* @param query depictionEntityId
|
||||||
* */
|
* @param sroffset number od depictions already fetched, this is useful in implementing
|
||||||
@GET("w/api.php?action=wbgetentities&props=labels&format=json&languagefallback=1")
|
* pagination
|
||||||
Observable<MwQueryResponse> fetchCaptionByFilename(@Query("language") String language, @Query("titles") String filename);
|
*/
|
||||||
|
|
||||||
/**
|
@GET("w/api.php?action=query&list=search&format=json&srnamespace=6")
|
||||||
* Fetches list of images from a depiction entity
|
Observable<DepictionResponse> fetchImagesForDepictedItem(@Query("srsearch") String query,
|
||||||
*
|
@Query("sroffset") String sroffset);
|
||||||
* @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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ import android.os.Parcel;
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes;
|
import androidx.annotation.DrawableRes;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
|
@ -17,6 +19,7 @@ import fr.free.nrw.commons.R;
|
||||||
*/
|
*/
|
||||||
public enum Label {
|
public enum Label {
|
||||||
|
|
||||||
|
BOOKMARKS("BOOKMARK", R.drawable.ic_filled_star_24dp),
|
||||||
BUILDING("Q41176", R.drawable.round_icon_generic_building),
|
BUILDING("Q41176", R.drawable.round_icon_generic_building),
|
||||||
HOUSE("Q3947", R.drawable.round_icon_house),
|
HOUSE("Q3947", R.drawable.round_icon_house),
|
||||||
COTTAGE("Q5783996", R.drawable.round_icon_house),
|
COTTAGE("Q5783996", R.drawable.round_icon_house),
|
||||||
|
|
@ -92,4 +95,8 @@ public enum Label {
|
||||||
Label label = TEXT_TO_DESCRIPTION.get(text);
|
Label label = TEXT_TO_DESCRIPTION.get(text);
|
||||||
return label == null ? UNKNOWN : label;
|
return label == null ? UNKNOWN : label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<Label> valuesAsList() {
|
||||||
|
return Arrays.asList(Label.values());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
|
||||||
|
import androidx.annotation.MainThread;
|
||||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
||||||
|
|
||||||
import com.mapbox.mapboxsdk.annotations.IconFactory;
|
import com.mapbox.mapboxsdk.annotations.IconFactory;
|
||||||
|
|
@ -14,6 +15,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
@ -166,36 +168,6 @@ public class NearbyController {
|
||||||
VectorDrawableCompat vectorDrawable = null;
|
VectorDrawableCompat vectorDrawable = null;
|
||||||
VectorDrawableCompat vectorDrawableGreen = null;
|
VectorDrawableCompat vectorDrawableGreen = null;
|
||||||
VectorDrawableCompat vectorDrawableGrey = null;
|
VectorDrawableCompat vectorDrawableGrey = null;
|
||||||
try {
|
|
||||||
vectorDrawable = VectorDrawableCompat.create(
|
|
||||||
context.getResources(), R.drawable.ic_custom_bookmark_marker, context.getTheme()
|
|
||||||
);
|
|
||||||
} catch (Resources.NotFoundException e) {
|
|
||||||
// ignore when running tests.
|
|
||||||
}
|
|
||||||
if (vectorDrawable != null) {
|
|
||||||
Bitmap icon = UiUtils.getBitmap(vectorDrawable);
|
|
||||||
|
|
||||||
for (Place place : bookmarkplacelist) {
|
|
||||||
|
|
||||||
String distance = formatDistanceBetween(curLatLng, place.location);
|
|
||||||
place.setDistance(distance);
|
|
||||||
|
|
||||||
NearbyBaseMarker nearbyBaseMarker = new NearbyBaseMarker();
|
|
||||||
nearbyBaseMarker.title(place.name);
|
|
||||||
nearbyBaseMarker.position(
|
|
||||||
new com.mapbox.mapboxsdk.geometry.LatLng(
|
|
||||||
place.location.getLatitude(),
|
|
||||||
place.location.getLongitude()));
|
|
||||||
nearbyBaseMarker.place(place);
|
|
||||||
nearbyBaseMarker.icon(IconFactory.getInstance(context)
|
|
||||||
.fromBitmap(icon));
|
|
||||||
placeList.remove(place);
|
|
||||||
|
|
||||||
baseMarkerOptions.add(nearbyBaseMarker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vectorDrawable = null;
|
vectorDrawable = null;
|
||||||
try {
|
try {
|
||||||
vectorDrawable = VectorDrawableCompat.create(
|
vectorDrawable = VectorDrawableCompat.create(
|
||||||
|
|
@ -255,4 +227,19 @@ public class NearbyController {
|
||||||
public LatLng curLatLng; // Current location when this places are populated
|
public LatLng curLatLng; // Current location when this places are populated
|
||||||
public LatLng searchLatLng; // Search location for finding this places
|
public LatLng searchLatLng; // Search location for finding this places
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates makerLabelList item isBookmarked value
|
||||||
|
* @param place place which is bookmarked
|
||||||
|
* @param isBookmarked true is bookmarked, false if bookmark removed
|
||||||
|
*/
|
||||||
|
@MainThread
|
||||||
|
public static void updateMarkerLabelListBookmark(Place place, boolean isBookmarked) {
|
||||||
|
for (ListIterator<MarkerPlaceGroup> iter = markerLabelList.listIterator(); iter.hasNext();) {
|
||||||
|
MarkerPlaceGroup markerPlaceGroup = iter.next();
|
||||||
|
if (markerPlaceGroup.getPlace().getWikiDataEntityId().equals(place.getWikiDataEntityId())) {
|
||||||
|
iter.set(new MarkerPlaceGroup(markerPlaceGroup.getMarker(), isBookmarked, place));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import java.util.ArrayList;
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter;
|
import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
public class NearbyFilterSearchRecyclerViewAdapter
|
public class NearbyFilterSearchRecyclerViewAdapter
|
||||||
extends RecyclerView.Adapter<NearbyFilterSearchRecyclerViewAdapter.RecyclerViewHolder>
|
extends RecyclerView.Adapter<NearbyFilterSearchRecyclerViewAdapter.RecyclerViewHolder>
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,9 @@ fun placeAdapterDelegate(
|
||||||
}
|
}
|
||||||
cameraButton.setOnClickListener { onCameraClicked(item) }
|
cameraButton.setOnClickListener { onCameraClicked(item) }
|
||||||
galleryButton.setOnClickListener { onGalleryClicked(item) }
|
galleryButton.setOnClickListener { onGalleryClicked(item) }
|
||||||
bookmarkRowButton.setOnClickListener {
|
bookmarkButtonImage.setOnClickListener {
|
||||||
val isBookmarked = bookmarkLocationDao.updateBookmarkLocation(item)
|
val isBookmarked = bookmarkLocationDao.updateBookmarkLocation(item)
|
||||||
bookmarkRowButtonImage.setImageResource(if (isBookmarked) R.drawable.ic_round_star_filled_24px else R.drawable.ic_round_star_border_24px)
|
bookmarkButtonImage.setImageResource(if (isBookmarked) R.drawable.ic_round_star_filled_24px else R.drawable.ic_round_star_border_24px)
|
||||||
onBookmarkClicked(item, isBookmarked)
|
onBookmarkClicked(item, isBookmarked)
|
||||||
}
|
}
|
||||||
iconOverflow.setOnClickListener { onOverflowIconClicked(item, it) }
|
iconOverflow.setOnClickListener { onOverflowIconClicked(item, it) }
|
||||||
|
|
@ -59,7 +59,7 @@ fun placeAdapterDelegate(
|
||||||
if (item.hasCommonsLink() || item.hasWikidataLink()) VISIBLE
|
if (item.hasCommonsLink() || item.hasWikidataLink()) VISIBLE
|
||||||
else GONE
|
else GONE
|
||||||
|
|
||||||
bookmarkRowButtonImage.setImageResource(
|
bookmarkButtonImage.setImageResource(
|
||||||
if (bookmarkLocationDao.findBookmarkLocation(item))
|
if (bookmarkLocationDao.findBookmarkLocation(item))
|
||||||
R.drawable.ic_round_star_filled_24px
|
R.drawable.ic_round_star_filled_24px
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import android.content.IntentFilter;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.drawable.VectorDrawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
@ -33,6 +34,7 @@ import android.widget.RelativeLayout;
|
||||||
import android.widget.SearchView;
|
import android.widget.SearchView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import androidx.annotation.DrawableRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
@ -427,7 +429,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
||||||
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
|
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
|
||||||
recyclerView.setLayoutManager(linearLayoutManager);
|
recyclerView.setLayoutManager(linearLayoutManager);
|
||||||
|
|
||||||
nearbyFilterSearchRecyclerViewAdapter = new NearbyFilterSearchRecyclerViewAdapter(getContext(),new ArrayList<>(TEXT_TO_DESCRIPTION.values()), recyclerView);
|
nearbyFilterSearchRecyclerViewAdapter = new NearbyFilterSearchRecyclerViewAdapter(getContext(), new ArrayList<>(Label.valuesAsList()), recyclerView);
|
||||||
nearbyFilterSearchRecyclerViewAdapter.setCallback(new NearbyFilterSearchRecyclerViewAdapter.Callback() {
|
nearbyFilterSearchRecyclerViewAdapter.setCallback(new NearbyFilterSearchRecyclerViewAdapter.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void setCheckboxUnknown() {
|
public void setCheckboxUnknown() {
|
||||||
|
|
@ -1128,7 +1130,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
||||||
// When label filter is engaged
|
// When label filter is engaged
|
||||||
// then compare it against place's label
|
// then compare it against place's label
|
||||||
if (selectedLabels != null && (selectedLabels.size() != 0 || !filterForPlaceState)
|
if (selectedLabels != null && (selectedLabels.size() != 0 || !filterForPlaceState)
|
||||||
&& !selectedLabels.contains(place.getLabel())) {
|
&& (!selectedLabels.contains(place.getLabel())
|
||||||
|
&& !(selectedLabels.contains(Label.BOOKMARKS) && markerPlaceGroup.getIsBookmarked()))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1168,25 +1171,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
||||||
public void updateMarker(final boolean isBookmarked, final Place place, @Nullable final fr.free.nrw.commons.location.LatLng curLatLng) {
|
public void updateMarker(final boolean isBookmarked, final Place place, @Nullable final fr.free.nrw.commons.location.LatLng curLatLng) {
|
||||||
addPlaceToNearbyList(place);
|
addPlaceToNearbyList(place);
|
||||||
|
|
||||||
final VectorDrawableCompat vectorDrawable;
|
VectorDrawableCompat vectorDrawable = VectorDrawableCompat.create(
|
||||||
if (isBookmarked) {
|
getContext().getResources(), getIconFor(place, isBookmarked), getContext().getTheme());
|
||||||
vectorDrawable = VectorDrawableCompat.create(
|
|
||||||
getContext().getResources(), R.drawable.ic_custom_bookmark_marker, getContext().getTheme()
|
for (Marker marker : mapBox.getMarkers()) {
|
||||||
);
|
|
||||||
} else if (!place.pic.trim().isEmpty()) {
|
|
||||||
vectorDrawable = VectorDrawableCompat.create( // Means place has picture
|
|
||||||
getContext().getResources(), R.drawable.ic_custom_map_marker_green, getContext().getTheme()
|
|
||||||
);
|
|
||||||
} else if (!place.destroyed.trim().isEmpty()) { // Means place is destroyed
|
|
||||||
vectorDrawable = VectorDrawableCompat.create( // Means place has picture
|
|
||||||
getContext().getResources(), R.drawable.ic_custom_map_marker_grey, getContext().getTheme()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
vectorDrawable = VectorDrawableCompat.create(
|
|
||||||
getContext().getResources(), R.drawable.ic_custom_map_marker, getContext().getTheme()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (final Marker marker : mapBox.getMarkers()) {
|
|
||||||
if (marker.getTitle() != null && marker.getTitle().equals(place.getName())) {
|
if (marker.getTitle() != null && marker.getTitle().equals(place.getName())) {
|
||||||
|
|
||||||
final Bitmap icon = UiUtils.getBitmap(vectorDrawable);
|
final Bitmap icon = UiUtils.getBitmap(vectorDrawable);
|
||||||
|
|
@ -1209,6 +1197,22 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @DrawableRes int getIconFor(Place place, Boolean isBookmarked) {
|
||||||
|
if (!place.pic.trim().isEmpty()) {
|
||||||
|
return (isBookmarked ?
|
||||||
|
R.drawable.ic_custom_map_marker_green_bookmarked :
|
||||||
|
R.drawable.ic_custom_map_marker_green);
|
||||||
|
} else if (!place.destroyed.trim().isEmpty()) { // Means place is destroyed
|
||||||
|
return (isBookmarked ?
|
||||||
|
R.drawable.ic_custom_map_marker_grey_bookmarked :
|
||||||
|
R.drawable.ic_custom_map_marker_grey);
|
||||||
|
} else {
|
||||||
|
return (isBookmarked ?
|
||||||
|
R.drawable.ic_custom_map_marker_blue_bookmarked :
|
||||||
|
R.drawable.ic_custom_map_marker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all markers except current location marker, an icon has been used
|
* Removes all markers except current location marker, an icon has been used
|
||||||
* but it is transparent more than grey(as the name of the icon might suggest)
|
* but it is transparent more than grey(as the name of the icon might suggest)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package fr.free.nrw.commons.nearby.presenter;
|
package fr.free.nrw.commons.nearby.presenter;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.MainThread;
|
||||||
import com.mapbox.mapboxsdk.annotations.Marker;
|
import com.mapbox.mapboxsdk.annotations.Marker;
|
||||||
|
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
|
|
@ -301,6 +303,7 @@ public class NearbyParentFragmentPresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@MainThread
|
||||||
public void updateMapMarkersToController(List<NearbyBaseMarker> nearbyBaseMarkers) {
|
public void updateMapMarkersToController(List<NearbyBaseMarker> nearbyBaseMarkers) {
|
||||||
NearbyController.markerExistsMap = new HashMap<>();
|
NearbyController.markerExistsMap = new HashMap<>();
|
||||||
NearbyController.markerNeedPicMap = new HashMap<>();
|
NearbyController.markerNeedPicMap = new HashMap<>();
|
||||||
|
|
@ -308,7 +311,7 @@ public class NearbyParentFragmentPresenter
|
||||||
for (int i = 0; i < nearbyBaseMarkers.size(); i++) {
|
for (int i = 0; i < nearbyBaseMarkers.size(); i++) {
|
||||||
NearbyBaseMarker nearbyBaseMarker = nearbyBaseMarkers.get(i);
|
NearbyBaseMarker nearbyBaseMarker = nearbyBaseMarkers.get(i);
|
||||||
NearbyController.markerLabelList.add(
|
NearbyController.markerLabelList.add(
|
||||||
new MarkerPlaceGroup(nearbyBaseMarkers.get(i).getMarker(), bookmarkLocationDao.findBookmarkLocation(nearbyBaseMarkers.get(i).getPlace()), nearbyBaseMarker.getPlace()));
|
new MarkerPlaceGroup(nearbyBaseMarker.getMarker(), bookmarkLocationDao.findBookmarkLocation(nearbyBaseMarker.getPlace()), nearbyBaseMarker.getPlace()));
|
||||||
//TODO: fix bookmark location
|
//TODO: fix bookmark location
|
||||||
NearbyController.markerExistsMap.put((nearbyBaseMarkers.get(i).getPlace().hasWikidataLink()), nearbyBaseMarkers.get(i).getMarker());
|
NearbyController.markerExistsMap.put((nearbyBaseMarkers.get(i).getPlace().hasWikidataLink()), nearbyBaseMarkers.get(i).getMarker());
|
||||||
NearbyController.markerNeedPicMap.put(((nearbyBaseMarkers.get(i).getPlace().pic == null) ? true : false), nearbyBaseMarkers.get(i).getMarker());
|
NearbyController.markerNeedPicMap.put(((nearbyBaseMarkers.get(i).getPlace().pic == null) ? true : false), nearbyBaseMarkers.get(i).getMarker());
|
||||||
|
|
|
||||||
|
|
@ -108,10 +108,12 @@ public class UploadRepository {
|
||||||
*
|
*
|
||||||
* @param query
|
* @param query
|
||||||
* @param imageTitleList
|
* @param imageTitleList
|
||||||
|
* @param selectedDepictions
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Observable<List<CategoryItem>> searchAll(String query, List<String> imageTitleList) {
|
public Observable<List<CategoryItem>> searchAll(String query, List<String> imageTitleList,
|
||||||
return categoriesModel.searchAll(query, imageTitleList);
|
List<DepictedItem> selectedDepictions) {
|
||||||
|
return categoriesModel.searchAll(query, imageTitleList, selectedDepictions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ public class Prefs {
|
||||||
public static String TRACKING_ENABLED = "eventLogging";
|
public static String TRACKING_ENABLED = "eventLogging";
|
||||||
public static final String DEFAULT_LICENSE = "defaultLicense";
|
public static final String DEFAULT_LICENSE = "defaultLicense";
|
||||||
public static final String UPLOADS_SHOWING = "uploadsshowing";
|
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 MANAGED_EXIF_TAGS = "managed_exif_tags";
|
||||||
public static final String KEY_LANGUAGE_VALUE = "languageDescription";
|
public static final String KEY_LANGUAGE_VALUE = "languageDescription";
|
||||||
public static final String KEY_THEME_VALUE = "appThemePref";
|
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");
|
langListPreference = findPreference("descriptionDefaultLanguagePref");
|
||||||
prepareLanguages();
|
prepareLanguages();
|
||||||
Preference betaTesterPreference = findPreference("becomeBetaTester");
|
Preference betaTesterPreference = findPreference("becomeBetaTester");
|
||||||
|
|
@ -121,7 +84,6 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||||
findPreference("displayNearbyCardView").setEnabled(false);
|
findPreference("displayNearbyCardView").setEnabled(false);
|
||||||
findPreference("displayLocationPermissionForCardView").setEnabled(false);
|
findPreference("displayLocationPermissionForCardView").setEnabled(false);
|
||||||
findPreference("displayCampaignsCardView").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.media.MediaClient;
|
||||||
import fr.free.nrw.commons.utils.CommonsDateUtil;
|
import fr.free.nrw.commons.utils.CommonsDateUtil;
|
||||||
import fr.free.nrw.commons.wikidata.WikidataEditService;
|
import fr.free.nrw.commons.wikidata.WikidataEditService;
|
||||||
|
import io.reactivex.Completable;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.Scheduler;
|
import io.reactivex.Scheduler;
|
||||||
|
import io.reactivex.Single;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
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.processors.PublishProcessor;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
@ -33,6 +38,7 @@ import java.io.IOException;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
@ -106,10 +112,10 @@ public class UploadService extends CommonsDaggerService {
|
||||||
notificationManager.notify(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build());
|
notificationManager.notify(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build());
|
||||||
|
|
||||||
contribution.setTransferred(transferred);
|
contribution.setTransferred(transferred);
|
||||||
compositeDisposable.add(contributionDao.
|
|
||||||
save(contribution).subscribeOn(ioThreadScheduler)
|
compositeDisposable.add(contributionDao.update(contribution)
|
||||||
.observeOn(mainThreadScheduler)
|
.subscribeOn(ioThreadScheduler)
|
||||||
.subscribe());
|
.subscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -156,14 +162,11 @@ public class UploadService extends CommonsDaggerService {
|
||||||
Timber.d("%d uploads left", toUpload);
|
Timber.d("%d uploads left", toUpload);
|
||||||
notificationManager.notify(contribution.getLocalUri().toString(), NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build());
|
notificationManager.notify(contribution.getLocalUri().toString(), NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
compositeDisposable.add(contributionDao
|
compositeDisposable.add(contributionDao
|
||||||
.save(contribution)
|
.save(contribution)
|
||||||
.subscribeOn(ioThreadScheduler)
|
.subscribeOn(ioThreadScheduler)
|
||||||
.observeOn(mainThreadScheduler)
|
.subscribe(() -> uploadContribution(contribution)));
|
||||||
.subscribe(aLong->{
|
|
||||||
contribution.set_id(aLong);
|
|
||||||
uploadContribution(contribution);
|
|
||||||
}, Throwable::printStackTrace));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean freshStart = true;
|
private boolean freshStart = true;
|
||||||
|
|
@ -269,7 +272,7 @@ public class UploadService extends CommonsDaggerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onUpload(Contribution contribution, String notificationTag,
|
private void onUpload(Contribution contribution, String notificationTag,
|
||||||
UploadResult uploadResult) throws ParseException {
|
UploadResult uploadResult) {
|
||||||
Timber.d("Stash upload response 2 is %s", uploadResult.toString());
|
Timber.d("Stash upload response 2 is %s", uploadResult.toString());
|
||||||
|
|
||||||
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
|
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
|
||||||
|
|
@ -282,8 +285,7 @@ public class UploadService extends CommonsDaggerService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSuccessfulUpload(Contribution contribution, UploadResult uploadResult)
|
private void onSuccessfulUpload(Contribution contribution, UploadResult uploadResult) {
|
||||||
throws ParseException {
|
|
||||||
compositeDisposable
|
compositeDisposable
|
||||||
.add(wikidataEditService.addDepictionsAndCaptions(uploadResult, contribution));
|
.add(wikidataEditService.addDepictionsAndCaptions(uploadResult, contribution));
|
||||||
WikidataPlace wikidataPlace = contribution.getWikidataPlace();
|
WikidataPlace wikidataPlace = contribution.getWikidataPlace();
|
||||||
|
|
@ -293,17 +295,11 @@ public class UploadService extends CommonsDaggerService {
|
||||||
saveCompletedContribution(contribution, uploadResult);
|
saveCompletedContribution(contribution, uploadResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveCompletedContribution(Contribution contribution, UploadResult uploadResult) throws ParseException {
|
private void saveCompletedContribution(Contribution contribution, UploadResult uploadResult) {
|
||||||
contribution.setFilename(uploadResult.createCanonicalFileName());
|
compositeDisposable.add(mediaClient.getMedia("File:" + uploadResult.getFilename())
|
||||||
contribution.setImageUrl(uploadResult.getImageinfo().getOriginalUrl());
|
.map(media -> new Contribution(media, Contribution.STATE_COMPLETED))
|
||||||
contribution.setState(Contribution.STATE_COMPLETED);
|
.flatMapCompletable(newContribution -> contributionDao.saveAndDelete(contribution, newContribution))
|
||||||
contribution.setDateUploaded(CommonsDateUtil.getIso8601DateFormatTimestamp()
|
.subscribe());
|
||||||
.parse(uploadResult.getImageinfo().getTimestamp()));
|
|
||||||
compositeDisposable.add(contributionDao
|
|
||||||
.save(contribution)
|
|
||||||
.subscribeOn(ioThreadScheduler)
|
|
||||||
.observeOn(mainThreadScheduler)
|
|
||||||
.subscribe());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("StringFormatInvalid")
|
@SuppressLint("StringFormatInvalid")
|
||||||
|
|
@ -317,10 +313,11 @@ public class UploadService extends CommonsDaggerService {
|
||||||
notificationManager.notify(contribution.getLocalUri().toString(), NOTIFICATION_UPLOAD_FAILED, curNotification.build());
|
notificationManager.notify(contribution.getLocalUri().toString(), NOTIFICATION_UPLOAD_FAILED, curNotification.build());
|
||||||
|
|
||||||
contribution.setState(Contribution.STATE_FAILED);
|
contribution.setState(Contribution.STATE_FAILED);
|
||||||
compositeDisposable.add(contributionDao.save(contribution)
|
|
||||||
.subscribeOn(ioThreadScheduler)
|
compositeDisposable.add(contributionDao
|
||||||
.observeOn(mainThreadScheduler)
|
.update(contribution)
|
||||||
.subscribe());
|
.subscribeOn(ioThreadScheduler)
|
||||||
|
.subscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String findUniqueFilename(String fileName) throws IOException {
|
private String findUniqueFilename(String fileName) throws IOException {
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ class CategoriesPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun searchResults(term: String) =
|
private fun searchResults(term: String) =
|
||||||
repository.searchAll(term, getImageTitleList())
|
repository.searchAll(term, getImageTitleList(), repository.selectedDepictions)
|
||||||
.subscribeOn(ioScheduler)
|
.subscribeOn(ioScheduler)
|
||||||
.map { it.filterNot { categoryItem -> repository.containsYear(categoryItem.name) } }
|
.map { it.filterNot { categoryItem -> repository.containsYear(categoryItem.name) } }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,5 +24,5 @@ public interface DepictsInterface {
|
||||||
Single<DepictSearchResponse> searchForDepicts(@Query("search") String query, @Query("limit") String limit, @Query("language") String language, @Query("uselang") String uselang, @Query("continue") String offset);
|
Single<DepictSearchResponse> searchForDepicts(@Query("search") String query, @Query("limit") String limit, @Query("language") String language, @Query("uselang") String uselang, @Query("continue") String offset);
|
||||||
|
|
||||||
@GET("/w/api.php?format=json&action=wbgetentities")
|
@GET("/w/api.php?format=json&action=wbgetentities")
|
||||||
Single<Entities> getEntities(@Query("ids")String ids, @Query("languages")String language);
|
Single<Entities> getEntities(@Query("ids") String ids);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,6 @@ class DepictsPresenter @Inject constructor(
|
||||||
view.noDepictionSelected()
|
view.noDepictionSelected()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,11 @@ import fr.free.nrw.commons.explore.depictions.THUMB_IMAGE_SIZE
|
||||||
import fr.free.nrw.commons.nearby.Place
|
import fr.free.nrw.commons.nearby.Place
|
||||||
import fr.free.nrw.commons.upload.WikidataItem
|
import fr.free.nrw.commons.upload.WikidataItem
|
||||||
import fr.free.nrw.commons.wikidata.WikidataProperties
|
import fr.free.nrw.commons.wikidata.WikidataProperties
|
||||||
|
import fr.free.nrw.commons.wikidata.WikidataProperties.*
|
||||||
import org.wikipedia.wikidata.DataValue
|
import org.wikipedia.wikidata.DataValue
|
||||||
import org.wikipedia.wikidata.Entities
|
import org.wikipedia.wikidata.Entities
|
||||||
import org.wikipedia.wikidata.Statement_partial
|
import org.wikipedia.wikidata.Statement_partial
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model class for Depicted Item in Upload and Explore
|
* Model class for Depicted Item in Upload and Explore
|
||||||
|
|
@ -17,14 +19,15 @@ data class DepictedItem constructor(
|
||||||
val description: String?,
|
val description: String?,
|
||||||
val imageUrl: String?,
|
val imageUrl: String?,
|
||||||
val instanceOfs: List<String>,
|
val instanceOfs: List<String>,
|
||||||
|
val commonsCategories: List<String>,
|
||||||
var isSelected: Boolean,
|
var isSelected: Boolean,
|
||||||
override val id: String
|
override val id: String
|
||||||
) : WikidataItem {
|
) : WikidataItem {
|
||||||
|
|
||||||
constructor(entity: Entities.Entity) : this(
|
constructor(entity: Entities.Entity) : this(
|
||||||
entity,
|
entity,
|
||||||
entity.labels().values.firstOrNull()?.value() ?: "",
|
entity.labels().byLanguageOrFirstOrEmpty(),
|
||||||
entity.descriptions().values.firstOrNull()?.value() ?: ""
|
entity.descriptions().byLanguageOrFirstOrEmpty()
|
||||||
)
|
)
|
||||||
|
|
||||||
constructor(entity: Entities.Entity, place: Place) : this(
|
constructor(entity: Entities.Entity, place: Place) : this(
|
||||||
|
|
@ -36,10 +39,12 @@ data class DepictedItem constructor(
|
||||||
constructor(entity: Entities.Entity, name: String, description: String) : this(
|
constructor(entity: Entities.Entity, name: String, description: String) : this(
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
entity[WikidataProperties.IMAGE].primaryImageValue?.let {
|
entity[IMAGE].primaryImageValue?.let {
|
||||||
getImageUrl(it.value, THUMB_IMAGE_SIZE)
|
getImageUrl(it.value, THUMB_IMAGE_SIZE)
|
||||||
},
|
},
|
||||||
entity[WikidataProperties.INSTANCE_OF].toIds(),
|
entity[INSTANCE_OF].toIds(),
|
||||||
|
entity[COMMONS_CATEGORY]?.map { (it.mainSnak.dataValue as DataValue.ValueString).value }
|
||||||
|
?: emptyList(),
|
||||||
false,
|
false,
|
||||||
entity.id()
|
entity.id()
|
||||||
)
|
)
|
||||||
|
|
@ -57,7 +62,7 @@ data class DepictedItem constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<Statement_partial>?.toIds(): List<String> {
|
private fun List<Statement_partial>?.toIds(): List<String> {
|
||||||
return this?.map { it.mainSnak.dataValue }
|
return this?.map { it.mainSnak.dataValue }
|
||||||
?.filterIsInstance<DataValue.EntityId>()
|
?.filterIsInstance<DataValue.EntityId>()
|
||||||
?.map { it.value.id }
|
?.map { it.value.id }
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
|
|
@ -69,3 +74,5 @@ private val List<Statement_partial>?.primaryImageValue: DataValue.ValueString?
|
||||||
operator fun Entities.Entity.get(property: WikidataProperties) =
|
operator fun Entities.Entity.get(property: WikidataProperties) =
|
||||||
statements?.get(property.propertyName)
|
statements?.get(property.propertyName)
|
||||||
|
|
||||||
|
private fun Map<String, Entities.Label>.byLanguageOrFirstOrEmpty() =
|
||||||
|
let { it[Locale.getDefault().language] ?: it.values.firstOrNull() }?.value() ?: ""
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,16 @@ public class CommonsDateUtil {
|
||||||
return simpleDateFormat;
|
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
|
* Gets the timestamp pattern for a date
|
||||||
* @return timestamp
|
* @return timestamp
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package fr.free.nrw.commons.wikidata
|
||||||
import fr.free.nrw.commons.BuildConfig
|
import fr.free.nrw.commons.BuildConfig
|
||||||
|
|
||||||
enum class WikidataProperties(val propertyName: String) {
|
enum class WikidataProperties(val propertyName: String) {
|
||||||
IMAGE("P18"), DEPICTS(BuildConfig.DEPICTS_PROPERTY), INSTANCE_OF("P31");
|
IMAGE("P18"),
|
||||||
|
DEPICTS(BuildConfig.DEPICTS_PROPERTY),
|
||||||
|
COMMONS_CATEGORY("P373"),
|
||||||
|
INSTANCE_OF("P31");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="28dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="28">
|
||||||
|
<path
|
||||||
|
android:pathData="M6.072,22.223a6.031,3.672 0,1 0,12.062 0a6.031,3.672 0,1 0,-12.062 0z"
|
||||||
|
android:strokeAlpha="0.1"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.1"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M11.575,11.62C10.689,11.462 9.902,10.759 9.625,9.878 9.553,9.65 9.535,9.499 9.538,9.14c0.004,-0.397 0.019,-0.492 0.13,-0.787 0.236,-0.631 0.646,-1.099 1.212,-1.382 0.386,-0.193 0.709,-0.272 1.116,-0.272 0.676,0 1.263,0.247 1.744,0.734 0.355,0.359 0.541,0.682 0.657,1.136 0.327,1.278 -0.442,2.611 -1.723,2.987 -0.282,0.083 -0.817,0.114 -1.099,0.063z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#00ff00"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M11.617,21.707C10.518,20.424 9.338,18.864 8.395,17.449 6.524,14.641 5.455,12.305 5.102,10.255 5.014,9.744 5.006,8.628 5.088,8.137 5.348,6.561 6.043,5.221 7.158,4.148 9.148,2.231 12.016,1.668 14.593,2.688c2.043,0.809 3.607,2.581 4.162,4.719 0.174,0.67 0.204,0.933 0.203,1.761 -0.001,0.81 -0.035,1.098 -0.22,1.857 -0.614,2.524 -2.571,5.977 -5.383,9.501 -0.645,0.809 -1.321,1.61 -1.358,1.61 -0.008,0 -0.179,-0.193 -0.381,-0.428zM12.617,11.603c0.783,-0.188 1.457,-0.795 1.738,-1.564 0.516,-1.415 -0.317,-2.962 -1.783,-3.312 -0.216,-0.052 -0.317,-0.059 -0.661,-0.047 -0.354,0.012 -0.441,0.025 -0.682,0.104 -0.673,0.221 -1.205,0.695 -1.506,1.344 -0.176,0.38 -0.218,0.584 -0.217,1.054 0.001,0.324 0.014,0.452 0.064,0.635 0.266,0.97 1.077,1.689 2.079,1.844 0.243,0.038 0.68,0.012 0.968,-0.057z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#006699"
|
||||||
|
android:strokeColor="#003b59"
|
||||||
|
android:fillAlpha="1"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M17.9025,7.0798 L14.1612,6.7552 12.7003,3.3154c-0.2628,-0.6261 -1.1595,-0.6261 -1.4223,0L9.817,6.7629 6.0835,7.0798C5.4032,7.134 5.125,7.9842 5.6429,8.4326l2.8369,2.4581 -0.8503,3.6485c-0.1546,0.6648 0.5643,1.1904 1.1518,0.8348l3.2079,-1.9325 3.2079,1.9402c0.5875,0.3556 1.3064,-0.1701 1.1518,-0.8348L15.4985,10.8907 18.3354,8.4326C18.8533,7.9842 18.5827,7.134 17.9025,7.0798Z"
|
||||||
|
android:strokeAlpha="1"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#f84d4d"
|
||||||
|
android:fillAlpha="1"
|
||||||
|
android:strokeColor="#003b59"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="28dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="28">
|
||||||
|
<path
|
||||||
|
android:pathData="M6.072,22.223a6.031,3.672 0,1 0,12.062 0a6.031,3.672 0,1 0,-12.062 0z"
|
||||||
|
android:strokeAlpha="0.1"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.1"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M11.575,11.62C10.689,11.462 9.902,10.759 9.625,9.878 9.553,9.65 9.535,9.499 9.538,9.14c0.004,-0.397 0.019,-0.492 0.13,-0.787 0.236,-0.631 0.646,-1.099 1.212,-1.382 0.386,-0.193 0.709,-0.272 1.116,-0.272 0.676,0 1.263,0.247 1.744,0.734 0.355,0.359 0.541,0.682 0.657,1.136 0.327,1.278 -0.442,2.611 -1.723,2.987 -0.282,0.083 -0.817,0.114 -1.099,0.063z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#00ff00"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M11.617,21.707C10.518,20.424 9.338,18.864 8.395,17.449 6.524,14.641 5.455,12.305 5.102,10.255 5.014,9.744 5.006,8.628 5.088,8.137 5.348,6.561 6.043,5.221 7.158,4.148 9.148,2.231 12.016,1.668 14.593,2.688c2.043,0.809 3.607,2.581 4.162,4.719 0.174,0.67 0.204,0.933 0.203,1.761 -0.001,0.81 -0.035,1.098 -0.22,1.857 -0.614,2.524 -2.571,5.977 -5.383,9.501 -0.645,0.809 -1.321,1.61 -1.358,1.61 -0.008,0 -0.179,-0.193 -0.381,-0.428zM12.617,11.603c0.783,-0.188 1.457,-0.795 1.738,-1.564 0.516,-1.415 -0.317,-2.962 -1.783,-3.312 -0.216,-0.052 -0.317,-0.059 -0.661,-0.047 -0.354,0.012 -0.441,0.025 -0.682,0.104 -0.673,0.221 -1.205,0.695 -1.506,1.344 -0.176,0.38 -0.218,0.584 -0.217,1.054 0.001,0.324 0.014,0.452 0.064,0.635 0.266,0.97 1.077,1.689 2.079,1.844 0.243,0.038 0.68,0.012 0.968,-0.057z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#4caf50"
|
||||||
|
android:strokeColor="#003b59"
|
||||||
|
android:fillAlpha="1"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M17.9025,7.0798 L14.1612,6.7552 12.7003,3.3154c-0.2628,-0.6261 -1.1595,-0.6261 -1.4223,0L9.817,6.7629 6.0835,7.0798C5.4032,7.134 5.125,7.9842 5.6429,8.4326l2.8369,2.4581 -0.8503,3.6485c-0.1546,0.6648 0.5643,1.1904 1.1518,0.8348l3.2079,-1.9325 3.2079,1.9402c0.5875,0.3556 1.3064,-0.1701 1.1518,-0.8348L15.4985,10.8907 18.3354,8.4326C18.8533,7.9842 18.5827,7.134 17.9025,7.0798Z"
|
||||||
|
android:strokeAlpha="1"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#f84d4d"
|
||||||
|
android:fillAlpha="1"
|
||||||
|
android:strokeColor="#003b59"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="28dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="28">
|
||||||
|
<path
|
||||||
|
android:pathData="M6.072,22.223a6.031,3.672 0,1 0,12.062 0a6.031,3.672 0,1 0,-12.062 0z"
|
||||||
|
android:strokeAlpha="0.1"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.1"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M11.575,11.62C10.689,11.462 9.902,10.759 9.625,9.878 9.553,9.65 9.535,9.499 9.538,9.14c0.004,-0.397 0.019,-0.492 0.13,-0.787 0.236,-0.631 0.646,-1.099 1.212,-1.382 0.386,-0.193 0.709,-0.272 1.116,-0.272 0.676,0 1.263,0.247 1.744,0.734 0.355,0.359 0.541,0.682 0.657,1.136 0.327,1.278 -0.442,2.611 -1.723,2.987 -0.282,0.083 -0.817,0.114 -1.099,0.063z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#00ff00"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M11.617,21.707C10.518,20.424 9.338,18.864 8.395,17.449 6.524,14.641 5.455,12.305 5.102,10.255 5.014,9.744 5.006,8.628 5.088,8.137 5.348,6.561 6.043,5.221 7.158,4.148 9.148,2.231 12.016,1.668 14.593,2.688c2.043,0.809 3.607,2.581 4.162,4.719 0.174,0.67 0.204,0.933 0.203,1.761 -0.001,0.81 -0.035,1.098 -0.22,1.857 -0.614,2.524 -2.571,5.977 -5.383,9.501 -0.645,0.809 -1.321,1.61 -1.358,1.61 -0.008,0 -0.179,-0.193 -0.381,-0.428zM12.617,11.603c0.783,-0.188 1.457,-0.795 1.738,-1.564 0.516,-1.415 -0.317,-2.962 -1.783,-3.312 -0.216,-0.052 -0.317,-0.059 -0.661,-0.047 -0.354,0.012 -0.441,0.025 -0.682,0.104 -0.673,0.221 -1.205,0.695 -1.506,1.344 -0.176,0.38 -0.218,0.584 -0.217,1.054 0.001,0.324 0.014,0.452 0.064,0.635 0.266,0.97 1.077,1.689 2.079,1.844 0.243,0.038 0.68,0.012 0.968,-0.057z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#454547"
|
||||||
|
android:strokeColor="#003b59"
|
||||||
|
android:fillAlpha="1"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M17.9025,7.0798 L14.1612,6.7552 12.7003,3.3154c-0.2628,-0.6261 -1.1595,-0.6261 -1.4223,0L9.817,6.7629 6.0835,7.0798C5.4032,7.134 5.125,7.9842 5.6429,8.4326l2.8369,2.4581 -0.8503,3.6485c-0.1546,0.6648 0.5643,1.1904 1.1518,0.8348l3.2079,-1.9325 3.2079,1.9402c0.5875,0.3556 1.3064,-0.1701 1.1518,-0.8348L15.4985,10.8907 18.3354,8.4326C18.8533,7.9842 18.5827,7.134 17.9025,7.0798Z"
|
||||||
|
android:strokeAlpha="1"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#f84d4d"
|
||||||
|
android:fillAlpha="1"
|
||||||
|
android:strokeColor="#003b59"/>
|
||||||
|
</vector>
|
||||||
29
app/src/main/res/layout/fragment_search_depictions.xml
Normal file
29
app/src/main/res/layout/fragment_search_depictions.xml
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingTop="@dimen/tiny_gap"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/depictionNotFound"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/depictionsSearchResultsList"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scrollbarSize="@dimen/standard_gap"
|
||||||
|
android:scrollbars="vertical" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/depictionSearchInitialLoadProgress"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
@ -6,13 +6,21 @@
|
||||||
android:focusableInTouchMode="true"
|
android:focusableInTouchMode="true"
|
||||||
android:minHeight="@dimen/large_height">
|
android:minHeight="@dimen/large_height">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/bookmarkButtonImage"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/standard_gap"
|
||||||
|
android:tint="?attr/rowButtonColor"
|
||||||
|
app:srcCompat="@drawable/ic_round_star_border_24px" />
|
||||||
|
|
||||||
<com.facebook.drawee.view.SimpleDraweeView
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
android:id="@+id/icon"
|
android:id="@+id/icon"
|
||||||
android:layout_width="@dimen/dimen_40"
|
android:layout_width="@dimen/dimen_40"
|
||||||
android:layout_height="@dimen/dimen_40"
|
android:layout_height="@dimen/dimen_40"
|
||||||
android:layout_marginLeft="@dimen/standard_gap"
|
|
||||||
android:layout_marginStart="@dimen/standard_gap"
|
android:layout_marginStart="@dimen/standard_gap"
|
||||||
android:layout_marginTop="@dimen/standard_gap"
|
android:layout_marginTop="@dimen/standard_gap"
|
||||||
|
android:layout_alignStart="@id/bookmarkButtonImage"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
tools:src="@drawable/empty_photo"
|
tools:src="@drawable/empty_photo"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,14 @@
|
||||||
<LinearLayout
|
<LinearLayout android:layout_width="match_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/buttonLayout"
|
android:id="@+id/buttonLayout"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:layout_marginTop="@dimen/standard_gap"
|
tools:visibility="visible"
|
||||||
android:layout_below="@+id/icon"
|
android:layout_marginTop="@dimen/standard_gap"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
android:layout_below="@+id/icon"
|
||||||
>
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/bookmarkRowButton"
|
|
||||||
android:layout_width="@dimen/dimen_0"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:padding="@dimen/standard_gap"
|
|
||||||
android:clickable="true"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@drawable/button_background_selector"
|
|
||||||
>
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/bookmarkRowButtonImage"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
app:srcCompat="@drawable/ic_round_star_border_24px"
|
|
||||||
android:tint="?attr/bookmarkButtonColor"/>
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:id="@+id/bookmarkButtonText"
|
|
||||||
android:paddingTop="@dimen/activity_margin_horizontal"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:text="CAMERA"
|
|
||||||
android:visibility="gone"
|
|
||||||
/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/cameraButton"
|
android:id="@+id/cameraButton"
|
||||||
|
|
@ -151,4 +123,4 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
||||||
|
|
@ -103,29 +103,28 @@
|
||||||
android:background="@color/divider_grey" />
|
android:background="@color/divider_grey" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/depicts_next"
|
android:id="@+id/depicts_next"
|
||||||
style="@style/Widget.AppCompat.Button.Borderless"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_marginBottom="24dp"
|
||||||
android:layout_marginEnd="24dp"
|
android:layout_marginEnd="24dp"
|
||||||
android:layout_marginRight="24dp"
|
android:layout_marginRight="24dp"
|
||||||
android:layout_marginBottom="24dp"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_alignParentBottom="true"
|
android:text="@string/next" />
|
||||||
android:text="@string/next" />
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/depicts_previous"
|
android:id="@+id/depicts_previous"
|
||||||
style="@style/Widget.AppCompat.Button.Borderless"
|
style="@style/Widget.AppCompat.Button.Borderless"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="@dimen/standard_gap"
|
android:layout_marginBottom="24dp"
|
||||||
android:layout_marginRight="@dimen/standard_gap"
|
android:layout_marginEnd="@dimen/standard_gap"
|
||||||
android:layout_marginBottom="24dp"
|
android:layout_marginRight="@dimen/standard_gap"
|
||||||
android:layout_toStartOf="@id/depicts_next"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_toLeftOf="@id/depicts_next"
|
android:layout_toLeftOf="@id/depicts_next"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_toStartOf="@id/depicts_next"
|
||||||
android:text="@string/previous" />
|
android:text="@string/previous" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,10 @@
|
||||||
<string name="provider_contributions">بارگذاریهای من</string>
|
<string name="provider_contributions">بارگذاریهای من</string>
|
||||||
<string name="menu_share">به اشتراکگذاشتن</string>
|
<string name="menu_share">به اشتراکگذاشتن</string>
|
||||||
<string name="menu_open_in_browser">مشاهده در مرورگر</string>
|
<string name="menu_open_in_browser">مشاهده در مرورگر</string>
|
||||||
<string name="share_title_hint" fuzzy="true">عنوان (مورد نیاز)</string>
|
<string name="share_title_hint">شرح مختصر (الزامی)</string>
|
||||||
|
<string name="add_caption_toast">لطفاً شرح مختصری برای این پرونده بنویسید</string>
|
||||||
<string name="share_description_hint">توضیحات</string>
|
<string name="share_description_hint">توضیحات</string>
|
||||||
|
<string name="share_caption_hint">شرح مختصر (محدود به ۲۵۵ نویسه)</string>
|
||||||
<string name="login_failed_network">قادر به ورود نیست - شکست شبکهای</string>
|
<string name="login_failed_network">قادر به ورود نیست - شکست شبکهای</string>
|
||||||
<string name="login_failed_wrong_credentials">ورود به سیستم امکان پذیر نیست . لطفا نام کاربری و رمز عبور خود را بررسی کنید.</string>
|
<string name="login_failed_wrong_credentials">ورود به سیستم امکان پذیر نیست . لطفا نام کاربری و رمز عبور خود را بررسی کنید.</string>
|
||||||
<string name="login_failed_throttled">تلاش ناموفق بیش از حد. لطفاً چند دقیقهٔ دیگر دوباره تلاش کنید</string>
|
<string name="login_failed_throttled">تلاش ناموفق بیش از حد. لطفاً چند دقیقهٔ دیگر دوباره تلاش کنید</string>
|
||||||
|
|
@ -175,6 +177,7 @@
|
||||||
<string name="detail_panel_cats_label">ردهها</string>
|
<string name="detail_panel_cats_label">ردهها</string>
|
||||||
<string name="detail_panel_cats_loading">در حال بارگیری…</string>
|
<string name="detail_panel_cats_loading">در حال بارگیری…</string>
|
||||||
<string name="detail_panel_cats_none">هیچکدام انتخابنشده</string>
|
<string name="detail_panel_cats_none">هیچکدام انتخابنشده</string>
|
||||||
|
<string name="detail_caption_empty">فاقد شرح</string>
|
||||||
<string name="detail_description_empty">بدون توضیحات</string>
|
<string name="detail_description_empty">بدون توضیحات</string>
|
||||||
<string name="detail_discussion_empty">بدون بحث</string>
|
<string name="detail_discussion_empty">بدون بحث</string>
|
||||||
<string name="detail_license_empty">مجوز ناشناخته</string>
|
<string name="detail_license_empty">مجوز ناشناخته</string>
|
||||||
|
|
@ -193,6 +196,7 @@
|
||||||
<string name="upload">بارگذاری</string>
|
<string name="upload">بارگذاری</string>
|
||||||
<string name="yes">بله</string>
|
<string name="yes">بله</string>
|
||||||
<string name="no">خیر</string>
|
<string name="no">خیر</string>
|
||||||
|
<string name="media_detail_caption">شرح مختصر</string>
|
||||||
<string name="media_detail_title">عنوان</string>
|
<string name="media_detail_title">عنوان</string>
|
||||||
<string name="media_detail_description">توضیح</string>
|
<string name="media_detail_description">توضیح</string>
|
||||||
<string name="media_detail_discussion">بحث</string>
|
<string name="media_detail_discussion">بحث</string>
|
||||||
|
|
@ -255,6 +259,7 @@
|
||||||
<string name="error_while_cache">خطا در زمان دریافت تصاویر</string>
|
<string name="error_while_cache">خطا در زمان دریافت تصاویر</string>
|
||||||
<string name="title_info">عنوانی توصیفی و یکتا برای پرونده که به عنوان نام پرونده در نظر گرفته خواهد شد. ترجیحاً به زبان ساده باشد، میتوانید فاصله هم به کار ببرید. پسوند پرونده را ننویسید.</string>
|
<string name="title_info">عنوانی توصیفی و یکتا برای پرونده که به عنوان نام پرونده در نظر گرفته خواهد شد. ترجیحاً به زبان ساده باشد، میتوانید فاصله هم به کار ببرید. پسوند پرونده را ننویسید.</string>
|
||||||
<string name="description_info">لطفاً تصویر را تا حد توان شرح دهید. کجا گرفته شدهاست؟ شامل چه چیزی میشود؟ لطفاً اشیا یا افراد را شرح دهید. اطلاعاتی که به راحتی قابل مشاهده هستند را صرفهنظر کنید. اگر چیزی در تصویر غیر طبیعی به نظر میرسد آن را شرح دهید.</string>
|
<string name="description_info">لطفاً تصویر را تا حد توان شرح دهید. کجا گرفته شدهاست؟ شامل چه چیزی میشود؟ لطفاً اشیا یا افراد را شرح دهید. اطلاعاتی که به راحتی قابل مشاهده هستند را صرفهنظر کنید. اگر چیزی در تصویر غیر طبیعی به نظر میرسد آن را شرح دهید.</string>
|
||||||
|
<string name="caption_info">لطفاً شرح مختصری برای این تصویر بنویسید. (محدود به ۲۵۵ نویسه)</string>
|
||||||
<string name="upload_image_too_dark">این تصویر خیلی تار است. آیا مطمئنید که میخواهید آن را بارگذاری کنید؟ ویکیانبار فقط برای نگهداری از تصاویری است که ارزش دانشنامهای داشته باشند.</string>
|
<string name="upload_image_too_dark">این تصویر خیلی تار است. آیا مطمئنید که میخواهید آن را بارگذاری کنید؟ ویکیانبار فقط برای نگهداری از تصاویری است که ارزش دانشنامهای داشته باشند.</string>
|
||||||
<string name="upload_image_blurry">این تصویر خیلی محو است. آیا مطمئنید که میخواهید آن را بارگذاری کنید؟ ویکیانبار فقط برای نگهداری از تصاویری است که ارزش دانشنامهای داشته باشند.</string>
|
<string name="upload_image_blurry">این تصویر خیلی محو است. آیا مطمئنید که میخواهید آن را بارگذاری کنید؟ ویکیانبار فقط برای نگهداری از تصاویری است که ارزش دانشنامهای داشته باشند.</string>
|
||||||
<string name="upload_problem_exist">مشکلات احتمالی با این تصویر :</string>
|
<string name="upload_problem_exist">مشکلات احتمالی با این تصویر :</string>
|
||||||
|
|
@ -385,6 +390,7 @@
|
||||||
<string name="list_sheet">فهرست</string>
|
<string name="list_sheet">فهرست</string>
|
||||||
<string name="storage_permission">اجازه ذخیره</string>
|
<string name="storage_permission">اجازه ذخیره</string>
|
||||||
<string name="step_count">گام %1$d از %2$d</string>
|
<string name="step_count">گام %1$d از %2$d</string>
|
||||||
|
<string name="image_in_set_label">تصویر %1$d در مجموعه</string>
|
||||||
<string name="next">بعدی</string>
|
<string name="next">بعدی</string>
|
||||||
<string name="previous">قبلی</string>
|
<string name="previous">قبلی</string>
|
||||||
<string name="submit">ارسال</string>
|
<string name="submit">ارسال</string>
|
||||||
|
|
@ -473,6 +479,7 @@
|
||||||
<string name="share_via">اشتراک از طریق...</string>
|
<string name="share_via">اشتراک از طریق...</string>
|
||||||
<string name="image_info">اطلاعات عکس</string>
|
<string name="image_info">اطلاعات عکس</string>
|
||||||
<string name="no_categories_found">هیچ ردهای یافت نشد</string>
|
<string name="no_categories_found">هیچ ردهای یافت نشد</string>
|
||||||
|
<string name="upload_cancelled">بارگذاری لغو شد</string>
|
||||||
<string name="delete_helper_show_deletion_title_success">موفق</string>
|
<string name="delete_helper_show_deletion_title_success">موفق</string>
|
||||||
<string name="delete_helper_show_deletion_title_failed">ناموفق</string>
|
<string name="delete_helper_show_deletion_title_failed">ناموفق</string>
|
||||||
<string name="delete_helper_ask_spam_selfie">یک سلفی</string>
|
<string name="delete_helper_ask_spam_selfie">یک سلفی</string>
|
||||||
|
|
|
||||||
|
|
@ -618,4 +618,5 @@
|
||||||
<string name="ask_to_turn_location_on">Activer la localisation ?</string>
|
<string name="ask_to_turn_location_on">Activer la localisation ?</string>
|
||||||
<string name="nearby_needs_location">À proximité nécessite que la localisation soit activée pour bien fonctionner</string>
|
<string name="nearby_needs_location">À proximité nécessite que la localisation soit activée pour bien fonctionner</string>
|
||||||
<string name="use_location_from_similar_image">Avez-vous pris ces deux photos du même endroit ? Voulez-vous utiliser les latitude/longitude de l’image sur la droite ?</string>
|
<string name="use_location_from_similar_image">Avez-vous pris ces deux photos du même endroit ? Voulez-vous utiliser les latitude/longitude de l’image sur la droite ?</string>
|
||||||
|
<string name="nearby_no_results">Pas d’endroit trouvé, essayez de modifier vos critères de recherche.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
<string name="preference_category_privacy">Persónuvernd</string>
|
<string name="preference_category_privacy">Persónuvernd</string>
|
||||||
<string name="preference_category_location">Staðsetning</string>
|
<string name="preference_category_location">Staðsetning</string>
|
||||||
<string name="app_name">Commons</string>
|
<string name="app_name">Commons</string>
|
||||||
<string name="bullet">• \</string>
|
<string name="bullet">• \\</string>
|
||||||
<string name="menu_settings">Stillingar</string>
|
<string name="menu_settings">Stillingar</string>
|
||||||
<string name="intent_share_upload_label">Senda inn á Commons</string>
|
<string name="intent_share_upload_label">Senda inn á Commons</string>
|
||||||
<string name="username">Notandanafn</string>
|
<string name="username">Notandanafn</string>
|
||||||
|
|
@ -92,7 +92,7 @@
|
||||||
<string name="title_activity_category_details">Flokkur</string>
|
<string name="title_activity_category_details">Flokkur</string>
|
||||||
<string name="title_activity_review">Yfirlestur jafningja</string>
|
<string name="title_activity_review">Yfirlestur jafningja</string>
|
||||||
<string name="menu_about">Um</string>
|
<string name="menu_about">Um</string>
|
||||||
<string name="about_license">Wikimedia Commons forritið er opinn og frjáls hugbúnaður sem gerður er og viðhaldið af stuðningsaðilum og sjálfboðaliðum Wikimedia samfélagsins. Wikimedia Foundation sjálfseignarstofnunin kemur ekki að gerð, forritun eða viðhaldi forritsins. \</string>
|
<string name="about_license">Wikimedia Commons forritið er opinn og frjáls hugbúnaður sem gerður er og viðhaldið af stuðningsaðilum og sjálfboðaliðum Wikimedia samfélagsins. Wikimedia Foundation sjálfseignarstofnunin kemur ekki að gerð, forritun eða viðhaldi forritsins. \\</string>
|
||||||
<string name="about_improve">Útbúðu nýjar <a href=\"%1$s\">GitHub tilkynningar (issue)</a> til að koma villum og uppástungum á framfæri.</string>
|
<string name="about_improve">Útbúðu nýjar <a href=\"%1$s\">GitHub tilkynningar (issue)</a> til að koma villum og uppástungum á framfæri.</string>
|
||||||
<string name="about_privacy_policy">Meðferð persónuupplýsinga</string>
|
<string name="about_privacy_policy">Meðferð persónuupplýsinga</string>
|
||||||
<string name="about_credits">Framlög</string>
|
<string name="about_credits">Framlög</string>
|
||||||
|
|
@ -322,7 +322,7 @@
|
||||||
<string name="wallpaper_set_successfully">Tókst að stilla bakgrunnsmynd!</string>
|
<string name="wallpaper_set_successfully">Tókst að stilla bakgrunnsmynd!</string>
|
||||||
<string name="quiz">Spurningaleikur</string>
|
<string name="quiz">Spurningaleikur</string>
|
||||||
<string name="quiz_question_string">Er í lagi að senda inn þessa mynd?</string>
|
<string name="quiz_question_string">Er í lagi að senda inn þessa mynd?</string>
|
||||||
<string name="question">Spurning \</string>
|
<string name="question">Spurning \\</string>
|
||||||
<string name="result">Niðurstaða</string>
|
<string name="result">Niðurstaða</string>
|
||||||
<string name="quiz_back_button">Ef þú heldur áfram að senda inn myndir sem krefjast að þeim sé eytt, er líklegt að aðgangurinn þinn verði bannaður. Ertu viss um að þú viljir hætta í spurningaleiknum?</string>
|
<string name="quiz_back_button">Ef þú heldur áfram að senda inn myndir sem krefjast að þeim sé eytt, er líklegt að aðgangurinn þinn verði bannaður. Ertu viss um að þú viljir hætta í spurningaleiknum?</string>
|
||||||
<string name="quiz_alert_message">Meira en %1$s myndanna sem þú hefur sent inn hefur verið eytt. Ef þú heldur áfram að senda inn myndir sem krefjast að þeim sé eytt, er líklegt að aðgangurinn þinn verði bannaður.\n\nMyndir þú vilja skða kennsluefnið aftur og taka síðan einn léttan spurningaleik til að hjálpa þér til að læra hvernig myndir þú ættir að senda inn og hverjar ekki?</string>
|
<string name="quiz_alert_message">Meira en %1$s myndanna sem þú hefur sent inn hefur verið eytt. Ef þú heldur áfram að senda inn myndir sem krefjast að þeim sé eytt, er líklegt að aðgangurinn þinn verði bannaður.\n\nMyndir þú vilja skða kennsluefnið aftur og taka síðan einn léttan spurningaleik til að hjálpa þér til að læra hvernig myndir þú ættir að senda inn og hverjar ekki?</string>
|
||||||
|
|
@ -361,7 +361,7 @@
|
||||||
<string name="images_used_by_wiki">Myndir notaðar</string>
|
<string name="images_used_by_wiki">Myndir notaðar</string>
|
||||||
<string name="achievements_share_message">Deildu frammistöðu þinni með vinum þínum!</string>
|
<string name="achievements_share_message">Deildu frammistöðu þinni með vinum þínum!</string>
|
||||||
<string name="achievements_info_message">Stig þitt eykst eftir því sem þú uppfyllir betur þessar kröfur. Atriði í \"tölfræði\"-hlutanum teljast ekki með í stigaútreikningi þínum.</string>
|
<string name="achievements_info_message">Stig þitt eykst eftir því sem þú uppfyllir betur þessar kröfur. Atriði í \"tölfræði\"-hlutanum teljast ekki með í stigaútreikningi þínum.</string>
|
||||||
<string name="achievements_revert_limit_message">lágmarksfjöldi sem er krafist: \</string>
|
<string name="achievements_revert_limit_message">lágmarksfjöldi sem er krafist: \\</string>
|
||||||
<string name="images_uploaded_explanation">Heildarfjöldi mynda sem þú hefur sent inn á Commons, með hverskyns innsendingarhugbúnaði</string>
|
<string name="images_uploaded_explanation">Heildarfjöldi mynda sem þú hefur sent inn á Commons, með hverskyns innsendingarhugbúnaði</string>
|
||||||
<string name="images_reverted_explanation">Prósentuhlutfall mynda sem þú hefur sent inn á Commons sem ekki hefur verið eytt</string>
|
<string name="images_reverted_explanation">Prósentuhlutfall mynda sem þú hefur sent inn á Commons sem ekki hefur verið eytt</string>
|
||||||
<string name="images_used_explanation">Heildarfjöldi mynda sem þú hefur sent inn á Commons sem hafa verið notaðar með Wikimedia-greinum</string>
|
<string name="images_used_explanation">Heildarfjöldi mynda sem þú hefur sent inn á Commons sem hafa verið notaðar með Wikimedia-greinum</string>
|
||||||
|
|
|
||||||
|
|
@ -620,4 +620,5 @@
|
||||||
<string name="ask_to_turn_location_on">להפעיל מיקום?</string>
|
<string name="ask_to_turn_location_on">להפעיל מיקום?</string>
|
||||||
<string name="nearby_needs_location">פעולת \"בסביבה\" זקוקה לשירותי מיקומי פועלים כדי לעבוד כמו שצריך</string>
|
<string name="nearby_needs_location">פעולת \"בסביבה\" זקוקה לשירותי מיקומי פועלים כדי לעבוד כמו שצריך</string>
|
||||||
<string name="use_location_from_similar_image">האם צילמת את שתי התמונות באותו המקום? האם ברצונך להשתמש בקו הרוחב וקו האורך של התמונה משמאל?</string>
|
<string name="use_location_from_similar_image">האם צילמת את שתי התמונות באותו המקום? האם ברצונך להשתמש בקו הרוחב וקו האורך של התמונה משמאל?</string>
|
||||||
|
<string name="nearby_no_results">לא נמצאו מקומות, נא לנסות לשנות את החיפוש.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Authors:
|
<!-- Authors:
|
||||||
* Abijeet Patro
|
* Abijeet Patro
|
||||||
|
* Diki Ananta
|
||||||
|
* N219
|
||||||
* NoiX180
|
* NoiX180
|
||||||
* Notanotheramy
|
* Notanotheramy
|
||||||
-->
|
-->
|
||||||
|
|
@ -15,11 +17,11 @@
|
||||||
<string name="bullet">•</string>
|
<string name="bullet">•</string>
|
||||||
<string name="menu_settings">Setèlan</string>
|
<string name="menu_settings">Setèlan</string>
|
||||||
<string name="intent_share_upload_label">Unggah menyang Commons</string>
|
<string name="intent_share_upload_label">Unggah menyang Commons</string>
|
||||||
<string name="username">Jeneng panganggo</string>
|
<string name="username">Jeneng naraguna</string>
|
||||||
<string name="password">Tembung wadi</string>
|
<string name="password">Tembung sandi</string>
|
||||||
<string name="login_credential">Mlebu log akun Commons Beta panjenengan</string>
|
<string name="login_credential">Mlebu log akun Commons Beta panjenengan</string>
|
||||||
<string name="login">Mlebu log</string>
|
<string name="login">Mlebu log</string>
|
||||||
<string name="forgot_password">Lali Tembung Wadi?</string>
|
<string name="forgot_password">Lali Tembung Sandi?</string>
|
||||||
<string name="signup">Dhaftar</string>
|
<string name="signup">Dhaftar</string>
|
||||||
<string name="logging_in_title">Lagi mlebu log</string>
|
<string name="logging_in_title">Lagi mlebu log</string>
|
||||||
<string name="logging_in_message">Entènana sadhela…</string>
|
<string name="logging_in_message">Entènana sadhela…</string>
|
||||||
|
|
@ -53,9 +55,9 @@
|
||||||
<string name="share_title_hint">Sesirah (Kudu)</string>
|
<string name="share_title_hint">Sesirah (Kudu)</string>
|
||||||
<string name="share_description_hint">Wedharan</string>
|
<string name="share_description_hint">Wedharan</string>
|
||||||
<string name="login_failed_network">Ora bisa mlebu log - jaringané gagal</string>
|
<string name="login_failed_network">Ora bisa mlebu log - jaringané gagal</string>
|
||||||
<string name="login_failed_wrong_credentials">Ora bisa mlebu log - priksa jeneng panganggo lan tembung wadi panjenengan</string>
|
<string name="login_failed_wrong_credentials">Ora bisa mlebu log - priksa jeneng naraguna lan tembung sandi panjenengan</string>
|
||||||
<string name="login_failed_throttled">Kakèhan upaya sing gagal. Jajalana manèh mengko.</string>
|
<string name="login_failed_throttled">Kakèhan upaya sing gagal. Jajalana manèh mengko.</string>
|
||||||
<string name="login_failed_blocked">Ngapunten, panganggo iki wis diblokir ing Commons</string>
|
<string name="login_failed_blocked">Ngapunten, naraguna iki wis diblokir ing Commons</string>
|
||||||
<string name="login_failed_2fa_needed">Panjenengan kudu ngisi kodhe otèntifikasi rong faktoré panjenengan</string>
|
<string name="login_failed_2fa_needed">Panjenengan kudu ngisi kodhe otèntifikasi rong faktoré panjenengan</string>
|
||||||
<string name="login_failed_generic">Wurung mlebu log</string>
|
<string name="login_failed_generic">Wurung mlebu log</string>
|
||||||
<string name="share_upload_button">Unggah</string>
|
<string name="share_upload_button">Unggah</string>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
<!-- Authors:
|
<!-- Authors:
|
||||||
* Abijeet Patro
|
* Abijeet Patro
|
||||||
* CYAN
|
* CYAN
|
||||||
|
* Codenstory
|
||||||
* Dlsrks1021
|
* Dlsrks1021
|
||||||
* Doyoon1995
|
* Doyoon1995
|
||||||
* Freebiekr
|
* Freebiekr
|
||||||
|
|
@ -78,6 +79,7 @@
|
||||||
<string name="provider_modifications">바뀜</string>
|
<string name="provider_modifications">바뀜</string>
|
||||||
<string name="menu_upload_single">올리기</string>
|
<string name="menu_upload_single">올리기</string>
|
||||||
<string name="categories_search_text_hint">분류 검색</string>
|
<string name="categories_search_text_hint">분류 검색</string>
|
||||||
|
<string name="depicts_search_text_hint">미디어가 서술한 항목을 검색하세요. (산, 타지마할 등)</string>
|
||||||
<string name="menu_save_categories">저장</string>
|
<string name="menu_save_categories">저장</string>
|
||||||
<string name="refresh_button">새로 고침</string>
|
<string name="refresh_button">새로 고침</string>
|
||||||
<string name="display_list_button">목록</string>
|
<string name="display_list_button">목록</string>
|
||||||
|
|
@ -98,6 +100,9 @@
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="categories_not_found">%1$s와(과) 일치하는 분류를 찾을 수 없습니다</string>
|
<string name="categories_not_found">%1$s와(과) 일치하는 분류를 찾을 수 없습니다</string>
|
||||||
<string name="depictions_not_found">%1$s에 대한 위키데이터 검색 결과가 없습니다</string>
|
<string name="depictions_not_found">%1$s에 대한 위키데이터 검색 결과가 없습니다</string>
|
||||||
|
<string name="no_child_classes">%1$s에 자식 클래스가 없습니다</string>
|
||||||
|
<string name="no_parent_classes">%1$s에 부모 클래스가 없습니다</string>
|
||||||
|
<string name="depictions_image_not_found">서술을 위한 이미지가 없습니다</string>
|
||||||
<string name="categories_skip_explanation">위키미디어 공용에서 그림을 더 찾기 쉽게 만들기 위해 분류를 추가합니다.\n분류를 추가하려면 입력을 시작하세요.</string>
|
<string name="categories_skip_explanation">위키미디어 공용에서 그림을 더 찾기 쉽게 만들기 위해 분류를 추가합니다.\n분류를 추가하려면 입력을 시작하세요.</string>
|
||||||
<string name="categories_activity_title">분류</string>
|
<string name="categories_activity_title">분류</string>
|
||||||
<string name="title_activity_settings">설정</string>
|
<string name="title_activity_settings">설정</string>
|
||||||
|
|
@ -113,6 +118,7 @@
|
||||||
<string name="menu_feedback">(이메일로) 피드백 보내기</string>
|
<string name="menu_feedback">(이메일로) 피드백 보내기</string>
|
||||||
<string name="no_email_client">설치된 이메일 클라이언트가 없습니다</string>
|
<string name="no_email_client">설치된 이메일 클라이언트가 없습니다</string>
|
||||||
<string name="provider_categories">최근에 사용한 분류</string>
|
<string name="provider_categories">최근에 사용한 분류</string>
|
||||||
|
<string name="provider_depictions">최근에 사용한 서술</string>
|
||||||
<string name="waiting_first_sync">첫 동기화를 기다리는 중…</string>
|
<string name="waiting_first_sync">첫 동기화를 기다리는 중…</string>
|
||||||
<string name="no_uploads_yet">아직 사진을 올리지 않았습니다.</string>
|
<string name="no_uploads_yet">아직 사진을 올리지 않았습니다.</string>
|
||||||
<string name="menu_retry_upload">다시 시도</string>
|
<string name="menu_retry_upload">다시 시도</string>
|
||||||
|
|
@ -173,6 +179,7 @@
|
||||||
<string name="detail_panel_cats_loading">불러오는 중…</string>
|
<string name="detail_panel_cats_loading">불러오는 중…</string>
|
||||||
<string name="detail_panel_cats_none">선택하지 않음</string>
|
<string name="detail_panel_cats_none">선택하지 않음</string>
|
||||||
<string name="detail_caption_empty">설명 없음</string>
|
<string name="detail_caption_empty">설명 없음</string>
|
||||||
|
<string name="detail_depiction_empty">서술 없음</string>
|
||||||
<string name="detail_description_empty">설명 없음</string>
|
<string name="detail_description_empty">설명 없음</string>
|
||||||
<string name="detail_discussion_empty">토론 없음</string>
|
<string name="detail_discussion_empty">토론 없음</string>
|
||||||
<string name="detail_license_empty">알 수 없는 라이선스</string>
|
<string name="detail_license_empty">알 수 없는 라이선스</string>
|
||||||
|
|
@ -193,6 +200,7 @@
|
||||||
<string name="no">아니오</string>
|
<string name="no">아니오</string>
|
||||||
<string name="media_detail_caption">설명</string>
|
<string name="media_detail_caption">설명</string>
|
||||||
<string name="media_detail_title">제목</string>
|
<string name="media_detail_title">제목</string>
|
||||||
|
<string name="media_detail_depiction">서술</string>
|
||||||
<string name="media_detail_description">설명</string>
|
<string name="media_detail_description">설명</string>
|
||||||
<string name="media_detail_discussion">토론</string>
|
<string name="media_detail_discussion">토론</string>
|
||||||
<string name="media_detail_author">저자</string>
|
<string name="media_detail_author">저자</string>
|
||||||
|
|
@ -253,6 +261,7 @@
|
||||||
<string name="error_while_cache">그림 캐시 처리 오류</string>
|
<string name="error_while_cache">그림 캐시 처리 오류</string>
|
||||||
<string name="title_info">이 파일을 설명할 수 있는 제목으로, 파일 이름으로 사용됩니다. 띄어쓰기를 포함한 일반적인 단어를 사용할 수 있습니다. 파일 확장자는 포함하지 마세요</string>
|
<string name="title_info">이 파일을 설명할 수 있는 제목으로, 파일 이름으로 사용됩니다. 띄어쓰기를 포함한 일반적인 단어를 사용할 수 있습니다. 파일 확장자는 포함하지 마세요</string>
|
||||||
<string name="description_info">미디어에 대해 가능한 많이 설명하십시오: 어디서 촬영한 것인가? 무엇을 보여주는가? 무슨 문맥을 가지는가? 물건이나 사람에 대해 설명하십시오. 풍경에서 시간을 알려주는 것처럼 쉽게 추측할 수 없는 정보를 제공합니다. 미디어가 평범하지 않다면 무엇이 이를 평범하지 않게 만들었는지 설명하십시오.</string>
|
<string name="description_info">미디어에 대해 가능한 많이 설명하십시오: 어디서 촬영한 것인가? 무엇을 보여주는가? 무슨 문맥을 가지는가? 물건이나 사람에 대해 설명하십시오. 풍경에서 시간을 알려주는 것처럼 쉽게 추측할 수 없는 정보를 제공합니다. 미디어가 평범하지 않다면 무엇이 이를 평범하지 않게 만들었는지 설명하십시오.</string>
|
||||||
|
<string name="caption_info">그림 설명을 작성해 주세요. (255자 제한)</string>
|
||||||
<string name="upload_image_too_dark">이 사진은 너무 어둡습니다. 정말 업로드하시겠습니까? 위키미디어 공용은 백과사전적인 가치가 있는 사진을 위한 공간입니다.</string>
|
<string name="upload_image_too_dark">이 사진은 너무 어둡습니다. 정말 업로드하시겠습니까? 위키미디어 공용은 백과사전적인 가치가 있는 사진을 위한 공간입니다.</string>
|
||||||
<string name="upload_image_blurry">이 사진은 흐릿합니다. 정말 업로드하시겠습니까? 위키미디어 공용은 백과사전적인 가치가 있는 사진을 위한 공간입니다.</string>
|
<string name="upload_image_blurry">이 사진은 흐릿합니다. 정말 업로드하시겠습니까? 위키미디어 공용은 백과사전적인 가치가 있는 사진을 위한 공간입니다.</string>
|
||||||
<string name="upload_problem_exist">이 그림에 잠재적인 문제가 있습니다:</string>
|
<string name="upload_problem_exist">이 그림에 잠재적인 문제가 있습니다:</string>
|
||||||
|
|
@ -326,6 +335,7 @@
|
||||||
<string name="search_recent_header">최근 검색:</string>
|
<string name="search_recent_header">최근 검색:</string>
|
||||||
<string name="provider_searches">최근 검색된 쿼리</string>
|
<string name="provider_searches">최근 검색된 쿼리</string>
|
||||||
<string name="error_loading_categories">분류를 불러오는 동안 오류가 발생했습니다.</string>
|
<string name="error_loading_categories">분류를 불러오는 동안 오류가 발생했습니다.</string>
|
||||||
|
<string name="error_loading_depictions">서술을 불러오는 동안 오류가 발생했습니다.</string>
|
||||||
<string name="error_loading_subcategories">하위 분류를 불러오는 동안 오류가 발생했습니다.</string>
|
<string name="error_loading_subcategories">하위 분류를 불러오는 동안 오류가 발생했습니다.</string>
|
||||||
<string name="search_tab_title_media">미디어</string>
|
<string name="search_tab_title_media">미디어</string>
|
||||||
<string name="search_tab_title_categories">분류</string>
|
<string name="search_tab_title_categories">분류</string>
|
||||||
|
|
@ -426,7 +436,8 @@
|
||||||
<string name="desc_language_Asia">아시아</string>
|
<string name="desc_language_Asia">아시아</string>
|
||||||
<string name="desc_language_Pacific">태평양</string>
|
<string name="desc_language_Pacific">태평양</string>
|
||||||
<string name="no_categories_selected">선택된 분류 없음</string>
|
<string name="no_categories_selected">선택된 분류 없음</string>
|
||||||
<string name="no_categories_selected_warning_desc" fuzzy="true">분류가 없는 그림은 거의 유용하지 않습니다. 분류를 선택하지 않고 제출하시겠습니까?</string>
|
<string name="no_categories_selected_warning_desc">분류가 없는 그림은 거의 유용하지 않습니다. 분류를 선택하지 않고 제출하시겠습니까?</string>
|
||||||
|
<string name="no_depictions_selected">선택된 서술 없음</string>
|
||||||
<string name="search_this_area">여기를 검색</string>
|
<string name="search_this_area">여기를 검색</string>
|
||||||
<string name="nearby_card_permission_title">권한 요청</string>
|
<string name="nearby_card_permission_title">권한 요청</string>
|
||||||
<string name="nearby_card_permission_explanation">사진이 필요한 주변 장소를 표시하기 위해 현재 위치를 사용하시겠습니까?</string>
|
<string name="nearby_card_permission_explanation">사진이 필요한 주변 장소를 표시하기 위해 현재 위치를 사용하시겠습니까?</string>
|
||||||
|
|
@ -477,6 +488,7 @@
|
||||||
<string name="share_via">앱 공유...</string>
|
<string name="share_via">앱 공유...</string>
|
||||||
<string name="image_info">이미지 정보</string>
|
<string name="image_info">이미지 정보</string>
|
||||||
<string name="no_categories_found">분류가 없습니다</string>
|
<string name="no_categories_found">분류가 없습니다</string>
|
||||||
|
<string name="no_depiction_found">서술이 발견되지 않았습니다</string>
|
||||||
<string name="upload_cancelled">업로드 취소됨</string>
|
<string name="upload_cancelled">업로드 취소됨</string>
|
||||||
<string name="default_description_language">기본 설명 언어</string>
|
<string name="default_description_language">기본 설명 언어</string>
|
||||||
<string name="delete_helper_show_deletion_title_success">성공</string>
|
<string name="delete_helper_show_deletion_title_success">성공</string>
|
||||||
|
|
@ -500,6 +512,8 @@
|
||||||
<string name="place_type">장소 유형:</string>
|
<string name="place_type">장소 유형:</string>
|
||||||
<string name="nearby_search_hint">다리, 박물관, 호텔 등.</string>
|
<string name="nearby_search_hint">다리, 박물관, 호텔 등.</string>
|
||||||
<string name="title_for_media">미디어</string>
|
<string name="title_for_media">미디어</string>
|
||||||
|
<string name="title_for_child_classes">자식 클래스</string>
|
||||||
|
<string name="title_for_parent_classes">부모 클래스</string>
|
||||||
<string name="title_app_shortcut_explore">찾아보기</string>
|
<string name="title_app_shortcut_explore">찾아보기</string>
|
||||||
<string name="title_app_shortcut_bookmark">북마크</string>
|
<string name="title_app_shortcut_bookmark">북마크</string>
|
||||||
<string name="title_app_shortcut_setting">설정</string>
|
<string name="title_app_shortcut_setting">설정</string>
|
||||||
|
|
@ -511,4 +525,5 @@
|
||||||
<string name="theme_dark_name">어두움</string>
|
<string name="theme_dark_name">어두움</string>
|
||||||
<string name="theme_light_name">밝음</string>
|
<string name="theme_light_name">밝음</string>
|
||||||
<string name="ask_to_turn_location_on">위치를 켭니까?</string>
|
<string name="ask_to_turn_location_on">위치를 켭니까?</string>
|
||||||
|
<string name="nearby_no_results">발견된 장소가 없습니다. 검색 기준을 바꾸어 보십시오.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -594,4 +594,5 @@
|
||||||
<string name="ask_to_turn_location_on">Да ја вклучам местоположбата?</string>
|
<string name="ask_to_turn_location_on">Да ја вклучам местоположбата?</string>
|
||||||
<string name="nearby_needs_location">„Во близина“ бара местоположба за да работи</string>
|
<string name="nearby_needs_location">„Во близина“ бара местоположба за да работи</string>
|
||||||
<string name="use_location_from_similar_image">Дали ги направивте ови две слики на истото место? Дали сакате да ја искористите географската ширина/должина од десната слика?</string>
|
<string name="use_location_from_similar_image">Дали ги направивте ови две слики на истото место? Дали сакате да ја искористите географската ширина/должина од десната слика?</string>
|
||||||
|
<string name="nearby_no_results">Не пронајдов места. Изменете ги критериумите во барањето.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -592,4 +592,5 @@
|
||||||
<string name="ask_to_turn_location_on">Anvisché la localisassion?</string>
|
<string name="ask_to_turn_location_on">Anvisché la localisassion?</string>
|
||||||
<string name="nearby_needs_location">Nearby a l\'ha damanca dla localisassion abilità për marcé \'me ch\'as dev</string>
|
<string name="nearby_needs_location">Nearby a l\'ha damanca dla localisassion abilità për marcé \'me ch\'as dev</string>
|
||||||
<string name="use_location_from_similar_image">A la fàit se doe fòto ant l\'istess pòst? A veul dovré la latitùdin e la longitùdin ëd la fòto an sla drita?</string>
|
<string name="use_location_from_similar_image">A la fàit se doe fòto ant l\'istess pòst? A veul dovré la latitùdin e la longitùdin ëd la fòto an sla drita?</string>
|
||||||
|
<string name="nearby_no_results">Gnun pòst trovà, ch\'a preuva a modifiché ij sò criteri d\'arserca.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -606,4 +606,5 @@
|
||||||
<string name="ask_to_turn_location_on">Ativar a localização?</string>
|
<string name="ask_to_turn_location_on">Ativar a localização?</string>
|
||||||
<string name="nearby_needs_location">Proximidade precisa de localização ativado para funcionar corretamente</string>
|
<string name="nearby_needs_location">Proximidade precisa de localização ativado para funcionar corretamente</string>
|
||||||
<string name="use_location_from_similar_image">Você tirou essas duas fotos no mesmo lugar? Deseja usar a latitude/longitude da imagem à direita?</string>
|
<string name="use_location_from_similar_image">Você tirou essas duas fotos no mesmo lugar? Deseja usar a latitude/longitude da imagem à direita?</string>
|
||||||
|
<string name="nearby_no_results">Nenhum local encontrado, tente alterar seus critérios de pesquisa.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
* Redredsonia
|
* Redredsonia
|
||||||
* Rubin16
|
* Rubin16
|
||||||
* Vlad5250
|
* Vlad5250
|
||||||
|
* VoxelJ
|
||||||
* Wikisaurus
|
* Wikisaurus
|
||||||
* Wirbel78
|
* Wirbel78
|
||||||
* Евгения
|
* Евгения
|
||||||
|
|
@ -76,6 +77,7 @@
|
||||||
<string name="menu_share">Поделиться</string>
|
<string name="menu_share">Поделиться</string>
|
||||||
<string name="menu_open_in_browser">Открыть в браузере</string>
|
<string name="menu_open_in_browser">Открыть в браузере</string>
|
||||||
<string name="share_title_hint">Подпись (Обязательна)</string>
|
<string name="share_title_hint">Подпись (Обязательна)</string>
|
||||||
|
<string name="add_caption_toast">Пожалуйста, укажите название этого файла</string>
|
||||||
<string name="share_description_hint">Описание</string>
|
<string name="share_description_hint">Описание</string>
|
||||||
<string name="login_failed_network">Не удаётся войти — сбой сети</string>
|
<string name="login_failed_network">Не удаётся войти — сбой сети</string>
|
||||||
<string name="login_failed_wrong_credentials">Не удаётся войти — проверьте ваше имя пользователя и пароль</string>
|
<string name="login_failed_wrong_credentials">Не удаётся войти — проверьте ваше имя пользователя и пароль</string>
|
||||||
|
|
@ -537,6 +539,7 @@
|
||||||
<string name="images_featured_explanation">Избранные изображения обычно сделаны профессиональными фотографами и иллюстраторами. Такие изображения отмечены сообществом участников Викисклада как имеющие высшее качество на этом вебсайте.</string>
|
<string name="images_featured_explanation">Избранные изображения обычно сделаны профессиональными фотографами и иллюстраторами. Такие изображения отмечены сообществом участников Викисклада как имеющие высшее качество на этом вебсайте.</string>
|
||||||
<string name="images_via_nearby_explanation">Изображения, загруженные участниками, которые находили места с помощью функционала \"Места поблизости\".</string>
|
<string name="images_via_nearby_explanation">Изображения, загруженные участниками, которые находили места с помощью функционала \"Места поблизости\".</string>
|
||||||
<string name="thanks_received_explanation">Это функция позволяет участникам послать благодарность другим участникам за их полезные правки с помощью маленькой ссылки на странице истории страницы или странице разницы версий.</string>
|
<string name="thanks_received_explanation">Это функция позволяет участникам послать благодарность другим участникам за их полезные правки с помощью маленькой ссылки на странице истории страницы или странице разницы версий.</string>
|
||||||
|
<string name="previous_image_caption_description">Скопировать предыдущие название и описание</string>
|
||||||
<string name="previous_button_tooltip_message">Нажмите, чтобы использовать название и описание, которые вы ввели для предыдущего изображения, а потом потом модифицировать их</string>
|
<string name="previous_button_tooltip_message">Нажмите, чтобы использовать название и описание, которые вы ввели для предыдущего изображения, а потом потом модифицировать их</string>
|
||||||
<string name="welcome_do_upload_content_description">Примеры изображений, подходящих для загрузки на Викисклад</string>
|
<string name="welcome_do_upload_content_description">Примеры изображений, подходящих для загрузки на Викисклад</string>
|
||||||
<string name="welcome_dont_upload_content_description">Примеры изображений, которые не следует загружать на Викисклад</string>
|
<string name="welcome_dont_upload_content_description">Примеры изображений, которые не следует загружать на Викисклад</string>
|
||||||
|
|
@ -607,4 +610,5 @@
|
||||||
<string name="recommend_high_accuracy_mode">Для достижения наилучших результатов выберите режим высокой точности.</string>
|
<string name="recommend_high_accuracy_mode">Для достижения наилучших результатов выберите режим высокой точности.</string>
|
||||||
<string name="ask_to_turn_location_on">Включить местоположение?</string>
|
<string name="ask_to_turn_location_on">Включить местоположение?</string>
|
||||||
<string name="nearby_needs_location">Рядом необходимо, чтобы местоположение было включено для правильной работы.</string>
|
<string name="nearby_needs_location">Рядом необходимо, чтобы местоположение было включено для правильной работы.</string>
|
||||||
|
<string name="nearby_no_results">Мест не найдено, попытайтесь изменить критерии поиска.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,10 @@
|
||||||
<string name="provider_contributions">Moje obrázky</string>
|
<string name="provider_contributions">Moje obrázky</string>
|
||||||
<string name="menu_share">Zdieľať</string>
|
<string name="menu_share">Zdieľať</string>
|
||||||
<string name="menu_open_in_browser">Otvoriť v prehliadači</string>
|
<string name="menu_open_in_browser">Otvoriť v prehliadači</string>
|
||||||
<string name="share_title_hint" fuzzy="true">Názov (povinné)</string>
|
<string name="share_title_hint">Popisok (povinné)</string>
|
||||||
|
<string name="add_caption_toast">Prosím, pridajte popisok pre tento súbor</string>
|
||||||
<string name="share_description_hint">Opis</string>
|
<string name="share_description_hint">Opis</string>
|
||||||
|
<string name="share_caption_hint">Popisok (max. 255 znakov)</string>
|
||||||
<string name="login_failed_network">prihlásenie zlyhalo - zlyhanie siete</string>
|
<string name="login_failed_network">prihlásenie zlyhalo - zlyhanie siete</string>
|
||||||
<string name="login_failed_wrong_credentials">Nie je možné sa prihlásiť – skontrolujte, prosím, svoje používateľské meno a heslo</string>
|
<string name="login_failed_wrong_credentials">Nie je možné sa prihlásiť – skontrolujte, prosím, svoje používateľské meno a heslo</string>
|
||||||
<string name="login_failed_throttled">Príliš veľa neúspešných pokusov. Skúste to znova o niekoľko minút.</string>
|
<string name="login_failed_throttled">Príliš veľa neúspešných pokusov. Skúste to znova o niekoľko minút.</string>
|
||||||
|
|
@ -67,6 +69,7 @@
|
||||||
<string name="provider_modifications">Úpravy</string>
|
<string name="provider_modifications">Úpravy</string>
|
||||||
<string name="menu_upload_single">Nahrať</string>
|
<string name="menu_upload_single">Nahrať</string>
|
||||||
<string name="categories_search_text_hint">Vyhľadávanie kategórií</string>
|
<string name="categories_search_text_hint">Vyhľadávanie kategórií</string>
|
||||||
|
<string name="depicts_search_text_hint">Hľadať položky, ktoré váš multimediálny súbor zobrazuje (vrch, Tádž Mahal atď.)</string>
|
||||||
<string name="menu_save_categories">Uložiť</string>
|
<string name="menu_save_categories">Uložiť</string>
|
||||||
<string name="refresh_button">Obnoviť</string>
|
<string name="refresh_button">Obnoviť</string>
|
||||||
<string name="display_list_button">Zoznam</string>
|
<string name="display_list_button">Zoznam</string>
|
||||||
|
|
@ -87,6 +90,7 @@
|
||||||
<item quantity="other">%1$d nahrania</item>
|
<item quantity="other">%1$d nahrania</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="categories_not_found">Žiadne kategórie nezodpovedajú „%1$s“</string>
|
<string name="categories_not_found">Žiadne kategórie nezodpovedajú „%1$s“</string>
|
||||||
|
<string name="depictions_not_found">Neboli nájdené žiadne položky Wikiúdajov zhodujúce sa s %1$s</string>
|
||||||
<string name="categories_skip_explanation">Pridajte kategórie, aby bolo vaše obrázky možné na Wikimedia Commons nájsť.\nPre pridanie kategórií začnite písať.</string>
|
<string name="categories_skip_explanation">Pridajte kategórie, aby bolo vaše obrázky možné na Wikimedia Commons nájsť.\nPre pridanie kategórií začnite písať.</string>
|
||||||
<string name="categories_activity_title">Kategórie</string>
|
<string name="categories_activity_title">Kategórie</string>
|
||||||
<string name="title_activity_settings">Nastavenia</string>
|
<string name="title_activity_settings">Nastavenia</string>
|
||||||
|
|
@ -162,6 +166,7 @@
|
||||||
<string name="detail_panel_cats_label">Kategórie</string>
|
<string name="detail_panel_cats_label">Kategórie</string>
|
||||||
<string name="detail_panel_cats_loading">Načítava sa…</string>
|
<string name="detail_panel_cats_loading">Načítava sa…</string>
|
||||||
<string name="detail_panel_cats_none">Nič nebolo vybrané</string>
|
<string name="detail_panel_cats_none">Nič nebolo vybrané</string>
|
||||||
|
<string name="detail_caption_empty">Žiadny titulok</string>
|
||||||
<string name="detail_description_empty">Bez popisu</string>
|
<string name="detail_description_empty">Bez popisu</string>
|
||||||
<string name="detail_discussion_empty">Žiadna diskusia</string>
|
<string name="detail_discussion_empty">Žiadna diskusia</string>
|
||||||
<string name="detail_license_empty">Neznáma licencia</string>
|
<string name="detail_license_empty">Neznáma licencia</string>
|
||||||
|
|
@ -175,6 +180,7 @@
|
||||||
<string name="title_activity_nearby">Miesta v okolí</string>
|
<string name="title_activity_nearby">Miesta v okolí</string>
|
||||||
<string name="no_nearby">V okolí sa nenašli žiadne miesta</string>
|
<string name="no_nearby">V okolí sa nenašli žiadne miesta</string>
|
||||||
<string name="warning">Upozornenie</string>
|
<string name="warning">Upozornenie</string>
|
||||||
|
<string name="duplicate_image_found">Nájdený duplicitný obrázok</string>
|
||||||
<string name="upload_image_duplicate">Tento súbor už na Commons existuje. Ste si istí, že chcete pokračovať?</string>
|
<string name="upload_image_duplicate">Tento súbor už na Commons existuje. Ste si istí, že chcete pokračovať?</string>
|
||||||
<string name="upload">Nahrať</string>
|
<string name="upload">Nahrať</string>
|
||||||
<string name="yes">Áno</string>
|
<string name="yes">Áno</string>
|
||||||
|
|
@ -566,4 +572,5 @@
|
||||||
<string name="theme_default_name" fuzzy="true">Predvolený</string>
|
<string name="theme_default_name" fuzzy="true">Predvolený</string>
|
||||||
<string name="theme_dark_name">Tmavý</string>
|
<string name="theme_dark_name">Tmavý</string>
|
||||||
<string name="theme_light_name">Svetlý</string>
|
<string name="theme_light_name">Svetlý</string>
|
||||||
|
<string name="ask_to_turn_location_on">Zapnúť lokáciu?</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -598,4 +598,5 @@
|
||||||
<string name="ask_to_turn_location_on">Aktivera plats?</string>
|
<string name="ask_to_turn_location_on">Aktivera plats?</string>
|
||||||
<string name="nearby_needs_location">I närheten behöver ha plats aktiverat för att fungera ordentligt</string>
|
<string name="nearby_needs_location">I närheten behöver ha plats aktiverat för att fungera ordentligt</string>
|
||||||
<string name="use_location_from_similar_image">Tog du dessa två bilder på samma plats? Vill du använda den högra bildens latitud/longitud?</string>
|
<string name="use_location_from_similar_image">Tog du dessa två bilder på samma plats? Vill du använda den högra bildens latitud/longitud?</string>
|
||||||
|
<string name="nearby_no_results">Inga platser hittades, försök ändra dina sökkriterier.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -610,4 +610,5 @@
|
||||||
<string name="ask_to_turn_location_on">Konum açılsın mı?</string>
|
<string name="ask_to_turn_location_on">Konum açılsın mı?</string>
|
||||||
<string name="nearby_needs_location">Yakınımdakilerin düzgün çalışması için konumun açık olması gerekiyor</string>
|
<string name="nearby_needs_location">Yakınımdakilerin düzgün çalışması için konumun açık olması gerekiyor</string>
|
||||||
<string name="use_location_from_similar_image">Bu iki fotoğrafı aynı yerde mi çektiniz? Sağdaki resmin enlem/boylamını kullanmak ister misiniz?</string>
|
<string name="use_location_from_similar_image">Bu iki fotoğrafı aynı yerde mi çektiniz? Sağdaki resmin enlem/boylamını kullanmak ister misiniz?</string>
|
||||||
|
<string name="nearby_no_results">Yer bulunamadı, arama kriterlerinizi değiştirmeyi deneyin.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,9 @@
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="categories_not_found">Категорій, відповідних %1$s, не знайдено</string>
|
<string name="categories_not_found">Категорій, відповідних %1$s, не знайдено</string>
|
||||||
<string name="depictions_not_found">Не знайдено елементів вікіданих, що відповідають %1$s</string>
|
<string name="depictions_not_found">Не знайдено елементів вікіданих, що відповідають %1$s</string>
|
||||||
|
<string name="no_child_classes">%1$s не має дочірніх класів</string>
|
||||||
|
<string name="no_parent_classes">%1$s не має батьківських класів</string>
|
||||||
|
<string name="depictions_image_not_found">Немає зображення для описування</string>
|
||||||
<string name="categories_skip_explanation">Додайте категорії, щоб Ваші зображення було легше знайти у Вікісховищі. \n\nПочніть вводити текст, щоб додати категорії.</string>
|
<string name="categories_skip_explanation">Додайте категорії, щоб Ваші зображення було легше знайти у Вікісховищі. \n\nПочніть вводити текст, щоб додати категорії.</string>
|
||||||
<string name="categories_activity_title">Категорії</string>
|
<string name="categories_activity_title">Категорії</string>
|
||||||
<string name="title_activity_settings">Налаштування</string>
|
<string name="title_activity_settings">Налаштування</string>
|
||||||
|
|
@ -206,6 +209,7 @@
|
||||||
<string name="no">Ні</string>
|
<string name="no">Ні</string>
|
||||||
<string name="media_detail_caption">Підпис</string>
|
<string name="media_detail_caption">Підпис</string>
|
||||||
<string name="media_detail_title">Назва</string>
|
<string name="media_detail_title">Назва</string>
|
||||||
|
<string name="media_detail_depiction">Описи зображеного</string>
|
||||||
<string name="media_detail_description">Опис</string>
|
<string name="media_detail_description">Опис</string>
|
||||||
<string name="media_detail_discussion">Обговорення</string>
|
<string name="media_detail_discussion">Обговорення</string>
|
||||||
<string name="media_detail_author">Автор</string>
|
<string name="media_detail_author">Автор</string>
|
||||||
|
|
@ -347,9 +351,11 @@
|
||||||
<string name="search_recent_header">Недавні пошуки:</string>
|
<string name="search_recent_header">Недавні пошуки:</string>
|
||||||
<string name="provider_searches">Недавні пошукові запити</string>
|
<string name="provider_searches">Недавні пошукові запити</string>
|
||||||
<string name="error_loading_categories">Сталася помилка під час завантаження категорій.</string>
|
<string name="error_loading_categories">Сталася помилка під час завантаження категорій.</string>
|
||||||
|
<string name="error_loading_depictions">Сталася помилка при завантаженні описів зображеного.</string>
|
||||||
<string name="error_loading_subcategories">Сталася помилка під час завантаження підкатегорій.</string>
|
<string name="error_loading_subcategories">Сталася помилка під час завантаження підкатегорій.</string>
|
||||||
<string name="search_tab_title_media">Медіафайли</string>
|
<string name="search_tab_title_media">Медіафайли</string>
|
||||||
<string name="search_tab_title_categories">Категорії</string>
|
<string name="search_tab_title_categories">Категорії</string>
|
||||||
|
<string name="search_tab_title_depictions">Елементи</string>
|
||||||
<string name="explore_tab_title_featured">Обране</string>
|
<string name="explore_tab_title_featured">Обране</string>
|
||||||
<string name="explore_tab_title_mobile">Завантаження з мобільного</string>
|
<string name="explore_tab_title_mobile">Завантаження з мобільного</string>
|
||||||
<string name="successful_wikidata_edit">Зображення додано до сторінки %1$s у Вікіданих!</string>
|
<string name="successful_wikidata_edit">Зображення додано до сторінки %1$s у Вікіданих!</string>
|
||||||
|
|
@ -455,6 +461,7 @@
|
||||||
<string name="desc_language_Pacific">Океанія</string>
|
<string name="desc_language_Pacific">Океанія</string>
|
||||||
<string name="no_categories_selected">Жодної категорії не вибрано</string>
|
<string name="no_categories_selected">Жодної категорії не вибрано</string>
|
||||||
<string name="no_categories_selected_warning_desc">Зображення без категорій рідко використовуються. Ви впевнені, що хочете продовжити без вказаних категорій?</string>
|
<string name="no_categories_selected_warning_desc">Зображення без категорій рідко використовуються. Ви впевнені, що хочете продовжити без вказаних категорій?</string>
|
||||||
|
<string name="no_depictions_selected">Описів зображеного не вибрано</string>
|
||||||
<string name="no_depictions_selected_warning_desc">Медіа, у яких вказані зображувані об\'єкти, можуть бути легше знайдені та використані надалі. Ви впевнені, що хочете продовжити не вказавши що саме тут зображено?</string>
|
<string name="no_depictions_selected_warning_desc">Медіа, у яких вказані зображувані об\'єкти, можуть бути легше знайдені та використані надалі. Ви впевнені, що хочете продовжити не вказавши що саме тут зображено?</string>
|
||||||
<string name="upload_flow_all_images_in_set">(Для всіх зображень у наборі)</string>
|
<string name="upload_flow_all_images_in_set">(Для всіх зображень у наборі)</string>
|
||||||
<string name="search_this_area">Шукати в цій зоні</string>
|
<string name="search_this_area">Шукати в цій зоні</string>
|
||||||
|
|
@ -555,6 +562,7 @@
|
||||||
<string name="share_via">Поділитися програмкою через…</string>
|
<string name="share_via">Поділитися програмкою через…</string>
|
||||||
<string name="image_info">Інформація про зображення</string>
|
<string name="image_info">Інформація про зображення</string>
|
||||||
<string name="no_categories_found">Категорії не знайдені</string>
|
<string name="no_categories_found">Категорії не знайдені</string>
|
||||||
|
<string name="no_depiction_found">Описів зображеного не знайдено</string>
|
||||||
<string name="upload_cancelled">Завантаження скасовано</string>
|
<string name="upload_cancelled">Завантаження скасовано</string>
|
||||||
<string name="previous_image_title_description_not_found">Відсутній заголовок або опис попереднього зображення</string>
|
<string name="previous_image_title_description_not_found">Відсутній заголовок або опис попереднього зображення</string>
|
||||||
<string name="dialog_box_text_nomination">Чому %1$s має бути видалено?</string>
|
<string name="dialog_box_text_nomination">Чому %1$s має бути видалено?</string>
|
||||||
|
|
@ -587,6 +595,9 @@
|
||||||
<string name="place_type">Тип місця:</string>
|
<string name="place_type">Тип місця:</string>
|
||||||
<string name="nearby_search_hint">Міст, музей, готель тощо</string>
|
<string name="nearby_search_hint">Міст, музей, готель тощо</string>
|
||||||
<string name="you_must_reset_your_passsword">Щось пішло не так із процесом входу, ви маєте скинути пароль !</string>
|
<string name="you_must_reset_your_passsword">Щось пішло не так із процесом входу, ви маєте скинути пароль !</string>
|
||||||
|
<string name="title_for_media">МЕДІА</string>
|
||||||
|
<string name="title_for_child_classes">ДОЧІРНІ КЛАСИ</string>
|
||||||
|
<string name="title_for_parent_classes">БАТЬКІВСЬКІ КЛАСИ</string>
|
||||||
<string name="upload_nearby_place_found_title">Знайдено місце поблизу</string>
|
<string name="upload_nearby_place_found_title">Знайдено місце поблизу</string>
|
||||||
<string name="upload_nearby_place_found_description">Чи це — фото місця %1$s?</string>
|
<string name="upload_nearby_place_found_description">Чи це — фото місця %1$s?</string>
|
||||||
<string name="title_app_shortcut_explore">Дослідити</string>
|
<string name="title_app_shortcut_explore">Дослідити</string>
|
||||||
|
|
@ -605,4 +616,5 @@
|
||||||
<string name="ask_to_turn_location_on">Увімкнути визначення місця розташування?</string>
|
<string name="ask_to_turn_location_on">Увімкнути визначення місця розташування?</string>
|
||||||
<string name="nearby_needs_location">«Поблизу» потребує увімкненого визначення місця розташування, щоб працювати належним чином</string>
|
<string name="nearby_needs_location">«Поблизу» потребує увімкненого визначення місця розташування, щоб працювати належним чином</string>
|
||||||
<string name="use_location_from_similar_image">Ви зробили ці два знімки в одному й тому ж місці? Хочете використати широту/довготу зображення справа?</string>
|
<string name="use_location_from_similar_image">Ви зробили ці два знімки в одному й тому ж місці? Хочете використати широту/довготу зображення справа?</string>
|
||||||
|
<string name="nearby_no_results">Місць не знайдено, спробуйте змінити критерії пошуку.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -603,4 +603,5 @@
|
||||||
<string name="ask_to_turn_location_on">打開位置?</string>
|
<string name="ask_to_turn_location_on">打開位置?</string>
|
||||||
<string name="nearby_needs_location">附近功能需要啟用位置才能運作正常</string>
|
<string name="nearby_needs_location">附近功能需要啟用位置才能運作正常</string>
|
||||||
<string name="use_location_from_similar_image">您是否在同一地點拍攝了這兩張圖片?您要使用圖片右側的緯度/經度嗎?</string>
|
<string name="use_location_from_similar_image">您是否在同一地點拍攝了這兩張圖片?您要使用圖片右側的緯度/經度嗎?</string>
|
||||||
|
<string name="nearby_no_results">查無地點,請嘗試更改您的搜尋條件。</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
* ZhaoGang
|
* ZhaoGang
|
||||||
* 予弦
|
* 予弦
|
||||||
* 佛壁灯
|
* 佛壁灯
|
||||||
|
* 列维劳德
|
||||||
* 夢蝶葬花
|
* 夢蝶葬花
|
||||||
* 沈澄心
|
* 沈澄心
|
||||||
* 神樂坂秀吉
|
* 神樂坂秀吉
|
||||||
|
|
@ -74,6 +75,7 @@
|
||||||
<string name="menu_share">分享</string>
|
<string name="menu_share">分享</string>
|
||||||
<string name="menu_open_in_browser">在浏览器中查看</string>
|
<string name="menu_open_in_browser">在浏览器中查看</string>
|
||||||
<string name="share_title_hint" fuzzy="true">标题 (要求)</string>
|
<string name="share_title_hint" fuzzy="true">标题 (要求)</string>
|
||||||
|
<string name="add_caption_toast">请提供此文件的描述</string>
|
||||||
<string name="share_description_hint">描述</string>
|
<string name="share_description_hint">描述</string>
|
||||||
<string name="login_failed_network">无法登录 - 网络故障</string>
|
<string name="login_failed_network">无法登录 - 网络故障</string>
|
||||||
<string name="login_failed_wrong_credentials">无法登录——请检查您的用户名和密码</string>
|
<string name="login_failed_wrong_credentials">无法登录——请检查您的用户名和密码</string>
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,6 @@
|
||||||
android:summary="@string/use_external_storage_summary"
|
android:summary="@string/use_external_storage_summary"
|
||||||
android:title="@string/use_external_storage" />
|
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
|
<ListPreference
|
||||||
android:key="descriptionDefaultLanguagePref"
|
android:key="descriptionDefaultLanguagePref"
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true"
|
||||||
|
|
|
||||||
23
app/src/test/kotlin/ModelFunctions.kt
Normal file
23
app/src/test/kotlin/ModelFunctions.kt
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import fr.free.nrw.commons.category.CategoryItem
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
|
|
||||||
|
fun depictedItem(
|
||||||
|
name: String = "label",
|
||||||
|
description: String = "desc",
|
||||||
|
imageUrl: String = "",
|
||||||
|
instanceOfs: List<String> = listOf(),
|
||||||
|
commonsCategories: List<String> = listOf(),
|
||||||
|
isSelected: Boolean = false,
|
||||||
|
id: String = "entityId"
|
||||||
|
) = DepictedItem(
|
||||||
|
name = name,
|
||||||
|
description = description,
|
||||||
|
imageUrl = imageUrl,
|
||||||
|
instanceOfs = instanceOfs,
|
||||||
|
commonsCategories = commonsCategories,
|
||||||
|
isSelected = isSelected,
|
||||||
|
id = id
|
||||||
|
)
|
||||||
|
|
||||||
|
fun categoryItem(name: String = "name", selected: Boolean = false) =
|
||||||
|
CategoryItem(name, selected)
|
||||||
|
|
@ -35,7 +35,9 @@ class BookMarkLocationDaoTest {
|
||||||
COLUMN_WIKIDATA_LINK,
|
COLUMN_WIKIDATA_LINK,
|
||||||
COLUMN_COMMONS_LINK,
|
COLUMN_COMMONS_LINK,
|
||||||
COLUMN_LAT,
|
COLUMN_LAT,
|
||||||
COLUMN_LONG)
|
COLUMN_LONG,
|
||||||
|
COLUMN_PIC,
|
||||||
|
COLUMN_DESTROYED)
|
||||||
private val client: ContentProviderClient = mock()
|
private val client: ContentProviderClient = mock()
|
||||||
private val database: SQLiteDatabase = mock()
|
private val database: SQLiteDatabase = mock()
|
||||||
private val captor = argumentCaptor<ContentValues>()
|
private val captor = argumentCaptor<ContentValues>()
|
||||||
|
|
@ -93,6 +95,8 @@ class BookMarkLocationDaoTest {
|
||||||
assertEquals(builder.build().wikipediaLink, it.siteLinks.wikipediaLink)
|
assertEquals(builder.build().wikipediaLink, it.siteLinks.wikipediaLink)
|
||||||
assertEquals(builder.build().wikidataLink, it.siteLinks.wikidataLink)
|
assertEquals(builder.build().wikidataLink, it.siteLinks.wikidataLink)
|
||||||
assertEquals(builder.build().commonsLink, it.siteLinks.commonsLink)
|
assertEquals(builder.build().commonsLink, it.siteLinks.commonsLink)
|
||||||
|
assertEquals("picName",it.pic)
|
||||||
|
assertEquals("placeDestroyed", it.destroyed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -145,7 +149,7 @@ class BookMarkLocationDaoTest {
|
||||||
assertTrue(testObject.updateBookmarkLocation(examplePlaceBookmark))
|
assertTrue(testObject.updateBookmarkLocation(examplePlaceBookmark))
|
||||||
verify(client).insert(eq(BASE_URI), captor.capture())
|
verify(client).insert(eq(BASE_URI), captor.capture())
|
||||||
captor.firstValue.let { cv ->
|
captor.firstValue.let { cv ->
|
||||||
assertEquals(11, cv.size())
|
assertEquals(12, cv.size())
|
||||||
assertEquals(examplePlaceBookmark.name, cv.getAsString(COLUMN_NAME))
|
assertEquals(examplePlaceBookmark.name, cv.getAsString(COLUMN_NAME))
|
||||||
assertEquals(examplePlaceBookmark.longDescription, cv.getAsString(COLUMN_DESCRIPTION))
|
assertEquals(examplePlaceBookmark.longDescription, cv.getAsString(COLUMN_DESCRIPTION))
|
||||||
assertEquals(examplePlaceBookmark.label.text, cv.getAsString(COLUMN_LABEL_TEXT))
|
assertEquals(examplePlaceBookmark.label.text, cv.getAsString(COLUMN_LABEL_TEXT))
|
||||||
|
|
@ -156,6 +160,7 @@ class BookMarkLocationDaoTest {
|
||||||
assertEquals(examplePlaceBookmark.siteLinks.wikidataLink.toString(), cv.getAsString(COLUMN_WIKIDATA_LINK))
|
assertEquals(examplePlaceBookmark.siteLinks.wikidataLink.toString(), cv.getAsString(COLUMN_WIKIDATA_LINK))
|
||||||
assertEquals(examplePlaceBookmark.siteLinks.commonsLink.toString(), cv.getAsString(COLUMN_COMMONS_LINK))
|
assertEquals(examplePlaceBookmark.siteLinks.commonsLink.toString(), cv.getAsString(COLUMN_COMMONS_LINK))
|
||||||
assertEquals(examplePlaceBookmark.pic.toString(), cv.getAsString(COLUMN_PIC))
|
assertEquals(examplePlaceBookmark.pic.toString(), cv.getAsString(COLUMN_PIC))
|
||||||
|
assertEquals(examplePlaceBookmark.destroyed.toString(), cv.getAsString(COLUMN_DESTROYED))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -251,12 +256,18 @@ class BookMarkLocationDaoTest {
|
||||||
verify(database).execSQL(CREATE_TABLE_STATEMENT)
|
verify(database).execSQL(CREATE_TABLE_STATEMENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun migrateTableVersionFrom_v12_to_v13() {
|
||||||
|
onUpdate(database, 12, 13)
|
||||||
|
verify(database).execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_destroyed STRING;")
|
||||||
|
}
|
||||||
|
|
||||||
private fun createCursor(rowCount: Int) = MatrixCursor(columns, rowCount).apply {
|
private fun createCursor(rowCount: Int) = MatrixCursor(columns, rowCount).apply {
|
||||||
|
|
||||||
for (i in 0 until rowCount) {
|
for (i in 0 until rowCount) {
|
||||||
addRow(listOf("placeName", "placeDescription","placeCategory", exampleLabel.text, exampleLabel.icon,
|
addRow(listOf("placeName", "placeDescription","placeCategory", exampleLabel.text, exampleLabel.icon,
|
||||||
exampleUri, builder.build().wikipediaLink, builder.build().wikidataLink, builder.build().commonsLink,
|
exampleUri, builder.build().wikipediaLink, builder.build().wikidataLink, builder.build().commonsLink,
|
||||||
exampleLocation.latitude, exampleLocation.longitude))
|
exampleLocation.latitude, exampleLocation.longitude, "picName", "placeDestroyed"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
package fr.free.nrw.commons.category
|
package fr.free.nrw.commons.category
|
||||||
|
|
||||||
|
import categoryItem
|
||||||
import com.nhaarman.mockitokotlin2.mock
|
import com.nhaarman.mockitokotlin2.mock
|
||||||
import com.nhaarman.mockitokotlin2.whenever
|
import com.nhaarman.mockitokotlin2.whenever
|
||||||
|
import depictedItem
|
||||||
|
import fr.free.nrw.commons.explore.depictions.DepictsClient
|
||||||
import fr.free.nrw.commons.upload.GpsCategoryModel
|
import fr.free.nrw.commons.upload.GpsCategoryModel
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.subjects.BehaviorSubject
|
import io.reactivex.subjects.BehaviorSubject
|
||||||
|
|
@ -36,11 +39,11 @@ class CategoriesModelTest {
|
||||||
|
|
||||||
// Checking if both return "Test"
|
// Checking if both return "Test"
|
||||||
val expectedItems = expectedList.map { CategoryItem(it, false) }
|
val expectedItems = expectedList.map { CategoryItem(it, false) }
|
||||||
categoriesModel.searchAll("tes", emptyList())
|
categoriesModel.searchAll("tes", emptyList(), emptyList())
|
||||||
.test()
|
.test()
|
||||||
.assertValues(expectedItems)
|
.assertValues(expectedItems)
|
||||||
|
|
||||||
categoriesModel.searchAll("Tes", emptyList())
|
categoriesModel.searchAll("Tes", emptyList(), emptyList())
|
||||||
.test()
|
.test()
|
||||||
.assertValues(expectedItems)
|
.assertValues(expectedItems)
|
||||||
}
|
}
|
||||||
|
|
@ -48,6 +51,7 @@ class CategoriesModelTest {
|
||||||
@Test
|
@Test
|
||||||
fun `searchAll with empty search terms creates results from gps, title search & recents`() {
|
fun `searchAll with empty search terms creates results from gps, title search & recents`() {
|
||||||
val gpsCategoryModel: GpsCategoryModel = mock()
|
val gpsCategoryModel: GpsCategoryModel = mock()
|
||||||
|
val depictedItem = depictedItem(commonsCategories = listOf("depictionCategory"))
|
||||||
|
|
||||||
whenever(gpsCategoryModel.categoriesFromLocation)
|
whenever(gpsCategoryModel.categoriesFromLocation)
|
||||||
.thenReturn(BehaviorSubject.createDefault(listOf("gpsCategory")))
|
.thenReturn(BehaviorSubject.createDefault(listOf("gpsCategory")))
|
||||||
|
|
@ -55,13 +59,14 @@ class CategoriesModelTest {
|
||||||
.thenReturn(Observable.just(listOf("titleSearch")))
|
.thenReturn(Observable.just(listOf("titleSearch")))
|
||||||
whenever(categoryDao.recentCategories(25)).thenReturn(listOf("recentCategories"))
|
whenever(categoryDao.recentCategories(25)).thenReturn(listOf("recentCategories"))
|
||||||
CategoriesModel(categoryClient, categoryDao, gpsCategoryModel)
|
CategoriesModel(categoryClient, categoryDao, gpsCategoryModel)
|
||||||
.searchAll("", listOf("tes"))
|
.searchAll("", listOf("tes"), listOf(depictedItem))
|
||||||
.test()
|
.test()
|
||||||
.assertValue(
|
.assertValue(
|
||||||
listOf(
|
listOf(
|
||||||
CategoryItem("gpsCategory", false),
|
categoryItem("depictionCategory"),
|
||||||
CategoryItem("titleSearch", false),
|
categoryItem("gpsCategory"),
|
||||||
CategoryItem("recentCategories", false)
|
categoryItem("titleSearch"),
|
||||||
|
categoryItem("recentCategories")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.lifecycle.MutableLiveData
|
||||||
import androidx.loader.content.CursorLoader
|
import androidx.loader.content.CursorLoader
|
||||||
import androidx.loader.content.Loader
|
import androidx.loader.content.Loader
|
||||||
|
import com.nhaarman.mockitokotlin2.any
|
||||||
import com.nhaarman.mockitokotlin2.mock
|
import com.nhaarman.mockitokotlin2.mock
|
||||||
import com.nhaarman.mockitokotlin2.verify
|
import com.nhaarman.mockitokotlin2.verify
|
||||||
import com.nhaarman.mockitokotlin2.whenever
|
import com.nhaarman.mockitokotlin2.whenever
|
||||||
|
import io.reactivex.Completable
|
||||||
import io.reactivex.Scheduler
|
import io.reactivex.Scheduler
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import io.reactivex.schedulers.TestScheduler
|
import io.reactivex.schedulers.TestScheduler
|
||||||
|
|
@ -17,9 +19,11 @@ import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.ArgumentMatchers
|
import org.mockito.ArgumentMatchers
|
||||||
|
import org.mockito.ArgumentMatchers.*
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The unit test class for ContributionsPresenter
|
* The unit test class for ContributionsPresenter
|
||||||
|
|
@ -42,7 +46,7 @@ class ContributionsPresenterTest {
|
||||||
|
|
||||||
@Rule @JvmField var instantTaskExecutorRule = InstantTaskExecutorRule()
|
@Rule @JvmField var instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||||
|
|
||||||
lateinit var scheduler : Scheduler
|
lateinit var scheduler : TestScheduler
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* initial setup
|
* initial setup
|
||||||
|
|
@ -60,23 +64,13 @@ class ContributionsPresenterTest {
|
||||||
liveData=MutableLiveData()
|
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 presenter actions onDeleteContribution
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun testDeleteContribution() {
|
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)
|
contributionsPresenter.deleteUpload(contribution)
|
||||||
verify(repository).deleteContributionFromDB(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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package fr.free.nrw.commons.explore.depictions
|
||||||
|
|
||||||
|
import com.nhaarman.mockitokotlin2.mock
|
||||||
|
import com.nhaarman.mockitokotlin2.spy
|
||||||
|
import com.nhaarman.mockitokotlin2.verify
|
||||||
|
import io.reactivex.processors.PublishProcessor
|
||||||
|
import org.hamcrest.MatcherAssert.assertThat
|
||||||
|
import org.hamcrest.Matchers.`is`
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Ignore
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
class SearchDepictionsDataSourceFactoryTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var depictsClient: DepictsClient
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var loadingStates: PublishProcessor<LoadingState>
|
||||||
|
private lateinit var factory: SearchDepictionsDataSourceFactory
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
factory = SearchDepictionsDataSourceFactory(depictsClient, "test", loadingStates)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `create returns a dataSource`() {
|
||||||
|
assertThat(
|
||||||
|
factory.create(),
|
||||||
|
`is`(SearchDepictionsDataSource(depictsClient, loadingStates, "test"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore("Rewrite with Mockk constructor mocks")
|
||||||
|
fun `retryFailedRequest invokes method if not null`() {
|
||||||
|
val spyFactory = spy(factory)
|
||||||
|
val dataSource = mock<SearchDepictionsDataSource>()
|
||||||
|
Mockito.doReturn(dataSource).`when`(spyFactory).create()
|
||||||
|
factory.retryFailedRequest()
|
||||||
|
verify(dataSource).retryFailedRequest()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `retryFailedRequest does not invoke method if null`() {
|
||||||
|
factory.retryFailedRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
package fr.free.nrw.commons.explore.depictions
|
||||||
|
|
||||||
|
import androidx.paging.PositionalDataSource
|
||||||
|
import com.nhaarman.mockitokotlin2.*
|
||||||
|
import fr.free.nrw.commons.explore.depictions.LoadingState.*
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
|
import io.reactivex.Single
|
||||||
|
import io.reactivex.plugins.RxJavaPlugins
|
||||||
|
import io.reactivex.processors.PublishProcessor
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
class SearchDepictionsDataSourceTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var depictsClient: DepictsClient
|
||||||
|
|
||||||
|
private lateinit var loadingStates: PublishProcessor<LoadingState>
|
||||||
|
private lateinit var searchDepictionsDataSource: SearchDepictionsDataSource
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
loadingStates = PublishProcessor.create()
|
||||||
|
searchDepictionsDataSource =
|
||||||
|
SearchDepictionsDataSource(depictsClient, loadingStates, "test")
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
RxJavaPlugins.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `loadInitial returns results and emits InitialLoad & Complete`() {
|
||||||
|
val params = PositionalDataSource.LoadInitialParams(0, 1, 2, false)
|
||||||
|
val callback = mock<PositionalDataSource.LoadInitialCallback<DepictedItem>>()
|
||||||
|
whenever(depictsClient.searchForDepictions("test", 1, 0))
|
||||||
|
.thenReturn(Single.just(emptyList()))
|
||||||
|
val testSubscriber = loadingStates.test()
|
||||||
|
searchDepictionsDataSource.loadInitial(params, callback)
|
||||||
|
verify(callback).onResult(emptyList(), 0)
|
||||||
|
testSubscriber.assertValues(InitialLoad, Complete)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `loadInitial onError does not return results and emits InitialLoad & Error`() {
|
||||||
|
val params = PositionalDataSource.LoadInitialParams(0, 1, 2, false)
|
||||||
|
val callback = mock<PositionalDataSource.LoadInitialCallback<DepictedItem>>()
|
||||||
|
whenever(depictsClient.searchForDepictions("test", 1, 0))
|
||||||
|
.thenThrow(RuntimeException())
|
||||||
|
val testSubscriber = loadingStates.test()
|
||||||
|
searchDepictionsDataSource.loadInitial(params, callback)
|
||||||
|
verify(callback, never()).onResult(any(), any())
|
||||||
|
testSubscriber.assertValues(InitialLoad, Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `loadRange returns results and emits Loading & Complete`() {
|
||||||
|
val callback: PositionalDataSource.LoadRangeCallback<DepictedItem> = mock()
|
||||||
|
val params = PositionalDataSource.LoadRangeParams(0, 1)
|
||||||
|
whenever(depictsClient.searchForDepictions("test", params.loadSize, params.startPosition))
|
||||||
|
.thenReturn(Single.just(emptyList()))
|
||||||
|
val testSubscriber = loadingStates.test()
|
||||||
|
searchDepictionsDataSource.loadRange(params, callback)
|
||||||
|
verify(callback).onResult(emptyList())
|
||||||
|
testSubscriber.assertValues(Loading, Complete)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `loadRange onError does not return results and emits Loading & Error`() {
|
||||||
|
val callback: PositionalDataSource.LoadRangeCallback<DepictedItem> = mock()
|
||||||
|
val params = PositionalDataSource.LoadRangeParams(0, 1)
|
||||||
|
whenever(depictsClient.searchForDepictions("test", params.loadSize, params.startPosition))
|
||||||
|
.thenThrow(RuntimeException())
|
||||||
|
val testSubscriber = loadingStates.test()
|
||||||
|
searchDepictionsDataSource.loadRange(params, callback)
|
||||||
|
verify(callback, never()).onResult(any())
|
||||||
|
testSubscriber.assertValues(Loading, Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `retryFailedRequest does nothing when null`() {
|
||||||
|
searchDepictionsDataSource.retryFailedRequest()
|
||||||
|
verifyNoMoreInteractions(depictsClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `retryFailedRequest retries last request`() {
|
||||||
|
val callback: PositionalDataSource.LoadRangeCallback<DepictedItem> = mock()
|
||||||
|
val params = PositionalDataSource.LoadRangeParams(0, 1)
|
||||||
|
whenever(depictsClient.searchForDepictions("test", params.loadSize, params.startPosition))
|
||||||
|
.thenThrow(RuntimeException()).thenReturn(Single.just(emptyList()))
|
||||||
|
val testSubscriber = loadingStates.test()
|
||||||
|
searchDepictionsDataSource.loadRange(params, callback)
|
||||||
|
verify(callback, never()).onResult(any())
|
||||||
|
searchDepictionsDataSource.retryFailedRequest()
|
||||||
|
verify(callback).onResult(emptyList())
|
||||||
|
testSubscriber.assertValues(Loading, Error, Loading, Complete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package fr.free.nrw.commons.explore.depictions
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.paging.PagedList
|
||||||
|
import com.nhaarman.mockitokotlin2.*
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
|
import io.reactivex.processors.PublishProcessor
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
class SearchableDepictionsDataSourceFactoryTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var searchDepictionsDataSourceFactoryFactory: SearchDepictionsDataSourceFactoryFactory
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var liveDataConverter: LiveDataConverter
|
||||||
|
|
||||||
|
private lateinit var factory: SearchableDepictionsDataSourceFactory
|
||||||
|
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
factory = SearchableDepictionsDataSourceFactory(
|
||||||
|
searchDepictionsDataSourceFactoryFactory,
|
||||||
|
liveDataConverter
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `onQueryUpdated emits new liveData`() {
|
||||||
|
val (_, liveData) = expectNewLiveData()
|
||||||
|
factory.searchResults.test()
|
||||||
|
.also { factory.onQueryUpdated("test") }
|
||||||
|
.assertValue(liveData)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `onQueryUpdated invokes livedatconverter with no items emitter`() {
|
||||||
|
val (captor, _) = expectNewLiveData()
|
||||||
|
factory.onQueryUpdated("test")
|
||||||
|
factory.noItemsLoadedEvent.test()
|
||||||
|
.also { captor.firstValue.invoke() }
|
||||||
|
.assertValue(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Just for coverage, no way to really assert this
|
||||||
|
* */
|
||||||
|
@Test
|
||||||
|
fun `retryFailedRequest does nothing without a factory`() {
|
||||||
|
factory.retryFailedRequest()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `retryFailedRequest retries with a factory`() {
|
||||||
|
val (_, _, dataSourceFactory) = expectNewLiveData()
|
||||||
|
factory.onQueryUpdated("test")
|
||||||
|
factory.retryFailedRequest()
|
||||||
|
verify(dataSourceFactory).retryFailedRequest()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun expectNewLiveData(): Triple<KArgumentCaptor<() -> Unit>, LiveData<PagedList<DepictedItem>>, SearchDepictionsDataSourceFactory> {
|
||||||
|
val dataSourceFactory: SearchDepictionsDataSourceFactory = mock()
|
||||||
|
whenever(
|
||||||
|
searchDepictionsDataSourceFactoryFactory.create(
|
||||||
|
"test",
|
||||||
|
factory.loadingStates as PublishProcessor<LoadingState>
|
||||||
|
)
|
||||||
|
).thenReturn(dataSourceFactory)
|
||||||
|
val captor = argumentCaptor<() -> Unit>()
|
||||||
|
val liveData: LiveData<PagedList<DepictedItem>> = mock()
|
||||||
|
whenever(liveDataConverter.convert(eq(dataSourceFactory), captor.capture()))
|
||||||
|
.thenReturn(liveData)
|
||||||
|
return Triple(captor, liveData, dataSourceFactory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package fr.free.nrw.commons.media
|
package fr.free.nrw.commons.media
|
||||||
|
|
||||||
|
import com.nhaarman.mockitokotlin2.whenever
|
||||||
import fr.free.nrw.commons.Media
|
import fr.free.nrw.commons.Media
|
||||||
import fr.free.nrw.commons.utils.CommonsDateUtil
|
import fr.free.nrw.commons.utils.CommonsDateUtil
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
|
|
@ -7,18 +8,16 @@ import junit.framework.Assert.*
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.*
|
import org.mockito.*
|
||||||
import org.mockito.Mockito.`when`
|
|
||||||
import org.mockito.Mockito.mock
|
|
||||||
import org.wikipedia.dataclient.mwapi.ImageDetails
|
import org.wikipedia.dataclient.mwapi.ImageDetails
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryPage
|
import org.wikipedia.dataclient.mwapi.MwQueryPage
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryResult
|
import org.wikipedia.dataclient.mwapi.MwQueryResult
|
||||||
import org.wikipedia.gallery.ImageInfo
|
import org.wikipedia.gallery.ImageInfo
|
||||||
import org.mockito.ArgumentCaptor
|
import org.mockito.ArgumentCaptor
|
||||||
|
import org.mockito.ArgumentMatchers.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import org.mockito.Captor
|
import org.mockito.Captor
|
||||||
|
import org.mockito.Mockito.*
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MediaClientTest {
|
class MediaClientTest {
|
||||||
|
|
@ -46,9 +45,10 @@ class MediaClientTest {
|
||||||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||||
|
|
||||||
`when`(mediaInterface!!.checkPageExistsUsingTitle(ArgumentMatchers.anyString()))
|
`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)
|
assertTrue(checkPageExistsUsingTitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,9 +63,10 @@ class MediaClientTest {
|
||||||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||||
|
|
||||||
`when`(mediaInterface!!.checkPageExistsUsingTitle(ArgumentMatchers.anyString()))
|
`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)
|
assertFalse(checkPageExistsUsingTitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,7 +81,7 @@ class MediaClientTest {
|
||||||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||||
|
|
||||||
`when`(mediaInterface!!.checkFileExistsUsingSha(ArgumentMatchers.anyString()))
|
`when`(mediaInterface!!.checkFileExistsUsingSha(ArgumentMatchers.anyString()))
|
||||||
.thenReturn(Observable.just(mockResponse))
|
.thenReturn(Observable.just(mockResponse))
|
||||||
|
|
||||||
val checkFileExistsUsingSha = mediaClient!!.checkFileExistsUsingSha("abcde").blockingGet()
|
val checkFileExistsUsingSha = mediaClient!!.checkFileExistsUsingSha("abcde").blockingGet()
|
||||||
assertTrue(checkFileExistsUsingSha)
|
assertTrue(checkFileExistsUsingSha)
|
||||||
|
|
@ -97,7 +98,7 @@ class MediaClientTest {
|
||||||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||||
|
|
||||||
`when`(mediaInterface!!.checkFileExistsUsingSha(ArgumentMatchers.anyString()))
|
`when`(mediaInterface!!.checkFileExistsUsingSha(ArgumentMatchers.anyString()))
|
||||||
.thenReturn(Observable.just(mockResponse))
|
.thenReturn(Observable.just(mockResponse))
|
||||||
|
|
||||||
val checkFileExistsUsingSha = mediaClient!!.checkFileExistsUsingSha("abcde").blockingGet()
|
val checkFileExistsUsingSha = mediaClient!!.checkFileExistsUsingSha("abcde").blockingGet()
|
||||||
assertFalse(checkFileExistsUsingSha)
|
assertFalse(checkFileExistsUsingSha)
|
||||||
|
|
@ -117,7 +118,7 @@ class MediaClientTest {
|
||||||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||||
|
|
||||||
`when`(mediaInterface!!.getMedia(ArgumentMatchers.anyString()))
|
`when`(mediaInterface!!.getMedia(ArgumentMatchers.anyString()))
|
||||||
.thenReturn(Observable.just(mockResponse))
|
.thenReturn(Observable.just(mockResponse))
|
||||||
|
|
||||||
assertEquals("Test", mediaClient!!.getMedia("abcde").blockingGet().filename)
|
assertEquals("Test", mediaClient!!.getMedia("abcde").blockingGet().filename)
|
||||||
}
|
}
|
||||||
|
|
@ -136,10 +137,11 @@ class MediaClientTest {
|
||||||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||||
|
|
||||||
`when`(mediaInterface!!.getMedia(ArgumentMatchers.anyString()))
|
`when`(mediaInterface!!.getMedia(ArgumentMatchers.anyString()))
|
||||||
.thenReturn(Observable.just(mockResponse))
|
.thenReturn(Observable.just(mockResponse))
|
||||||
|
|
||||||
assertEquals(Media.EMPTY, mediaClient!!.getMedia("abcde").blockingGet())
|
assertEquals(Media.EMPTY, mediaClient!!.getMedia("abcde").blockingGet())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Captor
|
@Captor
|
||||||
private val filenameCaptor: ArgumentCaptor<String>? = null
|
private val filenameCaptor: ArgumentCaptor<String>? = null
|
||||||
|
|
||||||
|
|
@ -159,18 +161,18 @@ class MediaClientTest {
|
||||||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||||
|
|
||||||
`when`(mediaInterface!!.getMediaWithGenerator(filenameCaptor!!.capture()))
|
`when`(mediaInterface!!.getMediaWithGenerator(filenameCaptor!!.capture()))
|
||||||
.thenReturn(Observable.just(mockResponse))
|
.thenReturn(Observable.just(mockResponse))
|
||||||
|
|
||||||
assertEquals("Test", mediaClient!!.getPictureOfTheDay().blockingGet().filename)
|
assertEquals("Test", mediaClient!!.getPictureOfTheDay().blockingGet().filename)
|
||||||
assertEquals(template, filenameCaptor.value);
|
assertEquals(template, filenameCaptor.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Captor
|
@Captor
|
||||||
private val continuationCaptor: ArgumentCaptor<Map<String, String>>? = null
|
private val continuationCaptor: ArgumentCaptor<Map<String, String>>? = null
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getMediaListFromCategoryTwice() {
|
fun getMediaListFromCategoryTwice() {
|
||||||
val mockContinuation= mapOf(Pair("gcmcontinue", "test"))
|
val mockContinuation = mapOf(Pair("gcmcontinue", "test"))
|
||||||
val imageInfo = ImageInfo()
|
val imageInfo = ImageInfo()
|
||||||
|
|
||||||
val mwQueryPage = mock(MwQueryPage::class.java)
|
val mwQueryPage = mock(MwQueryPage::class.java)
|
||||||
|
|
@ -184,9 +186,13 @@ class MediaClientTest {
|
||||||
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
`when`(mockResponse.query()).thenReturn(mwQueryResult)
|
||||||
`when`(mockResponse.continuation()).thenReturn(mockContinuation)
|
`when`(mockResponse.continuation()).thenReturn(mockContinuation)
|
||||||
|
|
||||||
`when`(mediaInterface!!.getMediaListFromCategory(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt(),
|
`when`(
|
||||||
continuationCaptor!!.capture()))
|
mediaInterface!!.getMediaListFromCategory(
|
||||||
.thenReturn(Observable.just(mockResponse))
|
ArgumentMatchers.anyString(), ArgumentMatchers.anyInt(),
|
||||||
|
continuationCaptor!!.capture()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.thenReturn(Observable.just(mockResponse))
|
||||||
val media1 = mediaClient!!.getMediaListFromCategory("abcde").blockingGet().get(0)
|
val media1 = mediaClient!!.getMediaListFromCategory("abcde").blockingGet().get(0)
|
||||||
val media2 = mediaClient!!.getMediaListFromCategory("abcde").blockingGet().get(0)
|
val media2 = mediaClient!!.getMediaListFromCategory("abcde").blockingGet().get(0)
|
||||||
|
|
||||||
|
|
@ -197,6 +203,38 @@ class MediaClientTest {
|
||||||
assertEquals(media2.filename, "Test")
|
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
|
@Test
|
||||||
fun getPageHtmlTest() {
|
fun getPageHtmlTest() {
|
||||||
val mwParseResult = mock(MwParseResult::class.java)
|
val mwParseResult = mock(MwParseResult::class.java)
|
||||||
|
|
@ -207,7 +245,7 @@ class MediaClientTest {
|
||||||
mockResponse.setParse(mwParseResult)
|
mockResponse.setParse(mwParseResult)
|
||||||
|
|
||||||
`when`(mediaInterface!!.getPageHtml(ArgumentMatchers.anyString()))
|
`when`(mediaInterface!!.getPageHtml(ArgumentMatchers.anyString()))
|
||||||
.thenReturn(Observable.just(mockResponse))
|
.thenReturn(Observable.just(mockResponse))
|
||||||
|
|
||||||
assertEquals("Test", mediaClient!!.getPageHtml("abcde").blockingGet())
|
assertEquals("Test", mediaClient!!.getPageHtml("abcde").blockingGet())
|
||||||
}
|
}
|
||||||
|
|
@ -218,7 +256,7 @@ class MediaClientTest {
|
||||||
mockResponse.setParse(null)
|
mockResponse.setParse(null)
|
||||||
|
|
||||||
`when`(mediaInterface!!.getPageHtml(ArgumentMatchers.anyString()))
|
`when`(mediaInterface!!.getPageHtml(ArgumentMatchers.anyString()))
|
||||||
.thenReturn(Observable.just(mockResponse))
|
.thenReturn(Observable.just(mockResponse))
|
||||||
|
|
||||||
assertEquals("", mediaClient!!.getPageHtml("abcde").blockingGet())
|
assertEquals("", mediaClient!!.getPageHtml("abcde").blockingGet())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package fr.free.nrw.commons.upload
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
|
import categoryItem
|
||||||
import com.nhaarman.mockitokotlin2.*
|
import com.nhaarman.mockitokotlin2.*
|
||||||
import fr.free.nrw.commons.R
|
import fr.free.nrw.commons.R
|
||||||
import fr.free.nrw.commons.category.CategoryItem
|
|
||||||
import fr.free.nrw.commons.repository.UploadRepository
|
import fr.free.nrw.commons.repository.UploadRepository
|
||||||
import fr.free.nrw.commons.upload.categories.CategoriesContract
|
import fr.free.nrw.commons.upload.categories.CategoriesContract
|
||||||
import fr.free.nrw.commons.upload.categories.CategoriesPresenter
|
import fr.free.nrw.commons.upload.categories.CategoriesPresenter
|
||||||
|
|
@ -27,11 +27,6 @@ class CategoriesPresenterTest {
|
||||||
|
|
||||||
private lateinit var testScheduler: TestScheduler
|
private lateinit var testScheduler: TestScheduler
|
||||||
|
|
||||||
private val categoryItems: ArrayList<CategoryItem> = ArrayList()
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
lateinit var categoryItem: CategoryItem
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* initial setup
|
* initial setup
|
||||||
*/
|
*/
|
||||||
|
|
@ -40,7 +35,6 @@ class CategoriesPresenterTest {
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
testScheduler = TestScheduler()
|
testScheduler = TestScheduler()
|
||||||
categoryItems.add(categoryItem)
|
|
||||||
categoriesPresenter = CategoriesPresenter(repository, testScheduler, testScheduler)
|
categoriesPresenter = CategoriesPresenter(repository, testScheduler, testScheduler)
|
||||||
categoriesPresenter.onAttachView(view)
|
categoriesPresenter.onAttachView(view)
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +56,7 @@ class CategoriesPresenterTest {
|
||||||
emptyCaptionUploadItem
|
emptyCaptionUploadItem
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
whenever(repository.searchAll("test", listOf("nonEmpty")))
|
whenever(repository.searchAll("test", listOf("nonEmpty"), repository.selectedDepictions))
|
||||||
.thenReturn(
|
.thenReturn(
|
||||||
Observable.just(
|
Observable.just(
|
||||||
listOf(
|
listOf(
|
||||||
|
|
@ -87,7 +81,7 @@ class CategoriesPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `searchForCategoriesTest sets Error when list is empty`() {
|
fun `searchForCategoriesTest sets Error when list is empty`() {
|
||||||
whenever(repository.uploads).thenReturn(listOf())
|
whenever(repository.uploads).thenReturn(listOf())
|
||||||
whenever(repository.searchAll(any(), any())).thenReturn(Observable.just(listOf()))
|
whenever(repository.searchAll(any(), any(), any())).thenReturn(Observable.just(listOf()))
|
||||||
whenever(repository.selectedCategories).thenReturn(listOf())
|
whenever(repository.selectedCategories).thenReturn(listOf())
|
||||||
categoriesPresenter.searchForCategories("test")
|
categoriesPresenter.searchForCategories("test")
|
||||||
testScheduler.triggerActions()
|
testScheduler.triggerActions()
|
||||||
|
|
@ -124,10 +118,8 @@ class CategoriesPresenterTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun onCategoryItemClickedTest() {
|
fun onCategoryItemClickedTest() {
|
||||||
|
val categoryItem = categoryItem()
|
||||||
categoriesPresenter.onCategoryItemClicked(categoryItem)
|
categoriesPresenter.onCategoryItemClicked(categoryItem)
|
||||||
verify(repository).onCategoryClicked(categoryItem)
|
verify(repository).onCategoryClicked(categoryItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun categoryItem(name: String = "name", selected: Boolean = false) =
|
|
||||||
CategoryItem(name, selected)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||||
import com.jraska.livedata.test
|
import com.jraska.livedata.test
|
||||||
import com.nhaarman.mockitokotlin2.verify
|
import com.nhaarman.mockitokotlin2.verify
|
||||||
import com.nhaarman.mockitokotlin2.whenever
|
import com.nhaarman.mockitokotlin2.whenever
|
||||||
|
import depictedItem
|
||||||
import fr.free.nrw.commons.explore.depictions.DepictsClient
|
import fr.free.nrw.commons.explore.depictions.DepictsClient
|
||||||
import fr.free.nrw.commons.repository.UploadRepository
|
import fr.free.nrw.commons.repository.UploadRepository
|
||||||
import fr.free.nrw.commons.upload.depicts.DepictsContract
|
import fr.free.nrw.commons.upload.depicts.DepictsContract
|
||||||
import fr.free.nrw.commons.upload.depicts.DepictsPresenter
|
import fr.free.nrw.commons.upload.depicts.DepictsPresenter
|
||||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
|
||||||
import fr.free.nrw.commons.wikidata.WikidataDisambiguationItems
|
import fr.free.nrw.commons.wikidata.WikidataDisambiguationItems
|
||||||
import io.reactivex.Flowable
|
import io.reactivex.Flowable
|
||||||
import io.reactivex.schedulers.TestScheduler
|
import io.reactivex.schedulers.TestScheduler
|
||||||
|
|
@ -62,8 +62,8 @@ class DepictsPresenterTest {
|
||||||
depictedItem(id="nonUnique"),
|
depictedItem(id="nonUnique"),
|
||||||
depictedItem(id="nonUnique"),
|
depictedItem(id="nonUnique"),
|
||||||
depictedItem(
|
depictedItem(
|
||||||
id = "unique",
|
instanceOfs = listOf(WikidataDisambiguationItems.CATEGORY.id),
|
||||||
instanceOfs = listOf(WikidataDisambiguationItems.CATEGORY.id)
|
id = "unique"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
whenever(repository.searchAllEntities("")).thenReturn(Flowable.just(searchResults))
|
whenever(repository.searchAllEntities("")).thenReturn(Flowable.just(searchResults))
|
||||||
|
|
@ -78,6 +78,7 @@ class DepictsPresenterTest {
|
||||||
.assertValue(listOf(selectedItem, depictedItem(id="nonUnique")))
|
.assertValue(listOf(selectedItem, depictedItem(id="nonUnique")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `empty search results with empty term do not show error`() {
|
fun `empty search results with empty term do not show error`() {
|
||||||
whenever(repository.searchAllEntities("")).thenReturn(Flowable.just(emptyList()))
|
whenever(repository.searchAllEntities("")).thenReturn(Flowable.just(emptyList()))
|
||||||
|
|
@ -137,15 +138,4 @@ class DepictsPresenterTest {
|
||||||
depictsPresenter.verifyDepictions()
|
depictsPresenter.verifyDepictions()
|
||||||
verify(view).noDepictionSelected()
|
verify(view).noDepictionSelected()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun depictedItem(
|
|
||||||
name: String = "label",
|
|
||||||
description: String = "desc",
|
|
||||||
imageUrl: String = "",
|
|
||||||
instanceOfs: List<String> = listOf(),
|
|
||||||
isSelected: Boolean = false,
|
|
||||||
id: String = "entityId"
|
|
||||||
) = DepictedItem(name, description, imageUrl, instanceOfs, isSelected, id)
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import android.net.ConnectivityManager;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|
@ -28,34 +29,30 @@ public class NetworkUtilsTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInternetConnectionEstablished() {
|
public void testInternetConnectionEstablished() {
|
||||||
Context mockContext = mock(Context.class);
|
Context mockContext = getContext(true);
|
||||||
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);
|
|
||||||
boolean internetConnectionEstablished = NetworkUtils.isInternetConnectionEstablished(mockContext);
|
boolean internetConnectionEstablished = NetworkUtils.isInternetConnectionEstablished(mockContext);
|
||||||
assertTrue(internetConnectionEstablished);
|
assertTrue(internetConnectionEstablished);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@NotNull
|
||||||
public void testInternetConnectionNotEstablished() {
|
public static Context getContext(boolean connectionEstablished) {
|
||||||
Context mockContext = mock(Context.class);
|
Context mockContext = mock(Context.class);
|
||||||
Application mockApplication = mock(Application.class);
|
Application mockApplication = mock(Application.class);
|
||||||
ConnectivityManager mockConnectivityManager = mock(ConnectivityManager.class);
|
ConnectivityManager mockConnectivityManager = mock(ConnectivityManager.class);
|
||||||
NetworkInfo mockNetworkInfo = mock(NetworkInfo.class);
|
NetworkInfo mockNetworkInfo = mock(NetworkInfo.class);
|
||||||
when(mockNetworkInfo.isConnectedOrConnecting())
|
when(mockNetworkInfo.isConnectedOrConnecting())
|
||||||
.thenReturn(false);
|
.thenReturn(connectionEstablished);
|
||||||
when(mockConnectivityManager.getActiveNetworkInfo())
|
when(mockConnectivityManager.getActiveNetworkInfo())
|
||||||
.thenReturn(mockNetworkInfo);
|
.thenReturn(mockNetworkInfo);
|
||||||
when(mockApplication.getSystemService(Context.CONNECTIVITY_SERVICE))
|
when(mockApplication.getSystemService(Context.CONNECTIVITY_SERVICE))
|
||||||
.thenReturn(mockConnectivityManager);
|
.thenReturn(mockConnectivityManager);
|
||||||
when(mockContext.getApplicationContext()).thenReturn(mockApplication);
|
when(mockContext.getApplicationContext()).thenReturn(mockApplication);
|
||||||
|
return mockContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInternetConnectionNotEstablished() {
|
||||||
|
Context mockContext = getContext(false);
|
||||||
boolean internetConnectionEstablished = NetworkUtils.isInternetConnectionEstablished(mockContext);
|
boolean internetConnectionEstablished = NetworkUtils.isInternetConnectionEstablished(mockContext);
|
||||||
assertFalse(internetConnectionEstablished);
|
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