Merge Latest and Browse into one screen (#7921)

* Merge Latest and Browse into one

* Add back Latest button

* Change context to IO instead of launching a job

* Use loading screen when loading initial page
This commit is contained in:
Andreas 2022-09-03 16:16:30 +02:00 committed by GitHub
parent 5a320d87e8
commit cc6aef693e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 389 additions and 475 deletions

View File

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

View File

@ -0,0 +1,62 @@
package eu.kanade.data.source
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.SManga
import eu.kanade.tachiyomi.util.lang.awaitSingle
import eu.kanade.tachiyomi.util.lang.withIOContext
abstract class SourcePagingSource(
protected val source: CatalogueSource,
) : SourcePagingSourceType() {
abstract suspend fun requestNextPage(currentPage: Int): MangasPage
override suspend fun load(params: LoadParams<Long>): LoadResult<Long, SManga> {
val page = params.key ?: 1
val mangasPage = try {
withIOContext {
requestNextPage(page.toInt())
.takeIf { it.mangas.isNotEmpty() }
?: throw NoResultsException()
}
} catch (e: Exception) {
return LoadResult.Error(e)
}
return LoadResult.Page(
data = mangasPage.mangas,
prevKey = null,
nextKey = if (mangasPage.hasNextPage) page + 1 else null,
)
}
override fun getRefreshKey(state: PagingState<Long, SManga>): Long? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey ?: anchorPage?.nextKey
}
}
}
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,10 +2,13 @@ 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 kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@ -49,4 +52,23 @@ class SourceRepositoryImpl(
}
}
}
override fun search(
sourceId: Long,
query: String,
filterList: FilterList,
): SourcePagingSourceType {
val source = sourceManager.get(sourceId) as CatalogueSource
return SourceSearchPagingSource(source, query, filterList)
}
override fun getPopular(sourceId: Long): SourcePagingSourceType {
val source = sourceManager.get(sourceId) as CatalogueSource
return SourcePopularPagingSource(source)
}
override fun getLatest(sourceId: Long): SourcePagingSourceType {
val source = sourceManager.get(sourceId) as CatalogueSource
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,6 @@
package eu.kanade.domain.source.model
import androidx.paging.PagingSource
import eu.kanade.tachiyomi.source.model.SManga
typealias SourcePagingSourceType = PagingSource<Long, SManga>

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,60 +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
@Composable
fun BrowseLatestScreen(
presenter: BrowseSourcePresenter,
navigateUp: () -> Unit,
onMangaClick: (Manga) -> Unit,
onMangaLongClick: (Manga) -> Unit,
onWebViewClick: () -> Unit,
) {
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,
onDisplayModeChange = { presenter.displayMode = it },
onHelpClick = onHelpClick,
onWebViewClick = onWebViewClick,
scrollBehavior = scrollBehavior,
)
},
) { paddingValues ->
BrowseSourceContent(
source = presenter.source,
mangaList = presenter.getMangaList().collectAsLazyPagingItems(),
getMangaState = { presenter.getManga(it) },
columns = columns,
displayMode = presenter.displayMode,
snackbarHostState = remember { SnackbarHostState() },
contentPadding = paddingValues,
onWebViewClick = onWebViewClick,
onHelpClick = { uriHandler.openUri(MoreController.URL_HELP) },
onLocalSourceHelpClick = onHelpClick,
onMangaClick = onMangaClick,
onMangaLongClick = onMangaLongClick,
)
}
}

View File

