#3822 Convert SubCategoryImagesListFragment to use Pagination (#3824)

This commit is contained in:
Seán Mac Gillicuddy 2020-06-25 13:40:02 +01:00 committed by GitHub
parent 7817518462
commit 9d59915459
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 444 additions and 477 deletions

View file

@ -141,5 +141,7 @@ public class BookmarksActivity extends NavigationBaseActivity
} }
@Override @Override
public void requestMoreImages() { } public void onMediaClicked(int position) {
//TODO use with pagination
}
} }

View file

@ -93,6 +93,7 @@ class CategoriesModel @Inject constructor(
else else
categoryClient.searchCategoriesForPrefix(term.toLowerCase(), SEARCH_CATS_LIMIT) categoryClient.searchCategoriesForPrefix(term.toLowerCase(), SEARCH_CATS_LIMIT)
.map { it.sortedWith(StringSortingUtils.sortBySimilarity(term)) } .map { it.sortedWith(StringSortingUtils.sortBySimilarity(term)) }
.toObservable()
} }
private fun categoriesFromDepiction(selectedDepictions: List<DepictedItem>) = private fun categoriesFromDepiction(selectedDepictions: List<DepictedItem>) =
@ -126,7 +127,7 @@ class CategoriesModel @Inject constructor(
* @return * @return
*/ */
private fun getTitleCategories(title: String): Observable<List<String>> { private fun getTitleCategories(title: String): Observable<List<String>> {
return categoryClient.searchCategories(title.toLowerCase(), SEARCH_CATS_LIMIT) return categoryClient.searchCategories(title.toLowerCase(), SEARCH_CATS_LIMIT).toObservable()
} }

View file

@ -1,16 +1,21 @@
package fr.free.nrw.commons.category package fr.free.nrw.commons.category
import io.reactivex.Observable import io.reactivex.Single
import org.wikipedia.dataclient.mwapi.MwQueryResponse import org.wikipedia.dataclient.mwapi.MwQueryResponse
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
const val CATEGORY_PREFIX = "Category:" const val CATEGORY_PREFIX = "Category:"
const val SUB_CATEGORY_CONTINUATION_PREFIX = "sub_category_"
const val PARENT_CATEGORY_CONTINUATION_PREFIX = "parent_category_"
/** /**
* Category Client to handle custom calls to Commons MediaWiki APIs * Category Client to handle custom calls to Commons MediaWiki APIs
*/ */
@Singleton @Singleton
class CategoryClient @Inject constructor(private val categoryInterface: CategoryInterface) { class CategoryClient @Inject constructor(private val categoryInterface: CategoryInterface) :
ContinuationClient<MwQueryResponse, String>() {
/** /**
* Searches for categories containing the specified string. * Searches for categories containing the specified string.
* *
@ -21,8 +26,8 @@ class CategoryClient @Inject constructor(private val categoryInterface: Category
*/ */
@JvmOverloads @JvmOverloads
fun searchCategories(filter: String?, itemLimit: Int, offset: Int = 0): fun searchCategories(filter: String?, itemLimit: Int, offset: Int = 0):
Observable<List<String>> { Single<List<String>> {
return responseToCategoryName(categoryInterface.searchCategories(filter, itemLimit, offset)) return responseMapper(categoryInterface.searchCategories(filter, itemLimit, offset))
} }
/** /**
@ -35,8 +40,8 @@ class CategoryClient @Inject constructor(private val categoryInterface: Category
*/ */
@JvmOverloads @JvmOverloads
fun searchCategoriesForPrefix(prefix: String?, itemLimit: Int, offset: Int = 0): fun searchCategoriesForPrefix(prefix: String?, itemLimit: Int, offset: Int = 0):
Observable<List<String>> { Single<List<String>> {
return responseToCategoryName( return responseMapper(
categoryInterface.searchCategoriesForPrefix(prefix, itemLimit, offset) categoryInterface.searchCategoriesForPrefix(prefix, itemLimit, offset)
) )
} }
@ -48,8 +53,12 @@ class CategoryClient @Inject constructor(private val categoryInterface: Category
* @param categoryName Category name as defined on commons * @param categoryName Category name as defined on commons
* @return Observable emitting the categories returned. If our search yielded "Category:Test", "Test" is emitted. * @return Observable emitting the categories returned. If our search yielded "Category:Test", "Test" is emitted.
*/ */
fun getSubCategoryList(categoryName: String?): Observable<List<String>> { fun getSubCategoryList(categoryName: String): Single<List<String>> {
return responseToCategoryName(categoryInterface.getSubCategoryList(categoryName)) return continuationRequest(SUB_CATEGORY_CONTINUATION_PREFIX, categoryName) {
categoryInterface.getSubCategoryList(
categoryName, it
)
}
} }
/** /**
@ -59,19 +68,29 @@ class CategoryClient @Inject constructor(private val categoryInterface: Category
* @param categoryName Category name as defined on commons * @param categoryName Category name as defined on commons
* @return * @return
*/ */
fun getParentCategoryList(categoryName: String?): Observable<List<String>> { fun getParentCategoryList(categoryName: String): Single<List<String>> {
return responseToCategoryName(categoryInterface.getParentCategoryList(categoryName)) return continuationRequest(PARENT_CATEGORY_CONTINUATION_PREFIX, categoryName) {
categoryInterface.getParentCategoryList(categoryName, it)
}
} }
/** fun resetSubCategoryContinuation(category: String) {
* Internal function to reduce code reuse. Extracts the categories returned from MwQueryResponse. resetContinuation(SUB_CATEGORY_CONTINUATION_PREFIX, category)
* }
* @param responseObservable The query response observable
* @return Observable emitting the categories returned. If our search yielded "Category:Test", "Test" is emitted. fun resetParentCategoryContinuation(category: String) {
*/ resetContinuation(PARENT_CATEGORY_CONTINUATION_PREFIX, category)
private fun responseToCategoryName(responseObservable: Observable<MwQueryResponse>): Observable<List<String>> { }
return responseObservable
.map { it.query()?.pages() ?: emptyList() } override fun responseMapper(
networkResult: Single<MwQueryResponse>,
key: String?
): Single<List<String>> {
return networkResult
.map {
handleContinuationResponse(it.continuation(), key)
it.query()?.pages() ?: emptyList()
}
.map { .map {
it.map { page -> page.title().replace(CATEGORY_PREFIX, "") } it.map { page -> page.title().replace(CATEGORY_PREFIX, "") }
} }

View file

@ -20,6 +20,8 @@ import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.explore.ViewPagerAdapter; import fr.free.nrw.commons.explore.ViewPagerAdapter;
import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment; import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment;
import fr.free.nrw.commons.explore.categories.parent.ParentCategoriesFragment;
import fr.free.nrw.commons.explore.categories.sub.SubCategoriesFragment;
import fr.free.nrw.commons.media.MediaDetailPagerFragment; import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.theme.NavigationBaseActivity; import fr.free.nrw.commons.theme.NavigationBaseActivity;
import java.util.ArrayList; import java.util.ArrayList;
@ -69,25 +71,21 @@ public class CategoryDetailsActivity extends NavigationBaseActivity
List<Fragment> fragmentList = new ArrayList<>(); List<Fragment> fragmentList = new ArrayList<>();
List<String> titleList = new ArrayList<>(); List<String> titleList = new ArrayList<>();
categoriesMediaFragment = new CategoriesMediaFragment(); categoriesMediaFragment = new CategoriesMediaFragment();
SubCategoryListFragment subCategoryListFragment = new SubCategoryListFragment(); SubCategoriesFragment subCategoryListFragment = new SubCategoriesFragment();
SubCategoryListFragment parentCategoryListFragment = new SubCategoryListFragment(); ParentCategoriesFragment parentCategoriesFragment = new ParentCategoriesFragment();
categoryName = getIntent().getStringExtra("categoryName"); categoryName = getIntent().getStringExtra("categoryName");
if (getIntent() != null && categoryName != null) { if (getIntent() != null && categoryName != null) {
Bundle arguments = new Bundle(); Bundle arguments = new Bundle();
arguments.putString("categoryName", categoryName); arguments.putString("categoryName", categoryName);
arguments.putBoolean("isParentCategory", false);
categoriesMediaFragment.setArguments(arguments); categoriesMediaFragment.setArguments(arguments);
subCategoryListFragment.setArguments(arguments); subCategoryListFragment.setArguments(arguments);
Bundle parentCategoryArguments = new Bundle(); parentCategoriesFragment.setArguments(arguments);
parentCategoryArguments.putString("categoryName", categoryName);
parentCategoryArguments.putBoolean("isParentCategory", true);
parentCategoryListFragment.setArguments(parentCategoryArguments);
} }
fragmentList.add(categoriesMediaFragment); fragmentList.add(categoriesMediaFragment);
titleList.add("MEDIA"); titleList.add("MEDIA");
fragmentList.add(subCategoryListFragment); fragmentList.add(subCategoryListFragment);
titleList.add("SUBCATEGORIES"); titleList.add("SUBCATEGORIES");
fragmentList.add(parentCategoryListFragment); fragmentList.add(parentCategoriesFragment);
titleList.add("PARENT CATEGORIES"); titleList.add("PARENT CATEGORIES");
viewPagerAdapter.setTabData(fragmentList, titleList); viewPagerAdapter.setTabData(fragmentList, titleList);
viewPagerAdapter.notifyDataSetChanged(); viewPagerAdapter.notifyDataSetChanged();
@ -133,7 +131,6 @@ public class CategoryDetailsActivity extends NavigationBaseActivity
*/ */
public static void startYourself(Context context, String categoryName) { public static void startYourself(Context context, String categoryName) {
Intent intent = new Intent(context, CategoryDetailsActivity.class); Intent intent = new Intent(context, CategoryDetailsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra("categoryName", categoryName); intent.putExtra("categoryName", categoryName);
context.startActivity(intent); context.startActivity(intent);
} }
@ -212,12 +209,4 @@ public class CategoryDetailsActivity extends NavigationBaseActivity
} }
} }
/**
* This method is called when viewPager has reached its end.
* Fetches more images using search query and adds it to the grid view and viewpager adapter
*/
@Override
public void requestMoreImages() {
//unneeded
}
} }

