mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +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
|
||||
* @return Media object
|
||||
*/
|
||||
@Nullable
|
||||
@NonNull
|
||||
public static Media from(MwQueryPage page) {
|
||||
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,6 @@ 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.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.LiveData
|
||||
|
|
@ -24,9 +22,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
|
||||
|
|
@ -52,10 +51,7 @@ abstract class BaseSearchFragment<T> : CommonsDaggerSupportFragment(),
|
|||
override fun observeSearchResults(searchResults: LiveData<PagedList<T>>) {
|
||||
this.searchResults?.removeObservers(viewLifecycleOwner)
|
||||
this.searchResults = searchResults
|
||||
searchResults.observe(viewLifecycleOwner, Observer {
|
||||
pagedListAdapter.submitList(it)
|
||||
contentNotFound.visibility = if (it.loadedCount == 0) VISIBLE else GONE
|
||||
})
|
||||
searchResults.observe(viewLifecycleOwner, Observer(pagedListAdapter::submitList))
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
|
|
@ -69,10 +65,6 @@ abstract class BaseSearchFragment<T> : CommonsDaggerSupportFragment(),
|
|||
injectedPresenter.onDetachView()
|
||||
}
|
||||
|
||||
override fun setEmptyViewText(query: String) {
|
||||
contentNotFound.text = getString(emptyTemplateTextId, query)
|
||||
}
|
||||
|
||||
override fun hideInitialLoadProgress() {
|
||||
paginatedSearchInitialLoadProgress.visibility = View.GONE
|
||||
}
|
||||
|
|
@ -82,12 +74,21 @@ abstract class BaseSearchFragment<T> : CommonsDaggerSupportFragment(),
|
|||
}
|
||||
|
||||
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 = View.VISIBLE
|
||||
}
|
||||
|
||||
override fun hideEmptyText() {
|
||||
contentNotFound.visibility = View.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
|
||||
//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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -207,7 +226,7 @@ public class SearchActivity extends NavigationBaseActivity
|
|||
//FIXME: Temporary fix for screen rotation inside media details. If we don't call onBackPressed then fragment stack is increasing every time.
|
||||
//FIXME: Similar issue like this https://github.com/commons-app/apps-android-commons/issues/894
|
||||
// This is called on screen rotation when user is inside media details. Ideally it should show Media Details but since we are not saving the state now. We are throwing the user to search screen otherwise the app was crashing.
|
||||
//
|
||||
//
|
||||
onBackPressed();
|
||||
}
|
||||
super.onResume();
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package fr.free.nrw.commons.explore.depictions
|
|||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity
|
||||
import fr.free.nrw.commons.explore.BaseSearchFragment
|
||||
import fr.free.nrw.commons.explore.SearchFragmentContract
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -17,7 +16,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,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 org.wikipedia.wikidata.DataValue.EntityId
|
||||
import org.wikipedia.wikidata.Entities
|
||||
import java.util.*
|
||||
|
||||
@Parcelize
|
||||
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) =
|
||||
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.
|
||||
*
|
||||
* @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);
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -207,8 +205,8 @@ public class MediaClient {
|
|||
* @param entityId EntityId (Ex: Q81566) of the depict entity
|
||||
* @return label
|
||||
*/
|
||||
public Single<String> getLabelForDepiction(String entityId, String language) {
|
||||
return mediaDetailInterface.getEntity(entityId, language)
|
||||
public Single<String> getLabelForDepiction(String entityId) {
|
||||
return getEntities(entityId)
|
||||
.map(entities -> {
|
||||
if (isSuccess(entities)) {
|
||||
for (Entity entity : entities.entities().values()) {
|
||||
|
|
@ -218,9 +216,10 @@ public class MediaClient {
|
|||
}
|
||||
}
|
||||
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;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
import org.wikipedia.wikidata.Entities;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
|
|
@ -25,7 +26,7 @@ public interface MediaDetailInterface {
|
|||
* @param language user's locale
|
||||
*/
|
||||
@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
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
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 io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
import java.util.Map;
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
import retrofit2.http.QueryMap;
|
||||
|
|
@ -53,13 +52,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
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
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.schedulers.TestScheduler
|
||||
import org.junit.Before
|
||||
|
|
@ -34,7 +36,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 +44,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 +62,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 +77,7 @@ class BaseSearchPresenterTest {
|
|||
@Test
|
||||
fun `InitialLoad shows initial loader`() {
|
||||
onLoadingState(LoadingState.InitialLoad)
|
||||
verify(view).hideEmptyText()
|
||||
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`() {
|
||||
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 +92,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