Implement better detection of text only messages.

This commit is contained in:
Lucio Maciel
2021-08-12 15:17:51 -03:00
committed by Cody Henthorne
parent fe8fcb1394
commit 4702ab1aeb
6 changed files with 144 additions and 98 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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,

View File

@@ -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)

View File

@@ -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;
}
}

View File

@@ -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)
)
}