com.sociablue.nanodegree_p1.MovieListFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.sociablue.nanodegree_p1.MovieListFragment.java

Source

/*
 * Copyright (c) 2015. Sociablue Labs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sociablue.nanodegree_p1;

import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.graphics.Color;

import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.FragmentManager;
import android.transition.ChangeBounds;
import android.transition.ChangeTransform;
import android.transition.Transition;
import android.transition.TransitionSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.view.animation.TranslateAnimation;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.RelativeLayout;

import com.sociablue.nanodegree_p1.Constants.MdbConstants;
import com.sociablue.nanodegree_p1.base.BaseFragment;
import com.sociablue.nanodegree_p1.events.MenuItemClicked;
import com.sociablue.nanodegree_p1.events.SharedImageLoadedEvent;
import com.sociablue.nanodegree_p1.managers.EventManager;
import com.sociablue.nanodegree_p1.menu.CustomRotatingMenu;
import com.sociablue.nanodegree_p1.menu.MenuAdapter;
import com.sociablue.nanodegree_p1.menu.MenuItem;
import com.sociablue.nanodegree_p1.models.DiscoverMovieResponse;
import com.sociablue.nanodegree_p1.models.Movie;
import com.sociablue.nanodegree_p1.managers.NetworkManager;
import com.sociablue.nanodegree_p1.utils.ReverseInterpolator;
import com.sociablue.nanodegree_p1.utils.Utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import retrofit.Callback;
import retrofit.RetrofitError;
import retrofit.client.Response;

/**
 * Created by okikejiani on 2015-08-27.
 * List movies in a grid view
 */
public class MovieListFragment extends BaseFragment {

    MovieListAdapter mMovieListAdapter;
    GridView mMovieListGridView;
    List<Movie> mMovieList = new ArrayList<>();
    View mClickedView;
    String mSort = MdbConstants.SORT_BY_POPULARITY_DESC;

