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

Java tutorial

Introduction

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

Source

/*
 * Copyright (c) 2016 Google Inc.
 *
 * 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.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.google.samples.apps.iosched.Config;
import com.google.samples.apps.iosched.R;
import com.google.samples.apps.iosched.archframework.UpdatableView;
import com.google.samples.apps.iosched.ui.widget.VideoThumbnail;
import com.google.samples.apps.iosched.ui.widget.recyclerview.UpdatableAdapter;
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.data.Video;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * A {@link RecyclerView.Adapter} for a list of {@link Video}. This adapter runs in two
 * <i>modes</i>: compact and detail.
 * <p/>
 * Compact mode is created via the {@link #createHorizontal(Activity, List, ImageLoader, List)}
 * factory method & shows a smaller presentation of a video. It is used by {@link
 * VideoLibraryFragment}.
 * <p/>
 * The detail mode is created via the {@link #createVerticalGrid(Activity, List, ImageLoader, List,
 * int)} factory method and adds headers dividing videos by year and shows a larger presentation of
 * a video. It is used by {@link VideoLibraryFilteredFragment}.
 */
public class VideoTrackAdapter extends UpdatableAdapter<List<Video>, RecyclerView.ViewHolder> {

    // Constants
    private static final int TYPE_VIDEO = 0;

    private static final int TYPE_YEAR_HEADER = 1;

    private static final String VIDEO_LIBRARY_ANALYTICS_CATEGORY = "Video Library";

    // Immutable state
    private final Activity mHost;

    private final LayoutInflater mInflater;

    private final ImageLoader mImageLoader;

    private final List<UpdatableView.UserActionListener> mListeners;

    private final boolean mCompactMode;

    private final int mColumns;

    private final ColorDrawable[] mBackgroundColors;

    // State
    private List mItems;

    // private constructor, see the more meaningful static factory methods
    private VideoTrackAdapter(@NonNull Activity activity, @NonNull List<Video> videos,
            @NonNull ImageLoader imageLoader, @NonNull List<UpdatableView.UserActionListener> listeners,
            boolean compactMode, int columns) {
        mHost = activity;
        mInflater = LayoutInflater.from(activity);
        mImageLoader = imageLoader;
        mListeners = listeners;
        mCompactMode = compactMode;
        mColumns = columns;
        // load the background colors
        final int[] colors = mHost.getResources().getIntArray(R.array.session_tile_backgrounds);
        mBackgroundColors = new ColorDrawable[colors.length];
        for (int i = 0; i < colors.length; i++) {
            mBackgroundColors[i] = new ColorDrawable(colors[i]);
        }
        mItems = processData(videos);
    }

    public static VideoTrackAdapter createHorizontal(@NonNull Activity activity, @NonNull List<Video> videos,
            @NonNull ImageLoader imageLoader, @NonNull List<UpdatableView.UserActionListener> listeners) {
        return new VideoTrackAdapter(activity, videos, imageLoader, listeners, true, -1);
    }

    public static VideoTrackAdapter createVerticalGrid(@NonNull Activity activity, @NonNull List<Video> videos,
            @NonNull ImageLoader imageLoader, @NonNull List<UpdatableView.UserActionListener> listeners,
            int columns) {
        return new VideoTrackAdapter(activity, videos, imageLoader, listeners, false, columns);
    }

    @Override
    public void update(@NonNull final List<Video> updatedData) {
        // Attempt to update data in place i.e. only if it has changed so as not to lose scroll
        // position etc when an item updates e.g. when a video is marked as watched
        final List newItems = processData(updatedData);
        if (newItems.size() != mItems.size()) {
            mItems = newItems;
            notifyDataSetChanged();
            return;
        }
        for (int i = 0; i < newItems.size(); i++) {
            final Object oldItem = mItems.get(i);
            final Object newItem = newItems.get(i);
            // Because we update the Video object directly from the model, we need to check if it
            // has been updated rather than using equals
            if (!oldItem.equals(newItem) || (newItem instanceof Video && ((Video) newItem).dataUpdated())) {
                mItems.set(i, newItem);
                notifyItemChanged(i);
            }
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
        switch (viewType) {
        case TYPE_VIDEO:
            return createVideoViewHolder(parent);
        case TYPE_YEAR_HEADER:
            return createYearHeaderViewHolder(parent);
        default:
            throw new IllegalArgumentException("Unknown view type");
        }
    }

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
        switch (getItemViewType(position)) {
        case TYPE_VIDEO:
            bindVideoViewHolder((VideoViewHolder) holder, (Video) mItems.get(position), position);
            break;
        case TYPE_YEAR_HEADER:
            bindYearHeaderViewHolder((HeaderViewHolder) holder, (YearHeader) mItems.get(position));
            break;
        }
    }

    @Override
    public int getItemViewType(final int position) {
        if (mCompactMode) {
            return TYPE_VIDEO;
        }
        Object item = mItems.get(position);
        if (item instanceof Video) {
            return TYPE_VIDEO;
        } else if (item instanceof YearHeader) {
            return TYPE_YEAR_HEADER;
        }
        throw new IllegalArgumentException("Unknown item type");
    }

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

