瀏覽代碼

seamless chapter transitions

Robin Appelman 9 年之前
父節點
當前提交
2566862e0f
共有 25 個文件被更改,包括 455 次插入420 次删除
  1. 16 0
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.java
  2. 4 0
      app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.java
  3. 17 0
      app/src/main/java/eu/kanade/tachiyomi/data/source/model/Page.java
  4. 28 9
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.java
  5. 6 3
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderMenu.java
  6. 151 90
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.java
  7. 57 16
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.java
  8. 0 7
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/OnChapterSingleTapListener.java
  9. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/OnChapterBoundariesOutListener.java
  10. 0 9
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/Pager.java
  11. 0 71
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerGestureListener.java
  12. 62 40
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.java
  13. 14 5
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.java
  14. 31 7
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderFragment.java
  15. 1 38
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.java
  16. 0 19
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalReader.java
  17. 11 7
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/LeftToRightReader.java
  18. 15 23
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/RightToLeftReader.java
  19. 1 38
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalPager.java
  20. 0 10
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalReader.java
  21. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.java
  22. 33 26
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.java
  23. 1 0
      app/src/main/res/values/keys.xml
  24. 1 0
      app/src/main/res/values/strings.xml
  25. 4 0
      app/src/main/res/xml/pref_reader.xml

+ 16 - 0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.java

@@ -4,8 +4,11 @@ import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
 import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
 
 import java.io.Serializable;
+import java.util.List;
 
 import eu.kanade.tachiyomi.data.database.tables.ChapterTable;
+import eu.kanade.tachiyomi.data.download.model.Download;
+import eu.kanade.tachiyomi.data.source.model.Page;
 import eu.kanade.tachiyomi.util.UrlUtil;
 
 @StorIOSQLiteType(table = ChapterTable.TABLE)
@@ -40,6 +43,8 @@ public class Chapter implements Serializable {
 
     public int status;
 
+    private transient List<Page> pages;
+
     public Chapter() {}
 
     public void setUrl(String url) {
@@ -68,4 +73,15 @@ public class Chapter implements Serializable {
         return chapter;
     }
 
+    public List<Page> getPages() {
+        return pages;
+    }
+
+    public void setPages(List<Page> pages) {
+        this.pages = pages;
+    }
+
+    public boolean isDownloaded() {
+        return status == Download.DOWNLOADED;
+    }
 }

+ 4 - 0
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.java

@@ -128,6 +128,10 @@ public class PreferencesHelper {
         return rxPrefs.getInteger(getKey(R.string.pref_last_catalogue_source_key), -1);
     }
 
+    public boolean seamlessMode() {
+        return prefs.getBoolean(getKey(R.string.pref_seamless_mode_key), true);
+    }
+
     public Preference<Boolean> catalogueAsList() {
         return rxPrefs.getBoolean(getKey(R.string.pref_display_catalogue_as_list), false);
     }

+ 17 - 0
app/src/main/java/eu/kanade/tachiyomi/data/source/model/Page.java

@@ -1,5 +1,9 @@
 package eu.kanade.tachiyomi.data.source.model;
 
+import java.io.Serializable;
+import java.util.List;
+
+import eu.kanade.tachiyomi.data.database.models.Chapter;
 import eu.kanade.tachiyomi.data.network.ProgressListener;
 import rx.subjects.PublishSubject;
 
@@ -8,6 +12,7 @@ public class Page implements ProgressListener {
     private int pageNumber;
     private String url;
     private String imageUrl;
+    private transient Chapter chapter;
     private transient String imagePath;
     private transient volatile int status;
     private transient volatile int progress;
@@ -82,4 +87,16 @@ public class Page implements ProgressListener {
         this.statusSubject = subject;
     }
 
+    public Chapter getChapter() {
+        return chapter;
+    }
+
+    public void setChapter(Chapter chapter) {
+        this.chapter = chapter;
+    }
+
+    public boolean isLastPage() {
+        List<Page> chapterPages = chapter.getPages();
+        return chapterPages.size() -1 == pageNumber;
+    }
 }

+ 28 - 9
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.java

@@ -158,16 +158,34 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
         ToastUtil.showShort(this, R.string.page_list_error);
     }
 