View file

@ -187,12 +187,8 @@ public class CategoryImagesActivity
} }
} }
/**
* This method is called when viewPager has reached its end.
* Fetches more images using search query and adds it to the gridView and viewpager adapter
*/
@Override @Override
public void requestMoreImages() { public void onMediaClicked(int position) {
//unneeded // this class is unused and will be deleted
} }
} }

View file

@ -7,7 +7,7 @@ package fr.free.nrw.commons.category;
public interface CategoryImagesCallback { public interface CategoryImagesCallback {
void viewPagerNotifyDataSetChanged(); void viewPagerNotifyDataSetChanged();
void requestMoreImages(); void onMediaClicked(int position);
} }

View file

@ -1,10 +1,11 @@
package fr.free.nrw.commons.category; package fr.free.nrw.commons.category;
import io.reactivex.Single;
import java.util.Map;
import org.wikipedia.dataclient.mwapi.MwQueryResponse; import org.wikipedia.dataclient.mwapi.MwQueryResponse;
import io.reactivex.Observable;
import retrofit2.http.GET; import retrofit2.http.GET;
import retrofit2.http.Query; import retrofit2.http.Query;
import retrofit2.http.QueryMap;
/** /**
* Interface for interacting with Commons category related APIs * Interface for interacting with Commons category related APIs
@ -20,7 +21,7 @@ public interface CategoryInterface {
*/ */
@GET("w/api.php?action=query&format=json&formatversion=2" @GET("w/api.php?action=query&format=json&formatversion=2"
+ "&generator=search&gsrnamespace=14") + "&generator=search&gsrnamespace=14")
Observable<MwQueryResponse> searchCategories(@Query("gsrsearch") String filter, Single<MwQueryResponse> searchCategories(@Query("gsrsearch") String filter,
@Query("gsrlimit") int itemLimit, @Query("gsroffset") int offset); @Query("gsrlimit") int itemLimit, @Query("gsroffset") int offset);
/** /**
@ -32,16 +33,18 @@ public interface CategoryInterface {
*/ */
@GET("w/api.php?action=query&format=json&formatversion=2" @GET("w/api.php?action=query&format=json&formatversion=2"
+ "&generator=allcategories") + "&generator=allcategories")
Observable<MwQueryResponse> searchCategoriesForPrefix(@Query("gacprefix") String prefix, Single<MwQueryResponse> searchCategoriesForPrefix(@Query("gacprefix") String prefix,
@Query("gaclimit") int itemLimit, @Query("gacoffset") int offset); @Query("gaclimit") int itemLimit, @Query("gacoffset") int offset);
@GET("w/api.php?action=query&format=json&formatversion=2" @GET("w/api.php?action=query&format=json&formatversion=2"
+ "&generator=categorymembers&gcmtype=subcat" + "&generator=categorymembers&gcmtype=subcat"
+ "&prop=info&gcmlimit=500") + "&prop=info&gcmlimit=50")
Observable<MwQueryResponse> getSubCategoryList(@Query("gcmtitle") String categoryName); Single<MwQueryResponse> getSubCategoryList(@Query("gcmtitle") String categoryName,
@QueryMap(encoded = true) Map<String, String> continuation);
@GET("w/api.php?action=query&format=json&formatversion=2" @GET("w/api.php?action=query&format=json&formatversion=2"
+ "&generator=categories&prop=info&gcllimit=500") + "&generator=categories&prop=info&gcllimit=50")
Observable<MwQueryResponse> getParentCategoryList(@Query("titles") String categoryName); Single<MwQueryResponse> getParentCategoryList(@Query("titles") String categoryName,
@QueryMap(encoded = true) Map<String, String> continuation);
} }

View file

@ -0,0 +1,41 @@
package fr.free.nrw.commons.category
import io.reactivex.Single
abstract class ContinuationClient<Network, Domain> {
private val continuationStore: MutableMap<String, Map<String, String>?> = mutableMapOf()
private val continuationExists: MutableMap<String, Boolean> = mutableMapOf()
private fun hasMorePagesFor(key: String) = continuationExists[key] ?: true
fun continuationRequest(
prefix: String,
name: String,
requestFunction: (Map<String, String>) -> Single<Network>
): Single<List<Domain>> {
val key = "$prefix$name"
return if (hasMorePagesFor(key)) {
responseMapper(requestFunction(continuationStore[key] ?: emptyMap()), key)
} else {
Single.just(emptyList())
}
}
abstract fun responseMapper(networkResult: Single<Network>, key: String?=null): Single<List<Domain>>
fun handleContinuationResponse(continuation:Map<String,String>?, key:String?){
if (key != null) {
continuationExists[key] =
continuation?.let { continuation ->
continuationStore[key] = continuation
true
} ?: false
}
}
protected fun resetContinuation(prefix: String, category: String) {
continuationExists.remove("$prefix$category")
continuationStore.remove("$prefix$category")
}
}

View file

@ -1,154 +0,0 @@
package fr.free.nrw.commons.category;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_PREFIX;
import android.content.Intent;
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.R;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.explore.categories.search.SearchCategoriesAdapter;
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.List;
import javax.inject.Inject;
import kotlin.Unit;
import timber.log.Timber;
/**
* Displays the category search screen.
*/
public class SubCategoryListFragment extends CommonsDaggerSupportFragment {
@BindView(R.id.imagesListBox)
RecyclerView categoriesRecyclerView;
@BindView(R.id.imageSearchInProgress)
ProgressBar progressBar;
@BindView(R.id.imagesNotFound)
TextView categoriesNotFoundView;
private String categoryName = null;
@Inject CategoryClient categoryClient;
private SearchCategoriesAdapter categoriesAdapter;
private boolean isParentCategory = true;
@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);
categoryName = getArguments().getString("categoryName");
isParentCategory = getArguments().getBoolean("isParentCategory");
initSubCategoryList();
if (getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
categoriesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
}
else{
categoriesRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
}
categoriesAdapter = new SearchCategoriesAdapter(item->{
Intent intent = new Intent(getContext(), CategoryDetailsActivity.class);
intent.putExtra("categoryName", item);
getContext().startActivity(intent);
return Unit.INSTANCE;
});
categoriesRecyclerView.setAdapter(categoriesAdapter);
return rootView;
}
/**
* Checks for internet connection and then initializes the recycler view with all(max 500) categories of the searched query
* Clearing categoryAdapter every time new keyword is searched so that user can see only new results
*/
public void initSubCategoryList() {
categoriesNotFoundView.setVisibility(GONE);
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
handleNoInternet();
return;
}
progressBar.setVisibility(View.VISIBLE);
if (isParentCategory) {
compositeDisposable.add(categoryClient.getParentCategoryList(
CATEGORY_PREFIX +categoryName)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::handleSuccess, this::handleError));
} else {
compositeDisposable.add(categoryClient.getSubCategoryList(
CATEGORY_PREFIX +categoryName)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::handleSuccess, this::handleError));
}
}
/**
* Handles the success scenario
* it initializes the recycler view by adding items to the adapter
* @param subCategoryList
*/
private void handleSuccess(List<String> subCategoryList) {
if (subCategoryList == null || subCategoryList.isEmpty()) {
initEmptyView();
}
else {
progressBar.setVisibility(View.GONE);
categoriesAdapter.addAll(subCategoryList);
categoriesAdapter.notifyDataSetChanged();
}
}
/**
* Logs and handles API error scenario
* @param throwable
*/
private void handleError(Throwable throwable) {
if (!isParentCategory){
Timber.e(throwable, "Error occurred while loading queried subcategories");
ViewUtil.showShortSnackbar(categoriesRecyclerView,R.string.error_loading_categories);
}else {
Timber.e(throwable, "Error occurred while loading queried parentcategories");
ViewUtil.showShortSnackbar(categoriesRecyclerView,R.string.error_loading_categories);
}
}
/**
* Handles the UI updates for a empty results scenario
*/
private void initEmptyView() {
progressBar.setVisibility(GONE);
categoriesNotFoundView.setVisibility(VISIBLE);
if (!isParentCategory){
categoriesNotFoundView.setText(getString(R.string.no_subcategory_found));
}else {
categoriesNotFoundView.setText(getString(R.string.no_parentcategory_found));
}
}
/**
* Handles the UI updates for no internet scenario
*/
private void handleNoInternet() {
progressBar.setVisibility(GONE);
ViewUtil.showShortSnackbar(categoriesRecyclerView, R.string.no_internet);
}
}

