Touch up manga grid/list items (#8307)

* Touch up library item touch indicator

Now the touch indicator has the same coverage as the selection indicator.
Experimental Modifier.Node API is used to draw the selection indicator

* Unify library and browse source list item layouts
This commit is contained in:
Ivan Iskandar
2022-10-28 22:46:10 +07:00
committed by GitHub
parent e36d31bf0f
commit 87391832ba
12 changed files with 470 additions and 679 deletions

View File

@@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import eu.kanade.presentation.components.CommonMangaItemDefaults
import eu.kanade.presentation.components.FastScrollLazyVerticalGrid
import eu.kanade.presentation.util.plus
import eu.kanade.tachiyomi.R
@@ -26,9 +27,9 @@ fun LazyLibraryGrid(
FastScrollLazyVerticalGrid(
columns = if (columns == 0) GridCells.Adaptive(128.dp) else GridCells.Fixed(columns),
modifier = modifier,
contentPadding = contentPadding + PaddingValues(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
contentPadding = contentPadding + PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridVerticalSpacer),
horizontalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridHorizontalSpacer),
content = content,
)
}

View File

@@ -1,21 +1,14 @@
package eu.kanade.presentation.library.components
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastAny
import eu.kanade.domain.library.model.LibraryManga
import eu.kanade.domain.manga.model.MangaCover
import eu.kanade.presentation.components.MangaComfortableGridItem
import eu.kanade.tachiyomi.ui.library.LibraryItem
@Composable
@@ -44,76 +37,37 @@ fun LibraryComfortableGrid(
items = items,
contentType = { "library_comfortable_grid_item" },
) { libraryItem ->
LibraryComfortableGridItem(
item = libraryItem,
showDownloadBadge = showDownloadBadges,
showUnreadBadge = showUnreadBadges,
showLocalBadge = showLocalBadges,
showLanguageBadge = showLanguageBadges,
val manga = libraryItem.libraryManga.manga
MangaComfortableGridItem(
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
onClick = onClick,
onLongClick = onLongClick,
title = manga.title,
coverData = MangaCover(
mangaId = manga.id,
sourceId = manga.source,
isMangaFavorite = manga.favorite,
url = manga.thumbnailUrl,
lastModified = manga.coverLastModified,
),
coverBadgeStart = {
DownloadsBadge(
enabled = showDownloadBadges,
item = libraryItem,
)
UnreadBadge(
enabled = showUnreadBadges,
item = libraryItem,
)
},
coverBadgeEnd = {
LanguageBadge(
showLanguage = showLanguageBadges,
showLocal = showLocalBadges,
item = libraryItem,
)
},
onLongClick = { onLongClick(libraryItem.libraryManga) },
onClick = { onClick(libraryItem.libraryManga) },
)
}
}
}
@Composable
fun LibraryComfortableGridItem(
item: LibraryItem,
showDownloadBadge: Boolean,
showUnreadBadge: Boolean,
showLocalBadge: Boolean,
showLanguageBadge: Boolean,
isSelected: Boolean,
onClick: (LibraryManga) -> Unit,
onLongClick: (LibraryManga) -> Unit,
) {
val libraryManga = item.libraryManga
val manga = libraryManga.manga
LibraryGridItemSelectable(isSelected = isSelected) {
Column(
modifier = Modifier
.combinedClickable(
onClick = {
onClick(libraryManga)
},
onLongClick = {
onLongClick(libraryManga)
},
),
) {
LibraryGridCover(
mangaCover = MangaCover(
manga.id,
manga.source,
manga.favorite,
manga.thumbnailUrl,
manga.coverLastModified,
),
item = item,
showDownloadBadge = showDownloadBadge,
showUnreadBadge = showUnreadBadge,
showLocalBadge = showLocalBadge,
showLanguageBadge = showLanguageBadge,
)
MangaGridComfortableText(
text = manga.title,
)
}
}
}
@Composable
fun MangaGridComfortableText(
text: String,
) {
Text(
modifier = Modifier.padding(4.dp),
text = text,
fontSize = 12.sp,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleSmall,
)
}

View File

