diff --git a/README.md b/README.md
index 06210d055..765935148 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ Initially started by the Wikimedia Foundation, this app is now maintained by gra
## 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]
* [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/
[3]: https://github.com/commons-app/apps-android-commons/issues
-[4]: https://github.com/commons-app/apps-android-commons/wiki
-[5]: https://github.com/commons-app/apps-android-commons/wiki#user-documentation
-[6]: https://github.com/commons-app/apps-android-commons/wiki#contributor-documentation
-[7]: https://github.com/commons-app/apps-android-commons/wiki/Volunteers-welcome%21
-[8]: https://github.com/commons-app/apps-android-commons/wiki#developer-documentation
-[9]: https://github.com/commons-app/apps-android-commons/wiki/Libraries-used
+[4]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#-android-documentation
+[5]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#-user-documentation
+[6]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#️-contributor-documentation
+[7]: https://github.com/commons-app/commons-app-documentation/blob/master/android/Volunteers-welcome!.md#volunteers-welcome
+[8]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#-developer-documentation
+[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
diff --git a/app/build.gradle b/app/build.gradle
index 19a66ab7d..1464de56b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -42,6 +42,7 @@ dependencies {
implementation 'com.dinuscxj:circleprogressbar:1.1.1'
implementation 'com.karumi:dexter:5.0.0'
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
+
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
implementation "com.hannesdorfmann:adapterdelegates4-kotlin-dsl-layoutcontainer:$ADAPTER_DELEGATES_VERSION"
implementation "com.hannesdorfmann:adapterdelegates4-pagination:$ADAPTER_DELEGATES_VERSION"
diff --git a/app/src/main/java/fr/free/nrw/commons/Media.java b/app/src/main/java/fr/free/nrw/commons/Media.java
index c3132d703..4de8afbd2 100644
--- a/app/src/main/java/fr/free/nrw/commons/Media.java
+++ b/app/src/main/java/fr/free/nrw/commons/Media.java
@@ -6,6 +6,7 @@ import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.room.Entity;
+import androidx.room.PrimaryKey;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.media.Depictions;
import fr.free.nrw.commons.utils.CommonsDateUtil;
@@ -15,6 +16,8 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
+import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.wikipedia.dataclient.mwapi.MwQueryPage;
import org.wikipedia.gallery.ExtMetadata;
@@ -50,6 +53,8 @@ public class Media implements Parcelable {
/**
* Wikibase Identifier associated with media files
*/
+ @PrimaryKey
+ @NonNull
private String pageId;
private List categories; // as loaded at runtime?
/**
@@ -64,14 +69,28 @@ public class Media implements Parcelable {
* Provides local constructor
*/
public Media() {
+ pageId = UUID.randomUUID().toString();
}
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public Media createFromParcel(final Parcel source) {
+ return new Media(source);
+ }
+
+ @Override
+ public Media[] newArray(final int size) {
+ return new Media[size];
+ }
+ };
+
/**
* Provides a minimal constructor
*
* @param filename Media filename
*/
- public Media(String filename) {
+ public Media(final String filename) {
+ this();
this.filename = filename;
}
@@ -86,11 +105,13 @@ public class Media implements Parcelable {
* @param dateUploaded Media date uploaded
* @param creator Media creator
*/
- public Media(Uri localUri, String imageUrl, String filename,
- String description,
- long dataLength, Date dateCreated, Date dateUploaded, String creator) {
+ public Media(final Uri localUri, final String imageUrl, final String filename,
+ final String description,
+ final long dataLength, final Date dateCreated, final Date dateUploaded,
+ final String creator) {
+ this();
this.localUri = localUri;
- this.thumbUrl = imageUrl;
+ thumbUrl = imageUrl;
this.imageUrl = imageUrl;
this.filename = filename;
this.description = description;
@@ -100,17 +121,80 @@ public class Media implements Parcelable {
this.creator = creator;
}
- public Media(Uri localUri, String filename,
- String description, String creator, List 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 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 categories) {
this(localUri,null, filename,
description, -1, null, new Date(), creator);
this.categories = categories;
}
- public Media(String title, Date date, String user) {
+ public Media(final String title, final Date date, final String user) {
this(null, null, title, "", -1, date, date, user);
}
+ protected Media(final Parcel in) {
+ localUri = in.readParcelable(Uri.class.getClassLoader());
+ thumbUrl = in.readString();
+ imageUrl = in.readString();
+ filename = in.readString();
+ thumbnailTitle = in.readString();
+ caption = in.readString();
+ description = in.readString();
+ discussion = in.readString();
+ dataLength = in.readLong();
+ final long tmpDateCreated = in.readLong();
+ dateCreated = tmpDateCreated == -1 ? null : new Date(tmpDateCreated);
+ final long tmpDateUploaded = in.readLong();
+ dateUploaded = tmpDateUploaded == -1 ? null : new Date(tmpDateUploaded);
+ license = in.readString();
+ licenseUrl = in.readString();
+ creator = in.readString();
+ pageId = in.readString();
+ final ArrayList list = new ArrayList<>();
+ in.readStringList(list);
+ categories = list;
+ in.readParcelable(Depictions.class.getClassLoader());
+ requestedDeletion = in.readByte() != 0;
+ coordinates = in.readParcelable(LatLng.class.getClassLoader());
+ }
+
/**
* Creating Media object from MWQueryPage.
* Earlier only basic details were set for the media object but going forward,
@@ -120,14 +204,14 @@ public class Media implements Parcelable {
* @return Media object
*/
@NonNull
- public static Media from(MwQueryPage page) {
- ImageInfo imageInfo = page.imageInfo();
+ public static Media from(final MwQueryPage page) {
+ final ImageInfo imageInfo = page.imageInfo();
if (imageInfo == null) {
return new Media(); // null is not allowed
}
- ExtMetadata metadata = imageInfo.getMetadata();
+ final ExtMetadata metadata = imageInfo.getMetadata();
if (metadata == null) {
- Media media = new Media(null, imageInfo.getOriginalUrl(),
+ final Media media = new Media(null, imageInfo.getOriginalUrl(),
page.title(), "", 0, null, null, null);
if (!StringUtils.isBlank(imageInfo.getThumbUrl())) {
media.setThumbUrl(imageInfo.getThumbUrl());
@@ -135,7 +219,7 @@ public class Media implements Parcelable {
return media;
}
- Media media = new Media(null,
+ final Media media = new Media(null,
imageInfo.getOriginalUrl(),
page.title(),
"",
@@ -158,11 +242,12 @@ public class Media implements Parcelable {
media.setDescription(metadata.imageDescription());
media.setCategories(MediaDataExtractorUtil.extractCategoriesFromList(metadata.getCategories()));
- String latitude = metadata.getGpsLatitude();
- String longitude = metadata.getGpsLongitude();
+ final String latitude = metadata.getGpsLatitude();
+ final String longitude = metadata.getGpsLongitude();
if (!StringUtils.isBlank(latitude) && !StringUtils.isBlank(longitude)) {
- LatLng latLng = new LatLng(Double.parseDouble(latitude), Double.parseDouble(longitude), 0);
+ final LatLng latLng = new LatLng(Double.parseDouble(latitude),
+ Double.parseDouble(longitude), 0);
media.setCoordinates(latLng);
}
@@ -175,29 +260,17 @@ public class Media implements Parcelable {
* @param metadata
* @return
*/
- private static String getArtist(ExtMetadata metadata) {
+ private static String getArtist(final ExtMetadata metadata) {
try {
- String artistHtml = metadata.artist();
+ final String artistHtml = metadata.artist();
return artistHtml.substring(artistHtml.indexOf("title=\""), artistHtml.indexOf("\">"))
.replace("title=\"User:", "");
- } catch (Exception ex) {
+ } catch (final Exception ex) {
return "";
}
}
- /**
- * @return pageId for the current media object*/
- public String getPageId() {
- return pageId;
- }
-
- /**
- *sets pageId for the current media object
- */
- public void setPageId(String pageId) {
- this.pageId = pageId;
- }
-
+ @Nullable
public String getThumbUrl() {
return thumbUrl;
}
@@ -210,11 +283,13 @@ public class Media implements Parcelable {
return filename != null ? getPageTitle().getDisplayTextWithoutNamespace().replaceFirst("[.][^.]+$", "") : "";
}
- /**
- * Set Caption(if available) as the thumbnail title of the image
- */
- public void setThumbnailTitle(String title) {
- this.thumbnailTitle = title;
+ @Nullable
+ private static Date safeParseDate(final String dateStr) {
+ try {
+ return CommonsDateUtil.getMediaSimpleDateFormat().parse(dateStr);
+ } catch (final ParseException e) {
+ return null;
+ }
}
/**
@@ -260,19 +335,17 @@ public class Media implements Parcelable {
}
/**
- * Sets the name of the file.
- * @param filename the new name of the file
- */
- public void setFilename(String filename) {
- this.filename = filename;
+ * @return pageId for the current media object*/
+ @NonNull
+ public String getPageId() {
+ return pageId;
}
/**
- * Sets the discussion of the file.
- * @param discussion
+ *sets pageId for the current media object
*/
- public void setDiscussion(String discussion) {
- this.discussion = discussion;
+ public void setPageId(final String pageId) {
+ this.pageId = pageId;
}
/**
@@ -310,11 +383,11 @@ public class Media implements Parcelable {
}
/**
- * Sets the file description.
- * @param description the new description of the file
+ * Sets the name of the file.
+ * @param filename the new name of the file
*/
- public void setDescription(String description) {
- this.description = description;
+ public void setFilename(final String filename) {
+ this.filename = filename;
}
/**
@@ -326,11 +399,11 @@ public class Media implements Parcelable {
}
/**
- * Sets the dataLength of the file.
- * @param dataLength as a long
+ * Sets the discussion of the file.
+ * @param discussion
*/
- public void setDataLength(long dataLength) {
- this.dataLength = dataLength;
+ public void setDiscussion(final String discussion) {
+ this.discussion = discussion;
}
/**
@@ -342,11 +415,11 @@ public class Media implements Parcelable {
}
/**
- * Sets the creation date of the file.
- * @param date creation date as a Date
+ * Sets the file description.
+ * @param description the new description of the file
*/
- public void setDateCreated(Date date) {
- this.dateCreated = date;
+ public void setDescription(final String description) {
+ this.description = description;
}
/**
@@ -368,11 +441,11 @@ public class Media implements Parcelable {
}
/**
- * Sets the creator name of the file.
- * @param creator creator name as a string
+ * Sets the dataLength of the file.
+ * @param dataLength as a long
*/
- public void setCreator(String creator) {
- this.creator = creator;
+ public void setDataLength(final long dataLength) {
+ this.dataLength = dataLength;
}
/**
@@ -383,8 +456,11 @@ public class Media implements Parcelable {
return license;
}
- public void setThumbUrl(String thumbUrl) {
- this.thumbUrl = thumbUrl;
+ /**
+ * Set Caption(if available) as the thumbnail title of the image
+ */
+ public void setThumbnailTitle(final String title) {
+ thumbnailTitle = title;
}
public String getLicenseUrl() {
@@ -392,16 +468,11 @@ public class Media implements Parcelable {
}
/**
- * Sets the license name of the file.
- * @param license license name as a String
+ * Sets the creator name of the file.
+ * @param creator creator name as a string
*/
- public void setLicenseInformation(String license, String licenseUrl) {
- this.license = license;
-
- if (!licenseUrl.startsWith("http://") && !licenseUrl.startsWith("https://")) {
- licenseUrl = "https://" + licenseUrl;
- }
- this.licenseUrl = licenseUrl;
+ public void setCreator(final String creator) {
+ this.creator = creator;
}
/**
@@ -413,12 +484,8 @@ public class Media implements Parcelable {
return coordinates;
}
- /**
- * Sets the coordinates of where the file was created.
- * @param coordinates file coordinates as a LatLng
- */
- public void setCoordinates(@Nullable LatLng coordinates) {
- this.coordinates = coordinates;
+ public void setThumbUrl(final String thumbUrl) {
+ this.thumbUrl = thumbUrl;
}
/**
@@ -430,6 +497,27 @@ public class Media implements Parcelable {
return categories;
}
+ /**
+ * Sets the license name of the file.
+ * @param license license name as a String
+ */
+ public void setLicenseInformation(final String license, String licenseUrl) {
+ this.license = license;
+
+ if (!licenseUrl.startsWith("http://") && !licenseUrl.startsWith("https://")) {
+ licenseUrl = "https://" + licenseUrl;
+ }
+ this.licenseUrl = licenseUrl;
+ }
+
+ /**
+ * Sets the coordinates of where the file was created.
+ * @param coordinates file coordinates as a LatLng
+ */
+ public void setCoordinates(@Nullable final LatLng coordinates) {
+ this.coordinates = coordinates;
+ }
+
/**
* Sets the categories the file falls under.
*
@@ -437,26 +525,10 @@ public class Media implements Parcelable {
* and then add the specified ones.
* @param categories file categories as a list of Strings
*/
- public void setCategories(List categories) {
+ public void setCategories(final List categories) {
this.categories = categories;
}
- @Nullable private static Date safeParseDate(String dateStr) {
- try {
- return CommonsDateUtil.getIso8601DateFormatShort().parse(dateStr);
- } catch (ParseException e) {
- return null;
- }
- }
-
- /**
- * Set requested deletion to true
- * @param requestedDeletion
- */
- public void setRequestedDeletion(boolean requestedDeletion){
- this.requestedDeletion = requestedDeletion;
- }
-
/**
* Get the value of requested deletion
* @return boolean requestedDeletion
@@ -465,12 +537,20 @@ public class Media implements Parcelable {
return requestedDeletion;
}
+ /**
+ * Set requested deletion to true
+ * @param requestedDeletion
+ */
+ public void setRequestedDeletion(final boolean requestedDeletion) {
+ this.requestedDeletion = requestedDeletion;
+ }
+
/**
* Sets the license name of the file.
*
* @param license license name as a String
*/
- public void setLicense(String license) {
+ public void setLicense(final String license) {
this.license = license;
}
@@ -482,15 +562,10 @@ public class Media implements Parcelable {
* This function sets captions
* @param caption
*/
- public void setCaption(String caption) {
+ public void setCaption(final String caption) {
this.caption = caption;
}
- /* Sets depictions for the current media obtained fro Wikibase API*/
- public void setDepictions(Depictions depictions) {
- this.depictions = depictions;
- }
-
public void setLocalUri(@Nullable final Uri localUri) {
this.localUri = localUri;
}
@@ -516,6 +591,19 @@ public class Media implements Parcelable {
return 0;
}
+ /* Sets depictions for the current media obtained fro Wikibase API*/
+ public void setDepictions(final Depictions depictions) {
+ this.depictions = depictions;
+ }
+
+ /**
+ * Sets the creation date of the file.
+ * @param date creation date as a Date
+ */
+ public void setDateCreated(final Date date) {
+ dateCreated = date;
+ }
+
/**
* Creates a way to transfer information between two or more
* activities.
@@ -523,63 +611,70 @@ public class Media implements Parcelable {
* @param flags Parcel flag
*/
@Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelable(this.localUri, flags);
- dest.writeString(this.thumbUrl);
- dest.writeString(this.imageUrl);
- dest.writeString(this.filename);
- dest.writeString(this.thumbnailTitle);
- dest.writeString(this.caption);
- dest.writeString(this.description);
- dest.writeString(this.discussion);
- dest.writeLong(this.dataLength);
- dest.writeLong(this.dateCreated != null ? this.dateCreated.getTime() : -1);
- dest.writeLong(this.dateUploaded != null ? this.dateUploaded.getTime() : -1);
- dest.writeString(this.license);
- dest.writeString(this.licenseUrl);
- dest.writeString(this.creator);
- dest.writeString(this.pageId);
- dest.writeStringList(this.categories);
- dest.writeParcelable(this.depictions, flags);
- dest.writeByte(this.requestedDeletion ? (byte) 1 : (byte) 0);
- dest.writeParcelable(this.coordinates, flags);
+ public void writeToParcel(final Parcel dest, final int flags) {
+ dest.writeParcelable(localUri, flags);
+ dest.writeString(thumbUrl);
+ dest.writeString(imageUrl);
+ dest.writeString(filename);
+ dest.writeString(thumbnailTitle);
+ dest.writeString(caption);
+ dest.writeString(description);
+ dest.writeString(discussion);
+ dest.writeLong(dataLength);
+ dest.writeLong(dateCreated != null ? dateCreated.getTime() : -1);
+ dest.writeLong(dateUploaded != null ? dateUploaded.getTime() : -1);
+ dest.writeString(license);
+ dest.writeString(licenseUrl);
+ dest.writeString(creator);
+ dest.writeString(pageId);
+ dest.writeStringList(categories);
+ dest.writeParcelable(depictions, flags);
+ dest.writeByte(requestedDeletion ? (byte) 1 : (byte) 0);
+ dest.writeParcelable(coordinates, flags);
}
- protected Media(Parcel in) {
- this.localUri = in.readParcelable(Uri.class.getClassLoader());
- this.thumbUrl = in.readString();
- this.imageUrl = in.readString();
- this.filename = in.readString();
- this.thumbnailTitle = in.readString();
- this.caption = in.readString();
- this.description = in.readString();
- this.discussion = in.readString();
- this.dataLength = in.readLong();
- long tmpDateCreated = in.readLong();
- this.dateCreated = tmpDateCreated == -1 ? null : new Date(tmpDateCreated);
- long tmpDateUploaded = in.readLong();
- this.dateUploaded = tmpDateUploaded == -1 ? null : new Date(tmpDateUploaded);
- this.license = in.readString();
- this.licenseUrl = in.readString();
- this.creator = in.readString();
- this.pageId = in.readString();
- final ArrayList list = new ArrayList<>();
- in.readStringList(list);
- this.categories=list;
- in.readParcelable(Depictions.class.getClassLoader());
- this.requestedDeletion = in.readByte() != 0;
- this.coordinates = in.readParcelable(LatLng.class.getClassLoader());
+ /**
+ * Equals implementation that matches all parameters for equality check
+ */
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Media)) {
+ return false;
+ }
+ final Media media = (Media) o;
+ return getDataLength() == media.getDataLength() &&
+ isRequestedDeletion() == media.isRequestedDeletion() &&
+ Objects.equals(getLocalUri(), media.getLocalUri()) &&
+ Objects.equals(getThumbUrl(), media.getThumbUrl()) &&
+ Objects.equals(getImageUrl(), media.getImageUrl()) &&
+ Objects.equals(getFilename(), media.getFilename()) &&
+ Objects.equals(getThumbnailTitle(), media.getThumbnailTitle()) &&
+ Objects.equals(getCaption(), media.getCaption()) &&
+ Objects.equals(getDescription(), media.getDescription()) &&
+ Objects.equals(getDiscussion(), media.getDiscussion()) &&
+ Objects.equals(getDateCreated(), media.getDateCreated()) &&
+ Objects.equals(getDateUploaded(), media.getDateUploaded()) &&
+ Objects.equals(getLicense(), media.getLicense()) &&
+ Objects.equals(getLicenseUrl(), media.getLicenseUrl()) &&
+ Objects.equals(getCreator(), media.getCreator()) &&
+ getPageId().equals(media.getPageId()) &&
+ Objects.equals(getCategories(), media.getCategories()) &&
+ Objects.equals(getDepictions(), media.getDepictions()) &&
+ Objects.equals(getCoordinates(), media.getCoordinates());
}
- public static final Creator CREATOR = new Creator() {
- @Override
- public Media createFromParcel(Parcel source) {
- return new Media(source);
- }
-
- @Override
- public Media[] newArray(int size) {
- return new Media[size];
- }
- };
+ /**
+ * Hashcode implementation that uses all parameters for calculating hash
+ */
+ @Override
+ public int hashCode() {
+ return Objects
+ .hash(getLocalUri(), getThumbUrl(), getImageUrl(), getFilename(), getThumbnailTitle(),
+ getCaption(), getDescription(), getDiscussion(), getDataLength(), getDateCreated(),
+ getDateUploaded(), getLicense(), getLicenseUrl(), getCreator(), getPageId(),
+ getCategories(), getDepictions(), isRequestedDeletion(), getCoordinates());
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.java b/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.java
index b9c10496c..59e33ea24 100644
--- a/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.java
+++ b/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.java
@@ -1,11 +1,6 @@
package fr.free.nrw.commons;
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.IOException;
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(),
CACHE_DIR_NAME), NET_CACHE_SIZE);
- @NonNull private static final OkHttpClient CLIENT = createClient();
+ @NonNull
+ private static final OkHttpClient CLIENT = createClient();
@NonNull public static OkHttpClient getClient() {
return CLIENT;
@@ -45,7 +41,7 @@ public final class OkHttpConnectionFactory {
private static HttpLoggingInterceptor getLoggingInterceptor() {
final HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor()
- .setLevel(Level.BASIC);
+ .setLevel(Level.BASIC);
httpLoggingInterceptor.redactHeader("Authorization");
httpLoggingInterceptor.redactHeader("Cookie");
@@ -54,7 +50,10 @@ public final class OkHttpConnectionFactory {
}
private static class CommonHeaderRequestInterceptor implements Interceptor {
- @Override @NonNull public Response intercept(@NonNull final Chain chain) throws IOException {
+
+ @Override
+ @NonNull
+ public Response intercept(@NonNull final Chain chain) throws IOException {
final Request request = chain.request().newBuilder()
.header("User-Agent", CommonsApplication.getInstance().getUserAgent())
.build();
@@ -66,16 +65,18 @@ public final class OkHttpConnectionFactory {
private static final String ERRORS_PREFIX = "{\"error";
- @Override @NonNull public Response intercept(@NonNull final Chain chain) throws IOException {
+ @Override
+ @NonNull
+ public Response intercept(@NonNull final Chain chain) throws IOException {
final Response rsp = chain.proceed(chain.request());
if (rsp.isSuccessful()) {
try (final ResponseBody responseBody = rsp.peekBody(ERRORS_PREFIX.length())) {
- if (ERRORS_PREFIX.equals(responseBody.string())){
+ if (ERRORS_PREFIX.equals(responseBody.string())) {
try (final ResponseBody body = rsp.body()) {
throw new IOException(body.string());
}
}
- }catch (final IOException e){
+ } catch (final IOException e) {
Timber.e(e);
}
return rsp;
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.java
index 3efef06d4..4b9fe4b55 100644
--- a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.java
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.java
@@ -9,6 +9,7 @@ import android.os.RemoteException;
import androidx.annotation.NonNull;
+import fr.free.nrw.commons.nearby.NearbyController;
import java.util.ArrayList;
import java.util.List;
@@ -74,8 +75,10 @@ public class BookmarkLocationsDao {
boolean bookmarkExists = findBookmarkLocation(bookmarkLocation);
if (bookmarkExists) {
deleteBookmarkLocation(bookmarkLocation);
+ NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, false);
} else {
addBookmarkLocation(bookmarkLocation);
+ NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, true);
}
return !bookmarkExists;
}
@@ -160,10 +163,9 @@ public class BookmarkLocationsDao {
location,
cursor.getString(cursor.getColumnIndex(Table.COLUMN_CATEGORY)),
builder.build(),
- null,
- null
+ cursor.getString(cursor.getColumnIndex(Table.COLUMN_PIC)),
+ cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESTROYED))
);
- // TODO: add pic and destroyed to bookmark location dao
}
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_LONG, bookmarkLocation.location.getLongitude());
cv.put(BookmarkLocationsDao.Table.COLUMN_PIC, bookmarkLocation.pic);
+ cv.put(BookmarkLocationsDao.Table.COLUMN_DESTROYED, bookmarkLocation.destroyed);
return cv;
}
@@ -197,6 +200,7 @@ public class BookmarkLocationsDao {
static final String COLUMN_WIKIDATA_LINK = "location_wikidata_link";
static final String COLUMN_COMMONS_LINK = "location_commons_link";
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.
public static final String[] ALL_FIELDS = {
@@ -211,7 +215,8 @@ public class BookmarkLocationsDao {
COLUMN_WIKIPEDIA_LINK,
COLUMN_WIKIDATA_LINK,
COLUMN_COMMONS_LINK,
- COLUMN_PIC
+ COLUMN_PIC,
+ COLUMN_DESTROYED
};
static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
@@ -228,7 +233,8 @@ public class BookmarkLocationsDao {
+ COLUMN_WIKIPEDIA_LINK + " STRING,"
+ COLUMN_WIKIDATA_LINK + " STRING,"
+ COLUMN_COMMONS_LINK + " STRING,"
- + COLUMN_PIC + " STRING"
+ + COLUMN_PIC + " STRING,"
+ + COLUMN_DESTROYED + " STRING"
+ ");";
public static void onCreate(SQLiteDatabase db) {
@@ -242,37 +248,24 @@ public class BookmarkLocationsDao {
public static void onUpdate(SQLiteDatabase db, int from, int to) {
Timber.d("bookmarksLocations db is updated from:"+from+", to:"+to);
- if (from == to) {
- return;
- }
- if (from < 7) {
- // doesn't exist yet
- from++;
- onUpdate(db, from, to);
- return;
- }
- if (from == 7) {
- // table added in version 8
- onCreate(db);
- from++;
- onUpdate(db, from, to);
- return;
- }
- if (from == 8) {
- from++;
- 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;
+ switch (from) {
+ case 7: onCreate(db);
+ case 8: // No change
+ case 9: // No change
+ case 10:
+ try {
+ db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_pic STRING;");
+ } catch (SQLiteException exception){
+ Timber.e(exception);
+ }
+ case 11: // No change
+ case 12:
+ try {
+ db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_destroyed STRING;");
+ }catch (SQLiteException exception){
+ Timber.e(exception);
+ }
+ break;
}
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.kt
index ae2d0b0fd..48ac1fc7c 100644
--- a/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.kt
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.kt
@@ -2,9 +2,10 @@ package fr.free.nrw.commons.category
import android.text.TextUtils
import fr.free.nrw.commons.upload.GpsCategoryModel
+import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import fr.free.nrw.commons.utils.StringSortingUtils
import io.reactivex.Observable
-import io.reactivex.functions.Function3
+import io.reactivex.functions.Function4
import timber.log.Timber
import java.util.*
import javax.inject.Inject
@@ -67,30 +68,42 @@ class CategoriesModel @Inject constructor(
* @param imageTitleList
* @return
*/
- fun searchAll(term: String, imageTitleList: List): Observable> {
- return suggestionsOrSearch(term, imageTitleList)
+ fun searchAll(
+ term: String,
+ imageTitleList: List,
+ selectedDepictions: List
+ ): Observable> {
+ return suggestionsOrSearch(term, imageTitleList, selectedDepictions)
.map { it.map { CategoryItem(it, false) } }
}
- private fun suggestionsOrSearch(term: String, imageTitleList: List):
- Observable> {
+ private fun suggestionsOrSearch(
+ term: String,
+ imageTitleList: List,
+ selectedDepictions: List
+ ): Observable> {
return if (TextUtils.isEmpty(term))
Observable.combineLatest(
+ categoriesFromDepiction(selectedDepictions),
gpsCategoryModel.categoriesFromLocation,
titleCategories(imageTitleList),
Observable.just(categoryDao.recentCategories(SEARCH_CATS_LIMIT)),
- Function3(::combine)
+ Function4(::combine)
)
else
categoryClient.searchCategoriesForPrefix(term.toLowerCase(), SEARCH_CATS_LIMIT)
.map { it.sortedWith(StringSortingUtils.sortBySimilarity(term)) }
}
+ private fun categoriesFromDepiction(selectedDepictions: List) =
+ Observable.just(selectedDepictions.map { it.commonsCategories }.flatten())
+
private fun combine(
+ depictionCategories: List,
locationCategories: List,
titles: List,
recents: List
- ) = locationCategories + titles + recents
+ ) = depictionCategories + locationCategories + titles + recents
/**
@@ -98,14 +111,13 @@ class CategoriesModel @Inject constructor(
* @param titleList
* @return
*/
- private fun titleCategories(titleList: List): Observable> {
- return if (titleList.isNotEmpty())
+ private fun titleCategories(titleList: List) =
+ if (titleList.isNotEmpty())
Observable.combineLatest(titleList.map { getTitleCategories(it) }) { searchResults ->
searchResults.map { it as List }.flatten()
}
else
Observable.just(emptyList())
- }
/**
* Return category for single title
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java
index 99e6c4b45..5461ccfca 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java
@@ -2,7 +2,6 @@ package fr.free.nrw.commons.contributions;
import android.os.Parcel;
import androidx.room.Entity;
-import androidx.room.PrimaryKey;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.upload.UploadMediaDetail;
@@ -13,7 +12,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import org.wikipedia.dataclient.mwapi.MwQueryLogEvent;
+import java.util.Objects;
@Entity(tableName = "contribution")
public class Contribution extends Media {
@@ -24,24 +23,21 @@ public class Contribution extends Media {
public static final int STATE_QUEUED = 2;
public static final int STATE_IN_PROGRESS = 3;
- @PrimaryKey (autoGenerate = true)
- private long _id;
private int state;
private long transferred;
private String decimalCoords;
private String dateCreatedSource;
private WikidataPlace wikidataPlace;
/**
- * Each depiction loaded in depictions activity is associated with a wikidata entity id,
- * this Id is in turn used to upload depictions to wikibase
+ * Each depiction loaded in depictions activity is associated with a wikidata entity id, this Id
+ * is in turn used to upload depictions to wikibase
*/
private List depictedItems = new ArrayList<>();
private String mimeType;
/**
- * This hasmap stores the list of multilingual captions, where
- * key of the HashMap is the language and value is the caption in the corresponding language
- * Ex: key = "en", value: "
"
- * key = "de" , value: "
"
+ * This hasmap stores the list of multilingual captions, where key of the HashMap is the language
+ * and value is the caption in the corresponding language Ex: key = "en", value: "
" key = "de" , value: "
"
*/
private Map captions = new HashMap<>();
@@ -55,20 +51,13 @@ public class Contribution extends Media {
UploadMediaDetail.formatList(item.getUploadMediaDetails()),
sessionManager.getAuthorName(),
categories);
- captions = UploadMediaDetail.formatCaptions(item.getUploadMediaDetails());
+ captions = UploadMediaDetail.formatCaptions(item.getUploadMediaDetails());
decimalCoords = item.getGpsCoords().getDecimalCoords();
dateCreatedSource = "";
this.depictedItems = depictedItems;
wikidataPlace = WikidataPlace.from(item.getPlace());
}
- public Contribution(final MwQueryLogEvent queryLogEvent, final String user) {
- super(queryLogEvent.title(),queryLogEvent.date(), user);
- decimalCoords = "";
- dateCreatedSource = "";
- state = STATE_COMPLETED;
- }
-
public void setDateCreatedSource(final String dateCreatedSource) {
this.dateCreatedSource = dateCreatedSource;
}
@@ -108,14 +97,6 @@ public class Contribution extends Media {
return wikidataPlace;
}
- public long get_id() {
- return _id;
- }
-
- public void set_id(final long _id) {
- this._id = _id;
- }
-
public String getDecimalCoords() {
return decimalCoords;
}
@@ -128,29 +109,30 @@ public class Contribution extends Media {
this.depictedItems = depictedItems;
}
- public void setMimeType(String mimeType) {
- this.mimeType = mimeType;
+ public String getMimeType() {
+ return mimeType;
}
- public String getMimeType() {
- return mimeType;
+ public void setMimeType(final String mimeType) {
+ this.mimeType = mimeType;
}
/**
- * Captions are a feature part of Structured data. They are meant to store short, multilingual descriptions about files
- * This is a replacement of the previously used titles for images (titles were not multilingual)
- * Also now captions replace the previous convention of using title for filename
- *
+ * Captions are a feature part of Structured data. They are meant to store short, multilingual
+ * descriptions about files This is a replacement of the previously used titles for images (titles
+ * were not multilingual) Also now captions replace the previous convention of using title for
+ * filename
+ *
* key of the HashMap is the language and value is the caption in the corresponding language
- *
+ *
* returns list of captions stored in hashmap
*/
public Map getCaptions() {
- return captions;
+ return captions;
}
public void setCaptions(Map captions) {
- this.captions = captions;
+ this.captions = captions;
}
@Override
@@ -161,7 +143,6 @@ public class Contribution extends Media {
@Override
public void writeToParcel(final Parcel dest, final int flags) {
super.writeToParcel(dest, flags);
- dest.writeLong(_id);
dest.writeInt(state);
dest.writeLong(transferred);
dest.writeString(decimalCoords);
@@ -169,9 +150,24 @@ public class Contribution extends Media {
dest.writeSerializable((HashMap) captions);
}
+ /**
+ * Constructor that takes Media object and state as parameters and builds a new Contribution object
+ * @param media
+ * @param state
+ */
+ public Contribution(Media media, int state) {
+ super(media.getPageId(),
+ media.getLocalUri(), media.getThumbUrl(), media.getImageUrl(), media.getFilename(),
+ media.getDescription(),
+ media.getDiscussion(),
+ media.getDataLength(), media.getDateCreated(), media.getDateUploaded(),
+ media.getLicense(), media.getLicenseUrl(), media.getCreator(), media.getCategories(),
+ media.isRequestedDeletion(), media.getCoordinates());
+ this.state = state;
+ }
+
protected Contribution(final Parcel in) {
super(in);
- _id = in.readLong();
state = in.readInt();
transferred = in.readLong();
decimalCoords = in.readString();
@@ -190,4 +186,35 @@ public class Contribution extends Media {
return new Contribution[size];
}
};
+
+ /**
+ * Equals implementation of Contributions that compares all parameters for checking equality
+ */
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Contribution)) {
+ return false;
+ }
+ final Contribution that = (Contribution) o;
+ return getState() == that.getState() && getTransferred() == that.getTransferred() && Objects
+ .equals(getDecimalCoords(), that.getDecimalCoords()) && Objects
+ .equals(getDateCreatedSource(), that.getDateCreatedSource()) && Objects
+ .equals(getWikidataPlace(), that.getWikidataPlace()) && Objects
+ .equals(getDepictedItems(), that.getDepictedItems()) && Objects
+ .equals(getMimeType(), that.getMimeType()) && Objects
+ .equals(getCaptions(), that.getCaptions());
+ }
+
+ /**
+ * Hash code implementation of contributions that considers all parameters for calculating hash.
+ */
+ @Override
+ public int hashCode() {
+ return Objects
+ .hash(getState(), getTransferred(), getDecimalCoords(), getDateCreatedSource(),
+ getWikidataPlace(), getDepictedItems(), getMimeType(), getCaptions());
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionBoundaryCallback.kt b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionBoundaryCallback.kt
new file mode 100644
index 000000000..ba5d1ed7f
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionBoundaryCallback.kt
@@ -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() {
+ 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 ->
+ 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) {
+ compositeDisposable.add(
+ repository.save(contributions)
+ .subscribeOn(ioThreadScheduler)
+ .subscribe { longs: List? ->
+ repository["last_fetch_timestamp"] = System.currentTimeMillis()
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java
index 3c0ac925f..e288a84af 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java
@@ -1,6 +1,6 @@
package fr.free.nrw.commons.contributions;
-import androidx.lifecycle.LiveData;
+import androidx.paging.DataSource;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
@@ -15,40 +15,55 @@ import java.util.List;
@Dao
public abstract class ContributionDao {
- @Query("SELECT * FROM contribution order by dateUploaded DESC")
- abstract LiveData> fetchContributions();
+ @Query("SELECT * FROM contribution order by dateUploaded DESC")
+ abstract DataSource.Factory fetchContributions();
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- public abstract Single save(Contribution contribution);
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ public abstract void saveSynchronous(Contribution contribution);
- public Completable deleteAllAndSave(List contributions){
- return Completable.fromAction(() -> deleteAllAndSaveTransaction(contributions));
- }
+ public Completable save(final Contribution contribution) {
+ return Completable
+ .fromAction(() -> saveSynchronous(contribution));
+ }
- @Transaction
- public void deleteAllAndSaveTransaction(List contributions){
- deleteAll(Contribution.STATE_COMPLETED);
- save(contributions);
- }
+ @Transaction
+ public void deleteAndSaveContribution(final Contribution oldContribution,
+ final Contribution newContribution) {
+ deleteSynchronous(oldContribution);
+ saveSynchronous(newContribution);
+ }
- @Insert
- public abstract void save(List contribution);
+ public Completable saveAndDelete(final Contribution oldContribution,
+ final Contribution newContribution) {
+ return Completable
+ .fromAction(() -> deleteAndSaveContribution(oldContribution, newContribution));
+ }
- @Delete
- public abstract Single delete(Contribution contribution);
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ public abstract Single> save(List contribution);
- @Query("SELECT * from contribution WHERE filename=:fileName")
- public abstract List getContributionWithTitle(String fileName);
+ @Delete
+ public abstract void deleteSynchronous(Contribution contribution);
- @Query("UPDATE contribution SET state=:state WHERE state in (:toUpdateStates)")
- public abstract Single updateStates(int state, int[] toUpdateStates);
+ public Completable delete(final Contribution contribution) {
+ return Completable
+ .fromAction(() -> deleteSynchronous(contribution));
+ }
- @Query("Delete FROM contribution")
- public abstract void deleteAll();
+ @Query("SELECT * from contribution WHERE filename=:fileName")
+ public abstract List getContributionWithTitle(String fileName);
- @Query("Delete FROM contribution WHERE state = :state")
- public abstract void deleteAll(int state);
+ @Query("UPDATE contribution SET state=:state WHERE state in (:toUpdateStates)")
+ public abstract Single updateStates(int state, int[] toUpdateStates);
- @Update
- public abstract Single update(Contribution contribution);
+ @Query("Delete FROM contribution")
+ public abstract void deleteAll();
+
+ @Update
+ public abstract void updateSynchronous(Contribution contribution);
+
+ public Completable update(final Contribution contribution) {
+ return Completable
+ .fromAction(() -> updateSynchronous(contribution));
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java
index 501f1fe58..a5de53bb1 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java
@@ -22,7 +22,6 @@ import fr.free.nrw.commons.media.MediaClient;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
-import java.util.Random;
import timber.log.Timber;
public class ContributionViewHolder extends RecyclerView.ViewHolder {
@@ -39,23 +38,22 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
private int position;
private Contribution contribution;
- private Random random = new Random();
- private CompositeDisposable compositeDisposable = new CompositeDisposable();
+ private final CompositeDisposable compositeDisposable = new CompositeDisposable();
private final MediaClient mediaClient;
- ContributionViewHolder(View parent, Callback callback,
- MediaClient mediaClient) {
+ ContributionViewHolder(final View parent, final Callback callback,
+ final MediaClient mediaClient) {
super(parent);
this.mediaClient = mediaClient;
ButterKnife.bind(this, parent);
this.callback=callback;
}
- public void init(int position, Contribution contribution) {
+ public void init(final int position, final Contribution contribution) {
this.contribution = contribution;
fetchAndDisplayCaption(contribution);
this.position = position;
- String imageSource = chooseImageSource(contribution.getThumbUrl(), contribution.getLocalUri());
+ final String imageSource = chooseImageSource(contribution.getThumbUrl(), contribution.getLocalUri());
if (!TextUtils.isEmpty(imageSource)) {
final ImageRequest imageRequest =
ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource))
@@ -84,8 +82,8 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
stateView.setVisibility(View.GONE);
progressView.setVisibility(View.VISIBLE);
failedImageOptions.setVisibility(View.GONE);
- long total = contribution.getDataLength();
- long transferred = contribution.getTransferred();
+ final long total = contribution.getDataLength();
+ final long transferred = contribution.getTransferred();
if (transferred == 0 || transferred >= total) {
progressView.setIndeterminate(true);
} else {
@@ -107,14 +105,14 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
*
* @param contribution
*/
- private void fetchAndDisplayCaption(Contribution contribution) {
+ private void fetchAndDisplayCaption(final Contribution contribution) {
if ((contribution.getState() != Contribution.STATE_COMPLETED)) {
titleView.setText(contribution.getDisplayTitle());
} else {
final String pageId = contribution.getPageId();
if (pageId != null) {
Timber.d("Fetching caption for %s", contribution.getFilename());
- String wikibaseMediaId = PAGE_ID_PREFIX
+ final String wikibaseMediaId = PAGE_ID_PREFIX
+ pageId; // Create Wikibase media id from the page id. Example media id: M80618155 for https://commons.wikimedia.org/wiki/File:Tantanmen.jpeg with has the pageid 80618155
compositeDisposable.add(mediaClient.getCaptionByWikibaseIdentifier(wikibaseMediaId)
.subscribeOn(Schedulers.io())
@@ -141,7 +139,7 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
* @return
*/
@Nullable
- private String chooseImageSource(String thumbUrl, Uri localUri) {
+ private String chooseImageSource(final String thumbUrl, final Uri localUri) {
return !TextUtils.isEmpty(thumbUrl) ? thumbUrl :
localUri != null ? localUri.toString() :
null;
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContract.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContract.java
index 8b0049004..d578d1185 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContract.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContract.java
@@ -12,16 +12,6 @@ public class ContributionsContract {
public interface View {
- void showWelcomeTip(boolean numberOfUploads);
-
- void showProgress(boolean shouldShow);
-
- void showNoContributionsUI(boolean shouldShow);
-
- void setUploadCount(int count);
-
- void showContributions(List contributionList);
-
void showMessage(String localizedMessage);
}
@@ -31,8 +21,6 @@ public class ContributionsContract {
void deleteUpload(Contribution contribution);
- Media getItemAtPosition(int i);
-
void updateContribution(Contribution contribution);
void fetchMediaDetails(Contribution contribution);
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java
index 9022bbfb9..8e3221ea9 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java
@@ -19,7 +19,6 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
import androidx.fragment.app.FragmentTransaction;
import butterknife.BindView;
@@ -30,8 +29,7 @@ import fr.free.nrw.commons.campaigns.Campaign;
import fr.free.nrw.commons.campaigns.CampaignView;
import fr.free.nrw.commons.campaigns.CampaignsPresenter;
import fr.free.nrw.commons.campaigns.ICampaignsView;
-import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
-import fr.free.nrw.commons.contributions.ContributionsListFragment.SourceRefresher;
+import fr.free.nrw.commons.contributions.ContributionsListFragment.Callback;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng;
@@ -54,7 +52,6 @@ import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
-import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
@@ -62,11 +59,10 @@ import timber.log.Timber;
public class ContributionsFragment
extends CommonsDaggerSupportFragment
implements
- MediaDetailProvider,
OnBackStackChangedListener,
- SourceRefresher,
LocationUpdateListener,
- ICampaignsView, ContributionsContract.View {
+ MediaDetailProvider,
+ ICampaignsView, ContributionsContract.View, Callback {
@Inject @Named("default_preferences") JsonKvStore store;
@Inject NearbyController nearbyController;
@Inject OkHttpJsonApiClient okHttpJsonApiClient;
@@ -78,8 +74,8 @@ public class ContributionsFragment
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private ContributionsListFragment contributionsListFragment;
- private MediaDetailPagerFragment mediaDetailPagerFragment;
private static final String CONTRIBUTION_LIST_FRAGMENT_TAG = "ContributionListFragmentTag";
+ private MediaDetailPagerFragment mediaDetailPagerFragment;
static final String MEDIA_DETAIL_PAGER_FRAGMENT_TAG = "MediaDetailFragmentTag";
@BindView(R.id.card_view_nearby) public NearbyNotificationCardView nearbyNotificationCardView;
@@ -113,7 +109,6 @@ public class ContributionsFragment
}
};
private boolean shouldShowMediaDetailsFragment;
- private int numberOfContributions;
private boolean isAuthCookieAcquired;
@Override
@@ -128,7 +123,6 @@ public class ContributionsFragment
ButterKnife.bind(this, view);
presenter.onAttachView(this);
contributionsPresenter.onAttachView(this);
- contributionsPresenter.setLifeCycleOwner(this.getViewLifecycleOwner());
campaignView.setVisibility(View.GONE);
checkBoxView = View.inflate(getActivity(), R.layout.nearby_permission_dialog, null);
checkBox = (CheckBox) checkBoxView.findViewById(R.id.never_ask_again);
@@ -141,103 +135,21 @@ public class ContributionsFragment
if (savedInstanceState != null) {
mediaDetailPagerFragment = (MediaDetailPagerFragment) getChildFragmentManager()
- .findFragmentByTag(MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
+ .findFragmentByTag(MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
contributionsListFragment = (ContributionsListFragment) getChildFragmentManager()
- .findFragmentByTag(CONTRIBUTION_LIST_FRAGMENT_TAG);
+ .findFragmentByTag(CONTRIBUTION_LIST_FRAGMENT_TAG);
shouldShowMediaDetailsFragment = savedInstanceState.getBoolean("mediaDetailsVisible");
}
initFragments();
- if(shouldShowMediaDetailsFragment){
- showMediaDetailPagerFragment();
- }else{
- showContributionsListFragment();
- }
-
if (!ConfigUtils.isBetaFlavour()) {
setUploadCount();
}
- getChildFragmentManager().registerFragmentLifecycleCallbacks(
- new FragmentManager.FragmentLifecycleCallbacks() {
- @Override public void onFragmentResumed(FragmentManager fm, Fragment f) {
- super.onFragmentResumed(fm, f);
- //If media detail pager fragment is visible, hide the campaigns view [might not be the best way to do, this but yeah, this proves to work for now]
- Timber.e("onFragmentResumed %s", f.getClass().getName());
- if (f instanceof MediaDetailPagerFragment) {
- campaignView.setVisibility(View.GONE);
- }
- }
-
- @Override public void onFragmentDetached(FragmentManager fm, Fragment f) {
- super.onFragmentDetached(fm, f);
- Timber.e("onFragmentDetached %s", f.getClass().getName());
- //If media detail pager fragment is detached, ContributionsList fragment is gonna be visible, [becomes tightly coupled though]
- if (f instanceof MediaDetailPagerFragment) {
- fetchCampaigns();
- }
- }
- }, true);
-
return view;
}
- /**
- * Initialose the ContributionsListFragment and MediaDetailPagerFragment fragment
- */
- private void initFragments() {
- if (null == contributionsListFragment) {
- contributionsListFragment = new ContributionsListFragment();
- }
-
- contributionsListFragment.setCallback(new Callback() {
- @Override
- public void retryUpload(Contribution contribution) {
- ContributionsFragment.this.retryUpload(contribution);
- }
-
- @Override
- public void deleteUpload(Contribution contribution) {
- contributionsPresenter.deleteUpload(contribution);
- }
-
- @Override
- public void openMediaDetail(int position) {
- showDetail(position);
- }
-
- @Override
- public Contribution getContributionForPosition(int position) {
- return (Contribution) contributionsPresenter.getItemAtPosition(position);
- }
-
- @Override
- public void fetchMediaUriFor(Contribution contribution) {
- Timber.d("Fetching thumbnail for %s", contribution.getFilename());
- contributionsPresenter.fetchMediaDetails(contribution);
- }
- });
-
- if(null==mediaDetailPagerFragment){
- mediaDetailPagerFragment=new MediaDetailPagerFragment();
- }
- }
-
-
- /**
- * Replaces the root frame layout with the given fragment
- * @param fragment
- * @param tag
- */
- private void showFragment(Fragment fragment, String tag) {
- FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
- transaction.replace(R.id.root_frame, fragment, tag);
- transaction.addToBackStack(CONTRIBUTION_LIST_FRAGMENT_TAG);
- transaction.commit();
- getChildFragmentManager().executePendingTransactions();
- }
-
@Override
public void onAttach(Context context) {
super.onAttach(context);
@@ -265,7 +177,7 @@ public class ContributionsFragment
if (nearbyNotificationCardView != null) {
if (store.getBoolean("displayNearbyCardView", true)) {
if (nearbyNotificationCardView.cardViewVisibilityState
- == NearbyNotificationCardView.CardViewVisibilityState.READY) {
+ == NearbyNotificationCardView.CardViewVisibilityState.READY) {
nearbyNotificationCardView.setVisibility(View.VISIBLE);
}
} else {
@@ -275,20 +187,22 @@ public class ContributionsFragment
showFragment(contributionsListFragment, CONTRIBUTION_LIST_FRAGMENT_TAG);
}
- /**
- * Replace FrameLayout with MediaDetailPagerFragment, user will see details of selected media.
- * Creates new one if null.
- */
private void showMediaDetailPagerFragment() {
// hide tabs on media detail view is visible
- ((MainActivity)getActivity()).hideTabs();
+ ((MainActivity) getActivity()).hideTabs();
// hide nearby card view on media detail is visible
nearbyNotificationCardView.setVisibility(View.GONE);
- showFragment(mediaDetailPagerFragment,MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
+ showFragment(mediaDetailPagerFragment, MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
}
+ private void setupViewForMediaDetails() {
+ campaignView.setVisibility(View.GONE);
+ nearbyNotificationCardView.setVisibility(View.GONE);
+ ((MainActivity)getActivity()).hideTabs();
+ }
+
@Override
public void onBackStackChanged() {
((MainActivity)getActivity()).initBackButton();
@@ -307,43 +221,42 @@ public class ContributionsFragment
}
+ private void initFragments() {
+ if (null == contributionsListFragment) {
+ contributionsListFragment = new ContributionsListFragment(this);
+ }
+
+ if (shouldShowMediaDetailsFragment) {
+ showMediaDetailPagerFragment();
+ } else {
+ showContributionsListFragment();
+ }
+
+ showFragment(contributionsListFragment, CONTRIBUTION_LIST_FRAGMENT_TAG);
+ }
+
+ /**
+ * Replaces the root frame layout with the given fragment
+ *
+ * @param fragment
+ * @param tag
+ */
+ private void showFragment(Fragment fragment, String tag) {
+ FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
+ transaction.replace(R.id.root_frame, fragment, tag);
+ transaction.addToBackStack(CONTRIBUTION_LIST_FRAGMENT_TAG);
+ transaction.commit();
+ getChildFragmentManager().executePendingTransactions();
+ }
+
public Intent getUploadServiceIntent(){
Intent intent = new Intent(getActivity(), UploadService.class);
intent.setAction(UploadService.ACTION_START_SERVICE);
return intent;
}
- /**
- * Replace whatever is in the current contributionsFragmentContainer view with
- * mediaDetailPagerFragment, and preserve previous state in back stack.
- * Called when user selects a contribution.
- */
- private void showDetail(int i) {
- if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) {
- mediaDetailPagerFragment = new MediaDetailPagerFragment();
- showMediaDetailPagerFragment();
- }
- mediaDetailPagerFragment.showImage(i);
- }
-
- @Override
- public void refreshSource() {
- contributionsPresenter.fetchContributions();
- }
-
- @Override
- public Media getMediaAtPosition(int i) {
- return contributionsPresenter.getItemAtPosition(i);
- }
-
- @Override
- public int getTotalMediaCount() {
- return numberOfContributions;
- }
-
@SuppressWarnings("ConstantConditions")
private void setUploadCount() {
-
compositeDisposable.add(okHttpJsonApiClient
.getUploadCount(((MainActivity)getActivity()).sessionManager.getCurrentAccount().name)
.subscribeOn(Schedulers.io())
@@ -373,8 +286,6 @@ public class ContributionsFragment
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- boolean mediaDetailsVisible = mediaDetailPagerFragment != null && mediaDetailPagerFragment.isVisible();
- outState.putBoolean("mediaDetailsVisible", mediaDetailsVisible);
}
@Override
@@ -384,13 +295,6 @@ public class ContributionsFragment
firstLocationUpdate = true;
locationManager.addLocationListener(this);
- boolean isSettingsChanged = store.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
- store.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
- if (isSettingsChanged) {
- refreshSource();
- }
-
-
if (store.getBoolean("displayNearbyCardView", true)) {
checkPermissionsAndShowNearbyCardView();
if (nearbyNotificationCardView.cardViewVisibilityState == NearbyNotificationCardView.CardViewVisibilityState.READY) {
@@ -403,10 +307,6 @@ public class ContributionsFragment
}
fetchCampaigns();
- if(isAuthCookieAcquired){
- contributionsPresenter.fetchContributions();
- }
-
}
private void checkPermissionsAndShowNearbyCardView() {
@@ -463,17 +363,11 @@ public class ContributionsFragment
}
private void updateNearbyNotification(@Nullable NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
-
if (nearbyPlacesInfo != null && nearbyPlacesInfo.placeList != null && nearbyPlacesInfo.placeList.size() > 0) {
Place closestNearbyPlace = nearbyPlacesInfo.placeList.get(0);
String distance = formatDistanceBetween(curLatLng, closestNearbyPlace.location);
closestNearbyPlace.setDistance(distance);
nearbyNotificationCardView.updateContent(closestNearbyPlace);
- if (mediaDetailPagerFragment != null && mediaDetailPagerFragment.isVisible()) {
- nearbyNotificationCardView.setVisibility(View.GONE);
- }else {
- nearbyNotificationCardView.setVisibility(View.VISIBLE);
- }
} else {
// Means that no close nearby place is found
nearbyNotificationCardView.setVisibility(View.GONE);
@@ -553,37 +447,13 @@ public class ContributionsFragment
presenter.onDetachView();
}
- @Override
- public void showWelcomeTip(boolean shouldShow) {
- contributionsListFragment.showWelcomeTip(shouldShow);
- }
-
- @Override
- public void showProgress(boolean shouldShow) {
- contributionsListFragment.showProgress(shouldShow);
- }
-
- @Override
- public void showNoContributionsUI(boolean shouldShow) {
- contributionsListFragment.showNoContributionsUI(shouldShow);
- }
-
- @Override
- public void setUploadCount(int count) {
- this.numberOfContributions=count;
- }
-
- @Override
- public void showContributions(List contributionList) {
- contributionsListFragment.setContributions(contributionList);
- }
-
/**
* Retry upload when it is failed
*
* @param contribution contribution to be retried
*/
- private void retryUpload(Contribution contribution) {
+ @Override
+ public void retryUpload(Contribution contribution) {
if (NetworkUtils.isInternetConnectionEstablished(getContext())) {
if (contribution.getState() == STATE_FAILED && null != uploadService) {
uploadService.queue(contribution);
@@ -596,5 +466,29 @@ public class ContributionsFragment
}
}
+
+ /**
+ * Replace whatever is in the current contributionsFragmentContainer view with
+ * mediaDetailPagerFragment, and preserve previous state in back stack. Called when user selects a
+ * contribution.
+ */
+ @Override
+ public void showDetail(int position) {
+ if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) {
+ mediaDetailPagerFragment = new MediaDetailPagerFragment();
+ showMediaDetailPagerFragment();
+ }
+ mediaDetailPagerFragment.showImage(position);
+ }
+
+ @Override
+ public Media getMediaAtPosition(int i) {
+ return contributionsListFragment.getMediaAtPosition(i);
+ }
+
+ @Override
+ public int getTotalMediaCount() {
+ return contributionsListFragment.getTotalMediaCount();
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java
index 82daf97f4..a0c8e1088 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java
@@ -1,68 +1,71 @@
-package fr.free.nrw.commons.contributions;
+ package fr.free.nrw.commons.contributions;
-import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
+import androidx.paging.PagedListAdapter;
+import androidx.recyclerview.widget.DiffUtil;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.media.MediaClient;
-import java.util.ArrayList;
-import java.util.List;
/**
- * Represents The View Adapter for the List of Contributions
+ * Represents The View Adapter for the List of Contributions
*/
-public class ContributionsListAdapter extends RecyclerView.Adapter {
+public class ContributionsListAdapter extends
+ PagedListAdapter {
- private Callback callback;
+ private final Callback callback;
private final MediaClient mediaClient;
- private List contributions;
- public ContributionsListAdapter(Callback callback,
- MediaClient mediaClient) {
+ ContributionsListAdapter(final Callback callback,
+ final MediaClient mediaClient) {
+ super(DIFF_CALLBACK);
this.callback = callback;
this.mediaClient = mediaClient;
- contributions = new ArrayList<>();
}
/**
- * Creates the new View Holder which will be used to display items(contributions)
- * using the onBindViewHolder(viewHolder,position)
+ * Uses DiffUtil to calculate the changes in the list
+ * It has methods that check ID and the content of the items to determine if its a new item
*/
- @NonNull
- @Override
- public ContributionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- ContributionViewHolder viewHolder = new ContributionViewHolder(
- LayoutInflater.from(parent.getContext())
- .inflate(R.layout.layout_contribution, parent, false), callback, mediaClient);
- return viewHolder;
- }
+ private static final DiffUtil.ItemCallback DIFF_CALLBACK =
+ new DiffUtil.ItemCallback() {
+ @Override
+ public boolean areItemsTheSame(final Contribution oldContribution, final Contribution newContribution) {
+ return oldContribution.getPageId().equals(newContribution.getPageId());
+ }
- @Override
- public void onBindViewHolder(@NonNull ContributionViewHolder holder, int position) {
- final Contribution contribution = contributions.get(position);
- if (TextUtils.isEmpty(contribution.getThumbUrl())
- && contribution.getState() == Contribution.STATE_COMPLETED) {
- callback.fetchMediaUriFor(contribution);
- }
+ @Override
+ public boolean areContentsTheSame(final Contribution oldContribution, final Contribution newContribution) {
+ return oldContribution.equals(newContribution);
+ }
+ };
+ /**
+ * Initializes the view holder with contribution data
+ */
+ @Override
+ public void onBindViewHolder(@NonNull final ContributionViewHolder holder, final int position) {
+ final Contribution contribution = getItem(position);
holder.init(position, contribution);
}
- @Override
- public int getItemCount() {
- return contributions.size();
- }
-
- public void setContributions(@NonNull List contributionList) {
- contributions = contributionList;
- notifyDataSetChanged();
+ Contribution getContributionForPosition(final int position) {
+ return getItem(position);
}
+ /**
+ * Creates the new View Holder which will be used to display items(contributions) using the
+ * onBindViewHolder(viewHolder,position)
+ */
+ @NonNull
@Override
- public long getItemId(int position) {
- return contributions.get(position).get_id();
+ public ContributionViewHolder onCreateViewHolder(@NonNull final ViewGroup parent,
+ final int viewType) {
+ final ContributionViewHolder viewHolder = new ContributionViewHolder(
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.layout_contribution, parent, false), callback, mediaClient);
+ return viewHolder;
}
public interface Callback {
@@ -72,9 +75,5 @@ public class ContributionsListAdapter extends RecyclerView.Adapter {
+
+ void deleteUpload(Contribution contribution);
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
index d7f813c6d..d707409e4 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
@@ -5,6 +5,7 @@ import static android.view.View.VISIBLE;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.os.Parcelable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -16,218 +17,217 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
-import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.LayoutManager;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
+import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
-import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.media.MediaClient;
-import java.util.ArrayList;
-import java.util.List;
+import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import javax.inject.Inject;
-import javax.inject.Named;
/**
* Created by root on 01.06.2018.
*/
-public class ContributionsListFragment extends CommonsDaggerSupportFragment {
+public class ContributionsListFragment extends CommonsDaggerSupportFragment implements
+ ContributionsListContract.View, ContributionsListAdapter.Callback {
- private static final String VISIBLE_ITEM_ID = "visible_item_id";
- @BindView(R.id.contributionsList)
- RecyclerView rvContributionsList;
- @BindView(R.id.loadingContributionsProgressBar)
- ProgressBar progressBar;
- @BindView(R.id.fab_plus)
- FloatingActionButton fabPlus;
- @BindView(R.id.fab_camera)
- FloatingActionButton fabCamera;
- @BindView(R.id.fab_gallery)
- FloatingActionButton fabGallery;
- @BindView(R.id.noContributionsYet)
- TextView noContributionsYet;
- @BindView(R.id.fab_layout)
- LinearLayout fab_layout;
+ private static final String RV_STATE = "rv_scroll_state";
- @Inject @Named("default_preferences") JsonKvStore kvStore;
- @Inject ContributionController controller;
- @Inject MediaClient mediaClient;
+ @BindView(R.id.contributionsList)
+ RecyclerView rvContributionsList;
+ @BindView(R.id.loadingContributionsProgressBar)
+ ProgressBar progressBar;
+ @BindView(R.id.fab_plus)
+ FloatingActionButton fabPlus;
+ @BindView(R.id.fab_camera)
+ FloatingActionButton fabCamera;
+ @BindView(R.id.fab_gallery)
+ FloatingActionButton fabGallery;
+ @BindView(R.id.noContributionsYet)
+ TextView noContributionsYet;
+ @BindView(R.id.fab_layout)
+ LinearLayout fab_layout;
- private Animation fab_close;
- private Animation fab_open;
- private Animation rotate_forward;
- private Animation rotate_backward;
+ @Inject
+ ContributionController controller;
+ @Inject
+ MediaClient mediaClient;
+
+ @Inject
+ ContributionsListPresenter contributionsListPresenter;
+
+ private Animation fab_close;
+ private Animation fab_open;
+ private Animation rotate_forward;
+ private Animation rotate_backward;
- private boolean isFabOpen = false;
+ private boolean isFabOpen;
- private ContributionsListAdapter adapter;
+ private ContributionsListAdapter adapter;
- private Callback callback;
- private String lastVisibleItemID;
+ private final Callback callback;
- private int SPAN_COUNT=3;
- private List contributions=new ArrayList<>();
+ private final int SPAN_COUNT_LANDSCAPE = 3;
+ private final int SPAN_COUNT_PORTRAIT = 1;
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_contributions_list, container, false);
- ButterKnife.bind(this, view);
- initAdapter();
- return view;
+ ContributionsListFragment(final Callback callback) {
+ this.callback = callback;
+ }
+
+ public View onCreateView(
+ final LayoutInflater inflater, @Nullable final ViewGroup container,
+ @Nullable final Bundle savedInstanceState) {
+ final View view = inflater.inflate(R.layout.fragment_contributions_list, container, false);
+ ButterKnife.bind(this, view);
+ contributionsListPresenter.onAttachView(this);
+ initAdapter();
+ return view;
+ }
+
+ private void initAdapter() {
+ adapter = new ContributionsListAdapter(this, mediaClient);
+ }
+
+ @Override
+ public void onViewCreated(final View view, @Nullable final Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ initRecyclerView();
+ initializeAnimations();
+ setListeners();
+ }
+
+ private void initRecyclerView() {
+ final GridLayoutManager layoutManager = new GridLayoutManager(getContext(),
+ getSpanCount(getResources().getConfiguration().orientation));
+ rvContributionsList.setLayoutManager(layoutManager);
+ contributionsListPresenter.setup();
+ contributionsListPresenter.contributionList.observe(this, adapter::submitList);
+ rvContributionsList.setAdapter(adapter);
+ }
+
+ private int getSpanCount(final int orientation) {
+ return orientation == Configuration.ORIENTATION_LANDSCAPE ?
+ SPAN_COUNT_LANDSCAPE : SPAN_COUNT_PORTRAIT;
+ }
+
+ @Override
+ public void onConfigurationChanged(final Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ // check orientation
+ fab_layout.setOrientation(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ?
+ LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
+ rvContributionsList
+ .setLayoutManager(new GridLayoutManager(getContext(), getSpanCount(newConfig.orientation)));
+ }
+
+ private void initializeAnimations() {
+ fab_open = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_open);
+ fab_close = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_close);
+ rotate_forward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_forward);
+ rotate_backward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_backward);
+ }
+
+ private void setListeners() {
+ fabPlus.setOnClickListener(view -> animateFAB(isFabOpen));
+ fabCamera.setOnClickListener(view -> {
+ controller.initiateCameraPick(getActivity());
+ animateFAB(isFabOpen);
+ });
+ fabGallery.setOnClickListener(view -> {
+ controller.initiateGalleryPick(getActivity(), true);
+ animateFAB(isFabOpen);
+ });
+ }
+
+ private void animateFAB(final boolean isFabOpen) {
+ this.isFabOpen = !isFabOpen;
+ if (fabPlus.isShown()) {
+ if (isFabOpen) {
+ fabPlus.startAnimation(rotate_backward);
+ fabCamera.startAnimation(fab_close);
+ fabGallery.startAnimation(fab_close);
+ fabCamera.hide();
+ fabGallery.hide();
+ } else {
+ fabPlus.startAnimation(rotate_forward);
+ fabCamera.startAnimation(fab_open);
+ fabGallery.startAnimation(fab_open);
+ fabCamera.show();
+ fabGallery.show();
+ }
+ this.isFabOpen = !isFabOpen;
}
+ }
- public void setCallback(Callback callback) {
- this.callback = callback;
+ /**
+ * Shows welcome message if user has no contributions yet i.e. new user.
+ */
+ public void showWelcomeTip(final boolean shouldShow) {
+ noContributionsYet.setVisibility(shouldShow ? VISIBLE : GONE);
+ }
+
+ /**
+ * Responsible to set progress bar invisible and visible
+ *
+ * @param shouldShow True when contributions list should be hidden.
+ */
+ public void showProgress(final boolean shouldShow) {
+ progressBar.setVisibility(shouldShow ? VISIBLE : GONE);
+ }
+
+ public void showNoContributionsUI(final boolean shouldShow) {
+ noContributionsYet.setVisibility(shouldShow ? VISIBLE : GONE);
+ }
+
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ final GridLayoutManager layoutManager = (GridLayoutManager) rvContributionsList.getLayoutManager();
+ outState.putParcelable(RV_STATE, layoutManager.onSaveInstanceState());
+ }
+
+ @Override
+ public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
+ super.onViewStateRestored(savedInstanceState);
+ if (null != savedInstanceState) {
+ final Parcelable savedRecyclerLayoutState = savedInstanceState.getParcelable(RV_STATE);
+ rvContributionsList.getLayoutManager().onRestoreInstanceState(savedRecyclerLayoutState);
}
+ }
- private void initAdapter() {
- adapter = new ContributionsListAdapter(callback, mediaClient);
- adapter.setHasStableIds(true);
- }
+ @Override
+ public void retryUpload(final Contribution contribution) {
+ callback.retryUpload(contribution);
+ }
- @Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- initRecyclerView();
- initializeAnimations();
- setListeners();
- }
+ @Override
+ public void deleteUpload(final Contribution contribution) {
+ contributionsListPresenter.deleteUpload(contribution);
+ }
- private void initRecyclerView() {
- if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
- rvContributionsList.setLayoutManager(new GridLayoutManager(getContext(),SPAN_COUNT));
- } else {
- rvContributionsList.setLayoutManager(new LinearLayoutManager(getContext()));
- }
+ @Override
+ public void openMediaDetail(final int position) {
+ callback.showDetail(position);
+ }
- rvContributionsList.setAdapter(adapter);
- adapter.setContributions(contributions);
- }
+ public Media getMediaAtPosition(final int i) {
+ return adapter.getContributionForPosition(i);
+ }
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- // check orientation
- if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
- fab_layout.setOrientation(LinearLayout.HORIZONTAL);
- rvContributionsList.setLayoutManager(new GridLayoutManager(getContext(),SPAN_COUNT));
- } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
- fab_layout.setOrientation(LinearLayout.VERTICAL);
- rvContributionsList.setLayoutManager(new LinearLayoutManager(getContext()));
- }
- }
+ public int getTotalMediaCount() {
+ return adapter.getItemCount();
+ }
- private void initializeAnimations() {
- fab_open = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_open);
- fab_close = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_close);
- rotate_forward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_forward);
- rotate_backward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_backward);
- }
+ public interface Callback {
- private void setListeners() {
- fabPlus.setOnClickListener(view -> animateFAB(isFabOpen));
- fabCamera.setOnClickListener(view -> {
- controller.initiateCameraPick(getActivity());
- animateFAB(isFabOpen);
- });
- fabGallery.setOnClickListener(view -> {
- controller.initiateGalleryPick(getActivity(), true);
- animateFAB(isFabOpen);
- });
- }
-
- private void animateFAB(boolean isFabOpen) {
- this.isFabOpen = !isFabOpen;
- if (fabPlus.isShown()){
- if (isFabOpen) {
- fabPlus.startAnimation(rotate_backward);
- fabCamera.startAnimation(fab_close);
- fabGallery.startAnimation(fab_close);
- fabCamera.hide();
- fabGallery.hide();
- } else {
- fabPlus.startAnimation(rotate_forward);
- fabCamera.startAnimation(fab_open);
- fabGallery.startAnimation(fab_open);
- fabCamera.show();
- fabGallery.show();
- }
- this.isFabOpen=!isFabOpen;
- }
- }
-
- /**
- * Shows welcome message if user has no contributions yet i.e. new user.
- */
- public void showWelcomeTip(boolean shouldShow) {
- noContributionsYet.setVisibility(shouldShow ? VISIBLE : GONE);
- }
-
- /**
- * Responsible to set progress bar invisible and visible
- *
- * @param shouldShow True when contributions list should be hidden.
- */
- public void showProgress(boolean shouldShow) {
- progressBar.setVisibility(shouldShow ? VISIBLE : GONE);
- }
-
- public void showNoContributionsUI(boolean shouldShow) {
- noContributionsYet.setVisibility(shouldShow ? VISIBLE : GONE);
- }
-
- public void setContributions(List contributionList) {
- this.contributions.clear();
- this.contributions.addAll(contributionList);
- adapter.setContributions(contributions);
- }
-
- public interface SourceRefresher {
- void refreshSource();
- }
-
- @Override
- public void onSaveInstanceState(@NonNull Bundle outState) {
- super.onSaveInstanceState(outState);
- LayoutManager layoutManager = rvContributionsList.getLayoutManager();
- int lastVisibleItemPosition=0;
- if(layoutManager instanceof LinearLayoutManager){
- lastVisibleItemPosition= ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition();
- }else if(layoutManager instanceof GridLayoutManager){
- lastVisibleItemPosition=((GridLayoutManager)layoutManager).findLastCompletelyVisibleItemPosition();
- }
- String idOfItemWithPosition = findIdOfItemWithPosition(lastVisibleItemPosition);
- if (null != idOfItemWithPosition) {
- outState.putString(VISIBLE_ITEM_ID, idOfItemWithPosition);
- }
- }
-
- @Override
- public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
- super.onViewStateRestored(savedInstanceState);
- if(null!=savedInstanceState){
- lastVisibleItemID =savedInstanceState.getString(VISIBLE_ITEM_ID, null);
- }
- }
-
-
- /**
- * Gets the id of the contribution from the db
- * @param position
- * @return
- */
- @Nullable
- private String findIdOfItemWithPosition(int position) {
- Contribution contributionForPosition = callback.getContributionForPosition(position);
- if (null != contributionForPosition) {
- return contributionForPosition.getFilename();
- }
- return null;
- }
+ void retryUpload(Contribution contribution);
+ void showDetail(int position);
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListPresenter.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListPresenter.java
new file mode 100644
index 000000000..f09c5f205
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListPresenter.java
@@ -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> 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());
+ }
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsLocalDataSource.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsLocalDataSource.java
index c16862ea2..3b94d0fa7 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsLocalDataSource.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsLocalDataSource.java
@@ -1,14 +1,13 @@
package fr.free.nrw.commons.contributions;
-import androidx.lifecycle.LiveData;
-
+import androidx.paging.DataSource.Factory;
+import io.reactivex.Completable;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import fr.free.nrw.commons.kvstore.JsonKvStore;
-import io.reactivex.Completable;
import io.reactivex.Single;
/**
@@ -59,23 +58,23 @@ class ContributionsLocalDataSource {
* @param contribution
* @return
*/
- public Single deleteContribution(Contribution contribution) {
+ public Completable deleteContribution(Contribution contribution) {
return contributionDao.delete(contribution);
}
- public LiveData> getContributions() {
+ public Factory getContributions() {
return contributionDao.fetchContributions();
}
- public Completable saveContributions(List contributions) {
- return contributionDao.deleteAllAndSave(contributions);
+ public Single> saveContributions(List contributions) {
+ return contributionDao.save(contributions);
}
public void set(String key, long value) {
defaultKVStore.putLong(key,value);
}
- public Single updateContribution(Contribution contribution) {
+ public Completable updateContribution(Contribution contribution) {
return contributionDao.update(contribution);
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsPresenter.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsPresenter.java
index 310699cd1..b4725fed8 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsPresenter.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsPresenter.java
@@ -9,8 +9,8 @@ import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.MediaDataExtractor;
import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.MediaDataExtractor;
import fr.free.nrw.commons.contributions.ContributionsContract.UserActionListener;
-import fr.free.nrw.commons.db.AppDatabase;
import fr.free.nrw.commons.di.CommonsApplicationModule;
import fr.free.nrw.commons.mwapi.UserClient;
import fr.free.nrw.commons.utils.NetworkUtils;
@@ -25,6 +25,9 @@ import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
+import javax.inject.Inject;
+import javax.inject.Named;
+
/**
* The presenter class for Contributions
*/
@@ -35,25 +38,10 @@ public class ContributionsPresenter implements UserActionListener {
private final Scheduler ioThreadScheduler;
private CompositeDisposable compositeDisposable;
private ContributionsContract.View view;
- private List contributionList=new ArrayList<>();
-
- @Inject
- Context context;
-
- @Inject
- UserClient userClient;
-
- @Inject
- AppDatabase appDatabase;
-
- @Inject
- SessionManager sessionManager;
@Inject
MediaDataExtractor mediaDataExtractor;
- private LifecycleOwner lifeCycleOwner;
-
@Inject
ContributionsPresenter(ContributionsRepository repository, @Named(CommonsApplicationModule.MAIN_THREAD) Scheduler mainThreadScheduler,@Named(CommonsApplicationModule.IO_THREAD) Scheduler ioThreadScheduler) {
this.repository = repository;
@@ -61,74 +49,12 @@ public class ContributionsPresenter implements UserActionListener {
this.ioThreadScheduler=ioThreadScheduler;
}
- private String user;
-
@Override
public void onAttachView(ContributionsContract.View view) {
this.view = view;
compositeDisposable=new CompositeDisposable();
}
- public void setLifeCycleOwner(LifecycleOwner lifeCycleOwner){
- this.lifeCycleOwner=lifeCycleOwner;
- }
-
- public void fetchContributions() {
- Timber.d("fetch Contributions");
- LiveData> 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 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 contributions) {
- Timber.e("Fetched: "+contributions.size()+" contributions "+" saving to db");
- repository.save(contributions).subscribeOn(ioThreadScheduler).subscribe();
- repository.set("last_fetch_timestamp",System.currentTimeMillis());
- }
-
- private boolean shouldFetchContributions() {
- long lastFetchTimestamp = repository.getLong("last_fetch_timestamp");
- Timber.d("last fetch timestamp: %s", lastFetchTimestamp);
- if(lastFetchTimestamp!=0){
- return System.currentTimeMillis()-lastFetchTimestamp>15*60*100;
- }
- Timber.d("should fetch contributions: %s", true);
- return true;
- }
-
@Override
public void onDetachView() {
this.view = null;
@@ -146,24 +72,10 @@ public class ContributionsPresenter implements UserActionListener {
*/
@Override
public void deleteUpload(Contribution contribution) {
- compositeDisposable.add(repository.deleteContributionFromDB(contribution)
- .subscribeOn(ioThreadScheduler)
- .subscribe());
- }
-
- /**
- * Returns a contribution at the specified cursor position
- *
- * @param i
- * @return
- */
- @Nullable
- @Override
- public Media getItemAtPosition(int i) {
- if (i == -1 || contributionList.size() < i+1) {
- return null;
- }
- return contributionList.get(i);
+ compositeDisposable.add(repository
+ .deleteContributionFromDB(contribution)
+ .subscribeOn(ioThreadScheduler)
+ .subscribe());
}
@Override
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsRepository.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsRepository.java
index 500babaf5..17b004802 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsRepository.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsRepository.java
@@ -1,12 +1,11 @@
package fr.free.nrw.commons.contributions;
-import androidx.lifecycle.LiveData;
-
+import androidx.paging.DataSource.Factory;
+import io.reactivex.Completable;
import java.util.List;
import javax.inject.Inject;
-import io.reactivex.Completable;
import io.reactivex.Single;
/**
@@ -33,7 +32,7 @@ public class ContributionsRepository {
* @param contribution
* @return
*/
- public Single deleteContributionFromDB(Contribution contribution) {
+ public Completable deleteContributionFromDB(Contribution contribution) {
return localDataSource.deleteContribution(contribution);
}
@@ -46,11 +45,11 @@ public class ContributionsRepository {
return localDataSource.getContributionWithFileName(fileName);
}
- public LiveData> fetchContributions() {
+ public Factory fetchContributions() {
return localDataSource.getContributions();
}
- public Completable save(List contributions) {
+ public Single> save(List contributions) {
return localDataSource.saveContributions(contributions);
}
@@ -58,11 +57,7 @@ public class ContributionsRepository {
localDataSource.set(key,value);
}
- public long getLong(String key) {
- return localDataSource.getLong(key);
- }
-
- public Single updateContribution(Contribution contribution) {
+ public Completable updateContribution(Contribution contribution) {
return localDataSource.updateContribution(contribution);
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java
index 648e21ef6..9606abee4 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java
@@ -2,7 +2,6 @@ package fr.free.nrw.commons.contributions;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
-import android.content.ContentResolver;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -12,7 +11,6 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
-
import androidx.annotation.Nullable;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
@@ -20,16 +18,9 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
-
-import com.google.android.material.tabs.TabLayout;
-
-import java.util.List;
-
-import javax.inject.Inject;
-
import butterknife.BindView;
import butterknife.ButterKnife;
-import fr.free.nrw.commons.BuildConfig;
+import com.google.android.material.tabs.TabLayout;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.location.LocationServiceManager;
@@ -44,10 +35,10 @@ import fr.free.nrw.commons.upload.UploadService;
import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
+import java.util.List;
+import javax.inject.Inject;
import timber.log.Timber;
-import static android.content.ContentResolver.requestSync;
-
public class MainActivity extends NavigationBaseActivity implements FragmentManager.OnBackStackChangedListener {
@BindView(R.id.tab_layout)
diff --git a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java
index b678d0491..7921e930f 100644
--- a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java
+++ b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java
@@ -13,7 +13,7 @@ import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
public class DBOpenHelper extends SQLiteOpenHelper {
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";
private final String DROP_TABLE_STATEMENT="DROP TABLE IF EXISTS %s";
diff --git a/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt b/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt
index 01095dd7c..a45fd0c6e 100644
--- a/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt
+++ b/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt
@@ -10,7 +10,7 @@ import fr.free.nrw.commons.contributions.ContributionDao
* The database for accessing the respective DAOs
*
*/
-@Database(entities = [Contribution::class], version = 1, exportSchema = false)
+@Database(entities = [Contribution::class], version = 2, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun contributionDao(): ContributionDao
diff --git a/app/src/main/java/fr/free/nrw/commons/depictions/Media/DepictedImagesContract.java b/app/src/main/java/fr/free/nrw/commons/depictions/Media/DepictedImagesContract.java
index 67e59390c..d79757ecd 100644
--- a/app/src/main/java/fr/free/nrw/commons/depictions/Media/DepictedImagesContract.java
+++ b/app/src/main/java/fr/free/nrw/commons/depictions/Media/DepictedImagesContract.java
@@ -90,8 +90,9 @@ public interface DepictedImagesContract {
/**
* 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)
diff --git a/app/src/main/java/fr/free/nrw/commons/depictions/Media/DepictedImagesFragment.java b/app/src/main/java/fr/free/nrw/commons/depictions/Media/DepictedImagesFragment.java
index 1c1aeaef3..6e3c128d3 100644
--- a/app/src/main/java/fr/free/nrw/commons/depictions/Media/DepictedImagesFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/depictions/Media/DepictedImagesFragment.java
@@ -136,7 +136,7 @@ public class DepictedImagesFragment extends DaggerFragment implements DepictedIm
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
handleNoInternet();
} else {
- presenter.fetchMoreImages();
+ presenter.fetchMoreImages(entityId);
}
}
if (isLastPage) {
diff --git a/app/src/main/java/fr/free/nrw/commons/depictions/Media/DepictedImagesPresenter.java b/app/src/main/java/fr/free/nrw/commons/depictions/Media/DepictedImagesPresenter.java
index 18bace3ab..02707b652 100644
--- a/app/src/main/java/fr/free/nrw/commons/depictions/Media/DepictedImagesPresenter.java
+++ b/app/src/main/java/fr/free/nrw/commons/depictions/Media/DepictedImagesPresenter.java
@@ -80,10 +80,11 @@ public class DepictedImagesPresenter implements DepictedImagesContract.UserActio
/**
* Fetches more images for the item and adds it to the grid view adapter
+ * @param entityId
*/
@SuppressLint("CheckResult")
@Override
- public void fetchMoreImages() {
+ public void fetchMoreImages(String entityId) {
view.progressBarVisible(true);
compositeDisposable.add(depictsClient.fetchImagesForDepictedItem(entityId, queryList.size())
.subscribeOn(ioScheduler)
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
index f19ab4f3a..c76c54673 100644
--- a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
@@ -224,7 +224,9 @@ public class CommonsApplicationModule {
@Provides
@Singleton
public AppDatabase provideAppDataBase() {
- return Room.databaseBuilder(applicationContext, AppDatabase.class, "commons_room.db").build();
+ return Room.databaseBuilder(applicationContext, AppDatabase.class, "commons_room.db")
+ .fallbackToDestructiveMigration()
+ .build();
}
@Provides
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/DepictionAdapter.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/DepictionAdapter.kt
index 8310915a1..f50e41764 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/DepictionAdapter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/DepictionAdapter.kt
@@ -4,10 +4,11 @@ import android.view.View
import android.view.ViewGroup
import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.RecyclerView
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.upload.structure.depictions.DepictedItem
+import kotlinx.android.extensions.LayoutContainer
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 {
- return DepictedItemViewHolder(parent.inflate(R.layout.item_depictions), onDepictionClicked)
+ return DepictedItemViewHolder(parent.inflate(R.layout.item_depictions))
}
override fun onBindViewHolder(holder: DepictedItemViewHolder, position: Int) {
- holder.bind(getItem(position)!!)
+ holder.bind(getItem(position)!!, onDepictionClicked)
}
}
-class DepictedItemViewHolder(containerView: View, val onDepictionClicked: (DepictedItem) -> Unit) :
- BaseViewHolder(containerView) {
-
- override fun bind(item: DepictedItem) {
+class DepictedItemViewHolder(override val containerView: View) :
+ RecyclerView.ViewHolder(containerView), LayoutContainer {
+ fun bind(item: DepictedItem, onDepictionClicked: (DepictedItem) -> Unit) {
containerView.setOnClickListener { onDepictionClicked(item) }
depicts_label.text = item.name
description.text = item.description
@@ -44,7 +44,3 @@ class DepictedItemViewHolder(containerView: View, val onDepictionClicked: (Depic
}
}
}
-
-
-
-
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/DepictsClient.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/DepictsClient.kt
index b363e44fc..183282d33 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/DepictsClient.kt
+++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/DepictsClient.kt
@@ -75,7 +75,7 @@ class DepictsClient @Inject constructor(
}
fun getEntities(ids: String): Single {
- return depictsInterface.getEntities(ids, Locale.getDefault().language)
+ return depictsInterface.getEntities(ids)
}
fun toDepictions(sparqlResponse: Observable): Observable> {
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/FooterAdapter.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/FooterAdapter.kt
new file mode 100644
index 000000000..76d4be40a
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/FooterAdapter.kt
@@ -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(object :
+ DiffUtil.ItemCallback() {
+ 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)
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsDataSource.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsDataSource.kt
new file mode 100644
index 000000000..413ee017e
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsDataSource.kt
@@ -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,
+ private val query: String
+) : PositionalDataSource() {
+
+ private var lastExecutedRequest: (() -> Boolean)? = null
+
+ override fun loadInitial(
+ params: LoadInitialParams,
+ callback: LoadInitialCallback
+ ) {
+ storeAndExecute {
+ loadingStates.offer(LoadingState.InitialLoad)
+ performWithTryCatch {
+ callback.onResult(
+ getItems(query, params.requestedLoadSize, params.requestedStartPosition),
+ params.requestedStartPosition
+ )
+ }
+ }
+ }
+
+ override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback) {
+ 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)
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchableDepictionsDataSourceFactory.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchableDepictionsDataSourceFactory.kt
new file mode 100644
index 000000000..90bdcdc40
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchableDepictionsDataSourceFactory.kt
@@ -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()
+ val loadingStates: Flowable = _loadingStates
+ private val _searchResults = PublishProcessor.create>>()
+ val searchResults: Flowable>> = _searchResults
+ private val _noItemsLoadedEvent = PublishProcessor.create()
+ val noItemsLoadedEvent: Flowable = _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> {
+ return dataSourceFactory.toLiveData(
+ Config(
+ pageSize = PAGE_SIZE,
+ initialLoadSizeHint = INITIAL_LOAD_SIZE,
+ enablePlaceholders = false
+ ),
+ boundaryCallback = object : PagedList.BoundaryCallback() {
+ override fun onZeroItemsLoaded() {
+ zeroItemsLoadedFunction()
+ }
+ }
+ )
+ }
+
+}
+
+interface SearchDepictionsDataSourceFactoryFactory {
+ fun create(query: String, loadingStates: PublishProcessor)
+ : SearchDepictionsDataSourceFactory
+}
+
+class SearchDepictionsDataSourceFactory constructor(
+ private val depictsClient: DepictsClient,
+ private val query: String,
+ private val loadingStates: PublishProcessor
+) : DataSource.Factory() {
+ 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()
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaClient.java b/app/src/main/java/fr/free/nrw/commons/media/MediaClient.java
index c0b2fd759..a615732ba 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaClient.java
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaClient.java
@@ -32,6 +32,7 @@ public class MediaClient {
//OkHttpJsonApiClient used JsonKvStore for this. I don't know why.
private Map> continuationStore;
+ private Map continuationExists;
public static final String NO_CAPTION = "No caption";
private static final String NO_DEPICTION = "No depiction";
@@ -40,6 +41,7 @@ public class MediaClient {
this.mediaInterface = mediaInterface;
this.mediaDetailInterface = mediaDetailInterface;
this.continuationStore = new HashMap<>();
+ this.continuationExists = new HashMap<>();
}
/**
@@ -83,6 +85,36 @@ public class MediaClient {
}
+ /**
+ * This method takes the userName as input and returns a list of Media objects filtered using
+ * allimages query It uses the allimages query API to get the images contributed by the userName,
+ * 10 at a time.
+ *
+ * @param userName the username
+ * @return
+ */
+ public Single> getMediaListForUser(String userName) {
+ Map continuation =
+ continuationStore.containsKey("user_" + userName)
+ ? continuationStore.get("user_" + userName)
+ : Collections.emptyMap();
+ return responseToMediaList(mediaInterface
+ .getMediaListForUser(userName, 10, continuation), "user_" + userName);
+ }
+
+ /**
+ * Check if media for user has reached the end of the list.
+ * @param userName
+ * @return
+ */
+ public boolean doesMediaListForUserHaveMorePages(String userName) {
+ final String key = "user_" + userName;
+ if(continuationExists.containsKey(key)) {
+ return continuationExists.get(key);
+ }
+ return true;
+ }
+
/**
* This method takes a keyword as input and returns a list of Media objects filtered using image generator query
* It uses the generator query API to get the images searched using a query, 10 at a time.
@@ -104,7 +136,12 @@ public class MediaClient {
|| null == mwQueryResponse.query().pages()) {
return Observable.empty();
}
- continuationStore.put(key, mwQueryResponse.continuation());
+ if(mwQueryResponse.continuation() != null) {
+ continuationStore.put(key, mwQueryResponse.continuation());
+ continuationExists.put(key, true);
+ } else {
+ continuationExists.put(key, false);
+ }
return Observable.fromIterable(mwQueryResponse.query().pages());
})
.map(Media::from)
@@ -169,7 +206,7 @@ public class MediaClient {
* @return caption for image using wikibaseIdentifier
*/
public Single getCaptionByWikibaseIdentifier(String wikibaseIdentifier) {
- return mediaDetailInterface.getCaptionForImage(Locale.getDefault().getLanguage(), wikibaseIdentifier)
+ return mediaDetailInterface.getEntityForImage(Locale.getDefault().getLanguage(), wikibaseIdentifier)
.map(mediaDetailResponse -> {
if (isSuccess(mediaDetailResponse)) {
for (Entity wikibaseItem : mediaDetailResponse.entities().values()) {
@@ -205,12 +242,16 @@ public class MediaClient {
* @param entityId EntityId (Ex: Q81566) of the depict entity
* @return label
*/
- public Single getLabelForDepiction(String entityId) {
- return getEntities(entityId)
+ public Single getLabelForDepiction(String entityId, String language) {
+ return mediaDetailInterface.getEntity(entityId)
.map(entities -> {
if (isSuccess(entities)) {
for (Entity entity : entities.entities().values()) {
- for (Label label : entity.labels().values()) {
+ final Map languageToLabelMap = entity.labels();
+ if (languageToLabelMap.containsKey(language)) {
+ return languageToLabelMap.get(language).value();
+ }
+ for (Label label : languageToLabelMap.values()) {
return label.value();
}
}
@@ -220,6 +261,6 @@ public class MediaClient {
}
public Single getEntities(String entityId) {
- return mediaDetailInterface.getEntity(entityId, Locale.getDefault().getLanguage());
+ return mediaDetailInterface.getEntity(entityId);
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
index 34a637425..d78c0026b 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
@@ -51,7 +51,6 @@ import fr.free.nrw.commons.delete.ReasonBuilder;
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
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 io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailInterface.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailInterface.java
index 856e51b07..ef9559c29 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailInterface.java
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailInterface.java
@@ -21,12 +21,11 @@ public interface MediaDetailInterface {
/**
* 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")
- Single getEntity(@Query("ids") String entityId, @Query("languages") String language);
+ Single getEntity(@Query("ids") String entityId);
/**
* Fetches caption using wikibaseIdentifier
@@ -34,5 +33,5 @@ public interface MediaDetailInterface {
* @param wikibaseIdentifier pageId for the media
*/
@GET("/w/api.php?action=wbgetentities&props=labels&format=json&languagefallback=1&sites=commonswiki")
- Observable getCaptionForImage(@Query("languages") String language, @Query("ids") String wikibaseIdentifier);
+ Observable getEntityForImage(@Query("languages") String language, @Query("ids") String wikibaseIdentifier);
}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java
index 39c81386e..3e5f3f0fd 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java
@@ -23,26 +23,21 @@ import butterknife.ButterKnife;
import com.google.android.material.snackbar.Snackbar;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.bookmarks.Bookmark;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
import fr.free.nrw.commons.category.CategoryImagesCallback;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
-import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.utils.DownloadUtils;
import fr.free.nrw.commons.utils.ImageUtils;
import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import javax.inject.Inject;
-import javax.inject.Named;
import timber.log.Timber;
public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment implements ViewPager.OnPageChangeListener {
- @Inject SessionManager sessionManager;
- @Inject @Named("default_preferences") JsonKvStore store;
@Inject BookmarkPicturesDao bookmarkDao;
@BindView(R.id.mediaDetailsPager) ViewPager pager;
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaInterface.java b/app/src/main/java/fr/free/nrw/commons/media/MediaInterface.java
index 0e3dd3753..aaec15136 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaInterface.java
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaInterface.java
@@ -16,6 +16,7 @@ public interface MediaInterface {
String MEDIA_PARAMS="&prop=imageinfo&iiprop=url|extmetadata&iiurlwidth=640" +
"&iiextmetadatafilter=DateTime|Categories|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal" +
"|Artist|LicenseShortName|LicenseUrl";
+
/**
* Checks if a page exists or not.
*
@@ -47,6 +48,19 @@ public interface MediaInterface {
MEDIA_PARAMS)
Observable getMediaListFromCategory(@Query("gcmtitle") String category, @Query("gcmlimit") int itemLimit, @QueryMap Map 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 getMediaListForUser(@Query("gaiuser") String username,
+ @Query("gailimit") int itemLimit, @QueryMap(encoded = true) Map continuation);
+
/**
* This method retrieves a list of Media objects filtered using image generator query
*
@@ -85,21 +99,15 @@ public interface MediaInterface {
Observable getPageHtml(@Query("page") String title);
/**
- * Fetches caption using file name
- *
- * @param filename name of the file to be used for fetching captions
- * */
- @GET("w/api.php?action=wbgetentities&props=labels&format=json&languagefallback=1")
- Observable fetchCaptionByFilename(@Query("language") String language, @Query("titles") String filename);
+ * Fetches list of images from a depiction entity
+ *
+ * @param query depictionEntityId
+ * @param sroffset number od depictions already fetched, this is useful in implementing
+ * pagination
+ */
- /**
- * Fetches list of images from a depiction entity
- *
- * @param query depictionEntityId
- * @param sroffset number od depictions already fetched, this is useful in implementing pagination
- */
-
- @GET("w/api.php?action=query&list=search&format=json&srnamespace=6")
- Observable fetchImagesForDepictedItem(@Query("srsearch") String query, @Query("sroffset") String sroffset);
+ @GET("w/api.php?action=query&list=search&format=json&srnamespace=6")
+ Observable fetchImagesForDepictedItem(@Query("srsearch") String query,
+ @Query("sroffset") String sroffset);
}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/Label.java b/app/src/main/java/fr/free/nrw/commons/nearby/Label.java
index ac939f5da..19c175fd8 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/Label.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/Label.java
@@ -4,7 +4,9 @@ import android.os.Parcel;
import androidx.annotation.DrawableRes;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import fr.free.nrw.commons.R;
@@ -17,6 +19,7 @@ import fr.free.nrw.commons.R;
*/
public enum Label {
+ BOOKMARKS("BOOKMARK", R.drawable.ic_filled_star_24dp),
BUILDING("Q41176", R.drawable.round_icon_generic_building),
HOUSE("Q3947", 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);
return label == null ? UNKNOWN : label;
}
+
+ public static List