package cl.monsoon.s1next.view.dialog; import android.app.Activity; import android.app.Dialog; import android.app.ProgressDialog; import android.content.Intent; import android.os.Bundle; import android.support.annotation.CallSuper; import android.support.annotation.NonNull; import android.support.design.widget.Snackbar; import android.support.v4.app.DialogFragment; import android.text.TextUtils; import android.widget.Toast; import cl.monsoon.s1next.App; import cl.monsoon.s1next.data.User; import cl.monsoon.s1next.data.api.S1Service; import cl.monsoon.s1next.data.api.UserValidator; import cl.monsoon.s1next.data.api.model.Account; import cl.monsoon.s1next.data.api.model.wrapper.ResultWrapper; import cl.monsoon.s1next.util.ErrorUtil; import cl.monsoon.s1next.util.RxJavaUtil; import cl.monsoon.s1next.view.activity.BaseActivity; import cl.monsoon.s1next.view.fragment.BaseFragment; import cl.monsoon.s1next.view.internal.CoordinatorLayoutAnchorDelegate; import rx.Observable; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Func1; import rx.schedulers.Schedulers; /** * A dialog shows {@link ProgressDialog}. * Also wraps some related methods to request data. * <p> * This {@link DialogFragment} is retained in order * to retain request when configuration changes. * * @param <D> The data we want to request. */ abstract class ProgressDialogFragment<D> extends DialogFragment { S1Service mS1Service; UserValidator mUserValidator; private User mUser; private Subscription mSubscription; @Override @CallSuper public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); App.AppComponent appComponent = App.getAppComponent(getContext()); mS1Service = appComponent.getS1Service(); mUser = appComponent.getUser(); mUserValidator = appComponent.getUserValidator(); // retain this Fragment setRetainInstance(true); request(); } @NonNull @Override public final Dialog onCreateDialog(Bundle savedInstanceState) { ProgressDialog progressDialog = new ProgressDialog(getContext()); progressDialog.setMessage(getProgressMessage()); return progressDialog; } @Override @CallSuper public void onDestroyView() { // see https://code.google.com/p/android/issues/detail?id=17423 Dialog dialog = getDialog(); if (dialog != null) { getDialog().setOnDismissListener(null); } super.onDestroyView(); } @Override public void onDestroy() { super.onDestroy(); RxJavaUtil.unsubscribeIfNotNull(mSubscription); } /** * @see BaseFragment#load() */ private void request() { mSubscription = getSourceObservable().subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doAfterTerminate(this::doAfterTerminate) .subscribe(this::onNext, this::onError); } /** * @see BaseFragment#getSourceObservable() */ abstract Observable<D> getSourceObservable(); /** * A helpers method provides authenticity token. * * @param func A function that, when applied to the authenticity token, returns an * Observable. And the {@link Observable} is what we want to return * if we get authenticity token successful. * @return Returns {@link S1Service#refreshAuthenticityToken()}'s result if we * failed to get authenticity token, otherwise returns {@code func.call(authenticityToken)}. */ final Observable<ResultWrapper> flatMappedWithAuthenticityToken(Func1<String, Observable<ResultWrapper>> func) { String authenticityToken = mUser.getAuthenticityToken(); if (TextUtils.isEmpty(authenticityToken)) { return mS1Service.refreshAuthenticityToken().flatMap(resultWrapper -> { Account account = resultWrapper.getAccount(); // return the ResultWrapper if we cannot get the authenticity token // (if account has expired or network error) if (TextUtils.isEmpty(account.getAuthenticityToken())) { return Observable.just(resultWrapper); } else { mUserValidator.validate(account); return func.call(account.getAuthenticityToken()); } }); } else { return func.call(authenticityToken); } } /** * @see BaseFragment#onNext(Object) */ abstract void onNext(D data); /** * @see BaseFragment#onError(Throwable) */ void onError(Throwable throwable) { showShortText(getString(ErrorUtil.parse(throwable))); } /** * @see BaseFragment#doAfterTerminate() */ private void doAfterTerminate() { dismissAllowingStateLoss(); } /** * @see cl.monsoon.s1next.view.activity.BaseActivity#showShortText(CharSequence) */ final void showShortText(CharSequence text) { ((CoordinatorLayoutAnchorDelegate) getActivity()).showShortText(text); } /** * If current {@link android.app.Activity} is visible, sets result message to {@link Activity} * in order to show a short {@link Snackbar} for message during {@link #onActivityResult(int, int, Intent)}, * otherwise show a short {@link android.widget.Toast}. * * @param text The text to show. * @see BaseActivity#onActivityResult(int, int, Intent) */ final void showShortTextAndFinishCurrentActivity(CharSequence text) { Activity activity = getActivity(); App app = (App) activity.getApplicationContext(); // Because Activity#onActivityResult(int, int, Intent) is always invoked when current app // is running in the foreground (so we are unable to set result message to Activity to // let BaseActivity to show a Toast if our app is running in the background). // So we need to handle it by ourselves. if (app.isAppVisible()) { BaseActivity.setResultMessage(activity, text); } else { Toast.makeText(app, text, Toast.LENGTH_SHORT).show(); } activity.finish(); } protected abstract CharSequence getProgressMessage(); }