#3810 Convert DepictedImagesFragment to use Pagination - extract common media paging methods - convert to DepictedImages to use pagination

This commit is contained in:
Sean Mac Gillicuddy 2020-06-17 08:52:04 +01:00
parent 48a4e10170
commit 181a63a233
28 changed files with 211 additions and 893 deletions

View file

@ -1,7 +1,7 @@
package fr.free.nrw.commons
import androidx.core.text.HtmlCompat
import fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX
import fr.free.nrw.commons.media.PAGE_ID_PREFIX
import fr.free.nrw.commons.media.IdAndCaptions
import fr.free.nrw.commons.media.MediaClient
import io.reactivex.Single

View file

@ -1,27 +0,0 @@
package fr.free.nrw.commons.depictions;
import dagger.Binds;
import dagger.Module;
import fr.free.nrw.commons.depictions.Media.DepictedImagesContract;
import fr.free.nrw.commons.depictions.Media.DepictedImagesPresenter;
import fr.free.nrw.commons.depictions.subClass.SubDepictionListContract;
import fr.free.nrw.commons.depictions.subClass.SubDepictionListPresenter;
/**
* The Dagger Module for explore:depictions related presenters and (some other objects maybe in future)
*/
@Module
public abstract class DepictionModule {
@Binds
public abstract DepictedImagesContract.UserActionListener bindsDepictedImagesPresenter(
DepictedImagesPresenter
presenter
);
@Binds
public abstract SubDepictionListContract.UserActionListener bindsSubDepictionListPresenter(
SubDepictionListPresenter
presenter
);
}

View file

@ -0,0 +1,23 @@
package fr.free.nrw.commons.depictions
import dagger.Binds
import dagger.Module
import fr.free.nrw.commons.depictions.Media.DepictedImagesContract
import fr.free.nrw.commons.depictions.Media.DepictedImagesPresenter
import fr.free.nrw.commons.depictions.subClass.SubDepictionListContract
import fr.free.nrw.commons.depictions.subClass.SubDepictionListPresenter
/**
* The Dagger Module for explore:depictions related presenters and (some other objects maybe in future)
*/
@Module
abstract class DepictionModule {
@Binds
abstract fun SubDepictionListPresenter.bindsSubDepictionListPresenter()
: SubDepictionListContract.UserActionListener
@Binds
abstract fun DepictedImagesPresenter.bindsDepictedImagesContractPresenter()
: DepictedImagesContract.Presenter
}

View file

@ -1,119 +0,0 @@
package fr.free.nrw.commons.depictions;
import android.content.Context;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.facebook.drawee.view.SimpleDraweeView;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
/**
* Adapter for Items in DepictionDetailsActivity
*/
public class GridViewAdapter extends ArrayAdapter {
private List<Media> data;
public GridViewAdapter(Context context, int layoutResourceId, List<Media> data) {
super(context, layoutResourceId, data);
this.data = data;
}
/**
* Adds more item to the list
* Its triggered on scrolling down in the list
* @param images
*/
public void addItems(List<Media> images) {
if (data == null) {
data = new ArrayList<>();
}
data.addAll(images);
notifyDataSetChanged();
}
/**
* Check the first item in the new list with old list and returns true if they are same
* Its triggered on successful response of the fetch images API.
* @param images
*/
public boolean containsAll(List<Media> images){
if (images == null || images.isEmpty()) {
return false;
}
if (data == null) {
data = new ArrayList<>();
return false;
}
if (data.size() == 0) {
return false;
}
String fileName = data.get(0).getFilename();
String imageName = images.get(0).getFilename();
return imageName.equals(fileName);
}
@Override
public boolean isEmpty() {
return data == null || data.isEmpty();
}
/**
* Sets up the UI for the depicted image item
* @param position
* @param convertView
* @param parent
* @return
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.layout_depict_image, null);
}
Media item = data.get(position);
SimpleDraweeView imageView = convertView.findViewById(R.id.depict_image_view);
TextView fileName = convertView.findViewById(R.id.depict_image_title);
TextView author = convertView.findViewById(R.id.depict_image_author);
fileName.setText(item.getDisplayTitle());
setAuthorView(item, author);
imageView.setImageURI(item.getThumbUrl());
return convertView;
}
@Nullable
@Override
public Media getItem(int position) {
return data.get(position);
}
/**
* Shows author information if its present
* @param item
* @param author
*/
private void setAuthorView(Media item, TextView author) {
if (!TextUtils.isEmpty(item.getCreator())) {
String uploadedByTemplate = getContext().getString(R.string.image_uploaded_by);
String uploadedBy = String.format(Locale.getDefault(), uploadedByTemplate, item.getCreator());
author.setText(uploadedBy);
} else {
author.setVisibility(View.GONE);
}
}
}

