#3820 Convert CategoryImagesListFragment to use Pagination - replace CategoryImagesListFragment with CategoriesMediaFragment - disallow the construction of media objects without imageinfo

This commit is contained in:
Sean Mac Gillicuddy 2020-06-18 12:58:46 +01:00
parent e5e110eafa
commit 0dad78358d
27 changed files with 243 additions and 416 deletions

View file

@ -8,26 +8,22 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.google.android.material.tabs.TabLayout;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.explore.ViewPagerAdapter;
import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import java.util.ArrayList;
import java.util.List;
/**
* This activity displays details of a particular category
@ -36,12 +32,11 @@ import fr.free.nrw.commons.theme.NavigationBaseActivity;
*/
public class CategoryDetailsActivity extends NavigationBaseActivity
implements MediaDetailPagerFragment.MediaDetailProvider,
AdapterView.OnItemClickListener, CategoryImagesCallback {
implements MediaDetailPagerFragment.MediaDetailProvider, CategoryImagesCallback {
private FragmentManager supportFragmentManager;
private CategoryImagesListFragment categoryImagesListFragment;
private CategoriesMediaFragment categoriesMediaFragment;
private MediaDetailPagerFragment mediaDetails;
private String categoryName;
@BindView(R.id.mediaContainer) FrameLayout mediaContainer;
@ -73,7 +68,7 @@ public class CategoryDetailsActivity extends NavigationBaseActivity
private void setTabs() {
List<Fragment> fragmentList = new ArrayList<>();
List<String> titleList = new ArrayList<>();
categoryImagesListFragment = new CategoryImagesListFragment();
categoriesMediaFragment = new CategoriesMediaFragment();
SubCategoryListFragment subCategoryListFragment = new SubCategoryListFragment();
SubCategoryListFragment parentCategoryListFragment = new SubCategoryListFragment();
categoryName = getIntent().getStringExtra("categoryName");
@ -81,14 +76,14 @@ public class CategoryDetailsActivity extends NavigationBaseActivity
Bundle arguments = new Bundle();
arguments.putString("categoryName", categoryName);
arguments.putBoolean("isParentCategory", false);
categoryImagesListFragment.setArguments(arguments);
categoriesMediaFragment.setArguments(arguments);
subCategoryListFragment.setArguments(arguments);
Bundle parentCategoryArguments = new Bundle();
parentCategoryArguments.putString("categoryName", categoryName);
parentCategoryArguments.putBoolean("isParentCategory", true);
parentCategoryListFragment.setArguments(parentCategoryArguments);
}
fragmentList.add(categoryImagesListFragment);
fragmentList.add(categoriesMediaFragment);
titleList.add("MEDIA");
fragmentList.add(subCategoryListFragment);
titleList.add("SUBCATEGORIES");
@ -111,8 +106,7 @@ public class CategoryDetailsActivity extends NavigationBaseActivity
/**
* This method is called onClick of media inside category details (CategoryImageListFragment).
*/
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
public void onMediaClicked(int position) {
tabLayout.setVisibility(View.GONE);
viewPager.setVisibility(View.GONE);
mediaContainer.setVisibility(View.VISIBLE);
@ -127,7 +121,7 @@ public class CategoryDetailsActivity extends NavigationBaseActivity
.commit();
supportFragmentManager.executePendingTransactions();
}
mediaDetails.showImage(i);
mediaDetails.showImage(position);
forceInitBackButton();
}
@ -152,12 +146,7 @@ public class CategoryDetailsActivity extends NavigationBaseActivity
*/
@Override
public Media getMediaAtPosition(int i) {
if (categoryImagesListFragment.getAdapter() == null) {
// not yet ready to return data
return null;
} else {
return (Media) categoryImagesListFragment.getAdapter().getItem(i);
}
return categoriesMediaFragment.getMediaAtPosition(i);
}
/**
@ -167,10 +156,7 @@ public class CategoryDetailsActivity extends NavigationBaseActivity
*/
@Override
public int getTotalMediaCount() {
if (categoryImagesListFragment.getAdapter() == null) {
return 0;
}
return categoryImagesListFragment.getAdapter().getCount();
return categoriesMediaFragment.getTotalMediaCount();
}
/**
@ -232,8 +218,6 @@ public class CategoryDetailsActivity extends NavigationBaseActivity
*/
@Override
public void requestMoreImages() {
if (categoryImagesListFragment!=null){
categoryImagesListFragment.fetchMoreImagesViewPager();
}
//unneeded
}
}