View file

@ -51,9 +51,6 @@ class ContributionBoundaryCallback @Inject constructor(
* Fetches contributions using the MediaWiki API * Fetches contributions using the MediaWiki API
*/ */
fun fetchContributions() { fun fetchContributions() {
if (mediaClient.doesMediaListForUserHaveMorePages(sessionManager.userName!!).not()) {
return
}
compositeDisposable.add( compositeDisposable.add(
mediaClient.getMediaListForUser(sessionManager.userName!!) mediaClient.getMediaListForUser(sessionManager.userName!!)
.map { mediaList: List<Media?> -> .map { mediaList: List<Media?> ->

View file

@ -4,14 +4,15 @@ import dagger.Module;
import dagger.android.ContributesAndroidInjector; import dagger.android.ContributesAndroidInjector;
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment; import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment; import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment;
import fr.free.nrw.commons.category.SubCategoryListFragment;
import fr.free.nrw.commons.contributions.ContributionsFragment; import fr.free.nrw.commons.contributions.ContributionsFragment;
import fr.free.nrw.commons.contributions.ContributionsListFragment; import fr.free.nrw.commons.contributions.ContributionsListFragment;
import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment; import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment;
import fr.free.nrw.commons.explore.categories.parent.ParentCategoriesFragment;
import fr.free.nrw.commons.explore.categories.search.SearchCategoryFragment;
import fr.free.nrw.commons.explore.categories.sub.SubCategoriesFragment;
import fr.free.nrw.commons.explore.depictions.child.ChildDepictionsFragment; import fr.free.nrw.commons.explore.depictions.child.ChildDepictionsFragment;
import fr.free.nrw.commons.explore.depictions.media.DepictedImagesFragment; import fr.free.nrw.commons.explore.depictions.media.DepictedImagesFragment;
import fr.free.nrw.commons.explore.depictions.parent.ParentDepictionsFragment; import fr.free.nrw.commons.explore.depictions.parent.ParentDepictionsFragment;
import fr.free.nrw.commons.explore.categories.search.SearchCategoryFragment;
import fr.free.nrw.commons.explore.depictions.search.SearchDepictionsFragment; import fr.free.nrw.commons.explore.depictions.search.SearchDepictionsFragment;
import fr.free.nrw.commons.explore.media.SearchMediaFragment; import fr.free.nrw.commons.explore.media.SearchMediaFragment;
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment; import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment;
@ -49,9 +50,6 @@ public abstract class FragmentBuilderModule {
@ContributesAndroidInjector @ContributesAndroidInjector
abstract DepictedImagesFragment bindDepictedImagesFragment(); abstract DepictedImagesFragment bindDepictedImagesFragment();
@ContributesAndroidInjector
abstract SubCategoryListFragment bindSubCategoryListFragment();
@ContributesAndroidInjector @ContributesAndroidInjector
abstract SearchMediaFragment bindBrowseImagesListFragment(); abstract SearchMediaFragment bindBrowseImagesListFragment();
@ -99,4 +97,10 @@ public abstract class FragmentBuilderModule {
@ContributesAndroidInjector @ContributesAndroidInjector
abstract CategoriesMediaFragment bindCategoriesMediaFragment(); abstract CategoriesMediaFragment bindCategoriesMediaFragment();
@ContributesAndroidInjector
abstract SubCategoriesFragment bindSubCategoriesFragment();
@ContributesAndroidInjector
abstract ParentCategoriesFragment bindParentCategoriesFragment();
} }

View file

@ -7,7 +7,6 @@ import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.AdapterView;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
@ -31,8 +30,7 @@ import java.util.List;
public class ExploreActivity public class ExploreActivity
extends NavigationBaseActivity extends NavigationBaseActivity
implements MediaDetailPagerFragment.MediaDetailProvider, implements MediaDetailPagerFragment.MediaDetailProvider, CategoryImagesCallback {
AdapterView.OnItemClickListener, CategoryImagesCallback {
private static final String FEATURED_IMAGES_CATEGORY = "Featured_pictures_on_Wikimedia_Commons"; private static final String FEATURED_IMAGES_CATEGORY = "Featured_pictures_on_Wikimedia_Commons";
private static final String MOBILE_UPLOADS_CATEGORY = "Uploaded_with_Mobile/Android"; private static final String MOBILE_UPLOADS_CATEGORY = "Uploaded_with_Mobile/Android";
@ -163,21 +161,11 @@ public class ExploreActivity
super.onBackPressed(); super.onBackPressed();
} }
/**
* This method is called when viewPager has reached its end.
* Fetches more images and adds them to the recycler view and viewpager adapter
*/
@Override
public void requestMoreImages() {
//unneeded
}
/** /**
* This method is called onClick of media inside category featured images or mobile uploads. * This method is called onClick of media inside category featured images or mobile uploads.
*/ */
@Override @Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { public void onMediaClicked( int position) {
tabLayout.setVisibility(View.GONE); tabLayout.setVisibility(View.GONE);
viewPager.setVisibility(View.GONE); viewPager.setVisibility(View.GONE);
mediaContainer.setVisibility(View.VISIBLE); mediaContainer.setVisibility(View.VISIBLE);
@ -195,7 +183,7 @@ public class ExploreActivity
// coming back to the explore activity. See https://github.com/commons-app/apps-android-commons/issues/1631 // coming back to the explore activity. See https://github.com/commons-app/apps-android-commons/issues/1631
// https://stackoverflow.com/questions/11353075/how-can-i-maintain-fragment-state-when-added-to-the-back-stack/19022550#19022550 supportFragmentManager.executePendingTransactions(); // https://stackoverflow.com/questions/11353075/how-can-i-maintain-fragment-state-when-added-to-the-back-stack/19022550#19022550 supportFragmentManager.executePendingTransactions();
} }
mediaDetails.showImage(i); mediaDetails.showImage(position);
forceInitBackButton(); forceInitBackButton();
} }

View file

@ -191,7 +191,8 @@ public class SearchActivity extends NavigationBaseActivity
* Open media detail pager fragment on click of image in search results * Open media detail pager fragment on click of image in search results
* @param index item index that should be opened * @param index item index that should be opened
*/ */
public void onSearchImageClicked(int index) { @Override
public void onMediaClicked(int index) {
ViewUtil.hideKeyboard(this.findViewById(R.id.searchBox)); ViewUtil.hideKeyboard(this.findViewById(R.id.searchBox));
toolbar.setVisibility(View.GONE); toolbar.setVisibility(View.GONE);
tabLayout.setVisibility(View.GONE); tabLayout.setVisibility(View.GONE);
@ -263,15 +264,6 @@ public class SearchActivity extends NavigationBaseActivity
viewPager.requestFocus(); viewPager.requestFocus();
} }
/**
* This method is called when viewPager has reached its end.
* Fetches more images using search query and adds it to the recycler view and viewpager adapter
*/
@Override
public void requestMoreImages() {
//unneeded
}
@Override protected void onDestroy() { @Override protected void onDestroy() {
super.onDestroy(); super.onDestroy();
//Dispose the disposables when the activity is destroyed //Dispose the disposables when the activity is destroyed

View file

@ -4,12 +4,24 @@ import dagger.Binds
import dagger.Module import dagger.Module
import fr.free.nrw.commons.explore.categories.media.CategoryMediaPresenter import fr.free.nrw.commons.explore.categories.media.CategoryMediaPresenter
import fr.free.nrw.commons.explore.categories.media.CategoryMediaPresenterImpl import fr.free.nrw.commons.explore.categories.media.CategoryMediaPresenterImpl
import fr.free.nrw.commons.explore.categories.parent.ParentCategoriesPresenter
import fr.free.nrw.commons.explore.categories.parent.ParentCategoriesPresenterImpl
import fr.free.nrw.commons.explore.categories.sub.SubCategoriesPresenter
import fr.free.nrw.commons.explore.categories.sub.SubCategoriesPresenterImpl
@Module @Module
abstract class CategoriesModule { abstract class CategoriesModule {
@Binds @Binds
abstract fun CategoryMediaPresenterImpl.bindsParentDepictionPresenter() abstract fun CategoryMediaPresenterImpl.bindsCategoryMediaPresenter()
: CategoryMediaPresenter : CategoryMediaPresenter
@Binds
abstract fun SubCategoriesPresenterImpl.bindsSubCategoriesPresenter()
: SubCategoriesPresenter
@Binds
abstract fun ParentCategoriesPresenterImpl.bindsParentCategoriesPresenter()
: ParentCategoriesPresenter
} }

View file

@ -0,0 +1,15 @@
package fr.free.nrw.commons.explore.categories
import fr.free.nrw.commons.R
import fr.free.nrw.commons.category.CategoryDetailsActivity
import fr.free.nrw.commons.explore.paging.BasePagingFragment
abstract class PageableCategoryFragment : BasePagingFragment<String>() {
override val errorTextId: Int = R.string.error_loading_categories
override val pagedListAdapter by lazy {
PagedSearchCategoriesAdapter {
CategoryDetailsActivity.startYourself(context, it)
}
}
}

View file

@ -1,4 +1,4 @@
package fr.free.nrw.commons.explore.categories.search package fr.free.nrw.commons.explore.categories
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -41,5 +41,3 @@ class CategoryItemViewHolder(containerView: View, val onCategoryClicked: (String
textView1.text = item.substringAfter(CATEGORY_PREFIX) textView1.text = item.substringAfter(CATEGORY_PREFIX)
} }
} }

View file

@ -3,8 +3,6 @@ package fr.free.nrw.commons.explore.categories.media
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import fr.free.nrw.commons.category.CATEGORY_PREFIX import fr.free.nrw.commons.category.CATEGORY_PREFIX
import fr.free.nrw.commons.category.CategoryDetailsActivity
import fr.free.nrw.commons.category.CategoryImagesCallback
import fr.free.nrw.commons.explore.media.PageableMediaFragment import fr.free.nrw.commons.explore.media.PageableMediaFragment
import javax.inject.Inject import javax.inject.Inject
@ -20,12 +18,4 @@ class CategoriesMediaFragment : PageableMediaFragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
onQueryUpdated("$CATEGORY_PREFIX${arguments!!.getString("categoryName")!!}") onQueryUpdated("$CATEGORY_PREFIX${arguments!!.getString("categoryName")!!}")
} }
override fun onItemClicked(position: Int) {
(activity as CategoryDetailsActivity).onMediaClicked(position)
}
override fun notifyViewPager() {
(activity as CategoryImagesCallback).viewPagerNotifyDataSetChanged()
}
} }

View file

@ -0,0 +1,19 @@
package fr.free.nrw.commons.explore.categories.parent
import fr.free.nrw.commons.category.CategoryClient
import fr.free.nrw.commons.explore.paging.LiveDataConverter
import fr.free.nrw.commons.explore.paging.PageableBaseDataSource
import javax.inject.Inject
class PageableParentCategoriesDataSource @Inject constructor(
liveDataConverter: LiveDataConverter,
val categoryClient: CategoryClient
) : PageableBaseDataSource<String>(liveDataConverter) {
override val loadFunction = { loadSize: Int, startPosition: Int ->
if (startPosition == 0) {
categoryClient.resetParentCategoryContinuation(query)
}
categoryClient.getParentCategoryList(query).blockingGet()
}
}

View file

@ -0,0 +1,26 @@
package fr.free.nrw.commons.explore.categories.parent
import android.os.Bundle
import android.view.View
import fr.free.nrw.commons.R
import fr.free.nrw.commons.category.CATEGORY_PREFIX
import fr.free.nrw.commons.explore.categories.PageableCategoryFragment
import javax.inject.Inject
class ParentCategoriesFragment : PageableCategoryFragment() {
@Inject
lateinit var presenter: ParentCategoriesPresenter
override val injectedPresenter
get() = presenter
override fun getEmptyText(query: String) = getString(R.string.no_parentcategory_found)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
onQueryUpdated("$CATEGORY_PREFIX${arguments!!.getString("categoryName")!!}")
}
}