    public int getSpanCount(final int position) {
        if (mCompactMode || getItemViewType(position) == TYPE_VIDEO) {
            return 1;
        } else {
            return mColumns;
        }
    }

    /**
     * Process the given {@code vidoe} to create the list of items to be displayed by the {@link
     * RecyclerView}. In detail mode, this means inserting date header objects to separate videos by
     * year.
     */
    private List processData(final List<Video> videos) {
        List data = new ArrayList(videos.size());

        if (mCompactMode) {
            data.addAll(videos);
        } else {
            int currentYear = Integer.MAX_VALUE;
            for (final Video video : videos) {
                if (video.getYear() < currentYear) {
                    currentYear = video.getYear();
                    data.add(new YearHeader(String.valueOf(currentYear)));
                }
                data.add(video);
            }
        }
        return data;
    }

    private @NonNull RecyclerView.ViewHolder createVideoViewHolder(final ViewGroup parent) {
        final VideoViewHolder holder = new VideoViewHolder(mInflater.inflate(
                mCompactMode ? R.layout.video_item_list_tile : R.layout.video_item_grid_tile, parent, false));
        if (mCompactMode) {
            ViewCompat.setImportantForAccessibility(holder.itemView, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
            ViewCompat.setImportantForAccessibility(holder.title, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
        }
        return holder;
    }

    private @NonNull RecyclerView.ViewHolder createYearHeaderViewHolder(final ViewGroup parent) {
        return new HeaderViewHolder(mInflater.inflate(R.layout.grid_header_major, parent, false));
    }

    private void bindVideoViewHolder(final VideoViewHolder holder, final Video video, final int position) {
        holder.itemView.setBackgroundDrawable(mBackgroundColors[position % mBackgroundColors.length]);
        holder.itemView.setOnClickListener(mVideoClick);
        mImageLoader.loadImage(video.getThumbnailUrl(), holder.thumbnail);
        holder.title.setText(video.getTitle());
        holder.thumbnail.setPlayed(video.getAlreadyPlayed());
    }

    private final View.OnClickListener mVideoClick = new View.OnClickListener() {
        @Override
        public void onClick(final View v) {
            final ViewGroup.LayoutParams lp = v.getLayoutParams();
            if (!(lp instanceof RecyclerView.LayoutParams)) {
                return;
            }
            final int position = ((RecyclerView.LayoutParams) lp).getViewAdapterPosition();
            if (position == RecyclerView.NO_POSITION) {
                return;
            }
            final Video video = (Video) mItems.get(position);
            final String videoId = video.getId();
            final String youtubeLink = TextUtils.isEmpty(videoId) ? ""
                    : videoId.contains("://") ? videoId
                            : String.format(Locale.US, Config.VIDEO_LIBRARY_URL_FMT, videoId);
            if (!TextUtils.isEmpty(youtubeLink)) {
                // ANALYTICS EVENT: Click on a video on the Video Library screen
                // Contains: video's YouTube URL, http://www.youtube.com/...
                AnalyticsHelper.sendEvent(VIDEO_LIBRARY_ANALYTICS_CATEGORY, "selectvideo", youtubeLink);
                // Start playing the video on Youtube.
                Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(youtubeLink));
                UIUtils.preferPackageForIntent(mHost, i, UIUtils.YOUTUBE_PACKAGE_NAME);
                i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
                mHost.startActivity(i);
                // Mark the video as played.
                fireVideoPlayedEvent(video);
            }
        }
    };

    private void bindYearHeaderViewHolder(final HeaderViewHolder holder, final YearHeader yearHeader) {
        holder.header.setText(yearHeader.year);
    }

    /**
     * Let all UserActionListener know that the given Video has been played.
     */
    private void fireVideoPlayedEvent(Video video) {
        for (UpdatableView.UserActionListener h1 : mListeners) {
            Bundle args = new Bundle();
            args.putString(VideoLibraryModel.KEY_VIDEO_ID, video.getId());
            h1.onUserAction(VideoLibraryModel.VideoLibraryUserActionEnum.VIDEO_PLAYED, args);
        }
    }

    static class VideoViewHolder extends RecyclerView.ViewHolder {

        final VideoThumbnail thumbnail;
        final TextView title;

        public VideoViewHolder(final View itemView) {
            super(itemView);
            thumbnail = (VideoThumbnail) itemView.findViewById(R.id.thumbnail);
            title = (TextView) itemView.findViewById(R.id.title);
        }
    }

    private static class HeaderViewHolder extends RecyclerView.ViewHolder {

        final TextView header;

        public HeaderViewHolder(View itemView) {
            super(itemView);
            header = (TextView) itemView;
        }
    }

    private static class YearHeader {

        private final String year;

        public YearHeader(String year) {
            this.year = year;
        }
    }
}