mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
#3818 Convert SubDepictionListFragment to use Pagination - replace SubDepictionList with Child and Parent Fragments - replace contracts with simple presenter declarations - move classes to appropriate packages - delete unused network models - delete duplicated paging classes
This commit is contained in:
parent
8318f78a71
commit
e5e110eafa
80 changed files with 476 additions and 1467 deletions
|
|
@ -5,7 +5,7 @@ import androidx.test.runner.AndroidJUnit4
|
|||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import fr.free.nrw.commons.explore.categories.ExploreActivity
|
||||
import fr.free.nrw.commons.explore.ExploreActivity
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExploreActivityTest {
|
||||
|
|
|
|||
|
|
@ -118,12 +118,12 @@
|
|||
android:parentActivityName=".contributions.MainActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".depictions.WikidataItemDetailsActivity"
|
||||
android:name=".explore.depictions.WikidataItemDetailsActivity"
|
||||
android:label="@string/title_activity_featured_images"
|
||||
android:parentActivityName=".contributions.MainActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".explore.categories.ExploreActivity"
|
||||
android:name=".explore.ExploreActivity"
|
||||
android:label="@string/title_activity_explore"
|
||||
android:parentActivityName=".contributions.MainActivity"
|
||||
android:configChanges="orientation|screenSize|keyboard" />
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ import fr.free.nrw.commons.Utils;
|
|||
import fr.free.nrw.commons.WelcomeActivity;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||
import fr.free.nrw.commons.explore.categories.ExploreActivity;
|
||||
import fr.free.nrw.commons.explore.ExploreActivity;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||
|
|
|
|||
|
|
@ -1,23 +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
|
||||
abstract class DepictionModule {
|
||||
@Binds
|
||||
abstract fun SubDepictionListPresenter.bindsSubDepictionListPresenter()
|
||||
: SubDepictionListContract.UserActionListener
|
||||
|
||||
|
||||
@Binds
|
||||
abstract fun DepictedImagesPresenter.bindsDepictedImagesContractPresenter()
|
||||
: DepictedImagesContract.Presenter
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package fr.free.nrw.commons.depictions.Media
|
||||
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.explore.PagingContract
|
||||
|
||||
/**
|
||||
* Contract with which DepictedImagesFragment and its presenter will talk to each other
|
||||
*/
|
||||
interface DepictedImagesContract {
|
||||
interface View : PagingContract.View<Media>
|
||||
interface Presenter : PagingContract.Presenter<Media>
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
package fr.free.nrw.commons.depictions.models;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Model class for object obtained while parsing depiction response
|
||||
*/
|
||||
public class Continue {
|
||||
|
||||
@SerializedName("sroffset")
|
||||
@Expose
|
||||
private Integer sroffset;
|
||||
@SerializedName("continue")
|
||||
@Expose
|
||||
private String _continue;
|
||||
|
||||
/**
|
||||
* No args constructor for use in serialization
|
||||
*
|
||||
*/
|
||||
public Continue() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param sroffset
|
||||
* @param _continue
|
||||
*/
|
||||
public Continue(Integer sroffset, String _continue) {
|
||||
super();
|
||||
this.sroffset = sroffset;
|
||||
this._continue = _continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets sroffset from Continue object
|
||||
*/
|
||||
public Integer getSroffset() {
|
||||
return sroffset;
|
||||
}
|
||||
|
||||
public void setSroffset(Integer sroffset) {
|
||||
this.sroffset = sroffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets continue string from Continue object
|
||||
*/
|
||||
public String getContinue() {
|
||||
return _continue;
|
||||
}
|
||||
|
||||
public void setContinue(String _continue) {
|
||||
this._continue = _continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
package fr.free.nrw.commons.depictions.models;
|
||||
import java.util.List;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Model class for object obtained while parsing depiction response
|
||||
*
|
||||
* the getSearch() function is used to parse media
|
||||
*/
|
||||
public class Query {
|
||||
|
||||
@SerializedName("searchinfo")
|
||||
@Expose
|
||||
private Searchinfo searchinfo;
|
||||
@SerializedName("search")
|
||||
@Expose
|
||||
private List<Search> search = null;
|
||||
|
||||
/**
|
||||
* No args constructor for use in serialization
|
||||
*
|
||||
*/
|
||||
public Query() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param search
|
||||
* @param searchinfo
|
||||
*/
|
||||
public Query(Searchinfo searchinfo, List<Search> search) {
|
||||
super();
|
||||
this.searchinfo = searchinfo;
|
||||
this.search = search;
|
||||
}
|
||||
|
||||
/**
|
||||
* return searchInfo
|
||||
*/
|
||||
public Searchinfo getSearchinfo() {
|
||||
return searchinfo;
|
||||
}
|
||||
|
||||
public void setSearchinfo(Searchinfo searchinfo) {
|
||||
this.searchinfo = searchinfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* the getSearch() function is used to parse media
|
||||
*/
|
||||
public List<Search> getSearch() {
|
||||
return search;
|
||||
}
|
||||
|
||||
public void setSearch(List<Search> search) {
|
||||
this.search = search;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
package fr.free.nrw.commons.depictions.models;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Model class for object obtained while parsing depiction response
|
||||
* this class contains all the details of for the media object
|
||||
*/
|
||||
|
||||
public class Search {
|
||||
|
||||
@SerializedName("ns")
|
||||
@Expose
|
||||
private Integer ns;
|
||||
@SerializedName("title")
|
||||
@Expose
|
||||
private String title;
|
||||
@SerializedName("pageid")
|
||||
@Expose
|
||||
private Integer pageid;
|
||||
@SerializedName("size")
|
||||
@Expose
|
||||
private Integer size;
|
||||
@SerializedName("wordcount")
|
||||
@Expose
|
||||
private Integer wordcount;
|
||||
@SerializedName("snippet")
|
||||
@Expose
|
||||
private String snippet;
|
||||
@SerializedName("timestamp")
|
||||
@Expose
|
||||
private String timestamp;
|
||||
|
||||
/**
|
||||
* No args constructor for use in serialization
|
||||
*
|
||||
*/
|
||||
public Search() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param timestamp
|
||||
* @param title
|
||||
* @param ns
|
||||
* @param snippet
|
||||
* @param wordcount
|
||||
* @param size
|
||||
* @param pageid
|
||||
*/
|
||||
public Search(Integer ns, String title, Integer pageid, Integer size, Integer wordcount, String snippet, String timestamp) {
|
||||
super();
|
||||
this.ns = ns;
|
||||
this.title = title;
|
||||
this.pageid = pageid;
|
||||
this.size = size;
|
||||
this.wordcount = wordcount;
|
||||
this.snippet = snippet;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns ns int from Search object
|
||||
*/
|
||||
public Integer getNs() {
|
||||
return ns;
|
||||
}
|
||||
|
||||
public void setNs(Integer ns) {
|
||||
this.ns = ns;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns title string from Search object
|
||||
*/
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns pageid int from Search object
|
||||
*/
|
||||
public Integer getPageid() {
|
||||
return pageid;
|
||||
}
|
||||
|
||||
public void setPageid(Integer pageid) {
|
||||
this.pageid = pageid;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns size int from Search object
|
||||
*/
|
||||
public Integer getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(Integer size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns wordcount int from Search object
|
||||
*/
|
||||
public Integer getWordcount() {
|
||||
return wordcount;
|
||||
}
|
||||
|
||||
public void setWordcount(Integer wordcount) {
|
||||
this.wordcount = wordcount;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns snippet String from Search object
|
||||
*/
|
||||
public String getSnippet() {
|
||||
return snippet;
|
||||
}
|
||||
|
||||
public void setSnippet(String snippet) {
|
||||
this.snippet = snippet;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns ns int from Search object
|
||||
*/
|
||||
public String getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(String timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package fr.free.nrw.commons.depictions.models;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Model class for object obtained while parsing query object
|
||||
*/
|
||||
|
||||
public class Searchinfo {
|
||||
|
||||
@SerializedName("totalhits")
|
||||
@Expose
|
||||
private Integer totalhits;
|
||||
|
||||
/**
|
||||
* No args constructor for use in serialization
|
||||
*
|
||||
*/
|
||||
public Searchinfo() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param totalhits
|
||||
*/
|
||||
public Searchinfo(Integer totalhits) {
|
||||
super();
|
||||
this.totalhits = totalhits;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns "totalhint" integer in SearchInfo object
|
||||
*/
|
||||
public Integer getTotalhits() {
|
||||
return totalhits;
|
||||
}
|
||||
|
||||
public void setTotalhits(Integer totalhits) {
|
||||
this.totalhits = totalhits;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package fr.free.nrw.commons.depictions.subClass
|
||||
|
||||
import fr.free.nrw.commons.explore.depictions.depictionDelegate
|
||||
import fr.free.nrw.commons.upload.categories.BaseDelegateAdapter
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
|
||||
class SubDepictionAdapter(clickListener: (DepictedItem) -> Unit) :
|
||||
BaseDelegateAdapter<DepictedItem>(
|
||||
depictionDelegate(clickListener),
|
||||
areItemsTheSame = { oldItem, newItem -> oldItem.id == newItem.id }
|
||||
)
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
package fr.free.nrw.commons.depictions.subClass;
|
||||
|
||||
import fr.free.nrw.commons.BasePresenter;
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The contract with which SubDepictionListFragment and its presenter would talk to each other
|
||||
*/
|
||||
public interface SubDepictionListContract {
|
||||
|
||||
interface View {
|
||||
|
||||
void onSuccess(List<DepictedItem> mediaList);
|
||||
|
||||
void initErrorView();
|
||||
|
||||
void showSnackbar();
|
||||
|
||||
void setIsLastPage(boolean b);
|
||||
|
||||
}
|
||||
|
||||
interface UserActionListener extends BasePresenter<View> {
|
||||
|
||||
void initSubDepictionList(String qid, Boolean isParentClass) throws IOException;
|
||||
|
||||
String getQuery();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
package fr.free.nrw.commons.depictions.subClass;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
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.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import dagger.android.support.DaggerFragment;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity;
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import javax.inject.Inject;
|
||||
import kotlin.Unit;
|
||||
|
||||
/**
|
||||
* Fragment for parent classes and child classes of Depicted items in Explore
|
||||
*/
|
||||
public class SubDepictionListFragment extends DaggerFragment implements SubDepictionListContract.View {
|
||||
|
||||
@BindView(R.id.imagesListBox)
|
||||
RecyclerView depictionsRecyclerView;
|
||||
@BindView(R.id.imageSearchInProgress)
|
||||
ProgressBar progressBar;
|
||||
@BindView(R.id.imagesNotFound)
|
||||
TextView depictionNotFound;
|
||||
@BindView(R.id.bottomProgressBar)
|
||||
ProgressBar bottomProgressBar;
|
||||
/**
|
||||
* Keeps a record of whether current instance of the fragment if of SubClass or ParentClass
|
||||
*/
|
||||
private boolean isParentClass = false;
|
||||
private SubDepictionAdapter depictionsAdapter;
|
||||
RecyclerView.LayoutManager layoutManager;
|
||||
/**
|
||||
* Stores entityId for the depiction
|
||||
*/
|
||||
private String entityId;
|
||||
/**
|
||||
* Stores name of the depiction searched
|
||||
*/
|
||||
private String depictsName;
|
||||
|
||||
@Inject SubDepictionListPresenter presenter;
|
||||
|
||||
private void initViews() {
|
||||
if (getArguments() != null) {
|
||||
depictsName = getArguments().getString("wikidataItemName");
|
||||
entityId = getArguments().getString("entityId");
|
||||
isParentClass = getArguments().getBoolean("isParentClass");
|
||||
if (entityId != null) {
|
||||
initList(entityId, isParentClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initList(String qid, Boolean isParentClass) {
|
||||
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
handleNoInternet();
|
||||
} else {
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
try {
|
||||
presenter.initSubDepictionList(qid, isParentClass);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.fragment_browse_image, container, false);
|
||||
ButterKnife.bind(this, v);
|
||||
presenter.onAttachView(this);
|
||||
isParentClass = false;
|
||||
depictionNotFound.setVisibility(GONE);
|
||||
if (getActivity().getResources().getConfiguration().orientation
|
||||
== Configuration.ORIENTATION_PORTRAIT) {
|
||||
layoutManager = new LinearLayoutManager(getContext());
|
||||
} else {
|
||||
layoutManager = new GridLayoutManager(getContext(), 2);
|
||||
}
|
||||
initViews();
|
||||
depictionsRecyclerView.setLayoutManager(layoutManager);
|
||||
depictionsAdapter = new SubDepictionAdapter(depictedItem -> {
|
||||
// Open SubDepiction Details page
|
||||
getActivity().finish();
|
||||
WikidataItemDetailsActivity.startYourself(getContext(), depictedItem);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
depictionsRecyclerView.setAdapter(depictionsAdapter);
|
||||
return v;
|
||||
}
|
||||
|
||||
private void handleNoInternet() {
|
||||
progressBar.setVisibility(GONE);
|
||||
ViewUtil.showShortSnackbar(depictionsRecyclerView, R.string.no_internet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(List<DepictedItem> mediaList) {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
depictionNotFound.setVisibility(GONE);
|
||||
bottomProgressBar.setVisibility(GONE);
|
||||
depictionsAdapter.addAll(mediaList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initErrorView() {
|
||||
progressBar.setVisibility(GONE);
|
||||
bottomProgressBar.setVisibility(GONE);
|
||||
depictionNotFound.setVisibility(VISIBLE);
|
||||
String no_depiction = getString(isParentClass? R.string.no_parent_classes: R.string.no_child_classes);
|
||||
depictionNotFound.setText(String.format(Locale.getDefault(), no_depiction, depictsName));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showSnackbar() {
|
||||
ViewUtil.showShortSnackbar(depictionsRecyclerView, R.string.error_loading_depictions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIsLastPage(boolean b) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
package fr.free.nrw.commons.depictions.subClass;
|
||||
|
||||
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
|
||||
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
||||
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient;
|
||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import java.io.IOException;
|
||||
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 parent classes and child classes of Depicted items in Explore
|
||||
*/
|
||||
public class SubDepictionListPresenter implements SubDepictionListContract.UserActionListener {
|
||||
|
||||
/**
|
||||
* This creates a dynamic proxy instance of the class,
|
||||
* proxy is to control access to the target object
|
||||
* here our target object is the view.
|
||||
* Thus we when onDettach method of fragment is called we replace the binding of view to our object with the proxy instance
|
||||
*/
|
||||
private static final SubDepictionListContract.View DUMMY = (SubDepictionListContract.View) Proxy
|
||||
.newProxyInstance(
|
||||
SubDepictionListContract.View.class.getClassLoader(),
|
||||
new Class[]{SubDepictionListContract.View.class},
|
||||
(proxy, method, methodArgs) -> null);
|
||||
|
||||
private final Scheduler ioScheduler;
|
||||
private final Scheduler mainThreadScheduler;
|
||||
private SubDepictionListContract.View view = DUMMY;
|
||||
RecentSearchesDao recentSearchesDao;
|
||||
/**
|
||||
* Value of the search query
|
||||
*/
|
||||
public String query;
|
||||
protected CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
DepictsClient depictsClient;
|
||||
private List<DepictedItem> queryList = new ArrayList<>();
|
||||
OkHttpJsonApiClient okHttpJsonApiClient;
|
||||
|
||||
@Inject
|
||||
public SubDepictionListPresenter(RecentSearchesDao recentSearchesDao, DepictsClient depictsClient, OkHttpJsonApiClient okHttpJsonApiClient, @Named(IO_THREAD) Scheduler ioScheduler,
|
||||
@Named(MAIN_THREAD) Scheduler mainThreadScheduler) {
|
||||
this.recentSearchesDao = recentSearchesDao;
|
||||
this.ioScheduler = ioScheduler;
|
||||
this.mainThreadScheduler = mainThreadScheduler;
|
||||
this.depictsClient = depictsClient;
|
||||
this.okHttpJsonApiClient = okHttpJsonApiClient;
|
||||
}
|
||||
@Override
|
||||
public void onAttachView(SubDepictionListContract.View view) {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachView() {
|
||||
this.view = DUMMY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSubDepictionList(String qid, Boolean isParentClass) throws IOException {
|
||||
if (isParentClass) {
|
||||
compositeDisposable.add(okHttpJsonApiClient.getParentQIDs(qid)
|
||||
.subscribeOn(ioScheduler)
|
||||
.observeOn(mainThreadScheduler)
|
||||
.subscribe(this::handleSuccess, this::handleError));
|
||||
} else {
|
||||
compositeDisposable.add(okHttpJsonApiClient.getChildQIDs(qid)
|
||||
.subscribeOn(ioScheduler)
|
||||
.observeOn(mainThreadScheduler)
|
||||
.subscribe(this::handleSuccess, this::handleError));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the success scenario
|
||||
* it initializes the recycler view by adding items to the adapter
|
||||
*/
|
||||
public void handleSuccess(List<DepictedItem> mediaList) {
|
||||
if (mediaList == null || mediaList.isEmpty()) {
|
||||
if(queryList.isEmpty()){
|
||||
view.initErrorView();
|
||||
}else{
|
||||
view.setIsLastPage(true);
|
||||
}
|
||||
} else {
|
||||
this.queryList.addAll(mediaList);
|
||||
view.onSuccess(mediaList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs and handles API error scenario
|
||||
*/
|
||||
private void handleError(Throwable throwable) {
|
||||
Timber.e(throwable, "Error occurred while loading queried depictions");
|
||||
view.initErrorView();
|
||||
view.showSnackbar();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -11,9 +11,9 @@ import fr.free.nrw.commons.bookmarks.BookmarksActivity;
|
|||
import fr.free.nrw.commons.category.CategoryDetailsActivity;
|
||||
import fr.free.nrw.commons.category.CategoryImagesActivity;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity;
|
||||
import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity;
|
||||
import fr.free.nrw.commons.explore.SearchActivity;
|
||||
import fr.free.nrw.commons.explore.categories.ExploreActivity;
|
||||
import fr.free.nrw.commons.explore.ExploreActivity;
|
||||
import fr.free.nrw.commons.notification.NotificationActivity;
|
||||
import fr.free.nrw.commons.review.ReviewActivity;
|
||||
import fr.free.nrw.commons.settings.SettingsActivity;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import dagger.android.support.AndroidSupportInjectionModule;
|
|||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.auth.LoginActivity;
|
||||
import fr.free.nrw.commons.contributions.ContributionsModule;
|
||||
import fr.free.nrw.commons.depictions.DepictionModule;
|
||||
import fr.free.nrw.commons.explore.depictions.DepictionModule;
|
||||
import fr.free.nrw.commons.explore.SearchModule;
|
||||
import fr.free.nrw.commons.review.ReviewController;
|
||||
import fr.free.nrw.commons.settings.SettingsFragment;
|
||||
|
|
|
|||
|
|
@ -8,10 +8,11 @@ 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.depictions.Media.DepictedImagesFragment;
|
||||
import fr.free.nrw.commons.depictions.subClass.SubDepictionListFragment;
|
||||
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.depictions.SearchDepictionsFragment;
|
||||
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;
|
||||
import fr.free.nrw.commons.media.MediaDetailFragment;
|
||||
|
|
@ -51,9 +52,6 @@ public abstract class FragmentBuilderModule {
|
|||
@ContributesAndroidInjector
|
||||
abstract DepictedImagesFragment bindDepictedImagesFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract SubDepictionListFragment bindSubDepictionListFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract SubCategoryListFragment bindSubCategoryListFragment();
|
||||
|
||||
|
|
@ -95,4 +93,10 @@ public abstract class FragmentBuilderModule {
|
|||
|
||||
@ContributesAndroidInjector
|
||||
abstract MediaLicenseFragment bindMediaLicenseFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract ParentDepictionsFragment bindParentDepictionsFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract ChildDepictionsFragment bindChildDepictionsFragment();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package fr.free.nrw.commons.explore.categories;
|
||||
package fr.free.nrw.commons.explore;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
|
@ -19,8 +19,6 @@ 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.SearchActivity;
|
||||
import fr.free.nrw.commons.explore.ViewPagerAdapter;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
package fr.free.nrw.commons.explore
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import fr.free.nrw.commons.R
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.list_item_load_more.*
|
||||
|
||||
class FooterAdapter(private val onRefreshClicked: () -> Unit) :
|
||||
ListAdapter<FooterItem, FooterViewHolder>(object :
|
||||
DiffUtil.ItemCallback<FooterItem>() {
|
||||
override fun areItemsTheSame(oldItem: FooterItem, newItem: FooterItem) = oldItem == newItem
|
||||
|
||||
override fun areContentsTheSame(oldItem: FooterItem, newItem: FooterItem) =
|
||||
oldItem == newItem
|
||||
}) {
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return getItem(position).ordinal
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||
when (FooterItem.values()[viewType]) {
|
||||
FooterItem.LoadingItem -> LoadingViewHolder(parent.inflate(R.layout.list_item_progress))
|
||||
FooterItem.RefreshItem -> RefreshViewHolder(
|
||||
parent.inflate(R.layout.list_item_load_more),
|
||||
onRefreshClicked
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: FooterViewHolder, position: Int) {}
|
||||
}
|
||||
|
||||
open class FooterViewHolder(override val containerView: View) :
|
||||
RecyclerView.ViewHolder(containerView),
|
||||
LayoutContainer
|
||||
|
||||
class LoadingViewHolder(containerView: View) : FooterViewHolder(containerView)
|
||||
class RefreshViewHolder(containerView: View, onRefreshClicked: () -> Unit) :
|
||||
FooterViewHolder(containerView) {
|
||||
init {
|
||||
listItemLoadMoreButton.setOnClickListener { onRefreshClicked() }
|
||||
}
|
||||
}
|
||||
|
||||
enum class FooterItem { LoadingItem, RefreshItem }
|
||||
|
||||
fun ViewGroup.inflate(@LayoutRes layoutId: Int, attachToRoot: Boolean = false): View =
|
||||
LayoutInflater.from(context).inflate(layoutId, this, attachToRoot)
|
||||
|
|
@ -20,7 +20,7 @@ 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.depictions.SearchDepictionsFragment;
|
||||
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;
|
||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
package fr.free.nrw.commons.explore
|
||||
|
||||
import fr.free.nrw.commons.di.CommonsApplicationModule
|
||||
import fr.free.nrw.commons.explore.categories.SearchCategoriesFragmentContract
|
||||
import fr.free.nrw.commons.explore.categories.PageableCategoriesDataSource
|
||||
import io.reactivex.Scheduler
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
class SearchCategoriesFragmentPresenter @Inject constructor(
|
||||
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
|
||||
dataSourceFactory: PageableCategoriesDataSource
|
||||
) : BasePagingPresenter<String>(mainThreadScheduler, dataSourceFactory),
|
||||
SearchCategoriesFragmentContract.Presenter
|
||||
|
|
@ -2,11 +2,12 @@ package fr.free.nrw.commons.explore
|
|||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import fr.free.nrw.commons.explore.categories.SearchCategoriesFragmentContract
|
||||
import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragmentContract
|
||||
import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragmentPresenter
|
||||
import fr.free.nrw.commons.explore.media.SearchMediaFragmentContract
|
||||
import fr.free.nrw.commons.explore.categories.SearchCategoriesFragmentPresenter
|
||||
import fr.free.nrw.commons.explore.categories.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
|
||||
import fr.free.nrw.commons.explore.media.SearchMediaFragmentPresenterImpl
|
||||
|
||||
/**
|
||||
* The Dagger Module for explore:depictions related presenters and (some other objects maybe in future)
|
||||
|
|
@ -14,14 +15,14 @@ import fr.free.nrw.commons.explore.media.SearchMediaFragmentPresenter
|
|||
@Module
|
||||
abstract class SearchModule {
|
||||
@Binds
|
||||
abstract fun SearchDepictionsFragmentPresenter.bindsSearchDepictionsFragmentPresenter()
|
||||
: SearchDepictionsFragmentContract.Presenter
|
||||
abstract fun SearchDepictionsFragmentPresenterImpl.bindsSearchDepictionsFragmentPresenter()
|
||||
: SearchDepictionsFragmentPresenter
|
||||
|
||||
@Binds
|
||||
abstract fun SearchCategoriesFragmentPresenter.bindsSearchCategoriesFragmentPresenter()
|
||||
: SearchCategoriesFragmentContract.Presenter
|
||||
abstract fun SearchCategoriesFragmentPresenterImpl.bindsSearchCategoriesFragmentPresenter()
|
||||
: SearchCategoriesFragmentPresenter
|
||||
|
||||
@Binds
|
||||
abstract fun SearchMediaFragmentPresenter.bindsSearchMediaFragmentPresenter()
|
||||
: SearchMediaFragmentContract.Presenter
|
||||
abstract fun SearchMediaFragmentPresenterImpl.bindsSearchMediaFragmentPresenter()
|
||||
: SearchMediaFragmentPresenter
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package fr.free.nrw.commons.explore.categories
|
||||
|
||||
import fr.free.nrw.commons.category.CategoryClient
|
||||
import fr.free.nrw.commons.explore.LiveDataConverter
|
||||
import fr.free.nrw.commons.explore.PageableBaseDataSource
|
||||
import fr.free.nrw.commons.explore.paging.LiveDataConverter
|
||||
import fr.free.nrw.commons.explore.paging.PageableBaseDataSource
|
||||
import javax.inject.Inject
|
||||
|
||||
class PageableCategoriesDataSource @Inject constructor(
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import androidx.paging.PagedListAdapter
|
|||
import androidx.recyclerview.widget.DiffUtil
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.category.CATEGORY_PREFIX
|
||||
import fr.free.nrw.commons.explore.BaseViewHolder
|
||||
import fr.free.nrw.commons.explore.inflate
|
||||
import fr.free.nrw.commons.explore.paging.BaseViewHolder
|
||||
import fr.free.nrw.commons.explore.paging.inflate
|
||||
import kotlinx.android.synthetic.main.item_recent_searches.*
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
package fr.free.nrw.commons.explore.categories
|
||||
|
||||
import fr.free.nrw.commons.explore.PagingContract
|
||||
|
||||
interface SearchCategoriesFragmentContract {
|
||||
interface View : PagingContract.View<String>
|
||||
interface Presenter : PagingContract.Presenter<String>
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package fr.free.nrw.commons.explore.categories
|
||||
|
||||
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 SearchCategoriesFragmentPresenter : PagingContract.Presenter<String>
|
||||
|
||||
class SearchCategoriesFragmentPresenterImpl @Inject constructor(
|
||||
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
|
||||
dataSourceFactory: PageableCategoriesDataSource
|
||||
) : BasePagingPresenter<String>(mainThreadScheduler, dataSourceFactory),
|
||||
SearchCategoriesFragmentPresenter
|
||||
|
|
@ -2,8 +2,7 @@ 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.BasePagingFragment
|
||||
import fr.free.nrw.commons.explore.PagingContract
|
||||
import fr.free.nrw.commons.explore.paging.BasePagingFragment
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
|
@ -11,7 +10,7 @@ import javax.inject.Inject
|
|||
*/
|
||||
class SearchCategoryFragment : BasePagingFragment<String>() {
|
||||
@Inject
|
||||
lateinit var presenter: SearchCategoriesFragmentContract.Presenter
|
||||
lateinit var presenter: SearchCategoriesFragmentPresenter
|
||||
|
||||
override val errorTextId: Int = R.string.error_loading_categories
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import androidx.paging.PagedListAdapter
|
|||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.explore.inflate
|
||||
import fr.free.nrw.commons.explore.paging.inflate
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.item_depictions.*
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import kotlinx.android.synthetic.main.item_depictions.*
|
||||
|
||||
|
||||
fun depictionDelegate(onDepictionClicked: (DepictedItem) -> Unit) =
|
||||
adapterDelegateLayoutContainer<DepictedItem, DepictedItem>(R.layout.item_depictions) {
|
||||
containerView.setOnClickListener { onDepictionClicked(item) }
|
||||
bind {
|
||||
depicts_label.text = item.name
|
||||
description.text = item.description
|
||||
if (item.imageUrl?.isNotBlank() == true) {
|
||||
depicts_image.setImageURI(item.imageUrl)
|
||||
} else {
|
||||
depicts_image.setActualImageResource(R.drawable.ic_wikidata_logo_24dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import fr.free.nrw.commons.explore.depictions.child.ChildDepictionsPresenter
|
||||
import fr.free.nrw.commons.explore.depictions.child.ChildDepictionsPresenterImpl
|
||||
import fr.free.nrw.commons.explore.depictions.media.DepictedImagesPresenter
|
||||
import fr.free.nrw.commons.explore.depictions.media.DepictedImagesPresenterImpl
|
||||
import fr.free.nrw.commons.explore.depictions.parent.ParentDepictionsPresenter
|
||||
import fr.free.nrw.commons.explore.depictions.parent.ParentDepictionsPresenterImpl
|
||||
|
||||
/**
|
||||
* The Dagger Module for explore:depictions related presenters and (some other objects maybe in future)
|
||||
*/
|
||||
@Module
|
||||
abstract class DepictionModule {
|
||||
|
||||
@Binds
|
||||
abstract fun ParentDepictionsPresenterImpl.bindsParentDepictionPresenter()
|
||||
: ParentDepictionsPresenter
|
||||
|
||||
@Binds
|
||||
abstract fun ChildDepictionsPresenterImpl.bindsChildDepictionPresenter()
|
||||
: ChildDepictionsPresenter
|
||||
|
||||
@Binds
|
||||
abstract fun DepictedImagesPresenterImpl.bindsDepictedImagesContractPresenter()
|
||||
: DepictedImagesPresenter
|
||||
}
|
||||
|
|
@ -1,31 +1,21 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import fr.free.nrw.commons.depictions.subClass.models.SparqlResponse
|
||||
import fr.free.nrw.commons.media.MediaInterface
|
||||
import fr.free.nrw.commons.mwapi.SparqlResponse
|
||||
import fr.free.nrw.commons.upload.depicts.DepictsInterface
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import fr.free.nrw.commons.utils.CommonsDateUtil
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import org.wikipedia.wikidata.Entities
|
||||
import java.math.BigInteger
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.text.ParseException
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
const val LARGE_IMAGE_SIZE = "640px"
|
||||
const val THUMB_IMAGE_SIZE = "70px"
|
||||
|
||||
/**
|
||||
* Depicts Client to handle custom calls to Commons Wikibase APIs
|
||||
*/
|
||||
@Singleton
|
||||
class DepictsClient @Inject constructor(
|
||||
private val depictsInterface: DepictsInterface,
|
||||
private val mediaInterface: MediaInterface
|
||||
private val depictsInterface: DepictsInterface
|
||||
) {
|
||||
|
||||
/**
|
||||
|
|
@ -40,10 +30,6 @@ class DepictsClient @Inject constructor(
|
|||
.map { it.entities().values.map(::DepictedItem) }
|
||||
}
|
||||
|
||||
private fun getUrl(title: String): String {
|
||||
return getImageUrl(title, LARGE_IMAGE_SIZE)
|
||||
}
|
||||
|
||||
fun getEntities(ids: String): Single<Entities> {
|
||||
return depictsInterface.getEntities(ids)
|
||||
}
|
||||
|
|
@ -57,63 +43,4 @@ class DepictsClient @Inject constructor(
|
|||
.flatMap { getEntities(it).toObservable() }
|
||||
.map { it.entities().values.map(::DepictedItem) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Get url for the image from media of depictions
|
||||
* Ex: Tiger_Woods
|
||||
* Value: https://upload.wikimedia.org/wikipedia/commons/thumb/6/67/Tiger_Woods.jpg/70px-Tiger_Woods.jpg
|
||||
*/
|
||||
fun getImageUrl(title: String, size: String): String {
|
||||
return title.substringAfter(":")
|
||||
.replace(" ", "_")
|
||||
.let {
|
||||
val MD5Hash = getMd5(it)
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/${MD5Hash[0]}/${MD5Hash[0]}${MD5Hash[1]}/$it/$size-$it"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates MD5 hash for the filename
|
||||
*/
|
||||
fun getMd5(input: String): String {
|
||||
return try {
|
||||
|
||||
// Static getInstance method is called with hashing MD5
|
||||
val md = MessageDigest.getInstance("MD5")
|
||||
|
||||
// digest() method is called to calculate message digest
|
||||
// of an input digest() return array of byte
|
||||
val messageDigest = md.digest(input.toByteArray())
|
||||
|
||||
// Convert byte array into signum representation
|
||||
val no = BigInteger(1, messageDigest)
|
||||
|
||||
// Convert message digest into hex value
|
||||
var hashtext = no.toString(16)
|
||||
while (hashtext.length < 32) {
|
||||
hashtext = "0$hashtext"
|
||||
}
|
||||
hashtext
|
||||
} // For specifying wrong message digest algorithms
|
||||
catch (e: NoSuchAlgorithmException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the date string into the required format
|
||||
* @param dateStr
|
||||
* @return date in the required format
|
||||
*/
|
||||
private fun safeParseDate(dateStr: String): Date? {
|
||||
return try {
|
||||
CommonsDateUtil.getIso8601DateFormatShort().parse(dateStr)
|
||||
} catch (e: ParseException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.explore.paging.BasePagingFragment
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
|
||||
|
||||
abstract class PageableDepictionsFragment : BasePagingFragment<DepictedItem>() {
|
||||
override val errorTextId: Int = R.string.error_loading_depictions
|
||||
override val pagedListAdapter by lazy {
|
||||
DepictionAdapter { WikidataItemDetailsActivity.startYourself(context, it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import androidx.paging.PositionalDataSource
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import timber.log.Timber
|
||||
|
||||
|
||||
data class SearchDepictionsDataSource constructor(
|
||||
private val depictsClient: DepictsClient,
|
||||
private val loadingStates: PublishProcessor<LoadingState>,
|
||||
private val query: String
|
||||
) : PositionalDataSource<DepictedItem>() {
|
||||
|
||||
private var lastExecutedRequest: (() -> Boolean)? = null
|
||||
|
||||
override fun loadInitial(
|
||||
params: LoadInitialParams,
|
||||
callback: LoadInitialCallback<DepictedItem>
|
||||
) {
|
||||
storeAndExecute {
|
||||
loadingStates.offer(LoadingState.InitialLoad)
|
||||
performWithTryCatch {
|
||||
callback.onResult(
|
||||
getItems(query, params.requestedLoadSize, params.requestedStartPosition),
|
||||
params.requestedStartPosition
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<DepictedItem>) {
|
||||
storeAndExecute {
|
||||
loadingStates.offer(LoadingState.Loading)
|
||||
performWithTryCatch {
|
||||
callback.onResult(getItems(query, params.loadSize, params.startPosition))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun retryFailedRequest() {
|
||||
Completable.fromAction { lastExecutedRequest?.invoke() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
private fun getItems(query: String, limit: Int, offset: Int) =
|
||||
depictsClient.searchForDepictions(query, limit, offset).blockingGet()
|
||||
|
||||
private fun storeAndExecute(function: () -> Boolean) {
|
||||
function.also { lastExecutedRequest = it }.invoke()
|
||||
}
|
||||
|
||||
private fun performWithTryCatch(function: () -> Unit) = try {
|
||||
function.invoke()
|
||||
loadingStates.offer(LoadingState.Complete)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
loadingStates.offer(LoadingState.Error)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity
|
||||
import fr.free.nrw.commons.explore.BasePagingFragment
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Display depictions in search fragment
|
||||
*/
|
||||
class SearchDepictionsFragment : BasePagingFragment<DepictedItem>(),
|
||||
SearchDepictionsFragmentContract.View {
|
||||
@Inject
|
||||
lateinit var presenter: SearchDepictionsFragmentContract.Presenter
|
||||
|
||||
override val errorTextId: Int = R.string.error_loading_depictions
|
||||
|
||||
override val injectedPresenter
|
||||
get() = presenter
|
||||
|
||||
override val pagedListAdapter by lazy {
|
||||
DepictionAdapter { WikidataItemDetailsActivity.startYourself(context, it) }
|
||||
}
|
||||
|
||||
override fun getEmptyText(query: String) = getString(R.string.depictions_not_found, query)
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import fr.free.nrw.commons.explore.PagingContract
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
|
||||
/**
|
||||
* The contract with with SearchDepictionsFragment and its presenter would talk to each other
|
||||
*/
|
||||
interface SearchDepictionsFragmentContract {
|
||||
interface View : PagingContract.View<DepictedItem>
|
||||
interface Presenter : PagingContract.Presenter<DepictedItem>
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.Config
|
||||
import androidx.paging.DataSource
|
||||
import androidx.paging.PagedList
|
||||
import androidx.paging.toLiveData
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val PAGE_SIZE = 50
|
||||
private const val INITIAL_LOAD_SIZE = 50
|
||||
|
||||
class SearchableDepictionsDataSourceFactory @Inject constructor(
|
||||
val searchDepictionsDataSourceFactoryFactory: SearchDepictionsDataSourceFactoryFactory,
|
||||
val liveDataConverter: LiveDataConverter
|
||||
) {
|
||||
private val _loadingStates = PublishProcessor.create<LoadingState>()
|
||||
val loadingStates: Flowable<LoadingState> = _loadingStates
|
||||
private val _searchResults = PublishProcessor.create<LiveData<PagedList<DepictedItem>>>()
|
||||
val searchResults: Flowable<LiveData<PagedList<DepictedItem>>> = _searchResults
|
||||
private val _noItemsLoadedEvent = PublishProcessor.create<Unit>()
|
||||
val noItemsLoadedEvent: Flowable<Unit> = _noItemsLoadedEvent
|
||||
|
||||
private var currentFactory: SearchDepictionsDataSourceFactory? = null
|
||||
|
||||
fun onQueryUpdated(query: String) {
|
||||
_searchResults.offer(
|
||||
liveDataConverter.convert(
|
||||
searchDepictionsDataSourceFactoryFactory.create(query, _loadingStates)
|
||||
.also { currentFactory = it }
|
||||
) { _noItemsLoadedEvent.offer(Unit) }
|
||||
)
|
||||
}
|
||||
|
||||
fun retryFailedRequest() {
|
||||
currentFactory?.retryFailedRequest()
|
||||
}
|
||||
}
|
||||
|
||||
class LiveDataConverter @Inject constructor() {
|
||||
fun convert(
|
||||
dataSourceFactory: SearchDepictionsDataSourceFactory,
|
||||
zeroItemsLoadedFunction: () -> Unit
|
||||
): LiveData<PagedList<DepictedItem>> {
|
||||
return dataSourceFactory.toLiveData(
|
||||
Config(
|
||||
pageSize = PAGE_SIZE,
|
||||
initialLoadSizeHint = INITIAL_LOAD_SIZE,
|
||||
enablePlaceholders = false
|
||||
),
|
||||
boundaryCallback = object : PagedList.BoundaryCallback<DepictedItem>() {
|
||||
override fun onZeroItemsLoaded() {
|
||||
zeroItemsLoadedFunction()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface SearchDepictionsDataSourceFactoryFactory {
|
||||
fun create(query: String, loadingStates: PublishProcessor<LoadingState>)
|
||||
: SearchDepictionsDataSourceFactory
|
||||
}
|
||||
|
||||
class SearchDepictionsDataSourceFactory constructor(
|
||||
private val depictsClient: DepictsClient,
|
||||
private val query: String,
|
||||
private val loadingStates: PublishProcessor<LoadingState>
|
||||
) : DataSource.Factory<Int, DepictedItem>() {
|
||||
private var currentDataSource: SearchDepictionsDataSource? = null
|
||||
override fun create() = SearchDepictionsDataSource(depictsClient, loadingStates, query)
|
||||
.also { currentDataSource = it }
|
||||
|
||||
fun retryFailedRequest() {
|
||||
currentDataSource?.retryFailedRequest()
|
||||
}
|
||||
}
|
||||
|
||||
sealed class LoadingState {
|
||||
object InitialLoad : LoadingState()
|
||||
object Loading : LoadingState()
|
||||
object Complete : LoadingState()
|
||||
object Error : LoadingState()
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package fr.free.nrw.commons.depictions;
|
||||
package fr.free.nrw.commons.explore.depictions;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
|
@ -13,8 +13,9 @@ 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;
|
||||
import fr.free.nrw.commons.depictions.subClass.SubDepictionListFragment;
|
||||
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.ViewPagerAdapter;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||
|
|
@ -87,28 +88,23 @@ public class WikidataItemDetailsActivity extends NavigationBaseActivity implemen
|
|||
List<Fragment> fragmentList = new ArrayList<>();
|
||||
List<String> titleList = new ArrayList<>();
|
||||
depictionImagesListFragment = new DepictedImagesFragment();
|
||||
SubDepictionListFragment subDepictionListFragment = new SubDepictionListFragment();
|
||||
SubDepictionListFragment parentDepictionListFragment = new SubDepictionListFragment();
|
||||
ChildDepictionsFragment childDepictionsFragment = new ChildDepictionsFragment();
|
||||
ParentDepictionsFragment parentDepictionsFragment = new ParentDepictionsFragment();
|
||||
wikidataItemName = getIntent().getStringExtra("wikidataItemName");
|
||||
String entityId = getIntent().getStringExtra("entityId");
|
||||
if (getIntent() != null && wikidataItemName != null) {
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString("wikidataItemName", wikidataItemName);
|
||||
arguments.putString("entityId", entityId);
|
||||
arguments.putBoolean("isParentClass", false);
|
||||
depictionImagesListFragment.setArguments(arguments);
|
||||
subDepictionListFragment.setArguments(arguments);
|
||||
Bundle parentClassArguments = new Bundle();
|
||||
parentClassArguments.putString("wikidataItemName", wikidataItemName);
|
||||
parentClassArguments.putString("entityId", entityId);
|
||||
parentClassArguments.putBoolean("isParentClass", true);
|
||||
parentDepictionListFragment.setArguments(parentClassArguments);
|
||||
parentDepictionsFragment.setArguments(arguments);
|
||||
childDepictionsFragment.setArguments(arguments);
|
||||
}
|
||||
fragmentList.add(depictionImagesListFragment);
|
||||
titleList.add(getResources().getString(R.string.title_for_media));
|
||||
fragmentList.add(subDepictionListFragment);
|
||||
fragmentList.add(childDepictionsFragment);
|
||||
titleList.add(getResources().getString(R.string.title_for_child_classes));
|
||||
fragmentList.add(parentDepictionListFragment);
|
||||
fragmentList.add(parentDepictionsFragment);
|
||||
titleList.add(getResources().getString(R.string.title_for_parent_classes));
|
||||
viewPagerAdapter.setTabData(fragmentList, titleList);
|
||||
viewPager.setOffscreenPageLimit(2);
|
||||
|
|
@ -183,7 +179,6 @@ public class WikidataItemDetailsActivity extends NavigationBaseActivity implemen
|
|||
*/
|
||||
public static void startYourself(Context context, DepictedItem depictedItem) {
|
||||
Intent intent = new Intent(context, WikidataItemDetailsActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
intent.putExtra("wikidataItemName", depictedItem.getName());
|
||||
intent.putExtra("entityId", depictedItem.getId());
|
||||
context.startActivity(intent);
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package fr.free.nrw.commons.explore.depictions.child
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.explore.depictions.PageableDepictionsFragment
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class ChildDepictionsFragment: PageableDepictionsFragment() {
|
||||
@Inject
|
||||
lateinit var presenter: ChildDepictionsPresenter
|
||||
|
||||
override val injectedPresenter
|
||||
get() = presenter
|
||||
|
||||
override fun getEmptyText(query: String) =
|
||||
getString(R.string.no_child_classes, arguments!!.getString("wikidataItemName")!!)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
onQueryUpdated(arguments!!.getString("entityId")!!)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package fr.free.nrw.commons.explore.depictions.child
|
||||
|
||||
import fr.free.nrw.commons.di.CommonsApplicationModule
|
||||
import fr.free.nrw.commons.explore.paging.BasePagingPresenter
|
||||
import fr.free.nrw.commons.explore.paging.PagingContract
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import io.reactivex.Scheduler
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
interface ChildDepictionsPresenter : PagingContract.Presenter<DepictedItem>
|
||||
|
||||
class ChildDepictionsPresenterImpl @Inject constructor(
|
||||
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
|
||||
dataSourceFactory: PageableChildDepictionsDataSource
|
||||
) : BasePagingPresenter<DepictedItem>(mainThreadScheduler, dataSourceFactory),
|
||||
ChildDepictionsPresenter
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package fr.free.nrw.commons.explore.depictions.child
|
||||
|
||||
import fr.free.nrw.commons.explore.paging.LiveDataConverter
|
||||
import fr.free.nrw.commons.explore.paging.PageableBaseDataSource
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class PageableChildDepictionsDataSource @Inject constructor(
|
||||
liveDataConverter: LiveDataConverter,
|
||||
private val okHttpJsonApiClient: OkHttpJsonApiClient
|
||||
) : PageableBaseDataSource<DepictedItem>(liveDataConverter) {
|
||||
override val loadFunction = { _: Int, startPosition: Int ->
|
||||
if (startPosition == 0) okHttpJsonApiClient.getChildDepictions(query).blockingFirst()
|
||||
else emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
package fr.free.nrw.commons.depictions.Media
|
||||
package fr.free.nrw.commons.explore.depictions.media
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity
|
||||
import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity
|
||||
import fr.free.nrw.commons.explore.media.PageableMediaFragment
|
||||
import javax.inject.Inject
|
||||
|
||||
class DepictedImagesFragment : PageableMediaFragment(), DepictedImagesContract.View {
|
||||
class DepictedImagesFragment : PageableMediaFragment() {
|
||||
@Inject
|
||||
lateinit var presenter: DepictedImagesContract.Presenter
|
||||
lateinit var presenter: DepictedImagesPresenter
|
||||
|
||||
override val injectedPresenter
|
||||
get() = presenter
|
||||
|
|
@ -1,17 +1,20 @@
|
|||
package fr.free.nrw.commons.depictions.Media
|
||||
package fr.free.nrw.commons.explore.depictions.media
|
||||
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.di.CommonsApplicationModule
|
||||
import fr.free.nrw.commons.explore.BasePagingPresenter
|
||||
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 DepictedImagesPresenter : PagingContract.Presenter<Media>
|
||||
|
||||
/**
|
||||
* Presenter for DepictedImagesFragment
|
||||
*/
|
||||
class DepictedImagesPresenter @Inject constructor(
|
||||
class DepictedImagesPresenterImpl @Inject constructor(
|
||||
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
|
||||
dataSourceFactory: PageableDepictedMediaDataSource
|
||||
) : BasePagingPresenter<Media>(mainThreadScheduler, dataSourceFactory),
|
||||
DepictedImagesContract.Presenter
|
||||
DepictedImagesPresenter
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package fr.free.nrw.commons.depictions.Media
|
||||
package fr.free.nrw.commons.explore.depictions.media
|
||||
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.explore.LiveDataConverter
|
||||
import fr.free.nrw.commons.explore.PageableBaseDataSource
|
||||
import fr.free.nrw.commons.explore.depictions.LoadFunction
|
||||
import fr.free.nrw.commons.explore.paging.LiveDataConverter
|
||||
import fr.free.nrw.commons.explore.paging.PageableBaseDataSource
|
||||
import fr.free.nrw.commons.explore.depictions.search.LoadFunction
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package fr.free.nrw.commons.explore.depictions.parent
|
||||
|
||||
import fr.free.nrw.commons.explore.paging.LiveDataConverter
|
||||
import fr.free.nrw.commons.explore.paging.PageableBaseDataSource
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class PageableParentDepictionsDataSource @Inject constructor(
|
||||
liveDataConverter: LiveDataConverter,
|
||||
private val okHttpJsonApiClient: OkHttpJsonApiClient
|
||||
) : PageableBaseDataSource<DepictedItem>(liveDataConverter) {
|
||||
override val loadFunction = { _: Int, startPosition: Int ->
|
||||
if (startPosition == 0) okHttpJsonApiClient.getParentDepictions(query).blockingFirst()
|
||||
else emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package fr.free.nrw.commons.explore.depictions.parent
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.explore.depictions.PageableDepictionsFragment
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class ParentDepictionsFragment : PageableDepictionsFragment() {
|
||||
@Inject
|
||||
lateinit var presenter: ParentDepictionsPresenter
|
||||
|
||||
override val injectedPresenter
|
||||
get() = presenter
|
||||
|
||||
override fun getEmptyText(query: String) =
|
||||
getString(R.string.no_parent_classes, arguments!!.getString("wikidataItemName")!!)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
onQueryUpdated(arguments!!.getString("entityId")!!)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package fr.free.nrw.commons.explore.depictions.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 fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import io.reactivex.Scheduler
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
interface ParentDepictionsPresenter : PagingContract.Presenter<DepictedItem>
|
||||
|
||||
class ParentDepictionsPresenterImpl @Inject constructor(
|
||||
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
|
||||
dataSourceFactory: PageableParentDepictionsDataSource
|
||||
) : BasePagingPresenter<DepictedItem>(mainThreadScheduler, dataSourceFactory),
|
||||
ParentDepictionsPresenter {
|
||||
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
package fr.free.nrw.commons.explore.depictions.search
|
||||
|
||||
import fr.free.nrw.commons.explore.LiveDataConverter
|
||||
import fr.free.nrw.commons.explore.LoadingState
|
||||
import fr.free.nrw.commons.explore.PageableBaseDataSource
|
||||
import fr.free.nrw.commons.explore.paging.LiveDataConverter
|
||||
import fr.free.nrw.commons.explore.paging.LoadingState
|
||||
import fr.free.nrw.commons.explore.paging.PageableBaseDataSource
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import javax.inject.Inject
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package fr.free.nrw.commons.explore.depictions.search
|
||||
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.explore.depictions.PageableDepictionsFragment
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Display depictions in search fragment
|
||||
*/
|
||||
class SearchDepictionsFragment : PageableDepictionsFragment() {
|
||||
@Inject
|
||||
lateinit var presenter: SearchDepictionsFragmentPresenter
|
||||
|
||||
override val injectedPresenter
|
||||
get() = presenter
|
||||
|
||||
override fun getEmptyText(query: String) = getString(R.string.depictions_not_found, query)
|
||||
}
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
package fr.free.nrw.commons.explore.depictions.search
|
||||
|
||||
import fr.free.nrw.commons.di.CommonsApplicationModule
|
||||
import fr.free.nrw.commons.explore.BasePagingPresenter
|
||||
import fr.free.nrw.commons.explore.paging.BasePagingPresenter
|
||||
import fr.free.nrw.commons.explore.paging.PagingContract
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import io.reactivex.Scheduler
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
interface SearchDepictionsFragmentPresenter : PagingContract.Presenter<DepictedItem>
|
||||
/**
|
||||
* The presenter class for SearchDepictionsFragment
|
||||
*/
|
||||
class SearchDepictionsFragmentPresenter @Inject constructor(
|
||||
class SearchDepictionsFragmentPresenterImpl @Inject constructor(
|
||||
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
|
||||
dataSourceFactory: PageableDepictionsDataSource
|
||||
) : BasePagingPresenter<DepictedItem>(mainThreadScheduler, dataSourceFactory),
|
||||
SearchDepictionsFragmentContract.Presenter
|
||||
SearchDepictionsFragmentPresenter
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package fr.free.nrw.commons.explore.media
|
||||
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.explore.LiveDataConverter
|
||||
import fr.free.nrw.commons.explore.PageableBaseDataSource
|
||||
import fr.free.nrw.commons.explore.depictions.LoadFunction
|
||||
import fr.free.nrw.commons.explore.paging.LiveDataConverter
|
||||
import fr.free.nrw.commons.explore.paging.PageableBaseDataSource
|
||||
import fr.free.nrw.commons.explore.depictions.search.LoadFunction
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ 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.BasePagingFragment
|
||||
import fr.free.nrw.commons.explore.paging.BasePagingFragment
|
||||
import kotlinx.android.synthetic.main.fragment_search_paginated.*
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import androidx.paging.PagedListAdapter
|
|||
import androidx.recyclerview.widget.DiffUtil
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.explore.BaseViewHolder
|
||||
import fr.free.nrw.commons.explore.inflate
|
||||
import fr.free.nrw.commons.explore.paging.BaseViewHolder
|
||||
import fr.free.nrw.commons.explore.paging.inflate
|
||||
import kotlinx.android.synthetic.main.layout_category_images.*
|
||||
|
||||
class PagedMediaAdapter(private val onImageClicked: (Int) -> Unit) :
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@ import javax.inject.Inject
|
|||
/**
|
||||
* Displays the image search screen.
|
||||
*/
|
||||
class SearchMediaFragment : PageableMediaFragment(), SearchMediaFragmentContract.View {
|
||||
class SearchMediaFragment : PageableMediaFragment(){
|
||||
@Inject
|
||||
lateinit var presenter: SearchMediaFragmentContract.Presenter
|
||||
lateinit var presenter: SearchMediaFragmentPresenter
|
||||
|
||||
override val injectedPresenter
|
||||
get() = presenter
|
||||
|
||||
override fun onItemClicked(position: Int) {
|
||||
(context as SearchActivity?)!!.onSearchImageClicked(position)
|
||||
(context as SearchActivity).onSearchImageClicked(position)
|
||||
}
|
||||
|
||||
override fun notifyViewPager() {
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
package fr.free.nrw.commons.explore.media
|
||||
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.explore.PagingContract
|
||||
|
||||
|
||||
interface SearchMediaFragmentContract {
|
||||
interface View : PagingContract.View<Media>
|
||||
interface Presenter : PagingContract.Presenter<Media>
|
||||
}
|
||||
|
|
@ -2,13 +2,16 @@ package fr.free.nrw.commons.explore.media
|
|||
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.di.CommonsApplicationModule
|
||||
import fr.free.nrw.commons.explore.BasePagingPresenter
|
||||
import fr.free.nrw.commons.explore.paging.PagingContract
|
||||
import fr.free.nrw.commons.explore.paging.BasePagingPresenter
|
||||
import io.reactivex.Scheduler
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
class SearchMediaFragmentPresenter @Inject constructor(
|
||||
interface SearchMediaFragmentPresenter : PagingContract.Presenter<Media>
|
||||
|
||||
class SearchMediaFragmentPresenterImpl @Inject constructor(
|
||||
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
|
||||
dataSourceFactory: PageableMediaDataSource
|
||||
) : BasePagingPresenter<Media>(mainThreadScheduler, dataSourceFactory),
|
||||
SearchMediaFragmentContract.Presenter
|
||||
SearchMediaFragmentPresenter
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package fr.free.nrw.commons.explore
|
||||
package fr.free.nrw.commons.explore.paging
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package fr.free.nrw.commons.explore
|
||||
package fr.free.nrw.commons.explore.paging
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import fr.free.nrw.commons.upload.depicts.proxy
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package fr.free.nrw.commons.explore
|
||||
package fr.free.nrw.commons.explore.paging
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
package fr.free.nrw.commons.explore.paging
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
|
@ -26,7 +26,9 @@ class FooterAdapter(private val onRefreshClicked: () -> Unit) :
|
|||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||
when (FooterItem.values()[viewType]) {
|
||||
FooterItem.LoadingItem -> LoadingViewHolder(parent.inflate(R.layout.list_item_progress))
|
||||
FooterItem.LoadingItem -> LoadingViewHolder(
|
||||
parent.inflate(R.layout.list_item_progress)
|
||||
)
|
||||
FooterItem.RefreshItem -> RefreshViewHolder(
|
||||
parent.inflate(R.layout.list_item_load_more),
|
||||
onRefreshClicked
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package fr.free.nrw.commons.explore
|
||||
package fr.free.nrw.commons.explore.paging
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
package fr.free.nrw.commons.explore
|
||||
package fr.free.nrw.commons.explore.paging
|
||||
|
||||
import androidx.paging.PositionalDataSource
|
||||
import fr.free.nrw.commons.explore.depictions.LoadFunction
|
||||
import fr.free.nrw.commons.explore.depictions.search.LoadFunction
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import timber.log.Timber
|
||||
|
||||
abstract class SearchDataSource<T>(
|
||||
abstract class PagingDataSource<T>(
|
||||
private val loadingStates: PublishProcessor<LoadingState>
|
||||
) : PositionalDataSource<T>() {
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ abstract class SearchDataSource<T>(
|
|||
fun <T> dataSource(
|
||||
loadingStates: PublishProcessor<LoadingState>,
|
||||
loadFunction: LoadFunction<T>
|
||||
) = object : SearchDataSource<T>(loadingStates) {
|
||||
) = object : PagingDataSource<T>(loadingStates) {
|
||||
override fun getItems(loadSize: Int, startPosition: Int): List<T> {
|
||||
return loadFunction(loadSize, startPosition)
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package fr.free.nrw.commons.explore
|
||||
package fr.free.nrw.commons.explore.paging
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.Config
|
||||
import androidx.paging.DataSource
|
||||
import androidx.paging.PagedList
|
||||
import androidx.paging.toLiveData
|
||||
import fr.free.nrw.commons.explore.depictions.LoadFunction
|
||||
import fr.free.nrw.commons.explore.depictions.LoadingStates
|
||||
import fr.free.nrw.commons.explore.depictions.search.LoadFunction
|
||||
import fr.free.nrw.commons.explore.depictions.search.LoadingStates
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import javax.inject.Inject
|
||||
|
|
@ -18,7 +18,10 @@ abstract class PageableBaseDataSource<T>(private val liveDataConverter: LiveData
|
|||
|
||||
lateinit var query: String
|
||||
private val dataSourceFactoryFactory: () -> PagingDataSourceFactory<T> = {
|
||||
dataSourceFactory(_loadingStates, loadFunction)
|
||||
dataSourceFactory(
|
||||
_loadingStates,
|
||||
loadFunction
|
||||
)
|
||||
}
|
||||
private val _loadingStates = PublishProcessor.create<LoadingState>()
|
||||
val loadingStates: Flowable<LoadingState> = _loadingStates
|
||||
|
|
@ -67,11 +70,14 @@ class LiveDataConverter @Inject constructor() {
|
|||
|
||||
abstract class PagingDataSourceFactory<T>(val loadingStates: LoadingStates) :
|
||||
DataSource.Factory<Int, T>() {
|
||||
private var currentDataSource: SearchDataSource<T>? = null
|
||||
private var currentDataSource: PagingDataSource<T>? = null
|
||||
abstract val loadFunction: LoadFunction<T>
|
||||
|
||||
override fun create() =
|
||||
dataSource(loadingStates, loadFunction).also { currentDataSource = it }
|
||||
dataSource(
|
||||
loadingStates,
|
||||
loadFunction
|
||||
).also { currentDataSource = it }
|
||||
|
||||
fun retryFailedRequest() {
|
||||
currentDataSource?.retryFailedRequest()
|
||||
|
|
@ -48,7 +48,7 @@ import fr.free.nrw.commons.category.CategoryDetailsActivity;
|
|||
import fr.free.nrw.commons.contributions.ContributionsFragment;
|
||||
import fr.free.nrw.commons.delete.DeleteHelper;
|
||||
import fr.free.nrw.commons.delete.ReasonBuilder;
|
||||
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity;
|
||||
import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.ui.widget.HtmlTextView;
|
||||
import fr.free.nrw.commons.utils.ViewUtilWrapper;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.google.gson.Gson;
|
|||
import fr.free.nrw.commons.achievements.FeaturedImages;
|
||||
import fr.free.nrw.commons.achievements.FeedbackResponse;
|
||||
import fr.free.nrw.commons.campaigns.CampaignResponseDTO;
|
||||
import fr.free.nrw.commons.depictions.subClass.models.SparqlResponse;
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
|
|
@ -210,7 +209,7 @@ public class OkHttpJsonApiClient {
|
|||
* Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
|
||||
* bridge -> suspended bridge, aqueduct, etc
|
||||
*/
|
||||
public Observable<List<DepictedItem>> getChildQIDs(String qid) throws IOException {
|
||||
public Observable<List<DepictedItem>> getChildDepictions(String qid) throws IOException {
|
||||
return depictedItemsFrom(sparqlQuery(qid, "/queries/subclasses_query.rq"));
|
||||
}
|
||||
|
||||
|
|
@ -218,7 +217,7 @@ public class OkHttpJsonApiClient {
|
|||
* Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
|
||||
* bridge -> suspended bridge, aqueduct, etc
|
||||
*/
|
||||
public Observable<List<DepictedItem>> getParentQIDs(String qid) throws IOException {
|
||||
public Observable<List<DepictedItem>> getParentDepictions(String qid) throws IOException {
|
||||
return depictedItemsFrom(sparqlQuery(qid, "/queries/parentclasses_query.rq"));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package fr.free.nrw.commons.depictions.subClass.models
|
||||
package fr.free.nrw.commons.mwapi
|
||||
|
||||
data class SparqlResponse(val results: Result)
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ import fr.free.nrw.commons.auth.LoginActivity;
|
|||
import fr.free.nrw.commons.auth.LogoutClient;
|
||||
import fr.free.nrw.commons.bookmarks.BookmarksActivity;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.explore.categories.ExploreActivity;
|
||||
import fr.free.nrw.commons.explore.ExploreActivity;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.logging.CommonsLogSender;
|
||||
import fr.free.nrw.commons.review.ReviewActivity;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package fr.free.nrw.commons.upload.structure.depictions
|
||||
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient.Companion.getImageUrl
|
||||
import fr.free.nrw.commons.explore.depictions.THUMB_IMAGE_SIZE
|
||||
import fr.free.nrw.commons.nearby.Place
|
||||
import fr.free.nrw.commons.upload.WikidataItem
|
||||
import fr.free.nrw.commons.wikidata.WikidataProperties
|
||||
|
|
@ -9,8 +7,13 @@ import fr.free.nrw.commons.wikidata.WikidataProperties.*
|
|||
import org.wikipedia.wikidata.DataValue
|
||||
import org.wikipedia.wikidata.Entities
|
||||
import org.wikipedia.wikidata.Statement_partial
|
||||
import java.math.BigInteger
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.*
|
||||
|
||||
const val THUMB_IMAGE_SIZE = "70px"
|
||||
|
||||
/**
|
||||
* Model class for Depicted Item in Upload and Explore
|
||||
*/
|
||||
|
|
@ -76,3 +79,40 @@ operator fun Entities.Entity.get(property: WikidataProperties) =
|
|||
|
||||
private fun Map<String, Entities.Label>.byLanguageOrFirstOrEmpty() =
|
||||
let { it[Locale.getDefault().language] ?: it.values.firstOrNull() }?.value() ?: ""
|
||||
|
||||
private fun getImageUrl(title: String, size: String): String {
|
||||
return title.substringAfter(":")
|
||||
.replace(" ", "_")
|
||||
.let {
|
||||
val MD5Hash = getMd5(it)
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/${MD5Hash[0]}/${MD5Hash[0]}${MD5Hash[1]}/$it/$size-$it"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates MD5 hash for the filename
|
||||
*/
|
||||
private fun getMd5(input: String): String {
|
||||
return try {
|
||||
|
||||
// Static getInstance method is called with hashing MD5
|
||||
val md = MessageDigest.getInstance("MD5")
|
||||
|
||||
// digest() method is called to calculate message digest
|
||||
// of an input digest() return array of byte
|
||||
val messageDigest = md.digest(input.toByteArray())
|
||||
|
||||
// Convert byte array into signum representation
|
||||
val no = BigInteger(1, messageDigest)
|
||||
|
||||
// Convert message digest into hex value
|
||||
var hashtext = no.toString(16)
|
||||
while (hashtext.length < 32) {
|
||||
hashtext = "0$hashtext"
|
||||
}
|
||||
hashtext
|
||||
} // For specifying wrong message digest algorithms
|
||||
catch (e: NoSuchAlgorithmException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +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:paddingTop="@dimen/tiny_gap"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/depictionNotFound"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:gravity="center"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/depictionsSearchResultsList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbarSize="@dimen/standard_gap"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/depictionSearchInitialLoadProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
tools:targetApi="n_mr1">
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:targetClass="fr.free.nrw.commons.explore.categories.ExploreActivity"
|
||||
android:targetClass="fr.free.nrw.commons.explore.ExploreActivity"
|
||||
android:targetPackage="fr.free.nrw.commons.beta" />
|
||||
</shortcut>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,70 +0,0 @@
|
|||
package fr.free.nrw.commons.depictions
|
||||
|
||||
import fr.free.nrw.commons.depictions.subClass.SubDepictionListContract
|
||||
import fr.free.nrw.commons.depictions.subClass.SubDepictionListPresenter
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient
|
||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import io.reactivex.Observable
|
||||
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 SubDepictionListPresenterTest {
|
||||
|
||||
@Mock
|
||||
internal lateinit var view: SubDepictionListContract.View
|
||||
|
||||
lateinit var subDepictionListPresenter: SubDepictionListPresenter
|
||||
|
||||
lateinit var testScheduler: TestScheduler
|
||||
|
||||
@Mock
|
||||
internal lateinit var recentSearchesDao: RecentSearchesDao
|
||||
|
||||
@Mock
|
||||
internal lateinit var depictsClient: DepictsClient
|
||||
|
||||
@Mock
|
||||
internal lateinit var okHttpJsonApiClient: OkHttpJsonApiClient
|
||||
|
||||
var testObservable: Observable<List<DepictedItem>>? = null
|
||||
|
||||
@Mock
|
||||
lateinit var depictedItem: DepictedItem
|
||||
|
||||
val depictedItems: ArrayList<DepictedItem> = ArrayList()
|
||||
|
||||
@Before
|
||||
@Throws(Exception::class)
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
testScheduler = TestScheduler()
|
||||
depictedItems.add(depictedItem)
|
||||
testObservable = Observable.just(depictedItems)
|
||||
subDepictionListPresenter = SubDepictionListPresenter(recentSearchesDao, depictsClient, okHttpJsonApiClient, testScheduler, testScheduler)
|
||||
subDepictionListPresenter.onAttachView(view)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initSubDepictionListForParentClass() {
|
||||
Mockito.`when`(okHttpJsonApiClient.getParentQIDs(ArgumentMatchers.anyString())).thenReturn(testObservable)
|
||||
subDepictionListPresenter.initSubDepictionList("Q9394", true)
|
||||
testScheduler.triggerActions()
|
||||
verify(view)?.onSuccess(depictedItems)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initSubDepictionListForChildClass() {
|
||||
Mockito.`when`(okHttpJsonApiClient.getChildQIDs(ArgumentMatchers.anyString())).thenReturn(testObservable)
|
||||
subDepictionListPresenter.initSubDepictionList("Q9394", false)
|
||||
testScheduler.triggerActions()
|
||||
verify(view)?.onSuccess(depictedItems)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import com.jraska.livedata.test
|
|||
import com.nhaarman.mockitokotlin2.mock
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import fr.free.nrw.commons.explore.paging.*
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import io.reactivex.schedulers.TestScheduler
|
||||
import org.junit.Before
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ package fr.free.nrw.commons.explore
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import fr.free.nrw.commons.explore.depictions.LoadFunction
|
||||
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.explore.paging.PagingDataSourceFactory
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ import com.nhaarman.mockitokotlin2.mock
|
|||
import com.nhaarman.mockitokotlin2.spy
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient
|
||||
import fr.free.nrw.commons.explore.paging.LoadingState
|
||||
import fr.free.nrw.commons.explore.paging.PagingDataSource
|
||||
import fr.free.nrw.commons.explore.paging.PagingDataSourceFactory
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.instanceOf
|
||||
|
|
@ -37,7 +40,7 @@ class PagingDataSourceFactoryTest {
|
|||
fun `create returns a dataSource`() {
|
||||
assertThat(
|
||||
factory.create(),
|
||||
instanceOf(SearchDataSource::class.java)
|
||||
instanceOf(PagingDataSource::class.java)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +48,7 @@ class PagingDataSourceFactoryTest {
|
|||
@Ignore("Rewrite with Mockk constructor mocks")
|
||||
fun `retryFailedRequest invokes method if not null`() {
|
||||
val spyFactory = spy(factory)
|
||||
val dataSource = mock<SearchDataSource<String>>()
|
||||
val dataSource = mock<PagingDataSource<String>>()
|
||||
Mockito.doReturn(dataSource).`when`(spyFactory).create()
|
||||
factory.retryFailedRequest()
|
||||
verify(dataSource).retryFailedRequest()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ package fr.free.nrw.commons.explore
|
|||
|
||||
import androidx.paging.PositionalDataSource
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import fr.free.nrw.commons.explore.depictions.LoadingStates
|
||||
import fr.free.nrw.commons.explore.depictions.search.LoadingStates
|
||||
import fr.free.nrw.commons.explore.paging.LoadingState
|
||||
import fr.free.nrw.commons.explore.paging.PagingDataSource
|
||||
import io.reactivex.plugins.RxJavaPlugins
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
|
@ -12,10 +14,10 @@ import org.junit.Test
|
|||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
class SearchDataSourceTest {
|
||||
class PagingDataSourceTest {
|
||||
|
||||
private lateinit var loadingStates: PublishProcessor<LoadingState>
|
||||
private lateinit var searchDepictionsDataSource: TestSearchDataSource
|
||||
private lateinit var searchDepictionsDataSource: TestPagingDataSource
|
||||
|
||||
@Mock
|
||||
private lateinit var mockGetItems: MockGetItems
|
||||
|
|
@ -26,7 +28,7 @@ class SearchDataSourceTest {
|
|||
MockitoAnnotations.initMocks(this)
|
||||
loadingStates = PublishProcessor.create()
|
||||
searchDepictionsDataSource =
|
||||
TestSearchDataSource(
|
||||
TestPagingDataSource(
|
||||
loadingStates,
|
||||
mockGetItems
|
||||
)
|
||||
|
|
@ -109,8 +111,8 @@ class SearchDataSourceTest {
|
|||
}
|
||||
}
|
||||
|
||||
class TestSearchDataSource(loadingStates: LoadingStates, val mockGetItems: MockGetItems) :
|
||||
SearchDataSource<String>(loadingStates) {
|
||||
class TestPagingDataSource(loadingStates: LoadingStates, val mockGetItems: MockGetItems) :
|
||||
PagingDataSource<String>(loadingStates) {
|
||||
override fun getItems(loadSize: Int, startPosition: Int): List<String> =
|
||||
mockGetItems.getItems(loadSize, startPosition)
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package fr.free.nrw.commons.explore.depictions
|
|||
|
||||
import com.nhaarman.mockitokotlin2.mock
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import fr.free.nrw.commons.explore.depictions.search.PageableDepictionsDataSource
|
||||
import io.reactivex.Single
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers
|
||||
|
|
@ -12,10 +13,14 @@ class PageableDepictionsDataSourceTest {
|
|||
@Test
|
||||
fun `loadFunction loads depictions`() {
|
||||
val depictsClient: DepictsClient = mock()
|
||||
whenever(depictsClient.searchForDepictions("test", 0, 1)).thenReturn(Single.just(emptyList()))
|
||||
whenever(depictsClient.searchForDepictions("test", 0, 1))
|
||||
.thenReturn(Single.just(emptyList()))
|
||||
val pageableDepictionsDataSource = PageableDepictionsDataSource(mock(), depictsClient)
|
||||
pageableDepictionsDataSource.onQueryUpdated("test")
|
||||
assertThat(pageableDepictionsDataSource.loadFunction.invoke(0, 1), Matchers.`is`(emptyList()))
|
||||
assertThat(
|
||||
pageableDepictionsDataSource.loadFunction.invoke(0, 1),
|
||||
Matchers.`is`(emptyList())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import com.nhaarman.mockitokotlin2.mock
|
||||
import com.nhaarman.mockitokotlin2.spy
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.`is`
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
class SearchDepictionsDataSourceFactoryTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var depictsClient: DepictsClient
|
||||
|
||||
@Mock
|
||||
private lateinit var loadingStates: PublishProcessor<LoadingState>
|
||||
private lateinit var factory: SearchDepictionsDataSourceFactory
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
factory = SearchDepictionsDataSourceFactory(depictsClient, "test", loadingStates)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create returns a dataSource`() {
|
||||
assertThat(
|
||||
factory.create(),
|
||||
`is`(SearchDepictionsDataSource(depictsClient, loadingStates, "test"))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Rewrite with Mockk constructor mocks")
|
||||
fun `retryFailedRequest invokes method if not null`() {
|
||||
val spyFactory = spy(factory)
|
||||
val dataSource = mock<SearchDepictionsDataSource>()
|
||||
Mockito.doReturn(dataSource).`when`(spyFactory).create()
|
||||
factory.retryFailedRequest()
|
||||
verify(dataSource).retryFailedRequest()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `retryFailedRequest does not invoke method if null`() {
|
||||
factory.retryFailedRequest()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import androidx.paging.PositionalDataSource
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import fr.free.nrw.commons.explore.depictions.LoadingState.*
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.plugins.RxJavaPlugins
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
class SearchDepictionsDataSourceTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var depictsClient: DepictsClient
|
||||
|
||||
private lateinit var loadingStates: PublishProcessor<LoadingState>
|
||||
private lateinit var searchDepictionsDataSource: SearchDepictionsDataSource
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
|
||||
MockitoAnnotations.initMocks(this)
|
||||
loadingStates = PublishProcessor.create()
|
||||
searchDepictionsDataSource =
|
||||
SearchDepictionsDataSource(depictsClient, loadingStates, "test")
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
RxJavaPlugins.reset()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loadInitial returns results and emits InitialLoad & Complete`() {
|
||||
val params = PositionalDataSource.LoadInitialParams(0, 1, 2, false)
|
||||
val callback = mock<PositionalDataSource.LoadInitialCallback<DepictedItem>>()
|
||||
whenever(depictsClient.searchForDepictions("test", 1, 0))
|
||||
.thenReturn(Single.just(emptyList()))
|
||||
val testSubscriber = loadingStates.test()
|
||||
searchDepictionsDataSource.loadInitial(params, callback)
|
||||
verify(callback).onResult(emptyList(), 0)
|
||||
testSubscriber.assertValues(InitialLoad, Complete)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loadInitial onError does not return results and emits InitialLoad & Error`() {
|
||||
val params = PositionalDataSource.LoadInitialParams(0, 1, 2, false)
|
||||
val callback = mock<PositionalDataSource.LoadInitialCallback<DepictedItem>>()
|
||||
whenever(depictsClient.searchForDepictions("test", 1, 0))
|
||||
.thenThrow(RuntimeException())
|
||||
val testSubscriber = loadingStates.test()
|
||||
searchDepictionsDataSource.loadInitial(params, callback)
|
||||
verify(callback, never()).onResult(any(), any())
|
||||
testSubscriber.assertValues(InitialLoad, Error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loadRange returns results and emits Loading & Complete`() {
|
||||
val callback: PositionalDataSource.LoadRangeCallback<DepictedItem> = mock()
|
||||
val params = PositionalDataSource.LoadRangeParams(0, 1)
|
||||
whenever(depictsClient.searchForDepictions("test", params.loadSize, params.startPosition))
|
||||
.thenReturn(Single.just(emptyList()))
|
||||
val testSubscriber = loadingStates.test()
|
||||
searchDepictionsDataSource.loadRange(params, callback)
|
||||
verify(callback).onResult(emptyList())
|
||||
testSubscriber.assertValues(Loading, Complete)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loadRange onError does not return results and emits Loading & Error`() {
|
||||
val callback: PositionalDataSource.LoadRangeCallback<DepictedItem> = mock()
|
||||
val params = PositionalDataSource.LoadRangeParams(0, 1)
|
||||
whenever(depictsClient.searchForDepictions("test", params.loadSize, params.startPosition))
|
||||
.thenThrow(RuntimeException())
|
||||
val testSubscriber = loadingStates.test()
|
||||
searchDepictionsDataSource.loadRange(params, callback)
|
||||
verify(callback, never()).onResult(any())
|
||||
testSubscriber.assertValues(Loading, Error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `retryFailedRequest does nothing when null`() {
|
||||
searchDepictionsDataSource.retryFailedRequest()
|
||||
verifyNoMoreInteractions(depictsClient)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `retryFailedRequest retries last request`() {
|
||||
val callback: PositionalDataSource.LoadRangeCallback<DepictedItem> = mock()
|
||||
val params = PositionalDataSource.LoadRangeParams(0, 1)
|
||||
whenever(depictsClient.searchForDepictions("test", params.loadSize, params.startPosition))
|
||||
.thenThrow(RuntimeException()).thenReturn(Single.just(emptyList()))
|
||||
val testSubscriber = loadingStates.test()
|
||||
searchDepictionsDataSource.loadRange(params, callback)
|
||||
verify(callback, never()).onResult(any())
|
||||
searchDepictionsDataSource.retryFailedRequest()
|
||||
verify(callback).onResult(emptyList())
|
||||
testSubscriber.assertValues(Loading, Error, Loading, Complete)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
class SearchableDepictionsDataSourceFactoryTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var searchDepictionsDataSourceFactoryFactory: SearchDepictionsDataSourceFactoryFactory
|
||||
|
||||
@Mock
|
||||
private lateinit var liveDataConverter: LiveDataConverter
|
||||
|
||||
private lateinit var factory: SearchableDepictionsDataSourceFactory
|
||||
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
factory = SearchableDepictionsDataSourceFactory(
|
||||
searchDepictionsDataSourceFactoryFactory,
|
||||
liveDataConverter
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onQueryUpdated emits new liveData`() {
|
||||
val (_, liveData) = expectNewLiveData()
|
||||
factory.searchResults.test()
|
||||
.also { factory.onQueryUpdated("test") }
|
||||
.assertValue(liveData)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onQueryUpdated invokes livedatconverter with no items emitter`() {
|
||||
val (captor, _) = expectNewLiveData()
|
||||
factory.onQueryUpdated("test")
|
||||
factory.noItemsLoadedEvent.test()
|
||||
.also { captor.firstValue.invoke() }
|
||||
.assertValue(Unit)
|
||||
}
|
||||
|
||||
/*
|
||||
* Just for coverage, no way to really assert this
|
||||
* */
|
||||
@Test
|
||||
fun `retryFailedRequest does nothing without a factory`() {
|
||||
factory.retryFailedRequest()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `retryFailedRequest retries with a factory`() {
|
||||
val (_, _, dataSourceFactory) = expectNewLiveData()
|
||||
factory.onQueryUpdated("test")
|
||||
factory.retryFailedRequest()
|
||||
verify(dataSourceFactory).retryFailedRequest()
|
||||
}
|
||||
|
||||
private fun expectNewLiveData(): Triple<KArgumentCaptor<() -> Unit>, LiveData<PagedList<DepictedItem>>, SearchDepictionsDataSourceFactory> {
|
||||
val dataSourceFactory: SearchDepictionsDataSourceFactory = mock()
|
||||
whenever(
|
||||
searchDepictionsDataSourceFactoryFactory.create(
|
||||
"test",
|
||||
factory.loadingStates as PublishProcessor<LoadingState>
|
||||
)
|
||||
).thenReturn(dataSourceFactory)
|
||||
val captor = argumentCaptor<() -> Unit>()
|
||||
val liveData: LiveData<PagedList<DepictedItem>> = mock()
|
||||
whenever(liveDataConverter.convert(eq(dataSourceFactory), captor.capture()))
|
||||
.thenReturn(liveData)
|
||||
return Triple(captor, liveData, dataSourceFactory)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package fr.free.nrw.commons.explore.depictions.child
|
||||
|
||||
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import depictedItem
|
||||
import fr.free.nrw.commons.explore.paging.LiveDataConverter
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||
import io.reactivex.Observable
|
||||
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 PageableChildDepictionsDataSourceTest {
|
||||
@Mock
|
||||
lateinit var okHttpJsonApiClient: OkHttpJsonApiClient
|
||||
@Mock
|
||||
lateinit var liveDataConverter: LiveDataConverter
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loadFunction loads from api at position 0`() {
|
||||
val dataSource =
|
||||
PageableChildDepictionsDataSource(liveDataConverter, okHttpJsonApiClient)
|
||||
dataSource.onQueryUpdated("test")
|
||||
whenever(okHttpJsonApiClient.getChildDepictions("test"))
|
||||
.thenReturn(Observable.just(listOf(depictedItem())))
|
||||
assertThat(dataSource.loadFunction(-1, 0), `is`(listOf(depictedItem())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loadFunction loads nothing at any other position`() {
|
||||
val dataSource =
|
||||
PageableChildDepictionsDataSource(liveDataConverter, okHttpJsonApiClient)
|
||||
assertThat(dataSource.loadFunction(-1, 1), `is`(emptyList()))
|
||||
verifyZeroInteractions(okHttpJsonApiClient)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package fr.free.nrw.commons.depictions.Media
|
||||
package fr.free.nrw.commons.explore.depictions.media
|
||||
|
||||
import com.nhaarman.mockitokotlin2.mock
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package fr.free.nrw.commons.explore.depictions.parent
|
||||
|
||||
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import depictedItem
|
||||
import fr.free.nrw.commons.explore.depictions.child.PageableChildDepictionsDataSource
|
||||
import fr.free.nrw.commons.explore.paging.LiveDataConverter
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||
import io.reactivex.Observable
|
||||
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 PageableParentDepictionsDataSourceTest {
|
||||
@Mock
|
||||
lateinit var okHttpJsonApiClient: OkHttpJsonApiClient
|
||||
|
||||
@Mock
|
||||
lateinit var liveDataConverter: LiveDataConverter
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loadFunction loads from api at position 0`() {
|
||||
val dataSource =
|
||||
PageableParentDepictionsDataSource(liveDataConverter, okHttpJsonApiClient)
|
||||
dataSource.onQueryUpdated("test")
|
||||
whenever(okHttpJsonApiClient.getParentDepictions("test"))
|
||||
.thenReturn(Observable.just(listOf(depictedItem())))
|
||||
assertThat(dataSource.loadFunction(-1, 0), `is`(listOf(depictedItem())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loadFunction loads nothing at any other position`() {
|
||||
val dataSource =
|
||||
PageableChildDepictionsDataSource(liveDataConverter, okHttpJsonApiClient)
|
||||
assertThat(dataSource.loadFunction(-1, 1), `is`(emptyList()))
|
||||
verifyZeroInteractions(okHttpJsonApiClient)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue