package com.distantfuture.videos.mainactivity;
import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.graphics.Typeface;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.text.util.Linkify;
import android.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import com.distantfuture.videos.R;
import com.distantfuture.videos.database.DatabaseAccess;
import com.distantfuture.videos.database.YouTubeData;
import com.distantfuture.videos.misc.AppUtils;
import com.distantfuture.videos.misc.DUtils;
import com.distantfuture.videos.misc.StandardAnimations;
import com.distantfuture.videos.misc.VideoMenuView;
import com.distantfuture.videos.services.ListServiceRequest;
import com.distantfuture.videos.youtube.VideoImageView;
import com.distantfuture.videos.youtube.ViewDecorations;
import com.distantfuture.videos.youtube.YouTubeAPI;
import com.squareup.picasso.Picasso;
import org.ocpsoft.prettytime.PrettyTime;
import java.util.Date;
public class YouTubeCursorAdapter extends SimpleCursorAdapter implements AdapterView.OnItemClickListener, VideoMenuView.VideoMenuViewListener, View.OnClickListener {
private final LayoutInflater inflater;
private final YouTubeData mReusedData = new YouTubeData(); // avoids a memory alloc when drawing
private final PublishedDateCache mDateCache = new PublishedDateCache();
private int animationID = 0;
private Context mContext;
private Theme mTheme;
private ListServiceRequest mRequest;
private YouTubeCursorAdapterListener mListener;
private boolean mFadeInLoadedImages = false; // turned off for speed
private boolean mClickAnimationsEnabled = false; // off for now
private ViewDecorations mDecorations;
private YouTubeCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags) {
super(context, layout, c, from, to, flags);
inflater = LayoutInflater.from(context);
DataSetObserver dataSetObserver = new DataSetObserver() {
@Override
public void onChanged() {
mListener.adapterDataChanged();
}
};
registerDataSetObserver(dataSetObserver);
}
public static YouTubeCursorAdapter newAdapter(Context context, ListServiceRequest request, YouTubeCursorAdapterListener listener) {
Theme theme = newTheme(context);
ViewDecorations decorations = new ViewDecorations(context, request.type() == ListServiceRequest.RequestType.PLAYLISTS);
decorations.setDrawShadows(theme.mTheme_drawImageShadows);
int strokeColor = context.getResources().getColor(R.color.card_fill_color);
decorations.strokeAndFill(context, theme.mCardImageFillColor, strokeColor, 8, 4);
String[] from = new String[]{};
int[] to = new int[]{};
YouTubeCursorAdapter result = new YouTubeCursorAdapter(context, theme.mTheme_resId, null, from, to, 0);
result.mTheme = theme;
result.mDecorations = decorations;
result.mRequest = request;
result.mContext = context.getApplicationContext();
result.mListener = listener;
return result;
}
private static Theme newTheme(Context context) {
Theme result = new Theme();
int themeStyle = AppUtils.instance(context).themeId();
result.mClickTextToExpand = true;
result.mTheme_drawImageShadows = false;
result.mDescriptionMaxLines = 2;
result.mTitleMaxLines = context.getResources().getInteger(R.integer.title_max_lines);
result.mSupportsMenuButton = false;
result.mCardImageFillColor = context.getResources()
.getColor(R.color.card_image_fill); // an app can set to transparent to turn this off
switch (themeStyle) {
case 0:
result.mTheme_itemResId = R.layout.youtube_item_dark;
result.mTheme_resId = R.layout.fragment_grid_dark;
result.mTheme_drawImageShadows = true;
result.mClickTextToExpand = false;
break;
case 1:
result.mTheme_itemResId = R.layout.youtube_item_light;
result.mTheme_resId = R.layout.fragment_grid_light;
break;
case 2:
result.mTheme_itemResId = R.layout.youtube_item_card;
result.mTheme_resId = R.layout.fragment_grid_card;
break;
case 3:
result.mTheme_itemResId = R.layout.youtube_item_poster;
result.mTheme_resId = R.layout.fragment_grid_card;
break;
}
return result;
}
public ViewGroup rootView(ViewGroup container) {
return (ViewGroup) inflater.inflate(mTheme.mTheme_resId, container, false);
}
private void animateViewForClick(final View theView) {
switch (animationID) {
case 0:
StandardAnimations.dosomething(theView);
break;
case 1:
StandardAnimations.upAndAway(theView);
break;
case 2:
StandardAnimations.rockBounce(theView);
break;
case 3:
StandardAnimations.winky(theView, 1.0f);
break;
default:
StandardAnimations.rubberClick(theView);
animationID = -1;
break;
}
animationID += 1;
}
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long row) {
ViewHolder holder = (ViewHolder) v.getTag();
if (holder != null) {
if (mClickAnimationsEnabled)
animateViewForClick(holder.image);
Cursor cursor = (Cursor) getItem(position);
YouTubeData itemMap = mRequest.databaseTable().cursorToItem(cursor, null);
if (itemMap != null) {
// if hidden, a click unhides it
if (itemMap.isHidden()) {
DatabaseAccess database = new DatabaseAccess(mContext, mRequest);
itemMap.setHidden(false);
database.updateItem(itemMap);
} else {
mListener.handleClickFromAdapter(position, itemMap);
}
} else {
DUtils.log("no holder on click?");
}
}
}
@Override
public void onClick(View v) {
ViewGroup row = (ViewGroup) v.getParent();
TextView titleView = (TextView) row.findViewById(R.id.text_view);
TextView descriptionView = (TextView) row.findViewById(R.id.description_view);
if (titleView != null) {
boolean setMax = titleView.getMaxLines() < Integer.MAX_VALUE;
titleView.setMaxLines(setMax ? Integer.MAX_VALUE : mTheme.mTitleMaxLines);
}
if (descriptionView != null) {
boolean setMax = descriptionView.getMaxLines() < Integer.MAX_VALUE;
descriptionView.setMaxLines(setMax ? Integer.MAX_VALUE : mTheme.mDescriptionMaxLines);
if (setMax)
descriptionView.setAutoLinkMask(Linkify.ALL);
else
descriptionView.setAutoLinkMask(0);
// toggles links by setting text again
String sequence = descriptionView.getText()
.toString(); // getText() could return a StringSpanner, toString() gets the raw string
descriptionView.setText(sequence);
}
}
private View prepareViews(View convertView, ViewGroup parent, boolean multiColumns) {
ViewHolder holder;
// ## This fixes a problem with the SwipeToDismissAdapter.
// After dismiss, the view is left in a state with a zero height
// rather than fixing it, just refusing to reuse it.
if (convertView != null) {
ViewGroup.LayoutParams lp = convertView.getLayoutParams();
if (lp != null && lp.height == 0) {
DUtils.log("not reusing dismissed view");
convertView = null;
}
}
if (convertView == null) {
convertView = inflater.inflate(mTheme.mTheme_itemResId, parent, false);
holder = new ViewHolder();
holder.image = (VideoImageView) convertView.findViewById(R.id.image);
holder.title = (TextView) convertView.findViewById(R.id.text_view);
holder.description = (TextView) convertView.findViewById(R.id.description_view);
holder.duration = (TextView) convertView.findViewById(R.id.duration);
holder.pubDate = (TextView) convertView.findViewById(R.id.pub_date);
holder.menuButton = (VideoMenuView) convertView.findViewById(R.id.menu_button);
holder.cardNumber = (TextView) convertView.findViewById(R.id.card_number);
holder.image.setDecorations(mDecorations);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if (holder != null) {
boolean setClickListeners = false;
// reset some stuff that might have been set on an animation
if (mClickAnimationsEnabled) {
// stops running animations?
holder.image.setAnimation(null);
holder.image.setAlpha(1.0f);
holder.image.setScaleX(1.0f);
holder.image.setScaleY(1.0f);
holder.image.setRotationX(0.0f);
holder.image.setRotationY(0.0f);
}
// restore this, user could have clicked on it
holder.description.setAutoLinkMask(0);
if (multiColumns) {
// lowering these to get less wasted space at bottom
int titleHeight = 2; // mTheme.mTitleMaxLines;
int descriptionHeight = 2; // mTheme.mDescriptionMaxLines;
holder.description.setMaxLines(descriptionHeight);
holder.title.setMaxLines(titleHeight);
// multiple columns must be same height
holder.description.setMinLines(descriptionHeight);
holder.title.setMinLines(titleHeight);
} else {
holder.description.setMaxLines(mTheme.mDescriptionMaxLines);
holder.title.setMaxLines(mTheme.mTitleMaxLines);
holder.description.setMinLines(0);
holder.title.setMinLines(0);
if (mTheme.mClickTextToExpand) {
setClickListeners = true;
holder.description.setOnClickListener(this);
holder.title.setOnClickListener(this);
}
}
if (!setClickListeners) {
holder.description.setOnClickListener(null);
holder.title.setOnClickListener(null);
}
}
return convertView;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
GridView gridView = (GridView) parent;
boolean multiColumns = gridView.getNumColumns() > 1;
convertView = prepareViews(convertView, parent, multiColumns);
ViewHolder holder = (ViewHolder) convertView.getTag();
Cursor cursor = (Cursor) getItem(position);
YouTubeData itemMap = mRequest.databaseTable().cursorToItem(cursor, mReusedData);
Picasso.with(mContext).load(itemMap.mThumbnail)
.fit()
// .noFade()
// .resize(250, 250) // put into dimens for dp values
.into(holder.image);
boolean hidden = itemMap.isHidden();
holder.image.setDrawHiddenIndicator(hidden);
holder.title.setText(itemMap.mTitle);
// hide description if empty
if (holder.description != null) {
String desc = itemMap.mDescription;
if (desc != null && (desc.length() > 0)) {
holder.description.setVisibility(View.VISIBLE);
holder.description.setText(desc);
} else {
if (multiColumns) {
holder.description.setVisibility(View.VISIBLE);
holder.description.setText(" "); // min lines doesn't work with "", used a space
} else
holder.description.setVisibility(View.GONE);
}
}
// set video id on menu button so clicking can know what video to act on
if (mTheme.mSupportsMenuButton && (itemMap.mVideo != null || itemMap.mPlaylist != null)) {
holder.menuButton.setVisibility(View.VISIBLE);
holder.menuButton.setListener(this);
holder.menuButton.mId = itemMap.mID;
} else {
holder.menuButton.setVisibility(View.GONE);
}
String duration = itemMap.mDuration;
if (duration != null) {
holder.duration.setText(duration);
} else {
long count = itemMap.mItemCount;
holder.duration.setText(Long.toString(count) + (count == 1 ? " video" : " videos"));
}
// card number
holder.cardNumber.setText(Long.toString(position+1));
Spannable date = mDateCache.spannable(itemMap.mPublishedDate);
if (date != null) {
holder.pubDate.setText(date);
} else {
holder.pubDate.setText("");
}
return convertView;
}
// VideoMenuViewListener
@Override
public void showVideoInfo(Long itemId) {
DatabaseAccess database = new DatabaseAccess(mContext, mRequest);
YouTubeData videoMap = database.getItemWithID(itemId);
if (videoMap != null) {
DUtils.log("fix me");
}
}
// VideoMenuViewListener
@Override
public void showVideoOnYouTube(Long itemId) {
DatabaseAccess database = new DatabaseAccess(mContext, mRequest);
YouTubeData videoMap = database.getItemWithID(itemId);
if (videoMap != null) {
if (videoMap.mVideo != null)
YouTubeAPI.playMovieUsingIntent(mContext, videoMap.mVideo);
else if (videoMap.mPlaylist != null)
YouTubeAPI.openPlaylistUsingIntent(mListener.accesActivity(), videoMap.mPlaylist);
}
}
// VideoMenuViewListener
@Override
public void hideVideo(Long itemId) {
DatabaseAccess database = new DatabaseAccess(mContext, mRequest);
YouTubeData videoMap = database.getItemWithID(itemId);
if (videoMap != null) {
videoMap.setHidden(!videoMap.isHidden());
database.updateItem(videoMap);
}
}
public interface YouTubeCursorAdapterListener {
public void handleClickFromAdapter(int position, YouTubeData itemMap);
public void adapterDataChanged();
public Activity accesActivity();
}
private static class Theme {
int mTheme_itemResId;
int mTheme_resId;
boolean mTheme_drawImageShadows;
boolean mClickTextToExpand;
int mTitleMaxLines;
int mDescriptionMaxLines;
boolean mSupportsMenuButton;
int mCardImageFillColor;
}
private static class ViewHolder {
TextView title;
TextView description;
TextView duration;
TextView cardNumber;
VideoImageView image;
VideoMenuView menuButton;
TextView pubDate;
}
// cached, used as an optimization, getView() 0,0,0, muliple times, so don't keep building this over and over
private static class PublishedDateCache {
private final LruCache cache = new LruCache(50);
private final PrettyTime mDateFormatter = new PrettyTime();
private final Date mDate = new Date(); // avoiding an alloc every call, just set the time
private final String mTitle = "Published: ";
private final StyleSpan mBoldSpan = new StyleSpan(Typeface.BOLD);
private final ForegroundColorSpan mColorSpan = new ForegroundColorSpan(0xffb1e2ff);
public Spannable spannable(long date) {
// is it cached?
Spannable result = (Spannable) cache.get(date);
if (result == null) {
// avoiding an alloc during draw, reusing Date
mDate.setTime(date);
String content = mDateFormatter.format(mDate);
result = new SpannableString(mTitle + content);
result.setSpan(mBoldSpan, 0, mTitle.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
result.setSpan(mColorSpan, mTitle.length(), mTitle.length() + content.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// add to cache
cache.put(date, result);
}
return result;
}
}
}