@@ -1,35 +1,20 @@
package eu.kanade.presentation.library.components
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.RoundedCornerShape
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.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastAny
import eu.kanade.domain.library.model.LibraryManga
import eu.kanade.domain.manga.model.MangaCover
import eu.kanade.presentation.components.MangaCompactGridItem
import eu.kanade.tachiyomi.ui.library.LibraryItem
@Composable
fun LibraryCompactGrid(
items: List<LibraryItem>,
showTitle: Boolean,
showDownloadBadges: Boolean,
showUnreadBadges: Boolean,
showLocalBadges: Boolean,
@@ -53,92 +38,37 @@ fun LibraryCompactGrid(
items = items,
contentType = { "library_compact_grid_item" },
) { libraryItem ->
LibraryCompactGridItem(
item = libraryItem,
showDownloadBadge = showDownloadBadges,
showUnreadBadge = showUnreadBadges,
showLocalBadge = showLocalBadges,
showLanguageBadge = showLanguageBadges,
val manga = libraryItem.libraryManga.manga
MangaCompactGridItem(
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
onClick = onClick,
onLongClick = onLongClick,
title = manga.title.takeIf { showTitle },
coverData = MangaCover(
mangaId = manga.id,
sourceId = manga.source,
isMangaFavorite = manga.favorite,
url = manga.thumbnailUrl,
lastModified = manga.coverLastModified,
),
coverBadgeStart = {
DownloadsBadge(
enabled = showDownloadBadges,
item = libraryItem,
)
UnreadBadge(
enabled = showUnreadBadges,
item = libraryItem,
)
},
coverBadgeEnd = {
LanguageBadge(
showLanguage = showLanguageBadges,
showLocal = showLocalBadges,
item = libraryItem,
)
},
onLongClick = { onLongClick(libraryItem.libraryManga) },
onClick = { onClick(libraryItem.libraryManga) },
)
}
}
}
@Composable
fun LibraryCompactGridItem(
item: LibraryItem,
showDownloadBadge: Boolean,
showUnreadBadge: Boolean,
showLocalBadge: Boolean,
showLanguageBadge: Boolean,
isSelected: Boolean,
onClick: (LibraryManga) -> Unit,
onLongClick: (LibraryManga) -> Unit,
) {
val libraryManga = item.libraryManga
val manga = libraryManga.manga
LibraryGridCover(
modifier = Modifier
.selectedOutline(isSelected)
.combinedClickable(
onClick = {
onClick(libraryManga)
},
onLongClick = {
onLongClick(libraryManga)
},
),
mangaCover = eu.kanade.domain.manga.model.MangaCover(
manga.id,
manga.source,
manga.favorite,
manga.thumbnailUrl,
manga.coverLastModified,
),
item = item,
showDownloadBadge = showDownloadBadge,
showUnreadBadge = showUnreadBadge,
showLocalBadge = showLocalBadge,
showLanguageBadge = showLanguageBadge,
) {
Box(
modifier = Modifier
.clip(RoundedCornerShape(bottomStart = 4.dp, bottomEnd = 4.dp))
.background(
Brush.verticalGradient(
0f to Color.Transparent,
1f to Color(0xAA000000),
),
)
.fillMaxHeight(0.33f)
.fillMaxWidth()
.align(Alignment.BottomCenter),
)
MangaGridCompactText(manga.title)
}
}
@Composable
fun BoxScope.MangaGridCompactText(
text: String,
) {
Text(
text = text,
modifier = Modifier
.padding(8.dp)
.align(Alignment.BottomStart),
color = Color.White,
fontSize = 12.sp,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleSmall.copy(
shadow = Shadow(
color = Color.Black,
blurRadius = 4f,
),
),
)
}

View File

@@ -1,90 +0,0 @@
package eu.kanade.presentation.library.components
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.util.fastAny
import eu.kanade.domain.library.model.LibraryManga
import eu.kanade.tachiyomi.ui.library.LibraryItem
@Composable
fun LibraryCoverOnlyGrid(
items: List<LibraryItem>,
showDownloadBadges: Boolean,
showUnreadBadges: Boolean,
showLocalBadges: Boolean,
showLanguageBadges: Boolean,
columns: Int,
contentPadding: PaddingValues,
selection: List<LibraryManga>,
onClick: (LibraryManga) -> Unit,
onLongClick: (LibraryManga) -> Unit,
searchQuery: String?,
onGlobalSearchClicked: () -> Unit,
) {
LazyLibraryGrid(
modifier = Modifier.fillMaxSize(),
columns = columns,
contentPadding = contentPadding,
) {
globalSearchItem(searchQuery, onGlobalSearchClicked)
items(
items = items,
contentType = { "library_only_cover_grid_item" },
) { libraryItem ->
LibraryCoverOnlyGridItem(
item = libraryItem,
showDownloadBadge = showDownloadBadges,
showUnreadBadge = showUnreadBadges,
showLocalBadge = showLocalBadges,
showLanguageBadge = showLanguageBadges,
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
onClick = onClick,
onLongClick = onLongClick,
)
}
}
}
@Composable
fun LibraryCoverOnlyGridItem(
item: LibraryItem,
showDownloadBadge: Boolean,
showUnreadBadge: Boolean,
showLocalBadge: Boolean,
showLanguageBadge: Boolean,
isSelected: Boolean,
onClick: (LibraryManga) -> Unit,
onLongClick: (LibraryManga) -> Unit,
) {
val libraryManga = item.libraryManga
val manga = libraryManga.manga
LibraryGridCover(
modifier = Modifier
.selectedOutline(isSelected)
.combinedClickable(
onClick = {
onClick(libraryManga)
},
onLongClick = {
onLongClick(libraryManga)
},
),
mangaCover = eu.kanade.domain.manga.model.MangaCover(
manga.id,
manga.source,
manga.favorite,
manga.thumbnailUrl,
manga.coverLastModified,
),
item = item,
showDownloadBadge = showDownloadBadge,
showUnreadBadge = showUnreadBadge,
showLocalBadge = showLocalBadge,
showLanguageBadge = showLanguageBadge,
)
}