View file

@ -0,0 +1,17 @@
package fr.free.nrw.commons.explore.categories.parent
import fr.free.nrw.commons.di.CommonsApplicationModule
import fr.free.nrw.commons.explore.paging.BasePagingPresenter
import fr.free.nrw.commons.explore.paging.PagingContract
import io.reactivex.Scheduler
import javax.inject.Inject
import javax.inject.Named
interface ParentCategoriesPresenter : PagingContract.Presenter<String>
class ParentCategoriesPresenterImpl @Inject constructor(
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
dataSourceFactory: PageableParentCategoriesDataSource
) : BasePagingPresenter<String>(mainThreadScheduler, dataSourceFactory),
ParentCategoriesPresenter

View file

@ -5,12 +5,12 @@ import fr.free.nrw.commons.explore.paging.LiveDataConverter
import fr.free.nrw.commons.explore.paging.PageableBaseDataSource import fr.free.nrw.commons.explore.paging.PageableBaseDataSource
import javax.inject.Inject import javax.inject.Inject
class PageableCategoriesDataSource @Inject constructor( class PageableSearchCategoriesDataSource @Inject constructor(
liveDataConverter: LiveDataConverter, liveDataConverter: LiveDataConverter,
val categoryClient: CategoryClient val categoryClient: CategoryClient
) : PageableBaseDataSource<String>(liveDataConverter) { ) : PageableBaseDataSource<String>(liveDataConverter) {
override val loadFunction = { loadSize: Int, startPosition: Int -> override val loadFunction = { loadSize: Int, startPosition: Int ->
categoryClient.searchCategories(query, loadSize, startPosition).blockingFirst() categoryClient.searchCategories(query, loadSize, startPosition).blockingGet()
} }
} }

View file

@ -1,11 +0,0 @@
package fr.free.nrw.commons.explore.categories.search
import fr.free.nrw.commons.upload.categories.BaseDelegateAdapter
class SearchCategoriesAdapter(onCateoryClicked: (String) -> Unit) : BaseDelegateAdapter<String>(
searchCategoryDelegate(
onCateoryClicked
),
areItemsTheSame = { oldItem, newItem -> oldItem == newItem }
)

View file

@ -1,15 +0,0 @@
package fr.free.nrw.commons.explore.categories.search
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer
import fr.free.nrw.commons.R
import fr.free.nrw.commons.category.CATEGORY_PREFIX
import kotlinx.android.synthetic.main.item_recent_searches.*
fun searchCategoryDelegate(onCategoryClicked: (String) -> Unit) =
adapterDelegateLayoutContainer<String, String>(R.layout.item_recent_searches) {
containerView.setOnClickListener { onCategoryClicked(item) }
bind {
textView1.text = item.substringAfter(CATEGORY_PREFIX)
}
}

View file

@ -11,6 +11,6 @@ interface SearchCategoriesFragmentPresenter : PagingContract.Presenter<String>
class SearchCategoriesFragmentPresenterImpl @Inject constructor( class SearchCategoriesFragmentPresenterImpl @Inject constructor(
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler, @Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
dataSourceFactory: PageableCategoriesDataSource dataSourceFactory: PageableSearchCategoriesDataSource
) : BasePagingPresenter<String>(mainThreadScheduler, dataSourceFactory), ) : BasePagingPresenter<String>(mainThreadScheduler, dataSourceFactory),
SearchCategoriesFragmentPresenter SearchCategoriesFragmentPresenter

View file

