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