Use FullComposeController for Source Feed

This commit is contained in:
Jobobby04 2022-08-31 16:48:31 -04:00
parent a760198981
commit 9dad9a6551
4 changed files with 91 additions and 57 deletions

View File

@ -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))
}
},
)
}
}

View File

@ -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)
} }

View File

@ -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,

View File

@ -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)