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

(cherry picked from commit cc6aef693e1a15f695ffa7eebd968004a6557010)

# Conflicts:
#	app/src/main/java/eu/kanade/data/source/SourceRepositoryImpl.kt
#	app/src/main/java/eu/kanade/presentation/browse/BrowseLatestScreen.kt
#	app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt
#	app/src/main/java/eu/kanade/presentation/browse/SourceSearchScreen.kt
#	app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt
#	app/src/main/java/eu/kanade/presentation/browse/components/BrowseLatestToolbar.kt
#	app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceComfortableGrid.kt
#	app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceToolbar.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowsePagingSource.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesPresenter.kt
This commit is contained in:
Andreas 2022-09-03 16:16:30 +02:00 committed by Jobobby04
parent 3ba39f6557
commit 99d5d8b91f
43 changed files with 483 additions and 561 deletions

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.ui.browse.source.browse
package eu.kanade.data.source
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList
@ -6,20 +6,16 @@ import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.MetadataMangasPage
import eu.kanade.tachiyomi.util.lang.awaitSingle
class EHentaiBrowsePagingSource(val source: CatalogueSource, val query: String, val filters: FilterList) : BrowsePagingSource() {
abstract class EHentaiPagingSource(source: CatalogueSource) : SourcePagingSource(source) {
private var lastMangaLink: String? = null
abstract suspend fun fetchNextPage(currentPage: Int): MangasPage
override suspend fun requestNextPage(currentPage: Int): MangasPage {
val lastMangaLink = lastMangaLink
val observable = if (query.isBlank() && filters.isEmpty()) {
source.fetchPopularManga(currentPage)
} else {
source.fetchSearchManga(currentPage, query, filters)
}
val mangasPage = observable.awaitSingle()
val mangasPage = fetchNextPage(currentPage)
mangasPage.mangas.lastOrNull()?.let {
this.lastMangaLink = it.url
@ -47,3 +43,21 @@ class EHentaiBrowsePagingSource(val source: CatalogueSource, val query: String,
}
}
}
class EHentaiSearchPagingSource(source: CatalogueSource, val query: String, val filters: FilterList) : EHentaiPagingSource(source) {
override suspend fun fetchNextPage(currentPage: Int): MangasPage {
return source.fetchSearchManga(currentPage, query, filters).awaitSingle()
}
}
class EHentaiPopularPagingSource(source: CatalogueSource) : EHentaiPagingSource(source) {
override suspend fun fetchNextPage(currentPage: Int): MangasPage {
return source.fetchPopularManga(currentPage).awaitSingle()
}
}
class EHentaiLatestPagingSource(source: CatalogueSource) : EHentaiPagingSource(source) {
override suspend fun fetchNextPage(currentPage: Int): MangasPage {
return source.fetchLatestUpdates(currentPage).awaitSingle()
}
}

View File

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

View File

@ -1,14 +1,19 @@
package eu.kanade.tachiyomi.ui.browse.source.browse
package eu.kanade.data.source
import androidx.paging.PagingSource
import androidx.paging.PagingState
import eu.kanade.domain.source.model.SourcePagingSourceType
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.MetadataMangasPage
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.lang.awaitSingle
import eu.kanade.tachiyomi.util.lang.withIOContext
import exh.metadata.metadata.base.RaisedSearchMetadata
abstract class BrowsePagingSource : PagingSource<Long, /*SY --> */ Pair<SManga, RaisedSearchMetadata?> /*SY <-- */>() {
abstract class SourcePagingSource(
protected val source: CatalogueSource,
) : SourcePagingSourceType() {
abstract suspend fun requestNextPage(currentPage: Int): MangasPage
@ -18,6 +23,8 @@ abstract class BrowsePagingSource : PagingSource<Long, /*SY --> */ Pair<SManga,
val mangasPage = try {
withIOContext {
requestNextPage(page.toInt())
.takeIf { it.mangas.isNotEmpty() }
?: throw NoResultsException()
}
} catch (e: Exception) {
return LoadResult.Error(e)
@ -38,10 +45,28 @@ abstract class BrowsePagingSource : PagingSource<Long, /*SY --> */ Pair<SManga,
)
}
override fun getRefreshKey(state: PagingState<Long, Pair<SManga, RaisedSearchMetadata?>>): Long? {
override fun getRefreshKey(state: PagingState<Long, /*SY --> */ Pair<SManga, RaisedSearchMetadata?>/*SY <-- */>): Long? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey ?: anchorPage?.nextKey
}
}
}
class SourceSearchPagingSource(source: CatalogueSource, val query: String, val filters: FilterList) : SourcePagingSource(source) {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
return source.fetchSearchManga(currentPage, query, filters).awaitSingle()
}
}
class SourcePopularPagingSource(source: CatalogueSource) : SourcePagingSource(source) {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
return source.fetchPopularManga(currentPage).awaitSingle()
}
}
class SourceLatestPagingSource(source: CatalogueSource) : SourcePagingSource(source) {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
return source.fetchLatestUpdates(currentPage).awaitSingle()
}
}

View File

@ -2,11 +2,15 @@ package eu.kanade.data.source
import eu.kanade.data.DatabaseHandler
import eu.kanade.domain.source.model.Source
import eu.kanade.domain.source.model.SourcePagingSourceType
import eu.kanade.domain.source.model.SourceWithCount
import eu.kanade.domain.source.repository.SourceRepository
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.FilterList
import exh.source.MERGED_SOURCE_ID
import exh.source.isEhBasedSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@ -50,4 +54,38 @@ class SourceRepositoryImpl(
}
}
}
override fun search(
sourceId: Long,
query: String,
filterList: FilterList,
): SourcePagingSourceType {
val source = sourceManager.get(sourceId) as CatalogueSource
// SY -->
if (source.isEhBasedSource()) {
return EHentaiSearchPagingSource(source, query, filterList)
}
// SY <--
return SourceSearchPagingSource(source, query, filterList)
}
override fun getPopular(sourceId: Long): SourcePagingSourceType {
val source = sourceManager.get(sourceId) as CatalogueSource
// SY -->
if (source.isEhBasedSource()) {
return EHentaiPopularPagingSource(source)
}
// SY <--
return SourcePopularPagingSource(source)
}
override fun getLatest(sourceId: Long): SourcePagingSourceType {
val source = sourceManager.get(sourceId) as CatalogueSource
// SY -->
if (source.isEhBasedSource()) {
return EHentaiLatestPagingSource(source)
}
// SY <--
return SourceLatestPagingSource(source)
}
}

