Tran M. Cuong 3408ef635d Fix disappearance items when fast scrolling (#1035)
* Don't use animateItem's fade-in/fade-out in FastScrollLazyColumn

* Move to extension function

Avoid using animateItemPlacement name since it's shadowed by compose-bom's deprecated one

(cherry picked from commit 913ff22132390a59a13c463645ce954c7cbc5c6b)
2024-10-14 13:50:10 -04:00

228 lines
7.4 KiB
Kotlin

package eu.kanade.presentation.browse
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import eu.kanade.presentation.browse.components.BrowseSourceFloatingActionButton
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
import eu.kanade.presentation.components.AppBarTitle
import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.presentation.util.animateItemFastScroll
import kotlinx.collections.immutable.ImmutableList
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.model.FeedSavedSearch
import tachiyomi.domain.source.model.SavedSearch
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.LoadingScreen
import tachiyomi.presentation.core.util.plus
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(MR.strings.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(MR.strings.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(
name: String,
isLoading: Boolean,
items: ImmutableList<SourceFeedUI>,
hasFilters: Boolean,
onFabClick: () -> Unit,
onClickBrowse: () -> Unit,
onClickLatest: () -> Unit,
onClickSavedSearch: (SavedSearch) -> Unit,
onClickDelete: (FeedSavedSearch) -> Unit,
onClickManga: (Manga) -> Unit,
onClickSearch: (String) -> Unit,
searchQuery: String?,
onSearchQueryChange: (String?) -> Unit,
getMangaState: @Composable (Manga) -> State<Manga>,
) {
Scaffold(
topBar = { scrollBehavior ->
SourceFeedToolbar(
title = name,
searchQuery = searchQuery,
onSearchQueryChange = onSearchQueryChange,
scrollBehavior = scrollBehavior,
onClickSearch = onClickSearch,
)
},
floatingActionButton = {
BrowseSourceFloatingActionButton(
isVisible = hasFilters,
onFabClick = onFabClick,
)
},
) { paddingValues ->
Crossfade(targetState = isLoading, label = "source_feed") { state ->
when (state) {
true -> LoadingScreen()
false -> {
SourceFeedList(
items = items,
paddingValues = paddingValues,
getMangaState = getMangaState,
onClickBrowse = onClickBrowse,
onClickLatest = onClickLatest,
onClickSavedSearch = onClickSavedSearch,
onClickDelete = onClickDelete,
onClickManga = onClickManga,
)
}
}
}
}
}
@Composable
fun SourceFeedList(
items: ImmutableList<SourceFeedUI>,
paddingValues: PaddingValues,
getMangaState: @Composable ((Manga) -> State<Manga>),
onClickBrowse: () -> Unit,
onClickLatest: () -> Unit,
onClickSavedSearch: (SavedSearch) -> Unit,
onClickDelete: (FeedSavedSearch) -> Unit,
onClickManga: (Manga) -> Unit,
) {
ScrollbarLazyColumn(
contentPadding = paddingValues + topSmallPaddingValues,
) {
items(
items,
key = { it.id },
) { item ->
GlobalSearchResultItem(
modifier = Modifier.animateItemFastScroll(),
title = item.title,
subtitle = null,
onLongClick = if (item is SourceFeedUI.SourceSavedSearch) {
{
onClickDelete(item.feed)
}
} else {
null
},
onClick = when (item) {
is SourceFeedUI.Browse -> onClickBrowse
is SourceFeedUI.Latest -> onClickLatest
is SourceFeedUI.SourceSavedSearch -> {
{ onClickSavedSearch(item.savedSearch) }
}
},
) {
SourceFeedItem(
item = item,
getMangaState = { getMangaState(it) },
onClickManga = onClickManga,
)
}
}
}
}
@Composable
fun SourceFeedItem(
item: SourceFeedUI,
getMangaState: @Composable ((Manga) -> State<Manga>),
onClickManga: (Manga) -> Unit,
) {
val results = item.results
when {
results == null -> {
GlobalSearchLoadingResultItem()
}
results.isEmpty() -> {
GlobalSearchErrorResultItem(message = stringResource(MR.strings.no_results_found))
}
else -> {
GlobalSearchCardRow(
titles = item.results.orEmpty(),
getManga = getMangaState,
onClick = onClickManga,
onLongClick = onClickManga,
)
}
}
}
@Composable
fun SourceFeedToolbar(
title: String,
searchQuery: String?,
onSearchQueryChange: (String?) -> Unit,
scrollBehavior: TopAppBarScrollBehavior,
onClickSearch: (String) -> Unit,
) {
SearchToolbar(
titleContent = { AppBarTitle(title) },
searchQuery = searchQuery,
onChangeSearchQuery = onSearchQueryChange,
onSearch = onClickSearch,
onClickCloseSearch = { onSearchQueryChange(null) },
scrollBehavior = scrollBehavior,
placeholderText = stringResource(MR.strings.action_search_hint),
)
}