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:
parent
b3d7c92475
commit
6d538db5f2
4 changed files with 189 additions and 107 deletions
|
@ -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
|
||||||
|
|
|
@ -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,92 +761,123 @@ 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
|
||||||
|
|
||||||
MangaChapterListItem(
|
when (item) {
|
||||||
title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) {
|
is ChapterList.MissingCount -> {
|
||||||
stringResource(
|
Row(
|
||||||
R.string.display_mode_chapter,
|
modifier = Modifier.padding(
|
||||||
formatChapterNumber(chapterItem.chapter.chapterNumber),
|
horizontal = MaterialTheme.padding.medium,
|
||||||
)
|
vertical = MaterialTheme.padding.small,
|
||||||
} else {
|
),
|
||||||
chapterItem.chapter.name
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
},
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
|
||||||
date = chapterItem.chapter.dateUpload
|
) {
|
||||||
.takeIf { it > 0L }
|
HorizontalDivider(modifier = Modifier.weight(1f))
|
||||||
?.let {
|
Text(
|
||||||
Date(it).toRelativeString(
|
text = pluralStringResource(
|
||||||
context,
|
id = R.plurals.missing_chapters,
|
||||||
dateRelativeTime,
|
count = item.count,
|
||||||
dateFormat,
|
item.count,
|
||||||
|
),
|
||||||
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
)
|
)
|
||||||
},
|
HorizontalDivider(modifier = Modifier.weight(1f))
|
||||||
readProgress = chapterItem.chapter.lastPageRead
|
}
|
||||||
.takeIf { !chapterItem.chapter.read && it > 0L }
|
}
|
||||||
?.let {
|
is ChapterList.Item -> {
|
||||||
stringResource(
|
MangaChapterListItem(
|
||||||
R.string.chapter_progress,
|
title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) {
|
||||||
it + 1,
|
stringResource(
|
||||||
)
|
R.string.display_mode_chapter,
|
||||||
},
|
formatChapterNumber(item.chapter.chapterNumber),
|
||||||
scanlator = chapterItem.chapter.scanlator.takeIf { !it.isNullOrBlank() },
|
)
|
||||||
read = chapterItem.chapter.read,
|
} else {
|
||||||
bookmark = chapterItem.chapter.bookmark,
|
item.chapter.name
|
||||||
selected = chapterItem.selected,
|
},
|
||||||
downloadIndicatorEnabled = chapters.fastAll { !it.selected },
|
date = item.chapter.dateUpload
|
||||||
downloadStateProvider = { chapterItem.downloadState },
|
.takeIf { it > 0L }
|
||||||
downloadProgressProvider = { chapterItem.downloadProgress },
|
?.let {
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
Date(it).toRelativeString(
|
||||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
context,
|
||||||
onLongClick = {
|
dateRelativeTime,
|
||||||
onChapterSelected(chapterItem, !chapterItem.selected, true, true)
|
dateFormat,
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
)
|
||||||
},
|
},
|
||||||
onClick = {
|
readProgress = item.chapter.lastPageRead
|
||||||
onChapterItemClick(
|
.takeIf { !item.chapter.read && it > 0L }
|
||||||
chapterItem = chapterItem,
|
?.let {
|
||||||
chapters = chapters,
|
stringResource(
|
||||||
onToggleSelection = { onChapterSelected(chapterItem, !chapterItem.selected, true, false) },
|
R.string.chapter_progress,
|
||||||
onChapterClicked = onChapterClicked,
|
it + 1,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
scanlator = item.chapter.scanlator.takeIf { !it.isNullOrBlank() },
|
||||||
|
read = item.chapter.read,
|
||||||
|
bookmark = item.chapter.bookmark,
|
||||||
|
selected = item.selected,
|
||||||
|
downloadIndicatorEnabled = !isAnyChapterSelected,
|
||||||
|
downloadStateProvider = { item.downloadState },
|
||||||
|
downloadProgressProvider = { item.downloadProgress },
|
||||||
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
|
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||||
|
onLongClick = {
|
||||||
|
onChapterSelected(item, !item.selected, true, true)
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
onChapterItemClick(
|
||||||
|
chapterItem = item,
|
||||||
|
isAnyChapterSelected = isAnyChapterSelected,
|
||||||
|
onToggleSelection = { onChapterSelected(item, !item.selected, true, false) },
|
||||||
|
onChapterClicked = onChapterClicked,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onDownloadClick = if (onDownloadChapter != null) {
|
||||||
|
{ onDownloadChapter(listOf(item), it) }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
|
onChapterSwipe = {
|
||||||
|
onChapterSwipe(item, it)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
onDownloadClick = if (onDownloadChapter != null) {
|
}
|
||||||
{ onDownloadChapter(listOf(chapterItem), it) }
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
},
|
|
||||||
onChapterSwipe = {
|
|
||||||
onChapterSwipe(chapterItem, 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
val chapter: Chapter,
|
@Immutable
|
||||||
val downloadState: Download.State,
|
data class MissingCount(
|
||||||
val downloadProgress: Int,
|
val id: String,
|
||||||
val selected: Boolean = false,
|
val count: Int,
|
||||||
) {
|
) : ChapterList()
|
||||||
val isDownloaded = downloadState == Download.State.DOWNLOADED
|
|
||||||
|
@Immutable
|
||||||
|
data class Item(
|
||||||
|
val chapter: Chapter,
|
||||||
|
val downloadState: Download.State,
|
||||||
|
val downloadProgress: Int,
|
||||||
|
val selected: Boolean = false,
|
||||||
|
) : ChapterList() {
|
||||||
|
val id = chapter.id
|
||||||
|
val isDownloaded = downloadState == Download.State.DOWNLOADED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
Reference in a new issue