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 {
|
interface View {
|
||||||
|
|
||||||
void onImageUrlFetched(String response, int position);
|
|
||||||
|
|
||||||
void onSuccess(List<DepictedItem> mediaList);
|
void onSuccess(List<DepictedItem> mediaList);
|
||||||
|
|
||||||
void initErrorView();
|
void initErrorView();
|
||||||
|
|
@ -23,15 +21,10 @@ public interface SubDepictionListContract {
|
||||||
|
|
||||||
void setIsLastPage(boolean b);
|
void setIsLastPage(boolean b);
|
||||||
|
|
||||||
boolean isParentClass();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserActionListener extends BasePresenter<View> {
|
interface UserActionListener extends BasePresenter<View> {
|
||||||
|
|
||||||
void saveQuery();
|
|
||||||
|
|
||||||
void fetchThumbnailForEntityId(String entityId, int position);
|
|
||||||
|
|
||||||
void initSubDepictionList(String qid, Boolean isParentClass) throws IOException;
|
void initSubDepictionList(String qid, Boolean isParentClass) throws IOException;
|
||||||
|
|
||||||
String getQuery();
|
String getQuery();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
package fr.free.nrw.commons.depictions.subClass;
|
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.content.res.Configuration;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
|
@ -7,23 +10,14 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
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.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
import dagger.android.support.DaggerFragment;
|
import dagger.android.support.DaggerFragment;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity;
|
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.upload.structure.depictions.DepictedItem;
|
||||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
import java.io.IOException;
|
||||||
import static android.view.View.GONE;
|
import java.util.List;
|
||||||
import static android.view.View.VISIBLE;
|
import java.util.Locale;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment for parent classes and child classes of Depicted items in Explore
|
* 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 boolean isParentClass = false;
|
||||||
private RVRendererAdapter<DepictedItem> depictionsAdapter;
|
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;
|
RecyclerView.LayoutManager layoutManager;
|
||||||
/**
|
/**
|
||||||
* Stores entityId for the depiction
|
* Stores entityId for the depiction
|
||||||
|
|
@ -77,12 +68,6 @@ public class SubDepictionListFragment extends DaggerFragment implements SubDepic
|
||||||
getActivity().finish();
|
getActivity().finish();
|
||||||
WikidataItemDetailsActivity.startYourself(getContext(), item);
|
WikidataItemDetailsActivity.startYourself(getContext(), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void fetchThumbnailUrlForEntity(String entityId, int position) {
|
|
||||||
presenter.fetchThumbnailForEntityId(entityId, position);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -140,15 +125,8 @@ public class SubDepictionListFragment extends DaggerFragment implements SubDepic
|
||||||
ViewUtil.showShortSnackbar(depictionsRecyclerView, R.string.no_internet);
|
ViewUtil.showShortSnackbar(depictionsRecyclerView, R.string.no_internet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onImageUrlFetched(String response, int position) {
|
|
||||||
depictionsAdapter.getItem(position).setImageUrl(response);
|
|
||||||
depictionsAdapter.notifyItemChanged(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<DepictedItem> mediaList) {
|
public void onSuccess(List<DepictedItem> mediaList) {
|
||||||
hasMoreImages = false;
|
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
depictionNotFound.setVisibility(GONE);
|
depictionNotFound.setVisibility(GONE);
|
||||||
bottomProgressBar.setVisibility(GONE);
|
bottomProgressBar.setVisibility(GONE);
|
||||||
|
|
@ -164,7 +142,6 @@ public class SubDepictionListFragment extends DaggerFragment implements SubDepic
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initErrorView() {
|
public void initErrorView() {
|
||||||
hasMoreImages = false;
|
|
||||||
progressBar.setVisibility(GONE);
|
progressBar.setVisibility(GONE);
|
||||||
bottomProgressBar.setVisibility(GONE);
|
bottomProgressBar.setVisibility(GONE);
|
||||||
depictionNotFound.setVisibility(VISIBLE);
|
depictionNotFound.setVisibility(VISIBLE);
|
||||||
|
|
@ -180,11 +157,6 @@ public class SubDepictionListFragment extends DaggerFragment implements SubDepic
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setIsLastPage(boolean b) {
|
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 static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
||||||
|
|
||||||
import fr.free.nrw.commons.explore.depictions.DepictsClient;
|
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.explore.recentsearches.RecentSearchesDao;
|
||||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
||||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
|
@ -13,7 +12,6 @@ import io.reactivex.disposables.CompositeDisposable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
@ -48,10 +46,6 @@ public class SubDepictionListPresenter implements SubDepictionListContract.UserA
|
||||||
DepictsClient depictsClient;
|
DepictsClient depictsClient;
|
||||||
private List<DepictedItem> queryList = new ArrayList<>();
|
private List<DepictedItem> queryList = new ArrayList<>();
|
||||||
OkHttpJsonApiClient okHttpJsonApiClient;
|
OkHttpJsonApiClient okHttpJsonApiClient;
|
||||||
/**
|
|
||||||
* variable used to record the number of API calls already made for fetching Thumbnails
|
|
||||||
*/
|
|
||||||
private int size = 0;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SubDepictionListPresenter(RecentSearchesDao recentSearchesDao, DepictsClient depictsClient, OkHttpJsonApiClient okHttpJsonApiClient, @Named(IO_THREAD) Scheduler ioScheduler,
|
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;
|
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
|
@Override
|
||||||
public void initSubDepictionList(String qid, Boolean isParentClass) throws IOException {
|
public void initSubDepictionList(String qid, Boolean isParentClass) throws IOException {
|
||||||
size = 0;
|
|
||||||
if (isParentClass) {
|
if (isParentClass) {
|
||||||
compositeDisposable.add(okHttpJsonApiClient.getParentQIDs(qid)
|
compositeDisposable.add(okHttpJsonApiClient.getParentQIDs(qid)
|
||||||
.subscribeOn(ioScheduler)
|
.subscribeOn(ioScheduler)
|
||||||
|
|
@ -137,9 +101,6 @@ public class SubDepictionListPresenter implements SubDepictionListContract.UserA
|
||||||
} else {
|
} else {
|
||||||
this.queryList.addAll(mediaList);
|
this.queryList.addAll(mediaList);
|
||||||
view.onSuccess(mediaList);
|
view.onSuccess(mediaList);
|
||||||
for (DepictedItem m : mediaList) {
|
|
||||||
fetchThumbnailForEntityId(m.getId(), size++);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,6 @@
|
||||||
package fr.free.nrw.commons.depictions.subClass.models
|
package fr.free.nrw.commons.depictions.subClass.models
|
||||||
|
|
||||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
data class SparqlResponse(val results: Result)
|
||||||
|
|
||||||
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 Result(val bindings: List<Binding>)
|
data class Result(val bindings: List<Binding>)
|
||||||
|
|
||||||
|
|
@ -21,6 +8,8 @@ data class Binding(
|
||||||
val item: SparqInfo,
|
val item: SparqInfo,
|
||||||
val itemLabel: SparqInfo,
|
val itemLabel: SparqInfo,
|
||||||
val itemDescription: SparqInfo? = null
|
val itemDescription: SparqInfo? = null
|
||||||
)
|
) {
|
||||||
|
val id: String by lazy { item.value.substringAfterLast("/") }
|
||||||
|
}
|
||||||
|
|
||||||
data class SparqInfo(val type: String, val value: String)
|
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.PageEditClient;
|
||||||
import fr.free.nrw.commons.actions.PageEditInterface;
|
import fr.free.nrw.commons.actions.PageEditInterface;
|
||||||
import fr.free.nrw.commons.category.CategoryInterface;
|
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.kvstore.JsonKvStore;
|
||||||
import fr.free.nrw.commons.media.MediaDetailInterface;
|
import fr.free.nrw.commons.media.MediaDetailInterface;
|
||||||
import fr.free.nrw.commons.media.MediaInterface;
|
import fr.free.nrw.commons.media.MediaInterface;
|
||||||
|
|
@ -77,10 +78,12 @@ public class NetworkingModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
public OkHttpJsonApiClient provideOkHttpJsonApiClient(OkHttpClient okHttpClient,
|
public OkHttpJsonApiClient provideOkHttpJsonApiClient(OkHttpClient okHttpClient,
|
||||||
|
DepictsClient depictsClient,
|
||||||
@Named("tools_forge") HttpUrl toolsForgeUrl,
|
@Named("tools_forge") HttpUrl toolsForgeUrl,
|
||||||
@Named("default_preferences") JsonKvStore defaultKvStore,
|
@Named("default_preferences") JsonKvStore defaultKvStore,
|
||||||
Gson gson) {
|
Gson gson) {
|
||||||
return new OkHttpJsonApiClient(okHttpClient,
|
return new OkHttpJsonApiClient(okHttpClient,
|
||||||
|
depictsClient,
|
||||||
toolsForgeUrl,
|
toolsForgeUrl,
|
||||||
WIKIDATA_SPARQL_QUERY_URL,
|
WIKIDATA_SPARQL_QUERY_URL,
|
||||||
BuildConfig.WIKIMEDIA_CAMPAIGNS_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);
|
WikidataItemDetailsActivity.startYourself(getContext(), item);
|
||||||
presenter.saveQuery();
|
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 RVRendererAdapter<DepictedItem> depictionsAdapter;
|
||||||
private boolean isLastPage;
|
private boolean isLastPage;
|
||||||
|
|
@ -218,12 +209,6 @@ public class SearchDepictionsFragment extends CommonsDaggerSupportFragment imple
|
||||||
return depictionsAdapter;
|
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
|
* Inform the view that there are no more items to be loaded for this search query
|
||||||
* or reset the isLastPage for the current query
|
* or reset the isLastPage for the current query
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,6 @@ public interface SearchDepictionsFragmentContract {
|
||||||
*/
|
*/
|
||||||
RVRendererAdapter<DepictedItem> getAdapter();
|
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
|
* Inform the view that there are no more items to be loaded for this search query
|
||||||
* or reset the isLastPage for the current query
|
* or reset the isLastPage for the current query
|
||||||
|
|
@ -85,10 +83,5 @@ public interface SearchDepictionsFragmentContract {
|
||||||
* @return query
|
* @return query
|
||||||
*/
|
*/
|
||||||
String getQuery();
|
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)
|
.subscribeOn(ioScheduler)
|
||||||
.observeOn(mainThreadScheduler)
|
.observeOn(mainThreadScheduler)
|
||||||
.doOnSubscribe(disposable -> saveQuery())
|
.doOnSubscribe(disposable -> saveQuery())
|
||||||
.collect(ArrayList<DepictedItem>::new, ArrayList::add)
|
|
||||||
.subscribe(this::handleSuccess, this::handleError));
|
.subscribe(this::handleSuccess, this::handleError));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,23 +153,7 @@ public class SearchDepictionsFragmentPresenter extends CommonsDaggerSupportFragm
|
||||||
this.queryList.addAll(mediaList);
|
this.queryList.addAll(mediaList);
|
||||||
view.onSuccess(mediaList);
|
view.onSuccess(mediaList);
|
||||||
offset=queryList.size();
|
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;
|
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.graphics.Bitmap;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
@ -10,12 +9,9 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
import com.facebook.common.executors.CallerThreadExecutor;
|
import com.facebook.common.executors.CallerThreadExecutor;
|
||||||
import com.facebook.common.references.CloseableReference;
|
import com.facebook.common.references.CloseableReference;
|
||||||
import com.facebook.datasource.DataSource;
|
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.ImageRequest;
|
||||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||||
import com.pedrogomez.renderers.Renderer;
|
import com.pedrogomez.renderers.Renderer;
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renderer for DepictedItem
|
* Renderer for DepictedItem
|
||||||
|
|
@ -81,10 +75,7 @@ public class SearchDepictionsRenderer extends Renderer<DepictedItem> {
|
||||||
tvDepictionDesc.setText(item.getDescription());
|
tvDepictionDesc.setText(item.getDescription());
|
||||||
imageView.setImageDrawable(getContext().getResources().getDrawable(R.drawable.ic_wikidata_logo_24dp));
|
imageView.setImageDrawable(getContext().getResources().getDrawable(R.drawable.ic_wikidata_logo_24dp));
|
||||||
|
|
||||||
Timber.e("line86"+item.getImageUrl());
|
|
||||||
if (!TextUtils.isEmpty(item.getImageUrl())) {
|
if (!TextUtils.isEmpty(item.getImageUrl())) {
|
||||||
if (!item.getImageUrl().equals(NO_DEPICTED_IMAGE) && !item.getImageUrl().equals(""))
|
|
||||||
{
|
|
||||||
ImageRequest imageRequest = ImageRequestBuilder
|
ImageRequest imageRequest = ImageRequestBuilder
|
||||||
.newBuilderWithSource(Uri.parse(item.getImageUrl()))
|
.newBuilderWithSource(Uri.parse(item.getImageUrl()))
|
||||||
.setAutoRotateEnabled(true)
|
.setAutoRotateEnabled(true)
|
||||||
|
|
@ -99,7 +90,6 @@ public class SearchDepictionsRenderer extends Renderer<DepictedItem> {
|
||||||
@Override
|
@Override
|
||||||
public void onNewResultImpl(@Nullable Bitmap bitmap) {
|
public void onNewResultImpl(@Nullable Bitmap bitmap) {
|
||||||
if (dataSource.isFinished() && bitmap != null) {
|
if (dataSource.isFinished() && bitmap != null) {
|
||||||
Timber.d("Bitmap loaded from url %s", item.getImageUrl());
|
|
||||||
//imageView.setImageBitmap(Bitmap.createBitmap(bitmap));
|
//imageView.setImageBitmap(Bitmap.createBitmap(bitmap));
|
||||||
imageView.post(() -> imageView.setImageBitmap(Bitmap.createBitmap(bitmap)));
|
imageView.post(() -> imageView.setImageBitmap(Bitmap.createBitmap(bitmap)));
|
||||||
dataSource.close();
|
dataSource.close();
|
||||||
|
|
@ -108,19 +98,15 @@ public class SearchDepictionsRenderer extends Renderer<DepictedItem> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailureImpl(DataSource dataSource) {
|
public void onFailureImpl(DataSource dataSource) {
|
||||||
Timber.d("Error getting bitmap from image url %s", item.getImageUrl());
|
|
||||||
if (dataSource != null) {
|
if (dataSource != null) {
|
||||||
dataSource.close();
|
dataSource.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, CallerThreadExecutor.getInstance());
|
}, CallerThreadExecutor.getInstance());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface DepictCallback {
|
public interface DepictCallback {
|
||||||
void depictsClicked(DepictedItem item);
|
void depictsClicked(DepictedItem item);
|
||||||
|
|
||||||
void fetchThumbnailUrlForEntity(String entityId,int position);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import android.os.Parcelable
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import fr.free.nrw.commons.wikidata.WikidataProperties.DEPICTS
|
import fr.free.nrw.commons.wikidata.WikidataProperties.DEPICTS
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import org.wikipedia.wikidata.DataValue.DataValueEntityId
|
import org.wikipedia.wikidata.DataValue.EntityId
|
||||||
import org.wikipedia.wikidata.Entities
|
import org.wikipedia.wikidata.Entities
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
@ -18,7 +18,7 @@ data class Depictions(val depictions: List<IdAndLabel>) : Parcelable {
|
||||||
entities.first?.statements
|
entities.first?.statements
|
||||||
?.getOrElse(DEPICTS.propertyName, { emptyList() })
|
?.getOrElse(DEPICTS.propertyName, { emptyList() })
|
||||||
?.map { statement ->
|
?.map { statement ->
|
||||||
(statement.mainSnak.dataValue as DataValueEntityId).value.id
|
(statement.mainSnak.dataValue as EntityId).value.id
|
||||||
}
|
}
|
||||||
?.map { id -> IdAndLabel(id, fetchLabel(mediaClient, id)) }
|
?.map { id -> IdAndLabel(id, fetchLabel(mediaClient, id)) }
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
|
|
|
||||||
|
|
@ -602,10 +602,9 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
textView.setText(depictionName);
|
textView.setText(depictionName);
|
||||||
if (depictionLoaded) {
|
if (depictionLoaded) {
|
||||||
item.setOnClickListener(view -> {
|
item.setOnClickListener(view -> {
|
||||||
DepictedItem depictedItem = new DepictedItem(depictionName, "", "", false, entityId);
|
|
||||||
Intent intent = new Intent(getContext(), WikidataItemDetailsActivity.class);
|
Intent intent = new Intent(getContext(), WikidataItemDetailsActivity.class);
|
||||||
intent.putExtra("wikidataItemName", depictedItem.getName());
|
intent.putExtra("wikidataItemName", depictionName);
|
||||||
intent.putExtra("entityId", depictedItem.getId());
|
intent.putExtra("entityId", entityId);
|
||||||
getContext().startActivity(intent);
|
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.achievements.FeedbackResponse;
|
||||||
import fr.free.nrw.commons.campaigns.CampaignResponseDTO;
|
import fr.free.nrw.commons.campaigns.CampaignResponseDTO;
|
||||||
import fr.free.nrw.commons.depictions.subClass.models.SparqlResponse;
|
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.location.LatLng;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.nearby.model.NearbyResponse;
|
import fr.free.nrw.commons.nearby.model.NearbyResponse;
|
||||||
|
|
@ -38,6 +39,7 @@ import timber.log.Timber;
|
||||||
public class OkHttpJsonApiClient {
|
public class OkHttpJsonApiClient {
|
||||||
|
|
||||||
private final OkHttpClient okHttpClient;
|
private final OkHttpClient okHttpClient;
|
||||||
|
private final DepictsClient depictsClient;
|
||||||
private final HttpUrl wikiMediaToolforgeUrl;
|
private final HttpUrl wikiMediaToolforgeUrl;
|
||||||
private final String sparqlQueryUrl;
|
private final String sparqlQueryUrl;
|
||||||
private final String campaignsUrl;
|
private final String campaignsUrl;
|
||||||
|
|
@ -46,11 +48,13 @@ public class OkHttpJsonApiClient {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public OkHttpJsonApiClient(OkHttpClient okHttpClient,
|
public OkHttpJsonApiClient(OkHttpClient okHttpClient,
|
||||||
|
DepictsClient depictsClient,
|
||||||
HttpUrl wikiMediaToolforgeUrl,
|
HttpUrl wikiMediaToolforgeUrl,
|
||||||
String sparqlQueryUrl,
|
String sparqlQueryUrl,
|
||||||
String campaignsUrl,
|
String campaignsUrl,
|
||||||
Gson gson) {
|
Gson gson) {
|
||||||
this.okHttpClient = okHttpClient;
|
this.okHttpClient = okHttpClient;
|
||||||
|
this.depictsClient = depictsClient;
|
||||||
this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl;
|
this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl;
|
||||||
this.sparqlQueryUrl = sparqlQueryUrl;
|
this.sparqlQueryUrl = sparqlQueryUrl;
|
||||||
this.campaignsUrl = campaignsUrl;
|
this.campaignsUrl = campaignsUrl;
|
||||||
|
|
@ -219,14 +223,11 @@ public class OkHttpJsonApiClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Observable<List<DepictedItem>> depictedItemsFrom(Request request) {
|
private Observable<List<DepictedItem>> depictedItemsFrom(Request request) {
|
||||||
return Observable.fromCallable(() -> {
|
return depictsClient.toDepictions(Observable.fromCallable(() -> {
|
||||||
try (ResponseBody body = okHttpClient.newCall(request).execute().body()) {
|
try (ResponseBody body = okHttpClient.newCall(request).execute().body()) {
|
||||||
return gson.fromJson(body.string(), SparqlResponse.class).toDepictedItems();
|
return gson.fromJson(body.string(), SparqlResponse.class);
|
||||||
}catch (Exception e) {
|
|
||||||
Timber.e(e);
|
|
||||||
return new ArrayList<DepictedItem>();
|
|
||||||
}
|
}
|
||||||
}).doOnError(Timber::e);
|
}).doOnError(Timber::e));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@ public class Place implements Parcelable {
|
||||||
public final Sitelinks siteLinks;
|
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.label = label;
|
||||||
this.longDescription = longDescription;
|
this.longDescription = longDescription;
|
||||||
this.location = location;
|
this.location = location;
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,6 @@ public class UploadDepictsRenderer extends Renderer<DepictedItem> {
|
||||||
final String imageUrl = item.getImageUrl();
|
final String imageUrl = item.getImageUrl();
|
||||||
if (TextUtils.isEmpty(imageUrl)) {
|
if (TextUtils.isEmpty(imageUrl)) {
|
||||||
imageView.setImageURI(UriUtil.getUriForResourceId(R.drawable.ic_wikidata_logo_24dp));
|
imageView.setImageURI(UriUtil.getUriForResourceId(R.drawable.ic_wikidata_logo_24dp));
|
||||||
listener.fetchThumbnailUrlForEntity(item);
|
|
||||||
} else {
|
} else {
|
||||||
imageView.setImageURI(Uri.parse(imageUrl));
|
imageView.setImageURI(Uri.parse(imageUrl));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import androidx.lifecycle.LiveData;
|
||||||
import fr.free.nrw.commons.BasePresenter;
|
import fr.free.nrw.commons.BasePresenter;
|
||||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The contract with which DepictsFragment and its presenter would talk to each other
|
* The contract with which DepictsFragment and its presenter would talk to each other
|
||||||
|
|
@ -41,9 +40,6 @@ public interface DepictsContract {
|
||||||
* add depictions to list
|
* add depictions to list
|
||||||
*/
|
*/
|
||||||
void setDepictsList(List<DepictedItem> depictedItemList);
|
void setDepictsList(List<DepictedItem> depictedItemList);
|
||||||
|
|
||||||
|
|
||||||
void onUrlFetched(@NotNull DepictedItem depictedItem, @NotNull String url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserActionListener extends BasePresenter<View> {
|
interface UserActionListener extends BasePresenter<View> {
|
||||||
|
|
@ -71,7 +67,5 @@ public interface DepictsContract {
|
||||||
void verifyDepictions();
|
void verifyDepictions();
|
||||||
|
|
||||||
LiveData<List<DepictedItem>> getDepictedItems();
|
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
|
@Nullable
|
||||||
private Pair<DepictedItem,Integer> returnItemAndPosition(@NotNull DepictedItem depictedItem) {
|
private Pair<DepictedItem,Integer> returnItemAndPosition(@NotNull DepictedItem depictedItem) {
|
||||||
for (int i = 0; i < adapter.getItemCount(); i++) {
|
for (int i = 0; i < adapter.getItemCount(); i++) {
|
||||||
|
|
@ -179,14 +170,6 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
|
||||||
presenter.onDepictItemClicked(item);
|
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
|
* Text change listener for the edit text view of depicts
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
package fr.free.nrw.commons.upload.depicts;
|
package fr.free.nrw.commons.upload.depicts;
|
||||||
|
|
||||||
import fr.free.nrw.commons.wikidata.model.DepictSearchResponse;
|
import fr.free.nrw.commons.wikidata.model.DepictSearchResponse;
|
||||||
import io.reactivex.Observable;
|
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
import org.wikipedia.wikidata.ClaimsResponse;
|
import org.wikipedia.wikidata.Entities;
|
||||||
import retrofit2.http.GET;
|
import retrofit2.http.GET;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
|
@ -24,6 +23,6 @@ public interface DepictsInterface {
|
||||||
@GET("/w/api.php?action=wbsearchentities&format=json&type=item&uselang=en")
|
@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);
|
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")
|
@GET("/w/api.php?format=json&action=wbgetentities")
|
||||||
Observable<ClaimsResponse> getImageForEntity(@Query("entity") String entityId);
|
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.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import fr.free.nrw.commons.di.CommonsApplicationModule
|
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.repository.UploadRepository
|
||||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
|
import fr.free.nrw.commons.wikidata.WikidataDisambiguationItems
|
||||||
import io.reactivex.Flowable
|
import io.reactivex.Flowable
|
||||||
import io.reactivex.Scheduler
|
import io.reactivex.Scheduler
|
||||||
import io.reactivex.Single
|
|
||||||
import io.reactivex.disposables.CompositeDisposable
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
import io.reactivex.functions.BiFunction
|
import io.reactivex.functions.BiFunction
|
||||||
import io.reactivex.processors.PublishProcessor
|
import io.reactivex.processors.PublishProcessor
|
||||||
|
|
@ -26,8 +24,7 @@ import javax.inject.Singleton
|
||||||
class DepictsPresenter @Inject constructor(
|
class DepictsPresenter @Inject constructor(
|
||||||
private val repository: UploadRepository,
|
private val repository: UploadRepository,
|
||||||
@param:Named(CommonsApplicationModule.IO_THREAD) private val ioScheduler: Scheduler,
|
@param:Named(CommonsApplicationModule.IO_THREAD) private val ioScheduler: Scheduler,
|
||||||
@param:Named(CommonsApplicationModule.MAIN_THREAD) private val mainThreadScheduler: Scheduler,
|
@param:Named(CommonsApplicationModule.MAIN_THREAD) private val mainThreadScheduler: Scheduler
|
||||||
private val depictsClient: DepictsClient
|
|
||||||
) : DepictsContract.UserActionListener {
|
) : DepictsContract.UserActionListener {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
@ -38,7 +35,6 @@ class DepictsPresenter @Inject constructor(
|
||||||
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
|
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
|
||||||
private val searchTerm: PublishProcessor<String> = PublishProcessor.create()
|
private val searchTerm: PublishProcessor<String> = PublishProcessor.create()
|
||||||
private val depictedItems: MutableLiveData<List<DepictedItem>> = MutableLiveData()
|
private val depictedItems: MutableLiveData<List<DepictedItem>> = MutableLiveData()
|
||||||
private val idsToImageUrls = mutableMapOf<String, String>()
|
|
||||||
|
|
||||||
override fun onAttachView(view: DepictsContract.View) {
|
override fun onAttachView(view: DepictsContract.View) {
|
||||||
this.view = view
|
this.view = view
|
||||||
|
|
@ -75,19 +71,14 @@ class DepictsPresenter @Inject constructor(
|
||||||
return repository.searchAllEntities(it)
|
return repository.searchAllEntities(it)
|
||||||
.subscribeOn(ioScheduler)
|
.subscribeOn(ioScheduler)
|
||||||
.map { repository.selectedDepictions + it }
|
.map { repository.selectedDepictions + it }
|
||||||
|
.map { it.filterNot { item -> WikidataDisambiguationItems.isDisambiguationItem(item.instanceOfs) } }
|
||||||
.map { it.distinctBy(DepictedItem::id) }
|
.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() {
|
override fun onDetachView() {
|
||||||
view = DUMMY
|
view = DUMMY
|
||||||
compositeDisposable.clear()
|
compositeDisposable.clear()
|
||||||
idsToImageUrls.clear()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPreviousButtonClicked() {
|
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
|
inline fun <reified T> proxy() = Proxy
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
package fr.free.nrw.commons.upload.structure.depictions
|
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.nearby.Place
|
||||||
import fr.free.nrw.commons.upload.depicts.DepictsInterface
|
|
||||||
import io.reactivex.Flowable
|
import io.reactivex.Flowable
|
||||||
import io.reactivex.processors.BehaviorProcessor
|
import io.reactivex.processors.BehaviorProcessor
|
||||||
import java.util.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
|
@ -12,10 +11,11 @@ import javax.inject.Singleton
|
||||||
* The model class for depictions in upload
|
* The model class for depictions in upload
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@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())
|
var nearbyPlaces: BehaviorProcessor<List<Place>> = BehaviorProcessor.createDefault(emptyList())
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val SEARCH_DEPICTS_LIMIT = 25
|
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>> {
|
fun searchAllEntities(query: String): Flowable<List<DepictedItem>> {
|
||||||
if (query.isBlank()) {
|
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)
|
return networkItems(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun networkItems(query: String): Flowable<List<DepictedItem>> {
|
private fun networkItems(query: String): Flowable<List<DepictedItem>> {
|
||||||
val language = Locale.getDefault().language
|
return depictsClient.searchForDepictions(query, SEARCH_DEPICTS_LIMIT, 0)
|
||||||
return depictsInterface
|
|
||||||
.searchForDepicts(query, "$SEARCH_DEPICTS_LIMIT", language, language, "0")
|
|
||||||
.map { it.search.map(::DepictedItem) }
|
|
||||||
.toFlowable()
|
.toFlowable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
package fr.free.nrw.commons.upload.structure.depictions
|
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.nearby.Place
|
||||||
import fr.free.nrw.commons.upload.WikidataItem
|
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
|
* 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(
|
data class DepictedItem constructor(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
val description: String?,
|
val description: String?,
|
||||||
var imageUrl: String,
|
val imageUrl: String?,
|
||||||
|
val instanceOfs: List<String>,
|
||||||
var isSelected: Boolean,
|
var isSelected: Boolean,
|
||||||
override val id: String
|
override val id: String
|
||||||
) : WikidataItem {
|
) : WikidataItem {
|
||||||
constructor(depictSearchItem: DepictSearchItem) : this(
|
|
||||||
depictSearchItem.label,
|
constructor(entity: Entities.Entity) : this(
|
||||||
depictSearchItem.description,
|
entity,
|
||||||
"",
|
entity.labels().values.firstOrNull()?.value() ?: "",
|
||||||
false,
|
entity.descriptions().values.firstOrNull()?.value() ?: ""
|
||||||
depictSearchItem.id
|
|
||||||
)
|
)
|
||||||
|
|
||||||
constructor(place: Place) : this(
|
constructor(entity: Entities.Entity, place: Place) : this(
|
||||||
|
entity,
|
||||||
place.name,
|
place.name,
|
||||||
place.longDescription,
|
place.longDescription
|
||||||
"",
|
|
||||||
false,
|
|
||||||
place.wikiDataEntityId!!
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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 {
|
override fun equals(other: Any?) = when {
|
||||||
this === o -> true
|
this === other -> true
|
||||||
o is DepictedItem -> name == o.name
|
other is DepictedItem -> name == other.name
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
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 {
|
public interface UploadDepictsCallback {
|
||||||
void depictsClicked(DepictedItem item);
|
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
|
import fr.free.nrw.commons.BuildConfig
|
||||||
|
|
||||||
enum class WikidataProperties(val propertyName: String) {
|
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)
|
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
|
@Test
|
||||||
fun initSubDepictionListForParentClass() {
|
fun initSubDepictionListForParentClass() {
|
||||||
Mockito.`when`(okHttpJsonApiClient?.getParentQIDs(ArgumentMatchers.anyString())).thenReturn(testObservable)
|
Mockito.`when`(okHttpJsonApiClient?.getParentQIDs(ArgumentMatchers.anyString())).thenReturn(testObservable)
|
||||||
|
|
|
||||||
|
|
@ -1,68 +1,60 @@
|
||||||
package fr.free.nrw.commons.explore.depictions
|
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.explore.recentsearches.RecentSearchesDao
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
import fr.free.nrw.commons.kvstore.JsonKvStore
|
||||||
|
import fr.free.nrw.commons.upload.depictedItem
|
||||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
import io.reactivex.Observable
|
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import io.reactivex.schedulers.TestScheduler
|
import io.reactivex.schedulers.TestScheduler
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.ArgumentMatchers
|
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
class SearchDepictionsPresenterTest {
|
class SearchDepictionsPresenterTest {
|
||||||
|
|
||||||
@Mock
|
@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
|
private lateinit var testScheduler: TestScheduler
|
||||||
|
|
||||||
var jsonKvStore: JsonKvStore? = null
|
|
||||||
|
|
||||||
//var mediaWikiApi: MediaWikiApi? = null
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
var recentSearchesDao: RecentSearchesDao? = null
|
private lateinit var jsonKvStore: JsonKvStore
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
var depictsClient: DepictsClient? = null
|
lateinit var recentSearchesDao: RecentSearchesDao
|
||||||
|
|
||||||
var testObservable: Observable<DepictedItem>? = null
|
@Mock
|
||||||
|
lateinit var depictsClient: DepictsClient
|
||||||
var mediaList: ArrayList<DepictedItem> = ArrayList()
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
testScheduler = TestScheduler()
|
testScheduler = TestScheduler()
|
||||||
val depictedItem: DepictedItem = DepictedItem("label", "description", "url", false, "Q9394")
|
val depictedItem: DepictedItem = depictedItem(instanceOfs = listOf())
|
||||||
mediaList.add(depictedItem)
|
searchDepictionsFragmentPresenter = SearchDepictionsFragmentPresenter(
|
||||||
testObservable = Observable.just(depictedItem)
|
jsonKvStore,
|
||||||
searchDepictionsFragmentPresenter = SearchDepictionsFragmentPresenter(jsonKvStore, recentSearchesDao, depictsClient, testScheduler, testScheduler)
|
recentSearchesDao,
|
||||||
searchDepictionsFragmentPresenter?.onAttachView(view)
|
depictsClient,
|
||||||
|
testScheduler,
|
||||||
|
testScheduler
|
||||||
|
)
|
||||||
|
searchDepictionsFragmentPresenter.onAttachView(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun updateDepictionList() {
|
fun updateDepictionList() {
|
||||||
Mockito.`when`(depictsClient?.searchForDepictions(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())).thenReturn(testObservable)
|
val expectedList = listOf(depictedItem())
|
||||||
searchDepictionsFragmentPresenter?.updateDepictionList("rabbit", 25, false)
|
whenever(depictsClient.searchForDepictions("rabbit", 25, 0))
|
||||||
testScheduler?.triggerActions()
|
.thenReturn(Single.just(expectedList))
|
||||||
verify(view)?.onSuccess(mediaList)
|
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 androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||||
import com.jraska.livedata.test
|
import com.jraska.livedata.test
|
||||||
import com.nhaarman.mockitokotlin2.never
|
|
||||||
import com.nhaarman.mockitokotlin2.times
|
|
||||||
import com.nhaarman.mockitokotlin2.verify
|
import com.nhaarman.mockitokotlin2.verify
|
||||||
import com.nhaarman.mockitokotlin2.whenever
|
import com.nhaarman.mockitokotlin2.whenever
|
||||||
import fr.free.nrw.commons.explore.depictions.DepictsClient
|
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.repository.UploadRepository
|
||||||
import fr.free.nrw.commons.upload.depicts.DepictsContract
|
import fr.free.nrw.commons.upload.depicts.DepictsContract
|
||||||
import fr.free.nrw.commons.upload.depicts.DepictsPresenter
|
import fr.free.nrw.commons.upload.depicts.DepictsPresenter
|
||||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
|
import fr.free.nrw.commons.wikidata.WikidataDisambiguationItems
|
||||||
import io.reactivex.Flowable
|
import io.reactivex.Flowable
|
||||||
import io.reactivex.Single
|
|
||||||
import io.reactivex.schedulers.TestScheduler
|
import io.reactivex.schedulers.TestScheduler
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
|
@ -48,7 +45,7 @@ class DepictsPresenterTest {
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
testScheduler = TestScheduler()
|
testScheduler = TestScheduler()
|
||||||
depictsPresenter = DepictsPresenter(repository, testScheduler, testScheduler, depictsClient)
|
depictsPresenter = DepictsPresenter(repository, testScheduler, testScheduler)
|
||||||
depictsPresenter.onAttachView(view)
|
depictsPresenter.onAttachView(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,8 +57,15 @@ class DepictsPresenterTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `search results emission returns distinct results + selected items`() {
|
fun `search results emission returns distinct results + selected items without disambiguations`() {
|
||||||
val searchResults = listOf(depictedItem(), depictedItem())
|
val searchResults = listOf(
|
||||||
|
depictedItem(id="nonUnique"),
|
||||||
|
depictedItem(id="nonUnique"),
|
||||||
|
depictedItem(
|
||||||
|
id = "unique",
|
||||||
|
instanceOfs = listOf(WikidataDisambiguationItems.CATEGORY.id)
|
||||||
|
)
|
||||||
|
)
|
||||||
whenever(repository.searchAllEntities("")).thenReturn(Flowable.just(searchResults))
|
whenever(repository.searchAllEntities("")).thenReturn(Flowable.just(searchResults))
|
||||||
val selectedItem = depictedItem(id = "selected")
|
val selectedItem = depictedItem(id = "selected")
|
||||||
whenever(repository.selectedDepictions).thenReturn(listOf(selectedItem))
|
whenever(repository.selectedDepictions).thenReturn(listOf(selectedItem))
|
||||||
|
|
@ -71,22 +75,7 @@ class DepictsPresenterTest {
|
||||||
verify(view).showError(false)
|
verify(view).showError(false)
|
||||||
depictsPresenter.depictedItems
|
depictsPresenter.depictedItems
|
||||||
.test()
|
.test()
|
||||||
.assertValue(listOf(selectedItem, depictedItem()))
|
.assertValue(listOf(selectedItem, depictedItem(id="nonUnique")))
|
||||||
}
|
|
||||||
|
|
||||||
@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")))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -149,42 +138,14 @@ class DepictsPresenterTest {
|
||||||
verify(view).noDepictionSelected()
|
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(
|
fun depictedItem(
|
||||||
name: String = "label",
|
name: String = "label",
|
||||||
description: String = "desc",
|
description: String = "desc",
|
||||||
imageUrl: String = "",
|
imageUrl: String = "",
|
||||||
|
instanceOfs: List<String> = listOf(),
|
||||||
isSelected: Boolean = false,
|
isSelected: Boolean = false,
|
||||||
id: String = "entityId"
|
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
|
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) {
|
sealed class DataValue(val type: String) {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val polymorphicTypeAdapter =
|
val polymorphicTypeAdapter =
|
||||||
RuntimeTypeAdapterFactory.of(DataValue::class.java, DataValue::type.name)
|
RuntimeTypeAdapterFactory.of(DataValue::class.java, DataValue::type.name)
|
||||||
.registerSubtype(DataValueEntityId::class.java, DataValueEntityId.TYPE)
|
.registerSubtype(EntityId::class.java, EntityId.TYPE)
|
||||||
.registerSubtype(DataValueString::class.java, DataValueString.TYPE)
|
.registerSubtype(ValueString::class.java, ValueString.TYPE)
|
||||||
.registerSubtype(
|
.registerSubtype(GlobeCoordinate_partial::class.java, GlobeCoordinate_partial.TYPE)
|
||||||
DataValueGlobeCoordinate_partial::class.java,
|
.registerSubtype(Time_partial::class.java, Time_partial.TYPE)
|
||||||
DataValueGlobeCoordinate_partial.TYPE
|
.registerSubtype(Quantity_partial::class.java, Quantity_partial.TYPE)
|
||||||
)
|
.registerSubtype(MonoLingualText_partial::class.java, MonoLingualText_partial.TYPE)
|
||||||
.registerSubtype(
|
|
||||||
DataValueTime_partial::class.java,
|
|
||||||
DataValueTime_partial.TYPE
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DataValueEntityId(val value: WikiBaseEntityValue) :
|
// "value": {
|
||||||
DataValue(TYPE) {
|
// "entity-type": "item",
|
||||||
|
// "id": "Q30",
|
||||||
|
// "numeric-id": 30
|
||||||
|
// },
|
||||||
|
// "type": "wikibase-entityid"
|
||||||
|
// }
|
||||||
|
data class EntityId(val value: WikiBaseEntityValue) : DataValue(TYPE) {
|
||||||
companion object {
|
companion object {
|
||||||
const val TYPE = "wikibase-entityid"
|
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 {
|
companion object {
|
||||||
const val TYPE = "string"
|
const val TYPE = "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DataValueGlobeCoordinate_partial() :
|
// "value": {
|
||||||
DataValue(TYPE) {
|
// "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 {
|
companion object {
|
||||||
const val TYPE = "globecoordinate"
|
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 {
|
companion object {
|
||||||
const val TYPE = "time"
|
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
|
package org.wikipedia.wikidata
|
||||||
|
|
||||||
import org.wikipedia.wikidata.DataValue.DataValueEntityId
|
import org.wikipedia.wikidata.DataValue.EntityId
|
||||||
|
|
||||||
|
|
||||||
data class EditClaim(val claims: List<Statement_partial>) {
|
data class EditClaim(val claims: List<Statement_partial>) {
|
||||||
|
|
@ -14,7 +14,7 @@ data class EditClaim(val claims: List<Statement_partial>) {
|
||||||
Snak_partial(
|
Snak_partial(
|
||||||
"value",
|
"value",
|
||||||
propertyName,
|
propertyName,
|
||||||
DataValueEntityId(
|
EntityId(
|
||||||
WikiBaseEntityValue(
|
WikiBaseEntityValue(
|
||||||
"item",
|
"item",
|
||||||
entityId,
|
entityId,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package org.wikipedia.wikidata;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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> labels;
|
||||||
@Nullable private Map<String, Label> descriptions;
|
@Nullable private Map<String, Label> descriptions;
|
||||||
@Nullable private Map<String, SiteLink> sitelinks;
|
@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;
|
@Nullable private String missing;
|
||||||
|
|
||||||
@NonNull public String id() {
|
@NonNull public String id() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue