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:
Andreas
2022-09-03 16:16:30 +02:00
committed by GitHub
parent 5a320d87e8
commit cc6aef693e
31 changed files with 389 additions and 475 deletions

View File

@@ -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) {

View File

@@ -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)
},

View File

@@ -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
}
}
}

View File

@@ -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

View File

@@ -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 })
}

View File

@@ -1,3 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.browse
class NoResultsException : Exception()

View File

@@ -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()
}
}

View File

@@ -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()
}
}

View File

@@ -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
}
}

View File

@@ -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!!)
}
}

View File

@@ -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)