komikku/app/src/main/java/eu/kanade/presentation/browse/SourceFeedScreen.kt
2023-02-07 13:04:41 -05:00

224 lines
7.2 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 androidx.compose.ui.res.stringResource
import eu.kanade.domain.manga.model.Manga
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.LoadingScreen
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.presentation.util.plus
import eu.kanade.presentation.util.topSmallPaddingValues
import eu.kanade.tachiyomi.R
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(
name: String,
isLoading: Boolean,
items: List<SourceFeedUI>,
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 = onFabClick != null,
onFabClick = onFabClick ?: {},
)
},
) { paddingValues ->
Crossfade(targetState = isLoading) { 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: List<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.animateItemPlacement(),
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(R.string.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(R.string.action_search_hint),
)
}