View file

@ -1,98 +0,0 @@
package fr.free.nrw.commons.depictions.Media;
import android.widget.ListAdapter;
import java.util.List;
import fr.free.nrw.commons.BasePresenter;
import fr.free.nrw.commons.Media;
/**
* Contract with which DepictedImagesFragment and its presenter will talk to each other
*/
public interface DepictedImagesContract {
interface View {
/**
* Handles the UI updates for no internet scenario
*/
void handleNoInternet();
/**
* Handles the UI updates for a error scenario
*/
void initErrorView();
/**
* Initializes the adapter with a list of Media objects
*
* @param mediaList List of new Media to be displayed
*/
void setAdapter(List<Media> mediaList);
/**
* Display snackbar
*/
void showSnackBar();
/**
* Inform the view that there are no more items to be loaded for this search query
* or reset the isLastPage for the current query
* @param isLastPage
*/
void setIsLastPage(boolean isLastPage);
/**
* Set visibility of progressbar depending on the boolean value
*/
void progressBarVisible(Boolean value);
/**
* It return an instance of gridView adapter which helps in extracting media details
* used by the gridView
*
* @return GridView Adapter
*/
ListAdapter getAdapter();
/**
* adds list to adapter
*/
void addItemsToAdapter(List<Media> media);
/**
* Sets loading status depending on the boolean value
*/
void setLoadingStatus(Boolean value);
/**
* 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
*/
void handleSuccess(List<Media> collection);
}
interface UserActionListener extends BasePresenter<View> {
/**
* Checks for internet connection and then initializes the grid view with first 10 images of that depiction
*/
void initList(String entityId);
/**
* Fetches more images for the item and adds it to the grid view adapter
* @param entityId
*/
void fetchMoreImages(String entityId);
/**
* add items to query list
*/
void addItemsToQueryList(List<Media> collection);
}
}

View file

@ -0,0 +1,12 @@
package fr.free.nrw.commons.depictions.Media
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.explore.SearchFragmentContract
/**
* Contract with which DepictedImagesFragment and its presenter will talk to each other
*/
interface DepictedImagesContract {
interface View : SearchFragmentContract.View<Media>
interface Presenter : SearchFragmentContract.Presenter<Media>
}

View file

