diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 0990eb355b..404a4c0878 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -304,13 +304,15 @@ class PreferencesHelper(val context: Context) { fun pinnedCatalogues() = flowPrefs.getStringSet("pinned_catalogues", mutableSetOf()) - fun downloadNewChapters() = flowPrefs.getBoolean(Keys.downloadNew, false) - fun saveChaptersAsCBZ() = flowPrefs.getBoolean("save_chapter_as_cbz", true) + fun downloadNewChapters() = flowPrefs.getBoolean(Keys.downloadNew, false) + fun downloadNewChaptersInCategories() = flowPrefs.getStringSet(Keys.downloadNewCategories, emptySet()) fun excludeCategoriesInDownloadNew() = flowPrefs.getStringSet(Keys.downloadNewCategoriesExclude, emptySet()) + fun autoDownloadAfterReading() = flowPrefs.getInt("auto_download_after_reading", 0) + fun defaultCategory() = prefs.getInt(Keys.defaultCategory, -1) fun skipRead() = prefs.getBoolean(Keys.skipRead, false) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index 330be04ed4..23dd0d9c77 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -23,6 +23,7 @@ import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter +import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem import eu.kanade.tachiyomi.ui.reader.chapter.ReaderChapterItem import eu.kanade.tachiyomi.ui.reader.loader.ChapterLoader import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter @@ -40,6 +41,7 @@ import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.executeOnIO import eu.kanade.tachiyomi.util.system.launchIO import eu.kanade.tachiyomi.util.system.launchUI +import eu.kanade.tachiyomi.util.system.toInt import eu.kanade.tachiyomi.util.system.withUIContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -107,6 +109,13 @@ class ReaderPresenter( */ private val isLoadingAdjacentChapterRelay = BehaviorRelay.create() + companion object { + /** + * To check if any chapter of the current reading session was downloaded + */ + private var isAnyPrevChapterDownloaded = false + } + /** * Chapter list for the active manga. It's retrieved lazily and should be accessed for the first * time in a background thread to avoid blocking the UI. @@ -168,8 +177,14 @@ class ReaderPresenter( val currentChapters = viewerChaptersRelay.value if (currentChapters != null) { currentChapters.unref() - saveChapterProgress(currentChapters.currChapter) - saveChapterHistory(currentChapters.currChapter) + val currentChapter = currentChapters.currChapter + saveChapterProgress(currentChapter) + saveChapterHistory(currentChapter) + val currentChapterPageCount = currentChapter.chapter.last_page_read + currentChapter.chapter.pages_left + if ((currentChapter.chapter.last_page_read + 1.0) / currentChapterPageCount > 0.33 || isAnyPrevChapterDownloaded) { + downloadNextChapters() + } + isAnyPrevChapterDownloaded = false } } @@ -493,6 +508,9 @@ class ReaderPresenter( (hasExtraPage && selectedChapter.pages?.lastIndex?.minus(1) == page.index) ) ) { + if (!isAnyPrevChapterDownloaded) { + isAnyPrevChapterDownloaded = downloadManager.isChapterDownloaded(selectedChapter.chapter, manga!!) + } selectedChapter.chapter.read = true updateTrackChapterAfterReading(selectedChapter) deleteChapterIfNeeded(selectedChapter) @@ -505,6 +523,45 @@ class ReaderPresenter( } } + private fun downloadNextChapters() { + val manga = manga ?: return + val chaptersNumberToDownload = preferences.autoDownloadAfterReading().get() + if (chaptersNumberToDownload == 0 || !manga.favorite) return + val currentChapter = viewerChapters?.currChapter ?: return + val isChapterDownloaded = downloadManager.isChapterDownloaded(currentChapter.chapter, manga) + if (isChapterDownloaded || isAnyPrevChapterDownloaded) { + downloadAutoNextChapters(chaptersNumberToDownload, !isChapterDownloaded && !currentChapter.chapter.read) + } + } + + private fun downloadAutoNextChapters(choice: Int, includeCurrentChapter: Boolean) { + val chaptersToDownload = getNextUnreadChaptersSorted(includeCurrentChapter).take(choice) + if (chaptersToDownload.isNotEmpty()) { + downloadChapters(chaptersToDownload) + } + } + + private fun getNextUnreadChaptersSorted(includeCurrentChapter: Boolean): List { + val currentChapterId = getCurrentChapter()?.chapter?.id + val chapterSort = ChapterSort(manga!!, chapterFilter, preferences) + + val chapters = chapterList.map { ChapterItem(it.chapter, manga!!) } + .filter { !it.read || it.id == currentChapterId } + .distinctBy { it.name } + .sortedWith(chapterSort.sortComparator(true)) + + val currentChapterIndex = chapters.indexOfFirst { it.id == currentChapterId } + return chapters.takeLast(chapters.lastIndex - currentChapterIndex + includeCurrentChapter.toInt()) + } + + /** + * Downloads the given list of chapters with the manager. + * @param chapters the list of chapters to download. + */ + private fun downloadChapters(chapters: List) { + downloadManager.downloadChapters(manga!!, chapters.filter { !it.isDownloaded }) + } + /** * Determines if deleting option is enabled and nth to last chapter actually exists. * If both conditions are satisfied enqueues chapter for delete diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt index 40a90e2b02..3f24d4b4a0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt @@ -1,16 +1,22 @@ package eu.kanade.tachiyomi.ui.reader.viewer import android.content.Context +import android.graphics.drawable.Drawable import android.util.AttributeSet import android.view.LayoutInflater import android.widget.LinearLayout +import android.widget.TextView import androidx.annotation.ColorInt import androidx.core.text.bold import androidx.core.text.buildSpannedString import androidx.core.view.isVisible import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.databinding.ReaderTransitionViewBinding import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition +import eu.kanade.tachiyomi.util.system.contextCompatDrawable +import eu.kanade.tachiyomi.util.system.dpToPx class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) { @@ -22,10 +28,11 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) } - fun bind(transition: ChapterTransition) { + fun bind(transition: ChapterTransition, downloadManager: DownloadManager, manga: Manga?) { + manga ?: return when (transition) { - is ChapterTransition.Prev -> bindPrevChapterTransition(transition) - is ChapterTransition.Next -> bindNextChapterTransition(transition) + is ChapterTransition.Prev -> bindPrevChapterTransition(transition, downloadManager, manga) + is ChapterTransition.Next -> bindNextChapterTransition(transition, downloadManager, manga) } missingChapterWarning(transition) @@ -34,21 +41,34 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At /** * Binds a previous chapter transition on this view and subscribes to the page load status. */ - private fun bindPrevChapterTransition(transition: ChapterTransition) { + private fun bindPrevChapterTransition( + transition: ChapterTransition, + downloadManager: DownloadManager, + manga: Manga, + ) { val prevChapter = transition.to val hasPrevChapter = prevChapter != null binding.lowerText.isVisible = hasPrevChapter if (hasPrevChapter) { binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START + val isPrevDownloaded = downloadManager.isChapterDownloaded(prevChapter!!.chapter, manga) + val isCurrentDownloaded = downloadManager.isChapterDownloaded(transition.from.chapter, manga) + + val downloadIcon = context.contextCompatDrawable(R.drawable.ic_file_download_24dp)?.mutate() + val cloudIcon = context.contextCompatDrawable(R.drawable.ic_cloud_24dp)?.mutate() binding.upperText.text = buildSpannedString { bold { append(context.getString(R.string.previous_title)) } - append("\n${prevChapter!!.chapter.name}") + append("\n${prevChapter.chapter.name}") } binding.lowerText.text = buildSpannedString { bold { append(context.getString(R.string.current_chapter)) } append("\n${transition.from.chapter.name}") } + + binding.lowerText.setDrawable(null) + if (isPrevDownloaded && !isCurrentDownloaded) binding.upperText.setDrawable(downloadIcon) + else if (!isPrevDownloaded && isCurrentDownloaded) binding.upperText.setDrawable(cloudIcon) } else { binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER binding.upperText.text = context.getString(R.string.theres_no_previous_chapter) @@ -58,27 +78,46 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At /** * Binds a next chapter transition on this view and subscribes to the load status. */ - private fun bindNextChapterTransition(transition: ChapterTransition) { + private fun bindNextChapterTransition( + transition: ChapterTransition, + downloadManager: DownloadManager, + manga: Manga, + ) { val nextChapter = transition.to val hasNextChapter = nextChapter != null binding.lowerText.isVisible = hasNextChapter if (hasNextChapter) { binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START + val isCurrentDownloaded = downloadManager.isChapterDownloaded(transition.from.chapter, manga) + val isNextDownloaded = downloadManager.isChapterDownloaded(nextChapter!!.chapter, manga) binding.upperText.text = buildSpannedString { bold { append(context.getString(R.string.finished_chapter)) } append("\n${transition.from.chapter.name}") } binding.lowerText.text = buildSpannedString { bold { append(context.getString(R.string.next_title)) } - append("\n${nextChapter!!.chapter.name}") + append("\n${nextChapter.chapter.name}") } + + val downloadIcon = context.contextCompatDrawable(R.drawable.ic_file_download_24dp)?.mutate() + val cloudIcon = context.contextCompatDrawable(R.drawable.ic_cloud_24dp)?.mutate() + + binding.upperText.setDrawable(null) + if (!isCurrentDownloaded && isNextDownloaded) binding.lowerText.setDrawable(downloadIcon) + else if (isCurrentDownloaded && !isNextDownloaded) binding.lowerText.setDrawable(cloudIcon) } else { binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER binding.upperText.text = context.getString(R.string.theres_no_next_chapter) } } + private fun TextView.setDrawable(drawable: Drawable?) { + drawable?.setTint(binding.lowerText.currentTextColor) + setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, drawable, null) + compoundDrawablePadding = 8.dpToPx + } + fun setTextColors(@ColorInt color: Int) { binding.upperText.setTextColor(color) binding.warningText.setTextColor(color) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt index a114c6579c..8f4505df56 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt @@ -57,7 +57,7 @@ class PagerTransitionHolder( addView(transitionView) addView(pagesContainer) - transitionView.bind(transition) + transitionView.bind(transition, viewer.downloadManager, viewer.activity.presenter.manga) transition.to?.let { observeStatus(it) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt index 97af56b81a..06b01961a1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt @@ -10,6 +10,7 @@ import androidx.core.view.children import androidx.core.view.isVisible import androidx.viewpager.widget.ViewPager import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition import eu.kanade.tachiyomi.ui.reader.model.InsertPage @@ -21,6 +22,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation import kotlinx.coroutines.MainScope import kotlinx.coroutines.cancel import timber.log.Timber +import uy.kohesive.injekt.injectLazy /** * Implementation of a [BaseViewer] to display pages with a [ViewPager]. @@ -28,6 +30,8 @@ import timber.log.Timber @Suppress("LeakingThis") abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { + val downloadManager: DownloadManager by injectLazy() + val scope = MainScope() /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt index e528266e90..175dad75b2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt @@ -64,7 +64,7 @@ class WebtoonTransitionHolder( * Binds the given [transition] with this view holder, subscribing to its state. */ fun bind(transition: ChapterTransition) { - transitionView.bind(transition) + transitionView.bind(transition, viewer.downloadManager, viewer.activity.presenter.manga) transition.to?.let { observeStatus(it, transition) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt index 418c635c3d..1f1065155a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt @@ -11,6 +11,7 @@ import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.WebtoonLayoutManager +import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition import eu.kanade.tachiyomi.ui.reader.model.ReaderPage @@ -21,6 +22,7 @@ import kotlinx.coroutines.MainScope import kotlinx.coroutines.cancel import rx.subscriptions.CompositeSubscription import timber.log.Timber +import uy.kohesive.injekt.injectLazy import kotlin.math.max import kotlin.math.min @@ -29,6 +31,8 @@ import kotlin.math.min */ class WebtoonViewer(val activity: ReaderActivity, val hasMargins: Boolean = false) : BaseViewer { + val downloadManager: DownloadManager by injectLazy() + private val scope = MainScope() /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt index 017da1d6e0..4b0057d083 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt @@ -82,9 +82,8 @@ class SettingsDownloadController : SettingsController() { titleRes = R.string.download_new_chapters switchPreference { - key = Keys.downloadNew + bindTo(preferences.downloadNewChapters()) titleRes = R.string.download_new_chapters - defaultValue = false } triStateListPreference(activity) { key = Keys.downloadNewCategories @@ -96,21 +95,39 @@ class SettingsDownloadController : SettingsController() { preferences.downloadNewChapters().asImmediateFlowIn(viewScope) { isVisible = it } } - preferenceCategory { - titleRes = R.string.automatic_removal + } - intListPreference(activity) { - key = Keys.deleteRemovedChapters - titleRes = R.string.delete_removed_chapters - summary = activity?.getString(R.string.delete_downloaded_if_removed_online) - entriesRes = arrayOf( - R.string.ask_on_chapters_page, - R.string.always_keep, - R.string.always_delete - ) - entryRange = 0..2 - defaultValue = 0 - } + preferenceCategory { + titleRes = R.string.download_ahead + + intListPreference(activity) { + bindTo(preferences.autoDownloadAfterReading()) + titleRes = R.string.auto_download_after_reading + entriesRes = arrayOf( + R.string.never, + R.string.next_unread_chapter, + R.string.next_2_unread, + R.string.next_3_unread, + R.string.next_5_unread, + ) + entryValues = listOf(0, 1, 2, 3, 5) + } + infoPreference(R.string.download_ahead_info) + } + + preferenceCategory { + titleRes = R.string.automatic_removal + + intListPreference(activity) { + bindTo(preferences.deleteRemovedChapters()) + titleRes = R.string.delete_removed_chapters + summary = activity?.getString(R.string.delete_downloaded_if_removed_online) + entriesRes = arrayOf( + R.string.ask_on_chapters_page, + R.string.always_keep, + R.string.always_delete + ) + entryRange = 0..2 } } } diff --git a/app/src/main/res/drawable/ic_cloud_24dp.xml b/app/src/main/res/drawable/ic_cloud_24dp.xml new file mode 100644 index 0000000000..052bbb49a5 --- /dev/null +++ b/app/src/main/res/drawable/ic_cloud_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9eb11b15bc..8121fd9898 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -525,6 +525,8 @@ Select ending chapter Search chapters Next unread chapter + Next 2 unread + Next 3 unread Next 5 unread Custom range All chapters @@ -932,6 +934,9 @@ Third to last chapter Fourth to last chapter Fifth to last chapter + Download ahead + Download next unread chapters after reading + Download ahead only applies to entries in library and if the last chapter read was downloaded Download new chapters Categories to include in download Delete removed chapters