mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 04:43:54 +01:00
#3772 Convert SearchImagesFragment to use Pagination - convert SearchImagesFragment - tidy up showing the empty view - make search fragments show snackbar with appropriate text
This commit is contained in:
parent
6368e500a8
commit
c0fcf1a1c3
26 changed files with 331 additions and 428 deletions
|
|
@ -119,7 +119,7 @@ public class Media implements Parcelable {
|
||||||
* @param page response from the API
|
* @param page response from the API
|
||||||
* @return Media object
|
* @return Media object
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@NonNull
|
||||||
public static Media from(MwQueryPage page) {
|
public static Media from(MwQueryPage page) {
|
||||||
ImageInfo imageInfo = page.imageInfo();
|
ImageInfo imageInfo = page.imageInfo();
|
||||||
if (imageInfo == null) {
|
if (imageInfo == null) {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import fr.free.nrw.commons.depictions.Media.DepictedImagesFragment;
|
||||||
import fr.free.nrw.commons.depictions.subClass.SubDepictionListFragment;
|
import fr.free.nrw.commons.depictions.subClass.SubDepictionListFragment;
|
||||||
import fr.free.nrw.commons.explore.categories.SearchCategoryFragment;
|
import fr.free.nrw.commons.explore.categories.SearchCategoryFragment;
|
||||||
import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragment;
|
import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragment;
|
||||||
import fr.free.nrw.commons.explore.images.SearchImageFragment;
|
import fr.free.nrw.commons.explore.media.SearchMediaFragment;
|
||||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment;
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment;
|
||||||
import fr.free.nrw.commons.media.MediaDetailFragment;
|
import fr.free.nrw.commons.media.MediaDetailFragment;
|
||||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
|
|
@ -58,7 +58,7 @@ public abstract class FragmentBuilderModule {
|
||||||
abstract SubCategoryListFragment bindSubCategoryListFragment();
|
abstract SubCategoryListFragment bindSubCategoryListFragment();
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract SearchImageFragment bindBrowseImagesListFragment();
|
abstract SearchMediaFragment bindBrowseImagesListFragment();
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract SearchCategoryFragment bindSearchCategoryListFragment();
|
abstract SearchCategoryFragment bindSearchCategoryListFragment();
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.GONE
|
|
||||||
import android.view.View.VISIBLE
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
|
@ -24,9 +22,10 @@ import kotlinx.android.synthetic.main.fragment_search_paginated.*
|
||||||
abstract class BaseSearchFragment<T> : CommonsDaggerSupportFragment(),
|
abstract class BaseSearchFragment<T> : CommonsDaggerSupportFragment(),
|
||||||
SearchFragmentContract.View<T> {
|
SearchFragmentContract.View<T> {
|
||||||
|
|
||||||
abstract val pagedListAdapter: PagedListAdapter<T,*>
|
abstract val pagedListAdapter: PagedListAdapter<T, *>
|
||||||
abstract val injectedPresenter: SearchFragmentContract.Presenter<T>
|
abstract val injectedPresenter: SearchFragmentContract.Presenter<T>
|
||||||
abstract val emptyTemplateTextId: Int
|
abstract val emptyTemplateTextId: Int
|
||||||
|
abstract val errorTextId: Int
|
||||||
private val loadingAdapter by lazy { FooterAdapter { injectedPresenter.retryFailedRequest() } }
|
private val loadingAdapter by lazy { FooterAdapter { injectedPresenter.retryFailedRequest() } }
|
||||||
private val mergeAdapter by lazy { MergeAdapter(pagedListAdapter, loadingAdapter) }
|
private val mergeAdapter by lazy { MergeAdapter(pagedListAdapter, loadingAdapter) }
|
||||||
private var searchResults: LiveData<PagedList<T>>? = null
|
private var searchResults: LiveData<PagedList<T>>? = null
|
||||||
|
|
@ -52,10 +51,7 @@ abstract class BaseSearchFragment<T> : CommonsDaggerSupportFragment(),
|
||||||
override fun observeSearchResults(searchResults: LiveData<PagedList<T>>) {
|
override fun observeSearchResults(searchResults: LiveData<PagedList<T>>) {
|
||||||
this.searchResults?.removeObservers(viewLifecycleOwner)
|
this.searchResults?.removeObservers(viewLifecycleOwner)
|
||||||
this.searchResults = searchResults
|
this.searchResults = searchResults
|
||||||
searchResults.observe(viewLifecycleOwner, Observer {
|
searchResults.observe(viewLifecycleOwner, Observer(pagedListAdapter::submitList))
|
||||||
pagedListAdapter.submitList(it)
|
|
||||||
contentNotFound.visibility = if (it.loadedCount == 0) VISIBLE else GONE
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
|
|
@ -69,10 +65,6 @@ abstract class BaseSearchFragment<T> : CommonsDaggerSupportFragment(),
|
||||||
injectedPresenter.onDetachView()
|
injectedPresenter.onDetachView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setEmptyViewText(query: String) {
|
|
||||||
contentNotFound.text = getString(emptyTemplateTextId, query)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hideInitialLoadProgress() {
|
override fun hideInitialLoadProgress() {
|
||||||
paginatedSearchInitialLoadProgress.visibility = View.GONE
|
paginatedSearchInitialLoadProgress.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
@ -82,12 +74,21 @@ abstract class BaseSearchFragment<T> : CommonsDaggerSupportFragment(),
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showSnackbar() {
|
override fun showSnackbar() {
|
||||||
ViewUtil.showShortSnackbar(paginatedSearchResultsList, R.string.error_loading_depictions)
|
ViewUtil.showShortSnackbar(paginatedSearchResultsList, errorTextId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onQueryUpdated(query: String) {
|
fun onQueryUpdated(query: String) {
|
||||||
injectedPresenter.onQueryUpdated(query)
|
injectedPresenter.onQueryUpdated(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun showEmptyText(query: String) {
|
||||||
|
contentNotFound.text = getString(emptyTemplateTextId, query)
|
||||||
|
contentNotFound.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hideEmptyText() {
|
||||||
|
contentNotFound.visibility = View.GONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val Fragment.isPortrait get() = orientation == Configuration.ORIENTATION_PORTRAIT
|
private val Fragment.isPortrait get() = orientation == Configuration.ORIENTATION_PORTRAIT
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@ abstract class BaseSearchPresenter<T>(
|
||||||
|
|
||||||
private val DUMMY: SearchFragmentContract.View<T> = proxy()
|
private val DUMMY: SearchFragmentContract.View<T> = proxy()
|
||||||
private var view: SearchFragmentContract.View<T> = DUMMY
|
private var view: SearchFragmentContract.View<T> = DUMMY
|
||||||
private var currentQuery: String? = null
|
|
||||||
|
|
||||||
|
|
||||||
private val compositeDisposable = CompositeDisposable()
|
private val compositeDisposable = CompositeDisposable()
|
||||||
override val listFooterData = MutableLiveData<List<FooterItem>>().apply { value = emptyList() }
|
override val listFooterData = MutableLiveData<List<FooterItem>>().apply { value = emptyList() }
|
||||||
|
|
@ -27,31 +25,30 @@ abstract class BaseSearchPresenter<T>(
|
||||||
pageableDataSource.loadingStates
|
pageableDataSource.loadingStates
|
||||||
.observeOn(mainThreadScheduler)
|
.observeOn(mainThreadScheduler)
|
||||||
.subscribe(::onLoadingState, Timber::e),
|
.subscribe(::onLoadingState, Timber::e),
|
||||||
pageableDataSource.noItemsLoadedEvent.subscribe {
|
pageableDataSource.noItemsLoadedQueries.subscribe(view::showEmptyText)
|
||||||
setEmptyViewText()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onLoadingState(it: LoadingState) = when (it) {
|
private fun onLoadingState(it: LoadingState) = when (it) {
|
||||||
LoadingState.Loading -> listFooterData.postValue(listOf(FooterItem.LoadingItem))
|
LoadingState.Loading -> {
|
||||||
|
view.hideEmptyText()
|
||||||
|
listFooterData.postValue(listOf(FooterItem.LoadingItem))
|
||||||
|
}
|
||||||
LoadingState.Complete -> {
|
LoadingState.Complete -> {
|
||||||
listFooterData.postValue(emptyList())
|
listFooterData.postValue(emptyList())
|
||||||
view.hideInitialLoadProgress()
|
view.hideInitialLoadProgress()
|
||||||
}
|
}
|
||||||
LoadingState.InitialLoad -> view.showInitialLoadInProgress()
|
LoadingState.InitialLoad -> {
|
||||||
|
view.hideEmptyText()
|
||||||
|
view.showInitialLoadInProgress()
|
||||||
|
}
|
||||||
LoadingState.Error -> {
|
LoadingState.Error -> {
|
||||||
setEmptyViewText()
|
|
||||||
view.showSnackbar()
|
view.showSnackbar()
|
||||||
view.hideInitialLoadProgress()
|
view.hideInitialLoadProgress()
|
||||||
listFooterData.postValue(listOf(FooterItem.RefreshItem))
|
listFooterData.postValue(listOf(FooterItem.RefreshItem))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setEmptyViewText() {
|
|
||||||
currentQuery?.let(view::setEmptyViewText)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun retryFailedRequest() {
|
override fun retryFailedRequest() {
|
||||||
pageableDataSource.retryFailedRequest()
|
pageableDataSource.retryFailedRequest()
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +59,6 @@ abstract class BaseSearchPresenter<T>(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQueryUpdated(query: String) {
|
override fun onQueryUpdated(query: String) {
|
||||||
currentQuery = query
|
|
||||||
pageableDataSource.onQueryUpdated(query)
|
pageableDataSource.onQueryUpdated(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import android.text.TextUtils;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.SearchView;
|
import android.widget.SearchView;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
|
@ -20,7 +21,9 @@ import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.category.CategoryImagesCallback;
|
import fr.free.nrw.commons.category.CategoryImagesCallback;
|
||||||
import fr.free.nrw.commons.explore.categories.SearchCategoryFragment;
|
import fr.free.nrw.commons.explore.categories.SearchCategoryFragment;
|
||||||
import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragment;
|
import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragment;
|
||||||
import fr.free.nrw.commons.explore.images.SearchImageFragment;
|
import fr.free.nrw.commons.explore.media.SearchMediaFragment;
|
||||||
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearch;
|
||||||
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
|
||||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment;
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment;
|
||||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||||
|
|
@ -28,8 +31,10 @@ import fr.free.nrw.commons.utils.FragmentUtils;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.inject.Inject;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -46,14 +51,16 @@ public class SearchActivity extends NavigationBaseActivity
|
||||||
@BindView(R.id.tab_layout) TabLayout tabLayout;
|
@BindView(R.id.tab_layout) TabLayout tabLayout;
|
||||||
@BindView(R.id.viewPager) ViewPager viewPager;
|
@BindView(R.id.viewPager) ViewPager viewPager;
|
||||||
|
|
||||||
private SearchImageFragment searchImageFragment;
|
@Inject
|
||||||
|
RecentSearchesDao recentSearchesDao;
|
||||||
|
|
||||||
|
private SearchMediaFragment searchMediaFragment;
|
||||||
private SearchCategoryFragment searchCategoryFragment;
|
private SearchCategoryFragment searchCategoryFragment;
|
||||||
private SearchDepictionsFragment searchDepictionsFragment;
|
private SearchDepictionsFragment searchDepictionsFragment;
|
||||||
private RecentSearchesFragment recentSearchesFragment;
|
private RecentSearchesFragment recentSearchesFragment;
|
||||||
private FragmentManager supportFragmentManager;
|
private FragmentManager supportFragmentManager;
|
||||||
private MediaDetailPagerFragment mediaDetails;
|
private MediaDetailPagerFragment mediaDetails;
|
||||||
ViewPagerAdapter viewPagerAdapter;
|
ViewPagerAdapter viewPagerAdapter;
|
||||||
private String query;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
|
@ -92,10 +99,10 @@ public class SearchActivity extends NavigationBaseActivity
|
||||||
public void setTabs() {
|
public void setTabs() {
|
||||||
List<Fragment> fragmentList = new ArrayList<>();
|
List<Fragment> fragmentList = new ArrayList<>();
|
||||||
List<String> titleList = new ArrayList<>();
|
List<String> titleList = new ArrayList<>();
|
||||||
searchImageFragment = new SearchImageFragment();
|
searchMediaFragment = new SearchMediaFragment();
|
||||||
searchDepictionsFragment = new SearchDepictionsFragment();
|
searchDepictionsFragment = new SearchDepictionsFragment();
|
||||||
searchCategoryFragment= new SearchCategoryFragment();
|
searchCategoryFragment= new SearchCategoryFragment();
|
||||||
fragmentList.add(searchImageFragment);
|
fragmentList.add(searchMediaFragment);
|
||||||
titleList.add(getResources().getString(R.string.search_tab_title_media).toUpperCase());
|
titleList.add(getResources().getString(R.string.search_tab_title_media).toUpperCase());
|
||||||
fragmentList.add(searchCategoryFragment);
|
fragmentList.add(searchCategoryFragment);
|
||||||
titleList.add(getResources().getString(R.string.search_tab_title_categories).toUpperCase());
|
titleList.add(getResources().getString(R.string.search_tab_title_categories).toUpperCase());
|
||||||
|
|
@ -109,9 +116,9 @@ public class SearchActivity extends NavigationBaseActivity
|
||||||
.debounce(500, TimeUnit.MILLISECONDS)
|
.debounce(500, TimeUnit.MILLISECONDS)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(query -> {
|
.subscribe(query -> {
|
||||||
this.query = query.toString();
|
//update image list
|
||||||
//update image list
|
|
||||||
if (!TextUtils.isEmpty(query)) {
|
if (!TextUtils.isEmpty(query)) {
|
||||||
|
saveRecentSearch(query.toString());
|
||||||
viewPager.setVisibility(View.VISIBLE);
|
viewPager.setVisibility(View.VISIBLE);
|
||||||
tabLayout.setVisibility(View.VISIBLE);
|
tabLayout.setVisibility(View.VISIBLE);
|
||||||
searchHistoryContainer.setVisibility(View.GONE);
|
searchHistoryContainer.setVisibility(View.GONE);
|
||||||
|
|
@ -120,8 +127,8 @@ public class SearchActivity extends NavigationBaseActivity
|
||||||
searchDepictionsFragment.onQueryUpdated(query.toString());
|
searchDepictionsFragment.onQueryUpdated(query.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FragmentUtils.isFragmentUIActive(searchImageFragment)) {
|
if (FragmentUtils.isFragmentUIActive(searchMediaFragment)) {
|
||||||
searchImageFragment.updateImageList(query.toString());
|
searchMediaFragment.onQueryUpdated(query.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FragmentUtils.isFragmentUIActive(searchCategoryFragment)) {
|
if (FragmentUtils.isFragmentUIActive(searchCategoryFragment)) {
|
||||||
|
|
@ -140,13 +147,25 @@ public class SearchActivity extends NavigationBaseActivity
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void saveRecentSearch(@NonNull final String query) {
|
||||||
|
final RecentSearch recentSearch = recentSearchesDao.find(query);
|
||||||
|
// Newly searched query...
|
||||||
|
if (recentSearch == null) {
|
||||||
|
recentSearchesDao.save(new RecentSearch(null, query, new Date()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
recentSearch.setLastSearched(new Date());
|
||||||
|
recentSearchesDao.save(recentSearch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns Media Object at position
|
* returns Media Object at position
|
||||||
* @param i position of Media in the imagesRecyclerView adapter.
|
* @param i position of Media in the imagesRecyclerView adapter.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Media getMediaAtPosition(int i) {
|
public Media getMediaAtPosition(int i) {
|
||||||
return searchImageFragment.getImageAtPosition(i);
|
return searchMediaFragment.getImageAtPosition(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -154,7 +173,7 @@ public class SearchActivity extends NavigationBaseActivity
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getTotalMediaCount() {
|
public int getTotalMediaCount() {
|
||||||
return searchImageFragment.getTotalImagesCount();
|
return searchMediaFragment.getTotalImagesCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -250,8 +269,8 @@ public class SearchActivity extends NavigationBaseActivity
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void requestMoreImages() {
|
public void requestMoreImages() {
|
||||||
if (searchImageFragment!=null){
|
if (searchMediaFragment!=null){
|
||||||
searchImageFragment.addImagesToList(query);
|
searchMediaFragment.requestMoreImages();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ abstract class PageableDataSource<T>(private val liveDataConverter: LiveDataConv
|
||||||
val loadingStates: Flowable<LoadingState> = _loadingStates
|
val loadingStates: Flowable<LoadingState> = _loadingStates
|
||||||
private val _searchResults = PublishProcessor.create<LiveData<PagedList<T>>>()
|
private val _searchResults = PublishProcessor.create<LiveData<PagedList<T>>>()
|
||||||
val searchResults: Flowable<LiveData<PagedList<T>>> = _searchResults
|
val searchResults: Flowable<LiveData<PagedList<T>>> = _searchResults
|
||||||
private val _noItemsLoadedEvent = PublishProcessor.create<Unit>()
|
private val _noItemsLoadedEvent = PublishProcessor.create<String>()
|
||||||
val noItemsLoadedEvent: Flowable<Unit> = _noItemsLoadedEvent
|
val noItemsLoadedQueries: Flowable<String> = _noItemsLoadedEvent
|
||||||
private var currentFactory: SearchDataSourceFactory<T>? = null
|
private var currentFactory: SearchDataSourceFactory<T>? = null
|
||||||
|
|
||||||
abstract val loadFunction: LoadFunction<T>
|
abstract val loadFunction: LoadFunction<T>
|
||||||
|
|
@ -34,7 +34,7 @@ abstract class PageableDataSource<T>(private val liveDataConverter: LiveDataConv
|
||||||
this.query = query
|
this.query = query
|
||||||
_searchResults.offer(
|
_searchResults.offer(
|
||||||
liveDataConverter.convert(dataSourceFactoryFactory().also { currentFactory = it }) {
|
liveDataConverter.convert(dataSourceFactoryFactory().also { currentFactory = it }) {
|
||||||
_noItemsLoadedEvent.offer(Unit)
|
_noItemsLoadedEvent.offer(query)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,10 @@ interface SearchFragmentContract {
|
||||||
interface View<T> {
|
interface View<T> {
|
||||||
fun showSnackbar()
|
fun showSnackbar()
|
||||||
fun observeSearchResults(searchResults: LiveData<PagedList<T>>)
|
fun observeSearchResults(searchResults: LiveData<PagedList<T>>)
|
||||||
fun setEmptyViewText(query: String)
|
|
||||||
fun showInitialLoadInProgress()
|
fun showInitialLoadInProgress()
|
||||||
fun hideInitialLoadProgress()
|
fun hideInitialLoadProgress()
|
||||||
|
fun showEmptyText(query: String)
|
||||||
|
fun hideEmptyText()
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Presenter<T> : BasePresenter<View<T>> {
|
interface Presenter<T> : BasePresenter<View<T>> {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import dagger.Module
|
||||||
import fr.free.nrw.commons.explore.categories.SearchCategoriesFragmentContract
|
import fr.free.nrw.commons.explore.categories.SearchCategoriesFragmentContract
|
||||||
import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragmentContract
|
import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragmentContract
|
||||||
import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragmentPresenter
|
import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragmentPresenter
|
||||||
|
import fr.free.nrw.commons.explore.media.SearchMediaFragmentContract
|
||||||
|
import fr.free.nrw.commons.explore.media.SearchMediaFragmentPresenter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Dagger Module for explore:depictions related presenters and (some other objects maybe in future)
|
* The Dagger Module for explore:depictions related presenters and (some other objects maybe in future)
|
||||||
|
|
@ -18,7 +20,12 @@ abstract class SearchModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindsSearchCategoriesFragmentPresenter(
|
abstract fun bindsSearchCategoriesFragmentPresenter(
|
||||||
presenter: SearchCategoriesFragmentPresenter?
|
presenter: SearchCategoriesFragmentPresenter
|
||||||
): SearchCategoriesFragmentContract.Presenter?
|
): SearchCategoriesFragmentContract.Presenter
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindsSearchMediaFragmentPresenter(
|
||||||
|
presenter: SearchMediaFragmentPresenter
|
||||||
|
): SearchMediaFragmentContract.Presenter
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ class SearchCategoryFragment : BaseSearchFragment<String>() {
|
||||||
|
|
||||||
override val emptyTemplateTextId: Int = R.string.categories_not_found
|
override val emptyTemplateTextId: Int = R.string.categories_not_found
|
||||||
|
|
||||||
|
override val errorTextId: Int = R.string.error_loading_categories
|
||||||
|
|
||||||
override val injectedPresenter: SearchFragmentContract.Presenter<String>
|
override val injectedPresenter: SearchFragmentContract.Presenter<String>
|
||||||
get() = presenter
|
get() = presenter
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package fr.free.nrw.commons.explore.depictions
|
||||||
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
|
||||||
import fr.free.nrw.commons.explore.BaseSearchFragment
|
import fr.free.nrw.commons.explore.BaseSearchFragment
|
||||||
import fr.free.nrw.commons.explore.SearchFragmentContract
|
|
||||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -17,7 +16,9 @@ class SearchDepictionsFragment : BaseSearchFragment<DepictedItem>(),
|
||||||
|
|
||||||
override val emptyTemplateTextId: Int = R.string.depictions_not_found
|
override val emptyTemplateTextId: Int = R.string.depictions_not_found
|
||||||
|
|
||||||
override val injectedPresenter: SearchFragmentContract.Presenter<DepictedItem>
|
override val errorTextId: Int = R.string.error_loading_depictions
|
||||||
|
|
||||||
|
override val injectedPresenter: SearchDepictionsFragmentContract.Presenter
|
||||||
get() = presenter
|
get() = presenter
|
||||||
|
|
||||||
override val pagedListAdapter by lazy {
|
override val pagedListAdapter by lazy {
|
||||||
|
|
|
||||||
|
|
@ -1,288 +0,0 @@
|
||||||
package fr.free.nrw.commons.explore.images;
|
|
||||||
|
|
||||||
import static android.view.View.GONE;
|
|
||||||
import static android.view.View.VISIBLE;
|
|
||||||
import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import fr.free.nrw.commons.Media;
|
|
||||||
import fr.free.nrw.commons.R;
|
|
||||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
|
||||||
import fr.free.nrw.commons.explore.SearchActivity;
|
|
||||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearch;
|
|
||||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
|
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
|
||||||
import fr.free.nrw.commons.media.MediaClient;
|
|
||||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import kotlin.Unit;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays the image search screen.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class SearchImageFragment extends CommonsDaggerSupportFragment {
|
|
||||||
|
|
||||||
@BindView(R.id.imagesListBox)
|
|
||||||
RecyclerView imagesRecyclerView;
|
|
||||||
@BindView(R.id.imageSearchInProgress)
|
|
||||||
ProgressBar progressBar;
|
|
||||||
@BindView(R.id.imagesNotFound)
|
|
||||||
TextView imagesNotFoundView;
|
|
||||||
String query;
|
|
||||||
@BindView(R.id.bottomProgressBar)
|
|
||||||
ProgressBar bottomProgressBar;
|
|
||||||
|
|
||||||
@Inject RecentSearchesDao recentSearchesDao;
|
|
||||||
@Inject
|
|
||||||
MediaClient mediaClient;
|
|
||||||
@Inject
|
|
||||||
@Named("default_preferences")
|
|
||||||
JsonKvStore defaultKvStore;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A variable to store number of list items for whom API has been called to fetch captions
|
|
||||||
*/
|
|
||||||
private int mediaSize = 0;
|
|
||||||
|
|
||||||
private SearchImagesAdapter imagesAdapter;
|
|
||||||
private List<Media> queryList = new ArrayList<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method saves Search Query in the Recent Searches Database.
|
|
||||||
* @param query
|
|
||||||
*/
|
|
||||||
private void saveQuery(String query) {
|
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
|
|
||||||
View rootView = inflater.inflate(R.layout.fragment_browse_image, container, false);
|
|
||||||
ButterKnife.bind(this, rootView);
|
|
||||||
if (getContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
|
|
||||||
imagesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
imagesRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
|
||||||
}
|
|
||||||
imagesAdapter =new SearchImagesAdapter(media -> {
|
|
||||||
((SearchActivity)getContext()).onSearchImageClicked(imagesAdapter.getItems().indexOf(media));
|
|
||||||
saveQuery(query);
|
|
||||||
return Unit.INSTANCE;
|
|
||||||
});
|
|
||||||
imagesRecyclerView.setAdapter(imagesAdapter);
|
|
||||||
imagesRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
|
||||||
@Override
|
|
||||||
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
|
||||||
super.onScrollStateChanged(recyclerView, newState);
|
|
||||||
// check if end of recycler view is reached, if yes then add more results to existing results
|
|
||||||
if (!recyclerView.canScrollVertically(1)) {
|
|
||||||
addImagesToList(query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return rootView;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks for internet connection and then initializes the recycler view with 25 images of the searched query
|
|
||||||
* Clearing imageAdapter every time new keyword is searched so that user can see only new results
|
|
||||||
*/
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
public void updateImageList(String query) {
|
|
||||||
this.query = query;
|
|
||||||
if (imagesNotFoundView != null) {
|
|
||||||
imagesNotFoundView.setVisibility(GONE);
|
|
||||||
}
|
|
||||||
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
|
||||||
handleNoInternet();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
|
||||||
bottomProgressBar.setVisibility(GONE);
|
|
||||||
queryList.clear();
|
|
||||||
imagesAdapter.clear();
|
|
||||||
compositeDisposable.add(mediaClient.getMediaListFromSearch(query)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.doOnSubscribe(disposable -> saveQuery(query))
|
|
||||||
.subscribe(this::handleSuccess, this::handleError));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds more results to existing search results
|
|
||||||
*/
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
public void addImagesToList(String query) {
|
|
||||||
this.query = query;
|
|
||||||
bottomProgressBar.setVisibility(View.VISIBLE);
|
|
||||||
progressBar.setVisibility(GONE);
|
|
||||||
compositeDisposable.add(mediaClient.getMediaListFromSearch(query)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(this::handlePaginationSuccess, this::handleError));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the success scenario
|
|
||||||
* it initializes the recycler view by adding items to the adapter
|
|
||||||
* @param mediaList List of media to be added
|
|
||||||
*/
|
|
||||||
private void handlePaginationSuccess(List<Media> mediaList) {
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
bottomProgressBar.setVisibility(GONE);
|
|
||||||
if (mediaList.size() != 0 && !queryList.get(queryList.size() - 1).getFilename().equals(mediaList.get(mediaList.size() - 1).getFilename())) {
|
|
||||||
queryList.addAll(mediaList);
|
|
||||||
imagesAdapter.addAll(mediaList);
|
|
||||||
((SearchActivity) getContext()).viewPagerNotifyDataSetChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the success scenario
|
|
||||||
* it initializes the recycler view by adding items to the adapter
|
|
||||||
* @param mediaList List of media to be shown
|
|
||||||
*/
|
|
||||||
private void handleSuccess(List<Media> mediaList) {
|
|
||||||
queryList = mediaList;
|
|
||||||
if (mediaList == null || mediaList.isEmpty()) {
|
|
||||||
initErrorView();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
bottomProgressBar.setVisibility(View.GONE);
|
|
||||||
progressBar.setVisibility(GONE);
|
|
||||||
imagesAdapter.addAll(mediaList);
|
|
||||||
imagesAdapter.notifyDataSetChanged();
|
|
||||||
((SearchActivity)getContext()).viewPagerNotifyDataSetChanged();
|
|
||||||
for (Media m : mediaList) {
|
|
||||||
final String pageId = m.getPageId();
|
|
||||||
if (pageId != null) {
|
|
||||||
replaceTitlesWithCaptions(PAGE_ID_PREFIX + pageId, mediaSize++);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In explore we first show title and simultaneously call the API to retrieve captions
|
|
||||||
* When captions are retrieved they replace title
|
|
||||||
*/
|
|
||||||
|
|
||||||
public void replaceTitlesWithCaptions(String wikibaseIdentifier, int position) {
|
|
||||||
compositeDisposable.add(mediaClient.getCaptionByWikibaseIdentifier(wikibaseIdentifier)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(subscriber -> {
|
|
||||||
handleLabelforImage(subscriber, position);
|
|
||||||
}));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleLabelforImage(String s, int position) {
|
|
||||||
if (!s.trim().equals(getString(R.string.detail_caption_empty))) {
|
|
||||||
imagesAdapter.updateThumbnail(position, s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs and handles API error scenario
|
|
||||||
* @param throwable
|
|
||||||
*/
|
|
||||||
private void handleError(Throwable throwable) {
|
|
||||||
Timber.e(throwable, "Error occurred while loading queried images");
|
|
||||||
try {
|
|
||||||
ViewUtil.showShortSnackbar(imagesRecyclerView, R.string.error_loading_images);
|
|
||||||
}catch (Exception e){
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the UI updates for a error scenario
|
|
||||||
*/
|
|
||||||
private void initErrorView() {
|
|
||||||
progressBar.setVisibility(GONE);
|
|
||||||
imagesNotFoundView.setVisibility(VISIBLE);
|
|
||||||
imagesNotFoundView.setText(getString(R.string.images_not_found,query));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the UI updates for no internet scenario
|
|
||||||
*/
|
|
||||||
private void handleNoInternet() {
|
|
||||||
if (null
|
|
||||||
!= getView()) {//We have exposed public methods to update our ui, we will have to add null checks until we make this lifecycle aware
|
|
||||||
if (null != progressBar) {
|
|
||||||
progressBar.setVisibility(GONE);
|
|
||||||
}
|
|
||||||
ViewUtil.showShortSnackbar(imagesRecyclerView, R.string.no_internet);
|
|
||||||
} else {
|
|
||||||
Timber.d("Attempt to update fragment ui after its view was destroyed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns total number of images present in the recyclerview adapter.
|
|
||||||
*/
|
|
||||||
public int getTotalImagesCount(){
|
|
||||||
if (imagesAdapter == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return imagesAdapter.getItemCount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns Media Object at position
|
|
||||||
* @param i position of Media in the recyclerview adapter.
|
|
||||||
*/
|
|
||||||
public Media getImageAtPosition(int i) {
|
|
||||||
if (imagesAdapter.getItemAt(i).getFilename() == null) {
|
|
||||||
// not yet ready to return data
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return imagesAdapter.getItemAt(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void onDestroyView() {
|
|
||||||
super.onDestroyView();
|
|
||||||
compositeDisposable.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package fr.free.nrw.commons.explore.images
|
|
||||||
|
|
||||||
import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter
|
|
||||||
import fr.free.nrw.commons.Media
|
|
||||||
|
|
||||||
class SearchImagesAdapter(onImageClicked: (Media) -> Unit) : ListDelegationAdapter<List<Media>>(
|
|
||||||
searchImagesAdapter(onImageClicked)
|
|
||||||
) {
|
|
||||||
fun getItemAt(position: Int) = items[position]
|
|
||||||
|
|
||||||
init {
|
|
||||||
items = emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clear() {
|
|
||||||
items = emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addAll(mediaList: List<Media>) {
|
|
||||||
items = items + mediaList
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateThumbnail(position: Int, thumbnailTitle: String) {
|
|
||||||
items[position].thumbnailTitle = thumbnailTitle
|
|
||||||
notifyItemChanged(position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package fr.free.nrw.commons.explore.images
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer
|
|
||||||
import fr.free.nrw.commons.Media
|
|
||||||
import fr.free.nrw.commons.R
|
|
||||||
import kotlinx.android.synthetic.main.layout_category_images.*
|
|
||||||
|
|
||||||
|
|
||||||
fun searchImagesAdapter(onImageClicked: (Media) -> Unit) =
|
|
||||||
adapterDelegateLayoutContainer<Media, Media>(R.layout.layout_category_images) {
|
|
||||||
categoryImageView.setOnClickListener { onImageClicked(item) }
|
|
||||||
bind {
|
|
||||||
categoryImageTitle.text = item.thumbnailTitle
|
|
||||||
categoryImageView.setImageURI(item.thumbUrl)
|
|
||||||
if (item.creator?.isNotEmpty() == true) {
|
|
||||||
categoryImageAuthor.visibility = View.VISIBLE
|
|
||||||
categoryImageAuthor.text = getString(R.string.image_uploaded_by, item.creator)
|
|
||||||
} else {
|
|
||||||
categoryImageAuthor.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package fr.free.nrw.commons.explore.media
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Media
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryPage
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class MediaConverter @Inject constructor() {
|
||||||
|
fun convert(mwQueryPage: MwQueryPage): Media = Media.from(mwQueryPage)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package fr.free.nrw.commons.explore.media
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Media
|
||||||
|
import fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX
|
||||||
|
import fr.free.nrw.commons.explore.LiveDataConverter
|
||||||
|
import fr.free.nrw.commons.explore.PageableDataSource
|
||||||
|
import fr.free.nrw.commons.explore.depictions.LoadFunction
|
||||||
|
import fr.free.nrw.commons.media.MediaClient
|
||||||
|
import fr.free.nrw.commons.media.MediaClient.NO_CAPTION
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class PageableMediaDataSource @Inject constructor(
|
||||||
|
liveDataConverter: LiveDataConverter,
|
||||||
|
private val mediaConverter: MediaConverter,
|
||||||
|
private val mediaClient: MediaClient
|
||||||
|
) : PageableDataSource<Media>(liveDataConverter) {
|
||||||
|
override val loadFunction: LoadFunction<Media> = { loadSize: Int, startPosition: Int ->
|
||||||
|
mediaClient.getMediaListFromSearch(query, loadSize, startPosition)
|
||||||
|
.map { it.query()?.pages()?.map(mediaConverter::convert) ?: emptyList() }
|
||||||
|
.map { it.zip(getCaptions(it)) }
|
||||||
|
.map { it.map { (media, caption) -> media.also { it.caption = caption } } }
|
||||||
|
.blockingGet()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCaptions(it: List<Media>) =
|
||||||
|
mediaClient.getEntities(it.joinToString("|") { PAGE_ID_PREFIX + it.pageId })
|
||||||
|
.map {
|
||||||
|
it.entities().values.map { entity ->
|
||||||
|
entity.labels().values.firstOrNull()?.value() ?: NO_CAPTION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.blockingGet()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package fr.free.nrw.commons.explore.media
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.paging.PagedListAdapter
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import fr.free.nrw.commons.Media
|
||||||
|
import fr.free.nrw.commons.R
|
||||||
|
import fr.free.nrw.commons.explore.BaseViewHolder
|
||||||
|
import fr.free.nrw.commons.explore.inflate
|
||||||
|
import kotlinx.android.synthetic.main.layout_category_images.*
|
||||||
|
|
||||||
|
class SearchImagesAdapter(private val onImageClicked: (Int) -> Unit) :
|
||||||
|
PagedListAdapter<Media, SearchImagesViewHolder>(object : DiffUtil.ItemCallback<Media>() {
|
||||||
|
override fun areItemsTheSame(oldItem: Media, newItem: Media) =
|
||||||
|
oldItem.pageId == newItem.pageId
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: Media, newItem: Media) =
|
||||||
|
oldItem.pageId == newItem.pageId
|
||||||
|
}) {
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
|
SearchImagesViewHolder(
|
||||||
|
parent.inflate(R.layout.layout_category_images),
|
||||||
|
onImageClicked
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: SearchImagesViewHolder, position: Int) {
|
||||||
|
holder.bind(getItem(position)!! to position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchImagesViewHolder(containerView: View, val onImageClicked: (Int) -> Unit) :
|
||||||
|
BaseViewHolder<Pair<Media, Int>>(containerView) {
|
||||||
|
override fun bind(item: Pair<Media, Int>) {
|
||||||
|
val media = item.first
|
||||||
|
categoryImageView.setOnClickListener { onImageClicked(item.second) }
|
||||||
|
categoryImageTitle.text = media.thumbnailTitle
|
||||||
|
categoryImageView.setImageURI(media.thumbUrl)
|
||||||
|
if (media.creator?.isNotEmpty() == true) {
|
||||||
|
categoryImageAuthor.visibility = View.VISIBLE
|
||||||
|
categoryImageAuthor.text =
|
||||||
|
containerView.context.getString(R.string.image_uploaded_by, media.creator)
|
||||||
|
} else {
|
||||||
|
categoryImageAuthor.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package fr.free.nrw.commons.explore.media
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Media
|
||||||
|
import fr.free.nrw.commons.R
|
||||||
|
import fr.free.nrw.commons.explore.BaseSearchFragment
|
||||||
|
import fr.free.nrw.commons.explore.SearchActivity
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the image search screen.
|
||||||
|
*/
|
||||||
|
class SearchMediaFragment : BaseSearchFragment<Media>(), SearchMediaFragmentContract.View {
|
||||||
|
@Inject
|
||||||
|
lateinit var presenter: SearchMediaFragmentContract.Presenter
|
||||||
|
|
||||||
|
override val emptyTemplateTextId: Int = R.string.depictions_not_found
|
||||||
|
|
||||||
|
override val errorTextId: Int = R.string.error_loading_images
|
||||||
|
|
||||||
|
override val injectedPresenter: SearchMediaFragmentContract.Presenter
|
||||||
|
get() = presenter
|
||||||
|
|
||||||
|
override val pagedListAdapter by lazy {
|
||||||
|
SearchImagesAdapter {
|
||||||
|
(context as SearchActivity?)!!.onSearchImageClicked(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestMoreImages() {
|
||||||
|
// This paradigm is not well suited to pagination
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getImageAtPosition(position: Int): Media? =
|
||||||
|
pagedListAdapter.currentList?.get(position)?.takeIf { it.filename != null }
|
||||||
|
|
||||||
|
fun getTotalImagesCount(): Int = pagedListAdapter.itemCount
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package fr.free.nrw.commons.explore.media
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Media
|
||||||
|
import fr.free.nrw.commons.explore.SearchFragmentContract
|
||||||
|
|
||||||
|
|
||||||
|
interface SearchMediaFragmentContract {
|
||||||
|
interface View : SearchFragmentContract.View<Media>
|
||||||
|
interface Presenter : SearchFragmentContract.Presenter<Media>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package fr.free.nrw.commons.explore.media
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Media
|
||||||
|
import fr.free.nrw.commons.di.CommonsApplicationModule
|
||||||
|
import fr.free.nrw.commons.explore.BaseSearchPresenter
|
||||||
|
import io.reactivex.Scheduler
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Named
|
||||||
|
|
||||||
|
class SearchMediaFragmentPresenter @Inject constructor(
|
||||||
|
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
|
||||||
|
dataSourceFactory: PageableMediaDataSource
|
||||||
|
) : BaseSearchPresenter<Media>(mainThreadScheduler, dataSourceFactory),
|
||||||
|
SearchMediaFragmentContract.Presenter
|
||||||
|
|
@ -6,7 +6,6 @@ import fr.free.nrw.commons.wikidata.WikidataProperties.DEPICTS
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import org.wikipedia.wikidata.DataValue.EntityId
|
import org.wikipedia.wikidata.DataValue.EntityId
|
||||||
import org.wikipedia.wikidata.Entities
|
import org.wikipedia.wikidata.Entities
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Depictions(val depictions: List<IdAndLabel>) : Parcelable {
|
data class Depictions(val depictions: List<IdAndLabel>) : Parcelable {
|
||||||
|
|
@ -25,6 +24,6 @@ data class Depictions(val depictions: List<IdAndLabel>) : Parcelable {
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun fetchLabel(mediaClient: MediaClient, id: String) =
|
private fun fetchLabel(mediaClient: MediaClient, id: String) =
|
||||||
mediaClient.getLabelForDepiction(id, Locale.getDefault().language).blockingGet()
|
mediaClient.getLabelForDepiction(id).blockingGet()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,14 +88,12 @@ public class MediaClient {
|
||||||
* It uses the generator query API to get the images searched using a query, 10 at a time.
|
* It uses the generator query API to get the images searched using a query, 10 at a time.
|
||||||
*
|
*
|
||||||
* @param keyword the search keyword
|
* @param keyword the search keyword
|
||||||
|
* @param limit
|
||||||
|
* @param offset
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Single<List<Media>> getMediaListFromSearch(String keyword) {
|
public Single<MwQueryResponse> getMediaListFromSearch(String keyword, int limit, int offset) {
|
||||||
return responseToMediaList(
|
return mediaInterface.getMediaListFromSearch(keyword, limit, offset);
|
||||||
continuationStore.containsKey("search_" + keyword) && (continuationStore.get("search_" + keyword) != null) ?
|
|
||||||
mediaInterface.getMediaListFromSearch(keyword, 10, continuationStore.get("search_" + keyword)) : //if true
|
|
||||||
mediaInterface.getMediaListFromSearch(keyword, 10, Collections.emptyMap()), //if false
|
|
||||||
"search_" + keyword);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,8 +205,8 @@ public class MediaClient {
|
||||||
* @param entityId EntityId (Ex: Q81566) of the depict entity
|
* @param entityId EntityId (Ex: Q81566) of the depict entity
|
||||||
* @return label
|
* @return label
|
||||||
*/
|
*/
|
||||||
public Single<String> getLabelForDepiction(String entityId, String language) {
|
public Single<String> getLabelForDepiction(String entityId) {
|
||||||
return mediaDetailInterface.getEntity(entityId, language)
|
return getEntities(entityId)
|
||||||
.map(entities -> {
|
.map(entities -> {
|
||||||
if (isSuccess(entities)) {
|
if (isSuccess(entities)) {
|
||||||
for (Entity entity : entities.entities().values()) {
|
for (Entity entity : entities.entities().values()) {
|
||||||
|
|
@ -218,9 +216,10 @@ public class MediaClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new RuntimeException("failed getEntities");
|
throw new RuntimeException("failed getEntities");
|
||||||
})
|
});
|
||||||
.singleOrError();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Single<Entities> getEntities(String entityId) {
|
||||||
|
return mediaDetailInterface.getEntity(entityId, Locale.getDefault().getLanguage());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package fr.free.nrw.commons.media;
|
package fr.free.nrw.commons.media;
|
||||||
|
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.Single;
|
||||||
import org.wikipedia.wikidata.Entities;
|
import org.wikipedia.wikidata.Entities;
|
||||||
import retrofit2.http.GET;
|
import retrofit2.http.GET;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
@ -25,7 +26,7 @@ public interface MediaDetailInterface {
|
||||||
* @param language user's locale
|
* @param language user's locale
|
||||||
*/
|
*/
|
||||||
@GET("/w/api.php?format=json&action=wbgetentities&props=labels&languagefallback=1")
|
@GET("/w/api.php?format=json&action=wbgetentities&props=labels&languagefallback=1")
|
||||||
Observable<Entities> getEntity(@Query("ids") String entityId, @Query("languages") String language);
|
Single<Entities> getEntity(@Query("ids") String entityId, @Query("languages") String language);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches caption using wikibaseIdentifier
|
* Fetches caption using wikibaseIdentifier
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
package fr.free.nrw.commons.media;
|
package fr.free.nrw.commons.media;
|
||||||
|
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.depictions.models.DepictionResponse;
|
import fr.free.nrw.commons.depictions.models.DepictionResponse;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.Single;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||||
import retrofit2.http.GET;
|
import retrofit2.http.GET;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
import retrofit2.http.QueryMap;
|
import retrofit2.http.QueryMap;
|
||||||
|
|
@ -53,13 +52,13 @@ public interface MediaInterface {
|
||||||
*
|
*
|
||||||
* @param keyword the searched keyword
|
* @param keyword the searched keyword
|
||||||
* @param itemLimit how many images are returned
|
* @param itemLimit how many images are returned
|
||||||
* @param continuation the continuation string from the previous query
|
* @param offset the offset in the result set
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@GET("w/api.php?action=query&format=json&formatversion=2" + //Basic parameters
|
@GET("w/api.php?action=query&format=json&formatversion=2" + //Basic parameters
|
||||||
"&generator=search&gsrwhat=text&gsrnamespace=6" + //Search parameters
|
"&generator=search&gsrwhat=text&gsrnamespace=6" + //Search parameters
|
||||||
MEDIA_PARAMS)
|
MEDIA_PARAMS)
|
||||||
Observable<MwQueryResponse> getMediaListFromSearch(@Query("gsrsearch") String keyword, @Query("gsrlimit") int itemLimit, @QueryMap Map<String, String> continuation);
|
Single<MwQueryResponse> getMediaListFromSearch(@Query("gsrsearch") String keyword, @Query("gsrlimit") int itemLimit, @Query("gsroffset") int offset);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches Media object from the imageInfo API
|
* Fetches Media object from the imageInfo API
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
import com.jraska.livedata.test
|
import com.jraska.livedata.test
|
||||||
import com.nhaarman.mockitokotlin2.*
|
import com.nhaarman.mockitokotlin2.mock
|
||||||
|
import com.nhaarman.mockitokotlin2.verify
|
||||||
|
import com.nhaarman.mockitokotlin2.whenever
|
||||||
import io.reactivex.processors.PublishProcessor
|
import io.reactivex.processors.PublishProcessor
|
||||||
import io.reactivex.schedulers.TestScheduler
|
import io.reactivex.schedulers.TestScheduler
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
|
|
@ -34,7 +36,7 @@ class BaseSearchPresenterTest {
|
||||||
private var searchResults: PublishProcessor<LiveData<PagedList<String>>> =
|
private var searchResults: PublishProcessor<LiveData<PagedList<String>>> =
|
||||||
PublishProcessor.create()
|
PublishProcessor.create()
|
||||||
|
|
||||||
private var noItemLoadedEvent: PublishProcessor<Unit> = PublishProcessor.create()
|
private var noItemLoadedQueries: PublishProcessor<String> = PublishProcessor.create()
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
|
|
@ -42,8 +44,8 @@ class BaseSearchPresenterTest {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
whenever(pageableDataSource.searchResults).thenReturn(searchResults)
|
whenever(pageableDataSource.searchResults).thenReturn(searchResults)
|
||||||
whenever(pageableDataSource.loadingStates).thenReturn(loadingStates)
|
whenever(pageableDataSource.loadingStates).thenReturn(loadingStates)
|
||||||
whenever(pageableDataSource.noItemsLoadedEvent)
|
whenever(pageableDataSource.noItemsLoadedQueries)
|
||||||
.thenReturn(noItemLoadedEvent)
|
.thenReturn(noItemLoadedQueries)
|
||||||
testScheduler = TestScheduler()
|
testScheduler = TestScheduler()
|
||||||
baseSearchPresenter =
|
baseSearchPresenter =
|
||||||
object : BaseSearchPresenter<String>(testScheduler, pageableDataSource) {}
|
object : BaseSearchPresenter<String>(testScheduler, pageableDataSource) {}
|
||||||
|
|
@ -60,6 +62,7 @@ class BaseSearchPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `Loading offers a loading list item`() {
|
fun `Loading offers a loading list item`() {
|
||||||
onLoadingState(LoadingState.Loading)
|
onLoadingState(LoadingState.Loading)
|
||||||
|
verify(view).hideEmptyText()
|
||||||
baseSearchPresenter.listFooterData.test().assertValue(listOf(FooterItem.LoadingItem))
|
baseSearchPresenter.listFooterData.test().assertValue(listOf(FooterItem.LoadingItem))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,6 +77,7 @@ class BaseSearchPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `InitialLoad shows initial loader`() {
|
fun `InitialLoad shows initial loader`() {
|
||||||
onLoadingState(LoadingState.InitialLoad)
|
onLoadingState(LoadingState.InitialLoad)
|
||||||
|
verify(view).hideEmptyText()
|
||||||
verify(view).showInitialLoadInProgress()
|
verify(view).showInitialLoadInProgress()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,16 +85,6 @@ class BaseSearchPresenterTest {
|
||||||
fun `Error offers a refresh list item, hides initial loader and shows error with a set text`() {
|
fun `Error offers a refresh list item, hides initial loader and shows error with a set text`() {
|
||||||
baseSearchPresenter.onQueryUpdated("test")
|
baseSearchPresenter.onQueryUpdated("test")
|
||||||
onLoadingState(LoadingState.Error)
|
onLoadingState(LoadingState.Error)
|
||||||
verify(view).setEmptyViewText("test")
|
|
||||||
verify(view).showSnackbar()
|
|
||||||
verify(view).hideInitialLoadProgress()
|
|
||||||
baseSearchPresenter.listFooterData.test().assertValue(listOf(FooterItem.RefreshItem))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Error offers a refresh list item, hides initial loader and shows error with a unset text`() {
|
|
||||||
onLoadingState(LoadingState.Error)
|
|
||||||
verify(view, never()).setEmptyViewText(any())
|
|
||||||
verify(view).showSnackbar()
|
verify(view).showSnackbar()
|
||||||
verify(view).hideInitialLoadProgress()
|
verify(view).hideInitialLoadProgress()
|
||||||
baseSearchPresenter.listFooterData.test().assertValue(listOf(FooterItem.RefreshItem))
|
baseSearchPresenter.listFooterData.test().assertValue(listOf(FooterItem.RefreshItem))
|
||||||
|
|
@ -98,9 +92,8 @@ class BaseSearchPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `no Items event sets empty view text`() {
|
fun `no Items event sets empty view text`() {
|
||||||
baseSearchPresenter.onQueryUpdated("test")
|
noItemLoadedQueries.offer("test")
|
||||||
noItemLoadedEvent.offer(Unit)
|
verify(view).showEmptyText("test")
|
||||||
verify(view).setEmptyViewText("test")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,9 @@ class PageableDataSourceTest {
|
||||||
fun `onQueryUpdated invokes livedatconverter with no items emitter`() {
|
fun `onQueryUpdated invokes livedatconverter with no items emitter`() {
|
||||||
val (zeroItemsFuncCaptor, _) = expectNewLiveData()
|
val (zeroItemsFuncCaptor, _) = expectNewLiveData()
|
||||||
pageableDataSource.onQueryUpdated("test")
|
pageableDataSource.onQueryUpdated("test")
|
||||||
pageableDataSource.noItemsLoadedEvent.test()
|
pageableDataSource.noItemsLoadedQueries.test()
|
||||||
.also { zeroItemsFuncCaptor.firstValue.invoke() }
|
.also { zeroItemsFuncCaptor.firstValue.invoke() }
|
||||||
.assertValue(Unit)
|
.assertValue("test")
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
package fr.free.nrw.commons.explore.media
|
||||||
|
|
||||||
|
import com.nhaarman.mockitokotlin2.mock
|
||||||
|
import com.nhaarman.mockitokotlin2.verify
|
||||||
|
import com.nhaarman.mockitokotlin2.whenever
|
||||||
|
import fr.free.nrw.commons.Media
|
||||||
|
import fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX
|
||||||
|
import fr.free.nrw.commons.media.MediaClient
|
||||||
|
import io.reactivex.Single
|
||||||
|
import org.hamcrest.MatcherAssert.assertThat
|
||||||
|
import org.hamcrest.Matchers.`is`
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryPage
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryResult
|
||||||
|
import org.wikipedia.wikidata.Entities
|
||||||
|
|
||||||
|
class PageableMediaDataSourceTest {
|
||||||
|
@Mock
|
||||||
|
lateinit var mediaConverter: MediaConverter
|
||||||
|
@Mock
|
||||||
|
lateinit var mediaClient: MediaClient
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `loadFunction invokes mediaClient and has Label`() {
|
||||||
|
val (media, entity: Entities.Entity) = expectMediaAndEntity()
|
||||||
|
val label: Entities.Label = mock()
|
||||||
|
whenever(entity.labels()).thenReturn(mapOf(" " to label))
|
||||||
|
whenever(label.value()).thenReturn("label")
|
||||||
|
val pageableMediaDataSource = PageableMediaDataSource(mock(), mediaConverter, mediaClient)
|
||||||
|
pageableMediaDataSource.onQueryUpdated("test")
|
||||||
|
assertThat(pageableMediaDataSource.loadFunction(0,1), `is`(listOf(media)))
|
||||||
|
verify(media).caption = "label"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `loadFunction invokes mediaClient and does not have Label`() {
|
||||||
|
val (media, entity: Entities.Entity) = expectMediaAndEntity()
|
||||||
|
whenever(entity.labels()).thenReturn(mapOf())
|
||||||
|
val pageableMediaDataSource = PageableMediaDataSource(mock(), mediaConverter, mediaClient)
|
||||||
|
pageableMediaDataSource.onQueryUpdated("test")
|
||||||
|
assertThat(pageableMediaDataSource.loadFunction(0,1), `is`(listOf(media)))
|
||||||
|
verify(media).caption = MediaClient.NO_CAPTION
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun expectMediaAndEntity(): Pair<Media, Entities.Entity> {
|
||||||
|
val queryResponse: MwQueryResponse = mock()
|
||||||
|
whenever(mediaClient.getMediaListFromSearch("test", 0, 1))
|
||||||
|
.thenReturn(Single.just(queryResponse))
|
||||||
|
val queryResult: MwQueryResult = mock()
|
||||||
|
whenever(queryResponse.query()).thenReturn(queryResult)
|
||||||
|
val queryPage: MwQueryPage = mock()
|
||||||
|
whenever(queryResult.pages()).thenReturn(listOf(queryPage))
|
||||||
|
val media = mock<Media>()
|
||||||
|
whenever(mediaConverter.convert(queryPage)).thenReturn(media)
|
||||||
|
whenever(media.pageId).thenReturn("1")
|
||||||
|
val entities: Entities = mock()
|
||||||
|
whenever(mediaClient.getEntities("${PAGE_ID_PREFIX}1")).thenReturn(Single.just(entities))
|
||||||
|
val entity: Entities.Entity = mock()
|
||||||
|
whenever(entities.entities()).thenReturn(mapOf("" to entity))
|
||||||
|
return Pair(media, entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue