diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java index f5de0d0f17..83f7e9e750 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java @@ -93,10 +93,11 @@ public class ConversationDataSource implements PagedDataSource load(int start, int length, @NonNull CancellationSignal cancellationSignal) { - Stopwatch stopwatch = new Stopwatch("load(" + start + ", " + length + "), thread " + threadId); - MmsSmsTable db = SignalDatabase.mmsSms(); - List records = new ArrayList<>(length); + Stopwatch stopwatch = new Stopwatch("load(" + start + ", " + length + "), thread " + threadId); + MmsSmsTable db = SignalDatabase.mmsSms(); + List records = new ArrayList<>(length); MentionHelper mentionHelper = new MentionHelper(); + QuotedHelper quotedHelper = new QuotedHelper(); AttachmentHelper attachmentHelper = new AttachmentHelper(); ReactionHelper reactionHelper = new ReactionHelper(); PaymentHelper paymentHelper = new PaymentHelper(); @@ -107,6 +108,7 @@ public class ConversationDataSource implements PagedDataSource messages = Stream.of(records) - .map(m -> ConversationMessageFactory.createWithUnresolvedData(context, m, mentionHelper.getMentions(m.getId()))) + .map(m -> ConversationMessageFactory.createWithUnresolvedData(context, m, mentionHelper.getMentions(m.getId()), quotedHelper.isQuoted(m.getId()))) .toList(); stopwatch.split("conversion"); @@ -180,28 +185,27 @@ public class ConversationDataSource implements PagedDataSource mentions = SignalDatabase.mentions().getMentionsForMessage(messageId.getId()); - stopwatch.split("mentions"); + boolean isQuoted = SignalDatabase.mmsSms().isQuoted(record); + stopwatch.split("is-quoted"); + List reactions = SignalDatabase.reactions().getReactions(messageId); record = ReactionHelper.recordWithReactions(record, reactions); - stopwatch.split("reactions"); List attachments = SignalDatabase.attachments().getAttachmentsForMessage(messageId.getId()); if (attachments.size() > 0) { record = ((MediaMmsMessageRecord) record).withAttachments(context, attachments); } - stopwatch.split("attachments"); if (record.isPaymentNotification()) { record = SignalDatabase.payments().updateMessageWithPayment(record); } - stopwatch.split("payments"); - return ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(ApplicationDependencies.getApplication(), record, mentions); + return ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(ApplicationDependencies.getApplication(), record, mentions, isQuoted); } else { return null; } @@ -235,6 +239,24 @@ public class ConversationDataSource implements PagedDataSource records = new LinkedList<>(); + private Set hasBeenQuotedIds = new HashSet<>(); + + void add(MessageRecord record) { + records.add(record); + } + + void fetchQuotedState() { + hasBeenQuotedIds = SignalDatabase.mmsSms().isQuoted(records); + } + + boolean isQuoted(long id) { + return hasBeenQuotedIds.contains(id); + } + } + private static class AttachmentHelper { private Collection messageIds = new LinkedList<>(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java index 807119f0e9..52821486e6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java @@ -35,10 +35,6 @@ public class ConversationMessage { @NonNull private final MessageStyler.Result styleResult; private final boolean hasBeenQuoted; - private ConversationMessage(@NonNull MessageRecord messageRecord) { - this(messageRecord, null, null, false); - } - private ConversationMessage(@NonNull MessageRecord messageRecord, boolean hasBeenQuoted) { this(messageRecord, null, null, hasBeenQuoted); } @@ -165,9 +161,7 @@ public class ConversationMessage { * @param mentions List of placeholder mentions to be used to update the body in the provided MessageRecord. */ @WorkerThread - public static @NonNull ConversationMessage createWithUnresolvedData(@NonNull Context context, @NonNull MessageRecord messageRecord, @Nullable List mentions) { - boolean hasBeenQuoted = SignalDatabase.mmsSms().isQuoted(messageRecord); - + public static @NonNull ConversationMessage createWithUnresolvedData(@NonNull Context context, @NonNull MessageRecord messageRecord, @Nullable List mentions, boolean hasBeenQuoted) { if (messageRecord.isMms() && mentions != null && !mentions.isEmpty()) { MentionUtil.UpdatedBodyAndMentions updated = MentionUtil.updateBodyAndMentionsWithDisplayNames(context, messageRecord, mentions); return new ConversationMessage(messageRecord, updated.getBody(), updated.getMentions(), hasBeenQuoted); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsTable.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsTable.java index 469991a106..3ee678c14c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsTable.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsTable.java @@ -52,10 +52,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -261,6 +263,42 @@ public class MmsSmsTable extends DatabaseTable { return queryTables(PROJECTION, selection, order, null, true); } + public Set isQuoted(@NonNull Collection records) { + if (records.isEmpty()) { + return Collections.emptySet(); + } + + Map byQuoteDescriptor = new HashMap<>(records.size()); + List args = new ArrayList<>(records.size()); + + for (MessageRecord record : records) { + long timestamp = record.getDateSent(); + RecipientId author = record.isOutgoing() ? Recipient.self().getId() : record.getRecipient().getId(); + + byQuoteDescriptor.put(new QuoteDescriptor(timestamp, author), record); + args.add(SqlUtil.buildArgs(timestamp, author)); + } + + + String[] projection = new String[] { MessageTable.QUOTE_ID, MessageTable.QUOTE_AUTHOR }; + List queries = SqlUtil.buildCustomCollectionQuery(MessageTable.QUOTE_ID + " = ? AND " + MessageTable.QUOTE_AUTHOR + " = ?", args); + Set quotedIds = new HashSet<>(); + + for (SqlUtil.Query query : queries) { + try (Cursor cursor = getReadableDatabase().query(MessageTable.TABLE_NAME, projection, query.getWhere(), query.getWhereArgs(), null, null, null)) { + while (cursor.moveToNext()) { + long timestamp = CursorUtil.requireLong(cursor, MessageTable.QUOTE_ID); + RecipientId author = RecipientId.from(CursorUtil.requireString(cursor, MessageTable.QUOTE_AUTHOR)); + QuoteDescriptor quoteLocator = new QuoteDescriptor(timestamp, author); + + quotedIds.add(byQuoteDescriptor.get(quoteLocator).getId()); + } + } + } + + return quotedIds; + } + /** * Whether or not the message has been quoted by another message. */ @@ -984,4 +1022,27 @@ public class MmsSmsTable extends DatabaseTable { this.threads = threads; } } + + private static class QuoteDescriptor { + private final long timestamp; + private final RecipientId author; + + private QuoteDescriptor(long timestamp, RecipientId author) { + this.author = author; + this.timestamp = timestamp; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final QuoteDescriptor that = (QuoteDescriptor) o; + return timestamp == that.timestamp && author.equals(that.author); + } + + @Override + public int hashCode() { + return Objects.hash(author, timestamp); + } + } }