public class VideoDetailsActivity extends AppCompatActivity {

    // region Lifecycle Methods
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_details);
        ButterKnife.bind(this);

        Fragment fragment = getSupportFragmentManager().findFragmentById(android.R.id.content);
        if(fragment == null){
            fragment = VideoDetailsFragment.newInstance(getIntent().getExtras());
            getSupportFragmentManager()
                    .beginTransaction()
                    .replace(android.R.id.content, fragment, "")
                    .commit();
        } else {
            getSupportFragmentManager()
                    .beginTransaction()
                    .attach(fragment)
                    .commit();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
    }
    // endregion

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            // Respond to the action bar's Up/Home button
            case android.R.id.home:
                supportFinishAfterTransition();
//                NavUtils.navigateUpFromSameTask(this);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

} public class VideosAdapter extends BaseAdapter<Video> {

    // region Member Variables
    private FooterViewHolder footerViewHolder;
    // endregion

    // region Constructors
    public VideosAdapter() {
        super();
    }
    // endregion

    @Override
    public int getItemViewType(int position) {
        return (isLastPosition(position) && isFooterAdded) ? FOOTER : ITEM;
    }

    @Override
    protected RecyclerView.ViewHolder createHeaderViewHolder(ViewGroup parent) {
        return null;
    }

    @Override
    protected RecyclerView.ViewHolder createItemViewHolder(ViewGroup parent) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.video_row, parent, false);

        final VideoViewHolder holder = new VideoViewHolder(v);

        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int adapterPos = holder.getAdapterPosition();
                if(adapterPos != RecyclerView.NO_POSITION){
                    if (onItemClickListener != null) {
                        onItemClickListener.onItemClick(adapterPos, holder.itemView);
                    }
                }
            }
        });

        return holder;
    }

    @Override
    protected RecyclerView.ViewHolder createFooterViewHolder(ViewGroup parent) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_footer, parent, false);

        final FooterViewHolder holder = new FooterViewHolder(v);
        holder.reloadButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(onReloadClickListener != null){
                    onReloadClickListener.onReloadClick();
                }
            }
        });

        return holder;
    }

    @Override
    protected void bindHeaderViewHolder(RecyclerView.ViewHolder viewHolder) {

    }

    @Override
    protected void bindItemViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        final VideoViewHolder holder = (VideoViewHolder) viewHolder;

        final Video video = getItem(position);
        if (video != null) {
            holder.bind(video);
        }
    }

    @Override
    protected void bindFooterViewHolder(RecyclerView.ViewHolder viewHolder) {
        FooterViewHolder holder = (FooterViewHolder) viewHolder;
        footerViewHolder = holder;

        holder.loadingImageView.setMaskOrientation(LoadingImageView.MaskOrientation.LeftToRight);
    }

    @Override
    protected void displayLoadMoreFooter() {
        if(footerViewHolder!= null){
            footerViewHolder.errorRelativeLayout.setVisibility(View.GONE);
            footerViewHolder.loadingFrameLayout.setVisibility(View.VISIBLE);
        }
    }

    @Override
    protected void displayErrorFooter() {
        if(footerViewHolder!= null){
            footerViewHolder.loadingFrameLayout.setVisibility(View.GONE);
            footerViewHolder.errorRelativeLayout.setVisibility(View.VISIBLE);
        }
    }

    @Override
    public void addFooter() {
        isFooterAdded = true;
        add(new Video());
    }

    // region Inner Classes

    public static class VideoViewHolder extends RecyclerView.ViewHolder {
        // region Views
        @BindView(R.id.video_thumbnail_iv)
        ImageView videoThumbnailImageView;
        @BindView(R.id.title_tv)
        TextView titleTextView;
        @BindView(R.id.caption_tv)
        TextView captionTextView;
        @BindView(R.id.duration_tv)
        TextView durationTextView;
        @BindView(R.id.subtitle_tv)
        TextView subtitleTextView;
        // endregion

        // region Constructors
        public VideoViewHolder(View view) {
            super(view);
            ButterKnife.bind(this, view);
        }
        // endregion

        // region Helper Methods
        private void bind(Video video){
            setUpTitle(titleTextView, video);
            setUpSubtitle(subtitleTextView, video);
            setUpVideoThumbnail(videoThumbnailImageView, video);
            setUpDuration(durationTextView, video);
            setUpCaption(captionTextView, video);

            int adapterPos = getAdapterPosition();
            ViewCompat.setTransitionName(subtitleTextView,"myTransition"+adapterPos);
        }

        private void setUpTitle(TextView tv, Video video) {
            String name = video.getName();
            if (!TextUtils.isEmpty(name)) {
                tv.setText(name);
            }
        }

        private void setUpSubtitle(TextView tv, Video video) {
            User user = video.getUser();
            if (user != null) {
                String userName = user.getName();
                if (!TextUtils.isEmpty(userName)) {
                    tv.setText(userName);
                }
            }
        }

        private void setUpVideoThumbnail(ImageView iv, Video video) {
            String thumbnailUrl = video.getThumbnailUrl();
            if (!TextUtils.isEmpty(thumbnailUrl)) {
                Glide.with(iv.getContext())
                        .load(thumbnailUrl)
//                                .placeholder(R.drawable.ic_placeholder)
//                                .error(R.drawable.ic_error)
                        .into(iv);
            }
        }

        private void setUpDuration(TextView tv, Video video) {
            String formattedDuration = video.getFormattedDuration();
            if(!TextUtils.isEmpty(formattedDuration))
                tv.setText(formattedDuration);
        }

        private void setUpCaption(TextView tv, Video video) {
            String caption = video.getCaption();
            if(!TextUtils.isEmpty(caption))
                tv.setText(caption);
        }
        // endregion
    }

    public static class FooterViewHolder extends RecyclerView.ViewHolder {
        // region Views
        @BindView(R.id.loading_fl)
        FrameLayout loadingFrameLayout;
        @BindView(R.id.error_rl)
        RelativeLayout errorRelativeLayout;
        @BindView(R.id.loading_iv)
        LoadingImageView loadingImageView;
        @BindView(R.id.reload_btn)
        Button reloadButton;
        // endregion

        // region Constructors
        public FooterViewHolder(View view) {
            super(view);
            ButterKnife.bind(this, view);
        }
        // endregion
    }

    // endregion

} public class AccessToken {

    // region Fields
    private String tokenType;
    private String accessToken;
    // endregion

    // region Constructors
    public AccessToken(String tokenType, String accessToken) {
        this.tokenType = tokenType;
        this.accessToken = accessToken;
    }
    // endregion

    // region Getters
    public String getTokenType() {
        // OAuth requires uppercase Authorization HTTP header value for token type
        if ( ! Character.isUpperCase(tokenType.charAt(0))) {
            tokenType = Character.toString(tokenType.charAt(0)).toUpperCase() + tokenType.substring(1);
        }

        return tokenType;
    }

    public String getAccessToken() {
        return accessToken;
    }
    // endregion
} public class ServiceGenerator {

    // region Constants
    private static final int DISK_CACHE_SIZE = 10 * 1024 * 1024; // 10MB
    // endregion

    private static Retrofit.Builder retrofitBuilder
            = new Retrofit.Builder()
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create());

    private static OkHttpClient defaultOkHttpClient
            = new OkHttpClient.Builder()
                .cache(getCache())
                .build();

    // No need to instantiate this class.
    private ServiceGenerator() {
    }

    public static <S> S createService(Class<S> serviceClass, String baseUrl) {
        return createService(serviceClass, baseUrl, null);
    }

    public static <S> S createService(Class<S> serviceClass, String baseUrl, Interceptor networkInterceptor) {
        OkHttpClient.Builder okHttpClientBuilder = defaultOkHttpClient.newBuilder();

        if(networkInterceptor != null){
            okHttpClientBuilder.addNetworkInterceptor(networkInterceptor);
        }

        OkHttpClient modifiedOkHttpClient = okHttpClientBuilder
                .addInterceptor(getHttpLoggingInterceptor())
                .build();

        retrofitBuilder.client(modifiedOkHttpClient);
        retrofitBuilder.baseUrl(baseUrl);

        Retrofit retrofit = retrofitBuilder.build();
        return retrofit.create(serviceClass);
    }

    private static Cache getCache() {

        Cache cache = null;
        // Install an HTTP cache in the application cache directory.
        try {
            File cacheDir = new File(LoopApplication.getCacheDirectory(), "http");
            cache = new Cache(cacheDir, DISK_CACHE_SIZE);
        } catch (Exception e) {
            Timber.e(e, "Unable to install disk cache.");
        }
        return cache;
    }

    private static HttpLoggingInterceptor getHttpLoggingInterceptor(){
        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
        if (BuildConfig.DEBUG) {
            httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        } else {
            httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.NONE);
        }
        return httpLoggingInterceptor;
    }
} public interface VimeoService {

    String BASE_URL = "https://api.vimeo.com/";

    @FormUrlEncoded
    @POST("oauth/access_token")
    Call<OAuthResponse> exchangeCode(@Field("grant_type") String grantType,
                                     @Field("code") String code,
                                     @Field("redirect_uri") String redirectUri);

    @GET("videos")
    Call<VideosEnvelope> findVideos(@Query("query") String query,
                                    @Query("sort") String sort,
                                    @Query("direction") String direction,
                                    @Query("page") Integer page,
                                    @Query("per_page") Integer perPage,
                                    @Query("filter") String filter);

    @GET("videos/{videoId}/comments")
    Call<CommentsEnvelope> getComments(@Path("videoId") Long videoId,
                                       @Query("sort") String sort,
                                       @Query("direction") String direction,
                                       @Query("page") Integer page,
                                       @Query("per_page") Integer perPage);

    @POST("videos/{videoId}/comments")
    Call<Comment> addComment(@Path("videoId") Long videoId,
                             @Body CommentPost commentPost);

    @DELETE("videos/{videoId}/comments/{commentId}")
    Call<ResponseBody> deleteComment(@Path("videoId") Long videoId,
                               @Path("commentId") Long commentId);

    @GET("videos/{videoId}/videos?filter=related")
    Call<VideosEnvelope> findRelatedVideos(@Path("videoId") Long videoId,
                                           @Query("page") Integer page,
                                           @Query("per_page") Integer perPage);

    @GET("users/{userId}/videos")
    Call<VideosEnvelope> getUserVideos(@Path("userId") Long userId,
                                           @Query("page") Integer page,
                                           @Query("per_page") Integer perPage);

    @GET("categories")
    Call<CategoriesEnvelope> getCategories();

    @GET("me/likes")
    Call<VideosEnvelope> findLikedVideos(@Query("query") String query,
                                         @Query("sort") String sort,
                                         @Query("direction") String direction,
                                         @Query("page") Integer page,
                                         @Query("per_page") Integer perPage);

    @GET("me/watchlater")
    Call<VideosEnvelope> findWatchLaterVideos(@Query("query") String query,
                                              @Query("sort") String sort,
                                              @Query("direction") String direction,
                                              @Query("page") Integer page,
                                              @Query("per_page") Integer perPage);

    @GET("me/feed")
    Call<FeedItemsEnvelope> findMyFeedVideos(@Query("page") Integer page,
                                             @Query("per_page") Integer perPage);

    @PUT("me/likes/{videoId}")
    Call<ResponseBody> likeVideo(@Path("videoId") String videoId);


    @DELETE("me/likes/{videoId}")
    Call<ResponseBody> unlikeVideo(@Path("videoId") String videoId);

    @PUT("me/watchlater/{videoId}")
    Call<ResponseBody> addVideoToWatchLater(@Path("videoId") String videoId);


    @DELETE("me/watchlater/{videoId}")
    Call<ResponseBody> removeVideoFromWatchLater(@Path("videoId") String videoId);

} public class AuthorizedNetworkInterceptor implements Interceptor {

    // region Member Variables
    private AccessToken accessToken;
    // endregion

    // region Constructors
    public AuthorizedNetworkInterceptor(AccessToken accessToken) {
        this.accessToken = accessToken;
    }
    // endregion

    @Override
    public Response intercept(Chain chain) throws IOException {
        if (chain != null) {
            Request originalRequest = chain.request();

            if (accessToken != null) {
                Map<String, String> headersMap = new HashMap<>();
                headersMap.put("Authorization", String.format("%s %s", accessToken.getTokenType(), accessToken.getAccessToken()));
                headersMap.put("Accept", "application/vnd.vimeo.*+json; version=3.2");
                Request modifiedRequest = RequestUtility.updateHeaders(originalRequest, headersMap);

                return chain.proceed(modifiedRequest);
            } else {
                return chain.proceed(originalRequest);
            }
        }

        return null;
    }
} public class FeedItem {

    // region Fields
    @SerializedName("uri")
    public Object uri;
    @SerializedName("clip")
    public Video clip;
    @SerializedName("type")
    public String type;
    @SerializedName("time")
    public String time;
    @SerializedName("metadata")
    public Metadata metadata;
    @SerializedName("channel")
    public Channel channel;
    // endregion

    // region Getters

    public Object getUri() {
        return uri;
    }

    public Video getClip() {
        return clip;
    }

    public String getType() {
        return type;
    }

    public String getTime() {
        return time;
    }

    public Metadata getMetadata() {
        return metadata;
    }

    public Channel getChannel() {
        return channel;
    }

    // endregion

    // region Setters

    public void setUri(Object uri) {
        this.uri = uri;
    }

    public void setClip(Video clip) {
        this.clip = clip;
    }

    public void setType(String type) {
        this.type = type;
    }

    public void setTime(String time) {
        this.time = time;
    }

    public void setMetadata(Metadata metadata) {
        this.metadata = metadata;
    }

    public void setChannel(Channel channel) {
        this.channel = channel;
    }

    // endregion
} public class FeedItemsEnvelope {

    // region Fields
    @SerializedName("total")
    private int total;
    @SerializedName("page")
    private int page;
    @SerializedName("per_page")
    private int perPage;
    @SerializedName("paging")
    private Paging paging;
    @SerializedName("data")
    private List<FeedItem> feedItems;
    // endregion

    // region Getters

    public int getTotal() {
        return total;
    }

    public int getPage() {
        return page;
    }

    public int getPerPage() {
        return perPage;
    }

    public Paging getPaging() {
        return paging;
    }

    public List<FeedItem> getFeedItems() {
        return feedItems;
    }

    // endregion

    // region Setters

    public void setTotal(int total) {
        this.total = total;
    }

    public void setPage(int page) {
        this.page = page;
    }

    public void setPerPage(int perPage) {
        this.perPage = perPage;
    }

    public void setPaging(Paging paging) {
        this.paging = paging;
    }

    public void setFeedItems(List<FeedItem> feedItems) {
        this.feedItems = feedItems;
    }

    // endregion
} public class Video implements Parcelable {

    // region Constants
    public static final String PATTERN = "yyyy-MM-dd'T'hh:mm:ssZ";
    // endregion

    // region Fields
    @SerializedName("uri")
    private String uri;
    @SerializedName("name")
    private String name;
    @SerializedName("description")
    private String description;
    @SerializedName("link")
    private String link;
    @SerializedName("duration")
    private int duration;
    @SerializedName("width")
    private int width;
    @SerializedName("language")
    private String language;
    @SerializedName("height")
    private int height;
    @SerializedName("embed")
    private Embed embed;
    @SerializedName("created_time")
    private String createdTime;
    @SerializedName("modified_time")
    private String modifiedTime;
    @SerializedName("content_rating")
    private List<String> contentRating;
//    @SerializedName("license")
//    private Object license;
    @SerializedName("pictures")
    private Pictures pictures;
    @SerializedName("tags")
    private List<Tag> tags;
    @SerializedName("stats")
    private Stats stats;
    @SerializedName("metadata")
    private Metadata metadata;
    @SerializedName("user")
    private User user;
//    @SerializedName("app")
//    private Object app;
    @SerializedName("status")
    private String status;
//    @SerializedName("embed_presets")
//    private Object embedPresets;
    // endregion

    // region Constructors
    public Video() {
    }

    protected Video(Parcel in) {
        this.uri = in.readString();
        this.name = in.readString();
        this.description = in.readString();
        this.link = in.readString();
        this.duration = in.readInt();
        this.width = in.readInt();
        this.language = in.readString();
        this.height = in.readInt();
        this.embed = in.readParcelable(Embed.class.getClassLoader());
        this.createdTime = in.readString();
        this.modifiedTime = in.readString();
        this.contentRating = in.createStringArrayList();
        this.pictures = in.readParcelable(Pictures.class.getClassLoader());
        this.tags = in.createTypedArrayList(Tag.CREATOR);
        this.stats = in.readParcelable(Stats.class.getClassLoader());
        this.metadata = in.readParcelable(Metadata.class.getClassLoader());
        this.user = in.readParcelable(User.class.getClassLoader());
        this.status = in.readString();
    }
    // endregion

    // region Getters

    public String getUri() {
        return uri;
    }

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }

    public String getLink() {
        return link;
    }

    public int getDuration() {
        return duration;
    }

    public int getWidth() {
        return width;
    }

    public String getLanguage() {
        return language;
    }

    public int getHeight() {
        return height;
    }

    public Embed getEmbed() {
        return embed;
    }

    public String getCreatedTime() {
        return createdTime;
    }

    public String getModifiedTime() {
        return modifiedTime;
    }

    public List<String> getContentRating() {
        return contentRating;
    }

    public Pictures getPictures() {
        return pictures;
    }

    public List<Tag> getTags() {
        return tags;
    }

    public Stats getStats() {
        return stats;
    }

    public Metadata getMetadata() {
        return metadata;
    }

    public User getUser() {
        return user;
    }

    public String getStatus() {
        return status;
    }

    public long getId() {
        long id = -1L;
        String uri = getUri();
        if (!TextUtils.isEmpty(uri)) {
            String lastPathSegment = Uri.parse(uri).getLastPathSegment();
            id = Long.parseLong(lastPathSegment);
        }
        return id;
    }

    public String getThumbnailUrl(){
        String thumbnailUrl = "";
        if (pictures != null) {
            List<Size> sizes = pictures.getSizes();
            if (sizes != null && sizes.size() > 0) {
                Size size = sizes.get(sizes.size() - 1);
                if (size != null) {
                    thumbnailUrl = size.getLink();
                }
            }
        }
        return thumbnailUrl;
    }

    public String getFormattedDuration(){
        long minutes = duration / 60;
        long seconds = duration % 60;

        String formattedDuration;
        if (minutes == 0L) {
            if (seconds > 0L) {
                if (seconds < 10L)
                    formattedDuration = String.format("0:0%s", String.valueOf(seconds));
                else
                    formattedDuration = String.format("0:%s", String.valueOf(seconds));
            } else {
                formattedDuration = "0:00";
            }
        } else {
            if (seconds > 0L) {
                if (seconds < 10L)
                    formattedDuration = String.format("%s:0%s", String.valueOf(minutes), String.valueOf(seconds));
                else
                    formattedDuration = String.format("%s:%s", String.valueOf(minutes), String.valueOf(seconds));
            } else {
                formattedDuration = String.format("%s:00", String.valueOf(minutes));
            }
        }
        return formattedDuration;
    }

    public String getCaption(){
        String caption = "";
        int viewCount = 0;
        if (stats != null) {
            viewCount = stats.getPlays();
        }

        String formattedCreatedTime = DateUtility.getFormattedDateAndTime(DateUtility.getCalendar(createdTime, PATTERN), DateUtility.FORMAT_RELATIVE);

        if (viewCount > 0) {
            String formattedViewCount = formatViewCount(viewCount);
            if(!TextUtils.isEmpty(createdTime))
                caption = String.format("%s \u2022 %s", formattedViewCount, formattedCreatedTime);
            else
                caption = formattedViewCount;

        } else {
            caption = formattedCreatedTime;
        }
        return caption;
    }

    private String formatViewCount(int viewCount) {
        String formattedViewCount = "";

        if (viewCount < 1000000000 && viewCount >= 1000000) {
            formattedViewCount = String.format("%dM views", viewCount / 1000000);
        } else if (viewCount < 1000000 && viewCount >= 1000) {
            formattedViewCount = String.format("%dK views", viewCount / 1000);
        } else if (viewCount < 1000 && viewCount > 1) {
            formattedViewCount = String.format("%d views", viewCount);
        } else if (viewCount == 1) {
            formattedViewCount = String.format("%d view", viewCount);
        }

        return formattedViewCount;
    }

    public boolean isLiked(){
        boolean isLiked = false;
        if (metadata != null) {
            Interactions interactions = metadata.getInteractions();
            if (interactions != null) {
                Interaction likeInteraction = interactions.getLike();
                if (likeInteraction != null) {
                    if (likeInteraction.isAdded()) {
                        isLiked = true;
                    }
                }
            }
        }
        return isLiked;
    }

    public boolean isAddedToWatchLater(){
        boolean isAddedToWatchLater = false;
        if (metadata != null) {
            Interactions interactions = metadata.getInteractions();
            if (interactions != null) {
                Interaction watchLaterInteraction = interactions.getWatchlater();
                if (watchLaterInteraction != null) {
                    if (watchLaterInteraction.isAdded()) {
                        isAddedToWatchLater = true;
                    }
                }
            }
        }
        return isAddedToWatchLater;
    }

    public String getFormattedViewCount(){
        String formattedViewCount = "";

        int viewCount = 0;
        if (stats != null) {
            viewCount = stats.getPlays();
        }

        if (viewCount > 0) {
            formattedViewCount = NumberFormat.getNumberInstance(Locale.US).format(viewCount);
            if (viewCount > 1) {
                formattedViewCount = String.format("%s views", formattedViewCount);
            } else {
                formattedViewCount = String.format("%s view", formattedViewCount);
            }
        }
        return formattedViewCount;
    }

    public String getFormattedCreatedTime(){
        String formattedCreatedTime = DateUtility.getFormattedDateAndTime(DateUtility.getCalendar(createdTime, PATTERN), DateUtility.FORMAT_RELATIVE);
        if(!TextUtils.isEmpty(formattedCreatedTime))
            formattedCreatedTime = String.format("Uploaded %s", formattedCreatedTime);
        return formattedCreatedTime;
    }

    public List<String> getCanonicalTags(){
        ArrayList<String> canonicalTags = new ArrayList<>();
        if (tags != null && tags.size() > 0) {
            for (Tag tag : tags) {
                String canonicalTag = tag.getCanonical();
                if (canonicalTag.length() > 0) {
                    canonicalTags.add(canonicalTag);
                }
            }
        }
        return canonicalTags;
    }

    public String getFormattedDescription(){
        String formattedDescription = "";
        if(!TextUtils.isEmpty(description))
            formattedDescription = description.trim();
        return formattedDescription;
    }
    // endregion

    // region Setters

    public void setUri(String uri) {
        this.uri = uri;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public void setLink(String link) {
        this.link = link;
    }

    public void setDuration(int duration) {
        this.duration = duration;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public void setLanguage(String language) {
        this.language = language;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public void setEmbed(Embed embed) {
        this.embed = embed;
    }

    public void setCreatedTime(String createdTime) {
        this.createdTime = createdTime;
    }

    public void setModifiedTime(String modifiedTime) {
        this.modifiedTime = modifiedTime;
    }

    public void setContentRating(List<String> contentRating) {
        this.contentRating = contentRating;
    }

    public void setPictures(Pictures pictures) {
        this.pictures = pictures;
    }

    public void setTags(List<Tag> tags) {
        this.tags = tags;
    }

    public void setStats(Stats stats) {
        this.stats = stats;
    }

    public void setMetadata(Metadata metadata) {
        this.metadata = metadata;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    // endregion

    // region Parcelable Methods
    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.uri);
        dest.writeString(this.name);
        dest.writeString(this.description);
        dest.writeString(this.link);
        dest.writeInt(this.duration);
        dest.writeInt(this.width);
        dest.writeString(this.language);
        dest.writeInt(this.height);
        dest.writeParcelable(this.embed, flags);
        dest.writeString(this.createdTime);
        dest.writeString(this.modifiedTime);
        dest.writeStringList(this.contentRating);
        dest.writeParcelable(this.pictures, flags);
        dest.writeTypedList(this.tags);
        dest.writeParcelable(this.stats, flags);
        dest.writeParcelable(this.metadata, flags);
        dest.writeParcelable(this.user, flags);
        dest.writeString(this.status);
    }
    // endregion

    public static final Parcelable.Creator<Video> CREATOR = new Parcelable.Creator<Video>() {
        @Override
        public Video createFromParcel(Parcel source) {
            return new Video(source);
        }

        @Override
        public Video[] newArray(int size) {
            return new Video[size];
        }
    };
} public class LoopPrefs {

    // region Constants
    private static final String LOOP_PREF = "LOOP_PREF";
    private static final String KEY_ACCESS_TOKEN = "KEY_ACCESS_TOKEN";
    private static final String KEY_AUTHORIZED_USER = "KEY_AUTHORIZED_USER";
    // endregion

    // region Constructors
    private LoopPrefs() {
        //no instance
    }

    // region Getters
    public static AccessToken getAccessToken(Context context) {
        SharedPreferences preferences = getSharedPreferences(context);
        Gson gson = new Gson();
        String json = preferences.getString(KEY_ACCESS_TOKEN, "");
        AccessToken accessToken = gson.fromJson(json, AccessToken.class);
        return accessToken;
    }

    public static AuthorizedUser getAuthorizedUser(Context context) {
        SharedPreferences preferences = getSharedPreferences(context);
        Gson gson = new Gson();
        String json = preferences.getString(KEY_AUTHORIZED_USER, "");
        AuthorizedUser authorizedUser = gson.fromJson(json, AuthorizedUser.class);
        return authorizedUser;
    }
    // endregion

    // region Setters
    public static void saveAccessToken(Context context, AccessToken accessToken) {
        SharedPreferences.Editor editor = getEditor(context);
        Gson gson = new Gson();
        String json = gson.toJson(accessToken);
        editor.putString(KEY_ACCESS_TOKEN, json)
            .apply();
    }

    public static void saveAuthorizedUser(Context context, AuthorizedUser authorizedUser) {
        SharedPreferences.Editor editor = getEditor(context);
        Gson gson = new Gson();
        String json = gson.toJson(authorizedUser);
        editor.putString(KEY_AUTHORIZED_USER, json)
            .apply();
    }
    // endregion

    public static void signOut(Context context) {
        SharedPreferences.Editor editor = getEditor(context);
        editor.remove(KEY_ACCESS_TOKEN)
            .remove(KEY_AUTHORIZED_USER)
            .apply();
    }

    // region Helper Methods
    private static SharedPreferences.Editor getEditor(Context context) {
        SharedPreferences preferences = getSharedPreferences(context);
        return preferences.edit();
    }

    private static SharedPreferences getSharedPreferences(Context context) {
        return context.getSharedPreferences(LOOP_PREF, Context.MODE_PRIVATE);
    }
    // endregion
} public class LoadingImageView extends ImageView {

    // region Member Variables
    private Paint imagePaint;
    private BitmapShader shader;
    private Paint paint;
    private int imageHeight, imageWidth;
    private int maskColor = Color.TRANSPARENT;
    private ClipDrawable clipDrawable;
    private Drawable maskDrawable;
    private ObjectAnimator animator;
    private int gravity = Gravity.LEFT;
    private int orientaion = ClipDrawable.HORIZONTAL;
    private float scaleX,scaleY;
    private boolean autoStart = true;
    private long animDuration = 800;
    private float[] f = new float[9];
    // endregion

    // region Constructors
    public LoadingImageView(Context context) {
        this(context, null);
    }

    public LoadingImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LoadingImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
        initAttrs(context, attrs);
    }
    // endregion

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        getImageMatrix().getValues(f);
        scaleX = f[Matrix.MSCALE_X];
        scaleY = f[Matrix.MSCALE_Y];
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //canvas.drawRect(0, maskHeight, getWidth(), getHeight(), paint);
        canvas.save();
        canvas.scale(scaleX, scaleY);
        clipDrawable.setBounds(getDrawable().getBounds());
        clipDrawable.draw(canvas);
        canvas.restore();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        stopAnim();
    }

    @Override
    public void setImageDrawable(Drawable drawable) {
        super.setImageDrawable(drawable);
        if(maskColor != Color.TRANSPARENT){
            init();
            initMaskBitmap(maskColor);
            initAnim();
        }
    }

    // region Helper Methods
    private void initAttrs(Context context, AttributeSet attrs){
        if(attrs == null){
            return;
        }
        TypedArray t = context.obtainStyledAttributes(attrs, R.styleable.LoadingImageView);
        maskColor = t.getColor(R.styleable.LoadingImageView_mask_color, maskColor);
        autoStart = t.getBoolean(R.styleable.LoadingImageView_anim_auto_start, autoStart);
        setMaskColor(maskColor);
        t.recycle();
    }

    private void init() {
        if (paint == null) {
            imagePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            imagePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
            paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        }
    }

    private Bitmap combineImages(Bitmap bgd, Bitmap fg) {
        Bitmap bmp = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bmp);
        canvas.drawBitmap(bgd, 0, 0, null);
        canvas.drawBitmap(fg, 0, 0, imagePaint);
        return bmp;
    }

    private void initMaskBitmap(int maskColor) {
        Drawable drawable = getDrawable();
        if(drawable == null){
            return;
        }
        Bitmap bgd = ((BitmapDrawable) drawable).getBitmap();
        imageWidth = drawable.getIntrinsicWidth();
        imageHeight = drawable.getIntrinsicHeight();

        Bitmap fg = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888);
        Canvas fgCanvas = new Canvas(fg);
        fgCanvas.drawColor(maskColor);


        Bitmap bitmap = combineImages(bgd, fg);
        maskDrawable = new BitmapDrawable(bitmap);
        clipDrawable = new ClipDrawable(maskDrawable, gravity, orientaion);

        //shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        //paint.setShader(shader);
    }

    private void setMaskHeight(int y) {
        int maskHeight = y;
        invalidate();
    }

    private void initAnim() {
        stopAnim();
        //animator = ObjectAnimator.ofInt(this, "maskHeight", 0, imageHeight);
        animator = ObjectAnimator.ofInt(clipDrawable, "level", 0, 10000);
        animator.setDuration(animDuration);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setRepeatMode(ValueAnimator.RESTART);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                invalidate();
            }
        });
        if(autoStart){
            animator.start();
        }
    }

    private void stopAnim(){
        if(animator != null){
            animator.cancel();
        }
    }

    /**
     * 设置动画时长
     */
    public void setMaskAnimDuration(long duration) {
        animDuration = duration;
        initAnim();
    }

    /**
     * 开始播放动画
     */
    public void startMaskAnim() {
        if (animator != null) {
            animator.start();
        }
    }

    public void setMaskColor(int color) {
        initMaskBitmap(color);
        initAnim();
    }

    /**
     * 设置方向
     * @param orientation {@link MaskOrientation}
     */
    public void setMaskOrientation(int orientation){
        switch (orientation){
            case MaskOrientation.LeftToRight:
                gravity = Gravity.LEFT;
                orientaion = ClipDrawable.HORIZONTAL;
                break;
            case MaskOrientation.RightToLeft:
                gravity = Gravity.RIGHT;
                orientaion = ClipDrawable.HORIZONTAL;
                break;
            case MaskOrientation.TopToBottom:
                gravity = Gravity.TOP;
                orientaion = ClipDrawable.VERTICAL;
                break;
            case MaskOrientation.BottomToTop:
            default:
                gravity = Gravity.BOTTOM;
                orientaion = ClipDrawable.VERTICAL;
                break;
        }
        if(maskDrawable == null){
            return;
        }
        clipDrawable = new ClipDrawable(maskDrawable, gravity, orientaion);
        initAnim();
    }

    public int getMaskOrientation() {
        int maskOrientation = MaskOrientation.LeftToRight;
        return maskOrientation;
    }
    // endregion

    // region Inner Classes

    public static final class MaskOrientation{
        public static final int LeftToRight = 1;
        public static final int RightToLeft = 2;
        public static final int TopToBottom = 3;
        public static final int BottomToTop = 4;
    }
    // endregion

} public class NetworkLogUtility {

    public static void logFailure(Call call, Throwable throwable){
        if(call != null){
            if (call.isCanceled())
                Timber.e("Request was cancelled");

//            Request request = call.request();
//            if(request != null){
//                HttpUrl httpUrl = request.url();
//                if(httpUrl != null){
//                    Timber.e(String.format("logFailure() : %s : failed", httpUrl));
//                }
//            }
        }

        if(throwable != null){
            Throwable cause = throwable.getCause();
//            String message = throwable.getMessage();

            if (cause != null) {
                Timber.e(String.format("logFailure() : cause.toString() : %s", cause.toString()));
            }

//            if (!TextUtils.isEmpty(message)) {
//                Timber.e(String.format("logFailure() : message : %s", message));
//            }

//            throwable.printStackTrace();
        }
    }
} public class NetworkUtility {

    public static boolean isNetworkAvailable(Context context){
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService( Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = cm.getActiveNetworkInfo();
        return networkInfo != null && networkInfo.isConnected();
    }

    public static boolean isKnownException(Throwable t){
        return (t instanceof ConnectException
                || t instanceof UnknownHostException
                || t instanceof SocketTimeoutException
                || t instanceof IOException);
    }

}
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.etiennelawlor.loop.R;
import com.etiennelawlor.loop.activities.VideoDetailsActivity;
import com.etiennelawlor.loop.adapters.VideosAdapter;
import com.etiennelawlor.loop.models.AccessToken;
import com.etiennelawlor.loop.network.ServiceGenerator;
import com.etiennelawlor.loop.network.VimeoService;
import com.etiennelawlor.loop.network.interceptors.AuthorizedNetworkInterceptor;
import com.etiennelawlor.loop.network.models.response.FeedItem;
import com.etiennelawlor.loop.network.models.response.FeedItemsEnvelope;
import com.etiennelawlor.loop.network.models.response.Video;
import com.etiennelawlor.loop.prefs.LoopPrefs;
import com.etiennelawlor.loop.ui.LoadingImageView;
import com.etiennelawlor.loop.utilities.NetworkLogUtility;
import com.etiennelawlor.loop.utilities.NetworkUtility;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
import jp.wasabeef.recyclerview.animators.SlideInUpAnimator;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
package com.etiennelawlor.loop.fragments;





/**
 * Created by etiennelawlor on 5/23/15.
 */
public class MyFeedFragment extends BaseFragment implements VideosAdapter.OnItemClickListener, VideosAdapter.OnReloadClickListener {

    // region Constants
    public static final int PAGE_SIZE = 30;
    public static final String KEY_TRANSITION = "KEY_TRANSITION";
    // endregion

    // region Views
    @BindView(R.id.rv)
    RecyclerView recyclerView;
    @BindView(android.R.id.empty)
    View emptyView;
    @BindView(R.id.loading_iv)
    LoadingImageView loadingImageView;
    @BindView(R.id.error_ll)
    LinearLayout errorLinearLayout;
    @BindView(R.id.error_tv)
    TextView errorTextView;
    @BindView(R.id.reload_btn)
    Button reloadButton;
    // endregion

    // region Member Variables
    private boolean isLastPage = false;
    private int currentPage = 1;
//    private int selectedSortByKey = 0;
//    private int selectedSortOrderKey = 1;
    private boolean isLoading = false;
//    private String sortByValue = "relevant";
//    private String sortOrderValue = "desc";
//    private String filter;
    private VideosAdapter videosAdapter;
//    private String query;
    private Unbinder unbinder;
    private LinearLayoutManager layoutManager;
    private VimeoService vimeoService;
    // endregion

    // region Listeners
    private RecyclerView.OnScrollListener recyclerViewOnScrollListener = new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            int visibleItemCount = layoutManager.getChildCount();
            int totalItemCount = layoutManager.getItemCount();
            int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();

            if (!isLoading && !isLastPage) {
                if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
                        && firstVisibleItemPosition >= 0
                        && totalItemCount >= PAGE_SIZE) {
                    loadMoreItems();
                }
            }
        }
    };

    @OnClick(R.id.reload_btn)
    public void onReloadButtonClicked() {
        errorLinearLayout.setVisibility(View.GONE);
        loadingImageView.setVisibility(View.VISIBLE);

        Call findMyFeedVideosCall = vimeoService.findMyFeedVideos(currentPage, PAGE_SIZE);
        calls.add(findMyFeedVideosCall);
        findMyFeedVideosCall.enqueue(findMyFeedVideosFirstFetchCallback);
    }
    // endregion

    // region Callbacks
    private Callback<FeedItemsEnvelope> findMyFeedVideosFirstFetchCallback = new Callback<FeedItemsEnvelope>() {
        @Override
        public void onResponse(Call<FeedItemsEnvelope> call, Response<FeedItemsEnvelope> response) {
            loadingImageView.setVisibility(View.GONE);
            isLoading = false;

            if (!response.isSuccessful()) {
                int responseCode = response.code();
                if(responseCode == 504) { // 504 Unsatisfiable Request (only-if-cached)
                    errorTextView.setText("Can't load data.\nCheck your network connection.");
                    errorLinearLayout.setVisibility(View.VISIBLE);
                }
                return;
            }

            FeedItemsEnvelope feedItemsEnvelope = response.body();
            if (feedItemsEnvelope != null) {
                List<FeedItem> feedItems = feedItemsEnvelope.getFeedItems();
                if (feedItems != null) {
                    List<Video> videos = new ArrayList<>();