Use global search UI for Feed

This commit is contained in:
Jobobby04 2023-02-07 13:04:41 -05:00
parent 9d0560f48b
commit 2b1809b2b7
3 changed files with 97 additions and 233 deletions

View File

@ -1,32 +1,19 @@
package eu.kanade.presentation.browse package eu.kanade.presentation.browse
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column 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.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ContentAlpha
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowForward
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
@ -39,18 +26,16 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
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 androidx.compose.ui.unit.sp
import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.presentation.components.Badge import eu.kanade.presentation.browse.components.GlobalSearchCardRow
import eu.kanade.presentation.components.BadgeGroup 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.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.MangaCover
import eu.kanade.presentation.components.PullRefresh import eu.kanade.presentation.components.PullRefresh
import eu.kanade.presentation.components.ScrollbarLazyColumn import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.util.plus import eu.kanade.presentation.util.plus
@ -62,7 +47,6 @@ import exh.savedsearches.models.FeedSavedSearch
import exh.savedsearches.models.SavedSearch import exh.savedsearches.models.SavedSearch
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
import eu.kanade.domain.manga.model.MangaCover as MangaCoverData
data class FeedItemUI( data class FeedItemUI(
val feed: FeedSavedSearch, val feed: FeedSavedSearch,
@ -114,40 +98,10 @@ fun FeedScreen(
state.items.orEmpty(), state.items.orEmpty(),
key = { it.feed.id }, key = { it.feed.id },
) { item -> ) { item ->
FeedItem( GlobalSearchResultItem(
modifier = Modifier.animateItemPlacement(), modifier = Modifier.animateItemPlacement(),
item = item, title = item.title,
getMangaState = { getMangaState(it, item.source) }, subtitle = item.subtitle,
onClickSavedSearch = onClickSavedSearch,
onClickSource = onClickSource,
onClickDelete = onClickDelete,
onClickManga = onClickManga,
)
}
}
}
}
}
}
@Composable
fun FeedItem(
modifier: Modifier,
item: FeedItemUI,
getMangaState: @Composable ((Manga) -> State<Manga>),
onClickSavedSearch: (SavedSearch, CatalogueSource) -> Unit,
onClickSource: (CatalogueSource) -> Unit,
onClickDelete: (FeedSavedSearch) -> Unit,
onClickManga: (Manga) -> Unit,
) {
Column(
modifier then Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Row(
Modifier
.fillMaxWidth()
.combinedClickable(
onLongClick = { onLongClick = {
onClickDelete(item.feed) onClickDelete(item.feed)
}, },
@ -158,44 +112,10 @@ fun FeedItem(
onClickSource(item.source) onClickSource(item.source)
} }
}, },
),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) { ) {
Column(Modifier.padding(start = 16.dp)) { FeedItem(
Text( item = item,
text = item.title, getMangaState = { getMangaState(it, item.source) },
style = MaterialTheme.typography.bodyMedium,
)
Text(
text = item.subtitle,
style = MaterialTheme.typography.bodyMedium,
fontSize = 12.sp,
color = LocalContentColor.current.copy(alpha = ContentAlpha.high),
)
}
Icon(
imageVector = Icons.Default.ArrowForward,
contentDescription = stringResource(R.string.label_more),
modifier = Modifier.padding(16.dp),
)
}
when {
item.results == null -> {
CircularProgressIndicator()
}
item.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(item.results) {
val manga by getMangaState(it)
FeedCardItem(
manga = manga,
onClickManga = onClickManga, onClickManga = onClickManga,
) )
} }
@ -203,60 +123,31 @@ fun FeedItem(
} }
} }
} }
}
} }
@Composable @Composable
fun FeedCardItem( fun FeedItem(
modifier: Modifier = Modifier, item: FeedItemUI,
manga: Manga, getMangaState: @Composable ((Manga) -> State<Manga>),
onClickManga: (Manga) -> Unit, onClickManga: (Manga) -> Unit,
) { ) {
Column( when {
modifier item.results == null -> {
.padding(vertical = 4.dp) GlobalSearchLoadingResultItem()
.width(112.dp) }
.clip(RoundedCornerShape(4.dp)) item.results.isEmpty() -> {
.clickable(onClick = { onClickManga(manga) }) GlobalSearchErrorResultItem(message = stringResource(R.string.no_results_found))
.padding(4.dp), }
) { else -> {
Box( GlobalSearchCardRow(
modifier = Modifier titles = item.results,
.fillMaxWidth() getManga = getMangaState,
.aspectRatio(MangaCover.Book.ratio), onClick = onClickManga,
) { onLongClick = onClickManga,
MangaCover.Book(
modifier = Modifier
.fillMaxWidth()
.alpha(
if (manga.favorite) 0.3f else 1.0f,
),
data = MangaCoverData(
manga.id,
manga.source,
manga.favorite,
manga.thumbnailUrl,
manga.coverLastModified,
),
) )
BadgeGroup(
modifier = Modifier
.padding(4.dp)
.align(Alignment.TopStart),
) {
if (manga.favorite) {
Badge(text = stringResource(R.string.in_library))
} }
} }
}
Text(
modifier = Modifier.padding(4.dp),
text = manga.title,
fontSize = 12.sp,
maxLines = 2,
style = MaterialTheme.typography.titleSmall,
)
}
} }
@Composable @Composable

