mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
#3077 Hide disambiguation items from depiction search - when fetching depictions get the entity instead of the search result (#3741)
This commit is contained in:
parent
34f02499e4
commit
057d11a0e0
31 changed files with 359 additions and 582 deletions
|
|
@ -13,8 +13,6 @@ public interface SubDepictionListContract {
|
|||
|
||||
interface View {
|
||||
|
||||
void onImageUrlFetched(String response, int position);
|
||||
|
||||
void onSuccess(List<DepictedItem> mediaList);
|
||||
|
||||
void initErrorView();
|
||||
|
|
@ -23,15 +21,10 @@ public interface SubDepictionListContract {
|
|||
|
||||
void setIsLastPage(boolean b);
|
||||
|
||||
boolean isParentClass();
|
||||
}
|
||||
|
||||
interface UserActionListener extends BasePresenter<View> {
|
||||
|
||||
void saveQuery();
|
||||
|
||||
void fetchThumbnailForEntityId(String entityId, int position);
|
||||
|
||||
void initSubDepictionList(String qid, Boolean isParentClass) throws IOException;
|
||||
|
||||
String getQuery();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package fr.free.nrw.commons.depictions.subClass;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
|
|
@ -7,23 +10,14 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
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 com.pedrogomez.renderers.RVRendererAdapter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||
import dagger.android.support.DaggerFragment;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity;
|
||||
|
|
@ -32,9 +26,10 @@ import fr.free.nrw.commons.explore.depictions.SearchDepictionsRenderer;
|
|||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Fragment for parent classes and child classes of Depicted items in Explore
|
||||
|
|
@ -54,10 +49,6 @@ public class SubDepictionListFragment extends DaggerFragment implements SubDepic
|
|||
*/
|
||||
private boolean isParentClass = false;
|
||||
private RVRendererAdapter<DepictedItem> depictionsAdapter;
|
||||
/**
|
||||
* Used by scroll state listener, when hasMoreImages is false scrolling does not fetches any more images
|
||||
*/
|
||||
private boolean hasMoreImages = true;
|
||||
RecyclerView.LayoutManager layoutManager;
|
||||
/**
|
||||
* Stores entityId for the depiction
|
||||
|
|
@ -77,12 +68,6 @@ public class SubDepictionListFragment extends DaggerFragment implements SubDepic
|
|||
getActivity().finish();
|
||||
WikidataItemDetailsActivity.startYourself(getContext(), item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchThumbnailUrlForEntity(String entityId, int position) {
|
||||
presenter.fetchThumbnailForEntityId(entityId, position);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@Override
|
||||
|
|
@ -140,15 +125,8 @@ public class SubDepictionListFragment extends DaggerFragment implements SubDepic
|
|||
ViewUtil.showShortSnackbar(depictionsRecyclerView, R.string.no_internet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImageUrlFetched(String response, int position) {
|
||||
depictionsAdapter.getItem(position).setImageUrl(response);
|
||||
depictionsAdapter.notifyItemChanged(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(List<DepictedItem> mediaList) {
|
||||
hasMoreImages = false;
|
||||
progressBar.setVisibility(View.GONE);
|
||||
depictionNotFound.setVisibility(GONE);
|
||||
bottomProgressBar.setVisibility(GONE);
|
||||
|
|
@ -164,7 +142,6 @@ public class SubDepictionListFragment extends DaggerFragment implements SubDepic
|
|||
|
||||
@Override
|
||||
public void initErrorView() {
|
||||
hasMoreImages = false;
|
||||
progressBar.setVisibility(GONE);
|
||||
bottomProgressBar.setVisibility(GONE);
|
||||
depictionNotFound.setVisibility(VISIBLE);
|
||||
|
|
@ -180,11 +157,6 @@ public class SubDepictionListFragment extends DaggerFragment implements SubDepic
|
|||
|
||||
@Override
|
||||
public void setIsLastPage(boolean b) {
|
||||
hasMoreImages = !b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isParentClass() {
|
||||
return isParentClass;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
|
|||
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
||||
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient;
|
||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearch;
|
||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
|
|
@ -13,7 +12,6 @@ import io.reactivex.disposables.CompositeDisposable;
|
|||
import java.io.IOException;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
|
@ -48,10 +46,6 @@ public class SubDepictionListPresenter implements SubDepictionListContract.UserA
|
|||
DepictsClient depictsClient;
|
||||
private List<DepictedItem> queryList = new ArrayList<>();
|
||||
OkHttpJsonApiClient okHttpJsonApiClient;
|
||||
/**
|
||||
* variable used to record the number of API calls already made for fetching Thumbnails
|
||||
*/
|
||||
private int size = 0;
|
||||
|
||||
@Inject
|
||||
public SubDepictionListPresenter(RecentSearchesDao recentSearchesDao, DepictsClient depictsClient, OkHttpJsonApiClient okHttpJsonApiClient, @Named(IO_THREAD) Scheduler ioScheduler,
|
||||
|
|
@ -72,38 +66,8 @@ public class SubDepictionListPresenter implements SubDepictionListContract.UserA
|
|||
this.view = DUMMY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the current query in Recent searches
|
||||
*/
|
||||
@Override
|
||||
public void saveQuery() {
|
||||
RecentSearch recentSearch = recentSearchesDao.find(query);
|
||||
|
||||
// Newly searched query...
|
||||
if (recentSearch == null) {
|
||||
recentSearch = new RecentSearch(null, query, new Date());
|
||||
} else {
|
||||
recentSearch.setLastSearched(new Date());
|
||||
}
|
||||
recentSearchesDao.save(recentSearch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls Wikibase APIs to fetch Thumbnail image for a given wikidata item
|
||||
*/
|
||||
@Override
|
||||
public void fetchThumbnailForEntityId(String entityId, int position) {
|
||||
compositeDisposable.add(depictsClient.getP18ForItem(entityId)
|
||||
.subscribeOn(ioScheduler)
|
||||
.observeOn(mainThreadScheduler)
|
||||
.subscribe(response -> {
|
||||
view.onImageUrlFetched(response,position);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSubDepictionList(String qid, Boolean isParentClass) throws IOException {
|
||||
size = 0;
|
||||
if (isParentClass) {
|
||||
compositeDisposable.add(okHttpJsonApiClient.getParentQIDs(qid)
|
||||
.subscribeOn(ioScheduler)
|
||||
|
|
@ -137,9 +101,6 @@ public class SubDepictionListPresenter implements SubDepictionListContract.UserA
|
|||
} else {
|
||||
this.queryList.addAll(mediaList);
|
||||
view.onSuccess(mediaList);
|
||||
for (DepictedItem m : mediaList) {
|
||||
fetchThumbnailForEntityId(m.getId(), size++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,6 @@
|
|||
package fr.free.nrw.commons.depictions.subClass.models
|
||||
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
|
||||
data class SparqlResponse(val results: Result) {
|
||||
fun toDepictedItems() =
|
||||
results.bindings.map {
|
||||
DepictedItem(
|
||||
it.itemLabel.value,
|
||||
it.itemDescription?.value ?: "",
|
||||
"",
|
||||
false,
|
||||
it.item.value.substringAfterLast("/")
|
||||
)
|
||||
}
|
||||
}
|
||||
data class SparqlResponse(val results: Result)
|
||||
|
||||
data class Result(val bindings: List<Binding>)
|
||||
|
||||
|
|
@ -21,6 +8,8 @@ data class Binding(
|
|||
val item: SparqInfo,
|
||||
val itemLabel: SparqInfo,
|
||||
val itemDescription: SparqInfo? = null
|
||||
)
|
||||
) {
|
||||
val id: String by lazy { item.value.substringAfterLast("/") }
|
||||
}
|
||||
|
||||
data class SparqInfo(val type: String, val value: String)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import fr.free.nrw.commons.BuildConfig;
|
|||
import fr.free.nrw.commons.actions.PageEditClient;
|
||||
import fr.free.nrw.commons.actions.PageEditInterface;
|
||||
import fr.free.nrw.commons.category.CategoryInterface;
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.media.MediaDetailInterface;
|
||||
import fr.free.nrw.commons.media.MediaInterface;
|
||||
|
|
@ -77,10 +78,12 @@ public class NetworkingModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
public OkHttpJsonApiClient provideOkHttpJsonApiClient(OkHttpClient okHttpClient,
|
||||
DepictsClient depictsClient,
|
||||
@Named("tools_forge") HttpUrl toolsForgeUrl,
|
||||
@Named("default_preferences") JsonKvStore defaultKvStore,
|
||||
Gson gson) {
|
||||
return new OkHttpJsonApiClient(okHttpClient,
|
||||
depictsClient,
|
||||
toolsForgeUrl,
|
||||
WIKIDATA_SPARQL_QUERY_URL,
|
||||
BuildConfig.WIKIMEDIA_CAMPAIGNS_URL,
|
||||
|
|
|
|||
|
|
@ -1,175 +0,0 @@
|
|||
package fr.free.nrw.commons.explore.depictions;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.depictions.models.Search;
|
||||
import fr.free.nrw.commons.media.MediaInterface;
|
||||
import fr.free.nrw.commons.upload.depicts.DepictsInterface;
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
import fr.free.nrw.commons.utils.CommonsDateUtil;
|
||||
import fr.free.nrw.commons.wikidata.WikidataProperties;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import org.wikipedia.wikidata.DataValue.DataValueString;
|
||||
import org.wikipedia.wikidata.Statement_partial;
|
||||
|
||||
/**
|
||||
* Depicts Client to handle custom calls to Commons Wikibase APIs
|
||||
*/
|
||||
@Singleton
|
||||
public class DepictsClient {
|
||||
|
||||
private final DepictsInterface depictsInterface;
|
||||
private final MediaInterface mediaInterface;
|
||||
public static final String NO_DEPICTED_IMAGE = "No Image for Depiction";
|
||||
|
||||
@Inject
|
||||
public DepictsClient(DepictsInterface depictsInterface, MediaInterface mediaInterface) {
|
||||
this.depictsInterface = depictsInterface;
|
||||
this.mediaInterface = mediaInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for depictions using the search item
|
||||
* @return list of depicted items
|
||||
*/
|
||||
public Observable<DepictedItem> searchForDepictions(String query, int limit, int offset) {
|
||||
return depictsInterface.searchForDepicts(
|
||||
query,
|
||||
String.valueOf(limit),
|
||||
Locale.getDefault().getLanguage(),
|
||||
Locale.getDefault().getLanguage(),
|
||||
String.valueOf(offset)
|
||||
)
|
||||
.toObservable()
|
||||
.flatMap( depictSearchResponse ->
|
||||
Observable.fromIterable(depictSearchResponse.getSearch()))
|
||||
.map(DepictedItem::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URL for image using image name
|
||||
* Ex: title = Guion Bluford
|
||||
* Url = https://upload.wikimedia.org/wikipedia/commons/thumb/0/04/Guion_Bluford.jpg/70px-Guion_Bluford.jpg
|
||||
*/
|
||||
private String getThumbnailUrl(String title) {
|
||||
String baseUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/";
|
||||
title = title.replace(" ", "_");
|
||||
String MD5Hash = getMd5(title);
|
||||
/**
|
||||
* We use 70 pixels as the size of our Thumbnail (as it is the perfect fits our UI)
|
||||
*/
|
||||
return baseUrl + MD5Hash.charAt(0) + '/' + MD5Hash.charAt(0) + MD5Hash.charAt(1) + '/' + title + "/70px-" + title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ex: entityId = Q357458
|
||||
* value returned = Elgin Baylor Night program.jpeg
|
||||
*/
|
||||
public Single<String> getP18ForItem(String entityId) {
|
||||
return depictsInterface.getImageForEntity(entityId)
|
||||
.map(claimsResponse -> {
|
||||
final List<Statement_partial> imageClaim = claimsResponse.getClaims()
|
||||
.get(WikidataProperties.IMAGE.getPropertyName());
|
||||
final DataValueString dataValue = (DataValueString) imageClaim
|
||||
.get(0)
|
||||
.getMainSnak()
|
||||
.getDataValue();
|
||||
return getThumbnailUrl((dataValue.getValue()));
|
||||
})
|
||||
.onErrorReturn(throwable -> NO_DEPICTED_IMAGE)
|
||||
.singleOrError();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of images for a particular depict entity
|
||||
*/
|
||||
public Observable<List<Media>> fetchImagesForDepictedItem(String query, int sroffset) {
|
||||
return mediaInterface.fetchImagesForDepictedItem("haswbstatement:" + BuildConfig.DEPICTS_PROPERTY + "=" + query, String.valueOf(sroffset))
|
||||
.map(mwQueryResponse -> {
|
||||
List<Media> mediaList = new ArrayList<>();
|
||||
for (Search s: mwQueryResponse.getQuery().getSearch()) {
|
||||
Media media = new Media(null,
|
||||
getUrl(s.getTitle()),
|
||||
s.getTitle(),
|
||||
"",
|
||||
0,
|
||||
safeParseDate(s.getTimestamp()),
|
||||
safeParseDate(s.getTimestamp()),
|
||||
""
|
||||
);
|
||||
mediaList.add(media);
|
||||
}
|
||||
return mediaList;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get url for the image from media of depictions
|
||||
* Ex: Tiger_Woods
|
||||
* Value: https://upload.wikimedia.org/wikipedia/commons/thumb/6/67/Tiger_Woods.jpg/70px-Tiger_Woods.jpg
|
||||
*/
|
||||
private String getUrl(String title) {
|
||||
String baseUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/";
|
||||
title = title.substring(title.indexOf(':')+1);
|
||||
title = title.replace(" ", "_");
|
||||
String MD5Hash = getMd5(title);
|
||||
return baseUrl + MD5Hash.charAt(0) + '/' + MD5Hash.charAt(0) + MD5Hash.charAt(1) + '/' + title + "/640px-" + title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates MD5 hash for the filename
|
||||
*/
|
||||
public String getMd5(String input)
|
||||
{
|
||||
try {
|
||||
|
||||
// Static getInstance method is called with hashing MD5
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
|
||||
// digest() method is called to calculate message digest
|
||||
// of an input digest() return array of byte
|
||||
byte[] messageDigest = md.digest(input.getBytes());
|
||||
|
||||
// Convert byte array into signum representation
|
||||
BigInteger no = new BigInteger(1, messageDigest);
|
||||
|
||||
// Convert message digest into hex value
|
||||
String hashtext = no.toString(16);
|
||||
while (hashtext.length() < 32) {
|
||||
hashtext = "0" + hashtext;
|
||||
}
|
||||
return hashtext;
|
||||
}
|
||||
|
||||
// For specifying wrong message digest algorithms
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the date string into the required format
|
||||
* @param dateStr
|
||||
* @return date in the required format
|
||||
*/
|
||||
@Nullable
|
||||
private static Date safeParseDate(String dateStr) {
|
||||
try {
|
||||
return CommonsDateUtil.getIso8601DateFormatShort().parse(dateStr);
|
||||
} catch (ParseException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.depictions.models.DepictionResponse
|
||||
import fr.free.nrw.commons.depictions.subClass.models.Binding
|
||||
import fr.free.nrw.commons.depictions.subClass.models.SparqlResponse
|
||||
import fr.free.nrw.commons.media.MediaInterface
|
||||
import fr.free.nrw.commons.upload.depicts.DepictsInterface
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import fr.free.nrw.commons.utils.CommonsDateUtil
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import org.wikipedia.wikidata.Entities
|
||||
import java.math.BigInteger
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.text.ParseException
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
const val LARGE_IMAGE_SIZE="640px"
|
||||
const val THUMB_IMAGE_SIZE="70px"
|
||||
/**
|
||||
* Depicts Client to handle custom calls to Commons Wikibase APIs
|
||||
*/
|
||||
@Singleton
|
||||
class DepictsClient @Inject constructor(
|
||||
private val depictsInterface: DepictsInterface,
|
||||
private val mediaInterface: MediaInterface
|
||||
) {
|
||||
|
||||
/**
|
||||
* Search for depictions using the search item
|
||||
* @return list of depicted items
|
||||
*/
|
||||
fun searchForDepictions(query: String?, limit: Int, offset: Int): Single<List<DepictedItem>> {
|
||||
val language = Locale.getDefault().language
|
||||
return depictsInterface.searchForDepicts(query, "$limit", language, language, "$offset")
|
||||
.map { it.search.joinToString("|") { searchItem -> searchItem.id } }
|
||||
.flatMap(::getEntities)
|
||||
.map { it.entities()?.values?.map(::DepictedItem) ?: emptyList() }
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of images for a particular depict entity
|
||||
*/
|
||||
fun fetchImagesForDepictedItem(query: String, sroffset: Int): Observable<List<Media>> {
|
||||
return mediaInterface.fetchImagesForDepictedItem(
|
||||
"haswbstatement:" + BuildConfig.DEPICTS_PROPERTY + "=" + query,
|
||||
sroffset.toString()
|
||||
)
|
||||
.map { mwQueryResponse: DepictionResponse ->
|
||||
mwQueryResponse.query
|
||||
.search
|
||||
.map {
|
||||
Media(
|
||||
null,
|
||||
getUrl(it.title),
|
||||
it.title,
|
||||
"",
|
||||
0,
|
||||
safeParseDate(it.timestamp),
|
||||
safeParseDate(it.timestamp),
|
||||
""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getUrl(title: String): String {
|
||||
return getImageUrl(title, LARGE_IMAGE_SIZE)
|
||||
}
|
||||
|
||||
fun getEntities(ids: String): Single<Entities> {
|
||||
return depictsInterface.getEntities(ids, Locale.getDefault().language)
|
||||
}
|
||||
|
||||
fun toDepictions(sparqlResponse: Observable<SparqlResponse>): Observable<List<DepictedItem>> {
|
||||
return sparqlResponse.map { it.results.bindings.joinToString("|", transform = Binding::id) }
|
||||
.flatMap { getEntities(it).toObservable() }
|
||||
.map { it.entities()?.values?.map(::DepictedItem) ?: emptyList() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Get url for the image from media of depictions
|
||||
* Ex: Tiger_Woods
|
||||
* Value: https://upload.wikimedia.org/wikipedia/commons/thumb/6/67/Tiger_Woods.jpg/70px-Tiger_Woods.jpg
|
||||
*/
|
||||
fun getImageUrl(title: String, size: String): String {
|
||||
return title.substringAfter(":")
|
||||
.replace(" ", "_")
|
||||
.let {
|
||||
val MD5Hash = getMd5(it)
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/${MD5Hash[0]}/${MD5Hash[0]}${MD5Hash[1]}/$it/$size-$it"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates MD5 hash for the filename
|
||||
*/
|
||||
fun getMd5(input: String): String {
|
||||
return try {
|
||||
|
||||
// Static getInstance method is called with hashing MD5
|
||||
val md = MessageDigest.getInstance("MD5")
|
||||
|
||||
// digest() method is called to calculate message digest
|
||||
// of an input digest() return array of byte
|
||||
val messageDigest = md.digest(input.toByteArray())
|
||||
|
||||
// Convert byte array into signum representation
|
||||
val no = BigInteger(1, messageDigest)
|
||||
|
||||
// Convert message digest into hex value
|
||||
var hashtext = no.toString(16)
|
||||
while (hashtext.length < 32) {
|
||||
hashtext = "0$hashtext"
|
||||
}
|
||||
hashtext
|
||||
} // For specifying wrong message digest algorithms
|
||||
catch (e: NoSuchAlgorithmException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the date string into the required format
|
||||
* @param dateStr
|
||||
* @return date in the required format
|
||||
*/
|
||||
private fun safeParseDate(dateStr: String): Date? {
|
||||
return try {
|
||||
CommonsDateUtil.getIso8601DateFormatShort().parse(dateStr)
|
||||
} catch (e: ParseException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -53,15 +53,6 @@ public class SearchDepictionsFragment extends CommonsDaggerSupportFragment imple
|
|||
WikidataItemDetailsActivity.startYourself(getContext(), item);
|
||||
presenter.saveQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
*fetch thumbnail image for all the depicted items (if available)
|
||||
*/
|
||||
@Override
|
||||
public void fetchThumbnailUrlForEntity(String entityId, int position) {
|
||||
presenter.fetchThumbnailForEntityId(entityId,position);
|
||||
}
|
||||
|
||||
});
|
||||
private RVRendererAdapter<DepictedItem> depictionsAdapter;
|
||||
private boolean isLastPage;
|
||||
|
|
@ -218,12 +209,6 @@ public class SearchDepictionsFragment extends CommonsDaggerSupportFragment imple
|
|||
return depictionsAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImageUrlFetched(String response, int position) {
|
||||
depictionsAdapter.getItem(position).setImageUrl(response);
|
||||
depictionsAdapter.notifyItemChanged(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inform the view that there are no more items to be loaded for this search query
|
||||
* or reset the isLastPage for the current query
|
||||
|
|
|
|||
|
|
@ -49,8 +49,6 @@ public interface SearchDepictionsFragmentContract {
|
|||
*/
|
||||
RVRendererAdapter<DepictedItem> getAdapter();
|
||||
|
||||
void onImageUrlFetched(String response, int position);
|
||||
|
||||
/**
|
||||
* Inform the view that there are no more items to be loaded for this search query
|
||||
* or reset the isLastPage for the current query
|
||||
|
|
@ -85,10 +83,5 @@ public interface SearchDepictionsFragmentContract {
|
|||
* @return query
|
||||
*/
|
||||
String getQuery();
|
||||
|
||||
/**
|
||||
* After all the depicted items are loaded fetch thumbnail image for all the depicted items (if available)
|
||||
*/
|
||||
void fetchThumbnailForEntityId(String entityId,int position);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ public class SearchDepictionsFragmentPresenter extends CommonsDaggerSupportFragm
|
|||
.subscribeOn(ioScheduler)
|
||||
.observeOn(mainThreadScheduler)
|
||||
.doOnSubscribe(disposable -> saveQuery())
|
||||
.collect(ArrayList<DepictedItem>::new, ArrayList::add)
|
||||
.subscribe(this::handleSuccess, this::handleError));
|
||||
}
|
||||
|
||||
|
|
@ -154,23 +153,7 @@ public class SearchDepictionsFragmentPresenter extends CommonsDaggerSupportFragm
|
|||
this.queryList.addAll(mediaList);
|
||||
view.onSuccess(mediaList);
|
||||
offset=queryList.size();
|
||||
for (DepictedItem m : mediaList) {
|
||||
fetchThumbnailForEntityId(m.getId(), size++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After all the depicted items are loaded fetch thumbnail image for all the depicted items (if available)
|
||||
*/
|
||||
@Override
|
||||
public void fetchThumbnailForEntityId(String entityId,int position) {
|
||||
compositeDisposable.add(depictsClient.getP18ForItem(entityId)
|
||||
.subscribeOn(ioScheduler)
|
||||
.observeOn(mainThreadScheduler)
|
||||
.subscribe(response -> {
|
||||
view.onImageUrlFetched(response,position);
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package fr.free.nrw.commons.explore.depictions;
|
||||
|
||||
import static fr.free.nrw.commons.explore.depictions.DepictsClient.NO_DEPICTED_IMAGE;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
|
|
@ -10,12 +9,9 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
import com.facebook.common.executors.CallerThreadExecutor;
|
||||
import com.facebook.common.references.CloseableReference;
|
||||
import com.facebook.datasource.DataSource;
|
||||
|
|
@ -26,10 +22,8 @@ import com.facebook.imagepipeline.image.CloseableImage;
|
|||
import com.facebook.imagepipeline.request.ImageRequest;
|
||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||
import com.pedrogomez.renderers.Renderer;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Renderer for DepictedItem
|
||||
|
|
@ -81,10 +75,7 @@ public class SearchDepictionsRenderer extends Renderer<DepictedItem> {
|
|||
tvDepictionDesc.setText(item.getDescription());
|
||||
imageView.setImageDrawable(getContext().getResources().getDrawable(R.drawable.ic_wikidata_logo_24dp));
|
||||
|
||||
Timber.e("line86"+item.getImageUrl());
|
||||
if (!TextUtils.isEmpty(item.getImageUrl())) {
|
||||
if (!item.getImageUrl().equals(NO_DEPICTED_IMAGE) && !item.getImageUrl().equals(""))
|
||||
{
|
||||
ImageRequest imageRequest = ImageRequestBuilder
|
||||
.newBuilderWithSource(Uri.parse(item.getImageUrl()))
|
||||
.setAutoRotateEnabled(true)
|
||||
|
|
@ -99,7 +90,6 @@ public class SearchDepictionsRenderer extends Renderer<DepictedItem> {
|
|||
@Override
|
||||
public void onNewResultImpl(@Nullable Bitmap bitmap) {
|
||||
if (dataSource.isFinished() && bitmap != null) {
|
||||
Timber.d("Bitmap loaded from url %s", item.getImageUrl());
|
||||
//imageView.setImageBitmap(Bitmap.createBitmap(bitmap));
|
||||
imageView.post(() -> imageView.setImageBitmap(Bitmap.createBitmap(bitmap)));
|
||||
dataSource.close();
|
||||
|
|
@ -108,7 +98,6 @@ public class SearchDepictionsRenderer extends Renderer<DepictedItem> {
|
|||
|
||||
@Override
|
||||
public void onFailureImpl(DataSource dataSource) {
|
||||
Timber.d("Error getting bitmap from image url %s", item.getImageUrl());
|
||||
if (dataSource != null) {
|
||||
dataSource.close();
|
||||
}
|
||||
|
|
@ -116,11 +105,8 @@ public class SearchDepictionsRenderer extends Renderer<DepictedItem> {
|
|||
}, CallerThreadExecutor.getInstance());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface DepictCallback {
|
||||
void depictsClicked(DepictedItem item);
|
||||
|
||||
void fetchThumbnailUrlForEntity(String entityId,int position);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import android.os.Parcelable
|
|||
import androidx.annotation.WorkerThread
|
||||
import fr.free.nrw.commons.wikidata.WikidataProperties.DEPICTS
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import org.wikipedia.wikidata.DataValue.DataValueEntityId
|
||||
import org.wikipedia.wikidata.DataValue.EntityId
|
||||
import org.wikipedia.wikidata.Entities
|
||||
import java.util.*
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ data class Depictions(val depictions: List<IdAndLabel>) : Parcelable {
|
|||
entities.first?.statements
|
||||
?.getOrElse(DEPICTS.propertyName, { emptyList() })
|
||||
?.map { statement ->
|
||||
(statement.mainSnak.dataValue as DataValueEntityId).value.id
|
||||
(statement.mainSnak.dataValue as EntityId).value.id
|
||||
}
|
||||
?.map { id -> IdAndLabel(id, fetchLabel(mediaClient, id)) }
|
||||
?: emptyList()
|
||||
|
|
|
|||
|
|
@ -602,10 +602,9 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
textView.setText(depictionName);
|
||||
if (depictionLoaded) {
|
||||
item.setOnClickListener(view -> {
|
||||
DepictedItem depictedItem = new DepictedItem(depictionName, "", "", false, entityId);
|
||||
Intent intent = new Intent(getContext(), WikidataItemDetailsActivity.class);
|
||||
intent.putExtra("wikidataItemName", depictedItem.getName());
|
||||
intent.putExtra("entityId", depictedItem.getId());
|
||||
intent.putExtra("wikidataItemName", depictionName);
|
||||
intent.putExtra("entityId", entityId);
|
||||
getContext().startActivity(intent);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import fr.free.nrw.commons.achievements.FeaturedImages;
|
|||
import fr.free.nrw.commons.achievements.FeedbackResponse;
|
||||
import fr.free.nrw.commons.campaigns.CampaignResponseDTO;
|
||||
import fr.free.nrw.commons.depictions.subClass.models.SparqlResponse;
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import fr.free.nrw.commons.nearby.model.NearbyResponse;
|
||||
|
|
@ -38,6 +39,7 @@ import timber.log.Timber;
|
|||
public class OkHttpJsonApiClient {
|
||||
|
||||
private final OkHttpClient okHttpClient;
|
||||
private final DepictsClient depictsClient;
|
||||
private final HttpUrl wikiMediaToolforgeUrl;
|
||||
private final String sparqlQueryUrl;
|
||||
private final String campaignsUrl;
|
||||
|
|
@ -46,11 +48,13 @@ public class OkHttpJsonApiClient {
|
|||
|
||||
@Inject
|
||||
public OkHttpJsonApiClient(OkHttpClient okHttpClient,
|
||||
DepictsClient depictsClient,
|
||||
HttpUrl wikiMediaToolforgeUrl,
|
||||
String sparqlQueryUrl,
|
||||
String campaignsUrl,
|
||||
Gson gson) {
|
||||
this.okHttpClient = okHttpClient;
|
||||
this.depictsClient = depictsClient;
|
||||
this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl;
|
||||
this.sparqlQueryUrl = sparqlQueryUrl;
|
||||
this.campaignsUrl = campaignsUrl;
|
||||
|
|
@ -219,14 +223,11 @@ public class OkHttpJsonApiClient {
|
|||
}
|
||||
|
||||
private Observable<List<DepictedItem>> depictedItemsFrom(Request request) {
|
||||
return Observable.fromCallable(() -> {
|
||||
return depictsClient.toDepictions(Observable.fromCallable(() -> {
|
||||
try (ResponseBody body = okHttpClient.newCall(request).execute().body()) {
|
||||
return gson.fromJson(body.string(), SparqlResponse.class).toDepictedItems();
|
||||
}catch (Exception e) {
|
||||
Timber.e(e);
|
||||
return new ArrayList<DepictedItem>();
|
||||
return gson.fromJson(body.string(), SparqlResponse.class);
|
||||
}
|
||||
}).doOnError(Timber::e);
|
||||
}).doOnError(Timber::e));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ public class Place implements Parcelable {
|
|||
public final Sitelinks siteLinks;
|
||||
|
||||
|
||||
public Place(String name, Label label, String longDescription, LatLng location, String category, Sitelinks siteLinks, String pic, String destroyed) { this.name = name;
|
||||
public Place(String name, Label label, String longDescription, LatLng location, String category, Sitelinks siteLinks, String pic, String destroyed) {
|
||||
this.name = name;
|
||||
this.label = label;
|
||||
this.longDescription = longDescription;
|
||||
this.location = location;
|
||||
|
|
|
|||
|
|
@ -79,7 +79,6 @@ public class UploadDepictsRenderer extends Renderer<DepictedItem> {
|
|||
final String imageUrl = item.getImageUrl();
|
||||
if (TextUtils.isEmpty(imageUrl)) {
|
||||
imageView.setImageURI(UriUtil.getUriForResourceId(R.drawable.ic_wikidata_logo_24dp));
|
||||
listener.fetchThumbnailUrlForEntity(item);
|
||||
} else {
|
||||
imageView.setImageURI(Uri.parse(imageUrl));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import androidx.lifecycle.LiveData;
|
|||
import fr.free.nrw.commons.BasePresenter;
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
import java.util.List;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The contract with which DepictsFragment and its presenter would talk to each other
|
||||
|
|
@ -41,9 +40,6 @@ public interface DepictsContract {
|
|||
* add depictions to list
|
||||
*/
|
||||
void setDepictsList(List<DepictedItem> depictedItemList);
|
||||
|
||||
|
||||
void onUrlFetched(@NotNull DepictedItem depictedItem, @NotNull String url);
|
||||
}
|
||||
|
||||
interface UserActionListener extends BasePresenter<View> {
|
||||
|
|
@ -71,7 +67,5 @@ public interface DepictsContract {
|
|||
void verifyDepictions();
|
||||
|
||||
LiveData<List<DepictedItem>> getDepictedItems();
|
||||
|
||||
void fetchThumbnailForEntityId(DepictedItem depictedItem);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,15 +144,6 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUrlFetched(@NotNull DepictedItem depictedItem, @NotNull String url) {
|
||||
final Pair<DepictedItem, Integer> itemAndPosition = returnItemAndPosition(depictedItem);
|
||||
if (itemAndPosition != null) {
|
||||
itemAndPosition.first.setImageUrl(url);
|
||||
adapter.notifyItemChanged(itemAndPosition.second);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Pair<DepictedItem,Integer> returnItemAndPosition(@NotNull DepictedItem depictedItem) {
|
||||
for (int i = 0; i < adapter.getItemCount(); i++) {
|
||||
|
|
@ -179,14 +170,6 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
|
|||
presenter.onDepictItemClicked(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch thumbnail for the given entityId at the given position
|
||||
*/
|
||||
@Override
|
||||
public void fetchThumbnailUrlForEntity(DepictedItem depictedItem) {
|
||||
presenter.fetchThumbnailForEntityId(depictedItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Text change listener for the edit text view of depicts
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
package fr.free.nrw.commons.upload.depicts;
|
||||
|
||||
import fr.free.nrw.commons.wikidata.model.DepictSearchResponse;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
import org.wikipedia.wikidata.ClaimsResponse;
|
||||
import org.wikipedia.wikidata.Entities;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
|
|
@ -24,6 +23,6 @@ public interface DepictsInterface {
|
|||
@GET("/w/api.php?action=wbsearchentities&format=json&type=item&uselang=en")
|
||||
Single<DepictSearchResponse> searchForDepicts(@Query("search") String query, @Query("limit") String limit, @Query("language") String language, @Query("uselang") String uselang, @Query("continue") String offset);
|
||||
|
||||
@GET("/w/api.php?action=wbgetclaims&format=json&property=P18")
|
||||
Observable<ClaimsResponse> getImageForEntity(@Query("entity") String entityId);
|
||||
@GET("/w/api.php?format=json&action=wbgetentities")
|
||||
Single<Entities> getEntities(@Query("ids")String ids, @Query("languages")String language);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,11 @@ package fr.free.nrw.commons.upload.depicts
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import fr.free.nrw.commons.di.CommonsApplicationModule
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient.NO_DEPICTED_IMAGE
|
||||
import fr.free.nrw.commons.repository.UploadRepository
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import fr.free.nrw.commons.wikidata.WikidataDisambiguationItems
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.Scheduler
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.functions.BiFunction
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
|
|
@ -26,8 +24,7 @@ import javax.inject.Singleton
|
|||
class DepictsPresenter @Inject constructor(
|
||||
private val repository: UploadRepository,
|
||||
@param:Named(CommonsApplicationModule.IO_THREAD) private val ioScheduler: Scheduler,
|
||||
@param:Named(CommonsApplicationModule.MAIN_THREAD) private val mainThreadScheduler: Scheduler,
|
||||
private val depictsClient: DepictsClient
|
||||
@param:Named(CommonsApplicationModule.MAIN_THREAD) private val mainThreadScheduler: Scheduler
|
||||
) : DepictsContract.UserActionListener {
|
||||
|
||||
companion object {
|
||||
|
|
@ -38,7 +35,6 @@ class DepictsPresenter @Inject constructor(
|
|||
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
|
||||
private val searchTerm: PublishProcessor<String> = PublishProcessor.create()
|
||||
private val depictedItems: MutableLiveData<List<DepictedItem>> = MutableLiveData()
|
||||
private val idsToImageUrls = mutableMapOf<String, String>()
|
||||
|
||||
override fun onAttachView(view: DepictsContract.View) {
|
||||
this.view = view
|
||||
|
|
@ -75,19 +71,14 @@ class DepictsPresenter @Inject constructor(
|
|||
return repository.searchAllEntities(it)
|
||||
.subscribeOn(ioScheduler)
|
||||
.map { repository.selectedDepictions + it }
|
||||
.map { it.filterNot { item -> WikidataDisambiguationItems.isDisambiguationItem(item.instanceOfs) } }
|
||||
.map { it.distinctBy(DepictedItem::id) }
|
||||
.map(::addImageUrlsFromCache)
|
||||
}
|
||||
|
||||
private fun addImageUrlsFromCache(depictions: List<DepictedItem>) =
|
||||
depictions.map { item ->
|
||||
idsToImageUrls[item.id]?.let { item.copy(imageUrl = it) } ?: item
|
||||
}
|
||||
|
||||
override fun onDetachView() {
|
||||
view = DUMMY
|
||||
compositeDisposable.clear()
|
||||
idsToImageUrls.clear()
|
||||
}
|
||||
|
||||
override fun onPreviousButtonClicked() {
|
||||
|
|
@ -122,30 +113,6 @@ class DepictsPresenter @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch thumbnail for the Wikidata Item
|
||||
* @param entityId entityId of the item
|
||||
* @param position position of the item
|
||||
*/
|
||||
override fun fetchThumbnailForEntityId(depictedItem: DepictedItem) {
|
||||
compositeDisposable.add(
|
||||
imageUrlFromNetworkOrCache(depictedItem)
|
||||
.observeOn(mainThreadScheduler)
|
||||
.filter { it != NO_DEPICTED_IMAGE }
|
||||
.subscribe(
|
||||
{ view.onUrlFetched(depictedItem, it) },
|
||||
{ Timber.e(it) }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun imageUrlFromNetworkOrCache(depictedItem: DepictedItem): Single<String> =
|
||||
if (idsToImageUrls.containsKey(depictedItem.id))
|
||||
Single.just(idsToImageUrls[depictedItem.id])
|
||||
else
|
||||
depictsClient.getP18ForItem(depictedItem.id)
|
||||
.subscribeOn(ioScheduler)
|
||||
.doOnSuccess { idsToImageUrls[depictedItem.id] = it }
|
||||
}
|
||||
|
||||
inline fun <reified T> proxy() = Proxy
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
package fr.free.nrw.commons.upload.structure.depictions
|
||||
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient
|
||||
import fr.free.nrw.commons.nearby.Place
|
||||
import fr.free.nrw.commons.upload.depicts.DepictsInterface
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.processors.BehaviorProcessor
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -12,10 +11,11 @@ import javax.inject.Singleton
|
|||
* The model class for depictions in upload
|
||||
*/
|
||||
@Singleton
|
||||
class DepictModel @Inject constructor(private val depictsInterface: DepictsInterface) {
|
||||
class DepictModel @Inject constructor(private val depictsClient: DepictsClient) {
|
||||
|
||||
var nearbyPlaces: BehaviorProcessor<List<Place>> = BehaviorProcessor.createDefault(emptyList())
|
||||
|
||||
|
||||
companion object {
|
||||
private const val SEARCH_DEPICTS_LIMIT = 25
|
||||
}
|
||||
|
|
@ -25,16 +25,22 @@ class DepictModel @Inject constructor(private val depictsInterface: DepictsInter
|
|||
*/
|
||||
fun searchAllEntities(query: String): Flowable<List<DepictedItem>> {
|
||||
if (query.isBlank()) {
|
||||
return nearbyPlaces.map { it.map(::DepictedItem) }
|
||||
return nearbyPlaces.switchMap { places: List<Place> ->
|
||||
depictsClient.getEntities(
|
||||
places.mapNotNull { it.wikiDataEntityId }.joinToString("|")
|
||||
)
|
||||
.map {
|
||||
it.entities()!!.values.mapIndexed { index, entity ->
|
||||
DepictedItem(entity, places[index])
|
||||
}
|
||||
}.toFlowable()
|
||||
}
|
||||
}
|
||||
return networkItems(query)
|
||||
}
|
||||
|
||||
private fun networkItems(query: String): Flowable<List<DepictedItem>> {
|
||||
val language = Locale.getDefault().language
|
||||
return depictsInterface
|
||||
.searchForDepicts(query, "$SEARCH_DEPICTS_LIMIT", language, language, "0")
|
||||
.map { it.search.map(::DepictedItem) }
|
||||
return depictsClient.searchForDepictions(query, SEARCH_DEPICTS_LIMIT, 0)
|
||||
.toFlowable()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
package fr.free.nrw.commons.upload.structure.depictions
|
||||
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient.Companion.getImageUrl
|
||||
import fr.free.nrw.commons.explore.depictions.THUMB_IMAGE_SIZE
|
||||
import fr.free.nrw.commons.nearby.Place
|
||||
import fr.free.nrw.commons.upload.WikidataItem
|
||||
import fr.free.nrw.commons.wikidata.model.DepictSearchItem
|
||||
import fr.free.nrw.commons.wikidata.WikidataProperties
|
||||
import org.wikipedia.wikidata.DataValue
|
||||
import org.wikipedia.wikidata.Entities
|
||||
import org.wikipedia.wikidata.Statement_partial
|
||||
|
||||
/**
|
||||
* Model class for Depicted Item in Upload and Explore
|
||||
|
|
@ -10,36 +15,57 @@ import fr.free.nrw.commons.wikidata.model.DepictSearchItem
|
|||
data class DepictedItem constructor(
|
||||
override val name: String,
|
||||
val description: String?,
|
||||
var imageUrl: String,
|
||||
val imageUrl: String?,
|
||||
val instanceOfs: List<String>,
|
||||
var isSelected: Boolean,
|
||||
override val id: String
|
||||
) : WikidataItem {
|
||||
constructor(depictSearchItem: DepictSearchItem) : this(
|
||||
depictSearchItem.label,
|
||||
depictSearchItem.description,
|
||||
"",
|
||||
false,
|
||||
depictSearchItem.id
|
||||
|
||||
constructor(entity: Entities.Entity) : this(
|
||||
entity,
|
||||
entity.labels().values.firstOrNull()?.value() ?: "",
|
||||
entity.descriptions().values.firstOrNull()?.value() ?: ""
|
||||
)
|
||||
|
||||
constructor(place: Place) : this(
|
||||
constructor(entity: Entities.Entity, place: Place) : this(
|
||||
entity,
|
||||
place.name,
|
||||
place.longDescription,
|
||||
"",
|
||||
false,
|
||||
place.wikiDataEntityId!!
|
||||
place.longDescription
|
||||
)
|
||||
|
||||
var position = 0
|
||||
constructor(entity: Entities.Entity, name: String, description: String) : this(
|
||||
name,
|
||||
description,
|
||||
entity[WikidataProperties.IMAGE].primaryImageValue?.let {
|
||||
getImageUrl(it.value, THUMB_IMAGE_SIZE)
|
||||
},
|
||||
entity[WikidataProperties.INSTANCE_OF].toIds(),
|
||||
false,
|
||||
entity.id()
|
||||
)
|
||||
|
||||
override fun equals(o: Any?) = when {
|
||||
this === o -> true
|
||||
o is DepictedItem -> name == o.name
|
||||
override fun equals(other: Any?) = when {
|
||||
this === other -> true
|
||||
other is DepictedItem -> name == other.name
|
||||
else -> false
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return name?.hashCode() ?: 0
|
||||
return name.hashCode()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun List<Statement_partial>?.toIds(): List<String> {
|
||||
return this?.map { it.mainSnak.dataValue }
|
||||
?.filterIsInstance<DataValue.EntityId>()
|
||||
?.map { it.value.id }
|
||||
?: emptyList()
|
||||
}
|
||||
|
||||
private val List<Statement_partial>?.primaryImageValue: DataValue.ValueString?
|
||||
get() = this?.first()?.mainSnak?.dataValue as? DataValue.ValueString
|
||||
|
||||
operator fun Entities.Entity.get(property: WikidataProperties) =
|
||||
statements?.get(property.propertyName)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,4 @@ package fr.free.nrw.commons.upload.structure.depictions;
|
|||
*/
|
||||
public interface UploadDepictsCallback {
|
||||
void depictsClicked(DepictedItem item);
|
||||
|
||||
void fetchThumbnailUrlForEntity(DepictedItem depictedItem);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package fr.free.nrw.commons.wikidata
|
||||
|
||||
|
||||
enum class WikidataDisambiguationItems(val id: String) {
|
||||
DISAMBIGUATION_PAGE("Q4167410"), INTERNAL_ITEM("Q17442446"), CATEGORY("Q4167836");
|
||||
|
||||
companion object {
|
||||
fun isDisambiguationItem(ids: List<String>) =
|
||||
values().any { disambiguationItem: WikidataDisambiguationItems ->
|
||||
ids.any { id -> disambiguationItem.id == id }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,6 @@ package fr.free.nrw.commons.wikidata
|
|||
import fr.free.nrw.commons.BuildConfig
|
||||
|
||||
enum class WikidataProperties(val propertyName: String) {
|
||||
IMAGE("P18"), DEPICTS(BuildConfig.DEPICTS_PROPERTY);
|
||||
IMAGE("P18"), DEPICTS(BuildConfig.DEPICTS_PROPERTY), INSTANCE_OF("P31");
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,15 +52,6 @@ class SubDepictionListPresenterTest {
|
|||
subDepictionListPresenter?.onAttachView(view)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fetchThumbnailForEntityId() {
|
||||
val singleString: Single<String> = Single.just(String())
|
||||
Mockito.`when`(depictsClient?.getP18ForItem(ArgumentMatchers.anyString())).thenReturn(singleString)
|
||||
subDepictionListPresenter?.fetchThumbnailForEntityId("Q9394", 0)
|
||||
testScheduler?.triggerActions()
|
||||
view?.onImageUrlFetched("url", 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initSubDepictionListForParentClass() {
|
||||
Mockito.`when`(okHttpJsonApiClient?.getParentQIDs(ArgumentMatchers.anyString())).thenReturn(testObservable)
|
||||
|
|
|
|||
|
|
@ -1,68 +1,60 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import org.mockito.Mockito.verify
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
||||
import fr.free.nrw.commons.upload.depictedItem
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.schedulers.TestScheduler
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
class SearchDepictionsPresenterTest {
|
||||
|
||||
@Mock
|
||||
internal var view: SearchDepictionsFragmentContract.View? = null
|
||||
internal lateinit var view: SearchDepictionsFragmentContract.View
|
||||
|
||||
var searchDepictionsFragmentPresenter: SearchDepictionsFragmentPresenter? = null
|
||||
private lateinit var searchDepictionsFragmentPresenter: SearchDepictionsFragmentPresenter
|
||||
|
||||
var testScheduler: TestScheduler? = null
|
||||
|
||||
var jsonKvStore: JsonKvStore? = null
|
||||
|
||||
//var mediaWikiApi: MediaWikiApi? = null
|
||||
private lateinit var testScheduler: TestScheduler
|
||||
|
||||
@Mock
|
||||
var recentSearchesDao: RecentSearchesDao? = null
|
||||
private lateinit var jsonKvStore: JsonKvStore
|
||||
|
||||
@Mock
|
||||
var depictsClient: DepictsClient? = null
|
||||
lateinit var recentSearchesDao: RecentSearchesDao
|
||||
|
||||
var testObservable: Observable<DepictedItem>? = null
|
||||
|
||||
var mediaList: ArrayList<DepictedItem> = ArrayList()
|
||||
@Mock
|
||||
lateinit var depictsClient: DepictsClient
|
||||
|
||||
@Before
|
||||
@Throws(Exception::class)
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
testScheduler = TestScheduler()
|
||||
val depictedItem: DepictedItem = DepictedItem("label", "description", "url", false, "Q9394")
|
||||
mediaList.add(depictedItem)
|
||||
testObservable = Observable.just(depictedItem)
|
||||
searchDepictionsFragmentPresenter = SearchDepictionsFragmentPresenter(jsonKvStore, recentSearchesDao, depictsClient, testScheduler, testScheduler)
|
||||
searchDepictionsFragmentPresenter?.onAttachView(view)
|
||||
val depictedItem: DepictedItem = depictedItem(instanceOfs = listOf())
|
||||
searchDepictionsFragmentPresenter = SearchDepictionsFragmentPresenter(
|
||||
jsonKvStore,
|
||||
recentSearchesDao,
|
||||
depictsClient,
|
||||
testScheduler,
|
||||
testScheduler
|
||||
)
|
||||
searchDepictionsFragmentPresenter.onAttachView(view)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateDepictionList() {
|
||||
Mockito.`when`(depictsClient?.searchForDepictions(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())).thenReturn(testObservable)
|
||||
searchDepictionsFragmentPresenter?.updateDepictionList("rabbit", 25, false)
|
||||
testScheduler?.triggerActions()
|
||||
verify(view)?.onSuccess(mediaList)
|
||||
val expectedList = listOf(depictedItem())
|
||||
whenever(depictsClient.searchForDepictions("rabbit", 25, 0))
|
||||
.thenReturn(Single.just(expectedList))
|
||||
searchDepictionsFragmentPresenter.updateDepictionList("rabbit", 25, false)
|
||||
testScheduler.triggerActions()
|
||||
verify(view)?.onSuccess(expectedList)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fetchThumbnailForEntityId() {
|
||||
val singleString: Single<String> = Single.just(String())
|
||||
Mockito.`when`(depictsClient?.getP18ForItem(ArgumentMatchers.anyString())).thenReturn(singleString)
|
||||
searchDepictionsFragmentPresenter?.fetchThumbnailForEntityId("Q9394", 0)
|
||||
testScheduler?.triggerActions()
|
||||
verify(view)?.onImageUrlFetched("", 0)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,18 +2,15 @@ package fr.free.nrw.commons.upload
|
|||
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.jraska.livedata.test
|
||||
import com.nhaarman.mockitokotlin2.never
|
||||
import com.nhaarman.mockitokotlin2.times
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient.NO_DEPICTED_IMAGE
|
||||
import fr.free.nrw.commons.repository.UploadRepository
|
||||
import fr.free.nrw.commons.upload.depicts.DepictsContract
|
||||
import fr.free.nrw.commons.upload.depicts.DepictsPresenter
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import fr.free.nrw.commons.wikidata.WikidataDisambiguationItems
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.schedulers.TestScheduler
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
|
|
@ -48,7 +45,7 @@ class DepictsPresenterTest {
|
|||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
testScheduler = TestScheduler()
|
||||
depictsPresenter = DepictsPresenter(repository, testScheduler, testScheduler, depictsClient)
|
||||
depictsPresenter = DepictsPresenter(repository, testScheduler, testScheduler)
|
||||
depictsPresenter.onAttachView(view)
|
||||
}
|
||||
|
||||
|
|
@ -60,8 +57,15 @@ class DepictsPresenterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `search results emission returns distinct results + selected items`() {
|
||||
val searchResults = listOf(depictedItem(), depictedItem())
|
||||
fun `search results emission returns distinct results + selected items without disambiguations`() {
|
||||
val searchResults = listOf(
|
||||
depictedItem(id="nonUnique"),
|
||||
depictedItem(id="nonUnique"),
|
||||
depictedItem(
|
||||
id = "unique",
|
||||
instanceOfs = listOf(WikidataDisambiguationItems.CATEGORY.id)
|
||||
)
|
||||
)
|
||||
whenever(repository.searchAllEntities("")).thenReturn(Flowable.just(searchResults))
|
||||
val selectedItem = depictedItem(id = "selected")
|
||||
whenever(repository.selectedDepictions).thenReturn(listOf(selectedItem))
|
||||
|
|
@ -71,22 +75,7 @@ class DepictsPresenterTest {
|
|||
verify(view).showError(false)
|
||||
depictsPresenter.depictedItems
|
||||
.test()
|
||||
.assertValue(listOf(selectedItem, depictedItem()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `searchResults retrieve imageUrls from cache`() {
|
||||
val depictedItem = depictedItem()
|
||||
whenever(depictsClient.getP18ForItem(depictedItem.id)).thenReturn(Single.just("url"))
|
||||
depictsPresenter.fetchThumbnailForEntityId(depictedItem)
|
||||
testScheduler.triggerActions()
|
||||
val searchResults = listOf(depictedItem(), depictedItem())
|
||||
whenever(repository.searchAllEntities("")).thenReturn(Flowable.just(searchResults))
|
||||
depictsPresenter.searchForDepictions("")
|
||||
testScheduler.triggerActions()
|
||||
depictsPresenter.depictedItems
|
||||
.test()
|
||||
.assertValue(listOf(depictedItem(imageUrl = "url")))
|
||||
.assertValue(listOf(selectedItem, depictedItem(id="nonUnique")))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -149,42 +138,14 @@ class DepictsPresenterTest {
|
|||
verify(view).noDepictionSelected()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `image urls fetched from network update the view`() {
|
||||
val depictedItem = depictedItem()
|
||||
whenever(depictsClient.getP18ForItem(depictedItem.id)).thenReturn(Single.just("url"))
|
||||
depictsPresenter.fetchThumbnailForEntityId(depictedItem)
|
||||
testScheduler.triggerActions()
|
||||
verify(view).onUrlFetched(depictedItem, "url")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `image urls fetched from network filter NO_DEPICTED_IMAGE`() {
|
||||
val depictedItem = depictedItem()
|
||||
whenever(depictsClient.getP18ForItem(depictedItem.id))
|
||||
.thenReturn(Single.just(NO_DEPICTED_IMAGE))
|
||||
depictsPresenter.fetchThumbnailForEntityId(depictedItem)
|
||||
testScheduler.triggerActions()
|
||||
verify(view, never()).onUrlFetched(depictedItem, NO_DEPICTED_IMAGE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `successive image urls fetched from cache`() {
|
||||
val depictedItem = depictedItem()
|
||||
whenever(depictsClient.getP18ForItem(depictedItem.id)).thenReturn(Single.just("url"))
|
||||
depictsPresenter.fetchThumbnailForEntityId(depictedItem)
|
||||
testScheduler.triggerActions()
|
||||
verify(view).onUrlFetched(depictedItem, "url")
|
||||
depictsPresenter.fetchThumbnailForEntityId(depictedItem)
|
||||
testScheduler.triggerActions()
|
||||
verify(view, times(2)).onUrlFetched(depictedItem, "url")
|
||||
}
|
||||
}
|
||||
|
||||
fun depictedItem(
|
||||
name: String = "label",
|
||||
description: String = "desc",
|
||||
imageUrl: String = "",
|
||||
instanceOfs: List<String> = listOf(),
|
||||
isSelected: Boolean = false,
|
||||
id: String = "entityId"
|
||||
) = DepictedItem(name, description, imageUrl, isSelected, id)
|
||||
) = DepictedItem(name, description, imageUrl, instanceOfs, isSelected, id)
|
||||
|
|
|
|||
|
|
@ -2,83 +2,94 @@ package org.wikipedia.wikidata
|
|||
|
||||
import org.wikipedia.json.RuntimeTypeAdapterFactory
|
||||
|
||||
/*"datavalue": {
|
||||
"value": {
|
||||
"entity-type": "item",
|
||||
"id": "Q30",
|
||||
"numeric-id": 30
|
||||
},
|
||||
"type": "wikibase-entityid"
|
||||
}
|
||||
OR
|
||||
"datavalue": {
|
||||
"value": "SomePicture.jpg",
|
||||
"type": "string"
|
||||
}
|
||||
OR
|
||||
"datavalue": {
|
||||
"value": {
|
||||
"latitude": 37.7733,
|
||||
"longitude": -122.412255,
|
||||
"altitude": null,
|
||||
"precision": 1.0e-6,
|
||||
"globe": "http://www.wikidata.org/entity/Q2"
|
||||
},
|
||||
"type": "globecoordinate"
|
||||
}
|
||||
OR
|
||||
"datavalue": {
|
||||
"value": {
|
||||
"time": "+2019-12-03T00:00:00Z",
|
||||
"timezone": 0,
|
||||
"before": 0,
|
||||
"after": 0,
|
||||
"precision": 11,
|
||||
"calendarmodel": "http://www.wikidata.org/entity/Q1985727"
|
||||
},
|
||||
"type": "time"
|
||||
}
|
||||
*/
|
||||
sealed class DataValue(val type: String) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val polymorphicTypeAdapter =
|
||||
RuntimeTypeAdapterFactory.of(DataValue::class.java, DataValue::type.name)
|
||||
.registerSubtype(DataValueEntityId::class.java, DataValueEntityId.TYPE)
|
||||
.registerSubtype(DataValueString::class.java, DataValueString.TYPE)
|
||||
.registerSubtype(
|
||||
DataValueGlobeCoordinate_partial::class.java,
|
||||
DataValueGlobeCoordinate_partial.TYPE
|
||||
)
|
||||
.registerSubtype(
|
||||
DataValueTime_partial::class.java,
|
||||
DataValueTime_partial.TYPE
|
||||
)
|
||||
.registerSubtype(EntityId::class.java, EntityId.TYPE)
|
||||
.registerSubtype(ValueString::class.java, ValueString.TYPE)
|
||||
.registerSubtype(GlobeCoordinate_partial::class.java, GlobeCoordinate_partial.TYPE)
|
||||
.registerSubtype(Time_partial::class.java, Time_partial.TYPE)
|
||||
.registerSubtype(Quantity_partial::class.java, Quantity_partial.TYPE)
|
||||
.registerSubtype(MonoLingualText_partial::class.java, MonoLingualText_partial.TYPE)
|
||||
}
|
||||
|
||||
data class DataValueEntityId(val value: WikiBaseEntityValue) :
|
||||
DataValue(TYPE) {
|
||||
// "value": {
|
||||
// "entity-type": "item",
|
||||
// "id": "Q30",
|
||||
// "numeric-id": 30
|
||||
// },
|
||||
// "type": "wikibase-entityid"
|
||||
// }
|
||||
data class EntityId(val value: WikiBaseEntityValue) : DataValue(TYPE) {
|
||||
companion object {
|
||||
const val TYPE = "wikibase-entityid"
|
||||
}
|
||||
}
|
||||
|
||||
data class DataValueString(val value: String) : DataValue(TYPE) {
|
||||
// {
|
||||
// "value": "SomePicture.jpg",
|
||||
// "type": "string"
|
||||
// }
|
||||
data class ValueString(val value: String) : DataValue(TYPE) {
|
||||
companion object {
|
||||
const val TYPE = "string"
|
||||
}
|
||||
}
|
||||
|
||||
class DataValueGlobeCoordinate_partial() :
|
||||
DataValue(TYPE) {
|
||||
// "value": {
|
||||
// "latitude": 37.7733,
|
||||
// "longitude": -122.412255,
|
||||
// "altitude": null,
|
||||
// "precision": 1.0e-6,
|
||||
// "globe": "http://www.wikidata.org/entity/Q2"
|
||||
// },
|
||||
// "type": "globecoordinate"
|
||||
// }
|
||||
class GlobeCoordinate_partial() : DataValue(TYPE) {
|
||||
companion object {
|
||||
const val TYPE = "globecoordinate"
|
||||
}
|
||||
}
|
||||
|
||||
class DataValueTime_partial() : DataValue(TYPE) {
|
||||
// "value": {
|
||||
// "time": "+2019-12-03T00:00:00Z",
|
||||
// "timezone": 0,
|
||||
// "before": 0,
|
||||
// "after": 0,
|
||||
// "precision": 11,
|
||||
// "calendarmodel": "http://www.wikidata.org/entity/Q1985727"
|
||||
// },
|
||||
// "type": "time"
|
||||
// }
|
||||
class Time_partial() : DataValue(TYPE) {
|
||||
companion object {
|
||||
const val TYPE = "time"
|
||||
}
|
||||
}
|
||||
|
||||
// {
|
||||
// "value": {
|
||||
// "amount": "+587",
|
||||
// "unit": "http://www.wikidata.org/entity/Q828224"
|
||||
// }
|
||||
// }
|
||||
class Quantity_partial() : DataValue(TYPE) {
|
||||
companion object {
|
||||
const val TYPE = "quantity"
|
||||
}
|
||||
}
|
||||
|
||||
// {
|
||||
// "value": {
|
||||
// "text": "활",
|
||||
// "language": "ko"
|
||||
// }
|
||||
// }
|
||||
class MonoLingualText_partial() : DataValue(TYPE) {
|
||||
companion object {
|
||||
const val TYPE = "monolingualtext"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package org.wikipedia.wikidata
|
||||
|
||||
import org.wikipedia.wikidata.DataValue.DataValueEntityId
|
||||
import org.wikipedia.wikidata.DataValue.EntityId
|
||||
|
||||
|
||||
data class EditClaim(val claims: List<Statement_partial>) {
|
||||
|
|
@ -14,7 +14,7 @@ data class EditClaim(val claims: List<Statement_partial>) {
|
|||
Snak_partial(
|
||||
"value",
|
||||
propertyName,
|
||||
DataValueEntityId(
|
||||
EntityId(
|
||||
WikiBaseEntityValue(
|
||||
"item",
|
||||
entityId,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package org.wikipedia.wikidata;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -42,7 +43,7 @@ public class Entities extends MwResponse implements PostProcessingTypeAdapter.Po
|
|||
@Nullable private Map<String, Label> labels;
|
||||
@Nullable private Map<String, Label> descriptions;
|
||||
@Nullable private Map<String, SiteLink> sitelinks;
|
||||
@Nullable private Map<String, List<Statement_partial>> statements;
|
||||
@Nullable @SerializedName(value = "statements", alternate = "claims") private Map<String, List<Statement_partial>> statements;
|
||||
@Nullable private String missing;
|
||||
|
||||
@NonNull public String id() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue