mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-14 04:58:56 +01:00
Merge Latest and Browse into one screen (#7921)
* Merge Latest and Browse into one * Add back Latest button * Change context to IO instead of launching a job * Use loading screen when loading initial page
This commit is contained in:
@@ -7,7 +7,9 @@ import androidx.core.os.bundleOf
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.presentation.browse.SourceSearchScreen
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||
import eu.kanade.tachiyomi.util.system.getSerializableCompat
|
||||
|
||||
class SourceSearchController(
|
||||
@@ -27,17 +29,26 @@ class SourceSearchController(
|
||||
|
||||
@Composable
|
||||
override fun ComposeContent() {
|
||||
// LocalContext is not a first available to us when we try access it
|
||||
// Decoupling from BrowseSourceController is needed
|
||||
val context = applicationContext!!
|
||||
|
||||
SourceSearchScreen(
|
||||
presenter = presenter,
|
||||
navigateUp = { router.popCurrentController() },
|
||||
onFabClick = { filterSheet?.show() },
|
||||
onClickManga = {
|
||||
onMangaClick = {
|
||||
newManga = it
|
||||
val searchController = router.backstack.findLast { it.controller.javaClass == SearchController::class.java }?.controller as SearchController?
|
||||
val dialog = SearchController.MigrationDialog(oldManga, newManga, this)
|
||||
dialog.targetController = searchController
|
||||
dialog.showDialog(router)
|
||||
},
|
||||
onWebViewClick = f@{
|
||||
val source = presenter.source as? HttpSource ?: return@f
|
||||
val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name)
|
||||
context.startActivity(intent)
|
||||
},
|
||||
)
|
||||
|
||||
LaunchedEffect(presenter.filters) {
|
||||
|
||||
@@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
|
||||
|
||||
@Composable
|
||||
fun sourcesTab(
|
||||
@@ -36,17 +35,13 @@ fun sourcesTab(
|
||||
content = {
|
||||
SourcesScreen(
|
||||
presenter = presenter,
|
||||
onClickItem = { source ->
|
||||
onClickItem = { source, query ->
|
||||
presenter.onOpenSource(source)
|
||||
router?.pushController(BrowseSourceController(source))
|
||||
router?.pushController(BrowseSourceController(source, query))
|
||||
},
|
||||
onClickDisable = { source ->
|
||||
presenter.toggleSource(source)
|
||||
},
|
||||
onClickLatest = { source ->
|
||||
presenter.onOpenSource(source)
|
||||
router?.pushController(LatestUpdatesController(source))
|
||||
},
|
||||
onClickPin = { source ->
|
||||
presenter.togglePin(source)
|
||||
},
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
|
||||
abstract class BrowsePagingSource : PagingSource<Long, SManga>() {
|
||||
|
||||
abstract suspend fun requestNextPage(currentPage: Int): MangasPage
|
||||
|
||||
override suspend fun load(params: LoadParams<Long>): LoadResult<Long, SManga> {
|
||||
val page = params.key ?: 1
|
||||
|
||||
val mangasPage = try {
|
||||
withIOContext {
|
||||
requestNextPage(page.toInt())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
return LoadResult.Error(e)
|
||||
}
|
||||
|
||||
return LoadResult.Page(
|
||||
data = mangasPage.mangas,
|
||||
prevKey = null,
|
||||
nextKey = if (mangasPage.hasNextPage) page + 1 else null,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getRefreshKey(state: PagingState<Long, SManga>): Long? {
|
||||
return state.anchorPosition?.let { anchorPosition ->
|
||||
val anchorPage = state.closestPageToPosition(anchorPosition)
|
||||
anchorPage?.prevKey ?: anchorPage?.nextKey
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,10 @@ open class BrowseSourceController(bundle: Bundle) :
|
||||
*/
|
||||
protected var filterSheet: SourceFilterSheet? = null
|
||||
|
||||
override fun createPresenter(): BrowseSourcePresenter {
|
||||
return BrowseSourcePresenter(args.getLong(SOURCE_ID_KEY), args.getString(SEARCH_QUERY_KEY))
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun ComposeContent() {
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -49,7 +53,6 @@ open class BrowseSourceController(bundle: Bundle) :
|
||||
BrowseSourceScreen(
|
||||
presenter = presenter,
|
||||
navigateUp = { router.popCurrentController() },
|
||||
onDisplayModeChange = { presenter.displayMode = (it) },
|
||||
onFabClick = { filterSheet?.show() },
|
||||
onMangaClick = { router.pushController(MangaController(it.id, true)) },
|
||||
onMangaLongClick = { manga ->
|
||||
@@ -108,10 +111,6 @@ open class BrowseSourceController(bundle: Bundle) :
|
||||
}
|
||||
}
|
||||
|
||||
override fun createPresenter(): BrowseSourcePresenter {
|
||||
return BrowseSourcePresenter(args.getLong(SOURCE_ID_KEY), args.getString(SEARCH_QUERY_KEY))
|
||||
}
|
||||
|
||||
open fun initFilterSheet() {
|
||||
if (presenter.filters.isEmpty()) {
|
||||
return
|
||||
|
||||
@@ -14,7 +14,6 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.paging.map
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
@@ -30,6 +29,7 @@ import eu.kanade.domain.manga.interactor.InsertManga
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.model.toDbManga
|
||||
import eu.kanade.domain.manga.model.toMangaUpdate
|
||||
import eu.kanade.domain.source.interactor.GetRemoteManga
|
||||
import eu.kanade.domain.track.interactor.InsertTrack
|
||||
import eu.kanade.domain.track.model.toDomainTrack
|
||||
import eu.kanade.presentation.browse.BrowseSourceState
|
||||
@@ -71,7 +71,6 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import logcat.LogPriority
|
||||
@@ -88,6 +87,7 @@ open class BrowseSourcePresenter(
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
private val preferences: PreferencesHelper = Injekt.get(),
|
||||
private val coverCache: CoverCache = Injekt.get(),
|
||||
private val getRemoteManga: GetRemoteManga = Injekt.get(),
|
||||
private val getManga: GetManga = Injekt.get(),
|
||||
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
|
||||
private val getCategories: GetCategories = Injekt.get(),
|
||||
@@ -99,6 +99,8 @@ open class BrowseSourcePresenter(
|
||||
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(),
|
||||
) : BasePresenter<BrowseSourceController>(), BrowseSourceState by state {
|
||||
|
||||
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } }
|
||||
|
||||
var displayMode by preferences.sourceDisplayMode().asState()
|
||||
|
||||
@Composable
|
||||
@@ -115,11 +117,11 @@ open class BrowseSourcePresenter(
|
||||
|
||||
@Composable
|
||||
fun getMangaList(): Flow<PagingData<DomainManga>> {
|
||||
return remember(currentQuery, appliedFilters) {
|
||||
return remember(currentQuery, currentFilters) {
|
||||
Pager(
|
||||
PagingConfig(pageSize = 25),
|
||||
) {
|
||||
createPager(currentQuery, appliedFilters)
|
||||
getRemoteManga.subscribe(sourceId, currentQuery, currentFilters)
|
||||
}.flow
|
||||
.map {
|
||||
it.map {
|
||||
@@ -134,12 +136,12 @@ open class BrowseSourcePresenter(
|
||||
|
||||
@Composable
|
||||
fun getManga(initialManga: DomainManga): State<DomainManga> {
|
||||
return produceState(initialValue = initialManga, initialManga.url, initialManga.source) {
|
||||
return produceState(initialValue = initialManga) {
|
||||
getManga.subscribe(initialManga.url, initialManga.source)
|
||||
.collectLatest { manga ->
|
||||
if (manga == null) return@collectLatest
|
||||
launchIO {
|
||||
initializeMangas(manga)
|
||||
withIOContext {
|
||||
initializeManga(manga)
|
||||
}
|
||||
value = manga
|
||||
}
|
||||
@@ -151,31 +153,20 @@ open class BrowseSourcePresenter(
|
||||
}
|
||||
|
||||
fun resetFilter() {
|
||||
state.appliedFilters = FilterList()
|
||||
val newFilters = source!!.getFilterList()
|
||||
state.filters = newFilters
|
||||
state.currentFilters = state.filters
|
||||
}
|
||||
|
||||
fun search() {
|
||||
state.currentQuery = searchQuery ?: ""
|
||||
fun search(query: String? = null) {
|
||||
state.currentQuery = query ?: searchQuery ?: ""
|
||||
}
|
||||
|
||||
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } }
|
||||
|
||||
override fun onCreate(savedState: Bundle?) {
|
||||
super.onCreate(savedState)
|
||||
|
||||
state.source = sourceManager.get(sourceId) as? CatalogueSource ?: return
|
||||
state.filters = source!!.getFilterList()
|
||||
|
||||
if (savedState != null) {
|
||||
query = savedState.getString(::query.name, "")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSave(state: Bundle) {
|
||||
state.putString(::query.name, query)
|
||||
super.onSave(state)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,9 +196,9 @@ open class BrowseSourcePresenter(
|
||||
/**
|
||||
* Initialize a manga.
|
||||
*
|
||||
* @param mangas the list of manga to initialize.
|
||||
* @param manga to initialize.
|
||||
*/
|
||||
private suspend fun initializeMangas(manga: DomainManga) {
|
||||
private suspend fun initializeManga(manga: DomainManga) {
|
||||
if (manga.thumbnailUrl != null && manga.initialized) return
|
||||
withContext(NonCancellable) {
|
||||
val db = manga.toDbManga()
|
||||
@@ -315,11 +306,7 @@ open class BrowseSourcePresenter(
|
||||
* @param filters a list of active filters.
|
||||
*/
|
||||
fun setSourceFilter(filters: FilterList) {
|
||||
state.appliedFilters = filters
|
||||
}
|
||||
|
||||
open fun createPager(query: String, filters: FilterList): PagingSource<Long, SManga> {
|
||||
return SourceBrowsePagingSource(source!!, query, filters)
|
||||
state.currentFilters = filters
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,12 +325,6 @@ open class BrowseSourcePresenter(
|
||||
return getDuplicateLibraryManga.await(manga.title, manga.source)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the given manga to categories.
|
||||
*
|
||||
* @param categories the selected categories.
|
||||
* @param manga the manga to move.
|
||||
*/
|
||||
fun moveMangaToCategories(manga: DomainManga, vararg categories: DomainCategory) {
|
||||
moveMangaToCategories(manga, categories.filter { it.id != 0L }.map { it.id })
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||
|
||||
class NoResultsException : Exception()
|
||||
@@ -1,20 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.util.lang.awaitSingle
|
||||
|
||||
class SourceBrowsePagingSource(val source: CatalogueSource, val query: String, val filters: FilterList) : BrowsePagingSource() {
|
||||
|
||||
override suspend fun requestNextPage(currentPage: Int): MangasPage {
|
||||
val observable = if (query.isBlank() && filters.isEmpty()) {
|
||||
source.fetchPopularManga(currentPage)
|
||||
} else {
|
||||
source.fetchSearchManga(currentPage, query, filters)
|
||||
}
|
||||
|
||||
return observable.awaitSingle()
|
||||
.takeIf { it.mangas.isNotEmpty() } ?: throw NoResultsException()
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.source.latest
|
||||
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowsePagingSource
|
||||
import eu.kanade.tachiyomi.util.lang.awaitSingle
|
||||
|
||||
class LatestUpdatesBrowsePagingSource(val source: CatalogueSource) : BrowsePagingSource() {
|
||||
|
||||
override suspend fun requestNextPage(currentPage: Int): MangasPage {
|
||||
return source.fetchLatestUpdates(currentPage).awaitSingle()
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.source.latest
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.os.bundleOf
|
||||
import eu.kanade.domain.source.model.Source
|
||||
import eu.kanade.presentation.browse.BrowseLatestScreen
|
||||
import eu.kanade.presentation.browse.components.RemoveMangaDialog
|
||||
import eu.kanade.presentation.components.ChangeCategoryDialog
|
||||
import eu.kanade.presentation.components.DuplicateMangaDialog
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
|
||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
|
||||
/**
|
||||
* Controller that shows the latest manga from the catalogue. Inherit [BrowseSourceController].
|
||||
*/
|
||||
class LatestUpdatesController(bundle: Bundle) : BrowseSourceController(bundle) {
|
||||
|
||||
constructor(source: Source) : this(
|
||||
bundleOf(SOURCE_ID_KEY to source.id),
|
||||
)
|
||||
|
||||
override fun createPresenter(): BrowseSourcePresenter {
|
||||
return LatestUpdatesPresenter(args.getLong(SOURCE_ID_KEY))
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun ComposeContent() {
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
|
||||
BrowseLatestScreen(
|
||||
presenter = presenter,
|
||||
navigateUp = { router.popCurrentController() },
|
||||
onMangaClick = { router.pushController(MangaController(it.id, true)) },
|
||||
onMangaLongClick = { manga ->
|
||||
scope.launchIO {
|
||||
val duplicateManga = presenter.getDuplicateLibraryManga(manga)
|
||||
when {
|
||||
manga.favorite -> presenter.dialog = BrowseSourcePresenter.Dialog.RemoveManga(manga)
|
||||
duplicateManga != null -> presenter.dialog = BrowseSourcePresenter.Dialog.AddDuplicateManga(manga, duplicateManga)
|
||||
else -> presenter.addFavorite(manga)
|
||||
}
|
||||
}
|
||||
},
|
||||
onWebViewClick = f@{
|
||||
val source = presenter.source as? HttpSource ?: return@f
|
||||
val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name)
|
||||
context.startActivity(intent)
|
||||
},
|
||||
)
|
||||
|
||||
val onDismissRequest = { presenter.dialog = null }
|
||||
when (val dialog = presenter.dialog) {
|
||||
is BrowseSourcePresenter.Dialog.AddDuplicateManga -> {
|
||||
DuplicateMangaDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onOpenManga = {
|
||||
router.pushController(MangaController(dialog.duplicate.id, true))
|
||||
},
|
||||
onConfirm = {
|
||||
presenter.addFavorite(dialog.manga)
|
||||
},
|
||||
duplicateFrom = presenter.getSourceOrStub(dialog.manga),
|
||||
)
|
||||
}
|
||||
is BrowseSourcePresenter.Dialog.RemoveManga -> {
|
||||
RemoveMangaDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onConfirm = {
|
||||
presenter.changeMangaFavorite(dialog.manga)
|
||||
},
|
||||
)
|
||||
}
|
||||
is BrowseSourcePresenter.Dialog.ChangeMangaCategory -> {
|
||||
ChangeCategoryDialog(
|
||||
initialSelection = dialog.initialSelection,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onEditCategories = {
|
||||
router.pushController(CategoryController())
|
||||
},
|
||||
onConfirm = { include, _ ->
|
||||
presenter.changeMangaFavorite(dialog.manga)
|
||||
presenter.moveMangaToCategories(dialog.manga, include)
|
||||
},
|
||||
)
|
||||
}
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
|
||||
override fun initFilterSheet() {
|
||||
// No-op: we don't allow filtering in latest
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.source.latest
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
|
||||
|
||||
class LatestUpdatesPresenter(sourceId: Long) : BrowseSourcePresenter(sourceId) {
|
||||
|
||||
override fun createPager(query: String, filters: FilterList): PagingSource<Long, SManga> {
|
||||
return LatestUpdatesBrowsePagingSource(source!!)
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,6 @@ import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
|
||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryController
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
@@ -313,10 +312,6 @@ class MangaController : FullComposeController<MangaPresenter> {
|
||||
val controller = router.getControllerWithTag(R.id.nav_library.toString()) as LibraryController
|
||||
controller.search(query)
|
||||
}
|
||||
is LatestUpdatesController -> {
|
||||
// Search doesn't currently work in source Latest view
|
||||
return
|
||||
}
|
||||
is BrowseSourceController -> {
|
||||
router.handleBack()
|
||||
previousController.searchWithQuery(query)
|
||||
|
||||
Reference in New Issue
Block a user