mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-10-30 22:34:02 +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,19 +98,15 @@ 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(); | ||||
|                         } | ||||
|                     } | ||||
|                 }, 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"); | ||||
| 
 | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Seán Mac Gillicuddy
						Seán Mac Gillicuddy