    CustomRotatingMenu mMenu;
    ImageView moviePoster;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        loadMovieData(mSort);
    }

    /*
     * Adds Fab Click handler that toggles mMenu open and closed. Also Initializes FAB rotation animations
     */
    private void initializeFab(View rootView) {
        final RelativeLayout buttonContainer = (RelativeLayout) rootView.findViewById(R.id.menu_button_container);
        final FloatingActionButton Fab = (FloatingActionButton) rootView.findViewById(R.id.fab);
        final ImageView bottomBar = (ImageView) rootView.findViewById(R.id.menu_bottom_bar);
        final ImageView topBar = (ImageView) rootView.findViewById(R.id.menu_top_bar);
        Fab.setOnClickListener(new View.OnClickListener() {
            @TargetApi(Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void onClick(View v) {

                //Menu Button Icon Animation
                //Setting up necessary variables
                long animationDuration = 500;
                float containerHeight = buttonContainer.getHeight();
                float containerCenterY = containerHeight / 2;
                float containerCenterX = buttonContainer.getWidth() / 2;
                float topBarCenter = topBar.getTop() + topBar.getHeight() / 2;
                float widthOfBar = topBar.getWidth();
                float heightOfBar = topBar.getHeight();
                final float distanceBetweenBars = (containerCenterY - topBarCenter);

                /**
                 *TODO: Refactor block of code to use Value Property Animator. Should be more efficient to animate multiple properties
                 *and objects at the same time. Also, will try to break intialization into smaller functions.
                 */

                //Setting up animations of hamburger bars and rotation

                /**
                 * Animation For Top Menu Button Icon Bar. Sliding from the top to rest on top of the middle bar.
                 * Y Translation is 1/2 the height of the hamburger bar minus the distance.
                 * Subtracting the distance from the height because the distance between bars is
                 * calculated of the exact center of the button.
                 * With out the subtraction the bar would translate slightly below the middle bar.
                 */
                float yTranslation = heightOfBar / 2 - distanceBetweenBars;
                float xTranslation = widthOfBar / 2 + heightOfBar / 2;
                TranslateAnimation topBarTranslationAnim = new TranslateAnimation(Animation.ABSOLUTE, 0f,
                        Animation.ABSOLUTE, 0F, Animation.ABSOLUTE, 0f, Animation.ABSOLUTE, distanceBetweenBars);
                topBarTranslationAnim.setDuration((long) (animationDuration * 0.8));
                topBarTranslationAnim.setFillAfter(true);

                //Animation for bottom hamburger bar. Translates and Rotates to create 'X'
                AnimationSet bottomBarAnimation = new AnimationSet(true);
                bottomBarAnimation.setFillAfter(true);

                //Rotate to create cross. (The cross becomes the X after the button rotation completes"
                RotateAnimation bottomBarRotationAnimation = new RotateAnimation(0f, 90f,
                        Animation.RELATIVE_TO_SELF, 1f, Animation.RELATIVE_TO_SELF, 1f);
                bottomBarRotationAnimation.setDuration(animationDuration);
                bottomBarAnimation.addAnimation(bottomBarRotationAnimation);

                //Translate to correct X alignment
                TranslateAnimation bottomBarTranslationAnimation = new TranslateAnimation(Animation.ABSOLUTE, 0f,
                        Animation.ABSOLUTE, -xTranslation, Animation.ABSOLUTE, 0f, Animation.ABSOLUTE,
                        -yTranslation);
                bottomBarTranslationAnimation.setDuration(animationDuration);
                bottomBarAnimation.addAnimation(bottomBarTranslationAnimation);

                //Button Specific Animations
                //Rotate Button Container
                RotateAnimation containerRotationAnimation = new RotateAnimation(0, 135f,
                        Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
                containerRotationAnimation.setDuration(animationDuration);
                containerRotationAnimation.setFillAfter(true);

                //Animate change of button color between Active and Disabled colors that have been
                //defined in color.xml
                int activeColor = getResources().getColor(R.color.active_button);
                int disabledColor = getResources().getColor(R.color.disabled_button);

                //Need to use ValueAnimator because property animator does not support BackgroundTint
                ValueAnimator buttonColorAnimation = ValueAnimator.ofArgb(activeColor, disabledColor);
                buttonColorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        Fab.setBackgroundTintList(ColorStateList.valueOf((int) animation.getAnimatedValue()));
                    }
                });
                buttonColorAnimation.setDuration(animationDuration);

                //Start all the animations
                topBar.startAnimation(topBarTranslationAnim);
                bottomBar.startAnimation(bottomBarAnimation);
                buttonContainer.startAnimation(containerRotationAnimation);
                buttonColorAnimation.start();

                //Toogle mMenu open and closed
                if (mMenu.isOpen()) {
                    //If mMenu is open, do the reverse of the animation
                    containerRotationAnimation
                            .setInterpolator(new ReverseInterpolator(new AccelerateInterpolator()));
                    topBarTranslationAnim.setInterpolator(new ReverseInterpolator(new AccelerateInterpolator()));
                    bottomBarAnimation.setInterpolator(new ReverseInterpolator(new AccelerateInterpolator()));
                    buttonColorAnimation.setInterpolator(new ReverseInterpolator(new LinearInterpolator()));
                    mMenu.close();
                } else {
                    bottomBarAnimation.setInterpolator(new AccelerateInterpolator());
                    mMenu.open();
                }
            }
        });
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        // Inflate the layout for this fragment
        final View rootView = inflater.inflate(R.layout.fragment_movie_list, container, false);

        RelativeLayout buttonContainer = (RelativeLayout) rootView.findViewById(R.id.menu_button_container);
        initializeFab(rootView);

        //Setup Menu
        mMenu = new CustomRotatingMenu(getActivity(), buttonContainer, (ViewGroup) rootView);
        MenuAdapter menuAdapter = new MenuAdapter(getActivity(), R.layout.menu_item);
        menuAdapter.addMenuItem(
                new MenuItem("Most Recent", R.drawable.ic_launcher, 1, MdbConstants.SORT_BY_RELEASE_DATE_DESC));
        menuAdapter.addMenuItem(
                new MenuItem("Highest Rated", R.drawable.ic_launcher, 2, MdbConstants.SORT_BY_VOTE_AVERAGE_DESC));
        menuAdapter.addMenuItem(
                new MenuItem("Most Popular", R.drawable.ic_launcher, 3, MdbConstants.SORT_BY_POPULARITY_DESC));
        mMenu.bindAdapter(menuAdapter);
        ((ViewGroup) rootView).addView(mMenu, ((ViewGroup) rootView).indexOfChild(buttonContainer) - 1);
        mMovieListAdapter = new MovieListAdapter(getActivity(), mMovieList);
        mMovieListGridView = (GridView) rootView.findViewById(R.id.movielistgridview);
        configureGridView(rootView);
        //loadMovieData(mSort);
        return rootView;
    }

    /*
     * Calls network manager to get movie data and passes data to the Movie List Adapter
     */
    private void loadMovieData(String sortBy) {
        //NetworkManager networkManager = ((MainActivity) getActivity()).getNetworkManager();
        NetworkManager networkManager = new NetworkManager();
        HashMap<String, Object> params = new HashMap<>();
        params.put(MdbConstants.SORT_BY, sortBy);
        params.put(MdbConstants.VOTE_COUNT_GTE, MdbConstants.MIN_VOTE_COUNT);
        //params.put(MdbConstants.SORT_BY, MdbConstants.SORT_BY_RELEASE_DATE_DESC);
        // params.put(MdbConstants.SORT_BY, MdbConstants.SORT_BY_POPULARITY_DESC);
        // params.put(MdbConstants.SORT_BY, MdbConstants.SORT_BY_VOTE_AVERAGE_DESC);

        //params.put(MdbConstants.SORT_BY, MdbConstants.SORT_BY_REVENUE_DESC);

        networkManager.discoverMovies(params, new Callback<DiscoverMovieResponse>() {
            @Override
            public void success(DiscoverMovieResponse payload, Response response) {
                mMovieList = payload.getResults();
                mMovieListAdapter.updateList(mMovieList);
                mMovieListAdapter.notifyDataSetChanged();
            }

            @Override
            public void failure(RetrofitError error) {
                //TODO: Gracefully handle failures
                Utils.log(error.toString());
            }
        });
    }

    /*
     * Initializes gridview. binds adapter and adds click listener handler
     * Onclick Detail Pager Fragment is created and Exit Transition is started.
     */
    private void configureGridView(final View rootView) {
        setGridColumns();
        mMovieListGridView.setAdapter(mMovieListAdapter);
        mMovieListGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                MovieDetailPagerFragment moviePagerFragment = new MovieDetailPagerFragment();
                moviePagerFragment.setMovieList(mMovieList);
                moviePagerFragment.setStartPosition(position);
                mClickedView = view;
                setupTransitions(moviePagerFragment, (ImageView) view);
                int[] location = new int[2];
                view.getLocationInWindow(location);

                Rect positionRect = new Rect();

                int statusBarHeight = Utils.screenHeight() - rootView.getMeasuredHeight();

                view.getGlobalVisibleRect(positionRect);
                startFragmentExitTransition(view.getWidth(), view.getHeight(), location[0],
                        location[1] - statusBarHeight, mMovieList.get(position));
            }
        });

    }

    /*
     * Adds interstitial view that covers the screen leaving only the click image in view.
     */
    private void startFragmentExitTransition(int width, int height, int x, int y, Movie movie) {

        RelativeLayout rl = (RelativeLayout) getView().findViewById(R.id.movie_list_container);
        PosterImageView posterImageView = new PosterImageView(getActivity());
        View transitionOverlay = new View(getActivity());
        //        RelativeLayout.LayoutParams overlayParams =
        //              new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
        RelativeLayout.LayoutParams overlayParams = new RelativeLayout.LayoutParams(Utils.screenWidth(),
                Utils.screenHeight());
        transitionOverlay.setBackgroundColor(Color.BLACK);
        rl.addView(transitionOverlay, overlayParams);
        Utils.circularReveal(transitionOverlay, x + width / 2, y + height / 2);
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
        params.leftMargin = x;
        params.topMargin = y;

        posterImageView.setImageResource(MdbConstants.IMAGE_BASE_URL + "w500" + movie.getPosterPath());

        rl.addView(posterImageView, params);
    }

    @Override
    public void onStop() {
        EventManager.unregister(this);
        super.onStop();
    }

    @Override
    public void onResume() {
        //If returning from detail view, remove detail view fragment
        if (getFragmentManager().findFragmentByTag("detail") != null) {
            getFragmentManager().popBackStack("transaction2", FragmentManager.POP_BACK_STACK_INCLUSIVE);
        }
        super.onResume();
    }

    @Override
    public void onStart() {
        super.onStart();
        EventManager.register(this);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        //      TODO: set number of columns based on the screen size

        super.onConfigurationChanged(newConfig);
        setGridColumns();

    }

    //Check device orientation and then set number of columns
    private void setGridColumns() {
        int currentOrientation = getResources().getConfiguration().orientation;
        if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
            mMovieListGridView.setNumColumns(4);
        } else {
            mMovieListGridView.setNumColumns(2);
        }
    }

    /*
     * Configures the sharedelement transitions to apply to the exiting and entering fragments
     */
    public void setupTransitions(final MovieDetailPagerFragment newFragment, ImageView posterImageView) {
        //TODO: Shared Element Return Transition

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            // Inflate transitions to apply
            TransitionSet changeTransitionSet = new TransitionSet();
            Transition changeTransform = new ChangeTransform();
            changeTransitionSet.addTransition(changeTransform);
            changeTransitionSet.addTransition(new ChangeBounds());
            changeTransitionSet.setDuration(200);
            changeTransitionSet.setInterpolator(new AccelerateDecelerateInterpolator());
            this.setAllowEnterTransitionOverlap(true);
            this.setAllowReturnTransitionOverlap(true);

            // Setup enter transition on second fragment
            newFragment.setSharedElementEnterTransition(changeTransitionSet);
            newFragment.setAllowEnterTransitionOverlap(true);

            // Find the shared element (in Fragment A)
            moviePoster = posterImageView;
            moviePoster.setTransitionName("movie_poster");

            // Add and hide details view pager fragment. Adding fragment, creates view and downloads images.
            getFragmentManager().beginTransaction().add(R.id.movie_list_fragment, newFragment, "detail")
                    .addToBackStack("transaction2").hide(newFragment).commit();

        } else {
            // Code to run on older devices
            //TODO: Handle older devices
        }
    }

    /*
     * Removes Movie List Fragment when SharedElement has been loaded in the Details Pager Interstitial View.
     */
    public void onEvent(SharedImageLoadedEvent event) {
        //TODO: make sure the share element is the same element that this fragment is waiting for.
        //if(event.getSharedElement().getTag or TransitionName == )

        getFragmentManager().beginTransaction()
                .setCustomAnimations(R.anim.abc_slide_in_bottom, R.anim.abc_popup_exit)
                .addSharedElement(moviePoster, "movie_poster")
                .show(getFragmentManager().findFragmentByTag("detail")).remove(this).addToBackStack("transaction")
                .commit();
    }

    /*
     * Closes the menu and refreshes the grid view
     */
    public void onEvent(MenuItemClicked event) {
        //mMenu.close();
        FloatingActionButton fab = (FloatingActionButton) getView().findViewById(R.id.fab);
        //Close menu and trigger Fab animation
        fab.callOnClick();
        //Scroll to top of grid view
        mMovieListGridView.setSelection(0);
        loadMovieData((String) event.getMenuItem().getTag());
    }
}