Merge pull request #112 from icewind1991/seamless-chapters

Seamless chapter transition
This commit is contained in:
inorichi 2016-02-08 22:25:56 +01:00
commit ea130a0899
25 changed files with 463 additions and 428 deletions

View File

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

View File

@ -128,6 +128,10 @@ public class PreferencesHelper {
return rxPrefs.getInteger(getKey(R.string.pref_last_catalogue_source_key), -1); 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() { public Preference<Boolean> catalogueAsList() {
return rxPrefs.getBoolean(getKey(R.string.pref_display_catalogue_as_list), false); return rxPrefs.getBoolean(getKey(R.string.pref_display_catalogue_as_list), false);
} }

View File

@ -1,5 +1,9 @@
package eu.kanade.tachiyomi.data.source.model; 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 eu.kanade.tachiyomi.data.network.ProgressListener;
import rx.subjects.PublishSubject; import rx.subjects.PublishSubject;
@ -8,6 +12,7 @@ public class Page implements ProgressListener {
private int pageNumber; private int pageNumber;
private String url; private String url;
private String imageUrl; private String imageUrl;
private transient Chapter chapter;
private transient String imagePath; private transient String imagePath;
private transient volatile int status; private transient volatile int status;
private transient volatile int progress; private transient volatile int progress;
@ -82,4 +87,16 @@ public class Page implements ProgressListener {
this.statusSubject = subject; 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;
}
} }

View File

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

View File

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

View File

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

View File

