Use FullComposeController for Source Feed
This commit is contained in:
parent
a760198981
commit
9dad9a6551
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.presentation.browse
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@ -7,28 +8,39 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
|
import androidx.compose.foundation.layout.only
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Search
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||||
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
import eu.kanade.presentation.util.bottomNavPaddingValues
|
import eu.kanade.presentation.util.bottomNavPaddingValues
|
||||||
import eu.kanade.presentation.util.plus
|
import eu.kanade.presentation.util.plus
|
||||||
import eu.kanade.presentation.util.topPaddingValues
|
import eu.kanade.presentation.util.topPaddingValues
|
||||||
@ -92,7 +104,6 @@ sealed class SourceFeedUI {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourceFeedScreen(
|
fun SourceFeedScreen(
|
||||||
nestedScrollInterop: NestedScrollConnection,
|
|
||||||
presenter: SourceFeedPresenter,
|
presenter: SourceFeedPresenter,
|
||||||
onClickBrowse: () -> Unit,
|
onClickBrowse: () -> Unit,
|
||||||
onClickLatest: () -> Unit,
|
onClickLatest: () -> Unit,
|
||||||
@ -100,12 +111,29 @@ fun SourceFeedScreen(
|
|||||||
onClickDelete: (FeedSavedSearch) -> Unit,
|
onClickDelete: (FeedSavedSearch) -> Unit,
|
||||||
onClickManga: (Manga) -> Unit,
|
onClickManga: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
when {
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
presenter.isLoading -> LoadingScreen()
|
val insets = WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal)
|
||||||
else -> {
|
Scaffold(
|
||||||
SourceFeedList(
|
modifier = Modifier
|
||||||
nestedScrollConnection = nestedScrollInterop,
|
.windowInsetsPadding(insets)
|
||||||
|
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
|
topBar = {
|
||||||
|
SourceFeedToolbar(
|
||||||
|
title = presenter.source.name,
|
||||||
state = presenter,
|
state = presenter,
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
incognitoMode = presenter.isIncognitoMode,
|
||||||
|
downloadedOnlyMode = presenter.isDownloadOnly,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
Crossfade(targetState = presenter.isLoading) { state ->
|
||||||
|
when (state) {
|
||||||
|
true -> LoadingScreen()
|
||||||
|
false -> {
|
||||||
|
SourceFeedList(
|
||||||
|
state = presenter,
|
||||||
|
paddingValues = paddingValues,
|
||||||
onClickBrowse = onClickBrowse,
|
onClickBrowse = onClickBrowse,
|
||||||
onClickLatest = onClickLatest,
|
onClickLatest = onClickLatest,
|
||||||
onClickSavedSearch = onClickSavedSearch,
|
onClickSavedSearch = onClickSavedSearch,
|
||||||
@ -114,12 +142,14 @@ fun SourceFeedScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourceFeedList(
|
fun SourceFeedList(
|
||||||
nestedScrollConnection: NestedScrollConnection,
|
|
||||||
state: SourceFeedState,
|
state: SourceFeedState,
|
||||||
|
paddingValues: PaddingValues,
|
||||||
onClickBrowse: () -> Unit,
|
onClickBrowse: () -> Unit,
|
||||||
onClickLatest: () -> Unit,
|
onClickLatest: () -> Unit,
|
||||||
onClickSavedSearch: (SavedSearch) -> Unit,
|
onClickSavedSearch: (SavedSearch) -> Unit,
|
||||||
@ -127,8 +157,10 @@ fun SourceFeedList(
|
|||||||
onClickManga: (Manga) -> Unit,
|
onClickManga: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
modifier = Modifier.nestedScroll(nestedScrollConnection),
|
contentPadding = paddingValues +
|
||||||
contentPadding = bottomNavPaddingValues + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
bottomNavPaddingValues +
|
||||||
|
WindowInsets.navigationBars.only(WindowInsetsSides.Vertical).asPaddingValues() +
|
||||||
|
topPaddingValues,
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
state.items.orEmpty(),
|
state.items.orEmpty(),
|
||||||
@ -217,3 +249,35 @@ fun SourceFeedItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SourceFeedToolbar(
|
||||||
|
title: String,
|
||||||
|
state: SourceFeedState,
|
||||||
|
scrollBehavior: TopAppBarScrollBehavior,
|
||||||
|
incognitoMode: Boolean,
|
||||||
|
downloadedOnlyMode: Boolean,
|
||||||
|
) {
|
||||||
|
when {
|
||||||
|
state.searchQuery != null -> SearchToolbar(
|
||||||
|
searchQuery = state.searchQuery!!,
|
||||||
|
onChangeSearchQuery = { state.searchQuery = it },
|
||||||
|
onClickCloseSearch = { state.searchQuery = null },
|
||||||
|
onClickResetSearch = { state.searchQuery = "" },
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
incognitoMode = incognitoMode,
|
||||||
|
downloadedOnlyMode = downloadedOnlyMode,
|
||||||
|
)
|
||||||
|
else -> AppBar(
|
||||||
|
title = title,
|
||||||
|
incognitoMode = incognitoMode,
|
||||||
|
downloadedOnlyMode = downloadedOnlyMode,
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = { state.searchQuery = "" }) {
|
||||||
|
Icon(Icons.Outlined.Search, contentDescription = stringResource(R.string.action_search))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,6 +8,7 @@ import androidx.compose.runtime.setValue
|
|||||||
@Stable
|
@Stable
|
||||||
interface SourceFeedState {
|
interface SourceFeedState {
|
||||||
val isLoading: Boolean
|
val isLoading: Boolean
|
||||||
|
var searchQuery: String?
|
||||||
val items: List<SourceFeedUI>?
|
val items: List<SourceFeedUI>?
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,5 +18,6 @@ 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 items: List<SourceFeedUI>? by mutableStateOf(null)
|
override var items: List<SourceFeedUI>? by mutableStateOf(null)
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source.feed
|
package eu.kanade.tachiyomi.ui.browse.source.feed
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
@ -17,7 +14,7 @@ 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.FabController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.SearchableComposeController
|
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterSheet
|
import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterSheet
|
||||||
@ -41,7 +38,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 :
|
||||||
SearchableComposeController<SourceFeedPresenter>,
|
FullComposeController<SourceFeedPresenter>,
|
||||||
FabController {
|
FabController {
|
||||||
|
|
||||||
constructor(source: CatalogueSource?) : super(
|
constructor(source: CatalogueSource?) : super(
|
||||||
@ -68,14 +65,6 @@ open class SourceFeedController :
|
|||||||
*/
|
*/
|
||||||
private var filterSheet: SourceFilterSheet? = null
|
private var filterSheet: SourceFilterSheet? = null
|
||||||
|
|
||||||
init {
|
|
||||||
setHasOptionsMenu(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTitle(): String? {
|
|
||||||
return source!!.name
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the [SourceFeedPresenter] used in controller.
|
* Create the [SourceFeedPresenter] used in controller.
|
||||||
*
|
*
|
||||||
@ -85,26 +74,6 @@ open class SourceFeedController :
|
|||||||
return SourceFeedPresenter(source = source!!)
|
return SourceFeedPresenter(source = source!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds items to the options menu.
|
|
||||||
*
|
|
||||||
* @param menu menu containing options.
|
|
||||||
* @param inflater used to load the menu xml.
|
|
||||||
*/
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
|
||||||
createOptionsMenu(menu, inflater, R.menu.global_search, R.id.action_search)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSearchViewQueryTextSubmit(query: String?) {
|
|
||||||
onBrowseClick(query?.nullIfBlank())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSearchViewQueryTextChange(newText: String?) {
|
|
||||||
if (router.backstack.lastOrNull()?.controller == this) {
|
|
||||||
presenter.query = newText ?: ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the view is created
|
* Called when the view is created
|
||||||
*
|
*
|
||||||
@ -136,11 +105,11 @@ open class SourceFeedController :
|
|||||||
filterSheet?.dismiss()
|
filterSheet?.dismiss()
|
||||||
if (allDefault) {
|
if (allDefault) {
|
||||||
onBrowseClick(
|
onBrowseClick(
|
||||||
presenter.query.nullIfBlank(),
|
presenter.searchQuery?.nullIfBlank(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
onBrowseClick(
|
onBrowseClick(
|
||||||
presenter.query.nullIfBlank(),
|
presenter.searchQuery?.nullIfBlank(),
|
||||||
filters = Json.encodeToString(filterSerializer.serialize(presenter.sourceFilters)),
|
filters = Json.encodeToString(filterSerializer.serialize(presenter.sourceFilters)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -173,7 +142,7 @@ open class SourceFeedController :
|
|||||||
|
|
||||||
if (!allDefault) {
|
if (!allDefault) {
|
||||||
onBrowseClick(
|
onBrowseClick(
|
||||||
search = presenter.query.nullIfBlank(),
|
search = presenter.searchQuery?.nullIfBlank(),
|
||||||
savedSearch = search.id,
|
savedSearch = search.id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -228,9 +197,8 @@ open class SourceFeedController :
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
|
override fun ComposeContent() {
|
||||||
SourceFeedScreen(
|
SourceFeedScreen(
|
||||||
nestedScrollInterop = nestedScrollInterop,
|
|
||||||
presenter = presenter,
|
presenter = presenter,
|
||||||
onClickBrowse = ::onBrowseClick,
|
onClickBrowse = ::onBrowseClick,
|
||||||
onClickLatest = ::onLatestClick,
|
onClickLatest = ::onLatestClick,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source.feed
|
package eu.kanade.tachiyomi.ui.browse.source.feed
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
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
|
||||||
@ -71,6 +72,9 @@ open class SourceFeedPresenter(
|
|||||||
private val getExhSavedSearch: GetExhSavedSearch = Injekt.get(),
|
private val getExhSavedSearch: GetExhSavedSearch = Injekt.get(),
|
||||||
) : BasePresenter<SourceFeedController>(), SourceFeedState by state {
|
) : BasePresenter<SourceFeedController>(), SourceFeedState by state {
|
||||||
|
|
||||||
|
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState()
|
||||||
|
val isIncognitoMode: Boolean by preferences.incognitoMode().asState()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the different sources by user settings.
|
* Fetches the different sources by user settings.
|
||||||
*/
|
*/
|
||||||
@ -97,10 +101,6 @@ open class SourceFeedPresenter(
|
|||||||
|
|
||||||
var filterItems: List<IFlexible<*>> = emptyList()
|
var filterItems: List<IFlexible<*>> = emptyList()
|
||||||
|
|
||||||
init {
|
|
||||||
query = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user