package com.afollestad.cardsui;
import android.content.Context;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import com.afollestad.silk.adapters.SilkAdapter;
/**
* A {@link SilkAdapter} that displays {@link Card} and {@link CardHeader}
* objects in a {@link CardListView}.
*
* @author Aidan Follestad (afollestad)
*/
public class CardAdapter<ItemType extends CardBase<ItemType>> extends SilkAdapter<ItemType>
{
private final static int TYPE_REGULAR = 0;
private final static int TYPE_NO_CONTENT = 1;
private final static int TYPE_HEADER = 2;
private int mAccentColor;
private int mPopupMenu = -1;
private Card.CardMenuListener<ItemType> mPopupListener;
private boolean mCardsClickable = true;
private int mLayout = R.layout.list_item_card;
private int mLayoutNoContent = R.layout.list_item_card_nocontent;
/**
* Initializes a new CardAdapter instance.
*
* @param context
* The context used to inflate layouts and retrieve resources.
*/
public CardAdapter(Context context)
{
super(context);
mAccentColor = context.getResources().getColor(android.R.color.black);
}
/**
* Initializes a new CardAdapter instance.
*
* @param context
* The context used to inflate layouts and retrieve resources.
* @param cardLayoutRes
* Sets a custom layout to be used for all cards (not including
* headers) in the adapter. This <b>does not</b> override layouts
* set to individual cards.
*/
public CardAdapter(Context context, int cardLayoutRes)
{
this(context);
mLayout = cardLayoutRes;
}
/**
* Initializes a new CardAdapter instance.
*
* @param context
* The context used to inflate layouts and retrieve resources.
* @param cardLayoutRes
* Sets a custom layout to be used for all cards (not including
* headers) in the adapter. This <b>does not</b> override layouts
* set to individual cards.
* @param cardLayoutNoContentRes
* Sets a custom layout to be used for all cards (not including
* headers) in the adapter with null content. This <b>does
* not</b> override layouts set to individual cards.
*/
public CardAdapter(Context context, int cardLayoutRes, int cardLayoutNoContentRes)
{
this(context, cardLayoutRes);
mLayoutNoContent = cardLayoutNoContentRes;
}
@Override
public final boolean isEnabled(int position)
{
ItemType item = getItem(position);
if (!mCardsClickable && !item.isHeader())
return false;
if (item.isHeader())
return item.getActionCallback() != null;
return item.isClickable();
}
/**
* Sets the accent color used on card titles and header action buttons. You
* <b>should</b> call this method before adding any cards to the adapter to
* avoid issues.
*
* @param color
* The resolved color to use as an accent.
*/
public final CardAdapter<ItemType> setAccentColor(int color)
{
mAccentColor = color;
return this;
}
/**
* Sets the accent color resource used on card titles and header action
* buttons. You <b>should</b> call this method before adding any cards to
* the adapter to avoid issues.
*
* @param colorRes
* The color resource ID to use as an accent.
*/
public final CardAdapter<ItemType> setAccentColorRes(int colorRes)
{
setAccentColor(getContext().getResources().getColor(colorRes));
return this;
}
/**
* Sets a popup menu used for every card in the adapter, this will not
* override individual card popup menus. You <b>should</b> call this method
* before adding any cards to the adapter to avoid issues.
*
* @param menuRes
* The menu resource ID to use for the card's popup menu.
* @param listener
* A listener invoked when an option in the popup menu is tapped
* by the user.
*/
public final CardAdapter<ItemType> setPopupMenu(int menuRes, Card.CardMenuListener<ItemType> listener)
{
mPopupMenu = menuRes;
mPopupListener = listener;
return this;
}
/**
* Sets whether or not cards in the adapter are clickable, setting it to
* false will turn card's list selectors off and the list's
* OnItemClickListener will not be called. This <b>will</b> override
* individual isClickable values set to {@link Card}s.
*/
public final CardAdapter<ItemType> setCardsClickable(boolean clickable)
{
mCardsClickable = clickable;
return this;
}
@Override
public int getLayout(int index, int type)
{
if (type == TYPE_HEADER)
return R.layout.list_item_header;
else if (type == TYPE_NO_CONTENT)
return mLayoutNoContent;
CardBase card = getItem(index);
int layout = card.getLayout();
if (layout <= 0)
{
// If no layout was specified for the individual card, use the
// adapter's set layout
layout = mLayout;
}
return layout;
}
protected void setupHeader(ItemType header, View view)
{
TextView title = (TextView) view.findViewById(android.R.id.title);
if (title == null)
throw new RuntimeException("Your header layout must contain a TextView with the ID @android:id/title.");
TextView subtitle = (TextView) view.findViewById(android.R.id.content);
if (subtitle == null)
throw new RuntimeException("Your header layout must contain a TextView with the ID @android:id/content.");
title.setText(header.getTitle());
if (header.getContent() != null && !header.getContent().trim().isEmpty())
{
subtitle.setVisibility(View.VISIBLE);
subtitle.setText(header.getContent());
}
else
subtitle.setVisibility(View.GONE);
TextView button = (TextView) view.findViewById(android.R.id.button1);
if (button == null)
throw new RuntimeException("The header layout must contain a TextView with the ID @android:id/button1.");
if (header.getActionCallback() != null)
{
button.setVisibility(View.VISIBLE);
button.setBackgroundColor(mAccentColor);
String titleTxt = header.getActionTitle();
if (header.getActionTitle() == null || header.getActionTitle().trim().isEmpty())
titleTxt = getContext().getString(R.string.see_more);
button.setText(titleTxt);
}
else
button.setVisibility(View.GONE);
}
private void setupMenu(final ItemType card, final View view)
{
if (view == null)
return;
if (card.getPopupMenu() < 0)
{
// Menu for this card is disabled
view.setVisibility(View.INVISIBLE);
view.setOnClickListener(null);
return;
}
int menuRes = mPopupMenu;
if (card.getPopupMenu() != 0)
menuRes = card.getPopupMenu();
if (menuRes < 0)
{
// No menu for the adapter or the card
view.setVisibility(View.INVISIBLE);
view.setOnClickListener(null);
return;
}
view.setVisibility(View.VISIBLE);
view.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
int menuRes = mPopupMenu;
if (card.getPopupMenu() != 0)
menuRes = card.getPopupMenu();
Context themedContext = getContext();
themedContext.setTheme(android.R.style.Theme_Holo_Light);
PopupMenu popup = new PopupMenu(themedContext, view);
MenuInflater inflater = popup.getMenuInflater();
inflater.inflate(menuRes, popup.getMenu());
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener()
{
@Override
public boolean onMenuItemClick(MenuItem item)
{
if (card.getPopupMenu() > 0 && card.getPopupListener() != null)
{
// This individual card has it unique menu
card.getPopupListener().onMenuItemClick(card, item);
}
else if (mPopupListener != null)
{
// The card does not have a unique menu, use the
// adapter's default
mPopupListener.onMenuItemClick(card, item);
}
return false;
}
});
popup.show();
}
});
}
private void invalidatePadding(int index, View view)
{
int top = index == 0 ? R.dimen.card_outer_padding_firstlast : R.dimen.card_outer_padding_top;
int bottom = index == (getCount() - 1) ? R.dimen.card_outer_padding_firstlast : R.dimen.card_outer_padding_top;
view.setPadding(view.getPaddingLeft(),
getContext().getResources().getDimensionPixelSize(top),
view.getPaddingRight(),
getContext().getResources().getDimensionPixelSize(bottom));
}
@Override
public View onViewCreated(int index, View recycled, ItemType item, ViewGroup parent, ViewHolder viewHolder)
{
if (item.isHeader())
{
setupHeader(item, recycled);
return recycled;
}
if (viewHolder.title == null)
viewHolder.title = (TextView) recycled.findViewById(android.R.id.title);
if (viewHolder.content1 == null)
viewHolder.content1 = (TextView) recycled.findViewById(android.R.id.content);
if (viewHolder.icon == null)
viewHolder.icon = (ImageView) recycled.findViewById(android.R.id.icon);
if (viewHolder.title != null)
onProcessTitle(viewHolder.title, item, mAccentColor);
if (viewHolder.content1 != null)
onProcessContent(viewHolder.content1, item);
if (viewHolder.icon != null)
{
if (onProcessThumbnail(viewHolder.icon, item, parent))
{
viewHolder.icon.setVisibility(View.VISIBLE);
}
else
{
viewHolder.icon.setVisibility(View.GONE);
}
}
invalidatePadding(index, recycled);
setupMenu(item, recycled.findViewById(android.R.id.button1));
return recycled;
}
@Override
public long getItemId(ItemType item)
{
return -1;
}
@Override
public int getViewTypeCount()
{
// There's 3 layout types by default: cards, cards with no content, and
// card headers.
int viewTypes = 3;
for (int i = 0; i < getItems().size(); i++)
{
int layout = getItem(i).getLayout();
if (layout > 0 &&
layout != mLayout &&
layout != mLayoutNoContent &&
layout != R.layout.list_item_header)
{
viewTypes++;
}
}
return viewTypes;
}
@Override
public int getItemViewType(int position)
{
CardBase item = getItem(position);
if (item.isHeader())
return TYPE_HEADER;
else if ((item.getContent() == null || item.getContent().trim().isEmpty()) && item.getLayout() <= 0)
return TYPE_NO_CONTENT;
else
return TYPE_REGULAR;
}
protected boolean onProcessTitle(TextView title, ItemType card, int accentColor)
{
if (title == null)
return false;
title.setText(card.getTitle());
title.setTextColor(accentColor);
return true;
}
protected boolean onProcessThumbnail(ImageView icon, ItemType card, ViewGroup parent)
{
if (icon == null)
return false;
if (card.getThumbnail() == null)
return false;
icon.setImageDrawable(card.getThumbnail());
return true;
}
protected boolean onProcessContent(TextView content, ItemType card)
{
content.setText(card.getContent());
return false;
}
}