mirror of
https://github.com/mihonapp/mihon.git
synced 2024-12-24 18:08:24 +01:00
Add chapter loader, drop non seamless mode
This commit is contained in:
parent
21ba371a32
commit
658860fdff
@ -54,8 +54,6 @@ class PreferenceKeys(context: Context) {
|
|||||||
|
|
||||||
val lastUsedCategory = context.getString(R.string.pref_last_used_category_key)
|
val lastUsedCategory = context.getString(R.string.pref_last_used_category_key)
|
||||||
|
|
||||||
val seamlessMode = context.getString(R.string.pref_seamless_mode_key)
|
|
||||||
|
|
||||||
val catalogueAsList = context.getString(R.string.pref_display_catalogue_as_list)
|
val catalogueAsList = context.getString(R.string.pref_display_catalogue_as_list)
|
||||||
|
|
||||||
val enabledLanguages = context.getString(R.string.pref_source_languages)
|
val enabledLanguages = context.getString(R.string.pref_source_languages)
|
||||||
|
@ -101,8 +101,6 @@ class PreferencesHelper(private val context: Context) {
|
|||||||
|
|
||||||
fun lastVersionCode() = rxPrefs.getInteger("last_version_code", 0)
|
fun lastVersionCode() = rxPrefs.getInteger("last_version_code", 0)
|
||||||
|
|
||||||
fun seamlessMode() = prefs.getBoolean(keys.seamlessMode, true)
|
|
||||||
|
|
||||||
fun catalogueAsList() = rxPrefs.getBoolean(keys.catalogueAsList, false)
|
fun catalogueAsList() = rxPrefs.getBoolean(keys.catalogueAsList, false)
|
||||||
|
|
||||||
fun enabledLanguages() = rxPrefs.getStringSet(keys.enabledLanguages, setOf("EN"))
|
fun enabledLanguages() = rxPrefs.getStringSet(keys.enabledLanguages, setOf("EN"))
|
||||||
|
138
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt
Normal file
138
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
|
import eu.kanade.tachiyomi.data.source.Source
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.util.plusAssign
|
||||||
|
import rx.Observable
|
||||||
|
import rx.schedulers.Schedulers
|
||||||
|
import rx.subscriptions.CompositeSubscription
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.PriorityBlockingQueue
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
class ChapterLoader(
|
||||||
|
private val downloadManager: DownloadManager,
|
||||||
|
private val manga: Manga,
|
||||||
|
private val source: Source
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val queue = PriorityBlockingQueue<PriorityPage>()
|
||||||
|
private val subscriptions = CompositeSubscription()
|
||||||
|
|
||||||
|
fun init() {
|
||||||
|
prepareOnlineReading()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restart() {
|
||||||
|
cleanup()
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cleanup() {
|
||||||
|
subscriptions.clear()
|
||||||
|
queue.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareOnlineReading() {
|
||||||
|
subscriptions += Observable.defer { Observable.just(queue.take().page) }
|
||||||
|
.filter { it.status == Page.QUEUE }
|
||||||
|
.concatMap { source.fetchImage(it) }
|
||||||
|
.repeat()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.subscribe({
|
||||||
|
}, {
|
||||||
|
if (it !is InterruptedException) {
|
||||||
|
Timber.e(it, it.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadChapter(chapter: ReaderChapter) = Observable.just(chapter)
|
||||||
|
.flatMap {
|
||||||
|
if (chapter.pages == null)
|
||||||
|
retrievePageList(chapter)
|
||||||
|
else
|
||||||
|
Observable.just(chapter.pages!!)
|
||||||
|
}
|
||||||
|
.doOnNext { pages ->
|
||||||
|
// Now that the number of pages is known, fix the requested page if the last one
|
||||||
|
// was requested.
|
||||||
|
if (chapter.requestedPage == -1) {
|
||||||
|
chapter.requestedPage = pages.lastIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPages(chapter)
|
||||||
|
}
|
||||||
|
.map { chapter }
|
||||||
|
|
||||||
|
private fun retrievePageList(chapter: ReaderChapter) = Observable.just(chapter)
|
||||||
|
.flatMap {
|
||||||
|
// Check if the chapter is downloaded.
|
||||||
|
chapter.isDownloaded = downloadManager.isChapterDownloaded(source, manga, chapter)
|
||||||
|
|
||||||
|
// Fetch the page list from disk.
|
||||||
|
if (chapter.isDownloaded)
|
||||||
|
Observable.just(downloadManager.getSavedPageList(source, manga, chapter)!!)
|
||||||
|
// Fetch the page list from cache or fallback to network
|
||||||
|
else
|
||||||
|
source.fetchPageList(chapter)
|
||||||
|
}
|
||||||
|
.doOnNext { pages ->
|
||||||
|
chapter.pages = pages
|
||||||
|
pages.forEach { it.chapter = chapter }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadPages(chapter: ReaderChapter) {
|
||||||
|
if (chapter.isDownloaded) {
|
||||||
|
loadDownloadedPages(chapter)
|
||||||
|
} else {
|
||||||
|
loadOnlinePages(chapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadDownloadedPages(chapter: ReaderChapter) {
|
||||||
|
val chapterDir = downloadManager.getAbsoluteChapterDirectory(source, manga, chapter)
|
||||||
|
subscriptions += Observable.from(chapter.pages!!)
|
||||||
|
.flatMap { downloadManager.getDownloadedImage(it, chapterDir) }
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadOnlinePages(chapter: ReaderChapter) {
|
||||||
|
chapter.pages?.let { pages ->
|
||||||
|
val startPage = chapter.requestedPage
|
||||||
|
val pagesToLoad = if (startPage == 0)
|
||||||
|
pages
|
||||||
|
else
|
||||||
|
pages.drop(startPage)
|
||||||
|
|
||||||
|
pagesToLoad.forEach { queue.offer(PriorityPage(it, 0)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadPriorizedPage(page: Page) {
|
||||||
|
queue.offer(PriorityPage(page, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun retryPage(page: Page) {
|
||||||
|
queue.offer(PriorityPage(page, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class PriorityPage(val page: Page, val priority: Int): Comparable<PriorityPage> {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val idGenerator = AtomicInteger()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val identifier = idGenerator.incrementAndGet()
|
||||||
|
|
||||||
|
override fun compareTo(other: PriorityPage): Int {
|
||||||
|
val p = other.priority.compareTo(priority)
|
||||||
|
return if (p != 0) p else identifier.compareTo(other.identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -20,7 +20,6 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
|
||||||
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
|
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader
|
||||||
@ -116,16 +115,6 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
setSystemUiVisibility()
|
setSystemUiVisibility()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
viewer?.let {
|
|
||||||
val activePage = it.getActivePage()
|
|
||||||
if (activePage != null) {
|
|
||||||
presenter.currentPage = activePage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
subscriptions.unsubscribe()
|
subscriptions.unsubscribe()
|
||||||
popupMenu?.dismiss()
|
popupMenu?.dismiss()
|
||||||
@ -230,6 +219,9 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from the presenter at startup, allowing to prepare the selected reader.
|
||||||
|
*/
|
||||||
fun onMangaOpen(manga: Manga) {
|
fun onMangaOpen(manga: Manga) {
|
||||||
if (viewer == null) {
|
if (viewer == null) {
|
||||||
viewer = getOrCreateViewer(manga)
|
viewer = getOrCreateViewer(manga)
|
||||||
@ -243,22 +235,23 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
please_wait.startAnimation(AnimationUtils.loadAnimation(this, R.anim.fade_in_long))
|
please_wait.startAnimation(AnimationUtils.loadAnimation(this, R.anim.fade_in_long))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onChapterReady(manga: Manga, chapter: Chapter, currentPage: Page?) {
|
fun onChapterReady(chapter: ReaderChapter) {
|
||||||
please_wait.visibility = View.GONE
|
please_wait.visibility = View.GONE
|
||||||
val activePage = currentPage ?: chapter.pages.last()
|
val pages = chapter.pages ?: run { onChapterError(Exception("Null pages")); return }
|
||||||
|
val activePage = pages.getOrElse(chapter.requestedPage) { pages.first() }
|
||||||
|
|
||||||
viewer?.onPageListReady(chapter, activePage)
|
viewer?.onPageListReady(chapter, activePage)
|
||||||
setActiveChapter(chapter, activePage.pageNumber)
|
setActiveChapter(chapter, activePage.pageNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onEnterChapter(chapter: Chapter, currentPage: Int) {
|
fun onEnterChapter(chapter: ReaderChapter, currentPage: Int) {
|
||||||
val activePage = if (currentPage == -1) chapter.pages.lastIndex else currentPage
|
val activePage = if (currentPage == -1) chapter.pages!!.lastIndex else currentPage
|
||||||
presenter.setActiveChapter(chapter)
|
presenter.setActiveChapter(chapter)
|
||||||
setActiveChapter(chapter, activePage)
|
setActiveChapter(chapter, activePage)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setActiveChapter(chapter: Chapter, currentPage: Int) {
|
fun setActiveChapter(chapter: ReaderChapter, currentPage: Int) {
|
||||||
val numPages = chapter.pages.size
|
val numPages = chapter.pages!!.size
|
||||||
if (page_seekbar.rotation != 180f) {
|
if (page_seekbar.rotation != 180f) {
|
||||||
right_page_text.text = "$numPages"
|
right_page_text.text = "$numPages"
|
||||||
left_page_text.text = "${currentPage + 1}"
|
left_page_text.text = "${currentPage + 1}"
|
||||||
@ -275,7 +268,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
chapter.name)
|
chapter.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onAppendChapter(chapter: Chapter) {
|
fun onAppendChapter(chapter: ReaderChapter) {
|
||||||
viewer?.onPageListAppendReady(chapter)
|
viewer?.onPageListAppendReady(chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,7 +317,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
viewer?.let {
|
viewer?.let {
|
||||||
val activePage = it.getActivePage()
|
val activePage = it.getActivePage()
|
||||||
if (activePage != null) {
|
if (activePage != null) {
|
||||||
val requestedPage = activePage.chapter.pages[pageIndex]
|
val requestedPage = activePage.chapter.pages!![pageIndex]
|
||||||
it.setActivePage(requestedPage)
|
it.setActivePage(requestedPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
|
||||||
|
class ReaderChapter(c: Chapter) : Chapter by c {
|
||||||
|
|
||||||
|
@Transient var pages: List<Page>? = null
|
||||||
|
|
||||||
|
var isDownloaded: Boolean = false
|
||||||
|
|
||||||
|
var requestedPage: Int = 0
|
||||||
|
}
|
@ -8,66 +8,127 @@ import eu.kanade.tachiyomi.data.database.models.History
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
|
||||||
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager
|
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager
|
||||||
import eu.kanade.tachiyomi.data.mangasync.UpdateMangaSyncService
|
import eu.kanade.tachiyomi.data.mangasync.UpdateMangaSyncService
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.source.Source
|
|
||||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
|
import eu.kanade.tachiyomi.util.RetryWithDelay
|
||||||
import eu.kanade.tachiyomi.util.SharedData
|
import eu.kanade.tachiyomi.util.SharedData
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import rx.subjects.PublishSubject
|
import uy.kohesive.injekt.injectLazy
|
||||||
import timber.log.Timber
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presenter of [ReaderActivity].
|
||||||
|
*/
|
||||||
class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
||||||
|
|
||||||
@Inject lateinit var prefs: PreferencesHelper
|
/**
|
||||||
@Inject lateinit var db: DatabaseHelper
|
* Preferences.
|
||||||
@Inject lateinit var downloadManager: DownloadManager
|
*/
|
||||||
@Inject lateinit var syncManager: MangaSyncManager
|
val prefs: PreferencesHelper by injectLazy()
|
||||||
@Inject lateinit var sourceManager: SourceManager
|
|
||||||
@Inject lateinit var chapterCache: ChapterCache
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database.
|
||||||
|
*/
|
||||||
|
val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download manager.
|
||||||
|
*/
|
||||||
|
val downloadManager: DownloadManager by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync manager.
|
||||||
|
*/
|
||||||
|
val syncManager: MangaSyncManager by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source manager.
|
||||||
|
*/
|
||||||
|
val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chapter cache.
|
||||||
|
*/
|
||||||
|
val chapterCache: ChapterCache by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manga being read.
|
||||||
|
*/
|
||||||
lateinit var manga: Manga
|
lateinit var manga: Manga
|
||||||
private set
|
private set
|
||||||
|
|
||||||
lateinit var chapter: Chapter
|
/**
|
||||||
|
* Active chapter.
|
||||||
|
*/
|
||||||
|
lateinit var chapter: ReaderChapter
|
||||||
private set
|
private set
|
||||||
|
|
||||||
lateinit var source: Source
|
/**
|
||||||
private set
|
* Previous chapter of the active.
|
||||||
|
*/
|
||||||
|
private var prevChapter: ReaderChapter? = null
|
||||||
|
|
||||||
var requestedPage: Int = 0
|
/**
|
||||||
var currentPage: Page? = null
|
* Next chapter of the active.
|
||||||
private var nextChapter: Chapter? = null
|
*/
|
||||||
private var previousChapter: Chapter? = null
|
private var nextChapter: ReaderChapter? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source of the manga.
|
||||||
|
*/
|
||||||
|
private val source by lazy { sourceManager.get(manga.source)!! }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
private val chapterList by lazy {
|
||||||
|
val dbChapters = db.getChapters(manga).executeAsBlocking().map { it.toModel() }
|
||||||
|
|
||||||
|
val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) {
|
||||||
|
Manga.SORTING_SOURCE -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
|
||||||
|
Manga.SORTING_NUMBER -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) }
|
||||||
|
else -> throw NotImplementedError("Unknown sorting method")
|
||||||
|
}
|
||||||
|
|
||||||
|
dbChapters.sortedWith(Comparator<Chapter> { c1, c2 -> sortFunction(c1, c2) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of manga services linked to the active manga, or null if auto syncing is not enabled.
|
||||||
|
*/
|
||||||
private var mangaSyncList: List<MangaSync>? = null
|
private var mangaSyncList: List<MangaSync>? = null
|
||||||
|
|
||||||
private val retryPageSubject by lazy { PublishSubject.create<Page>() }
|
/**
|
||||||
private val pageInitializerSubject by lazy { PublishSubject.create<Chapter>() }
|
* Chapter loader whose job is to obtain the chapter list and initialize every page.
|
||||||
|
*/
|
||||||
val isSeamlessMode by lazy { prefs.seamlessMode() }
|
private val loader by lazy { ChapterLoader(downloadManager, manga, source) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription for appending a chapter to the reader (seamless mode).
|
||||||
|
*/
|
||||||
private var appenderSubscription: Subscription? = null
|
private var appenderSubscription: Subscription? = null
|
||||||
|
|
||||||
private val PREPARE_READER = 1
|
/**
|
||||||
private val GET_PAGE_LIST = 2
|
* Subscription for retrieving the adjacent chapters to the current one.
|
||||||
private val GET_ADJACENT_CHAPTERS = 3
|
*/
|
||||||
private val GET_MANGA_SYNC = 4
|
private var adjacentChaptersSubscription: Subscription? = null
|
||||||
private val PRELOAD_NEXT_CHAPTER = 5
|
|
||||||
|
|
||||||
private val MANGA_KEY = "manga_key"
|
companion object {
|
||||||
private val CHAPTER_KEY = "chapter_key"
|
/**
|
||||||
private val PAGE_KEY = "page_key"
|
* Id of the restartable that loads the active chapter.
|
||||||
|
*/
|
||||||
|
private const val LOAD_ACTIVE_CHAPTER = 1
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
@ -75,306 +136,287 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
|||||||
if (savedState == null) {
|
if (savedState == null) {
|
||||||
val event = SharedData.get(ReaderEvent::class.java) ?: return
|
val event = SharedData.get(ReaderEvent::class.java) ?: return
|
||||||
manga = event.manga
|
manga = event.manga
|
||||||
chapter = event.chapter
|
chapter = event.chapter.toModel()
|
||||||
} else {
|
} else {
|
||||||
manga = savedState.getSerializable(MANGA_KEY) as Manga
|
manga = savedState.getSerializable(ReaderPresenter::manga.name) as Manga
|
||||||
chapter = savedState.getSerializable(CHAPTER_KEY) as Chapter
|
chapter = savedState.getSerializable(ReaderPresenter::chapter.name) as ReaderChapter
|
||||||
requestedPage = savedState.getInt(PAGE_KEY)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
source = sourceManager.get(manga.source)!!
|
// Send the active manga to the view to initialize the reader.
|
||||||
|
Observable.just(manga)
|
||||||
|
.subscribeLatestCache({ view, manga -> view.onMangaOpen(manga) })
|
||||||
|
|
||||||
initializeSubjects()
|
// Retrieve the sync list if auto syncing is enabled.
|
||||||
|
if (prefs.autoUpdateMangaSync()) {
|
||||||
|
add(db.getMangasSync(manga).asRxSingle()
|
||||||
|
.subscribe({ mangaSyncList = it }))
|
||||||
|
}
|
||||||
|
|
||||||
restartableLatestCache(PREPARE_READER,
|
restartableLatestCache(LOAD_ACTIVE_CHAPTER,
|
||||||
{ Observable.just(manga) },
|
{ loadChapterObservable(chapter) },
|
||||||
{ view, manga -> view.onMangaOpen(manga) })
|
{ view, chapter -> view.onChapterReady(this.chapter) },
|
||||||
|
|
||||||
startableLatestCache(GET_ADJACENT_CHAPTERS,
|
|
||||||
{ getAdjacentChaptersObservable() },
|
|
||||||
{ view, pair -> view.onAdjacentChapters(pair.first, pair.second) })
|
|
||||||
|
|
||||||
startable(PRELOAD_NEXT_CHAPTER,
|
|
||||||
{ getPreloadNextChapterObservable() },
|
|
||||||
{ },
|
|
||||||
{ error -> Timber.e("Error preloading chapter") })
|
|
||||||
|
|
||||||
|
|
||||||
restartable(GET_MANGA_SYNC,
|
|
||||||
{ getMangaSyncObservable().subscribe() })
|
|
||||||
|
|
||||||
restartableLatestCache(GET_PAGE_LIST,
|
|
||||||
{ getPageListObservable(chapter) },
|
|
||||||
{ view, chapter -> view.onChapterReady(manga, this.chapter, currentPage) },
|
|
||||||
{ view, error -> view.onChapterError(error) })
|
{ view, error -> view.onChapterError(error) })
|
||||||
|
|
||||||
if (savedState == null) {
|
if (savedState == null) {
|
||||||
start(PREPARE_READER)
|
|
||||||
loadChapter(chapter)
|
loadChapter(chapter)
|
||||||
if (prefs.autoUpdateMangaSync()) {
|
|
||||||
start(GET_MANGA_SYNC)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSave(state: Bundle) {
|
override fun onSave(state: Bundle) {
|
||||||
|
chapter.requestedPage = chapter.last_page_read
|
||||||
onChapterLeft()
|
onChapterLeft()
|
||||||
state.putSerializable(MANGA_KEY, manga)
|
state.putSerializable(ReaderPresenter::manga.name, manga)
|
||||||
state.putSerializable(CHAPTER_KEY, chapter)
|
state.putSerializable(ReaderPresenter::chapter.name, chapter)
|
||||||
state.putSerializable(PAGE_KEY, currentPage?.pageNumber ?: 0)
|
|
||||||
super.onSave(state)
|
super.onSave(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initializeSubjects() {
|
override fun onDestroy() {
|
||||||
// Listen for pages initialization events
|
loader.cleanup()
|
||||||
add(pageInitializerSubject.observeOn(Schedulers.io())
|
super.onDestroy()
|
||||||
.concatMap { ch ->
|
|
||||||
val observable: Observable<Page>
|
|
||||||
if (ch.isDownloaded) {
|
|
||||||
val chapterDir = downloadManager.getAbsoluteChapterDirectory(source, manga, ch)
|
|
||||||
observable = Observable.from(ch.pages)
|
|
||||||
.flatMap { downloadManager.getDownloadedImage(it, chapterDir) }
|
|
||||||
} else {
|
|
||||||
observable = source.let { source ->
|
|
||||||
if (source is OnlineSource) {
|
|
||||||
source.fetchAllImageUrlsFromPageList(ch.pages)
|
|
||||||
.flatMap({ source.getCachedImage(it) }, 2)
|
|
||||||
.doOnCompleted { source.savePageList(ch, ch.pages) }
|
|
||||||
} else {
|
|
||||||
Observable.from(ch.pages)
|
|
||||||
.flatMap { source.fetchImage(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
observable.doOnCompleted {
|
|
||||||
if (!isSeamlessMode && chapter === ch) {
|
|
||||||
preloadNextChapter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.subscribe())
|
|
||||||
|
|
||||||
// Listen por retry events
|
|
||||||
add(retryPageSubject.observeOn(Schedulers.io())
|
|
||||||
.flatMap { source.fetchImage(it) }
|
|
||||||
.subscribe())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the page list of a chapter
|
/**
|
||||||
private fun getPageListObservable(chapter: Chapter): Observable<Chapter> {
|
* Converts a chapter to a [ReaderChapter] if needed.
|
||||||
val observable: Observable<List<Page>> = if (chapter.isDownloaded)
|
*/
|
||||||
// Fetch the page list from disk
|
private fun Chapter.toModel(): ReaderChapter {
|
||||||
Observable.just(downloadManager.getSavedPageList(source, manga, chapter)!!)
|
if (this is ReaderChapter) return this
|
||||||
else
|
return ReaderChapter(this)
|
||||||
// Fetch the page list from cache or fallback to network
|
|
||||||
source.fetchPageList(chapter)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
|
|
||||||
return observable.map { pages ->
|
|
||||||
for (page in pages) {
|
|
||||||
page.chapter = chapter
|
|
||||||
}
|
|
||||||
chapter.pages = pages
|
|
||||||
if (requestedPage >= -1 || currentPage == null) {
|
|
||||||
if (requestedPage == -1) {
|
|
||||||
currentPage = pages[pages.size - 1]
|
|
||||||
} else {
|
|
||||||
currentPage = pages[requestedPage]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requestedPage = -2
|
|
||||||
pageInitializerSubject.onNext(chapter)
|
|
||||||
chapter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAdjacentChaptersObservable(): Observable<Pair<Chapter, Chapter>> {
|
/**
|
||||||
val strategy = getAdjacentChaptersStrategy()
|
* Returns an observable that loads the given chapter, discarding any previous work.
|
||||||
return Observable.zip(strategy.first, strategy.second) { prev, next -> Pair(prev, next) }
|
*
|
||||||
.doOnNext { pair ->
|
* @param chapter the now active chapter.
|
||||||
previousChapter = pair.first
|
*/
|
||||||
nextChapter = pair.second
|
private fun loadChapterObservable(chapter: ReaderChapter): Observable<ReaderChapter> {
|
||||||
}
|
loader.restart()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
return loader.loadChapter(chapter)
|
||||||
}
|
|
||||||
|
|
||||||
private fun getAdjacentChaptersStrategy() = when (manga.sorting) {
|
|
||||||
Manga.SORTING_NUMBER -> Pair(
|
|
||||||
db.getPreviousChapter(chapter).asRxObservable().take(1),
|
|
||||||
db.getNextChapter(chapter).asRxObservable().take(1))
|
|
||||||
Manga.SORTING_SOURCE -> Pair(
|
|
||||||
db.getPreviousChapterBySource(chapter).asRxObservable().take(1),
|
|
||||||
db.getNextChapterBySource(chapter).asRxObservable().take(1))
|
|
||||||
else -> throw AssertionError("Unknown sorting method")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preload the first pages of the next chapter. Only for non seamless mode
|
|
||||||
private fun getPreloadNextChapterObservable(): Observable<Page> {
|
|
||||||
val nextChapter = nextChapter ?: return Observable.error(Exception("No next chapter"))
|
|
||||||
return source.fetchPageList(nextChapter)
|
|
||||||
.flatMap { pages ->
|
|
||||||
nextChapter.pages = pages
|
|
||||||
val pagesToPreload = Math.min(pages.size, 5)
|
|
||||||
Observable.from(pages).take(pagesToPreload)
|
|
||||||
}
|
|
||||||
// Preload up to 5 images
|
|
||||||
.concatMap { source.fetchImage(it) }
|
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnCompleted { stopPreloadingNextChapter() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMangaSyncObservable(): Observable<List<MangaSync>> {
|
/**
|
||||||
return db.getMangasSync(manga).asRxObservable()
|
* Obtains the adjacent chapters of the given one in a background thread, and notifies the view
|
||||||
.take(1)
|
* when they are known.
|
||||||
.doOnNext { mangaSyncList = it }
|
*
|
||||||
|
* @param chapter the current active chapter.
|
||||||
|
*/
|
||||||
|
private fun getAdjacentChapters(chapter: ReaderChapter) {
|
||||||
|
// Keep only one subscription
|
||||||
|
adjacentChaptersSubscription?.let { remove(it) }
|
||||||
|
|
||||||
|
adjacentChaptersSubscription = Observable
|
||||||
|
.fromCallable { getAdjacentChaptersStrategy(chapter) }
|
||||||
|
.doOnNext { pair ->
|
||||||
|
prevChapter = pair.first
|
||||||
|
nextChapter = pair.second
|
||||||
|
}
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribeLatestCache({ view, pair ->
|
||||||
|
view.onAdjacentChapters(pair.first, pair.second)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loads the given chapter
|
/**
|
||||||
private fun loadChapter(chapter: Chapter, requestedPage: Int = 0) {
|
* Returns the previous and next chapters of the given one in a [Pair] according to the sorting
|
||||||
if (isSeamlessMode) {
|
* strategy set for the manga.
|
||||||
if (appenderSubscription != null)
|
*
|
||||||
remove(appenderSubscription)
|
* @param chapter the current active chapter.
|
||||||
} else {
|
*/
|
||||||
stopPreloadingNextChapter()
|
private fun getAdjacentChaptersStrategy(chapter: ReaderChapter) = when (manga.sorting) {
|
||||||
|
Manga.SORTING_SOURCE -> {
|
||||||
|
val currChapterIndex = chapterList.indexOfFirst { chapter.id == it.id }
|
||||||
|
val nextChapter = chapterList.getOrNull(currChapterIndex + 1)
|
||||||
|
val prevChapter = chapterList.getOrNull(currChapterIndex - 1)
|
||||||
|
Pair(prevChapter, nextChapter)
|
||||||
}
|
}
|
||||||
|
Manga.SORTING_NUMBER -> {
|
||||||
|
val currChapterIndex = chapterList.indexOfFirst { chapter.id == it.id }
|
||||||
|
val chapterNumber = chapter.chapter_number
|
||||||
|
|
||||||
|
var prevChapter: ReaderChapter? = null
|
||||||
|
for (i in (currChapterIndex - 1) downTo 0) {
|
||||||
|
val c = chapterList[i]
|
||||||
|
if (c.chapter_number < chapterNumber && c.chapter_number >= chapterNumber - 1) {
|
||||||
|
prevChapter = c
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextChapter: ReaderChapter? = null
|
||||||
|
for (i in (currChapterIndex + 1) until chapterList.size) {
|
||||||
|
val c = chapterList[i]
|
||||||
|
if (c.chapter_number > chapterNumber && c.chapter_number <= chapterNumber + 1) {
|
||||||
|
nextChapter = c
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Pair(prevChapter, nextChapter)
|
||||||
|
}
|
||||||
|
else -> throw NotImplementedError("Unknown sorting method")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the given chapter and sets it as the active one. This method also accepts a requested
|
||||||
|
* page, which will be set as active when it's displayed in the view.
|
||||||
|
*
|
||||||
|
* @param chapter the chapter to load.
|
||||||
|
* @param requestedPage the requested page from the view.
|
||||||
|
*/
|
||||||
|
private fun loadChapter(chapter: ReaderChapter, requestedPage: Int = 0) {
|
||||||
|
// Cleanup any append.
|
||||||
|
appenderSubscription?.let { remove(it) }
|
||||||
|
|
||||||
this.chapter = chapter
|
this.chapter = chapter
|
||||||
chapter.status = if (isChapterDownloaded(chapter)) Download.DOWNLOADED else Download.NOT_DOWNLOADED
|
|
||||||
|
|
||||||
// If the chapter is partially read, set the starting page to the last the user read
|
// If the chapter is partially read, set the starting page to the last the user read
|
||||||
if (!chapter.read && chapter.last_page_read != 0)
|
// otherwise use the requested page.
|
||||||
this.requestedPage = chapter.last_page_read
|
chapter.requestedPage = if (!chapter.read) chapter.last_page_read else requestedPage
|
||||||
else
|
|
||||||
this.requestedPage = requestedPage
|
|
||||||
|
|
||||||
// Reset next and previous chapter. They have to be fetched again
|
// Reset next and previous chapter. They have to be fetched again
|
||||||
nextChapter = null
|
nextChapter = null
|
||||||
previousChapter = null
|
prevChapter = null
|
||||||
|
|
||||||
start(GET_PAGE_LIST)
|
start(LOAD_ACTIVE_CHAPTER)
|
||||||
start(GET_ADJACENT_CHAPTERS)
|
getAdjacentChapters(chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setActiveChapter(chapter: Chapter) {
|
/**
|
||||||
|
* Changes the active chapter, but doesn't load anything. Called when changing chapters from
|
||||||
|
* the reader with the seamless mode.
|
||||||
|
*
|
||||||
|
* @param chapter the chapter to set as active.
|
||||||
|
*/
|
||||||
|
fun setActiveChapter(chapter: ReaderChapter) {
|
||||||
onChapterLeft()
|
onChapterLeft()
|
||||||
this.chapter = chapter
|
this.chapter = chapter
|
||||||
nextChapter = null
|
nextChapter = null
|
||||||
previousChapter = null
|
prevChapter = null
|
||||||
start(GET_ADJACENT_CHAPTERS)
|
getAdjacentChapters(chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the next chapter to the reader, if possible.
|
||||||
|
*/
|
||||||
fun appendNextChapter() {
|
fun appendNextChapter() {
|
||||||
if (nextChapter == null)
|
appenderSubscription?.let { remove(it) }
|
||||||
return
|
|
||||||
|
|
||||||
if (appenderSubscription != null)
|
val nextChapter = nextChapter ?: return
|
||||||
remove(appenderSubscription)
|
|
||||||
|
|
||||||
nextChapter?.let {
|
appenderSubscription = loader.loadChapter(nextChapter)
|
||||||
if (appenderSubscription != null)
|
.subscribeOn(Schedulers.io())
|
||||||
remove(appenderSubscription)
|
.retryWhen(RetryWithDelay(1, { 3000 }))
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
it.status = if (isChapterDownloaded(it)) Download.DOWNLOADED else Download.NOT_DOWNLOADED
|
.subscribeLatestCache({ view, chapter ->
|
||||||
|
view.onAppendChapter(chapter)
|
||||||
appenderSubscription = getPageListObservable(it).subscribeOn(Schedulers.io())
|
}, { view, error ->
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
view.onChapterAppendError()
|
||||||
.compose(deliverLatestCache<Chapter>())
|
})
|
||||||
.subscribe(split({ view, chapter ->
|
|
||||||
view.onAppendChapter(chapter)
|
|
||||||
}, { view, error ->
|
|
||||||
view.onChapterAppendError()
|
|
||||||
}))
|
|
||||||
|
|
||||||
add(appenderSubscription)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Check whether the given chapter is downloaded
|
|
||||||
fun isChapterDownloaded(chapter: Chapter): Boolean {
|
|
||||||
return downloadManager.isChapterDownloaded(source, manga, chapter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retries a page that failed to load due to network error or corruption.
|
||||||
|
*
|
||||||
|
* @param page the page that failed.
|
||||||
|
*/
|
||||||
fun retryPage(page: Page?) {
|
fun retryPage(page: Page?) {
|
||||||
if (page != null) {
|
if (page != null && source is OnlineSource) {
|
||||||
page.status = Page.QUEUE
|
page.status = Page.QUEUE
|
||||||
if (page.imagePath != null) {
|
if (page.imagePath != null) {
|
||||||
val file = File(page.imagePath)
|
val file = File(page.imagePath)
|
||||||
chapterCache.removeFileFromCache(file.name)
|
chapterCache.removeFileFromCache(file.name)
|
||||||
}
|
}
|
||||||
retryPageSubject.onNext(page)
|
loader.retryPage(page)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called before loading another chapter or leaving the reader. It allows to do operations
|
/**
|
||||||
// over the chapter read like saving progress
|
* Called before loading another chapter or leaving the reader. It allows to do operations
|
||||||
|
* over the chapter read like saving progress
|
||||||
|
*/
|
||||||
fun onChapterLeft() {
|
fun onChapterLeft() {
|
||||||
val pages = chapter.pages ?: return
|
val pages = chapter.pages ?: return
|
||||||
|
|
||||||
// Get the last page read
|
// Reference these locally because they are needed later from another thread.
|
||||||
var activePageNumber = chapter.last_page_read
|
val chapter = chapter
|
||||||
|
val prevChapter = prevChapter
|
||||||
|
|
||||||
// Just in case, avoid out of index exceptions
|
Observable
|
||||||
if (activePageNumber >= pages.size) {
|
.fromCallable {
|
||||||
activePageNumber = pages.size - 1
|
if (!chapter.isDownloaded) {
|
||||||
}
|
source.let { if (it is OnlineSource) it.savePageList(chapter, pages) }
|
||||||
val activePage = pages[activePageNumber]
|
|
||||||
|
|
||||||
// Cache current page list progress for online chapters to allow a faster reopen
|
|
||||||
if (!chapter.isDownloaded) {
|
|
||||||
source.let { if (it is OnlineSource) it.savePageList(chapter, pages) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save current progress of the chapter. Mark as read if the chapter is finished
|
|
||||||
if (activePage.isLastPage) {
|
|
||||||
chapter.read = true
|
|
||||||
|
|
||||||
// Check if remove after read is selected by user
|
|
||||||
if (prefs.removeAfterRead()) {
|
|
||||||
if (prefs.removeAfterReadPrevious() ) {
|
|
||||||
if (previousChapter != null) {
|
|
||||||
deleteChapter(previousChapter!!, manga)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
deleteChapter(chapter, manga)
|
// Cache current page list progress for online chapters to allow a faster reopen
|
||||||
|
if (chapter.read) {
|
||||||
|
// Check if remove after read is selected by user
|
||||||
|
if (prefs.removeAfterRead()) {
|
||||||
|
if (prefs.removeAfterReadPrevious() ) {
|
||||||
|
if (prevChapter != null) {
|
||||||
|
deleteChapter(prevChapter, manga)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deleteChapter(chapter, manga)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db.updateChapterProgress(chapter).executeAsBlocking()
|
||||||
|
|
||||||
|
val history = History.create(chapter).apply { last_read = Date().time }
|
||||||
|
db.updateHistoryLastRead(history).executeAsBlocking()
|
||||||
}
|
}
|
||||||
}
|
.subscribeOn(Schedulers.io())
|
||||||
}
|
|
||||||
db.updateChapterProgress(chapter).asRxObservable().subscribe()
|
|
||||||
// Update last read data
|
|
||||||
db.updateHistoryLastRead(History.create(chapter)
|
|
||||||
.apply { last_read = Date().time })
|
|
||||||
.asRxObservable()
|
|
||||||
.doOnError { Timber.e(it.message) }
|
|
||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the active page changes in the reader.
|
||||||
|
*
|
||||||
|
* @param page the active page
|
||||||
|
*/
|
||||||
|
fun onPageChanged(page: Page) {
|
||||||
|
val chapter = page.chapter
|
||||||
|
chapter.last_page_read = page.pageNumber
|
||||||
|
if (chapter.pages!!.last() === page) {
|
||||||
|
chapter.read = true
|
||||||
|
}
|
||||||
|
if (!chapter.isDownloaded && page.status == Page.QUEUE) {
|
||||||
|
loader.loadPriorizedPage(page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete selected chapter
|
* Delete selected chapter
|
||||||
|
*
|
||||||
* @param chapter chapter that is selected
|
* @param chapter chapter that is selected
|
||||||
* *
|
|
||||||
* @param manga manga that belongs to chapter
|
* @param manga manga that belongs to chapter
|
||||||
*/
|
*/
|
||||||
fun deleteChapter(chapter: Chapter, manga: Manga) {
|
fun deleteChapter(chapter: ReaderChapter, manga: Manga) {
|
||||||
val source = sourceManager.get(manga.source)!!
|
chapter.isDownloaded = false
|
||||||
|
chapter.pages?.forEach { it.status == Page.QUEUE }
|
||||||
downloadManager.deleteChapter(source, manga, chapter)
|
downloadManager.deleteChapter(source, manga, chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the current chapter has been read, we check with this one
|
/**
|
||||||
// If not, we check if the previous chapter has been read
|
* Returns the chapter to be marked as last read in sync services or 0 if no update required.
|
||||||
// We know the chapter we have to check, but we don't know yet if an update is required.
|
*/
|
||||||
// This boolean is used to return 0 if no update is required
|
|
||||||
fun getMangaSyncChapterToUpdate(): Int {
|
fun getMangaSyncChapterToUpdate(): Int {
|
||||||
if (chapter.pages == null || mangaSyncList == null || mangaSyncList!!.isEmpty())
|
if (chapter.pages == null || mangaSyncList == null || mangaSyncList!!.isEmpty())
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
var lastChapterReadLocal = 0
|
var lastChapterReadLocal = 0
|
||||||
|
|
||||||
|
// If the current chapter has been read, we check with this one
|
||||||
if (chapter.read)
|
if (chapter.read)
|
||||||
lastChapterReadLocal = Math.floor(chapter.chapter_number.toDouble()).toInt()
|
lastChapterReadLocal = Math.floor(chapter.chapter_number.toDouble()).toInt()
|
||||||
else if (previousChapter != null && previousChapter!!.read)
|
// If not, we check if the previous chapter has been read
|
||||||
lastChapterReadLocal = Math.floor(previousChapter!!.chapter_number.toDouble()).toInt()
|
else if (prevChapter != null && prevChapter!!.read)
|
||||||
|
lastChapterReadLocal = Math.floor(prevChapter!!.chapter_number.toDouble()).toInt()
|
||||||
|
|
||||||
|
// We know the chapter we have to check, but we don't know yet if an update is required.
|
||||||
|
// This boolean is used to return 0 if no update is required
|
||||||
var hasToUpdate = false
|
var hasToUpdate = false
|
||||||
|
|
||||||
for (mangaSync in mangaSyncList!!) {
|
for (mangaSync in mangaSyncList!!) {
|
||||||
@ -387,6 +429,9 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
|||||||
return if (hasToUpdate) lastChapterReadLocal else 0
|
return if (hasToUpdate) lastChapterReadLocal else 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the service that updates the last chapter read in sync services
|
||||||
|
*/
|
||||||
fun updateMangaSyncLastChapterRead() {
|
fun updateMangaSyncLastChapterRead() {
|
||||||
for (mangaSync in mangaSyncList ?: emptyList()) {
|
for (mangaSync in mangaSyncList ?: emptyList()) {
|
||||||
val service = syncManager.getService(mangaSync.sync_id) ?: continue
|
val service = syncManager.getService(mangaSync.sync_id) ?: continue
|
||||||
@ -396,6 +441,11 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the next chapter.
|
||||||
|
*
|
||||||
|
* @return true if the next chapter is being loaded, false if there is no next chapter.
|
||||||
|
*/
|
||||||
fun loadNextChapter(): Boolean {
|
fun loadNextChapter(): Boolean {
|
||||||
nextChapter?.let {
|
nextChapter?.let {
|
||||||
onChapterLeft()
|
onChapterLeft()
|
||||||
@ -405,44 +455,42 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the next chapter.
|
||||||
|
*
|
||||||
|
* @return true if the previous chapter is being loaded, false if there is no previous chapter.
|
||||||
|
*/
|
||||||
fun loadPreviousChapter(): Boolean {
|
fun loadPreviousChapter(): Boolean {
|
||||||
previousChapter?.let {
|
prevChapter?.let {
|
||||||
onChapterLeft()
|
onChapterLeft()
|
||||||
loadChapter(it, 0)
|
loadChapter(it, if (it.read) -1 else 0)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there's a next chapter.
|
||||||
|
*/
|
||||||
fun hasNextChapter(): Boolean {
|
fun hasNextChapter(): Boolean {
|
||||||
return nextChapter != null
|
return nextChapter != null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there's a previous chapter.
|
||||||
|
*/
|
||||||
fun hasPreviousChapter(): Boolean {
|
fun hasPreviousChapter(): Boolean {
|
||||||
return previousChapter != null
|
return prevChapter != null
|
||||||
}
|
|
||||||
|
|
||||||
private fun preloadNextChapter() {
|
|
||||||
nextChapter?.let {
|
|
||||||
if (!isChapterDownloaded(it)) {
|
|
||||||
start(PRELOAD_NEXT_CHAPTER)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun stopPreloadingNextChapter() {
|
|
||||||
if (!isUnsubscribed(PRELOAD_NEXT_CHAPTER)) {
|
|
||||||
stop(PRELOAD_NEXT_CHAPTER)
|
|
||||||
nextChapter?.let { chapter ->
|
|
||||||
if (chapter.pages != null) {
|
|
||||||
source.let { if (it is OnlineSource) it.savePageList(chapter, chapter.pages) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the viewer for this manga.
|
||||||
|
*
|
||||||
|
* @param viewer the id of the viewer to set.
|
||||||
|
*/
|
||||||
fun updateMangaViewer(viewer: Int) {
|
fun updateMangaViewer(viewer: Int) {
|
||||||
manga.viewer = viewer
|
manga.viewer = viewer
|
||||||
db.insertManga(manga).executeAsBlocking()
|
db.insertManga(manga).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.base
|
package eu.kanade.tachiyomi.ui.reader.viewer.base
|
||||||
|
|
||||||
import com.davemorrissey.labs.subscaleview.decoder.*
|
import com.davemorrissey.labs.subscaleview.decoder.*
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.ReaderChapter
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,7 +29,7 @@ abstract class BaseReader : BaseFragment() {
|
|||||||
/**
|
/**
|
||||||
* List of chapters added in the reader.
|
* List of chapters added in the reader.
|
||||||
*/
|
*/
|
||||||
private var chapters = ArrayList<Chapter>()
|
private val chapters = ArrayList<ReaderChapter>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of pages added in the reader. It can contain pages from more than one chapter.
|
* List of pages added in the reader. It can contain pages from more than one chapter.
|
||||||
@ -72,7 +72,7 @@ abstract class BaseReader : BaseFragment() {
|
|||||||
fun updatePageNumber() {
|
fun updatePageNumber() {
|
||||||
val activePage = getActivePage()
|
val activePage = getActivePage()
|
||||||
if (activePage != null) {
|
if (activePage != null) {
|
||||||
readerActivity.onPageChanged(activePage.pageNumber, activePage.chapter.pages.size)
|
readerActivity.onPageChanged(activePage.pageNumber, activePage.chapter.pages!!.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,23 +91,22 @@ abstract class BaseReader : BaseFragment() {
|
|||||||
fun onPageChanged(position: Int) {
|
fun onPageChanged(position: Int) {
|
||||||
val oldPage = pages[currentPage]
|
val oldPage = pages[currentPage]
|
||||||
val newPage = pages[position]
|
val newPage = pages[position]
|
||||||
newPage.chapter.last_page_read = newPage.pageNumber
|
readerActivity.presenter.onPageChanged(newPage)
|
||||||
|
|
||||||
if (readerActivity.presenter.isSeamlessMode) {
|
val oldChapter = oldPage.chapter
|
||||||
val oldChapter = oldPage.chapter
|
val newChapter = newPage.chapter
|
||||||
val newChapter = newPage.chapter
|
|
||||||
|
|
||||||
// Active chapter has changed.
|
// Active chapter has changed.
|
||||||
if (oldChapter.id != newChapter.id) {
|
if (oldChapter.id != newChapter.id) {
|
||||||
readerActivity.onEnterChapter(newPage.chapter, newPage.pageNumber)
|
readerActivity.onEnterChapter(newPage.chapter, newPage.pageNumber)
|
||||||
}
|
|
||||||
// Request next chapter only when the conditions are met.
|
|
||||||
if (pages.size - position < 5 && chapters.last().id == newChapter.id
|
|
||||||
&& readerActivity.presenter.hasNextChapter() && !hasRequestedNextChapter) {
|
|
||||||
hasRequestedNextChapter = true
|
|
||||||
readerActivity.presenter.appendNextChapter()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// Request next chapter only when the conditions are met.
|
||||||
|
if (pages.size - position < 5 && chapters.last().id == newChapter.id
|
||||||
|
&& readerActivity.presenter.hasNextChapter() && !hasRequestedNextChapter) {
|
||||||
|
hasRequestedNextChapter = true
|
||||||
|
readerActivity.presenter.appendNextChapter()
|
||||||
|
}
|
||||||
|
|
||||||
currentPage = position
|
currentPage = position
|
||||||
updatePageNumber()
|
updatePageNumber()
|
||||||
}
|
}
|
||||||
@ -144,10 +143,10 @@ abstract class BaseReader : BaseFragment() {
|
|||||||
* @param chapter the chapter to set.
|
* @param chapter the chapter to set.
|
||||||
* @param currentPage the initial page to display.
|
* @param currentPage the initial page to display.
|
||||||
*/
|
*/
|
||||||
fun onPageListReady(chapter: Chapter, currentPage: Page) {
|
fun onPageListReady(chapter: ReaderChapter, currentPage: Page) {
|
||||||
if (!chapters.contains(chapter)) {
|
if (!chapters.contains(chapter)) {
|
||||||
// if we reset the loaded page we also need to reset the loaded chapters
|
// if we reset the loaded page we also need to reset the loaded chapters
|
||||||
chapters = ArrayList<Chapter>()
|
chapters.clear()
|
||||||
chapters.add(chapter)
|
chapters.add(chapter)
|
||||||
pages = ArrayList(chapter.pages)
|
pages = ArrayList(chapter.pages)
|
||||||
onChapterSet(chapter, currentPage)
|
onChapterSet(chapter, currentPage)
|
||||||
@ -162,11 +161,11 @@ abstract class BaseReader : BaseFragment() {
|
|||||||
*
|
*
|
||||||
* @param chapter the chapter to append.
|
* @param chapter the chapter to append.
|
||||||
*/
|
*/
|
||||||
fun onPageListAppendReady(chapter: Chapter) {
|
fun onPageListAppendReady(chapter: ReaderChapter) {
|
||||||
if (!chapters.contains(chapter)) {
|
if (!chapters.contains(chapter)) {
|
||||||
hasRequestedNextChapter = false
|
hasRequestedNextChapter = false
|
||||||
chapters.add(chapter)
|
chapters.add(chapter)
|
||||||
pages.addAll(chapter.pages)
|
pages.addAll(chapter.pages!!)
|
||||||
onChapterAppended(chapter)
|
onChapterAppended(chapter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,14 +183,14 @@ abstract class BaseReader : BaseFragment() {
|
|||||||
* @param chapter the chapter set.
|
* @param chapter the chapter set.
|
||||||
* @param currentPage the initial page to display.
|
* @param currentPage the initial page to display.
|
||||||
*/
|
*/
|
||||||
abstract fun onChapterSet(chapter: Chapter, currentPage: Page)
|
abstract fun onChapterSet(chapter: ReaderChapter, currentPage: Page)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a chapter is appended in [BaseReader].
|
* Called when a chapter is appended in [BaseReader].
|
||||||
*
|
*
|
||||||
* @param chapter the chapter appended.
|
* @param chapter the chapter appended.
|
||||||
*/
|
*/
|
||||||
abstract fun onChapterAppended(chapter: Chapter)
|
abstract fun onChapterAppended(chapter: ReaderChapter)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves pages forward. Implementations decide how to move (by a page, by some distance...).
|
* Moves pages forward. Implementations decide how to move (by a page, by some distance...).
|
||||||
|
@ -5,8 +5,8 @@ import android.view.MotionEvent
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.ReaderChapter
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader
|
||||||
@ -181,7 +181,7 @@ abstract class PagerReader : BaseReader() {
|
|||||||
* @param chapter the chapter set.
|
* @param chapter the chapter set.
|
||||||
* @param currentPage the initial page to display.
|
* @param currentPage the initial page to display.
|
||||||
*/
|
*/
|
||||||
override fun onChapterSet(chapter: Chapter, currentPage: Page) {
|
override fun onChapterSet(chapter: ReaderChapter, currentPage: Page) {
|
||||||
this.currentPage = getPageIndex(currentPage) // we might have a new page object
|
this.currentPage = getPageIndex(currentPage) // we might have a new page object
|
||||||
|
|
||||||
// Make sure the view is already initialized.
|
// Make sure the view is already initialized.
|
||||||
@ -195,7 +195,7 @@ abstract class PagerReader : BaseReader() {
|
|||||||
*
|
*
|
||||||
* @param chapter the chapter appended.
|
* @param chapter the chapter appended.
|
||||||
*/
|
*/
|
||||||
override fun onChapterAppended(chapter: Chapter) {
|
override fun onChapterAppended(chapter: ReaderChapter) {
|
||||||
// Make sure the view is already initialized.
|
// Make sure the view is already initialized.
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
adapter.pages = pages
|
adapter.pages = pages
|
||||||
|
@ -6,8 +6,8 @@ import android.view.*
|
|||||||
import android.view.GestureDetector.SimpleOnGestureListener
|
import android.view.GestureDetector.SimpleOnGestureListener
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.ReaderChapter
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
||||||
import eu.kanade.tachiyomi.widget.PreCachingLayoutManager
|
import eu.kanade.tachiyomi.widget.PreCachingLayoutManager
|
||||||
import rx.subscriptions.CompositeSubscription
|
import rx.subscriptions.CompositeSubscription
|
||||||
@ -147,7 +147,7 @@ class WebtoonReader : BaseReader() {
|
|||||||
* @param chapter the chapter set.
|
* @param chapter the chapter set.
|
||||||
* @param currentPage the initial page to display.
|
* @param currentPage the initial page to display.
|
||||||
*/
|
*/
|
||||||
override fun onChapterSet(chapter: Chapter, currentPage: Page) {
|
override fun onChapterSet(chapter: ReaderChapter, currentPage: Page) {
|
||||||
// Restoring current page is not supported. It's getting weird scrolling jumps
|
// Restoring current page is not supported. It's getting weird scrolling jumps
|
||||||
// this.currentPage = currentPage;
|
// this.currentPage = currentPage;
|
||||||
|
|
||||||
@ -162,11 +162,11 @@ class WebtoonReader : BaseReader() {
|
|||||||
*
|
*
|
||||||
* @param chapter the chapter appended.
|
* @param chapter the chapter appended.
|
||||||
*/
|
*/
|
||||||
override fun onChapterAppended(chapter: Chapter) {
|
override fun onChapterAppended(chapter: ReaderChapter) {
|
||||||
// Make sure the view is already initialized.
|
// Make sure the view is already initialized.
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
val insertStart = pages.size - chapter.pages.size
|
val insertStart = pages.size - chapter.pages!!.size
|
||||||
adapter.notifyItemRangeInserted(insertStart, chapter.pages.size)
|
adapter.notifyItemRangeInserted(insertStart, chapter.pages!!.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
22
app/src/main/java/eu/kanade/tachiyomi/util/RetryWithDelay.kt
Normal file
22
app/src/main/java/eu/kanade/tachiyomi/util/RetryWithDelay.kt
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package eu.kanade.tachiyomi.util
|
||||||
|
|
||||||
|
import rx.Observable
|
||||||
|
import rx.functions.Func1
|
||||||
|
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||||
|
|
||||||
|
class RetryWithDelay(
|
||||||
|
private val maxRetries: Int = 1,
|
||||||
|
private val retryStrategy: (Int) -> Int = { 1000 }
|
||||||
|
) : Func1<Observable<out Throwable>, Observable<*>> {
|
||||||
|
|
||||||
|
private var retryCount = 0
|
||||||
|
|
||||||
|
override fun call(attempts: Observable<out Throwable>) = attempts.flatMap { error ->
|
||||||
|
val count = ++retryCount
|
||||||
|
if (count <= maxRetries) {
|
||||||
|
Observable.timer(retryStrategy(count).toLong(), MILLISECONDS)
|
||||||
|
} else {
|
||||||
|
Observable.error(error as Throwable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,6 @@
|
|||||||
<string name="pref_custom_brightness_value_key">pref_custom_brightness_value_key</string>
|
<string name="pref_custom_brightness_value_key">pref_custom_brightness_value_key</string>
|
||||||
<string name="pref_reader_theme_key">pref_reader_theme_key</string>
|
<string name="pref_reader_theme_key">pref_reader_theme_key</string>
|
||||||
<string name="pref_image_decoder_key">pref_image_decoder_key</string>
|
<string name="pref_image_decoder_key">pref_image_decoder_key</string>
|
||||||
<string name="pref_seamless_mode_key">pref_seamless_mode_key</string>
|
|
||||||
<string name="pref_read_with_volume_keys_key">reader_volume_keys</string>
|
<string name="pref_read_with_volume_keys_key">reader_volume_keys</string>
|
||||||
<string name="pref_read_with_tapping_key">reader_tap</string>
|
<string name="pref_read_with_tapping_key">reader_tap</string>
|
||||||
<string name="pref_reencode_key">reencode_image</string>
|
<string name="pref_reencode_key">reencode_image</string>
|
||||||
|
@ -101,7 +101,6 @@
|
|||||||
<string name="pref_enable_transitions">Enable transitions</string>
|
<string name="pref_enable_transitions">Enable transitions</string>
|
||||||
<string name="pref_show_page_number">Show page number</string>
|
<string name="pref_show_page_number">Show page number</string>
|
||||||
<string name="pref_custom_brightness">Use custom brightness</string>
|
<string name="pref_custom_brightness">Use custom brightness</string>
|
||||||
<string name="pref_seamless_mode">Seamless chapter transitions</string>
|
|
||||||
<string name="pref_keep_screen_on">Keep screen on</string>
|
<string name="pref_keep_screen_on">Keep screen on</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>
|
||||||
|
@ -75,11 +75,6 @@
|
|||||||
android:key="@string/pref_keep_screen_on_key"
|
android:key="@string/pref_keep_screen_on_key"
|
||||||
android:defaultValue="true" />
|
android:defaultValue="true" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:title="@string/pref_seamless_mode"
|
|
||||||
android:key="@string/pref_seamless_mode_key"
|
|
||||||
android:defaultValue="true" />
|
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:title="@string/pref_reader_navigation">
|
android:title="@string/pref_reader_navigation">
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user