mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
Auto download next unread chapters while reading (#1174)
* Add auto download for next chapters * Change downloaded text in transition to icon * Change auto download while reading to download when exiting reader * change transition icon to compound * split downloadNew and downloadWhileReading setting * remove unused requestDownloadNextChapters * pass down downloadManager and manga to transitionView * removes useless settings * improves download next chapters * download only if 20% of chapter is read * Add download if any chapter read during last session * move downloadManager to viewer and isAnyPrevChapterDownloaded to companion object
This commit is contained in:
parent
0919148128
commit
f5f0001006
10 changed files with 167 additions and 29 deletions
|
@ -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)
|
||||
|
|
|
@ -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<Boolean>()
|
||||
|
||||
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<ChapterItem> {
|
||||
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<ChapterItem>) {
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
/**
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
10
app/src/main/res/drawable/ic_cloud_24dp.xml
Normal file
10
app/src/main/res/drawable/ic_cloud_24dp.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?actionBarTintColor">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96z"/>
|
||||
</vector>
|
|
@ -525,6 +525,8 @@
|
|||
<string name="select_ending_chapter">Select ending chapter</string>
|
||||
<string name="search_chapters">Search chapters</string>
|
||||
<string name="next_unread_chapter">Next unread chapter</string>
|
||||
<string name="next_2_unread">Next 2 unread</string>
|
||||
<string name="next_3_unread">Next 3 unread</string>
|
||||
<string name="next_5_unread">Next 5 unread</string>
|
||||
<string name="custom_range">Custom range</string>
|
||||
<string name="all_chapters">All chapters</string>
|
||||
|
@ -932,6 +934,9 @@
|
|||
<string name="third_to_last">Third to last chapter</string>
|
||||
<string name="fourth_to_last">Fourth to last chapter</string>
|
||||
<string name="fifth_to_last">Fifth to last chapter</string>
|
||||
<string name="download_ahead">Download ahead</string>
|
||||
<string name="auto_download_after_reading">Download next unread chapters after reading</string>
|
||||
<string name="download_ahead_info">Download ahead only applies to entries in library and if the last chapter read was downloaded</string>
|
||||
<string name="download_new_chapters">Download new chapters</string>
|
||||
<string name="categories_to_include_in_download">Categories to include in download</string>
|
||||
<string name="delete_removed_chapters">Delete removed chapters</string>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue