update BrowseSourceController to match upstream

p much done to stop using a deprecated source details method
This commit is contained in:
Jays2Kings 2022-04-20 23:47:16 -04:00
parent e415a365b9
commit 67e048a20c
8 changed files with 113 additions and 164 deletions

View file

@ -1,21 +1,38 @@
package eu.kanade.tachiyomi.ui.base.presenter
import android.os.Bundle
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import nucleus.presenter.RxPresenter
import nucleus.presenter.delivery.Delivery
import rx.Observable
open class BasePresenter<V> : RxPresenter<V>() {
lateinit var presenterScope: CoroutineScope
/**
* Query from the view where applicable
*/
var query: String = ""
protected set
override fun onCreate(savedState: Bundle?) {
try {
super.onCreate(savedState)
presenterScope = MainScope()
} catch (e: NullPointerException) {
// Swallow this error. This should be fixed in the library but since it's not critical
// (only used by restartables) it should be enough. It saves me a fork.
}
}
override fun onDestroy() {
super.onDestroy()
presenterScope.cancel()
}
/**
* Subscribes an observable with [deliverFirst] and adds it to the presenter's lifecycle
* subscription list.

View file

@ -12,12 +12,6 @@ import uy.kohesive.injekt.api.get
*/
open class SettingsSearchPresenter : BasePresenter<SettingsSearchController>() {
/**
* Query from the view.
*/
var query = ""
private set
val preferences: PreferencesHelper = Injekt.get()
override fun onCreate(savedState: Bundle?) {

View file

@ -187,7 +187,7 @@ open class BrowseSourceController(bundle: Bundle) :
binding.catalogueView.removeView(oldRecycler)
}
val recycler = if (presenter.isListMode) {
val recycler = if (presenter.prefs.browseAsList().get()) {
RecyclerView(view.context).apply {
id = R.id.recycler
layoutManager = LinearLayoutManagerAccurateOffset(context)
@ -283,7 +283,7 @@ open class BrowseSourceController(bundle: Bundle) :
private fun updateDisplayMenuItem(menu: Menu?, isListMode: Boolean? = null) {
menu?.findItem(R.id.action_display_mode)?.apply {
val icon = if (isListMode ?: presenter.isListMode) {
val icon = if (isListMode ?: presenter.prefs.browseAsList().get()) {
R.drawable.ic_view_module_24dp
} else {
R.drawable.ic_view_list_24dp
@ -405,6 +405,7 @@ open class BrowseSourceController(bundle: Bundle) :
when (filter) {
is Filter.TriState -> filter.state = 1
is Filter.CheckBox -> filter.state = true
else -> break
}
filterList = presenter.sourceFilters
break@filter
@ -615,13 +616,14 @@ open class BrowseSourceController(bundle: Bundle) :
val view = view ?: return
val adapter = adapter ?: return
presenter.swapDisplayMode()
val isListMode = presenter.isListMode
updateDisplayMenuItem(activityBinding?.toolbar?.menu, isListMode)
updateDisplayMenuItem(activityBinding?.cardToolbar?.menu, isListMode)
val isListMode = !presenter.prefs.browseAsList().get()
presenter.prefs.browseAsList().set(isListMode)
listOf(activityBinding?.toolbar?.menu, activityBinding?.cardToolbar?.menu).forEach {
updateDisplayMenuItem(it, isListMode)
}
setupRecycler(view)
if (!isListMode || !view.context.connectivityManager.isActiveNetworkMetered) {
// Initialize mangas if going to grid view or if over wifi when going to list view
// Initialize mangas if not on a metered connection
if (!view.context.connectivityManager.isActiveNetworkMetered) {
val mangas = (0 until adapter.itemCount).mapNotNull {
(adapter.getItem(it) as? BrowseSourceItem)?.manga
}

View file

@ -2,14 +2,11 @@ package eu.kanade.tachiyomi.ui.source.browse
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import eu.kanade.tachiyomi.util.system.awaitSingle
open class BrowseSourcePager(val source: CatalogueSource, val query: String, val filters: FilterList) : Pager() {
override fun requestNext(): Observable<MangasPage> {
override suspend fun requestNextPage() {
val page = currentPage
val observable = if (query.isBlank() && filters.isEmpty()) {
@ -18,15 +15,12 @@ open class BrowseSourcePager(val source: CatalogueSource, val query: String, val
source.fetchSearchManga(page, query, filters)
}
return observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext {
if (it.mangas.isNotEmpty()) {
onPageReceived(it)
} else {
throw NoResultsException()
}
}
val mangasPage = observable.awaitSingle()
if (mangasPage.mangas.isNotEmpty()) {
onPageReceived(mangasPage)
} else {
throw NoResultsException()
}
}
}

View file

@ -6,13 +6,16 @@ import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.source.filter.CheckboxItem
import eu.kanade.tachiyomi.ui.source.filter.CheckboxSectionItem
@ -28,17 +31,18 @@ import eu.kanade.tachiyomi.ui.source.filter.TextSectionItem
import eu.kanade.tachiyomi.ui.source.filter.TriStateItem
import eu.kanade.tachiyomi.ui.source.filter.TriStateSectionItem
import eu.kanade.tachiyomi.util.system.launchIO
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import eu.kanade.tachiyomi.util.system.withUIContext
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import rx.Observable
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import rx.subjects.PublishSubject
import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -47,24 +51,18 @@ import uy.kohesive.injekt.api.get
* Presenter of [BrowseSourceController].
*/
open class BrowseSourcePresenter(
sourceId: Long,
private val sourceId: Long,
searchQuery: String? = null,
sourceManager: SourceManager = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(),
val db: DatabaseHelper = Injekt.get(),
private val prefs: PreferencesHelper = Injekt.get(),
private val coverCache: CoverCache = Injekt.get()
val prefs: PreferencesHelper = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(),
) : BasePresenter<BrowseSourceController>() {
/**
* Selected source.
*/
val source = sourceManager.get(sourceId) as CatalogueSource
/**
* Query from the view.
*/
var query = ""
private set
lateinit var source: CatalogueSource
var filtersChanged = false
@ -90,17 +88,6 @@ open class BrowseSourcePresenter(
*/
private lateinit var pager: Pager
/**
* Subject that initializes a list of manga.
*/
private val mangaDetailSubject = PublishSubject.create<List<Manga>>()
/**
* Whether the view is in list mode or not.
*/
var isListMode: Boolean = false
private set
/**
* Subscription for the pager.
*/
@ -109,14 +96,9 @@ open class BrowseSourcePresenter(
/**
* Subscription for one request from the pager.
*/
private var pageSubscription: Subscription? = null
private var nextPageJob: Job? = null
/**
* Subscription to initialize manga details.
*/
private var initializerSubscription: Subscription? = null
private var scope = CoroutineScope(Job() + Dispatchers.IO)
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } }
init {
query = searchQuery ?: ""
@ -125,24 +107,17 @@ open class BrowseSourcePresenter(
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
source = sourceManager.get(sourceId) as? CatalogueSource ?: return
sourceFilters = source.getFilterList()
if (savedState != null) {
query = savedState.getString(::query.name, "")
}
prefs.browseAsList().asFlow()
.onEach { setDisplayMode(it) }
.launchIn(scope)
restartPager()
}
override fun onDestroy() {
super.onDestroy()
scope.cancel()
}
override fun onSave(state: Bundle) {
state.putString(::query.name, query)
super.onSave(state)
@ -158,8 +133,6 @@ open class BrowseSourcePresenter(
this.query = query
this.appliedFilters = filters
subscribeToMangaInitializer()
// Create a new pager.
pager = createPager(query, filters)
@ -173,9 +146,9 @@ open class BrowseSourcePresenter(
pagerSubscription?.let { remove(it) }
pagerSubscription = pager.results()
.observeOn(Schedulers.io())
.map { it.first to it.second.map { networkToLocalManga(it, sourceId) } }
.map { (first, second) -> first to second.map { networkToLocalManga(it, sourceId) } }
.doOnNext { initializeMangas(it.second) }
.map { it.first to it.second.map { BrowseSourceItem(it, browseAsList, sourceListType, outlineCovers) } }
.map { (first, second) -> first to second.map { BrowseSourceItem(it, browseAsList, sourceListType, outlineCovers) } }
.observeOn(AndroidSchedulers.mainThread())
.subscribeReplay(
{ view, (page, mangas) ->
@ -196,14 +169,17 @@ open class BrowseSourcePresenter(
fun requestNext() {
if (!hasNextPage()) return
pageSubscription?.let { remove(it) }
pageSubscription = Observable.defer { pager.requestNext() }
.subscribeFirst(
{ _, _ ->
// Nothing to do when onNext is emitted.
},
BrowseSourceController::onAddPageError
)
nextPageJob?.cancel()
nextPageJob = launchIO {
try {
pager.requestNextPage()
} catch (e: Throwable) {
withUIContext {
@Suppress("DEPRECATION")
view?.onAddPageError(e)
}
}
}
}
/**
@ -213,39 +189,6 @@ open class BrowseSourcePresenter(
return pager.hasNextPage
}
/**
* Sets the display mode.
*
* @param asList whether the current mode is in list or not.
*/
private fun setDisplayMode(asList: Boolean) {
isListMode = asList
subscribeToMangaInitializer()
}
/**
* Subscribes to the initializer of manga details and updates the view if needed.
*/
private fun subscribeToMangaInitializer() {
initializerSubscription?.let { remove(it) }
initializerSubscription = mangaDetailSubject.observeOn(Schedulers.io())
.flatMap { Observable.from(it) }
.filter { it.thumbnail_url == null && !it.initialized }
.concatMap { getMangaDetailsObservable(it) }
.onBackpressureBuffer()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ manga ->
@Suppress("DEPRECATION")
view?.onMangaInitialized(manga)
},
{ error ->
Timber.e(error)
}
)
.apply { add(this) }
}
/**
* Returns a manga from the database for the given manga from network. It creates a new entry
* if the manga is not yet in the database.
@ -264,6 +207,10 @@ open class BrowseSourcePresenter(
} else if (localManga.title.isBlank()) {
localManga.title = sManga.title
db.insertManga(localManga).executeAsBlocking()
} else if (!localManga.favorite) {
// if the manga isn't a favorite, set its display title from source
// if it later becomes a favorite, updated title will go to db
localManga.title = sManga.title
}
return localManga
}
@ -274,24 +221,37 @@ open class BrowseSourcePresenter(
* @param mangas the list of manga to initialize.
*/
fun initializeMangas(mangas: List<Manga>) {
mangaDetailSubject.onNext(mangas)
presenterScope.launchIO {
mangas.asFlow()
.filter { it.thumbnail_url == null && !it.initialized }
.map { getMangaDetails(it) }
.onEach {
withUIContext {
@Suppress("DEPRECATION")
view?.onMangaInitialized(it)
}
}
.catch { e -> Timber.e(e) }
.collect()
}
}
/**
* Returns an observable of manga that initializes the given manga.
* Returns the initialized manga.
*
* @param manga the manga to initialize.
* @return an observable of the manga to initialize
* @return the initialized manga
*/
private fun getMangaDetailsObservable(manga: Manga): Observable<Manga> {
return source.fetchMangaDetails(manga)
.flatMap { networkManga ->
manga.copyFrom(networkManga)
manga.initialized = true
db.insertManga(manga).executeAsBlocking()
Observable.just(manga)
}
.onErrorResumeNext { Observable.just(manga) }
private suspend fun getMangaDetails(manga: Manga): Manga {
try {
val networkManga = source.getMangaDetails(manga.toMangaInfo())
manga.copyFrom(networkManga.toSManga())
manga.initialized = true
db.insertManga(manga).executeAsBlocking()
} catch (e: Exception) {
Timber.e(e)
}
return manga
}
fun confirmDeletion(manga: Manga) {
@ -302,13 +262,6 @@ open class BrowseSourcePresenter(
}
}
/**
* Changes the active display mode.
*/
fun swapDisplayMode() {
prefs.browseAsList().set(!isListMode)
}
/**
* Set the filter states for the current source.
*
@ -323,17 +276,17 @@ open class BrowseSourcePresenter(
}
private fun FilterList.toItems(): List<IFlexible<*>> {
return mapNotNull {
when (it) {
is Filter.Header -> HeaderItem(it)
is Filter.Separator -> SeparatorItem(it)
is Filter.CheckBox -> CheckboxItem(it)
is Filter.TriState -> TriStateItem(it)
is Filter.Text -> TextItem(it)
is Filter.Select<*> -> SelectItem(it)
return mapNotNull { filter ->
when (filter) {
is Filter.Header -> HeaderItem(filter)
is Filter.Separator -> SeparatorItem(filter)
is Filter.CheckBox -> CheckboxItem(filter)
is Filter.TriState -> TriStateItem(filter)
is Filter.Text -> TextItem(filter)
is Filter.Select<*> -> SelectItem(filter)
is Filter.Group<*> -> {
val group = GroupItem(it)
val subItems = it.state.mapNotNull { type ->
val group = GroupItem(filter)
val subItems = filter.state.mapNotNull { type ->
when (type) {
is Filter.CheckBox -> CheckboxSectionItem(type)
is Filter.TriState -> TriStateSectionItem(type)
@ -347,8 +300,8 @@ open class BrowseSourcePresenter(
group
}
is Filter.Sort -> {
val group = SortGroup(it)
val subItems = it.values.map {
val group = SortGroup(filter)
val subItems = filter.values.map {
SortItem(it, group)
}
group.subItems = subItems

View file

@ -19,7 +19,7 @@ abstract class Pager(var currentPage: Int = 1) {
return results.asObservable()
}
abstract fun requestNext(): Observable<MangasPage>
abstract suspend fun requestNextPage()
fun onPageReceived(mangasPage: MangasPage) {
val page = currentPage

View file

@ -49,12 +49,6 @@ open class GlobalSearchPresenter(
*/
val sources by lazy { getSourcesToQuery() }
/**
* Query from the view.
*/
var query = ""
private set
/**
* Fetches the different sources by user settings.
*/

View file

@ -1,21 +1,16 @@
package eu.kanade.tachiyomi.ui.source.latest
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.ui.source.browse.Pager
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import eu.kanade.tachiyomi.util.system.awaitSingle
/**
* LatestUpdatesPager inherited from the general Pager.
*/
class LatestUpdatesPager(val source: CatalogueSource) : Pager() {
override fun requestNext(): Observable<MangasPage> {
return source.fetchLatestUpdates(currentPage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { onPageReceived(it) }
override suspend fun requestNextPage() {
val mangasPage = source.fetchLatestUpdates(currentPage).awaitSingle()
onPageReceived(mangasPage)
}
}