Move library page EmptyScreens into list/grids

It does look awkward due to the lack of filled height within those list/grids though.

Fixes #8720
Fixes #8721
This commit is contained in:
arkon 2022-12-17 12:06:02 -05:00
parent 0e2bdb7863
commit 376bbeb724
6 changed files with 206 additions and 204 deletions

View File

@ -22,12 +22,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastFirstOrNull
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.util.ThemePreviews
import eu.kanade.presentation.util.secondaryItemAlpha
@ -54,68 +51,45 @@ fun EmptyScreen(
actions: List<EmptyScreenAction>? = null,
) {
val face = remember { getRandomErrorFace() }
Layout(
content = {
Column(
modifier = Modifier
.layoutId("face")
.padding(horizontal = 24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = face,
modifier = Modifier.secondaryItemAlpha(),
style = MaterialTheme.typography.displayMedium,
)
Column(
modifier = modifier
.fillMaxSize()
.padding(horizontal = 24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text(
text = face,
modifier = Modifier.secondaryItemAlpha(),
style = MaterialTheme.typography.displayMedium,
)
Text(
text = message,
modifier = Modifier.paddingFromBaseline(top = 24.dp).secondaryItemAlpha(),
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
)
}
if (!actions.isNullOrEmpty()) {
Row(
modifier = Modifier
.layoutId("actions")
.padding(
top = 24.dp,
start = horizontalPadding,
end = horizontalPadding,
),
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
) {
actions.forEach {
ActionButton(
modifier = Modifier.weight(1f),
title = stringResource(it.stringResId),
icon = it.icon,
onClick = it.onClick,
)
}
Text(
text = message,
modifier = Modifier.paddingFromBaseline(top = 24.dp).secondaryItemAlpha(),
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
)
if (!actions.isNullOrEmpty()) {
Row(
modifier = Modifier
.padding(
top = 24.dp,
start = 24.dp,
end = 24.dp,
),
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
) {
actions.forEach {
ActionButton(
modifier = Modifier.weight(1f),
title = stringResource(it.stringResId),
icon = it.icon,
onClick = it.onClick,
)
}
}
},
modifier = modifier.fillMaxSize(),
) { measurables, constraints ->
val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
val facePlaceable = measurables.fastFirstOrNull { it.layoutId == "face" }!!
.measure(looseConstraints)
val actionsPlaceable = measurables.fastFirstOrNull { it.layoutId == "actions" }
?.measure(looseConstraints)
layout(constraints.maxWidth, constraints.maxHeight) {
val faceY = (constraints.maxHeight - facePlaceable.height) / 2
facePlaceable.placeRelative(
x = (constraints.maxWidth - facePlaceable.width) / 2,
y = faceY,
)
actionsPlaceable?.placeRelative(
x = (constraints.maxWidth - actionsPlaceable.width) / 2,
y = faceY + facePlaceable.height,
)
}
}
}
@ -187,8 +161,6 @@ data class EmptyScreenAction(
val onClick: () -> Unit,
)
private val horizontalPadding = 24.dp
private val ERROR_FACES = listOf(
"(・o・;)",
"Σ(ಠ_ಠ)",

View File

@ -2,6 +2,7 @@ package eu.kanade.presentation.library.components
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@ -22,6 +23,7 @@ fun LibraryComfortableGrid(
onClickContinueReading: ((LibraryManga) -> Unit)?,
searchQuery: String?,
onGlobalSearchClicked: () -> Unit,
hasActiveFilters: Boolean,
) {
LazyLibraryGrid(
modifier = Modifier.fillMaxSize(),
@ -30,39 +32,48 @@ fun LibraryComfortableGrid(
) {
globalSearchItem(searchQuery, onGlobalSearchClicked)
items(
items = items,
contentType = { "library_comfortable_grid_item" },
) { libraryItem ->
val manga = libraryItem.libraryManga.manga
MangaComfortableGridItem(
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
title = manga.title,
coverData = MangaCover(
mangaId = manga.id,
sourceId = manga.source,
isMangaFavorite = manga.favorite,
url = manga.thumbnailUrl,
lastModified = manga.coverLastModified,
),
coverBadgeStart = {
DownloadsBadge(count = libraryItem.downloadCount.toInt())
UnreadBadge(count = libraryItem.unreadCount.toInt())
},
coverBadgeEnd = {
LanguageBadge(
isLocal = libraryItem.isLocal,
sourceLanguage = libraryItem.sourceLanguage,
)
},
onLongClick = { onLongClick(libraryItem.libraryManga) },
onClick = { onClick(libraryItem.libraryManga) },
onClickContinueReading = if (onClickContinueReading != null) {
{ onClickContinueReading(libraryItem.libraryManga) }
} else {
null
},
)
if (items.isEmpty()) {
item(
span = { GridItemSpan(maxLineSpan) },
contentType = "library_comfortable_grid_empty",
) {
LibraryPagerEmptyScreen(searchQuery, hasActiveFilters, contentPadding)
}
} else {
items(
items = items,
contentType = { "library_comfortable_grid_item" },
) { libraryItem ->
val manga = libraryItem.libraryManga.manga
MangaComfortableGridItem(
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
title = manga.title,
coverData = MangaCover(
mangaId = manga.id,
sourceId = manga.source,
isMangaFavorite = manga.favorite,
url = manga.thumbnailUrl,
lastModified = manga.coverLastModified,
),
coverBadgeStart = {
DownloadsBadge(count = libraryItem.downloadCount.toInt())
UnreadBadge(count = libraryItem.unreadCount.toInt())
},
coverBadgeEnd = {
LanguageBadge(
isLocal = libraryItem.isLocal,
sourceLanguage = libraryItem.sourceLanguage,
)
},
onLongClick = { onLongClick(libraryItem.libraryManga) },
onClick = { onClick(libraryItem.libraryManga) },
onClickContinueReading = if (onClickContinueReading != null) {
{ onClickContinueReading(libraryItem.libraryManga) }
} else {
null
},
)
}
}
}
}

View File

@ -2,6 +2,7 @@ package eu.kanade.presentation.library.components
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@ -23,6 +24,7 @@ fun LibraryCompactGrid(
onClickContinueReading: ((LibraryManga) -> Unit)?,
searchQuery: String?,
onGlobalSearchClicked: () -> Unit,
hasActiveFilters: Boolean,
) {
LazyLibraryGrid(
modifier = Modifier.fillMaxSize(),
@ -31,39 +33,48 @@ fun LibraryCompactGrid(
) {
globalSearchItem(searchQuery, onGlobalSearchClicked)
items(
items = items,
contentType = { "library_compact_grid_item" },
) { libraryItem ->
val manga = libraryItem.libraryManga.manga
MangaCompactGridItem(
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
title = manga.title.takeIf { showTitle },
coverData = MangaCover(
mangaId = manga.id,
sourceId = manga.source,
isMangaFavorite = manga.favorite,
url = manga.thumbnailUrl,
lastModified = manga.coverLastModified,
),
coverBadgeStart = {
DownloadsBadge(count = libraryItem.downloadCount.toInt())
UnreadBadge(count = libraryItem.unreadCount.toInt())
},
coverBadgeEnd = {
LanguageBadge(
isLocal = libraryItem.isLocal,
sourceLanguage = libraryItem.sourceLanguage,
)
},
onLongClick = { onLongClick(libraryItem.libraryManga) },
onClick = { onClick(libraryItem.libraryManga) },
onClickContinueReading = if (onClickContinueReading != null) {
{ onClickContinueReading(libraryItem.libraryManga) }
} else {
null
},
)
if (items.isEmpty()) {
item(
span = { GridItemSpan(maxLineSpan) },
contentType = "library_compact_grid_empty",
) {
LibraryPagerEmptyScreen(searchQuery, hasActiveFilters, contentPadding)
}
} else {
items(
items = items,
contentType = { "library_compact_grid_item" },
) { libraryItem ->
val manga = libraryItem.libraryManga.manga
MangaCompactGridItem(
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
title = manga.title.takeIf { showTitle },
coverData = MangaCover(
mangaId = manga.id,
sourceId = manga.source,
isMangaFavorite = manga.favorite,
url = manga.thumbnailUrl,
lastModified = manga.coverLastModified,
),
coverBadgeStart = {
DownloadsBadge(count = libraryItem.downloadCount.toInt())
UnreadBadge(count = libraryItem.unreadCount.toInt())
},
coverBadgeEnd = {
LanguageBadge(
isLocal = libraryItem.isLocal,
sourceLanguage = libraryItem.sourceLanguage,
)
},
onLongClick = { onLongClick(libraryItem.libraryManga) },
onClick = { onClick(libraryItem.libraryManga) },
onClickContinueReading = if (onClickContinueReading != null) {
{ onClickContinueReading(libraryItem.libraryManga) }
} else {
null
},
)
}
}
}
}

View File

@ -30,6 +30,7 @@ fun LibraryList(
onClickContinueReading: ((LibraryManga) -> Unit)?,
searchQuery: String?,
onGlobalSearchClicked: () -> Unit,
hasActiveFilters: Boolean,
) {
FastScrollLazyColumn(
modifier = Modifier.fillMaxSize(),
@ -49,37 +50,45 @@ fun LibraryList(
}
}
items(
items = items,
contentType = { "library_list_item" },
) { libraryItem ->
val manga = libraryItem.libraryManga.manga
MangaListItem(
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
title = manga.title,
coverData = MangaCover(
mangaId = manga.id,
sourceId = manga.source,
isMangaFavorite = manga.favorite,
url = manga.thumbnailUrl,
lastModified = manga.coverLastModified,
),
badge = {
DownloadsBadge(count = libraryItem.downloadCount.toInt())
UnreadBadge(count = libraryItem.unreadCount.toInt())
LanguageBadge(
isLocal = libraryItem.isLocal,
sourceLanguage = libraryItem.sourceLanguage,
)
},
onLongClick = { onLongClick(libraryItem.libraryManga) },
onClick = { onClick(libraryItem.libraryManga) },
onClickContinueReading = if (onClickContinueReading != null) {
{ onClickContinueReading(libraryItem.libraryManga) }
} else {
null
},
)
if (items.isEmpty()) {
item(
contentType = "library_list_empty",
) {
LibraryPagerEmptyScreen(searchQuery, hasActiveFilters, contentPadding)
}
} else {
items(
items = items,
contentType = { "library_list_item" },
) { libraryItem ->
val manga = libraryItem.libraryManga.manga
MangaListItem(
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
title = manga.title,
coverData = MangaCover(
mangaId = manga.id,
sourceId = manga.source,
isMangaFavorite = manga.favorite,
url = manga.thumbnailUrl,
lastModified = manga.coverLastModified,
),
badge = {
DownloadsBadge(count = libraryItem.downloadCount.toInt())
UnreadBadge(count = libraryItem.unreadCount.toInt())
LanguageBadge(
isLocal = libraryItem.isLocal,
sourceLanguage = libraryItem.sourceLanguage,
)
},
onLongClick = { onLongClick(libraryItem.libraryManga) },
onClick = { onClick(libraryItem.libraryManga) },
onClickContinueReading = if (onClickContinueReading != null) {
{ onClickContinueReading(libraryItem.libraryManga) }
} else {
null
},
)
}
}
}
}

View File

@ -48,11 +48,6 @@ fun LibraryPager(
}
val library = getLibraryForPage(page)
if (library.isEmpty()) {
LibraryPagerEmptyScreen(searchQuery, hasActiveFilters, contentPadding)
return@HorizontalPager
}
val displayMode = getDisplayModeForPage(page)
val columns by if (displayMode != LibraryDisplayMode.List) {
val configuration = LocalConfiguration.current
@ -74,6 +69,7 @@ fun LibraryPager(
onClickContinueReading = onClickContinueReading,
searchQuery = searchQuery,
onGlobalSearchClicked = onGlobalSearchClicked,
hasActiveFilters = hasActiveFilters,
)
}
LibraryDisplayMode.CompactGrid, LibraryDisplayMode.CoverOnlyGrid -> {
@ -88,6 +84,7 @@ fun LibraryPager(
onClickContinueReading = onClickContinueReading,
searchQuery = searchQuery,
onGlobalSearchClicked = onGlobalSearchClicked,
hasActiveFilters = hasActiveFilters,
)
}
LibraryDisplayMode.ComfortableGrid -> {
@ -101,6 +98,7 @@ fun LibraryPager(
onClickContinueReading = onClickContinueReading,
searchQuery = searchQuery,
onGlobalSearchClicked = onGlobalSearchClicked,
hasActiveFilters = hasActiveFilters,
)
}
}
@ -108,7 +106,7 @@ fun LibraryPager(
}
@Composable
private fun LibraryPagerEmptyScreen(
internal fun LibraryPagerEmptyScreen(
searchQuery: String?,
hasActiveFilters: Boolean,
contentPadding: PaddingValues,
@ -119,6 +117,7 @@ private fun LibraryPagerEmptyScreen(
else -> R.string.information_no_manga_category
}
// TODO: vertically center this better
EmptyScreen(
textResource = msg,
modifier = Modifier.padding(contentPadding),

View File

@ -212,43 +212,43 @@ private fun SearchResult(
}
Crossfade(targetState = result) {
LazyColumn(
modifier = modifier.fillMaxSize(),
state = listState,
contentPadding = contentPadding,
horizontalAlignment = Alignment.CenterHorizontally,
) {
when {
it == null -> {
/* Don't show anything just yet */
}
// No result
it.isEmpty() -> item { EmptyScreen(stringResource(R.string.no_results_found)) }
// Show result list
else -> items(
items = it,
key = { i -> i.hashCode() },
) { item ->
Column(
modifier = Modifier
.fillMaxWidth()
.clickable { onItemClick(item) }
.padding(horizontal = 24.dp, vertical = 14.dp),
) {
Text(
text = item.title,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
fontWeight = FontWeight.Normal,
style = MaterialTheme.typography.titleMedium,
)
Text(
text = item.breadcrumbs,
modifier = Modifier.paddingFromBaseline(top = 16.dp),
maxLines = 1,
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodySmall,
)
when {
it == null -> {}
it.isEmpty() -> {
EmptyScreen(stringResource(R.string.no_results_found))
}
else -> {
LazyColumn(
modifier = modifier.fillMaxSize(),
state = listState,
contentPadding = contentPadding,
horizontalAlignment = Alignment.CenterHorizontally,
) {
items(
items = it,
key = { i -> i.hashCode() },
) { item ->
Column(
modifier = Modifier
.fillMaxWidth()
.clickable { onItemClick(item) }
.padding(horizontal = 24.dp, vertical = 14.dp),
) {
Text(
text = item.title,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
fontWeight = FontWeight.Normal,
style = MaterialTheme.typography.titleMedium,
)
Text(
text = item.breadcrumbs,
modifier = Modifier.paddingFromBaseline(top = 16.dp),
maxLines = 1,
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodySmall,
)
}
}
}
}