View File

@@ -1,80 +0,0 @@
package eu.kanade.presentation.library.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.components.BadgeGroup
import eu.kanade.presentation.components.MangaCover
import eu.kanade.tachiyomi.ui.library.LibraryItem
@Composable
fun MangaGridCover(
modifier: Modifier = Modifier,
cover: @Composable BoxScope.() -> Unit = {},
badgesStart: (@Composable RowScope.() -> Unit)? = null,
badgesEnd: (@Composable RowScope.() -> Unit)? = null,
content: @Composable BoxScope.() -> Unit = {},
) {
Box(
modifier = modifier
.fillMaxWidth()
.aspectRatio(MangaCover.Book.ratio),
) {
cover()
content()
if (badgesStart != null) {
BadgeGroup(
modifier = Modifier
.padding(4.dp)
.align(Alignment.TopStart),
content = badgesStart,
)
}
if (badgesEnd != null) {
BadgeGroup(
modifier = Modifier
.padding(4.dp)
.align(Alignment.TopEnd),
content = badgesEnd,
)
}
}
}
@Composable
fun LibraryGridCover(
modifier: Modifier = Modifier,
mangaCover: eu.kanade.domain.manga.model.MangaCover,
item: LibraryItem,
showDownloadBadge: Boolean,
showUnreadBadge: Boolean,
showLocalBadge: Boolean,
showLanguageBadge: Boolean,
content: @Composable BoxScope.() -> Unit = {},
) {
MangaGridCover(
modifier = modifier,
cover = {
MangaCover.Book(
modifier = Modifier.fillMaxWidth(),
data = mangaCover,
)
},
badgesStart = {
DownloadsBadge(enabled = showDownloadBadge, item = item)
UnreadBadge(enabled = showUnreadBadge, item = item)
},
badgesEnd = {
LanguageBadge(showLanguage = showLanguageBadge, showLocal = showLocalBadge, item = item)
},
content = content,
)
}

View File

@@ -1,46 +0,0 @@
package eu.kanade.presentation.library.components
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.unit.dp
fun Modifier.selectedOutline(isSelected: Boolean) = composed {
val secondary = MaterialTheme.colorScheme.secondary
if (isSelected) {
drawBehind {
val additional = 24.dp.value
val offset = additional / 2
val height = size.height + additional
val width = size.width + additional
drawRoundRect(
color = secondary,
topLeft = Offset(-offset, -offset),
size = Size(width, height),
cornerRadius = CornerRadius(offset),
)
}
} else {
this
}
}
@Composable
fun LibraryGridItemSelectable(
isSelected: Boolean,
content: @Composable () -> Unit,
) {
Box(Modifier.selectedOutline(isSelected)) {
CompositionLocalProvider(LocalContentColor provides if (isSelected) MaterialTheme.colorScheme.onSecondary else MaterialTheme.colorScheme.onBackground) {
content()
}
}
}

View File