@ -1,15 +1,15 @@
package eu.kanade.tachiyomi.ui.reader.viewer.base; 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.ImageDecoder;
import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder; import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder;
import com.davemorrissey.labs.subscaleview.decoder.RapidImageRegionDecoder; import com.davemorrissey.labs.subscaleview.decoder.RapidImageRegionDecoder;
import com.davemorrissey.labs.subscaleview.decoder.SkiaImageDecoder; import com.davemorrissey.labs.subscaleview.decoder.SkiaImageDecoder;
import com.davemorrissey.labs.subscaleview.decoder.SkiaImageRegionDecoder; import com.davemorrissey.labs.subscaleview.decoder.SkiaImageRegionDecoder;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import eu.kanade.tachiyomi.data.database.models.Chapter;
import eu.kanade.tachiyomi.data.source.model.Page; import eu.kanade.tachiyomi.data.source.model.Page;
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment; import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment;
import eu.kanade.tachiyomi.ui.reader.ReaderActivity; import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
@ -18,40 +18,81 @@ public abstract class BaseReader extends BaseFragment {
protected int currentPage; protected int currentPage;
protected List<Page> pages; protected List<Page> pages;
protected List<Chapter> chapters;
protected Class<? extends ImageRegionDecoder> regionDecoderClass; protected Class<? extends ImageRegionDecoder> regionDecoderClass;
protected Class<? extends ImageDecoder> bitmapDecoderClass; protected Class<? extends ImageDecoder> bitmapDecoderClass;
private boolean hasRequestedNextChapter;
public static final int RAPID_DECODER = 0; public static final int RAPID_DECODER = 0;
public static final int SKIA_DECODER = 1; public static final int SKIA_DECODER = 1;
public void updatePageNumber() { public void updatePageNumber() {
getReaderActivity().onPageChanged(getCurrentPage(), getTotalPages()); getReaderActivity().onPageChanged(getCurrentPage().getPageNumber(), getCurrentPage().getChapter().getPages().size());
} }
public int getCurrentPage() { public Page getCurrentPage() {
return currentPage; return pages.get(currentPage);
}
public int getPageForPosition(int position) {
return position;
}
public int getPositionForPage(int page) {
return page;
} }
public void onPageChanged(int position) { public void onPageChanged(int position) {
currentPage = getPageForPosition(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(); updatePageNumber();
} }
public int getTotalPages() { private void onChapterChanged(Chapter chapter, Page currentPage) {
return pages == null ? 0 : pages.size(); getReaderActivity().onEnterChapter(chapter, currentPage.getPageNumber());
}
public void setSelectedPage(Page page) {
setSelectedPage(getPageIndex(page));
}
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 setSelectedPage(int pageNumber);
public abstract void onPageListReady(List<Page> pages, int currentPage); public abstract void onSetChapter(Chapter chapter, Page currentPage);
public abstract boolean onImageTouch(MotionEvent motionEvent); public abstract void onAppendChapter(Chapter chapter);
public void setDecoderClass(int value) { public void setDecoderClass(int value) {
switch (value) { switch (value) {

View File

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

View File

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

View File

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

View File

@ -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();
}
}
}

View File

@ -1,16 +1,16 @@
package eu.kanade.tachiyomi.ui.reader.viewer.pager; package eu.kanade.tachiyomi.ui.reader.viewer.pager;
import android.view.GestureDetector;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.ViewGroup; import android.view.ViewGroup;
import java.util.List; import java.util.ArrayList;
import eu.kanade.tachiyomi.R; 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.preference.PreferencesHelper;
import eu.kanade.tachiyomi.data.source.model.Page; 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.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.LeftToRightReader;
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader; import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader;
import rx.subscriptions.CompositeSubscription; import rx.subscriptions.CompositeSubscription;
@ -21,8 +21,8 @@ public abstract class PagerReader extends BaseReader {
protected PagerReaderAdapter adapter; protected PagerReaderAdapter adapter;
protected Pager pager; protected Pager pager;
protected GestureDetector gestureDetector;
private boolean isReady;
protected boolean transitions; protected boolean transitions;
protected CompositeSubscription subscriptions; 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_RIGHT = 3;
public static final int ALIGN_CENTER = 4; 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) { protected void initializePager(Pager pager) {
this.pager = pager; this.pager = pager;
pager.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); pager.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
@ -42,30 +45,15 @@ public abstract class PagerReader extends BaseReader {
pager.setOnChapterBoundariesOutListener(new OnChapterBoundariesOutListener() { pager.setOnChapterBoundariesOutListener(new OnChapterBoundariesOutListener() {
@Override @Override
public void onFirstPageOutEvent() { public void onFirstPageOutEvent() {
onFirstPageOut(); getReaderActivity().requestPreviousChapter();
} }
@Override @Override
public void onLastPageOutEvent() { public void onLastPageOutEvent() {
onLastPageOut(); getReaderActivity().requestNextChapter();
}
});
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);
} }
}); });
gestureDetector = createGestureDetector();
adapter = new PagerReaderAdapter(getChildFragmentManager()); adapter = new PagerReaderAdapter(getChildFragmentManager());
pager.setAdapter(adapter); pager.setAdapter(adapter);
@ -77,28 +65,27 @@ public abstract class PagerReader extends BaseReader {
.doOnNext(this::setDecoderClass) .doOnNext(this::setDecoderClass)
.skip(1) .skip(1)
.distinctUntilChanged() .distinctUntilChanged()
.subscribe(v -> adapter.notifyDataSetChanged())); .subscribe(v -> pager.setAdapter(adapter)));
subscriptions.add(preferences.imageScaleType() subscriptions.add(preferences.imageScaleType()
.asObservable() .asObservable()
.doOnNext(this::setImageScaleType) .doOnNext(this::setImageScaleType)
.skip(1) .skip(1)
.distinctUntilChanged() .distinctUntilChanged()
.subscribe(v -> adapter.notifyDataSetChanged())); .subscribe(v -> pager.setAdapter(adapter)));
subscriptions.add(preferences.zoomStart() subscriptions.add(preferences.zoomStart()
.asObservable() .asObservable()
.doOnNext(this::setZoomStart) .doOnNext(this::setZoomStart)
.skip(1) .skip(1)
.distinctUntilChanged() .distinctUntilChanged()
.subscribe(v -> adapter.notifyDataSetChanged())); .subscribe(v -> pager.setAdapter(adapter)));
subscriptions.add(preferences.enableTransitions() subscriptions.add(preferences.enableTransitions()
.asObservable() .asObservable()
.subscribe(value -> transitions = value)); .subscribe(value -> transitions = value));
setPages(); setPages();
isReady = true;
} }
@Override @Override
@ -107,15 +94,42 @@ public abstract class PagerReader extends BaseReader {
super.onDestroyView(); super.onDestroyView();
} }
protected GestureDetector createGestureDetector() {
return new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override @Override
public void onPageListReady(List<Page> pages, int currentPage) { public boolean onSingleTapConfirmed(MotionEvent e) {
if (this.pages != pages) { final float positionX = e.getX();
this.pages = pages;
this.currentPage = currentPage; if (positionX < pager.getWidth() * LEFT_REGION) {
if (isReady) { 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(); 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);
}
} }
protected void setPages() { protected void setPages() {
@ -130,12 +144,23 @@ public abstract class PagerReader extends BaseReader {
@Override @Override
public void setSelectedPage(int pageNumber) { public void setSelectedPage(int pageNumber) {
pager.setCurrentItem(getPositionForPage(pageNumber), false); pager.setCurrentItem(pageNumber, false);
} }
@Override protected void onLeftSideTap() {
public boolean onImageTouch(MotionEvent motionEvent) { if (pager.getCurrentItem() != 0) {
return pager.onImageTouch(motionEvent); 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) { private void setImageScaleType(int scaleType) {
@ -155,7 +180,4 @@ public abstract class PagerReader extends BaseReader {
} }
} }
public abstract void onFirstPageOut();
public abstract void onLastPageOut();
} }

View File

@ -31,14 +31,10 @@ public class PagerReaderAdapter extends FragmentStatePagerAdapter {
public Object instantiateItem(ViewGroup container, int position) { public Object instantiateItem(ViewGroup container, int position) {
PagerReaderFragment f = (PagerReaderFragment) super.instantiateItem(container, position); PagerReaderFragment f = (PagerReaderFragment) super.instantiateItem(container, position);
f.setPage(pages.get(position)); f.setPage(pages.get(position));
f.setPosition(position);
return f; return f;
} }
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
public List<Page> getPages() { public List<Page> getPages() {
return pages; return pages;
} }
@ -48,4 +44,17 @@ public class PagerReaderAdapter extends FragmentStatePagerAdapter {
notifyDataSetChanged(); 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);
}
} }

View File

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

View File

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

View File

@ -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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,6 +27,7 @@
<string name="pref_custom_brightness_value_key">pref_custom_brightness_value_key</string> <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_reader_theme_key">pref_reader_theme_key</string>
<string name="pref_image_decoder_key">pref_image_decoder_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_directory_key">pref_download_directory_key</string>
<string name="pref_download_slots_key">pref_download_slots_key</string> <string name="pref_download_slots_key">pref_download_slots_key</string>

View File

@ -84,6 +84,7 @@
<string name="pref_enable_transitions">Enable transitions</string> <string name="pref_enable_transitions">Enable transitions</string>
<string name="pref_show_page_number">Show page number</string> <string name="pref_show_page_number">Show page number</string>
<string name="pref_custom_brightness">Use custom brightness</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_keep_screen_on">Keep screen on</string>
<string name="pref_reader_theme">Background color</string> <string name="pref_reader_theme">Background color</string>
<string name="white_theme">White</string> <string name="white_theme">White</string>

View File

@ -21,6 +21,10 @@
android:key="@string/pref_custom_brightness_key" android:key="@string/pref_custom_brightness_key"
android:defaultValue="false" /> 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 <eu.kanade.tachiyomi.widget.preference.IntListPreference
android:title="@string/pref_viewer_type" android:title="@string/pref_viewer_type"
android:key="@string/pref_default_viewer_key" android:key="@string/pref_default_viewer_key"