Home | History | Annotate | Download | only in chips
      1 package com.android.ex.chips;
      2 
      3 import android.content.Context;
      4 import android.graphics.Bitmap;
      5 import android.graphics.BitmapFactory;
      6 import android.graphics.drawable.StateListDrawable;
      7 import android.net.Uri;
      8 import android.support.annotation.DrawableRes;
      9 import android.support.annotation.IdRes;
     10 import android.support.annotation.LayoutRes;
     11 import android.text.TextUtils;
     12 import android.text.util.Rfc822Tokenizer;
     13 import android.view.LayoutInflater;
     14 import android.view.View;
     15 import android.view.ViewGroup;
     16 import android.widget.ImageView;
     17 import android.widget.TextView;
     18 
     19 import com.android.ex.chips.Queries.Query;
     20 
     21 /**
     22  * A class that inflates and binds the views in the dropdown list from
     23  * RecipientEditTextView.
     24  */
     25 public class DropdownChipLayouter {
     26     /**
     27      * The type of adapter that is requesting a chip layout.
     28      */
     29     public enum AdapterType {
     30         BASE_RECIPIENT,
     31         RECIPIENT_ALTERNATES,
     32         SINGLE_RECIPIENT
     33     }
     34 
     35     public interface ChipDeleteListener {
     36         void onChipDelete();
     37     }
     38 
     39     private final LayoutInflater mInflater;
     40     private final Context mContext;
     41     private ChipDeleteListener mDeleteListener;
     42     private Query mQuery;
     43 
     44     public DropdownChipLayouter(LayoutInflater inflater, Context context) {
     45         mInflater = inflater;
     46         mContext = context;
     47     }
     48 
     49     public void setQuery(Query query) {
     50         mQuery = query;
     51     }
     52 
     53     public void setDeleteListener(ChipDeleteListener listener) {
     54         mDeleteListener = listener;
     55     }
     56 
     57 
     58     /**
     59      * Layouts and binds recipient information to the view. If convertView is null, inflates a new
     60      * view with getItemLaytout().
     61      *
     62      * @param convertView The view to bind information to.
     63      * @param parent The parent to bind the view to if we inflate a new view.
     64      * @param entry The recipient entry to get information from.
     65      * @param position The position in the list.
     66      * @param type The adapter type that is requesting the bind.
     67      * @param constraint The constraint typed in the auto complete view.
     68      *
     69      * @return A view ready to be shown in the drop down list.
     70      */
     71     public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position,
     72         AdapterType type, String constraint) {
     73         return bindView(convertView, parent, entry, position, type, constraint, null);
     74     }
     75 
     76     /**
     77      * See {@link #bindView(View, ViewGroup, RecipientEntry, int, AdapterType, String)}
     78      * @param deleteDrawable
     79      */
     80     public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position,
     81             AdapterType type, String constraint, StateListDrawable deleteDrawable) {
     82         // Default to show all the information
     83         String displayName = entry.getDisplayName();
     84         String destination = entry.getDestination();
     85         boolean showImage = true;
     86         CharSequence destinationType = getDestinationType(entry);
     87 
     88         final View itemView = reuseOrInflateView(convertView, parent, type);
     89 
     90         final ViewHolder viewHolder = new ViewHolder(itemView);
     91 
     92         // Hide some information depending on the entry type and adapter type
     93         switch (type) {
     94             case BASE_RECIPIENT:
     95                 if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, destination)) {
     96                     displayName = destination;
     97 
     98                     // We only show the destination for secondary entries, so clear it only for the
     99                     // first level.
    100                     if (entry.isFirstLevel()) {
    101                         destination = null;
    102                     }
    103                 }
    104 
    105                 if (!entry.isFirstLevel()) {
    106                     displayName = null;
    107                     showImage = false;
    108                 }
    109 
    110                 // For BASE_RECIPIENT set all top dividers except for the first one to be GONE.
    111                 if (viewHolder.topDivider != null) {
    112                     viewHolder.topDivider.setVisibility(position == 0 ? View.VISIBLE : View.GONE);
    113                 }
    114                 break;
    115             case RECIPIENT_ALTERNATES:
    116                 if (position != 0) {
    117                     displayName = null;
    118                     showImage = false;
    119                 }
    120                 break;
    121             case SINGLE_RECIPIENT:
    122                 destination = Rfc822Tokenizer.tokenize(entry.getDestination())[0].getAddress();
    123                 destinationType = null;
    124         }
    125 
    126         // Bind the information to the view
    127         bindTextToView(displayName, viewHolder.displayNameView);
    128         bindTextToView(destination, viewHolder.destinationView);
    129         bindTextToView(destinationType, viewHolder.destinationTypeView);
    130         bindIconToView(showImage, entry, viewHolder.imageView, type);
    131         bindDrawableToDeleteView(deleteDrawable, viewHolder.deleteView);
    132 
    133         return itemView;
    134     }
    135 
    136     /**
    137      * Returns a new view with {@link #getItemLayoutResId(AdapterType)}.
    138      */
    139     public View newView(AdapterType type) {
    140         return mInflater.inflate(getItemLayoutResId(type), null);
    141     }
    142 
    143     /**
    144      * Returns the same view, or inflates a new one if the given view was null.
    145      */
    146     protected View reuseOrInflateView(View convertView, ViewGroup parent, AdapterType type) {
    147         int itemLayout = getItemLayoutResId(type);
    148         switch (type) {
    149             case BASE_RECIPIENT:
    150             case RECIPIENT_ALTERNATES:
    151                 break;
    152             case SINGLE_RECIPIENT:
    153                 itemLayout = getAlternateItemLayoutResId(type);
    154                 break;
    155         }
    156         return convertView != null ? convertView : mInflater.inflate(itemLayout, parent, false);
    157     }
    158 
    159     /**
    160      * Binds the text to the given text view. If the text was null, hides the text view.
    161      */
    162     protected void bindTextToView(CharSequence text, TextView view) {
    163         if (view == null) {
    164             return;
    165         }
    166 
    167         if (text != null) {
    168             view.setText(text);
    169             view.setVisibility(View.VISIBLE);
    170         } else {
    171             view.setVisibility(View.GONE);
    172         }
    173     }
    174 
    175     /**
    176      * Binds the avatar icon to the image view. If we don't want to show the image, hides the
    177      * image view.
    178      */
    179     protected void bindIconToView(boolean showImage, RecipientEntry entry, ImageView view,
    180         AdapterType type) {
    181         if (view == null) {
    182             return;
    183         }
    184 
    185         if (showImage) {
    186             switch (type) {
    187                 case BASE_RECIPIENT:
    188                     byte[] photoBytes = entry.getPhotoBytes();
    189                     if (photoBytes != null && photoBytes.length > 0) {
    190                         final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0,
    191                             photoBytes.length);
    192                         view.setImageBitmap(photo);
    193                     } else {
    194                         view.setImageResource(getDefaultPhotoResId());
    195                     }
    196                     break;
    197                 case RECIPIENT_ALTERNATES:
    198                     Uri thumbnailUri = entry.getPhotoThumbnailUri();
    199                     if (thumbnailUri != null) {
    200                         // TODO: see if this needs to be done outside the main thread
    201                         // as it may be too slow to get immediately.
    202                         view.setImageURI(thumbnailUri);
    203                     } else {
    204                         view.setImageResource(getDefaultPhotoResId());
    205                     }
    206                     break;
    207                 case SINGLE_RECIPIENT:
    208                 default:
    209                     break;
    210             }
    211             view.setVisibility(View.VISIBLE);
    212         } else {
    213             view.setVisibility(View.GONE);
    214         }
    215     }
    216 
    217     protected void bindDrawableToDeleteView(final StateListDrawable drawable, ImageView view) {
    218         if (view == null) {
    219             return;
    220         }
    221         if (drawable == null) {
    222             view.setVisibility(View.GONE);
    223         }
    224 
    225         view.setImageDrawable(drawable);
    226         if (mDeleteListener != null) {
    227             view.setOnClickListener(new View.OnClickListener() {
    228                 @Override
    229                 public void onClick(View view) {
    230                     if (drawable.getCurrent() != null) {
    231                         mDeleteListener.onChipDelete();
    232                     }
    233                 }
    234             });
    235         }
    236     }
    237 
    238     protected CharSequence getDestinationType(RecipientEntry entry) {
    239         return mQuery.getTypeLabel(mContext.getResources(), entry.getDestinationType(),
    240             entry.getDestinationLabel()).toString().toUpperCase();
    241     }
    242 
    243     /**
    244      * Returns a layout id for each item inside auto-complete list.
    245      *
    246      * Each View must contain two TextViews (for display name and destination) and one ImageView
    247      * (for photo). Ids for those should be available via {@link #getDisplayNameResId()},
    248      * {@link #getDestinationResId()}, and {@link #getPhotoResId()}.
    249      */
    250     protected @LayoutRes int getItemLayoutResId(AdapterType type) {
    251         switch (type) {
    252             case BASE_RECIPIENT:
    253                 return R.layout.chips_autocomplete_recipient_dropdown_item;
    254             case RECIPIENT_ALTERNATES:
    255                 return R.layout.chips_recipient_dropdown_item;
    256             default:
    257                 return R.layout.chips_recipient_dropdown_item;
    258         }
    259     }
    260 
    261     /**
    262      * Returns a layout id for each item inside alternate auto-complete list.
    263      *
    264      * Each View must contain two TextViews (for display name and destination) and one ImageView
    265      * (for photo). Ids for those should be available via {@link #getDisplayNameResId()},
    266      * {@link #getDestinationResId()}, and {@link #getPhotoResId()}.
    267      */
    268     protected @LayoutRes int getAlternateItemLayoutResId(AdapterType type) {
    269         switch (type) {
    270             case BASE_RECIPIENT:
    271                 return R.layout.chips_autocomplete_recipient_dropdown_item;
    272             case RECIPIENT_ALTERNATES:
    273                 return R.layout.chips_recipient_dropdown_item;
    274             default:
    275                 return R.layout.chips_recipient_dropdown_item;
    276         }
    277     }
    278 
    279     /**
    280      * Returns a resource ID representing an image which should be shown when ther's no relevant
    281      * photo is available.
    282      */
    283     protected @DrawableRes int getDefaultPhotoResId() {
    284         return R.drawable.ic_contact_picture;
    285     }
    286 
    287     /**
    288      * Returns an id for TextView in an item View for showing a display name. By default
    289      * {@link android.R.id#title} is returned.
    290      */
    291     protected @IdRes int getDisplayNameResId() {
    292         return android.R.id.title;
    293     }
    294 
    295     /**
    296      * Returns an id for TextView in an item View for showing a destination
    297      * (an email address or a phone number).
    298      * By default {@link android.R.id#text1} is returned.
    299      */
    300     protected @IdRes int getDestinationResId() {
    301         return android.R.id.text1;
    302     }
    303 
    304     /**
    305      * Returns an id for TextView in an item View for showing the type of the destination.
    306      * By default {@link android.R.id#text2} is returned.
    307      */
    308     protected @IdRes int getDestinationTypeResId() {
    309         return android.R.id.text2;
    310     }
    311 
    312     /**
    313      * Returns an id for ImageView in an item View for showing photo image for a person. In default
    314      * {@link android.R.id#icon} is returned.
    315      */
    316     protected @IdRes int getPhotoResId() {
    317         return android.R.id.icon;
    318     }
    319 
    320     /**
    321      * Returns an id for ImageView in an item View for showing the delete button. In default
    322      * {@link android.R.id#icon1} is returned.
    323      */
    324     protected @IdRes int getDeleteResId() { return android.R.id.icon1; }
    325 
    326     /**
    327      * A holder class the view. Uses the getters in DropdownChipLayouter to find the id of the
    328      * corresponding views.
    329      */
    330     protected class ViewHolder {
    331         public final TextView displayNameView;
    332         public final TextView destinationView;
    333         public final TextView destinationTypeView;
    334         public final ImageView imageView;
    335         public final ImageView deleteView;
    336         public final View topDivider;
    337 
    338         public ViewHolder(View view) {
    339             displayNameView = (TextView) view.findViewById(getDisplayNameResId());
    340             destinationView = (TextView) view.findViewById(getDestinationResId());
    341             destinationTypeView = (TextView) view.findViewById(getDestinationTypeResId());
    342             imageView = (ImageView) view.findViewById(getPhotoResId());
    343             deleteView = (ImageView) view.findViewById(getDeleteResId());
    344             topDivider = view.findViewById(R.id.chip_autocomplete_top_divider);
    345         }
    346     }
    347 }
    348