diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index d18f5be8c..936d6e5a6 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -7,6 +7,7 @@
+
diff --git a/app/build.gradle b/app/build.gradle
index 31dafb6d1..596560b44 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -19,10 +19,9 @@ dependencies {
implementation project(':wikimedia-data-client')
// Utils
- implementation 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07'
implementation 'in.yuvi:http.fluent:1.3'
implementation 'com.google.code.gson:gson:2.8.5'
- implementation 'com.squareup.okhttp3:okhttp:4.2.0'
+ implementation 'com.squareup.okhttp3:okhttp:4.5.0'
implementation 'com.squareup.okio:okio:2.2.2'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.3'
@@ -44,6 +43,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"
// Logging
@@ -53,7 +53,7 @@ dependencies {
api('com.github.tony19:logback-android-classic:1.1.1-6') {
exclude group: 'com.google.android', module: 'android'
}
- implementation "com.squareup.okhttp3:logging-interceptor:4.2.0"
+ implementation "com.squareup.okhttp3:logging-interceptor:4.5.0"
// Dependency injector
implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"
@@ -65,7 +65,7 @@ dependencies {
//Mocking
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0'
- testImplementation 'org.mockito:mockito-inline:2.8.47'
+ testImplementation 'org.mockito:mockito-inline:2.13.0'
testImplementation 'org.mockito:mockito-core:2.23.0'
testImplementation "org.powermock:powermock-module-junit4:2.0.0-beta.5"
testImplementation "org.powermock:powermock-api-mockito2:2.0.0-beta.5"
@@ -108,9 +108,10 @@ dependencies {
//Room
implementation "androidx.room:room-runtime:$ROOM_VERSION"
- kapt "androidx.room:room-compiler:$ROOM_VERSION" // For Kotlin use kapt instead of annotationProcessor
- implementation 'com.squareup.retrofit2:retrofit:2.7.1'
+ implementation "androidx.room:room-ktx:$ROOM_VERSION"
implementation "androidx.room:room-rxjava2:$ROOM_VERSION"
+ kapt "androidx.room:room-compiler:$ROOM_VERSION" // For Kotlin use kapt instead of annotationProcessor
+ implementation 'com.squareup.retrofit2:retrofit:2.8.1'
testImplementation "androidx.arch.core:core-testing:2.1.0"
// Pref
@@ -208,6 +209,7 @@ android {
buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\""
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.org/w/api.php\""
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
+ buildConfigField "String", "WIKIDATA_URL", "\"https://www.wikidata.org\""
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
buildConfigField "String", "WIKIMEDIA_CAMPAIGNS_URL", "\"https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns.json\""
buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.wikimedia.org/wikipedia/commons\""
@@ -229,6 +231,7 @@ android {
buildConfigField "String", "COMMIT_SHA", "\"" + getBuildVersion().toString() + "\""
buildConfigField "String", "TEST_USERNAME", "\"" + System.getenv("test_user_name") + "\""
buildConfigField "String", "TEST_PASSWORD", "\"" + System.getenv("test_user_password") + "\""
+ buildConfigField "String", "DEPICTS_PROPERTY", "\"P180\""
dimension 'tier'
}
@@ -240,6 +243,7 @@ android {
buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\""
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.beta.wmflabs.org/w/api.php\""
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
+ buildConfigField "String", "WIKIDATA_URL", "\"https://www.wikidata.org\""
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
buildConfigField "String", "WIKIMEDIA_CAMPAIGNS_URL", "\"https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns_beta_active.json\""
buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.beta.wmflabs.org/wikipedia/commons\""
@@ -261,6 +265,7 @@ android {
buildConfigField "String", "COMMIT_SHA", "\"" + getBuildVersion().toString() + "\""
buildConfigField "String", "TEST_USERNAME", "\"" + System.getenv("test_user_name") + "\""
buildConfigField "String", "TEST_PASSWORD", "\"" + System.getenv("test_user_password") + "\""
+ buildConfigField "String", "DEPICTS_PROPERTY", "\"P245962\""
dimension 'tier'
}
diff --git a/app/src/androidTest/java/fr/free/nrw/commons/DepictionSearchTest.kt b/app/src/androidTest/java/fr/free/nrw/commons/DepictionSearchTest.kt
new file mode 100644
index 000000000..b62b04349
--- /dev/null
+++ b/app/src/androidTest/java/fr/free/nrw/commons/DepictionSearchTest.kt
@@ -0,0 +1,46 @@
+package fr.free.nrw.commons
+
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Rule
+import org.junit.runner.RunWith
+import android.net.Uri
+import androidx.test.espresso.Espresso
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.matcher.ViewMatchers
+import androidx.test.rule.ActivityTestRule
+import fr.free.nrw.commons.upload.UploadActivity
+import org.hamcrest.Matchers
+import org.hamcrest.core.AllOf
+import org.junit.Test
+
+@RunWith(AndroidJUnit4::class)
+class DepictionSearchTest {
+ @get:Rule
+ var activityRule = ActivityTestRule(UploadActivity::class.java)
+
+ @Test
+ fun TestForCaptionsAndDepictions() {
+ val imageUri = Uri.parse("file://mnt/sdcard/image.jpg")
+
+ // Build a result to return from the Camera app
+
+
+ // Stub out the File picker. When an intent is sent to the File picker, this tells
+ // Espresso to respond with the ActivityResult we just created
+
+ Espresso.onView(ViewMatchers.withId(R.id.caption_item_edit_text))
+ .perform(ViewActions.typeText("caption in english"))
+ Espresso.onView(ViewMatchers.withId(R.id.description_item_edit_text))
+ .perform(ViewActions.typeText("description in english"))
+ Espresso.onView(ViewMatchers.withId(R.id.spinner_description_languages))
+ .perform(ViewActions.click())
+ Espresso.onView(ViewMatchers.withId(R.id.spinner_description_languages)).perform(ViewActions.click());
+ Espresso.onData(AllOf.allOf(Matchers.anything("spinner text"))).atPosition(1).perform(ViewActions.click());
+ Espresso.onView(ViewMatchers.withId(R.id.caption_item_edit_text))
+ .perform(ViewActions.typeText("caption in some other language"))
+ Espresso.onView(ViewMatchers.withId(R.id.description_item_edit_text))
+ .perform(ViewActions.typeText("description in some other language"))
+ Espresso.onView(ViewMatchers.withId(R.id.btn_next))
+ .perform(ViewActions.click())
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/fr/free/nrw/commons/UploadActivityTest.kt b/app/src/androidTest/java/fr/free/nrw/commons/UploadActivityTest.kt
index b7f89e54e..fcd311db5 100644
--- a/app/src/androidTest/java/fr/free/nrw/commons/UploadActivityTest.kt
+++ b/app/src/androidTest/java/fr/free/nrw/commons/UploadActivityTest.kt
@@ -1,8 +1,17 @@
package fr.free.nrw.commons
+import android.net.Uri
+import androidx.test.espresso.Espresso
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.intent.Intents
+import androidx.test.espresso.intent.matcher.IntentMatchers
+import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.AndroidJUnit4
import fr.free.nrw.commons.upload.UploadActivity
+import fr.free.nrw.commons.upload.depicts.DepictsFragment
+import org.hamcrest.Matchers
+import org.hamcrest.core.AllOf
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -16,4 +25,25 @@ class UploadActivityTest {
fun orientationChange() {
UITestHelper.changeOrientation(activityRule)
}
-}
\ No newline at end of file
+
+ @Test
+ fun TestForCaptionsAndDepictions() {
+ val imageUri = Uri.parse("file://mnt/sdcard/image.jpg")
+
+ Espresso.onView(ViewMatchers.withId(R.id.caption_item_edit_text))
+ .perform(ViewActions.typeText("caption in english"))
+ Espresso.onView(ViewMatchers.withId(R.id.description_item_edit_text))
+ .perform(ViewActions.typeText("description in english"))
+ Espresso.onView(ViewMatchers.withId(R.id.spinner_description_languages))
+ .perform(ViewActions.click())
+ Espresso.onView(ViewMatchers.withId(R.id.spinner_description_languages)).perform(ViewActions.click());
+ Espresso.onData(AllOf.allOf(Matchers.anything("spinner text"))).atPosition(1).perform(ViewActions.click());
+ Espresso.onView(ViewMatchers.withId(R.id.caption_item_edit_text))
+ .perform(ViewActions.typeText("caption in some other language"))
+ Espresso.onView(ViewMatchers.withId(R.id.description_item_edit_text))
+ .perform(ViewActions.typeText("description in some other language"))
+ Espresso.onView(ViewMatchers.withId(R.id.btn_next))
+ .perform(ViewActions.click())
+ Intents.intended(IntentMatchers.hasComponent(DepictsFragment::class.java.name))
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d904a5ecc..e0cf4d03c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -119,6 +119,11 @@
android:label="@string/title_activity_featured_images"
android:parentActivityName=".contributions.MainActivity" />
+
+
+ android:syncable="false" />
categories; // as loaded at runtime?
- public boolean requestedDeletion;
- public HashMap descriptions; // multilingual descriptions as loaded
- public HashMap tags = new HashMap<>();
- @Nullable public LatLng coordinates;
+ private Uri localUri;
+ private String thumbUrl;
+ private String imageUrl;
+ private String filename;
+ private String thumbnailTitle;
+ /*
+ * Captions are a feature part of Structured data. They are meant to store short, multilingual descriptions about files
+ * This is a replacement of the previously used titles for images (titles were not multilingual)
+ * Also now captions replace the previous convention of using title for filename
+ */
+ private String caption;
+ private String description; // monolingual description on input...
+ private String discussion;
+ private long dataLength;
+ private Date dateCreated;
+ @Nullable private Date dateUploaded;
+ private String license;
+ private String licenseUrl;
+ private String creator;
+ /**
+ * Wikibase Identifier associated with media files
+ */
+ private String pageId;
+ private List categories; // as loaded at runtime?
+ /**
+ * Depicts is a feature part of Structured data. Multiple Depictions can be added for an image just like categories.
+ * However unlike categories depictions is multi-lingual
+ */
+ private Depictions depictions;
+ private boolean requestedDeletion;
+ @Nullable private LatLng coordinates;
/**
* Provides local constructor
*/
- protected Media() {
- this.categories = new ArrayList<>();
- this.descriptions = new HashMap<>();
+ public Media() {
}
/**
@@ -69,7 +72,6 @@ public class Media implements Parcelable {
* @param filename Media filename
*/
public Media(String filename) {
- this();
this.filename = filename;
}
@@ -84,9 +86,9 @@ 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) {
- this();
+ public Media(Uri localUri, String imageUrl, String filename,
+ String description,
+ long dataLength, Date dateCreated, Date dateUploaded, String creator) {
this.localUri = localUri;
this.thumbUrl = imageUrl;
this.imageUrl = imageUrl;
@@ -96,8 +98,17 @@ public class Media implements Parcelable {
this.dateCreated = dateCreated;
this.dateUploaded = dateUploaded;
this.creator = creator;
- this.categories = new ArrayList<>();
- this.descriptions = new HashMap<>();
+ }
+
+ public Media(Uri localUri, String filename,
+ String description, String creator, List categories) {
+ this(localUri,null, filename,
+ description, -1, null, new Date(), creator);
+ this.categories = categories;
+ }
+
+ public Media(String title, Date date, String user) {
+ this(null, null, title, "", -1, date, date, user);
}
/**
@@ -112,7 +123,7 @@ public class Media implements Parcelable {
public static Media from(MwQueryPage page) {
ImageInfo imageInfo = page.imageInfo();
if (imageInfo == null) {
- return null;
+ return new Media(); // null is not allowed
}
ExtMetadata metadata = imageInfo.getMetadata();
if (metadata == null) {
@@ -127,7 +138,7 @@ public class Media implements Parcelable {
Media media = new Media(null,
imageInfo.getOriginalUrl(),
page.title(),
- "",
+ "",
0,
safeParseDate(metadata.dateTime()),
safeParseDate(metadata.dateTime()),
@@ -138,12 +149,14 @@ public class Media implements Parcelable {
media.setThumbUrl(imageInfo.getThumbUrl());
}
+ media.setPageId(String.valueOf(page.pageId()));
+
String language = Locale.getDefault().getLanguage();
if (StringUtils.isBlank(language)) {
language = "default";
}
- media.setDescriptions(Collections.singletonMap(language, metadata.imageDescription()));
+ media.setDescription(metadata.imageDescription());
media.setCategories(MediaDataExtractorUtil.extractCategoriesFromList(metadata.getCategories()));
String latitude = metadata.getGpsLatitude();
String longitude = metadata.getGpsLongitude();
@@ -172,28 +185,23 @@ public class Media implements Parcelable {
}
}
+ /**
+ * @return pageId for the current media object*/
+ public String getPageId() {
+ return pageId;
+ }
+
+ /**
+ *sets pageId for the current media object
+ */
+ public void setPageId(String pageId) {
+ this.pageId = pageId;
+ }
+
public String getThumbUrl() {
return thumbUrl;
}
- /**
- * Gets tag of media
- * @param key Media key
- * @return Media tag
- */
- public Object getTag(String key) {
- return tags.get(key);
- }
-
- /**
- * Modifies( or creates a) tag of media
- * @param key Media key
- * @param value Media value
- */
- public void setTag(String key, String value) {
- tags.put(key, value);
- }
-
/**
* Gets media display title
* @return Media title
@@ -202,6 +210,21 @@ 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;
+ }
+
+ /**
+ * @return title to be shown on image thumbnail
+ * If caption is available for the image then it returns caption else filename
+ */
+ public String getThumbnailTitle() {
+ return thumbnailTitle != null? thumbnailTitle : getDisplayTitle();
+ }
+
/**
* Gets file page title
* @return New media page title
@@ -268,6 +291,24 @@ public class Media implements Parcelable {
return description;
}
+ /**
+ * Captions are a feature part of Structured data. They are meant to store short, multilingual descriptions about files
+ * This is a replacement of the previously used titles for images (titles were not multilingual)
+ * Also now captions replace the previous convention of using title for filename
+ *
+ * @return caption
+ */
+ public String getCaption() {
+ return caption;
+ }
+
+ /**
+ * @return depictions associated with the current media
+ */
+ public Depictions getDepiction() {
+ return depictions;
+ }
+
/**
* Sets the file description.
* @param description the new description of the file
@@ -334,38 +375,6 @@ public class Media implements Parcelable {
this.creator = creator;
}
- /**
- * Gets the width of the media.
- * @return file width as an int
- */
- public int getWidth() {
- return width;
- }
-
- /**
- * Sets the width of the media.
- * @param width file width as an int
- */
- public void setWidth(int width) {
- this.width = width;
- }
-
- /**
- * Gets the height of the media.
- * @return file height as an int
- */
- public int getHeight() {
- return height;
- }
-
- /**
- * Sets the height of the media.
- * @param height file height as an int
- */
- public void setHeight(int height) {
- this.height = height;
- }
-
/**
* Gets the license name of the file.
* @return license as a String
@@ -417,8 +426,8 @@ public class Media implements Parcelable {
* @return file categories as an ArrayList of Strings
*/
@SuppressWarnings("unchecked")
- public ArrayList getCategories() {
- return (ArrayList) categories.clone(); // feels dirty
+ public List getCategories() {
+ return categories;
}
/**
@@ -429,38 +438,7 @@ public class Media implements Parcelable {
* @param categories file categories as a list of Strings
*/
public void setCategories(List categories) {
- this.categories.clear();
- this.categories.addAll(categories);
- }
-
- /**
- * Modifies (or sets) media descriptions
- * @param descriptions Media descriptions
- */
- void setDescriptions(Map descriptions) {
- this.descriptions.clear();
- this.descriptions.putAll(descriptions);
- }
-
- /**
- * Gets media description in preferred language
- * @param preferredLanguage Language preferred
- * @return Description in preferred language
- */
- public String getDescription(String preferredLanguage) {
- if (descriptions.containsKey(preferredLanguage)) {
- // See if the requested language is there.
- return descriptions.get(preferredLanguage);
- } else if (descriptions.containsKey("en")) {
- // Ah, English. Language of the world, until the Chinese crush us.
- return descriptions.get("en");
- } else if (descriptions.containsKey("default")) {
- // No languages marked...
- return descriptions.get("default");
- } else {
- // FIXME: return the first available non-English description?
- return "";
- }
+ this.categories = categories;
}
@Nullable private static Date safeParseDate(String dateStr) {
@@ -473,16 +451,17 @@ public class Media implements Parcelable {
/**
* Set requested deletion to true
+ * @param requestedDeletion
*/
- public void setRequestedDeletion(){
- requestedDeletion = true;
+ public void setRequestedDeletion(boolean requestedDeletion){
+ this.requestedDeletion = requestedDeletion;
}
/**
* Get the value of requested deletion
* @return boolean requestedDeletion
*/
- public boolean getRequestedDeletion(){
+ public boolean isRequestedDeletion(){
return requestedDeletion;
}
@@ -495,6 +474,42 @@ public class Media implements Parcelable {
this.license = license;
}
+ /**
+ * Captions are a feature part of Structured data. They are meant to store short, multilingual descriptions about files
+ * This is a replacement of the previously used titles for images (titles were not multilingual)
+ * Also now captions replace the previous convention of using title for filename
+ *
+ * This function sets captions
+ * @param caption
+ */
+ public void setCaption(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;
+ }
+
+ public void setImageUrl(final String imageUrl) {
+ this.imageUrl = imageUrl;
+ }
+
+ public void setDateUploaded(@Nullable final Date dateUploaded) {
+ this.dateUploaded = dateUploaded;
+ }
+
+ public void setLicenseUrl(final String licenseUrl) {
+ this.licenseUrl = licenseUrl;
+ }
+
+ public Depictions getDepictions() {
+ return depictions;
+ }
@Override
public int describeContents() {
@@ -513,20 +528,20 @@ public class Media implements Parcelable {
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.writeInt(this.width);
- dest.writeInt(this.height);
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.writeSerializable(this.descriptions);
- dest.writeSerializable(this.tags);
dest.writeParcelable(this.coordinates, flags);
}
@@ -535,6 +550,8 @@ public class Media implements Parcelable {
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();
@@ -542,15 +559,15 @@ public class Media implements Parcelable {
this.dateCreated = tmpDateCreated == -1 ? null : new Date(tmpDateCreated);
long tmpDateUploaded = in.readLong();
this.dateUploaded = tmpDateUploaded == -1 ? null : new Date(tmpDateUploaded);
- this.width = in.readInt();
- this.height = in.readInt();
this.license = in.readString();
this.licenseUrl = in.readString();
this.creator = in.readString();
- this.categories = in.createStringArrayList();
+ 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.descriptions = (HashMap) in.readSerializable();
- this.tags = (HashMap) in.readSerializable();
this.coordinates = in.readParcelable(LatLng.class.getClassLoader());
}
diff --git a/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java b/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java
index 0564626da..58b6be2c9 100644
--- a/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java
+++ b/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java
@@ -1,12 +1,14 @@
package fr.free.nrw.commons;
+import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX;
+
import androidx.core.text.HtmlCompat;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
+import fr.free.nrw.commons.media.Depictions;
import fr.free.nrw.commons.media.MediaClient;
import io.reactivex.Single;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import org.jetbrains.annotations.NotNull;
import timber.log.Timber;
/**
@@ -17,30 +19,61 @@ import timber.log.Timber;
*/
@Singleton
public class MediaDataExtractor {
- private final MediaClient mediaClient;
+
+ private final MediaClient mediaClient;
@Inject
- public MediaDataExtractor(MediaClient mediaClient) {
+ public MediaDataExtractor(final MediaClient mediaClient) {
this.mediaClient = mediaClient;
}
/**
* Simplified method to extract all details required to show media details.
- * It fetches media object, deletion status and talk page for the filename
+ * It fetches media object, deletion status, talk page and captions for the filename
* @param filename for which the details are to be fetched
* @return full Media object with all details including deletion status and talk page
*/
- public Single fetchMediaDetails(String filename) {
- Single mediaSingle = getMediaFromFileName(filename);
- Single pageExistsSingle = mediaClient.checkPageExistsUsingTitle("Commons:Deletion_requests/" + filename);
- Single discussionSingle = getDiscussion(filename);
- return Single.zip(mediaSingle, pageExistsSingle, discussionSingle, (media, deletionStatus, discussion) -> {
- media.setDiscussion(discussion);
- if (deletionStatus) {
- media.setRequestedDeletion();
- }
- return media;
- });
+ public Single fetchMediaDetails(final String filename, final String pageId) {
+ return Single.zip(getMediaFromFileName(filename),
+ mediaClient.checkPageExistsUsingTitle("Commons:Deletion_requests/" + filename),
+ getDiscussion(filename),
+ pageId != null ? getCaption(PAGE_ID_PREFIX + pageId)
+ : Single.just(MediaClient.NO_CAPTION),
+ getDepictions(filename),
+ this::combineToMedia);
+ }
+
+ @NotNull
+ private Media combineToMedia(final Media media, final Boolean deletionStatus, final String discussion,
+ final String caption, final Depictions depictions) {
+ media.setDiscussion(discussion);
+ media.setCaption(caption);
+ media.setDepictions(depictions);
+ if (deletionStatus) {
+ media.setRequestedDeletion(true);
+ }
+ return media;
+ }
+
+ /**
+ * Obtains captions using filename
+ * @param wikibaseIdentifier
+ *
+ * @return caption for the image in user's locale
+ * Ex: "a nice painting" (english locale) and "No Caption" in case the caption is not available for the image
+ */
+ private Single getCaption(final String wikibaseIdentifier) {
+ return mediaClient.getCaptionByWikibaseIdentifier(wikibaseIdentifier);
+ }
+
+ /**
+ * Fetch depictions from the MediaWiki API
+ * @param filename the filename we will return the caption for
+ * @return Depictions
+ */
+ private Single getDepictions(final String filename) {
+ return mediaClient.getDepictions(filename)
+ .doOnError(throwable -> Timber.e(throwable, "error while fetching depictions"));
}
/**
@@ -48,7 +81,7 @@ public class MediaDataExtractor {
* @param filename Eg. File:Test.jpg
* @return return data rich Media object
*/
- public Single getMediaFromFileName(String filename) {
+ public Single getMediaFromFileName(final String filename) {
return mediaClient.getMedia(filename);
}
@@ -57,7 +90,7 @@ public class MediaDataExtractor {
* @param filename
* @return
*/
- private Single getDiscussion(String filename) {
+ private Single getDiscussion(final String filename) {
return mediaClient.getPageHtml(filename.replace("File", "File talk"))
.map(discussion -> HtmlCompat.fromHtml(discussion, HtmlCompat.FROM_HTML_MODE_LEGACY).toString())
.onErrorReturn(throwable -> {
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 9d3bde848..b9c10496c 100644
--- a/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.java
+++ b/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.java
@@ -1,6 +1,11 @@
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;
diff --git a/app/src/main/java/fr/free/nrw/commons/caching/CacheController.java b/app/src/main/java/fr/free/nrw/commons/caching/CacheController.java
deleted file mode 100644
index 8820101e8..000000000
--- a/app/src/main/java/fr/free/nrw/commons/caching/CacheController.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package fr.free.nrw.commons.caching;
-
-import com.github.varunpant.quadtree.Point;
-import com.github.varunpant.quadtree.QuadTree;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-import timber.log.Timber;
-
-@Singleton
-public class CacheController {
-
- private final QuadTree> quadTree;
- private double x, y;
- private double xMinus, xPlus, yMinus, yPlus;
-
- private static final int EARTH_RADIUS = 6378137;
-
- @Inject
- public CacheController(QuadTree quadTree) {
- this.quadTree = quadTree;
- }
-
- public void setQtPoint(double decLongitude, double decLatitude) {
- x = decLongitude;
- y = decLatitude;
- Timber.d("New QuadTree created");
- Timber.d("X (longitude) value: %f, Y (latitude) value: %f", x, y);
- }
-
- public List findCategory() {
- Point>[] pointsFound;
- //Convert decLatitude and decLongitude to a coordinate offset range
- convertCoordRange();
- pointsFound = quadTree.searchWithin(xMinus, yMinus, xPlus, yPlus);
- List displayCatList = new ArrayList<>();
- Timber.d("Points found in quadtree: %s", Arrays.toString(pointsFound));
-
- if (pointsFound.length != 0) {
- Timber.d("Entering for loop");
-
- for (Point> point : pointsFound) {
- Timber.d("Nearby point: %s", point);
- displayCatList = point.getValue();
- Timber.d("Nearby cat: %s", point.getValue());
- }
-
- Timber.d("Categories found in cache: %s", displayCatList);
- } else {
- Timber.d("No categories found in cache");
- }
- return displayCatList;
- }
-
- //Based on algorithm at http://gis.stackexchange.com/questions/2951/algorithm-for-offsetting-a-latitude-longitude-by-some-amount-of-meters
- private void convertCoordRange() {
- //Position, decimal degrees
- double lat = y;
- double lon = x;
-
- //offsets in meters
- double offset = 100;
-
- //Coordinate offsets in radians
- double dLat = offset / EARTH_RADIUS;
- double dLon = offset / (EARTH_RADIUS * Math.cos(Math.PI * lat / 180));
-
- //OffsetPosition, decimal degrees
- yPlus = lat + dLat * 180 / Math.PI;
- yMinus = lat - dLat * 180 / Math.PI;
- xPlus = lon + dLon * 180 / Math.PI;
- xMinus = lon - dLon * 180 / Math.PI;
- Timber.d("Search within: xMinus=%s, yMinus=%s, xPlus=%s, yPlus=%s",
- xMinus, yMinus, xPlus, yPlus);
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.java b/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.java
index caa66e433..d8f9eb45e 100644
--- a/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.java
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.java
@@ -1,21 +1,17 @@
package fr.free.nrw.commons.category;
import android.text.TextUtils;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.upload.GpsCategoryModel;
import fr.free.nrw.commons.utils.StringSortingUtils;
import io.reactivex.Observable;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import javax.inject.Inject;
+import javax.inject.Named;
import timber.log.Timber;
/**
@@ -27,19 +23,19 @@ public class CategoriesModel{
private final CategoryClient categoryClient;
private final CategoryDao categoryDao;
private final JsonKvStore directKvStore;
+ private final GpsCategoryModel gpsCategoryModel;
- private HashMap> categoriesCache;
private List selectedCategories;
- @Inject GpsCategoryModel gpsCategoryModel;
@Inject
public CategoriesModel(CategoryClient categoryClient,
- CategoryDao categoryDao,
- @Named("default_preferences") JsonKvStore directKvStore) {
+ CategoryDao categoryDao,
+ @Named("default_preferences") JsonKvStore directKvStore,
+ final GpsCategoryModel gpsCategoryModel) {
this.categoryClient = categoryClient;
this.categoryDao = categoryDao;
this.directKvStore = directKvStore;
- this.categoriesCache = new HashMap<>();
+ this.gpsCategoryModel = gpsCategoryModel;
this.selectedCategories = new ArrayList<>();
}
@@ -94,10 +90,6 @@ public class CategoriesModel{
categoryDao.save(category);
}
- boolean cacheContainsKey(String term) {
- return categoriesCache.containsKey(term);
- }
- //endregion
/**
* Regional category search
@@ -108,20 +100,18 @@ public class CategoriesModel{
public Observable searchAll(String term, List imageTitleList) {
//If query text is empty, show him category based on gps and title and recent searches
if (TextUtils.isEmpty(term)) {
- Observable categoryItemObservable = gpsCategories()
- .concatWith(titleCategories(imageTitleList));
+ Observable categoryItemObservable =
+ Observable.concat(gpsCategories(), titleCategories(imageTitleList));
if (hasDirectCategories()) {
- categoryItemObservable.concatWith(directCategories().concatWith(recentCategories()));
+ return Observable.concat(
+ categoryItemObservable,
+ directCategories(),
+ recentCategories()
+ );
}
return categoryItemObservable;
}
- //if user types in something that is in cache, return cached category
- if (cacheContainsKey(term)) {
- return Observable.fromIterable(getCachedCategories(term))
- .map(name -> new CategoryItem(name, false));
- }
-
//otherwise, search API for matching categories
//term passed as lower case to make search case-insensitive(taking only lower case for everything)
return categoryClient
@@ -130,15 +120,6 @@ public class CategoriesModel{
}
- /**
- * Returns cached categories
- * @param term
- * @return
- */
- private ArrayList getCachedCategories(String term) {
- return categoriesCache.get(term);
- }
-
/**
* Returns if we have a category in DirectKV Store
* @return
@@ -256,7 +237,6 @@ public class CategoriesModel{
* Cleanup the existing in memory cache's
*/
public void cleanUp() {
- this.categoriesCache.clear();
this.selectedCategories.clear();
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesListFragment.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesListFragment.java
index 8280e6b25..e83edfac4 100644
--- a/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesListFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesListFragment.java
@@ -1,5 +1,9 @@
package fr.free.nrw.commons.category;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX;
+
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -12,15 +16,7 @@ import android.widget.ListAdapter;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
-
import androidx.annotation.Nullable;
-
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
import butterknife.BindView;
import butterknife.ButterKnife;
import dagger.android.support.DaggerFragment;
@@ -33,17 +29,22 @@ import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import javax.inject.Inject;
+import javax.inject.Named;
import timber.log.Timber;
-import static android.view.View.GONE;
-import static android.view.View.VISIBLE;
-
/**
* Displays images for a particular category with load more on scrolling incorporated
*/
public class CategoryImagesListFragment extends DaggerFragment {
private static int TIMEOUT_SECONDS = 15;
+ /**
+ * counts the total number of items loaded from the API
+ */
+ private int mediaSize = 0;
private GridViewAdapter gridAdapter;
@@ -256,6 +257,38 @@ public class CategoryImagesListFragment extends DaggerFragment {
progressBar.setVisibility(GONE);
isLoading = false;
statusTextView.setVisibility(GONE);
+ for (Media m : collection) {
+ final String pageId = m.getPageId();
+ if (pageId != null) {
+ replaceTitlesWithCaptions(PAGE_ID_PREFIX + pageId, mediaSize++);
+ }
+ }
+ }
+
+ /**
+ * fetch captions for the image using filename and replace title of on the image thumbnail(if captions are available)
+ * else show filename
+ */
+ public void replaceTitlesWithCaptions(String wikibaseIdentifier, int i) {
+ compositeDisposable.add(mediaClient.getCaptionByWikibaseIdentifier(wikibaseIdentifier)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
+ .subscribe(subscriber -> {
+ handleLabelforImage(subscriber, i);
+ }));
+
+ }
+
+ /**
+ * If caption is available for the image, then modify grid adapter
+ * to show captions
+ */
+ private void handleLabelforImage(String s, int position) {
+ if (!s.trim().equals(getString(R.string.detail_caption_empty))) {
+ gridAdapter.getItem(position).setThumbnailTitle(s);
+ gridAdapter.notifyDataSetChanged();
+ }
}
/**
diff --git a/app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.java b/app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.java
index 03b33b11c..c983af03a 100644
--- a/app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.java
+++ b/app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.java
@@ -8,6 +8,8 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.facebook.drawee.view.SimpleDraweeView;
import java.util.ArrayList;
@@ -55,7 +57,7 @@ public class GridViewAdapter extends ArrayAdapter {
data = new ArrayList<>();
return false;
}
- if (data.size() <= 0) {
+ if (data.isEmpty()) {
return false;
}
String fileName = data.get(0).getFilename();
@@ -86,12 +88,22 @@ public class GridViewAdapter extends ArrayAdapter {
SimpleDraweeView imageView = convertView.findViewById(R.id.categoryImageView);
TextView fileName = convertView.findViewById(R.id.categoryImageTitle);
TextView author = convertView.findViewById(R.id.categoryImageAuthor);
- fileName.setText(item.getDisplayTitle());
+ fileName.setText(item.getThumbnailTitle());
setAuthorView(item, author);
imageView.setImageURI(item.getThumbUrl());
return convertView;
}
+ /**
+ * @return the Media item at the given position
+ */
+ @Nullable
+ @Override
+ public Media getItem(int position) {
+ return data.get(position);
+ }
+
+
/**
* Shows author information if its present
* @param item
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 c67070acb..80c378137 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
@@ -1,36 +1,22 @@
package fr.free.nrw.commons.contributions;
-import android.content.Context;
-import android.net.Uri;
import android.os.Parcel;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.StringDef;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
-
-import org.apache.commons.lang3.StringUtils;
-
-import java.lang.annotation.Retention;
-import java.util.Date;
-import java.util.Locale;
-
-import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media;
-import fr.free.nrw.commons.filepicker.UploadableFile;
-import fr.free.nrw.commons.settings.Prefs;
-import fr.free.nrw.commons.utils.ConfigUtils;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
+import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.upload.UploadMediaDetail;
+import fr.free.nrw.commons.upload.UploadModel.UploadItem;
+import fr.free.nrw.commons.upload.WikidataPlace;
+import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.wikipedia.dataclient.mwapi.MwQueryLogEvent;
@Entity(tableName = "contribution")
-public class Contribution extends Media {
-
- //{{According to Exif data|2009-01-09}}
- private static final String TEMPLATE_DATE_ACC_TO_EXIF = "{{According to Exif data|%s}}";
-
- //2009-01-09 → 9 January 2009
- private static final String TEMPLATE_DATA_OTHER_SOURCE = "%s";
+public class Contribution extends Media {
// No need to be bitwise - they're mutually exclusive
public static final int STATE_COMPLETED = -1;
@@ -38,219 +24,133 @@ public class Contribution extends Media {
public static final int STATE_QUEUED = 2;
public static final int STATE_IN_PROGRESS = 3;
- @Retention(SOURCE)
- @StringDef({SOURCE_CAMERA, SOURCE_GALLERY, SOURCE_EXTERNAL})
- public @interface FileSource {}
-
- public static final String SOURCE_CAMERA = "camera";
- public static final String SOURCE_GALLERY = "gallery";
- public static final String SOURCE_EXTERNAL = "external";
@PrimaryKey (autoGenerate = true)
- @NonNull
- public long _id;
- public Uri contentUri;
- public String source;
- public String editSummary;
- public int state;
- public long transferred;
- public String decimalCoords;
- public boolean isMultiple;
- public String wikiDataEntityId;
- public String wikiItemName;
- private String p18Value;
- public Uri contentProviderUri;
- public String dateCreatedSource;
+ 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
+ */
+ 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: ""
+ */
+ private Map captions = new HashMap<>();
- public Contribution(Uri localUri, String imageUrl, String filename, String description, long dataLength,
- Date dateCreated, Date dateUploaded, String creator, String editSummary, String decimalCoords) {
- super(localUri, imageUrl, filename, description, dataLength, dateCreated, dateUploaded, creator);
- this.decimalCoords = decimalCoords;
- this.editSummary = editSummary;
- this.dateCreatedSource = "";
+ public Contribution() {
}
- public Contribution(Uri localUri, String imageUrl, String filename, String description, long dataLength,
- Date dateCreated, Date dateUploaded, String creator, String editSummary, String decimalCoords, int state) {
- super(localUri, imageUrl, filename, description, dataLength, dateCreated, dateUploaded, creator);
- this.decimalCoords = decimalCoords;
- this.editSummary = editSummary;
- this.dateCreatedSource = "";
- this.state=state;
+ public Contribution(final UploadItem item, final SessionManager sessionManager,
+ final List depictedItems, final List categories) {
+ super(item.getMediaUri(),
+ item.getFileName(),
+ UploadMediaDetail.formatList(item.getUploadMediaDetails()),
+ sessionManager.getAuthorName(),
+ categories);
+ 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(String dateCreatedSource) {
+ public void setDateCreatedSource(final String dateCreatedSource) {
this.dateCreatedSource = dateCreatedSource;
}
- public boolean getMultiple() {
- return isMultiple;
- }
-
- public void setMultiple(boolean multiple) {
- isMultiple = multiple;
+ public String getDateCreatedSource() {
+ return dateCreatedSource;
}
public long getTransferred() {
return transferred;
}
- public void setTransferred(long transferred) {
+ public void setTransferred(final long transferred) {
this.transferred = transferred;
}
- public String getEditSummary() {
- return editSummary != null ? editSummary : CommonsApplication.DEFAULT_EDIT_SUMMARY;
- }
-
- public Uri getContentUri() {
- return contentUri;
- }
-
- public void setContentUri(Uri contentUri) {
- this.contentUri = contentUri;
- }
-
public int getState() {
return state;
}
- public void setState(int state) {
+ public void setState(final int state) {
this.state = state;
}
- public void setDateUploaded(Date date) {
- this.dateUploaded = date;
+ /**
+ * @return array list of entityids for the depictions
+ */
+ public List getDepictedItems() {
+ return depictedItems;
}
- public String getPageContents(Context applicationContext) {
- StringBuilder buffer = new StringBuilder();
- buffer
- .append("== {{int:filedesc}} ==\n")
- .append("{{Information\n")
- .append("|description=").append(getDescription()).append("\n")
- .append("|source=").append("{{own}}\n")
- .append("|author=[[User:").append(creator).append("|").append(creator).append("]]\n");
+ public void setWikidataPlace(final WikidataPlace wikidataPlace) {
+ this.wikidataPlace = wikidataPlace;
+ }
- String templatizedCreatedDate = getTemplatizedCreatedDate();
- if (!StringUtils.isBlank(templatizedCreatedDate)) {
- buffer.append("|date=").append(templatizedCreatedDate);
- }
+ public WikidataPlace getWikidataPlace() {
+ return wikidataPlace;
+ }
- buffer.append("}}").append("\n");
+ public long get_id() {
+ return _id;
+ }
- //Only add Location template (e.g. {{Location|37.51136|-77.602615}} ) if coords is not null
- if (decimalCoords != null) {
- buffer.append("{{Location|").append(decimalCoords).append("}}").append("\n");
- }
+ public void set_id(final long _id) {
+ this._id = _id;
+ }
- buffer.append("== {{int:license-header}} ==\n")
- .append(licenseTemplateFor(getLicense())).append("\n\n")
- .append("{{Uploaded from Mobile|platform=Android|version=")
- .append(ConfigUtils.getVersionNameWithSha(applicationContext)).append("}}\n");
- if(categories!=null&&categories.size()!=0) {
- for (int i = 0; i < categories.size(); i++) {
- String category = categories.get(i);
- buffer.append("\n[[Category:").append(category).append("]]");
- }
- }
- else
- buffer.append("{{subst:unc}}");
- return buffer.toString();
+ public String getDecimalCoords() {
+ return decimalCoords;
+ }
+
+ public void setDecimalCoords(final String decimalCoords) {
+ this.decimalCoords = decimalCoords;
+ }
+
+ public void setDepictedItems(final List depictedItems) {
+ this.depictedItems = depictedItems;
+ }
+
+ public void setMimeType(String mimeType) {
+ this.mimeType = mimeType;
+ }
+
+ public String getMimeType() {
+ return mimeType;
}
/**
- * Returns upload date in either TEMPLATE_DATE_ACC_TO_EXIF or TEMPLATE_DATA_OTHER_SOURCE
- * @return
+ * 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
*/
- private String getTemplatizedCreatedDate() {
- if (dateCreated != null) {
- java.text.SimpleDateFormat dateFormat = new java.text.SimpleDateFormat("yyyy-MM-dd");
- if (UploadableFile.DateTimeWithSource.EXIF_SOURCE.equals(dateCreatedSource)) {
- return String.format(Locale.ENGLISH, TEMPLATE_DATE_ACC_TO_EXIF, dateFormat.format(dateCreated)) + "\n";
- } else {
- return String.format(Locale.ENGLISH, TEMPLATE_DATA_OTHER_SOURCE, dateFormat.format(dateCreated)) + "\n";
- }
- }
- return "";
+ public Map getCaptions() {
+ return captions;
}
- @Override
- public void setFilename(String filename) {
- this.filename = filename;
- }
-
- public void setImageUrl(String imageUrl) {
- this.imageUrl = imageUrl;
- }
-
- public Contribution() {
-
- }
-
- public String getSource() {
- return source;
- }
-
- public void setSource(String source) {
- this.source = source;
- }
-
- @NonNull
- private String licenseTemplateFor(String license) {
- switch (license) {
- case Prefs.Licenses.CC_BY_3:
- return "{{self|cc-by-3.0}}";
- case Prefs.Licenses.CC_BY_4:
- return "{{self|cc-by-4.0}}";
- case Prefs.Licenses.CC_BY_SA_3:
- return "{{self|cc-by-sa-3.0}}";
- case Prefs.Licenses.CC_BY_SA_4:
- return "{{self|cc-by-sa-4.0}}";
- case Prefs.Licenses.CC0:
- return "{{self|cc-zero}}";
- }
-
- throw new RuntimeException("Unrecognized license value: " + license);
- }
-
- public String getWikiDataEntityId() {
- return wikiDataEntityId;
- }
-
- public String getWikiItemName() {
- return wikiItemName;
- }
-
- /**
- * When the corresponding wikidata entity is known as in case of nearby uploads, it can be set
- * using the setter method
- * @param wikiDataEntityId wikiDataEntityId
- */
- public void setWikiDataEntityId(String wikiDataEntityId) {
- this.wikiDataEntityId = wikiDataEntityId;
- }
-
- public void setWikiItemName(String wikiItemName) {
- this.wikiItemName = wikiItemName;
- }
-
- public String getP18Value() {
- return p18Value;
- }
-
- /**
- * When the corresponding image property of wiki entity is known as in case of nearby uploads,
- * it can be set using the setter method
- * @param p18Value p18 value, image property of the wikidata item
- */
- public void setP18Value(String p18Value) {
- this.p18Value = p18Value;
- }
-
- public void setContentProviderUri(Uri contentProviderUri) {
- this.contentProviderUri = contentProviderUri;
+ public void setCaptions(Map captions) {
+ this.captions = captions;
}
@Override
@@ -259,48 +159,34 @@ public class Contribution extends Media {
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(final Parcel dest, final int flags) {
super.writeToParcel(dest, flags);
- dest.writeLong(this._id);
- dest.writeParcelable(this.contentUri, flags);
- dest.writeString(this.source);
- dest.writeString(this.editSummary);
- dest.writeInt(this.state);
- dest.writeLong(this.transferred);
- dest.writeString(this.decimalCoords);
- dest.writeByte(this.isMultiple ? (byte) 1 : (byte) 0);
- dest.writeString(this.wikiDataEntityId);
- dest.writeString(this.wikiItemName);
- dest.writeString(this.p18Value);
- dest.writeParcelable(this.contentProviderUri, flags);
- dest.writeString(this.dateCreatedSource);
+ dest.writeLong(_id);
+ dest.writeInt(state);
+ dest.writeLong(transferred);
+ dest.writeString(decimalCoords);
+ dest.writeString(dateCreatedSource);
+ dest.writeSerializable((HashMap) captions);
}
- protected Contribution(Parcel in) {
+ protected Contribution(final Parcel in) {
super(in);
- this._id = in.readLong();
- this.contentUri = in.readParcelable(Uri.class.getClassLoader());
- this.source = in.readString();
- this.editSummary = in.readString();
- this.state = in.readInt();
- this.transferred = in.readLong();
- this.decimalCoords = in.readString();
- this.isMultiple = in.readByte() != 0;
- this.wikiDataEntityId = in.readString();
- this.wikiItemName = in.readString();
- this.p18Value = in.readString();
- this.contentProviderUri = in.readParcelable(Uri.class.getClassLoader());
- this.dateCreatedSource = in.readString();
+ _id = in.readLong();
+ state = in.readInt();
+ transferred = in.readLong();
+ decimalCoords = in.readString();
+ dateCreatedSource = in.readString();
+ captions = (HashMap) in.readSerializable();
}
public static final Creator CREATOR = new Creator() {
@Override
- public Contribution createFromParcel(Parcel source) {
+ public Contribution createFromParcel(final Parcel source) {
return new Contribution(source);
}
@Override
- public Contribution[] newArray(int size) {
+ public Contribution[] newArray(final int size) {
return new Contribution[size];
}
};
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
index eb4d5711b..9e1de9736 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
@@ -1,19 +1,13 @@
package fr.free.nrw.commons.contributions;
+import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES;
+import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
+
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
-
import androidx.annotation.NonNull;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.filepicker.DefaultCallback;
import fr.free.nrw.commons.filepicker.FilePicker;
@@ -23,12 +17,11 @@ import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.upload.UploadActivity;
import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtil;
-
-import static fr.free.nrw.commons.contributions.Contribution.SOURCE_CAMERA;
-import static fr.free.nrw.commons.contributions.Contribution.SOURCE_GALLERY;
-import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES;
-import static fr.free.nrw.commons.upload.UploadService.EXTRA_SOURCE;
-import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
+import java.util.ArrayList;
+import java.util.List;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
@Singleton
public class ContributionController {
@@ -109,7 +102,7 @@ public class ContributionController {
@Override
public void onImagesPicked(@NonNull List imagesFiles, FilePicker.ImageSource source, int type) {
- Intent intent = handleImagesPicked(activity, imagesFiles, getSourceFromImageSource(source));
+ Intent intent = handleImagesPicked(activity, imagesFiles);
activity.startActivity(intent);
}
});
@@ -125,11 +118,9 @@ public class ContributionController {
* Attaches place object for nearby uploads
*/
private Intent handleImagesPicked(Context context,
- List imagesFiles,
- String source) {
+ List imagesFiles) {
Intent shareIntent = new Intent(context, UploadActivity.class);
shareIntent.setAction(ACTION_INTERNAL_UPLOADS);
- shareIntent.putExtra(EXTRA_SOURCE, source);
shareIntent.putParcelableArrayListExtra(EXTRA_FILES, new ArrayList<>(imagesFiles));
Place place = defaultKvStore.getJson(PLACE_OBJECT, Place.class);
if (place != null) {
@@ -139,13 +130,4 @@ public class ContributionController {
return shareIntent;
}
- /**
- * Get image upload source
- */
- private String getSourceFromImageSource(FilePicker.ImageSource source) {
- if (source.equals(FilePicker.ImageSource.CAMERA_IMAGE)) {
- return SOURCE_CAMERA;
- }
- return SOURCE_GALLERY;
- }
}
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 261784b52..3c0ac925f 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
@@ -7,13 +7,10 @@ import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Transaction;
-
import androidx.room.Update;
-import io.reactivex.disposables.Disposable;
-import java.util.List;
-
import io.reactivex.Completable;
import io.reactivex.Single;
+import java.util.List;
@Dao
public abstract class ContributionDao {
@@ -40,9 +37,6 @@ public abstract class ContributionDao {
@Delete
public abstract Single delete(Contribution contribution);
- @Query("SELECT * from contribution WHERE contentProviderUri=:uri")
- public abstract List getContributionWithUri(String uri);
-
@Query("SELECT * from contribution WHERE filename=:fileName")
public abstract List getContributionWithTitle(String fileName);
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 457c31fa5..824953650 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
@@ -1,33 +1,34 @@
package fr.free.nrw.commons.contributions;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
+import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX;
+
import android.net.Uri;
import android.text.TextUtils;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
-
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
-
-import com.facebook.drawee.view.SimpleDraweeView;
-
-import com.facebook.imagepipeline.request.ImageRequest;
-import com.facebook.imagepipeline.request.ImageRequestBuilder;
-
-
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
+import com.facebook.drawee.view.SimpleDraweeView;
+import com.facebook.imagepipeline.request.ImageRequest;
+import com.facebook.imagepipeline.request.ImageRequestBuilder;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
-import java.util.HashMap;
+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 java.util.concurrent.TimeUnit;
+import timber.log.Timber;
public class ContributionViewHolder extends RecyclerView.ViewHolder {
+ private static final long TIMEOUT_SECONDS = 15;
private final Callback callback;
@BindView(R.id.contributionImage)
SimpleDraweeView imageView;
@@ -37,20 +38,26 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.contributionProgress) ProgressBar progressView;
@BindView(R.id.failed_image_options) LinearLayout failedImageOptions;
+
private int position;
private Contribution contribution;
private Random random = new Random();
+ private CompositeDisposable compositeDisposable = new CompositeDisposable();
+ private final MediaClient mediaClient;
- ContributionViewHolder(View parent, Callback callback) {
+ ContributionViewHolder(View parent, Callback callback,
+ MediaClient mediaClient) {
super(parent);
+ this.mediaClient = mediaClient;
ButterKnife.bind(this, parent);
this.callback=callback;
}
public void init(int position, Contribution contribution) {
this.contribution = contribution;
+ fetchAndDisplayCaption(contribution);
this.position = position;
- String imageSource = chooseImageSource(contribution.thumbUrl, contribution.getLocalUri());
+ String imageSource = chooseImageSource(contribution.getThumbUrl(), contribution.getLocalUri());
if (!TextUtils.isEmpty(imageSource)) {
final ImageRequest imageRequest =
ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource))
@@ -58,7 +65,6 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
.build();
imageView.setImageRequest(imageRequest);
}
- titleView.setText(contribution.getDisplayTitle());
seqNumView.setText(String.valueOf(position + 1));
seqNumView.setVisibility(View.VISIBLE);
@@ -97,6 +103,38 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
}
}
+ /**
+ * In contributions first we show the title for the image stored in cache,
+ * then we fetch captions associated with the image and replace title on the thumbnail with caption
+ *
+ * @param contribution
+ */
+ private void fetchAndDisplayCaption(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
+ + pageId; // Create Wikibase media id from the page id. Example media id: M80618155 for https://commons.wikimedia.org/wiki/File:Tantanmen.jpeg with has the pageid 80618155
+ compositeDisposable.add(mediaClient.getCaptionByWikibaseIdentifier(wikibaseMediaId)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
+ .subscribe(subscriber -> {
+ if (!subscriber.trim().equals(MediaClient.NO_CAPTION)) {
+ titleView.setText(subscriber);
+ } else {
+ titleView.setText(contribution.getDisplayTitle());
+ }
+ }));
+ } else {
+ titleView.setText(contribution.getDisplayTitle());
+ }
+ }
+ }
+
/**
* Returns the image source for the image view, first preference is given to thumbUrl if that is
* null, moves to local uri and if both are null return null
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 ab49e5993..64b836c39 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
@@ -1,5 +1,9 @@
package fr.free.nrw.commons.contributions;
+import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
+import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
+import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
+
import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
@@ -12,21 +16,12 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
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 fr.free.nrw.commons.MediaDataExtractor;
-import io.reactivex.disposables.Disposable;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.HandlerService;
@@ -60,12 +55,11 @@ 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;
-import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
-import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
-import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
-
public class ContributionsFragment
extends CommonsDaggerSupportFragment
implements
@@ -221,7 +215,7 @@ public class ContributionsFragment
@Override
public void fetchMediaUriFor(Contribution contribution) {
- Timber.d("Fetching thumbnail for %s", contribution.filename);
+ Timber.d("Fetching thumbnail for %s", contribution.getFilename());
contributionsPresenter.fetchMediaDetails(contribution);
}
});
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 8b8d2fff1..82daf97f4 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,29 +1,28 @@
package fr.free.nrw.commons.contributions;
-import android.os.Handler;
-import android.os.Looper;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.ViewGroup;
-
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
-
+import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.media.MediaClient;
import java.util.ArrayList;
import java.util.List;
-import fr.free.nrw.commons.R;
-
/**
* Represents The View Adapter for the List of Contributions
*/
public class ContributionsListAdapter extends RecyclerView.Adapter {
private Callback callback;
+ private final MediaClient mediaClient;
private List contributions;
- public ContributionsListAdapter(Callback callback) {
+ public ContributionsListAdapter(Callback callback,
+ MediaClient mediaClient) {
this.callback = callback;
+ this.mediaClient = mediaClient;
contributions = new ArrayList<>();
}
@@ -36,7 +35,7 @@ public class ContributionsListAdapter extends RecyclerView.Adapter Timber.d("Received image %s", mwQueryLogEvent.title()))
.filter(mwQueryLogEvent -> !mwQueryLogEvent.isDeleted()).doOnNext(mwQueryLogEvent -> Timber.d("Image %s passed filters", mwQueryLogEvent.title()))
- .map(image -> {
- Contribution contribution = new Contribution(null, null, image.title(),
- "", -1, image.date(), image.date(), user,
- "", "", STATE_COMPLETED);
- return contribution;
- })
+ .map(image -> new Contribution(image, user))
.toList()
.subscribe(this::saveContributionsToDB, error -> {
Timber.e("Failed to fetch contributions: %s", error.getMessage());
@@ -197,11 +177,11 @@ public class ContributionsPresenter implements UserActionListener {
@Override
public void fetchMediaDetails(Contribution contribution) {
compositeDisposable.add(mediaDataExtractor
- .getMediaFromFileName(contribution.filename)
+ .getMediaFromFileName(contribution.getFilename())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(media -> {
- contribution.thumbUrl=media.thumbUrl;
+ contribution.setThumbUrl(media.getThumbUrl());
updateContribution(contribution);
}));
}
diff --git a/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.java b/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.java
deleted file mode 100644
index 61097fa6f..000000000
--- a/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package fr.free.nrw.commons.db;
-
-import androidx.room.Database;
-import androidx.room.RoomDatabase;
-import androidx.room.TypeConverters;
-
-import fr.free.nrw.commons.contributions.Contribution;
-import fr.free.nrw.commons.contributions.ContributionDao;
-
-@Database(entities = {Contribution.class}, version = 1, exportSchema = false)
-@TypeConverters({Converters.class})
-abstract public class AppDatabase extends RoomDatabase {
- public abstract ContributionDao getContributionDao();
-}
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
new file mode 100644
index 000000000..01095dd7c
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt
@@ -0,0 +1,17 @@
+package fr.free.nrw.commons.db
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+import androidx.room.TypeConverters
+import fr.free.nrw.commons.contributions.Contribution
+import fr.free.nrw.commons.contributions.ContributionDao
+
+/**
+ * The database for accessing the respective DAOs
+ *
+ */
+@Database(entities = [Contribution::class], version = 1, exportSchema = false)
+@TypeConverters(Converters::class)
+abstract class AppDatabase : RoomDatabase() {
+ abstract fun contributionDao(): ContributionDao
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/db/Converters.java b/app/src/main/java/fr/free/nrw/commons/db/Converters.java
index bea973612..94311c67c 100644
--- a/app/src/main/java/fr/free/nrw/commons/db/Converters.java
+++ b/app/src/main/java/fr/free/nrw/commons/db/Converters.java
@@ -1,22 +1,22 @@
package fr.free.nrw.commons.db;
import android.net.Uri;
-
import androidx.room.TypeConverter;
-
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
-
-import org.wikipedia.json.GsonUtil;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.location.LatLng;
+import fr.free.nrw.commons.media.Depictions;
+import fr.free.nrw.commons.upload.WikidataPlace;
+import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+/**
+ * This class supplies converters to write/read types to/from the database.
+ */
public class Converters {
public static Gson getGson() {
@@ -44,33 +44,74 @@ public class Converters {
}
@TypeConverter
- public static String listObjectToString(ArrayList objectList) {
- return objectList == null ? null : getGson().toJson(objectList);
+ public static String listObjectToString(List objectList) {
+ return writeObjectToString(objectList);
}
@TypeConverter
- public static ArrayList stringToArrayListObject(String objectList) {
- return objectList == null ? null : getGson().fromJson(objectList,new TypeToken>(){}.getType());
+ public static List stringToListObject(String objectList) {
+ return readObjectWithTypeToken(objectList, new TypeToken>() {});
}
@TypeConverter
- public static String mapObjectToString(HashMap objectList) {
- return objectList == null ? null : getGson().toJson(objectList);
+ public static String mapObjectToString(Map objectList) {
+ return writeObjectToString(objectList);
}
@TypeConverter
- public static HashMap stringToMap(String objectList) {
- return objectList == null ? null : getGson().fromJson(objectList,new TypeToken>(){}.getType());
+ public static Map stringToMap(String objectList) {
+ return readObjectWithTypeToken(objectList, new TypeToken