diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index ccc2da979..405537524 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -7,13 +7,6 @@
-
-
-
@@ -248,6 +241,28 @@
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
@@ -275,211 +290,12 @@
- .*:.*Style
-
- http://schemas.android.com/apk/res/android
-
-
- BY_NAME
-
-
-
-
-
-
- .*:layout_width
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:layout_height
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:layout_weight
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:layout_margin
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:layout_marginTop
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:layout_marginBottom
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:layout_marginStart
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:layout_marginEnd
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:layout_marginLeft
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:layout_marginRight
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:layout_.*
-
- http://schemas.android.com/apk/res/android
-
-
- BY_NAME
-
-
-
-
-
-
- .*:padding
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:paddingTop
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:paddingBottom
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:paddingStart
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:paddingEnd
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:paddingLeft
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:paddingRight
+ .*
http://schemas.android.com/apk/res/android
+ ANDROID_ATTRIBUTE_ORDER
@@ -487,39 +303,7 @@
.*
- http://schemas.android.com/apk/res/android
-
-
- BY_NAME
-
-
-
-
-
-
- .*
- http://schemas.android.com/apk/res-auto
-
-
- BY_NAME
-
-
-
-
-
-
- .*
- http://schemas.android.com/tools
-
-
- BY_NAME
-
-
-
-
-
-
- .*
+
.*
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/BaseSearchFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/BaseSearchFragment.kt
new file mode 100644
index 000000000..62e6049d8
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/BaseSearchFragment.kt
@@ -0,0 +1,95 @@
+package fr.free.nrw.commons.explore
+
+import android.content.Context
+import android.content.res.Configuration
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.GONE
+import android.view.View.VISIBLE
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+import androidx.paging.PagedList
+import androidx.paging.PagedListAdapter
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.MergeAdapter
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
+import fr.free.nrw.commons.utils.ViewUtil
+import kotlinx.android.synthetic.main.fragment_search_paginated.*
+
+
+abstract class BaseSearchFragment : CommonsDaggerSupportFragment(),
+ SearchFragmentContract.View {
+
+ abstract val pagedListAdapter: PagedListAdapter
+ abstract val injectedPresenter: SearchFragmentContract.Presenter
+ abstract val emptyTemplateTextId: Int
+ private val loadingAdapter by lazy { FooterAdapter { injectedPresenter.retryFailedRequest() } }
+ private val mergeAdapter by lazy { MergeAdapter(pagedListAdapter, loadingAdapter) }
+ private var searchResults: LiveData>? = null
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ) = inflater.inflate(R.layout.fragment_search_paginated, container, false)
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ paginatedSearchResultsList.apply {
+ layoutManager = GridLayoutManager(context, if (isPortrait) 1 else 2)
+ adapter = mergeAdapter
+ }
+ injectedPresenter.listFooterData.observe(
+ viewLifecycleOwner,
+ Observer(loadingAdapter::submitList)
+ )
+ }
+
+ override fun observeSearchResults(searchResults: LiveData>) {
+ this.searchResults?.removeObservers(viewLifecycleOwner)
+ this.searchResults = searchResults
+ searchResults.observe(viewLifecycleOwner, Observer {
+ pagedListAdapter.submitList(it)
+ contentNotFound.visibility = if (it.loadedCount == 0) VISIBLE else GONE
+ })
+ }
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ injectedPresenter.onAttachView(this)
+ }
+
+
+ override fun onDetach() {
+ super.onDetach()
+ injectedPresenter.onDetachView()
+ }
+
+ override fun setEmptyViewText(query: String) {
+ contentNotFound.text = getString(emptyTemplateTextId, query)
+ }
+
+ override fun hideInitialLoadProgress() {
+ paginatedSearchInitialLoadProgress.visibility = View.GONE
+ }
+
+ override fun showInitialLoadInProgress() {
+ paginatedSearchInitialLoadProgress.visibility = View.VISIBLE
+ }
+
+ override fun showSnackbar() {
+ ViewUtil.showShortSnackbar(paginatedSearchResultsList, R.string.error_loading_depictions)
+ }
+
+ fun onQueryUpdated(query: String) {
+ injectedPresenter.onQueryUpdated(query)
+ }
+}
+
+private val Fragment.isPortrait get() = orientation == Configuration.ORIENTATION_PORTRAIT
+
+private val Fragment.orientation get() = activity!!.resources.configuration.orientation
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/BaseSearchPresenter.kt b/app/src/main/java/fr/free/nrw/commons/explore/BaseSearchPresenter.kt
new file mode 100644
index 000000000..3f2bdfbb7
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/BaseSearchPresenter.kt
@@ -0,0 +1,69 @@
+package fr.free.nrw.commons.explore
+
+import androidx.lifecycle.MutableLiveData
+import fr.free.nrw.commons.upload.depicts.proxy
+import io.reactivex.Scheduler
+import io.reactivex.disposables.CompositeDisposable
+import timber.log.Timber
+
+
+abstract class BaseSearchPresenter(
+ val mainThreadScheduler: Scheduler,
+ val pageableDataSource: PageableDataSource
+) : SearchFragmentContract.Presenter {
+
+ private val DUMMY: SearchFragmentContract.View = proxy()
+ private var view: SearchFragmentContract.View = DUMMY
+ private var currentQuery: String? = null
+
+
+ private val compositeDisposable = CompositeDisposable()
+ override val listFooterData = MutableLiveData>().apply { value = emptyList() }
+
+ override fun onAttachView(view: SearchFragmentContract.View) {
+ this.view = view
+ compositeDisposable.addAll(
+ pageableDataSource.searchResults.subscribe(view::observeSearchResults),
+ pageableDataSource.loadingStates
+ .observeOn(mainThreadScheduler)
+ .subscribe(::onLoadingState, Timber::e),
+ pageableDataSource.noItemsLoadedEvent.subscribe {
+ setEmptyViewText()
+ }
+ )
+ }
+
+ private fun onLoadingState(it: LoadingState) = when (it) {
+ LoadingState.Loading -> listFooterData.postValue(listOf(FooterItem.LoadingItem))
+ LoadingState.Complete -> {
+ listFooterData.postValue(emptyList())
+ view.hideInitialLoadProgress()
+ }
+ LoadingState.InitialLoad -> view.showInitialLoadInProgress()
+ LoadingState.Error -> {
+ setEmptyViewText()
+ view.showSnackbar()
+ view.hideInitialLoadProgress()
+ listFooterData.postValue(listOf(FooterItem.RefreshItem))
+ }
+ }
+
+ private fun setEmptyViewText() {
+ currentQuery?.let(view::setEmptyViewText)
+ }
+
+ override fun retryFailedRequest() {
+ pageableDataSource.retryFailedRequest()
+ }
+
+ override fun onDetachView() {
+ view = DUMMY
+ compositeDisposable.clear()
+ }
+
+ override fun onQueryUpdated(query: String) {
+ currentQuery = query
+ pageableDataSource.onQueryUpdated(query)
+ }
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/BaseViewHolder.kt b/app/src/main/java/fr/free/nrw/commons/explore/BaseViewHolder.kt
new file mode 100644
index 000000000..98115046f
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/BaseViewHolder.kt
@@ -0,0 +1,10 @@
+package fr.free.nrw.commons.explore
+
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.android.extensions.LayoutContainer
+
+abstract class BaseViewHolder(override val containerView: View) :
+ RecyclerView.ViewHolder(containerView), LayoutContainer {
+ abstract fun bind(item: T)
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/FooterAdapter.kt b/app/src/main/java/fr/free/nrw/commons/explore/FooterAdapter.kt
similarity index 97%
rename from app/src/main/java/fr/free/nrw/commons/explore/depictions/FooterAdapter.kt
rename to app/src/main/java/fr/free/nrw/commons/explore/FooterAdapter.kt
index 76d4be40a..f51dad4e6 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/FooterAdapter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/explore/FooterAdapter.kt
@@ -1,4 +1,4 @@
-package fr.free.nrw.commons.explore.depictions
+package fr.free.nrw.commons.explore
import android.view.LayoutInflater
import android.view.View
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java b/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java
index 1273bb2c4..39ce648fd 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java
@@ -125,7 +125,7 @@ public class SearchActivity extends NavigationBaseActivity
}
if (FragmentUtils.isFragmentUIActive(searchCategoryFragment)) {
- searchCategoryFragment.updateCategoryList(query.toString());
+ searchCategoryFragment.onQueryUpdated(query.toString());
}
} else {
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/SearchCategoriesFragmentPresenter.kt b/app/src/main/java/fr/free/nrw/commons/explore/SearchCategoriesFragmentPresenter.kt
new file mode 100644
index 000000000..3de2c9778
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/SearchCategoriesFragmentPresenter.kt
@@ -0,0 +1,14 @@
+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
+) : BaseSearchPresenter(mainThreadScheduler, dataSourceFactory),
+ SearchCategoriesFragmentContract.Presenter
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsDataSource.kt b/app/src/main/java/fr/free/nrw/commons/explore/SearchDataSource.kt
similarity index 61%
rename from app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsDataSource.kt
rename to app/src/main/java/fr/free/nrw/commons/explore/SearchDataSource.kt
index 413ee017e..9bda6d26f 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsDataSource.kt
+++ b/app/src/main/java/fr/free/nrw/commons/explore/SearchDataSource.kt
@@ -1,54 +1,17 @@
-package fr.free.nrw.commons.explore.depictions
+package fr.free.nrw.commons.explore
import androidx.paging.PositionalDataSource
-import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
+import fr.free.nrw.commons.explore.depictions.LoadFunction
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,
- private val query: String
-) : PositionalDataSource() {
+abstract class SearchDataSource(
+ private val loadingStates: PublishProcessor
+) : PositionalDataSource() {
private var lastExecutedRequest: (() -> Boolean)? = null
-
- override fun loadInitial(
- params: LoadInitialParams,
- callback: LoadInitialCallback
- ) {
- storeAndExecute {
- loadingStates.offer(LoadingState.InitialLoad)
- performWithTryCatch {
- callback.onResult(
- getItems(query, params.requestedLoadSize, params.requestedStartPosition),
- params.requestedStartPosition
- )
- }
- }
- }
-
- override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback) {
- 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()
}
@@ -60,4 +23,45 @@ data class SearchDepictionsDataSource constructor(
Timber.e(e)
loadingStates.offer(LoadingState.Error)
}
+
+ override fun loadInitial(
+ params: LoadInitialParams,
+ callback: LoadInitialCallback
+ ) {
+ storeAndExecute {
+ loadingStates.offer(LoadingState.InitialLoad)
+ performWithTryCatch {
+ callback.onResult(
+ getItems(params.requestedLoadSize, params.requestedStartPosition),
+ params.requestedStartPosition
+ )
+ }
+ }
+ }
+
+ override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback) {
+ storeAndExecute {
+ loadingStates.offer(LoadingState.Loading)
+ performWithTryCatch {
+ callback.onResult(getItems(params.loadSize, params.startPosition))
+ }
+ }
+ }
+
+ protected abstract fun getItems(loadSize: Int, startPosition: Int): List
+
+ fun retryFailedRequest() {
+ Completable.fromAction { lastExecutedRequest?.invoke() }
+ .subscribeOn(Schedulers.io())
+ .subscribe()
+ }
+}
+
+fun dataSource(
+ loadingStates: PublishProcessor,
+ loadFunction: LoadFunction
+) = object : SearchDataSource(loadingStates) {
+ override fun getItems(loadSize: Int, startPosition: Int): List {
+ return loadFunction(loadSize, startPosition)
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchableDepictionsDataSourceFactory.kt b/app/src/main/java/fr/free/nrw/commons/explore/SearchDataSourceFactory.kt
similarity index 52%
rename from app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchableDepictionsDataSourceFactory.kt
rename to app/src/main/java/fr/free/nrw/commons/explore/SearchDataSourceFactory.kt
index 90bdcdc40..7a53572e7 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchableDepictionsDataSourceFactory.kt
+++ b/app/src/main/java/fr/free/nrw/commons/explore/SearchDataSourceFactory.kt
@@ -1,11 +1,12 @@
-package fr.free.nrw.commons.explore.depictions
+package fr.free.nrw.commons.explore
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 fr.free.nrw.commons.explore.depictions.LoadFunction
+import fr.free.nrw.commons.explore.depictions.LoadingStates
import io.reactivex.Flowable
import io.reactivex.processors.PublishProcessor
import javax.inject.Inject
@@ -13,25 +14,28 @@ 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
-) {
+abstract class PageableDataSource(private val liveDataConverter: LiveDataConverter) {
+
+ lateinit var query: String
+ private val dataSourceFactoryFactory: () -> SearchDataSourceFactory = {
+ dataSourceFactory(_loadingStates, loadFunction)
+ }
private val _loadingStates = PublishProcessor.create()
val loadingStates: Flowable = _loadingStates
- private val _searchResults = PublishProcessor.create>>()
- val searchResults: Flowable>> = _searchResults
+ private val _searchResults = PublishProcessor.create>>()
+ val searchResults: Flowable>> = _searchResults
private val _noItemsLoadedEvent = PublishProcessor.create()
val noItemsLoadedEvent: Flowable = _noItemsLoadedEvent
+ private var currentFactory: SearchDataSourceFactory? = null
- private var currentFactory: SearchDepictionsDataSourceFactory? = null
+ abstract val loadFunction: LoadFunction
fun onQueryUpdated(query: String) {
+ this.query = query
_searchResults.offer(
- liveDataConverter.convert(
- searchDepictionsDataSourceFactoryFactory.create(query, _loadingStates)
- .also { currentFactory = it }
- ) { _noItemsLoadedEvent.offer(Unit) }
+ liveDataConverter.convert(dataSourceFactoryFactory().also { currentFactory = it }) {
+ _noItemsLoadedEvent.offer(Unit)
+ }
)
}
@@ -41,17 +45,17 @@ class SearchableDepictionsDataSourceFactory @Inject constructor(
}
class LiveDataConverter @Inject constructor() {
- fun convert(
- dataSourceFactory: SearchDepictionsDataSourceFactory,
+ fun convert(
+ dataSourceFactory: SearchDataSourceFactory,
zeroItemsLoadedFunction: () -> Unit
- ): LiveData> {
+ ): LiveData> {
return dataSourceFactory.toLiveData(
Config(
pageSize = PAGE_SIZE,
initialLoadSizeHint = INITIAL_LOAD_SIZE,
enablePlaceholders = false
),
- boundaryCallback = object : PagedList.BoundaryCallback() {
+ boundaryCallback = object : PagedList.BoundaryCallback() {
override fun onZeroItemsLoaded() {
zeroItemsLoadedFunction()
}
@@ -61,25 +65,25 @@ class LiveDataConverter @Inject constructor() {
}
-interface SearchDepictionsDataSourceFactoryFactory {
- fun create(query: String, loadingStates: PublishProcessor)
- : SearchDepictionsDataSourceFactory
-}
+abstract class SearchDataSourceFactory(val loadingStates: LoadingStates) :
+ DataSource.Factory() {
+ private var currentDataSource: SearchDataSource? = null
+ abstract val loadFunction: LoadFunction
-class SearchDepictionsDataSourceFactory constructor(
- private val depictsClient: DepictsClient,
- private val query: String,
- private val loadingStates: PublishProcessor
-) : DataSource.Factory() {
- private var currentDataSource: SearchDepictionsDataSource? = null
- override fun create() = SearchDepictionsDataSource(depictsClient, loadingStates, query)
- .also { currentDataSource = it }
+ override fun create() =
+ dataSource(loadingStates, loadFunction).also { currentDataSource = it }
fun retryFailedRequest() {
currentDataSource?.retryFailedRequest()
}
+
}
+fun dataSourceFactory(loadingStates: LoadingStates, loadFunction: LoadFunction) =
+ object : SearchDataSourceFactory(loadingStates) {
+ override val loadFunction: LoadFunction = loadFunction
+ }
+
sealed class LoadingState {
object InitialLoad : LoadingState()
object Loading : LoadingState()
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/SearchFragmentContract.kt b/app/src/main/java/fr/free/nrw/commons/explore/SearchFragmentContract.kt
new file mode 100644
index 000000000..19507a303
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/SearchFragmentContract.kt
@@ -0,0 +1,21 @@
+package fr.free.nrw.commons.explore
+
+import androidx.lifecycle.LiveData
+import androidx.paging.PagedList
+import fr.free.nrw.commons.BasePresenter
+
+interface SearchFragmentContract {
+ interface View {
+ fun showSnackbar()
+ fun observeSearchResults(searchResults: LiveData>)
+ fun setEmptyViewText(query: String)
+ fun showInitialLoadInProgress()
+ fun hideInitialLoadProgress()
+ }
+
+ interface Presenter : BasePresenter> {
+ val listFooterData: LiveData>
+ fun onQueryUpdated(query: String)
+ fun retryFailedRequest()
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/SearchModule.java b/app/src/main/java/fr/free/nrw/commons/explore/SearchModule.java
deleted file mode 100644
index 4b1fa5326..000000000
--- a/app/src/main/java/fr/free/nrw/commons/explore/SearchModule.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package fr.free.nrw.commons.explore;
-
-import dagger.Binds;
-import dagger.Module;
-import dagger.Provides;
-import fr.free.nrw.commons.explore.depictions.DepictsClient;
-import fr.free.nrw.commons.explore.depictions.SearchDepictionsDataSourceFactory;
-import fr.free.nrw.commons.explore.depictions.SearchDepictionsDataSourceFactoryFactory;
-import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragmentContract;
-import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragmentPresenter;
-
-/**
- * The Dagger Module for explore:depictions related presenters and (some other objects maybe in future)
- */
-@Module
-public abstract class SearchModule {
-
- @Binds
- public abstract SearchDepictionsFragmentContract.UserActionListener bindsSearchDepictionsFragmentPresenter(
- SearchDepictionsFragmentPresenter
- presenter
- );
-
- @Provides
- static public SearchDepictionsDataSourceFactoryFactory providesSearchDepictionsFactoryFactory(
- DepictsClient depictsClient){
- return (query, loadingStates) -> new SearchDepictionsDataSourceFactory(depictsClient, query, loadingStates);
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/SearchModule.kt b/app/src/main/java/fr/free/nrw/commons/explore/SearchModule.kt
new file mode 100644
index 000000000..22ac34939
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/SearchModule.kt
@@ -0,0 +1,24 @@
+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
+
+/**
+ * The Dagger Module for explore:depictions related presenters and (some other objects maybe in future)
+ */
+@Module
+abstract class SearchModule {
+ @Binds
+ abstract fun bindsSearchDepictionsFragmentPresenter(
+ presenter: SearchDepictionsFragmentPresenter
+ ): SearchDepictionsFragmentContract.Presenter
+
+ @Binds
+ abstract fun bindsSearchCategoriesFragmentPresenter(
+ presenter: SearchCategoriesFragmentPresenter?
+ ): SearchCategoriesFragmentContract.Presenter?
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/categories/PageableCategoriesDataSource.kt b/app/src/main/java/fr/free/nrw/commons/explore/categories/PageableCategoriesDataSource.kt
new file mode 100644
index 000000000..67caaf1ab
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/categories/PageableCategoriesDataSource.kt
@@ -0,0 +1,16 @@
+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.PageableDataSource
+import javax.inject.Inject
+
+class PageableCategoriesDataSource @Inject constructor(
+ liveDataConverter: LiveDataConverter,
+ val categoryClient: CategoryClient
+) : PageableDataSource(liveDataConverter) {
+
+ override val loadFunction = { loadSize: Int, startPosition: Int ->
+ categoryClient.searchCategories(query, loadSize, startPosition).blockingFirst()
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/categories/PagedSearchCategoriesAdapter.kt b/app/src/main/java/fr/free/nrw/commons/explore/categories/PagedSearchCategoriesAdapter.kt
new file mode 100644
index 000000000..52eb49165
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/categories/PagedSearchCategoriesAdapter.kt
@@ -0,0 +1,45 @@
+package fr.free.nrw.commons.explore.categories
+
+import android.view.View
+import android.view.ViewGroup
+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 kotlinx.android.synthetic.main.item_recent_searches.*
+
+
+class PagedSearchCategoriesAdapter(val onCategoryClicked: (String) -> Unit) :
+ PagedListAdapter(
+ object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: String, newItem: String) =
+ oldItem == newItem
+
+ override fun areContentsTheSame(oldItem: String, newItem: String) =
+ oldItem == newItem
+ }
+ ) {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryItemViewHolder {
+ return CategoryItemViewHolder(
+ parent.inflate(R.layout.item_recent_searches),
+ onCategoryClicked
+ )
+ }
+
+ override fun onBindViewHolder(holder: CategoryItemViewHolder, position: Int) {
+ holder.bind(getItem(position)!!)
+ }
+}
+
+class CategoryItemViewHolder(containerView: View, val onCategoryClicked: (String) -> Unit) :
+ BaseViewHolder(containerView) {
+
+ override fun bind(item: String) {
+ containerView.setOnClickListener { onCategoryClicked(item) }
+ textView1.text = item.substringAfter(CATEGORY_PREFIX)
+ }
+}
+
+
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/categories/SearchCategoriesFragmentContract.kt b/app/src/main/java/fr/free/nrw/commons/explore/categories/SearchCategoriesFragmentContract.kt
new file mode 100644
index 000000000..97defd543
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/categories/SearchCategoriesFragmentContract.kt
@@ -0,0 +1,8 @@
+package fr.free.nrw.commons.explore.categories
+
+import fr.free.nrw.commons.explore.SearchFragmentContract
+
+interface SearchCategoriesFragmentContract {
+ interface View : SearchFragmentContract.View
+ interface Presenter : SearchFragmentContract.Presenter
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/categories/SearchCategoryFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/categories/SearchCategoryFragment.java
deleted file mode 100644
index 855461e1d..000000000
--- a/app/src/main/java/fr/free/nrw/commons/explore/categories/SearchCategoryFragment.java
+++ /dev/null
@@ -1,214 +0,0 @@
-package fr.free.nrw.commons.explore.categories;
-
-
-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.recyclerview.widget.GridLayoutManager;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import butterknife.BindView;
-import butterknife.ButterKnife;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.category.CategoryClient;
-import fr.free.nrw.commons.category.CategoryDetailsActivity;
-import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
-import fr.free.nrw.commons.explore.recentsearches.RecentSearch;
-import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
-import fr.free.nrw.commons.kvstore.JsonKvStore;
-import fr.free.nrw.commons.utils.NetworkUtils;
-import fr.free.nrw.commons.utils.ViewUtil;
-import io.reactivex.android.schedulers.AndroidSchedulers;
-import io.reactivex.schedulers.Schedulers;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import javax.inject.Inject;
-import javax.inject.Named;
-import kotlin.Unit;
-import timber.log.Timber;
-
-/**
- * Displays the category search screen.
- */
-
-public class SearchCategoryFragment extends CommonsDaggerSupportFragment {
-
- @BindView(R.id.imagesListBox)
- RecyclerView categoriesRecyclerView;
- @BindView(R.id.imageSearchInProgress)
- ProgressBar progressBar;
- @BindView(R.id.imagesNotFound)
- TextView categoriesNotFoundView;
- String query;
- @BindView(R.id.bottomProgressBar)
- ProgressBar bottomProgressBar;
- boolean isLoadingCategories;
-
- @Inject RecentSearchesDao recentSearchesDao;
- @Inject CategoryClient categoryClient;
-
- @Inject
- @Named("default_preferences")
- JsonKvStore basicKvStore;
-
- private SearchCategoriesAdapter categoriesAdapter;
- private List queryList = new ArrayList<>();
-
- /**
- * This method saves Search Query in the Recent Searches Database.
- * @param query
- */
- private void saveQuery(String query) {
- RecentSearch recentSearch = recentSearchesDao.find(query);
-
- // Newly searched query...
- if (recentSearch == null) {
- recentSearch = new RecentSearch(null, query, new Date());
- }
- else {
- recentSearch.setLastSearched(new Date());
- }
- recentSearchesDao.save(recentSearch);
-
- }
-
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
- View rootView = inflater.inflate(R.layout.fragment_browse_image, container, false);
- ButterKnife.bind(this, rootView);
- if (getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
- categoriesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
- }
- else{
- categoriesRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
- }
- categoriesAdapter = new SearchCategoriesAdapter(item -> {
- CategoryDetailsActivity.startYourself(getContext(), item);
- saveQuery(query);
- return Unit.INSTANCE;
- });
- categoriesRecyclerView.setAdapter(categoriesAdapter);
- categoriesRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
- @Override
- public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
- super.onScrollStateChanged(recyclerView, newState);
- // check if end of recycler view is reached, if yes then add more results to existing results
- if (!recyclerView.canScrollVertically(1)) {
- addCategoriesToList(query);
- }
- }
- });
- return rootView;
- }
-
- /**
- * Checks for internet connection and then initializes the recycler view with 25 categories of the searched query
- * Clearing categoryAdapter every time new keyword is searched so that user can see only new results
- */
- public void updateCategoryList(String query) {
- this.query = query;
- categoriesNotFoundView.setVisibility(GONE);
- if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
- handleNoInternet();
- return;
- }
- bottomProgressBar.setVisibility(View.VISIBLE);
- progressBar.setVisibility(GONE);
- queryList.clear();
- categoriesAdapter.clear();
- compositeDisposable.add(categoryClient.searchCategories(query,25)
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .doOnSubscribe(disposable -> saveQuery(query))
- .subscribe(this::handleSuccess, this::handleError));
- }
-
-
- /**
- * Adds 25 more results to existing search results
- */
- public void addCategoriesToList(String query) {
- if(isLoadingCategories) {
- return;
- }
- isLoadingCategories=true;
- this.query = query;
- bottomProgressBar.setVisibility(View.VISIBLE);
- progressBar.setVisibility(GONE);
- compositeDisposable.add(categoryClient.searchCategories(query,25, queryList.size())
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(this::handlePaginationSuccess, this::handleError));
- }
-
- /**
- * Handles the success scenario
- * it initializes the recycler view by adding items to the adapter
- */
- private void handlePaginationSuccess(List mediaList) {
- queryList.addAll(mediaList);
- progressBar.setVisibility(View.GONE);
- bottomProgressBar.setVisibility(GONE);
- categoriesAdapter.addAll(mediaList);
- isLoadingCategories=false;
- }
-
-
-
- /**
- * Handles the success scenario
- * it initializes the recycler view by adding items to the adapter
- */
- private void handleSuccess(List mediaList) {
- queryList = mediaList;
- if (mediaList == null || mediaList.isEmpty()) {
- initErrorView();
- }
- else {
-
- bottomProgressBar.setVisibility(View.GONE);
- progressBar.setVisibility(GONE);
- categoriesAdapter.addAll(mediaList);
- }
- }
-
- /**
- * Logs and handles API error scenario
- */
- private void handleError(Throwable throwable) {
- Timber.e(throwable, "Error occurred while loading queried categories");
- try {
- initErrorView();
- ViewUtil.showShortSnackbar(categoriesRecyclerView, R.string.error_loading_categories);
- }catch (Exception e){
- e.printStackTrace();
- }
-
- }
-
- /**
- * Handles the UI updates for a error scenario
- */
- private void initErrorView() {
- progressBar.setVisibility(GONE);
- categoriesNotFoundView.setVisibility(VISIBLE);
- categoriesNotFoundView.setText(getString(R.string.categories_not_found,query));
- }
-
- /**
- * Handles the UI updates for no internet scenario
- */
- private void handleNoInternet() {
- progressBar.setVisibility(GONE);
- ViewUtil.showShortSnackbar(categoriesRecyclerView, R.string.no_internet);
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/categories/SearchCategoryFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/categories/SearchCategoryFragment.kt
new file mode 100644
index 000000000..aa5cae887
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/categories/SearchCategoryFragment.kt
@@ -0,0 +1,24 @@
+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.BaseSearchFragment
+import fr.free.nrw.commons.explore.SearchFragmentContract
+import javax.inject.Inject
+
+/**
+ * Displays the category search screen.
+ */
+class SearchCategoryFragment : BaseSearchFragment() {
+ @Inject
+ lateinit var presenter: SearchCategoriesFragmentContract.Presenter
+
+ override val emptyTemplateTextId: Int = R.string.categories_not_found
+
+ override val injectedPresenter: SearchFragmentContract.Presenter
+ get() = presenter
+
+ override val pagedListAdapter by lazy {
+ PagedSearchCategoriesAdapter { CategoryDetailsActivity.startYourself(context, it) }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/DepictionAdapter.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/DepictionAdapter.kt
index 46dbd2bfc..8310915a1 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/DepictionAdapter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/DepictionAdapter.kt
@@ -4,10 +4,10 @@ import android.view.View
import android.view.ViewGroup
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.BaseViewHolder
+import fr.free.nrw.commons.explore.inflate
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
-import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_depictions.*
@@ -19,22 +19,21 @@ class DepictionAdapter(val onDepictionClicked: (DepictedItem) -> Unit) :
override fun areContentsTheSame(oldItem: DepictedItem, newItem: DepictedItem) =
oldItem == newItem
-
}
-
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DepictedItemViewHolder {
- return DepictedItemViewHolder(parent.inflate(R.layout.item_depictions))
+ return DepictedItemViewHolder(parent.inflate(R.layout.item_depictions), onDepictionClicked)
}
override fun onBindViewHolder(holder: DepictedItemViewHolder, position: Int) {
- holder.bind(getItem(position)!!, onDepictionClicked)
+ holder.bind(getItem(position)!!)
}
}
-class DepictedItemViewHolder(override val containerView: View) :
- RecyclerView.ViewHolder(containerView), LayoutContainer {
- fun bind(item: DepictedItem, onDepictionClicked: (DepictedItem) -> Unit) {
+class DepictedItemViewHolder(containerView: View, val onDepictionClicked: (DepictedItem) -> Unit) :
+ BaseViewHolder(containerView) {
+
+ override fun bind(item: DepictedItem) {
containerView.setOnClickListener { onDepictionClicked(item) }
depicts_label.text = item.name
description.text = item.description
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/PageableDepictionsDataSource.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/PageableDepictionsDataSource.kt
new file mode 100644
index 000000000..f6abeec60
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/PageableDepictionsDataSource.kt
@@ -0,0 +1,22 @@
+package fr.free.nrw.commons.explore.depictions
+
+import fr.free.nrw.commons.explore.LiveDataConverter
+import fr.free.nrw.commons.explore.LoadingState
+import fr.free.nrw.commons.explore.PageableDataSource
+import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
+import io.reactivex.processors.PublishProcessor
+import javax.inject.Inject
+
+typealias LoadFunction = (Int, Int) -> List
+typealias LoadingStates = PublishProcessor
+
+class PageableDepictionsDataSource @Inject constructor(
+ liveDataConverter: LiveDataConverter,
+ val depictsClient: DepictsClient
+) : PageableDataSource(liveDataConverter) {
+
+ override val loadFunction = { loadSize: Int, startPosition: Int ->
+ depictsClient.searchForDepictions(query, loadSize, startPosition).blockingGet()
+ }
+}
+
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragment.kt
index 7d9ec10de..0acf619fc 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragment.kt
@@ -1,99 +1,26 @@
package fr.free.nrw.commons.explore.depictions
-import android.content.Context
-import android.content.res.Configuration
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.View.GONE
-import android.view.View.VISIBLE
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.Observer
-import androidx.paging.PagedList
-import androidx.recyclerview.widget.GridLayoutManager
-import androidx.recyclerview.widget.MergeAdapter
import fr.free.nrw.commons.R
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity
-import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
+import fr.free.nrw.commons.explore.BaseSearchFragment
+import fr.free.nrw.commons.explore.SearchFragmentContract
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
-import fr.free.nrw.commons.utils.ViewUtil
-import kotlinx.android.synthetic.main.fragment_search_depictions.*
import javax.inject.Inject
/**
* Display depictions in search fragment
*/
-class SearchDepictionsFragment : CommonsDaggerSupportFragment(),
+class SearchDepictionsFragment : BaseSearchFragment(),
SearchDepictionsFragmentContract.View {
-
@Inject
- lateinit var presenter: SearchDepictionsFragmentContract.UserActionListener
+ lateinit var presenter: SearchDepictionsFragmentContract.Presenter
- private val depictionsAdapter by lazy {
+ override val emptyTemplateTextId: Int = R.string.depictions_not_found
+
+ override val injectedPresenter: SearchFragmentContract.Presenter
+ get() = presenter
+
+ override val pagedListAdapter by lazy {
DepictionAdapter { WikidataItemDetailsActivity.startYourself(context, it) }
}
- private val loadingAdapter by lazy { FooterAdapter { presenter.retryFailedRequest() } }
- private val mergeAdapter by lazy { MergeAdapter(depictionsAdapter, loadingAdapter) }
-
- var searchResults: LiveData>? = null
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ) = inflater.inflate(R.layout.fragment_search_depictions, container, false)
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- depictionsSearchResultsList.apply {
- layoutManager = GridLayoutManager(context, if (isPortrait) 1 else 2)
- adapter = mergeAdapter
- }
- presenter.listFooterData.observe(viewLifecycleOwner, Observer(loadingAdapter::submitList))
- }
-
- override fun onAttach(context: Context) {
- super.onAttach(context)
- presenter.onAttachView(this)
- }
-
- override fun onDetach() {
- super.onDetach()
- presenter.onDetachView()
- }
-
- override fun observeSearchResults(searchResults: LiveData>) {
- this.searchResults?.removeObservers(viewLifecycleOwner)
- this.searchResults = searchResults
- searchResults.observe(viewLifecycleOwner, Observer {
- depictionsAdapter.submitList(it)
- depictionNotFound.visibility = if (it.loadedCount == 0) VISIBLE else GONE
- })
- }
-
- override fun setEmptyViewText(query: String) {
- depictionNotFound.text = getString(R.string.depictions_not_found, query)
- }
-
- override fun hideInitialLoadProgress() {
- depictionSearchInitialLoadProgress.visibility = GONE
- }
-
- override fun showInitialLoadInProgress() {
- depictionSearchInitialLoadProgress.visibility = VISIBLE
- }
-
- override fun showSnackbar() {
- ViewUtil.showShortSnackbar(depictionsSearchResultsList, R.string.error_loading_depictions)
- }
-
- fun onQueryUpdated(query: String) {
- presenter.onQueryUpdated(query)
- }
}
-
-private val Fragment.isPortrait get() = orientation == Configuration.ORIENTATION_PORTRAIT
-
-private val Fragment.orientation get() = activity!!.resources.configuration.orientation
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragmentContract.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragmentContract.kt
index 69ade7c7b..3f4da119e 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragmentContract.kt
+++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragmentContract.kt
@@ -1,25 +1,12 @@
package fr.free.nrw.commons.explore.depictions
-import androidx.lifecycle.LiveData
-import androidx.paging.PagedList
-import fr.free.nrw.commons.BasePresenter
+import fr.free.nrw.commons.explore.SearchFragmentContract
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 {
- fun showSnackbar()
- fun observeSearchResults(searchResults: LiveData>)
- fun setEmptyViewText(query: String)
- fun showInitialLoadInProgress()
- fun hideInitialLoadProgress()
- }
-
- interface UserActionListener : BasePresenter {
- val listFooterData: LiveData>
- fun onQueryUpdated(query: String)
- fun retryFailedRequest()
- }
+ interface View : SearchFragmentContract.View
+ interface Presenter : SearchFragmentContract.Presenter
}
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragmentPresenter.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragmentPresenter.kt
index b4d02409c..dbf0e6e63 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragmentPresenter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragmentPresenter.kt
@@ -1,11 +1,9 @@
package fr.free.nrw.commons.explore.depictions
-import androidx.lifecycle.MutableLiveData
import fr.free.nrw.commons.di.CommonsApplicationModule
-import fr.free.nrw.commons.upload.depicts.proxy
+import fr.free.nrw.commons.explore.BaseSearchPresenter
+import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import io.reactivex.Scheduler
-import io.reactivex.disposables.CompositeDisposable
-import timber.log.Timber
import javax.inject.Inject
import javax.inject.Named
@@ -13,61 +11,7 @@ import javax.inject.Named
* The presenter class for SearchDepictionsFragment
*/
class SearchDepictionsFragmentPresenter @Inject constructor(
- @param:Named(CommonsApplicationModule.MAIN_THREAD) private val mainThreadScheduler: Scheduler,
- private val searchableDataSourceFactory: SearchableDepictionsDataSourceFactory
-) : SearchDepictionsFragmentContract.UserActionListener {
- private val compositeDisposable = CompositeDisposable()
- private var view = DUMMY
- private var currentQuery: String? = null
- override val listFooterData = MutableLiveData>().apply { value = emptyList() }
-
- override fun onAttachView(view: SearchDepictionsFragmentContract.View) {
- this.view = view
- compositeDisposable.addAll(
- searchableDataSourceFactory.searchResults.subscribe(view::observeSearchResults),
- searchableDataSourceFactory.loadingStates
- .observeOn(mainThreadScheduler)
- .subscribe(::onLoadingState, Timber::e),
- searchableDataSourceFactory.noItemsLoadedEvent.subscribe {
- setEmptyViewText()
- }
- )
- }
-
- private fun onLoadingState(it: LoadingState) = when (it) {
- LoadingState.Loading -> listFooterData.postValue(listOf(FooterItem.LoadingItem))
- LoadingState.Complete -> {
- listFooterData.postValue(emptyList())
- view.hideInitialLoadProgress()
- }
- LoadingState.InitialLoad -> view.showInitialLoadInProgress()
- LoadingState.Error -> {
- setEmptyViewText()
- view.showSnackbar()
- view.hideInitialLoadProgress()
- listFooterData.postValue(listOf(FooterItem.RefreshItem))
- }
- }
-
- private fun setEmptyViewText() {
- currentQuery?.let(view::setEmptyViewText)
- }
-
- override fun retryFailedRequest() {
- searchableDataSourceFactory.retryFailedRequest()
- }
-
- override fun onDetachView() {
- view = DUMMY
- compositeDisposable.clear()
- }
-
- override fun onQueryUpdated(query: String) {
- currentQuery = query
- searchableDataSourceFactory.onQueryUpdated(query)
- }
-
- companion object {
- private val DUMMY: SearchDepictionsFragmentContract.View = proxy()
- }
-}
+ @Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
+ dataSourceFactory: PageableDepictionsDataSource
+) : BaseSearchPresenter(mainThreadScheduler, dataSourceFactory),
+ SearchDepictionsFragmentContract.Presenter
diff --git a/app/src/main/res/layout/fragment_search_depictions.xml b/app/src/main/res/layout/fragment_search_paginated.xml
similarity index 73%
rename from app/src/main/res/layout/fragment_search_depictions.xml
rename to app/src/main/res/layout/fragment_search_paginated.xml
index c87bbafcd..265c6914c 100644
--- a/app/src/main/res/layout/fragment_search_depictions.xml
+++ b/app/src/main/res/layout/fragment_search_paginated.xml
@@ -2,11 +2,11 @@
+ android:orientation="vertical"
+ android:paddingTop="@dimen/tiny_gap">
+ android:layout_centerInParent="true" />
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragmentPresenterTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/explore/BaseSearchPresenterTest.kt
similarity index 58%
rename from app/src/test/kotlin/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragmentPresenterTest.kt
rename to app/src/test/kotlin/fr/free/nrw/commons/explore/BaseSearchPresenterTest.kt
index 21c4845dd..740984882 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragmentPresenterTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/explore/BaseSearchPresenterTest.kt
@@ -1,11 +1,10 @@
-package fr.free.nrw.commons.explore.depictions
+package fr.free.nrw.commons.explore
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.LiveData
import androidx.paging.PagedList
import com.jraska.livedata.test
import com.nhaarman.mockitokotlin2.*
-import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import io.reactivex.processors.PublishProcessor
import io.reactivex.schedulers.TestScheduler
import org.junit.Before
@@ -14,25 +13,25 @@ import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
-class SearchDepictionsFragmentPresenterTest {
+class BaseSearchPresenterTest {
@Rule
@JvmField
var instantTaskExecutorRule = InstantTaskExecutorRule()
@Mock
- internal lateinit var view: SearchDepictionsFragmentContract.View
+ internal lateinit var view: SearchFragmentContract.View
- private lateinit var searchDepictionsFragmentPresenter: SearchDepictionsFragmentPresenter
+ private lateinit var baseSearchPresenter: BaseSearchPresenter
private lateinit var testScheduler: TestScheduler
@Mock
- private lateinit var searchableDepictionsDataSourceFactory: SearchableDepictionsDataSourceFactory
+ private lateinit var pageableDataSource: PageableDataSource
private var loadingStates: PublishProcessor = PublishProcessor.create()
- private var searchResults: PublishProcessor>> =
+ private var searchResults: PublishProcessor>> =
PublishProcessor.create()
private var noItemLoadedEvent: PublishProcessor = PublishProcessor.create()
@@ -41,21 +40,19 @@ class SearchDepictionsFragmentPresenterTest {
@Throws(Exception::class)
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(searchableDepictionsDataSourceFactory.searchResults).thenReturn(searchResults)
- whenever(searchableDepictionsDataSourceFactory.loadingStates).thenReturn(loadingStates)
- whenever(searchableDepictionsDataSourceFactory.noItemsLoadedEvent)
+ whenever(pageableDataSource.searchResults).thenReturn(searchResults)
+ whenever(pageableDataSource.loadingStates).thenReturn(loadingStates)
+ whenever(pageableDataSource.noItemsLoadedEvent)
.thenReturn(noItemLoadedEvent)
testScheduler = TestScheduler()
- searchDepictionsFragmentPresenter = SearchDepictionsFragmentPresenter(
- testScheduler,
- searchableDepictionsDataSourceFactory
- )
- searchDepictionsFragmentPresenter.onAttachView(view)
+ baseSearchPresenter =
+ object : BaseSearchPresenter(testScheduler, pageableDataSource) {}
+ baseSearchPresenter.onAttachView(view)
}
@Test
fun `searchResults emission updates the view`() {
- val pagedListLiveData = mock>>()
+ val pagedListLiveData = mock>>()
searchResults.offer(pagedListLiveData)
verify(view).observeSearchResults(pagedListLiveData)
}
@@ -63,14 +60,13 @@ class SearchDepictionsFragmentPresenterTest {
@Test
fun `Loading offers a loading list item`() {
onLoadingState(LoadingState.Loading)
- searchDepictionsFragmentPresenter.listFooterData.test()
- .assertValue(listOf(FooterItem.LoadingItem))
+ baseSearchPresenter.listFooterData.test().assertValue(listOf(FooterItem.LoadingItem))
}
@Test
fun `Complete offers an empty list item and hides initial loader`() {
onLoadingState(LoadingState.Complete)
- searchDepictionsFragmentPresenter.listFooterData.test()
+ baseSearchPresenter.listFooterData.test()
.assertValue(emptyList())
verify(view).hideInitialLoadProgress()
}
@@ -83,13 +79,12 @@ class SearchDepictionsFragmentPresenterTest {
@Test
fun `Error offers a refresh list item, hides initial loader and shows error with a set text`() {
- searchDepictionsFragmentPresenter.onQueryUpdated("test")
+ baseSearchPresenter.onQueryUpdated("test")
onLoadingState(LoadingState.Error)
verify(view).setEmptyViewText("test")
verify(view).showSnackbar()
verify(view).hideInitialLoadProgress()
- searchDepictionsFragmentPresenter.listFooterData.test()
- .assertValue(listOf(FooterItem.RefreshItem))
+ baseSearchPresenter.listFooterData.test().assertValue(listOf(FooterItem.RefreshItem))
}
@Test
@@ -98,35 +93,33 @@ class SearchDepictionsFragmentPresenterTest {
verify(view, never()).setEmptyViewText(any())
verify(view).showSnackbar()
verify(view).hideInitialLoadProgress()
- searchDepictionsFragmentPresenter.listFooterData.test()
- .assertValue(listOf(FooterItem.RefreshItem))
+ baseSearchPresenter.listFooterData.test().assertValue(listOf(FooterItem.RefreshItem))
}
@Test
fun `no Items event sets empty view text`() {
- searchDepictionsFragmentPresenter.onQueryUpdated("test")
+ baseSearchPresenter.onQueryUpdated("test")
noItemLoadedEvent.offer(Unit)
verify(view).setEmptyViewText("test")
}
@Test
fun `retryFailedRequest calls retry`() {
- searchDepictionsFragmentPresenter.retryFailedRequest()
- verify(searchableDepictionsDataSourceFactory).retryFailedRequest()
+ baseSearchPresenter.retryFailedRequest()
+ verify(pageableDataSource).retryFailedRequest()
}
@Test
fun `onDetachView stops subscriptions`() {
- searchDepictionsFragmentPresenter.onDetachView()
+ baseSearchPresenter.onDetachView()
onLoadingState(LoadingState.Loading)
- searchDepictionsFragmentPresenter.listFooterData.test()
- .assertValue(emptyList())
+ baseSearchPresenter.listFooterData.test().assertValue(emptyList())
}
@Test
fun `onQueryUpdated updates dataSourceFactory`() {
- searchDepictionsFragmentPresenter.onQueryUpdated("test")
- verify(searchableDepictionsDataSourceFactory).onQueryUpdated("test")
+ baseSearchPresenter.onQueryUpdated("test")
+ verify(pageableDataSource).onQueryUpdated("test")
}
private fun onLoadingState(loadingState: LoadingState) {
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/explore/PageableDataSourceTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/explore/PageableDataSourceTest.kt
new file mode 100644
index 000000000..2ced05a27
--- /dev/null
+++ b/app/src/test/kotlin/fr/free/nrw/commons/explore/PageableDataSourceTest.kt
@@ -0,0 +1,73 @@
+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 org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+class PageableDataSourceTest {
+
+ @Mock
+ private lateinit var liveDataConverter: LiveDataConverter
+
+ private lateinit var pageableDataSource: PageableDataSource
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ pageableDataSource = object: PageableDataSource(liveDataConverter){
+ override val loadFunction: LoadFunction
+ get() = mock()
+
+ }
+ }
+
+ @Test
+ fun `onQueryUpdated emits new liveData`() {
+ val (_, liveData) = expectNewLiveData()
+ pageableDataSource.searchResults.test()
+ .also { pageableDataSource.onQueryUpdated("test") }
+ .assertValue(liveData)
+ }
+
+ @Test
+ fun `onQueryUpdated invokes livedatconverter with no items emitter`() {
+ val (zeroItemsFuncCaptor, _) = expectNewLiveData()
+ pageableDataSource.onQueryUpdated("test")
+ pageableDataSource.noItemsLoadedEvent.test()
+ .also { zeroItemsFuncCaptor.firstValue.invoke() }
+ .assertValue(Unit)
+ }
+
+ /*
+ * Just for coverage, no way to really assert this
+ * */
+ @Test
+ fun `retryFailedRequest does nothing without a factory`() {
+ pageableDataSource.retryFailedRequest()
+ }
+
+ @Test
+ @Ignore("Rewrite with Mockk constructor mocks")
+ fun `retryFailedRequest retries with a factory`() {
+ val (_, _, dataSourceFactoryCaptor) = expectNewLiveData()
+ pageableDataSource.onQueryUpdated("test")
+ val dataSourceFactory = spy(dataSourceFactoryCaptor.firstValue)
+ pageableDataSource.retryFailedRequest()
+ verify(dataSourceFactory).retryFailedRequest()
+ }
+
+ private fun expectNewLiveData(): Triple Unit>, LiveData>, KArgumentCaptor>> {
+ val captor = argumentCaptor<() -> Unit>()
+ val dataSourceFactoryCaptor = argumentCaptor>()
+ val liveData: LiveData> = mock()
+ whenever(liveDataConverter.convert(dataSourceFactoryCaptor.capture(), captor.capture()))
+ .thenReturn(liveData)
+ return Triple(captor, liveData, dataSourceFactoryCaptor)
+ }
+}
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/explore/depictions/SearchDepictionsDataSourceFactoryTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/explore/SearchDataSourceFactoryTest.kt
similarity index 67%
rename from app/src/test/kotlin/fr/free/nrw/commons/explore/depictions/SearchDepictionsDataSourceFactoryTest.kt
rename to app/src/test/kotlin/fr/free/nrw/commons/explore/SearchDataSourceFactoryTest.kt
index 614b3a5cb..bbe6e9991 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/explore/depictions/SearchDepictionsDataSourceFactoryTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/explore/SearchDataSourceFactoryTest.kt
@@ -1,11 +1,12 @@
-package fr.free.nrw.commons.explore.depictions
+package fr.free.nrw.commons.explore
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.spy
import com.nhaarman.mockitokotlin2.verify
+import fr.free.nrw.commons.explore.depictions.DepictsClient
import io.reactivex.processors.PublishProcessor
import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.`is`
+import org.hamcrest.Matchers.instanceOf
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
@@ -13,26 +14,30 @@ import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
-class SearchDepictionsDataSourceFactoryTest {
+class SearchDataSourceFactoryTest {
@Mock
private lateinit var depictsClient: DepictsClient
@Mock
private lateinit var loadingStates: PublishProcessor
- private lateinit var factory: SearchDepictionsDataSourceFactory
+ private lateinit var factory: SearchDataSourceFactory
+
+ private var function: (Int, Int) -> List = mock()
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- factory = SearchDepictionsDataSourceFactory(depictsClient, "test", loadingStates)
+ factory = object : SearchDataSourceFactory(loadingStates) {
+ override val loadFunction get() = function
+ }
}
@Test
fun `create returns a dataSource`() {
assertThat(
factory.create(),
- `is`(SearchDepictionsDataSource(depictsClient, loadingStates, "test"))
+ instanceOf(SearchDataSource::class.java)
)
}
@@ -40,7 +45,7 @@ class SearchDepictionsDataSourceFactoryTest {
@Ignore("Rewrite with Mockk constructor mocks")
fun `retryFailedRequest invokes method if not null`() {
val spyFactory = spy(factory)
- val dataSource = mock()
+ val dataSource = mock>()
Mockito.doReturn(dataSource).`when`(spyFactory).create()
factory.retryFailedRequest()
verify(dataSource).retryFailedRequest()
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/explore/depictions/SearchDepictionsDataSourceTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/explore/SearchDataSourceTest.kt
similarity index 57%
rename from app/src/test/kotlin/fr/free/nrw/commons/explore/depictions/SearchDepictionsDataSourceTest.kt
rename to app/src/test/kotlin/fr/free/nrw/commons/explore/SearchDataSourceTest.kt
index 50af80ea1..8254e517d 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/explore/depictions/SearchDepictionsDataSourceTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/explore/SearchDataSourceTest.kt
@@ -1,10 +1,8 @@
-package fr.free.nrw.commons.explore.depictions
+package fr.free.nrw.commons.explore
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 fr.free.nrw.commons.explore.depictions.LoadingStates
import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.processors.PublishProcessor
import io.reactivex.schedulers.Schedulers
@@ -14,13 +12,13 @@ import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
-class SearchDepictionsDataSourceTest {
-
- @Mock
- private lateinit var depictsClient: DepictsClient
+class SearchDataSourceTest {
private lateinit var loadingStates: PublishProcessor
- private lateinit var searchDepictionsDataSource: SearchDepictionsDataSource
+ private lateinit var searchDepictionsDataSource: TestSearchDataSource
+
+ @Mock
+ private lateinit var mockGetItems: MockGetItems
@Before
fun setUp() {
@@ -28,7 +26,10 @@ class SearchDepictionsDataSourceTest {
MockitoAnnotations.initMocks(this)
loadingStates = PublishProcessor.create()
searchDepictionsDataSource =
- SearchDepictionsDataSource(depictsClient, loadingStates, "test")
+ TestSearchDataSource(
+ loadingStates,
+ mockGetItems
+ )
}
@After
@@ -39,68 +40,81 @@ class SearchDepictionsDataSourceTest {
@Test
fun `loadInitial returns results and emits InitialLoad & Complete`() {
val params = PositionalDataSource.LoadInitialParams(0, 1, 2, false)
- val callback = mock>()
- whenever(depictsClient.searchForDepictions("test", 1, 0))
- .thenReturn(Single.just(emptyList()))
+ val callback = mock>()
+ whenever(mockGetItems.getItems(1, 0)).thenReturn(emptyList())
val testSubscriber = loadingStates.test()
searchDepictionsDataSource.loadInitial(params, callback)
verify(callback).onResult(emptyList(), 0)
- testSubscriber.assertValues(InitialLoad, Complete)
+ testSubscriber.assertValues(LoadingState.InitialLoad, LoadingState.Complete)
}
@Test
fun `loadInitial onError does not return results and emits InitialLoad & Error`() {
val params = PositionalDataSource.LoadInitialParams(0, 1, 2, false)
- val callback = mock>()
- whenever(depictsClient.searchForDepictions("test", 1, 0))
- .thenThrow(RuntimeException())
+ val callback = mock>()
+ whenever(mockGetItems.getItems(1, 0)).thenThrow(RuntimeException())
val testSubscriber = loadingStates.test()
searchDepictionsDataSource.loadInitial(params, callback)
verify(callback, never()).onResult(any(), any())
- testSubscriber.assertValues(InitialLoad, Error)
+ testSubscriber.assertValues(LoadingState.InitialLoad, LoadingState.Error)
}
@Test
fun `loadRange returns results and emits Loading & Complete`() {
- val callback: PositionalDataSource.LoadRangeCallback = mock()
+ val callback: PositionalDataSource.LoadRangeCallback = mock()
val params = PositionalDataSource.LoadRangeParams(0, 1)
- whenever(depictsClient.searchForDepictions("test", params.loadSize, params.startPosition))
- .thenReturn(Single.just(emptyList()))
+ whenever(mockGetItems.getItems(params.loadSize, params.startPosition))
+ .thenReturn(emptyList())
val testSubscriber = loadingStates.test()
searchDepictionsDataSource.loadRange(params, callback)
verify(callback).onResult(emptyList())
- testSubscriber.assertValues(Loading, Complete)
+ testSubscriber.assertValues(LoadingState.Loading, LoadingState.Complete)
}
@Test
fun `loadRange onError does not return results and emits Loading & Error`() {
- val callback: PositionalDataSource.LoadRangeCallback = mock()
+ val callback: PositionalDataSource.LoadRangeCallback = mock()
val params = PositionalDataSource.LoadRangeParams(0, 1)
- whenever(depictsClient.searchForDepictions("test", params.loadSize, params.startPosition))
+ whenever(mockGetItems.getItems(params.loadSize, params.startPosition))
.thenThrow(RuntimeException())
val testSubscriber = loadingStates.test()
searchDepictionsDataSource.loadRange(params, callback)
verify(callback, never()).onResult(any())
- testSubscriber.assertValues(Loading, Error)
+ testSubscriber.assertValues(LoadingState.Loading, LoadingState.Error)
}
@Test
fun `retryFailedRequest does nothing when null`() {
searchDepictionsDataSource.retryFailedRequest()
- verifyNoMoreInteractions(depictsClient)
+ verifyNoMoreInteractions(mockGetItems)
}
@Test
fun `retryFailedRequest retries last request`() {
- val callback: PositionalDataSource.LoadRangeCallback = mock()
+ val callback: PositionalDataSource.LoadRangeCallback = mock()
val params = PositionalDataSource.LoadRangeParams(0, 1)
- whenever(depictsClient.searchForDepictions("test", params.loadSize, params.startPosition))
- .thenThrow(RuntimeException()).thenReturn(Single.just(emptyList()))
+ whenever(mockGetItems.getItems(params.loadSize, params.startPosition))
+ .thenThrow(RuntimeException()).thenReturn(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)
+ testSubscriber.assertValues(
+ LoadingState.Loading,
+ LoadingState.Error,
+ LoadingState.Loading,
+ LoadingState.Complete
+ )
}
}
+
+class TestSearchDataSource(loadingStates: LoadingStates, val mockGetItems: MockGetItems) :
+ SearchDataSource(loadingStates) {
+ override fun getItems(loadSize: Int, startPosition: Int): List =
+ mockGetItems.getItems(loadSize, startPosition)
+}
+
+interface MockGetItems {
+ fun getItems(loadSize: Int, startPosition: Int): List
+}
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/explore/categroies/PageableCategoriesDataSourceTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/explore/categroies/PageableCategoriesDataSourceTest.kt
new file mode 100644
index 000000000..1561273c6
--- /dev/null
+++ b/app/src/test/kotlin/fr/free/nrw/commons/explore/categroies/PageableCategoriesDataSourceTest.kt
@@ -0,0 +1,22 @@
+package fr.free.nrw.commons.explore.categroies
+
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.whenever
+import fr.free.nrw.commons.category.CategoryClient
+import fr.free.nrw.commons.explore.categories.PageableCategoriesDataSource
+import io.reactivex.Observable
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers
+import org.junit.Test
+
+class PageableCategoriesDataSourceTest {
+ @Test
+ fun `loadFunction loads categories`() {
+ val categoryClient: CategoryClient = mock()
+ whenever(categoryClient.searchCategories("test", 0, 1))
+ .thenReturn(Observable.just(emptyList()))
+ val pageableCategoriesDataSource = PageableCategoriesDataSource(mock(), categoryClient)
+ pageableCategoriesDataSource.onQueryUpdated("test")
+ assertThat(pageableCategoriesDataSource.loadFunction(0, 1), Matchers.`is`(emptyList()))
+ }
+}
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/explore/depictions/PageableDepictionsDataSourceTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/explore/depictions/PageableDepictionsDataSourceTest.kt
new file mode 100644
index 000000000..df3496c62
--- /dev/null
+++ b/app/src/test/kotlin/fr/free/nrw/commons/explore/depictions/PageableDepictionsDataSourceTest.kt
@@ -0,0 +1,21 @@
+package fr.free.nrw.commons.explore.depictions
+
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.whenever
+import io.reactivex.Single
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers
+import org.junit.Test
+
+class PageableDepictionsDataSourceTest {
+
+ @Test
+ fun `loadFunction loads depictions`() {
+ val depictsClient: DepictsClient = mock()
+ 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()))
+ }
+}
+
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/explore/depictions/SearchableDepictionsDataSourceFactoryTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/explore/depictions/SearchableDepictionsDataSourceFactoryTest.kt
deleted file mode 100644
index 5cabbaf78..000000000
--- a/app/src/test/kotlin/fr/free/nrw/commons/explore/depictions/SearchableDepictionsDataSourceFactoryTest.kt
+++ /dev/null
@@ -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 Unit>, LiveData>, SearchDepictionsDataSourceFactory> {
- val dataSourceFactory: SearchDepictionsDataSourceFactory = mock()
- whenever(
- searchDepictionsDataSourceFactoryFactory.create(
- "test",
- factory.loadingStates as PublishProcessor
- )
- ).thenReturn(dataSourceFactory)
- val captor = argumentCaptor<() -> Unit>()
- val liveData: LiveData> = mock()
- whenever(liveDataConverter.convert(eq(dataSourceFactory), captor.capture()))
- .thenReturn(liveData)
- return Triple(captor, liveData, dataSourceFactory)
- }
-}