package com.florianmski.tracktoid.ui.fragments.traktitems;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateInterpolator;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import android.widget.Toast;
import com.florianmski.tracktoid.R;
import com.florianmski.tracktoid.TraktoidConstants;
import com.florianmski.tracktoid.data.TraktBase;
import com.florianmski.tracktoid.data.TraktoidItem;
import com.florianmski.tracktoid.rx.observables.CursorObservable;
import com.florianmski.tracktoid.rx.observables.TraktFallbackTransformer;
import com.florianmski.tracktoid.rx.observables.TraktObservable;
import com.florianmski.tracktoid.trakt.TraktSender;
import com.florianmski.tracktoid.ui.activities.HeaderActivity;
import com.florianmski.tracktoid.ui.fragments.base.list.ItemScrollViewFragment;
import com.florianmski.tracktoid.ui.widgets.ReadMoreView;
import com.florianmski.tracktoid.ui.widgets.TraktActionView;
import com.florianmski.tracktoid.ui.widgets.TraktImageView;
import com.florianmski.tracktoid.utils.Utils;
import com.getbase.floatingactionbutton.FloatingActionButton2;
import com.uwetrottmann.trakt.v2.entities.BaseCheckinResponse;
import com.uwetrottmann.trakt.v2.entities.SyncResponse;
import com.uwetrottmann.trakt.v2.exceptions.OAuthUnauthorizedException;
import java.util.ArrayList;
import java.util.Collection;
import rx.Observable;
import rx.Observer;
import rx.Subscriber;
import rx.Subscription;
import rx.android.observables.AndroidObservable;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
public abstract class TraktItemFragment<T extends TraktoidItem, C extends BaseCheckinResponse> extends ItemScrollViewFragment<T>
{
// TODO declare it in values.xml
private final static int NB_COLUMNS = 2;
protected TraktImageView tivPoster;
protected LinearLayout ll;
protected LinearLayout llBasicInfos;
protected TraktActionView tavSeen;
protected TraktActionView tavCollection;
protected TraktActionView tavWatchlist;
protected ReadMoreView rmvInfos;
protected TableLayout tlInfos;
protected TextView tvOverview;
protected TextView tvTitle;
protected TextView tvSubtitle;
protected TextView tvRate;
protected TextView tvComments;
protected FloatingActionButton2 fab;
protected String id;
protected T item;
public abstract String getDateText(boolean invalidTime);
public abstract T getTraktObject();
public abstract CursorObservable<T> getCursorObservable();
public abstract void launchCommentActivity();
public abstract TraktSender.Builder addItemToBuilder(TraktSender.Builder builder);
public abstract Observable<T> getDownloadAndInsertItemObservable();
public abstract Observable<C> getCheckinObservable();
public static Bundle getBundle(String id)
{
Bundle args = new Bundle();
args.putString(TraktoidConstants.BUNDLE_ID, id);
return args;
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
id = getArguments().getString(TraktoidConstants.BUNDLE_ID);
}
@Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
rmvInfos.makeItScroll(getGroupView());
tvRate.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
// TODO
}
});
tvComments.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
launchCommentActivity();
}
});
// setup the tavs
setTavListener(tavSeen, new TraktSender.HistoryBuilder(getActivity()));
setTavListener(tavCollection, new TraktSender.CollectionBuilder(getActivity()));
setTavListener(tavWatchlist, new TraktSender.WatchlistBuilder(getActivity()));
fab.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
final Observable<C> observable = getCheckinObservable();
Observable<C> finalObservable;
// make sur we get the item in local if we haven't before checkin
if(!item.isLocal())
{
finalObservable = getDownloadAndInsertItemObservable().flatMap(new Func1<T, Observable<C>>()
{
@Override
public Observable<C> call(T t)
{
return observable;
}
});
}
else
finalObservable = observable;
// TODO would be great to display a loading indicator on the fab while the checkin request is made
Subscription s = finalObservable
.subscribe(new Subscriber<BaseCheckinResponse>()
{
@Override
public void onCompleted() {}
@Override
public void onError(Throwable e)
{
// TODO
e.printStackTrace();
Toast.makeText(getActivity(), "Error!", Toast.LENGTH_SHORT).show();
}
@Override
public void onNext(BaseCheckinResponse checkinResponse)
{
// TODO
Toast.makeText(getActivity(), "Checked in!", Toast.LENGTH_SHORT).show();
// sweet fab animation to the status bar
ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(fab, View.SCALE_X, 0f);
ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(fab, View.SCALE_Y, 0f);
AnimatorSet set = new AnimatorSet();
Collection<Animator> animators = new ArrayList<>();
animators.add(scaleXAnimator);
animators.add(scaleYAnimator);
// if it's not Lollipop or higher, don't do the curved path animation
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
int height = getGroupView().getPaddingTop() - getGroupView().getScrollY();
Path path = new Path();
RectF r = new RectF(0f, 2 * (-height - fab.getHeight() / 2), 2 * fab.getX(), 2 * fab.getY());
path.arcTo(r, 90, 90);
ObjectAnimator pathAnimator = ObjectAnimator.ofFloat(fab, View.X, View.Y, path);
animators.add(pathAnimator);
}
set.playTogether(animators);
set.setInterpolator(new AccelerateInterpolator());
set.setDuration(800);
set.start();
}
});
subscriptions.add(s);
}
});
}
protected void setTavListener(final TraktActionView tav, final TraktSender.Builder builder)
{
tav.setOnTAVClickListener(new TraktActionView.OnTAVClickListener()
{
@Override
public void onClick(boolean checked)
{
// make sure we don't add the same item multiple time if user tap several times the same tav
builder.clear();
final Observable<SyncResponse> sendAndUpdateDbObservable = addItemToBuilder(builder).getObservable(!checked);
Observable<SyncResponse> finalObservable;
if (!item.isLocal())
finalObservable = getDownloadAndInsertItemObservable()
.flatMap(new Func1<T, Observable<SyncResponse>>()
{
@Override
public Observable<SyncResponse> call(T item)
{
return sendAndUpdateDbObservable;
}
});
else
finalObservable = sendAndUpdateDbObservable;
Subscription subscription = AndroidObservable
.bindFragment(TraktItemFragment.this, finalObservable.subscribeOn(Schedulers.io()))
.subscribe(new TAVObserver(tav));
subscriptions.add(subscription);
}
});
}
public Observable<T> createObservable()
{
// trying to get the item from db and if it fails we don't have this item in db and
// use the network
return Observable
.create(getCursorObservable())
.compose(new TraktFallbackTransformer<>(new TraktObservable<T>()
{
@Override
public T fire() throws OAuthUnauthorizedException
{
return getTraktObject();
}
}));
}
@Override
public int getContentLayoutId()
{
return R.layout.fragment_trakt_item;
}
@Override
public void onViewCreated(final View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
// init the right visibility for the scrollView
// the idea is to have the BG of the scrollView always visible but not the content
getViewToHide().setVisibility(View.INVISIBLE);
getGroupView().setVisibility(View.VISIBLE);
// even if we don't use recyclerview, we have to set a layoutmanager or when destroying fragment it will crash
((RecyclerView) view.findViewById(R.id.recyclerViewSeasons)).setLayoutManager(new LinearLayoutManager(getActivity()));
ll = (LinearLayout)view.findViewById(R.id.linearLayout);
llBasicInfos = (LinearLayout)view.findViewById(R.id.linearLayoutBasicInfos);
tivPoster = (TraktImageView)view.findViewById(R.id.tivPoster);
rmvInfos = (ReadMoreView)view.findViewById(R.id.expandableTextViewInfos);
tlInfos = (TableLayout)view.findViewById(R.id.tableLayoutInfos);
tvOverview = (TextView)view.findViewById(R.id.textViewOverview);
tvTitle = (TextView)view.findViewById(R.id.textViewTitle);
tvSubtitle = (TextView)view.findViewById(R.id.textViewSubtitle);
tvRate = (TextView)view.findViewById(R.id.textViewRate);
tvComments = (TextView)view.findViewById(R.id.textViewComments);
tavSeen = (TraktActionView)view.findViewById(R.id.traktActionViewSeen);
tavCollection = (TraktActionView)view.findViewById(R.id.traktActionViewCollection);
tavWatchlist = (TraktActionView)view.findViewById(R.id.traktActionViewWatchlist);
llBasicInfos.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
{
@Override
public void onGlobalLayout()
{
llBasicInfos.getViewTreeObserver().removeGlobalOnLayoutListener(this);
((HeaderActivity)getActivity()).setGroupView(TraktItemFragment.this, llBasicInfos.getTop());
}
});
fab = (FloatingActionButton2) view.findViewById(R.id.fab);
fab.setAnimation(FloatingActionButton2.ANIM_SCALE);
// allow the fab animation to work
Utils.disableClipOnParents(fab);
}
protected void refreshGeneralView(TraktBase traktBase)
{
tvTitle.setText(traktBase.title);
String firstAired = getDateText(traktBase.firstAired == null);
tvSubtitle.setText(firstAired);
tvOverview.setText(traktBase.overview);
rmvInfos.setExpanded(false, false);
tavSeen.setChecked(traktBase.watched);
tavCollection.setChecked(traktBase.collected);
tavWatchlist.setChecked(traktBase.watchlisted);
// TODO revealview should be intelligent, open overview if user has not seen a movie or a show and close otherwise
// but has to be close if user has not seen an episode to avoid spoiler etc...
}
protected void addInfo(String title, String info)
{
TableRow tr;
int chidlCount = tlInfos.getChildCount();
// create a new tablerow if we reach the nb_columns or if there is none
if(chidlCount == 0)
tlInfos.addView(tr = new TableRow(getActivity()));
else
{
tr = (TableRow) tlInfos.getChildAt(chidlCount-1);
if(tr.getChildCount() >= NB_COLUMNS)
tlInfos.addView(tr = new TableRow(getActivity()));
}
LinearLayout ll = (LinearLayout) LayoutInflater.from(getActivity()).inflate(R.layout.view_grid_layout_info, tr, false);
TextView tvTitle = (TextView) ll.findViewById(R.id.textViewTitle);
TextView tvInfo = (TextView) ll.findViewById(R.id.textViewInfo);
tvTitle.setText(title);
tvInfo.setText(info);
// set gravity depending on the position of the view
int trChildCount = tr.getChildCount();
if(trChildCount == 0)
{
tvTitle.setGravity(Gravity.START);
tvInfo.setGravity(Gravity.START);
}
else if(trChildCount == NB_COLUMNS - 1)
{
tvTitle.setGravity(Gravity.END);
tvInfo.setGravity(Gravity.END);
}
else
{
tvTitle.setGravity(Gravity.CENTER_HORIZONTAL);
tvInfo.setGravity(Gravity.CENTER_HORIZONTAL);
}
tr.addView(ll);
}
private class TAVObserver implements Observer<SyncResponse>
{
private TraktActionView tav;
public TAVObserver(TraktActionView tav)
{
this.tav = tav;
}
@Override
public void onCompleted()
{
}
@Override
public void onError(Throwable e)
{
// TODO
e.printStackTrace();
Toast.makeText(getActivity(), "Error!", Toast.LENGTH_SHORT).show();
tav.error();
}
@Override
public void onNext(SyncResponse syncResponse)
{
// TODO
Toast.makeText(getActivity(), "Sent!", Toast.LENGTH_SHORT).show();
tav.success();
}
}
@Override
protected void showProgressBar(Animator.AnimatorListener listener)
{
getGroupView().setFillViewport(true);
show(frameLayoutLoadingWrapper, listener, getViewToHide(), frameLayoutErrorWrapper);
}
@Override
protected void showView()
{
show(getViewToHide(), new AnimatorListenerAdapter()
{
@Override
public void onAnimationEnd(Animator animation)
{
// there's a bug if the content of the scrollview is too small, we cant' scroll and see
// what's after, so setFillViewPort when displaying the view as a workaround
getGroupView().setFillViewport(false);
}
}, frameLayoutErrorWrapper, frameLayoutLoadingWrapper);
}
@Override
protected void showErrorView()
{
getGroupView().setFillViewport(true);
show(frameLayoutErrorWrapper, getViewToHide(), frameLayoutLoadingWrapper);
}
private View getViewToHide()
{
return getGroupView().findViewById(R.id.linearLayout);
}
@Override
public void onScrollChanged(ScrollView who, int l, int t, int oldl, int oldt)
{
super.onScrollChanged(who, l, t, oldl, oldt);
int dy = t - oldt;
// in some case some little variation doesn't look good
if(Math.abs(dy) > 5)
fab.reactToScroll(dy);
}
}