package io.codetail.adapters;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
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.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import com.telly.mrvector.MrVector;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import butterknife.ButterKnife;
import butterknife.InjectView;
import codetail.graphics.drawables.DrawableHelper;
import codetail.graphics.drawables.RippleDrawable;
import codetail.utils.ResourceUtils;
import codetail.utils.ThemeUtils;
import io.codetail.WatchMeApplication;
import io.codetail.client.models.Video;
import io.codetail.watchme.R;
import static android.view.ViewGroup.MarginLayoutParams;
public class WatchMeAdapterNew extends RecyclerView.Adapter<WatchMeAdapterNew.WatchMeHolder>{
/**
* header row with title of section !must be always first
*/
public final static int TYPE_HEADER = 0;
public final static int TYPE_HEADER_FIRST = 1;
/**
* middle content row with video of specific section
* goes after {@link #TYPE_HEADER}
*/
public final static int TYPE_VIDEO = 2;
public final static int TYPE_VIDEO_LAST = 3;
/**
* last row with more button,
*/
public final static int TYPE_MORE = 4;
/**
* Lock used to modify the content of {@link #mSections}. Any write operation
* performed on the array should be synchronized on this lock.
*/
private final Object mLock = new Object();
/**
* Contains the list of section objects that represent the data of this Adapter.
* The content of this list is referred to as "the array" in the documentation.
*/
private ArrayList<Section> mSections;
/**
* Key for saving {@link #mSections} in {@link android.os.Bundle}
*
* @see {@link #onSaveInstanceState(android.os.Bundle)}
*/
private String mKey;
private LayoutInflater mFactory;
private Drawable mPlaceholder;
private Drawable mExpandDrawable;
private ColorStateList mRippleColors;
private ColorStateList mButtonsRippleColors;
@Inject Picasso mPicasso;
public WatchMeAdapterNew(@NonNull String saveKey) {
mKey = saveKey;
mSections = new ArrayList<>();
}
public void initResources(Context context){
mFactory = LayoutInflater.from(context);
mPlaceholder = new ColorDrawable(ResourceUtils.getColor(R.color.placeholder_color));
mExpandDrawable = MrVector.inflateCompatOnly(context.getResources(), R.drawable.ic_expand_more);
DrawableHelper.setTint(mExpandDrawable, 0xff000000);
mRippleColors = ThemeUtils.getThemeColorStateList(context, R.attr.rippleOverlayColor);
mButtonsRippleColors = ResourceUtils.getColorList(R.color.overlay_color);
}
public void onRestoreInstanceState(Bundle state){
ArrayList<Section> sections = state.getParcelableArrayList(mKey);
if(sections != null){
mSections = sections;
}
}
/*
* {@inheritDoc}
*/
@Override
public WatchMeHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType){
case TYPE_HEADER_FIRST:
case TYPE_HEADER:
return new HeaderHolder(mFactory
.inflate(R.layout.card_header_item, parent, false), viewType == TYPE_HEADER_FIRST);
case TYPE_VIDEO:
return new VideoHolder(mFactory
.inflate(R.layout.card_video_item, parent, false));
case TYPE_VIDEO_LAST:
return new VideoHolder(mFactory
.inflate(R.layout.card_last_video_item, parent, false));
case TYPE_MORE:
return new MoreButtonHolder(mFactory
.inflate(R.layout.card_more_button_item, parent, false));
default:
throw new IllegalStateException(
String.format("Unknown view type found (%s)", viewType));
}
}
/**
* {@inheritDoc}
*/
@Override
public void onBindViewHolder(WatchMeHolder absHolder, int position) {
absHolder.mObject = getItemInfo(position);
switch (absHolder.getItemViewType()){
case TYPE_HEADER:
case TYPE_HEADER_FIRST:
bindHeaderHolder((HeaderHolder) absHolder);
return;
case TYPE_VIDEO:
case TYPE_VIDEO_LAST:
bindVideoHolder((VideoHolder) absHolder);
return;
case TYPE_MORE:
bindMoreButton((MoreButtonHolder) absHolder);
break;
}
}
private void bindHeaderHolder(HeaderHolder holder){
CharSequence title = holder.getObject();
holder.mTitle.setText(title);
}
private void bindMoreButton(MoreButtonHolder holder){
holder.mShowMore.setImageDrawable(mExpandDrawable);
if(!(holder.mShowMore.getBackground() instanceof RippleDrawable)) {
RippleDrawable.makeFor(holder.mShowMore, mButtonsRippleColors, true);
}
}
private void bindVideoHolder(VideoHolder holder){
Video video = holder.getObject();
holder.mTitle.setText(video.getTitle());
holder.mThumbnail.setContentDescription(video.getTitle());
holder.mAuthor.setText(video.getOwner().getUsername());
holder.mViews.setText(ResourceUtils.getString(R.string.video_views_count, video.getViewsCount()));
holder.mDuration.setText(video.getDuration());
mPicasso.load(video.getThumbnail())
.placeholder(mPlaceholder)
.resizeDimen(R.dimen.thumbnail_width, R.dimen.thumbnail_height)
.centerCrop()
.tag(WatchMeApplication.PICASSO_INSTANCE)
.into(holder.mThumbnail);
if(!(holder.itemView.getBackground() instanceof RippleDrawable)) {
//RippleDrawable.makeFor(holder.itemView, mRippleColors, true);
RippleDrawable.makeFor(holder.itemView, mRippleColors, true);
}
}
public void displayAllSectionItems(int position){
synchronized (mLock){
for(Section section : mSections){
if(section.contains(position)){
final int startPosition = section.mPositionOffset + section.mVisibleCount - 1;
int itemCount = section.mVisibleCount;
section.mItems.remove(section.mVisibleCount - 1);
section.mVisibleCount = section.mItems.size();
itemCount = section.mVisibleCount - itemCount;
recomputeSectionsPositionOffsets();
notifyItemRangeInserted(startPosition, itemCount);
break;
}
}
}
}
private void recomputeSectionsPositionOffsets(){
int last = 0;
for(Section section: mSections){
section.mPositionOffset = last;
last = section.mPositionOffset + section.mVisibleCount;
}
}
/***
* @param position The position of item data to return
* @return item data at given position
*/
public Object getItem(int position){
return getItemInfo(position).getInfo();
}
private ItemInfo getItemInfo(int position){
for(Section section : mSections){
if(section.contains(position)){
return section.getItemInfo(position);
}
}
throw new IllegalStateException("Sections does not contains position = " + position);
}
/**
* {@inheritDoc}
*/
@Override
public int getItemCount() {
return getVisibleItemsCount();
}
/**
* {@inheritDoc}
*/
@Override
public int getItemViewType(int position) {
for(Section section : mSections){
if(section.contains(position)){
return section.getViewType(position);
}
}
return -1;
}
/**
* Return size of current visible data
*
* @return size of current visible data
*/
public int getVisibleItemsCount(){
int count = 0;
for(Section section : mSections){
count += section.mVisibleCount;
}
return count;
}
/**
* Return size of all items in section
*
* @return size of all items in section
*/
public int getAllItemsCount(){
int count = 0;
for(Section section : mSections){
count += section.mItems.size();
}
return count;
}
public void add(CharSequence title, List<Video> videos, int visible){
synchronized (mLock) {
if(videos == null){
throw new IllegalStateException("videos must not be null");
}
if(0 >= videos.size() || visible == 0){
return;
}
Section section = new Section();
section.mVisibleCount = visible;
section.mSize = videos.size();
boolean firstItem = mSections.size() == 0;
if (firstItem) {
section.mPositionOffset = 0;
} else {
Section last = mSections.get(mSections.size() - 1);
section.mPositionOffset = last.mPositionOffset + last.mVisibleCount;
}
section.mVisibleCount += 1; // header row
HeaderInfo headerInfo = new HeaderInfo(title);
if(firstItem){
headerInfo.mViewType = TYPE_HEADER_FIRST;
}
section.mItems.add(headerInfo);
for(int index = 0; index < visible; index++){
section.mItems.add(new VideoInfo(videos.get(index)));
}
if(section.hasMore()){
section.mVisibleCount += 1;// more row
section.mItems.add(new MoreButtonInfo());
}else{
section.mItems.get(section.mVisibleCount - 1)
.mViewType = TYPE_VIDEO_LAST;
}
for(int index = visible; index < section.mSize; index++){
section.mItems.add(new VideoInfo(videos.get(index)));
}
section.mItems.get(section.mItems.size() - 1).mViewType = TYPE_VIDEO_LAST;
mSections.add(section);
notifyItemRangeInserted(getVisibleItemsCount(),
visible);
}
}
public void clear(){
synchronized (mLock){
mSections.clear();
notifyDataSetChanged();
}
}
public void onSaveInstanceState(Bundle outState){
if(mSections.size() > 0) {
outState.putParcelableArrayList(mKey, mSections);
}
}
/**
* @param object the object for we generating key
* @return key for using at{@link #onRestoreInstanceState(android.os.Bundle)},
* {@link #onSaveInstanceState(android.os.Bundle)}
*/
public static String generateKey(Object object){
return object.getClass().getName();
}
/**
* Full information about section
*/
public static class Section implements Parcelable {
int mSize;
int mVisibleCount;
int mPositionOffset;
ArrayList<ItemInfo> mItems;
public Section(){
mItems = new ArrayList<>();
mSize = 0;
mPositionOffset = 0;
mVisibleCount = 0;
}
public int getViewType(int position){
return getItemInfo(position).getViewType();
}
public Object getItem(int position){
return getItemInfo(position).getInfo();
}
public ItemInfo getItemInfo(int position){
return mItems.get(normalizePosition(position));
}
public int normalizePosition(int position){
return position - mPositionOffset;
}
/**
* Returns does section contains given position
*
* @param position position of item
* @return does section contains given position
*/
public boolean contains(int position){
return position >= mPositionOffset &&
position < mVisibleCount + mPositionOffset;
}
/**
* @return true if need to show more button
*/
public boolean hasMore(){
return mSize > mVisibleCount;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mPositionOffset);
dest.writeInt(mVisibleCount);
dest.writeInt(mSize);
List<ItemInfo> list = mItems;
int N = list.size();
dest.writeInt(N);
for(int i=0; i<N; i++){
ItemInfo ii = list.get(i);
dest.writeInt(ii.mViewType);
ii.writeToParcel(dest, flags);
}
}
private Section(Parcel source){
this();
mPositionOffset = source.readInt();
mVisibleCount = source.readInt();
mSize = source.readInt();
int N = source.readInt();
ArrayList<ItemInfo> list = new ArrayList<>();
for(int i=0;i<N;i++){
list.add(ItemInfo.CREATOR.createFromParcel(source));
}
mItems = list;
}
public static final Creator<Section> CREATOR = new Creator<Section>() {
@Override
public Section createFromParcel(Parcel in) {
return new Section(in);
}
@Override
public Section[] newArray(int size) {
return new Section[size];
}
};
}
public static class ItemInfo implements Parcelable{
int mViewType;
protected ItemInfo(int viewType) {
mViewType = viewType;
}
protected ItemInfo(int type, Parcel parcel){
mViewType = type;
}
public int getViewType() {
return mViewType;
}
public Object getInfo() {
return null;
}
@Override
public int hashCode(){
return mViewType + 31;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
//dest.writeInt(mViewType);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<ItemInfo> CREATOR = new Creator<ItemInfo>() {
@Override
public ItemInfo createFromParcel(Parcel in) {
int type = in.readInt();
switch (type){
case TYPE_HEADER:
case TYPE_HEADER_FIRST:
return new HeaderInfo(type, in);
case TYPE_VIDEO:
case TYPE_VIDEO_LAST:
return new VideoInfo(type, in);
case TYPE_MORE:
return new MoreButtonInfo(type, in);
default:
throw new RuntimeException(String.format("Unknown ItemInfo type (%s)", type));
}
}
@Override
public ItemInfo[] newArray(int size) {
return new ItemInfo[size];
}
};
}
public static class HeaderInfo extends ItemInfo{
CharSequence mTitle;
protected HeaderInfo(CharSequence title) {
super(TYPE_HEADER);
mTitle = title;
}
private HeaderInfo(int type, Parcel in) {
super(type, in);
mTitle = TextUtils.CHAR_SEQUENCE_CREATOR
.createFromParcel(in);
}
@Override
public Object getInfo() {
return mTitle;
}
@Override
public int hashCode() {
return mTitle.hashCode();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
TextUtils.writeToParcel(mTitle, dest, flags);
}
}
public static class VideoInfo extends ItemInfo {
Video mVideo;
protected VideoInfo(Video item) {
super(TYPE_VIDEO);
mVideo = item;
}
private VideoInfo(int type, Parcel in) {
super(type, in);
mVideo = Video.CREATOR.createFromParcel(in);
}
@Override
public Object getInfo() {
return mVideo;
}
@Override
public int hashCode() {
return mVideo.hashCode();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
mVideo.writeToParcel(dest, flags);
}
}
private static class MoreButtonInfo extends ItemInfo{
protected MoreButtonInfo() {
super(TYPE_MORE);
}
private MoreButtonInfo(int type, Parcel in) {
super(type, in);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
}
}
/**
* Extended version of {@link android.support.v7.widget.RecyclerView.ViewHolder}
* with associating object, and know view type
*/
public abstract static class WatchMeHolder extends RecyclerView.ViewHolder{
private ItemInfo mObject;
public WatchMeHolder(View itemView) {
super(itemView);
}
@SuppressWarnings("unchecked")
public <T> T getObject(){
return (T) mObject.getInfo();
}
}
public static class VideoHolder extends WatchMeHolder{
@InjectView(R.id.card_video_item_thumbnails)
ImageView mThumbnail;
@InjectView(R.id.card_video_item_duration)
TextView mDuration;
@InjectView(R.id.card_video_item_title)
TextView mTitle;
@InjectView(R.id.card_video_item_author)
TextView mAuthor;
@InjectView(R.id.card_video_item_views)
TextView mViews;
public VideoHolder(View itemView) {
super(itemView);
ButterKnife.inject(this, itemView);
}
}
public static class HeaderHolder extends WatchMeHolder{
@InjectView(R.id.card_header_title)
TextView mTitle;
public HeaderHolder(View itemView, boolean firstItem) {
super(itemView);
ButterKnife.inject(this, itemView);
if(firstItem){
MarginLayoutParams params = (MarginLayoutParams) mTitle.getLayoutParams();
params.topMargin = 0;
}
mTitle.setClickable(false);
}
}
public static class MoreButtonHolder extends WatchMeHolder{
@InjectView(R.id.card_show_more_button)
ImageView mShowMore;
public MoreButtonHolder(View itemView) {
super(itemView);
ButterKnife.inject(this, itemView);
mShowMore.setClickable(true);
}
}
}