/*
* 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;
}
}
}