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