blob: f88ea7d22c3b8bf2dd82798537d00d2570d721b1 [file] [log] [blame]
package com.android.ex.chips;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.StateListDrawable;
import android.net.Uri;
import android.support.annotation.DrawableRes;
import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
import android.text.TextUtils;
import android.text.util.Rfc822Tokenizer;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.ex.chips.Queries.Query;
/**
* A class that inflates and binds the views in the dropdown list from
* RecipientEditTextView.
*/
public class DropdownChipLayouter {
/**
* The type of adapter that is requesting a chip layout.
*/
public enum AdapterType {
BASE_RECIPIENT,
RECIPIENT_ALTERNATES,
SINGLE_RECIPIENT
}
public interface ChipDeleteListener {
void onChipDelete();
}
private final LayoutInflater mInflater;
private final Context mContext;
private ChipDeleteListener mDeleteListener;
private Query mQuery;
public DropdownChipLayouter(LayoutInflater inflater, Context context) {
mInflater = inflater;
mContext = context;
}
public void setQuery(Query query) {
mQuery = query;
}
public void setDeleteListener(ChipDeleteListener listener) {
mDeleteListener = listener;
}
/**
* Layouts and binds recipient information to the view. If convertView is null, inflates a new
* view with getItemLaytout().
*
* @param convertView The view to bind information to.
* @param parent The parent to bind the view to if we inflate a new view.
* @param entry The recipient entry to get information from.
* @param position The position in the list.
* @param type The adapter type that is requesting the bind.
* @param constraint The constraint typed in the auto complete view.
*
* @return A view ready to be shown in the drop down list.
*/
public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position,
AdapterType type, String constraint) {
return bindView(convertView, parent, entry, position, type, constraint, null);
}
/**
* See {@link #bindView(View, ViewGroup, RecipientEntry, int, AdapterType, String)}
* @param deleteDrawable
*/
public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position,
AdapterType type, String constraint, StateListDrawable deleteDrawable) {
// Default to show all the information
String displayName = entry.getDisplayName();
String destination = entry.getDestination();
boolean showImage = true;
CharSequence destinationType = getDestinationType(entry);
final View itemView = reuseOrInflateView(convertView, parent, type);
final ViewHolder viewHolder = new ViewHolder(itemView);
// Hide some information depending on the entry type and adapter type
switch (type) {
case BASE_RECIPIENT:
if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, destination)) {
displayName = destination;
// We only show the destination for secondary entries, so clear it only for the
// first level.
if (entry.isFirstLevel()) {
destination = null;
}
}
if (!entry.isFirstLevel()) {
displayName = null;
showImage = false;
}
// For BASE_RECIPIENT set all top dividers except for the first one to be GONE.
if (viewHolder.topDivider != null) {
viewHolder.topDivider.setVisibility(position == 0 ? View.VISIBLE : View.GONE);
}
break;
case RECIPIENT_ALTERNATES:
if (position != 0) {
displayName = null;
showImage = false;
}
break;
case SINGLE_RECIPIENT:
destination = Rfc822Tokenizer.tokenize(entry.getDestination())[0].getAddress();
destinationType = null;
}
// Bind the information to the view
bindTextToView(displayName, viewHolder.displayNameView);
bindTextToView(destination, viewHolder.destinationView);
bindTextToView(destinationType, viewHolder.destinationTypeView);
bindIconToView(showImage, entry, viewHolder.imageView, type);
bindDrawableToDeleteView(deleteDrawable, viewHolder.deleteView);
return itemView;
}
/**
* Returns a new view with {@link #getItemLayoutResId(AdapterType)}.
*/
public View newView(AdapterType type) {
return mInflater.inflate(getItemLayoutResId(type), null);
}
/**
* Returns the same view, or inflates a new one if the given view was null.
*/
protected View reuseOrInflateView(View convertView, ViewGroup parent, AdapterType type) {
int itemLayout = getItemLayoutResId(type);
switch (type) {
case BASE_RECIPIENT:
case RECIPIENT_ALTERNATES:
break;
case SINGLE_RECIPIENT:
itemLayout = getAlternateItemLayoutResId(type);
break;
}
return convertView != null ? convertView : mInflater.inflate(itemLayout, parent, false);
}
/**
* Binds the text to the given text view. If the text was null, hides the text view.
*/
protected void bindTextToView(CharSequence text, TextView view) {
if (view == null) {
return;
}
if (text != null) {
view.setText(text);
view.setVisibility(View.VISIBLE);
} else {
view.setVisibility(View.GONE);
}
}
/**
* Binds the avatar icon to the image view. If we don't want to show the image, hides the
* image view.
*/
protected void bindIconToView(boolean showImage, RecipientEntry entry, ImageView view,
AdapterType type) {
if (view == null) {
return;
}
if (showImage) {
switch (type) {
case BASE_RECIPIENT:
byte[] photoBytes = entry.getPhotoBytes();
if (photoBytes != null && photoBytes.length > 0) {
final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0,
photoBytes.length);
view.setImageBitmap(photo);
} else {
view.setImageResource(getDefaultPhotoResId());
}
break;
case RECIPIENT_ALTERNATES:
Uri thumbnailUri = entry.getPhotoThumbnailUri();
if (thumbnailUri != null) {
// TODO: see if this needs to be done outside the main thread
// as it may be too slow to get immediately.
view.setImageURI(thumbnailUri);
} else {
view.setImageResource(getDefaultPhotoResId());
}
break;
case SINGLE_RECIPIENT:
default:
break;
}
view.setVisibility(View.VISIBLE);
} else {
view.setVisibility(View.GONE);
}
}
protected void bindDrawableToDeleteView(final StateListDrawable drawable, ImageView view) {
if (view == null) {
return;
}
if (drawable == null) {
view.setVisibility(View.GONE);
}
view.setImageDrawable(drawable);
if (mDeleteListener != null) {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (drawable.getCurrent() != null) {
mDeleteListener.onChipDelete();
}
}
});
}
}
protected CharSequence getDestinationType(RecipientEntry entry) {
return mQuery.getTypeLabel(mContext.getResources(), entry.getDestinationType(),
entry.getDestinationLabel()).toString().toUpperCase();
}
/**
* Returns a layout id for each item inside auto-complete list.
*
* Each View must contain two TextViews (for display name and destination) and one ImageView
* (for photo). Ids for those should be available via {@link #getDisplayNameResId()},
* {@link #getDestinationResId()}, and {@link #getPhotoResId()}.
*/
protected @LayoutRes int getItemLayoutResId(AdapterType type) {
switch (type) {
case BASE_RECIPIENT:
return R.layout.chips_autocomplete_recipient_dropdown_item;
case RECIPIENT_ALTERNATES:
return R.layout.chips_recipient_dropdown_item;
default:
return R.layout.chips_recipient_dropdown_item;
}
}
/**
* Returns a layout id for each item inside alternate auto-complete list.
*
* Each View must contain two TextViews (for display name and destination) and one ImageView
* (for photo). Ids for those should be available via {@link #getDisplayNameResId()},
* {@link #getDestinationResId()}, and {@link #getPhotoResId()}.
*/
protected @LayoutRes int getAlternateItemLayoutResId(AdapterType type) {
switch (type) {
case BASE_RECIPIENT:
return R.layout.chips_autocomplete_recipient_dropdown_item;
case RECIPIENT_ALTERNATES:
return R.layout.chips_recipient_dropdown_item;
default:
return R.layout.chips_recipient_dropdown_item;
}
}
/**
* Returns a resource ID representing an image which should be shown when ther's no relevant
* photo is available.
*/
protected @DrawableRes int getDefaultPhotoResId() {
return R.drawable.ic_contact_picture;
}
/**
* Returns an id for TextView in an item View for showing a display name. By default
* {@link android.R.id#title} is returned.
*/
protected @IdRes int getDisplayNameResId() {
return android.R.id.title;
}
/**
* Returns an id for TextView in an item View for showing a destination
* (an email address or a phone number).
* By default {@link android.R.id#text1} is returned.
*/
protected @IdRes int getDestinationResId() {
return android.R.id.text1;
}
/**
* Returns an id for TextView in an item View for showing the type of the destination.
* By default {@link android.R.id#text2} is returned.
*/
protected @IdRes int getDestinationTypeResId() {
return android.R.id.text2;
}
/**
* Returns an id for ImageView in an item View for showing photo image for a person. In default
* {@link android.R.id#icon} is returned.
*/
protected @IdRes int getPhotoResId() {
return android.R.id.icon;
}
/**
* Returns an id for ImageView in an item View for showing the delete button. In default
* {@link android.R.id#icon1} is returned.
*/
protected @IdRes int getDeleteResId() { return android.R.id.icon1; }
/**
* A holder class the view. Uses the getters in DropdownChipLayouter to find the id of the
* corresponding views.
*/
protected class ViewHolder {
public final TextView displayNameView;
public final TextView destinationView;
public final TextView destinationTypeView;
public final ImageView imageView;
public final ImageView deleteView;
public final View topDivider;
public ViewHolder(View view) {
displayNameView = (TextView) view.findViewById(getDisplayNameResId());
destinationView = (TextView) view.findViewById(getDestinationResId());
destinationTypeView = (TextView) view.findViewById(getDestinationTypeResId());
imageView = (ImageView) view.findViewById(getPhotoResId());
deleteView = (ImageView) view.findViewById(getDeleteResId());
topDivider = view.findViewById(R.id.chip_autocomplete_top_divider);
}
}
}