Show missing chapter count between two chapters in chapter list (#10096)

* Show missing chapter count between two chapters in chapter list

Closes #8460

* Fix crash

* Lint

* Review changes

* Lint
This commit is contained in:
AntsyLich 2023-11-02 08:18:19 +06:00 committed by GitHub
parent b3d7c92475
commit 6d538db5f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 189 additions and 107 deletions

View file

@ -2,7 +2,7 @@ package eu.kanade.domain.chapter.model
import eu.kanade.domain.manga.model.downloadedFilter import eu.kanade.domain.manga.model.downloadedFilter
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.ui.manga.ChapterItem import eu.kanade.tachiyomi.ui.manga.ChapterList
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.service.getChapterSort import tachiyomi.domain.chapter.service.getChapterSort
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
@ -34,7 +34,7 @@ fun List<Chapter>.applyFilters(manga: Manga, downloadManager: DownloadManager):
* Applies the view filters to the list of chapters obtained from the database. * Applies the view filters to the list of chapters obtained from the database.
* @return an observable of the list of chapters filtered and sorted. * @return an observable of the list of chapters filtered and sorted.
*/ */
fun List<ChapterItem>.applyFilters(manga: Manga): Sequence<ChapterItem> { fun List<ChapterList.Item>.applyFilters(manga: Manga): Sequence<ChapterList.Item> {
val isLocalManga = manga.isLocal() val isLocalManga = manga.isLocal()
val unreadFilter = manga.unreadFilter val unreadFilter = manga.unreadFilter
val downloadedFilter = manga.downloadedFilter val downloadedFilter = manga.downloadedFilter

View file

@ -5,9 +5,11 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.asPaddingValues
@ -26,7 +28,9 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -44,6 +48,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.util.fastAll import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny import androidx.compose.ui.util.fastAny
@ -61,7 +66,7 @@ import eu.kanade.presentation.util.formatChapterNumber
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.source.getNameForMangaInfo import eu.kanade.tachiyomi.source.getNameForMangaInfo
import eu.kanade.tachiyomi.ui.manga.ChapterItem import eu.kanade.tachiyomi.ui.manga.ChapterList
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
import eu.kanade.tachiyomi.util.lang.toRelativeString import eu.kanade.tachiyomi.util.lang.toRelativeString
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
@ -75,8 +80,10 @@ import tachiyomi.presentation.core.components.VerticalFastScroller
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
import tachiyomi.presentation.core.components.material.PullRefresh import tachiyomi.presentation.core.components.material.PullRefresh
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.util.isScrolledToEnd import tachiyomi.presentation.core.util.isScrolledToEnd
import tachiyomi.presentation.core.util.isScrollingUp import tachiyomi.presentation.core.util.isScrollingUp
import tachiyomi.presentation.core.util.secondaryItemAlpha
import java.text.DateFormat import java.text.DateFormat
import java.util.Date import java.util.Date
@ -92,7 +99,7 @@ fun MangaScreen(
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
onChapterClicked: (Chapter) -> Unit, onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?, onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
onAddToLibraryClicked: () -> Unit, onAddToLibraryClicked: () -> Unit,
onWebViewClicked: (() -> Unit)?, onWebViewClicked: (() -> Unit)?,
onWebViewLongClicked: (() -> Unit)?, onWebViewLongClicked: (() -> Unit)?,
@ -123,10 +130,10 @@ fun MangaScreen(
onMultiDeleteClicked: (List<Chapter>) -> Unit, onMultiDeleteClicked: (List<Chapter>) -> Unit,
// For chapter swipe // For chapter swipe
onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit, onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
// Chapter selection // Chapter selection
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit, onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
onAllChapterSelected: (Boolean) -> Unit, onAllChapterSelected: (Boolean) -> Unit,
onInvertSelection: () -> Unit, onInvertSelection: () -> Unit,
) { ) {
@ -225,7 +232,7 @@ private fun MangaScreenSmallImpl(
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
onChapterClicked: (Chapter) -> Unit, onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?, onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
onAddToLibraryClicked: () -> Unit, onAddToLibraryClicked: () -> Unit,
onWebViewClicked: (() -> Unit)?, onWebViewClicked: (() -> Unit)?,
onWebViewLongClicked: (() -> Unit)?, onWebViewLongClicked: (() -> Unit)?,
@ -257,16 +264,17 @@ private fun MangaScreenSmallImpl(
onMultiDeleteClicked: (List<Chapter>) -> Unit, onMultiDeleteClicked: (List<Chapter>) -> Unit,
// For chapter swipe // For chapter swipe
onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit, onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
// Chapter selection // Chapter selection
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit, onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
onAllChapterSelected: (Boolean) -> Unit, onAllChapterSelected: (Boolean) -> Unit,
onInvertSelection: () -> Unit, onInvertSelection: () -> Unit,
) { ) {
val chapterListState = rememberLazyListState() val chapterListState = rememberLazyListState()
val chapters = remember(state) { state.processedChapters } val chapters = remember(state) { state.processedChapters }
val listItem = remember(state) { state.chapterListItems }
val isAnySelected by remember { val isAnySelected by remember {
derivedStateOf { derivedStateOf {
@ -447,7 +455,8 @@ private fun MangaScreenSmallImpl(
sharedChapterItems( sharedChapterItems(
manga = state.manga, manga = state.manga,
chapters = chapters, chapters = listItem,
isAnyChapterSelected = chapters.fastAny { it.selected },
dateRelativeTime = dateRelativeTime, dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat, dateFormat = dateFormat,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
@ -474,7 +483,7 @@ fun MangaScreenLargeImpl(
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
onChapterClicked: (Chapter) -> Unit, onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?, onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
onAddToLibraryClicked: () -> Unit, onAddToLibraryClicked: () -> Unit,
onWebViewClicked: (() -> Unit)?, onWebViewClicked: (() -> Unit)?,
onWebViewLongClicked: (() -> Unit)?, onWebViewLongClicked: (() -> Unit)?,
@ -506,10 +515,10 @@ fun MangaScreenLargeImpl(
onMultiDeleteClicked: (List<Chapter>) -> Unit, onMultiDeleteClicked: (List<Chapter>) -> Unit,
// For swipe actions // For swipe actions
onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit, onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
// Chapter selection // Chapter selection
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit, onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
onAllChapterSelected: (Boolean) -> Unit, onAllChapterSelected: (Boolean) -> Unit,
onInvertSelection: () -> Unit, onInvertSelection: () -> Unit,
) { ) {
@ -517,6 +526,7 @@ fun MangaScreenLargeImpl(
val density = LocalDensity.current val density = LocalDensity.current
val chapters = remember(state) { state.processedChapters } val chapters = remember(state) { state.processedChapters }
val listItem = remember(state) { state.chapterListItems }
val isAnySelected by remember { val isAnySelected by remember {
derivedStateOf { derivedStateOf {
@ -688,7 +698,8 @@ fun MangaScreenLargeImpl(
sharedChapterItems( sharedChapterItems(
manga = state.manga, manga = state.manga,
chapters = chapters, chapters = listItem,
isAnyChapterSelected = chapters.fastAny { it.selected },
dateRelativeTime = dateRelativeTime, dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat, dateFormat = dateFormat,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
@ -708,12 +719,12 @@ fun MangaScreenLargeImpl(
@Composable @Composable
private fun SharedMangaBottomActionMenu( private fun SharedMangaBottomActionMenu(
selected: List<ChapterItem>, selected: List<ChapterList.Item>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit, onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
onMultiMarkAsReadClicked: (List<Chapter>, markAsRead: Boolean) -> Unit, onMultiMarkAsReadClicked: (List<Chapter>, markAsRead: Boolean) -> Unit,
onMarkPreviousAsReadClicked: (Chapter) -> Unit, onMarkPreviousAsReadClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?, onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
onMultiDeleteClicked: (List<Chapter>) -> Unit, onMultiDeleteClicked: (List<Chapter>) -> Unit,
fillFraction: Float, fillFraction: Float,
) { ) {
@ -750,34 +761,63 @@ private fun SharedMangaBottomActionMenu(
private fun LazyListScope.sharedChapterItems( private fun LazyListScope.sharedChapterItems(
manga: Manga, manga: Manga,
chapters: List<ChapterItem>, chapters: List<ChapterList>,
isAnyChapterSelected: Boolean,
dateRelativeTime: Boolean, dateRelativeTime: Boolean,
dateFormat: DateFormat, dateFormat: DateFormat,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onChapterClicked: (Chapter) -> Unit, onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?, onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit, onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit, onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
) { ) {
items( items(
items = chapters, items = chapters,
key = { "chapter-${it.chapter.id}" }, key = { item ->
when (item) {
is ChapterList.MissingCount -> "missing-count-${item.id}"
is ChapterList.Item -> "chapter-${item.id}"
}
},
contentType = { MangaScreenItem.CHAPTER }, contentType = { MangaScreenItem.CHAPTER },
) { chapterItem -> ) { item ->
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val context = LocalContext.current val context = LocalContext.current
when (item) {
is ChapterList.MissingCount -> {
Row(
modifier = Modifier.padding(
horizontal = MaterialTheme.padding.medium,
vertical = MaterialTheme.padding.small,
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
) {
HorizontalDivider(modifier = Modifier.weight(1f))
Text(
text = pluralStringResource(
id = R.plurals.missing_chapters,
count = item.count,
item.count,
),
modifier = Modifier.secondaryItemAlpha(),
)
HorizontalDivider(modifier = Modifier.weight(1f))
}
}
is ChapterList.Item -> {
MangaChapterListItem( MangaChapterListItem(
title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) { title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) {
stringResource( stringResource(
R.string.display_mode_chapter, R.string.display_mode_chapter,
formatChapterNumber(chapterItem.chapter.chapterNumber), formatChapterNumber(item.chapter.chapterNumber),
) )
} else { } else {
chapterItem.chapter.name item.chapter.name
}, },
date = chapterItem.chapter.dateUpload date = item.chapter.dateUpload
.takeIf { it > 0L } .takeIf { it > 0L }
?.let { ?.let {
Date(it).toRelativeString( Date(it).toRelativeString(
@ -786,56 +826,58 @@ private fun LazyListScope.sharedChapterItems(
dateFormat, dateFormat,
) )
}, },
readProgress = chapterItem.chapter.lastPageRead readProgress = item.chapter.lastPageRead
.takeIf { !chapterItem.chapter.read && it > 0L } .takeIf { !item.chapter.read && it > 0L }
?.let { ?.let {
stringResource( stringResource(
R.string.chapter_progress, R.string.chapter_progress,
it + 1, it + 1,
) )
}, },
scanlator = chapterItem.chapter.scanlator.takeIf { !it.isNullOrBlank() }, scanlator = item.chapter.scanlator.takeIf { !it.isNullOrBlank() },
read = chapterItem.chapter.read, read = item.chapter.read,
bookmark = chapterItem.chapter.bookmark, bookmark = item.chapter.bookmark,
selected = chapterItem.selected, selected = item.selected,
downloadIndicatorEnabled = chapters.fastAll { !it.selected }, downloadIndicatorEnabled = !isAnyChapterSelected,
downloadStateProvider = { chapterItem.downloadState }, downloadStateProvider = { item.downloadState },
downloadProgressProvider = { chapterItem.downloadProgress }, downloadProgressProvider = { item.downloadProgress },
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction, chapterSwipeEndAction = chapterSwipeEndAction,
onLongClick = { onLongClick = {
onChapterSelected(chapterItem, !chapterItem.selected, true, true) onChapterSelected(item, !item.selected, true, true)
haptic.performHapticFeedback(HapticFeedbackType.LongPress) haptic.performHapticFeedback(HapticFeedbackType.LongPress)
}, },
onClick = { onClick = {
onChapterItemClick( onChapterItemClick(
chapterItem = chapterItem, chapterItem = item,
chapters = chapters, isAnyChapterSelected = isAnyChapterSelected,
onToggleSelection = { onChapterSelected(chapterItem, !chapterItem.selected, true, false) }, onToggleSelection = { onChapterSelected(item, !item.selected, true, false) },
onChapterClicked = onChapterClicked, onChapterClicked = onChapterClicked,
) )
}, },
onDownloadClick = if (onDownloadChapter != null) { onDownloadClick = if (onDownloadChapter != null) {
{ onDownloadChapter(listOf(chapterItem), it) } { onDownloadChapter(listOf(item), it) }
} else { } else {
null null
}, },
onChapterSwipe = { onChapterSwipe = {
onChapterSwipe(chapterItem, it) onChapterSwipe(item, it)
}, },
) )
} }
} }
}
}
private fun onChapterItemClick( private fun onChapterItemClick(
chapterItem: ChapterItem, chapterItem: ChapterList.Item,
chapters: List<ChapterItem>, isAnyChapterSelected: Boolean,
onToggleSelection: (Boolean) -> Unit, onToggleSelection: (Boolean) -> Unit,
onChapterClicked: (Chapter) -> Unit, onChapterClicked: (Chapter) -> Unit,
) { ) {
when { when {
chapterItem.selected -> onToggleSelection(false) chapterItem.selected -> onToggleSelection(false)
chapters.fastAny { it.selected } -> onToggleSelection(true) isAnyChapterSelected -> onToggleSelection(true)
else -> onChapterClicked(chapterItem.chapter) else -> onChapterClicked(chapterItem.chapter)
} }
} }

View file

@ -10,6 +10,7 @@ import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.core.preference.asState import eu.kanade.core.preference.asState
import eu.kanade.core.util.addOrRemove import eu.kanade.core.util.addOrRemove
import eu.kanade.core.util.insertSeparators
import eu.kanade.domain.chapter.interactor.SetReadStatus import eu.kanade.domain.chapter.interactor.SetReadStatus
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
@ -61,6 +62,7 @@ import tachiyomi.domain.chapter.interactor.UpdateChapter
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.model.ChapterUpdate import tachiyomi.domain.chapter.model.ChapterUpdate
import tachiyomi.domain.chapter.model.NoChaptersException import tachiyomi.domain.chapter.model.NoChaptersException
import tachiyomi.domain.chapter.service.calculateChapterGap
import tachiyomi.domain.chapter.service.getChapterSort import tachiyomi.domain.chapter.service.getChapterSort
import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
@ -75,6 +77,7 @@ import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.source.local.isLocal import tachiyomi.source.local.isLocal
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import kotlin.math.floor
class MangaScreenModel( class MangaScreenModel(
val context: Context, val context: Context,
@ -117,10 +120,10 @@ class MangaScreenModel(
private val isFavorited: Boolean private val isFavorited: Boolean
get() = manga?.favorite ?: false get() = manga?.favorite ?: false
private val allChapters: List<ChapterItem>? private val allChapters: List<ChapterList.Item>?
get() = successState?.chapters get() = successState?.chapters
private val filteredChapters: List<ChapterItem>? private val filteredChapters: List<ChapterList.Item>?
get() = successState?.processedChapters get() = successState?.processedChapters
val chapterSwipeStartAction = libraryPreferences.swipeToEndAction().get() val chapterSwipeStartAction = libraryPreferences.swipeToEndAction().get()
@ -158,7 +161,7 @@ class MangaScreenModel(
updateSuccessState { updateSuccessState {
it.copy( it.copy(
manga = manga, manga = manga,
chapters = chapters.toChapterItems(manga), chapters = chapters.toChapterListItems(manga),
) )
} }
} }
@ -169,7 +172,7 @@ class MangaScreenModel(
screenModelScope.launchIO { screenModelScope.launchIO {
val manga = getMangaAndChapters.awaitManga(mangaId) val manga = getMangaAndChapters.awaitManga(mangaId)
val chapters = getMangaAndChapters.awaitChapters(mangaId) val chapters = getMangaAndChapters.awaitChapters(mangaId)
.toChapterItems(manga) .toChapterListItems(manga)
if (!manga.favorite) { if (!manga.favorite) {
setMangaDefaultChapterFlags.await(manga) setMangaDefaultChapterFlags.await(manga)
@ -455,7 +458,7 @@ class MangaScreenModel(
private fun updateDownloadState(download: Download) { private fun updateDownloadState(download: Download) {
updateSuccessState { successState -> updateSuccessState { successState ->
val modifiedIndex = successState.chapters.indexOfFirst { it.chapter.id == download.chapter.id } val modifiedIndex = successState.chapters.indexOfFirst { it.id == download.chapter.id }
if (modifiedIndex < 0) return@updateSuccessState successState if (modifiedIndex < 0) return@updateSuccessState successState
val newChapters = successState.chapters.toMutableList().apply { val newChapters = successState.chapters.toMutableList().apply {
@ -467,7 +470,7 @@ class MangaScreenModel(
} }
} }
private fun List<Chapter>.toChapterItems(manga: Manga): List<ChapterItem> { private fun List<Chapter>.toChapterListItems(manga: Manga): List<ChapterList.Item> {
val isLocal = manga.isLocal() val isLocal = manga.isLocal()
return map { chapter -> return map { chapter ->
val activeDownload = if (isLocal) { val activeDownload = if (isLocal) {
@ -486,7 +489,7 @@ class MangaScreenModel(
else -> Download.State.NOT_DOWNLOADED else -> Download.State.NOT_DOWNLOADED
} }
ChapterItem( ChapterList.Item(
chapter = chapter, chapter = chapter,
downloadState = downloadState, downloadState = downloadState,
downloadProgress = activeDownload?.progress ?: 0, downloadProgress = activeDownload?.progress ?: 0,
@ -534,7 +537,7 @@ class MangaScreenModel(
/** /**
* @throws IllegalStateException if the swipe action is [LibraryPreferences.ChapterSwipeAction.Disabled] * @throws IllegalStateException if the swipe action is [LibraryPreferences.ChapterSwipeAction.Disabled]
*/ */
fun chapterSwipe(chapterItem: ChapterItem, swipeAction: LibraryPreferences.ChapterSwipeAction) { fun chapterSwipe(chapterItem: ChapterList.Item, swipeAction: LibraryPreferences.ChapterSwipeAction) {
screenModelScope.launch { screenModelScope.launch {
executeChapterSwipeAction(chapterItem, swipeAction) executeChapterSwipeAction(chapterItem, swipeAction)
} }
@ -544,7 +547,7 @@ class MangaScreenModel(
* @throws IllegalStateException if the swipe action is [LibraryPreferences.ChapterSwipeAction.Disabled] * @throws IllegalStateException if the swipe action is [LibraryPreferences.ChapterSwipeAction.Disabled]
*/ */
private fun executeChapterSwipeAction( private fun executeChapterSwipeAction(
chapterItem: ChapterItem, chapterItem: ChapterList.Item,
swipeAction: LibraryPreferences.ChapterSwipeAction, swipeAction: LibraryPreferences.ChapterSwipeAction,
) { ) {
val chapter = chapterItem.chapter val chapter = chapterItem.chapter
@ -626,7 +629,7 @@ class MangaScreenModel(
} }
fun runChapterDownloadActions( fun runChapterDownloadActions(
items: List<ChapterItem>, items: List<ChapterList.Item>,
action: ChapterDownloadAction, action: ChapterDownloadAction,
) { ) {
when (action) { when (action) {
@ -641,7 +644,7 @@ class MangaScreenModel(
startDownload(listOf(chapter), true) startDownload(listOf(chapter), true)
} }
ChapterDownloadAction.CANCEL -> { ChapterDownloadAction.CANCEL -> {
val chapterId = items.singleOrNull()?.chapter?.id ?: return val chapterId = items.singleOrNull()?.id ?: return
cancelDownload(chapterId) cancelDownload(chapterId)
} }
ChapterDownloadAction.DELETE -> { ChapterDownloadAction.DELETE -> {
@ -842,14 +845,14 @@ class MangaScreenModel(
} }
fun toggleSelection( fun toggleSelection(
item: ChapterItem, item: ChapterList.Item,
selected: Boolean, selected: Boolean,
userSelected: Boolean = false, userSelected: Boolean = false,
fromLongPress: Boolean = false, fromLongPress: Boolean = false,
) { ) {
updateSuccessState { successState -> updateSuccessState { successState ->
val newChapters = successState.processedChapters.toMutableList().apply { val newChapters = successState.processedChapters.toMutableList().apply {
val selectedIndex = successState.processedChapters.indexOfFirst { it.chapter.id == item.chapter.id } val selectedIndex = successState.processedChapters.indexOfFirst { it.id == item.chapter.id }
if (selectedIndex < 0) return@apply if (selectedIndex < 0) return@apply
val selectedItem = get(selectedIndex) val selectedItem = get(selectedIndex)
@ -857,7 +860,7 @@ class MangaScreenModel(
val firstSelection = none { it.selected } val firstSelection = none { it.selected }
set(selectedIndex, selectedItem.copy(selected = selected)) set(selectedIndex, selectedItem.copy(selected = selected))
selectedChapterIds.addOrRemove(item.chapter.id, selected) selectedChapterIds.addOrRemove(item.id, selected)
if (selected && userSelected && fromLongPress) { if (selected && userSelected && fromLongPress) {
if (firstSelection) { if (firstSelection) {
@ -880,7 +883,7 @@ class MangaScreenModel(
range.forEach { range.forEach {
val inbetweenItem = get(it) val inbetweenItem = get(it)
if (!inbetweenItem.selected) { if (!inbetweenItem.selected) {
selectedChapterIds.add(inbetweenItem.chapter.id) selectedChapterIds.add(inbetweenItem.id)
set(it, inbetweenItem.copy(selected = true)) set(it, inbetweenItem.copy(selected = true))
} }
} }
@ -908,7 +911,7 @@ class MangaScreenModel(
fun toggleAllSelection(selected: Boolean) { fun toggleAllSelection(selected: Boolean) {
updateSuccessState { successState -> updateSuccessState { successState ->
val newChapters = successState.chapters.map { val newChapters = successState.chapters.map {
selectedChapterIds.addOrRemove(it.chapter.id, selected) selectedChapterIds.addOrRemove(it.id, selected)
it.copy(selected = selected) it.copy(selected = selected)
} }
selectedPositions[0] = -1 selectedPositions[0] = -1
@ -920,7 +923,7 @@ class MangaScreenModel(
fun invertSelection() { fun invertSelection() {
updateSuccessState { successState -> updateSuccessState { successState ->
val newChapters = successState.chapters.map { val newChapters = successState.chapters.map {
selectedChapterIds.addOrRemove(it.chapter.id, !it.selected) selectedChapterIds.addOrRemove(it.id, !it.selected)
it.copy(selected = !it.selected) it.copy(selected = !it.selected)
} }
selectedPositions[0] = -1 selectedPositions[0] = -1
@ -994,7 +997,7 @@ class MangaScreenModel(
val manga: Manga, val manga: Manga,
val source: Source, val source: Source,
val isFromSource: Boolean, val isFromSource: Boolean,
val chapters: List<ChapterItem>, val chapters: List<ChapterList.Item>,
val trackItems: List<TrackItem> = emptyList(), val trackItems: List<TrackItem> = emptyList(),
val isRefreshingData: Boolean = false, val isRefreshingData: Boolean = false,
val dialog: Dialog? = null, val dialog: Dialog? = null,
@ -1005,6 +1008,33 @@ class MangaScreenModel(
chapters.applyFilters(manga).toList() chapters.applyFilters(manga).toList()
} }
val chapterListItems by lazy {
processedChapters.insertSeparators { before, after ->
val (lowerChapter, higherChapter) = if (manga.sortDescending()) {
after to before
} else {
before to after
}
if (higherChapter == null) return@insertSeparators null
if (lowerChapter == null) {
floor(higherChapter.chapter.chapterNumber)
.toInt()
.minus(1)
.coerceAtLeast(0)
} else {
calculateChapterGap(higherChapter.chapter, lowerChapter.chapter)
}
.takeIf { it > 0 }
?.let { missingCount ->
ChapterList.MissingCount(
id = "${lowerChapter?.id}-${higherChapter.id}",
count = missingCount,
)
}
}
}
val trackingAvailable: Boolean val trackingAvailable: Boolean
get() = trackItems.isNotEmpty() get() = trackItems.isNotEmpty()
@ -1015,7 +1045,7 @@ class MangaScreenModel(
* Applies the view filters to the list of chapters obtained from the database. * Applies the view filters to the list of chapters obtained from the database.
* @return an observable of the list of chapters filtered and sorted. * @return an observable of the list of chapters filtered and sorted.
*/ */
private fun List<ChapterItem>.applyFilters(manga: Manga): Sequence<ChapterItem> { private fun List<ChapterList.Item>.applyFilters(manga: Manga): Sequence<ChapterList.Item> {
val isLocalManga = manga.isLocal() val isLocalManga = manga.isLocal()
val unreadFilter = manga.unreadFilter val unreadFilter = manga.unreadFilter
val downloadedFilter = manga.downloadedFilter val downloadedFilter = manga.downloadedFilter
@ -1031,11 +1061,21 @@ class MangaScreenModel(
} }
@Immutable @Immutable
data class ChapterItem( sealed class ChapterList {
@Immutable
data class MissingCount(
val id: String,
val count: Int,
) : ChapterList()
@Immutable
data class Item(
val chapter: Chapter, val chapter: Chapter,
val downloadState: Download.State, val downloadState: Download.State,
val downloadProgress: Int, val downloadProgress: Int,
val selected: Boolean = false, val selected: Boolean = false,
) { ) : ChapterList() {
val id = chapter.id
val isDownloaded = downloadState == Download.State.DOWNLOADED val isDownloaded = downloadState == Download.State.DOWNLOADED
} }
}

View file

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.util.chapter
import eu.kanade.domain.chapter.model.applyFilters import eu.kanade.domain.chapter.model.applyFilters
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.ui.manga.ChapterItem import eu.kanade.tachiyomi.ui.manga.ChapterList
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
@ -22,7 +22,7 @@ fun List<Chapter>.getNextUnread(manga: Manga, downloadManager: DownloadManager):
/** /**
* Gets next unread chapter with filters and sorting applied * Gets next unread chapter with filters and sorting applied
*/ */
fun List<ChapterItem>.getNextUnread(manga: Manga): Chapter? { fun List<ChapterList.Item>.getNextUnread(manga: Manga): Chapter? {
return applyFilters(manga).let { chapters -> return applyFilters(manga).let { chapters ->
if (manga.sortDescending()) { if (manga.sortDescending()) {
chapters.findLast { !it.chapter.read } chapters.findLast { !it.chapter.read }