View file

@ -8,14 +8,13 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import butterknife.ButterKnife;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.explore.SearchActivity;
import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
@ -34,7 +33,7 @@ public class CategoryImagesActivity
private FragmentManager supportFragmentManager;
private CategoryImagesListFragment categoryImagesListFragment;
private CategoriesMediaFragment categoriesMediaFragment;
private MediaDetailPagerFragment mediaDetails;
/**
@ -66,15 +65,15 @@ public class CategoryImagesActivity
* Gets the categoryName from the intent and initializes the fragment for showing images of that category
*/
private void setCategoryImagesFragment() {
categoryImagesListFragment = new CategoryImagesListFragment();
categoriesMediaFragment = new CategoriesMediaFragment();
String categoryName = getIntent().getStringExtra("categoryName");
if (getIntent() != null && categoryName != null) {
Bundle arguments = new Bundle();
arguments.putString("categoryName", categoryName);
categoryImagesListFragment.setArguments(arguments);
categoriesMediaFragment.setArguments(arguments);
FragmentTransaction transaction = supportFragmentManager.beginTransaction();
transaction
.add(R.id.fragmentContainer, categoryImagesListFragment)
.add(R.id.fragmentContainer, categoriesMediaFragment)
.commit();
}
}
@ -137,12 +136,7 @@ public class CategoryImagesActivity
*/
@Override
public Media getMediaAtPosition(int i) {
if (categoryImagesListFragment.getAdapter() == null) {
// not yet ready to return data
return null;
} else {
return (Media) categoryImagesListFragment.getAdapter().getItem(i);
}
return categoriesMediaFragment.getMediaAtPosition(i);
}
/**
@ -163,10 +157,7 @@ public class CategoryImagesActivity
*/
@Override
public int getTotalMediaCount() {
if (categoryImagesListFragment.getAdapter() == null) {
return 0;
}
return categoryImagesListFragment.getAdapter().getCount();
return categoriesMediaFragment.getTotalMediaCount();
}
/**
@ -202,8 +193,6 @@ public class CategoryImagesActivity
*/
@Override
public void requestMoreImages() {
if (categoryImagesListFragment!=null){
categoryImagesListFragment.fetchMoreImagesViewPager();
}
//unneeded
}
}

View file

