1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.messaging.ui.conversationlist;
17 
18 import android.content.Context;
19 import android.content.res.Resources;
20 import android.database.Cursor;
21 import android.graphics.Color;
22 import android.graphics.Rect;
23 import android.graphics.Typeface;
24 import android.net.Uri;
25 import android.support.v4.text.BidiFormatter;
26 import android.support.v4.text.TextDirectionHeuristicsCompat;
27 import android.text.TextPaint;
28 import android.text.TextUtils;
29 import android.util.AttributeSet;
30 import android.view.View;
31 import android.view.View.OnClickListener;
32 import android.view.View.OnLayoutChangeListener;
33 import android.view.View.OnLongClickListener;
34 import android.view.ViewGroup;
35 import android.widget.FrameLayout;
36 import android.widget.ImageView;
37 import android.widget.TextView;
38 
39 import com.android.messaging.Factory;
40 import com.android.messaging.R;
41 import com.android.messaging.annotation.VisibleForAnimation;
42 import com.android.messaging.datamodel.MessagingContentProvider;
43 import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction;
44 import com.android.messaging.datamodel.data.ConversationListItemData;
45 import com.android.messaging.datamodel.data.MessageData;
46 import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
47 import com.android.messaging.sms.MmsUtils;
48 import com.android.messaging.ui.AsyncImageView;
49 import com.android.messaging.ui.AudioAttachmentView;
50 import com.android.messaging.ui.ContactIconView;
51 import com.android.messaging.ui.SnackBar;
52 import com.android.messaging.ui.SnackBarInteraction;
53 import com.android.messaging.util.Assert;
54 import com.android.messaging.util.ContentType;
55 import com.android.messaging.util.ImageUtils;
56 import com.android.messaging.util.OsUtil;
57 import com.android.messaging.util.PhoneUtils;
58 import com.android.messaging.util.Typefaces;
59 import com.android.messaging.util.UiUtils;
60 import com.android.messaging.util.UriUtil;
61 
62 import java.util.List;
63 
64 /**
65  * The view for a single entry in a conversation list.
66  */
67 public class ConversationListItemView extends FrameLayout implements OnClickListener,
68         OnLongClickListener, OnLayoutChangeListener {
69     static final int UNREAD_SNIPPET_LINE_COUNT = 3;
70     static final int NO_UNREAD_SNIPPET_LINE_COUNT = 1;
71     private int mListItemReadColor;
72     private int mListItemUnreadColor;
73     private Typeface mListItemReadTypeface;
74     private Typeface mListItemUnreadTypeface;
75     private static String sPlusOneString;
76     private static String sPlusNString;
77 
78     public interface HostInterface {
isConversationSelected(final String conversationId)79         boolean isConversationSelected(final String conversationId);
onConversationClicked(final ConversationListItemData conversationListItemData, boolean isLongClick, final ConversationListItemView conversationView)80         void onConversationClicked(final ConversationListItemData conversationListItemData,
81                 boolean isLongClick, final ConversationListItemView conversationView);
isSwipeAnimatable()82         boolean isSwipeAnimatable();
getSnackBarInteractions()83         List<SnackBarInteraction> getSnackBarInteractions();
startFullScreenPhotoViewer(final Uri initialPhoto, final Rect initialPhotoBounds, final Uri photosUri)84         void startFullScreenPhotoViewer(final Uri initialPhoto, final Rect initialPhotoBounds,
85                 final Uri photosUri);
startFullScreenVideoViewer(final Uri videoUri)86         void startFullScreenVideoViewer(final Uri videoUri);
isSelectionMode()87         boolean isSelectionMode();
88     }
89 
90     private final OnClickListener fullScreenPreviewClickListener = new OnClickListener() {
91         @Override
92         public void onClick(final View v) {
93             final String previewType = mData.getShowDraft() ?
94                     mData.getDraftPreviewContentType() : mData.getPreviewContentType();
95             Assert.isTrue(ContentType.isImageType(previewType) ||
96                     ContentType.isVideoType(previewType));
97 
98             final Uri previewUri = mData.getShowDraft() ?
99                     mData.getDraftPreviewUri() : mData.getPreviewUri();
100             if (ContentType.isImageType(previewType)) {
101                 final Uri imagesUri = mData.getShowDraft() ?
102                         MessagingContentProvider.buildDraftImagesUri(mData.getConversationId()) :
103                         MessagingContentProvider
104                                 .buildConversationImagesUri(mData.getConversationId());
105                 final Rect previewImageBounds = UiUtils.getMeasuredBoundsOnScreen(v);
106                 mHostInterface.startFullScreenPhotoViewer(
107                         previewUri, previewImageBounds, imagesUri);
108             } else {
109                 mHostInterface.startFullScreenVideoViewer(previewUri);
110             }
111         }
112     };
113 
114     private final ConversationListItemData mData;
115 
116     private int mAnimatingCount;
117     private ViewGroup mSwipeableContainer;
118     private ViewGroup mCrossSwipeBackground;
119     private ViewGroup mSwipeableContent;
120     private TextView mConversationNameView;
121     private TextView mSnippetTextView;
122     private TextView mSubjectTextView;
123     private TextView mTimestampTextView;
124     private ContactIconView mContactIconView;
125     private ImageView mContactCheckmarkView;
126     private ImageView mNotificationBellView;
127     private ImageView mFailedStatusIconView;
128     private ImageView mCrossSwipeArchiveLeftImageView;
129     private ImageView mCrossSwipeArchiveRightImageView;
130     private AsyncImageView mImagePreviewView;
131     private AudioAttachmentView mAudioAttachmentView;
132     private HostInterface mHostInterface;
133 
ConversationListItemView(final Context context, final AttributeSet attrs)134     public ConversationListItemView(final Context context, final AttributeSet attrs) {
135         super(context, attrs);
136         mData = new ConversationListItemData();
137         final Resources res = context.getResources();
138     }
139 
140     @Override
onFinishInflate()141     protected void onFinishInflate() {
142         mSwipeableContainer = (ViewGroup) findViewById(R.id.swipeableContainer);
143         mCrossSwipeBackground = (ViewGroup) findViewById(R.id.crossSwipeBackground);
144         mSwipeableContent = (ViewGroup) findViewById(R.id.swipeableContent);
145         mConversationNameView = (TextView) findViewById(R.id.conversation_name);
146         mSnippetTextView = (TextView) findViewById(R.id.conversation_snippet);
147         mSubjectTextView = (TextView) findViewById(R.id.conversation_subject);
148         mTimestampTextView = (TextView) findViewById(R.id.conversation_timestamp);
149         mContactIconView = (ContactIconView) findViewById(R.id.conversation_icon);
150         mContactCheckmarkView = (ImageView) findViewById(R.id.conversation_checkmark);
151         mNotificationBellView = (ImageView) findViewById(R.id.conversation_notification_bell);
152         mFailedStatusIconView = (ImageView) findViewById(R.id.conversation_failed_status_icon);
153         mCrossSwipeArchiveLeftImageView = (ImageView) findViewById(R.id.crossSwipeArchiveIconLeft);
154         mCrossSwipeArchiveRightImageView =
155                 (ImageView) findViewById(R.id.crossSwipeArchiveIconRight);
156         mImagePreviewView = (AsyncImageView) findViewById(R.id.conversation_image_preview);
157         mAudioAttachmentView = (AudioAttachmentView) findViewById(R.id.audio_attachment_view);
158         mConversationNameView.addOnLayoutChangeListener(this);
159         mSnippetTextView.addOnLayoutChangeListener(this);
160 
161         final Resources resources = getContext().getResources();
162         mListItemReadColor = resources.getColor(R.color.conversation_list_item_read);
163         mListItemUnreadColor = resources.getColor(R.color.conversation_list_item_unread);
164 
165         mListItemReadTypeface = Typefaces.getRobotoNormal();
166         mListItemUnreadTypeface = Typefaces.getRobotoBold();
167 
168         if (OsUtil.isAtLeastL()) {
169             setTransitionGroup(true);
170         }
171     }
172 
173     @Override
onLayoutChange(final View v, final int left, final int top, final int right, final int bottom, final int oldLeft, final int oldTop, final int oldRight, final int oldBottom)174     public void onLayoutChange(final View v, final int left, final int top, final int right,
175             final int bottom, final int oldLeft, final int oldTop, final int oldRight,
176             final int oldBottom) {
177         if (v == mConversationNameView) {
178             setConversationName();
179         } else if (v == mSnippetTextView) {
180             setSnippet();
181         } else if (v == mSubjectTextView) {
182             setSubject();
183         }
184     }
185 
setConversationName()186     private void setConversationName() {
187         if (mData.getIsRead() || mData.getShowDraft()) {
188             mConversationNameView.setTextColor(mListItemReadColor);
189             mConversationNameView.setTypeface(mListItemReadTypeface);
190         } else {
191             mConversationNameView.setTextColor(mListItemUnreadColor);
192             mConversationNameView.setTypeface(mListItemUnreadTypeface);
193         }
194 
195         final String conversationName = mData.getName();
196 
197         // For group conversations, ellipsize the group members that do not fit
198         final CharSequence ellipsizedName = UiUtils.commaEllipsize(
199                 conversationName,
200                 mConversationNameView.getPaint(),
201                 mConversationNameView.getMeasuredWidth(),
202                 getPlusOneString(),
203                 getPlusNString());
204         // RTL : To format conversation name if it happens to be phone number.
205         final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
206         final String bidiFormattedName = bidiFormatter.unicodeWrap(
207                 ellipsizedName.toString(),
208                 TextDirectionHeuristicsCompat.LTR);
209 
210         mConversationNameView.setText(bidiFormattedName);
211     }
212 
getPlusOneString()213     private static String getPlusOneString() {
214         if (sPlusOneString == null) {
215             sPlusOneString =  Factory.get().getApplicationContext().getResources()
216                     .getString(R.string.plus_one);
217         }
218         return sPlusOneString;
219     }
220 
getPlusNString()221     private static String getPlusNString() {
222         if (sPlusNString == null) {
223             sPlusNString =  Factory.get().getApplicationContext().getResources()
224                     .getString(R.string.plus_n);
225         }
226         return sPlusNString;
227     }
228 
setSubject()229     private void setSubject() {
230         final String subjectText = mData.getShowDraft() ?
231                 mData.getDraftSubject() :
232                     MmsUtils.cleanseMmsSubject(getContext().getResources(), mData.getSubject());
233         if (!TextUtils.isEmpty(subjectText)) {
234             final String subjectPrepend = getResources().getString(R.string.subject_label);
235             mSubjectTextView.setText(TextUtils.concat(subjectPrepend, subjectText));
236             mSubjectTextView.setVisibility(VISIBLE);
237         } else {
238             mSubjectTextView.setVisibility(GONE);
239         }
240     }
241 
setSnippet()242     private void setSnippet() {
243         mSnippetTextView.setText(getSnippetText());
244     }
245 
246     // Resource Ids of content descriptions prefixes for different message status.
247     private static final int [][][] sPrimaryContentDescriptions = {
248         // 1:1 conversation
249         {
250             // Incoming message
251             {
252                 R.string.one_on_one_incoming_failed_message_prefix,
253                 R.string.one_on_one_incoming_successful_message_prefix
254             },
255             // Outgoing message
256             {
257                 R.string.one_on_one_outgoing_failed_message_prefix,
258                 R.string.one_on_one_outgoing_successful_message_prefix,
259                 R.string.one_on_one_outgoing_draft_message_prefix,
260                 R.string.one_on_one_outgoing_sending_message_prefix,
261             }
262         },
263 
264         // Group conversation
265         {
266             // Incoming message
267             {
268                 R.string.group_incoming_failed_message_prefix,
269                 R.string.group_incoming_successful_message_prefix,
270             },
271             // Outgoing message
272             {
273                 R.string.group_outgoing_failed_message_prefix,
274                 R.string.group_outgoing_successful_message_prefix,
275                 R.string.group_outgoing_draft_message_prefix,
276                 R.string.group_outgoing_sending_message_prefix,
277             }
278         }
279     };
280 
281     // Resource Id of the secondary part of the content description for an edge case of a message
282     // which is in both draft status and failed status.
283     private static final int sSecondaryContentDescription =
284                                         R.string.failed_message_content_description;
285 
286     // 1:1 versus group
287     private static final int CONV_TYPE_ONE_ON_ONE_INDEX = 0;
288     private static final int CONV_TYPE_ONE_GROUP_INDEX = 1;
289     // Direction
290     private static final int DIRECTION_INCOMING_INDEX = 0;
291     private static final int DIRECTION_OUTGOING_INDEX = 1;
292     // Message status
293     private static final int MESSAGE_STATUS_FAILED_INDEX = 0;
294     private static final int MESSAGE_STATUS_SUCCESSFUL_INDEX = 1;
295     private static final int MESSAGE_STATUS_DRAFT_INDEX = 2;
296     private static final int MESSAGE_STATUS_SENDING_INDEX = 3;
297 
298     private static final int WIDTH_FOR_ACCESSIBLE_CONVERSATION_NAME = 600;
299 
buildContentDescription(final Resources resources, final ConversationListItemData data, final TextPaint conversationNameViewPaint)300     public static String buildContentDescription(final Resources resources,
301             final ConversationListItemData data, final TextPaint conversationNameViewPaint) {
302         int messageStatusIndex;
303         boolean outgoingSnippet = data.getIsMessageTypeOutgoing() || data.getShowDraft();
304         if (outgoingSnippet) {
305             if (data.getShowDraft()) {
306                 messageStatusIndex = MESSAGE_STATUS_DRAFT_INDEX;
307             } else if (data.getIsSendRequested()) {
308                 messageStatusIndex = MESSAGE_STATUS_SENDING_INDEX;
309             } else {
310                 messageStatusIndex = data.getIsFailedStatus() ? MESSAGE_STATUS_FAILED_INDEX
311                         : MESSAGE_STATUS_SUCCESSFUL_INDEX;
312             }
313         } else {
314             messageStatusIndex = data.getIsFailedStatus() ? MESSAGE_STATUS_FAILED_INDEX
315                     : MESSAGE_STATUS_SUCCESSFUL_INDEX;
316         }
317 
318         int resId = sPrimaryContentDescriptions
319                 [data.getIsGroup() ? CONV_TYPE_ONE_GROUP_INDEX : CONV_TYPE_ONE_ON_ONE_INDEX]
320                 [outgoingSnippet ? DIRECTION_OUTGOING_INDEX : DIRECTION_INCOMING_INDEX]
321                 [messageStatusIndex];
322 
323         final String snippetText = data.getShowDraft() ?
324                 data.getDraftSnippetText() : data.getSnippetText();
325 
326         final String conversationName = data.getName();
327         String senderOrConvName = outgoingSnippet ? conversationName : data.getSnippetSenderName();
328 
329         String primaryContentDescription = resources.getString(resId, senderOrConvName,
330                 snippetText == null ? "" : snippetText,
331                 data.getFormattedTimestamp(),
332                 // This is used only for incoming group messages
333                 conversationName);
334         String contentDescription = primaryContentDescription;
335 
336         // An edge case : for an outgoing message, it might be in both draft status and
337         // failed status.
338         if (outgoingSnippet && data.getShowDraft() && data.getIsFailedStatus()) {
339             StringBuilder contentDescriptionBuilder = new StringBuilder();
340             contentDescriptionBuilder.append(primaryContentDescription);
341 
342             String secondaryContentDescription =
343                     resources.getString(sSecondaryContentDescription);
344             contentDescriptionBuilder.append(" ");
345             contentDescriptionBuilder.append(secondaryContentDescription);
346             contentDescription = contentDescriptionBuilder.toString();
347         }
348         return contentDescription;
349     }
350 
351     /**
352      * Fills in the data associated with this view.
353      *
354      * @param cursor The cursor from a ConversationList that this view is in, pointing to its
355      * entry.
356      */
bind(final Cursor cursor, final HostInterface hostInterface)357     public void bind(final Cursor cursor, final HostInterface hostInterface) {
358         // Update our UI model
359         mHostInterface = hostInterface;
360         mData.bind(cursor);
361 
362         resetAnimatingState();
363 
364         mSwipeableContainer.setOnClickListener(this);
365         mSwipeableContainer.setOnLongClickListener(this);
366 
367         final Resources resources = getContext().getResources();
368 
369         int color;
370         final int maxLines;
371         final Typeface typeface;
372         final int typefaceStyle = mData.getShowDraft() ? Typeface.ITALIC : Typeface.NORMAL;
373         final String snippetText = getSnippetText();
374 
375         if (mData.getIsRead() || mData.getShowDraft()) {
376             maxLines = TextUtils.isEmpty(snippetText) ? 0 : NO_UNREAD_SNIPPET_LINE_COUNT;
377             color = mListItemReadColor;
378             typeface = mListItemReadTypeface;
379         } else {
380             maxLines = TextUtils.isEmpty(snippetText) ? 0 : UNREAD_SNIPPET_LINE_COUNT;
381             color = mListItemUnreadColor;
382             typeface = mListItemUnreadTypeface;
383         }
384 
385         mSnippetTextView.setMaxLines(maxLines);
386         mSnippetTextView.setTextColor(color);
387         mSnippetTextView.setTypeface(typeface, typefaceStyle);
388         mSubjectTextView.setTextColor(color);
389         mSubjectTextView.setTypeface(typeface, typefaceStyle);
390 
391         setSnippet();
392         setConversationName();
393         setSubject();
394         setContentDescription(buildContentDescription(resources, mData,
395                 mConversationNameView.getPaint()));
396 
397         final boolean isDefaultSmsApp = PhoneUtils.getDefault().isDefaultSmsApp();
398         // don't show the error state unless we're the default sms app
399         if (mData.getIsFailedStatus() && isDefaultSmsApp) {
400             mTimestampTextView.setTextColor(resources.getColor(R.color.conversation_list_error));
401             mTimestampTextView.setTypeface(mListItemReadTypeface, typefaceStyle);
402             int failureMessageId = R.string.message_status_download_failed;
403             if (mData.getIsMessageTypeOutgoing()) {
404                 failureMessageId = MmsUtils.mapRawStatusToErrorResourceId(mData.getMessageStatus(),
405                         mData.getMessageRawTelephonyStatus());
406             }
407             mTimestampTextView.setText(resources.getString(failureMessageId));
408         } else if (mData.getShowDraft()
409                 || mData.getMessageStatus() == MessageData.BUGLE_STATUS_OUTGOING_DRAFT
410                 // also check for unknown status which we get because sometimes the conversation
411                 // row is left with a latest_message_id of a no longer existing message and
412                 // therefore the join values come back as null (or in this case zero).
413                 || mData.getMessageStatus() == MessageData.BUGLE_STATUS_UNKNOWN) {
414             mTimestampTextView.setTextColor(mListItemReadColor);
415             mTimestampTextView.setTypeface(mListItemReadTypeface, typefaceStyle);
416             mTimestampTextView.setText(resources.getString(
417                     R.string.conversation_list_item_view_draft_message));
418          } else {
419             mTimestampTextView.setTextColor(mListItemReadColor);
420             mTimestampTextView.setTypeface(mListItemReadTypeface, typefaceStyle);
421             final String formattedTimestamp = mData.getFormattedTimestamp();
422             if (mData.getIsSendRequested()) {
423                 mTimestampTextView.setText(R.string.message_status_sending);
424             } else {
425                 mTimestampTextView.setText(formattedTimestamp);
426             }
427         }
428 
429         final boolean isSelected = mHostInterface.isConversationSelected(mData.getConversationId());
430         setSelected(isSelected);
431         Uri iconUri = null;
432         int contactIconVisibility = GONE;
433         int checkmarkVisiblity = GONE;
434         int failStatusVisiblity = GONE;
435         if (isSelected) {
436             checkmarkVisiblity = VISIBLE;
437         } else {
438             contactIconVisibility = VISIBLE;
439             // Only show the fail icon if it is not a group conversation.
440             // And also require that we be the default sms app.
441             if (mData.getIsFailedStatus() && !mData.getIsGroup() && isDefaultSmsApp) {
442                 failStatusVisiblity = VISIBLE;
443             }
444         }
445         if (mData.getIcon() != null) {
446             iconUri = Uri.parse(mData.getIcon());
447         }
448         mContactIconView.setImageResourceUri(iconUri, mData.getParticipantContactId(),
449                 mData.getParticipantLookupKey(), mData.getOtherParticipantNormalizedDestination());
450         mContactIconView.setVisibility(contactIconVisibility);
451         mContactIconView.setOnLongClickListener(this);
452         mContactIconView.setClickable(!mHostInterface.isSelectionMode());
453         mContactIconView.setLongClickable(!mHostInterface.isSelectionMode());
454 
455         mContactCheckmarkView.setVisibility(checkmarkVisiblity);
456         mFailedStatusIconView.setVisibility(failStatusVisiblity);
457 
458         final Uri previewUri = mData.getShowDraft() ?
459                 mData.getDraftPreviewUri() : mData.getPreviewUri();
460         final String previewContentType = mData.getShowDraft() ?
461                 mData.getDraftPreviewContentType() : mData.getPreviewContentType();
462         OnClickListener previewClickListener = null;
463         Uri previewImageUri = null;
464         int previewImageVisibility = GONE;
465         int audioPreviewVisiblity = GONE;
466         if (previewUri != null && !TextUtils.isEmpty(previewContentType)) {
467             if (ContentType.isAudioType(previewContentType)) {
468                 boolean incoming = !(mData.getShowDraft() || mData.getIsMessageTypeOutgoing());
469                 mAudioAttachmentView.bind(previewUri, incoming, false);
470                 audioPreviewVisiblity = VISIBLE;
471             } else if (ContentType.isVideoType(previewContentType)) {
472                 previewImageUri = UriUtil.getUriForResourceId(
473                         getContext(), R.drawable.ic_preview_play);
474                 previewClickListener = fullScreenPreviewClickListener;
475                 previewImageVisibility = VISIBLE;
476             } else if (ContentType.isImageType(previewContentType)) {
477                 previewImageUri = previewUri;
478                 previewClickListener = fullScreenPreviewClickListener;
479                 previewImageVisibility = VISIBLE;
480             }
481         }
482 
483         final int imageSize = resources.getDimensionPixelSize(
484                 R.dimen.conversation_list_image_preview_size);
485         mImagePreviewView.setImageResourceId(
486                 new UriImageRequestDescriptor(previewImageUri, imageSize, imageSize,
487                         true /* allowCompression */, false /* isStatic */, false /*cropToCircle*/,
488                         ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
489                         ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */));
490         mImagePreviewView.setOnLongClickListener(this);
491         mImagePreviewView.setVisibility(previewImageVisibility);
492         mImagePreviewView.setOnClickListener(previewClickListener);
493         mAudioAttachmentView.setOnLongClickListener(this);
494         mAudioAttachmentView.setVisibility(audioPreviewVisiblity);
495 
496         final int notificationBellVisiblity = mData.getNotificationEnabled() ? GONE : VISIBLE;
497         mNotificationBellView.setVisibility(notificationBellVisiblity);
498     }
499 
isSwipeAnimatable()500     public boolean isSwipeAnimatable() {
501         return mHostInterface.isSwipeAnimatable();
502     }
503 
504     @VisibleForAnimation
getSwipeTranslationX()505     public float getSwipeTranslationX() {
506         return mSwipeableContainer.getTranslationX();
507     }
508 
509     @VisibleForAnimation
setSwipeTranslationX(final float translationX)510     public void setSwipeTranslationX(final float translationX) {
511         mSwipeableContainer.setTranslationX(translationX);
512         if (translationX == 0) {
513             mCrossSwipeBackground.setVisibility(View.GONE);
514             mCrossSwipeArchiveLeftImageView.setVisibility(GONE);
515             mCrossSwipeArchiveRightImageView.setVisibility(GONE);
516 
517             mSwipeableContainer.setBackgroundColor(Color.TRANSPARENT);
518         } else {
519             mCrossSwipeBackground.setVisibility(View.VISIBLE);
520             if (translationX > 0) {
521                 mCrossSwipeArchiveLeftImageView.setVisibility(VISIBLE);
522                 mCrossSwipeArchiveRightImageView.setVisibility(GONE);
523             } else {
524                 mCrossSwipeArchiveLeftImageView.setVisibility(GONE);
525                 mCrossSwipeArchiveRightImageView.setVisibility(VISIBLE);
526             }
527             mSwipeableContainer.setBackgroundResource(R.drawable.swipe_shadow_drag);
528         }
529     }
530 
onSwipeComplete()531     public void onSwipeComplete() {
532         final String conversationId = mData.getConversationId();
533         UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
534 
535         final Runnable undoRunnable = new Runnable() {
536             @Override
537             public void run() {
538                 UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
539             }
540         };
541         final String message = getResources().getString(R.string.archived_toast_message, 1);
542         UiUtils.showSnackBar(getContext(), getRootView(), message, undoRunnable,
543                 SnackBar.Action.SNACK_BAR_UNDO,
544                 mHostInterface.getSnackBarInteractions());
545     }
546 
setShortAndLongClickable(final boolean clickable)547     private void setShortAndLongClickable(final boolean clickable) {
548         setClickable(clickable);
549         setLongClickable(clickable);
550     }
551 
resetAnimatingState()552     private void resetAnimatingState() {
553         mAnimatingCount = 0;
554         setShortAndLongClickable(true);
555         setSwipeTranslationX(0);
556     }
557 
558     /**
559      * Notifies this view that it is undergoing animation. This view should disable its click
560      * targets.
561      *
562      * The animating counter is used to reset the swipe controller when the counter becomes 0. A
563      * positive counter also makes the view not clickable.
564      */
setAnimating(final boolean animating)565     public final void setAnimating(final boolean animating) {
566         final int oldAnimatingCount = mAnimatingCount;
567         if (animating) {
568             mAnimatingCount++;
569         } else {
570             mAnimatingCount--;
571             if (mAnimatingCount < 0) {
572                 mAnimatingCount = 0;
573             }
574         }
575 
576         if (mAnimatingCount == 0) {
577             // New count is 0. All animations ended.
578             setShortAndLongClickable(true);
579         } else if (oldAnimatingCount == 0) {
580             // New count is > 0. Waiting for some animations to end.
581             setShortAndLongClickable(false);
582         }
583     }
584 
isAnimating()585     public boolean isAnimating() {
586         return mAnimatingCount > 0;
587     }
588 
589     /**
590      * {@inheritDoc} from OnClickListener
591      */
592     @Override
onClick(final View v)593     public void onClick(final View v) {
594         processClick(v, false);
595     }
596 
597     /**
598      * {@inheritDoc} from OnLongClickListener
599      */
600     @Override
onLongClick(final View v)601     public boolean onLongClick(final View v) {
602         return processClick(v, true);
603     }
604 
processClick(final View v, final boolean isLongClick)605     private boolean processClick(final View v, final boolean isLongClick) {
606         Assert.isTrue(v == mSwipeableContainer || v == mContactIconView || v == mImagePreviewView);
607         Assert.notNull(mData.getName());
608 
609         if (mHostInterface != null) {
610             mHostInterface.onConversationClicked(mData, isLongClick, this);
611             return true;
612         }
613         return false;
614     }
615 
getSwipeableContent()616     public View getSwipeableContent() {
617         return mSwipeableContent;
618     }
619 
getContactIconView()620     public View getContactIconView() {
621         return mContactIconView;
622     }
623 
getSnippetText()624     private String getSnippetText() {
625         String snippetText = mData.getShowDraft() ?
626                 mData.getDraftSnippetText() : mData.getSnippetText();
627         final String previewContentType = mData.getShowDraft() ?
628                 mData.getDraftPreviewContentType() : mData.getPreviewContentType();
629         if (TextUtils.isEmpty(snippetText)) {
630             Resources resources = getResources();
631             // Use the attachment type as a snippet so the preview doesn't look odd
632             if (ContentType.isAudioType(previewContentType)) {
633                 snippetText = resources.getString(R.string.conversation_list_snippet_audio_clip);
634             } else if (ContentType.isImageType(previewContentType)) {
635                 snippetText = resources.getString(R.string.conversation_list_snippet_picture);
636             } else if (ContentType.isVideoType(previewContentType)) {
637                 snippetText = resources.getString(R.string.conversation_list_snippet_video);
638             } else if (ContentType.isVCardType(previewContentType)) {
639                 snippetText = resources.getString(R.string.conversation_list_snippet_vcard);
640             }
641         }
642         return snippetText;
643     }
644 }
645