arkon 241517a13a Make top app bar lift behavior more consistent
(cherry picked from commit 504844a892635fe9554a3eb907ad328b94ffab29)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/browse/BrowseLatestScreen.kt
#	app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt
#	app/src/main/java/eu/kanade/presentation/browse/components/BrowseLatestToolbar.kt
#	app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceToolbar.kt
2022-09-03 18:10:38 -04:00

275 lines
9.3 KiB
Kotlin

package eu.kanade.presentation.browse
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyRow
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.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.domain.manga.model.Manga
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.presentation.util.bottomNavPaddingValues
import eu.kanade.presentation.util.plus
import eu.kanade.presentation.util.topPaddingValues
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedPresenter
import exh.savedsearches.models.FeedSavedSearch
import exh.savedsearches.models.SavedSearch
sealed class SourceFeedUI {
abstract val id: Long
abstract val title: String
@Composable
@ReadOnlyComposable
get
abstract val results: List<Manga>?
abstract fun withResults(results: List<Manga>?): SourceFeedUI
data class Latest(override val results: List<Manga>?) : SourceFeedUI() {
override val id: Long = -1
override val title: String
@Composable
@ReadOnlyComposable
get() = stringResource(R.string.latest)
override fun withResults(results: List<Manga>?): SourceFeedUI {
return copy(results = results)
}
}
data class Browse(override val results: List<Manga>?) : SourceFeedUI() {
override val id: Long = -2
override val title: String
@Composable
@ReadOnlyComposable
get() = stringResource(R.string.browse)
override fun withResults(results: List<Manga>?): SourceFeedUI {
return copy(results = results)
}
}
data class SourceSavedSearch(
val feed: FeedSavedSearch,
val savedSearch: SavedSearch,
override val results: List<Manga>?,
) : SourceFeedUI() {
override val id: Long
get() = feed.id
override val title: String
@Composable
@ReadOnlyComposable
get() = savedSearch.name
override fun withResults(results: List<Manga>?): SourceFeedUI {
return copy(results = results)
}
}
}
@Composable
fun SourceFeedScreen(
presenter: SourceFeedPresenter,
onClickBrowse: () -> Unit,
onClickLatest: () -> Unit,
onClickSavedSearch: (SavedSearch) -> Unit,
onClickDelete: (FeedSavedSearch) -> Unit,
onClickManga: (Manga) -> Unit,
) {
Scaffold(
topBar = { scrollBehavior ->
SourceFeedToolbar(
title = presenter.source.name,
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,
onClickLatest = onClickLatest,
onClickSavedSearch = onClickSavedSearch,
onClickDelete = onClickDelete,
onClickManga = onClickManga,
)
}
}
}
}
}
@Composable
fun SourceFeedList(
state: SourceFeedState,
paddingValues: PaddingValues,
onClickBrowse: () -> Unit,
onClickLatest: () -> Unit,
onClickSavedSearch: (SavedSearch) -> Unit,
onClickDelete: (FeedSavedSearch) -> Unit,
onClickManga: (Manga) -> Unit,
) {
ScrollbarLazyColumn(
contentPadding = paddingValues +
bottomNavPaddingValues +
WindowInsets.navigationBars.only(WindowInsetsSides.Vertical).asPaddingValues() +
topPaddingValues,
) {
items(
state.items.orEmpty(),
key = { it.id },
) { item ->
SourceFeedItem(
modifier = Modifier.animateItemPlacement(),
item = item,
onClickTitle = when (item) {
is SourceFeedUI.Browse -> onClickBrowse
is SourceFeedUI.Latest -> onClickLatest
is SourceFeedUI.SourceSavedSearch -> {
{ onClickSavedSearch(item.savedSearch) }
}
},
onClickDelete = onClickDelete,
onClickManga = onClickManga,
)
}
}
}
@Composable
fun SourceFeedItem(
modifier: Modifier,
item: SourceFeedUI,
onClickTitle: () -> Unit,
onClickDelete: (FeedSavedSearch) -> Unit,
onClickManga: (Manga) -> Unit,
) {
Column(
modifier then Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Row(
Modifier
.fillMaxWidth()
.let {
if (item is SourceFeedUI.SourceSavedSearch) {
it.combinedClickable(
onLongClick = {
onClickDelete(item.feed)
},
onClick = onClickTitle,
)
} else {
it.clickable(onClick = onClickTitle)
}
},
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Column(Modifier.padding(start = 16.dp)) {
Text(
text = item.title,
style = MaterialTheme.typography.bodyMedium,
)
}
Icon(
painter = painterResource(R.drawable.ic_arrow_forward_24dp),
contentDescription = stringResource(R.string.label_more),
modifier = Modifier.padding(16.dp),
)
}
val results = item.results
when {
results == null -> {
CircularProgressIndicator()
}
results.isEmpty() -> {
Text(stringResource(R.string.no_results_found), modifier = Modifier.padding(bottom = 16.dp))
}
else -> {
LazyRow(
Modifier.fillMaxWidth(),
contentPadding = PaddingValues(horizontal = 12.dp),
) {
items(results) {
FeedCardItem(
manga = it,
onClickManga = onClickManga,
)
}
}
}
}
}
}
@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))
}
},
)
}
}