@ -1,249 +0,0 @@
package fr.free.nrw.commons.depictions.Media;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
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.NonNull;
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.depictions.GridViewAdapter;
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity;
import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import java.util.List;
import javax.inject.Inject;
import timber.log.Timber;
/**
* Fragment for showing image list after selected an item from SearchActivity In Explore
*/
public class DepictedImagesFragment extends DaggerFragment implements DepictedImagesContract.View {
public static final String PAGE_ID_PREFIX = "M";
@BindView(R.id.statusMessage)
TextView statusTextView;
@BindView(R.id.loadingImagesProgressBar)
ProgressBar progressBar;
@BindView(R.id.depicts_image_list)
GridView gridView;
@BindView(R.id.parentLayout)
RelativeLayout parentLayout;
@Inject
DepictedImagesPresenter presenter;
private GridViewAdapter gridAdapter;
private String entityId = null;
private boolean isLastPage;
private boolean isLoading = true;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_depict_image, container, false);
ButterKnife.bind(this, v);
presenter.onAttachView(this);
return v;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
gridView.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());
initViews();
}
/**
* Initializes the UI elements for the fragment
* Setup the grid view to and scroll listener for it
*/
private void initViews() {
String depictsName = getArguments().getString("wikidataItemName");
entityId = getArguments().getString("entityId");
if (getArguments() != null && depictsName != null) {
initList();
setScrollListener();
}
}
private void initList() {
presenter.initList(entityId);
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
handleNoInternet();
} else {
presenter.initList(entityId);
}
}
/**
* Handles the UI updates for no internet scenario
*/
@Override
public 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);
}
}
/**
* Handles the UI updates for a error scenario
*/
@Override
public 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);
}
}
/**
* Sets the scroll listener for the grid view so that more images are fetched when the user scrolls down
* Checks if the item 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 (!isLastPage && !isLoading && (firstVisibleItem + visibleItemCount >= totalItemCount)) {
isLoading = true;
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
handleNoInternet();
} else {
presenter.fetchMoreImages(entityId);
}
}
if (isLastPage) {
progressBar.setVisibility(GONE);
}
}
});
}
/**
* Display snackbar
*/
@Override
public void showSnackBar() {
ViewUtil.showShortSnackbar(parentLayout, R.string.error_loading_images);
}
/**
* Set visibility of progressbar depending on the boolean value
*/
@Override
public void progressBarVisible(Boolean value) {
if (value) {
progressBar.setVisibility(VISIBLE);
} else {
progressBar.setVisibility(GONE);
}
}
/**
* It return an instance of gridView adapter which helps in extracting media details
* used by the gridView
*
* @return GridView Adapter
*/
@Override
public ListAdapter getAdapter() {
return gridAdapter;
}
/**
* Initializes the adapter with a list of Media objects
*
* @param mediaList List of new Media to be displayed
*/
@Override
public void setAdapter(List<Media> mediaList) {
gridAdapter = new fr.free.nrw.commons.depictions.GridViewAdapter(getContext(), R.layout.layout_depict_image, mediaList);
gridView.setAdapter(gridAdapter);
}
/**
* adds list to adapter
*/
@Override
public void addItemsToAdapter(List<Media> media) {
gridAdapter.addAll(media);
gridAdapter.notifyDataSetChanged();
}
/**
* Sets loading status depending on the boolean value
*/
@Override
public void setLoadingStatus(Boolean value) {
if (!value) {
statusTextView.setVisibility(GONE);
}
isLoading = value;
}
/**
* Inform the view that there are no more items to be loaded for this search query
* or reset the isLastPage for the current query
* @param isLastPage
*/
@Override
public void setIsLastPage(boolean isLastPage) {
this.isLastPage=isLastPage;
progressBar.setVisibility(GONE);
}
/**
* 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
*/
@Override
public void handleSuccess(List<Media> collection) {
presenter.addItemsToQueryList(collection);
if (gridAdapter == null) {
setAdapter(collection);
} else {
if (gridAdapter.containsAll(collection)) {
return;
}
gridAdapter.addItems(collection);
try {
((WikidataItemDetailsActivity) getContext()).viewPagerNotifyDataSetChanged();
} catch (RuntimeException e) {
Timber.e(e);
}
}
progressBar.setVisibility(GONE);
isLoading = false;
statusTextView.setVisibility(GONE);
}
}

View file

@ -0,0 +1,28 @@
package fr.free.nrw.commons.depictions.Media
import android.os.Bundle
import android.view.View
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity
import fr.free.nrw.commons.explore.media.PageableMediaFragment
import javax.inject.Inject
class DepictedImagesFragment : PageableMediaFragment(), DepictedImagesContract.View {
@Inject
lateinit var presenter: DepictedImagesContract.Presenter
override val injectedPresenter: DepictedImagesContract.Presenter
get() = presenter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
injectedPresenter.onQueryUpdated(arguments!!.getString("entityId")!!)
}
override fun onItemClicked(position: Int) {
(activity as WikidataItemDetailsActivity).onMediaClicked(position)
}
override fun notifyViewPager() {
(activity as WikidataItemDetailsActivity).viewPagerNotifyDataSetChanged()
}
}

View file