@@ -1,33 +1,21 @@
package eu.kanade.presentation.library.components
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
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.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastAny
import androidx.compose.ui.zIndex
import eu.kanade.domain.library.model.LibraryManga
import eu.kanade.domain.manga.model.MangaCover
import eu.kanade.presentation.components.BadgeGroup
import eu.kanade.presentation.components.FastScrollLazyColumn
import eu.kanade.presentation.components.MangaCover.Square
import eu.kanade.presentation.util.horizontalPadding
import eu.kanade.presentation.util.selectedBackground
import eu.kanade.presentation.util.verticalPadding
import eu.kanade.presentation.components.MangaListItem
import eu.kanade.presentation.util.plus
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.library.LibraryItem
@@ -47,7 +35,7 @@ fun LibraryList(
) {
FastScrollLazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = contentPadding,
contentPadding = contentPadding + PaddingValues(vertical = 8.dp),
) {
item {
if (searchQuery.isNullOrEmpty().not()) {
@@ -64,116 +52,25 @@ fun LibraryList(
items = items,
contentType = { "library_list_item" },
) { libraryItem ->
LibraryListItem(
item = libraryItem,
showDownloadBadge = showDownloadBadges,
showUnreadBadge = showUnreadBadges,
showLocalBadge = showLocalBadges,
showLanguageBadge = showLanguageBadges,
val manga = libraryItem.libraryManga.manga
MangaListItem(
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
onClick = onClick,
onLongClick = onLongClick,
title = manga.title,
coverData = MangaCover(
mangaId = manga.id,
sourceId = manga.source,
isMangaFavorite = manga.favorite,
url = manga.thumbnailUrl,
lastModified = manga.coverLastModified,
),
badge = {
DownloadsBadge(enabled = showDownloadBadges, item = libraryItem)
UnreadBadge(enabled = showUnreadBadges, item = libraryItem)
LanguageBadge(showLanguage = showLanguageBadges, showLocal = showLocalBadges, item = libraryItem)
},
onLongClick = { onLongClick(libraryItem.libraryManga) },
onClick = { onClick(libraryItem.libraryManga) },
)
}
}
}
@Composable
fun LibraryListItem(
item: LibraryItem,
showDownloadBadge: Boolean,
showUnreadBadge: Boolean,
showLocalBadge: Boolean,
showLanguageBadge: Boolean,
isSelected: Boolean,
onClick: (LibraryManga) -> Unit,
onLongClick: (LibraryManga) -> Unit,
) {
val libraryManga = item.libraryManga
val manga = libraryManga.manga
MangaListItem(
modifier = Modifier.selectedBackground(isSelected),
title = manga.title,
cover = MangaCover(
manga.id,
manga.source,
manga.favorite,
manga.thumbnailUrl,
manga.coverLastModified,
),
onClick = { onClick(libraryManga) },
onLongClick = { onLongClick(libraryManga) },
) {
DownloadsBadge(enabled = showDownloadBadge, item = item)
UnreadBadge(enabled = showUnreadBadge, item = item)
LanguageBadge(showLanguage = showLanguageBadge, showLocal = showLocalBadge, item = item)
}
}
@Composable
fun MangaListItem(
modifier: Modifier = Modifier,
title: String,
cover: MangaCover,
onClick: () -> Unit,
onLongClick: () -> Unit = onClick,
badges: @Composable RowScope.() -> Unit,
) {
MangaListItem(
modifier = modifier,
coverContent = {
Square(
modifier = Modifier
.padding(vertical = verticalPadding)
.fillMaxHeight(),
data = cover,
)
},
badges = badges,
onClick = onClick,
onLongClick = onLongClick,
content = {
MangaListItemContent(title)
},
)
}
@Composable
fun MangaListItem(
modifier: Modifier = Modifier,
coverContent: @Composable RowScope.() -> Unit,
badges: @Composable RowScope.() -> Unit,
onClick: () -> Unit,
onLongClick: () -> Unit,
content: @Composable RowScope.() -> Unit,
) {
Row(
modifier = modifier
.height(56.dp)
.combinedClickable(
onClick = onClick,
onLongClick = onLongClick,
)
.padding(horizontal = horizontalPadding),
verticalAlignment = Alignment.CenterVertically,
) {
coverContent()
content()
BadgeGroup(content = badges)
}
}
@Composable
fun RowScope.MangaListItemContent(
text: String,
) {
Text(
text = text,
modifier = Modifier
.padding(horizontal = horizontalPadding)
.weight(1f),
maxLines = 2,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.bodyMedium,
)
}

View File

@@ -72,9 +72,10 @@ fun LibraryPager(
onGlobalSearchClicked = onGlobalSearchClicked,
)
}
LibraryDisplayMode.CompactGrid -> {
LibraryDisplayMode.CompactGrid, LibraryDisplayMode.CoverOnlyGrid -> {
LibraryCompactGrid(
items = library,
showTitle = displayMode is LibraryDisplayMode.CompactGrid,
showDownloadBadges = showDownloadBadges,
showUnreadBadges = showUnreadBadges,
showLocalBadges = showLocalBadges,
@@ -104,22 +105,6 @@ fun LibraryPager(
onGlobalSearchClicked = onGlobalSearchClicked,
)
}
LibraryDisplayMode.CoverOnlyGrid -> {
LibraryCoverOnlyGrid(
items = library,
showDownloadBadges = showDownloadBadges,
showUnreadBadges = showUnreadBadges,
showLocalBadges = showLocalBadges,
showLanguageBadges = showLanguageBadges,
columns = columns,
contentPadding = contentPadding,
selection = selectedManga,
onClick = onClickManga,
onLongClick = onLongClickManga,
searchQuery = searchQuery,
onGlobalSearchClicked = onGlobalSearchClicked,
)
}
}
}
}