mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Add chapter loader, drop non seamless mode
This commit is contained in:
		| @@ -54,8 +54,6 @@ class PreferenceKeys(context: Context) { | ||||
|  | ||||
|     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 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 seamlessMode() = prefs.getBoolean(keys.seamlessMode, true) | ||||
|  | ||||
|     fun catalogueAsList() = rxPrefs.getBoolean(keys.catalogueAsList, false) | ||||
|  | ||||
|     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.preference.PreferencesHelper | ||||
| 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.reader.viewer.base.BaseReader | ||||
| import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader | ||||
| @@ -116,16 +115,6 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() { | ||||
|         setSystemUiVisibility() | ||||
|     } | ||||
|  | ||||
|     override fun onPause() { | ||||
|         viewer?.let { | ||||
|             val activePage = it.getActivePage() | ||||
|             if (activePage != null) { | ||||
|                 presenter.currentPage = activePage | ||||
|             } | ||||
|         } | ||||
|         super.onPause() | ||||
|     } | ||||
|  | ||||
|     override fun onDestroy() { | ||||
|         subscriptions.unsubscribe() | ||||
|         popupMenu?.dismiss() | ||||
| @@ -230,6 +219,9 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() { | ||||
|         // Ignore | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called from the presenter at startup, allowing to prepare the selected reader. | ||||
|      */ | ||||
|     fun onMangaOpen(manga: Manga) { | ||||
|         if (viewer == null) { | ||||
|             viewer = getOrCreateViewer(manga) | ||||
| @@ -243,22 +235,23 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() { | ||||
|         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 | ||||
|         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) | ||||
|         setActiveChapter(chapter, activePage.pageNumber) | ||||
|     } | ||||
|  | ||||
|     fun onEnterChapter(chapter: Chapter, currentPage: Int) { | ||||
|         val activePage = if (currentPage == -1) chapter.pages.lastIndex else currentPage | ||||
|     fun onEnterChapter(chapter: ReaderChapter, currentPage: Int) { | ||||
|         val activePage = if (currentPage == -1) chapter.pages!!.lastIndex else currentPage | ||||
|         presenter.setActiveChapter(chapter) | ||||
|         setActiveChapter(chapter, activePage) | ||||
|     } | ||||
|  | ||||
|     fun setActiveChapter(chapter: Chapter, currentPage: Int) { | ||||
|         val numPages = chapter.pages.size | ||||
|     fun setActiveChapter(chapter: ReaderChapter, currentPage: Int) { | ||||
|         val numPages = chapter.pages!!.size | ||||
|         if (page_seekbar.rotation != 180f) { | ||||
|             right_page_text.text = "$numPages" | ||||
|             left_page_text.text = "${currentPage + 1}" | ||||
| @@ -275,7 +268,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() { | ||||
|             chapter.name) | ||||
|     } | ||||
|  | ||||
|     fun onAppendChapter(chapter: Chapter) { | ||||
|     fun onAppendChapter(chapter: ReaderChapter) { | ||||
|         viewer?.onPageListAppendReady(chapter) | ||||
|     } | ||||
|  | ||||
| @@ -324,7 +317,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() { | ||||
|         viewer?.let { | ||||
|             val activePage = it.getActivePage() | ||||
|             if (activePage != null) { | ||||
|                 val requestedPage = activePage.chapter.pages[pageIndex] | ||||
|                 val requestedPage = activePage.chapter.pages!![pageIndex] | ||||
|                 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.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 | ||||
|     /** | ||||
|      * Next chapter of the active. | ||||
|      */ | ||||
|     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 val retryPageSubject by lazy { PublishSubject.create<Page>() } | ||||
|     private val pageInitializerSubject by lazy { PublishSubject.create<Chapter>() } | ||||
|  | ||||
|     val isSeamlessMode by lazy { prefs.seamlessMode() } | ||||
|     /** | ||||
|      * 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)!! | ||||
|         // 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, | ||||
|                 { Observable.just(manga) }, | ||||
|                 { view, manga -> view.onMangaOpen(manga) }) | ||||
|  | ||||
|         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) }, | ||||
|         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 | ||||
|                 } | ||||
|                 .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") | ||||
|     } | ||||
|  | ||||
|     // 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) } | ||||
|     /** | ||||
|      * 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()) | ||||
|                 .doOnCompleted { stopPreloadingNextChapter() } | ||||
|     } | ||||
|  | ||||
|     private fun getMangaSyncObservable(): Observable<List<MangaSync>> { | ||||
|         return db.getMangasSync(manga).asRxObservable() | ||||
|                 .take(1) | ||||
|                 .doOnNext { mangaSyncList = it } | ||||
|     /** | ||||
|      * 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) } | ||||
|  | ||||
|         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) { | ||||
|         if (isSeamlessMode) { | ||||
|             if (appenderSubscription != null) | ||||
|                 remove(appenderSubscription) | ||||
|         } else { | ||||
|             stopPreloadingNextChapter() | ||||
|     /** | ||||
|      * 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 | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             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 | ||||
|         appenderSubscription?.let { remove(it) } | ||||
|  | ||||
|         if (appenderSubscription != null) | ||||
|             remove(appenderSubscription) | ||||
|         val nextChapter = nextChapter ?: return | ||||
|  | ||||
|         nextChapter?.let { | ||||
|             if (appenderSubscription != null) | ||||
|                 remove(appenderSubscription) | ||||
|  | ||||
|             it.status = if (isChapterDownloaded(it)) Download.DOWNLOADED else Download.NOT_DOWNLOADED | ||||
|  | ||||
|             appenderSubscription = getPageListObservable(it).subscribeOn(Schedulers.io()) | ||||
|                     .observeOn(AndroidSchedulers.mainThread()) | ||||
|                     .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) | ||||
|         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] | ||||
|  | ||||
|         // 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) | ||||
|         Observable | ||||
|                 .fromCallable { | ||||
|                     if (!chapter.isDownloaded) { | ||||
|                         source.let { if (it is OnlineSource) it.savePageList(chapter, pages) } | ||||
|                     } | ||||
|                 } 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() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         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() | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| package eu.kanade.tachiyomi.ui.reader.viewer.base | ||||
|  | ||||
| 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.source.model.Page | ||||
| import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment | ||||
| import eu.kanade.tachiyomi.ui.reader.ReaderActivity | ||||
| import eu.kanade.tachiyomi.ui.reader.ReaderChapter | ||||
| import java.util.* | ||||
|  | ||||
| /** | ||||
| @@ -29,7 +29,7 @@ abstract class BaseReader : BaseFragment() { | ||||
|     /** | ||||
|      * 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. | ||||
| @@ -72,7 +72,7 @@ abstract class BaseReader : BaseFragment() { | ||||
|     fun updatePageNumber() { | ||||
|         val activePage = getActivePage() | ||||
|         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) { | ||||
|         val oldPage = pages[currentPage] | ||||
|         val newPage = pages[position] | ||||
|         newPage.chapter.last_page_read = newPage.pageNumber | ||||
|         readerActivity.presenter.onPageChanged(newPage) | ||||
|  | ||||
|         if (readerActivity.presenter.isSeamlessMode) { | ||||
|             val oldChapter = oldPage.chapter | ||||
|             val newChapter = newPage.chapter | ||||
|         val oldChapter = oldPage.chapter | ||||
|         val newChapter = newPage.chapter | ||||
|  | ||||
|             // Active chapter has changed. | ||||
|             if (oldChapter.id != newChapter.id) { | ||||
|                 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() | ||||
|             } | ||||
|         // Active chapter has changed. | ||||
|         if (oldChapter.id != newChapter.id) { | ||||
|             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() | ||||
|         } | ||||
|  | ||||
|         currentPage = position | ||||
|         updatePageNumber() | ||||
|     } | ||||
| @@ -144,10 +143,10 @@ abstract class BaseReader : BaseFragment() { | ||||
|      * @param chapter the chapter to set. | ||||
|      * @param currentPage the initial page to display. | ||||
|      */ | ||||
|     fun onPageListReady(chapter: Chapter, currentPage: Page) { | ||||
|     fun onPageListReady(chapter: ReaderChapter, currentPage: Page) { | ||||
|         if (!chapters.contains(chapter)) { | ||||
|             // if we reset the loaded page we also need to reset the loaded chapters | ||||
|             chapters = ArrayList<Chapter>() | ||||
|             chapters.clear() | ||||
|             chapters.add(chapter) | ||||
|             pages = ArrayList(chapter.pages) | ||||
|             onChapterSet(chapter, currentPage) | ||||
| @@ -162,11 +161,11 @@ abstract class BaseReader : BaseFragment() { | ||||
|      * | ||||
|      * @param chapter the chapter to append. | ||||
|      */ | ||||
|     fun onPageListAppendReady(chapter: Chapter) { | ||||
|     fun onPageListAppendReady(chapter: ReaderChapter) { | ||||
|         if (!chapters.contains(chapter)) { | ||||
|             hasRequestedNextChapter = false | ||||
|             chapters.add(chapter) | ||||
|             pages.addAll(chapter.pages) | ||||
|             pages.addAll(chapter.pages!!) | ||||
|             onChapterAppended(chapter) | ||||
|         } | ||||
|     } | ||||
| @@ -184,14 +183,14 @@ abstract class BaseReader : BaseFragment() { | ||||
|      * @param chapter the chapter set. | ||||
|      * @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]. | ||||
|      * | ||||
|      * @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...). | ||||
|   | ||||
| @@ -5,8 +5,8 @@ import android.view.MotionEvent | ||||
| import android.view.ViewGroup | ||||
| import android.view.ViewGroup.LayoutParams.MATCH_PARENT | ||||
| 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.ui.reader.ReaderChapter | ||||
| 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.RightToLeftReader | ||||
| @@ -181,7 +181,7 @@ abstract class PagerReader : BaseReader() { | ||||
|      * @param chapter the chapter set. | ||||
|      * @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 | ||||
|  | ||||
|         // Make sure the view is already initialized. | ||||
| @@ -195,7 +195,7 @@ abstract class PagerReader : BaseReader() { | ||||
|      * | ||||
|      * @param chapter the chapter appended. | ||||
|      */ | ||||
|     override fun onChapterAppended(chapter: Chapter) { | ||||
|     override fun onChapterAppended(chapter: ReaderChapter) { | ||||
|         // Make sure the view is already initialized. | ||||
|         if (view != null) { | ||||
|             adapter.pages = pages | ||||
|   | ||||
| @@ -6,8 +6,8 @@ import android.view.* | ||||
| import android.view.GestureDetector.SimpleOnGestureListener | ||||
| import android.view.ViewGroup.LayoutParams.MATCH_PARENT | ||||
| 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.ui.reader.ReaderChapter | ||||
| import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader | ||||
| import eu.kanade.tachiyomi.widget.PreCachingLayoutManager | ||||
| import rx.subscriptions.CompositeSubscription | ||||
| @@ -147,7 +147,7 @@ class WebtoonReader : BaseReader() { | ||||
|      * @param chapter the chapter set. | ||||
|      * @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 | ||||
|         // this.currentPage = currentPage; | ||||
|  | ||||
| @@ -162,11 +162,11 @@ class WebtoonReader : BaseReader() { | ||||
|      * | ||||
|      * @param chapter the chapter appended. | ||||
|      */ | ||||
|     override fun onChapterAppended(chapter: Chapter) { | ||||
|     override fun onChapterAppended(chapter: ReaderChapter) { | ||||
|         // Make sure the view is already initialized. | ||||
|         if (view != null) { | ||||
|             val insertStart = pages.size - chapter.pages.size | ||||
|             adapter.notifyItemRangeInserted(insertStart, chapter.pages.size) | ||||
|             val insertStart = pages.size - 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_reader_theme_key">pref_reader_theme_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_tapping_key">reader_tap</string> | ||||
|     <string name="pref_reencode_key">reencode_image</string> | ||||
|   | ||||
| @@ -101,7 +101,6 @@ | ||||
|     <string name="pref_enable_transitions">Enable transitions</string> | ||||
|     <string name="pref_show_page_number">Show page number</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_reader_navigation">Navigation</string> | ||||
|     <string name="pref_read_with_volume_keys">Volume keys</string> | ||||
|   | ||||
| @@ -75,11 +75,6 @@ | ||||
|         android:key="@string/pref_keep_screen_on_key" | ||||
|         android:defaultValue="true" /> | ||||
|  | ||||
|     <SwitchPreferenceCompat | ||||
|         android:title="@string/pref_seamless_mode" | ||||
|         android:key="@string/pref_seamless_mode_key" | ||||
|         android:defaultValue="true" /> | ||||
|  | ||||
|     <PreferenceCategory | ||||
|         android:title="@string/pref_reader_navigation"> | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user