com.google.samples.apps.iosched.videolibrary.VideoLibraryFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.google.samples.apps.iosched.videolibrary.VideoLibraryFragment.java

Source

/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * 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.google.samples.apps.iosched.videolibrary;

import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.util.Pair;
import android.support.v4.util.SparseArrayCompat;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.google.samples.apps.iosched.R;
import com.google.samples.apps.iosched.archframework.ModelWithLoaderManager;
import com.google.samples.apps.iosched.archframework.PresenterImpl;
import com.google.samples.apps.iosched.archframework.UpdatableView;
import com.google.samples.apps.iosched.injection.ModelProvider;
import com.google.samples.apps.iosched.provider.ScheduleContract;
import com.google.samples.apps.iosched.ui.widget.DrawShadowFrameLayout;
import com.google.samples.apps.iosched.ui.widget.recyclerview.ItemMarginDecoration;
import com.google.samples.apps.iosched.ui.widget.recyclerview.UpdatableAdapter;
import com.google.samples.apps.iosched.util.AccountUtils;
import com.google.samples.apps.iosched.util.AnalyticsHelper;
import com.google.samples.apps.iosched.util.ImageLoader;
import com.google.samples.apps.iosched.util.UIUtils;
import com.google.samples.apps.iosched.videolibrary.VideoLibraryModel.VideoLibraryQueryEnum;
import com.google.samples.apps.iosched.videolibrary.VideoLibraryModel.VideoLibraryUserActionEnum;
import com.google.samples.apps.iosched.videolibrary.data.VideoTrack;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;

/**
 * This Fragment displays all the videos of past Google I/O sessions in the form of a card for each
 * topics and a card for new videos of the current year.
 */
