diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java index 6862517a34..b0bab45b02 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java @@ -55,6 +55,7 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.CachedInflater; import org.thoughtcrime.securesms.util.DateUtils; +import org.thoughtcrime.securesms.util.MessageRecordUtil; import org.thoughtcrime.securesms.util.Projection; import org.thoughtcrime.securesms.util.StickyHeaderDecoration; import org.thoughtcrime.securesms.util.ThemeUtil; @@ -108,6 +109,7 @@ public class ConversationAdapter private static final long FOOTER_ID = Long.MIN_VALUE + 1; private final ItemClickListener clickListener; + private final Context context; private final LifecycleOwner lifecycleOwner; private final GlideRequests glideRequests; private final Locale locale; @@ -130,7 +132,8 @@ public class ConversationAdapter private ConversationMessage inlineContent; private Colorizer colorizer; - ConversationAdapter(@NonNull LifecycleOwner lifecycleOwner, + ConversationAdapter(@NonNull Context context, + @NonNull LifecycleOwner lifecycleOwner, @NonNull GlideRequests glideRequests, @NonNull Locale locale, @Nullable ItemClickListener clickListener, @@ -151,6 +154,7 @@ public class ConversationAdapter }); this.lifecycleOwner = lifecycleOwner; + this.context = context; this.glideRequests = glideRequests; this.locale = locale; @@ -187,9 +191,9 @@ public class ConversationAdapter } else if (messageRecord.isUpdate()) { return MESSAGE_TYPE_UPDATE; } else if (messageRecord.isOutgoing()) { - return messageRecord.isMms() ? MESSAGE_TYPE_OUTGOING_MULTIMEDIA : MESSAGE_TYPE_OUTGOING_TEXT; + return MessageRecordUtil.isTextOnly(messageRecord, context) ? MESSAGE_TYPE_OUTGOING_TEXT : MESSAGE_TYPE_OUTGOING_MULTIMEDIA; } else { - return messageRecord.isMms() ? MESSAGE_TYPE_INCOMING_MULTIMEDIA : MESSAGE_TYPE_INCOMING_TEXT; + return MessageRecordUtil.isTextOnly(messageRecord, context) ? MESSAGE_TYPE_INCOMING_TEXT : MESSAGE_TYPE_INCOMING_MULTIMEDIA; } } @@ -586,9 +590,9 @@ public class ConversationAdapter */ @MainThread static void initializePool(@NonNull RecyclerView.RecycledViewPool pool) { - pool.setMaxRecycledViews(MESSAGE_TYPE_INCOMING_TEXT, 15); + pool.setMaxRecycledViews(MESSAGE_TYPE_INCOMING_TEXT, 25); pool.setMaxRecycledViews(MESSAGE_TYPE_INCOMING_MULTIMEDIA, 15); - pool.setMaxRecycledViews(MESSAGE_TYPE_OUTGOING_TEXT, 15); + pool.setMaxRecycledViews(MESSAGE_TYPE_OUTGOING_TEXT, 25); pool.setMaxRecycledViews(MESSAGE_TYPE_OUTGOING_MULTIMEDIA, 15); pool.setMaxRecycledViews(MESSAGE_TYPE_PLACEHOLDER, 15); pool.setMaxRecycledViews(MESSAGE_TYPE_HEADER, 1); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 1b761ceade..39220bf312 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -226,8 +226,8 @@ public class ConversationFragment extends LoggingFragment { FrameLayout parent = new FrameLayout(context); parent.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)); - CachedInflater.from(context).cacheUntilLimit(R.layout.conversation_item_received_text_only, parent, 15); - CachedInflater.from(context).cacheUntilLimit(R.layout.conversation_item_sent_text_only, parent, 15); + CachedInflater.from(context).cacheUntilLimit(R.layout.conversation_item_received_text_only, parent, 25); + CachedInflater.from(context).cacheUntilLimit(R.layout.conversation_item_sent_text_only, parent, 25); CachedInflater.from(context).cacheUntilLimit(R.layout.conversation_item_received_multimedia, parent, 10); CachedInflater.from(context).cacheUntilLimit(R.layout.conversation_item_sent_multimedia, parent, 10); CachedInflater.from(context).cacheUntilLimit(R.layout.conversation_item_update, parent, 5); @@ -668,7 +668,7 @@ public class ConversationFragment extends LoggingFragment { } Log.d(TAG, "Initializing adapter for " + recipient.getId()); - ConversationAdapter adapter = new ConversationAdapter(this, GlideApp.with(this), locale, selectionClickListener, this.recipient.get(), new AttachmentMediaSourceFactory(requireContext()), colorizer); + ConversationAdapter adapter = new ConversationAdapter(requireContext(), this, GlideApp.with(this), locale, selectionClickListener, this.recipient.get(), new AttachmentMediaSourceFactory(requireContext()), colorizer); adapter.setPagingController(conversationViewModel.getPagingController()); list.setAdapter(adapter); setInlineDateDecoration(adapter); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index 9b9eda0959..4d6d6734c1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -88,7 +88,6 @@ import org.thoughtcrime.securesms.components.mention.MentionAnnotation; import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.conversation.colors.ChatColors; import org.thoughtcrime.securesms.conversation.colors.Colorizer; -import org.thoughtcrime.securesms.conversation.mutiselect.Multiselect; import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectCollection; import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart; import org.thoughtcrime.securesms.database.AttachmentDatabase; @@ -122,10 +121,10 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.revealable.ViewOnceMessageView; -import org.thoughtcrime.securesms.stickers.StickerUrl; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.InterceptableLongClickCopyLinkSpan; import org.thoughtcrime.securesms.util.LongClickMovementMethod; +import org.thoughtcrime.securesms.util.MessageRecordUtil; import org.thoughtcrime.securesms.util.Projection; import org.thoughtcrime.securesms.util.SearchUtil; import org.thoughtcrime.securesms.util.StringUtil; @@ -162,7 +161,6 @@ public final class ConversationItem extends RelativeLayout implements BindableCo private static final String TAG = Log.tag(ConversationItem.class); private static final int MAX_MEASURE_CALLS = 3; - private static final int MAX_BODY_DISPLAY_LENGTH = 1000; private static final Rect SWIPE_RECT = new Rect(); @@ -709,85 +707,59 @@ public final class ConversationItem extends RelativeLayout implements BindableCo } private boolean isCaptionlessMms(MessageRecord messageRecord) { - return TextUtils.isEmpty(messageRecord.getDisplayBody(getContext())) && messageRecord.isMms() && ((MmsMessageRecord) messageRecord).getSlideDeck().getTextSlide() == null; + return MessageRecordUtil.isCaptionlessMms(messageRecord, context); } private boolean hasAudio(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getAudioSlide() != null; + return MessageRecordUtil.hasAudio(messageRecord); } private boolean hasThumbnail(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide() != null; + return MessageRecordUtil.hasThumbnail(messageRecord); } private boolean hasSticker(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getStickerSlide() != null; + return MessageRecordUtil.hasSticker(messageRecord); } private boolean isBorderless(MessageRecord messageRecord) { - //noinspection ConstantConditions - return isCaptionlessMms(messageRecord) && - hasThumbnail(messageRecord) && - ((MmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide().isBorderless(); + return MessageRecordUtil.isBorderless(messageRecord, context); } private boolean hasNoBubble(MessageRecord messageRecord) { - return hasSticker(messageRecord) || isBorderless(messageRecord); + return MessageRecordUtil.hasNoBubble(messageRecord, context); } private boolean hasOnlyThumbnail(MessageRecord messageRecord) { - return hasThumbnail(messageRecord) && - !hasAudio(messageRecord) && - !hasDocument(messageRecord) && - !hasSharedContact(messageRecord) && - !hasSticker(messageRecord) && - !isBorderless(messageRecord) && - !isViewOnceMessage(messageRecord); + return MessageRecordUtil.hasOnlyThumbnail(messageRecord, context); } private boolean hasDocument(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getDocumentSlide() != null; + return MessageRecordUtil.hasDocument(messageRecord); } private boolean hasExtraText(MessageRecord messageRecord) { - boolean hasTextSlide = messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getTextSlide() != null; - boolean hasOverflowText = messageRecord.getBody().length() > MAX_BODY_DISPLAY_LENGTH; - - return hasTextSlide || hasOverflowText; + return MessageRecordUtil.hasExtraText(messageRecord); } private boolean hasQuote(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getQuote() != null; + return MessageRecordUtil.hasQuote(messageRecord); } private boolean hasSharedContact(MessageRecord messageRecord) { - return messageRecord.isMms() && !((MmsMessageRecord)messageRecord).getSharedContacts().isEmpty(); + return MessageRecordUtil.hasSharedContact(messageRecord); } private boolean hasLinkPreview(MessageRecord messageRecord) { - return messageRecord.isMms() && !((MmsMessageRecord)messageRecord).getLinkPreviews().isEmpty(); + return MessageRecordUtil.hasLinkPreview(messageRecord); } private boolean hasBigImageLinkPreview(MessageRecord messageRecord) { - if (!hasLinkPreview(messageRecord)) { - return false; - } - - LinkPreview linkPreview = ((MmsMessageRecord) messageRecord).getLinkPreviews().get(0); - - if (linkPreview.getThumbnail().isPresent() && !Util.isEmpty(linkPreview.getDescription())) { - return true; - } - - int minWidth = getResources().getDimensionPixelSize(R.dimen.media_bubble_min_width_solo); - - return linkPreview.getThumbnail().isPresent() && - linkPreview.getThumbnail().get().getWidth() >= minWidth && - !StickerUrl.isValidShareLink(linkPreview.getUrl()); + return MessageRecordUtil.hasBigImageLinkPreview(messageRecord, context); } private boolean isViewOnceMessage(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord) messageRecord).isViewOnce(); + return MessageRecordUtil.isViewOnceMessage(messageRecord); } private void setBodyText(@NonNull MessageRecord messageRecord, diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt index 35c8442b44..681b86782a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt @@ -23,9 +23,11 @@ import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientUtil import org.thoughtcrime.securesms.service.KeyCachingService import org.thoughtcrime.securesms.util.MediaUtil -import org.thoughtcrime.securesms.util.MessageRecordUtil import org.thoughtcrime.securesms.util.SpanUtil import org.thoughtcrime.securesms.util.Util +import org.thoughtcrime.securesms.util.hasSharedContact +import org.thoughtcrime.securesms.util.hasSticker +import org.thoughtcrime.securesms.util.isMediaMessage private val TAG: String = Log.tag(NotificationItemV2::class.java) private const val EMOJI_REPLACEMENT_STRING = "__EMOJI__" @@ -269,23 +271,23 @@ class ReactionNotification(threadRecipient: Recipient, record: MessageRecord, va val body: CharSequence = MentionUtil.updateBodyWithDisplayNames(context, record) val bodyIsEmpty: Boolean = TextUtils.isEmpty(body) - return if (MessageRecordUtil.hasSharedContact(record)) { + return if (record.hasSharedContact()) { val contact: Contact = (record as MmsMessageRecord).sharedContacts[0] val summary: CharSequence = ContactUtil.getStringSummary(context, contact) context.getString(R.string.MessageNotifier_reacted_s_to_s, EMOJI_REPLACEMENT_STRING, summary) - } else if (MessageRecordUtil.hasSticker(record)) { + } else if (record.hasSticker()) { context.getString(R.string.MessageNotifier_reacted_s_to_your_sticker, EMOJI_REPLACEMENT_STRING) } else if (record.isMms && record.isViewOnce) { context.getString(R.string.MessageNotifier_reacted_s_to_your_view_once_media, EMOJI_REPLACEMENT_STRING) } else if (!bodyIsEmpty) { context.getString(R.string.MessageNotifier_reacted_s_to_s, EMOJI_REPLACEMENT_STRING, body) - } else if (MessageRecordUtil.isMediaMessage(record) && MediaUtil.isVideoType(getMessageContentType((record as MmsMessageRecord)))) { + } else if (record.isMediaMessage() && MediaUtil.isVideoType(getMessageContentType((record as MmsMessageRecord)))) { context.getString(R.string.MessageNotifier_reacted_s_to_your_video, EMOJI_REPLACEMENT_STRING) - } else if (MessageRecordUtil.isMediaMessage(record) && MediaUtil.isImageType(getMessageContentType((record as MmsMessageRecord)))) { + } else if (record.isMediaMessage() && MediaUtil.isImageType(getMessageContentType((record as MmsMessageRecord)))) { context.getString(R.string.MessageNotifier_reacted_s_to_your_image, EMOJI_REPLACEMENT_STRING) - } else if (MessageRecordUtil.isMediaMessage(record) && MediaUtil.isAudioType(getMessageContentType((record as MmsMessageRecord)))) { + } else if (record.isMediaMessage() && MediaUtil.isAudioType(getMessageContentType((record as MmsMessageRecord)))) { context.getString(R.string.MessageNotifier_reacted_s_to_your_audio, EMOJI_REPLACEMENT_STRING) - } else if (MessageRecordUtil.isMediaMessage(record)) { + } else if (record.isMediaMessage()) { context.getString(R.string.MessageNotifier_reacted_s_to_your_file, EMOJI_REPLACEMENT_STRING) } else { context.getString(R.string.MessageNotifier_reacted_s_to_s, EMOJI_REPLACEMENT_STRING, body) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MessageRecordUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/MessageRecordUtil.java deleted file mode 100644 index 955ebbbf44..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/MessageRecordUtil.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.thoughtcrime.securesms.util; - -import androidx.annotation.NonNull; - -import com.annimon.stream.Stream; - -import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; -import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.database.model.MmsMessageRecord; -import org.thoughtcrime.securesms.mms.Slide; - -public final class MessageRecordUtil { - - private MessageRecordUtil() { - } - - public static boolean isMediaMessage(@NonNull MessageRecord messageRecord) { - return messageRecord.isMms() && - !messageRecord.isMmsNotification() && - ((MediaMmsMessageRecord)messageRecord).containsMediaSlide() && - ((MediaMmsMessageRecord)messageRecord).getSlideDeck().getStickerSlide() == null; - } - - public static boolean hasSticker(@NonNull MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getStickerSlide() != null; - } - - public static boolean hasSharedContact(@NonNull MessageRecord messageRecord) { - return messageRecord.isMms() && !((MmsMessageRecord)messageRecord).getSharedContacts().isEmpty(); - } - - public static boolean hasLocation(@NonNull MessageRecord messageRecord) { - return messageRecord.isMms() && Stream.of(((MmsMessageRecord) messageRecord).getSlideDeck().getSlides()) - .anyMatch(Slide::hasLocation); - } - - public static boolean hasAudio(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getAudioSlide() != null; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MessageRecordUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/util/MessageRecordUtil.kt new file mode 100644 index 0000000000..cc4a5b6636 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/MessageRecordUtil.kt @@ -0,0 +1,108 @@ +@file:JvmName("MessageRecordUtil") + +package org.thoughtcrime.securesms.util + +import android.content.Context +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord +import org.thoughtcrime.securesms.database.model.MessageRecord +import org.thoughtcrime.securesms.database.model.MmsMessageRecord +import org.thoughtcrime.securesms.stickers.StickerUrl + +const val MAX_BODY_DISPLAY_LENGTH = 1000 + +fun MessageRecord.isMediaMessage(): Boolean { + return isMms && + !isMmsNotification && + (this as MediaMmsMessageRecord).containsMediaSlide() && + slideDeck.stickerSlide == null +} + +fun MessageRecord.hasSticker(): Boolean = + isMms && (this as MmsMessageRecord).slideDeck.stickerSlide != null + +fun MessageRecord.hasSharedContact(): Boolean = + isMms && (this as MmsMessageRecord).sharedContacts.isNotEmpty() + +fun MessageRecord.hasLocation(): Boolean = + isMms && ((this as MmsMessageRecord).slideDeck.slides).any { slide -> slide.hasLocation() } + +fun MessageRecord.hasAudio(): Boolean = + isMms && (this as MmsMessageRecord).slideDeck.audioSlide != null + +fun MessageRecord.isCaptionlessMms(context: Context): Boolean = + getDisplayBody(context).isEmpty() && isMms && (this as MmsMessageRecord).slideDeck.textSlide == null + +fun MessageRecord.hasThumbnail(): Boolean = + isMms && (this as MmsMessageRecord).slideDeck.thumbnailSlide != null + +fun MessageRecord.isBorderless(context: Context): Boolean { + return isCaptionlessMms(context) && + hasThumbnail() && + (this as MmsMessageRecord).slideDeck.thumbnailSlide?.isBorderless == true +} + +fun MessageRecord.hasNoBubble(context: Context): Boolean = + hasSticker() || isBorderless(context) + +fun MessageRecord.hasOnlyThumbnail(context: Context): Boolean { + return hasThumbnail() && + !hasAudio() && + !hasDocument() && + !hasSharedContact() && + !hasSticker() && + !isBorderless(context) && + !isViewOnceMessage() +} + +fun MessageRecord.hasDocument(): Boolean = + isMms && (this as MmsMessageRecord).slideDeck.documentSlide != null + +fun MessageRecord.isViewOnceMessage(): Boolean = + isMms && (this as MmsMessageRecord).isViewOnce + +fun MessageRecord.hasExtraText(): Boolean { + val hasTextSlide = isMms && (this as MmsMessageRecord).slideDeck.textSlide != null + val hasOverflowText: Boolean = body.length > MAX_BODY_DISPLAY_LENGTH + + return hasTextSlide || hasOverflowText +} + +fun MessageRecord.hasQuote(): Boolean = + isMms && (this as MmsMessageRecord).quote != null + +fun MessageRecord.hasLinkPreview(): Boolean = + isMms && (this as MmsMessageRecord).linkPreviews.isNotEmpty() + +fun MessageRecord.hasBigImageLinkPreview(context: Context): Boolean { + if (!hasLinkPreview()) { + return false + } + + val linkPreview = (this as MmsMessageRecord).linkPreviews[0] + + if (linkPreview.thumbnail.isPresent && !Util.isEmpty(linkPreview.description)) { + return true + } + + val minWidth = context.resources.getDimensionPixelSize(R.dimen.media_bubble_min_width_solo) + + return linkPreview.thumbnail.isPresent && linkPreview.thumbnail.get().width >= minWidth && !StickerUrl.isValidShareLink(linkPreview.url) +} + +fun MessageRecord.isTextOnly(context: Context): Boolean { + return !( + !isMms || + isViewOnceMessage() || + hasLinkPreview() || + hasQuote() || + hasExtraText() || + hasDocument() || + hasThumbnail() || + hasAudio() || + hasLocation() || + hasSharedContact() || + hasSticker() || + isCaptionlessMms(context) + ) +}