blob: f88ea7d22c3b8bf2dd82798537d00d2570d721b1 [file] [log] [blame]
Kevin Linb10d1c62014-01-24 12:45:00 -08001package com.android.ex.chips;
2
3import android.content.Context;
4import android.graphics.Bitmap;
5import android.graphics.BitmapFactory;
Jin Cao4db8ccc2014-07-30 10:11:07 -07006import android.graphics.drawable.StateListDrawable;
Kevin Linb10d1c62014-01-24 12:45:00 -08007import android.net.Uri;
Jin Cao4db8ccc2014-07-30 10:11:07 -07008import android.support.annotation.DrawableRes;
9import android.support.annotation.IdRes;
Jin Cao4ddcdae2014-07-28 19:03:56 -070010import android.support.annotation.LayoutRes;
Kevin Linb10d1c62014-01-24 12:45:00 -080011import android.text.TextUtils;
12import android.text.util.Rfc822Tokenizer;
13import android.view.LayoutInflater;
14import android.view.View;
15import android.view.ViewGroup;
16import android.widget.ImageView;
17import android.widget.TextView;
18
19import 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 */
25public 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
Jin Cao4db8ccc2014-07-30 10:11:07 -070035 public interface ChipDeleteListener {
36 void onChipDelete();
37 }
38
Kevin Linb10d1c62014-01-24 12:45:00 -080039 private final LayoutInflater mInflater;
40 private final Context mContext;
Jin Cao4db8ccc2014-07-30 10:11:07 -070041 private ChipDeleteListener mDeleteListener;
Kevin Linb10d1c62014-01-24 12:45:00 -080042 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
Jin Cao4db8ccc2014-07-30 10:11:07 -070053 public void setDeleteListener(ChipDeleteListener listener) {
54 mDeleteListener = listener;
55 }
56
Kevin Linb10d1c62014-01-24 12:45:00 -080057
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) {
Jin Cao4db8ccc2014-07-30 10:11:07 -070073 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) {
Kevin Linb10d1c62014-01-24 12:45:00 -080082 // 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 }
Jin Caob58c9a62014-08-06 14:29:14 -0700109
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 }
Kevin Linb10d1c62014-01-24 12:45:00 -0800114 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);
Jin Cao4db8ccc2014-07-30 10:11:07 -0700131 bindDrawableToDeleteView(deleteDrawable, viewHolder.deleteView);
Kevin Linb10d1c62014-01-24 12:45:00 -0800132
133 return itemView;
134 }
135
136 /**
Jin Cao4ddcdae2014-07-28 19:03:56 -0700137 * Returns a new view with {@link #getItemLayoutResId(AdapterType)}.
Kevin Linb10d1c62014-01-24 12:45:00 -0800138 */
Jin Cao4ddcdae2014-07-28 19:03:56 -0700139 public View newView(AdapterType type) {
140 return mInflater.inflate(getItemLayoutResId(type), null);
Kevin Linb10d1c62014-01-24 12:45:00 -0800141 }
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) {
Jin Cao4ddcdae2014-07-28 19:03:56 -0700147 int itemLayout = getItemLayoutResId(type);
Kevin Linb10d1c62014-01-24 12:45:00 -0800148 switch (type) {
149 case BASE_RECIPIENT:
150 case RECIPIENT_ALTERNATES:
151 break;
152 case SINGLE_RECIPIENT:
Jin Cao4ddcdae2014-07-28 19:03:56 -0700153 itemLayout = getAlternateItemLayoutResId(type);
Kevin Linb10d1c62014-01-24 12:45:00 -0800154 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.
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800202 view.setImageURI(thumbnailUri);
Kevin Linb10d1c62014-01-24 12:45:00 -0800203 } 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
Jin Cao4db8ccc2014-07-30 10:11:07 -0700217 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
Kevin Linb10d1c62014-01-24 12:45:00 -0800238 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 */
Jin Cao4ddcdae2014-07-28 19:03:56 -0700250 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 }
Kevin Linb10d1c62014-01-24 12:45:00 -0800259 }
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 */
Jin Cao4ddcdae2014-07-28 19:03:56 -0700268 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 }
Kevin Linb10d1c62014-01-24 12:45:00 -0800277 }
278
279 /**
280 * Returns a resource ID representing an image which should be shown when ther's no relevant
281 * photo is available.
282 */
Jin Cao4db8ccc2014-07-30 10:11:07 -0700283 protected @DrawableRes int getDefaultPhotoResId() {
Kevin Linb10d1c62014-01-24 12:45:00 -0800284 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 */
Jin Cao4db8ccc2014-07-30 10:11:07 -0700291 protected @IdRes int getDisplayNameResId() {
Kevin Linb10d1c62014-01-24 12:45:00 -0800292 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 */
Jin Cao4db8ccc2014-07-30 10:11:07 -0700300 protected @IdRes int getDestinationResId() {
Kevin Linb10d1c62014-01-24 12:45:00 -0800301 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 */
Jin Cao4db8ccc2014-07-30 10:11:07 -0700308 protected @IdRes int getDestinationTypeResId() {
Kevin Linb10d1c62014-01-24 12:45:00 -0800309 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 */
Jin Cao4db8ccc2014-07-30 10:11:07 -0700316 protected @IdRes int getPhotoResId() {
Kevin Linb10d1c62014-01-24 12:45:00 -0800317 return android.R.id.icon;
318 }
319
320 /**
Jin Cao4db8ccc2014-07-30 10:11:07 -0700321 * 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 /**
Kevin Linb10d1c62014-01-24 12:45:00 -0800327 * 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;
Jin Cao4db8ccc2014-07-30 10:11:07 -0700335 public final ImageView deleteView;
Jin Caob58c9a62014-08-06 14:29:14 -0700336 public final View topDivider;
Kevin Linb10d1c62014-01-24 12:45:00 -0800337
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());
Jin Cao4db8ccc2014-07-30 10:11:07 -0700343 deleteView = (ImageView) view.findViewById(getDeleteResId());
Jin Caob58c9a62014-08-06 14:29:14 -0700344 topDivider = view.findViewById(R.id.chip_autocomplete_top_divider);
Kevin Linb10d1c62014-01-24 12:45:00 -0800345 }
346 }
347}