#3810 Convert DepictedImagesFragment to use Pagination - rename base classes to better reflect usage

This commit is contained in:
Sean Mac Gillicuddy 2020-06-17 10:40:20 +01:00
parent 181a63a233
commit 310c29c2bd
25 changed files with 104 additions and 103 deletions

View file

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

View file

@ -10,12 +10,12 @@ class DepictedImagesFragment : PageableMediaFragment(), DepictedImagesContract.V
@Inject @Inject
lateinit var presenter: DepictedImagesContract.Presenter lateinit var presenter: DepictedImagesContract.Presenter
override val injectedPresenter: DepictedImagesContract.Presenter override val injectedPresenter
get() = presenter get() = presenter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
injectedPresenter.onQueryUpdated(arguments!!.getString("entityId")!!) onQueryUpdated(arguments!!.getString("entityId")!!)
} }
override fun onItemClicked(position: Int) { override fun onItemClicked(position: Int) {

View file

@ -2,7 +2,7 @@ package fr.free.nrw.commons.depictions.Media
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.di.CommonsApplicationModule import fr.free.nrw.commons.di.CommonsApplicationModule
import fr.free.nrw.commons.explore.BaseSearchPresenter import fr.free.nrw.commons.explore.BasePagingPresenter
import io.reactivex.Scheduler import io.reactivex.Scheduler
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named import javax.inject.Named
@ -13,5 +13,5 @@ import javax.inject.Named
class DepictedImagesPresenter @Inject constructor( class DepictedImagesPresenter @Inject constructor(
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler, @Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
dataSourceFactory: PageableDepictedMediaDataSource dataSourceFactory: PageableDepictedMediaDataSource
) : BaseSearchPresenter<Media>(mainThreadScheduler, dataSourceFactory), ) : BasePagingPresenter<Media>(mainThreadScheduler, dataSourceFactory),
DepictedImagesContract.Presenter DepictedImagesContract.Presenter

View file

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

View file

@ -19,11 +19,11 @@ import fr.free.nrw.commons.utils.ViewUtil
import kotlinx.android.synthetic.main.fragment_search_paginated.* import kotlinx.android.synthetic.main.fragment_search_paginated.*
abstract class BaseSearchFragment<T> : CommonsDaggerSupportFragment(), abstract class BasePagingFragment<T> : CommonsDaggerSupportFragment(),
SearchFragmentContract.View<T> { PagingContract.View<T> {
abstract val pagedListAdapter: PagedListAdapter<T, *> abstract val pagedListAdapter: PagedListAdapter<T, *>
abstract val injectedPresenter: SearchFragmentContract.Presenter<T> abstract val injectedPresenter: PagingContract.Presenter<T>
abstract val errorTextId: Int abstract val errorTextId: Int
private val loadingAdapter by lazy { FooterAdapter { injectedPresenter.retryFailedRequest() } } private val loadingAdapter by lazy { FooterAdapter { injectedPresenter.retryFailedRequest() } }
private val mergeAdapter by lazy { MergeAdapter(pagedListAdapter, loadingAdapter) } private val mergeAdapter by lazy { MergeAdapter(pagedListAdapter, loadingAdapter) }
@ -47,11 +47,12 @@ abstract class BaseSearchFragment<T> : CommonsDaggerSupportFragment(),
) )
} }
override fun observeSearchResults(searchResults: LiveData<PagedList<T>>) { override fun observePagingResults(searchResults: LiveData<PagedList<T>>) {
this.searchResults?.removeObservers(viewLifecycleOwner) this.searchResults?.removeObservers(viewLifecycleOwner)
this.searchResults = searchResults this.searchResults = searchResults
searchResults.observe(viewLifecycleOwner, Observer { searchResults.observe(viewLifecycleOwner, Observer {
pagedListAdapter.submitList(it) }) pagedListAdapter.submitList(it)
})
} }
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
@ -85,7 +86,7 @@ abstract class BaseSearchFragment<T> : CommonsDaggerSupportFragment(),
contentNotFound.visibility = View.VISIBLE contentNotFound.visibility = View.VISIBLE
} }
abstract fun getEmptyText(query: String):String abstract fun getEmptyText(query: String): String
override fun hideEmptyText() { override fun hideEmptyText() {
contentNotFound.visibility = View.GONE contentNotFound.visibility = View.GONE

View file

@ -7,25 +7,25 @@ import io.reactivex.disposables.CompositeDisposable
import timber.log.Timber import timber.log.Timber
abstract class BaseSearchPresenter<T>( abstract class BasePagingPresenter<T>(
val mainThreadScheduler: Scheduler, val mainThreadScheduler: Scheduler,
val pageableDataSource: PageableDataSource<T> val pageableBaseDataSource: PageableBaseDataSource<T>
) : SearchFragmentContract.Presenter<T> { ) : PagingContract.Presenter<T> {
private val DUMMY: SearchFragmentContract.View<T> = proxy() private val DUMMY: PagingContract.View<T> = proxy()
private var view: SearchFragmentContract.View<T> = DUMMY private var view: PagingContract.View<T> = DUMMY
private val compositeDisposable = CompositeDisposable() private val compositeDisposable = CompositeDisposable()
override val listFooterData = MutableLiveData<List<FooterItem>>().apply { value = emptyList() } override val listFooterData = MutableLiveData<List<FooterItem>>().apply { value = emptyList() }
override fun onAttachView(view: SearchFragmentContract.View<T>) { override fun onAttachView(view: PagingContract.View<T>) {
this.view = view this.view = view
compositeDisposable.addAll( compositeDisposable.addAll(
pageableDataSource.searchResults.subscribe(view::observeSearchResults), pageableBaseDataSource.pagingResults.subscribe(view::observePagingResults),
pageableDataSource.loadingStates pageableBaseDataSource.loadingStates
.observeOn(mainThreadScheduler) .observeOn(mainThreadScheduler)
.subscribe(::onLoadingState, Timber::e), .subscribe(::onLoadingState, Timber::e),
pageableDataSource.noItemsLoadedQueries.subscribe(view::showEmptyText) pageableBaseDataSource.noItemsLoadedQueries.subscribe(view::showEmptyText)
) )
} }
@ -50,7 +50,7 @@ abstract class BaseSearchPresenter<T>(
} }
override fun retryFailedRequest() { override fun retryFailedRequest() {
pageableDataSource.retryFailedRequest() pageableBaseDataSource.retryFailedRequest()
} }
override fun onDetachView() { override fun onDetachView() {
@ -59,7 +59,7 @@ abstract class BaseSearchPresenter<T>(
} }
override fun onQueryUpdated(query: String) { override fun onQueryUpdated(query: String) {
pageableDataSource.onQueryUpdated(query) pageableBaseDataSource.onQueryUpdated(query)
} }
} }

View file

@ -4,10 +4,10 @@ import androidx.lifecycle.LiveData
import androidx.paging.PagedList import androidx.paging.PagedList
import fr.free.nrw.commons.BasePresenter import fr.free.nrw.commons.BasePresenter
interface SearchFragmentContract { interface PagingContract {
interface View<T> { interface View<T> {
fun showSnackbar() fun showSnackbar()
fun observeSearchResults(searchResults: LiveData<PagedList<T>>) fun observePagingResults(searchResults: LiveData<PagedList<T>>)
fun showInitialLoadInProgress() fun showInitialLoadInProgress()
fun hideInitialLoadProgress() fun hideInitialLoadProgress()
fun showEmptyText(query: String) fun showEmptyText(query: String)

View file

@ -14,25 +14,25 @@ import javax.inject.Inject
private const val PAGE_SIZE = 50 private const val PAGE_SIZE = 50
private const val INITIAL_LOAD_SIZE = 50 private const val INITIAL_LOAD_SIZE = 50
abstract class PageableDataSource<T>(private val liveDataConverter: LiveDataConverter) { abstract class PageableBaseDataSource<T>(private val liveDataConverter: LiveDataConverter) {
lateinit var query: String lateinit var query: String
private val dataSourceFactoryFactory: () -> SearchDataSourceFactory<T> = { private val dataSourceFactoryFactory: () -> PagingDataSourceFactory<T> = {
dataSourceFactory(_loadingStates, loadFunction) dataSourceFactory(_loadingStates, loadFunction)
} }
private val _loadingStates = PublishProcessor.create<LoadingState>() private val _loadingStates = PublishProcessor.create<LoadingState>()
val loadingStates: Flowable<LoadingState> = _loadingStates val loadingStates: Flowable<LoadingState> = _loadingStates
private val _searchResults = PublishProcessor.create<LiveData<PagedList<T>>>() private val _pagingResults = PublishProcessor.create<LiveData<PagedList<T>>>()
val searchResults: Flowable<LiveData<PagedList<T>>> = _searchResults val pagingResults: Flowable<LiveData<PagedList<T>>> = _pagingResults
private val _noItemsLoadedEvent = PublishProcessor.create<String>() private val _noItemsLoadedEvent = PublishProcessor.create<String>()
val noItemsLoadedQueries: Flowable<String> = _noItemsLoadedEvent val noItemsLoadedQueries: Flowable<String> = _noItemsLoadedEvent
private var currentFactory: SearchDataSourceFactory<T>? = null private var currentFactory: PagingDataSourceFactory<T>? = null
abstract val loadFunction: LoadFunction<T> abstract val loadFunction: LoadFunction<T>
fun onQueryUpdated(query: String) { fun onQueryUpdated(query: String) {
this.query = query this.query = query
_searchResults.offer( _pagingResults.offer(
liveDataConverter.convert(dataSourceFactoryFactory().also { currentFactory = it }) { liveDataConverter.convert(dataSourceFactoryFactory().also { currentFactory = it }) {
_noItemsLoadedEvent.offer(query) _noItemsLoadedEvent.offer(query)
} }
@ -46,7 +46,7 @@ abstract class PageableDataSource<T>(private val liveDataConverter: LiveDataConv
class LiveDataConverter @Inject constructor() { class LiveDataConverter @Inject constructor() {
fun <T> convert( fun <T> convert(
dataSourceFactory: SearchDataSourceFactory<T>, dataSourceFactory: PagingDataSourceFactory<T>,
zeroItemsLoadedFunction: () -> Unit zeroItemsLoadedFunction: () -> Unit
): LiveData<PagedList<T>> { ): LiveData<PagedList<T>> {
return dataSourceFactory.toLiveData( return dataSourceFactory.toLiveData(
@ -65,7 +65,7 @@ class LiveDataConverter @Inject constructor() {
} }
abstract class SearchDataSourceFactory<T>(val loadingStates: LoadingStates) : abstract class PagingDataSourceFactory<T>(val loadingStates: LoadingStates) :
DataSource.Factory<Int, T>() { DataSource.Factory<Int, T>() {
private var currentDataSource: SearchDataSource<T>? = null private var currentDataSource: SearchDataSource<T>? = null
abstract val loadFunction: LoadFunction<T> abstract val loadFunction: LoadFunction<T>
@ -80,7 +80,7 @@ abstract class SearchDataSourceFactory<T>(val loadingStates: LoadingStates) :
} }
fun <T> dataSourceFactory(loadingStates: LoadingStates, loadFunction: LoadFunction<T>) = fun <T> dataSourceFactory(loadingStates: LoadingStates, loadFunction: LoadFunction<T>) =
object : SearchDataSourceFactory<T>(loadingStates) { object : PagingDataSourceFactory<T>(loadingStates) {
override val loadFunction: LoadFunction<T> = loadFunction override val loadFunction: LoadFunction<T> = loadFunction
} }

View file

@ -10,5 +10,5 @@ import javax.inject.Named
class SearchCategoriesFragmentPresenter @Inject constructor( class SearchCategoriesFragmentPresenter @Inject constructor(
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler, @Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
dataSourceFactory: PageableCategoriesDataSource dataSourceFactory: PageableCategoriesDataSource
) : BaseSearchPresenter<String>(mainThreadScheduler, dataSourceFactory), ) : BasePagingPresenter<String>(mainThreadScheduler, dataSourceFactory),
SearchCategoriesFragmentContract.Presenter SearchCategoriesFragmentContract.Presenter

View file

@ -2,13 +2,13 @@ package fr.free.nrw.commons.explore.categories
import fr.free.nrw.commons.category.CategoryClient import fr.free.nrw.commons.category.CategoryClient
import fr.free.nrw.commons.explore.LiveDataConverter import fr.free.nrw.commons.explore.LiveDataConverter
import fr.free.nrw.commons.explore.PageableDataSource import fr.free.nrw.commons.explore.PageableBaseDataSource
import javax.inject.Inject import javax.inject.Inject
class PageableCategoriesDataSource @Inject constructor( class PageableCategoriesDataSource @Inject constructor(
liveDataConverter: LiveDataConverter, liveDataConverter: LiveDataConverter,
val categoryClient: CategoryClient val categoryClient: CategoryClient
) : PageableDataSource<String>(liveDataConverter) { ) : PageableBaseDataSource<String>(liveDataConverter) {
override val loadFunction = { loadSize: Int, startPosition: Int -> override val loadFunction = { loadSize: Int, startPosition: Int ->
categoryClient.searchCategories(query, loadSize, startPosition).blockingFirst() categoryClient.searchCategories(query, loadSize, startPosition).blockingFirst()

View file

@ -1,8 +1,8 @@
package fr.free.nrw.commons.explore.categories package fr.free.nrw.commons.explore.categories
import fr.free.nrw.commons.explore.SearchFragmentContract import fr.free.nrw.commons.explore.PagingContract
interface SearchCategoriesFragmentContract { interface SearchCategoriesFragmentContract {
interface View : SearchFragmentContract.View<String> interface View : PagingContract.View<String>
interface Presenter : SearchFragmentContract.Presenter<String> interface Presenter : PagingContract.Presenter<String>
} }

View file

@ -2,20 +2,20 @@ package fr.free.nrw.commons.explore.categories
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.category.CategoryDetailsActivity import fr.free.nrw.commons.category.CategoryDetailsActivity
import fr.free.nrw.commons.explore.BaseSearchFragment import fr.free.nrw.commons.explore.BasePagingFragment
import fr.free.nrw.commons.explore.SearchFragmentContract import fr.free.nrw.commons.explore.PagingContract
import javax.inject.Inject import javax.inject.Inject
/** /**
* Displays the category search screen. * Displays the category search screen.
*/ */
class SearchCategoryFragment : BaseSearchFragment<String>() { class SearchCategoryFragment : BasePagingFragment<String>() {
@Inject @Inject
lateinit var presenter: SearchCategoriesFragmentContract.Presenter lateinit var presenter: SearchCategoriesFragmentContract.Presenter
override val errorTextId: Int = R.string.error_loading_categories override val errorTextId: Int = R.string.error_loading_categories
override val injectedPresenter: SearchFragmentContract.Presenter<String> override val injectedPresenter
get() = presenter get() = presenter
override val pagedListAdapter by lazy { override val pagedListAdapter by lazy {

View file

@ -2,7 +2,7 @@ package fr.free.nrw.commons.explore.depictions
import fr.free.nrw.commons.explore.LiveDataConverter import fr.free.nrw.commons.explore.LiveDataConverter
import fr.free.nrw.commons.explore.LoadingState import fr.free.nrw.commons.explore.LoadingState
import fr.free.nrw.commons.explore.PageableDataSource import fr.free.nrw.commons.explore.PageableBaseDataSource
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import io.reactivex.processors.PublishProcessor import io.reactivex.processors.PublishProcessor
import javax.inject.Inject import javax.inject.Inject
@ -13,7 +13,7 @@ typealias LoadingStates = PublishProcessor<LoadingState>
class PageableDepictionsDataSource @Inject constructor( class PageableDepictionsDataSource @Inject constructor(
liveDataConverter: LiveDataConverter, liveDataConverter: LiveDataConverter,
val depictsClient: DepictsClient val depictsClient: DepictsClient
) : PageableDataSource<DepictedItem>(liveDataConverter) { ) : PageableBaseDataSource<DepictedItem>(liveDataConverter) {
override val loadFunction = { loadSize: Int, startPosition: Int -> override val loadFunction = { loadSize: Int, startPosition: Int ->
depictsClient.searchForDepictions(query, loadSize, startPosition).blockingGet() depictsClient.searchForDepictions(query, loadSize, startPosition).blockingGet()

View file

@ -2,21 +2,21 @@ package fr.free.nrw.commons.explore.depictions
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity
import fr.free.nrw.commons.explore.BaseSearchFragment import fr.free.nrw.commons.explore.BasePagingFragment
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import javax.inject.Inject import javax.inject.Inject
/** /**
* Display depictions in search fragment * Display depictions in search fragment
*/ */
class SearchDepictionsFragment : BaseSearchFragment<DepictedItem>(), class SearchDepictionsFragment : BasePagingFragment<DepictedItem>(),
SearchDepictionsFragmentContract.View { SearchDepictionsFragmentContract.View {
@Inject @Inject
lateinit var presenter: SearchDepictionsFragmentContract.Presenter lateinit var presenter: SearchDepictionsFragmentContract.Presenter
override val errorTextId: Int = R.string.error_loading_depictions override val errorTextId: Int = R.string.error_loading_depictions
override val injectedPresenter: SearchDepictionsFragmentContract.Presenter override val injectedPresenter
get() = presenter get() = presenter
override val pagedListAdapter by lazy { override val pagedListAdapter by lazy {

View file

@ -1,12 +1,12 @@
package fr.free.nrw.commons.explore.depictions package fr.free.nrw.commons.explore.depictions
import fr.free.nrw.commons.explore.SearchFragmentContract import fr.free.nrw.commons.explore.PagingContract
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
/** /**
* The contract with with SearchDepictionsFragment and its presenter would talk to each other * The contract with with SearchDepictionsFragment and its presenter would talk to each other
*/ */
interface SearchDepictionsFragmentContract { interface SearchDepictionsFragmentContract {
interface View : SearchFragmentContract.View<DepictedItem> interface View : PagingContract.View<DepictedItem>
interface Presenter : SearchFragmentContract.Presenter<DepictedItem> interface Presenter : PagingContract.Presenter<DepictedItem>
} }

View file

@ -1,7 +1,7 @@
package fr.free.nrw.commons.explore.depictions package fr.free.nrw.commons.explore.depictions
import fr.free.nrw.commons.di.CommonsApplicationModule import fr.free.nrw.commons.di.CommonsApplicationModule
import fr.free.nrw.commons.explore.BaseSearchPresenter import fr.free.nrw.commons.explore.BasePagingPresenter
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import io.reactivex.Scheduler import io.reactivex.Scheduler
import javax.inject.Inject import javax.inject.Inject
@ -13,5 +13,5 @@ import javax.inject.Named
class SearchDepictionsFragmentPresenter @Inject constructor( class SearchDepictionsFragmentPresenter @Inject constructor(
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler, @Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
dataSourceFactory: PageableDepictionsDataSource dataSourceFactory: PageableDepictionsDataSource
) : BaseSearchPresenter<DepictedItem>(mainThreadScheduler, dataSourceFactory), ) : BasePagingPresenter<DepictedItem>(mainThreadScheduler, dataSourceFactory),
SearchDepictionsFragmentContract.Presenter SearchDepictionsFragmentContract.Presenter

View file

@ -2,7 +2,7 @@ package fr.free.nrw.commons.explore.media
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.explore.LiveDataConverter import fr.free.nrw.commons.explore.LiveDataConverter
import fr.free.nrw.commons.explore.PageableDataSource import fr.free.nrw.commons.explore.PageableBaseDataSource
import fr.free.nrw.commons.explore.depictions.LoadFunction import fr.free.nrw.commons.explore.depictions.LoadFunction
import fr.free.nrw.commons.media.MediaClient import fr.free.nrw.commons.media.MediaClient
import javax.inject.Inject import javax.inject.Inject
@ -10,7 +10,7 @@ import javax.inject.Inject
class PageableMediaDataSource @Inject constructor( class PageableMediaDataSource @Inject constructor(
liveDataConverter: LiveDataConverter, liveDataConverter: LiveDataConverter,
private val mediaClient: MediaClient private val mediaClient: MediaClient
) : PageableDataSource<Media>(liveDataConverter) { ) : PageableBaseDataSource<Media>(liveDataConverter) {
override val loadFunction: LoadFunction<Media> = { loadSize: Int, startPosition: Int -> override val loadFunction: LoadFunction<Media> = { loadSize: Int, startPosition: Int ->
mediaClient.getMediaListFromSearch(query, loadSize, startPosition).blockingGet() mediaClient.getMediaListFromSearch(query, loadSize, startPosition).blockingGet()
} }

View file

@ -4,11 +4,11 @@ import android.os.Bundle
import android.view.View import android.view.View
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.explore.BaseSearchFragment import fr.free.nrw.commons.explore.BasePagingFragment
import kotlinx.android.synthetic.main.fragment_search_paginated.* import kotlinx.android.synthetic.main.fragment_search_paginated.*
abstract class PageableMediaFragment : BaseSearchFragment<Media>() { abstract class PageableMediaFragment : BasePagingFragment<Media>() {
override val pagedListAdapter by lazy { PagedMediaAdapter(::onItemClicked) } override val pagedListAdapter by lazy { PagedMediaAdapter(::onItemClicked) }
override val errorTextId: Int = R.string.error_loading_images override val errorTextId: Int = R.string.error_loading_images

View file

@ -11,7 +11,7 @@ class SearchMediaFragment : PageableMediaFragment(), SearchMediaFragmentContract
@Inject @Inject
lateinit var presenter: SearchMediaFragmentContract.Presenter lateinit var presenter: SearchMediaFragmentContract.Presenter
override val injectedPresenter: SearchMediaFragmentContract.Presenter override val injectedPresenter
get() = presenter get() = presenter
override fun onItemClicked(position: Int) { override fun onItemClicked(position: Int) {

View file

@ -1,10 +1,10 @@
package fr.free.nrw.commons.explore.media package fr.free.nrw.commons.explore.media
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.explore.SearchFragmentContract import fr.free.nrw.commons.explore.PagingContract
interface SearchMediaFragmentContract { interface SearchMediaFragmentContract {
interface View : SearchFragmentContract.View<Media> interface View : PagingContract.View<Media>
interface Presenter : SearchFragmentContract.Presenter<Media> interface Presenter : PagingContract.Presenter<Media>
} }

View file

@ -2,7 +2,7 @@ package fr.free.nrw.commons.explore.media
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.di.CommonsApplicationModule import fr.free.nrw.commons.di.CommonsApplicationModule
import fr.free.nrw.commons.explore.BaseSearchPresenter import fr.free.nrw.commons.explore.BasePagingPresenter
import io.reactivex.Scheduler import io.reactivex.Scheduler
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named import javax.inject.Named
@ -10,5 +10,5 @@ import javax.inject.Named
class SearchMediaFragmentPresenter @Inject constructor( class SearchMediaFragmentPresenter @Inject constructor(
@Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler, @Named(CommonsApplicationModule.MAIN_THREAD) mainThreadScheduler: Scheduler,
dataSourceFactory: PageableMediaDataSource dataSourceFactory: PageableMediaDataSource
) : BaseSearchPresenter<Media>(mainThreadScheduler, dataSourceFactory), ) : BasePagingPresenter<Media>(mainThreadScheduler, dataSourceFactory),
SearchMediaFragmentContract.Presenter SearchMediaFragmentContract.Presenter

View file

@ -15,21 +15,21 @@ import org.junit.Test
import org.mockito.Mock import org.mockito.Mock
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
class BaseSearchPresenterTest { class BasePagingPresenterTest {
@Rule @Rule
@JvmField @JvmField
var instantTaskExecutorRule = InstantTaskExecutorRule() var instantTaskExecutorRule = InstantTaskExecutorRule()
@Mock @Mock
internal lateinit var view: SearchFragmentContract.View<String> internal lateinit var view: PagingContract.View<String>
private lateinit var baseSearchPresenter: BaseSearchPresenter<String> private lateinit var basePagingPresenter: BasePagingPresenter<String>
private lateinit var testScheduler: TestScheduler private lateinit var testScheduler: TestScheduler
@Mock @Mock
private lateinit var pageableDataSource: PageableDataSource<String> private lateinit var pageableBaseDataSource: PageableBaseDataSource<String>
private var loadingStates: PublishProcessor<LoadingState> = PublishProcessor.create() private var loadingStates: PublishProcessor<LoadingState> = PublishProcessor.create()
@ -42,34 +42,34 @@ class BaseSearchPresenterTest {
@Throws(Exception::class) @Throws(Exception::class)
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
whenever(pageableDataSource.searchResults).thenReturn(searchResults) whenever(pageableBaseDataSource.pagingResults).thenReturn(searchResults)
whenever(pageableDataSource.loadingStates).thenReturn(loadingStates) whenever(pageableBaseDataSource.loadingStates).thenReturn(loadingStates)
whenever(pageableDataSource.noItemsLoadedQueries) whenever(pageableBaseDataSource.noItemsLoadedQueries)
.thenReturn(noItemLoadedQueries) .thenReturn(noItemLoadedQueries)
testScheduler = TestScheduler() testScheduler = TestScheduler()
baseSearchPresenter = basePagingPresenter =
object : BaseSearchPresenter<String>(testScheduler, pageableDataSource) {} object : BasePagingPresenter<String>(testScheduler, pageableBaseDataSource) {}
baseSearchPresenter.onAttachView(view) basePagingPresenter.onAttachView(view)
} }
@Test @Test
fun `searchResults emission updates the view`() { fun `searchResults emission updates the view`() {
val pagedListLiveData = mock<LiveData<PagedList<String>>>() val pagedListLiveData = mock<LiveData<PagedList<String>>>()
searchResults.offer(pagedListLiveData) searchResults.offer(pagedListLiveData)
verify(view).observeSearchResults(pagedListLiveData) verify(view).observePagingResults(pagedListLiveData)
} }
@Test @Test
fun `Loading offers a loading list item`() { fun `Loading offers a loading list item`() {
onLoadingState(LoadingState.Loading) onLoadingState(LoadingState.Loading)
verify(view).hideEmptyText() verify(view).hideEmptyText()
baseSearchPresenter.listFooterData.test().assertValue(listOf(FooterItem.LoadingItem)) basePagingPresenter.listFooterData.test().assertValue(listOf(FooterItem.LoadingItem))
} }
@Test @Test
fun `Complete offers an empty list item and hides initial loader`() { fun `Complete offers an empty list item and hides initial loader`() {
onLoadingState(LoadingState.Complete) onLoadingState(LoadingState.Complete)
baseSearchPresenter.listFooterData.test() basePagingPresenter.listFooterData.test()
.assertValue(emptyList()) .assertValue(emptyList())
verify(view).hideInitialLoadProgress() verify(view).hideInitialLoadProgress()
} }
@ -83,11 +83,11 @@ class BaseSearchPresenterTest {
@Test @Test
fun `Error offers a refresh list item, hides initial loader and shows error with a set text`() { fun `Error offers a refresh list item, hides initial loader and shows error with a set text`() {
baseSearchPresenter.onQueryUpdated("test") basePagingPresenter.onQueryUpdated("test")
onLoadingState(LoadingState.Error) onLoadingState(LoadingState.Error)
verify(view).showSnackbar() verify(view).showSnackbar()
verify(view).hideInitialLoadProgress() verify(view).hideInitialLoadProgress()
baseSearchPresenter.listFooterData.test().assertValue(listOf(FooterItem.RefreshItem)) basePagingPresenter.listFooterData.test().assertValue(listOf(FooterItem.RefreshItem))
} }
@Test @Test
@ -98,21 +98,21 @@ class BaseSearchPresenterTest {
@Test @Test
fun `retryFailedRequest calls retry`() { fun `retryFailedRequest calls retry`() {
baseSearchPresenter.retryFailedRequest() basePagingPresenter.retryFailedRequest()
verify(pageableDataSource).retryFailedRequest() verify(pageableBaseDataSource).retryFailedRequest()
} }
@Test @Test
fun `onDetachView stops subscriptions`() { fun `onDetachView stops subscriptions`() {
baseSearchPresenter.onDetachView() basePagingPresenter.onDetachView()
onLoadingState(LoadingState.Loading) onLoadingState(LoadingState.Loading)
baseSearchPresenter.listFooterData.test().assertValue(emptyList()) basePagingPresenter.listFooterData.test().assertValue(emptyList())
} }
@Test @Test
fun `onQueryUpdated updates dataSourceFactory`() { fun `onQueryUpdated updates dataSourceFactory`() {
baseSearchPresenter.onQueryUpdated("test") basePagingPresenter.onQueryUpdated("test")
verify(pageableDataSource).onQueryUpdated("test") verify(pageableBaseDataSource).onQueryUpdated("test")
} }
private fun onLoadingState(loadingState: LoadingState) { private fun onLoadingState(loadingState: LoadingState) {

View file

@ -10,17 +10,17 @@ import org.junit.Test
import org.mockito.Mock import org.mockito.Mock
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
class PageableDataSourceTest { class PageableBaseDataSourceTest {
@Mock @Mock
private lateinit var liveDataConverter: LiveDataConverter private lateinit var liveDataConverter: LiveDataConverter
private lateinit var pageableDataSource: PageableDataSource<String> private lateinit var pageableBaseDataSource: PageableBaseDataSource<String>
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
pageableDataSource = object: PageableDataSource<String>(liveDataConverter){ pageableBaseDataSource = object: PageableBaseDataSource<String>(liveDataConverter){
override val loadFunction: LoadFunction<String> override val loadFunction: LoadFunction<String>
get() = mock() get() = mock()
@ -30,16 +30,16 @@ class PageableDataSourceTest {
@Test @Test
fun `onQueryUpdated emits new liveData`() { fun `onQueryUpdated emits new liveData`() {
val (_, liveData) = expectNewLiveData() val (_, liveData) = expectNewLiveData()
pageableDataSource.searchResults.test() pageableBaseDataSource.pagingResults.test()
.also { pageableDataSource.onQueryUpdated("test") } .also { pageableBaseDataSource.onQueryUpdated("test") }
.assertValue(liveData) .assertValue(liveData)
} }
@Test @Test
fun `onQueryUpdated invokes livedatconverter with no items emitter`() { fun `onQueryUpdated invokes livedatconverter with no items emitter`() {
val (zeroItemsFuncCaptor, _) = expectNewLiveData() val (zeroItemsFuncCaptor, _) = expectNewLiveData()
pageableDataSource.onQueryUpdated("test") pageableBaseDataSource.onQueryUpdated("test")
pageableDataSource.noItemsLoadedQueries.test() pageableBaseDataSource.noItemsLoadedQueries.test()
.also { zeroItemsFuncCaptor.firstValue.invoke() } .also { zeroItemsFuncCaptor.firstValue.invoke() }
.assertValue("test") .assertValue("test")
} }
@ -49,22 +49,22 @@ class PageableDataSourceTest {
* */ * */
@Test @Test
fun `retryFailedRequest does nothing without a factory`() { fun `retryFailedRequest does nothing without a factory`() {
pageableDataSource.retryFailedRequest() pageableBaseDataSource.retryFailedRequest()
} }
@Test @Test
@Ignore("Rewrite with Mockk constructor mocks") @Ignore("Rewrite with Mockk constructor mocks")
fun `retryFailedRequest retries with a factory`() { fun `retryFailedRequest retries with a factory`() {
val (_, _, dataSourceFactoryCaptor) = expectNewLiveData() val (_, _, dataSourceFactoryCaptor) = expectNewLiveData()
pageableDataSource.onQueryUpdated("test") pageableBaseDataSource.onQueryUpdated("test")
val dataSourceFactory = spy(dataSourceFactoryCaptor.firstValue) val dataSourceFactory = spy(dataSourceFactoryCaptor.firstValue)
pageableDataSource.retryFailedRequest() pageableBaseDataSource.retryFailedRequest()
verify(dataSourceFactory).retryFailedRequest() verify(dataSourceFactory).retryFailedRequest()
} }
private fun expectNewLiveData(): Triple<KArgumentCaptor<() -> Unit>, LiveData<PagedList<String>>, KArgumentCaptor<SearchDataSourceFactory<String>>> { private fun expectNewLiveData(): Triple<KArgumentCaptor<() -> Unit>, LiveData<PagedList<String>>, KArgumentCaptor<PagingDataSourceFactory<String>>> {
val captor = argumentCaptor<() -> Unit>() val captor = argumentCaptor<() -> Unit>()
val dataSourceFactoryCaptor = argumentCaptor<SearchDataSourceFactory<String>>() val dataSourceFactoryCaptor = argumentCaptor<PagingDataSourceFactory<String>>()
val liveData: LiveData<PagedList<String>> = mock() val liveData: LiveData<PagedList<String>> = mock()
whenever(liveDataConverter.convert(dataSourceFactoryCaptor.capture(), captor.capture())) whenever(liveDataConverter.convert(dataSourceFactoryCaptor.capture(), captor.capture()))
.thenReturn(liveData) .thenReturn(liveData)

View file

@ -14,21 +14,21 @@ import org.mockito.Mock
import org.mockito.Mockito import org.mockito.Mockito
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
class SearchDataSourceFactoryTest { class PagingDataSourceFactoryTest {
@Mock @Mock
private lateinit var depictsClient: DepictsClient private lateinit var depictsClient: DepictsClient
@Mock @Mock
private lateinit var loadingStates: PublishProcessor<LoadingState> private lateinit var loadingStates: PublishProcessor<LoadingState>
private lateinit var factory: SearchDataSourceFactory<String> private lateinit var factory: PagingDataSourceFactory<String>
private var function: (Int, Int) -> List<String> = mock() private var function: (Int, Int) -> List<String> = mock()
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
factory = object : SearchDataSourceFactory<String>(loadingStates) { factory = object : PagingDataSourceFactory<String>(loadingStates) {
override val loadFunction get() = function override val loadFunction get() = function
} }
} }