Group update entries together (#610)
* collapsible grouping Updates * - Reverse chapter order - Only show cover & marker on the first one * - Bigger cover & support panorama view
This commit is contained in:
parent
a6f3a2cdcf
commit
1f3d2cbd3c
5 changed files with 226 additions and 27 deletions
|
|
@ -99,9 +99,9 @@ enum class MangaCover(val ratio: Float) {
|
|||
modifier = Modifier
|
||||
.size(
|
||||
when (size) {
|
||||
Size.Big -> 16.dp
|
||||
Size.Medium -> 24.dp
|
||||
else -> 32.dp
|
||||
Size.Big -> COVER_TEMPLATE_SIZE_BIG
|
||||
Size.Medium -> COVER_TEMPLATE_SIZE_MEDIUM
|
||||
else -> COVER_TEMPLATE_SIZE_NORMAL
|
||||
},
|
||||
)
|
||||
.align(Alignment.Center),
|
||||
|
|
@ -122,9 +122,9 @@ enum class MangaCover(val ratio: Float) {
|
|||
modifier = Modifier
|
||||
.size(
|
||||
when (size) {
|
||||
Size.Big -> 16.dp
|
||||
Size.Medium -> 24.dp
|
||||
else -> 32.dp
|
||||
Size.Big -> COVER_TEMPLATE_SIZE_BIG
|
||||
Size.Medium -> COVER_TEMPLATE_SIZE_MEDIUM
|
||||
else -> COVER_TEMPLATE_SIZE_NORMAL
|
||||
},
|
||||
)
|
||||
.align(Alignment.Center),
|
||||
|
|
@ -149,6 +149,12 @@ enum class MangaCover(val ratio: Float) {
|
|||
contentScale = scale,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val COVER_TEMPLATE_SIZE_BIG = 16.dp
|
||||
val COVER_TEMPLATE_SIZE_MEDIUM = 24.dp
|
||||
val COVER_TEMPLATE_SIZE_NORMAL = 32.dp
|
||||
}
|
||||
}
|
||||
|
||||
enum class MangaCoverHide(private val ratio: Float) {
|
||||
|
|
@ -200,7 +206,7 @@ enum class MangaCoverHide(private val ratio: Float) {
|
|||
}
|
||||
}
|
||||
|
||||
internal val RatioSwitchToPanorama = 0.75f
|
||||
internal const val RatioSwitchToPanorama = 0.75f
|
||||
|
||||
internal val CoverPlaceholderColor = Color(0x1F888888)
|
||||
internal val CoverPlaceholderOnBgColor = Color(0x8F888888)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.CalendarMonth
|
||||
import androidx.compose.material.icons.outlined.FlipToBack
|
||||
import androidx.compose.material.icons.outlined.Panorama
|
||||
import androidx.compose.material.icons.outlined.Refresh
|
||||
import androidx.compose.material.icons.outlined.SelectAll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
|
|
@ -27,10 +29,12 @@ import eu.kanade.presentation.manga.components.MangaBottomActionMenu
|
|||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
|
||||
import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel
|
||||
import eu.kanade.tachiyomi.ui.updates.groupByDateAndManga
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.i18n.kmk.KMR
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
|
|
@ -59,7 +63,13 @@ fun UpdateScreen(
|
|||
onMultiDeleteClicked: (List<UpdatesItem>) -> Unit,
|
||||
onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit,
|
||||
onOpenChapter: (UpdatesItem) -> Unit,
|
||||
// KMK -->
|
||||
collapseToggle: (key: String) -> Unit,
|
||||
// KMK <--
|
||||
) {
|
||||
// KMK -->
|
||||
val usePanoramaCover = remember { mutableStateOf(false) }
|
||||
// KMK <--
|
||||
BackHandler(enabled = state.selectionMode, onBack = { onSelectAll(false) })
|
||||
|
||||
Scaffold(
|
||||
|
|
@ -72,6 +82,10 @@ fun UpdateScreen(
|
|||
onInvertSelection = { onInvertSelection() },
|
||||
onCancelActionMode = { onSelectAll(false) },
|
||||
scrollBehavior = scrollBehavior,
|
||||
// KMK -->
|
||||
usePanoramaCover = usePanoramaCover.value,
|
||||
usePanoramaCoverClick = { usePanoramaCover.value = !usePanoramaCover.value },
|
||||
// KMK
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
|
|
@ -116,7 +130,19 @@ fun UpdateScreen(
|
|||
updatesLastUpdatedItem(lastUpdated)
|
||||
|
||||
updatesUiItems(
|
||||
uiModels = state.getUiModel(),
|
||||
uiModels = state.getUiModel()
|
||||
// KMK -->
|
||||
.filter {
|
||||
when (it) {
|
||||
is UpdatesUiModel.Header, is UpdatesUiModel.Leader -> true
|
||||
is UpdatesUiModel.Item ->
|
||||
state.expandedState.contains(it.item.update.groupByDateAndManga())
|
||||
}
|
||||
},
|
||||
expandedState = state.expandedState,
|
||||
collapseToggle = collapseToggle,
|
||||
usePanoramaCover = usePanoramaCover.value,
|
||||
// KMK <--
|
||||
selectionMode = state.selectionMode,
|
||||
// SY -->
|
||||
preserveReadingPosition = preserveReadingPosition,
|
||||
|
|
@ -143,6 +169,10 @@ private fun UpdatesAppBar(
|
|||
onInvertSelection: () -> Unit,
|
||||
onCancelActionMode: () -> Unit,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
// KMK -->
|
||||
usePanoramaCover: Boolean,
|
||||
usePanoramaCoverClick: () -> Unit,
|
||||
// KMK <--
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AppBar(
|
||||
|
|
@ -151,6 +181,14 @@ private fun UpdatesAppBar(
|
|||
actions = {
|
||||
AppBarActions(
|
||||
persistentListOf(
|
||||
// KMK -->
|
||||
AppBar.Action(
|
||||
title = stringResource(KMR.strings.action_panorama_cover),
|
||||
icon = Icons.Outlined.Panorama,
|
||||
iconTint = MaterialTheme.colorScheme.primary.takeIf { usePanoramaCover },
|
||||
onClick = usePanoramaCoverClick,
|
||||
),
|
||||
// KMK <--
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.action_view_upcoming),
|
||||
icon = Icons.Outlined.CalendarMonth,
|
||||
|
|
@ -222,5 +260,9 @@ private fun UpdatesBottomBar(
|
|||
|
||||
sealed interface UpdatesUiModel {
|
||||
data class Header(val date: LocalDate) : UpdatesUiModel
|
||||
data class Item(val item: UpdatesItem) : UpdatesUiModel
|
||||
open class Item(open val item: UpdatesItem, open val isExpandable: Boolean = false) : UpdatesUiModel
|
||||
// KMK -->
|
||||
/** The first [Item] in a group of chapters from same manga */
|
||||
data class Leader(override val item: UpdatesItem, override val isExpandable: Boolean) : Item(item)
|
||||
// KMK <--
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
|
|
@ -15,12 +15,17 @@ import androidx.compose.foundation.lazy.items
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Bookmark
|
||||
import androidx.compose.material.icons.filled.Circle
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableFloatState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
|
|
@ -38,10 +43,12 @@ import eu.kanade.presentation.manga.components.ChapterDownloadAction
|
|||
import eu.kanade.presentation.manga.components.ChapterDownloadIndicator
|
||||
import eu.kanade.presentation.manga.components.DotSeparatorText
|
||||
import eu.kanade.presentation.manga.components.MangaCover
|
||||
import eu.kanade.presentation.manga.components.RatioSwitchToPanorama
|
||||
import eu.kanade.presentation.util.animateItemFastScroll
|
||||
import eu.kanade.presentation.util.relativeTimeSpanString
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
|
||||
import eu.kanade.tachiyomi.ui.updates.groupByDateAndManga
|
||||
import tachiyomi.domain.updates.model.UpdatesWithRelations
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.ListGroupHeader
|
||||
|
|
@ -69,6 +76,11 @@ internal fun LazyListScope.updatesLastUpdatedItem(
|
|||
|
||||
internal fun LazyListScope.updatesUiItems(
|
||||
uiModels: List<UpdatesUiModel>,
|
||||
// KMK -->
|
||||
expandedState: Set<String>,
|
||||
collapseToggle: (key: String) -> Unit,
|
||||
usePanoramaCover: Boolean,
|
||||
// KMK <--
|
||||
selectionMode: Boolean,
|
||||
// SY -->
|
||||
preserveReadingPosition: Boolean,
|
||||
|
|
@ -96,7 +108,10 @@ internal fun LazyListScope.updatesUiItems(
|
|||
when (item) {
|
||||
is UpdatesUiModel.Header -> {
|
||||
ListGroupHeader(
|
||||
modifier = Modifier.animateItemFastScroll(),
|
||||
modifier = Modifier.animateItemFastScroll()
|
||||
// KMK -->
|
||||
.padding(top = MaterialTheme.padding.extraSmall),
|
||||
// KMK <--
|
||||
text = relativeDateText(item.date),
|
||||
)
|
||||
}
|
||||
|
|
@ -135,6 +150,13 @@ internal fun LazyListScope.updatesUiItems(
|
|||
}.takeIf { !selectionMode },
|
||||
downloadStateProvider = updatesItem.downloadStateProvider,
|
||||
downloadProgressProvider = updatesItem.downloadProgressProvider,
|
||||
// KMK -->
|
||||
isLeader = item is UpdatesUiModel.Leader,
|
||||
isExpandable = item.isExpandable,
|
||||
expanded = expandedState.contains(updatesItem.update.groupByDateAndManga()),
|
||||
collapseToggle = collapseToggle,
|
||||
usePanoramaCover = usePanoramaCover,
|
||||
// KMK <--
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -153,6 +175,14 @@ private fun UpdatesUiItem(
|
|||
// Download Indicator
|
||||
downloadStateProvider: () -> Download.State,
|
||||
downloadProgressProvider: () -> Int,
|
||||
// KMK -->
|
||||
isLeader: Boolean,
|
||||
isExpandable: Boolean,
|
||||
expanded: Boolean,
|
||||
collapseToggle: (key: String) -> Unit,
|
||||
usePanoramaCover: Boolean,
|
||||
coverRatio: MutableFloatState = remember { mutableFloatStateOf(1f) },
|
||||
// KMK <--
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
|
@ -168,27 +198,64 @@ private fun UpdatesUiItem(
|
|||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
},
|
||||
)
|
||||
.height(56.dp)
|
||||
.padding(horizontal = MaterialTheme.padding.medium),
|
||||
.padding(
|
||||
// KMK -->
|
||||
vertical = MaterialTheme.padding.extraSmall,
|
||||
// KMK <--
|
||||
horizontal = MaterialTheme.padding.medium,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
// KMK -->
|
||||
val mangaCover = update.coverData
|
||||
val coverIsWide = coverRatio.floatValue <= RatioSwitchToPanorama
|
||||
val bgColor = mangaCover.dominantCoverColors?.first?.let { Color(it) }
|
||||
val onBgColor = mangaCover.dominantCoverColors?.second
|
||||
// KMK <--
|
||||
MangaCover.Square(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 6.dp)
|
||||
.fillMaxHeight(),
|
||||
data = mangaCover,
|
||||
onClick = onClickCover,
|
||||
// KMK -->
|
||||
bgColor = bgColor,
|
||||
tint = onBgColor,
|
||||
size = MangaCover.Size.Big,
|
||||
if (isLeader) {
|
||||
if (usePanoramaCover && coverIsWide) {
|
||||
MangaCover.Panorama(
|
||||
modifier = Modifier
|
||||
.padding(top = MaterialTheme.padding.small)
|
||||
.width(UpdateItemPanoramaWidth),
|
||||
data = mangaCover,
|
||||
onClick = onClickCover,
|
||||
// KMK -->
|
||||
bgColor = bgColor,
|
||||
tint = onBgColor,
|
||||
size = MangaCover.Size.Medium,
|
||||
onCoverLoaded = { _, result ->
|
||||
val image = result.result.image
|
||||
coverRatio.floatValue = image.height.toFloat() / image.width
|
||||
},
|
||||
// KMK <--
|
||||
)
|
||||
} else {
|
||||
// KMK <--
|
||||
MangaCover.Book(
|
||||
modifier = Modifier
|
||||
// KMK -->
|
||||
.padding(top = MaterialTheme.padding.small)
|
||||
.width(UpdateItemWidth),
|
||||
// KMK <--
|
||||
data = mangaCover,
|
||||
onClick = onClickCover,
|
||||
// KMK -->
|
||||
bgColor = bgColor,
|
||||
tint = onBgColor,
|
||||
size = MangaCover.Size.Medium,
|
||||
onCoverLoaded = { _, result ->
|
||||
val image = result.result.image
|
||||
coverRatio.floatValue = image.height.toFloat() / image.width
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(if (usePanoramaCover && coverIsWide) UpdateItemPanoramaWidth else UpdateItemWidth),
|
||||
)
|
||||
// KMK <--
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
|
@ -247,6 +314,15 @@ private fun UpdatesUiItem(
|
|||
}
|
||||
}
|
||||
|
||||
// KMK -->
|
||||
if (isLeader && isExpandable) {
|
||||
CollapseButton(
|
||||
expanded = expanded,
|
||||
collapseToggle = { collapseToggle(update.groupByDateAndManga()) },
|
||||
)
|
||||
}
|
||||
// KMK <--
|
||||
|
||||
ChapterDownloadIndicator(
|
||||
enabled = onDownloadChapter != null,
|
||||
modifier = Modifier.padding(start = 4.dp),
|
||||
|
|
@ -256,3 +332,30 @@ private fun UpdatesUiItem(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
// KMK -->
|
||||
@Composable
|
||||
fun CollapseButton(
|
||||
expanded: Boolean,
|
||||
collapseToggle: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(IndicatorSize),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
IconButton(onClick = { collapseToggle() }) {
|
||||
Icon(
|
||||
imageVector = if (expanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val IndicatorSize = 18.dp
|
||||
private val UpdateItemPanoramaWidth = 126.dp
|
||||
private val UpdateItemWidth = 56.dp
|
||||
// KMK <--
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import exh.source.EXH_SOURCE_ID
|
|||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.mutate
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -379,18 +380,43 @@ class UpdatesScreenModel(
|
|||
libraryPreferences.newUpdatesCount().set(0)
|
||||
}
|
||||
|
||||
// KMK -->
|
||||
fun toggleExpandedState(key: String) {
|
||||
mutableState.update {
|
||||
it.copy(
|
||||
expandedState = it.expandedState.toMutableSet().apply {
|
||||
if (it.expandedState.contains(key)) remove(key) else add(key)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
// KMK <--
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val isLoading: Boolean = true,
|
||||
val items: PersistentList<UpdatesItem> = persistentListOf(),
|
||||
// KMK -->
|
||||
val expandedState: Set<String> = persistentSetOf(),
|
||||
// KMK <--
|
||||
val dialog: Dialog? = null,
|
||||
) {
|
||||
val selected = items.filter { it.selected }
|
||||
val selectionMode = selected.isNotEmpty()
|
||||
|
||||
fun getUiModel(): List<UpdatesUiModel> {
|
||||
return items
|
||||
.map { UpdatesUiModel.Item(it) }
|
||||
// KMK -->
|
||||
var lastMangaId = -1L
|
||||
// KMK <--
|
||||
return items.groupBy { it.update.dateFetch.toLocalDate() }
|
||||
.flatMap { groupDate ->
|
||||
groupDate.value.groupBy { it.update.mangaId }
|
||||
.flatMap { groupManga ->
|
||||
val list = groupManga.value
|
||||
list.sortedBy { it.update.dateFetch }
|
||||
.map { UpdatesUiModel.Item(it, list.size > 1) }
|
||||
}
|
||||
}
|
||||
.insertSeparators { before, after ->
|
||||
val beforeDate = before?.item?.update?.dateFetch?.toLocalDate()
|
||||
val afterDate = after?.item?.update?.dateFetch?.toLocalDate()
|
||||
|
|
@ -400,6 +426,21 @@ class UpdatesScreenModel(
|
|||
else -> null
|
||||
}
|
||||
}
|
||||
// KMK -->
|
||||
.map {
|
||||
if (it is UpdatesUiModel.Header) {
|
||||
lastMangaId = -1L
|
||||
it
|
||||
} else {
|
||||
if ((it as UpdatesUiModel.Item).item.update.mangaId != lastMangaId) {
|
||||
lastMangaId = it.item.update.mangaId
|
||||
UpdatesUiModel.Leader(it.item, it.isExpandable)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
// KMK <--
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -426,3 +467,7 @@ data class UpdatesItem(
|
|||
}
|
||||
// SY <--
|
||||
}
|
||||
|
||||
// KMK -->
|
||||
fun UpdatesWithRelations.groupByDateAndManga() = "${dateFetch.toLocalDate().toEpochDay()}-$mangaId"
|
||||
// KMK <--
|
||||
|
|
|
|||
|
|
@ -94,6 +94,9 @@ data object UpdatesTab : Tab {
|
|||
context.startActivity(intent)
|
||||
},
|
||||
onCalendarClicked = { navigator.push(UpcomingScreen()) },
|
||||
// KMK -->
|
||||
collapseToggle = screenModel::toggleExpandedState,
|
||||
// KMK <--
|
||||
)
|
||||
|
||||
val onDismissDialog = { screenModel.setDialog(null) }
|
||||
|
|
|
|||
Loading…
Reference in a new issue