Browse Source

seamless chapter transitions

Robin Appelman 10 năm trước cách đây
mục cha
commit
2566862e0f
25 tập tin đã thay đổi với 455 bổ sung và 420 xóa
  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"