|  | @@ -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.MangaSync
 | 
	
		
			
				|  |  |  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.UpdateMangaSyncService
 | 
	
		
			
				|  |  |  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.model.Page
 | 
	
		
			
				|  |  |  import eu.kanade.tachiyomi.data.source.online.OnlineSource
 | 
	
		
			
				|  |  |  import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.util.RetryWithDelay
 | 
	
		
			
				|  |  |  import eu.kanade.tachiyomi.util.SharedData
 | 
	
		
			
				|  |  |  import rx.Observable
 | 
	
		
			
				|  |  |  import rx.Subscription
 | 
	
		
			
				|  |  |  import rx.android.schedulers.AndroidSchedulers
 | 
	
		
			
				|  |  |  import rx.schedulers.Schedulers
 | 
	
		
			
				|  |  | -import rx.subjects.PublishSubject
 | 
	
		
			
				|  |  | -import timber.log.Timber
 | 
	
		
			
				|  |  | +import uy.kohesive.injekt.injectLazy
 | 
	
		
			
				|  |  |  import java.io.File
 | 
	
		
			
				|  |  |  import java.util.*
 | 
	
		
			
				|  |  | -import javax.inject.Inject
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +/**
 | 
	
		
			
				|  |  | + * Presenter of [ReaderActivity].
 | 
	
		
			
				|  |  | + */
 | 
	
		
			
				|  |  |  class ReaderPresenter : BasePresenter<ReaderActivity>() {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    @Inject lateinit var prefs: PreferencesHelper
 | 
	
		
			
				|  |  | -    @Inject lateinit var db: DatabaseHelper
 | 
	
		
			
				|  |  | -    @Inject lateinit var downloadManager: DownloadManager
 | 
	
		
			
				|  |  | -    @Inject lateinit var syncManager: MangaSyncManager
 | 
	
		
			
				|  |  | -    @Inject lateinit var sourceManager: SourceManager
 | 
	
		
			
				|  |  | -    @Inject lateinit var chapterCache: ChapterCache
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Preferences.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    val prefs: PreferencesHelper by injectLazy()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 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
 | 
	
		
			
				|  |  |          private set
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    lateinit var chapter: Chapter
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Active chapter.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    lateinit var chapter: ReaderChapter
 | 
	
		
			
				|  |  |          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
 | 
	
		
			
				|  |  | -    private var nextChapter: Chapter? = null
 | 
	
		
			
				|  |  | -    private var previousChapter: Chapter? = null
 | 
	
		
			
				|  |  | -    private var mangaSyncList: List<MangaSync>? = null
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Next chapter of the active.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private var nextChapter: ReaderChapter? = null
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Source of the manga.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private val source by lazy { sourceManager.get(manga.source)!! }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    private val retryPageSubject by lazy { PublishSubject.create<Page>() }
 | 
	
		
			
				|  |  | -    private val pageInitializerSubject by lazy { PublishSubject.create<Chapter>() }
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 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) })
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    val isSeamlessMode by lazy { prefs.seamlessMode() }
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * List of manga services linked to the active manga, or null if auto syncing is not enabled.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private var mangaSyncList: List<MangaSync>? = null
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Chapter loader whose job is to obtain the chapter list and initialize every page.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    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 val PREPARE_READER = 1
 | 
	
		
			
				|  |  | -    private val GET_PAGE_LIST = 2
 | 
	
		
			
				|  |  | -    private val GET_ADJACENT_CHAPTERS = 3
 | 
	
		
			
				|  |  | -    private val GET_MANGA_SYNC = 4
 | 
	
		
			
				|  |  | -    private val PRELOAD_NEXT_CHAPTER = 5
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Subscription for retrieving the adjacent chapters to the current one.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private var adjacentChaptersSubscription: Subscription? = null
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    private val MANGA_KEY = "manga_key"
 | 
	
		
			
				|  |  | -    private val CHAPTER_KEY = "chapter_key"
 | 
	
		
			
				|  |  | -    private val PAGE_KEY = "page_key"
 | 
	
		
			
				|  |  | +    companion object {
 | 
	
		
			
				|  |  | +        /**
 | 
	
		
			
				|  |  | +         * Id of the restartable that loads the active chapter.
 | 
	
		
			
				|  |  | +         */
 | 
	
		
			
				|  |  | +        private const val LOAD_ACTIVE_CHAPTER = 1
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      override fun onCreate(savedState: Bundle?) {
 | 
	
		
			
				|  |  |          super.onCreate(savedState)
 | 
	
	
		
			
				|  | @@ -75,306 +136,287 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
 | 
	
		
			
				|  |  |          if (savedState == null) {
 | 
	
		
			
				|  |  |              val event = SharedData.get(ReaderEvent::class.java) ?: return
 | 
	
		
			
				|  |  |              manga = event.manga
 | 
	
		
			
				|  |  | -            chapter = event.chapter
 | 
	
		
			
				|  |  | +            chapter = event.chapter.toModel()
 | 
	
		
			
				|  |  |          } else {
 | 
	
		
			
				|  |  | -            manga = savedState.getSerializable(MANGA_KEY) as Manga
 | 
	
		
			
				|  |  | -            chapter = savedState.getSerializable(CHAPTER_KEY) as Chapter
 | 
	
		
			
				|  |  | -            requestedPage = savedState.getInt(PAGE_KEY)
 | 
	
		
			
				|  |  | +            manga = savedState.getSerializable(ReaderPresenter::manga.name) as Manga
 | 
	
		
			
				|  |  | +            chapter = savedState.getSerializable(ReaderPresenter::chapter.name) as ReaderChapter
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        source = sourceManager.get(manga.source)!!
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        initializeSubjects()
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        restartableLatestCache(PREPARE_READER,
 | 
	
		
			
				|  |  | -                { Observable.just(manga) },
 | 
	
		
			
				|  |  | -                { view, manga -> view.onMangaOpen(manga) })
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        startableLatestCache(GET_ADJACENT_CHAPTERS,
 | 
	
		
			
				|  |  | -                { getAdjacentChaptersObservable() },
 | 
	
		
			
				|  |  | -                { view, pair -> view.onAdjacentChapters(pair.first, pair.second) })
 | 
	
		
			
				|  |  | +        // Send the active manga to the view to initialize the reader.
 | 
	
		
			
				|  |  | +        Observable.just(manga)
 | 
	
		
			
				|  |  | +                .subscribeLatestCache({ view, manga -> view.onMangaOpen(manga) })
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        startable(PRELOAD_NEXT_CHAPTER,
 | 
	
		
			
				|  |  | -                { getPreloadNextChapterObservable() },
 | 
	
		
			
				|  |  | -                { },
 | 
	
		
			
				|  |  | -                { error -> Timber.e("Error preloading chapter") })
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        restartable(GET_MANGA_SYNC,
 | 
	
		
			
				|  |  | -                { getMangaSyncObservable().subscribe() })
 | 
	
		
			
				|  |  | +        // Retrieve the sync list if auto syncing is enabled.
 | 
	
		
			
				|  |  | +        if (prefs.autoUpdateMangaSync()) {
 | 
	
		
			
				|  |  | +            add(db.getMangasSync(manga).asRxSingle()
 | 
	
		
			
				|  |  | +                    .subscribe({ mangaSyncList = it }))
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        restartableLatestCache(GET_PAGE_LIST,
 | 
	
		
			
				|  |  | -                { getPageListObservable(chapter) },
 | 
	
		
			
				|  |  | -                { view, chapter -> view.onChapterReady(manga, this.chapter, currentPage) },
 | 
	
		
			
				|  |  | +        restartableLatestCache(LOAD_ACTIVE_CHAPTER,
 | 
	
		
			
				|  |  | +                { loadChapterObservable(chapter) },
 | 
	
		
			
				|  |  | +                { view, chapter -> view.onChapterReady(this.chapter) },
 | 
	
		
			
				|  |  |                  { view, error -> view.onChapterError(error) })
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          if (savedState == null) {
 | 
	
		
			
				|  |  | -            start(PREPARE_READER)
 | 
	
		
			
				|  |  |              loadChapter(chapter)
 | 
	
		
			
				|  |  | -            if (prefs.autoUpdateMangaSync()) {
 | 
	
		
			
				|  |  | -                start(GET_MANGA_SYNC)
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      override fun onSave(state: Bundle) {
 | 
	
		
			
				|  |  | +        chapter.requestedPage = chapter.last_page_read
 | 
	
		
			
				|  |  |          onChapterLeft()
 | 
	
		
			
				|  |  | -        state.putSerializable(MANGA_KEY, manga)
 | 
	
		
			
				|  |  | -        state.putSerializable(CHAPTER_KEY, chapter)
 | 
	
		
			
				|  |  | -        state.putSerializable(PAGE_KEY, currentPage?.pageNumber ?: 0)
 | 
	
		
			
				|  |  | +        state.putSerializable(ReaderPresenter::manga.name, manga)
 | 
	
		
			
				|  |  | +        state.putSerializable(ReaderPresenter::chapter.name, chapter)
 | 
	
		
			
				|  |  |          super.onSave(state)
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    private fun initializeSubjects() {
 | 
	
		
			
				|  |  | -        // Listen for pages initialization events
 | 
	
		
			
				|  |  | -        add(pageInitializerSubject.observeOn(Schedulers.io())
 | 
	
		
			
				|  |  | -                .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())
 | 
	
		
			
				|  |  | +    override fun onDestroy() {
 | 
	
		
			
				|  |  | +        loader.cleanup()
 | 
	
		
			
				|  |  | +        super.onDestroy()
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    // Returns the page list of a chapter
 | 
	
		
			
				|  |  | -    private fun getPageListObservable(chapter: Chapter): Observable<Chapter> {
 | 
	
		
			
				|  |  | -        val observable: Observable<List<Page>> = if (chapter.isDownloaded)
 | 
	
		
			
				|  |  | -        // Fetch the page list from disk
 | 
	
		
			
				|  |  | -            Observable.just(downloadManager.getSavedPageList(source, manga, chapter)!!)
 | 
	
		
			
				|  |  | -        else
 | 
	
		
			
				|  |  | -        // 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
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Converts a chapter to a [ReaderChapter] if needed.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun Chapter.toModel(): ReaderChapter {
 | 
	
		
			
				|  |  | +        if (this is ReaderChapter) return this
 | 
	
		
			
				|  |  | +        return ReaderChapter(this)
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    private fun getAdjacentChaptersObservable(): Observable<Pair<Chapter, Chapter>> {
 | 
	
		
			
				|  |  | -        val strategy = getAdjacentChaptersStrategy()
 | 
	
		
			
				|  |  | -        return Observable.zip(strategy.first, strategy.second) { prev, next -> Pair(prev, next) }
 | 
	
		
			
				|  |  | -                .doOnNext { pair ->
 | 
	
		
			
				|  |  | -                    previousChapter = pair.first
 | 
	
		
			
				|  |  | -                    nextChapter = pair.second
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Returns an observable that loads the given chapter, discarding any previous work.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param chapter the now active chapter.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun loadChapterObservable(chapter: ReaderChapter): Observable<ReaderChapter> {
 | 
	
		
			
				|  |  | +        loader.restart()
 | 
	
		
			
				|  |  | +        return loader.loadChapter(chapter)
 | 
	
		
			
				|  |  | +                .subscribeOn(Schedulers.io())
 | 
	
		
			
				|  |  |                  .observeOn(AndroidSchedulers.mainThread())
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    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")
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Obtains the adjacent chapters of the given one in a background thread, and notifies the view
 | 
	
		
			
				|  |  | +     * when they are known.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param chapter the current active chapter.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun getAdjacentChapters(chapter: ReaderChapter) {
 | 
	
		
			
				|  |  | +        // Keep only one subscription
 | 
	
		
			
				|  |  | +        adjacentChaptersSubscription?.let { remove(it) }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    // 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)
 | 
	
		
			
				|  |  | +        adjacentChaptersSubscription = Observable
 | 
	
		
			
				|  |  | +                .fromCallable { getAdjacentChaptersStrategy(chapter) }
 | 
	
		
			
				|  |  | +                .doOnNext { pair ->
 | 
	
		
			
				|  |  | +                    prevChapter = pair.first
 | 
	
		
			
				|  |  | +                    nextChapter = pair.second
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  | -                // Preload up to 5 images
 | 
	
		
			
				|  |  | -                .concatMap { source.fetchImage(it) }
 | 
	
		
			
				|  |  |                  .subscribeOn(Schedulers.io())
 | 
	
		
			
				|  |  |                  .observeOn(AndroidSchedulers.mainThread())
 | 
	
		
			
				|  |  | -                .doOnCompleted { stopPreloadingNextChapter() }
 | 
	
		
			
				|  |  | +                .subscribeLatestCache({ view, pair ->
 | 
	
		
			
				|  |  | +                    view.onAdjacentChapters(pair.first, pair.second)
 | 
	
		
			
				|  |  | +                })
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    private fun getMangaSyncObservable(): Observable<List<MangaSync>> {
 | 
	
		
			
				|  |  | -        return db.getMangasSync(manga).asRxObservable()
 | 
	
		
			
				|  |  | -                .take(1)
 | 
	
		
			
				|  |  | -                .doOnNext { mangaSyncList = it }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Returns the previous and next chapters of the given one in a [Pair] according to the sorting
 | 
	
		
			
				|  |  | +     * strategy set for the manga.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param chapter the current active chapter.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    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
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    // Loads the given chapter
 | 
	
		
			
				|  |  | -    private fun loadChapter(chapter: Chapter, requestedPage: Int = 0) {
 | 
	
		
			
				|  |  | -        if (isSeamlessMode) {
 | 
	
		
			
				|  |  | -            if (appenderSubscription != null)
 | 
	
		
			
				|  |  | -                remove(appenderSubscription)
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -            stopPreloadingNextChapter()
 | 
	
		
			
				|  |  | +            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
 | 
	
		
			
				|  |  | -        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 (!chapter.read && chapter.last_page_read != 0)
 | 
	
		
			
				|  |  | -            this.requestedPage = chapter.last_page_read
 | 
	
		
			
				|  |  | -        else
 | 
	
		
			
				|  |  | -            this.requestedPage = requestedPage
 | 
	
		
			
				|  |  | +        // otherwise use the requested page.
 | 
	
		
			
				|  |  | +        chapter.requestedPage = if (!chapter.read) chapter.last_page_read else requestedPage
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          // Reset next and previous chapter. They have to be fetched again
 | 
	
		
			
				|  |  |          nextChapter = null
 | 
	
		
			
				|  |  | -        previousChapter = null
 | 
	
		
			
				|  |  | +        prevChapter = null
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        start(GET_PAGE_LIST)
 | 
	
		
			
				|  |  | -        start(GET_ADJACENT_CHAPTERS)
 | 
	
		
			
				|  |  | +        start(LOAD_ACTIVE_CHAPTER)
 | 
	
		
			
				|  |  | +        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()
 | 
	
		
			
				|  |  |          this.chapter = chapter
 | 
	
		
			
				|  |  |          nextChapter = null
 | 
	
		
			
				|  |  | -        previousChapter = null
 | 
	
		
			
				|  |  | -        start(GET_ADJACENT_CHAPTERS)
 | 
	
		
			
				|  |  | +        prevChapter = null
 | 
	
		
			
				|  |  | +        getAdjacentChapters(chapter)
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Appends the next chapter to the reader, if possible.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  |      fun appendNextChapter() {
 | 
	
		
			
				|  |  | -        if (nextChapter == null)
 | 
	
		
			
				|  |  | -            return
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if (appenderSubscription != null)
 | 
	
		
			
				|  |  | -            remove(appenderSubscription)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        nextChapter?.let {
 | 
	
		
			
				|  |  | -            if (appenderSubscription != null)
 | 
	
		
			
				|  |  | -                remove(appenderSubscription)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            it.status = if (isChapterDownloaded(it)) Download.DOWNLOADED else Download.NOT_DOWNLOADED
 | 
	
		
			
				|  |  | +        appenderSubscription?.let { remove(it) }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            appenderSubscription = getPageListObservable(it).subscribeOn(Schedulers.io())
 | 
	
		
			
				|  |  | -                    .observeOn(AndroidSchedulers.mainThread())
 | 
	
		
			
				|  |  | -                    .compose(deliverLatestCache<Chapter>())
 | 
	
		
			
				|  |  | -                    .subscribe(split({ view, chapter ->
 | 
	
		
			
				|  |  | -                        view.onAppendChapter(chapter)
 | 
	
		
			
				|  |  | -                    }, { view, error ->
 | 
	
		
			
				|  |  | -                        view.onChapterAppendError()
 | 
	
		
			
				|  |  | -                    }))
 | 
	
		
			
				|  |  | +        val nextChapter = nextChapter ?: return
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            add(appenderSubscription)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    // Check whether the given chapter is downloaded
 | 
	
		
			
				|  |  | -    fun isChapterDownloaded(chapter: Chapter): Boolean {
 | 
	
		
			
				|  |  | -        return downloadManager.isChapterDownloaded(source, manga, chapter)
 | 
	
		
			
				|  |  | +        appenderSubscription = loader.loadChapter(nextChapter)
 | 
	
		
			
				|  |  | +                .subscribeOn(Schedulers.io())
 | 
	
		
			
				|  |  | +                .retryWhen(RetryWithDelay(1, { 3000 }))
 | 
	
		
			
				|  |  | +                .observeOn(AndroidSchedulers.mainThread())
 | 
	
		
			
				|  |  | +                .subscribeLatestCache({ view, chapter ->
 | 
	
		
			
				|  |  | +                    view.onAppendChapter(chapter)
 | 
	
		
			
				|  |  | +                }, { view, error ->
 | 
	
		
			
				|  |  | +                    view.onChapterAppendError()
 | 
	
		
			
				|  |  | +                })
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Retries a page that failed to load due to network error or corruption.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param page the page that failed.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  |      fun retryPage(page: Page?) {
 | 
	
		
			
				|  |  | -        if (page != null) {
 | 
	
		
			
				|  |  | +        if (page != null && source is OnlineSource) {
 | 
	
		
			
				|  |  |              page.status = Page.QUEUE
 | 
	
		
			
				|  |  |              if (page.imagePath != null) {
 | 
	
		
			
				|  |  |                  val file = File(page.imagePath)
 | 
	
		
			
				|  |  |                  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() {
 | 
	
		
			
				|  |  |          val pages = chapter.pages ?: return
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        // Get the last page read
 | 
	
		
			
				|  |  | -        var activePageNumber = chapter.last_page_read
 | 
	
		
			
				|  |  | +        // Reference these locally because they are needed later from another thread.
 | 
	
		
			
				|  |  | +        val chapter = chapter
 | 
	
		
			
				|  |  | +        val prevChapter = prevChapter
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        // Just in case, avoid out of index exceptions
 | 
	
		
			
				|  |  | -        if (activePageNumber >= pages.size) {
 | 
	
		
			
				|  |  | -            activePageNumber = pages.size - 1
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        val activePage = pages[activePageNumber]
 | 
	
		
			
				|  |  | +        Observable
 | 
	
		
			
				|  |  | +                .fromCallable {
 | 
	
		
			
				|  |  | +                    if (!chapter.isDownloaded) {
 | 
	
		
			
				|  |  | +                        source.let { if (it is OnlineSource) it.savePageList(chapter, pages) }
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        // 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) }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +                    // 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)
 | 
	
		
			
				|  |  | +                            }
 | 
	
		
			
				|  |  | +                        }
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        // Save current progress of the chapter. Mark as read if the chapter is finished
 | 
	
		
			
				|  |  | -        if (activePage.isLastPage) {
 | 
	
		
			
				|  |  | -            chapter.read = true
 | 
	
		
			
				|  |  | +                    db.updateChapterProgress(chapter).executeAsBlocking()
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            // 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)
 | 
	
		
			
				|  |  | +                    val history = History.create(chapter).apply { last_read = Date().time }
 | 
	
		
			
				|  |  | +                    db.updateHistoryLastRead(history).executeAsBlocking()
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        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) }
 | 
	
		
			
				|  |  | +                .subscribeOn(Schedulers.io())
 | 
	
		
			
				|  |  |                  .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
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  |       * @param chapter chapter that is selected
 | 
	
		
			
				|  |  | -     * *
 | 
	
		
			
				|  |  |       * @param manga manga that belongs to chapter
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  | -    fun deleteChapter(chapter: Chapter, manga: Manga) {
 | 
	
		
			
				|  |  | -        val source = sourceManager.get(manga.source)!!
 | 
	
		
			
				|  |  | +    fun deleteChapter(chapter: ReaderChapter, manga: Manga) {
 | 
	
		
			
				|  |  | +        chapter.isDownloaded = false
 | 
	
		
			
				|  |  | +        chapter.pages?.forEach { it.status == Page.QUEUE }
 | 
	
		
			
				|  |  |          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
 | 
	
		
			
				|  |  | -    // 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
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Returns the chapter to be marked as last read in sync services or 0 if no update required.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  |      fun getMangaSyncChapterToUpdate(): Int {
 | 
	
		
			
				|  |  |          if (chapter.pages == null || mangaSyncList == null || mangaSyncList!!.isEmpty())
 | 
	
		
			
				|  |  |              return 0
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          var lastChapterReadLocal = 0
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // If the current chapter has been read, we check with this one
 | 
	
		
			
				|  |  |          if (chapter.read)
 | 
	
		
			
				|  |  |              lastChapterReadLocal = Math.floor(chapter.chapter_number.toDouble()).toInt()
 | 
	
		
			
				|  |  | -        else if (previousChapter != null && previousChapter!!.read)
 | 
	
		
			
				|  |  | -            lastChapterReadLocal = Math.floor(previousChapter!!.chapter_number.toDouble()).toInt()
 | 
	
		
			
				|  |  | +        // If not, we check if the previous chapter has been read
 | 
	
		
			
				|  |  | +        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
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          for (mangaSync in mangaSyncList!!) {
 | 
	
	
		
			
				|  | @@ -387,6 +429,9 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
 | 
	
		
			
				|  |  |          return if (hasToUpdate) lastChapterReadLocal else 0
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Starts the service that updates the last chapter read in sync services
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  |      fun updateMangaSyncLastChapterRead() {
 | 
	
		
			
				|  |  |          for (mangaSync in mangaSyncList ?: emptyList()) {
 | 
	
		
			
				|  |  |              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 {
 | 
	
		
			
				|  |  |          nextChapter?.let {
 | 
	
		
			
				|  |  |              onChapterLeft()
 | 
	
	
		
			
				|  | @@ -405,44 +455,42 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
 | 
	
		
			
				|  |  |          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 {
 | 
	
		
			
				|  |  | -        previousChapter?.let {
 | 
	
		
			
				|  |  | +        prevChapter?.let {
 | 
	
		
			
				|  |  |              onChapterLeft()
 | 
	
		
			
				|  |  | -            loadChapter(it, 0)
 | 
	
		
			
				|  |  | +            loadChapter(it, if (it.read) -1 else 0)
 | 
	
		
			
				|  |  |              return true
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          return false
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Returns true if there's a next chapter.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  |      fun hasNextChapter(): Boolean {
 | 
	
		
			
				|  |  |          return nextChapter != null
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Returns true if there's a previous chapter.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  |      fun hasPreviousChapter(): Boolean {
 | 
	
		
			
				|  |  | -        return previousChapter != 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) }
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +        return prevChapter != null
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Updates the viewer for this manga.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param viewer the id of the viewer to set.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  |      fun updateMangaViewer(viewer: Int) {
 | 
	
		
			
				|  |  |          manga.viewer = viewer
 | 
	
		
			
				|  |  |          db.insertManga(manga).executeAsBlocking()
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  }
 |