Skip to content

Commit c216fdf

Browse files
authored
commons-app#3756 Convert SearchDepictionsFragment to use Pagination (commons-app#3758)
1 parent 0ebd59a commit c216fdf

33 files changed

+1033
-614
lines changed

app/build.gradle

+4
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ dependencies {
4545
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
4646
implementation "com.hannesdorfmann:adapterdelegates4-kotlin-dsl-layoutcontainer:$ADAPTER_DELEGATES_VERSION"
4747
implementation "com.hannesdorfmann:adapterdelegates4-pagination:$ADAPTER_DELEGATES_VERSION"
48+
implementation "androidx.paging:paging-runtime-ktx:$PAGING_VERSION"
49+
testImplementation "androidx.paging:paging-common-ktx:$PAGING_VERSION"
50+
implementation "androidx.paging:paging-rxjava2-ktx:$PAGING_VERSION"
51+
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
4852

4953
// Logging
5054
implementation 'ch.acra:acra-dialog:5.3.0'

app/src/main/java/fr/free/nrw/commons/BasePresenter.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package fr.free.nrw.commons;
22

3+
import androidx.annotation.NonNull;
4+
35
/**
46
* Base presenter, enforcing contracts to atach and detach view
57
*/
68
public interface BasePresenter<T> {
79
/**
810
* Until a view is attached, it is open to listen events from the presenter
911
*/
10-
void onAttachView(T view);
12+
void onAttachView(@NonNull T view);
1113

1214
/**
1315
* Detaching a view makes sure that the view no more receives events from the presenter
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package fr.free.nrw.commons.depictions.subClass
2+
3+
import fr.free.nrw.commons.explore.depictions.depictionDelegate
4+
import fr.free.nrw.commons.upload.categories.BaseDelegateAdapter
5+
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
6+
7+
class SubDepictionAdapter(clickListener: (DepictedItem) -> Unit) :
8+
BaseDelegateAdapter<DepictedItem>(
9+
depictionDelegate(clickListener),
10+
areItemsTheSame = { oldItem, newItem -> oldItem.id == newItem.id }
11+
)

app/src/main/java/fr/free/nrw/commons/depictions/subClass/SubDepictionListContract.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
package fr.free.nrw.commons.depictions.subClass;
22

3-
import java.io.IOException;
4-
import java.util.List;
5-
63
import fr.free.nrw.commons.BasePresenter;
74
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
5+
import java.io.IOException;
6+
import java.util.List;
87

98
/**
109
* The contract with which SubDepictionListFragment and its presenter would talk to each other
@@ -28,5 +27,6 @@ interface UserActionListener extends BasePresenter<View> {
2827
void initSubDepictionList(String qid, Boolean isParentClass) throws IOException;
2928

3029
String getQuery();
30+
3131
}
3232
}

app/src/main/java/fr/free/nrw/commons/depictions/subClass/SubDepictionListFragment.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import dagger.android.support.DaggerFragment;
2121
import fr.free.nrw.commons.R;
2222
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity;
23-
import fr.free.nrw.commons.explore.depictions.DepictionAdapter;
2423
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
2524
import fr.free.nrw.commons.utils.NetworkUtils;
2625
import fr.free.nrw.commons.utils.ViewUtil;
@@ -47,7 +46,7 @@ public class SubDepictionListFragment extends DaggerFragment implements SubDepic
4746
* Keeps a record of whether current instance of the fragment if of SubClass or ParentClass
4847
*/
4948
private boolean isParentClass = false;
50-
private DepictionAdapter depictionsAdapter;
49+
private SubDepictionAdapter depictionsAdapter;
5150
RecyclerView.LayoutManager layoutManager;
5251
/**
5352
* Stores entityId for the depiction
@@ -100,7 +99,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
10099
}
101100
initViews();
102101
depictionsRecyclerView.setLayoutManager(layoutManager);
103-
depictionsAdapter = new DepictionAdapter(depictedItem -> {
102+
depictionsAdapter = new SubDepictionAdapter(depictedItem -> {
104103
// Open SubDepiction Details page
105104
getActivity().finish();
106105
WikidataItemDetailsActivity.startYourself(getContext(), depictedItem);

app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public void setTabs() {
117117
searchHistoryContainer.setVisibility(View.GONE);
118118

119119
if (FragmentUtils.isFragmentUIActive(searchDepictionsFragment)) {
120-
searchDepictionsFragment.updateDepictionList(query.toString());
120+
searchDepictionsFragment.onQueryUpdated(query.toString());
121121
}
122122

123123
if (FragmentUtils.isFragmentUIActive(searchImageFragment)) {

app/src/main/java/fr/free/nrw/commons/explore/SearchModule.java

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
import dagger.Binds;
44
import dagger.Module;
5+
import dagger.Provides;
6+
import fr.free.nrw.commons.explore.depictions.DepictsClient;
7+
import fr.free.nrw.commons.explore.depictions.SearchDepictionsDataSourceFactory;
8+
import fr.free.nrw.commons.explore.depictions.SearchDepictionsDataSourceFactoryFactory;
59
import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragmentContract;
610
import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragmentPresenter;
711

@@ -16,4 +20,10 @@ public abstract SearchDepictionsFragmentContract.UserActionListener bindsSearchD
1620
SearchDepictionsFragmentPresenter
1721
presenter
1822
);
23+
24+
@Provides
25+
static public SearchDepictionsDataSourceFactoryFactory providesSearchDepictionsFactoryFactory(
26+
DepictsClient depictsClient){
27+
return (query, loadingStates) -> new SearchDepictionsDataSourceFactory(depictsClient, query, loadingStates);
28+
}
1929
}
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,47 @@
11
package fr.free.nrw.commons.explore.depictions
22

3-
import fr.free.nrw.commons.upload.categories.BaseDelegateAdapter
3+
import android.view.View
4+
import android.view.ViewGroup
5+
import androidx.paging.PagedListAdapter
6+
import androidx.recyclerview.widget.DiffUtil
7+
import androidx.recyclerview.widget.RecyclerView
8+
import fr.free.nrw.commons.R
49
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
10+
import kotlinx.android.extensions.LayoutContainer
11+
import kotlinx.android.synthetic.main.item_depictions.*
512

613

7-
class DepictionAdapter(clickListener: (DepictedItem) -> Unit) : BaseDelegateAdapter<DepictedItem>(
8-
depictionDelegate(clickListener),
9-
areItemsTheSame = { oldItem, newItem -> oldItem.id == newItem.id }
10-
)
14+
class DepictionAdapter(val onDepictionClicked: (DepictedItem) -> Unit) :
15+
PagedListAdapter<DepictedItem, DepictedItemViewHolder>(
16+
object : DiffUtil.ItemCallback<DepictedItem>() {
17+
override fun areItemsTheSame(oldItem: DepictedItem, newItem: DepictedItem) =
18+
oldItem.id == newItem.id
1119

20+
override fun areContentsTheSame(oldItem: DepictedItem, newItem: DepictedItem) =
21+
oldItem == newItem
1222

23+
}
24+
25+
) {
26+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DepictedItemViewHolder {
27+
return DepictedItemViewHolder(parent.inflate(R.layout.item_depictions))
28+
}
29+
30+
override fun onBindViewHolder(holder: DepictedItemViewHolder, position: Int) {
31+
holder.bind(getItem(position)!!, onDepictionClicked)
32+
}
33+
}
34+
35+
class DepictedItemViewHolder(override val containerView: View) :
36+
RecyclerView.ViewHolder(containerView), LayoutContainer {
37+
fun bind(item: DepictedItem, onDepictionClicked: (DepictedItem) -> Unit) {
38+
containerView.setOnClickListener { onDepictionClicked(item) }
39+
depicts_label.text = item.name
40+
description.text = item.description
41+
if (item.imageUrl?.isNotBlank() == true) {
42+
depicts_image.setImageURI(item.imageUrl)
43+
} else {
44+
depicts_image.setActualImageResource(R.drawable.ic_wikidata_logo_24dp)
45+
}
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package fr.free.nrw.commons.explore.depictions
2+
3+
import android.view.LayoutInflater
4+
import android.view.View
5+
import android.view.ViewGroup
6+
import androidx.annotation.LayoutRes
7+
import androidx.recyclerview.widget.DiffUtil
8+
import androidx.recyclerview.widget.ListAdapter
9+
import androidx.recyclerview.widget.RecyclerView
10+
import fr.free.nrw.commons.R
11+
import kotlinx.android.extensions.LayoutContainer
12+
import kotlinx.android.synthetic.main.list_item_load_more.*
13+
14+
class FooterAdapter(private val onRefreshClicked: () -> Unit) :
15+
ListAdapter<FooterItem, FooterViewHolder>(object :
16+
DiffUtil.ItemCallback<FooterItem>() {
17+
override fun areItemsTheSame(oldItem: FooterItem, newItem: FooterItem) = oldItem == newItem
18+
19+
override fun areContentsTheSame(oldItem: FooterItem, newItem: FooterItem) =
20+
oldItem == newItem
21+
}) {
22+
23+
override fun getItemViewType(position: Int): Int {
24+
return getItem(position).ordinal
25+
}
26+
27+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
28+
when (FooterItem.values()[viewType]) {
29+
FooterItem.LoadingItem -> LoadingViewHolder(parent.inflate(R.layout.list_item_progress))
30+
FooterItem.RefreshItem -> RefreshViewHolder(
31+
parent.inflate(R.layout.list_item_load_more),
32+
onRefreshClicked
33+
)
34+
}
35+
36+
override fun onBindViewHolder(holder: FooterViewHolder, position: Int) {}
37+
}
38+
39+
open class FooterViewHolder(override val containerView: View) :
40+
RecyclerView.ViewHolder(containerView),
41+
LayoutContainer
42+
43+
class LoadingViewHolder(containerView: View) : FooterViewHolder(containerView)
44+
class RefreshViewHolder(containerView: View, onRefreshClicked: () -> Unit) :
45+
FooterViewHolder(containerView) {
46+
init {
47+
listItemLoadMoreButton.setOnClickListener { onRefreshClicked() }
48+
}
49+
}
50+
51+
enum class FooterItem { LoadingItem, RefreshItem }
52+
53+
fun ViewGroup.inflate(@LayoutRes layoutId: Int, attachToRoot: Boolean = false): View =
54+
LayoutInflater.from(context).inflate(layoutId, this, attachToRoot)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package fr.free.nrw.commons.explore.depictions
2+
3+
import androidx.paging.PositionalDataSource
4+
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
5+
import io.reactivex.Completable
6+
import io.reactivex.processors.PublishProcessor
7+
import io.reactivex.schedulers.Schedulers
8+
import timber.log.Timber
9+
10+
11+
data class SearchDepictionsDataSource constructor(
12+
private val depictsClient: DepictsClient,
13+
private val loadingStates: PublishProcessor<LoadingState>,
14+
private val query: String
15+
) : PositionalDataSource<DepictedItem>() {
16+
17+
private var lastExecutedRequest: (() -> Boolean)? = null
18+
19+
override fun loadInitial(
20+
params: LoadInitialParams,
21+
callback: LoadInitialCallback<DepictedItem>
22+
) {
23+
storeAndExecute {
24+
loadingStates.offer(LoadingState.InitialLoad)
25+
performWithTryCatch {
26+
callback.onResult(
27+
getItems(query, params.requestedLoadSize, params.requestedStartPosition),
28+
params.requestedStartPosition
29+
)
30+
}
31+
}
32+
}
33+
34+
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<DepictedItem>) {
35+
storeAndExecute {
36+
loadingStates.offer(LoadingState.Loading)
37+
performWithTryCatch {
38+
callback.onResult(getItems(query, params.loadSize, params.startPosition))
39+
}
40+
}
41+
}
42+
43+
fun retryFailedRequest() {
44+
Completable.fromAction { lastExecutedRequest?.invoke() }
45+
.subscribeOn(Schedulers.io())
46+
.subscribe()
47+
}
48+
49+
private fun getItems(query: String, limit: Int, offset: Int) =
50+
depictsClient.searchForDepictions(query, limit, offset).blockingGet()
51+
52+
private fun storeAndExecute(function: () -> Boolean) {
53+
function.also { lastExecutedRequest = it }.invoke()
54+
}
55+
56+
private fun performWithTryCatch(function: () -> Unit) = try {
57+
function.invoke()
58+
loadingStates.offer(LoadingState.Complete)
59+
} catch (e: Exception) {
60+
Timber.e(e)
61+
loadingStates.offer(LoadingState.Error)
62+
}
63+
}

0 commit comments

Comments
 (0)