Use Compose on Global/Migrate Search screen (#8631)

* Use Compose on Global/Migrate Search screen

- Refactor to use Voyager and Compose
- Use sealed class for state
- Somethings are broken/missing due to screens using different navigation libraries

* Review changes
This commit is contained in:
Andreas
2022-11-27 20:56:21 +01:00
committed by GitHub
parent ac1bed38f9
commit f99b62a069
38 changed files with 1229 additions and 1506 deletions

View File

@@ -0,0 +1,13 @@
package eu.kanade.presentation.browse
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.components.Badge
import eu.kanade.tachiyomi.R
@Composable
fun InLibraryBadge(enabled: Boolean) {
if (enabled) {
Badge(text = stringResource(R.string.in_library))
}
}

View File

@@ -0,0 +1,111 @@
package eu.kanade.presentation.browse
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.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.browse.components.GlobalSearchToolbar
import eu.kanade.presentation.components.LazyColumn
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.util.padding
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchItemResult
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchState
import eu.kanade.tachiyomi.util.system.LocaleHelper
@Composable
fun GlobalSearchScreen(
state: GlobalSearchState,
navigateUp: () -> Unit,
onChangeSearchQuery: (String?) -> Unit,
onSearch: (String) -> Unit,
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>,
onClickSource: (CatalogueSource) -> Unit,
onClickItem: (Manga) -> Unit,
onLongClickItem: (Manga) -> Unit,
) {
Scaffold(
topBar = {
GlobalSearchToolbar(
searchQuery = state.searchQuery,
progress = state.progress,
total = state.total,
navigateUp = navigateUp,
onChangeSearchQuery = onChangeSearchQuery,
onSearch = onSearch,
)
},
) { paddingValues ->
GlobalSearchContent(
items = state.items,
contentPadding = paddingValues,
getManga = getManga,
onClickSource = onClickSource,
onClickItem = onClickItem,
onLongClickItem = onLongClickItem,
)
}
}
@Composable
fun GlobalSearchContent(
items: Map<CatalogueSource, GlobalSearchItemResult>,
contentPadding: PaddingValues,
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>,
onClickSource: (CatalogueSource) -> Unit,
onClickItem: (Manga) -> Unit,
onLongClickItem: (Manga) -> Unit,
) {
LazyColumn(
contentPadding = contentPadding,
) {
items.forEach { (source, result) ->
item {
GlobalSearchResultItem(
title = source.name,
subtitle = LocaleHelper.getDisplayName(source.lang),
onClick = { onClickSource(source) },
) {
when (result) {
is GlobalSearchItemResult.Error -> {
GlobalSearchErrorResultItem(message = result.throwable.message)
}
GlobalSearchItemResult.Loading -> {
GlobalSearchLoadingResultItem()
}
is GlobalSearchItemResult.Success -> {
if (result.isEmpty) {
Text(
text = stringResource(id = R.string.no_results_found),
modifier = Modifier
.padding(
horizontal = MaterialTheme.padding.medium,
vertical = MaterialTheme.padding.small,
),
)
return@GlobalSearchResultItem
}
GlobalSearchCardRow(
titles = result.result,
getManga = { getManga(source, it) },
onClick = onClickItem,
onLongClick = onLongClickItem,
)
}
}
}
}
}
}
}

View File

@@ -0,0 +1,100 @@
package eu.kanade.presentation.browse
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import eu.kanade.domain.manga.model.Manga
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
import eu.kanade.presentation.browse.components.GlobalSearchEmptyResultItem
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.browse.components.GlobalSearchToolbar
import eu.kanade.presentation.components.LazyColumn
import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchState
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchItemResult
import eu.kanade.tachiyomi.util.system.LocaleHelper
@Composable
fun MigrateSearchScreen(
navigateUp: () -> Unit,
state: MigrateSearchState,
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>,
onChangeSearchQuery: (String?) -> Unit,
onSearch: (String) -> Unit,
onClickSource: (CatalogueSource) -> Unit,
onClickItem: (Manga) -> Unit,
onLongClickItem: (Manga) -> Unit,
) {
Scaffold(
topBar = {
GlobalSearchToolbar(
searchQuery = state.searchQuery,
progress = state.progress,
total = state.total,
navigateUp = navigateUp,
onChangeSearchQuery = onChangeSearchQuery,
onSearch = onSearch,
)
},
) { paddingValues ->
MigrateSearchContent(
sourceId = state.manga?.source ?: -1,
items = state.items,
contentPadding = paddingValues,
getManga = getManga,
onClickSource = onClickSource,
onClickItem = onClickItem,
onLongClickItem = onLongClickItem,
)
}
}
@Composable
fun MigrateSearchContent(
sourceId: Long,
items: Map<CatalogueSource, GlobalSearchItemResult>,
contentPadding: PaddingValues,
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>,
onClickSource: (CatalogueSource) -> Unit,
onClickItem: (Manga) -> Unit,
onLongClickItem: (Manga) -> Unit,
) {
LazyColumn(
contentPadding = contentPadding,
) {
items.forEach { (source, result) ->
item {
GlobalSearchResultItem(
title = if (source.id == sourceId) "${source.name}" else source.name,
subtitle = LocaleHelper.getDisplayName(source.lang),
onClick = { onClickSource(source) },
) {
when (result) {
is GlobalSearchItemResult.Error -> {
GlobalSearchErrorResultItem(message = result.throwable.message)
}
GlobalSearchItemResult.Loading -> {
GlobalSearchLoadingResultItem()
}
is GlobalSearchItemResult.Success -> {
if (result.isEmpty) {
GlobalSearchEmptyResultItem()
return@GlobalSearchResultItem
}
GlobalSearchCardRow(
titles = result.result,
getManga = { getManga(source, it) },
onClick = onClickItem,
onLongClick = onLongClickItem,
)
}
}
}
}
}
}
}

View File

@@ -8,17 +8,15 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.MangaCover
import eu.kanade.presentation.components.Badge
import eu.kanade.presentation.browse.InLibraryBadge
import eu.kanade.presentation.components.CommonMangaItemDefaults
import eu.kanade.presentation.components.MangaComfortableGridItem
import eu.kanade.presentation.util.plus
import eu.kanade.tachiyomi.R
@Composable
fun BrowseSourceComfortableGrid(
@@ -76,9 +74,7 @@ fun BrowseSourceComfortableGridItem(
),
coverAlpha = if (manga.favorite) CommonMangaItemDefaults.BrowseFavoriteCoverAlpha else 1f,
coverBadgeStart = {
if (manga.favorite) {
Badge(text = stringResource(R.string.in_library))
}
InLibraryBadge(enabled = manga.favorite)
},
onLongClick = onLongClick,
onClick = onClick,

View File

@@ -8,17 +8,15 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.MangaCover
import eu.kanade.presentation.components.Badge
import eu.kanade.presentation.browse.InLibraryBadge
import eu.kanade.presentation.components.CommonMangaItemDefaults
import eu.kanade.presentation.components.MangaCompactGridItem
import eu.kanade.presentation.util.plus
import eu.kanade.tachiyomi.R
@Composable
fun BrowseSourceCompactGrid(
@@ -76,9 +74,7 @@ private fun BrowseSourceCompactGridItem(
),
coverAlpha = if (manga.favorite) CommonMangaItemDefaults.BrowseFavoriteCoverAlpha else 1f,
coverBadgeStart = {
if (manga.favorite) {
Badge(text = stringResource(R.string.in_library))
}
InLibraryBadge(enabled = manga.favorite)
},
onLongClick = onLongClick,
onClick = onClick,

View File

@@ -4,19 +4,17 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.items
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.MangaCover
import eu.kanade.presentation.components.Badge
import eu.kanade.presentation.browse.InLibraryBadge
import eu.kanade.presentation.components.CommonMangaItemDefaults
import eu.kanade.presentation.components.LazyColumn
import eu.kanade.presentation.components.MangaListItem
import eu.kanade.presentation.util.plus
import eu.kanade.tachiyomi.R
@Composable
fun BrowseSourceList(
@@ -70,9 +68,7 @@ fun BrowseSourceListItem(
),
coverAlpha = if (manga.favorite) CommonMangaItemDefaults.BrowseFavoriteCoverAlpha else 1f,
badge = {
if (manga.favorite) {
Badge(text = stringResource(R.string.in_library))
}
InLibraryBadge(enabled = manga.favorite)
},
onLongClick = onLongClick,
onClick = onClick,

View File

@@ -0,0 +1,40 @@
package eu.kanade.presentation.browse.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.asMangaCover
import eu.kanade.presentation.util.padding
@Composable
fun GlobalSearchCardRow(
titles: List<Manga>,
getManga: @Composable (Manga) -> State<Manga>,
onClick: (Manga) -> Unit,
onLongClick: (Manga) -> Unit,
) {
LazyRow(
contentPadding = PaddingValues(
horizontal = MaterialTheme.padding.medium,
vertical = MaterialTheme.padding.small,
),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) {
items(titles) { title ->
val title by getManga(title)
GlobalSearchCard(
title = title.title,
cover = title.asMangaCover(),
isFavorite = title.favorite,
onClick = { onClick(title) },
onLongClick = { onLongClick(title) },
)
}
}
}

View File

@@ -0,0 +1,101 @@
package eu.kanade.presentation.browse.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowForward
import androidx.compose.material.icons.outlined.Error
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.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.util.padding
import eu.kanade.tachiyomi.R
@Composable
fun GlobalSearchResultItem(
title: String,
subtitle: String,
onClick: () -> Unit,
content: @Composable () -> Unit,
) {
Column {
Row(
modifier = Modifier
.padding(
start = MaterialTheme.padding.medium,
end = MaterialTheme.padding.tiny,
)
.fillMaxWidth()
.clickable(onClick = onClick),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Column {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
)
Text(text = subtitle)
}
IconButton(onClick = onClick) {
Icon(imageVector = Icons.Outlined.ArrowForward, contentDescription = null)
}
}
content()
}
}
@Composable
fun GlobalSearchEmptyResultItem() {
Text(
text = stringResource(id = R.string.no_results_found),
modifier = Modifier
.padding(
horizontal = MaterialTheme.padding.medium,
vertical = MaterialTheme.padding.small,
),
)
}
@Composable
fun GlobalSearchLoadingResultItem() {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = MaterialTheme.padding.medium),
) {
CircularProgressIndicator(
modifier = Modifier
.size(16.dp)
.align(Alignment.Center),
strokeWidth = 2.dp,
)
}
}
@Composable
fun GlobalSearchErrorResultItem(message: String?) {
Column(
modifier = Modifier
.padding(vertical = MaterialTheme.padding.medium)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Icon(imageVector = Icons.Outlined.Error, contentDescription = null)
Text(text = message ?: stringResource(id = R.string.unknown_error))
}
}

View File

@@ -0,0 +1,36 @@
package eu.kanade.presentation.browse.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import eu.kanade.presentation.components.SearchToolbar
@Composable
fun GlobalSearchToolbar(
searchQuery: String?,
progress: Int,
total: Int,
navigateUp: () -> Unit,
onChangeSearchQuery: (String?) -> Unit,
onSearch: (String) -> Unit,
) {
Box {
SearchToolbar(
searchQuery = searchQuery,
onChangeSearchQuery = onChangeSearchQuery,
onSearch = onSearch,
navigateUp = navigateUp,
)
if (progress in 1 until total) {
LinearProgressIndicator(
progress = progress / total.toFloat(),
modifier = Modifier
.align(Alignment.BottomStart)
.fillMaxWidth(),
)
}
}
}

View File

@@ -0,0 +1,33 @@
package eu.kanade.presentation.browse.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import eu.kanade.domain.manga.model.MangaCover
import eu.kanade.presentation.browse.InLibraryBadge
import eu.kanade.presentation.components.CommonMangaItemDefaults
import eu.kanade.presentation.components.MangaComfortableGridItem
@Composable
fun GlobalSearchCard(
title: String,
cover: MangaCover,
isFavorite: Boolean,
onClick: () -> Unit,
onLongClick: () -> Unit,
) {
Box(modifier = Modifier.width(128.dp)) {
MangaComfortableGridItem(
title = title,
coverData = cover,
coverBadgeStart = {
InLibraryBadge(enabled = isFavorite)
},
coverAlpha = if (isFavorite) CommonMangaItemDefaults.BrowseFavoriteCoverAlpha else 1f,
onClick = onClick,
onLongClick = onLongClick,
)
}
}