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.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList 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.source.model.MetadataMangasPage
import eu.kanade.tachiyomi.util.lang.awaitSingle 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 private var lastMangaLink: String? = null
abstract suspend fun fetchNextPage(currentPage: Int): MangasPage
override suspend fun requestNextPage(currentPage: Int): MangasPage { override suspend fun requestNextPage(currentPage: Int): MangasPage {
val lastMangaLink = lastMangaLink val lastMangaLink = lastMangaLink
val observable = if (query.isBlank() && filters.isEmpty()) { val mangasPage = fetchNextPage(currentPage)
source.fetchPopularManga(currentPage)
} else {
source.fetchSearchManga(currentPage, query, filters)
}
val mangasPage = observable.awaitSingle()
mangasPage.mangas.lastOrNull()?.let { mangasPage.mangas.lastOrNull()?.let {
this.lastMangaLink = it.url 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 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.MangasPage
import eu.kanade.tachiyomi.source.model.MetadataMangasPage import eu.kanade.tachiyomi.source.model.MetadataMangasPage
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.lang.awaitSingle
import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withIOContext
import exh.metadata.metadata.base.RaisedSearchMetadata 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 abstract suspend fun requestNextPage(currentPage: Int): MangasPage
@ -18,6 +23,8 @@ abstract class BrowsePagingSource : PagingSource<Long, /*SY --> */ Pair<SManga,
val mangasPage = try { val mangasPage = try {
withIOContext { withIOContext {
requestNextPage(page.toInt()) requestNextPage(page.toInt())
.takeIf { it.mangas.isNotEmpty() }
?: throw NoResultsException()
} }
} catch (e: Exception) { } catch (e: Exception) {
return LoadResult.Error(e) 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 -> return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition) val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey ?: anchorPage?.nextKey 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.data.DatabaseHandler
import eu.kanade.domain.source.model.Source 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.model.SourceWithCount
import eu.kanade.domain.source.repository.SourceRepository import eu.kanade.domain.source.repository.SourceRepository
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.FilterList
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import exh.source.isEhBasedSource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map 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.manga.repository.MangaRepository
import eu.kanade.domain.source.interactor.GetEnabledSources import eu.kanade.domain.source.interactor.GetEnabledSources
import eu.kanade.domain.source.interactor.GetLanguagesWithSources 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.GetSourcesWithFavoriteCount
import eu.kanade.domain.source.interactor.GetSourcesWithNonLibraryManga import eu.kanade.domain.source.interactor.GetSourcesWithNonLibraryManga
import eu.kanade.domain.source.interactor.SetMigrateSorting import eu.kanade.domain.source.interactor.SetMigrateSorting
@ -133,6 +134,7 @@ class DomainModule : InjektModule {
addSingletonFactory<SourceDataRepository> { SourceDataRepositoryImpl(get()) } addSingletonFactory<SourceDataRepository> { SourceDataRepositoryImpl(get()) }
addFactory { GetEnabledSources(get(), get()) } addFactory { GetEnabledSources(get(), get()) }
addFactory { GetLanguagesWithSources(get(), get()) } addFactory { GetLanguagesWithSources(get(), get()) }
addFactory { GetRemoteManga(get()) }
addFactory { GetSourcesWithFavoriteCount(get(), get()) } addFactory { GetSourcesWithFavoriteCount(get(), get()) }
addFactory { GetSourcesWithNonLibraryManga(get()) } addFactory { GetSourcesWithNonLibraryManga(get()) }
addFactory { SetMigrateSorting(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 package eu.kanade.domain.source.repository
import eu.kanade.domain.source.model.Source 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.model.SourceWithCount
import eu.kanade.tachiyomi.source.model.FilterList
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface SourceRepository { interface SourceRepository {
@ -13,4 +15,10 @@ interface SourceRepository {
fun getSourcesWithFavoriteCount(): Flow<List<Pair<Source, Long>>> fun getSourcesWithFavoriteCount(): Flow<List<Pair<Source, Long>>>
fun getSourcesWithNonLibraryManga(): Flow<List<SourceWithCount>> 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 -> ) { paddingValues ->
BrowseSourceContent( BrowseSourceContent(
source = presenter.source, state = presenter,
mangaList = mangaList, mangaList = mangaList,
getMangaState = { presenter.getManga(it) }, getMangaState = { presenter.getManga(it) },
// SY --> // SY -->

View File

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

View File

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

View File

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

View File

@ -1,38 +1,81 @@
package eu.kanade.presentation.browse package eu.kanade.presentation.browse
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable 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.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.browse.source.browse.BrowseSourcePresenter
import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.ui.more.MoreController
@Composable @Composable
fun SourceSearchScreen( fun SourceSearchScreen(
presenter: BrowseSourcePresenter, presenter: BrowseSourcePresenter,
navigateUp: () -> Unit, navigateUp: () -> Unit,
onFabClick: () -> Unit, onFabClick: () -> Unit,
onClickManga: (Manga) -> Unit, onMangaClick: (Manga) -> Unit,
// SY --> onWebViewClick: () -> Unit,
onSettingsClick: () -> Unit,
// SY <--
) { ) {
val context = LocalContext.current val columns by presenter.getColumnsPreferenceForCurrentOrientation()
BrowseSourceScreen( val mangaList = presenter.getMangaList().collectAsLazyPagingItems()
presenter = presenter,
navigateUp = navigateUp, val snackbarHostState = remember { SnackbarHostState() }
onDisplayModeChange = { presenter.displayMode = (it) },
onFabClick = onFabClick, val uriHandler = LocalUriHandler.current
onMangaClick = onClickManga,
onMangaLongClick = onClickManga, val onHelpClick = {
onWebViewClick = f@{ uriHandler.openUri(LocalSource.HELP_URL)
val source = presenter.source as? HttpSource ?: return@f }
val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name)
context.startActivity(intent) Scaffold(
topBar = { scrollBehavior ->
BrowseSourceSearchToolbar(
searchQuery = presenter.searchQuery ?: "",
onSearchQueryChanged = { presenter.searchQuery = it },
navigateUp = navigateUp,
onResetClick = { presenter.searchQuery = "" },
onSearchClick = { presenter.search() },
scrollBehavior = scrollBehavior,
)
}, },
// SY --> floatingActionButton = {
onSettingsClick = onSettingsClick, BrowseSourceFloatingActionButton(
// SY <-- isVisible = presenter.filters.isNotEmpty(),
) onFabClick = onFabClick,
)
},
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
) { paddingValues ->
BrowseSourceContent(
state = presenter,
mangaList = mangaList,
getMangaState = { presenter.getManga(it) },
// SY -->
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.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp 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.Pin
import eu.kanade.domain.source.model.Source import eu.kanade.domain.source.model.Source
import eu.kanade.presentation.browse.components.BaseSourceItem import eu.kanade.presentation.browse.components.BaseSourceItem
@ -50,9 +51,8 @@ import kotlinx.coroutines.flow.collectLatest
@Composable @Composable
fun SourcesScreen( fun SourcesScreen(
presenter: SourcesPresenter, presenter: SourcesPresenter,
onClickItem: (Source) -> Unit, onClickItem: (Source, String) -> Unit,
onClickDisable: (Source) -> Unit, onClickDisable: (Source) -> Unit,
onClickLatest: (Source) -> Unit,
onClickPin: (Source) -> Unit, onClickPin: (Source) -> Unit,
onClickSetCategories: (Source, List<String>) -> Unit, onClickSetCategories: (Source, List<String>) -> Unit,
onClickToggleDataSaver: (Source) -> Unit, onClickToggleDataSaver: (Source) -> Unit,
@ -66,7 +66,6 @@ fun SourcesScreen(
state = presenter, state = presenter,
onClickItem = onClickItem, onClickItem = onClickItem,
onClickDisable = onClickDisable, onClickDisable = onClickDisable,
onClickLatest = onClickLatest,
onClickPin = onClickPin, onClickPin = onClickPin,
onClickSetCategories = onClickSetCategories, onClickSetCategories = onClickSetCategories,
onClickToggleDataSaver = onClickToggleDataSaver, onClickToggleDataSaver = onClickToggleDataSaver,
@ -87,9 +86,8 @@ fun SourcesScreen(
@Composable @Composable
fun SourceList( fun SourceList(
state: SourcesState, state: SourcesState,
onClickItem: (Source) -> Unit, onClickItem: (Source, String) -> Unit,
onClickDisable: (Source) -> Unit, onClickDisable: (Source) -> Unit,
onClickLatest: (Source) -> Unit,
onClickPin: (Source) -> Unit, onClickPin: (Source) -> Unit,
onClickSetCategories: (Source, List<String>) -> Unit, onClickSetCategories: (Source, List<String>) -> Unit,
onClickToggleDataSaver: (Source) -> Unit, onClickToggleDataSaver: (Source) -> Unit,
@ -127,7 +125,6 @@ fun SourceList(
showPin = state.showPin, showPin = state.showPin,
onClickItem = onClickItem, onClickItem = onClickItem,
onLongClickItem = { state.dialog = Dialog.SourceLongClick(it) }, onLongClickItem = { state.dialog = Dialog.SourceLongClick(it) },
onClickLatest = onClickLatest,
onClickPin = onClickPin, onClickPin = onClickPin,
) )
} }
@ -199,19 +196,18 @@ fun SourceItem(
showLatest: Boolean, showLatest: Boolean,
showPin: Boolean, showPin: Boolean,
// SY <-- // SY <--
onClickItem: (Source) -> Unit, onClickItem: (Source, String) -> Unit,
onLongClickItem: (Source) -> Unit, onLongClickItem: (Source) -> Unit,
onClickLatest: (Source) -> Unit,
onClickPin: (Source) -> Unit, onClickPin: (Source) -> Unit,
) { ) {
BaseSourceItem( BaseSourceItem(
modifier = modifier, modifier = modifier,
source = source, source = source,
onClickItem = { onClickItem(source) }, onClickItem = { onClickItem(source, GetRemoteManga.QUERY_POPULAR) },
onLongClickItem = { onLongClickItem(source) }, onLongClickItem = { onLongClickItem(source) },
action = { source -> action = { source ->
if (source.supportsLatest /* SY --> */ && showLatest /* SY <-- */) { if (source.supportsLatest /* SY --> */ && showLatest /* SY <-- */) {
TextButton(onClick = { onClickLatest(source) }) { TextButton(onClick = { onClickItem(source, GetRemoteManga.QUERY_LATEST) }) {
Text( Text(
text = stringResource(R.string.latest), text = stringResource(R.string.latest),
style = LocalTextStyle.current.copy( 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 --> // SY -->
getMetadataState: @Composable ((Manga, RaisedSearchMetadata?) -> State<RaisedSearchMetadata?>), getMetadataState: @Composable ((Manga, RaisedSearchMetadata?) -> State<RaisedSearchMetadata?>),
// SY <-- // SY <--
header: (@Composable () -> Unit)? = null,
columns: GridCells, columns: GridCells,
contentPadding: PaddingValues, contentPadding: PaddingValues,
onMangaClick: (Manga) -> Unit, onMangaClick: (Manga) -> Unit,
@ -44,12 +45,18 @@ fun BrowseSourceComfortableGrid(
) { ) {
LazyVerticalGrid( LazyVerticalGrid(
columns = columns, columns = columns,
contentPadding = PaddingValues(8.dp) + contentPadding, contentPadding = PaddingValues(8.dp, 4.dp) + contentPadding,
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
item(span = { GridItemSpan(maxLineSpan) }) { if (header != null) {
if (mangaList.loadState.prepend is LoadState.Loading) { item(span = { GridItemSpan(maxLineSpan) }) {
header()
}
}
if (mangaList.loadState.prepend is LoadState.Loading) {
item(span = { GridItemSpan(maxLineSpan) }) {
BrowseSourceLoadingItem() BrowseSourceLoadingItem()
} }
} }
@ -70,8 +77,8 @@ fun BrowseSourceComfortableGrid(
) )
} }
item(span = { GridItemSpan(maxLineSpan) }) { if (mangaList.loadState.refresh is LoadState.Loading || mangaList.loadState.append is LoadState.Loading) {
if (mangaList.loadState.refresh is LoadState.Loading || mangaList.loadState.append is LoadState.Loading) { item(span = { GridItemSpan(maxLineSpan) }) {
BrowseSourceLoadingItem() BrowseSourceLoadingItem()
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,4 +3,12 @@ package eu.kanade.tachiyomi.source.model
data class FilterList(val list: List<Filter<*>>) : List<Filter<*>> by list { data class FilterList(val list: List<Filter<*>>) : List<Filter<*>> by list {
constructor(vararg fs: Filter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList()) 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.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.Router
import eu.kanade.domain.source.interactor.GetRemoteManga
import eu.kanade.presentation.browse.FeedScreen import eu.kanade.presentation.browse.FeedScreen
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.TabContent import eu.kanade.presentation.components.TabContent
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.pushController 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.BrowseSourceController
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
@Composable @Composable
@ -44,7 +44,7 @@ fun feedTab(
}, },
onClickSource = { source -> onClickSource = { source ->
presenter.preferences.lastUsedSource().set(source.id) presenter.preferences.lastUsedSource().set(source.id)
router?.pushController(LatestUpdatesController(source)) router?.pushController(BrowseSourceController(source, GetRemoteManga.QUERY_LATEST))
}, },
onClickDelete = { onClickDelete = {
presenter.dialog = FeedPresenter.Dialog.DeleteFeed(it) 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.presentation.browse.SourceSearchScreen
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.pushController import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesController
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListController import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListController
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController 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.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -31,12 +31,16 @@ class SourceSearchController(
@Composable @Composable
override fun ComposeContent() { 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( SourceSearchScreen(
presenter = presenter, presenter = presenter,
navigateUp = { router.popCurrentController() }, navigateUp = { router.popCurrentController() },
onFabClick = { filterSheet?.show() }, onFabClick = { filterSheet?.show() },
// SY --> // SY -->
onClickManga = { manga -> onMangaClick = { manga ->
val migrationListController = targetController as? MigrationListController ?: return@SourceSearchScreen val migrationListController = targetController as? MigrationListController ?: return@SourceSearchScreen
val sourceManager = Injekt.get<SourceManager>() val sourceManager = Injekt.get<SourceManager>()
val source = sourceManager.get(manga.source) ?: return@SourceSearchScreen val source = sourceManager.get(manga.source) ?: return@SourceSearchScreen
@ -44,10 +48,12 @@ class SourceSearchController(
router.popCurrentController() router.popCurrentController()
router.popCurrentController() router.popCurrentController()
}, },
onSettingsClick = {
router.pushController(SourcePreferencesController(presenter.source!!.id))
},
// SY <-- // 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) { LaunchedEffect(presenter.filters) {

View File

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

View File

@ -102,6 +102,17 @@ open class BrowseSourceController(bundle: Bundle) :
*/ */
protected var filterSheet: SourceFilterSheet? = null 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 @Composable
override fun ComposeContent() { override fun ComposeContent() {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@ -110,7 +121,6 @@ open class BrowseSourceController(bundle: Bundle) :
BrowseSourceScreen( BrowseSourceScreen(
presenter = presenter, presenter = presenter,
navigateUp = { router.popCurrentController() }, navigateUp = { router.popCurrentController() },
onDisplayModeChange = { presenter.displayMode = (it) },
onFabClick = { filterSheet?.show() }, onFabClick = { filterSheet?.show() },
onMangaClick = { router.pushController(MangaController(it.id, true)) }, onMangaClick = { router.pushController(MangaController(it.id, true)) },
onMangaLongClick = { manga -> 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>) { fun setSavedSearches(savedSearches: List<EXHSavedSearch>) {
filterSheet?.setSavedSearches(savedSearches) filterSheet?.setSavedSearches(savedSearches)
} }

View File

@ -14,7 +14,6 @@ import androidx.compose.ui.unit.dp
import androidx.paging.Pager import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import androidx.paging.PagingSource
import androidx.paging.cachedIn import androidx.paging.cachedIn
import androidx.paging.map import androidx.paging.map
import eu.davidea.flexibleadapter.items.IFlexible 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.manga.model.toMangaUpdate
import eu.kanade.domain.source.interactor.DeleteSavedSearchById import eu.kanade.domain.source.interactor.DeleteSavedSearchById
import eu.kanade.domain.source.interactor.GetExhSavedSearch 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.interactor.InsertSavedSearch
import eu.kanade.domain.source.model.SourcePagingSourceType
import eu.kanade.domain.track.interactor.InsertTrack import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDomainTrack import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.presentation.browse.BrowseSourceState 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.metadata.metadata.base.RaisedSearchMetadata
import exh.savedsearches.models.SavedSearch import exh.savedsearches.models.SavedSearch
import exh.source.getMainSource import exh.source.getMainSource
import exh.source.isEhBasedSource
import exh.util.nullIfBlank import exh.util.nullIfBlank
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -112,6 +112,7 @@ open class BrowseSourcePresenter(
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
private val preferences: PreferencesHelper = Injekt.get(), private val preferences: PreferencesHelper = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(), private val coverCache: CoverCache = Injekt.get(),
private val getRemoteManga: GetRemoteManga = Injekt.get(),
private val getManga: GetManga = Injekt.get(), private val getManga: GetManga = Injekt.get(),
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(), private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(), private val getCategories: GetCategories = Injekt.get(),
@ -130,6 +131,8 @@ open class BrowseSourcePresenter(
// SY <-- // SY <--
) : BasePresenter<BrowseSourceController>(), BrowseSourceState by state { ) : BasePresenter<BrowseSourceController>(), BrowseSourceState by state {
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } }
var displayMode by preferences.sourceDisplayMode().asState() var displayMode by preferences.sourceDisplayMode().asState()
val ehentaiBrowseDisplayMode by preferences.enhancedEHentaiView().asState() val ehentaiBrowseDisplayMode by preferences.enhancedEHentaiView().asState()
@ -148,11 +151,11 @@ open class BrowseSourcePresenter(
@Composable @Composable
fun getMangaList(): Flow<PagingData</* SY --> */Pair<DomainManga, RaisedSearchMetadata?>/* SY <-- */>> { fun getMangaList(): Flow<PagingData</* SY --> */Pair<DomainManga, RaisedSearchMetadata?>/* SY <-- */>> {
return remember(currentQuery, appliedFilters) { return remember(currentQuery, currentFilters) {
Pager( Pager(
PagingConfig(pageSize = 25), PagingConfig(pageSize = 25),
) { ) {
createPager(currentQuery, appliedFilters) createSourcePagingSource(currentQuery, currentFilters)
}.flow }.flow
.map { .map {
it.map { it.map {
@ -169,12 +172,12 @@ open class BrowseSourcePresenter(
@Composable @Composable
fun getManga(initialManga: DomainManga): State<DomainManga> { fun getManga(initialManga: DomainManga): State<DomainManga> {
return produceState(initialValue = initialManga, initialManga.url, initialManga.source) { return produceState(initialValue = initialManga) {
getManga.subscribe(initialManga.url, initialManga.source) getManga.subscribe(initialManga.url, initialManga.source)
.collectLatest { manga -> .collectLatest { manga ->
if (manga == null) return@collectLatest if (manga == null) return@collectLatest
launchIO { withIOContext {
initializeMangas(manga) initializeManga(manga)
} }
value = manga value = manga
} }
@ -198,17 +201,15 @@ open class BrowseSourcePresenter(
} }
fun resetFilter() { fun resetFilter() {
state.appliedFilters = FilterList()
val newFilters = source!!.getFilterList() val newFilters = source!!.getFilterList()
state.filters = newFilters state.filters = newFilters
state.currentFilters = state.filters
} }
fun search() { fun search(query: String? = null) {
state.currentQuery = searchQuery ?: "" state.currentQuery = query ?: searchQuery ?: ""
} }
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } }
// SY --> // SY -->
private val filterSerializer = FilterSerializer() private val filterSerializer = FilterSerializer()
// SY <-- // SY <--
@ -246,15 +247,6 @@ open class BrowseSourcePresenter(
} }
.launchIn(presenterScope) .launchIn(presenterScope)
// SY <-- // 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. * 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 if (manga.thumbnailUrl != null && manga.initialized) return
withContext(NonCancellable) { withContext(NonCancellable) {
val db = manga.toDbManga() val db = manga.toDbManga()
@ -394,18 +386,14 @@ open class BrowseSourcePresenter(
* @param filters a list of active filters. * @param filters a list of active filters.
*/ */
fun setSourceFilter(filters: FilterList) { fun setSourceFilter(filters: FilterList) {
state.appliedFilters = filters state.currentFilters = filters
} }
open fun createPager(query: String, filters: FilterList): PagingSource<Long, Pair<SManga, RaisedSearchMetadata?>> { // SY -->
// SY --> open fun createSourcePagingSource(query: String, filters: FilterList): SourcePagingSourceType {
return if (source!!.isEhBasedSource()) { return getRemoteManga.subscribe(sourceId, currentQuery, currentFilters)
EHentaiBrowsePagingSource(source!!, query, filters)
} else {
SourceBrowsePagingSource(source!!, query, filters)
}
// SY <--
} }
// SY <--
/** /**
* Get user categories. * Get user categories.
@ -423,12 +411,6 @@ open class BrowseSourcePresenter(
return getDuplicateLibraryManga.await(manga.title, manga.source) 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) { fun moveMangaToCategories(manga: DomainManga, vararg categories: DomainCategory) {
moveMangaToCategories(manga, categories.filter { it.id != 0L }.map { it.id }) 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.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.source.interactor.GetRemoteManga
import eu.kanade.presentation.browse.SourceFeedScreen import eu.kanade.presentation.browse.SourceFeedScreen
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource 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.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController 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.browse.SourceFilterSheet
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.lang.withUIContext
@ -223,11 +223,11 @@ open class SourceFeedController :
} }
private fun onLatestClick() { private fun onLatestClick() {
router.replaceTopController(LatestUpdatesController(presenter.source).withFadeTransaction()) router.replaceTopController(BrowseSourceController(presenter.source, GetRemoteManga.QUERY_LATEST).withFadeTransaction())
} }
private fun onBrowseClick() { private fun onBrowseClick() {
router.replaceTopController(BrowseSourceController(presenter.source).withFadeTransaction()) router.replaceTopController(BrowseSourceController(presenter.source, GetRemoteManga.QUERY_POPULAR).withFadeTransaction())
} }
private fun onSavedSearchClick(savedSearch: SavedSearch) { 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.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedController 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.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.category.CategoryController import eu.kanade.tachiyomi.ui.category.CategoryController
import eu.kanade.tachiyomi.ui.library.LibraryController import eu.kanade.tachiyomi.ui.library.LibraryController
import eu.kanade.tachiyomi.ui.main.MainActivity 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 val controller = router.getControllerWithTag(R.id.nav_library.toString()) as LibraryController
controller.search(query) controller.search(query)
} }
is LatestUpdatesController -> {
// Search doesn't currently work in source Latest view
return
}
is BrowseSourceController -> { is BrowseSourceController -> {
router.handleBack() router.handleBack()
previousController.searchWithQuery(query) 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.base.controller.pushController
import eu.kanade.tachiyomi.ui.setting.database.ClearDatabaseController import eu.kanade.tachiyomi.ui.setting.database.ClearDatabaseController
import eu.kanade.tachiyomi.util.CrashLogUtil 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.launchNonCancellableIO
import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.preference.bindTo import eu.kanade.tachiyomi.util.preference.bindTo

View File

@ -1,15 +1,15 @@
package exh.md.follows package exh.md.follows
import eu.kanade.data.source.SourcePagingSource
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.online.all.MangaDex import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowsePagingSource
/** /**
* LatestUpdatesPager inherited from the general Pager. * 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 { 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.State
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.paging.PagingSource
import eu.kanade.domain.manga.model.Manga 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.FilterList
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.all.MangaDex import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
@ -18,7 +17,7 @@ import exh.source.getMainSource
*/ */
class MangaDexFollowsPresenter(sourceId: Long) : BrowseSourcePresenter(sourceId) { 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) return MangaDexFollowsPagingSource(source!!.getMainSource() as MangaDex)
} }

View File

@ -1,23 +1,23 @@
package exh.md.similar 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.domain.manga.model.Manga
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.MetadataMangasPage import eu.kanade.tachiyomi.source.model.MetadataMangasPage
import eu.kanade.tachiyomi.source.online.all.MangaDex 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.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
/** /**
* MangaDexSimilarPagingSource inherited from the general Pager. * 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 { override suspend fun requestNextPage(currentPage: Int): MangasPage {
val mangasPage = coroutineScope { val mangasPage = coroutineScope {
val similarPageDef = async { source.getMangaSimilar(manga.toSManga()) } val similarPageDef = async { mangadex.getMangaSimilar(manga.toSManga()) }
val relatedPageDef = async { source.getMangaRelated(manga.toSManga()) } val relatedPageDef = async { mangadex.getMangaRelated(manga.toSManga()) }
val similarPage = similarPageDef.await() val similarPage = similarPageDef.await()
val relatedPage = relatedPageDef.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.State
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.paging.PagingSource
import eu.kanade.domain.manga.interactor.GetManga import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.model.Manga 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.FilterList
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.all.MangaDex import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
@ -34,7 +33,7 @@ class MangaDexSimilarPresenter(
this.manga = runBlocking { getManga.await(mangaId) } 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) return MangaDexSimilarPagingSource(manga!!, source!!.getMainSource() as MangaDex)
} }

View File

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

View File

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

View File

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