Option to show only downloaded chapters

This commit is contained in:
Koitharu
2025-07-25 13:43:01 +03:00
parent d8efe374a8
commit d641e7933d
7 changed files with 66 additions and 21 deletions

View File

@@ -21,7 +21,6 @@ import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.flow.transformWhile
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withTimeout
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.parsers.util.suspendlazy.SuspendLazy
import java.util.concurrent.TimeUnit
@@ -135,6 +134,28 @@ fun <T1, T2, T3, T4, T5, T6, R> combine(
)
}
@Suppress("UNCHECKED_CAST")
fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
flow: Flow<T1>,
flow2: Flow<T2>,
flow3: Flow<T3>,
flow4: Flow<T4>,
flow5: Flow<T5>,
flow6: Flow<T6>,
flow7: Flow<T7>,
transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R,
): Flow<R> = combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) { args: Array<*> ->
transform(
args[0] as T1,
args[1] as T2,
args[2] as T3,
args[3] as T4,
args[4] as T5,
args[5] as T6,
args[6] as T7,
)
}
suspend fun <T : Any> Flow<T?>.firstNotNull(): T = checkNotNull(first { x -> x != null })
suspend fun <T : Any> Flow<T?>.firstNotNullOrNull(): T? = firstOrNull { x -> x != null }

View File

@@ -17,7 +17,6 @@ data class MangaDetails(
private val localManga: LocalManga?,
private val override: MangaOverride?,
val description: CharSequence?,
@Deprecated("Caller should decide if manga is loaded enough by itself")
val isLoaded: Boolean,
) {

View File

@@ -16,6 +16,7 @@ fun MangaDetails.mapChapters(
branch: String?,
bookmarks: List<Bookmark>,
isGrid: Boolean,
isDownloadedOnly: Boolean,
): List<ChapterListItem> {
val remoteChapters = chapters[branch].orEmpty()
val localChapters = local?.manga?.getChapters(branch).orEmpty()
@@ -35,19 +36,21 @@ fun MangaDetails.mapChapters(
null
}
var isUnread = currentChapterId !in ids
for (chapter in remoteChapters) {
val local = localMap?.remove(chapter.id)
if (chapter.id == currentChapterId) {
isUnread = true
if (!isDownloadedOnly || local?.manga?.chapters == null) {
for (chapter in remoteChapters) {
val local = localMap?.remove(chapter.id)
if (chapter.id == currentChapterId) {
isUnread = true
}
result += (local ?: chapter).toListItem(
isCurrent = chapter.id == currentChapterId,
isUnread = isUnread,
isNew = isUnread && result.size >= newFrom,
isDownloaded = local != null,
isBookmarked = chapter.id in bookmarked,
isGrid = isGrid,
)
}
result += (local ?: chapter).toListItem(
isCurrent = chapter.id == currentChapterId,
isUnread = isUnread,
isNew = isUnread && result.size >= newFrom,
isDownloaded = local != null,
isBookmarked = chapter.id in bookmarked,
isGrid = isGrid,
)
}
if (!localMap.isNullOrEmpty()) {
for (chapter in localMap.values) {

View File

@@ -9,6 +9,7 @@ import androidx.core.view.MenuProvider
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.slider.LabelFormatter
import com.google.android.material.slider.Slider
import com.google.android.material.slider.TickVisibilityMode
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
@@ -41,6 +42,10 @@ class ChapterPagesMenuProvider(
menu.findItem(R.id.action_search)?.isVisible = viewModel.isChaptersEmpty.value == false
menu.findItem(R.id.action_reversed)?.isChecked = viewModel.isChaptersReversed.value == true
menu.findItem(R.id.action_grid_view)?.isChecked = viewModel.isChaptersInGridView.value == true
menu.findItem(R.id.action_downloaded)?.let { menuItem ->
menuItem.isVisible = viewModel.mangaDetails.value?.local != null
menuItem.isChecked = viewModel.isDownloadedOnly.value == true
}
}
TAB_PAGES, TAB_BOOKMARKS -> {
@@ -64,6 +69,11 @@ class ChapterPagesMenuProvider(
true
}
R.id.action_downloaded -> {
viewModel.isDownloadedOnly.value = !menuItem.isChecked
true
}
else -> false
}
@@ -110,7 +120,7 @@ class ChapterPagesMenuProvider(
valueFrom = 50f
valueTo = 150f
stepSize = 5f
isTickVisible = false
tickVisibilityMode = TickVisibilityMode.TICK_VISIBILITY_HIDDEN
labelBehavior = LabelFormatter.LABEL_FLOATING
setLabelFormatter(IntPercentLabelFormatter(context))
setValueRounded(settings.gridSizePages.toFloat())

View File

@@ -81,6 +81,7 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(),
val menuInvalidator = MenuInvalidator(binding.toolbar)
viewModel.isChaptersReversed.observe(viewLifecycleOwner, menuInvalidator)
viewModel.isChaptersInGridView.observe(viewLifecycleOwner, menuInvalidator)
viewModel.isDownloadedOnly.observe(viewLifecycleOwner, menuInvalidator)
actionModeDelegate?.addListener(this, viewLifecycleOwner)
addSheetCallback(this, viewLifecycleOwner)

View File

@@ -87,6 +87,8 @@ abstract class ChaptersPagesViewModel(
valueProducer = { isChaptersGridView },
)
val isDownloadedOnly = MutableStateFlow(false)
val newChaptersCount = mangaDetails.flatMapLatest { d ->
if (d?.isLocal == false) {
interactor.observeNewChapters(d.id)
@@ -115,13 +117,15 @@ abstract class ChaptersPagesViewModel(
newChaptersCount,
bookmarks,
isChaptersInGridView,
) { manga, currentChapterId, branch, news, bookmarks, grid ->
isDownloadedOnly,
) { manga, currentChapterId, branch, news, bookmarks, grid, downloadedOnly ->
manga?.mapChapters(
currentChapterId,
news,
branch,
bookmarks,
grid,
currentChapterId = currentChapterId,
newCount = news,
branch = branch,
bookmarks = bookmarks,
isGrid = grid,
isDownloadedOnly = downloadedOnly,
).orEmpty()
},
isChaptersReversed,

View File

@@ -11,6 +11,13 @@
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="ifRoom|collapseActionView" />
<item
android:id="@+id/action_downloaded"
android:checkable="true"
android:orderInCategory="18"
android:title="@string/on_device"
app:showAsAction="never" />
<item
android:id="@+id/action_reversed"
android:checkable="true"