mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
parent
e4190f3f7d
commit
c77ed747fe
26 changed files with 386 additions and 419 deletions
|
|
@ -203,7 +203,7 @@ public class Media implements Parcelable {
|
|||
* @param page response from the API
|
||||
* @return Media object
|
||||
*/
|
||||
@Nullable
|
||||
@NonNull
|
||||
public static Media from(final MwQueryPage page) {
|
||||
final ImageInfo imageInfo = page.imageInfo();
|
||||
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.explore.categories.SearchCategoryFragment;
|
||||
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.media.MediaDetailFragment;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
|
|
@ -58,7 +58,7 @@ public abstract class FragmentBuilderModule {
|
|||
abstract SubCategoryListFragment bindSubCategoryListFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract SearchImageFragment bindBrowseImagesListFragment();
|
||||
abstract SearchMediaFragment bindBrowseImagesListFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract SearchCategoryFragment bindSearchCategoryListFragment();
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ import android.content.res.Configuration
|
|||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.View.*
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.LiveData
|
||||
|
|
@ -24,9 +23,10 @@ import kotlinx.android.synthetic.main.fragment_search_paginated.*
|
|||
abstract class BaseSearchFragment<T> : CommonsDaggerSupportFragment(),
|
||||
SearchFragmentContract.View<T> {
|
||||
|
||||
abstract val pagedListAdapter: PagedListAdapter<T,*>
|
||||
abstract val pagedListAdapter: PagedListAdapter<T, *>
|
||||
abstract val injectedPresenter: SearchFragmentContract.Presenter<T>
|
||||
abstract val emptyTemplateTextId: Int
|
||||
abstract val errorTextId: Int
|
||||
private val loadingAdapter by lazy { FooterAdapter { injectedPresenter.retryFailedRequest() } }
|
||||
private val mergeAdapter by lazy { MergeAdapter(pagedListAdapter, loadingAdapter) }
|
||||
private var searchResults: LiveData<PagedList<T>>? = null
|
||||
|
|
@ -53,9 +53,7 @@ abstract class BaseSearchFragment<T> : CommonsDaggerSupportFragment(),
|
|||
this.searchResults?.removeObservers(viewLifecycleOwner)
|
||||
this.searchResults = searchResults
|
||||
searchResults.observe(viewLifecycleOwner, Observer {
|
||||
pagedListAdapter.submitList(it)
|
||||
contentNotFound.visibility = if (it.loadedCount == 0) VISIBLE else GONE
|
||||
})
|
||||
pagedListAdapter.submitList(it) })
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
|
|
@ -69,25 +67,30 @@ abstract class BaseSearchFragment<T> : CommonsDaggerSupportFragment(),
|
|||
injectedPresenter.onDetachView()
|
||||
}
|
||||
|
||||
override fun setEmptyViewText(query: String) {
|
||||
contentNotFound.text = getString(emptyTemplateTextId, query)
|
||||
}
|
||||
|
||||
override fun hideInitialLoadProgress() {
|
||||
paginatedSearchInitialLoadProgress.visibility = View.GONE
|
||||
paginatedSearchInitialLoadProgress.visibility = GONE
|
||||
}
|
||||
|
||||
override fun showInitialLoadInProgress() {
|
||||
paginatedSearchInitialLoadProgress.visibility = View.VISIBLE
|
||||
paginatedSearchInitialLoadProgress.visibility = VISIBLE
|
||||
}
|
||||
|
||||
override fun showSnackbar() {
|
||||
ViewUtil.showShortSnackbar(paginatedSearchResultsList, R.string.error_loading_depictions)
|
||||
ViewUtil.showShortSnackbar(paginatedSearchResultsList, errorTextId)
|
||||
}
|
||||
|
||||
fun onQueryUpdated(query: String) {
|
||||
injectedPresenter.onQueryUpdated(query)
|
||||
}
|
||||
|
||||
override fun showEmptyText(query: String) {
|
||||
contentNotFound.text = getString(emptyTemplateTextId, query)
|
||||
contentNotFound.visibility = VISIBLE
|
||||
}
|
||||
|
||||
override fun hideEmptyText() {
|
||||
contentNotFound.visibility = GONE
|
||||
}
|
||||
}
|
||||
|
||||
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 var view: SearchFragmentContract.View<T> = DUMMY
|
||||
private var currentQuery: String? = null
|
||||
|
||||
|
||||
private val compositeDisposable = CompositeDisposable()
|
||||
override val listFooterData = MutableLiveData<List<FooterItem>>().apply { value = emptyList() }
|
||||
|
|
@ -27,31 +25,30 @@ abstract class BaseSearchPresenter<T>(
|
|||
pageableDataSource.loadingStates
|
||||
.observeOn(mainThreadScheduler)
|
||||
.subscribe(::onLoadingState, Timber::e),
|
||||
pageableDataSource.noItemsLoadedEvent.subscribe {
|
||||
setEmptyViewText()
|
||||
}
|
||||
pageableDataSource.noItemsLoadedQueries.subscribe(view::showEmptyText)
|
||||
)
|
||||
}
|
||||
|
||||
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 -> {
|
||||
listFooterData.postValue(emptyList())
|
||||
view.hideInitialLoadProgress()
|
||||
}
|
||||
LoadingState.InitialLoad -> view.showInitialLoadInProgress()
|
||||
LoadingState.InitialLoad -> {
|
||||
view.hideEmptyText()
|
||||
view.showInitialLoadInProgress()
|
||||
}
|
||||
LoadingState.Error -> {
|
||||
setEmptyViewText()
|
||||
view.showSnackbar()
|
||||
view.hideInitialLoadProgress()
|
||||
listFooterData.postValue(listOf(FooterItem.RefreshItem))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setEmptyViewText() {
|
||||
currentQuery?.let(view::setEmptyViewText)
|
||||
}
|
||||
|
||||
override fun retryFailedRequest() {
|
||||
pageableDataSource.retryFailedRequest()
|
||||
}
|
||||
|
|
@ -62,7 +59,6 @@ abstract class BaseSearchPresenter<T>(
|
|||
}
|
||||
|
||||
override fun onQueryUpdated(query: String) {
|
||||
currentQuery = query
|
||||
pageableDataSource.onQueryUpdated(query)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.text.TextUtils;
|
|||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.SearchView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
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.explore.categories.SearchCategoryFragment;
|
||||
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.media.MediaDetailPagerFragment;
|
||||
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 io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.inject.Inject;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
|
|
@ -46,14 +51,16 @@ public class SearchActivity extends NavigationBaseActivity
|
|||
@BindView(R.id.tab_layout) TabLayout tabLayout;
|
||||
@BindView(R.id.viewPager) ViewPager viewPager;
|
||||
|
||||
private SearchImageFragment searchImageFragment;
|
||||
@Inject
|
||||
RecentSearchesDao recentSearchesDao;
|
||||
|
||||
private SearchMediaFragment searchMediaFragment;
|
||||
private SearchCategoryFragment searchCategoryFragment;
|
||||
private SearchDepictionsFragment searchDepictionsFragment;
|
||||
private RecentSearchesFragment recentSearchesFragment;
|
||||
private FragmentManager supportFragmentManager;
|
||||
private MediaDetailPagerFragment mediaDetails;
|
||||
ViewPagerAdapter viewPagerAdapter;
|
||||
private String query;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
|
@ -92,10 +99,10 @@ public class SearchActivity extends NavigationBaseActivity
|
|||
public void setTabs() {
|
||||
List<Fragment> fragmentList = new ArrayList<>();
|
||||
List<String> titleList = new ArrayList<>();
|
||||
searchImageFragment = new SearchImageFragment();
|
||||
searchMediaFragment = new SearchMediaFragment();
|
||||
searchDepictionsFragment = new SearchDepictionsFragment();
|
||||
searchCategoryFragment= new SearchCategoryFragment();
|
||||
fragmentList.add(searchImageFragment);
|
||||
fragmentList.add(searchMediaFragment);
|
||||
titleList.add(getResources().getString(R.string.search_tab_title_media).toUpperCase());
|
||||
fragmentList.add(searchCategoryFragment);
|
||||
titleList.add(getResources().getString(R.string.search_tab_title_categories).toUpperCase());
|
||||
|
|
@ -109,9 +116,9 @@ public class SearchActivity extends NavigationBaseActivity
|
|||
.debounce(500, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(query -> {
|
||||
this.query = query.toString();
|
||||
//update image list
|
||||
if (!TextUtils.isEmpty(query)) {
|
||||
saveRecentSearch(query.toString());
|
||||
viewPager.setVisibility(View.VISIBLE);
|
||||
tabLayout.setVisibility(View.VISIBLE);
|
||||
searchHistoryContainer.setVisibility(View.GONE);
|
||||
|
|
@ -120,8 +127,8 @@ public class SearchActivity extends NavigationBaseActivity
|
|||
searchDepictionsFragment.onQueryUpdated(query.toString());
|
||||
}
|
||||
|
||||
if (FragmentUtils.isFragmentUIActive(searchImageFragment)) {
|
||||
searchImageFragment.updateImageList(query.toString());
|
||||
if (FragmentUtils.isFragmentUIActive(searchMediaFragment)) {
|
||||
searchMediaFragment.onQueryUpdated(query.toString());
|
||||
}
|
||||
|
||||
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
|
||||
* @param i position of Media in the imagesRecyclerView adapter.
|
||||
*/
|
||||
@Override
|
||||
public Media getMediaAtPosition(int i) {
|
||||
return searchImageFragment.getImageAtPosition(i);
|
||||
return searchMediaFragment.getImageAtPosition(i);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -154,7 +173,7 @@ public class SearchActivity extends NavigationBaseActivity
|
|||
*/
|
||||
@Override
|
||||
public int getTotalMediaCount() {
|
||||
return searchImageFragment.getTotalImagesCount();
|
||||
return searchMediaFragment.getTotalImagesCount();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -250,8 +269,8 @@ public class SearchActivity extends NavigationBaseActivity
|
|||
*/
|
||||
@Override
|
||||
public void requestMoreImages() {
|
||||
if (searchImageFragment!=null){
|
||||
searchImageFragment.addImagesToList(query);
|
||||
if (searchMediaFragment!=null){
|
||||
searchMediaFragment.requestMoreImages();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ abstract class PageableDataSource<T>(private val liveDataConverter: LiveDataConv
|
|||
val loadingStates: Flowable<LoadingState> = _loadingStates
|
||||
private val _searchResults = PublishProcessor.create<LiveData<PagedList<T>>>()
|
||||
val searchResults: Flowable<LiveData<PagedList<T>>> = _searchResults
|
||||
private val _noItemsLoadedEvent = PublishProcessor.create<Unit>()
|
||||
val noItemsLoadedEvent: Flowable<Unit> = _noItemsLoadedEvent
|
||||
private val _noItemsLoadedEvent = PublishProcessor.create<String>()
|
||||
val noItemsLoadedQueries: Flowable<String> = _noItemsLoadedEvent
|
||||
private var currentFactory: SearchDataSourceFactory<T>? = null
|
||||
|
||||
abstract val loadFunction: LoadFunction<T>
|
||||
|
|
@ -34,7 +34,7 @@ abstract class PageableDataSource<T>(private val liveDataConverter: LiveDataConv
|
|||
this.query = query
|
||||
_searchResults.offer(
|
||||
liveDataConverter.convert(dataSourceFactoryFactory().also { currentFactory = it }) {
|
||||
_noItemsLoadedEvent.offer(Unit)
|
||||
_noItemsLoadedEvent.offer(query)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@ interface SearchFragmentContract {
|
|||
interface View<T> {
|
||||
fun showSnackbar()
|
||||
fun observeSearchResults(searchResults: LiveData<PagedList<T>>)
|
||||
fun setEmptyViewText(query: String)
|
||||
fun showInitialLoadInProgress()
|
||||
fun hideInitialLoadProgress()
|
||||
fun showEmptyText(query: String)
|
||||
fun hideEmptyText()
|
||||
}
|
||||
|
||||
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.depictions.SearchDepictionsFragmentContract
|
||||
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)
|
||||
|
|
@ -18,7 +20,12 @@ abstract class SearchModule {
|
|||
|
||||
@Binds
|
||||
abstract fun bindsSearchCategoriesFragmentPresenter(
|
||||
presenter: SearchCategoriesFragmentPresenter?
|
||||
): SearchCategoriesFragmentContract.Presenter?
|
||||
presenter: SearchCategoriesFragmentPresenter
|
||||
): 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 errorTextId: Int = R.string.error_loading_categories
|
||||
|
||||
override val injectedPresenter: SearchFragmentContract.Presenter<String>
|
||||
get() = presenter
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ class SearchDepictionsFragment : BaseSearchFragment<DepictedItem>(),
|
|||
|
||||
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
|
||||
|
||||
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,57 @@
|
|||
package fr.free.nrw.commons.explore.media
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
private val simpleDataObserver = SimpleDataObserver { notifyViewPager() }
|
||||
|
||||
fun requestMoreImages() {
|
||||
// This functionality is replaced by a dataSetObserver and by using loadAround
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
pagedListAdapter.registerAdapterDataObserver(simpleDataObserver)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
pagedListAdapter.unregisterAdapterDataObserver(simpleDataObserver)
|
||||
}
|
||||
|
||||
private fun notifyViewPager() {
|
||||
(activity as SearchActivity).viewPagerNotifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun getImageAtPosition(position: Int): Media? =
|
||||
pagedListAdapter.currentList?.get(position)?.takeIf { it.filename != null }
|
||||
.also { pagedListAdapter.currentList?.loadAround(position) }
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package fr.free.nrw.commons.explore.media
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class SimpleDataObserver(private val onAnyChange: () -> Unit) : RecyclerView.AdapterDataObserver() {
|
||||
override fun onChanged() {
|
||||
super.onChanged()
|
||||
onAnyChange.invoke()
|
||||
}
|
||||
|
||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
||||
super.onItemRangeRemoved(positionStart, itemCount)
|
||||
onAnyChange.invoke()
|
||||
}
|
||||
|
||||
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
|
||||
super.onItemRangeMoved(fromPosition, toPosition, itemCount)
|
||||
onAnyChange.invoke()
|
||||
}
|
||||
|
||||
override fun onStateRestorationPolicyChanged() {
|
||||
super.onStateRestorationPolicyChanged()
|
||||
onAnyChange.invoke()
|
||||
}
|
||||
|
||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
super.onItemRangeInserted(positionStart, itemCount)
|
||||
onAnyChange.invoke()
|
||||
}
|
||||
|
||||
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
|
||||
super.onItemRangeChanged(positionStart, itemCount)
|
||||
onAnyChange.invoke()
|
||||
}
|
||||
|
||||
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
|
||||
super.onItemRangeChanged(positionStart, itemCount, payload)
|
||||
onAnyChange.invoke()
|
||||
}
|
||||
}
|
||||
|
|
@ -124,14 +124,12 @@ public class MediaClient {
|
|||
* It uses the generator query API to get the images searched using a query, 10 at a time.
|
||||
*
|
||||
* @param keyword the search keyword
|
||||
* @param limit
|
||||
* @param offset
|
||||
* @return
|
||||
*/
|
||||
public Single<List<Media>> getMediaListFromSearch(String keyword) {
|
||||
return responseToMediaList(
|
||||
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);
|
||||
public Single<MwQueryResponse> getMediaListFromSearch(String keyword, int limit, int offset) {
|
||||
return mediaInterface.getMediaListFromSearch(keyword, limit, offset);
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -270,9 +268,10 @@ public class MediaClient {
|
|||
}
|
||||
}
|
||||
throw new RuntimeException("failed getEntities");
|
||||
})
|
||||
.singleOrError();
|
||||
});
|
||||
}
|
||||
|
||||
public Single<Entities> getEntities(String entityId) {
|
||||
return mediaDetailInterface.getEntity(entityId);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package fr.free.nrw.commons.media;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
import org.wikipedia.wikidata.Entities;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
|
|
@ -24,7 +25,7 @@ public interface MediaDetailInterface {
|
|||
*
|
||||
*/
|
||||
@GET("/w/api.php?format=json&action=wbgetentities&props=labels&languagefallback=1")
|
||||
Observable<Entities> getEntity(@Query("ids") String entityId);
|
||||
Single<Entities> getEntity(@Query("ids") String entityId);
|
||||
|
||||
/**
|
||||
* Fetches caption using wikibaseIdentifier
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package fr.free.nrw.commons.media;
|
|||
|
||||
import fr.free.nrw.commons.depictions.models.DepictionResponse;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
import java.util.Map;
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||
import retrofit2.http.GET;
|
||||
|
|
@ -65,13 +66,13 @@ public interface MediaInterface {
|
|||
*
|
||||
* @param keyword the searched keyword
|
||||
* @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
|
||||
*/
|
||||
@GET("w/api.php?action=query&format=json&formatversion=2" + //Basic parameters
|
||||
"&generator=search&gsrwhat=text&gsrnamespace=6" + //Search parameters
|
||||
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
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class BaseSearchPresenterTest {
|
|||
private var searchResults: PublishProcessor<LiveData<PagedList<String>>> =
|
||||
PublishProcessor.create()
|
||||
|
||||
private var noItemLoadedEvent: PublishProcessor<Unit> = PublishProcessor.create()
|
||||
private var noItemLoadedQueries: PublishProcessor<String> = PublishProcessor.create()
|
||||
|
||||
@Before
|
||||
@Throws(Exception::class)
|
||||
|
|
@ -42,8 +42,8 @@ class BaseSearchPresenterTest {
|
|||
MockitoAnnotations.initMocks(this)
|
||||
whenever(pageableDataSource.searchResults).thenReturn(searchResults)
|
||||
whenever(pageableDataSource.loadingStates).thenReturn(loadingStates)
|
||||
whenever(pageableDataSource.noItemsLoadedEvent)
|
||||
.thenReturn(noItemLoadedEvent)
|
||||
whenever(pageableDataSource.noItemsLoadedQueries)
|
||||
.thenReturn(noItemLoadedQueries)
|
||||
testScheduler = TestScheduler()
|
||||
baseSearchPresenter =
|
||||
object : BaseSearchPresenter<String>(testScheduler, pageableDataSource) {}
|
||||
|
|
@ -60,6 +60,7 @@ class BaseSearchPresenterTest {
|
|||
@Test
|
||||
fun `Loading offers a loading list item`() {
|
||||
onLoadingState(LoadingState.Loading)
|
||||
verify(view).hideEmptyText()
|
||||
baseSearchPresenter.listFooterData.test().assertValue(listOf(FooterItem.LoadingItem))
|
||||
}
|
||||
|
||||
|
|
@ -74,6 +75,7 @@ class BaseSearchPresenterTest {
|
|||
@Test
|
||||
fun `InitialLoad shows initial loader`() {
|
||||
onLoadingState(LoadingState.InitialLoad)
|
||||
verify(view).hideEmptyText()
|
||||
verify(view).showInitialLoadInProgress()
|
||||
}
|
||||
|
||||
|
|
@ -81,16 +83,6 @@ class BaseSearchPresenterTest {
|
|||
fun `Error offers a refresh list item, hides initial loader and shows error with a set text`() {
|
||||
baseSearchPresenter.onQueryUpdated("test")
|
||||
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).hideInitialLoadProgress()
|
||||
baseSearchPresenter.listFooterData.test().assertValue(listOf(FooterItem.RefreshItem))
|
||||
|
|
@ -98,9 +90,8 @@ class BaseSearchPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `no Items event sets empty view text`() {
|
||||
baseSearchPresenter.onQueryUpdated("test")
|
||||
noItemLoadedEvent.offer(Unit)
|
||||
verify(view).setEmptyViewText("test")
|
||||
noItemLoadedQueries.offer("test")
|
||||
verify(view).showEmptyText("test")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -39,9 +39,9 @@ class PageableDataSourceTest {
|
|||
fun `onQueryUpdated invokes livedatconverter with no items emitter`() {
|
||||
val (zeroItemsFuncCaptor, _) = expectNewLiveData()
|
||||
pageableDataSource.onQueryUpdated("test")
|
||||
pageableDataSource.noItemsLoadedEvent.test()
|
||||
pageableDataSource.noItemsLoadedQueries.test()
|
||||
.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