@ -1,27 +1,18 @@
package fr.free.nrw.commons.explore.categories.search package fr.free.nrw.commons.explore.categories.search
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.category.CategoryDetailsActivity import fr.free.nrw.commons.explore.categories.PageableCategoryFragment
import fr.free.nrw.commons.explore.paging.BasePagingFragment
import javax.inject.Inject import javax.inject.Inject
/** /**
* Displays the category search screen. * Displays the category search screen.
*/ */
class SearchCategoryFragment : BasePagingFragment<String>() { class SearchCategoryFragment : PageableCategoryFragment() {
@Inject @Inject
lateinit var presenter: SearchCategoriesFragmentPresenter lateinit var presenter: SearchCategoriesFragmentPresenter
override val errorTextId: Int = R.string.error_loading_categories
override val injectedPresenter override val injectedPresenter
get() = presenter get() = presenter
override val pagedListAdapter by lazy {
PagedSearchCategoriesAdapter {
CategoryDetailsActivity.startYourself(context, it)
}
}
override fun getEmptyText(query: String) = getString(R.string.categories_not_found, query) override fun getEmptyText(query: String) = getString(R.string.categories_not_found, query)
} }

View file

@ -0,0 +1,19 @@
package fr.free.nrw.commons.explore.categories.sub
import fr.free.nrw.commons.category.CategoryClient
import fr.free.nrw.commons.explore.paging.LiveDataConverter
import fr.free.nrw.commons.explore.paging.PageableBaseDataSource
import javax.inject.Inject
class PageableSubCategoriesDataSource @Inject constructor(
liveDataConverter: LiveDataConverter,
val categoryClient: CategoryClient
) : PageableBaseDataSource<String>(liveDataConverter) {
override val loadFunction = { loadSize: Int, startPosition: Int ->
if (startPosition == 0) {
categoryClient.resetSubCategoryContinuation(query)
}
categoryClient.getSubCategoryList(query).blockingGet()
}
}

View file

@ -0,0 +1,24 @@
package fr.free.nrw.commons.explore.categories.sub
import android.os.Bundle
import android.view.View
import fr.free.nrw.commons.R
import fr.free.nrw.commons.category.CATEGORY_PREFIX
import fr.free.nrw.commons.explore.categories.PageableCategoryFragment
import javax.inject.Inject
class SubCategoriesFragment : PageableCategoryFragment() {
@Inject lateinit var presenter: SubCategoriesPresenter
override val injectedPresenter
get() = presenter
override fun getEmptyText(query: String) = getString(R.string.no_subcategory_found)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
onQueryUpdated("$CATEGORY_PREFIX${arguments!!.getString("categoryName")!!}")
}
}

View file

@ -0,0 +1,16 @@
package fr.free.nrw.commons.explore.categories.sub
import fr.free.nrw.commons.di.CommonsApplicationModule
import fr.free.nrw.commons.explore.paging.BasePagingPresenter
import fr.free.nrw.commons.explore.paging.PagingContract
import io.reactivex.Scheduler
import javax.inject.Inject
import javax.inject.Named
interface SubCategoriesPresenter : PagingContract.Presenter<String>
class SubCategoriesPresenterImpl @Inject constructor(
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
dataSourceFactory: PageableSubCategoriesDataSource
) : BasePagingPresenter<String>(mainThreadScheduler, dataSourceFactory),
SubCategoriesPresenter

View file

