mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-21 20:47:03 -05:00
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;
|
return defaultPageUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the URL of the remaining pages that contains source images
|
// Get the URL of the pages that contains source images
|
||||||
protected String overrideRemainingPagesUrl(String defaultPageUrl) {
|
protected String overridePageUrl(String defaultPageUrl) {
|
||||||
return defaultPageUrl;
|
return defaultPageUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@ public abstract class Source extends BaseSource {
|
||||||
public Observable<Page> getImageUrlFromPage(final Page page) {
|
public Observable<Page> getImageUrlFromPage(final Page page) {
|
||||||
page.setStatus(Page.LOAD_PAGE);
|
page.setStatus(Page.LOAD_PAGE);
|
||||||
return mNetworkService
|
return mNetworkService
|
||||||
.getStringResponse(overrideRemainingPagesUrl(page.getUrl()), mRequestHeaders, null)
|
.getStringResponse(overridePageUrl(page.getUrl()), mRequestHeaders, null)
|
||||||
.flatMap(unparsedHtml -> Observable.just(parseHtmlToImageUrl(unparsedHtml)))
|
.flatMap(unparsedHtml -> Observable.just(parseHtmlToImageUrl(unparsedHtml)))
|
||||||
.onErrorResumeNext(e -> {
|
.onErrorResumeNext(e -> {
|
||||||
page.setStatus(Page.ERROR);
|
page.setStatus(Page.ERROR);
|
||||||
|
|
|
@ -135,7 +135,7 @@ public class Batoto extends Source {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String overrideRemainingPagesUrl(String defaultPageUrl) {
|
protected String overridePageUrl(String defaultPageUrl) {
|
||||||
int start = defaultPageUrl.indexOf("#") + 1;
|
int start = defaultPageUrl.indexOf("#") + 1;
|
||||||
int end = defaultPageUrl.indexOf("_", start);
|
int end = defaultPageUrl.indexOf("_", start);
|
||||||
String id = defaultPageUrl.substring(start, end);
|
String id = defaultPageUrl.substring(start, end);
|
||||||
|
|
|
@ -6,7 +6,6 @@ import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import de.greenrobot.event.EventBus;
|
import de.greenrobot.event.EventBus;
|
||||||
import icepick.Icepick;
|
import icepick.Icepick;
|
||||||
import nucleus.presenter.RxPresenter;
|
|
||||||
import nucleus.view.ViewWithPresenter;
|
import nucleus.view.ViewWithPresenter;
|
||||||
|
|
||||||
public class BasePresenter<V extends ViewWithPresenter> extends RxPresenter<V> {
|
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;
|
chapterSync.last_chapter_read = chapterNumber;
|
||||||
|
|
||||||
add(updateSubscription = myAnimeList.update(chapterSync)
|
add(updateSubscription = myAnimeList.update(chapterSync)
|
||||||
|
.flatMap(response -> db.insertChapterSync(chapterSync).createObservable())
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(response -> {},
|
.subscribe(response -> {},
|
||||||
|
|
|
@ -45,6 +45,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
||||||
private Chapter nextChapter;
|
private Chapter nextChapter;
|
||||||
private Chapter previousChapter;
|
private Chapter previousChapter;
|
||||||
private List<Page> pageList;
|
private List<Page> pageList;
|
||||||
|
private List<Page> nextChapterPageList;
|
||||||
private boolean isDownloaded;
|
private boolean isDownloaded;
|
||||||
@State int currentPage;
|
@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_LIST = 1;
|
||||||
private static final int GET_PAGE_IMAGES = 2;
|
private static final int GET_PAGE_IMAGES = 2;
|
||||||
private static final int RETRY_IMAGES = 3;
|
private static final int RETRY_IMAGES = 3;
|
||||||
|
private static final int PRELOAD_NEXT_CHAPTER = 4;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedState) {
|
protected void onCreate(Bundle savedState) {
|
||||||
|
@ -81,7 +83,8 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
||||||
});
|
});
|
||||||
|
|
||||||
restartableReplay(GET_PAGE_IMAGES,
|
restartableReplay(GET_PAGE_IMAGES,
|
||||||
this::getPageImagesObservable,
|
() -> getPageImagesObservable()
|
||||||
|
.doOnCompleted(this::preloadNextChapter),
|
||||||
(view, page) -> {},
|
(view, page) -> {},
|
||||||
(view, error) -> Timber.e("An error occurred while downloading an image"));
|
(view, error) -> Timber.e("An error occurred while downloading an image"));
|
||||||
|
|
||||||
|
@ -89,6 +92,11 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
||||||
this::getRetryPageObservable,
|
this::getRetryPageObservable,
|
||||||
(view, page) -> {},
|
(view, page) -> {},
|
||||||
(view, error) -> Timber.e("An error occurred while downloading an image"));
|
(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
|
@Override
|
||||||
|
@ -105,7 +113,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
onChapterChange();
|
onChapterLeft();
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,10 +133,73 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
||||||
retryPageSubject.onNext(page);
|
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) {
|
private void loadChapter(Chapter chapter) {
|
||||||
|
// Before loading the chapter, stop preloading (if it's working) and save current progress
|
||||||
|
stopPreloadingNextChapter();
|
||||||
|
|
||||||
this.chapter = chapter;
|
this.chapter = chapter;
|
||||||
isDownloaded = isChapterDownloaded(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;
|
currentPage = chapter.last_page_read;
|
||||||
else
|
else
|
||||||
currentPage = 0;
|
currentPage = 0;
|
||||||
|
@ -136,11 +207,22 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
||||||
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
if (pageList == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -158,6 +240,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
||||||
db.insertChapter(chapter).executeAsBlocking();
|
db.insertChapter(chapter).executeAsBlocking();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check whether the chapter has been read
|
||||||
private boolean isChapterFinished() {
|
private boolean isChapterFinished() {
|
||||||
return !chapter.read && currentPage == pageList.size() - 1;
|
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) {
|
public void setCurrentPage(int currentPage) {
|
||||||
this.currentPage = currentPage;
|
this.currentPage = currentPage;
|
||||||
}
|
}
|
||||||
|
@ -247,33 +290,49 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
||||||
.subscribe(result -> previousChapter = result));
|
.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() {
|
public void loadNextChapter() {
|
||||||
if (nextChapter != null) {
|
if (hasNextChapter()) {
|
||||||
onChapterChange();
|
onChapterLeft();
|
||||||
loadChapter(nextChapter);
|
loadChapter(nextChapter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadPreviousChapter() {
|
public void loadPreviousChapter() {
|
||||||
if (previousChapter != null) {
|
if (hasPreviousChapter()) {
|
||||||
onChapterChange();
|
onChapterLeft();
|
||||||
loadChapter(previousChapter);
|
loadChapter(previousChapter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Manga getManga() {
|
public boolean hasNextChapter() {
|
||||||
return manga;
|
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) {
|
public void updateMangaViewer(int viewer) {
|
||||||
manga.viewer = viewer;
|
manga.viewer = viewer;
|
||||||
db.insertManga(manga).executeAsBlocking();
|
db.insertManga(manga).executeAsBlocking();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Manga getManga() {
|
||||||
|
return manga;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,23 @@ import android.view.ViewGroup;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import eu.kanade.mangafeed.R;
|
||||||
import eu.kanade.mangafeed.data.source.model.Page;
|
import eu.kanade.mangafeed.data.source.model.Page;
|
||||||
import eu.kanade.mangafeed.ui.reader.ReaderActivity;
|
import eu.kanade.mangafeed.ui.reader.ReaderActivity;
|
||||||
|
import eu.kanade.mangafeed.ui.reader.ReaderPresenter;
|
||||||
|
import eu.kanade.mangafeed.util.ToastUtil;
|
||||||
|
|
||||||
public abstract class BaseReader {
|
public abstract class BaseReader {
|
||||||
|
|
||||||
protected ReaderActivity activity;
|
protected ReaderActivity activity;
|
||||||
|
protected ReaderPresenter presenter;
|
||||||
protected ViewGroup container;
|
protected ViewGroup container;
|
||||||
protected int currentPosition;
|
protected int currentPosition;
|
||||||
|
|
||||||
public BaseReader(ReaderActivity activity) {
|
public BaseReader(ReaderActivity activity) {
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
this.container = activity.getContainer();
|
this.container = activity.getContainer();
|
||||||
|
this.presenter = activity.getPresenter();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updatePageNumber() {
|
public void updatePageNumber() {
|
||||||
|
@ -34,13 +39,22 @@ public abstract class BaseReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void requestNextChapter() {
|
public void requestNextChapter() {
|
||||||
activity.getPresenter().setCurrentPage(getCurrentPosition());
|
if (presenter.hasNextChapter()) {
|
||||||
activity.getPresenter().loadNextChapter();
|
presenter.setCurrentPage(getCurrentPosition());
|
||||||
|
presenter.loadNextChapter();
|
||||||
|
} else {
|
||||||
|
ToastUtil.showShort(activity, R.string.no_next_chapter);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void requestPreviousChapter() {
|
public void requestPreviousChapter() {
|
||||||
activity.getPresenter().setCurrentPage(getCurrentPosition());
|
if (presenter.hasPreviousChapter()) {
|
||||||
activity.getPresenter().loadPreviousChapter();
|
presenter.setCurrentPage(getCurrentPosition());
|
||||||
|
presenter.loadPreviousChapter();
|
||||||
|
} else {
|
||||||
|
ToastUtil.showShort(activity, R.string.no_previous_chapter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void destroy() {}
|
public void destroy() {}
|
||||||
|
|
|
@ -30,7 +30,7 @@ public abstract class HorizontalReader extends BaseReader {
|
||||||
transitionsSubscription = activity.getPreferences().enableTransitions().asObservable()
|
transitionsSubscription = activity.getPreferences().enableTransitions().asObservable()
|
||||||
.subscribe(value -> transitions = value);
|
.subscribe(value -> transitions = value);
|
||||||
|
|
||||||
viewPager.setOffscreenPageLimit(3);
|
viewPager.setOffscreenPageLimit(2);
|
||||||
viewPager.addOnPageChangeListener(new HorizontalViewPager.SimpleOnPageChangeListener() {
|
viewPager.addOnPageChangeListener(new HorizontalViewPager.SimpleOnPageChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onPageSelected(int position) {
|
public void onPageSelected(int position) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ public class VerticalReader extends BaseReader {
|
||||||
transitionsSubscription = activity.getPreferences().enableTransitions().asObservable()
|
transitionsSubscription = activity.getPreferences().enableTransitions().asObservable()
|
||||||
.subscribe(value -> transitions = value);
|
.subscribe(value -> transitions = value);
|
||||||
|
|
||||||
viewPager.setOffscreenPageLimit(3);
|
viewPager.setOffscreenPageLimit(2);
|
||||||
viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
|
viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onPageSelected(int position) {
|
public void onPageSelected(int position) {
|
||||||
|
|
|
@ -92,6 +92,8 @@
|
||||||
<string name="chapter_progress">Page: %1$d</string>
|
<string name="chapter_progress">Page: %1$d</string>
|
||||||
<string name="page_list_error">Error fetching page list. Is network available?</string>
|
<string name="page_list_error">Error fetching page list. Is network available?</string>
|
||||||
<string name="chapter_subtitle">Chapter %1$s</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 -->
|
<!-- Library update service notifications -->
|
||||||
<string name="notification_progress">Update progress: %1$d/%2$d</string>
|
<string name="notification_progress">Update progress: %1$d/%2$d</string>
|
||||||
|
|
Loading…
Reference in a new issue