From c290472012eec6364f63d56519cd9e2e975a7f34 Mon Sep 17 00:00:00 2001 From: NarwhalHorns Date: Sat, 21 Feb 2026 10:14:55 +0000 Subject: [PATCH] Add option for bookmarked chapters to download dropdown (mihonapp/mihon#2891) Co-authored-by: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com> (cherry picked from commit 3c6f0f1697ccab055ee7af47da84b2161d406f0c) (cherry picked from commit 185cd923c0bf838d55b38ce0ebfb5c010760b880) --- CHANGELOG.md | 1 + .../java/eu/kanade/domain/DomainModule.kt | 2 + .../components/DownloadDropdownMenu.kt | 1 + .../manga/MangaScreenConstants.kt | 1 + .../ui/library/LibraryScreenModel.kt | 68 +++++++++++++++++-- .../tachiyomi/ui/manga/MangaScreenModel.kt | 8 +++ .../GetBookmarkedChaptersByMangaId.kt | 33 +++++++++ .../interactor/GetMergedChaptersByMangaId.kt | 3 + .../moko-resources/base/strings.xml | 1 + 9 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 domain/src/main/java/tachiyomi/domain/chapter/interactor/GetBookmarkedChaptersByMangaId.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 70e240fe8..7cf728ffb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co - Automatically remove downloads on Suwayomi after reading, configurable via extension settings ([@cpiber](https://github.com/cpiber)) ([#2673](https://github.com/mihonapp/mihon/pull/2673)) - Display author & artist name in MAL search results ([@MajorTanya](https://github.com/MajorTanya)) ([#2833](https://github.com/mihonapp/mihon/pull/2833)) - Add filter options to Updates tab ([@MajorTanya](https://github.com/MajorTanya)) ([#2851](https://github.com/mihonapp/mihon/pull/2851)) +- Add bookmarked chapters to chapter download options ([@NarwhalHorns](https://github.com/NarwhalHorns)) ([#2891](https://github.com/mihonapp/mihon/pull/2891)) - Add `src:` prefix to search the library by source ID ([@MajorTanya](https://github.com/MajorTanya)) ([#2927](https://github.com/mihonapp/mihon/pull/2927)) - Add `src:local` as a way to search for Local Source entries ([@MajorTanya](https://github.com/MajorTanya)) ([#2928](https://github.com/mihonapp/mihon/pull/2928)) diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index 30c3244be..3c1c9e6bf 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -58,6 +58,7 @@ import tachiyomi.domain.category.interactor.SetMangaCategories import tachiyomi.domain.category.interactor.SetSortModeForCategory import tachiyomi.domain.category.interactor.UpdateCategory import tachiyomi.domain.category.repository.CategoryRepository +import tachiyomi.domain.chapter.interactor.GetBookmarkedChaptersByMangaId import tachiyomi.domain.chapter.interactor.GetChapter import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId @@ -161,6 +162,7 @@ class DomainModule : InjektModule { addSingletonFactory { ChapterRepositoryImpl(get()) } addFactory { GetChapter(get()) } addFactory { GetChaptersByMangaId(get()) } + addFactory { GetBookmarkedChaptersByMangaId(get(), get(), get()) } addFactory { GetChapterByUrlAndMangaId(get()) } addFactory { UpdateChapter(get()) } addFactory { SetReadStatus(get(), get(), get(), get(), get()) } diff --git a/app/src/main/java/eu/kanade/presentation/components/DownloadDropdownMenu.kt b/app/src/main/java/eu/kanade/presentation/components/DownloadDropdownMenu.kt index 7bdf97d71..dfe7998b7 100644 --- a/app/src/main/java/eu/kanade/presentation/components/DownloadDropdownMenu.kt +++ b/app/src/main/java/eu/kanade/presentation/components/DownloadDropdownMenu.kt @@ -44,6 +44,7 @@ private fun DownloadDropdownMenuItems( DownloadAction.NEXT_10_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 10, 10), DownloadAction.NEXT_25_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 25, 25), DownloadAction.UNREAD_CHAPTERS to stringResource(MR.strings.download_unread), + DownloadAction.BOOKMARKED_CHAPTERS to stringResource(MR.strings.download_bookmarked), ) options.forEach { (downloadAction, string) -> diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaScreenConstants.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaScreenConstants.kt index 8f337bb2a..c53e254b9 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreenConstants.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/MangaScreenConstants.kt @@ -6,6 +6,7 @@ enum class DownloadAction { NEXT_10_CHAPTERS, NEXT_25_CHAPTERS, UNREAD_CHAPTERS, + BOOKMARKED_CHAPTERS, } enum class EditCoverAction { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt index 52df18913..668880fb1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt @@ -94,6 +94,7 @@ import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.SetMangaCategories import tachiyomi.domain.category.model.Category import tachiyomi.domain.category.model.Category.Companion.UNCATEGORIZED_ID +import tachiyomi.domain.chapter.interactor.GetBookmarkedChaptersByMangaId import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId import tachiyomi.domain.chapter.interactor.GetMergedChaptersByMangaId import tachiyomi.domain.chapter.model.Chapter @@ -134,6 +135,7 @@ class LibraryScreenModel( private val getTracksPerManga: GetTracksPerManga = Injekt.get(), private val getNextChapters: GetNextChapters = Injekt.get(), private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get(), + private val getBookmarkedChaptersByMangaId: GetBookmarkedChaptersByMangaId = Injekt.get(), private val setReadStatus: SetReadStatus = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(), private val setMangaCategories: SetMangaCategories = Injekt.get(), @@ -891,15 +893,19 @@ class LibraryScreenModel( * Queues the amount specified of unread chapters from the list of selected manga */ fun performDownloadAction(action: DownloadAction) { - val mangas = state.value.selectedManga - val amount = when (action) { - DownloadAction.NEXT_1_CHAPTER -> 1 - DownloadAction.NEXT_5_CHAPTERS -> 5 - DownloadAction.NEXT_10_CHAPTERS -> 10 - DownloadAction.NEXT_25_CHAPTERS -> 25 - DownloadAction.UNREAD_CHAPTERS -> null + when (action) { + DownloadAction.NEXT_1_CHAPTER -> downloadNextChapters(1) + DownloadAction.NEXT_5_CHAPTERS -> downloadNextChapters(5) + DownloadAction.NEXT_10_CHAPTERS -> downloadNextChapters(10) + DownloadAction.NEXT_25_CHAPTERS -> downloadNextChapters(25) + DownloadAction.UNREAD_CHAPTERS -> downloadNextChapters(null) + DownloadAction.BOOKMARKED_CHAPTERS -> downloadBookmarkedChapters() } clearSelection() + } + + private fun downloadNextChapters(amount: Int?) { + val mangas = state.value.selectedManga screenModelScope.launchNonCancellable { mangas.forEach { manga -> // SY --> @@ -949,6 +955,54 @@ class LibraryScreenModel( } } + private fun downloadBookmarkedChapters() { + val mangas = state.value.selectedManga + screenModelScope.launchNonCancellable { + mangas.forEach { manga -> + // SY --> + if (manga.source == MERGED_SOURCE_ID) { + val mergedMangas = getMergedMangaById.await(manga.id) + .associateBy { it.id } + getBookmarkedChaptersByMangaId.await(manga.id) + .groupBy { it.mangaId } + .forEach ab@{ (mangaId, chapters) -> + val mergedManga = mergedMangas[mangaId] ?: return@ab + val downloadChapters = chapters.fastFilterNot { chapter -> + downloadManager.queueState.value.fastAny { chapter.id == it.chapter.id } || + downloadManager.isChapterDownloaded( + chapter.name, + chapter.scanlator, + chapter.url, + mergedManga.ogTitle, + mergedManga.source, + ) + } + + downloadManager.downloadChapters(mergedManga, downloadChapters) + } + + return@forEach + } + // SY <-- + + val chapters = getBookmarkedChaptersByMangaId.await(manga.id) + .fastFilterNot { chapter -> + downloadManager.getQueuedDownloadOrNull(chapter.id) != null || + downloadManager.isChapterDownloaded( + chapter.name, + chapter.scanlator, + chapter.url, + // SY --> + manga.ogTitle, + // SY <-- + manga.source, + ) + } + downloadManager.downloadChapters(manga, chapters) + } + } + } + // SY --> fun cleanTitles() { val regex1 = "\\[.*?]".toRegex() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt index 55117315f..a17e2f39f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt @@ -1322,6 +1322,13 @@ class MangaScreenModel( return if (manga.sortDescending()) chaptersSorted.reversed() else chaptersSorted } + private fun getBookmarkedChapters(): List { + val chapterItems = if (skipFiltered) filteredChapters.orEmpty() else allChapters.orEmpty() + return chapterItems + .filter { (chapter, dlStatus) -> chapter.bookmark && dlStatus == Download.State.NOT_DOWNLOADED } + .map { it.chapter } + } + private fun startDownload( chapters: List, startNow: Boolean, @@ -1384,6 +1391,7 @@ class MangaScreenModel( DownloadAction.NEXT_10_CHAPTERS -> getUnreadChaptersSorted().take(10) DownloadAction.NEXT_25_CHAPTERS -> getUnreadChaptersSorted().take(25) DownloadAction.UNREAD_CHAPTERS -> getUnreadChapters() + DownloadAction.BOOKMARKED_CHAPTERS -> getBookmarkedChapters() } if (chaptersToDownload.isNotEmpty()) { startDownload(chaptersToDownload, false) diff --git a/domain/src/main/java/tachiyomi/domain/chapter/interactor/GetBookmarkedChaptersByMangaId.kt b/domain/src/main/java/tachiyomi/domain/chapter/interactor/GetBookmarkedChaptersByMangaId.kt new file mode 100644 index 000000000..a27b3d13f --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/chapter/interactor/GetBookmarkedChaptersByMangaId.kt @@ -0,0 +1,33 @@ +package tachiyomi.domain.chapter.interactor + +import exh.source.MERGED_SOURCE_ID +import logcat.LogPriority +import tachiyomi.core.common.util.system.logcat +import tachiyomi.domain.chapter.model.Chapter +import tachiyomi.domain.chapter.repository.ChapterRepository +import tachiyomi.domain.manga.interactor.GetManga + +class GetBookmarkedChaptersByMangaId( + private val chapterRepository: ChapterRepository, + // SY --> + private val getManga: GetManga, + private val getMergedChaptersByMangaId: GetMergedChaptersByMangaId, + // SY <-- +) { + + suspend fun await(mangaId: Long): List { + return try { + // SY --> + val manga = getManga.await(mangaId) ?: return emptyList() + if (manga.source == MERGED_SOURCE_ID) { + return getMergedChaptersByMangaId.await(mangaId, applyFilter = /* KMK --> */false /* KMK <-- */) + .filter { it.bookmark } + } + // SY <-- + chapterRepository.getBookmarkedChaptersByMangaId(mangaId) + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + emptyList() + } + } +} diff --git a/domain/src/main/java/tachiyomi/domain/chapter/interactor/GetMergedChaptersByMangaId.kt b/domain/src/main/java/tachiyomi/domain/chapter/interactor/GetMergedChaptersByMangaId.kt index 39a283304..d6658141f 100644 --- a/domain/src/main/java/tachiyomi/domain/chapter/interactor/GetMergedChaptersByMangaId.kt +++ b/domain/src/main/java/tachiyomi/domain/chapter/interactor/GetMergedChaptersByMangaId.kt @@ -19,6 +19,7 @@ class GetMergedChaptersByMangaId( suspend fun await( mangaId: Long, dedupe: Boolean = true, + /** Filter excluded scanlators & bookmarked/unbookmarked filter */ applyFilter: Boolean = false, ): List { return transformMergedChapters( @@ -31,6 +32,7 @@ class GetMergedChaptersByMangaId( suspend fun subscribe( mangaId: Long, dedupe: Boolean = true, + /** Filter excluded scanlators & bookmarked/unbookmarked filter */ applyFilter: Boolean = false, ): Flow> { return try { @@ -46,6 +48,7 @@ class GetMergedChaptersByMangaId( private suspend fun getFromDatabase( mangaId: Long, + /** Filter excluded scanlators & bookmarked/unbookmarked filter */ applyFilter: Boolean = false, ): List { return try { diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 608a39356..a1466b4f4 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -759,6 +759,7 @@ By upload date Download Unread + Bookmarked Custom cover Cover Cover saved