add option to skip chapters marked read (#1791)

This commit is contained in:
Carlos 2019-04-03 04:22:32 -04:00 committed by inorichi
parent a62a7d5330
commit 77296348a0
5 changed files with 159 additions and 136 deletions

View File

@ -107,6 +107,8 @@ object PreferenceKeys {
const val defaultCategory = "default_category" const val defaultCategory = "default_category"
const val skipRead = "skip_read"
const val downloadBadge = "display_download_badge" const val downloadBadge = "display_download_badge"
@Deprecated("Use the preferences of the source") @Deprecated("Use the preferences of the source")

View File

@ -167,6 +167,8 @@ class PreferencesHelper(val context: Context) {
fun defaultCategory() = prefs.getInt(Keys.defaultCategory, -1) fun defaultCategory() = prefs.getInt(Keys.defaultCategory, -1)
fun skipRead() = prefs.getBoolean(Keys.skipRead, false)
fun migrateFlags() = rxPrefs.getInteger("migrate_flags", Int.MAX_VALUE) fun migrateFlags() = rxPrefs.getInteger("migrate_flags", Int.MAX_VALUE)
fun trustedSignatures() = rxPrefs.getStringSet("trusted_signatures", emptySet()) fun trustedSignatures() = rxPrefs.getStringSet("trusted_signatures", emptySet())

View File

@ -32,7 +32,7 @@ import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File import java.io.File
import java.util.Date import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
/** /**
@ -84,12 +84,25 @@ class ReaderPresenter(
private val chapterList by lazy { private val chapterList by lazy {
val manga = manga!! val manga = manga!!
val dbChapters = db.getChapters(manga).executeAsBlocking() val dbChapters = db.getChapters(manga).executeAsBlocking()
val selectedChapter = dbChapters.find { it.id == chapterId } val selectedChapter = dbChapters.find { it.id == chapterId }
?: error("Requested chapter of id $chapterId not found in chapter list") ?: error("Requested chapter of id $chapterId not found in chapter list")
val chaptersForReader =
if (preferences.skipRead()) {
var list = dbChapters.filter { it -> !it.read }.toMutableList()
val find = list.find { it.id == chapterId }
if (find == null) {
list.add(selectedChapter)
}
list
} else {
dbChapters
}
when (manga.sorting) { when (manga.sorting) {
Manga.SORTING_SOURCE -> ChapterLoadBySource().get(dbChapters) Manga.SORTING_SOURCE -> ChapterLoadBySource().get(chaptersForReader)
Manga.SORTING_NUMBER -> ChapterLoadByNumber().get(dbChapters, selectedChapter) Manga.SORTING_NUMBER -> ChapterLoadByNumber().get(chaptersForReader, selectedChapter)
else -> error("Unknown sorting method") else -> error("Unknown sorting method")
}.map(::ReaderChapter) }.map(::ReaderChapter)
} }
@ -165,12 +178,12 @@ class ReaderPresenter(
if (!needsInit()) return if (!needsInit()) return
db.getManga(mangaId).asRxObservable() db.getManga(mangaId).asRxObservable()
.first() .first()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnNext { init(it, initialChapterId) } .doOnNext { init(it, initialChapterId) }
.subscribeFirst({ _, _ -> .subscribeFirst({ _, _ ->
// Ignore onNext event // Ignore onNext event
}, ReaderActivity::setInitialChapterError) }, ReaderActivity::setInitialChapterError)
} }
/** /**
@ -193,13 +206,13 @@ class ReaderPresenter(
// Read chapterList from an io thread because it's retrieved lazily and would block main. // Read chapterList from an io thread because it's retrieved lazily and would block main.
activeChapterSubscription?.unsubscribe() activeChapterSubscription?.unsubscribe()
activeChapterSubscription = Observable activeChapterSubscription = Observable
.fromCallable { chapterList.first { chapterId == it.chapter.id } } .fromCallable { chapterList.first { chapterId == it.chapter.id } }
.flatMap { getLoadObservable(loader!!, it) } .flatMap { getLoadObservable(loader!!, it) }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeFirst({ _, _ -> .subscribeFirst({ _, _ ->
// Ignore onNext event // Ignore onNext event
}, ReaderActivity::setInitialChapterError) }, ReaderActivity::setInitialChapterError)
} }
/** /**
@ -214,23 +227,23 @@ class ReaderPresenter(
chapter: ReaderChapter chapter: ReaderChapter
): Observable<ViewerChapters> { ): Observable<ViewerChapters> {
return loader.loadChapter(chapter) return loader.loadChapter(chapter)
.andThen(Observable.fromCallable { .andThen(Observable.fromCallable {
val chapterPos = chapterList.indexOf(chapter) val chapterPos = chapterList.indexOf(chapter)
ViewerChapters(chapter, ViewerChapters(chapter,
chapterList.getOrNull(chapterPos - 1), chapterList.getOrNull(chapterPos - 1),
chapterList.getOrNull(chapterPos + 1)) chapterList.getOrNull(chapterPos + 1))
}) })
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnNext { newChapters -> .doOnNext { newChapters ->
val oldChapters = viewerChaptersRelay.value val oldChapters = viewerChaptersRelay.value
// Add new references first to avoid unnecessary recycling // Add new references first to avoid unnecessary recycling
newChapters.ref() newChapters.ref()
oldChapters?.unref() oldChapters?.unref()
viewerChaptersRelay.call(newChapters) viewerChaptersRelay.call(newChapters)
} }
} }
/** /**
@ -244,10 +257,10 @@ class ReaderPresenter(
activeChapterSubscription?.unsubscribe() activeChapterSubscription?.unsubscribe()
activeChapterSubscription = getLoadObservable(loader, chapter) activeChapterSubscription = getLoadObservable(loader, chapter)
.toCompletable() .toCompletable()
.onErrorComplete() .onErrorComplete()
.subscribe() .subscribe()
.also(::add) .also(::add)
} }
/** /**
@ -262,13 +275,13 @@ class ReaderPresenter(
activeChapterSubscription?.unsubscribe() activeChapterSubscription?.unsubscribe()
activeChapterSubscription = getLoadObservable(loader, chapter) activeChapterSubscription = getLoadObservable(loader, chapter)
.doOnSubscribe { isLoadingAdjacentChapterRelay.call(true) } .doOnSubscribe { isLoadingAdjacentChapterRelay.call(true) }
.doOnUnsubscribe { isLoadingAdjacentChapterRelay.call(false) } .doOnUnsubscribe { isLoadingAdjacentChapterRelay.call(false) }
.subscribeFirst({ view, _ -> .subscribeFirst({ view, _ ->
view.moveToPageIndex(0) view.moveToPageIndex(0)
}, { _, _ -> }, { _, _ ->
// Ignore onError event, viewers handle that state // Ignore onError event, viewers handle that state
}) })
} }
/** /**
@ -285,12 +298,12 @@ class ReaderPresenter(
val loader = loader ?: return val loader = loader ?: return
loader.loadChapter(chapter) loader.loadChapter(chapter)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
// Update current chapters whenever a chapter is preloaded // Update current chapters whenever a chapter is preloaded
.doOnCompleted { viewerChaptersRelay.value?.let(viewerChaptersRelay::call) } .doOnCompleted { viewerChaptersRelay.value?.let(viewerChaptersRelay::call) }
.onErrorComplete() .onErrorComplete()
.subscribe() .subscribe()
.also(::add) .also(::add)
} }
/** /**
@ -331,9 +344,9 @@ class ReaderPresenter(
*/ */
private fun saveChapterProgress(chapter: ReaderChapter) { private fun saveChapterProgress(chapter: ReaderChapter) {
db.updateChapterProgress(chapter.chapter).asRxCompletable() db.updateChapterProgress(chapter.chapter).asRxCompletable()
.onErrorComplete() .onErrorComplete()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe() .subscribe()
} }
/** /**
@ -342,9 +355,9 @@ class ReaderPresenter(
private fun saveChapterHistory(chapter: ReaderChapter) { private fun saveChapterHistory(chapter: ReaderChapter) {
val history = History.create(chapter.chapter).apply { last_read = Date().time } val history = History.create(chapter.chapter).apply { last_read = Date().time }
db.updateHistoryLastRead(history).asRxCompletable() db.updateHistoryLastRead(history).asRxCompletable()
.onErrorComplete() .onErrorComplete()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe() .subscribe()
} }
/** /**
@ -394,18 +407,18 @@ class ReaderPresenter(
db.updateMangaViewer(manga).executeAsBlocking() db.updateMangaViewer(manga).executeAsBlocking()
Observable.timer(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) Observable.timer(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribeFirst({ view, _ -> .subscribeFirst({ view, _ ->
val currChapters = viewerChaptersRelay.value val currChapters = viewerChaptersRelay.value
if (currChapters != null) { if (currChapters != null) {
// Save current page // Save current page
val currChapter = currChapters.currChapter val currChapter = currChapters.currChapter
currChapter.requestedPage = currChapter.chapter.last_page_read currChapter.requestedPage = currChapter.chapter.last_page_read
// Emit manga and chapters to the new viewer // Emit manga and chapters to the new viewer
view.setManga(manga) view.setManga(manga)
view.setChapters(currChapters) view.setChapters(currChapters)
} }
}) })
} }
/** /**
@ -446,22 +459,22 @@ class ReaderPresenter(
// Pictures directory. // Pictures directory.
val destDir = File(Environment.getExternalStorageDirectory().absolutePath + val destDir = File(Environment.getExternalStorageDirectory().absolutePath +
File.separator + Environment.DIRECTORY_PICTURES + File.separator + Environment.DIRECTORY_PICTURES +
File.separator + "Tachiyomi") File.separator + "Tachiyomi")
// Copy file in background. // Copy file in background.
Observable.fromCallable { saveImage(page, destDir, manga) } Observable.fromCallable { saveImage(page, destDir, manga) }
.doOnNext { file -> .doOnNext { file ->
DiskUtil.scanMedia(context, file) DiskUtil.scanMedia(context, file)
notifier.onComplete(file) notifier.onComplete(file)
} }
.doOnError { notifier.onError(it.message) } .doOnError { notifier.onError(it.message) }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeFirst( .subscribeFirst(
{ view, file -> view.onSaveImageResult(SaveImageResult.Success(file)) }, { view, file -> view.onSaveImageResult(SaveImageResult.Success(file)) },
{ view, error -> view.onSaveImageResult(SaveImageResult.Error(error)) } { view, error -> view.onSaveImageResult(SaveImageResult.Error(error)) }
) )
} }
/** /**
@ -479,13 +492,13 @@ class ReaderPresenter(
val destDir = File(context.cacheDir, "shared_image") val destDir = File(context.cacheDir, "shared_image")
Observable.fromCallable { destDir.delete() } // Keep only the last shared file Observable.fromCallable { destDir.delete() } // Keep only the last shared file
.map { saveImage(page, destDir, manga) } .map { saveImage(page, destDir, manga) }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeFirst( .subscribeFirst(
{ view, file -> view.onShareImageResult(file) }, { view, file -> view.onShareImageResult(file) },
{ view, error -> /* Empty */ } { view, error -> /* Empty */ }
) )
} }
/** /**
@ -497,28 +510,28 @@ class ReaderPresenter(
val stream = page.stream ?: return val stream = page.stream ?: return
Observable Observable
.fromCallable { .fromCallable {
if (manga.source == LocalSource.ID) { if (manga.source == LocalSource.ID) {
val context = Injekt.get<Application>() val context = Injekt.get<Application>()
LocalSource.updateCover(context, manga, stream()) LocalSource.updateCover(context, manga, stream())
R.string.cover_updated R.string.cover_updated
SetAsCoverResult.Success
} else {
val thumbUrl = manga.thumbnail_url ?: throw Exception("Image url not found")
if (manga.favorite) {
coverCache.copyToCache(thumbUrl, stream())
SetAsCoverResult.Success SetAsCoverResult.Success
} else { } else {
SetAsCoverResult.AddToLibraryFirst val thumbUrl = manga.thumbnail_url ?: throw Exception("Image url not found")
if (manga.favorite) {
coverCache.copyToCache(thumbUrl, stream())
SetAsCoverResult.Success
} else {
SetAsCoverResult.AddToLibraryFirst
}
} }
} }
} .subscribeOn(Schedulers.io())
.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread()) .subscribeFirst(
.subscribeFirst( { view, result -> view.onSetAsCoverResult(result) },
{ view, result -> view.onSetAsCoverResult(result) }, { view, _ -> view.onSetAsCoverResult(SetAsCoverResult.Error) }
{ view, _ -> view.onSetAsCoverResult(SetAsCoverResult.Error) } )
)
} }
/** /**
@ -559,26 +572,26 @@ class ReaderPresenter(
val trackManager = Injekt.get<TrackManager>() val trackManager = Injekt.get<TrackManager>()
db.getTracks(manga).asRxSingle() db.getTracks(manga).asRxSingle()
.flatMapCompletable { trackList -> .flatMapCompletable { trackList ->
Completable.concat(trackList.map { track -> Completable.concat(trackList.map { track ->
val service = trackManager.getService(track.sync_id) val service = trackManager.getService(track.sync_id)
if (service != null && service.isLogged && lastChapterRead > track.last_chapter_read) { if (service != null && service.isLogged && lastChapterRead > track.last_chapter_read) {
track.last_chapter_read = lastChapterRead track.last_chapter_read = lastChapterRead
// We wan't these to execute even if the presenter is destroyed and leaks // We wan't these to execute even if the presenter is destroyed and leaks
// for a while. The view can still be garbage collected. // for a while. The view can still be garbage collected.
Observable.defer { service.update(track) } Observable.defer { service.update(track) }
.map { db.insertTrack(track).executeAsBlocking() } .map { db.insertTrack(track).executeAsBlocking() }
.toCompletable() .toCompletable()
.onErrorComplete() .onErrorComplete()
} else { } else {
Completable.complete() Completable.complete()
} }
}) })
} }
.onErrorComplete() .onErrorComplete()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe() .subscribe()
} }
/** /**
@ -594,19 +607,19 @@ class ReaderPresenter(
if (removeAfterReadSlots == -1) return if (removeAfterReadSlots == -1) return
Completable Completable
.fromCallable { .fromCallable {
// Position of the read chapter // Position of the read chapter
val position = chapterList.indexOf(chapter) val position = chapterList.indexOf(chapter)
// Retrieve chapter to delete according to preference // Retrieve chapter to delete according to preference
val chapterToDelete = chapterList.getOrNull(position - removeAfterReadSlots) val chapterToDelete = chapterList.getOrNull(position - removeAfterReadSlots)
if (chapterToDelete != null) { if (chapterToDelete != null) {
downloadManager.enqueueDeleteChapters(listOf(chapterToDelete.chapter), manga) downloadManager.enqueueDeleteChapters(listOf(chapterToDelete.chapter), manga)
}
} }
} .onErrorComplete()
.onErrorComplete() .subscribeOn(Schedulers.io())
.subscribeOn(Schedulers.io()) .subscribe()
.subscribe()
} }
/** /**
@ -615,9 +628,9 @@ class ReaderPresenter(
*/ */
private fun deletePendingChapters() { private fun deletePendingChapters() {
Completable.fromCallable { downloadManager.deletePendingChapters() } Completable.fromCallable { downloadManager.deletePendingChapters() }
.onErrorComplete() .onErrorComplete()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe() .subscribe()
} }
} }