@ -1,266 +0,0 @@
package fr.free.nrw.commons.category;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ListAdapter;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import butterknife.BindView;
import butterknife.ButterKnife;
import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
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.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
/**
* Displays images for a particular category with load more on scrolling incorporated
*/
public class CategoryImagesListFragment extends DaggerFragment {
/**
* counts the total number of items loaded from the API
*/
private int mediaSize = 0;
private GridViewAdapter gridAdapter;
@BindView(R.id.statusMessage)
TextView statusTextView;
@BindView(R.id.loadingImagesProgressBar) ProgressBar progressBar;
@BindView(R.id.categoryImagesList) GridView gridView;
@BindView(R.id.parentLayout) RelativeLayout parentLayout;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private boolean hasMoreImages = true;
private boolean isLoading = true;
private String categoryName = null;
@Inject MediaClient mediaClient;
@Inject
@Named("default_preferences")
JsonKvStore categoryKvStore;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_category_images, container, false);
ButterKnife.bind(this, v);
return v;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
gridView.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());
initViews();
}
@Override
public void onDestroy() {
super.onDestroy();
compositeDisposable.clear();
}
/**
* Initializes the UI elements for the fragment
* Setup the grid view to and scroll listener for it
*/
private void initViews() {
String categoryName = getArguments().getString("categoryName");
if (getArguments() != null && categoryName != null) {
this.categoryName = categoryName;
resetQueryContinueValues(categoryName);
initList();
setScrollListener();
}
}
/**
* Query continue values determine the last page that was loaded for the particular keyword
* This method resets those values, so that the results can be queried from the first page itself
* @param keyword
*/
private void resetQueryContinueValues(String keyword) {
categoryKvStore.remove("query_continue_" + keyword);
}
/**
* Checks for internet connection and then initializes the grid view with first 10 images of that category
*/
@SuppressLint("CheckResult")
private void initList() {
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
handleNoInternet();
return;
}
isLoading = true;
progressBar.setVisibility(VISIBLE);
compositeDisposable.add(mediaClient.getMediaListFromCategory(categoryName)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::handleSuccess, this::handleError));
}
/**
* Handles the UI updates for no internet scenario
*/
private void handleNoInternet() {
progressBar.setVisibility(GONE);
if (gridAdapter == null || gridAdapter.isEmpty()) {
statusTextView.setVisibility(VISIBLE);
statusTextView.setText(getString(R.string.no_internet));
} else {
ViewUtil.showShortSnackbar(parentLayout, R.string.no_internet);
}
}
/**
* Logs and handles API error scenario
* @param throwable
*/
private void handleError(Throwable throwable) {
Timber.e(throwable, "Error occurred while loading images inside a category");
try{
ViewUtil.showShortSnackbar(parentLayout, R.string.error_loading_images);
initErrorView();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* Handles the UI updates for a error scenario
*/
private void initErrorView() {
progressBar.setVisibility(GONE);
if (gridAdapter == null || gridAdapter.isEmpty()) {
statusTextView.setVisibility(VISIBLE);
statusTextView.setText(getString(R.string.no_images_found));
} else {
statusTextView.setVisibility(GONE);
}
}
/**
* Initializes the adapter with a list of Media objects
* @param mediaList List of new Media to be displayed
*/
private void setAdapter(List<Media> mediaList) {
gridAdapter = new GridViewAdapter(this.getContext(), R.layout.layout_category_images, mediaList);
gridView.setAdapter(gridAdapter);
}
/**
* Sets the scroll listener for the grid view so that more images are fetched when the user scrolls down
* Checks if the category has more images before loading
* Also checks whether images are currently being fetched before triggering another request
*/
private void setScrollListener() {
gridView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (hasMoreImages && !isLoading && (firstVisibleItem + visibleItemCount + 1 >= totalItemCount)) {
isLoading = true;
fetchMoreImages();
}
if (!hasMoreImages){
progressBar.setVisibility(GONE);
}
}
});
}
/**
* This method is called when viewPager has reached its end.
* Fetches more images for the category and adds it to the grid view and viewpager adapter
*/
public void fetchMoreImagesViewPager(){
if (hasMoreImages && !isLoading) {
isLoading = true;
fetchMoreImages();
}
if (!hasMoreImages){
progressBar.setVisibility(GONE);
}
}
/**
* Fetches more images for the category and adds it to the grid view adapter
*/
@SuppressLint("CheckResult")
private void fetchMoreImages() {
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
handleNoInternet();
return;
}
progressBar.setVisibility(VISIBLE);
compositeDisposable.add(mediaClient.getMediaListFromCategory(categoryName)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::handleSuccess, this::handleError));
}
/**
* Handles the success scenario
* On first load, it initializes the grid view. On subsequent loads, it adds items to the adapter
* @param collection List of new Media to be displayed
*/
private void handleSuccess(List<Media> collection) {
if (collection == null || collection.isEmpty()) {
initErrorView();
hasMoreImages = false;
return;
}
if (gridAdapter == null) {
setAdapter(collection);
} else {
if (gridAdapter.containsAll(collection)) {
hasMoreImages = false;
return;
}
gridAdapter.addItems(collection);
((CategoryImagesCallback) getContext()).viewPagerNotifyDataSetChanged();
}
progressBar.setVisibility(GONE);
isLoading = false;
statusTextView.setVisibility(GONE);
}
/**
* It return an instance of gridView adapter which helps in extracting media details
* used by the gridView
* @return GridView Adapter
*/
public ListAdapter getAdapter() {
return gridAdapter;
}
}