@ -1,10 +1,18 @@
package eu.kanade.presentation.browse
import androidx.compose.animation.AnimatedVisibility
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.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,22 +28,24 @@ 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.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
@ -44,7 +54,6 @@ import eu.kanade.tachiyomi.widget.EmptyView
fun BrowseSourceScreen(
presenter: BrowseSourcePresenter,
navigateUp: () -> Unit,
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
onFabClick: () -> Unit,
onMangaClick: (Manga) -> Unit,
onMangaLongClick: (Manga) -> Unit,
@ -68,7 +77,7 @@ fun BrowseSourceScreen(
state = presenter,
source = presenter.source!!,
displayMode = presenter.displayMode,
onDisplayModeChange = onDisplayModeChange,
onDisplayModeChange = { presenter.displayMode = it },
navigateUp = navigateUp,
onWebViewClick = onWebViewClick,
onHelpClick = onHelpClick,
@ -77,21 +86,17 @@ fun BrowseSourceScreen(
)
},
floatingActionButton = {
if (presenter.filters.isNotEmpty()) {
ExtendedFloatingActionButton(
modifier = Modifier.navigationBarsPadding(),
text = { Text(text = stringResource(id = R.string.action_filter)) },
icon = { Icon(Icons.Outlined.FilterList, contentDescription = "") },
onClick = onFabClick,
BrowseSourceFloatingActionButton(
isVisible = presenter.filters.isNotEmpty(),
onFabClick = onFabClick,
)
}
},
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
) { paddingValues ->
BrowseSourceContent(
source = presenter.source,
state = presenter,
mangaList = mangaList,
getMangaState = { presenter.getManga(it) },
columns = columns,
@ -103,15 +108,93 @@ fun BrowseSourceScreen(
onLocalSourceHelpClick = onHelpClick,
onMangaClick = onMangaClick,
onMangaLongClick = onMangaLongClick,
header = {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
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))
},
)
}
if (presenter.filters.isNotEmpty()) {
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 = {
Text(text = stringResource(id = R.string.action_filter))
},
)
}
}
},
)
}
}
@Composable
fun BrowseSourceFloatingActionButton(
modifier: Modifier = Modifier.navigationBarsPadding(),
isVisible: Boolean,
onFabClick: () -> Unit,
) {
AnimatedVisibility(visible = isVisible) {
ExtendedFloatingActionButton(
modifier = modifier,
text = { Text(text = stringResource(id = R.string.action_filter)) },
icon = { Icon(Icons.Outlined.FilterList, contentDescription = "") },
onClick = onFabClick,
)
}
}
@Composable
fun BrowseSourceContent(
source: CatalogueSource?,
state: BrowseSourceState,
mangaList: LazyPagingItems<Manga>,
getMangaState: @Composable ((Manga) -> State<Manga>),
header: (@Composable () -> Unit)? = null,
columns: GridCells,
displayMode: LibraryDisplayMode,
snackbarHostState: SnackbarHostState,
@ -153,7 +236,7 @@ fun BrowseSourceContent(
if (mangaList.itemCount <= 0 && errorState != null && errorState is LoadState.Error) {
EmptyScreen(
message = getErrorMessage(errorState),
actions = if (source is LocalSource) {
actions = if (state.source is LocalSource) {
listOf(
EmptyView.Action(R.string.local_source_help_guide, R.drawable.ic_help_24dp) { onLocalSourceHelpClick() },
)
@ -169,6 +252,11 @@ fun BrowseSourceContent(
return
}
if (mangaList.itemCount == 0 && mangaList.loadState.refresh is LoadState.Loading) {
LoadingScreen()
return
}
when (displayMode) {
LibraryDisplayMode.ComfortableGrid -> {
BrowseSourceComfortableGrid(
@ -178,6 +266,7 @@ fun BrowseSourceContent(
contentPadding = contentPadding,
onMangaClick = onMangaClick,
onMangaLongClick = onMangaLongClick,
header = header,
)
}
LibraryDisplayMode.List -> {
@ -187,6 +276,7 @@ fun BrowseSourceContent(
contentPadding = contentPadding,
onMangaClick = onMangaClick,
onMangaLongClick = onMangaLongClick,
header = header,
)
}
else -> {
@ -197,6 +287,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,32 +1,73 @@
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,
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) },
columns = columns,
displayMode = presenter.displayMode,
snackbarHostState = snackbarHostState,
contentPadding = paddingValues,
onWebViewClick = onWebViewClick,
onHelpClick = { uriHandler.openUri(MoreController.URL_HELP) },
onLocalSourceHelpClick = onHelpClick,
onMangaClick = onMangaClick,
onMangaLongClick = onMangaClick,
)
}
}

View File

@ -24,6 +24,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
@ -45,9 +46,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,
) {
val context = LocalContext.current
@ -59,7 +59,6 @@ fun SourcesScreen(
state = presenter,
onClickItem = onClickItem,
onClickDisable = onClickDisable,
onClickLatest = onClickLatest,
onClickPin = onClickPin,
)
}
@ -78,9 +77,8 @@ fun SourcesScreen(
@Composable
fun SourceList(
state: SourcesState,
onClickItem: (Source) -> Unit,
onClickItem: (Source, String) -> Unit,
onClickDisable: (Source) -> Unit,
onClickLatest: (Source) -> Unit,
onClickPin: (Source) -> Unit,
) {
ScrollbarLazyColumn(
@ -113,7 +111,6 @@ fun SourceList(
source = model.source,
onClickItem = onClickItem,
onLongClickItem = { state.dialog = SourcesPresenter.Dialog(it) },
onClickLatest = onClickLatest,
onClickPin = onClickPin,
)
}
@ -155,19 +152,18 @@ fun SourceHeader(
fun SourceItem(
modifier: Modifier = Modifier,
source: Source,
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) {
TextButton(onClick = { onClickLatest(source) }) {
TextButton(onClick = { onClickItem(source, GetRemoteManga.QUERY_LATEST) }) {
Text(
text = stringResource(R.string.latest),
style = LocalTextStyle.current.copy(

View File

@ -1,108 +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.ViewModule
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.LocalSource
import eu.kanade.tachiyomi.ui.library.setting.LibraryDisplayMode
@Composable
fun BrowseLatestToolbar(
navigateUp: () -> Unit,
source: CatalogueSource,
displayMode: LibraryDisplayMode,
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
onHelpClick: () -> Unit,
onWebViewClick: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior,
) {
AppBar(
navigateUp = navigateUp,
title = source.name,
actions = {
var selectingDisplayMode by remember { mutableStateOf(false) }
AppBarActions(
actions = listOf(
AppBar.Action(
title = stringResource(id = R.string.action_display_mode),
icon = Icons.Filled.ViewModule,
onClick = { selectingDisplayMode = true },
),
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,
)
},
),
)
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

@ -30,6 +30,7 @@ import eu.kanade.tachiyomi.R
fun BrowseSourceComfortableGrid(
mangaList: LazyPagingItems<Manga>,
getMangaState: @Composable ((Manga) -> State<Manga>),
header: (@Composable () -> Unit)? = null,
columns: GridCells,
contentPadding: PaddingValues,
onMangaClick: (Manga) -> Unit,
@ -37,12 +38,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()
}
}
@ -57,8 +64,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

@ -41,13 +41,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

@ -30,10 +30,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

@ -43,11 +43,12 @@ fun BrowseSourceToolbar(
) {
if (state.searchQuery == null) {
BrowseSourceRegularToolbar(
source = source,
title = if (state.isUserQuery) state.currentQuery else source.name,
isLocalSource = source is LocalSource,
displayMode = displayMode,
onDisplayModeChange = onDisplayModeChange,
navigateUp = navigateUp,
onSearchClick = { state.searchQuery = "" },
onSearchClick = { state.searchQuery = if (state.isUserQuery) state.currentQuery else "" },
onWebViewClick = onWebViewClick,
onHelpClick = onHelpClick,
scrollBehavior = scrollBehavior,
@ -56,10 +57,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,
@ -69,7 +67,8 @@ fun BrowseSourceToolbar(
@Composable
fun BrowseSourceRegularToolbar(
source: CatalogueSource,
title: String,
isLocalSource: Boolean,
displayMode: LibraryDisplayMode,
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
navigateUp: () -> Unit,
@ -80,7 +79,7 @@ fun BrowseSourceRegularToolbar(
) {
AppBar(
navigateUp = navigateUp,
title = source.name,
title = title,
actions = {
var selectingDisplayMode by remember { mutableStateOf(false) }
AppBarActions(
@ -95,7 +94,7 @@ fun BrowseSourceRegularToolbar(
icon = Icons.Filled.ViewModule,
onClick = { selectingDisplayMode = true },
),
if (source is LocalSource) {
if (isLocalSource) {
AppBar.Action(
title = stringResource(id = R.string.label_help),
icon = Icons.Outlined.Help,

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

@ -7,7 +7,9 @@ import androidx.core.os.bundleOf
import eu.kanade.domain.manga.model.Manga
import eu.kanade.presentation.browse.SourceSearchScreen
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.system.getSerializableCompat
class SourceSearchController(
@ -27,17 +29,26 @@ class SourceSearchController(
@Composable
override fun ComposeContent() {
// LocalContext is not a first available to us when we try access it
// Decoupling from BrowseSourceController is needed
val context = applicationContext!!
SourceSearchScreen(
presenter = presenter,
navigateUp = { router.popCurrentController() },
onFabClick = { filterSheet?.show() },
onClickManga = {
onMangaClick = {
newManga = it
val searchController = router.backstack.findLast { it.controller.javaClass == SearchController::class.java }?.controller as SearchController?
val dialog = SearchController.MigrationDialog(oldManga, newManga, this)
dialog.targetController = searchController
dialog.showDialog(router)
},
onWebViewClick = f@{
val source = presenter.source as? HttpSource ?: return@f
val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name)
context.startActivity(intent)
},
)
LaunchedEffect(presenter.filters) {

View File

@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
@Composable
fun sourcesTab(
@ -36,17 +35,13 @@ fun sourcesTab(
content = {
SourcesScreen(
presenter = presenter,
onClickItem = { source ->
onClickItem = { source, query ->
presenter.onOpenSource(source)
router?.pushController(BrowseSourceController(source))
router?.pushController(BrowseSourceController(source, query))
},
onClickDisable = { source ->
presenter.toggleSource(source)
},
onClickLatest = { source ->
presenter.onOpenSource(source)
router?.pushController(LatestUpdatesController(source))
},
onClickPin = { source ->
presenter.togglePin(source)
},

View File

@ -1,37 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.browse
import androidx.paging.PagingSource
import androidx.paging.PagingState
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.lang.withIOContext
abstract class BrowsePagingSource : PagingSource<Long, SManga>() {
abstract suspend fun requestNextPage(currentPage: Int): MangasPage
override suspend fun load(params: LoadParams<Long>): LoadResult<Long, SManga> {
val page = params.key ?: 1
val mangasPage = try {
withIOContext {
requestNextPage(page.toInt())
}
} catch (e: Exception) {
return LoadResult.Error(e)
}
return LoadResult.Page(
data = mangasPage.mangas,
prevKey = null,
nextKey = if (mangasPage.hasNextPage) page + 1 else null,
)
}
override fun getRefreshKey(state: PagingState<Long, SManga>): Long? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey ?: anchorPage?.nextKey
}
}
}

View File

@ -41,6 +41,10 @@ open class BrowseSourceController(bundle: Bundle) :
*/
protected var filterSheet: SourceFilterSheet? = null
override fun createPresenter(): BrowseSourcePresenter {
return BrowseSourcePresenter(args.getLong(SOURCE_ID_KEY), args.getString(SEARCH_QUERY_KEY))
}
@Composable
override fun ComposeContent() {
val scope = rememberCoroutineScope()
@ -49,7 +53,6 @@ open class BrowseSourceController(bundle: Bundle) :
BrowseSourceScreen(
presenter = presenter,
navigateUp = { router.popCurrentController() },
onDisplayModeChange = { presenter.displayMode = (it) },
onFabClick = { filterSheet?.show() },
onMangaClick = { router.pushController(MangaController(it.id, true)) },
onMangaLongClick = { manga ->
@ -108,10 +111,6 @@ open class BrowseSourceController(bundle: Bundle) :
}
}
override fun createPresenter(): BrowseSourcePresenter {
return BrowseSourcePresenter(args.getLong(SOURCE_ID_KEY), args.getString(SEARCH_QUERY_KEY))
}
open fun initFilterSheet() {
if (presenter.filters.isEmpty()) {
return

View File

@ -14,7 +14,6 @@ import androidx.compose.ui.unit.dp
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import androidx.paging.cachedIn
import androidx.paging.map
import eu.davidea.flexibleadapter.items.IFlexible
@ -30,6 +29,7 @@ import eu.kanade.domain.manga.interactor.InsertManga
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.domain.source.interactor.GetRemoteManga
import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.presentation.browse.BrowseSourceState
@ -71,7 +71,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import logcat.LogPriority
@ -88,6 +87,7 @@ open class BrowseSourcePresenter(
private val sourceManager: SourceManager = Injekt.get(),
private val preferences: PreferencesHelper = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(),
private val getRemoteManga: GetRemoteManga = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
@ -99,6 +99,8 @@ open class BrowseSourcePresenter(
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(),
) : BasePresenter<BrowseSourceController>(), BrowseSourceState by state {
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } }
var displayMode by preferences.sourceDisplayMode().asState()
@Composable
@ -115,11 +117,11 @@ open class BrowseSourcePresenter(
@Composable
fun getMangaList(): Flow<PagingData<DomainManga>> {
return remember(currentQuery, appliedFilters) {
return remember(currentQuery, currentFilters) {
Pager(
PagingConfig(pageSize = 25),
) {
createPager(currentQuery, appliedFilters)
getRemoteManga.subscribe(sourceId, currentQuery, currentFilters)
}.flow
.map {
it.map {
@ -134,12 +136,12 @@ open class BrowseSourcePresenter(
@Composable
fun getManga(initialManga: DomainManga): State<DomainManga> {
return produceState(initialValue = initialManga, initialManga.url, initialManga.source) {
return produceState(initialValue = initialManga) {
getManga.subscribe(initialManga.url, initialManga.source)
.collectLatest { manga ->
if (manga == null) return@collectLatest
launchIO {
initializeMangas(manga)
withIOContext {
initializeManga(manga)
}
value = manga
}
@ -151,31 +153,20 @@ open class BrowseSourcePresenter(
}
fun resetFilter() {
state.appliedFilters = FilterList()
val newFilters = source!!.getFilterList()
state.filters = newFilters
state.currentFilters = state.filters
}
fun search() {
state.currentQuery = searchQuery ?: ""
fun search(query: String? = null) {
state.currentQuery = query ?: searchQuery ?: ""
}
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } }
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
state.source = sourceManager.get(sourceId) as? CatalogueSource ?: return
state.filters = source!!.getFilterList()
if (savedState != null) {
query = savedState.getString(::query.name, "")
}
}
override fun onSave(state: Bundle) {
state.putString(::query.name, query)
super.onSave(state)
}
/**
@ -205,9 +196,9 @@ open class BrowseSourcePresenter(
/**
* Initialize a manga.
*
* @param mangas the list of manga to initialize.
* @param manga to initialize.
*/
private suspend fun initializeMangas(manga: DomainManga) {
private suspend fun initializeManga(manga: DomainManga) {
if (manga.thumbnailUrl != null && manga.initialized) return
withContext(NonCancellable) {
val db = manga.toDbManga()
@ -315,11 +306,7 @@ open class BrowseSourcePresenter(
* @param filters a list of active filters.
*/
fun setSourceFilter(filters: FilterList) {
state.appliedFilters = filters
}
open fun createPager(query: String, filters: FilterList): PagingSource<Long, SManga> {
return SourceBrowsePagingSource(source!!, query, filters)
state.currentFilters = filters
}
/**
@ -338,12 +325,6 @@ open class BrowseSourcePresenter(
return getDuplicateLibraryManga.await(manga.title, manga.source)
}
/**
* Move the given manga to categories.
*
* @param categories the selected categories.
* @param manga the manga to move.
*/
fun moveMangaToCategories(manga: DomainManga, vararg categories: DomainCategory) {
moveMangaToCategories(manga, categories.filter { it.id != 0L }.map { it.id })
}

View File

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

View File

@ -1,20 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.browse
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.util.lang.awaitSingle
class SourceBrowsePagingSource(val source: CatalogueSource, val query: String, val filters: FilterList) : BrowsePagingSource() {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
val observable = if (query.isBlank() && filters.isEmpty()) {
source.fetchPopularManga(currentPage)
} else {
source.fetchSearchManga(currentPage, query, filters)
}
return observable.awaitSingle()
.takeIf { it.mangas.isNotEmpty() } ?: throw NoResultsException()
}
}

View File

@ -1,13 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.latest
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowsePagingSource
import eu.kanade.tachiyomi.util.lang.awaitSingle
class LatestUpdatesBrowsePagingSource(val source: CatalogueSource) : BrowsePagingSource() {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
return source.fetchLatestUpdates(currentPage).awaitSingle()
}
}

View File

@ -1,103 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.latest
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import androidx.core.os.bundleOf
import eu.kanade.domain.source.model.Source
import eu.kanade.presentation.browse.BrowseLatestScreen
import eu.kanade.presentation.browse.components.RemoveMangaDialog
import eu.kanade.presentation.components.ChangeCategoryDialog
import eu.kanade.presentation.components.DuplicateMangaDialog
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
import eu.kanade.tachiyomi.ui.category.CategoryController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.lang.launchIO
/**
* Controller that shows the latest manga from the catalogue. Inherit [BrowseSourceController].
*/
class LatestUpdatesController(bundle: Bundle) : BrowseSourceController(bundle) {
constructor(source: Source) : this(
bundleOf(SOURCE_ID_KEY to source.id),
)
override fun createPresenter(): BrowseSourcePresenter {
return LatestUpdatesPresenter(args.getLong(SOURCE_ID_KEY))
}
@Composable
override fun ComposeContent() {
val scope = rememberCoroutineScope()
val context = LocalContext.current
BrowseLatestScreen(
presenter = presenter,
navigateUp = { router.popCurrentController() },
onMangaClick = { router.pushController(MangaController(it.id, true)) },
onMangaLongClick = { manga ->
scope.launchIO {
val duplicateManga = presenter.getDuplicateLibraryManga(manga)
when {
manga.favorite -> presenter.dialog = BrowseSourcePresenter.Dialog.RemoveManga(manga)
duplicateManga != null -> presenter.dialog = BrowseSourcePresenter.Dialog.AddDuplicateManga(manga, duplicateManga)
else -> presenter.addFavorite(manga)
}
}
},
onWebViewClick = f@{
val source = presenter.source as? HttpSource ?: return@f
val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name)
context.startActivity(intent)
},
)
val onDismissRequest = { presenter.dialog = null }
when (val dialog = presenter.dialog) {
is BrowseSourcePresenter.Dialog.AddDuplicateManga -> {
DuplicateMangaDialog(
onDismissRequest = onDismissRequest,
onOpenManga = {
router.pushController(MangaController(dialog.duplicate.id, true))
},
onConfirm = {
presenter.addFavorite(dialog.manga)
},
duplicateFrom = presenter.getSourceOrStub(dialog.manga),
)
}
is BrowseSourcePresenter.Dialog.RemoveManga -> {
RemoveMangaDialog(
onDismissRequest = onDismissRequest,
onConfirm = {
presenter.changeMangaFavorite(dialog.manga)
},
)
}
is BrowseSourcePresenter.Dialog.ChangeMangaCategory -> {
ChangeCategoryDialog(
initialSelection = dialog.initialSelection,
onDismissRequest = onDismissRequest,
onEditCategories = {
router.pushController(CategoryController())
},
onConfirm = { include, _ ->
presenter.changeMangaFavorite(dialog.manga)
presenter.moveMangaToCategories(dialog.manga, include)
},
)
}
null -> {}
}
}
override fun initFilterSheet() {
// No-op: we don't allow filtering in latest
}
}

View File

@ -1,13 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source.latest
import androidx.paging.PagingSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
class LatestUpdatesPresenter(sourceId: Long) : BrowseSourcePresenter(sourceId) {
override fun createPager(query: String, filters: FilterList): PagingSource<Long, SManga> {
return LatestUpdatesBrowsePagingSource(source!!)
}
}

View File

@ -42,7 +42,6 @@ import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.category.CategoryController
import eu.kanade.tachiyomi.ui.library.LibraryController
import eu.kanade.tachiyomi.ui.main.MainActivity
@ -313,10 +312,6 @@ class MangaController : FullComposeController<MangaPresenter> {
val controller = router.getControllerWithTag(R.id.nav_library.toString()) as LibraryController
controller.search(query)
}
is LatestUpdatesController -> {
// Search doesn't currently work in source Latest view
return
}
is BrowseSourceController -> {
router.handleBack()
previousController.searchWithQuery(query)

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>