View File

@ -63,6 +63,11 @@ class SettingsReaderController : SettingsController() {
defaultValue = "500" defaultValue = "500"
summary = "%s" summary = "%s"
} }
switchPreference {
key = Keys.skipRead
titleRes = R.string.pref_skip_read_chapters
defaultValue = false
}
switchPreference { switchPreference {
key = Keys.fullscreen key = Keys.fullscreen
titleRes = R.string.pref_fullscreen titleRes = R.string.pref_fullscreen

View File

@ -178,6 +178,7 @@
<string name="pref_custom_brightness">Use custom brightness</string> <string name="pref_custom_brightness">Use custom brightness</string>
<string name="pref_custom_color_filter">Use custom color filter</string> <string name="pref_custom_color_filter">Use custom color filter</string>
<string name="pref_keep_screen_on">Keep screen on</string> <string name="pref_keep_screen_on">Keep screen on</string>
<string name="pref_skip_read_chapters">Skip chapters marked read</string>
<string name="pref_reader_navigation">Navigation</string> <string name="pref_reader_navigation">Navigation</string>
<string name="pref_read_with_volume_keys">Volume keys</string> <string name="pref_read_with_volume_keys">Volume keys</string>
<string name="pref_read_with_volume_keys_inverted">Invert volume keys</string> <string name="pref_read_with_volume_keys_inverted">Invert volume keys</string>