-    public void onChapterReady(List<Page> pages, Manga manga, Chapter chapter, int currentPage) {
-        if (currentPage == -1) {
-            currentPage = pages.size() - 1;
+    public void onChapterAppendError() {
+        // Ignore
+    }
+
+    public void onChapterReady(Manga manga, Chapter chapter, Page currentPage) {
+        List<Page> pages = chapter.getPages();
+        if (currentPage == null) {
+            currentPage = pages.get(pages.size() - 1);
         }
 
         if (viewer == null) {
             viewer = getOrCreateViewer(manga);
         }
-        viewer.onPageListReady(pages, currentPage);
-        readerMenu.onChapterReady(pages.size(), manga, chapter, currentPage);
+        viewer.onPageListReady(chapter, currentPage);
+        readerMenu.setActiveManga(manga);
+        readerMenu.setActiveChapter(chapter, currentPage.getPageNumber());
+    }
+
+    public void onEnterChapter(Chapter chapter, int currentPage) {
+        if (currentPage == -1) {
+            currentPage = chapter.getPages().size() - 1;
+        }
+        getPresenter().setActiveChapter(chapter);
+        readerMenu.setActiveChapter(chapter, currentPage);
+    }
+
+    public void onAppendChapter(Chapter chapter) {
+        viewer.onPageListAppendReady(chapter);
     }
 
     public void onAdjacentChapters(Chapter previous, Chapter next) {
@@ -209,8 +227,9 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
         readerMenu.onPageChanged(currentPageIndex);
     }
 
-    public void setSelectedPage(int pageIndex) {
-        viewer.setSelectedPage(pageIndex);
+    public void gotoPageInCurrentChapter(int pageIndex) {
+        Page requestedPage = viewer.getCurrentPage().getChapter().getPages().get(pageIndex);
+        viewer.setSelectedPage(requestedPage);
     }
 
     public void onCenterSingleTap() {
@@ -218,7 +237,7 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
     }
 
     public void requestNextChapter() {
-        getPresenter().setCurrentPage(viewer != null ? viewer.getCurrentPage() : 0);
+        getPresenter().setCurrentPage(viewer.getCurrentPage());
         if (!getPresenter().loadNextChapter()) {
             ToastUtil.showShort(this, R.string.no_next_chapter);
         }
@@ -226,7 +245,7 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
     }
 
     public void requestPreviousChapter() {
-        getPresenter().setCurrentPage(viewer != null ? viewer.getCurrentPage() : 0);
+        getPresenter().setCurrentPage(viewer.getCurrentPage());
         if (!getPresenter().loadPreviousChapter()) {
             ToastUtil.showShort(this, R.string.no_previous_chapter);
         }

+ 6 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderMenu.java

@@ -134,7 +134,7 @@ public class ReaderMenu {
         return true;
     }
 
-    public void onChapterReady(int numPages, Manga manga, Chapter chapter, int currentPageIndex) {
+    public void setActiveManga(Manga manga) {
         if (manga.viewer == ReaderActivity.RIGHT_TO_LEFT && !inverted) {
             // Invert the seekbar and textview fields for the right to left reader
             seekBar.setRotation(180);
@@ -144,14 +144,17 @@ public class ReaderMenu {
             // Don't invert again on chapter change
             inverted = true;
         }
+        activity.setToolbarTitle(manga.title);
+    }
 
+    public void setActiveChapter(Chapter chapter, int currentPageIndex) {
         // Set initial values
+        int numPages = chapter.getPages().size();
         totalPages.setText("" + numPages);
         currentPage.setText("" + (currentPageIndex + 1));
         seekBar.setMax(numPages - 1);
         seekBar.setProgress(currentPageIndex);
 
-        activity.setToolbarTitle(manga.title);
         activity.setToolbarSubtitle(chapter.chapter_number != -1 ?
                 activity.getString(R.string.chapter_subtitle,
                         decimalFormat.format(chapter.chapter_number)) :
@@ -353,7 +356,7 @@ public class ReaderMenu {
         @Override
         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
             if (fromUser) {
-                activity.setSelectedPage(progress);
+                activity.gotoPageInCurrentChapter(progress);
             }
         }
 

+ 151 - 90
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.java

@@ -15,6 +15,7 @@ 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.base.MangaSyncService;
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
@@ -27,6 +28,7 @@ import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
 import eu.kanade.tachiyomi.util.EventBusHook;
 import icepick.State;
 import rx.Observable;
+import rx.Subscription;
 import rx.android.schedulers.AndroidSchedulers;
 import rx.schedulers.Schedulers;
 import rx.subjects.PublishSubject;
@@ -41,25 +43,25 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
     @Inject SourceManager sourceManager;
 
     @State Manga manga;
-    @State Chapter chapter;
+    @State Chapter activeChapter;
     @State int sourceId;
-    @State boolean isDownloaded;
-    @State int currentPage;
+    @State int requestedPage;
+    private Page currentPage;
     private Source source;
     private Chapter nextChapter;
     private Chapter previousChapter;
-    private List<Page> pageList;
-    private List<Page> nextChapterPageList;
     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_PAGE_IMAGES = 2;
-    private static final int GET_ADJACENT_CHAPTERS = 3;
-    private static final int RETRY_IMAGES = 4;
-    private static final int PRELOAD_NEXT_CHAPTER = 5;
-    private static final int GET_MANGA_SYNC = 6;
+    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) {
@@ -67,38 +69,29 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
 
         if (savedState != null) {
             source = sourceManager.get(sourceId);
+            initializeSubjects();
         }
 
-        retryPageSubject = PublishSubject.create();
-
-        startable(PRELOAD_NEXT_CHAPTER, this::getPreloadNextChapterObservable,
-                next -> {},
-                error -> Timber.e("Error preloading chapter"));
-
-        startable(GET_PAGE_IMAGES, this::getPageImagesObservable,
-                next -> {},
-                error -> Timber.e("Error fetching images"));
+        seamlessMode = prefs.seamlessMode();
 
         startableLatestCache(GET_ADJACENT_CHAPTERS, this::getAdjacentChaptersObservable,
                 (view, pair) -> view.onAdjacentChapters(pair.first, pair.second));
 
-        startable(RETRY_IMAGES, this::getRetryPageObservable);
+        startable(PRELOAD_NEXT_CHAPTER, this::getPreloadNextChapterObservable,
+            next -> {},
+            error -> Timber.e("Error preloading chapter"));
+
 
         restartable(GET_MANGA_SYNC, () -> getMangaSyncObservable().subscribe());
 
         restartableLatestCache(GET_PAGE_LIST,
-                () -> getPageListObservable()
-                        .doOnNext(pages -> pageList = pages)
-                        .doOnCompleted(() -> {
-                            start(GET_ADJACENT_CHAPTERS);
-                            start(GET_PAGE_IMAGES);
-                            start(RETRY_IMAGES);
-                        }),
-                (view, pages) -> view.onChapterReady(pages, manga, chapter, currentPage),
+                () -> getPageListObservable(activeChapter),
+                (view, chapter) -> view.onChapterReady(manga, activeChapter, currentPage),
                 (view, error) -> view.onChapterError());
 
-
-        registerForStickyEvents();
+        if (savedState == null) {
+            registerForStickyEvents();
+        }
     }
 
     @Override
@@ -119,43 +112,79 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
         manga = event.getManga();
         source = event.getSource();
         sourceId = source.getId();
+        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<List<Page>> getPageListObservable() {
-        return isDownloaded ?
+    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());
-    }
-
-    // Get the chapter images from network or disk
-    private Observable<Page> getPageImagesObservable() {
-        Observable<Page> pageObservable;
-
-        if (!isDownloaded) {
-            pageObservable = source.getAllImageUrlsFromPageList(pageList)
-                    .flatMap(source::getCachedImage, 2);
-        } else {
-            File chapterDir = downloadManager.getAbsoluteChapterDirectory(source, manga, chapter);
-            pageObservable = Observable.from(pageList)
-                    .flatMap(page -> downloadManager.getDownloadedImage(page, chapterDir));
-        }
-        return pageObservable.subscribeOn(Schedulers.io())
-                .doOnCompleted(this::preloadNextChapter);
+                        .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(chapter).asRxObservable().take(1),
-                db.getNextChapter(chapter).asRxObservable().take(1),
+                db.getPreviousChapter(activeChapter).asRxObservable().take(1),
+                db.getNextChapter(activeChapter).asRxObservable().take(1),
                 Pair::create)
                 .doOnNext(pair -> {
                     previousChapter = pair.first;
@@ -164,22 +193,11 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
                 .observeOn(AndroidSchedulers.mainThread());
     }
 
-    // Listen for retry page events
-    private Observable<Page> getRetryPageObservable() {
-        return retryPageSubject
-                .observeOn(Schedulers.io())
-                .flatMap(page -> page.getImageUrl() == null ?
-                        source.getImageUrlFromPage(page) :
-                        Observable.just(page))
-                .flatMap(source::getCachedImage);
-    }
-
-    // Preload the first pages of the next chapter
+    // Preload the first pages of the next chapter. Only for non seamless mode
     private Observable<Page> getPreloadNextChapterObservable() {
         return source.getCachedPageListOrPullFromNetwork(nextChapter.url)
                 .flatMap(pages -> {
-                    nextChapterPageList = pages;
-                    // Preload at most 5 pages
+                    nextChapter.setPages(pages);
                     int pagesToPreload = Math.min(pages.size(), 5);
                     return Observable.from(pages).take(pagesToPreload);
                 })
@@ -198,6 +216,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
 
     private Observable<List<MangaSync>> getMangaSyncObservable() {
         return db.getMangasSync(manga).asRxObservable()
+                .take(1)
                 .doOnNext(mangaSync -> this.mangaSyncList = mangaSync);
     }
 
@@ -207,24 +226,58 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
 
     // Loads the given chapter
     private void loadChapter(Chapter chapter, int requestedPage) {
-        // Before loading the chapter, stop preloading (if it's working) and save current progress
-        stopPreloadingNextChapter();
+        if (seamlessMode) {
+            if (appenderSubscription != null)
+                remove(appenderSubscription);
+        } else {
+            stopPreloadingNextChapter();
+        }
 
-        this.chapter = chapter;
-        isDownloaded = isChapterDownloaded(chapter);
+        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)
-            currentPage = chapter.last_page_read;
+            this.requestedPage = chapter.last_page_read;
         else
-            currentPage = requestedPage;
+            this.requestedPage = requestedPage;
 
         // Reset next and previous chapter. They have to be fetched again
         nextChapter = null;
         previousChapter = null;
-        nextChapterPageList = null;
 
         start(GET_PAGE_LIST);
+        start(GET_ADJACENT_CHAPTERS);
+    }
+
+    public void setActiveChapter(Chapter chapter) {
+        onChapterLeft(true); // force markAsRead since at this point the current page is already for the next chapter
+        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
@@ -237,37 +290,38 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
         retryPageSubject.onNext(page);
     }
 
+    public void onChapterLeft() {
+        onChapterLeft(false);
+    }
+
     // Called before loading another chapter or leaving the reader. It allows to do operations
     // over the chapter read like saving progress
-    public void onChapterLeft() {
-        if (pageList == null)
+    public void onChapterLeft(boolean forceMarkAsRead) {
+        if (activeChapter.getPages() == null)
             return;
 
+        Page activePage = getCurrentPage();
+
         // Cache current page list progress for online chapters to allow a faster reopen
-        if (!isDownloaded)
-            source.savePageList(chapter.url, pageList);
+        if (!activeChapter.isDownloaded())
+            source.savePageList(activeChapter.url, activePage.getChapter().getPages());
 
         // Save current progress of the chapter. Mark as read if the chapter is finished
-        chapter.last_page_read = currentPage;
-        if (isChapterFinished()) {
-            chapter.read = true;
+        activeChapter.last_page_read = activePage.getPageNumber();
+        if (forceMarkAsRead || activePage.isLastPage()) {
+            activeChapter.read = true;
         }
-        db.insertChapter(chapter).asRxObservable().subscribe();
-    }
-
-    // Check whether the chapter has been read
-    private boolean isChapterFinished() {
-        return !chapter.read && currentPage == pageList.size() - 1;
+        db.insertChapter(activeChapter).asRxObservable().subscribe();
     }
 
     public int getMangaSyncChapterToUpdate() {
-        if (pageList == null || mangaSyncList == null || mangaSyncList.isEmpty())
+        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 (chapter.read)
-            lastChapterReadLocal = (int) Math.floor(chapter.chapter_number);
+        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);
@@ -295,7 +349,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
         }
     }
 
-    public void setCurrentPage(int currentPage) {
+    public void setCurrentPage(Page currentPage) {
         this.currentPage = currentPage;
     }
 
@@ -334,8 +388,8 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
     private void stopPreloadingNextChapter() {
         if (!isUnsubscribed(PRELOAD_NEXT_CHAPTER)) {
             stop(PRELOAD_NEXT_CHAPTER);
-            if (nextChapterPageList != null)
-                source.savePageList(nextChapter.url, nextChapterPageList);
+            if (nextChapter.getPages() != null)
+                source.savePageList(nextChapter.url, nextChapter.getPages());
         }
     }
 
@@ -348,4 +402,11 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
         return manga;
     }
 
+    public Page getCurrentPage() {
+        return currentPage;
+    }
+
+    public boolean isSeamlessMode() {
+        return seamlessMode;
+    }
 }

+ 57 - 16
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.java

@@ -1,15 +1,15 @@
 package eu.kanade.tachiyomi.ui.reader.viewer.base;
 
-import android.view.MotionEvent;
-
 import com.davemorrissey.labs.subscaleview.decoder.ImageDecoder;
 import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder;
 import com.davemorrissey.labs.subscaleview.decoder.RapidImageRegionDecoder;
 import com.davemorrissey.labs.subscaleview.decoder.SkiaImageDecoder;
 import com.davemorrissey.labs.subscaleview.decoder.SkiaImageRegionDecoder;
 
+import java.util.ArrayList;
 import java.util.List;
 
+import eu.kanade.tachiyomi.data.database.models.Chapter;
 import eu.kanade.tachiyomi.data.source.model.Page;
 import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment;
 import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
@@ -18,40 +18,81 @@ public abstract class BaseReader extends BaseFragment {
 
     protected int currentPage;
     protected List<Page> pages;
+    protected List<Chapter> chapters;
     protected Class<? extends ImageRegionDecoder> regionDecoderClass;
     protected Class<? extends ImageDecoder> bitmapDecoderClass;
 
+    private boolean hasRequestedNextChapter;
+
     public static final int RAPID_DECODER = 0;
     public static final int SKIA_DECODER = 1;
 
     public void updatePageNumber() {
-        getReaderActivity().onPageChanged(getCurrentPage(), getTotalPages());
+        getReaderActivity().onPageChanged(getCurrentPage().getPageNumber(), getCurrentPage().getChapter().getPages().size());
     }
 
-    public int getCurrentPage() {
-        return currentPage;
+    public Page getCurrentPage() {
+        return pages.get(currentPage);
     }
 
-    public int getPageForPosition(int position) {
-        return position;
+    public void onPageChanged(int position) {
+        if (getReaderActivity().getPresenter().isSeamlessMode()) {
+            Chapter oldChapter = pages.get(currentPage).getChapter();
+            Chapter newChapter = pages.get(position).getChapter();
+            if (!hasRequestedNextChapter && position > pages.size() - 5) {
+                hasRequestedNextChapter = true;
+                getReaderActivity().getPresenter().appendNextChapter();
+            }
+            if (!oldChapter.id.equals(newChapter.id)) {
+                Page page = pages.get(position);
+                onChapterChanged(page.getChapter(), page);
+            }
+        }
+        currentPage = position;
+        updatePageNumber();
     }
 
-    public int getPositionForPage(int page) {
-        return page;
+    private void onChapterChanged(Chapter chapter, Page currentPage) {
+        getReaderActivity().onEnterChapter(chapter, currentPage.getPageNumber());
     }
 
-    public void onPageChanged(int position) {
-        currentPage = getPageForPosition(position);
-        updatePageNumber();
+    public void setSelectedPage(Page page) {
+        setSelectedPage(getPageIndex(page));
     }
 
-    public int getTotalPages() {
-        return pages == null ? 0 : pages.size();
+    public int getPageIndex(Page search) {
+        // search for the index of a page in the current list without requiring them to be the same object
+        for (Page page : pages) {
+            if (page.getPageNumber() == search.getPageNumber() &&
+                    page.getChapter().id.equals(search.getChapter().id)) {
+                return pages.indexOf(page);
+            }
+        }
+        return 0;
+    }
+
+    public void onPageListReady(Chapter chapter, Page currentPage) {
+        if (chapters == null || !chapters.contains(chapter)) {
+            // if we reset the loaded page we also need to reset the loaded chapters
+            chapters = new ArrayList<>();
+            chapters.add(chapter);
+            onSetChapter(chapter, currentPage);
+        } else {
+            setSelectedPage(currentPage);
+        }
+    }
+
+    public void onPageListAppendReady(Chapter chapter) {
+        if (!chapters.contains(chapter)) {
+            hasRequestedNextChapter = false;
+            chapters.add(chapter);
+            onAppendChapter(chapter);
+        }
     }
 
     public abstract void setSelectedPage(int pageNumber);
-    public abstract void onPageListReady(List<Page> pages, int currentPage);
-    public abstract boolean onImageTouch(MotionEvent motionEvent);
+    public abstract void onSetChapter(Chapter chapter, Page currentPage);
+    public abstract void onAppendChapter(Chapter chapter);
 
     public void setDecoderClass(int value) {
         switch (value) {

+ 0 - 7
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/OnChapterSingleTapListener.java

@@ -1,7 +0,0 @@
-package eu.kanade.tachiyomi.ui.reader.viewer.base;
-
-public interface OnChapterSingleTapListener {
-    void onCenterTap();
-    void onLeftSideTap();
-    void onRightSideTap();
-}

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/OnChapterBoundariesOutListener.java → app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/OnChapterBoundariesOutListener.java

@@ -1,4 +1,4 @@
-package eu.kanade.tachiyomi.ui.reader.viewer.base;
+package eu.kanade.tachiyomi.ui.reader.viewer.pager;
 
 public interface OnChapterBoundariesOutListener {
     void onFirstPageOutEvent();

+ 0 - 9
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/Pager.java

@@ -1,11 +1,8 @@
 package eu.kanade.tachiyomi.ui.reader.viewer.pager;
 
 import android.support.v4.view.PagerAdapter;
-import android.view.MotionEvent;
 import android.view.ViewGroup;
 
-import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterBoundariesOutListener;
-import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterSingleTapListener;
 import rx.functions.Action1;
 
 public interface Pager {
@@ -24,13 +21,7 @@ public interface Pager {
     PagerAdapter getAdapter();
     void setAdapter(PagerAdapter adapter);
 
-    boolean onImageTouch(MotionEvent motionEvent);
-
     void setOnChapterBoundariesOutListener(OnChapterBoundariesOutListener listener);
-    void setOnChapterSingleTapListener(OnChapterSingleTapListener listener);
-
-    OnChapterBoundariesOutListener getChapterBoundariesListener();
-    OnChapterSingleTapListener getChapterSingleTapListener();
 
     void setOnPageChangeListener(Action1<Integer> onPageChanged);
     void clearOnPageChangeListeners();

+ 0 - 71
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerGestureListener.java

@@ -1,71 +0,0 @@
-package eu.kanade.tachiyomi.ui.reader.viewer.pager;
-
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-
-public class PagerGestureListener extends GestureDetector.SimpleOnGestureListener {
-
-    private Pager pager;
-
-    private static final float LEFT_REGION = 0.33f;
-    private static final float RIGHT_REGION = 0.66f;
-
-    public PagerGestureListener(Pager pager) {
-        this.pager = pager;
-    }
-
-    @Override
-    public boolean onSingleTapConfirmed(MotionEvent e) {
-        final int position = pager.getCurrentItem();
-        final float positionX = e.getX();
-
-        if (positionX < pager.getWidth() * LEFT_REGION) {
-            if (position != 0) {
-                onLeftSideTap();
-            } else {
-                onFirstPageOut();
-            }
-        } else if (positionX > pager.getWidth() * RIGHT_REGION) {
-            if (position != pager.getAdapter().getCount() - 1) {
-                onRightSideTap();
-            } else {
-                onLastPageOut();
-            }
-        } else {
-            onCenterTap();
-        }
-
-        return true;
-    }
-
-    private void onLeftSideTap() {
-        if (pager.getChapterSingleTapListener() != null) {
-            pager.getChapterSingleTapListener().onLeftSideTap();
-        }
-    }
-
-    private void onRightSideTap() {
-        if (pager.getChapterSingleTapListener() != null) {
-            pager.getChapterSingleTapListener().onRightSideTap();
-        }
-    }
-
-    private void onCenterTap() {
-        if (pager.getChapterSingleTapListener() != null) {
-            pager.getChapterSingleTapListener().onCenterTap();
-        }
-    }
-
-    private void onFirstPageOut() {
-        if (pager.getChapterBoundariesListener() != null) {
-            pager.getChapterBoundariesListener().onFirstPageOutEvent();
-        }
-    }
-
-    private void onLastPageOut() {
-        if (pager.getChapterBoundariesListener() != null) {
-            pager.getChapterBoundariesListener().onLastPageOutEvent();
-        }
-    }
-
-}

+ 62 - 40
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.java

@@ -1,16 +1,16 @@
 package eu.kanade.tachiyomi.ui.reader.viewer.pager;
 
+import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.ViewGroup;
 
-import java.util.List;
+import java.util.ArrayList;
 
 import eu.kanade.tachiyomi.R;
+import eu.kanade.tachiyomi.data.database.models.Chapter;
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
 import eu.kanade.tachiyomi.data.source.model.Page;
 import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader;
-import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterBoundariesOutListener;
-import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterSingleTapListener;
 import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader;
 import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader;
 import rx.subscriptions.CompositeSubscription;
@@ -21,8 +21,8 @@ public abstract class PagerReader extends BaseReader {
 
     protected PagerReaderAdapter adapter;
     protected Pager pager;
+    protected GestureDetector gestureDetector;
 
-    private boolean isReady;
     protected boolean transitions;
     protected CompositeSubscription subscriptions;
 
@@ -34,6 +34,9 @@ public abstract class PagerReader extends BaseReader {
     public static final int ALIGN_RIGHT = 3;
     public static final int ALIGN_CENTER = 4;
 
+    private static final float LEFT_REGION = 0.33f;
+    private static final float RIGHT_REGION = 0.66f;
+
     protected void initializePager(Pager pager) {
         this.pager = pager;
         pager.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
@@ -42,30 +45,15 @@ public abstract class PagerReader extends BaseReader {
         pager.setOnChapterBoundariesOutListener(new OnChapterBoundariesOutListener() {
             @Override
             public void onFirstPageOutEvent() {
-                onFirstPageOut();
+                getReaderActivity().requestPreviousChapter();
             }
 
             @Override
             public void onLastPageOutEvent() {
-                onLastPageOut();
-            }
-        });
-        pager.setOnChapterSingleTapListener(new OnChapterSingleTapListener() {
-            @Override
-            public void onCenterTap() {
-                getReaderActivity().onCenterSingleTap();
-            }
-
-            @Override
-            public void onLeftSideTap() {
-                pager.setCurrentItem(pager.getCurrentItem() - 1, transitions);
-            }
-
-            @Override
-            public void onRightSideTap() {
-                pager.setCurrentItem(pager.getCurrentItem() + 1, transitions);
+                getReaderActivity().requestNextChapter();
             }
         });
+        gestureDetector = createGestureDetector();
 
         adapter = new PagerReaderAdapter(getChildFragmentManager());
         pager.setAdapter(adapter);
@@ -77,28 +65,27 @@ public abstract class PagerReader extends BaseReader {
                 .doOnNext(this::setDecoderClass)
                 .skip(1)
                 .distinctUntilChanged()
-                .subscribe(v -> adapter.notifyDataSetChanged()));
+                .subscribe(v -> pager.setAdapter(adapter)));
 
         subscriptions.add(preferences.imageScaleType()
                 .asObservable()
                 .doOnNext(this::setImageScaleType)
                 .skip(1)
                 .distinctUntilChanged()
-                .subscribe(v -> adapter.notifyDataSetChanged()));
+                .subscribe(v -> pager.setAdapter(adapter)));
 
         subscriptions.add(preferences.zoomStart()
                 .asObservable()
                 .doOnNext(this::setZoomStart)
                 .skip(1)
                 .distinctUntilChanged()
-                .subscribe(v -> adapter.notifyDataSetChanged()));
+                .subscribe(v -> pager.setAdapter(adapter)));
 
         subscriptions.add(preferences.enableTransitions()
                 .asObservable()
                 .subscribe(value -> transitions = value));
 
         setPages();
-        isReady = true;
     }
 
     @Override
@@ -107,14 +94,41 @@ public abstract class PagerReader extends BaseReader {
         super.onDestroyView();
     }
 
-    @Override
-    public void onPageListReady(List<Page> pages, int currentPage) {
-        if (this.pages != pages) {
-            this.pages = pages;
-            this.currentPage = currentPage;
-            if (isReady) {
-                setPages();
+    protected GestureDetector createGestureDetector() {
+        return new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
+            @Override
+            public boolean onSingleTapConfirmed(MotionEvent e) {
+                final float positionX = e.getX();
+
+                if (positionX < pager.getWidth() * LEFT_REGION) {
+                    onLeftSideTap();
+                } else if (positionX > pager.getWidth() * RIGHT_REGION) {
+                    onRightSideTap();
+                } else {
+                    getReaderActivity().onCenterSingleTap();
+                }
+                return true;
             }
+        });
+    }
+
+    @Override
+    public void onSetChapter(Chapter chapter, Page currentPage) {
+        pages = new ArrayList<>(chapter.getPages());
+        this.currentPage = getPageIndex(currentPage); // we might have a new page object
+
+        // This method can be called before the view is created
+        if (pager != null) {
+            setPages();
+        }
+    }
+
+    public void onAppendChapter(Chapter chapter) {
+        pages.addAll(chapter.getPages());
+
+        // This method can be called before the view is created
+        if (pager != null) {
+            adapter.setPages(pages);
         }
     }
 
@@ -130,12 +144,23 @@ public abstract class PagerReader extends BaseReader {
 
     @Override
     public void setSelectedPage(int pageNumber) {
-        pager.setCurrentItem(getPositionForPage(pageNumber), false);
+        pager.setCurrentItem(pageNumber, false);
     }
 
-    @Override
-    public boolean onImageTouch(MotionEvent motionEvent) {
-        return pager.onImageTouch(motionEvent);
+    protected void onLeftSideTap() {
+        if (pager.getCurrentItem() != 0) {
+            pager.setCurrentItem(pager.getCurrentItem() - 1, transitions);
+        } else {
+            getReaderActivity().requestPreviousChapter();
+        }
+    }
+
+    protected void onRightSideTap() {
+        if (pager.getCurrentItem() != pager.getAdapter().getCount() - 1) {
+            pager.setCurrentItem(pager.getCurrentItem() + 1, transitions);
+        } else {
+            getReaderActivity().requestNextChapter();
+        }
     }
 
     private void setImageScaleType(int scaleType) {
@@ -155,7 +180,4 @@ public abstract class PagerReader extends BaseReader {
         }
     }
 
-    public abstract void onFirstPageOut();
-    public abstract void onLastPageOut();
-
 }

+ 14 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.java

@@ -31,14 +31,10 @@ public class PagerReaderAdapter extends FragmentStatePagerAdapter {
     public Object instantiateItem(ViewGroup container, int position) {
         PagerReaderFragment f = (PagerReaderFragment) super.instantiateItem(container, position);
         f.setPage(pages.get(position));
+        f.setPosition(position);
         return f;
     }
 
-    @Override
-    public int getItemPosition(Object object) {
-        return POSITION_NONE;
-    }
-
     public List<Page> getPages() {
         return pages;
     }
@@ -48,4 +44,17 @@ public class PagerReaderAdapter extends FragmentStatePagerAdapter {
         notifyDataSetChanged();
     }
 
+    @Override
+    public int getItemPosition(Object object) {
+        PagerReaderFragment f = (PagerReaderFragment) object;
+        int position = f.getPosition();
+        if (position >= 0 && position < getCount()) {
+            if (pages.get(position) == f.getPage()) {
+                return POSITION_UNCHANGED;
+            } else {
+                return POSITION_NONE;
+            }
+        }
+        return super.getItemPosition(object);
+    }
 }

+ 31 - 7
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderFragment.java

@@ -26,6 +26,7 @@ import eu.kanade.tachiyomi.R;
 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.viewer.pager.horizontal.RightToLeftReader;
 import eu.kanade.tachiyomi.ui.reader.viewer.pager.vertical.VerticalReader;
 import rx.Observable;
 import rx.Subscription;
@@ -42,9 +43,12 @@ public class PagerReaderFragment extends BaseFragment {
     @Bind(R.id.retry_button) Button retryButton;
 
     private Page page;
-    private boolean isReady;
     private Subscription progressSubscription;
     private Subscription statusSubscription;
+    private int position = -1;
+
+    private int lightGreyColor;
+    private int blackColor;
 
     public static PagerReaderFragment newInstance() {
         return new PagerReaderFragment();
@@ -57,8 +61,15 @@ public class PagerReaderFragment extends BaseFragment {
         ReaderActivity activity = getReaderActivity();
         PagerReader parentFragment = (PagerReader) getParentFragment();
 
+        lightGreyColor = ContextCompat.getColor(getContext(), R.color.light_grey);
+        blackColor = ContextCompat.getColor(getContext(), R.color.primary_text);
+
         if (activity.getReaderTheme() == ReaderActivity.BLACK_THEME) {
-             progressText.setTextColor(ContextCompat.getColor(getContext(), R.color.light_grey));
+             progressText.setTextColor(lightGreyColor);
+        }
+
+        if (parentFragment instanceof RightToLeftReader) {
+            view.setRotation(-180);
         }
 
         imageView.setParallelLoadingEnabled(true);
@@ -69,7 +80,7 @@ public class PagerReaderFragment extends BaseFragment {
         imageView.setRegionDecoderClass(parentFragment.getRegionDecoderClass());
         imageView.setBitmapDecoderClass(parentFragment.getBitmapDecoderClass());
         imageView.setVerticalScrollingParent(parentFragment instanceof VerticalReader);
-        imageView.setOnTouchListener((v, motionEvent) -> parentFragment.onImageTouch(motionEvent));
+        imageView.setOnTouchListener((v, motionEvent) -> parentFragment.gestureDetector.onTouchEvent(motionEvent));
         imageView.setOnImageEventListener(new SubsamplingScaleImageView.DefaultOnImageEventListener() {
             @Override
             public void onReady() {
@@ -103,7 +114,6 @@ public class PagerReaderFragment extends BaseFragment {
         });
 
         observeStatus();
-        isReady = true;
         return view;
     }
 
@@ -111,6 +121,7 @@ public class PagerReaderFragment extends BaseFragment {
     public void onDestroyView() {
         unsubscribeProgress();
         unsubscribeStatus();
+        imageView.setOnTouchListener(null);
         imageView.setOnImageEventListener(null);
         ButterKnife.unbind(this);
         super.onDestroyView();
@@ -118,11 +129,17 @@ public class PagerReaderFragment extends BaseFragment {
 
     public void setPage(Page page) {
         this.page = page;
-        if (isReady) {
+
+        // This method can be called before the view is created
+        if (imageView != null) {
             observeStatus();
         }
     }
 
+    public void setPosition(int position) {
+        this.position = position;
+    }
+
     private void showImage() {
         if (page == null || page.getImagePath() == null)
             return;
@@ -160,8 +177,7 @@ public class PagerReaderFragment extends BaseFragment {
         errorText.setGravity(Gravity.CENTER);
         errorText.setText(R.string.decode_image_error);
         errorText.setTextColor(getReaderActivity().getReaderTheme() == ReaderActivity.BLACK_THEME ?
-                    ContextCompat.getColor(getContext(), R.color.light_grey) :
-                    ContextCompat.getColor(getContext(), R.color.primary_text));
+                    lightGreyColor : blackColor);
 
         view.addView(errorText);
     }
@@ -236,6 +252,14 @@ public class PagerReaderFragment extends BaseFragment {
         }
     }
 
+    public Page getPage() {
+        return page;
+    }
+
+    public int getPosition() {
+        return position;
+    }
+
     private ReaderActivity getReaderActivity() {
         return (ReaderActivity) getActivity();
     }

+ 1 - 38
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.java

@@ -2,38 +2,21 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal;
 
 import android.content.Context;
 import android.support.v4.view.ViewPager;
-import android.util.AttributeSet;
-import android.view.GestureDetector;
 import android.view.MotionEvent;
 
-import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterBoundariesOutListener;
-import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterSingleTapListener;
+import eu.kanade.tachiyomi.ui.reader.viewer.pager.OnChapterBoundariesOutListener;
 import eu.kanade.tachiyomi.ui.reader.viewer.pager.Pager;
-import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerGestureListener;
 import rx.functions.Action1;
 
 public class HorizontalPager extends ViewPager implements Pager {
 
-    private GestureDetector gestureDetector;
-
     private OnChapterBoundariesOutListener onChapterBoundariesOutListener;
-    private OnChapterSingleTapListener onChapterSingleTapListener;
 
     private static final float SWIPE_TOLERANCE = 0.25f;
     private float startDragX;
 
     public HorizontalPager(Context context) {
         super(context);
-        init(context);
-    }
-
-    public HorizontalPager(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        init(context);
-    }
-
-    private void init(Context context) {
-        gestureDetector = new GestureDetector(context, new PagerGestureListener(this));
     }
 
     @Override
@@ -86,31 +69,11 @@ public class HorizontalPager extends ViewPager implements Pager {
         }
     }
 
-    @Override
-    public boolean onImageTouch(MotionEvent event) {
-        return gestureDetector.onTouchEvent(event);
-    }
-
     @Override
     public void setOnChapterBoundariesOutListener(OnChapterBoundariesOutListener listener) {
         onChapterBoundariesOutListener = listener;
     }
 
-    @Override
-    public void setOnChapterSingleTapListener(OnChapterSingleTapListener listener) {
-        onChapterSingleTapListener = listener;
-    }
-
-    @Override
-    public OnChapterBoundariesOutListener getChapterBoundariesListener() {
-        return onChapterBoundariesOutListener;
-    }
-
-    @Override
-    public OnChapterSingleTapListener getChapterSingleTapListener() {
-        return onChapterSingleTapListener;
-    }
-
     @Override
     public void setOnPageChangeListener(Action1<Integer> function) {
         addOnPageChangeListener(new SimpleOnPageChangeListener() {

+ 0 - 19
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalReader.java

@@ -1,19 +0,0 @@
-package eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader;
-
-public abstract class HorizontalReader extends PagerReader {
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
-        HorizontalPager pager = new HorizontalPager(getActivity());
-        initializePager(pager);
-        return pager;
-    }
-
-}

+ 11 - 7
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/LeftToRightReader.java

@@ -1,15 +1,19 @@
 package eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal;
 
-public class LeftToRightReader extends HorizontalReader {
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
 
-    @Override
-    public void onFirstPageOut() {
-        getReaderActivity().requestPreviousChapter();
-    }
+import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader;
+
+public class LeftToRightReader extends PagerReader {
 
     @Override
-    public void onLastPageOut() {
-        getReaderActivity().requestNextChapter();
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+        HorizontalPager pager = new HorizontalPager(getActivity());
+        initializePager(pager);
+        return pager;
     }
 
 }

+ 15 - 23
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/RightToLeftReader.java

@@ -1,38 +1,30 @@
 package eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
 
-import eu.kanade.tachiyomi.data.source.model.Page;
+import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader;
 
-public class RightToLeftReader extends HorizontalReader {
+public class RightToLeftReader extends PagerReader {
 
     @Override
-    public void onPageListReady(List<Page> pages, int currentPage) {
-        ArrayList<Page> inversedPages = new ArrayList<>(pages);
-        Collections.reverse(inversedPages);
-        super.onPageListReady(inversedPages, currentPage);
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+        HorizontalPager pager = new HorizontalPager(getActivity());
+        pager.setRotation(180);
+        initializePager(pager);
+        return pager;
     }
 
     @Override
-    public int getPageForPosition(int position) {
-        return (getTotalPages() - 1) - position;
+    protected void onLeftSideTap() {
+        super.onRightSideTap();
     }
 
     @Override
-    public int getPositionForPage(int page) {
-        return (getTotalPages() - 1) - page;
-    }
-
-    @Override
-    public void onFirstPageOut() {
-        getReaderActivity().requestNextChapter();
-    }
-
-    @Override
-    public void onLastPageOut() {
-        getReaderActivity().requestPreviousChapter();
+    protected void onRightSideTap() {
+        super.onLeftSideTap();
     }
 
 }

+ 1 - 38
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalPager.java

@@ -1,38 +1,21 @@
 package eu.kanade.tachiyomi.ui.reader.viewer.pager.vertical;
 
 import android.content.Context;
-import android.util.AttributeSet;
-import android.view.GestureDetector;
 import android.view.MotionEvent;
 
-import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterBoundariesOutListener;
-import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterSingleTapListener;
+import eu.kanade.tachiyomi.ui.reader.viewer.pager.OnChapterBoundariesOutListener;
 import eu.kanade.tachiyomi.ui.reader.viewer.pager.Pager;
-import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerGestureListener;
 import rx.functions.Action1;
 
 public class VerticalPager extends VerticalViewPagerImpl implements Pager {
 
-    private GestureDetector gestureDetector;
-
     private OnChapterBoundariesOutListener onChapterBoundariesOutListener;
-    private OnChapterSingleTapListener onChapterSingleTapListener;
 
     private static final float SWIPE_TOLERANCE = 0.25f;
     private float startDragY;
 
     public VerticalPager(Context context) {
         super(context);
-        init(context);
-    }
-
-    public VerticalPager(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        init(context);
-    }
-
-    private void init(Context context) {
-        gestureDetector = new GestureDetector(context, new PagerGestureListener(this));
     }
 
     @Override
@@ -85,31 +68,11 @@ public class VerticalPager extends VerticalViewPagerImpl implements Pager {
         }
     }
 
-    @Override
-    public boolean onImageTouch(MotionEvent event) {
-        return gestureDetector.onTouchEvent(event);
-    }
-
     @Override
     public void setOnChapterBoundariesOutListener(OnChapterBoundariesOutListener listener) {
         onChapterBoundariesOutListener = listener;
     }
 
-    @Override
-    public void setOnChapterSingleTapListener(OnChapterSingleTapListener listener) {
-        onChapterSingleTapListener = listener;
-    }
-
-    @Override
-    public OnChapterBoundariesOutListener getChapterBoundariesListener() {
-        return onChapterBoundariesOutListener;
-    }
-
-    @Override
-    public OnChapterSingleTapListener getChapterSingleTapListener() {
-        return onChapterSingleTapListener;
-    }
-
     @Override
     public void setOnPageChangeListener(Action1<Integer> function) {
         addOnPageChangeListener(new SimpleOnPageChangeListener() {

+ 0 - 10
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalReader.java

@@ -16,14 +16,4 @@ public class VerticalReader extends PagerReader {
         return pager;
     }
 
-    @Override
-    public void onFirstPageOut() {
-        getReaderActivity().requestPreviousChapter();
-    }
-
-    @Override
-    public void onLastPageOut() {
-        getReaderActivity().requestNextChapter();
-    }
-
 }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.java

@@ -21,7 +21,7 @@ public class WebtoonAdapter extends RecyclerView.Adapter<WebtoonHolder> {
     public WebtoonAdapter(WebtoonReader fragment) {
         this.fragment = fragment;
         pages = new ArrayList<>();
-        touchListener = (v, event) -> fragment.onImageTouch(event);
+        touchListener = (v, event) -> fragment.gestureDetector.onTouchEvent(event);
     }
 
     public Page getItem(int position) {

+ 33 - 26
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.java

@@ -8,8 +8,9 @@ import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 
-import java.util.List;
+import java.util.ArrayList;
 
+import eu.kanade.tachiyomi.data.database.models.Chapter;
 import eu.kanade.tachiyomi.data.source.model.Page;
 import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader;
 import eu.kanade.tachiyomi.widget.PreCachingLayoutManager;
@@ -27,12 +28,11 @@ public class WebtoonReader extends BaseReader {
     private PreCachingLayoutManager layoutManager;
     private Subscription subscription;
     private Subscription decoderSubscription;
-    private GestureDetector gestureDetector;
+    protected GestureDetector gestureDetector;
 
-    private boolean isReady;
     private int scrollDistance;
 
-    private static final String SCROLL_STATE = "scroll_state";
+    private static final String SAVED_POSITION = "saved_position";
 
     private static final float LEFT_REGION = 0.33f;
     private static final float RIGHT_REGION = 0.66f;
@@ -47,7 +47,7 @@ public class WebtoonReader extends BaseReader {
         layoutManager = new PreCachingLayoutManager(getActivity());
         layoutManager.setExtraLayoutSpace(screenHeight / 2);
         if (savedState != null) {
-            layoutManager.onRestoreInstanceState(savedState.getParcelable(SCROLL_STATE));
+            layoutManager.scrollToPositionWithOffset(savedState.getInt(SAVED_POSITION), 0);
         }
 
         recycler = new RecyclerView(getActivity());
@@ -80,8 +80,6 @@ public class WebtoonReader extends BaseReader {
         });
 
         setPages();
-        isReady = true;
-
         return recycler;
     }
 
@@ -100,7 +98,9 @@ public class WebtoonReader extends BaseReader {
     @Override
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
-        outState.putParcelable(SCROLL_STATE, layoutManager.onSaveInstanceState());
+        int savedPosition = pages != null ?
+                pages.get(layoutManager.findFirstVisibleItemPosition()).getPageNumber() : 0;
+        outState.putInt(SAVED_POSITION, savedPosition);
     }
 
     private void unsubscribeStatus() {
@@ -110,18 +110,30 @@ public class WebtoonReader extends BaseReader {
 
     @Override
     public void setSelectedPage(int pageNumber) {
-        recycler.scrollToPosition(getPositionForPage(pageNumber));
+        recycler.scrollToPosition(pageNumber);
     }
 
     @Override
-    public void onPageListReady(List<Page> pages, int currentPage) {
-        if (this.pages != pages) {
-            this.pages = pages;
-            // Restoring current page is not supported. It's getting weird scrolling jumps
-            // this.currentPage = currentPage;
-            if (isReady) {
-                setPages();
-            }
+    public void onSetChapter(Chapter chapter, Page currentPage) {
+        pages = new ArrayList<>(chapter.getPages());
+        // Restoring current page is not supported. It's getting weird scrolling jumps
+        // this.currentPage = currentPage;
+
+        // This method can be called before the view is created
+        if (recycler != null) {
+            setPages();
+        }
+    }
+
+    @Override
+    public void onAppendChapter(Chapter chapter) {
+        int insertStart = pages.size();
+        pages.addAll(chapter.getPages());
+
+        // This method can be called before the view is created
+        if (recycler != null) {
+            adapter.setPages(pages);
+            adapter.notifyItemRangeInserted(insertStart, chapter.getPages().size());
         }
     }
 
@@ -141,19 +153,14 @@ public class WebtoonReader extends BaseReader {
         recycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
             @Override
             public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
-                super.onScrolled(recyclerView, dx, dy);
-
-                currentPage = layoutManager.findLastVisibleItemPosition();
-                updatePageNumber();
+                int page = layoutManager.findLastVisibleItemPosition();
+                if (page != currentPage) {
+                    onPageChanged(page);
+                }
             }
         });
     }
 
-    @Override
-    public boolean onImageTouch(MotionEvent motionEvent) {
-        return gestureDetector.onTouchEvent(motionEvent);
-    }
-
     private void observeStatus(int position) {
         if (position == pages.size())
             return;

+ 1 - 0
app/src/main/res/values/keys.xml

@@ -27,6 +27,7 @@
     <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_download_directory_key">pref_download_directory_key</string>
     <string name="pref_download_slots_key">pref_download_slots_key</string>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -84,6 +84,7 @@
     <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">Enable seamless chapter transitions</string>
     <string name="pref_keep_screen_on">Keep screen on</string>
     <string name="pref_reader_theme">Background color</string>
     <string name="white_theme">White</string>

+ 4 - 0
app/src/main/res/xml/pref_reader.xml

@@ -21,6 +21,10 @@
         android:key="@string/pref_custom_brightness_key"
         android:defaultValue="false" />
 
+    <SwitchPreference android:title="@string/pref_seamless_mode"
+        android:key="@string/pref_seamless_mode_key"
+        android:defaultValue="true" />
+
     <eu.kanade.tachiyomi.widget.preference.IntListPreference
         android:title="@string/pref_viewer_type"
         android:key="@string/pref_default_viewer_key"