mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
update BrowseSourceController to match upstream
p much done to stop using a deprecated source details method
This commit is contained in:
parent
e415a365b9
commit
67e048a20c
8 changed files with 113 additions and 164 deletions
|
@ -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.
|
||||
|
|
|
@ -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?) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue