Reader presenter in Kotlin + remove Icepick

This commit is contained in:
len 2016-03-19 15:14:51 +01:00
parent 8e0a9d6d66
commit 0d519b3d16
9 changed files with 424 additions and 466 deletions

View file

@ -105,7 +105,6 @@ dependencies {
final OKHTTP_VERSION = '3.2.0'
final RETROFIT_VERSION = '2.0.0'
final STORIO_VERSION = '1.8.0'
final ICEPICK_VERSION = '3.2.0'
final MOCKITO_VERSION = '1.10.19'
compile fileTree(dir: 'libs', include: ['*.jar'])
@ -138,8 +137,6 @@ dependencies {
compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'com.jakewharton.timber:timber:4.1.1'
compile 'ch.acra:acra:4.8.3'
compile "frankiesardo:icepick:$ICEPICK_VERSION"
provided "frankiesardo:icepick-processor:$ICEPICK_VERSION"
compile 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
compile 'eu.davidea:flexible-adapter:4.2.0'
compile 'com.nononsenseapps:filepicker:2.5.2'

View file

@ -58,7 +58,7 @@ public class App extends Application {
protected void setupEventBus() {
EventBus.builder()
.addIndex(new EventBusIndex())
// .addIndex(new EventBusIndex())
.logNoSubscriberMessages(false)
.installDefaultEventBus();
}

View file

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.ui.base.activity
import android.graphics.Color
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
@ -10,21 +9,10 @@ import android.view.View
import android.widget.TextView
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.R
import icepick.Icepick
import org.greenrobot.eventbus.EventBus
open class BaseActivity : AppCompatActivity() {
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
Icepick.restoreInstanceState(this, savedState)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Icepick.saveInstanceState(this, outState)
}
protected fun setupToolbar(toolbar: Toolbar) {
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)

View file

@ -1,23 +1,11 @@
package eu.kanade.tachiyomi.ui.base.fragment
import android.os.Bundle
import android.support.v4.app.Fragment
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import icepick.Icepick
import org.greenrobot.eventbus.EventBus
open class BaseFragment : Fragment() {
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
Icepick.restoreInstanceState(this, savedState)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Icepick.saveInstanceState(this, outState)
}
fun setToolbarTitle(title: String) {
baseActivity.setToolbarTitle(title)
}

View file

@ -1,8 +1,6 @@
package eu.kanade.tachiyomi.ui.base.presenter
import android.content.Context
import android.os.Bundle
import icepick.Icepick
import nucleus.view.ViewWithPresenter
import org.greenrobot.eventbus.EventBus
@ -10,16 +8,6 @@ open class BasePresenter<V : ViewWithPresenter<*>> : RxPresenter<V>() {
lateinit var context: Context
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
Icepick.restoreInstanceState(this, savedState)
}
override fun onSave(state: Bundle) {
super.onSave(state)
Icepick.saveInstanceState(this, state)
}
fun registerForEvents() {
EventBus.getDefault().register(this)
}

View file

@ -172,7 +172,7 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
val onItemSelected = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View? , position: Int, id: Long) {
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
val source = spinnerAdapter.getItem(position)
if (selectedIndex != position || adapter.isEmpty) {
// Set previous selection if it's not a valid source and notify the user

View file

@ -144,7 +144,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
override fun onBackPressed() {
presenter.onChapterLeft()
val chapterToUpdate = presenter.mangaSyncChapterToUpdate
val chapterToUpdate = presenter.getMangaSyncChapterToUpdate()
if (chapterToUpdate > 0) {
if (presenter.prefs.askUpdateMangaSync()) {

View file

@ -1,424 +0,0 @@
package eu.kanade.tachiyomi.ui.reader;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.util.Pair;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.io.File;
import java.util.List;
import javax.inject.Inject;
import eu.kanade.tachiyomi.data.cache.ChapterCache;
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
import eu.kanade.tachiyomi.data.database.models.Chapter;
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.mangasync.base.MangaSyncService;
import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
import eu.kanade.tachiyomi.data.source.SourceManager;
import eu.kanade.tachiyomi.data.source.base.Source;
import eu.kanade.tachiyomi.data.source.model.Page;
import eu.kanade.tachiyomi.event.ReaderEvent;
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
import icepick.State;
import rx.Observable;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
import rx.subjects.PublishSubject;
import timber.log.Timber;
public class ReaderPresenter extends BasePresenter<ReaderActivity> {
@Inject PreferencesHelper prefs;
@Inject DatabaseHelper db;
@Inject DownloadManager downloadManager;
@Inject MangaSyncManager syncManager;
@Inject SourceManager sourceManager;
@Inject ChapterCache chapterCache;
@State Manga manga;
@State Chapter activeChapter;
@State int requestedPage;
private Page currentPage;
private Source source;
private Chapter nextChapter;
private Chapter previousChapter;
private List<MangaSync> mangaSyncList;
private PublishSubject<Page> retryPageSubject;
private PublishSubject<Chapter> pageInitializerSubject;
private boolean seamlessMode;
private Subscription appenderSubscription;
private static final int GET_PAGE_LIST = 1;
private static final int GET_ADJACENT_CHAPTERS = 2;
private static final int GET_MANGA_SYNC = 3;
private static final int PRELOAD_NEXT_CHAPTER = 4;
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
if (savedState != null) {
source = sourceManager.get(manga.source);
initializeSubjects();
}
seamlessMode = prefs.seamlessMode();
startableLatestCache(GET_ADJACENT_CHAPTERS, this::getAdjacentChaptersObservable,
(view, pair) -> view.onAdjacentChapters(pair.first, pair.second));
startable(PRELOAD_NEXT_CHAPTER, this::getPreloadNextChapterObservable,
next -> {},
error -> Timber.e("Error preloading chapter"));
restartable(GET_MANGA_SYNC, () -> getMangaSyncObservable().subscribe());
restartableLatestCache(GET_PAGE_LIST,
() -> getPageListObservable(activeChapter),
(view, chapter) -> view.onChapterReady(manga, activeChapter, currentPage),
(view, error) -> view.onChapterError());
if (savedState == null) {
registerForEvents();
}
}
@Override
protected void onDestroy() {
unregisterForEvents();
super.onDestroy();
}
@Override
protected void onSave(@NonNull Bundle state) {
onChapterLeft();
super.onSave(state);
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEvent(ReaderEvent event) {
EventBus.getDefault().removeStickyEvent(event);
manga = event.getManga();
source = sourceManager.get(manga.source);
initializeSubjects();
loadChapter(event.getChapter());
if (prefs.autoUpdateMangaSync()) {
start(GET_MANGA_SYNC);
}
}
private void initializeSubjects() {
// Listen for pages initialization events
pageInitializerSubject = PublishSubject.create();
add(pageInitializerSubject
.observeOn(Schedulers.io())
.concatMap(chapter -> {
Observable observable;
if (chapter.isDownloaded()) {
File chapterDir = downloadManager.getAbsoluteChapterDirectory(source, manga, chapter);
observable = Observable.from(chapter.getPages())
.flatMap(page -> downloadManager.getDownloadedImage(page, chapterDir));
} else {
observable = source.getAllImageUrlsFromPageList(chapter.getPages())
.flatMap(source::getCachedImage, 2)
.doOnCompleted(() -> source.savePageList(chapter.url, chapter.getPages()));
}
return observable.doOnCompleted(() -> {
if (!seamlessMode && activeChapter == chapter) {
preloadNextChapter();
}
});
})
.subscribe());
// Listen por retry events
retryPageSubject = PublishSubject.create();
add(retryPageSubject
.observeOn(Schedulers.io())
.flatMap(page -> page.getImageUrl() == null ?
source.getImageUrlFromPage(page) :
Observable.just(page))
.flatMap(source::getCachedImage)
.subscribe());
}
// Returns the page list of a chapter
private Observable<Chapter> getPageListObservable(Chapter chapter) {
return (chapter.isDownloaded() ?
// Fetch the page list from disk
Observable.just(downloadManager.getSavedPageList(source, manga, chapter)) :
// Fetch the page list from cache or fallback to network
source.getCachedPageListOrPullFromNetwork(chapter.url)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
).map(pages -> {
for (Page page : pages) {
page.setChapter(chapter);
}
chapter.setPages(pages);
if (requestedPage >= -1 || currentPage == null) {
if (requestedPage == -1) {
currentPage = pages.get(pages.size() - 1);
} else {
currentPage = pages.get(requestedPage);
}
}
requestedPage = -2;
pageInitializerSubject.onNext(chapter);
return chapter;
});
}
private Observable<Pair<Chapter, Chapter>> getAdjacentChaptersObservable() {
return Observable.zip(
db.getPreviousChapter(activeChapter).asRxObservable().take(1),
db.getNextChapter(activeChapter).asRxObservable().take(1),
Pair::create)
.doOnNext(pair -> {
previousChapter = pair.first;
nextChapter = pair.second;
})
.observeOn(AndroidSchedulers.mainThread());
}
// Preload the first pages of the next chapter. Only for non seamless mode
private Observable<Page> getPreloadNextChapterObservable() {
return source.getCachedPageListOrPullFromNetwork(nextChapter.url)
.flatMap(pages -> {
nextChapter.setPages(pages);
int pagesToPreload = Math.min(pages.size(), 5);
return Observable.from(pages).take(pagesToPreload);
})
// Preload up to 5 images
.concatMap(page -> page.getImageUrl() == null ?
source.getImageUrlFromPage(page) :
Observable.just(page))
// Download the first image
.concatMap(page -> page.getPageNumber() == 0 ?
source.getCachedImage(page) :
Observable.just(page))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnCompleted(this::stopPreloadingNextChapter);
}
private Observable<List<MangaSync>> getMangaSyncObservable() {
return db.getMangasSync(manga).asRxObservable()
.take(1)
.doOnNext(mangaSync -> this.mangaSyncList = mangaSync);
}
private void loadChapter(Chapter chapter) {
loadChapter(chapter, 0);
}
// Loads the given chapter
private void loadChapter(Chapter chapter, int requestedPage) {
if (seamlessMode) {
if (appenderSubscription != null)
remove(appenderSubscription);
} else {
stopPreloadingNextChapter();
}
this.activeChapter = chapter;
chapter.status = isChapterDownloaded(chapter) ? Download.DOWNLOADED : 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;
// Reset next and previous chapter. They have to be fetched again
nextChapter = null;
previousChapter = null;
start(GET_PAGE_LIST);
start(GET_ADJACENT_CHAPTERS);
}
public void setActiveChapter(Chapter chapter) {
onChapterLeft();
this.activeChapter = chapter;
nextChapter = null;
previousChapter = null;
start(GET_ADJACENT_CHAPTERS);
}
public void appendNextChapter() {
if (nextChapter == null)
return;
if (appenderSubscription != null)
remove(appenderSubscription);
nextChapter.status = isChapterDownloaded(nextChapter) ? Download.DOWNLOADED : Download.NOT_DOWNLOADED;
appenderSubscription = getPageListObservable(nextChapter)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(deliverLatestCache())
.subscribe(split((view, chapter) -> {
view.onAppendChapter(chapter);
}, (view, error) -> {
view.onChapterAppendError();
}));
add(appenderSubscription);
}
// Check whether the given chapter is downloaded
public boolean isChapterDownloaded(Chapter chapter) {
return downloadManager.isChapterDownloaded(source, manga, chapter);
}
public void retryPage(Page page) {
if (page != null) {
page.setStatus(Page.QUEUE);
if (page.getImagePath() != null) {
File file = new File(page.getImagePath());
chapterCache.removeFileFromCache(file.getName());
}
retryPageSubject.onNext(page);
}
}
// Called before loading another chapter or leaving the reader. It allows to do operations
// over the chapter read like saving progress
public void onChapterLeft() {
List<Page> pages = activeChapter.getPages();
if (pages == null)
return;
// Get the last page read
int activePageNumber = activeChapter.last_page_read;
// Just in case, avoid out of index exceptions
if (activePageNumber >= pages.size()) {
activePageNumber = pages.size() - 1;
}
Page activePage = pages.get(activePageNumber);
// Cache current page list progress for online chapters to allow a faster reopen
if (!activeChapter.isDownloaded()) {
source.savePageList(activeChapter.url, pages);
}
// Save current progress of the chapter. Mark as read if the chapter is finished
if (activePage.isLastPage()) {
activeChapter.read = true;
}
db.insertChapter(activeChapter).asRxObservable().subscribe();
}
public int getMangaSyncChapterToUpdate() {
if (activeChapter.getPages() == null || mangaSyncList == null || mangaSyncList.isEmpty())
return 0;
int lastChapterReadLocal = 0;
// If the current chapter has been read, we check with this one
if (activeChapter.read)
lastChapterReadLocal = (int) Math.floor(activeChapter.chapter_number);
// If not, we check if the previous chapter has been read
else if (previousChapter != null && previousChapter.read)
lastChapterReadLocal = (int) Math.floor(previousChapter.chapter_number);
// 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
boolean hasToUpdate = false;
for (MangaSync mangaSync : mangaSyncList) {
if (lastChapterReadLocal > mangaSync.last_chapter_read) {
mangaSync.last_chapter_read = lastChapterReadLocal;
mangaSync.update = true;
hasToUpdate = true;
}
}
return hasToUpdate ? lastChapterReadLocal : 0;
}
public void updateMangaSyncLastChapterRead() {
for (MangaSync mangaSync : mangaSyncList) {
MangaSyncService service = syncManager.getService(mangaSync.sync_id);
if (service.isLogged() && mangaSync.update) {
UpdateMangaSyncService.start(getContext(), mangaSync);
}
}
}
public void setCurrentPage(Page currentPage) {
this.currentPage = currentPage;
}
public boolean loadNextChapter() {
if (hasNextChapter()) {
onChapterLeft();
loadChapter(nextChapter, 0);
return true;
}
return false;
}
public boolean loadPreviousChapter() {
if (hasPreviousChapter()) {
onChapterLeft();
loadChapter(previousChapter, -1);
return true;
}
return false;
}
public boolean hasNextChapter() {
return nextChapter != null;
}
public boolean hasPreviousChapter() {
return previousChapter != null;
}
private void preloadNextChapter() {
if (hasNextChapter() && !isChapterDownloaded(nextChapter)) {
start(PRELOAD_NEXT_CHAPTER);
}
}
private void stopPreloadingNextChapter() {
if (!isUnsubscribed(PRELOAD_NEXT_CHAPTER)) {
stop(PRELOAD_NEXT_CHAPTER);
if (nextChapter.getPages() != null)
source.savePageList(nextChapter.url, nextChapter.getPages());
}
}
public void updateMangaViewer(int viewer) {
manga.viewer = viewer;
db.insertManga(manga).executeAsBlocking();
}
public Manga getManga() {
return manga;
}
public Page getCurrentPage() {
return currentPage;
}
public boolean isSeamlessMode() {
return seamlessMode;
}
}

View file

@ -0,0 +1,421 @@
package eu.kanade.tachiyomi.ui.reader
import android.os.Bundle
import android.util.Pair
import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
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.SourceManager
import eu.kanade.tachiyomi.data.source.base.Source
import eu.kanade.tachiyomi.data.source.model.Page
import eu.kanade.tachiyomi.event.ReaderEvent
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import rx.Observable
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import rx.subjects.PublishSubject
import timber.log.Timber
import java.io.File
import javax.inject.Inject
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
lateinit var manga: Manga
private set
lateinit var chapter: Chapter
private set
lateinit var source: Source
private set
var requestedPage: Int = 0
var currentPage: Page? = null
private var nextChapter: Chapter? = null
private var previousChapter: Chapter? = null
private var mangaSyncList: List<MangaSync>? = null
private lateinit var retryPageSubject: PublishSubject<Page>
private lateinit var pageInitializerSubject: PublishSubject<Chapter>
val isSeamlessMode by lazy { prefs.seamlessMode() }
private var appenderSubscription: Subscription? = null
private val GET_PAGE_LIST = 1
private val GET_ADJACENT_CHAPTERS = 2
private val GET_MANGA_SYNC = 3
private val PRELOAD_NEXT_CHAPTER = 4
private val MANGA_KEY = "manga_key"
private val CHAPTER_KEY = "chapter_key"
private val PAGE_KEY = "page_key"
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
if (savedState != null) {
source = sourceManager.get(manga.source)!!
manga = savedState.getSerializable(MANGA_KEY) as Manga
chapter = savedState.getSerializable(CHAPTER_KEY) as Chapter
requestedPage = savedState.getInt(PAGE_KEY)
initializeSubjects()
}
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, chapter, currentPage) },
{ view, error -> view.onChapterError() })
if (savedState == null) {
registerForEvents()
}
}
override fun onDestroy() {
unregisterForEvents()
super.onDestroy()
}
override fun onSave(state: Bundle) {
onChapterLeft()
state.putSerializable(MANGA_KEY, manga)
state.putSerializable(CHAPTER_KEY, chapter)
state.putSerializable(PAGE_KEY, requestedPage)
super.onSave(state)
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEvent(event: ReaderEvent) {
EventBus.getDefault().removeStickyEvent(event)
manga = event.manga
source = sourceManager.get(manga.source)!!
initializeSubjects()
loadChapter(event.chapter)
if (prefs.autoUpdateMangaSync()) {
start(GET_MANGA_SYNC)
}
}
private fun initializeSubjects() {
// Listen for pages initialization events
pageInitializerSubject = PublishSubject.create<Chapter>()
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.getAllImageUrlsFromPageList(ch.pages)
.flatMap({ source.getCachedImage(it) }, 2)
.doOnCompleted { source.savePageList(ch.url, ch.pages) }
}
observable.doOnCompleted {
if (!isSeamlessMode && chapter === ch) {
preloadNextChapter()
}
}
}.subscribe())
// Listen por retry events
retryPageSubject = PublishSubject.create<Page>()
add(retryPageSubject.observeOn(Schedulers.io())
.flatMap { page ->
if (page.imageUrl == null)
source.getImageUrlFromPage(page)
else
Observable.just<Page>(page)
}
.flatMap { source.getCachedImage(it) }
.subscribe())
}
// 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.getCachedPageListOrPullFromNetwork(chapter.url)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
return observable.map { pages ->
for (page in pages) {
page.chapter = chapter
}
chapter.pages = pages
if (requestedPage >= -1 || currentPage == null) {
if (requestedPage == -1) {
currentPage = pages[pages.size - 1]
} else {
currentPage = pages[requestedPage]
}
}
requestedPage = -2
pageInitializerSubject.onNext(chapter)
chapter
}
}
private fun getAdjacentChaptersObservable(): Observable<Pair<Chapter, Chapter>> {
return Observable.zip(
db.getPreviousChapter(chapter).asRxObservable().take(1),
db.getNextChapter(chapter).asRxObservable().take(1),
{ a, b -> Pair.create(a, b) })
.doOnNext { pair ->
previousChapter = pair.first
nextChapter = pair.second
}
.observeOn(AndroidSchedulers.mainThread())
}
// Preload the first pages of the next chapter. Only for non seamless mode
private fun getPreloadNextChapterObservable(): Observable<Page> {
return source.getCachedPageListOrPullFromNetwork(nextChapter!!.url)
.flatMap { pages ->
nextChapter!!.pages = pages
val pagesToPreload = Math.min(pages.size, 5)
Observable.from(pages).take(pagesToPreload)
}
// Preload up to 5 images
.concatMap { page ->
if (page.imageUrl == null)
source.getImageUrlFromPage(page)
else
Observable.just<Page>(page)
}
// Download the first image
.concatMap { page ->
if (page.pageNumber == 0)
source.getCachedImage(page)
else
Observable.just<Page>(page)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnCompleted { stopPreloadingNextChapter() }
}
private fun getMangaSyncObservable(): Observable<List<MangaSync>> {
return db.getMangasSync(manga).asRxObservable()
.take(1)
.doOnNext { mangaSyncList = it }
}
// Loads the given chapter
private fun loadChapter(chapter: Chapter, requestedPage: Int = 0) {
if (isSeamlessMode) {
if (appenderSubscription != null)
remove(appenderSubscription)
} else {
stopPreloadingNextChapter()
}
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
// Reset next and previous chapter. They have to be fetched again
nextChapter = null
previousChapter = null
start(GET_PAGE_LIST)
start(GET_ADJACENT_CHAPTERS)
}
fun setActiveChapter(chapter: Chapter) {
onChapterLeft()
this.chapter = chapter
nextChapter = null
previousChapter = null
start(GET_ADJACENT_CHAPTERS)
}
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 = 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)
}
fun retryPage(page: Page?) {
if (page != null) {
page.status = Page.QUEUE
if (page.imagePath != null) {
val file = File(page.imagePath)
chapterCache.removeFileFromCache(file.name)
}
retryPageSubject.onNext(page)
}
}
// 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
// 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.savePageList(chapter.url, pages)
}
// Save current progress of the chapter. Mark as read if the chapter is finished
if (activePage.isLastPage) {
chapter.read = true
}
db.insertChapter(chapter).asRxObservable().subscribe()
}
// 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
fun getMangaSyncChapterToUpdate(): Int {
if (chapter.pages == null || mangaSyncList == null || mangaSyncList!!.isEmpty())
return 0
var lastChapterReadLocal = 0
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()
var hasToUpdate = false
for (mangaSync in mangaSyncList!!) {
if (lastChapterReadLocal > mangaSync.last_chapter_read) {
mangaSync.last_chapter_read = lastChapterReadLocal
mangaSync.update = true
hasToUpdate = true
}
}
return if (hasToUpdate) lastChapterReadLocal else 0
}
fun updateMangaSyncLastChapterRead() {
for (mangaSync in mangaSyncList!!) {
val service = syncManager.getService(mangaSync.sync_id)
if (service.isLogged && mangaSync.update) {
UpdateMangaSyncService.start(context, mangaSync)
}
}
}
fun loadNextChapter(): Boolean {
nextChapter?.let {
onChapterLeft()
loadChapter(it, 0)
return true
}
return false
}
fun loadPreviousChapter(): Boolean {
previousChapter?.let {
onChapterLeft()
loadChapter(it, 0)
return true
}
return false
}
fun hasNextChapter(): Boolean {
return nextChapter != null
}
fun hasPreviousChapter(): Boolean {
return previousChapter != null
}
private fun preloadNextChapter() {
if (hasNextChapter() && !isChapterDownloaded(nextChapter!!)) {
start(PRELOAD_NEXT_CHAPTER)
}
}
private fun stopPreloadingNextChapter() {
if (!isUnsubscribed(PRELOAD_NEXT_CHAPTER)) {
stop(PRELOAD_NEXT_CHAPTER)
if (nextChapter!!.pages != null)
source.savePageList(nextChapter!!.url, nextChapter!!.pages)
}
}
fun updateMangaViewer(viewer: Int) {
manga.viewer = viewer
db.insertManga(manga).executeAsBlocking()
}
}