From a44a105cbcb3262055ae82c7b484fb8a8095bb93 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Fri, 8 Apr 2022 11:57:27 -0300 Subject: [PATCH] Add ability to copy text slides in full. --- .../conversation/ConversationFragment.java | 57 +++++++++++++------ .../securesms/util/MessageRecordUtil.kt | 7 +++ .../org/thoughtcrime/securesms/util/Util.java | 4 +- 3 files changed, 50 insertions(+), 18 deletions(-) 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 069f049d4..eeb5e80e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -69,8 +69,11 @@ import com.annimon.stream.Stream; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; +import org.jetbrains.annotations.NotNull; import org.signal.core.util.DimensionUnit; +import org.signal.core.util.StreamUtil; import org.signal.core.util.concurrent.SignalExecutors; +import org.signal.core.util.concurrent.SimpleTask; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.LoggingFragment; import org.thoughtcrime.securesms.R; @@ -133,6 +136,7 @@ import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.mms.Slide; +import org.thoughtcrime.securesms.mms.TextSlide; import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; @@ -169,7 +173,6 @@ import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.WindowUtil; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; -import org.signal.core.util.concurrent.SimpleTask; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.verify.VerifyIdentityActivity; import org.thoughtcrime.securesms.wallpaper.ChatWallpaper; @@ -931,22 +934,44 @@ public class ConversationFragment extends LoggingFragment implements Multiselect } private void handleCopyMessage(final Set multiselectParts) { - CharSequence bodies = Stream.of(multiselectParts) - .sortBy(m -> m.getMessageRecord().getDateReceived()) - .map(MultiselectPart::getConversationMessage) - .distinct() - .map(m -> m.getDisplayBody(requireContext())) - .filterNot(TextUtils::isEmpty) - .collect(SpannableStringBuilder::new, (bodyBuilder, body) -> { - if (bodyBuilder.length() > 0) { - bodyBuilder.append('\n'); - } - bodyBuilder.append(body); - }); + SimpleTask.run(() -> extractBodies(multiselectParts), + bodies -> { + if (!Util.isEmpty(bodies)) { + Util.copyToClipboard(requireContext(), bodies); + } + }); + } - if (!TextUtils.isEmpty(bodies)) { - Util.copyToClipboard(requireContext(), bodies); - } + private @NotNull CharSequence extractBodies(final Set multiselectParts) { + return Stream.of(multiselectParts) + .sortBy(m -> m.getMessageRecord().getDateReceived()) + .map(MultiselectPart::getConversationMessage) + .distinct() + .map(message -> { + if (MessageRecordUtil.hasTextSlide(message.getMessageRecord())) { + TextSlide textSlide = MessageRecordUtil.requireTextSlide(message.getMessageRecord()); + if (textSlide.getUri() == null) { + return message.getDisplayBody(requireContext()); + } + + try (InputStream stream = PartAuthority.getAttachmentStream(requireContext(), textSlide.getUri())) { + String body = StreamUtil.readFullyAsString(stream); + return ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(requireContext(), message.getMessageRecord(), body) + .getDisplayBody(requireContext()); + } catch (IOException e) { + Log.w(TAG, "Failed to read text slide data."); + } + } + + return message.getDisplayBody(requireContext()); + }) + .filterNot(Util::isEmpty) + .collect(SpannableStringBuilder::new, (bodyBuilder, body) -> { + if (bodyBuilder.length() > 0) { + bodyBuilder.append('\n'); + } + bodyBuilder.append(body); + }); } private void handleDeleteMessages(final Set multiselectParts) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MessageRecordUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/util/MessageRecordUtil.kt index dd873d174..1d65016c6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/MessageRecordUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/MessageRecordUtil.kt @@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.database.MmsSmsColumns 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.TextSlide import org.thoughtcrime.securesms.stickers.StickerUrl const val MAX_BODY_DISPLAY_LENGTH = 1000 @@ -78,6 +79,12 @@ fun MessageRecord.hasQuote(): Boolean = fun MessageRecord.hasLinkPreview(): Boolean = isMms && (this as MmsMessageRecord).linkPreviews.isNotEmpty() +fun MessageRecord.hasTextSlide(): Boolean = + isMms && (this as MmsMessageRecord).slideDeck.textSlide != null && this.slideDeck.textSlide?.uri != null + +fun MessageRecord.requireTextSlide(): TextSlide = + requireNotNull((this as MmsMessageRecord).slideDeck.textSlide) + fun MessageRecord.hasBigImageLinkPreview(context: Context): Boolean { if (!hasLinkPreview()) { return false diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/Util.java b/app/src/main/java/org/thoughtcrime/securesms/util/Util.java index c01f9f6c7..2d0c7abaa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/Util.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/Util.java @@ -153,8 +153,8 @@ public class Util { return collection == null || collection.isEmpty(); } - public static boolean isEmpty(@Nullable String value) { - return value == null || value.length() == 0; + public static boolean isEmpty(@Nullable CharSequence charSequence) { + return charSequence == null || charSequence.length() == 0; } public static boolean hasItems(@Nullable Collection collection) {