Use Voyager for source feed
This commit is contained in:
parent
bd73eff732
commit
658c84bef8
@ -34,7 +34,6 @@ import eu.kanade.presentation.components.SearchToolbar
|
||||
import eu.kanade.presentation.util.plus
|
||||
import eu.kanade.presentation.util.topSmallPaddingValues
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedPresenter
|
||||
import exh.savedsearches.models.FeedSavedSearch
|
||||
import exh.savedsearches.models.SavedSearch
|
||||
|
||||
@ -93,41 +92,49 @@ sealed class SourceFeedUI {
|
||||
|
||||
@Composable
|
||||
fun SourceFeedScreen(
|
||||
presenter: SourceFeedPresenter,
|
||||
onFabClick: () -> Unit,
|
||||
name: String,
|
||||
isLoading: Boolean,
|
||||
items: List<SourceFeedUI>,
|
||||
onFabClick: (() -> Unit)?,
|
||||
onClickBrowse: () -> Unit,
|
||||
onClickLatest: () -> Unit,
|
||||
onClickSavedSearch: (SavedSearch) -> Unit,
|
||||
onClickDelete: (FeedSavedSearch) -> Unit,
|
||||
onClickManga: (Manga) -> Unit,
|
||||
onClickSearch: (String) -> Unit,
|
||||
searchQuery: String?,
|
||||
onSearchQueryChange: (String?) -> Unit,
|
||||
isIncognitoMode: Boolean,
|
||||
isDownloadOnly: Boolean,
|
||||
getMangaState: @Composable (Manga) -> State<Manga>,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
SourceFeedToolbar(
|
||||
title = presenter.source.name,
|
||||
state = presenter,
|
||||
title = name,
|
||||
searchQuery = searchQuery,
|
||||
onSearchQueryChange = onSearchQueryChange,
|
||||
scrollBehavior = scrollBehavior,
|
||||
incognitoMode = presenter.isIncognitoMode,
|
||||
downloadedOnlyMode = presenter.isDownloadOnly,
|
||||
incognitoMode = isIncognitoMode,
|
||||
downloadedOnlyMode = isDownloadOnly,
|
||||
onClickSearch = onClickSearch,
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
BrowseSourceFloatingActionButton(
|
||||
isVisible = presenter.filterItems.isNotEmpty(),
|
||||
onFabClick = onFabClick,
|
||||
isVisible = onFabClick != null,
|
||||
onFabClick = onFabClick ?: {},
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
Crossfade(targetState = presenter.isLoading) { state ->
|
||||
Crossfade(targetState = isLoading) { state ->
|
||||
when (state) {
|
||||
true -> LoadingScreen()
|
||||
false -> {
|
||||
SourceFeedList(
|
||||
state = presenter,
|
||||
items = items,
|
||||
paddingValues = paddingValues,
|
||||
getMangaState = { presenter.getManga(it) },
|
||||
getMangaState = getMangaState,
|
||||
onClickBrowse = onClickBrowse,
|
||||
onClickLatest = onClickLatest,
|
||||
onClickSavedSearch = onClickSavedSearch,
|
||||
@ -142,7 +149,7 @@ fun SourceFeedScreen(
|
||||
|
||||
@Composable
|
||||
fun SourceFeedList(
|
||||
state: SourceFeedState,
|
||||
items: List<SourceFeedUI>,
|
||||
paddingValues: PaddingValues,
|
||||
getMangaState: @Composable ((Manga) -> State<Manga>),
|
||||
onClickBrowse: () -> Unit,
|
||||
@ -155,7 +162,7 @@ fun SourceFeedList(
|
||||
contentPadding = paddingValues + topSmallPaddingValues,
|
||||
) {
|
||||
items(
|
||||
state.items.orEmpty(),
|
||||
items.orEmpty(),
|
||||
key = { it.id },
|
||||
) { item ->
|
||||
SourceFeedItem(
|
||||
@ -248,7 +255,8 @@ fun SourceFeedItem(
|
||||
@Composable
|
||||
fun SourceFeedToolbar(
|
||||
title: String,
|
||||
state: SourceFeedState,
|
||||
searchQuery: String?,
|
||||
onSearchQueryChange: (String?) -> Unit,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
incognitoMode: Boolean,
|
||||
downloadedOnlyMode: Boolean,
|
||||
@ -256,10 +264,10 @@ fun SourceFeedToolbar(
|
||||
) {
|
||||
SearchToolbar(
|
||||
titleContent = { AppBarTitle(title) },
|
||||
searchQuery = state.searchQuery,
|
||||
onChangeSearchQuery = { state.searchQuery = it },
|
||||
searchQuery = searchQuery,
|
||||
onChangeSearchQuery = onSearchQueryChange,
|
||||
onSearch = onClickSearch,
|
||||
onClickCloseSearch = { state.searchQuery = null },
|
||||
onClickCloseSearch = { onSearchQueryChange(null) },
|
||||
scrollBehavior = scrollBehavior,
|
||||
incognitoMode = incognitoMode,
|
||||
downloadedOnlyMode = downloadedOnlyMode,
|
||||
|
@ -1,31 +0,0 @@
|
||||
package eu.kanade.presentation.browse
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.toItems
|
||||
|
||||
@Stable
|
||||
interface SourceFeedState {
|
||||
val isLoading: Boolean
|
||||
var searchQuery: String?
|
||||
val filters: FilterList
|
||||
val filterItems: List<IFlexible<*>>
|
||||
val items: List<SourceFeedUI>?
|
||||
}
|
||||
|
||||
fun SourceFeedState(): SourceFeedState {
|
||||
return SourceFeedStateImpl()
|
||||
}
|
||||
|
||||
class SourceFeedStateImpl : SourceFeedState {
|
||||
override var isLoading: Boolean by mutableStateOf(true)
|
||||
override var searchQuery: String? by mutableStateOf(null)
|
||||
override var filters: FilterList by mutableStateOf(FilterList())
|
||||
override val filterItems: List<IFlexible<*>> by derivedStateOf { filters.toItems() }
|
||||
override var items: List<SourceFeedUI>? by mutableStateOf(null)
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package eu.kanade.presentation.browse.components
|
||||
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
||||
@Composable
|
||||
fun SourceFeedAddDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
name: String,
|
||||
addFeed: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
TextButton(onClick = addFeed) {
|
||||
Text(text = stringResource(R.string.action_add))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(android.R.string.cancel))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.feed))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.feed_add, name))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SourceFeedDeleteDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
deleteFeed: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
TextButton(onClick = deleteFeed) {
|
||||
Text(text = stringResource(R.string.action_delete))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(android.R.string.cancel))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.feed))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.feed_delete))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SourceFeedFailedToLoadSavedSearchDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.save_search_failed_to_load))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.save_search_failed_to_load_message))
|
||||
},
|
||||
)
|
||||
}
|
@ -208,7 +208,7 @@ open class BrowseSourceController(bundle: Bundle) :
|
||||
filterSheet = SourceFilterSheet(
|
||||
activity!!,
|
||||
// SY -->
|
||||
this,
|
||||
router,
|
||||
presenter.source!!,
|
||||
emptyList(),
|
||||
// SY <--
|
||||
|
@ -1,6 +1,5 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
@ -8,13 +7,13 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import com.google.android.material.chip.Chip
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.databinding.SourceFilterSheetBinding
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
||||
import eu.kanade.tachiyomi.ui.base.controller.BaseController
|
||||
import eu.kanade.tachiyomi.widget.SimpleNavigationView
|
||||
import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
|
||||
import exh.md.MangaDexFabHeaderAdapter
|
||||
@ -22,9 +21,9 @@ import exh.savedsearches.EXHSavedSearch
|
||||
import exh.source.getMainSource
|
||||
|
||||
class SourceFilterSheet(
|
||||
activity: Activity,
|
||||
context: Context,
|
||||
// SY -->
|
||||
controller: BaseController<*>,
|
||||
router: Router,
|
||||
source: CatalogueSource,
|
||||
searches: List<EXHSavedSearch> = emptyList(),
|
||||
// SY <--
|
||||
@ -35,14 +34,14 @@ class SourceFilterSheet(
|
||||
var onSavedSearchClicked: (Long) -> Unit = {},
|
||||
var onSavedSearchDeleteClicked: (Long, String) -> Unit = { _, _ -> },
|
||||
// EXH <--
|
||||
) : BaseBottomSheetDialog(activity) {
|
||||
) : BaseBottomSheetDialog(context) {
|
||||
|
||||
private var filterNavView: FilterNavigationView = FilterNavigationView(
|
||||
activity,
|
||||
context,
|
||||
// SY -->
|
||||
searches = searches,
|
||||
source = source,
|
||||
controller = controller,
|
||||
router = router,
|
||||
dismissSheet = ::dismiss,
|
||||
// SY <--
|
||||
)
|
||||
@ -85,7 +84,7 @@ class SourceFilterSheet(
|
||||
// SY -->
|
||||
searches: List<EXHSavedSearch> = emptyList(),
|
||||
source: CatalogueSource? = null,
|
||||
controller: BaseController<*>? = null,
|
||||
router: Router? = null,
|
||||
dismissSheet: (() -> Unit)? = null,
|
||||
// SY <--
|
||||
) :
|
||||
@ -117,10 +116,10 @@ class SourceFilterSheet(
|
||||
// SY -->
|
||||
recycler.adapter = ConcatAdapter(
|
||||
listOfNotNull(
|
||||
controller?.let {
|
||||
router?.let {
|
||||
source?.getMainSource<MangaDex>()
|
||||
?.let {
|
||||
MangaDexFabHeaderAdapter(controller, it) {
|
||||
MangaDexFabHeaderAdapter(router, it) {
|
||||
dismissSheet?.invoke()
|
||||
}
|
||||
}
|
||||
|
@ -1,229 +1,34 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.source.feed
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.core.os.bundleOf
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.domain.source.interactor.GetRemoteManga
|
||||
import eu.kanade.presentation.browse.SourceFeedScreen
|
||||
import eu.kanade.tachiyomi.R
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterSheet
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import exh.savedsearches.models.FeedSavedSearch
|
||||
import exh.savedsearches.models.SavedSearch
|
||||
import exh.util.nullIfBlank
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import xyz.nulldev.ts.api.http.serializer.FilterSerializer
|
||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
||||
|
||||
/**
|
||||
* This controller shows and manages the different search result in global search.
|
||||
* This controller should only handle UI actions, IO actions should be done by [SourceFeedPresenter]
|
||||
* [SourceFeedCardAdapter.OnMangaClickListener] called when manga is clicked in global search
|
||||
*/
|
||||
open class SourceFeedController :
|
||||
FullComposeController<SourceFeedPresenter> {
|
||||
class SourceFeedController : BasicFullComposeController {
|
||||
|
||||
constructor(source: CatalogueSource?) : super(
|
||||
constructor(source: CatalogueSource) : super(
|
||||
bundleOf(
|
||||
SOURCE_EXTRA to (source?.id ?: 0),
|
||||
SOURCE_EXTRA to source.id,
|
||||
),
|
||||
) {
|
||||
this.source = source
|
||||
}
|
||||
)
|
||||
|
||||
constructor(sourceId: Long) : this(
|
||||
Injekt.get<SourceManager>().get(sourceId) as? CatalogueSource,
|
||||
constructor(sourceId: Long) : super(
|
||||
bundleOf(
|
||||
SOURCE_EXTRA to sourceId,
|
||||
),
|
||||
)
|
||||
|
||||
@Suppress("unused")
|
||||
constructor(bundle: Bundle) : this(bundle.getLong(SOURCE_EXTRA))
|
||||
constructor(bundle: Bundle) : super(bundle)
|
||||
|
||||
var source: CatalogueSource? = null
|
||||
|
||||
/**
|
||||
* Sheet containing filter items.
|
||||
*/
|
||||
private var filterSheet: SourceFilterSheet? = null
|
||||
|
||||
/**
|
||||
* Create the [SourceFeedPresenter] used in controller.
|
||||
*
|
||||
* @return instance of [SourceFeedPresenter]
|
||||
*/
|
||||
override fun createPresenter(): SourceFeedPresenter {
|
||||
return SourceFeedPresenter(source = source!!)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the view is created
|
||||
*
|
||||
* @param view view of controller
|
||||
*/
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
|
||||
// Prepare filter sheet
|
||||
initFilterSheet()
|
||||
}
|
||||
|
||||
private val filterSerializer = FilterSerializer()
|
||||
|
||||
fun initFilterSheet() {
|
||||
filterSheet = SourceFilterSheet(
|
||||
activity!!,
|
||||
// SY -->
|
||||
this,
|
||||
presenter.source,
|
||||
emptyList(),
|
||||
// SY <--
|
||||
onFilterClicked = {
|
||||
val allDefault = presenter.filters == presenter.source.getFilterList()
|
||||
filterSheet?.dismiss()
|
||||
if (allDefault) {
|
||||
onBrowseClick(
|
||||
presenter.searchQuery?.nullIfBlank(),
|
||||
)
|
||||
} else {
|
||||
onBrowseClick(
|
||||
presenter.searchQuery?.nullIfBlank(),
|
||||
filters = Json.encodeToString(filterSerializer.serialize(presenter.filters)),
|
||||
)
|
||||
}
|
||||
},
|
||||
onResetClicked = {},
|
||||
onSaveClicked = {},
|
||||
onSavedSearchClicked = { idOfSearch ->
|
||||
viewScope.launchUI {
|
||||
val search = presenter.loadSearch(idOfSearch)
|
||||
|
||||
if (search == null) {
|
||||
filterSheet?.context?.let {
|
||||
MaterialAlertDialogBuilder(it)
|
||||
.setTitle(R.string.save_search_failed_to_load)
|
||||
.setMessage(R.string.save_search_failed_to_load_message)
|
||||
.show()
|
||||
}
|
||||
return@launchUI
|
||||
}
|
||||
|
||||
if (search.filterList == null && presenter.filters.isNotEmpty()) {
|
||||
activity?.toast(R.string.save_search_invalid)
|
||||
return@launchUI
|
||||
}
|
||||
|
||||
if (search.filterList != null) {
|
||||
presenter.setFilters(FilterList(search.filterList))
|
||||
filterSheet?.setFilters(presenter.filterItems)
|
||||
}
|
||||
val allDefault = search.filterList != null && presenter.filters == presenter.source.getFilterList()
|
||||
filterSheet?.dismiss()
|
||||
|
||||
if (!allDefault) {
|
||||
onBrowseClick(
|
||||
search = presenter.searchQuery?.nullIfBlank(),
|
||||
savedSearch = search.id,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onSavedSearchDeleteClicked = { idOfSearch, name ->
|
||||
viewScope.launchUI {
|
||||
if (presenter.hasTooManyFeeds()) {
|
||||
activity?.toast(R.string.too_many_in_feed)
|
||||
return@launchUI
|
||||
}
|
||||
withUIContext {
|
||||
MaterialAlertDialogBuilder(activity!!)
|
||||
.setTitle(R.string.feed)
|
||||
.setMessage(activity!!.getString(R.string.feed_add, name))
|
||||
.setPositiveButton(R.string.action_add) { _, _ ->
|
||||
presenter.createFeed(idOfSearch)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
launchUI {
|
||||
filterSheet?.setSavedSearches(presenter.loadSearches())
|
||||
}
|
||||
filterSheet?.setFilters(presenter.filterItems)
|
||||
}
|
||||
val sourceId = args.getLong(SOURCE_EXTRA)
|
||||
|
||||
@Composable
|
||||
override fun ComposeContent() {
|
||||
SourceFeedScreen(
|
||||
presenter = presenter,
|
||||
onFabClick = { filterSheet?.show() },
|
||||
onClickBrowse = ::onBrowseClick,
|
||||
onClickLatest = ::onLatestClick,
|
||||
onClickSavedSearch = ::onSavedSearchClick,
|
||||
onClickDelete = ::onRemoveClick,
|
||||
onClickManga = ::onMangaClick,
|
||||
onClickSearch = ::onSearchClick,
|
||||
)
|
||||
|
||||
BackHandler(presenter.searchQuery != null) {
|
||||
presenter.searchQuery = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when manga in global search is clicked, opens manga.
|
||||
*
|
||||
* @param manga clicked item containing manga information.
|
||||
*/
|
||||
private fun onMangaClick(manga: Manga) {
|
||||
// Open MangaController.
|
||||
router.pushController(MangaController(manga.id, true))
|
||||
}
|
||||
|
||||
fun onBrowseClick(search: String? = null, savedSearch: Long? = null, filters: String? = null) {
|
||||
router.replaceTopController(BrowseSourceController(presenter.source, search, savedSearch = savedSearch, filterList = filters).withFadeTransaction())
|
||||
}
|
||||
|
||||
private fun onLatestClick() {
|
||||
router.replaceTopController(BrowseSourceController(presenter.source, GetRemoteManga.QUERY_LATEST).withFadeTransaction())
|
||||
}
|
||||
|
||||
private fun onBrowseClick() {
|
||||
router.replaceTopController(BrowseSourceController(presenter.source, GetRemoteManga.QUERY_POPULAR).withFadeTransaction())
|
||||
}
|
||||
|
||||
private fun onSavedSearchClick(savedSearch: SavedSearch) {
|
||||
router.replaceTopController(BrowseSourceController(presenter.source, savedSearch = savedSearch.id).withFadeTransaction())
|
||||
}
|
||||
|
||||
private fun onSearchClick(query: String) {
|
||||
onBrowseClick(query.nullIfBlank())
|
||||
}
|
||||
|
||||
private fun onRemoveClick(feedSavedSearch: FeedSavedSearch) {
|
||||
MaterialAlertDialogBuilder(activity!!)
|
||||
.setTitle(R.string.feed)
|
||||
.setMessage(R.string.feed_delete)
|
||||
.setPositiveButton(R.string.action_delete) { _, _ ->
|
||||
presenter.deleteFeed(feedSavedSearch)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
Navigator(screen = SourceFeedScreen(sourceId))
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -0,0 +1,214 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.source.feed
|
||||
|
||||
import android.content.Context
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.domain.source.interactor.GetRemoteManga
|
||||
import eu.kanade.presentation.browse.SourceFeedScreen
|
||||
import eu.kanade.presentation.browse.components.SourceFeedAddDialog
|
||||
import eu.kanade.presentation.browse.components.SourceFeedDeleteDialog
|
||||
import eu.kanade.presentation.browse.components.SourceFeedFailedToLoadSavedSearchDialog
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterSheet
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import exh.savedsearches.models.SavedSearch
|
||||
import exh.util.nullIfBlank
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import xyz.nulldev.ts.api.http.serializer.FilterSerializer
|
||||
|
||||
class SourceFeedScreen(val sourceId: Long) : Screen {
|
||||
|
||||
@Transient
|
||||
private var filterSheet: SourceFilterSheet? = null
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val screenModel = rememberScreenModel { SourceFeedScreenModel(sourceId) }
|
||||
val state by screenModel.state.collectAsState()
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val router = LocalRouter.currentOrThrow
|
||||
|
||||
SourceFeedScreen(
|
||||
name = screenModel.source.name,
|
||||
isLoading = state.isLoading,
|
||||
items = state.items,
|
||||
onFabClick = if (state.filters.isEmpty()) null else { { filterSheet?.show() } },
|
||||
onClickBrowse = { onBrowseClick(router, screenModel.source) },
|
||||
onClickLatest = { onLatestClick(router, screenModel.source) },
|
||||
onClickSavedSearch = { onSavedSearchClick(router, screenModel.source, it) },
|
||||
onClickDelete = screenModel::openDeleteFeed,
|
||||
onClickManga = { onMangaClick(navigator, it) },
|
||||
onClickSearch = { onSearchClick(router, screenModel.source, it) },
|
||||
searchQuery = state.searchQuery,
|
||||
onSearchQueryChange = screenModel::search,
|
||||
isIncognitoMode = screenModel.isIncognitoMode,
|
||||
isDownloadOnly = screenModel.isDownloadOnly,
|
||||
getMangaState = { screenModel.getManga(initialManga = it) },
|
||||
)
|
||||
|
||||
val onDismissRequest = screenModel::dismissDialog
|
||||
when (val dialog = state.dialog) {
|
||||
is SourceFeedScreenModel.Dialog.AddFeed -> {
|
||||
SourceFeedAddDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
name = dialog.name,
|
||||
addFeed = {
|
||||
screenModel.createFeed(dialog.feedId)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
}
|
||||
is SourceFeedScreenModel.Dialog.DeleteFeed -> {
|
||||
SourceFeedDeleteDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
deleteFeed = {
|
||||
screenModel.deleteFeed(dialog.feed)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
}
|
||||
SourceFeedScreenModel.Dialog.FailedToLoadSavedSearch -> {
|
||||
SourceFeedFailedToLoadSavedSearchDialog(onDismissRequest)
|
||||
}
|
||||
null -> Unit
|
||||
}
|
||||
|
||||
BackHandler(state.searchQuery != null) {
|
||||
screenModel.search(null)
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
|
||||
LaunchedEffect(state.filters) {
|
||||
initFilterSheet(state, screenModel, scope, context, router)
|
||||
}
|
||||
}
|
||||
|
||||
fun initFilterSheet(
|
||||
state: SourceFeedState,
|
||||
screenModel: SourceFeedScreenModel,
|
||||
viewScope: CoroutineScope,
|
||||
context: Context,
|
||||
router: Router,
|
||||
) {
|
||||
val filterSerializer = FilterSerializer()
|
||||
filterSheet = SourceFilterSheet(
|
||||
context,
|
||||
// SY -->
|
||||
router,
|
||||
screenModel.source,
|
||||
emptyList(),
|
||||
// SY <--
|
||||
onFilterClicked = {
|
||||
val allDefault = state.filters == screenModel.source.getFilterList()
|
||||
filterSheet?.dismiss()
|
||||
if (allDefault) {
|
||||
onBrowseClick(
|
||||
router,
|
||||
screenModel.source.id,
|
||||
state.searchQuery?.nullIfBlank(),
|
||||
)
|
||||
} else {
|
||||
onBrowseClick(
|
||||
router,
|
||||
screenModel.source.id,
|
||||
state.searchQuery?.nullIfBlank(),
|
||||
filters = Json.encodeToString(filterSerializer.serialize(state.filters)),
|
||||
)
|
||||
}
|
||||
},
|
||||
onResetClicked = {},
|
||||
onSaveClicked = {},
|
||||
onSavedSearchClicked = { idOfSearch ->
|
||||
viewScope.launchUI {
|
||||
val search = screenModel.loadSearch(idOfSearch)
|
||||
|
||||
if (search == null) {
|
||||
screenModel.openFailedToLoadSavedSearch()
|
||||
return@launchUI
|
||||
}
|
||||
|
||||
if (search.filterList == null && state.filters.isNotEmpty()) {
|
||||
context.toast(R.string.save_search_invalid)
|
||||
return@launchUI
|
||||
}
|
||||
|
||||
if (search.filterList != null) {
|
||||
screenModel.setFilters(FilterList(search.filterList))
|
||||
filterSheet?.setFilters(state.filterItems)
|
||||
}
|
||||
val allDefault = search.filterList != null && state.filters == screenModel.source.getFilterList()
|
||||
filterSheet?.dismiss()
|
||||
|
||||
if (!allDefault) {
|
||||
onBrowseClick(
|
||||
router,
|
||||
screenModel.source.id,
|
||||
search = state.searchQuery?.nullIfBlank(),
|
||||
savedSearch = search.id,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onSavedSearchDeleteClicked = { idOfSearch, name ->
|
||||
viewScope.launchUI {
|
||||
if (screenModel.hasTooManyFeeds()) {
|
||||
context.toast(R.string.too_many_in_feed)
|
||||
return@launchUI
|
||||
}
|
||||
screenModel.openAddFeed(idOfSearch, name)
|
||||
}
|
||||
},
|
||||
)
|
||||
viewScope.launchUI {
|
||||
filterSheet?.setSavedSearches(screenModel.loadSearches())
|
||||
}
|
||||
filterSheet?.setFilters(state.filterItems)
|
||||
}
|
||||
|
||||
private fun onMangaClick(navigator: Navigator, manga: Manga) {
|
||||
navigator.push(MangaScreen(manga.id, true))
|
||||
}
|
||||
|
||||
fun onBrowseClick(router: Router, sourceId: Long, search: String? = null, savedSearch: Long? = null, filters: String? = null) {
|
||||
router.replaceTopController(BrowseSourceController(sourceId, search, savedSearch = savedSearch, filterList = filters).withFadeTransaction())
|
||||
}
|
||||
|
||||
private fun onLatestClick(router: Router, source: CatalogueSource) {
|
||||
router.replaceTopController(BrowseSourceController(source, GetRemoteManga.QUERY_LATEST).withFadeTransaction())
|
||||
}
|
||||
|
||||
fun onBrowseClick(router: Router, source: CatalogueSource) {
|
||||
router.replaceTopController(BrowseSourceController(source, GetRemoteManga.QUERY_POPULAR).withFadeTransaction())
|
||||
}
|
||||
|
||||
private fun onSavedSearchClick(router: Router, source: CatalogueSource, savedSearch: SavedSearch) {
|
||||
router.replaceTopController(BrowseSourceController(source, savedSearch = savedSearch.id).withFadeTransaction())
|
||||
}
|
||||
|
||||
private fun onSearchClick(router: Router, source: CatalogueSource, query: String) {
|
||||
onBrowseClick(router, source.id, query.nullIfBlank())
|
||||
}
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.source.feed
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.produceState
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.coroutineScope
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.core.prefs.asState
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.domain.manga.interactor.GetManga
|
||||
import eu.kanade.domain.manga.interactor.NetworkToLocalManga
|
||||
@ -17,33 +21,32 @@ import eu.kanade.domain.source.interactor.GetExhSavedSearch
|
||||
import eu.kanade.domain.source.interactor.GetFeedSavedSearchBySourceId
|
||||
import eu.kanade.domain.source.interactor.GetSavedSearchBySourceIdFeed
|
||||
import eu.kanade.domain.source.interactor.InsertFeedSavedSearch
|
||||
import eu.kanade.presentation.browse.SourceFeedState
|
||||
import eu.kanade.presentation.browse.SourceFeedStateImpl
|
||||
import eu.kanade.presentation.browse.SourceFeedUI
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.toItems
|
||||
import eu.kanade.tachiyomi.util.lang.awaitSingle
|
||||
import eu.kanade.tachiyomi.util.lang.launchNonCancellable
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import exh.savedsearches.models.FeedSavedSearch
|
||||
import exh.savedsearches.models.SavedSearch
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import logcat.LogPriority
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.schedulers.Schedulers
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import xyz.nulldev.ts.api.http.serializer.FilterSerializer
|
||||
import java.util.concurrent.Executors
|
||||
import eu.kanade.domain.manga.model.Manga as DomainManga
|
||||
|
||||
/**
|
||||
@ -52,9 +55,9 @@ import eu.kanade.domain.manga.model.Manga as DomainManga
|
||||
*
|
||||
* @param source the source.
|
||||
*/
|
||||
open class SourceFeedPresenter(
|
||||
private val state: SourceFeedStateImpl = SourceFeedState() as SourceFeedStateImpl,
|
||||
val source: CatalogueSource,
|
||||
open class SourceFeedScreenModel(
|
||||
val sourceId: Long,
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
private val preferences: BasePreferences = Injekt.get(),
|
||||
private val getManga: GetManga = Injekt.get(),
|
||||
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
||||
@ -65,38 +68,33 @@ open class SourceFeedPresenter(
|
||||
private val insertFeedSavedSearch: InsertFeedSavedSearch = Injekt.get(),
|
||||
private val deleteFeedSavedSearchById: DeleteFeedSavedSearchById = Injekt.get(),
|
||||
private val getExhSavedSearch: GetExhSavedSearch = Injekt.get(),
|
||||
) : BasePresenter<SourceFeedController>(), SourceFeedState by state {
|
||||
) : StateScreenModel<SourceFeedState>(SourceFeedState()) {
|
||||
|
||||
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState()
|
||||
val isIncognitoMode: Boolean by preferences.incognitoMode().asState()
|
||||
val source = sourceManager.getOrStub(sourceId) as CatalogueSource
|
||||
|
||||
/**
|
||||
* Fetches the different sources by user settings.
|
||||
*/
|
||||
private var fetchSourcesSubscription: Subscription? = null
|
||||
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState(coroutineScope)
|
||||
val isIncognitoMode: Boolean by preferences.incognitoMode().asState(coroutineScope)
|
||||
|
||||
override fun onCreate(savedState: Bundle?) {
|
||||
super.onCreate(savedState)
|
||||
private val coroutineDispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher()
|
||||
|
||||
init {
|
||||
setFilters(source.getFilterList())
|
||||
|
||||
getFeedSavedSearchBySourceId.subscribe(source.id)
|
||||
.onEach {
|
||||
val items = getSourcesToGetFeed(it)
|
||||
state.items = items
|
||||
state.isLoading = false
|
||||
mutableState.update { state ->
|
||||
state.copy(
|
||||
items = items,
|
||||
)
|
||||
}
|
||||
getFeed(items)
|
||||
}
|
||||
.launchIn(presenterScope)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
fetchSourcesSubscription?.unsubscribe()
|
||||
super.onDestroy()
|
||||
.launchIn(coroutineScope)
|
||||
}
|
||||
|
||||
fun setFilters(filters: FilterList) {
|
||||
state.filters = filters
|
||||
mutableState.update { it.copy(filters = filters) }
|
||||
}
|
||||
|
||||
suspend fun hasTooManyFeeds(): Boolean {
|
||||
@ -104,7 +102,7 @@ open class SourceFeedPresenter(
|
||||
}
|
||||
|
||||
fun createFeed(savedSearchId: Long) {
|
||||
presenterScope.launchNonCancellable {
|
||||
coroutineScope.launchNonCancellable {
|
||||
insertFeedSavedSearch.await(
|
||||
FeedSavedSearch(
|
||||
id = -1,
|
||||
@ -117,7 +115,7 @@ open class SourceFeedPresenter(
|
||||
}
|
||||
|
||||
fun deleteFeed(feed: FeedSavedSearch) {
|
||||
presenterScope.launchNonCancellable {
|
||||
coroutineScope.launchNonCancellable {
|
||||
deleteFeedSavedSearchById.await(feed.id)
|
||||
}
|
||||
}
|
||||
@ -141,11 +139,10 @@ open class SourceFeedPresenter(
|
||||
* Initiates get manga per feed.
|
||||
*/
|
||||
private fun getFeed(feedSavedSearch: List<SourceFeedUI>) {
|
||||
fetchSourcesSubscription?.unsubscribe()
|
||||
fetchSourcesSubscription = Observable.from(feedSavedSearch)
|
||||
.flatMap(
|
||||
{ sourceFeed ->
|
||||
Observable.defer {
|
||||
coroutineScope.launch {
|
||||
feedSavedSearch.forEach { sourceFeed ->
|
||||
val page = try {
|
||||
withContext(coroutineDispatcher) {
|
||||
when (sourceFeed) {
|
||||
is SourceFeedUI.Browse -> source.fetchPopularManga(1)
|
||||
is SourceFeedUI.Latest -> source.fetchLatestUpdates(1)
|
||||
@ -154,30 +151,25 @@ open class SourceFeedPresenter(
|
||||
query = sourceFeed.savedSearch.query.orEmpty(),
|
||||
filters = getFilterList(sourceFeed.savedSearch, source),
|
||||
)
|
||||
}.awaitSingle()
|
||||
}.mangas
|
||||
} catch (e: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
val titles = page.map {
|
||||
withIOContext {
|
||||
networkToLocalManga.await(it.toDomainManga(source.id))
|
||||
}
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.onErrorReturn { MangasPage(emptyList(), false) } // Ignore timeouts or other exceptions
|
||||
.map { it.mangas } // Get manga from search result.
|
||||
.map { list -> runBlocking { list.map { networkToLocalManga.await(it.toDomainManga(source.id)) } } } // Convert to local manga.
|
||||
.map { list -> sourceFeed.withResults(list) }
|
||||
},
|
||||
5,
|
||||
|
||||
mutableState.update { state ->
|
||||
state.copy(
|
||||
items = state.items.map { item -> if (item.id == sourceFeed.id) sourceFeed.withResults(titles) else item },
|
||||
)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
// Update matching source with the obtained results
|
||||
.doOnNext { result ->
|
||||
synchronized(state) {
|
||||
state.items = state.items?.map { item -> if (item.id == result.id) result else item }
|
||||
}
|
||||
}
|
||||
// Deliver initial state
|
||||
.subscribe(
|
||||
{},
|
||||
{ error ->
|
||||
logcat(LogPriority.ERROR, error)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val filterSerializer = FilterSerializer()
|
||||
@ -233,4 +225,48 @@ open class SourceFeedPresenter(
|
||||
|
||||
suspend fun loadSearches() =
|
||||
getExhSavedSearch.await(source.id, source::getFilterList)
|
||||
|
||||
fun search(query: String?) {
|
||||
mutableState.update { it.copy(searchQuery = query) }
|
||||
}
|
||||
|
||||
fun openDeleteFeed(feed: FeedSavedSearch) {
|
||||
mutableState.update { it.copy(dialog = Dialog.DeleteFeed(feed)) }
|
||||
}
|
||||
|
||||
fun openAddFeed(feedId: Long, name: String) {
|
||||
mutableState.update { it.copy(dialog = Dialog.AddFeed(feedId, name)) }
|
||||
}
|
||||
|
||||
fun openFailedToLoadSavedSearch() {
|
||||
mutableState.update { it.copy(dialog = Dialog.FailedToLoadSavedSearch) }
|
||||
}
|
||||
|
||||
fun dismissDialog() {
|
||||
mutableState.update { it.copy(dialog = null) }
|
||||
}
|
||||
|
||||
sealed class Dialog {
|
||||
data class DeleteFeed(val feed: FeedSavedSearch) : Dialog()
|
||||
data class AddFeed(val feedId: Long, val name: String) : Dialog()
|
||||
object FailedToLoadSavedSearch : Dialog()
|
||||
}
|
||||
|
||||
override fun onDispose() {
|
||||
super.onDispose()
|
||||
coroutineDispatcher.close()
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class SourceFeedState(
|
||||
val searchQuery: String? = null,
|
||||
val items: List<SourceFeedUI> = emptyList(),
|
||||
val filters: FilterList = FilterList(),
|
||||
val dialog: SourceFeedScreenModel.Dialog? = null,
|
||||
) {
|
||||
val filterItems: List<IFlexible<*>> by lazy { filters.toItems() }
|
||||
|
||||
val isLoading
|
||||
get() = items.isEmpty()
|
||||
}
|
@ -64,6 +64,7 @@ import eu.kanade.tachiyomi.ui.browse.source.SourcesController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesController.Companion.SMART_SEARCH_SOURCE_TAG
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedScreen
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryController
|
||||
@ -160,11 +161,11 @@ class MangaScreen(
|
||||
// SY <--
|
||||
onWebViewLongClicked = { copyMangaUrl(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource },
|
||||
onTrackingClicked = screenModel::showTrackDialog.takeIf { successState.trackingAvailable },
|
||||
onTagClicked = { performGenreSearch(router, it, screenModel.source!!) },
|
||||
onTagClicked = { performGenreSearch(router, navigator, it, screenModel.source!!) },
|
||||
onFilterButtonClicked = screenModel::showSettingsDialog,
|
||||
onRefresh = screenModel::fetchAllFromSource,
|
||||
onContinueReading = { continueReading(context, screenModel.getNextUnreadChapter()) },
|
||||
onSearch = { query, global -> performSearch(router, query, global) },
|
||||
onSearch = { query, global -> performSearch(router, navigator, query, global) },
|
||||
onCoverClicked = screenModel::showCoverDialog,
|
||||
onShareClicked = { shareManga(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource },
|
||||
onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() },
|
||||
@ -368,12 +369,25 @@ class MangaScreen(
|
||||
*
|
||||
* @param query the search query to the parent controller
|
||||
*/
|
||||
private fun performSearch(router: Router, query: String, global: Boolean) {
|
||||
private fun performSearch(router: Router, navigator: Navigator, query: String, global: Boolean) {
|
||||
if (global) {
|
||||
router.pushController(GlobalSearchController(query))
|
||||
return
|
||||
}
|
||||
|
||||
// SY -->
|
||||
if (navigator.canPop) {
|
||||
when (val previousScreen = navigator.items[navigator.items.size - 2]) {
|
||||
is SourceFeedScreen -> {
|
||||
navigator.pop()
|
||||
previousScreen.onBrowseClick(router, previousScreen.sourceId, query)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
// SY <--
|
||||
|
||||
if (router.backstackSize < 2) {
|
||||
return
|
||||
}
|
||||
@ -399,7 +413,8 @@ class MangaScreen(
|
||||
// SY -->
|
||||
is SourceFeedController -> {
|
||||
router.handleBack()
|
||||
previousController.onBrowseClick(query)
|
||||
router.handleBack()
|
||||
router.pushController(BrowseSourceController(previousController.sourceId, query))
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
@ -410,7 +425,7 @@ class MangaScreen(
|
||||
*
|
||||
* @param genreName the search genre to the parent controller
|
||||
*/
|
||||
private fun performGenreSearch(router: Router, genreName: String, source: Source) {
|
||||
private fun performGenreSearch(router: Router, navigator: Navigator, genreName: String, source: Source) {
|
||||
if (router.backstackSize < 2) {
|
||||
return
|
||||
}
|
||||
@ -423,7 +438,7 @@ class MangaScreen(
|
||||
router.handleBack()
|
||||
previousController.searchWithGenre(genreName)
|
||||
} else {
|
||||
performSearch(router, genreName, global = false)
|
||||
performSearch(router, navigator, genreName, global = false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,19 +4,17 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import eu.kanade.tachiyomi.databinding.SourceFilterMangadexHeaderBinding
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.online.RandomMangaSource
|
||||
import eu.kanade.tachiyomi.ui.base.controller.BaseController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
import exh.md.follows.MangaDexFollowsController
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.view.clicks
|
||||
|
||||
class MangaDexFabHeaderAdapter(val controller: BaseController<*>, val source: CatalogueSource, val onClick: () -> Unit) :
|
||||
class MangaDexFabHeaderAdapter(val router: Router, val source: CatalogueSource, val onClick: () -> Unit) :
|
||||
RecyclerView.Adapter<MangaDexFabHeaderAdapter.SavedSearchesViewHolder>() {
|
||||
|
||||
private lateinit var binding: SourceFilterMangadexHeaderBinding
|
||||
@ -35,22 +33,23 @@ class MangaDexFabHeaderAdapter(val controller: BaseController<*>, val source: Ca
|
||||
inner class SavedSearchesViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
fun bind() {
|
||||
binding.mangadexFollows.setOnClickListener {
|
||||
controller.router.replaceTopController(MangaDexFollowsController(source).withFadeTransaction())
|
||||
router.replaceTopController(MangaDexFollowsController(source).withFadeTransaction())
|
||||
onClick()
|
||||
}
|
||||
binding.mangadexRandom.clicks()
|
||||
.onEach {
|
||||
binding.mangadexRandom.setOnClickListener {
|
||||
launchUI {
|
||||
val randomMangaUrl = withIOContext {
|
||||
(source as? RandomMangaSource)?.fetchRandomMangaUrl()
|
||||
}
|
||||
controller.router.replaceTopController(
|
||||
router.replaceTopController(
|
||||
BrowseSourceController(
|
||||
source,
|
||||
"id:$randomMangaUrl",
|
||||
).withFadeTransaction(),
|
||||
)
|
||||
onClick()
|
||||
}.launchIn(controller.viewScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user