public class VideoLibraryFragment extends Fragment
        implements UpdatableView<VideoLibraryModel, VideoLibraryQueryEnum, VideoLibraryUserActionEnum> {

    private static final String TAG = makeLogTag(VideoLibraryFragment.class);

    private static final String VIDEO_LIBRARY_ANALYTICS_CATEGORY = "Video Library";

    private ImageLoader mImageLoader;

    private RecyclerView mCardList = null;

    private VideosAdapter mAdapter;

    private View mEmptyView = null;

    private List<UserActionListener> mListeners = new ArrayList<>();

    private SharedPreferences.OnSharedPreferenceChangeListener mSettingsChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
        @Override
        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
            if (AccountUtils.PREF_ACTIVE_ACCOUNT.equals(key)) {
                fireReloadUserVideosEvent();
            }
        }
    };

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.video_library_frag, container, false);
        mCardList = (RecyclerView) root.findViewById(R.id.videos_card_list);
        mCardList.setHasFixedSize(true);
        final int cardVerticalMargin = getResources().getDimensionPixelSize(R.dimen.spacing_normal);
        mCardList.addItemDecoration(new ItemMarginDecoration(0, cardVerticalMargin, 0, cardVerticalMargin));
        mEmptyView = root.findViewById(android.R.id.empty);
        getActivity().overridePendingTransition(0, 0);

        return root;
    }

    @Override
    public void displayData(final VideoLibraryModel model, final VideoLibraryQueryEnum query) {
        if ((VideoLibraryModel.VideoLibraryQueryEnum.VIDEOS == query
                || VideoLibraryModel.VideoLibraryQueryEnum.MY_VIEWED_VIDEOS == query) && model.hasVideos()) {

            if (mAdapter == null) {
                mAdapter = new VideosAdapter(getActivity(), model, mImageLoader, mListeners);
                mCardList.setAdapter(mAdapter);
            } else {
                mAdapter.update(model);
            }
        }
        mEmptyView.setVisibility((mAdapter != null && mAdapter.getItemCount() > 0) ? View.GONE : View.VISIBLE);
    }

    @Override
    public void displayErrorMessage(final VideoLibraryQueryEnum query) {
        // No UI changes upon query error
    }

    @Override
    public void displayUserActionResult(final VideoLibraryModel model, final VideoLibraryUserActionEnum userAction,
            final boolean success) {
        switch (userAction) {
        case VIDEO_PLAYED:
        case RELOAD_USER_VIDEOS:
            displayData(model, VideoLibraryQueryEnum.MY_VIEWED_VIDEOS);
            break;
        }
    }

    @Override
    public Uri getDataUri(final VideoLibraryQueryEnum query) {
        switch (query) {
        case VIDEOS:
            return ScheduleContract.Videos.CONTENT_URI;
        case MY_VIEWED_VIDEOS:
            return ScheduleContract.MyViewedVideos.CONTENT_URI;
        default:
            return Uri.EMPTY;
        }
    }

    @Override
    public Context getContext() {
        return getActivity();
    }

    @Override
    public void addListener(UserActionListener toAdd) {
        mListeners.add(toAdd);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mImageLoader = new ImageLoader(getActivity(), android.R.color.transparent);
        initPresenter();
    }

    private void initPresenter() {
        VideoLibraryModel model = ModelProvider.provideVideoLibraryModel(getDataUri(VideoLibraryQueryEnum.VIDEOS),
                getDataUri(VideoLibraryQueryEnum.MY_VIEWED_VIDEOS), getDataUri(VideoLibraryQueryEnum.FILTERS),
                getActivity(), getLoaderManager());
        PresenterImpl presenter = new PresenterImpl(model, this, VideoLibraryUserActionEnum.values(),
                VideoLibraryQueryEnum.values());
        presenter.loadInitialQueries();
    }

    @Override
    public void onResume() {
        super.onResume();
        getActivity().invalidateOptionsMenu();

        final DrawShadowFrameLayout drawShadowFrameLayout = (DrawShadowFrameLayout) getActivity()
                .findViewById(R.id.main_content);
        if (drawShadowFrameLayout != null) {
            // Configure video fragment's top clearance to take our overlaid Toolbar into account.
            drawShadowFrameLayout.setShadowTopOffset(UIUtils.calculateActionBarSize(getActivity()));
        }
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Register preference change listeners
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
        sp.registerOnSharedPreferenceChangeListener(mSettingsChangeListener);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        if (mSettingsChangeListener != null) {
            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
            sp.unregisterOnSharedPreferenceChangeListener(mSettingsChangeListener);
        }
    }

    /**
     * Let all UserActionListener know that the user has changed.
     */
    private void fireReloadUserVideosEvent() {
        if (!isAdded()) {
            return;
        }
        for (UserActionListener h1 : mListeners) {
            Bundle args = new Bundle();
            args.putInt(ModelWithLoaderManager.KEY_RUN_QUERY_ID, VideoLibraryQueryEnum.MY_VIEWED_VIDEOS.getId());
            h1.onUserAction(VideoLibraryUserActionEnum.RELOAD_USER_VIDEOS, args);
        }
    }

    /**
     * An adapter for providing data for the main list in this fragment. This shows a card per
     * video track. Each card contains a scrolling list of videos within that track or can lead
     * to a details screen showing an expanded view of the track.
     */
    private static class VideosAdapter extends UpdatableAdapter<VideoLibraryModel, VideoTrackViewHolder> {

        // Immutable state
        private final Activity mHost;

        private final LayoutInflater mInflater;

        private final ImageLoader mImageLoader;

        private final List<UserActionListener> mListeners;

        private final RecyclerView.RecycledViewPool mRecycledViewPool;

        // State
        private List<VideoTrack> mVideoTracks;

        private SparseArrayCompat<VideoTrackAdapter> mTrackVideosAdapters;

        private SparseArrayCompat<Parcelable> mTrackVideosState;

        VideosAdapter(@NonNull Activity activity, @NonNull VideoLibraryModel model,
                @NonNull ImageLoader imageLoader, @NonNull List<UserActionListener> listeners) {
            mHost = activity;
            mInflater = LayoutInflater.from(activity);
            mImageLoader = imageLoader;
            mListeners = listeners;
            mRecycledViewPool = new RecyclerView.RecycledViewPool();
            mVideoTracks = processVideos(model);
            setupVideoTrackAdapters();
        }

        @Override
        public VideoTrackViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
            final VideoTrackViewHolder holder = new VideoTrackViewHolder(
                    mInflater.inflate(R.layout.explore_io_track_card, parent, false));
            holder.videos.setHasFixedSize(true);
            holder.videos.setRecycledViewPool(mRecycledViewPool);
            ViewCompat.setImportantForAccessibility(holder.videos,
                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
            holder.header.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(final View v) {
                    final int position = holder.getAdapterPosition();
                    if (position == RecyclerView.NO_POSITION)
                        return;
                    final VideoTrack videoTrack = mVideoTracks.get(position);
                    // ANALYTICS EVENT: Click on the "More" button of a card in the Video Library
                    // Contains: The clicked header's label
                    AnalyticsHelper.sendEvent(VIDEO_LIBRARY_ANALYTICS_CATEGORY, "morebutton",
                            videoTrack.getTrack());
                    // Start the Filtered Video Library intent.
                    final Intent filtered = new Intent(mHost, VideoLibraryFilteredActivity.class);
                    if (videoTrack.getTrackId() == VideoLibraryModel.TRACK_ID_KEYNOTES) {
                        filtered.putExtra(VideoLibraryFilteredActivity.KEY_FILTER_TOPIC,
                                VideoLibraryModel.KEYNOTES_TOPIC);
                    } else if (videoTrack.getTrackId() == VideoLibraryModel.TRACK_ID_NEW) {
                        filtered.putExtra(VideoLibraryFilteredActivity.KEY_FILTER_YEAR,
                                Calendar.getInstance().get(Calendar.YEAR));
                    } else {
                        filtered.putExtra(VideoLibraryFilteredActivity.KEY_FILTER_TOPIC, videoTrack.getTrack());
                    }
                    final ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(mHost,
                            Pair.create((View) holder.headerImage,
                                    mHost.getString(R.string.transition_track_header)),
                            Pair.create(holder.itemView, mHost.getString(R.string.transition_track_background)));
                    ActivityCompat.startActivity(mHost, filtered, options.toBundle());
                }
            });
            return holder;
        }

        @Override
        public void onBindViewHolder(final VideoTrackViewHolder holder, final int position) {
            final VideoTrack videoTrack = mVideoTracks.get(position);
            holder.title.setText(videoTrack.getTrack());
            holder.header.setContentDescription(videoTrack.getTrack());
            if (videoTrack.getTrackImageUrl() != null) {
                holder.headerImage.setScaleType(ImageView.ScaleType.CENTER_CROP);
                mImageLoader.loadImage(videoTrack.getTrackImageUrl(), holder.headerImage);
            } else {
                holder.headerImage.setScaleType(ImageView.ScaleType.FIT_CENTER);
                holder.headerImage.setImageResource(R.drawable.ic_hash_io_16_monochrome);
            }
            holder.videos.setAdapter(mTrackVideosAdapters.get(videoTrack.getTrackId()));
            holder.videos.getLayoutManager().onRestoreInstanceState(mTrackVideosState.get(videoTrack.getTrackId()));
        }

        @Override
        public void onViewRecycled(final VideoTrackViewHolder holder) {
            // Cache the scroll position of the video list so that we can restore it in onBind
            final int position = holder.getAdapterPosition();
            if (position != RecyclerView.NO_POSITION) {
                final VideoTrack videoTrack = mVideoTracks.get(position);
                mTrackVideosState.put(videoTrack.getTrackId(),
                        holder.videos.getLayoutManager().onSaveInstanceState());
            }
            super.onViewRecycled(holder);
        }

        @Override
        public int getItemCount() {
            return mVideoTracks.size();
        }

        @Override
        public void update(@NonNull final VideoLibraryModel updatedData) {
            // Attempt to update our model in-place to keep scroll position etc
            final List<VideoTrack> newVideos = processVideos(updatedData);
            boolean changed = false;
            if (newVideos.size() != mVideoTracks.size()) {
                changed = true;
            } else {
                for (int i = 0; i < newVideos.size(); i++) {
                    final VideoTrack newVideoTrack = newVideos.get(i);
                    final VideoTrack oldVideoTrack = mVideoTracks.get(i);
                    if (newVideoTrack.equals(oldVideoTrack)) {
                        mTrackVideosAdapters.get(newVideoTrack.getTrackId()).update(newVideoTrack.getVideos());
                    } else {
                        changed = true;
                        break;
                    }
                }
            }
            if (changed) {
                // Couldn't do an in-place update, do a full refresh
                mVideoTracks = newVideos;
                setupVideoTrackAdapters();
                notifyDataSetChanged();
            }
        }

        /**
         * Process the data for use; we get given a flat list of videos. Group them by track with
         * special handling of keynotes and new talks from this year. We assume that the provided
         * data is already sorted by track.
         * @param model
         */
        private List<VideoTrack> processVideos(VideoLibraryModel model) {
            final List<VideoTrack> data = new ArrayList<>();

            final VideoTrack keynoteVideos = model.getKeynoteVideos();
            if (keynoteVideos != null) {
                data.add(keynoteVideos);
            }

            final VideoTrack currentYearVideos = model.getCurrentYearVideos();
            if (currentYearVideos != null) {
                data.add(currentYearVideos);
            }

            final List<VideoTrack> videos = model.getVideos();
            if (videos != null && !videos.isEmpty()) {
                data.addAll(videos);
            }
            return data;
        }

        /**
         * Loop over {@link #mVideoTracks} and create adaptor & state objects for each track.
         */
        private void setupVideoTrackAdapters() {
            mTrackVideosAdapters = new SparseArrayCompat<>(mVideoTracks.size());
            mTrackVideosState = new SparseArrayCompat<>(mVideoTracks.size());

            for (final VideoTrack videoTrack : mVideoTracks) {
                mTrackVideosAdapters.put(videoTrack.getTrackId(), VideoTrackAdapter.createHorizontal(mHost,
                        videoTrack.getVideos(), mImageLoader, mListeners));
            }
        }

    }

    private static class VideoTrackViewHolder extends RecyclerView.ViewHolder {

        final ViewGroup header;
        final TextView title;
        final RecyclerView videos;
        final ImageView headerImage;

        public VideoTrackViewHolder(final View itemView) {
            super(itemView);
            header = (ViewGroup) itemView.findViewById(R.id.header);
            title = (TextView) itemView.findViewById(R.id.title);
            videos = (RecyclerView) itemView.findViewById(R.id.sessions);
            headerImage = (ImageView) itemView.findViewById(R.id.header_image);
        }
    }

}