View file

@ -20,7 +20,7 @@ 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.SearchCategoriesAdapter;
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;

View file

@ -2,6 +2,7 @@ package fr.free.nrw.commons.di;
import com.google.gson.Gson;
import fr.free.nrw.commons.explore.categories.CategoriesModule;
import javax.inject.Singleton;
import dagger.Component;
@ -33,7 +34,12 @@ import fr.free.nrw.commons.widget.PicOfDayAppWidget;
ActivityBuilderModule.class,
FragmentBuilderModule.class,
ServiceBuilderModule.class,
ContentProviderBuilderModule.class, UploadModule.class, ContributionsModule.class, SearchModule.class, DepictionModule.class
ContentProviderBuilderModule.class,
UploadModule.class,
ContributionsModule.class,
SearchModule.class,
DepictionModule.class,
CategoriesModule.class
})
public interface CommonsApplicationComponent extends AndroidInjector<ApplicationlessInjection> {
void inject(CommonsApplication application);

View file

@ -4,14 +4,14 @@ import dagger.Module;
import dagger.android.ContributesAndroidInjector;
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment;
import fr.free.nrw.commons.category.CategoryImagesListFragment;
import fr.free.nrw.commons.category.SubCategoryListFragment;
import fr.free.nrw.commons.contributions.ContributionsFragment;
import fr.free.nrw.commons.contributions.ContributionsListFragment;
import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment;
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.parent.ParentDepictionsFragment;
import fr.free.nrw.commons.explore.categories.SearchCategoryFragment;
import fr.free.nrw.commons.explore.categories.search.SearchCategoryFragment;
import fr.free.nrw.commons.explore.depictions.search.SearchDepictionsFragment;
import fr.free.nrw.commons.explore.media.SearchMediaFragment;
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment;
@ -46,9 +46,6 @@ public abstract class FragmentBuilderModule {
@ContributesAndroidInjector
abstract SettingsFragment bindSettingsFragment();
@ContributesAndroidInjector
abstract CategoryImagesListFragment bindFeaturedImagesListFragment();
@ContributesAndroidInjector
abstract DepictedImagesFragment bindDepictedImagesFragment();
@ -99,4 +96,7 @@ public abstract class FragmentBuilderModule {
@ContributesAndroidInjector
abstract ChildDepictionsFragment bindChildDepictionsFragment();
@ContributesAndroidInjector
abstract CategoriesMediaFragment bindCategoriesMediaFragment();
}

View file

@ -18,7 +18,7 @@ import com.google.android.material.tabs.TabLayout;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.category.CategoryImagesCallback;
import fr.free.nrw.commons.category.CategoryImagesListFragment;
import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import java.util.ArrayList;
@ -34,8 +34,8 @@ public class ExploreActivity
implements MediaDetailPagerFragment.MediaDetailProvider,
AdapterView.OnItemClickListener, CategoryImagesCallback {
private static final String FEATURED_IMAGES_CATEGORY = "Category:Featured_pictures_on_Wikimedia_Commons";
private static final String MOBILE_UPLOADS_CATEGORY = "Category:Uploaded_with_Mobile/Android";
private static final String FEATURED_IMAGES_CATEGORY = "Featured_pictures_on_Wikimedia_Commons";
private static final String MOBILE_UPLOADS_CATEGORY = "Uploaded_with_Mobile/Android";
@BindView(R.id.mediaContainer)
@ -47,8 +47,8 @@ public class ExploreActivity
ViewPagerAdapter viewPagerAdapter;
private FragmentManager supportFragmentManager;
private MediaDetailPagerFragment mediaDetails;
private CategoryImagesListFragment mobileImagesListFragment;
private CategoryImagesListFragment featuredImagesListFragment;
private CategoriesMediaFragment mobileImagesListFragment;
private CategoriesMediaFragment featuredImagesListFragment;
/**
* Consumers should be simply using this method to use this activity.
@ -83,14 +83,14 @@ public class ExploreActivity
List<Fragment> fragmentList = new ArrayList<>();
List<String> titleList = new ArrayList<>();
featuredImagesListFragment = new CategoryImagesListFragment();
featuredImagesListFragment = new CategoriesMediaFragment();
Bundle featuredArguments = new Bundle();
featuredArguments.putString("categoryName", FEATURED_IMAGES_CATEGORY);
featuredImagesListFragment.setArguments(featuredArguments);
fragmentList.add(featuredImagesListFragment);
titleList.add(getString(R.string.explore_tab_title_featured).toUpperCase());
mobileImagesListFragment = new CategoryImagesListFragment();
mobileImagesListFragment = new CategoriesMediaFragment();
Bundle mobileArguments = new Bundle();
mobileArguments.putString("categoryName", MOBILE_UPLOADS_CATEGORY);
mobileImagesListFragment.setArguments(mobileArguments);
@ -110,10 +110,10 @@ public class ExploreActivity
*/
@Override
public Media getMediaAtPosition(int i) {
if (mobileImagesListFragment.getAdapter() != null && tabLayout.getSelectedTabPosition() == 1) {
return (Media) mobileImagesListFragment.getAdapter().getItem(i);
} else if (featuredImagesListFragment.getAdapter() != null && tabLayout.getSelectedTabPosition() == 0) {
return (Media) featuredImagesListFragment.getAdapter().getItem(i);
if (tabLayout.getSelectedTabPosition() == 1) {
return mobileImagesListFragment.getMediaAtPosition(i);
} else if (tabLayout.getSelectedTabPosition() == 0) {
return featuredImagesListFragment.getMediaAtPosition(i);
} else {
return null;
}
@ -127,10 +127,10 @@ public class ExploreActivity
*/
@Override
public int getTotalMediaCount() {
if (mobileImagesListFragment.getAdapter() != null && tabLayout.getSelectedTabPosition() == 1) {
return mobileImagesListFragment.getAdapter().getCount();
} else if (featuredImagesListFragment.getAdapter() != null && tabLayout.getSelectedTabPosition() == 0) {
return featuredImagesListFragment.getAdapter().getCount();
if (tabLayout.getSelectedTabPosition() == 1) {
return mobileImagesListFragment.getTotalMediaCount();
} else if (tabLayout.getSelectedTabPosition() == 0) {
return featuredImagesListFragment.getTotalMediaCount();
} else {
return 0;
}
@ -170,11 +170,7 @@ public class ExploreActivity
*/
@Override
public void requestMoreImages() {
if (mobileImagesListFragment != null && tabLayout.getSelectedTabPosition() == 1) {
mobileImagesListFragment.fetchMoreImagesViewPager();
} else if (featuredImagesListFragment != null && tabLayout.getSelectedTabPosition() == 0) {
featuredImagesListFragment.fetchMoreImagesViewPager();
}
//unneeded
}
/**

View file

@ -19,7 +19,7 @@ import com.jakewharton.rxbinding2.widget.RxSearchView;
import fr.free.nrw.commons.Media;
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.categories.search.SearchCategoryFragment;
import fr.free.nrw.commons.explore.depictions.search.SearchDepictionsFragment;
import fr.free.nrw.commons.explore.media.SearchMediaFragment;
import fr.free.nrw.commons.explore.recentsearches.RecentSearch;
@ -165,7 +165,7 @@ public class SearchActivity extends NavigationBaseActivity
*/
@Override
public Media getMediaAtPosition(int i) {
return searchMediaFragment.getImageAtPosition(i);
return searchMediaFragment.getMediaAtPosition(i);
}
/**
@ -173,7 +173,7 @@ public class SearchActivity extends NavigationBaseActivity
*/
@Override
public int getTotalMediaCount() {
return searchMediaFragment.getTotalImagesCount();
return searchMediaFragment.getTotalMediaCount();
}
/**

View file

@ -2,8 +2,8 @@ package fr.free.nrw.commons.explore
import dagger.Binds
import dagger.Module
import fr.free.nrw.commons.explore.categories.SearchCategoriesFragmentPresenter
import fr.free.nrw.commons.explore.categories.SearchCategoriesFragmentPresenterImpl
import fr.free.nrw.commons.explore.categories.search.SearchCategoriesFragmentPresenter
import fr.free.nrw.commons.explore.categories.search.SearchCategoriesFragmentPresenterImpl
import fr.free.nrw.commons.explore.depictions.search.SearchDepictionsFragmentPresenter
import fr.free.nrw.commons.explore.depictions.search.SearchDepictionsFragmentPresenterImpl
import fr.free.nrw.commons.explore.media.SearchMediaFragmentPresenter

View file

@ -0,0 +1,15 @@
package fr.free.nrw.commons.explore.categories
import dagger.Binds
import dagger.Module
import fr.free.nrw.commons.explore.categories.media.CategoryMediaPresenter
import fr.free.nrw.commons.explore.categories.media.CategoryMediaPresenterImpl
@Module
abstract class CategoriesModule {
@Binds
abstract fun CategoryMediaPresenterImpl.bindsParentDepictionPresenter()
: CategoryMediaPresenter
}

View file

@ -0,0 +1,31 @@
package fr.free.nrw.commons.explore.categories.media
import android.os.Bundle
import android.view.View
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 javax.inject.Inject
class CategoriesMediaFragment : PageableMediaFragment() {
@Inject
lateinit var presenter: CategoryMediaPresenter
override val injectedPresenter
get() = presenter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
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,20 @@
package fr.free.nrw.commons.explore.categories.media
import fr.free.nrw.commons.Media
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 CategoryMediaPresenter : PagingContract.Presenter<Media>
/**
* Presenter for DepictedImagesFragment
*/
class CategoryMediaPresenterImpl @Inject constructor(
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
dataSourceFactory: PageableCategoriesMediaDataSource
) : BasePagingPresenter<Media>(mainThreadScheduler, dataSourceFactory),
CategoryMediaPresenter

View file

@ -0,0 +1,20 @@
package fr.free.nrw.commons.explore.categories.media
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.explore.depictions.search.LoadFunction
import fr.free.nrw.commons.explore.paging.LiveDataConverter
import fr.free.nrw.commons.explore.paging.PageableBaseDataSource
import fr.free.nrw.commons.media.MediaClient
import javax.inject.Inject
class PageableCategoriesMediaDataSource @Inject constructor(
liveDataConverter: LiveDataConverter,
private val mediaClient: MediaClient
) : PageableBaseDataSource<Media>(liveDataConverter) {
override val loadFunction: LoadFunction<Media> = { loadSize: Int, startPosition: Int ->
if(startPosition == 0){
mediaClient.resetCategoryContinuation(query)
}
mediaClient.getMediaListFromCategory(query).blockingGet()
}
}

View file

@ -1,4 +1,4 @@
package fr.free.nrw.commons.explore.categories
package fr.free.nrw.commons.explore.categories.search
import fr.free.nrw.commons.category.CategoryClient
import fr.free.nrw.commons.explore.paging.LiveDataConverter

View file

@ -1,4 +1,4 @@
package fr.free.nrw.commons.explore.categories
package fr.free.nrw.commons.explore.categories.search
import android.view.View
import android.view.ViewGroup

View file

@ -1,9 +1,11 @@
package fr.free.nrw.commons.explore.categories
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),
searchCategoryDelegate(
onCateoryClicked
),
areItemsTheSame = { oldItem, newItem -> oldItem == newItem }
)

View file

@ -1,4 +1,4 @@
package fr.free.nrw.commons.explore.categories
package fr.free.nrw.commons.explore.categories.search
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer
import fr.free.nrw.commons.R

View file

@ -1,4 +1,4 @@
package fr.free.nrw.commons.explore.categories
package fr.free.nrw.commons.explore.categories.search
import fr.free.nrw.commons.di.CommonsApplicationModule
import fr.free.nrw.commons.explore.paging.BasePagingPresenter

View file

@ -1,4 +1,4 @@
package fr.free.nrw.commons.explore.categories
package fr.free.nrw.commons.explore.categories.search
import fr.free.nrw.commons.R
import fr.free.nrw.commons.category.CategoryDetailsActivity
@ -18,7 +18,9 @@ class SearchCategoryFragment : BasePagingFragment<String>() {
get() = presenter
override val pagedListAdapter by lazy {
PagedSearchCategoriesAdapter { CategoryDetailsActivity.startYourself(context, it) }
PagedSearchCategoriesAdapter {
CategoryDetailsActivity.startYourself(context, it)
}
}
override fun getEmptyText(query: String) = getString(R.string.categories_not_found, query)

View file

@ -143,7 +143,7 @@ public class WikidataItemDetailsActivity extends NavigationBaseActivity implemen
*/
@Override
public Media getMediaAtPosition(int i) {
return depictionImagesListFragment.getImageAtPosition(i);
return depictionImagesListFragment.getMediaAtPosition(i);
}
/**
@ -168,7 +168,7 @@ public class WikidataItemDetailsActivity extends NavigationBaseActivity implemen
*/
@Override
public int getTotalMediaCount() {
return depictionImagesListFragment.getTotalImagesCount();
return depictionImagesListFragment.getTotalMediaCount();
}
/**

View file

@ -9,6 +9,7 @@ import fr.free.nrw.commons.wikidata.WikidataProperties
import org.apache.commons.lang3.StringUtils
import org.wikipedia.dataclient.mwapi.MwQueryPage
import org.wikipedia.gallery.ExtMetadata
import org.wikipedia.gallery.ImageInfo
import org.wikipedia.wikidata.DataValue
import org.wikipedia.wikidata.Entities
import java.text.ParseException
@ -16,9 +17,11 @@ import java.util.*
import javax.inject.Inject
class MediaConverter @Inject constructor() {
fun convert(page: MwQueryPage, entity: Entities.Entity): Media {
val imageInfo = page.imageInfo()
requireNotNull(imageInfo) { "No image info" }
fun convert(
page: MwQueryPage,
entity: Entities.Entity,
imageInfo: ImageInfo
): Media {
val metadata = imageInfo.metadata
requireNotNull(metadata) { "No metadata" }
return Media(

View file

@ -31,12 +31,12 @@ abstract class PageableMediaFragment : BasePagingFragment<Media>() {
pagedListAdapter.unregisterAdapterDataObserver(simpleDataObserver)
}
fun getImageAtPosition(position: Int): Media? =
fun getMediaAtPosition(position: Int): Media? =
pagedListAdapter.currentList?.get(position)?.takeIf { it.filename != null }
.also {
pagedListAdapter.currentList?.loadAround(position)
paginatedSearchResultsList.scrollToPosition(position)
}
fun getTotalImagesCount(): Int = pagedListAdapter.itemCount
fun getTotalMediaCount(): Int = pagedListAdapter.itemCount
}

View file

@ -13,6 +13,7 @@ import javax.inject.Inject
import javax.inject.Singleton
const val PAGE_ID_PREFIX = "M"
const val CATEGORY_CONTINUATION_PREFIX = "category_"
/**
* Media Client to handle custom calls to Commons MediaWiki APIs
@ -61,14 +62,19 @@ class MediaClient @Inject constructor(
* @return
*/
fun getMediaListFromCategory(category: String): Single<List<Media>> {
return responseToMediaList(
mediaInterface.getMediaListFromCategory(
category,
10,
continuationStore["category_$category"] ?: emptyMap()
),
"category_$category"
)
val key = "$CATEGORY_CONTINUATION_PREFIX$category"
return if (hasMorePagesFor(key)) {
responseToMediaList(
mediaInterface.getMediaListFromCategory(
category,
10,
continuationStore[key] ?: emptyMap()
),
key
)
} else {
Single.just(emptyList())
}
}
/**
@ -144,7 +150,11 @@ class MediaClient @Inject constructor(
getEntities(pages.map { "$PAGE_ID_PREFIX${it.pageId()}" })
.map {
pages.zip(it.entities().values)
.map { (page, entity) -> mediaConverter.convert(page, entity) }
.mapNotNull { (page, entity) ->
page.imageInfo()?.let {
mediaConverter.convert(page, entity, it)
}
}
}
}
@ -189,12 +199,17 @@ class MediaClient @Inject constructor(
* @return
*/
fun doesMediaListForUserHaveMorePages(userName: String): Boolean {
val key = "user_$userName"
return if (continuationExists.containsKey(key)) continuationExists[key]!! else true
return hasMorePagesFor("user_$userName")
}
private fun hasMorePagesFor(key: String) = continuationExists[key] ?: true
fun doesPageContainMedia(title: String?): Single<Boolean> {
return pageMediaInterface.getMediaList(title)
.map { it.items.isNotEmpty() }
}
fun resetCategoryContinuation(category: String) {
continuationExists.remove("$CATEGORY_CONTINUATION_PREFIX$category")
}
}

View file

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/parentLayout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/mainBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/waiting_first_sync"
android:id="@+id/statusMessage"
android:layout_gravity="center"
android:visibility="gone"
tools:visibility="visible"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
/>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
android:id="@+id/loadingImagesProgressBar"
/>
<GridView
android:id="@+id/categoryImagesList"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:stretchMode="columnWidth"
android:columnWidth="@dimen/very_large_height"
android:numColumns="auto_fit"
android:listSelector="@null"
android:fadingEdge="none"
/>
</RelativeLayout>

View file

@ -0,0 +1,49 @@
package fr.free.nrw.commons.explore.categories.media
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.explore.paging.LiveDataConverter
import fr.free.nrw.commons.media.MediaClient
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 PageableCategoriesMediaDataSourceTest {
@Mock
lateinit var mediaClient: MediaClient
@Mock
lateinit var liveDataConverter: LiveDataConverter
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
}
@Test
fun `loadFunction calls reset at position 0`() {
val dataSource =
PageableCategoriesMediaDataSource(liveDataConverter, mediaClient)
dataSource.onQueryUpdated("test")
whenever(mediaClient.getMediaListFromCategory("test"))
.thenReturn(Single.just(emptyList()))
assertThat(dataSource.loadFunction(-1, 0), `is`(emptyList()))
verify(mediaClient).resetCategoryContinuation("test")
}
@Test
fun `loadFunction does not call reset at any other position`() {
val dataSource =
PageableCategoriesMediaDataSource(liveDataConverter, mediaClient)
dataSource.onQueryUpdated("test")
whenever(mediaClient.getMediaListFromCategory("test"))
.thenReturn(Single.just(emptyList()))
assertThat(dataSource.loadFunction(-1, 1), `is`(emptyList()))
verify(mediaClient, never()).resetCategoryContinuation("test")
}
}

View file

@ -3,7 +3,7 @@ package fr.free.nrw.commons.explore.categroies
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.category.CategoryClient
import fr.free.nrw.commons.explore.categories.PageableCategoriesDataSource
import fr.free.nrw.commons.explore.categories.search.PageableCategoriesDataSource
import io.reactivex.Observable
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers

View file

@ -164,7 +164,9 @@ class MediaClientTest {
val entity: Entities.Entity = mock()
whenever(entities.entities()).thenReturn(mapOf("id" to entity))
val media: Media = mock()
whenever(mediaConverter!!.convert(queryPage, entity)).thenReturn(media)
val imageInfo = mock<ImageInfo>()
whenever(queryPage.imageInfo()).thenReturn(imageInfo)
whenever(mediaConverter!!.convert(queryPage, entity, imageInfo)).thenReturn(media)
return Pair(mockResponse, media)
}