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:
inorichi
2015-11-28 10:48:01 +01:00
parent 9db81b1832
commit 30b907bdf2
11 changed files with 475 additions and 68 deletions
@@ -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);
}
}