From 87391832bafff1e9f90fdfe064725fe8d1ec0184 Mon Sep 17 00:00:00 2001 From: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Date: Fri, 28 Oct 2022 22:46:10 +0700 Subject: [PATCH] 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 --- .../components/BrowseSourceComfortableGrid.kt | 68 ++-- .../components/BrowseSourceCompactGrid.kt | 91 ++--- .../browse/components/BrowseSourceList.kt | 48 +-- .../components/CommonMangaItem.kt | 317 ++++++++++++++++++ .../library/components/LazyLibraryGrid.kt | 7 +- .../components/LibraryComfortableGrid.kt | 106 ++---- .../library/components/LibraryCompactGrid.kt | 134 ++------ .../components/LibraryCoverOnlyGrid.kt | 90 ----- .../library/components/LibraryGridCover.kt | 80 ----- .../components/LibraryGridItemSelectable.kt | 46 --- .../library/components/LibraryList.kt | 143 ++------ .../library/components/LibraryPager.kt | 19 +- 12 files changed, 470 insertions(+), 679 deletions(-) create mode 100644 app/src/main/java/eu/kanade/presentation/components/CommonMangaItem.kt delete mode 100644 app/src/main/java/eu/kanade/presentation/library/components/LibraryCoverOnlyGrid.kt delete mode 100644 app/src/main/java/eu/kanade/presentation/library/components/LibraryGridCover.kt delete mode 100644 app/src/main/java/eu/kanade/presentation/library/components/LibraryGridItemSelectable.kt diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceComfortableGrid.kt b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceComfortableGrid.kt index 7bea057af..3d4d0a0e2 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceComfortableGrid.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceComfortableGrid.kt @@ -1,28 +1,22 @@ package eu.kanade.presentation.browse.components -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.fillMaxWidth import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawWithContent 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.components.MangaCover -import eu.kanade.presentation.library.components.MangaGridComfortableText -import eu.kanade.presentation.library.components.MangaGridCover +import eu.kanade.presentation.components.CommonMangaItemDefaults +import eu.kanade.presentation.components.MangaComfortableGridItem import eu.kanade.presentation.util.plus import eu.kanade.tachiyomi.R @@ -37,9 +31,9 @@ fun BrowseSourceComfortableGrid( ) { LazyVerticalGrid( columns = columns, - contentPadding = PaddingValues(8.dp, 4.dp) + contentPadding, - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = contentPadding + PaddingValues(8.dp), + verticalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridVerticalSpacer), + horizontalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridHorizontalSpacer), ) { if (mangaList.loadState.prepend is LoadState.Loading) { item(span = { GridItemSpan(maxLineSpan) }) { @@ -71,36 +65,22 @@ fun BrowseSourceComfortableGridItem( onClick: () -> Unit = {}, onLongClick: () -> Unit = onClick, ) { - val overlayColor = MaterialTheme.colorScheme.background.copy(alpha = 0.66f) - Column( - modifier = Modifier - .combinedClickable( - onClick = onClick, - onLongClick = onLongClick, - ), - ) { - MangaGridCover( - cover = { - MangaCover.Book( - modifier = Modifier - .fillMaxWidth() - .drawWithContent { - drawContent() - if (manga.favorite) { - drawRect(overlayColor) - } - }, - data = manga.thumbnailUrl, - ) - }, - badgesStart = { - if (manga.favorite) { - Badge(text = stringResource(R.string.in_library)) - } - }, - ) - MangaGridComfortableText( - text = manga.title, - ) - } + MangaComfortableGridItem( + title = manga.title, + coverData = MangaCover( + mangaId = manga.id, + sourceId = manga.source, + isMangaFavorite = manga.favorite, + url = manga.thumbnailUrl, + lastModified = manga.coverLastModified, + ), + coverAlpha = if (manga.favorite) CommonMangaItemDefaults.BrowseFavoriteCoverAlpha else 1f, + coverBadgeStart = { + if (manga.favorite) { + Badge(text = stringResource(R.string.in_library)) + } + }, + onLongClick = onLongClick, + onClick = onClick, + ) } diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceCompactGrid.kt b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceCompactGrid.kt index 4d416634a..7c987ca91 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceCompactGrid.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceCompactGrid.kt @@ -1,35 +1,22 @@ package eu.kanade.presentation.browse.components -import androidx.compose.foundation.background -import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.drawWithContent -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color 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.components.MangaCover -import eu.kanade.presentation.library.components.MangaGridCompactText -import eu.kanade.presentation.library.components.MangaGridCover +import eu.kanade.presentation.components.CommonMangaItemDefaults +import eu.kanade.presentation.components.MangaCompactGridItem import eu.kanade.presentation.util.plus import eu.kanade.tachiyomi.R @@ -44,12 +31,12 @@ fun BrowseSourceCompactGrid( ) { LazyVerticalGrid( columns = columns, - contentPadding = PaddingValues(8.dp, 4.dp) + contentPadding, - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = contentPadding + PaddingValues(8.dp), + verticalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridVerticalSpacer), + horizontalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridHorizontalSpacer), ) { - item(span = { GridItemSpan(maxLineSpan) }) { - if (mangaList.loadState.prepend is LoadState.Loading) { + if (mangaList.loadState.prepend is LoadState.Loading) { + item(span = { GridItemSpan(maxLineSpan) }) { BrowseSourceLoadingItem() } } @@ -64,8 +51,8 @@ fun BrowseSourceCompactGrid( ) } - item(span = { GridItemSpan(maxLineSpan) }) { - if (mangaList.loadState.refresh is LoadState.Loading || mangaList.loadState.append is LoadState.Loading) { + if (mangaList.loadState.refresh is LoadState.Loading || mangaList.loadState.append is LoadState.Loading) { + item(span = { GridItemSpan(maxLineSpan) }) { BrowseSourceLoadingItem() } } @@ -73,57 +60,27 @@ fun BrowseSourceCompactGrid( } @Composable -fun BrowseSourceCompactGridItem( +private fun BrowseSourceCompactGridItem( manga: Manga, onClick: () -> Unit = {}, onLongClick: () -> Unit = onClick, ) { - val overlayColor = MaterialTheme.colorScheme.background.copy(alpha = 0.66f) - MangaGridCover( - modifier = Modifier - .combinedClickable( - onClick = onClick, - onLongClick = onLongClick, - ), - cover = { - MangaCover.Book( - modifier = Modifier - .fillMaxHeight() - .drawWithContent { - drawContent() - if (manga.favorite) { - drawRect(overlayColor) - } - }, - data = eu.kanade.domain.manga.model.MangaCover( - manga.id, - manga.source, - manga.favorite, - manga.thumbnailUrl, - manga.coverLastModified, - ), - ) - }, - badgesStart = { + MangaCompactGridItem( + title = manga.title, + coverData = MangaCover( + mangaId = manga.id, + sourceId = manga.source, + isMangaFavorite = manga.favorite, + url = manga.thumbnailUrl, + lastModified = manga.coverLastModified, + ), + coverAlpha = if (manga.favorite) CommonMangaItemDefaults.BrowseFavoriteCoverAlpha else 1f, + coverBadgeStart = { if (manga.favorite) { Badge(text = stringResource(R.string.in_library)) } }, - content = { - 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) - }, + onLongClick = onLongClick, + onClick = onClick, ) } diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceList.kt b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceList.kt index 64e4e0aaf..ebb712998 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceList.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceList.kt @@ -1,26 +1,21 @@ package eu.kanade.presentation.browse.components import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawWithContent 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.components.CommonMangaItemDefaults import eu.kanade.presentation.components.LazyColumn -import eu.kanade.presentation.components.MangaCover -import eu.kanade.presentation.library.components.MangaListItem -import eu.kanade.presentation.library.components.MangaListItemContent +import eu.kanade.presentation.components.MangaListItem import eu.kanade.presentation.util.plus -import eu.kanade.presentation.util.verticalPadding import eu.kanade.tachiyomi.R @Composable @@ -32,7 +27,7 @@ fun BrowseSourceList( onMangaLongClick: (Manga) -> Unit, ) { LazyColumn( - contentPadding = contentPadding, + contentPadding = contentPadding + PaddingValues(vertical = 8.dp), ) { item { if (mangaList.loadState.prepend is LoadState.Loading) { @@ -64,31 +59,22 @@ fun BrowseSourceListItem( onClick: () -> Unit = {}, onLongClick: () -> Unit = onClick, ) { - val overlayColor = MaterialTheme.colorScheme.background.copy(alpha = 0.66f) MangaListItem( - coverContent = { - MangaCover.Square( - modifier = Modifier - .padding(vertical = verticalPadding) - .fillMaxHeight() - .drawWithContent { - drawContent() - if (manga.favorite) { - drawRect(overlayColor) - } - }, - data = manga.thumbnailUrl, - ) - }, - onClick = onClick, - onLongClick = onLongClick, - badges = { + title = manga.title, + coverData = MangaCover( + mangaId = manga.id, + sourceId = manga.source, + isMangaFavorite = manga.favorite, + url = manga.thumbnailUrl, + lastModified = manga.coverLastModified, + ), + coverAlpha = if (manga.favorite) CommonMangaItemDefaults.BrowseFavoriteCoverAlpha else 1f, + badge = { if (manga.favorite) { Badge(text = stringResource(R.string.in_library)) } }, - content = { - MangaListItemContent(text = manga.title) - }, + onLongClick = onLongClick, + onClick = onClick, ) } diff --git a/app/src/main/java/eu/kanade/presentation/components/CommonMangaItem.kt b/app/src/main/java/eu/kanade/presentation/components/CommonMangaItem.kt new file mode 100644 index 000000000..552bcb558 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/components/CommonMangaItem.kt @@ -0,0 +1,317 @@ +package eu.kanade.presentation.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.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +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.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.modifierElementOf +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import eu.kanade.presentation.util.selectedBackground + +object CommonMangaItemDefaults { + val GridHorizontalSpacer = 4.dp + val GridVerticalSpacer = 4.dp + + const val BrowseFavoriteCoverAlpha = 0.34f +} + +private const val GridSelectedCoverAlpha = 0.76f + +/** + * Layout of grid list item with title overlaying the cover. + * Accepts null [title] for a cover-only view. + */ +@Composable +fun MangaCompactGridItem( + isSelected: Boolean = false, + title: String? = null, + coverData: eu.kanade.domain.manga.model.MangaCover, + coverAlpha: Float = 1f, + coverBadgeStart: (@Composable RowScope.() -> Unit)? = null, + coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null, + onLongClick: () -> Unit, + onClick: () -> Unit, +) { + GridItemSelectable( + isSelected = isSelected, + onClick = onClick, + onLongClick = onLongClick, + ) { + MangaGridCover( + cover = { + MangaCover.Book( + modifier = Modifier + .fillMaxWidth() + .alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha), + data = coverData, + ) + }, + badgesStart = coverBadgeStart, + badgesEnd = coverBadgeEnd, + content = { + if (title != null) { + CoverTextOverlay(title = title) + } + }, + ) + } +} + +/** + * Title overlay for [MangaCompactGridItem] + */ +@Composable +private fun BoxScope.CoverTextOverlay(title: String) { + 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), + ) + GridItemTitle( + modifier = Modifier + .padding(8.dp) + .align(Alignment.BottomStart), + title = title, + style = MaterialTheme.typography.titleSmall.copy( + color = Color.White, + shadow = Shadow( + color = Color.Black, + blurRadius = 4f, + ), + ), + ) +} + +/** + * Layout of grid list item with title below the cover. + */ +@Composable +fun MangaComfortableGridItem( + isSelected: Boolean = false, + title: String, + coverData: eu.kanade.domain.manga.model.MangaCover, + coverAlpha: Float = 1f, + coverBadgeStart: (@Composable RowScope.() -> Unit)? = null, + coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null, + onLongClick: () -> Unit, + onClick: () -> Unit, +) { + GridItemSelectable( + isSelected = isSelected, + onClick = onClick, + onLongClick = onLongClick, + ) { + Column { + MangaGridCover( + cover = { + MangaCover.Book( + modifier = Modifier + .fillMaxWidth() + .alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha), + data = coverData, + ) + }, + badgesStart = coverBadgeStart, + badgesEnd = coverBadgeEnd, + ) + GridItemTitle( + modifier = Modifier.padding(4.dp), + title = title, + style = MaterialTheme.typography.titleSmall, + ) + } + } +} + +/** + * Common cover layout to add contents to be drawn on top of the cover. + */ +@Composable +private fun MangaGridCover( + modifier: Modifier = Modifier, + cover: @Composable BoxScope.() -> Unit = {}, + badgesStart: (@Composable RowScope.() -> Unit)? = null, + badgesEnd: (@Composable RowScope.() -> Unit)? = null, + content: @Composable (BoxScope.() -> Unit)? = null, +) { + Box( + modifier = modifier + .fillMaxWidth() + .aspectRatio(MangaCover.Book.ratio), + ) { + cover() + content?.invoke(this) + 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 +private fun GridItemTitle( + modifier: Modifier, + title: String, + style: TextStyle, +) { + Text( + modifier = modifier, + text = title, + fontSize = 12.sp, + lineHeight = 18.sp, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + style = style, + ) +} + +/** + * Wrapper for grid items to handle selection state, click and long click. + */ +@Composable +private fun GridItemSelectable( + modifier: Modifier = Modifier, + isSelected: Boolean, + onClick: () -> Unit, + onLongClick: () -> Unit, + content: @Composable () -> Unit, +) { + Box( + modifier = modifier + .clip(RoundedCornerShape(8.dp)) + .combinedClickable( + onClick = onClick, + onLongClick = onLongClick, + ) + .selectedOutline(isSelected = isSelected, color = MaterialTheme.colorScheme.secondary) + .padding(4.dp), + ) { + val contentColor = if (isSelected) { + MaterialTheme.colorScheme.onSecondary + } else { + LocalContentColor.current + } + CompositionLocalProvider(LocalContentColor provides contentColor) { + content() + } + } +} + +/** + * @see GridItemSelectable + */ +private fun Modifier.selectedOutline( + isSelected: Boolean, + color: Color, +): Modifier { + class SelectedOutlineNode(var selected: Boolean, var color: Color) : DrawModifierNode, Modifier.Node() { + override fun ContentDrawScope.draw() { + if (selected) drawRect(color) + drawContent() + } + } + + return this then modifierElementOf( + params = isSelected.hashCode() + color.hashCode(), + create = { SelectedOutlineNode(isSelected, color) }, + update = { + it.selected = isSelected + it.color = color + }, + definitions = { + name = "selectionOutline" + properties["isSelected"] = isSelected + properties["color"] = color + }, + ) +} + +/** + * Layout of list item. + */ +@Composable +fun MangaListItem( + isSelected: Boolean = false, + title: String, + coverData: eu.kanade.domain.manga.model.MangaCover, + coverAlpha: Float = 1f, + badge: @Composable RowScope.() -> Unit, + onLongClick: () -> Unit, + onClick: () -> Unit, +) { + Row( + modifier = Modifier + .selectedBackground(isSelected) + .height(56.dp) + .combinedClickable( + onClick = onClick, + onLongClick = onLongClick, + ) + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + MangaCover.Square( + modifier = Modifier + .fillMaxHeight() + .alpha(coverAlpha), + data = coverData, + ) + Text( + text = title, + modifier = Modifier + .padding(horizontal = 16.dp) + .weight(1f), + maxLines = 2, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodyMedium, + ) + BadgeGroup(content = badge) + } +} diff --git a/app/src/main/java/eu/kanade/presentation/library/components/LazyLibraryGrid.kt b/app/src/main/java/eu/kanade/presentation/library/components/LazyLibraryGrid.kt index a2b9a5e50..201124743 100644 --- a/app/src/main/java/eu/kanade/presentation/library/components/LazyLibraryGrid.kt +++ b/app/src/main/java/eu/kanade/presentation/library/components/LazyLibraryGrid.kt @@ -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, ) } diff --git a/app/src/main/java/eu/kanade/presentation/library/components/LibraryComfortableGrid.kt b/app/src/main/java/eu/kanade/presentation/library/components/LibraryComfortableGrid.kt index 1078397a6..bfbf2373f 100644 --- a/app/src/main/java/eu/kanade/presentation/library/components/LibraryComfortableGrid.kt +++ b/app/src/main/java/eu/kanade/presentation/library/components/LibraryComfortableGrid.kt @@ -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, - ) -} diff --git a/app/src/main/java/eu/kanade/presentation/library/components/LibraryCompactGrid.kt b/app/src/main/java/eu/kanade/presentation/library/components/LibraryCompactGrid.kt index 8e747f2db..6291bee28 100644 --- a/app/src/main/java/eu/kanade/presentation/library/components/LibraryCompactGrid.kt +++ b/app/src/main/java/eu/kanade/presentation/library/components/LibraryCompactGrid.kt @@ -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, + 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, - ), - ), - ) -} diff --git a/app/src/main/java/eu/kanade/presentation/library/components/LibraryCoverOnlyGrid.kt b/app/src/main/java/eu/kanade/presentation/library/components/LibraryCoverOnlyGrid.kt deleted file mode 100644 index 61e1e2f5b..000000000 --- a/app/src/main/java/eu/kanade/presentation/library/components/LibraryCoverOnlyGrid.kt +++ /dev/null @@ -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, - showDownloadBadges: Boolean, - showUnreadBadges: Boolean, - showLocalBadges: Boolean, - showLanguageBadges: Boolean, - columns: Int, - contentPadding: PaddingValues, - selection: List, - 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, - ) -} diff --git a/app/src/main/java/eu/kanade/presentation/library/components/LibraryGridCover.kt b/app/src/main/java/eu/kanade/presentation/library/components/LibraryGridCover.kt deleted file mode 100644 index 6483e9aff..000000000 --- a/app/src/main/java/eu/kanade/presentation/library/components/LibraryGridCover.kt +++ /dev/null @@ -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, - ) -} diff --git a/app/src/main/java/eu/kanade/presentation/library/components/LibraryGridItemSelectable.kt b/app/src/main/java/eu/kanade/presentation/library/components/LibraryGridItemSelectable.kt deleted file mode 100644 index 4145c9d44..000000000 --- a/app/src/main/java/eu/kanade/presentation/library/components/LibraryGridItemSelectable.kt +++ /dev/null @@ -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() - } - } -} diff --git a/app/src/main/java/eu/kanade/presentation/library/components/LibraryList.kt b/app/src/main/java/eu/kanade/presentation/library/components/LibraryList.kt index a8f405773..067033644 100644 --- a/app/src/main/java/eu/kanade/presentation/library/components/LibraryList.kt +++ b/app/src/main/java/eu/kanade/presentation/library/components/LibraryList.kt @@ -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, - ) -} diff --git a/app/src/main/java/eu/kanade/presentation/library/components/LibraryPager.kt b/app/src/main/java/eu/kanade/presentation/library/components/LibraryPager.kt index 6b889eafd..bbf7ff9bd 100644 --- a/app/src/main/java/eu/kanade/presentation/library/components/LibraryPager.kt +++ b/app/src/main/java/eu/kanade/presentation/library/components/LibraryPager.kt @@ -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, - ) - } } } }