Control FAB with Compose in SourceFeed
This commit is contained in:
parent
e6767b747b
commit
d0518515e9
@ -101,6 +101,7 @@ sealed class SourceFeedUI {
|
|||||||
@Composable
|
@Composable
|
||||||
fun SourceFeedScreen(
|
fun SourceFeedScreen(
|
||||||
presenter: SourceFeedPresenter,
|
presenter: SourceFeedPresenter,
|
||||||
|
onFabClick: () -> Unit,
|
||||||
onClickBrowse: () -> Unit,
|
onClickBrowse: () -> Unit,
|
||||||
onClickLatest: () -> Unit,
|
onClickLatest: () -> Unit,
|
||||||
onClickSavedSearch: (SavedSearch) -> Unit,
|
onClickSavedSearch: (SavedSearch) -> Unit,
|
||||||
@ -117,6 +118,12 @@ fun SourceFeedScreen(
|
|||||||
downloadedOnlyMode = presenter.isDownloadOnly,
|
downloadedOnlyMode = presenter.isDownloadOnly,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
floatingActionButton = {
|
||||||
|
BrowseSourceFloatingActionButton(
|
||||||
|
isVisible = presenter.filterItems.isNotEmpty(),
|
||||||
|
onFabClick = onFabClick,
|
||||||
|
)
|
||||||
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Crossfade(targetState = presenter.isLoading) { state ->
|
Crossfade(targetState = presenter.isLoading) { state ->
|
||||||
when (state) {
|
when (state) {
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
package eu.kanade.presentation.browse
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.browse.toItems
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
interface SourceFeedState {
|
interface SourceFeedState {
|
||||||
val isLoading: Boolean
|
val isLoading: Boolean
|
||||||
var searchQuery: String?
|
var searchQuery: String?
|
||||||
|
val filters: FilterList
|
||||||
|
val filterItems: List<IFlexible<*>>
|
||||||
val items: List<SourceFeedUI>?
|
val items: List<SourceFeedUI>?
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,5 +25,7 @@ fun SourceFeedState(): SourceFeedState {
|
|||||||
class SourceFeedStateImpl : SourceFeedState {
|
class SourceFeedStateImpl : SourceFeedState {
|
||||||
override var isLoading: Boolean by mutableStateOf(true)
|
override var isLoading: Boolean by mutableStateOf(true)
|
||||||
override var searchQuery: String? by mutableStateOf(null)
|
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)
|
override var items: List<SourceFeedUI>? by mutableStateOf(null)
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,7 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
import eu.kanade.domain.source.interactor.GetRemoteManga
|
import eu.kanade.domain.source.interactor.GetRemoteManga
|
||||||
import eu.kanade.presentation.browse.SourceFeedScreen
|
import eu.kanade.presentation.browse.SourceFeedScreen
|
||||||
@ -14,7 +12,6 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.FabController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||||
@ -38,8 +35,7 @@ import xyz.nulldev.ts.api.http.serializer.FilterSerializer
|
|||||||
* [SourceFeedCardAdapter.OnMangaClickListener] called when manga is clicked in global search
|
* [SourceFeedCardAdapter.OnMangaClickListener] called when manga is clicked in global search
|
||||||
*/
|
*/
|
||||||
open class SourceFeedController :
|
open class SourceFeedController :
|
||||||
FullComposeController<SourceFeedPresenter>,
|
FullComposeController<SourceFeedPresenter> {
|
||||||
FabController {
|
|
||||||
|
|
||||||
constructor(source: CatalogueSource?) : super(
|
constructor(source: CatalogueSource?) : super(
|
||||||
bundleOf(
|
bundleOf(
|
||||||
@ -58,8 +54,6 @@ open class SourceFeedController :
|
|||||||
|
|
||||||
var source: CatalogueSource? = null
|
var source: CatalogueSource? = null
|
||||||
|
|
||||||
private var actionFab: ExtendedFloatingActionButton? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sheet containing filter items.
|
* Sheet containing filter items.
|
||||||
*/
|
*/
|
||||||
@ -89,10 +83,6 @@ open class SourceFeedController :
|
|||||||
private val filterSerializer = FilterSerializer()
|
private val filterSerializer = FilterSerializer()
|
||||||
|
|
||||||
fun initFilterSheet() {
|
fun initFilterSheet() {
|
||||||
if (presenter.sourceFilters.isEmpty()) {
|
|
||||||
actionFab?.text = activity!!.getString(R.string.saved_searches)
|
|
||||||
}
|
|
||||||
|
|
||||||
filterSheet = SourceFilterSheet(
|
filterSheet = SourceFilterSheet(
|
||||||
activity!!,
|
activity!!,
|
||||||
// SY -->
|
// SY -->
|
||||||
@ -101,7 +91,7 @@ open class SourceFeedController :
|
|||||||
emptyList(),
|
emptyList(),
|
||||||
// SY <--
|
// SY <--
|
||||||
onFilterClicked = {
|
onFilterClicked = {
|
||||||
val allDefault = presenter.sourceFilters == presenter.source.getFilterList()
|
val allDefault = presenter.filters == presenter.source.getFilterList()
|
||||||
filterSheet?.dismiss()
|
filterSheet?.dismiss()
|
||||||
if (allDefault) {
|
if (allDefault) {
|
||||||
onBrowseClick(
|
onBrowseClick(
|
||||||
@ -110,7 +100,7 @@ open class SourceFeedController :
|
|||||||
} else {
|
} else {
|
||||||
onBrowseClick(
|
onBrowseClick(
|
||||||
presenter.searchQuery?.nullIfBlank(),
|
presenter.searchQuery?.nullIfBlank(),
|
||||||
filters = Json.encodeToString(filterSerializer.serialize(presenter.sourceFilters)),
|
filters = Json.encodeToString(filterSerializer.serialize(presenter.filters)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -135,9 +125,9 @@ open class SourceFeedController :
|
|||||||
return@launchUI
|
return@launchUI
|
||||||
}
|
}
|
||||||
|
|
||||||
presenter.sourceFilters = FilterList(search.filterList)
|
presenter.setFilters(FilterList(search.filterList))
|
||||||
filterSheet?.setFilters(presenter.filterItems)
|
filterSheet?.setFilters(presenter.filterItems)
|
||||||
val allDefault = presenter.sourceFilters == presenter.source.getFilterList()
|
val allDefault = presenter.filters == presenter.source.getFilterList()
|
||||||
filterSheet?.dismiss()
|
filterSheet?.dismiss()
|
||||||
|
|
||||||
if (!allDefault) {
|
if (!allDefault) {
|
||||||
@ -171,35 +161,13 @@ open class SourceFeedController :
|
|||||||
filterSheet?.setSavedSearches(presenter.loadSearches())
|
filterSheet?.setSavedSearches(presenter.loadSearches())
|
||||||
}
|
}
|
||||||
filterSheet?.setFilters(presenter.filterItems)
|
filterSheet?.setFilters(presenter.filterItems)
|
||||||
|
|
||||||
// TODO: [ExtendedFloatingActionButton] hide/show methods don't work properly
|
|
||||||
filterSheet?.setOnShowListener { actionFab?.isVisible = false }
|
|
||||||
filterSheet?.setOnDismissListener { actionFab?.isVisible = true }
|
|
||||||
|
|
||||||
actionFab?.setOnClickListener { filterSheet?.show() }
|
|
||||||
|
|
||||||
actionFab?.isVisible = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun configureFab(fab: ExtendedFloatingActionButton) {
|
|
||||||
actionFab = fab
|
|
||||||
|
|
||||||
// Controlled by initFilterSheet()
|
|
||||||
fab.isVisible = false
|
|
||||||
|
|
||||||
fab.setText(R.string.action_filter)
|
|
||||||
fab.setIconResource(R.drawable.ic_filter_list_24dp)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cleanupFab(fab: ExtendedFloatingActionButton) {
|
|
||||||
fab.setOnClickListener(null)
|
|
||||||
actionFab = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun ComposeContent() {
|
override fun ComposeContent() {
|
||||||
SourceFeedScreen(
|
SourceFeedScreen(
|
||||||
presenter = presenter,
|
presenter = presenter,
|
||||||
|
onFabClick = { filterSheet?.show() },
|
||||||
onClickBrowse = ::onBrowseClick,
|
onClickBrowse = ::onBrowseClick,
|
||||||
onClickLatest = ::onLatestClick,
|
onClickLatest = ::onLatestClick,
|
||||||
onClickSavedSearch = ::onSavedSearchClick,
|
onClickSavedSearch = ::onSavedSearchClick,
|
||||||
|
@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.browse.source.feed
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
|
||||||
import eu.kanade.domain.manga.interactor.GetManga
|
import eu.kanade.domain.manga.interactor.GetManga
|
||||||
import eu.kanade.domain.manga.interactor.InsertManga
|
import eu.kanade.domain.manga.interactor.InsertManga
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
@ -12,7 +11,6 @@ import eu.kanade.domain.source.interactor.CountFeedSavedSearchBySourceId
|
|||||||
import eu.kanade.domain.source.interactor.DeleteFeedSavedSearchById
|
import eu.kanade.domain.source.interactor.DeleteFeedSavedSearchById
|
||||||
import eu.kanade.domain.source.interactor.GetExhSavedSearch
|
import eu.kanade.domain.source.interactor.GetExhSavedSearch
|
||||||
import eu.kanade.domain.source.interactor.GetFeedSavedSearchBySourceId
|
import eu.kanade.domain.source.interactor.GetFeedSavedSearchBySourceId
|
||||||
import eu.kanade.domain.source.interactor.GetSavedSearchBySourceId
|
|
||||||
import eu.kanade.domain.source.interactor.GetSavedSearchBySourceIdFeed
|
import eu.kanade.domain.source.interactor.GetSavedSearchBySourceIdFeed
|
||||||
import eu.kanade.domain.source.interactor.InsertFeedSavedSearch
|
import eu.kanade.domain.source.interactor.InsertFeedSavedSearch
|
||||||
import eu.kanade.presentation.browse.SourceFeedState
|
import eu.kanade.presentation.browse.SourceFeedState
|
||||||
@ -27,8 +25,7 @@ import eu.kanade.tachiyomi.source.model.FilterList
|
|||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.toItems
|
import eu.kanade.tachiyomi.util.lang.launchNonCancellableIO
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
|
||||||
import eu.kanade.tachiyomi.util.lang.runAsObservable
|
import eu.kanade.tachiyomi.util.lang.runAsObservable
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
import exh.savedsearches.models.FeedSavedSearch
|
import exh.savedsearches.models.FeedSavedSearch
|
||||||
@ -53,8 +50,6 @@ import xyz.nulldev.ts.api.http.serializer.FilterSerializer
|
|||||||
* Function calls should be done from here. UI calls should be done from the controller.
|
* Function calls should be done from here. UI calls should be done from the controller.
|
||||||
*
|
*
|
||||||
* @param source the source.
|
* @param source the source.
|
||||||
* @param handler manages the database calls.
|
|
||||||
* @param preferences manages the preference calls.
|
|
||||||
*/
|
*/
|
||||||
open class SourceFeedPresenter(
|
open class SourceFeedPresenter(
|
||||||
private val state: SourceFeedStateImpl = SourceFeedState() as SourceFeedStateImpl,
|
private val state: SourceFeedStateImpl = SourceFeedState() as SourceFeedStateImpl,
|
||||||
@ -66,7 +61,6 @@ open class SourceFeedPresenter(
|
|||||||
private val getFeedSavedSearchBySourceId: GetFeedSavedSearchBySourceId = Injekt.get(),
|
private val getFeedSavedSearchBySourceId: GetFeedSavedSearchBySourceId = Injekt.get(),
|
||||||
private val getSavedSearchBySourceIdFeed: GetSavedSearchBySourceIdFeed = Injekt.get(),
|
private val getSavedSearchBySourceIdFeed: GetSavedSearchBySourceIdFeed = Injekt.get(),
|
||||||
private val countFeedSavedSearchBySourceId: CountFeedSavedSearchBySourceId = Injekt.get(),
|
private val countFeedSavedSearchBySourceId: CountFeedSavedSearchBySourceId = Injekt.get(),
|
||||||
private val getSavedSearchBySourceId: GetSavedSearchBySourceId = Injekt.get(),
|
|
||||||
private val insertFeedSavedSearch: InsertFeedSavedSearch = Injekt.get(),
|
private val insertFeedSavedSearch: InsertFeedSavedSearch = Injekt.get(),
|
||||||
private val deleteFeedSavedSearchById: DeleteFeedSavedSearchById = Injekt.get(),
|
private val deleteFeedSavedSearchById: DeleteFeedSavedSearchById = Injekt.get(),
|
||||||
private val getExhSavedSearch: GetExhSavedSearch = Injekt.get(),
|
private val getExhSavedSearch: GetExhSavedSearch = Injekt.get(),
|
||||||
@ -90,21 +84,10 @@ open class SourceFeedPresenter(
|
|||||||
*/
|
*/
|
||||||
private var fetchImageSubscription: Subscription? = null
|
private var fetchImageSubscription: Subscription? = null
|
||||||
|
|
||||||
/**
|
|
||||||
* Modifiable list of filters.
|
|
||||||
*/
|
|
||||||
var sourceFilters = FilterList()
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
filterItems = value.toItems()
|
|
||||||
}
|
|
||||||
|
|
||||||
var filterItems: List<IFlexible<*>> = emptyList()
|
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
sourceFilters = source.getFilterList()
|
setFilters(source.getFilterList())
|
||||||
|
|
||||||
getFeedSavedSearchBySourceId.subscribe(source.id)
|
getFeedSavedSearchBySourceId.subscribe(source.id)
|
||||||
.onEach {
|
.onEach {
|
||||||
@ -122,16 +105,16 @@ open class SourceFeedPresenter(
|
|||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setFilters(filters: FilterList) {
|
||||||
|
state.filters = filters
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun hasTooManyFeeds(): Boolean {
|
suspend fun hasTooManyFeeds(): Boolean {
|
||||||
return countFeedSavedSearchBySourceId.await(source.id) > 10
|
return countFeedSavedSearchBySourceId.await(source.id) > 10
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getSourceSavedSearches(): List<SavedSearch> {
|
|
||||||
return getSavedSearchBySourceId.await(source.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createFeed(savedSearchId: Long) {
|
fun createFeed(savedSearchId: Long) {
|
||||||
launchIO {
|
presenterScope.launchNonCancellableIO {
|
||||||
insertFeedSavedSearch.await(
|
insertFeedSavedSearch.await(
|
||||||
FeedSavedSearch(
|
FeedSavedSearch(
|
||||||
id = -1,
|
id = -1,
|
||||||
@ -144,7 +127,7 @@ open class SourceFeedPresenter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun deleteFeed(feed: FeedSavedSearch) {
|
fun deleteFeed(feed: FeedSavedSearch) {
|
||||||
launchIO {
|
presenterScope.launchNonCancellableIO {
|
||||||
deleteFeedSavedSearchById.await(feed.id)
|
deleteFeedSavedSearchById.await(feed.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ open class AutoComplete(val filter: Filter.AutoComplete) : AbstractFlexibleItem<
|
|||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
return filter == (other as SelectItem).filter
|
return filter == (other as AutoComplete).filter
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user