View File

@ -1,33 +1,20 @@
package eu.kanade.presentation.browse package eu.kanade.presentation.browse
import androidx.compose.animation.Crossfade 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.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
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.filled.ArrowForward
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.presentation.browse.components.BrowseSourceFloatingActionButton 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.AppBarTitle
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
@ -160,94 +147,61 @@ fun SourceFeedList(
contentPadding = paddingValues + topSmallPaddingValues, contentPadding = paddingValues + topSmallPaddingValues,
) { ) {
items( items(
items.orEmpty(), items,
key = { it.id }, key = { it.id },
) { item -> ) { item ->
SourceFeedItem( GlobalSearchResultItem(
modifier = Modifier.animateItemPlacement(), modifier = Modifier.animateItemPlacement(),
item = item, title = item.title,
getMangaState = getMangaState, subtitle = null,
onClickTitle = when (item) { onLongClick = if (item is SourceFeedUI.SourceSavedSearch) {
{
onClickDelete(item.feed)
}
} else {
null
},
onClick = when (item) {
is SourceFeedUI.Browse -> onClickBrowse is SourceFeedUI.Browse -> onClickBrowse
is SourceFeedUI.Latest -> onClickLatest is SourceFeedUI.Latest -> onClickLatest
is SourceFeedUI.SourceSavedSearch -> { is SourceFeedUI.SourceSavedSearch -> {
{ onClickSavedSearch(item.savedSearch) } { onClickSavedSearch(item.savedSearch) }
} }
}, },
onClickDelete = onClickDelete, ) {
SourceFeedItem(
item = item,
getMangaState = { getMangaState(it) },
onClickManga = onClickManga, onClickManga = onClickManga,
) )
} }
} }
}
} }
@Composable @Composable
fun SourceFeedItem( fun SourceFeedItem(
modifier: Modifier,
item: SourceFeedUI, item: SourceFeedUI,
getMangaState: @Composable ((Manga) -> State<Manga>), getMangaState: @Composable ((Manga) -> State<Manga>),
onClickTitle: () -> Unit,
onClickDelete: (FeedSavedSearch) -> Unit,
onClickManga: (Manga) -> 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(
imageVector = Icons.Default.ArrowForward,
contentDescription = stringResource(R.string.label_more),
modifier = Modifier.padding(16.dp),
)
}
val results = item.results val results = item.results
when { when {
results == null -> { results == null -> {
CircularProgressIndicator() GlobalSearchLoadingResultItem()
} }
results.isEmpty() -> { results.isEmpty() -> {
Text(stringResource(R.string.no_results_found), modifier = Modifier.padding(bottom = 16.dp)) GlobalSearchErrorResultItem(message = stringResource(R.string.no_results_found))
} }
else -> { else -> {
LazyRow( GlobalSearchCardRow(
Modifier.fillMaxWidth(), titles = item.results.orEmpty(),
contentPadding = PaddingValues(horizontal = 12.dp), getManga = getMangaState,
) { onClick = onClickManga,
items(results) { onLongClick = onClickManga,
val manga by getMangaState(it)
FeedCardItem(
manga = manga,
onClickManga = onClickManga,
) )
} }
} }
}
}
}
} }
@Composable @Composable

View File

@ -1,6 +1,7 @@
package eu.kanade.presentation.browse.components package eu.kanade.presentation.browse.components
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -29,12 +30,20 @@ import eu.kanade.tachiyomi.R
@Composable @Composable
fun GlobalSearchResultItem( fun GlobalSearchResultItem(
// SY -->
modifier: Modifier = Modifier,
// SY <--
title: String, title: String,
subtitle: String, // SY -->
subtitle: String?,
// SY <--
onClick: () -> Unit, onClick: () -> Unit,
// SY -->
onLongClick: (() -> Unit)? = null,
// SY <--
content: @Composable () -> Unit, content: @Composable () -> Unit,
) { ) {
Column { Column(modifier) {
Row( Row(
modifier = Modifier modifier = Modifier
.padding( .padding(
@ -42,7 +51,15 @@ fun GlobalSearchResultItem(
end = MaterialTheme.padding.tiny, end = MaterialTheme.padding.tiny,
) )
.fillMaxWidth() .fillMaxWidth()
.clickable(onClick = onClick), // SY -->
.let {
if (onLongClick == null) {
it.clickable(onClick = onClick)
} else {
it.combinedClickable(onClick = onClick, onLongClick = onLongClick)
}
},
// SY <--
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
@ -51,8 +68,10 @@ fun GlobalSearchResultItem(
text = title, text = title,
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
) )
if (subtitle != null) {
Text(text = subtitle) Text(text = subtitle)
} }
}
IconButton(onClick = onClick) { IconButton(onClick = onClick) {
Icon(imageVector = Icons.Outlined.ArrowForward, contentDescription = null) Icon(imageVector = Icons.Outlined.ArrowForward, contentDescription = null)
} }