View File

@ -51,6 +51,7 @@ import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.domain.source.interactor.GetEnabledSources
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
import eu.kanade.domain.source.interactor.GetRemoteManga
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
import eu.kanade.domain.source.interactor.GetSourcesWithNonLibraryManga
import eu.kanade.domain.source.interactor.SetMigrateSorting
@ -133,6 +134,7 @@ class DomainModule : InjektModule {
addSingletonFactory<SourceDataRepository> { SourceDataRepositoryImpl(get()) }
addFactory { GetEnabledSources(get(), get()) }
addFactory { GetLanguagesWithSources(get(), get()) }
addFactory { GetRemoteManga(get()) }
addFactory { GetSourcesWithFavoriteCount(get(), get()) }
addFactory { GetSourcesWithNonLibraryManga(get()) }
addFactory { SetMigrateSorting(get()) }

View File

@ -0,0 +1,23 @@
package eu.kanade.domain.source.interactor
import eu.kanade.domain.source.model.SourcePagingSourceType
import eu.kanade.domain.source.repository.SourceRepository
import eu.kanade.tachiyomi.source.model.FilterList
class GetRemoteManga(
private val repository: SourceRepository,
) {
fun subscribe(sourceId: Long, query: String, filterList: FilterList): SourcePagingSourceType {
return when (query) {
QUERY_POPULAR -> repository.getPopular(sourceId)
QUERY_LATEST -> repository.getLatest(sourceId)
else -> repository.search(sourceId, query, filterList)
}
}
companion object {
const val QUERY_POPULAR = "eu.kanade.domain.source.interactor.POPULAR"
const val QUERY_LATEST = "eu.kanade.domain.source.interactor.LATEST"
}
}

View File

@ -0,0 +1,7 @@
package eu.kanade.domain.source.model
import androidx.paging.PagingSource
import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.metadata.base.RaisedSearchMetadata
typealias SourcePagingSourceType = PagingSource<Long, /*SY --> */ Pair<SManga, RaisedSearchMetadata?>/*SY <-- */>

View File

@ -1,7 +1,9 @@
package eu.kanade.domain.source.repository
import eu.kanade.domain.source.model.Source
import eu.kanade.domain.source.model.SourcePagingSourceType
import eu.kanade.domain.source.model.SourceWithCount
import eu.kanade.tachiyomi.source.model.FilterList
import kotlinx.coroutines.flow.Flow
interface SourceRepository {
@ -13,4 +15,10 @@ interface SourceRepository {
fun getSourcesWithFavoriteCount(): Flow<List<Pair<Source, Long>>>
fun getSourcesWithNonLibraryManga(): Flow<List<SourceWithCount>>
fun search(sourceId: Long, query: String, filterList: FilterList): SourcePagingSourceType
fun getPopular(sourceId: Long): SourcePagingSourceType
fun getLatest(sourceId: Long): SourcePagingSourceType
}

View File

@ -1,75 +0,0 @@
package eu.kanade.presentation.browse
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalUriHandler
import androidx.paging.compose.collectAsLazyPagingItems
import eu.kanade.domain.manga.model.Manga
import eu.kanade.presentation.browse.components.BrowseLatestToolbar
import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
import eu.kanade.tachiyomi.ui.more.MoreController
import exh.source.isEhBasedSource
@Composable
fun BrowseLatestScreen(
presenter: BrowseSourcePresenter,
navigateUp: () -> Unit,
onMangaClick: (Manga) -> Unit,
onMangaLongClick: (Manga) -> Unit,
onWebViewClick: () -> Unit,
// SY -->
onSettingsClick: () -> Unit,
// SY <--
) {
val columns by presenter.getColumnsPreferenceForCurrentOrientation()
val uriHandler = LocalUriHandler.current
val onHelpClick = {
uriHandler.openUri(LocalSource.HELP_URL)
}
Scaffold(
topBar = { scrollBehavior ->
BrowseLatestToolbar(
navigateUp = navigateUp,
source = presenter.source!!,
displayMode = presenter.displayMode.takeUnless { presenter.source!!.isEhBasedSource() && presenter.ehentaiBrowseDisplayMode },
onDisplayModeChange = { presenter.displayMode = it },
onHelpClick = onHelpClick,
onWebViewClick = onWebViewClick,
// SY -->
onSettingsClick = onSettingsClick,
// SY <--
scrollBehavior = scrollBehavior,
)
},
) { paddingValues ->
BrowseSourceContent(
source = presenter.source,
mangaList = presenter.getMangaList().collectAsLazyPagingItems(),
getMangaState = { presenter.getManga(it) },
// SY -->
getMetadataState = { manga, metadata ->
presenter.getRaisedSearchMetadata(manga, metadata)
},
// SY <--
columns = columns,
// SY -->
ehentaiBrowseDisplayMode = presenter.ehentaiBrowseDisplayMode,
// SY <--
displayMode = presenter.displayMode,
snackbarHostState = remember { SnackbarHostState() },
contentPadding = paddingValues,
onWebViewClick = onWebViewClick,
onHelpClick = { uriHandler.openUri(MoreController.URL_HELP) },
onLocalSourceHelpClick = onHelpClick,
onMangaClick = onMangaClick,
onMangaLongClick = onMangaLongClick,
)
}
}

View File

@ -43,7 +43,7 @@ fun BrowseMangadexFollowsScreen(
},
) { paddingValues ->
BrowseSourceContent(
source = presenter.source,
state = presenter,
mangaList = mangaList,
getMangaState = { presenter.getManga(it) },
// SY -->

View File

@ -31,7 +31,7 @@ fun BrowseRecommendationsScreen(
},
) { paddingValues ->
BrowseSourceContent(
source = presenter.source,
state = presenter,
mangaList = presenter.getMangaList().collectAsLazyPagingItems(),
getMangaState = { presenter.getManga(it) },
// SY -->

View File

@ -1,10 +1,19 @@
package eu.kanade.presentation.browse
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Favorite
import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material.icons.outlined.NewReleases
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
@ -20,10 +29,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import eu.kanade.data.source.NoResultsException
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.source.interactor.GetRemoteManga
import eu.kanade.presentation.browse.components.BrowseSourceComfortableGrid
import eu.kanade.presentation.browse.components.BrowseSourceCompactGrid
import eu.kanade.presentation.browse.components.BrowseSourceEHentaiList
@ -31,12 +43,11 @@ import eu.kanade.presentation.browse.components.BrowseSourceList
import eu.kanade.presentation.browse.components.BrowseSourceToolbar
import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.ExtendedFloatingActionButton
import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
import eu.kanade.tachiyomi.ui.browse.source.browse.NoResultsException
import eu.kanade.tachiyomi.ui.library.setting.LibraryDisplayMode
import eu.kanade.tachiyomi.ui.more.MoreController
import eu.kanade.tachiyomi.widget.EmptyView
@ -47,7 +58,6 @@ import exh.source.isEhBasedSource
fun BrowseSourceScreen(
presenter: BrowseSourcePresenter,
navigateUp: () -> Unit,
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
onFabClick: () -> Unit,
onMangaClick: (Manga) -> Unit,
onMangaLongClick: (Manga) -> Unit,
@ -74,7 +84,7 @@ fun BrowseSourceScreen(
state = presenter,
source = presenter.source!!,
displayMode = presenter.displayMode.takeUnless { presenter.source!!.isEhBasedSource() && presenter.ehentaiBrowseDisplayMode },
onDisplayModeChange = onDisplayModeChange,
onDisplayModeChange = { presenter.displayMode = it },
navigateUp = navigateUp,
onWebViewClick = onWebViewClick,
onHelpClick = onHelpClick,
@ -86,31 +96,17 @@ fun BrowseSourceScreen(
)
},
floatingActionButton = {
// SY -->
// if (presenter.filters.isNotEmpty()) {
ExtendedFloatingActionButton(
modifier = Modifier.navigationBarsPadding(),
text = {
Text(
text = if (presenter.filters.isNotEmpty()) {
stringResource(id = R.string.action_filter)
} else {
stringResource(R.string.saved_searches)
},
BrowseSourceFloatingActionButton(
isVisible = presenter.filters.isNotEmpty(),
onFabClick = onFabClick,
)
},
icon = { Icon(Icons.Outlined.FilterList, contentDescription = "") },
onClick = onFabClick,
)
// }
// SY <--
},
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
) { paddingValues ->
BrowseSourceContent(
source = presenter.source,
state = presenter,
mangaList = mangaList,
getMangaState = { presenter.getManga(it) },
// SY -->
@ -130,18 +126,113 @@ fun BrowseSourceScreen(
onLocalSourceHelpClick = onHelpClick,
onMangaClick = onMangaClick,
onMangaLongClick = onMangaLongClick,
header = {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.horizontalScroll(rememberScrollState()),
) {
FilterChip(
selected = presenter.currentQuery == GetRemoteManga.QUERY_POPULAR,
onClick = {
presenter.resetFilter()
presenter.search(GetRemoteManga.QUERY_POPULAR)
},
leadingIcon = {
Icon(
imageVector = Icons.Outlined.Favorite,
contentDescription = "",
modifier = Modifier
.size(FilterChipDefaults.IconSize),
)
},
label = {
Text(text = stringResource(id = R.string.popular))
},
)
if (presenter.source?.supportsLatest == true) {
FilterChip(
selected = presenter.currentQuery == GetRemoteManga.QUERY_LATEST,
onClick = {
presenter.resetFilter()
presenter.search(GetRemoteManga.QUERY_LATEST)
},
leadingIcon = {
Icon(
imageVector = Icons.Outlined.NewReleases,
contentDescription = "",
modifier = Modifier
.size(FilterChipDefaults.IconSize),
)
},
label = {
Text(text = stringResource(id = R.string.latest))
},
)
}
/* SY --> if (presenter.filters.isNotEmpty())*/ run /* SY <-- */ {
FilterChip(
selected = presenter.currentQuery != GetRemoteManga.QUERY_POPULAR && presenter.currentQuery != GetRemoteManga.QUERY_LATEST,
onClick = onFabClick,
leadingIcon = {
Icon(
imageVector = Icons.Outlined.FilterList,
contentDescription = "",
modifier = Modifier
.size(FilterChipDefaults.IconSize),
)
},
label = {
// SY -->
Text(
text = if (presenter.filters.isNotEmpty()) {
stringResource(id = R.string.action_filter)
} else {
stringResource(id = R.string.saved_searches)
},
)
// SY <--
},
)
}
}
},
)
}
}
@Composable
fun BrowseSourceFloatingActionButton(
modifier: Modifier = Modifier.navigationBarsPadding(),
isVisible: Boolean,
onFabClick: () -> Unit,
) {
run {
ExtendedFloatingActionButton(
modifier = modifier,
text = {
Text(
text = if (isVisible) {
stringResource(id = R.string.action_filter)
} else {
stringResource(id = R.string.saved_searches)
},
)
},
icon = { Icon(Icons.Outlined.FilterList, contentDescription = "") },
onClick = onFabClick,
)
}
}
@Composable
fun BrowseSourceContent(
source: CatalogueSource?,
state: BrowseSourceState,
mangaList: LazyPagingItems</* SY --> */Pair<Manga, RaisedSearchMetadata?>/* SY <-- */>,
getMangaState: @Composable ((Manga) -> State<Manga>),
// SY -->
getMetadataState: @Composable ((Manga, RaisedSearchMetadata?) -> State<RaisedSearchMetadata?>),
// SY <--
header: (@Composable () -> Unit)? = null,
columns: GridCells,
ehentaiBrowseDisplayMode: Boolean,
displayMode: LibraryDisplayMode,
@ -186,7 +277,7 @@ fun BrowseSourceContent(
if (mangaList.itemCount <= 0 && errorState != null && errorState is LoadState.Error) {
EmptyScreen(
message = getErrorMessage(errorState),
actions = if (source is LocalSource /* SY --> */ && onLocalSourceHelpClick != null /* SY <-- */) {
actions = if (state.source is LocalSource /* SY --> */ && onLocalSourceHelpClick != null /* SY <-- */) {
listOf(
EmptyView.Action(R.string.local_source_help_guide, R.drawable.ic_help_24dp) { onLocalSourceHelpClick() },
)
@ -204,8 +295,13 @@ fun BrowseSourceContent(
return
}
if (mangaList.itemCount == 0 && mangaList.loadState.refresh is LoadState.Loading) {
LoadingScreen()
return
}
// SY -->
if (source?.isEhBasedSource() == true && ehentaiBrowseDisplayMode) {
if (state.source?.isEhBasedSource() == true && ehentaiBrowseDisplayMode) {
BrowseSourceEHentaiList(
mangaList = mangaList,
getMangaState = getMangaState,
@ -213,6 +309,7 @@ fun BrowseSourceContent(
contentPadding = contentPadding,
onMangaClick = onMangaClick,
onMangaLongClick = onMangaLongClick,
header = header,
)
return
}
@ -230,6 +327,7 @@ fun BrowseSourceContent(
contentPadding = contentPadding,
onMangaClick = onMangaClick,
onMangaLongClick = onMangaLongClick,
header = header,
)
}
LibraryDisplayMode.List -> {
@ -242,6 +340,7 @@ fun BrowseSourceContent(
contentPadding = contentPadding,
onMangaClick = onMangaClick,
onMangaLongClick = onMangaLongClick,
header = header,
)
}
else -> {
@ -255,6 +354,7 @@ fun BrowseSourceContent(
contentPadding = contentPadding,
onMangaClick = onMangaClick,
onMangaLongClick = onMangaLongClick,
header = header,
)
}
}

View File

@ -6,6 +6,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.domain.source.interactor.GetRemoteManga
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
@ -16,22 +17,31 @@ interface BrowseSourceState {
val source: CatalogueSource?
var searchQuery: String?
val currentQuery: String
val isUserQuery: Boolean
val filters: FilterList
val filterItems: List<IFlexible<*>>
val appliedFilters: FilterList
val currentFilters: FilterList
var dialog: BrowseSourcePresenter.Dialog?
}
fun BrowseSourceState(initialQuery: String?): BrowseSourceState {
return BrowseSourceStateImpl(initialQuery)
if (initialQuery == GetRemoteManga.QUERY_POPULAR || initialQuery == GetRemoteManga.QUERY_LATEST) {
return BrowseSourceStateImpl(initialCurrentQuery = initialQuery)
}
return BrowseSourceStateImpl(initialQuery = initialQuery)
}
class BrowseSourceStateImpl(initialQuery: String?) : BrowseSourceState {
class BrowseSourceStateImpl(initialQuery: String? = null, initialCurrentQuery: String? = initialQuery) : BrowseSourceState {
override var source: CatalogueSource? by mutableStateOf(null)
override var searchQuery: String? by mutableStateOf(initialQuery)
override var currentQuery: String by mutableStateOf(initialQuery ?: "")
override var currentQuery: String by mutableStateOf(initialCurrentQuery ?: "")
override val isUserQuery: Boolean by derivedStateOf {
currentQuery.isNotEmpty() &&
currentQuery != GetRemoteManga.QUERY_POPULAR &&
currentQuery != GetRemoteManga.QUERY_LATEST
}
override var filters: FilterList by mutableStateOf(FilterList())
override val filterItems: List<IFlexible<*>> by derivedStateOf { filters.toItems() }
override var appliedFilters by mutableStateOf(FilterList())
override var currentFilters by mutableStateOf(FilterList())
override var dialog: BrowseSourcePresenter.Dialog? by mutableStateOf(null)
}

View File

@ -1,38 +1,81 @@
package eu.kanade.presentation.browse
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.glance.LocalContext
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalUriHandler
import androidx.paging.compose.collectAsLazyPagingItems
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.presentation.browse.components.BrowseSourceSearchToolbar
import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.ui.more.MoreController
@Composable
fun SourceSearchScreen(
presenter: BrowseSourcePresenter,
navigateUp: () -> Unit,
onFabClick: () -> Unit,
onClickManga: (Manga) -> Unit,
// SY -->
onSettingsClick: () -> Unit,
// SY <--
onMangaClick: (Manga) -> Unit,
onWebViewClick: () -> Unit,
) {
val context = LocalContext.current
val columns by presenter.getColumnsPreferenceForCurrentOrientation()
BrowseSourceScreen(
presenter = presenter,
val mangaList = presenter.getMangaList().collectAsLazyPagingItems()
val snackbarHostState = remember { SnackbarHostState() }
val uriHandler = LocalUriHandler.current
val onHelpClick = {
uriHandler.openUri(LocalSource.HELP_URL)
}
Scaffold(
topBar = { scrollBehavior ->
BrowseSourceSearchToolbar(
searchQuery = presenter.searchQuery ?: "",
onSearchQueryChanged = { presenter.searchQuery = it },
navigateUp = navigateUp,
onDisplayModeChange = { presenter.displayMode = (it) },
onFabClick = onFabClick,
onMangaClick = onClickManga,
onMangaLongClick = onClickManga,
onWebViewClick = f@{
val source = presenter.source as? HttpSource ?: return@f
val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name)
context.startActivity(intent)
onResetClick = { presenter.searchQuery = "" },
onSearchClick = { presenter.search() },
scrollBehavior = scrollBehavior,
)
},
floatingActionButton = {
BrowseSourceFloatingActionButton(
isVisible = presenter.filters.isNotEmpty(),
onFabClick = onFabClick,
)
},
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
) { paddingValues ->
BrowseSourceContent(
state = presenter,
mangaList = mangaList,
getMangaState = { presenter.getManga(it) },
// SY -->
onSettingsClick = onSettingsClick,
getMetadataState = { manga, metadata ->
presenter.getRaisedSearchMetadata(manga, metadata)
},
// SY <--
columns = columns,
// SY -->
ehentaiBrowseDisplayMode = presenter.ehentaiBrowseDisplayMode,
// SY <--
displayMode = presenter.displayMode,
snackbarHostState = snackbarHostState,
contentPadding = paddingValues,
onWebViewClick = onWebViewClick,
onHelpClick = { uriHandler.openUri(MoreController.URL_HELP) },
onLocalSourceHelpClick = onHelpClick,
onMangaClick = onMangaClick,
onMangaLongClick = onMangaClick,
)
}
}

View File

@ -27,6 +27,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.domain.source.interactor.GetRemoteManga
import eu.kanade.domain.source.model.Pin
import eu.kanade.domain.source.model.Source
import eu.kanade.presentation.browse.components.BaseSourceItem
@ -50,9 +51,8 @@ import kotlinx.coroutines.flow.collectLatest
@Composable
fun SourcesScreen(
presenter: SourcesPresenter,
onClickItem: (Source) -> Unit,
onClickItem: (Source, String) -> Unit,
onClickDisable: (Source) -> Unit,
onClickLatest: (Source) -> Unit,
onClickPin: (Source) -> Unit,
onClickSetCategories: (Source, List<String>) -> Unit,
onClickToggleDataSaver: (Source) -> Unit,
@ -66,7 +66,6 @@ fun SourcesScreen(
state = presenter,
onClickItem = onClickItem,
onClickDisable = onClickDisable,
onClickLatest = onClickLatest,
onClickPin = onClickPin,
onClickSetCategories = onClickSetCategories,
onClickToggleDataSaver = onClickToggleDataSaver,
@ -87,9 +86,8 @@ fun SourcesScreen(
@Composable
fun SourceList(
state: SourcesState,
onClickItem: (Source) -> Unit,
onClickItem: (Source, String) -> Unit,
onClickDisable: (Source) -> Unit,
onClickLatest: (Source) -> Unit,
onClickPin: (Source) -> Unit,
onClickSetCategories: (Source, List<String>) -> Unit,
onClickToggleDataSaver: (Source) -> Unit,
@ -127,7 +125,6 @@ fun SourceList(
showPin = state.showPin,
onClickItem = onClickItem,
onLongClickItem = { state.dialog = Dialog.SourceLongClick(it) },
onClickLatest = onClickLatest,
onClickPin = onClickPin,
)
}
@ -199,19 +196,18 @@ fun SourceItem(
showLatest: Boolean,
showPin: Boolean,
// SY <--
onClickItem: (Source) -> Unit,
onClickItem: (Source, String) -> Unit,
onLongClickItem: (Source) -> Unit,
onClickLatest: (Source) -> Unit,
onClickPin: (Source) -> Unit,
) {
BaseSourceItem(
modifier = modifier,
source = source,
onClickItem = { onClickItem(source) },
onClickItem = { onClickItem(source, GetRemoteManga.QUERY_POPULAR) },
onLongClickItem = { onLongClickItem(source) },
action = { source ->
if (source.supportsLatest /* SY --> */ && showLatest /* SY <-- */) {
TextButton(onClick = { onClickLatest(source) }) {
TextButton(onClick = { onClickItem(source, GetRemoteManga.QUERY_LATEST) }) {
Text(
text = stringResource(R.string.latest),
style = LocalTextStyle.current.copy(

View File

@ -1,122 +0,0 @@
package eu.kanade.presentation.browse.components
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ViewModule
import androidx.compose.material.icons.outlined.Check
import androidx.compose.material.icons.outlined.Help
import androidx.compose.material.icons.outlined.Public
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.ui.library.setting.LibraryDisplayMode
import exh.source.anyIs
@Composable
fun BrowseLatestToolbar(
navigateUp: () -> Unit,
source: CatalogueSource,
displayMode: LibraryDisplayMode?,
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
onHelpClick: () -> Unit,
onWebViewClick: () -> Unit,
// SY -->
onSettingsClick: () -> Unit,
// SY <--
scrollBehavior: TopAppBarScrollBehavior,
) {
AppBar(
navigateUp = navigateUp,
title = source.name,
actions = {
var selectingDisplayMode by remember { mutableStateOf(false) }
AppBarActions(
// SY -->
actions = listOfNotNull(
AppBar.Action(
title = stringResource(id = R.string.action_display_mode),
icon = Icons.Filled.ViewModule,
onClick = { selectingDisplayMode = true },
).takeIf { displayMode != null },
// SY <--
if (source is LocalSource) {
AppBar.Action(
title = stringResource(id = R.string.label_help),
icon = Icons.Outlined.Help,
onClick = onHelpClick,
)
} else {
AppBar.Action(
title = stringResource(id = R.string.action_web_view),
icon = Icons.Outlined.Public,
onClick = onWebViewClick,
)
},
// SY -->
AppBar.Action(
title = stringResource(R.string.action_settings),
icon = Icons.Outlined.Settings,
onClick = onSettingsClick,
).takeIf { source.anyIs<ConfigurableSource>() },
// SY <--
),
)
DropdownMenu(
expanded = selectingDisplayMode,
onDismissRequest = { selectingDisplayMode = false },
) {
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_display_comfortable_grid)) },
onClick = { onDisplayModeChange(LibraryDisplayMode.ComfortableGrid) },
trailingIcon = {
if (displayMode == LibraryDisplayMode.ComfortableGrid) {
Icon(
imageVector = Icons.Outlined.Check,
contentDescription = "",
)
}
},
)
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_display_grid)) },
onClick = { onDisplayModeChange(LibraryDisplayMode.CompactGrid) },
trailingIcon = {
if (displayMode == LibraryDisplayMode.CompactGrid) {
Icon(
imageVector = Icons.Outlined.Check,
contentDescription = "",
)
}
},
)
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_display_list)) },
onClick = { onDisplayModeChange(LibraryDisplayMode.List) },
trailingIcon = {
if (displayMode == LibraryDisplayMode.List) {
Icon(
imageVector = Icons.Outlined.Check,
contentDescription = "",
)
}
},
)
}
},
scrollBehavior = scrollBehavior,
)
}

View File

@ -37,6 +37,7 @@ fun BrowseSourceComfortableGrid(
// SY -->
getMetadataState: @Composable ((Manga, RaisedSearchMetadata?) -> State<RaisedSearchMetadata?>),
// SY <--
header: (@Composable () -> Unit)? = null,
columns: GridCells,
contentPadding: PaddingValues,
onMangaClick: (Manga) -> Unit,
@ -44,12 +45,18 @@ fun BrowseSourceComfortableGrid(
) {
LazyVerticalGrid(
columns = columns,
contentPadding = PaddingValues(8.dp) + contentPadding,
contentPadding = PaddingValues(8.dp, 4.dp) + contentPadding,
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
if (header != null) {
item(span = { GridItemSpan(maxLineSpan) }) {
header()
}
}
if (mangaList.loadState.prepend is LoadState.Loading) {
item(span = { GridItemSpan(maxLineSpan) }) {
BrowseSourceLoadingItem()
}
}
@ -70,8 +77,8 @@ fun BrowseSourceComfortableGrid(
)
}
item(span = { GridItemSpan(maxLineSpan) }) {
if (mangaList.loadState.refresh is LoadState.Loading || mangaList.loadState.append is LoadState.Loading) {
item(span = { GridItemSpan(maxLineSpan) }) {
BrowseSourceLoadingItem()
}
}

View File

@ -48,13 +48,20 @@ fun BrowseSourceCompactGrid(
contentPadding: PaddingValues,
onMangaClick: (Manga) -> Unit,
onMangaLongClick: (Manga) -> Unit,
header: (@Composable () -> Unit)? = null,
) {
LazyVerticalGrid(
columns = columns,
contentPadding = PaddingValues(8.dp) + contentPadding,
contentPadding = PaddingValues(8.dp, 4.dp) + contentPadding,
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
if (header != null) {
item(span = { GridItemSpan(maxLineSpan) }) {
header()
}
}
item(span = { GridItemSpan(maxLineSpan) }) {
if (mangaList.loadState.prepend is LoadState.Loading) {
BrowseSourceLoadingItem()

View File

@ -58,10 +58,17 @@ fun BrowseSourceEHentaiList(
contentPadding: PaddingValues,
onMangaClick: (Manga) -> Unit,
onMangaLongClick: (Manga) -> Unit,
header: (@Composable () -> Unit)? = null,
) {
LazyColumn(
contentPadding = contentPadding,
) {
if (header != null) {
item {
header()
}
}
item {
if (mangaList.loadState.prepend is LoadState.Loading) {
BrowseSourceLoadingItem()

View File

@ -36,10 +36,17 @@ fun BrowseSourceList(
contentPadding: PaddingValues,
onMangaClick: (Manga) -> Unit,
onMangaLongClick: (Manga) -> Unit,
header: (@Composable () -> Unit)? = null,
) {
LazyColumn(
contentPadding = contentPadding,
) {
if (header != null) {
item {
header()
}
}
item {
if (mangaList.loadState.prepend is LoadState.Loading) {
BrowseSourceLoadingItem()

View File

@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@ -18,8 +17,6 @@ fun BrowseSourceLoadingItem() {
.padding(vertical = 16.dp),
horizontalArrangement = Arrangement.Center,
) {
CircularProgressIndicator(
modifier = Modifier.size(64.dp),
)
CircularProgressIndicator()
}
}

View File

@ -49,11 +49,15 @@ fun BrowseSourceToolbar(
) {
if (state.searchQuery == null) {
BrowseSourceRegularToolbar(
source = source,
title = if (state.isUserQuery) state.currentQuery else source.name,
isLocalSource = source is LocalSource,
// SY -->
isConfigurableSource = source.anyIs<ConfigurableSource>(),
// SY <--
displayMode = displayMode,
onDisplayModeChange = onDisplayModeChange,
navigateUp = navigateUp,
onSearchClick = { state.searchQuery = "" },
onSearchClick = { state.searchQuery = if (state.isUserQuery) state.currentQuery else "" },
onWebViewClick = onWebViewClick,
onHelpClick = onHelpClick,
// SY -->
@ -65,10 +69,7 @@ fun BrowseSourceToolbar(
BrowseSourceSearchToolbar(
searchQuery = state.searchQuery!!,
onSearchQueryChanged = { state.searchQuery = it },
navigateUp = {
state.searchQuery = null
onSearch()
},
navigateUp = { state.searchQuery = null },
onResetClick = { state.searchQuery = "" },
onSearchClick = onSearch,
scrollBehavior = scrollBehavior,
@ -78,7 +79,9 @@ fun BrowseSourceToolbar(
@Composable
fun BrowseSourceRegularToolbar(
source: CatalogueSource,
title: String,
isLocalSource: Boolean,
isConfigurableSource: Boolean,
displayMode: LibraryDisplayMode?,
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
navigateUp: () -> Unit,
@ -92,7 +95,7 @@ fun BrowseSourceRegularToolbar(
) {
AppBar(
navigateUp = navigateUp,
title = source.name,
title = title,
actions = {
var selectingDisplayMode by remember { mutableStateOf(false) }
AppBarActions(
@ -109,7 +112,7 @@ fun BrowseSourceRegularToolbar(
onClick = { selectingDisplayMode = true },
).takeIf { displayMode != null },
// SY <--
if (source is LocalSource) {
if (isLocalSource) {
AppBar.Action(
title = stringResource(id = R.string.label_help),
icon = Icons.Outlined.Help,
@ -123,11 +126,13 @@ fun BrowseSourceRegularToolbar(
)
},
// SY -->
if (isConfigurableSource) {
AppBar.Action(
title = stringResource(R.string.action_settings),
icon = Icons.Outlined.Settings,
onClick = onSettingsClick,
).takeIf { source.anyIs<ConfigurableSource>() },
)
} else null,
// SY <--
),
)

View File

@ -3,4 +3,12 @@ package eu.kanade.tachiyomi.source.model
data class FilterList(val list: List<Filter<*>>) : List<Filter<*>> by list {
constructor(vararg fs: Filter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList())
override fun equals(other: Any?): Boolean {
return false
}
override fun hashCode(): Int {
return list.hashCode()
}
}

View File

@ -5,13 +5,13 @@ import androidx.compose.material.icons.outlined.Add
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.bluelinelabs.conductor.Router
import eu.kanade.domain.source.interactor.GetRemoteManga
import eu.kanade.presentation.browse.FeedScreen
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.TabContent
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.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.manga.MangaController
@Composable
@ -44,7 +44,7 @@ fun feedTab(
},
onClickSource = { source ->
presenter.preferences.lastUsedSource().set(source.id)
router?.pushController(LatestUpdatesController(source))
router?.pushController(BrowseSourceController(source, GetRemoteManga.QUERY_LATEST))
},
onClickDelete = {
presenter.dialog = FeedPresenter.Dialog.DeleteFeed(it)

View File

@ -8,10 +8,10 @@ import eu.kanade.domain.manga.model.Manga
import eu.kanade.presentation.browse.SourceSearchScreen
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesController
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListController
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -31,12 +31,16 @@ 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() },
// SY -->
onClickManga = { manga ->
onMangaClick = { manga ->
val migrationListController = targetController as? MigrationListController ?: return@SourceSearchScreen
val sourceManager = Injekt.get<SourceManager>()
val source = sourceManager.get(manga.source) ?: return@SourceSearchScreen
@ -44,10 +48,12 @@ class SourceSearchController(
router.popCurrentController()
router.popCurrentController()
},
onSettingsClick = {
router.pushController(SourcePreferencesController(presenter.source!!.id))
},
// SY <--
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

@ -6,6 +6,7 @@ import androidx.compose.material.icons.outlined.TravelExplore
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.bluelinelabs.conductor.Router
import eu.kanade.domain.source.interactor.GetRemoteManga.Companion.QUERY_POPULAR
import eu.kanade.presentation.browse.SourcesScreen
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.TabContent
@ -14,7 +15,6 @@ import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedController
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
import exh.ui.smartsearch.SmartSearchController
@Composable
@ -45,13 +45,13 @@ fun sourcesTab(
content = {
SourcesScreen(
presenter = presenter,
onClickItem = { source ->
onClickItem = { source, query ->
// SY -->
val controller = when {
presenter.controllerMode == SourcesController.Mode.SMART_SEARCH ->
SmartSearchController(source.id, presenter.smartSearchConfig!!)
presenter.useNewSourceNavigation -> SourceFeedController(source.id)
else -> BrowseSourceController(source)
(query.isBlank() || query == QUERY_POPULAR) && presenter.useNewSourceNavigation -> SourceFeedController(source.id)
else -> BrowseSourceController(source, query)
}
presenter.onOpenSource(source)
router?.pushController(controller)
@ -60,10 +60,6 @@ fun sourcesTab(
onClickDisable = { source ->
presenter.toggleSource(source)
},
onClickLatest = { source ->
presenter.onOpenSource(source)
router?.pushController(LatestUpdatesController(source))
},
onClickPin = { source ->
presenter.togglePin(source)
},

View File

@ -102,6 +102,17 @@ open class BrowseSourceController(bundle: Bundle) :
*/
protected var filterSheet: SourceFilterSheet? = null
override fun createPresenter(): BrowseSourcePresenter {
// SY -->
return BrowseSourcePresenter(
args.getLong(SOURCE_ID_KEY),
args.getString(SEARCH_QUERY_KEY),
filtersJson = args.getString(FILTERS_CONFIG_KEY),
savedSearch = args.getLong(SAVED_SEARCH_CONFIG_KEY, 0).takeUnless { it == 0L },
)
// SY <--
}
@Composable
override fun ComposeContent() {
val scope = rememberCoroutineScope()
@ -110,7 +121,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 ->
@ -174,17 +184,6 @@ open class BrowseSourceController(bundle: Bundle) :
}
}
override fun createPresenter(): BrowseSourcePresenter {
// SY -->
return BrowseSourcePresenter(
args.getLong(SOURCE_ID_KEY),
args.getString(SEARCH_QUERY_KEY),
filtersJson = args.getString(FILTERS_CONFIG_KEY),
savedSearch = args.getLong(SAVED_SEARCH_CONFIG_KEY, 0).takeUnless { it == 0L },
)
// SY <--
}
fun setSavedSearches(savedSearches: List<EXHSavedSearch>) {
filterSheet?.setSavedSearches(savedSearches)
}

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
@ -33,7 +32,9 @@ import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.domain.source.interactor.DeleteSavedSearchById
import eu.kanade.domain.source.interactor.GetExhSavedSearch
import eu.kanade.domain.source.interactor.GetRemoteManga
import eu.kanade.domain.source.interactor.InsertSavedSearch
import eu.kanade.domain.source.model.SourcePagingSourceType
import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.presentation.browse.BrowseSourceState
@ -77,7 +78,6 @@ import eu.kanade.tachiyomi.util.system.logcat
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.savedsearches.models.SavedSearch
import exh.source.getMainSource
import exh.source.isEhBasedSource
import exh.util.nullIfBlank
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.Flow
@ -112,6 +112,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(),
@ -130,6 +131,8 @@ open class BrowseSourcePresenter(
// SY <--
) : BasePresenter<BrowseSourceController>(), BrowseSourceState by state {
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } }
var displayMode by preferences.sourceDisplayMode().asState()
val ehentaiBrowseDisplayMode by preferences.enhancedEHentaiView().asState()
@ -148,11 +151,11 @@ open class BrowseSourcePresenter(
@Composable
fun getMangaList(): Flow<PagingData</* SY --> */Pair<DomainManga, RaisedSearchMetadata?>/* SY <-- */>> {
return remember(currentQuery, appliedFilters) {
return remember(currentQuery, currentFilters) {
Pager(
PagingConfig(pageSize = 25),
) {
createPager(currentQuery, appliedFilters)
createSourcePagingSource(currentQuery, currentFilters)
}.flow
.map {
it.map {
@ -169,12 +172,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
}
@ -198,17 +201,15 @@ 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 } }
// SY -->
private val filterSerializer = FilterSerializer()
// SY <--
@ -246,15 +247,6 @@ open class BrowseSourcePresenter(
}
.launchIn(presenterScope)
// SY <--
if (savedState != null) {
query = savedState.getString(::query.name, "")
}
}
override fun onSave(state: Bundle) {
state.putString(::query.name, query)
super.onSave(state)
}
/**
@ -284,9 +276,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()
@ -394,18 +386,14 @@ open class BrowseSourcePresenter(
* @param filters a list of active filters.
*/
fun setSourceFilter(filters: FilterList) {
state.appliedFilters = filters
state.currentFilters = filters
}
open fun createPager(query: String, filters: FilterList): PagingSource<Long, Pair<SManga, RaisedSearchMetadata?>> {
// SY -->
return if (source!!.isEhBasedSource()) {
EHentaiBrowsePagingSource(source!!, query, filters)
} else {
SourceBrowsePagingSource(source!!, query, filters)
open fun createSourcePagingSource(query: String, filters: FilterList): SourcePagingSourceType {
return getRemoteManga.subscribe(sourceId, currentQuery, currentFilters)
}
// SY <--
}
/**
* Get user categories.
@ -423,12 +411,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

@ -8,6 +8,7 @@ import androidx.core.view.isVisible
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.source.interactor.GetRemoteManga
import eu.kanade.presentation.browse.SourceFeedScreen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource
@ -18,7 +19,6 @@ import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterSheet
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withUIContext
@ -223,11 +223,11 @@ open class SourceFeedController :
}
private fun onLatestClick() {
router.replaceTopController(LatestUpdatesController(presenter.source).withFadeTransaction())
router.replaceTopController(BrowseSourceController(presenter.source, GetRemoteManga.QUERY_LATEST).withFadeTransaction())
}
private fun onBrowseClick() {
router.replaceTopController(BrowseSourceController(presenter.source).withFadeTransaction())
router.replaceTopController(BrowseSourceController(presenter.source, GetRemoteManga.QUERY_POPULAR).withFadeTransaction())
}
private fun onSavedSearchClick(savedSearch: SavedSearch) {

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,116 +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.CatalogueSource
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesController
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),
)
// SY -->
constructor(source: CatalogueSource) : this(
bundleOf(SOURCE_ID_KEY to source.id),
)
// SY <--
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)
},
// SY -->
onSettingsClick = {
router.pushController(SourcePreferencesController(presenter.source!!.id))
},
// SY <--
)
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,14 +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
import exh.metadata.metadata.base.RaisedSearchMetadata
class LatestUpdatesPresenter(sourceId: Long) : BrowseSourcePresenter(sourceId) {
override fun createPager(query: String, filters: FilterList): PagingSource<Long, /*SY --> */ Pair<SManga, RaisedSearchMetadata?> /*SY <-- */> {
return LatestUpdatesBrowsePagingSource(source!!)
}
}

View File

@ -50,7 +50,6 @@ import eu.kanade.tachiyomi.ui.browse.source.SourcesController.Companion.SMART_SE
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedController
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
@ -483,10 +482,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)

View File

@ -45,6 +45,7 @@ import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.setting.database.ClearDatabaseController
import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchNonCancellableIO
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.preference.bindTo

View File

@ -1,15 +1,15 @@
package exh.md.follows
import eu.kanade.data.source.SourcePagingSource
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowsePagingSource
/**
* LatestUpdatesPager inherited from the general Pager.
*/
class MangaDexFollowsPagingSource(val source: MangaDex) : BrowsePagingSource() {
class MangaDexFollowsPagingSource(val mangadex: MangaDex) : SourcePagingSource(mangadex) {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
return source.fetchFollows(currentPage)
return mangadex.fetchFollows(currentPage)
}
}

View File

@ -4,10 +4,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.paging.PagingSource
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.source.model.SourcePagingSourceType
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
import exh.metadata.metadata.base.RaisedSearchMetadata
@ -18,7 +17,7 @@ import exh.source.getMainSource
*/
class MangaDexFollowsPresenter(sourceId: Long) : BrowseSourcePresenter(sourceId) {
override fun createPager(query: String, filters: FilterList): PagingSource<Long, Pair<SManga, RaisedSearchMetadata?>> {
override fun createSourcePagingSource(query: String, filters: FilterList): SourcePagingSourceType {
return MangaDexFollowsPagingSource(source!!.getMainSource() as MangaDex)
}

View File

@ -1,23 +1,23 @@
package exh.md.similar
import eu.kanade.data.source.NoResultsException
import eu.kanade.data.source.SourcePagingSource
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.MetadataMangasPage
import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowsePagingSource
import eu.kanade.tachiyomi.ui.browse.source.browse.NoResultsException
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
/**
* MangaDexSimilarPagingSource inherited from the general Pager.
*/
class MangaDexSimilarPagingSource(val manga: Manga, val source: MangaDex) : BrowsePagingSource() {
class MangaDexSimilarPagingSource(val manga: Manga, val mangadex: MangaDex) : SourcePagingSource(mangadex) {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
val mangasPage = coroutineScope {
val similarPageDef = async { source.getMangaSimilar(manga.toSManga()) }
val relatedPageDef = async { source.getMangaRelated(manga.toSManga()) }
val similarPageDef = async { mangadex.getMangaSimilar(manga.toSManga()) }
val relatedPageDef = async { mangadex.getMangaRelated(manga.toSManga()) }
val similarPage = similarPageDef.await()
val relatedPage = relatedPageDef.await()

View File

@ -5,11 +5,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.paging.PagingSource
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.source.model.SourcePagingSourceType
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
import exh.metadata.metadata.base.RaisedSearchMetadata
@ -34,7 +33,7 @@ class MangaDexSimilarPresenter(
this.manga = runBlocking { getManga.await(mangaId) }
}
override fun createPager(query: String, filters: FilterList): PagingSource<Long, Pair<SManga, RaisedSearchMetadata?>> {
override fun createSourcePagingSource(query: String, filters: FilterList): SourcePagingSourceType {
return MangaDexSimilarPagingSource(manga!!, source!!.getMainSource() as MangaDex)
}

View File

@ -1,15 +1,16 @@
package exh.recs
import eu.kanade.data.source.NoResultsException
import eu.kanade.data.source.SourcePagingSource
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowsePagingSource
import eu.kanade.tachiyomi.ui.browse.source.browse.NoResultsException
import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.system.logcat
import exh.util.MangaType
@ -193,10 +194,11 @@ class Anilist : API("https://graphql.anilist.co/") {
}
open class RecommendsPagingSource(
source: CatalogueSource,
private val manga: Manga,
private val smart: Boolean = true,
private var preferredApi: API = API.MYANIMELIST,
) : BrowsePagingSource() {
) : SourcePagingSource(source) {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
if (smart) preferredApi = if (manga.mangaType() != MangaType.TYPE_MANGA) API.ANILIST else preferredApi

View File

@ -1,13 +1,11 @@
package exh.recs
import android.os.Bundle
import androidx.paging.PagingSource
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.source.model.SourcePagingSourceType
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
import exh.metadata.metadata.base.RaisedSearchMetadata
import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -28,7 +26,7 @@ class RecommendsPresenter(
this.manga = runBlocking { getManga.await(mangaId) }
}
override fun createPager(query: String, filters: FilterList): PagingSource<Long, Pair<SManga, RaisedSearchMetadata?>> {
return RecommendsPagingSource(manga!!)
override fun createSourcePagingSource(query: String, filters: FilterList): SourcePagingSourceType {
return RecommendsPagingSource(source!!, manga!!)
}
}

View File

@ -582,6 +582,7 @@
<string name="action_global_search_hint">Global search…</string>
<string name="action_global_search_query">Search for \"%1$s\" globally</string>
<string name="latest">Latest</string>
<string name="popular">Popular</string>
<string name="browse">Browse</string>
<string name="local_source_help_guide">Local source guide</string>
<string name="no_pinned_sources">You have no pinned sources</string>