diff --git a/app/src/main/java/eu/kanade/data/source/NoResultsException.kt b/app/src/main/java/eu/kanade/data/source/NoResultsException.kt new file mode 100644 index 000000000..e0eee5fc6 --- /dev/null +++ b/app/src/main/java/eu/kanade/data/source/NoResultsException.kt @@ -0,0 +1,3 @@ +package eu.kanade.data.source + +class NoResultsException : Exception() diff --git a/app/src/main/java/eu/kanade/data/source/SourcePagingSource.kt b/app/src/main/java/eu/kanade/data/source/SourcePagingSource.kt new file mode 100644 index 000000000..87c6e1c51 --- /dev/null +++ b/app/src/main/java/eu/kanade/data/source/SourcePagingSource.kt @@ -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): LoadResult { + 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? { + 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() + } +} diff --git a/app/src/main/java/eu/kanade/data/source/SourceRepositoryImpl.kt b/app/src/main/java/eu/kanade/data/source/SourceRepositoryImpl.kt index fa65314eb..45846d362 100644 --- a/app/src/main/java/eu/kanade/data/source/SourceRepositoryImpl.kt +++ b/app/src/main/java/eu/kanade/data/source/SourceRepositoryImpl.kt @@ -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) + } } diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index 3fc504d98..eccee1c59 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -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 { SourceDataRepositoryImpl(get()) } addFactory { GetEnabledSources(get(), get()) } addFactory { GetLanguagesWithSources(get(), get()) } + addFactory { GetRemoteManga(get()) } addFactory { GetSourcesWithFavoriteCount(get(), get()) } addFactory { GetSourcesWithNonLibraryManga(get()) } addFactory { SetMigrateSorting(get()) } diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/GetRemoteManga.kt b/app/src/main/java/eu/kanade/domain/source/interactor/GetRemoteManga.kt new file mode 100644 index 000000000..0fefeeee6 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/GetRemoteManga.kt @@ -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" + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/model/SourcePagingSourceType.kt b/app/src/main/java/eu/kanade/domain/source/model/SourcePagingSourceType.kt new file mode 100644 index 000000000..456f65f7c --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/model/SourcePagingSourceType.kt @@ -0,0 +1,6 @@ +package eu.kanade.domain.source.model + +import androidx.paging.PagingSource +import eu.kanade.tachiyomi.source.model.SManga + +typealias SourcePagingSourceType = PagingSource diff --git a/app/src/main/java/eu/kanade/domain/source/repository/SourceRepository.kt b/app/src/main/java/eu/kanade/domain/source/repository/SourceRepository.kt index daece063d..c49a04e7d 100644 --- a/app/src/main/java/eu/kanade/domain/source/repository/SourceRepository.kt +++ b/app/src/main/java/eu/kanade/domain/source/repository/SourceRepository.kt @@ -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>> fun getSourcesWithNonLibraryManga(): Flow> + + fun search(sourceId: Long, query: String, filterList: FilterList): SourcePagingSourceType + + fun getPopular(sourceId: Long): SourcePagingSourceType + + fun getLatest(sourceId: Long): SourcePagingSourceType } diff --git a/app/src/main/java/eu/kanade/presentation/browse/BrowseLatestScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/BrowseLatestScreen.kt deleted file mode 100644 index 30a259a69..000000000 --- a/app/src/main/java/eu/kanade/presentation/browse/BrowseLatestScreen.kt +++ /dev/null @@ -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, - ) - } -} diff --git a/app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt index e287c6fca..98fc48a6c 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt @@ -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, getMangaState: @Composable ((Manga) -> State), + 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, ) } } diff --git a/app/src/main/java/eu/kanade/presentation/browse/BrowseSourceState.kt b/app/src/main/java/eu/kanade/presentation/browse/BrowseSourceState.kt index 45a9fa4ce..2e71236bf 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/BrowseSourceState.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/BrowseSourceState.kt @@ -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> - 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> by derivedStateOf { filters.toItems() } - override var appliedFilters by mutableStateOf(FilterList()) + override var currentFilters by mutableStateOf(FilterList()) override var dialog: BrowseSourcePresenter.Dialog? by mutableStateOf(null) } diff --git a/app/src/main/java/eu/kanade/presentation/browse/SourceSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/SourceSearchScreen.kt index cd14f6923..756925151 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/SourceSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/SourceSearchScreen.kt @@ -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, - 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) + 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, + 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, + ) + } } diff --git a/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt index e124f53e7..0720594c9 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt @@ -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( diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseLatestToolbar.kt b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseLatestToolbar.kt deleted file mode 100644 index f1100470e..000000000 --- a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseLatestToolbar.kt +++ /dev/null @@ -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, - ) -} diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceComfortableGrid.kt b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceComfortableGrid.kt index 75bb50c7c..24b366fc3 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceComfortableGrid.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceComfortableGrid.kt @@ -30,6 +30,7 @@ import eu.kanade.tachiyomi.R fun BrowseSourceComfortableGrid( mangaList: LazyPagingItems, getMangaState: @Composable ((Manga) -> State), + 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), ) { - item(span = { GridItemSpan(maxLineSpan) }) { - if (mangaList.loadState.prepend is LoadState.Loading) { + 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) { + if (mangaList.loadState.refresh is LoadState.Loading || mangaList.loadState.append is LoadState.Loading) { + item(span = { GridItemSpan(maxLineSpan) }) { BrowseSourceLoadingItem() } } diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceCompactGrid.kt b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceCompactGrid.kt index b9cb1e271..08fb8fa06 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceCompactGrid.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceCompactGrid.kt @@ -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() diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceList.kt b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceList.kt index 7195188d6..6c0ed83b2 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceList.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceList.kt @@ -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() diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceLoadingItem.kt b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceLoadingItem.kt index d27fadf4d..2ecf8539b 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceLoadingItem.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceLoadingItem.kt @@ -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() } } diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceToolbar.kt b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceToolbar.kt index 6cf7b9366..3bfff6539 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceToolbar.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceToolbar.kt @@ -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, diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt index 42b6bc74b..77f339b9d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt @@ -3,4 +3,12 @@ package eu.kanade.tachiyomi.source.model data class FilterList(val list: List>) : List> 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() + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchController.kt index e32c16980..54c824eff 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchController.kt @@ -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) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt index bf2503fad..7f455f5d2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt @@ -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) }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowsePagingSource.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowsePagingSource.kt deleted file mode 100644 index 5fbc501bd..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowsePagingSource.kt +++ /dev/null @@ -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() { - - abstract suspend fun requestNextPage(currentPage: Int): MangasPage - - override suspend fun load(params: LoadParams): LoadResult { - 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? { - return state.anchorPosition?.let { anchorPosition -> - val anchorPage = state.closestPageToPosition(anchorPosition) - anchorPage?.prevKey ?: anchorPage?.nextKey - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt index 3ce32e71f..41be40ee2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt index ae43419d9..db3c2b98d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt @@ -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(), BrowseSourceState by state { + private val loggedServices by lazy { Injekt.get().services.filter { it.isLogged } } + var displayMode by preferences.sourceDisplayMode().asState() @Composable @@ -115,11 +117,11 @@ open class BrowseSourcePresenter( @Composable fun getMangaList(): Flow> { - 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 { - 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().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 { - 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 }) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/NoResultsException.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/NoResultsException.kt deleted file mode 100644 index cf52294e7..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/NoResultsException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.source.browse - -class NoResultsException : Exception() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceBrowsePagingSource.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceBrowsePagingSource.kt deleted file mode 100644 index 5bb308724..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceBrowsePagingSource.kt +++ /dev/null @@ -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() - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesBrowsePagingSource.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesBrowsePagingSource.kt deleted file mode 100644 index 166a4a184..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesBrowsePagingSource.kt +++ /dev/null @@ -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() - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesController.kt deleted file mode 100644 index ac89ac8ec..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesController.kt +++ /dev/null @@ -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 - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesPresenter.kt deleted file mode 100644 index b5626a3aa..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesPresenter.kt +++ /dev/null @@ -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 { - return LatestUpdatesBrowsePagingSource(source!!) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt index f025be34b..b3e0b0d34 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt @@ -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 { 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) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bdc655608..8d29865c6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -582,6 +582,7 @@ Global search… Search for \"%1$s\" globally Latest + Popular Browse Local source guide You have no pinned sources