package com.florianmski.tracktoid.ui.fragments.base.list;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.florianmski.tracktoid.R;
import com.florianmski.tracktoid.adapters.AdapterInterface;
import com.florianmski.tracktoid.containers.ContainerInterface;
import com.florianmski.tracktoid.errors.Comportment;
import com.florianmski.tracktoid.errors.ErrorHandler;
import com.florianmski.tracktoid.errors.NoResultException;
import com.florianmski.tracktoid.errors.RetrofitComportment;
import com.florianmski.tracktoid.ui.fragments.BaseFragment;
import com.florianmski.tracktoid.utils.Utils;
import java.util.ArrayList;
import java.util.List;
import rx.Observable;
import rx.Observer;
import rx.Subscription;
import rx.android.observables.AndroidObservable;
import rx.schedulers.Schedulers;
import rx.subscriptions.CompositeSubscription;
import rx.subscriptions.Subscriptions;
public abstract class ItemScrollFragment<T, E, V extends ViewGroup, S, A extends AdapterInterface<T>> extends BaseFragment implements Observer<E>, ScrollListenerProvider<S>, SwipeRefreshLayout.OnRefreshListener
{
protected E data;
protected ContainerInterface.ViewContainerInterface<T, V, A> viewContainer;
protected List<S> scrollListeners = new ArrayList<>();
protected RelativeLayout root;
protected SwipeRefreshLayout swipeRefreshLayout;
protected FrameLayout frameLayoutLoadingWrapper;
protected ProgressBar progressBar;
protected FrameLayout frameLayoutErrorWrapper;
protected TextView errorView;
protected View.OnClickListener retryListener = new View.OnClickListener()
{
@Override
public void onClick(View v)
{
refresh(true);
}
};
protected Observable<E> observable;
protected CompositeSubscription subscriptions = new CompositeSubscription();
protected Subscription subscription = Subscriptions.empty();
protected ErrorHandler errorHandler;
protected Comportment defaultComportment = new Comportment(
null,
"Unknown error\nA report has been send to the dev",
"tap to retry",
retryListener);
protected boolean refreshAtStart = true;
protected boolean instantLoad = false;
protected AnimatorSet fadeAnim;
protected View viewBeingAnimated;
public ItemScrollFragment(ContainerInterface.ViewContainerInterface<T, V, A> viewContainer)
{
this.viewContainer = viewContainer;
}
protected abstract Observable<E> createObservable();
protected abstract void refreshView(E data);
protected abstract boolean isEmpty(E data);
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}
@Override
public void onDestroy()
{
super.onDestroy();
if(fadeAnim != null)
{
fadeAnim.removeAllListeners();
fadeAnim.cancel();
}
subscriptions.unsubscribe();
}
@Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
errorHandler = new ErrorHandler(getActivity(), errorView, defaultComportment)
.putComportment(new Comportment(NoResultException.class, "No result found :(", "tap to retry", retryListener))
.putComportment(new RetrofitComportment(retryListener));
// by default, pull to refresh is not possible
setPullToRefresh(false);
swipeRefreshLayout.setOnRefreshListener(this);
swipeRefreshLayout.setColorSchemeColors(getTheme().getColorDark(getActivity()));
if(refreshAtStart)
refresh(!instantLoad);
else
showView();
}
protected View getCustomLayout(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.inflate(R.layout.fragment_item_group, container, false);
}
@Override
public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = getCustomLayout(inflater, container, savedInstanceState);
ViewStub vs = (ViewStub) view.findViewById(R.id.viewStub);
vs.setLayoutResource(viewContainer.getLayoutId());
vs.inflate();
return view;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
root = (RelativeLayout) view.findViewById(R.id.root);
viewContainer.set((V) view.findViewById(android.R.id.list));
frameLayoutLoadingWrapper = (FrameLayout) view.findViewById(R.id.frameLayoutLoadingWrapper);
progressBar = (ProgressBar) view.findViewById(R.id.pb_loading);
frameLayoutErrorWrapper = (FrameLayout) view.findViewById(R.id.frameLayoutErrorWrapper);
errorView = (TextView) view.findViewById(R.id.error);
swipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipe_refresh_layout);
}
protected void refresh(boolean showProgressBar)
{
if(showProgressBar)
{
showProgressBar(new AnimatorListenerAdapter()
{
@Override
public void onAnimationEnd(Animator animation)
{
refresh();
}
});
}
else
refresh();
}
private void refresh()
{
// unsubscribe the subscription and add the new one
subscriptions.remove(subscription);
subscription = AndroidObservable.bindFragment(this, createObservable().subscribeOn(Schedulers.io()))
.subscribe(ItemScrollFragment.this);
subscriptions.add(subscription);
}
@Override
public void onInsetsChanged(Rect insets)
{
super.onInsetsChanged(insets);
getGroupView().setClipToPadding(false);
setGroupViewPadding(
getGroupView().getPaddingLeft(),
// don't touch padding top if there is already a custom value
getGroupView().getPaddingTop() != 0 ? getGroupView().getPaddingTop() : Utils.getActionBarHeight(getActivity()),
getGroupView().getPaddingRight(),
insets.bottom);
swipeRefreshLayout.setProgressViewEndTarget(false, getGroupView().getPaddingTop() * 2);
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser)
{
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser)
showActionBar(true);
}
public V getGroupView()
{
return viewContainer.get();
}
public E getData()
{
return data;
}
protected void showProgressBar(Animator.AnimatorListener listener)
{
show(frameLayoutLoadingWrapper, listener, viewContainer.get(), frameLayoutErrorWrapper);
}
protected void showView()
{
show(viewContainer.get(), frameLayoutErrorWrapper, frameLayoutLoadingWrapper);
}
protected void showErrorView()
{
show(frameLayoutErrorWrapper, viewContainer.get(), frameLayoutLoadingWrapper);
}
protected void show(View viewToShow, View... viewsToHide)
{
show(viewToShow, null, viewsToHide);
}
protected void show(View viewToShow, Animator.AnimatorListener listener, View... viewsToHide)
{
// if the view is already visible, nothing to do!
if((viewBeingAnimated != null && viewBeingAnimated.getId() == viewToShow.getId()) || viewToShow.getVisibility() == View.VISIBLE)
return;
if(fadeAnim != null)
fadeAnim.cancel();
fadeAnim = new AnimatorSet();
if(viewsToHide.length == 0)
fadeAnim.play(changeToVisibility(View.VISIBLE, viewToShow));
else
{
AnimatorSet.Builder builder = fadeAnim.play(changeToVisibility(View.GONE, viewsToHide[0]));
if(viewsToHide.length > 1)
{
for(int i = 1; i < viewsToHide.length; i++)
builder.with(changeToVisibility(View.GONE, viewsToHide[i]));
}
builder.before(changeToVisibility(View.VISIBLE, viewToShow));
}
if(listener != null)
fadeAnim.addListener(listener);
fadeAnim.start();
}
private Animator changeToVisibility(final int toVisibility, final View v)
{
float startAlpha = toVisibility == View.VISIBLE ? 0f : 1f;
float endAlpha = toVisibility == View.VISIBLE ? 1f : 0f;
float startY = toVisibility == View.VISIBLE ? 50f : 0f;
float endY = toVisibility == View.VISIBLE ? 0f : -50f;
PropertyValuesHolder fadeAnimator = PropertyValuesHolder.ofFloat("alpha", startAlpha, endAlpha);
PropertyValuesHolder translateAnimator = PropertyValuesHolder.ofFloat("y", startY, endY);
ValueAnimator animator = ObjectAnimator.ofPropertyValuesHolder(v, fadeAnimator, translateAnimator);
animator.addListener(new Animator.AnimatorListener()
{
@Override
public void onAnimationStart(Animator animation)
{
viewBeingAnimated = v;
if (toVisibility == View.VISIBLE)
v.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animator animation)
{
viewBeingAnimated = null;
if (toVisibility != View.VISIBLE)
v.setVisibility(toVisibility);
}
@Override
public void onAnimationCancel(Animator animation) {}
@Override
public void onAnimationRepeat(Animator animation) {}
});
return animator;
}
// avoid flickering of the progressbar when we get the results almost immediately
protected void setInstantLoad(boolean instantLoad)
{
this.instantLoad = instantLoad;
}
protected void setRefreshOnStart(boolean refreshAtStart)
{
this.refreshAtStart = refreshAtStart;
}
public void setGroupViewPadding(int left, int top, int right, int bottom)
{
getGroupView().setPadding(left, top, right, bottom);
// set padding on wrappers
if(getInsets() != null)
{
top -= getInsets().bottom;
frameLayoutLoadingWrapper.setPadding(left, top, right, 0);
frameLayoutErrorWrapper.setPadding(left, top, right, 0);
}
}
@Override
public void onNext(E item)
{
data = item;
if(isEmpty(item))
// this way the observable do not call onCompleted so it is still active.
// Useful when doing the first sync because the cursor can be reloaded and
// display the item being synced for the first time
onError(new NoResultException());
else
{
refreshView(item);
showView();
}
swipeRefreshLayout.setRefreshing(false);
}
@Override
public void onCompleted() {}
@Override
public void onError(Throwable throwable)
{
errorHandler.handle(throwable, "Error while loading stuff");
showErrorView();
}
@Override
public void addScrollListener(S listener)
{
scrollListeners.add(listener);
}
@Override
public void removeScrollListener(S listener)
{
scrollListeners.remove(listener);
}
@Override
public void onRefresh()
{
refresh(false);
}
public void setPullToRefresh(boolean pullToRefresh)
{
swipeRefreshLayout.setEnabled(pullToRefresh);
}
}