@ -13,6 +13,7 @@ import butterknife.ButterKnife;
import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayout;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.category.CategoryImagesCallback;
import fr.free.nrw.commons.explore.depictions.child.ChildDepictionsFragment; import fr.free.nrw.commons.explore.depictions.child.ChildDepictionsFragment;
import fr.free.nrw.commons.explore.depictions.media.DepictedImagesFragment; import fr.free.nrw.commons.explore.depictions.media.DepictedImagesFragment;
import fr.free.nrw.commons.explore.depictions.parent.ParentDepictionsFragment; import fr.free.nrw.commons.explore.depictions.parent.ParentDepictionsFragment;
@ -26,7 +27,8 @@ import java.util.List;
/** /**
* Activity to show depiction media, parent classes and child classes of depicted items in Explore * Activity to show depiction media, parent classes and child classes of depicted items in Explore
*/ */
public class WikidataItemDetailsActivity extends NavigationBaseActivity implements MediaDetailPagerFragment.MediaDetailProvider { public class WikidataItemDetailsActivity extends NavigationBaseActivity implements MediaDetailPagerFragment.MediaDetailProvider,
CategoryImagesCallback {
private FragmentManager supportFragmentManager; private FragmentManager supportFragmentManager;
private DepictedImagesFragment depictionImagesListFragment; private DepictedImagesFragment depictionImagesListFragment;
private MediaDetailPagerFragment mediaDetailPagerFragment; private MediaDetailPagerFragment mediaDetailPagerFragment;
@ -73,13 +75,13 @@ public class WikidataItemDetailsActivity extends NavigationBaseActivity implemen
* This method is called on success of API call for featured Images. * This method is called on success of API call for featured Images.
* The viewpager will notified that number of items have changed. * The viewpager will notified that number of items have changed.
*/ */
@Override
public void viewPagerNotifyDataSetChanged() { public void viewPagerNotifyDataSetChanged() {
if (mediaDetailPagerFragment !=null){ if (mediaDetailPagerFragment !=null){
mediaDetailPagerFragment.notifyDataSetChanged(); mediaDetailPagerFragment.notifyDataSetChanged();
} }
} }
/** /**
* This activity contains 3 tabs and a viewpager. This method is used to set the titles of tab, * This activity contains 3 tabs and a viewpager. This method is used to set the titles of tab,
* Set the fragments according to the tab selected in the viewPager. * Set the fragments according to the tab selected in the viewPager.

View file

@ -17,12 +17,4 @@ class DepictedImagesFragment : PageableMediaFragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
onQueryUpdated(arguments!!.getString("entityId")!!) onQueryUpdated(arguments!!.getString("entityId")!!)
} }
override fun onItemClicked(position: Int) {
(activity as WikidataItemDetailsActivity).onMediaClicked(position)
}
override fun notifyViewPager() {
(activity as WikidataItemDetailsActivity).viewPagerNotifyDataSetChanged()
}
} }

View file

@ -1,25 +1,35 @@
package fr.free.nrw.commons.explore.media package fr.free.nrw.commons.explore.media
import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.category.CategoryImagesCallback
import fr.free.nrw.commons.explore.paging.BasePagingFragment import fr.free.nrw.commons.explore.paging.BasePagingFragment
import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider
import kotlinx.android.synthetic.main.fragment_search_paginated.* import kotlinx.android.synthetic.main.fragment_search_paginated.*
abstract class PageableMediaFragment : BasePagingFragment<Media>() { abstract class PageableMediaFragment : BasePagingFragment<Media>(), MediaDetailProvider {
override val pagedListAdapter by lazy { PagedMediaAdapter(::onItemClicked) }
override val pagedListAdapter by lazy {
PagedMediaAdapter(categoryImagesCallback::onMediaClicked)
}
override val errorTextId: Int = R.string.error_loading_images override val errorTextId: Int = R.string.error_loading_images
override fun getEmptyText(query: String) = getString(R.string.no_images_found) override fun getEmptyText(query: String) = getString(R.string.no_images_found)
protected abstract fun onItemClicked(position: Int) lateinit var categoryImagesCallback: CategoryImagesCallback
protected abstract fun notifyViewPager() override fun onAttach(context: Context) {
super.onAttach(context)
categoryImagesCallback = (context as CategoryImagesCallback)
}
private val simpleDataObserver = SimpleDataObserver { notifyViewPager() } private val simpleDataObserver =
SimpleDataObserver { categoryImagesCallback.viewPagerNotifyDataSetChanged() }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -31,12 +41,12 @@ abstract class PageableMediaFragment : BasePagingFragment<Media>() {
pagedListAdapter.unregisterAdapterDataObserver(simpleDataObserver) pagedListAdapter.unregisterAdapterDataObserver(simpleDataObserver)
} }
fun getMediaAtPosition(position: Int): Media? = override fun getMediaAtPosition(position: Int): Media? =
pagedListAdapter.currentList?.get(position)?.takeIf { it.filename != null } pagedListAdapter.currentList?.get(position)?.takeIf { it.filename != null }
.also { .also {
pagedListAdapter.currentList?.loadAround(position) pagedListAdapter.currentList?.loadAround(position)
paginatedSearchResultsList.scrollToPosition(position) paginatedSearchResultsList.scrollToPosition(position)
} }
fun getTotalMediaCount(): Int = pagedListAdapter.itemCount override fun getTotalMediaCount(): Int = pagedListAdapter.itemCount
} }

View file

@ -1,7 +1,5 @@
package fr.free.nrw.commons.explore.media package fr.free.nrw.commons.explore.media
import fr.free.nrw.commons.category.CategoryImagesCallback
import fr.free.nrw.commons.explore.SearchActivity
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -13,14 +11,5 @@ class SearchMediaFragment : PageableMediaFragment(){
override val injectedPresenter override val injectedPresenter
get() = presenter get() = presenter
override fun onItemClicked(position: Int) {
(context as SearchActivity).onSearchImageClicked(position)
}
override fun notifyViewPager() {
(activity as CategoryImagesCallback).viewPagerNotifyDataSetChanged()
}
} }

View file

@ -2,6 +2,7 @@ package fr.free.nrw.commons.media
import fr.free.nrw.commons.BuildConfig import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.category.ContinuationClient
import fr.free.nrw.commons.explore.media.MediaConverter import fr.free.nrw.commons.explore.media.MediaConverter
import fr.free.nrw.commons.utils.CommonsDateUtil import fr.free.nrw.commons.utils.CommonsDateUtil
import io.reactivex.Single import io.reactivex.Single
@ -24,14 +25,10 @@ class MediaClient @Inject constructor(
private val pageMediaInterface: PageMediaInterface, private val pageMediaInterface: PageMediaInterface,
private val mediaDetailInterface: MediaDetailInterface, private val mediaDetailInterface: MediaDetailInterface,
private val mediaConverter: MediaConverter private val mediaConverter: MediaConverter
) { ) : ContinuationClient<MwQueryResponse, Media>() {
fun getMediaById(id: String) = fun getMediaById(id: String) =
responseToMediaList(mediaInterface.getMediaById(id)).map { it.first() } responseMapper(mediaInterface.getMediaById(id)).map { it.first() }
//OkHttpJsonApiClient used JsonKvStore for this. I don't know why.
private val continuationStore: MutableMap<String, Map<String, String>?> = mutableMapOf()
private val continuationExists: MutableMap<String, Boolean> = mutableMapOf()
/** /**
* Checks if a page exists on Commons * Checks if a page exists on Commons
@ -62,18 +59,8 @@ class MediaClient @Inject constructor(
* @return * @return
*/ */
fun getMediaListFromCategory(category: String): Single<List<Media>> { fun getMediaListFromCategory(category: String): Single<List<Media>> {
val key = "$CATEGORY_CONTINUATION_PREFIX$category" return continuationRequest(CATEGORY_CONTINUATION_PREFIX, category) {
return if (hasMorePagesFor(key)) { mediaInterface.getMediaListFromCategory(category, 10, it)
responseToMediaList(
mediaInterface.getMediaListFromCategory(
category,
10,
continuationStore[key] ?: emptyMap()
),
key
)
} else {
Single.just(emptyList())
} }
} }
@ -86,16 +73,10 @@ class MediaClient @Inject constructor(
* @return * @return
*/ */
fun getMediaListForUser(userName: String): Single<List<Media>> { fun getMediaListForUser(userName: String): Single<List<Media>> {
return responseToMediaList( return continuationRequest("user_", userName) {
mediaInterface.getMediaListForUser( mediaInterface.getMediaListForUser(userName, 10, it)
userName, }
10,
continuationStore["user_$userName"] ?: Collections.emptyMap()
),
"user_$userName"
)
} }
/** /**
* This method takes a keyword as input and returns a list of Media objects filtered using image generator query * This method takes a keyword as input and returns a list of Media objects filtered using image generator query
@ -107,7 +88,7 @@ class MediaClient @Inject constructor(
* @return * @return
*/ */
fun getMediaListFromSearch(keyword: String?, limit: Int, offset: Int) = fun getMediaListFromSearch(keyword: String?, limit: Int, offset: Int) =
responseToMediaList(mediaInterface.getMediaListFromSearch(keyword, limit, offset)) responseMapper(mediaInterface.getMediaListFromSearch(keyword, limit, offset))
/** /**
* @return list of images for a particular depict entity * @return list of images for a particular depict entity
@ -117,7 +98,7 @@ class MediaClient @Inject constructor(
srlimit: Int, srlimit: Int,
sroffset: Int sroffset: Int
): Single<List<Media>> { ): Single<List<Media>> {
return responseToMediaList( return responseMapper(
mediaInterface.fetchImagesForDepictedItem( mediaInterface.fetchImagesForDepictedItem(
"haswbstatement:" + BuildConfig.DEPICTS_PROPERTY + "=" + query, "haswbstatement:" + BuildConfig.DEPICTS_PROPERTY + "=" + query,
srlimit.toString(), srlimit.toString(),
@ -126,23 +107,6 @@ class MediaClient @Inject constructor(
) )
} }
private fun responseToMediaList(
response: Single<MwQueryResponse>,
key: String? = null
): Single<List<Media>> {
return response.map {
if (key != null) {
continuationExists[key] =
it.continuation()?.let { continuation ->
continuationStore[key] = continuation
true
} ?: false
}
it.query()?.pages() ?: emptyList()
}.flatMap(::mediaFromPageAndEntity)
}
private fun mediaFromPageAndEntity(pages: List<MwQueryPage>): Single<List<Media>> { private fun mediaFromPageAndEntity(pages: List<MwQueryPage>): Single<List<Media>> {
return if (pages.isEmpty()) return if (pages.isEmpty())
Single.just(emptyList()) Single.just(emptyList())
@ -165,7 +129,7 @@ class MediaClient @Inject constructor(
* @return * @return
*/ */
fun getMedia(titles: String?): Single<Media> { fun getMedia(titles: String?): Single<Media> {
return responseToMediaList(mediaInterface.getMedia(titles)) return responseMapper(mediaInterface.getMedia(titles))
.map { it.first() } .map { it.first() }
} }
@ -176,7 +140,7 @@ class MediaClient @Inject constructor(
*/ */
fun getPictureOfTheDay(): Single<Media> { fun getPictureOfTheDay(): Single<Media> {
val date = CommonsDateUtil.getIso8601DateFormatShort().format(Date()) val date = CommonsDateUtil.getIso8601DateFormatShort().format(Date())
return responseToMediaList(mediaInterface.getMediaWithGenerator("Template:Potd/$date")).map { it.first() } return responseMapper(mediaInterface.getMediaWithGenerator("Template:Potd/$date")).map { it.first() }
} }
@ -193,23 +157,22 @@ class MediaClient @Inject constructor(
} }
/**
* Check if media for user has reached the end of the list.
* @param userName
* @return
*/
fun doesMediaListForUserHaveMorePages(userName: String): Boolean {
return hasMorePagesFor("user_$userName")
}
private fun hasMorePagesFor(key: String) = continuationExists[key] ?: true
fun doesPageContainMedia(title: String?): Single<Boolean> { fun doesPageContainMedia(title: String?): Single<Boolean> {
return pageMediaInterface.getMediaList(title) return pageMediaInterface.getMediaList(title)
.map { it.items.isNotEmpty() } .map { it.items.isNotEmpty() }
} }
fun resetCategoryContinuation(category: String) { fun resetCategoryContinuation(category: String) {
continuationExists.remove("$CATEGORY_CONTINUATION_PREFIX$category") resetContinuation(CATEGORY_CONTINUATION_PREFIX, category)
}
override fun responseMapper(
networkResult: Single<MwQueryResponse>,
key: String?
): Single<List<Media>> {
return networkResult.map {
handleContinuationResponse(it.continuation(), key)
it.query()?.pages() ?: emptyList()
}.flatMap(::mediaFromPageAndEntity)
} }
} }

View file

@ -13,7 +13,6 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Toast;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.fragment.app.FragmentStatePagerAdapter;
@ -26,7 +25,6 @@ import fr.free.nrw.commons.R;
import fr.free.nrw.commons.bookmarks.Bookmark; import fr.free.nrw.commons.bookmarks.Bookmark;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider; import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao; import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
import fr.free.nrw.commons.category.CategoryImagesCallback;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.utils.DownloadUtils; import fr.free.nrw.commons.utils.DownloadUtils;
@ -269,8 +267,6 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
Timber.d("Returning as activity is destroyed!"); Timber.d("Returning as activity is destroyed!");
return; return;
} }
if (i+1 >= adapter.getCount() && getContext() instanceof CategoryImagesCallback)
((CategoryImagesCallback) getContext()).requestMoreImages();
getActivity().invalidateOptionsMenu(); getActivity().invalidateOptionsMenu();
} }

View file

