Fix an issue where the retry button wasn't doing anything. Preload the first pages of the next chapter (if available). Show a toast if no next/previous chapter is available. Other minor changes.
This commit is contained in:
parent
9db81b1832
commit
30b907bdf2
11 changed files with 475 additions and 68 deletions
|
@ -80,8 +80,8 @@ public abstract class BaseSource {
|
|||
return defaultPageUrl;
|
||||
}
|
||||
|
||||
// Get the URL of the remaining pages that contains source images
|
||||
protected String overrideRemainingPagesUrl(String defaultPageUrl) {
|
||||
// Get the URL of the pages that contains source images
|
||||
protected String overridePageUrl(String defaultPageUrl) {
|
||||
return defaultPageUrl;
|
||||
}
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ public abstract class Source extends BaseSource {
|
|||
public Observable<Page> getImageUrlFromPage(final Page page) {
|
||||
page.setStatus(Page.LOAD_PAGE);
|
||||
return mNetworkService
|
||||
.getStringResponse(overrideRemainingPagesUrl(page.getUrl()), mRequestHeaders, null)
|
||||
.getStringResponse(overridePageUrl(page.getUrl()), mRequestHeaders, null)
|
||||
.flatMap(unparsedHtml -> Observable.just(parseHtmlToImageUrl(unparsedHtml)))
|
||||
.onErrorResumeNext(e -> {
|
||||
page.setStatus(Page.ERROR);
|
||||
|
|
|
@ -135,7 +135,7 @@ public class Batoto extends Source {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected String overrideRemainingPagesUrl(String defaultPageUrl) {
|
||||
protected String overridePageUrl(String defaultPageUrl) {
|
||||
int start = defaultPageUrl.indexOf("#") + 1;
|
||||
int end = defaultPageUrl.indexOf("_", start);
|
||||
String id = defaultPageUrl.substring(start, end);
|
||||
|
|
|
@ -6,7 +6,6 @@ import android.support.annotation.NonNull;
|
|||
|
||||
import de.greenrobot.event.EventBus;
|
||||
import icepick.Icepick;
|
||||
import nucleus.presenter.RxPresenter;
|
||||
import nucleus.view.ViewWithPresenter;
|
||||
|
||||
public class BasePresenter<V extends ViewWithPresenter> extends RxPresenter<V> {
|
||||
|
|
|
@ -0,0 +1,332 @@
|
|||
package eu.kanade.mangafeed.ui.base.presenter;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import nucleus.presenter.Presenter;
|
||||
import nucleus.presenter.delivery.DeliverFirst;
|
||||
import nucleus.presenter.delivery.DeliverLatestCache;
|
||||
import nucleus.presenter.delivery.DeliverReplay;
|
||||
import nucleus.presenter.delivery.Delivery;
|
||||
import rx.Observable;
|
||||
import rx.Subscription;
|
||||
import rx.functions.Action1;
|
||||
import rx.functions.Action2;
|
||||
import rx.functions.Func0;
|
||||
import rx.internal.util.SubscriptionList;
|
||||
import rx.subjects.BehaviorSubject;
|
||||
|
||||
/**
|
||||
* This is an extension of {@link Presenter} which provides RxJava functionality.
|
||||
*
|
||||
* @param <View> a type of view.
|
||||
*/
|
||||
public class RxPresenter<View> extends Presenter<View> {
|
||||
|
||||
private static final String REQUESTED_KEY = RxPresenter.class.getName() + "#requested";
|
||||
|
||||
private final BehaviorSubject<View> views = BehaviorSubject.create();
|
||||
private final SubscriptionList subscriptions = new SubscriptionList();
|
||||
|
||||
private final HashMap<Integer, Func0<Subscription>> restartables = new HashMap<>();
|
||||
private final HashMap<Integer, Subscription> restartableSubscriptions = new HashMap<>();
|
||||
private final ArrayList<Integer> requested = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Returns an {@link rx.Observable} that emits the current attached view or null.
|
||||
* See {@link BehaviorSubject} for more information.
|
||||
*
|
||||
* @return an observable that emits the current attached view or null.
|
||||
*/
|
||||
public Observable<View> view() {
|
||||
return views;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a subscription to automatically unsubscribe it during onDestroy.
|
||||
* See {@link SubscriptionList#add(Subscription) for details.}
|
||||
*
|
||||
* @param subscription a subscription to add.
|
||||
*/
|
||||
public void add(Subscription subscription) {
|
||||
subscriptions.add(subscription);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes and unsubscribes a subscription that has been registered with {@link #add} previously.
|
||||
* See {@link SubscriptionList#remove(Subscription)} for details.
|
||||
*
|
||||
* @param subscription a subscription to remove.
|
||||
*/
|
||||
public void remove(Subscription subscription) {
|
||||
subscriptions.remove(subscription);
|
||||
}
|
||||
|
||||
/**
|
||||
* A restartable is any RxJava observable that can be started (subscribed) and
|
||||
* should be automatically restarted (re-subscribed) after a process restart if
|
||||
* it was still subscribed at the moment of saving presenter's state.
|
||||
*
|
||||
* Registers a factory. Re-subscribes the restartable after the process restart.
|
||||
*
|
||||
* @param restartableId id of the restartable
|
||||
* @param factory factory of the restartable
|
||||
*/
|
||||
public void restartable(int restartableId, Func0<Subscription> factory) {
|
||||
restartables.put(restartableId, factory);
|
||||
if (requested.contains(restartableId))
|
||||
start(restartableId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the given restartable.
|
||||
*
|
||||
* @param restartableId id of the restartable
|
||||
*/
|
||||
public void start(int restartableId) {
|
||||
stop(restartableId);
|
||||
requested.add(restartableId);
|
||||
restartableSubscriptions.put(restartableId, restartables.get(restartableId).call());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes a restartable
|
||||
*
|
||||
* @param restartableId id of a restartable.
|
||||
*/
|
||||
public void stop(int restartableId) {
|
||||
requested.remove((Integer)restartableId);
|
||||
Subscription subscription = restartableSubscriptions.get(restartableId);
|
||||
if (subscription != null)
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a restartable is started.
|
||||
*
|
||||
* @param restartableId id of a restartable.
|
||||
* @return True if the restartable is started, false otherwise.
|
||||
*/
|
||||
public boolean isStarted(int restartableId) {
|
||||
return requested.contains(restartableId);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a shortcut that can be used instead of combining together
|
||||
* {@link #restartable(int, Func0)},
|
||||
* {@link #deliverFirst()},
|
||||
* {@link #split(Action2, Action2)}.
|
||||
*
|
||||
* @param restartableId an id of the restartable.
|
||||
* @param observableFactory a factory that should return an Observable when the restartable should run.
|
||||
* @param onNext a callback that will be called when received data should be delivered to view.
|
||||
* @param onError a callback that will be called if the source observable emits onError.
|
||||
* @param <T> the type of the observable.
|
||||
*/
|
||||
public <T> void restartableFirst(int restartableId, final Func0<Observable<T>> observableFactory,
|
||||
final Action2<View, T> onNext, @Nullable final Action2<View, Throwable> onError) {
|
||||
|
||||
restartable(restartableId, new Func0<Subscription>() {
|
||||
@Override
|
||||
public Subscription call() {
|
||||
return observableFactory.call()
|
||||
.compose(RxPresenter.this.<T>deliverFirst())
|
||||
.subscribe(split(onNext, onError));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a shortcut for calling {@link #restartableFirst(int, Func0, Action2, Action2)} with the last parameter = null.
|
||||
*/
|
||||
public <T> void restartableFirst(int restartableId, final Func0<Observable<T>> observableFactory, final Action2<View, T> onNext) {
|
||||
restartableFirst(restartableId, observableFactory, onNext, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a shortcut that can be used instead of combining together
|
||||
* {@link #restartable(int, Func0)},
|
||||
* {@link #deliverLatestCache()},
|
||||
* {@link #split(Action2, Action2)}.
|
||||
*
|
||||
* @param restartableId an id of the restartable.
|
||||
* @param observableFactory a factory that should return an Observable when the restartable should run.
|
||||
* @param onNext a callback that will be called when received data should be delivered to view.
|
||||
* @param onError a callback that will be called if the source observable emits onError.
|
||||
* @param <T> the type of the observable.
|
||||
*/
|
||||
public <T> void restartableLatestCache(int restartableId, final Func0<Observable<T>> observableFactory,
|
||||
final Action2<View, T> onNext, @Nullable final Action2<View, Throwable> onError) {
|
||||
|
||||
restartable(restartableId, new Func0<Subscription>() {
|
||||
@Override
|
||||
public Subscription call() {
|
||||
return observableFactory.call()
|
||||
.compose(RxPresenter.this.<T>deliverLatestCache())
|
||||
.subscribe(split(onNext, onError));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a shortcut for calling {@link #restartableLatestCache(int, Func0, Action2, Action2)} with the last parameter = null.
|
||||
*/
|
||||
public <T> void restartableLatestCache(int restartableId, final Func0<Observable<T>> observableFactory, final Action2<View, T> onNext) {
|
||||
restartableLatestCache(restartableId, observableFactory, onNext, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a shortcut that can be used instead of combining together
|
||||
* {@link #restartable(int, Func0)},
|
||||
* {@link #deliverReplay()},
|
||||
* {@link #split(Action2, Action2)}.
|
||||
*
|
||||
* @param restartableId an id of the restartable.
|
||||
* @param observableFactory a factory that should return an Observable when the restartable should run.
|
||||
* @param onNext a callback that will be called when received data should be delivered to view.
|
||||
* @param onError a callback that will be called if the source observable emits onError.
|
||||
* @param <T> the type of the observable.
|
||||
*/
|
||||
public <T> void restartableReplay(int restartableId, final Func0<Observable<T>> observableFactory,
|
||||
final Action2<View, T> onNext, @Nullable final Action2<View, Throwable> onError) {
|
||||
|
||||
restartable(restartableId, new Func0<Subscription>() {
|
||||
@Override
|
||||
public Subscription call() {
|
||||
return observableFactory.call()
|
||||
.compose(RxPresenter.this.<T>deliverReplay())
|
||||
.subscribe(split(onNext, onError));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a shortcut for calling {@link #restartableReplay(int, Func0, Action2, Action2)} with the last parameter = null.
|
||||
*/
|
||||
public <T> void restartableReplay(int restartableId, final Func0<Observable<T>> observableFactory, final Action2<View, T> onNext) {
|
||||
restartableReplay(restartableId, observableFactory, onNext, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link rx.Observable.Transformer} that couples views with data that has been emitted by
|
||||
* the source {@link rx.Observable}.
|
||||
*
|
||||
* {@link #deliverLatestCache} keeps the latest onNext value and emits it each time a new view gets attached.
|
||||
* If a new onNext value appears while a view is attached, it will be delivered immediately.
|
||||
*
|
||||
* @param <T> the type of source observable emissions
|
||||
*/
|
||||
public <T> DeliverLatestCache<View, T> deliverLatestCache() {
|
||||
return new DeliverLatestCache<>(views);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link rx.Observable.Transformer} that couples views with data that has been emitted by
|
||||
* the source {@link rx.Observable}.
|
||||
*
|
||||
* {@link #deliverFirst} delivers only the first onNext value that has been emitted by the source observable.
|
||||
*
|
||||
* @param <T> the type of source observable emissions
|
||||
*/
|
||||
public <T> DeliverFirst<View, T> deliverFirst() {
|
||||
return new DeliverFirst<>(views);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link rx.Observable.Transformer} that couples views with data that has been emitted by
|
||||
* the source {@link rx.Observable}.
|
||||
*
|
||||
* {@link #deliverReplay} keeps all onNext values and emits them each time a new view gets attached.
|
||||
* If a new onNext value appears while a view is attached, it will be delivered immediately.
|
||||
*
|
||||
* @param <T> the type of source observable emissions
|
||||
*/
|
||||
public <T> DeliverReplay<View, T> deliverReplay() {
|
||||
return new DeliverReplay<>(views);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a method that can be used for manual restartable chain build. It returns an Action1 that splits
|
||||
* a received {@link Delivery} into two {@link Action2} onNext and onError calls.
|
||||
*
|
||||
* @param onNext a method that will be called if the delivery contains an emitted onNext value.
|
||||
* @param onError a method that will be called if the delivery contains an onError throwable.
|
||||
* @param <T> a type on onNext value.
|
||||
* @return an Action1 that splits a received {@link Delivery} into two {@link Action2} onNext and onError calls.
|
||||
*/
|
||||
public <T> Action1<Delivery<View, T>> split(final Action2<View, T> onNext, @Nullable final Action2<View, Throwable> onError) {
|
||||
return new Action1<Delivery<View, T>>() {
|
||||
@Override
|
||||
public void call(Delivery<View, T> delivery) {
|
||||
delivery.split(onNext, onError);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a shortcut for calling {@link #split(Action2, Action2)} when the second parameter is null.
|
||||
*/
|
||||
public <T> Action1<Delivery<View, T>> split(Action2<View, T> onNext) {
|
||||
return split(onNext, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@CallSuper
|
||||
@Override
|
||||
protected void onCreate(Bundle savedState) {
|
||||
if (savedState != null)
|
||||
requested.addAll(savedState.getIntegerArrayList(REQUESTED_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@CallSuper
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
views.onCompleted();
|
||||
subscriptions.unsubscribe();
|
||||
for (Map.Entry<Integer, Subscription> entry : restartableSubscriptions.entrySet())
|
||||
entry.getValue().unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@CallSuper
|
||||
@Override
|
||||
protected void onSave(Bundle state) {
|
||||
for (int i = requested.size() - 1; i >= 0; i--) {
|
||||
int restartableId = requested.get(i);
|
||||
Subscription subscription = restartableSubscriptions.get(restartableId);
|
||||
if (subscription != null && subscription.isUnsubscribed())
|
||||
requested.remove(i);
|
||||
}
|
||||
state.putIntegerArrayList(REQUESTED_KEY, requested);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@CallSuper
|
||||
@Override
|
||||
protected void onTakeView(View view) {
|
||||
views.onNext(view);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@CallSuper
|
||||
@Override
|
||||
protected void onDropView() {
|
||||
views.onNext(null);
|
||||
}
|
||||
}
|
|
@ -84,6 +84,7 @@ public class MyAnimeListPresenter extends BasePresenter<MyAnimeListFragment> {
|
|||
chapterSync.last_chapter_read = chapterNumber;
|
||||
|
||||
add(updateSubscription = myAnimeList.update(chapterSync)
|
||||
.flatMap(response -> db.insertChapterSync(chapterSync).createObservable())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(response -> {},
|
||||
|
|
|
@ -45,6 +45,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||
private Chapter nextChapter;
|
||||
private Chapter previousChapter;
|
||||
private List<Page> pageList;
|
||||
private List<Page> nextChapterPageList;
|
||||
private boolean isDownloaded;
|
||||
@State int currentPage;
|
||||
|
||||
|
@ -56,6 +57,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||
private static final int GET_PAGE_LIST = 1;
|
||||
private static final int GET_PAGE_IMAGES = 2;
|
||||
private static final int RETRY_IMAGES = 3;
|
||||
private static final int PRELOAD_NEXT_CHAPTER = 4;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedState) {
|
||||
|
@ -81,7 +83,8 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||
});
|
||||
|
||||
restartableReplay(GET_PAGE_IMAGES,
|
||||
this::getPageImagesObservable,
|
||||
() -> getPageImagesObservable()
|
||||
.doOnCompleted(this::preloadNextChapter),
|
||||
(view, page) -> {},
|
||||
(view, error) -> Timber.e("An error occurred while downloading an image"));
|
||||
|
||||
|
@ -89,6 +92,11 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||
this::getRetryPageObservable,
|
||||
(view, page) -> {},
|
||||
(view, error) -> Timber.e("An error occurred while downloading an image"));
|
||||
|
||||
restartableLatestCache(PRELOAD_NEXT_CHAPTER,
|
||||
this::getPreloadNextChapterObservable,
|
||||
(view, pages) -> {},
|
||||
(view, error) -> Timber.e("An error occurred while preloading a chapter"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -105,7 +113,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
onChapterChange();
|
||||
onChapterLeft();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
|
@ -125,10 +133,73 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||
retryPageSubject.onNext(page);
|
||||
}
|
||||
|
||||
// Returns the page list of a chapter
|
||||
private Observable<List<Page>> getPageListObservable() {
|
||||
return 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 = Observable.from(pageList)
|
||||
.filter(page -> page.getImageUrl() != null)
|
||||
.mergeWith(source.getRemainingImageUrlsFromPageList(pageList))
|
||||
.flatMap(source::getCachedImage, 3);
|
||||
} else {
|
||||
File chapterDir = downloadManager.getAbsoluteChapterDirectory(source, manga, chapter);
|
||||
pageObservable = Observable.from(pageList)
|
||||
.flatMap(page -> downloadManager.getDownloadedImage(page, source, chapterDir));
|
||||
}
|
||||
return pageObservable
|
||||
.subscribeOn(Schedulers.io())
|
||||
.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)
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
// Preload the first pages of the next chapter
|
||||
private Observable<Page> getPreloadNextChapterObservable() {
|
||||
return source.getCachedPageListOrPullFromNetwork(nextChapter.url)
|
||||
.flatMap(pages -> {
|
||||
nextChapterPageList = pages;
|
||||
// Preload at most 5 pages
|
||||
int pagesToPreload = Math.min(pages.size(), 5);
|
||||
return Observable.from(pages)
|
||||
.take(pagesToPreload)
|
||||
.concatMap(source::getImageUrlFromPage)
|
||||
.doOnCompleted(this::stopPreloadingNextChapter);
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
// Loads the given chapter
|
||||
private void loadChapter(Chapter chapter) {
|
||||
// Before loading the chapter, stop preloading (if it's working) and save current progress
|
||||
stopPreloadingNextChapter();
|
||||
|
||||
this.chapter = chapter;
|
||||
isDownloaded = isChapterDownloaded(chapter);
|
||||
if (chapter.last_page_read != 0 && !chapter.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)
|
||||
currentPage = chapter.last_page_read;
|
||||
else
|
||||
currentPage = 0;
|
||||
|
@ -136,11 +207,22 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||
// Reset next and previous chapter. They have to be fetched again
|
||||
nextChapter = null;
|
||||
previousChapter = null;
|
||||
nextChapterPageList = null;
|
||||
|
||||
start(GET_PAGE_LIST);
|
||||
}
|
||||
|
||||
private void onChapterChange() {
|
||||
// Check whether the given chapter is downloaded
|
||||
public boolean isChapterDownloaded(Chapter chapter) {
|
||||
File dir = downloadManager.getAbsoluteChapterDirectory(source, manga, chapter);
|
||||
List<Page> pageList = downloadManager.getSavedPageList(source, manga, chapter);
|
||||
|
||||
return pageList != null && pageList.size() + 1 == dir.listFiles().length;
|
||||
}
|
||||
|
||||
// Called before loading another chapter or leaving the reader. It allows to do operations
|
||||
// over the chapter read like saving progress
|
||||
private void onChapterLeft() {
|
||||
if (pageList == null)
|
||||
return;
|
||||
|
||||
|
@ -158,6 +240,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||
db.insertChapter(chapter).executeAsBlocking();
|
||||
}
|
||||
|
||||
// Check whether the chapter has been read
|
||||
private boolean isChapterFinished() {
|
||||
return !chapter.read && currentPage == pageList.size() - 1;
|
||||
}
|
||||
|
@ -185,46 +268,6 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||
}
|
||||
}
|
||||
|
||||
private Observable<List<Page>> getPageListObservable() {
|
||||
if (!isDownloaded)
|
||||
return source.getCachedPageListOrPullFromNetwork(chapter.url)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
else
|
||||
return Observable.just(downloadManager.getSavedPageList(source, manga, chapter));
|
||||
}
|
||||
|
||||
private Observable<Page> getPageImagesObservable() {
|
||||
Observable<Page> pages;
|
||||
|
||||
if (!isDownloaded) {
|
||||
pages = Observable.from(pageList)
|
||||
.filter(page -> page.getImageUrl() != null)
|
||||
.mergeWith(source.getRemainingImageUrlsFromPageList(pageList))
|
||||
.flatMap(source::getCachedImage, 3);
|
||||
} else {
|
||||
File chapterDir = downloadManager.getAbsoluteChapterDirectory(source, manga, chapter);
|
||||
|
||||
pages = Observable.from(pageList)
|
||||
.flatMap(page -> downloadManager.getDownloadedImage(page, source, chapterDir));
|
||||
}
|
||||
return pages
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
private Observable<Page> getRetryPageObservable() {
|
||||
return retryPageSubject
|
||||
.flatMap(page -> {
|
||||
if (page.getImageUrl() == null)
|
||||
return source.getImageUrlFromPage(page);
|
||||
return Observable.just(page);
|
||||
})
|
||||
.flatMap(source::getCachedImage)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
public void setCurrentPage(int currentPage) {
|
||||
this.currentPage = currentPage;
|
||||
}
|
||||
|
@ -247,33 +290,49 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||
.subscribe(result -> previousChapter = result));
|
||||
}
|
||||
|
||||
public boolean isChapterDownloaded(Chapter chapter) {
|
||||
File dir = downloadManager.getAbsoluteChapterDirectory(source, manga, chapter);
|
||||
List<Page> pageList = downloadManager.getSavedPageList(source, manga, chapter);
|
||||
|
||||
return pageList != null && pageList.size() + 1 == dir.listFiles().length;
|
||||
}
|
||||
|
||||
public void loadNextChapter() {
|
||||
if (nextChapter != null) {
|
||||
onChapterChange();
|
||||
if (hasNextChapter()) {
|
||||
onChapterLeft();
|
||||
loadChapter(nextChapter);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadPreviousChapter() {
|
||||
if (previousChapter != null) {
|
||||
onChapterChange();
|
||||
if (hasPreviousChapter()) {
|
||||
onChapterLeft();
|
||||
loadChapter(previousChapter);
|
||||
}
|
||||
}
|
||||
|
||||
public Manga getManga() {
|
||||
return manga;
|
||||
public boolean hasNextChapter() {
|
||||
return nextChapter != null;
|
||||
}
|
||||
|
||||
public boolean hasPreviousChapter() {
|
||||
return previousChapter != null;
|
||||
}
|
||||
|
||||
private void preloadNextChapter() {
|
||||
if (hasNextChapter() && !isChapterDownloaded(nextChapter)) {
|
||||
start(PRELOAD_NEXT_CHAPTER);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopPreloadingNextChapter() {
|
||||
if (isStarted(PRELOAD_NEXT_CHAPTER)) {
|
||||
stop(PRELOAD_NEXT_CHAPTER);
|
||||
if (nextChapterPageList != null)
|
||||
source.savePageList(nextChapter.url, nextChapterPageList);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateMangaViewer(int viewer) {
|
||||
manga.viewer = viewer;
|
||||
db.insertManga(manga).executeAsBlocking();
|
||||
}
|
||||
|
||||
public Manga getManga() {
|
||||
return manga;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,18 +5,23 @@ import android.view.ViewGroup;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import eu.kanade.mangafeed.R;
|
||||
import eu.kanade.mangafeed.data.source.model.Page;
|
||||
import eu.kanade.mangafeed.ui.reader.ReaderActivity;
|
||||
import eu.kanade.mangafeed.ui.reader.ReaderPresenter;
|
||||
import eu.kanade.mangafeed.util.ToastUtil;
|
||||
|
||||
public abstract class BaseReader {
|
||||
|
||||
protected ReaderActivity activity;
|
||||
protected ReaderPresenter presenter;
|
||||
protected ViewGroup container;
|
||||
protected int currentPosition;
|
||||
|
||||
public BaseReader(ReaderActivity activity) {
|
||||
this.activity = activity;
|
||||
this.container = activity.getContainer();
|
||||
this.presenter = activity.getPresenter();
|
||||
}
|
||||
|
||||
public void updatePageNumber() {
|
||||
|
@ -34,13 +39,22 @@ public abstract class BaseReader {
|
|||
}
|
||||
|
||||
public void requestNextChapter() {
|
||||
activity.getPresenter().setCurrentPage(getCurrentPosition());
|
||||
activity.getPresenter().loadNextChapter();
|
||||
if (presenter.hasNextChapter()) {
|
||||
presenter.setCurrentPage(getCurrentPosition());
|
||||
presenter.loadNextChapter();
|
||||
} else {
|
||||
ToastUtil.showShort(activity, R.string.no_next_chapter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void requestPreviousChapter() {
|
||||
activity.getPresenter().setCurrentPage(getCurrentPosition());
|
||||
activity.getPresenter().loadPreviousChapter();
|
||||
if (presenter.hasPreviousChapter()) {
|
||||
presenter.setCurrentPage(getCurrentPosition());
|
||||
presenter.loadPreviousChapter();
|
||||
} else {
|
||||
ToastUtil.showShort(activity, R.string.no_previous_chapter);
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {}
|
||||
|
|
|
@ -30,7 +30,7 @@ public abstract class HorizontalReader extends BaseReader {
|
|||
transitionsSubscription = activity.getPreferences().enableTransitions().asObservable()
|
||||
.subscribe(value -> transitions = value);
|
||||
|
||||
viewPager.setOffscreenPageLimit(3);
|
||||
viewPager.setOffscreenPageLimit(2);
|
||||
viewPager.addOnPageChangeListener(new HorizontalViewPager.SimpleOnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
|
|
|
@ -31,7 +31,7 @@ public class VerticalReader extends BaseReader {
|
|||
transitionsSubscription = activity.getPreferences().enableTransitions().asObservable()
|
||||
.subscribe(value -> transitions = value);
|
||||
|
||||
viewPager.setOffscreenPageLimit(3);
|
||||
viewPager.setOffscreenPageLimit(2);
|
||||
viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
|
|
|
@ -92,6 +92,8 @@
|
|||
<string name="chapter_progress">Page: %1$d</string>
|
||||
<string name="page_list_error">Error fetching page list. Is network available?</string>
|
||||
<string name="chapter_subtitle">Chapter %1$s</string>
|
||||
<string name="no_next_chapter">Next chapter not found</string>
|
||||
<string name="no_previous_chapter">Previous chapter not found</string>
|
||||
|
||||
<!-- Library update service notifications -->
|
||||
<string name="notification_progress">Update progress: %1$d/%2$d</string>
|
||||
|
|
Reference in a new issue