@ -1,144 +0,0 @@
package fr.free.nrw.commons.depictions.Media;
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
import android.annotation.SuppressLint;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.media.MediaClient;
import io.reactivex.Scheduler;
import io.reactivex.disposables.CompositeDisposable;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
/**
* Presenter for DepictedImagesFragment
*/
public class DepictedImagesPresenter implements DepictedImagesContract.UserActionListener {
private static final DepictedImagesContract.View DUMMY = (DepictedImagesContract.View) Proxy
.newProxyInstance(
DepictedImagesContract.View.class.getClassLoader(),
new Class[]{DepictedImagesContract.View.class},
(proxy, method, methodArgs) -> null);
MediaClient mediaClient;
@Named("default_preferences")
JsonKvStore depictionKvStore;
private final Scheduler ioScheduler;
private final Scheduler mainThreadScheduler;
private DepictedImagesContract.View view = DUMMY;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
/**
* Wikibase enitityId for the depicted Item
* Ex: Q9394
*/
private List<Media> queryList = new ArrayList<>();
@Inject
public DepictedImagesPresenter(@Named("default_preferences") JsonKvStore depictionKvStore,
MediaClient mediaClient,
@Named(IO_THREAD) Scheduler ioScheduler,
@Named(MAIN_THREAD) Scheduler mainThreadScheduler) {
this.depictionKvStore = depictionKvStore;
this.ioScheduler = ioScheduler;
this.mainThreadScheduler = mainThreadScheduler;
this.mediaClient = mediaClient;
}
@Override
public void onAttachView(DepictedImagesContract.View view) {
this.view = view;
}
@Override
public void onDetachView() {
this.view = DUMMY;
}
/**
* Checks for internet connection and then initializes the grid view with first 10 images of that depiction
*/
@SuppressLint("CheckResult")
@Override
public void initList(String entityId) {
view.setLoadingStatus(true);
view.progressBarVisible(true);
view.setIsLastPage(false);
compositeDisposable.add(mediaClient.fetchImagesForDepictedItem(entityId, 0)
.subscribeOn(ioScheduler)
.observeOn(mainThreadScheduler)
.subscribe(this::handleSuccess, this::handleError));
}
/**
* Fetches more images for the item and adds it to the grid view adapter
* @param entityId
*/
@SuppressLint("CheckResult")
@Override
public void fetchMoreImages(String entityId) {
view.progressBarVisible(true);
compositeDisposable.add(mediaClient.fetchImagesForDepictedItem(entityId, queryList.size())
.subscribeOn(ioScheduler)
.observeOn(mainThreadScheduler)
.subscribe(this::handlePaginationSuccess, this::handleError));
}
/**
* Handles the success scenario
* it initializes the recycler view by adding items to the adapter
*/
private void handlePaginationSuccess(List<Media> media) {
queryList.addAll(media);
view.progressBarVisible(false);
view.addItemsToAdapter(media);
}
/**
* Logs and handles API error scenario
*
* @param throwable
*/
public void handleError(Throwable throwable) {
Timber.e(throwable, "Error occurred while loading images inside items");
try {
view.initErrorView();
view.showSnackBar();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 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
*/
public void handleSuccess(List<Media> collection) {
if (collection == null || collection.isEmpty()) {
if (queryList.isEmpty()) {
view.initErrorView();
} else {
view.setIsLastPage(true);
}
} else {
this.queryList.addAll(collection);
view.handleSuccess(collection);
}
}
/**
* add items to query list
*/
@Override
public void addItemsToQueryList(List<Media> collection) {
queryList.addAll(collection);
}
}

View file

@ -0,0 +1,17 @@
package fr.free.nrw.commons.depictions.Media
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.di.CommonsApplicationModule
import fr.free.nrw.commons.explore.BaseSearchPresenter
import io.reactivex.Scheduler
import javax.inject.Inject
import javax.inject.Named
/**
* Presenter for DepictedImagesFragment
*/
class DepictedImagesPresenter @Inject constructor(
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
dataSourceFactory: PageableDepictedMediaDataSource
) : BaseSearchPresenter<Media>(mainThreadScheduler, dataSourceFactory),
DepictedImagesContract.Presenter

View file

@ -0,0 +1,17 @@
package fr.free.nrw.commons.depictions.Media
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.explore.LiveDataConverter
import fr.free.nrw.commons.explore.PageableDataSource
import fr.free.nrw.commons.explore.depictions.LoadFunction
import fr.free.nrw.commons.media.MediaClient
import javax.inject.Inject
class PageableDepictedMediaDataSource @Inject constructor(
liveDataConverter: LiveDataConverter,
private val mediaClient: MediaClient
) : PageableDataSource<Media>(liveDataConverter) {
override val loadFunction: LoadFunction<Media> = { loadSize: Int, startPosition: Int ->
mediaClient.fetchImagesForDepictedItem(query, loadSize, startPosition).blockingGet()
}
}

View file

@ -4,20 +4,13 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
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.depictions.Media.DepictedImagesFragment;
@ -26,11 +19,13 @@ import fr.free.nrw.commons.explore.ViewPagerAdapter;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import java.util.ArrayList;
import java.util.List;
/**
* Activity to show depiction media, parent classes and child classes of depicted items in Explore
*/
public class WikidataItemDetailsActivity extends NavigationBaseActivity implements MediaDetailPagerFragment.MediaDetailProvider, AdapterView.OnItemClickListener {
public class WikidataItemDetailsActivity extends NavigationBaseActivity implements MediaDetailPagerFragment.MediaDetailProvider {
private FragmentManager supportFragmentManager;
private DepictedImagesFragment depictionImagesListFragment;
private MediaDetailPagerFragment mediaDetailPagerFragment;
@ -121,11 +116,11 @@ public class WikidataItemDetailsActivity extends NavigationBaseActivity implemen
}
/**
* Shows media detail fragment when user clicks on any image in the list
*/
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
public void onMediaClicked(int position) {
tabLayout.setVisibility(View.GONE);
viewPager.setVisibility(View.GONE);
mediaContainer.setVisibility(View.VISIBLE);
@ -152,12 +147,7 @@ public class WikidataItemDetailsActivity extends NavigationBaseActivity implemen
*/
@Override
public Media getMediaAtPosition(int i) {
if (depictionImagesListFragment.getAdapter() == null) {
// not yet ready to return data
return null;
} else {
return (Media) depictionImagesListFragment.getAdapter().getItem(i);
}
return depictionImagesListFragment.getImageAtPosition(i);
}
/**
@ -182,10 +172,7 @@ public class WikidataItemDetailsActivity extends NavigationBaseActivity implemen
*/
@Override
public int getTotalMediaCount() {
if (depictionImagesListFragment.getAdapter() == null) {
return 0;
}
return depictionImagesListFragment.getAdapter().getCount();
return depictionImagesListFragment.getTotalImagesCount();
}
/**

View file

@ -24,7 +24,6 @@ abstract class BaseSearchFragment<T> : CommonsDaggerSupportFragment(),
abstract val pagedListAdapter: PagedListAdapter<T, *>
abstract val injectedPresenter: SearchFragmentContract.Presenter<T>
abstract val emptyTemplateTextId: Int
abstract val errorTextId: Int
private val loadingAdapter by lazy { FooterAdapter { injectedPresenter.retryFailedRequest() } }
private val mergeAdapter by lazy { MergeAdapter(pagedListAdapter, loadingAdapter) }
@ -60,7 +59,6 @@ abstract class BaseSearchFragment<T> : CommonsDaggerSupportFragment(),
injectedPresenter.onAttachView(this)
}
override fun onDetach() {
super.onDetach()
injectedPresenter.onDetachView()
@ -83,10 +81,12 @@ abstract class BaseSearchFragment<T> : CommonsDaggerSupportFragment(),
}
override fun showEmptyText(query: String) {
contentNotFound.text = getString(emptyTemplateTextId, query)
contentNotFound.text = getEmptyText(query)
contentNotFound.visibility = View.VISIBLE
}
abstract fun getEmptyText(query: String):String
override fun hideEmptyText() {
contentNotFound.visibility = View.GONE
}

View file

@ -269,9 +269,7 @@ public class SearchActivity extends NavigationBaseActivity
*/
@Override
public void requestMoreImages() {
if (searchMediaFragment!=null){
searchMediaFragment.requestMoreImages();
}
//unneeded
}
@Override protected void onDestroy() {

View file

@ -14,18 +14,14 @@ import fr.free.nrw.commons.explore.media.SearchMediaFragmentPresenter
@Module
abstract class SearchModule {
@Binds
abstract fun bindsSearchDepictionsFragmentPresenter(
presenter: SearchDepictionsFragmentPresenter
): SearchDepictionsFragmentContract.Presenter
abstract fun SearchDepictionsFragmentPresenter.bindsSearchDepictionsFragmentPresenter()
: SearchDepictionsFragmentContract.Presenter
@Binds
abstract fun bindsSearchCategoriesFragmentPresenter(
presenter: SearchCategoriesFragmentPresenter
): SearchCategoriesFragmentContract.Presenter
abstract fun SearchCategoriesFragmentPresenter.bindsSearchCategoriesFragmentPresenter()
: SearchCategoriesFragmentContract.Presenter
@Binds
abstract fun bindsSearchMediaFragmentPresenter(
presenter: SearchMediaFragmentPresenter
): SearchMediaFragmentContract.Presenter
abstract fun SearchMediaFragmentPresenter.bindsSearchMediaFragmentPresenter()
: SearchMediaFragmentContract.Presenter
}

View file

@ -13,8 +13,6 @@ class SearchCategoryFragment : BaseSearchFragment<String>() {
@Inject
lateinit var presenter: SearchCategoriesFragmentContract.Presenter
override val emptyTemplateTextId: Int = R.string.categories_not_found
override val errorTextId: Int = R.string.error_loading_categories
override val injectedPresenter: SearchFragmentContract.Presenter<String>
@ -23,4 +21,6 @@ class SearchCategoryFragment : BaseSearchFragment<String>() {
override val pagedListAdapter by lazy {
PagedSearchCategoriesAdapter { CategoryDetailsActivity.startYourself(context, it) }
}
override fun getEmptyText(query: String) = getString(R.string.categories_not_found, query)
}

View file

@ -14,8 +14,6 @@ class SearchDepictionsFragment : BaseSearchFragment<DepictedItem>(),
@Inject
lateinit var presenter: SearchDepictionsFragmentContract.Presenter
override val emptyTemplateTextId: Int = R.string.depictions_not_found
override val errorTextId: Int = R.string.error_loading_depictions
override val injectedPresenter: SearchDepictionsFragmentContract.Presenter
@ -24,4 +22,6 @@ class SearchDepictionsFragment : BaseSearchFragment<DepictedItem>(),
override val pagedListAdapter by lazy {
DepictionAdapter { WikidataItemDetailsActivity.startYourself(context, it) }
}
override fun getEmptyText(query: String) = getString(R.string.depictions_not_found, query)
}

View file

@ -0,0 +1,42 @@
package fr.free.nrw.commons.explore.media
import android.os.Bundle
import android.view.View
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R
import fr.free.nrw.commons.explore.BaseSearchFragment
import kotlinx.android.synthetic.main.fragment_search_paginated.*
abstract class PageableMediaFragment : BaseSearchFragment<Media>() {
override val pagedListAdapter by lazy { PagedMediaAdapter(::onItemClicked) }
override val errorTextId: Int = R.string.error_loading_images
override fun getEmptyText(query: String) = getString(R.string.no_images_found)
protected abstract fun onItemClicked(position: Int)
protected abstract fun notifyViewPager()
private val simpleDataObserver = SimpleDataObserver { notifyViewPager() }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
pagedListAdapter.registerAdapterDataObserver(simpleDataObserver)
}
override fun onDestroyView() {
super.onDestroyView()
pagedListAdapter.unregisterAdapterDataObserver(simpleDataObserver)
}
fun getImageAtPosition(position: Int): Media? =
pagedListAdapter.currentList?.get(position)?.takeIf { it.filename != null }
.also {
pagedListAdapter.currentList?.loadAround(position)
paginatedSearchResultsList.scrollToPosition(position)
}
fun getTotalImagesCount(): Int = pagedListAdapter.itemCount
}

View file

@ -10,7 +10,7 @@ import fr.free.nrw.commons.explore.BaseViewHolder
import fr.free.nrw.commons.explore.inflate
import kotlinx.android.synthetic.main.layout_category_images.*
class SearchImagesAdapter(private val onImageClicked: (Int) -> Unit) :
class PagedMediaAdapter(private val onImageClicked: (Int) -> Unit) :
PagedListAdapter<Media, SearchImagesViewHolder>(object : DiffUtil.ItemCallback<Media>() {
override fun areItemsTheSame(oldItem: Media, newItem: Media) =
oldItem.pageId == newItem.pageId

View file

@ -1,57 +1,26 @@
package fr.free.nrw.commons.explore.media
import android.os.Bundle
import android.view.View
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R
import fr.free.nrw.commons.explore.BaseSearchFragment
import fr.free.nrw.commons.category.CategoryImagesCallback
import fr.free.nrw.commons.explore.SearchActivity
import javax.inject.Inject
/**
* Displays the image search screen.
*/
class SearchMediaFragment : BaseSearchFragment<Media>(), SearchMediaFragmentContract.View {
class SearchMediaFragment : PageableMediaFragment(), SearchMediaFragmentContract.View {
@Inject
lateinit var presenter: SearchMediaFragmentContract.Presenter
override val emptyTemplateTextId: Int = R.string.depictions_not_found
override val errorTextId: Int = R.string.error_loading_images
override val injectedPresenter: SearchMediaFragmentContract.Presenter
get() = presenter
override val pagedListAdapter by lazy {
SearchImagesAdapter {
(context as SearchActivity?)!!.onSearchImageClicked(it)
}
override fun onItemClicked(position: Int) {
(context as SearchActivity?)!!.onSearchImageClicked(position)
}
private val simpleDataObserver = SimpleDataObserver { notifyViewPager() }
fun requestMoreImages() {
// This functionality is replaced by a dataSetObserver and by using loadAround
override fun notifyViewPager() {
(activity as CategoryImagesCallback).viewPagerNotifyDataSetChanged()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
pagedListAdapter.registerAdapterDataObserver(simpleDataObserver)
}
override fun onDestroyView() {
super.onDestroyView()
pagedListAdapter.unregisterAdapterDataObserver(simpleDataObserver)
}
private fun notifyViewPager() {
(activity as SearchActivity).viewPagerNotifyDataSetChanged()
}
fun getImageAtPosition(position: Int): Media? =
pagedListAdapter.currentList?.get(position)?.takeIf { it.filename != null }
.also { pagedListAdapter.currentList?.loadAround(position) }
fun getTotalImagesCount(): Int = pagedListAdapter.itemCount
}

View file

@ -2,7 +2,6 @@ package fr.free.nrw.commons.media
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX
import fr.free.nrw.commons.explore.media.MediaConverter
import fr.free.nrw.commons.utils.CommonsDateUtil
import io.reactivex.Single
@ -13,6 +12,8 @@ import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
const val PAGE_ID_PREFIX = "M"
/**
* Media Client to handle custom calls to Commons MediaWiki APIs
*/
@ -104,11 +105,18 @@ class MediaClient @Inject constructor(
/**
* @return list of images for a particular depict entity
*/
fun fetchImagesForDepictedItem(query: String, sroffset: Int): Single<List<Media>> {
return responseToMediaList(mediaInterface.fetchImagesForDepictedItem(
fun fetchImagesForDepictedItem(
query: String,
srlimit: Int,
sroffset: Int
): Single<List<Media>> {
return responseToMediaList(
mediaInterface.fetchImagesForDepictedItem(
"haswbstatement:" + BuildConfig.DEPICTS_PROPERTY + "=" + query,
srlimit.toString(),
sroffset.toString()
))
)
)
}

View file

@ -117,14 +117,14 @@ public interface MediaInterface {
/**
* Fetches list of images from a depiction entity
*
* @param query depictionEntityId
* @param srlimit the number of items to fetch
* @param sroffset number od depictions already fetched, this is useful in implementing pagination
*/
@GET("w/api.php?action=query&format=json&formatversion=2" + //Basic parameters
"&generator=search&gsrnamespace=6" + //Search parameters
MEDIA_PARAMS)
Single<MwQueryResponse> fetchImagesForDepictedItem(@Query("gsrsearch") String query, @Query("gsroffset") String sroffset);
Single<MwQueryResponse> fetchImagesForDepictedItem(@Query("gsrsearch") String query,
@Query("gsrlimit")String srlimit, @Query("gsroffset") String sroffset);
}

View file

@ -1,6 +1,6 @@
package fr.free.nrw.commons.wikidata;
import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX;
import static fr.free.nrw.commons.media.MediaClientKt.PAGE_ID_PREFIX;
import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF;
import fr.free.nrw.commons.upload.UploadResult;

View file

@ -1,6 +1,7 @@
package fr.free.nrw.commons.wikidata;
import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX;
import static fr.free.nrw.commons.media.MediaClientKt.PAGE_ID_PREFIX;
import android.annotation.SuppressLint;
import android.content.Context;

View file

@ -1,40 +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:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/mediaContainer"
android:background="?attr/mainBackground"
android:orientation="vertical">
<TextView
android:id="@+id/statusMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_gravity="center"
android:text="@string/waiting_first_sync"
android:visibility="gone"
tools:visibility="visible" />
<ProgressBar
android:id="@+id/loadingImagesProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone" />
<GridView
android:id="@+id/depicts_image_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:columnWidth="240dp"
android:fadingEdge="none"
android:fastScrollEnabled="true"
android:listSelector="@null"
android:numColumns="auto_fit"
android:stretchMode="columnWidth" />
</RelativeLayout>

View file

@ -1,61 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="2dp"
android:paddingBottom="0dp">
<TextView
android:id="@+id/depict_images_sequence_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:textColor="#33FFFFFF"
android:textSize="98sp"
android:typeface="serif" />
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/depict_image_view"
android:layout_width="match_parent"
android:layout_height="240dp"
app:actualImageScaleType="centerCrop" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center|bottom"
android:background="#AA000000"
android:orientation="vertical"
android:padding="@dimen/small_gap">
<ProgressBar
android:id="@+id/depict_progress"
style="@style/ProgressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminateOnly="false"
android:max="100"
android:visibility="gone" />
<TextView
android:id="@+id/depict_image_title"
style="?android:textAppearanceLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="#FFFFFFFF" />
<TextView
android:id="@+id/depict_image_author"
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="#FFFFFFFF" />
</LinearLayout>
</FrameLayout>

View file

@ -1,63 +0,0 @@
package fr.free.nrw.commons.depictions
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.depictions.Media.DepictedImagesFragment
import fr.free.nrw.commons.depictions.Media.DepictedImagesPresenter
import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.media.MediaClient
import io.reactivex.Single
import io.reactivex.schedulers.TestScheduler
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
class DepictedImagesPresenterTest {
@Mock
internal lateinit var view: DepictedImagesFragment
lateinit var depictedImagesPresenter: DepictedImagesPresenter
@Mock
lateinit var jsonKvStore: JsonKvStore
@Mock
lateinit var mediaClient: MediaClient
lateinit var testScheduler: TestScheduler
val mediaList: ArrayList<Media> = ArrayList()
@Mock
lateinit var mediaItem: Media
var testSingle: Single<List<Media>>? = null
@Before
@Throws(Exception::class)
fun setUp() {
MockitoAnnotations.initMocks(this)
testScheduler = TestScheduler()
mediaList.add(mediaItem)
testSingle = Single.just(mediaList)
depictedImagesPresenter = DepictedImagesPresenter(jsonKvStore,
mediaClient, testScheduler, testScheduler)
depictedImagesPresenter.onAttachView(view)
}
@Test
fun initList() {
Mockito.`when`(
mediaClient.fetchImagesForDepictedItem(ArgumentMatchers.anyString(),
ArgumentMatchers.anyInt())
).thenReturn(testSingle)
depictedImagesPresenter.initList("rabbit")
depictedImagesPresenter.handleSuccess(mediaList)
verify(view)?.handleSuccess(mediaList)
}
}

View file

@ -0,0 +1,21 @@
package fr.free.nrw.commons.depictions.Media
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.media.MediaClient
import io.reactivex.Single
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.Test
class PageableDepictedMediaDataSourceTest{
@Test
fun `loadFunction loads Media`() {
val mediaClient = mock<MediaClient>()
whenever(mediaClient.fetchImagesForDepictedItem("test",0,1))
.thenReturn(Single.just(emptyList()))
val pageableDepictedMediaDataSource = PageableDepictedMediaDataSource(mock(), mediaClient)
pageableDepictedMediaDataSource.onQueryUpdated("test")
assertThat(pageableDepictedMediaDataSource.loadFunction(0,1), `is`(emptyList()))
}
}