@ -1,44 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusableInTouchMode="true"
android:id="@+id/image_search_results_display"
android:orientation="vertical"
android:paddingTop="@dimen/tiny_gap"
>
<TextView
android:id="@+id/imagesNotFound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_centerInParent="true"
android:visibility="gone" />
<ProgressBar
android:id="@+id/bottomProgressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:paddingTop="@dimen/tiny_padding"
android:paddingBottom="@dimen/tiny_padding"
android:visibility="gone"
/>
<ProgressBar
android:id="@+id/imageSearchInProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/imagesListBox"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:layout_above="@+id/bottomProgressBar"
android:scrollbarSize="@dimen/standard_gap"
android:fadingEdge="none" />
</RelativeLayout>

View file

@ -4,9 +4,8 @@ import categoryItem
import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever
import depictedItem import depictedItem
import fr.free.nrw.commons.explore.depictions.DepictsClient
import fr.free.nrw.commons.upload.GpsCategoryModel import fr.free.nrw.commons.upload.GpsCategoryModel
import io.reactivex.Observable import io.reactivex.Single
import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.BehaviorSubject
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -35,7 +34,7 @@ class CategoriesModelTest {
val expectedList = listOf("Test") val expectedList = listOf("Test")
whenever(categoryClient.searchCategoriesForPrefix("tes", 25)) whenever(categoryClient.searchCategoriesForPrefix("tes", 25))
.thenReturn(Observable.just(expectedList)) .thenReturn(Single.just(expectedList))
// Checking if both return "Test" // Checking if both return "Test"
val expectedItems = expectedList.map { CategoryItem(it, false) } val expectedItems = expectedList.map { CategoryItem(it, false) }
@ -56,7 +55,7 @@ class CategoriesModelTest {
whenever(gpsCategoryModel.categoriesFromLocation) whenever(gpsCategoryModel.categoriesFromLocation)
.thenReturn(BehaviorSubject.createDefault(listOf("gpsCategory"))) .thenReturn(BehaviorSubject.createDefault(listOf("gpsCategory")))
whenever(categoryClient.searchCategories("tes", 25)) whenever(categoryClient.searchCategories("tes", 25))
.thenReturn(Observable.just(listOf("titleSearch"))) .thenReturn(Single.just(listOf("titleSearch")))
whenever(categoryDao.recentCategories(25)).thenReturn(listOf("recentCategories")) whenever(categoryDao.recentCategories(25)).thenReturn(listOf("recentCategories"))
CategoriesModel(categoryClient, categoryDao, gpsCategoryModel) CategoriesModel(categoryClient, categoryDao, gpsCategoryModel)
.searchAll("", listOf("tes"), listOf(depictedItem)) .searchAll("", listOf("tes"), listOf(depictedItem))

View file

@ -2,11 +2,10 @@ package fr.free.nrw.commons.category
import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever
import io.reactivex.Observable import io.reactivex.Single
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.*
import org.mockito.ArgumentMatchers.anyString
import org.mockito.InjectMocks import org.mockito.InjectMocks
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.mock import org.mockito.Mockito.mock
@ -32,7 +31,7 @@ class CategoryClientTest {
fun searchCategoriesFound() { fun searchCategoriesFound() {
val mockResponse = withMockResponse("Category:Test") val mockResponse = withMockResponse("Category:Test")
whenever(categoryInterface.searchCategories(anyString(), anyInt(), anyInt())) whenever(categoryInterface.searchCategories(anyString(), anyInt(), anyInt()))
.thenReturn(Observable.just(mockResponse)) .thenReturn(Single.just(mockResponse))
categoryClient.searchCategories("tes", 10) categoryClient.searchCategories("tes", 10)
.test() .test()
.assertValues(listOf("Test")) .assertValues(listOf("Test"))
@ -45,7 +44,7 @@ class CategoryClientTest {
fun searchCategoriesNull() { fun searchCategoriesNull() {
val mockResponse = withNullPages() val mockResponse = withNullPages()
whenever(categoryInterface.searchCategories(anyString(), anyInt(), anyInt())) whenever(categoryInterface.searchCategories(anyString(), anyInt(), anyInt()))
.thenReturn(Observable.just(mockResponse)) .thenReturn(Single.just(mockResponse))
categoryClient.searchCategories("tes", 10) categoryClient.searchCategories("tes", 10)
.test() .test()
.assertValues(emptyList()) .assertValues(emptyList())
@ -58,7 +57,7 @@ class CategoryClientTest {
fun searchCategoriesForPrefixFound() { fun searchCategoriesForPrefixFound() {
val mockResponse = withMockResponse("Category:Test") val mockResponse = withMockResponse("Category:Test")
whenever(categoryInterface.searchCategoriesForPrefix(anyString(), anyInt(), anyInt())) whenever(categoryInterface.searchCategoriesForPrefix(anyString(), anyInt(), anyInt()))
.thenReturn(Observable.just(mockResponse)) .thenReturn(Single.just(mockResponse))
categoryClient.searchCategoriesForPrefix("tes", 10) categoryClient.searchCategoriesForPrefix("tes", 10)
.test() .test()
.assertValues(listOf("Test")) .assertValues(listOf("Test"))
@ -71,7 +70,7 @@ class CategoryClientTest {
fun searchCategoriesForPrefixNull() { fun searchCategoriesForPrefixNull() {
val mockResponse = withNullPages() val mockResponse = withNullPages()
whenever(categoryInterface.searchCategoriesForPrefix(anyString(), anyInt(), anyInt())) whenever(categoryInterface.searchCategoriesForPrefix(anyString(), anyInt(), anyInt()))
.thenReturn(Observable.just(mockResponse)) .thenReturn(Single.just(mockResponse))
categoryClient.searchCategoriesForPrefix("tes", 10) categoryClient.searchCategoriesForPrefix("tes", 10)
.test() .test()
.assertValues(emptyList()) .assertValues(emptyList())
@ -83,8 +82,8 @@ class CategoryClientTest {
@Test @Test
fun getParentCategoryListFound() { fun getParentCategoryListFound() {
val mockResponse = withMockResponse("Category:Test") val mockResponse = withMockResponse("Category:Test")
whenever(categoryInterface.getParentCategoryList(anyString())) whenever(categoryInterface.getParentCategoryList(anyString(), anyMap()))
.thenReturn(Observable.just(mockResponse)) .thenReturn(Single.just(mockResponse))
categoryClient.getParentCategoryList("tes") categoryClient.getParentCategoryList("tes")
.test() .test()
.assertValues(listOf("Test")) .assertValues(listOf("Test"))
@ -93,8 +92,8 @@ class CategoryClientTest {
@Test @Test
fun getParentCategoryListNull() { fun getParentCategoryListNull() {
val mockResponse = withNullPages() val mockResponse = withNullPages()
whenever(categoryInterface.getParentCategoryList(anyString())) whenever(categoryInterface.getParentCategoryList(anyString(), anyMap()))
.thenReturn(Observable.just(mockResponse)) .thenReturn(Single.just(mockResponse))
categoryClient.getParentCategoryList("tes") categoryClient.getParentCategoryList("tes")
.test() .test()
.assertValues(emptyList()) .assertValues(emptyList())
@ -103,8 +102,8 @@ class CategoryClientTest {
@Test @Test
fun getSubCategoryListFound() { fun getSubCategoryListFound() {
val mockResponse = withMockResponse("Category:Test") val mockResponse = withMockResponse("Category:Test")
whenever(categoryInterface.getSubCategoryList("tes")) whenever(categoryInterface.getSubCategoryList("tes", emptyMap()))
.thenReturn(Observable.just(mockResponse)) .thenReturn(Single.just(mockResponse))
categoryClient.getSubCategoryList("tes") categoryClient.getSubCategoryList("tes")
.test() .test()
.assertValues(listOf("Test")) .assertValues(listOf("Test"))
@ -113,8 +112,11 @@ class CategoryClientTest {
@Test @Test
fun getSubCategoryListNull() { fun getSubCategoryListNull() {
val mockResponse = withNullPages() val mockResponse = withNullPages()
whenever(categoryInterface.getSubCategoryList(anyString())) whenever(categoryInterface.getSubCategoryList(
.thenReturn(Observable.just(mockResponse)) anyString(),
anyMap()
))
.thenReturn(Single.just(mockResponse))
categoryClient.getSubCategoryList("tes") categoryClient.getSubCategoryList("tes")
.test() .test()
.assertValues(emptyList()) .assertValues(emptyList())

View file

@ -1,7 +1,9 @@
package fr.free.nrw.commons.contributions package fr.free.nrw.commons.contributions
import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.nhaarman.mockitokotlin2.* import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.auth.SessionManager import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.media.MediaClient import fr.free.nrw.commons.media.MediaClient
@ -58,8 +60,6 @@ class ContributionBoundaryCallbackTest {
whenever(mediaClient.getMediaListForUser(anyString())).thenReturn( whenever(mediaClient.getMediaListForUser(anyString())).thenReturn(
Single.just(listOf(mock(Media::class.java))) Single.just(listOf(mock(Media::class.java)))
) )
whenever(mediaClient.doesMediaListForUserHaveMorePages(anyString()))
.thenReturn(true)
contributionBoundaryCallback.onZeroItemsLoaded() contributionBoundaryCallback.onZeroItemsLoaded()
verify(repository).save(anyList<Contribution>()); verify(repository).save(anyList<Contribution>());
verify(mediaClient).getMediaListForUser(anyString()); verify(mediaClient).getMediaListForUser(anyString());
@ -73,8 +73,6 @@ class ContributionBoundaryCallbackTest {
whenever(mediaClient.getMediaListForUser(anyString())).thenReturn( whenever(mediaClient.getMediaListForUser(anyString())).thenReturn(
Single.just(listOf(mock(Media::class.java))) Single.just(listOf(mock(Media::class.java)))
) )
whenever(mediaClient.doesMediaListForUserHaveMorePages(anyString()))
.thenReturn(true)
contributionBoundaryCallback.onItemAtEndLoaded(mock(Contribution::class.java)) contributionBoundaryCallback.onItemAtEndLoaded(mock(Contribution::class.java))
verify(repository).save(anyList()); verify(repository).save(anyList());
verify(mediaClient).getMediaListForUser(anyString()); verify(mediaClient).getMediaListForUser(anyString());
@ -88,8 +86,6 @@ class ContributionBoundaryCallbackTest {
whenever(mediaClient.getMediaListForUser(anyString())).thenReturn( whenever(mediaClient.getMediaListForUser(anyString())).thenReturn(
Single.just(listOf(mock(Media::class.java))) Single.just(listOf(mock(Media::class.java)))
) )
whenever(mediaClient.doesMediaListForUserHaveMorePages(anyString()))
.thenReturn(true)
contributionBoundaryCallback.onItemAtFrontLoaded(mock(Contribution::class.java)) contributionBoundaryCallback.onItemAtFrontLoaded(mock(Contribution::class.java))
verify(repository).save(anyList()); verify(repository).save(anyList());
verify(mediaClient).getMediaListForUser(anyString()); verify(mediaClient).getMediaListForUser(anyString());
@ -103,28 +99,14 @@ class ContributionBoundaryCallbackTest {
whenever(mediaClient.getMediaListForUser(anyString())).thenReturn( whenever(mediaClient.getMediaListForUser(anyString())).thenReturn(
Single.just(listOf(mock(Media::class.java))) Single.just(listOf(mock(Media::class.java)))
) )
whenever(mediaClient.doesMediaListForUserHaveMorePages(anyString()))
.thenReturn(true)
contributionBoundaryCallback.fetchContributions() contributionBoundaryCallback.fetchContributions()
verify(repository).save(anyList()); verify(repository).save(anyList());
verify(mediaClient).getMediaListForUser(anyString()); verify(mediaClient).getMediaListForUser(anyString());
} }
@Test
fun testFetchContributionsForEndOfList() {
whenever(sessionManager.userName).thenReturn("Test")
whenever(mediaClient.doesMediaListForUserHaveMorePages(anyString()))
.thenReturn(false)
contributionBoundaryCallback.fetchContributions()
verify(mediaClient, times(0)).getMediaListForUser(anyString())
verifyNoMoreInteractions(repository)
}
@Test @Test
fun testFetchContributionsFailed() { fun testFetchContributionsFailed() {
whenever(sessionManager.userName).thenReturn("Test") whenever(sessionManager.userName).thenReturn("Test")
whenever(mediaClient.doesMediaListForUserHaveMorePages(anyString()))
.thenReturn(true)
whenever(mediaClient.getMediaListForUser(anyString())).thenReturn(Single.error(Exception("Error"))) whenever(mediaClient.getMediaListForUser(anyString())).thenReturn(Single.error(Exception("Error")))
contributionBoundaryCallback.fetchContributions() contributionBoundaryCallback.fetchContributions()
verifyZeroInteractions(repository); verifyZeroInteractions(repository);

View file

@ -0,0 +1,48 @@
package fr.free.nrw.commons.explore.categories.parent
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.category.CategoryClient
import fr.free.nrw.commons.explore.paging.LiveDataConverter
import io.reactivex.Single
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
class PageableParentCategoriesDataSourceTest{
@Mock
lateinit var categoryClient: CategoryClient
@Mock
lateinit var liveDataConverter: LiveDataConverter
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
}
@Test
fun `loadFunction calls reset at position 0`() {
val dataSource =
PageableParentCategoriesDataSource(liveDataConverter, categoryClient)
dataSource.onQueryUpdated("test")
whenever(categoryClient.getParentCategoryList("test"))
.thenReturn(Single.just(emptyList()))
assertThat(dataSource.loadFunction(-1, 0), `is`(emptyList()))
verify(categoryClient).resetParentCategoryContinuation("test")
}
@Test
fun `loadFunction does not call reset at any other position`() {
val dataSource =
PageableParentCategoriesDataSource(liveDataConverter, categoryClient)
dataSource.onQueryUpdated("test")
whenever(categoryClient.getParentCategoryList("test"))
.thenReturn(Single.just(emptyList()))
assertThat(dataSource.loadFunction(-1, 1), `is`(emptyList()))
verify(categoryClient, never()).resetParentCategoryContinuation("test")
}
}

View file

@ -0,0 +1,48 @@
package fr.free.nrw.commons.explore.categories.sub
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.category.CategoryClient
import fr.free.nrw.commons.explore.paging.LiveDataConverter
import io.reactivex.Single
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
class PageableSubCategoriesDataSourceTest{
@Mock
lateinit var categoryClient: CategoryClient
@Mock
lateinit var liveDataConverter: LiveDataConverter
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
}
@Test
fun `loadFunction calls reset at position 0`() {
val dataSource =
PageableSubCategoriesDataSource(liveDataConverter, categoryClient)
dataSource.onQueryUpdated("test")
whenever(categoryClient.getSubCategoryList("test"))
.thenReturn(Single.just(emptyList()))
assertThat(dataSource.loadFunction(-1, 0), `is`(emptyList()))
verify(categoryClient).resetSubCategoryContinuation("test")
}
@Test
fun `loadFunction does not call reset at any other position`() {
val dataSource =
PageableSubCategoriesDataSource(liveDataConverter, categoryClient)
dataSource.onQueryUpdated("test")
whenever(categoryClient.getSubCategoryList("test"))
.thenReturn(Single.just(emptyList()))
assertThat(dataSource.loadFunction(-1, 1), `is`(emptyList()))
verify(categoryClient, never()).resetSubCategoryContinuation("test")
}
}

View file

@ -3,19 +3,20 @@ package fr.free.nrw.commons.explore.categroies
import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.category.CategoryClient import fr.free.nrw.commons.category.CategoryClient
import fr.free.nrw.commons.explore.categories.search.PageableCategoriesDataSource import fr.free.nrw.commons.explore.categories.search.PageableSearchCategoriesDataSource
import io.reactivex.Observable import io.reactivex.Single
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers import org.hamcrest.Matchers
import org.junit.Test import org.junit.Test
class PageableCategoriesDataSourceTest { class PageableSearchCategoriesDataSourceTest {
@Test @Test
fun `loadFunction loads categories`() { fun `loadFunction loads categories`() {
val categoryClient: CategoryClient = mock() val categoryClient: CategoryClient = mock()
whenever(categoryClient.searchCategories("test", 0, 1)) whenever(categoryClient.searchCategories("test", 0, 1))
.thenReturn(Observable.just(emptyList())) .thenReturn(Single.just(emptyList()))
val pageableCategoriesDataSource = PageableCategoriesDataSource(mock(), categoryClient) val pageableCategoriesDataSource =
PageableSearchCategoriesDataSource(mock(), categoryClient)
pageableCategoriesDataSource.onQueryUpdated("test") pageableCategoriesDataSource.onQueryUpdated("test")
assertThat(pageableCategoriesDataSource.loadFunction(0, 1), Matchers.`is`(emptyList())) assertThat(pageableCategoriesDataSource.loadFunction(0, 1), Matchers.`is`(emptyList()))
} }