From e3c491860aa80b8cfc67b72a015956b23227e27f Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Thu, 17 Mar 2022 16:03:26 -0300 Subject: [PATCH] Allow forwarding of Text Stories. --- .../forward/MultiselectForwardFragment.kt | 68 +++++++++++-------- .../forward/MultiselectForwardFragmentArgs.kt | 1 + .../forward/MultiselectForwardRepository.kt | 10 ++- .../securesms/sharing/MultiShareArgs.java | 24 ++++++- .../securesms/sharing/MultiShareSender.java | 46 +++++++++---- 5 files changed, 103 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt index e6e3e40bf..68ebe6dde 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt @@ -130,13 +130,15 @@ class MultiselectForwardFragment : container.addView(bottomBar) - contactSearchMediator.getSelectionState().observe(viewLifecycleOwner) { - shareSelectionAdapter.submitList(it.mapIndexed { index, key -> ShareSelectionMappingModel(key.requireShareContact(), index == 0) }) + contactSearchMediator.getSelectionState().observe(viewLifecycleOwner) { contactSelection -> + shareSelectionAdapter.submitList(contactSelection.mapIndexed { index, key -> ShareSelectionMappingModel(key.requireShareContact(), index == 0) }) - if (it.isNotEmpty() && !bottomBar.isVisible) { + addMessage.visible = contactSelection.any { key -> key !is ContactSearchKey.Story } && getMultiShareArgs().isNotEmpty() + + if (contactSelection.isNotEmpty() && !bottomBar.isVisible) { bottomBar.animation = AnimationUtils.loadAnimation(requireContext(), R.anim.slide_fade_from_bottom) bottomBar.visible = true - } else if (it.isEmpty() && bottomBar.isVisible) { + } else if (contactSelection.isEmpty() && bottomBar.isVisible) { bottomBar.animation = AnimationUtils.loadAnimation(requireContext(), R.anim.slide_fade_to_bottom) bottomBar.visible = false } @@ -162,8 +164,6 @@ class MultiselectForwardFragment : sendButton.isEnabled = it.stage == MultiselectForwardState.Stage.Selection } - addMessage.visible = getMultiShareArgs().isNotEmpty() - setFragmentResultListener(CreateStoryWithViewersFragment.REQUEST_KEY) { _, bundle -> val recipientId: RecipientId = bundle.getParcelable(CreateStoryWithViewersFragment.STORY_RECIPIENT)!! contactSearchMediator.setKeysSelected(setOf(ContactSearchKey.Story(recipientId))) @@ -282,40 +282,48 @@ class MultiselectForwardFragment : query = contactSearchState.query if (Stories.isFeatureEnabled() && isSelectedMediaValidForStories()) { + val expandedConfig: ContactSearchConfiguration.ExpandConfig? = if (isSelectedMediaValidForNonStories()) { + ContactSearchConfiguration.ExpandConfig( + isExpanded = contactSearchState.expandedSections.contains(ContactSearchConfiguration.SectionKey.STORIES) + ) + } else { + null + } + addSection( ContactSearchConfiguration.Section.Stories( groupStories = contactSearchState.groupStories, includeHeader = true, headerAction = getHeaderAction(childFragmentManager), - expandConfig = ContactSearchConfiguration.ExpandConfig( - isExpanded = contactSearchState.expandedSections.contains(ContactSearchConfiguration.SectionKey.STORIES) + expandConfig = expandedConfig + ) + ) + } + + if (isSelectedMediaValidForNonStories()) { + if (query.isNullOrEmpty()) { + addSection( + ContactSearchConfiguration.Section.Recents( + includeHeader = true ) ) - ) - } + } - if (query.isNullOrEmpty()) { addSection( - ContactSearchConfiguration.Section.Recents( - includeHeader = true + ContactSearchConfiguration.Section.Individuals( + includeHeader = true, + transportType = if (includeSms()) ContactSearchConfiguration.TransportType.ALL else ContactSearchConfiguration.TransportType.PUSH, + includeSelf = true + ) + ) + + addSection( + ContactSearchConfiguration.Section.Groups( + includeHeader = true, + includeMms = includeSms() ) ) } - - addSection( - ContactSearchConfiguration.Section.Individuals( - includeHeader = true, - transportType = if (includeSms()) ContactSearchConfiguration.TransportType.ALL else ContactSearchConfiguration.TransportType.PUSH, - includeSelf = true - ) - ) - - addSection( - ContactSearchConfiguration.Section.Groups( - includeHeader = true, - includeMms = includeSms() - ) - ) } } @@ -327,6 +335,10 @@ class MultiselectForwardFragment : return getMultiShareArgs().all { it.isValidForStories } } + private fun isSelectedMediaValidForNonStories(): Boolean { + return getMultiShareArgs().all { it.isValidForNonStories } + } + override fun onGroupStoryClicked() { ChooseGroupStoryBottomSheet().show(parentFragmentManager, ChooseGroupStoryBottomSheet.GROUP_STORY) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragmentArgs.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragmentArgs.kt index 37ec5e9be..ec3196d22 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragmentArgs.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragmentArgs.kt @@ -72,6 +72,7 @@ class MultiselectForwardFragmentArgs( val linkPreview = mediaMessage?.linkPreviews?.firstOrNull() builder.withLinkPreview(linkPreview) + builder.asTextStory(mediaMessage?.storyType?.isTextStory ?: false) } if (conversationMessage.messageRecord.isMms && conversationMessage.multiselectCollection.isMediaSelected(selectedParts)) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardRepository.kt index d6454b746..fa99a79c2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardRepository.kt @@ -61,13 +61,17 @@ class MultiselectForwardRepository(context: Context) { val results = mappedArgs.sortedBy { it.timestamp }.map { MultiShareSender.sendSync(it) } if (additionalMessage.isNotEmpty()) { - val additional = MultiShareArgs.Builder(sharedContactsAndThreads) + val additional = MultiShareArgs.Builder(sharedContactsAndThreads.filterNot { it.isStory }.toSet()) .withDraftText(additionalMessage) .build() - val additionalResult: MultiShareSender.MultiShareSendResultCollection = MultiShareSender.sendSync(additional) + if (additional.shareContactAndThreads.isNotEmpty()) { + val additionalResult: MultiShareSender.MultiShareSendResultCollection = MultiShareSender.sendSync(additional) - handleResults(results + additionalResult, resultHandlers) + handleResults(results + additionalResult, resultHandlers) + } else { + handleResults(results, resultHandlers) + } } else { handleResults(results, resultHandlers) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareArgs.java b/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareArgs.java index 6663c2ab5..a4be32d72 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareArgs.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareArgs.java @@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.mediasend.Media; import org.thoughtcrime.securesms.stickers.StickerLocator; import org.thoughtcrime.securesms.util.MediaUtil; +import org.thoughtcrime.securesms.util.ParcelUtil; import java.io.IOException; import java.util.ArrayList; @@ -37,6 +38,7 @@ public final class MultiShareArgs implements Parcelable { private final List mentions; private final long timestamp; private final long expiresAt; + private final boolean isTextStory; private MultiShareArgs(@NonNull Builder builder) { shareContactAndThreads = builder.shareContactAndThreads; @@ -51,6 +53,7 @@ public final class MultiShareArgs implements Parcelable { mentions = builder.mentions == null ? new ArrayList<>() : new ArrayList<>(builder.mentions); timestamp = builder.timestamp; expiresAt = builder.expiresAt; + isTextStory = builder.isTextStory; } protected MultiShareArgs(Parcel in) { @@ -65,6 +68,7 @@ public final class MultiShareArgs implements Parcelable { mentions = in.createTypedArrayList(Mention.CREATOR); timestamp = in.readLong(); expiresAt = in.readLong(); + isTextStory = ParcelUtil.readBoolean(in); String linkedPreviewString = in.readString(); LinkPreview preview; @@ -109,6 +113,10 @@ public final class MultiShareArgs implements Parcelable { return viewOnce; } + public boolean isTextStory() { + return isTextStory; + } + public @Nullable LinkPreview getLinkPreview() { return linkPreview; } @@ -126,7 +134,11 @@ public final class MultiShareArgs implements Parcelable { } public boolean isValidForStories() { - return !media.isEmpty() && media.stream().allMatch(m -> MediaUtil.isImageOrVideoType(m.getMimeType()) && !MediaUtil.isGif(m.getMimeType())); + return isTextStory || !media.isEmpty() && media.stream().allMatch(m -> MediaUtil.isImageOrVideoType(m.getMimeType()) && !MediaUtil.isGif(m.getMimeType())); + } + + public boolean isValidForNonStories() { + return !isTextStory; } public @NonNull InterstitialContentType getInterstitialContentType() { @@ -174,6 +186,7 @@ public final class MultiShareArgs implements Parcelable { dest.writeTypedList(mentions); dest.writeLong(timestamp); dest.writeLong(expiresAt); + ParcelUtil.writeBoolean(dest, isTextStory); if (linkPreview != null) { try { @@ -201,7 +214,8 @@ public final class MultiShareArgs implements Parcelable { .withStickerLocator(stickerLocator) .withMentions(mentions) .withTimestamp(timestamp) - .withExpiration(expiresAt); + .withExpiration(expiresAt) + .asTextStory(isTextStory); } private boolean requiresInterstitial() { @@ -224,6 +238,7 @@ public final class MultiShareArgs implements Parcelable { private List mentions; private long timestamp; private long expiresAt; + private boolean isTextStory; public Builder(@NonNull Set shareContactAndThreads) { this.shareContactAndThreads = shareContactAndThreads; @@ -284,6 +299,11 @@ public final class MultiShareArgs implements Parcelable { return this; } + public @NonNull Builder asTextStory(boolean isTextStory) { + this.isTextStory = isTextStory; + return this; + } + public @NonNull MultiShareArgs build() { return new MultiShareArgs(this); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareSender.java b/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareSender.java index 2ffe235a5..786fc067e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareSender.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sharing/MultiShareSender.java @@ -104,7 +104,7 @@ public final class MultiShareSender { if ((recipient.isMmsGroup() || recipient.getEmail().isPresent()) && !isMmsEnabled) { results.add(new MultiShareSendResult(shareContactAndThread, MultiShareSendResult.Type.MMS_NOT_ENABLED)); - } else if (hasMmsMedia && transport.isSms() || hasPushMedia && !transport.isSms()) { + } else if (hasMmsMedia && transport.isSms() || hasPushMedia && !transport.isSms() || multiShareArgs.isTextStory()) { sendMediaMessage(context, multiShareArgs, recipient, slideDeck, transport, shareContactAndThread.getThreadId(), forceSms, expiresIn, multiShareArgs.isViewOnce(), subscriptionId, mentions, shareContactAndThread.isStory()); results.add(new MultiShareSendResult(shareContactAndThread, MultiShareSendResult.Type.SUCCESS)); } else if (shareContactAndThread.isStory()) { @@ -184,32 +184,52 @@ public final class MultiShareSender { SignalDatabase.groups().markDisplayAsStory(recipient.requireGroupId()); } - for (final Slide slide : slideDeck.getSlides()) { - SlideDeck singletonDeck = new SlideDeck(); - singletonDeck.addSlide(slide); - + if (multiShareArgs.isTextStory()) { OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient, - singletonDeck, + new SlideDeck(), body, System.currentTimeMillis(), subscriptionId, - expiresIn, - isViewOnce, + 0L, + false, ThreadDatabase.DistributionTypes.DEFAULT, - storyType, + storyType.toTextStoryType(), null, false, null, Collections.emptyList(), multiShareArgs.getLinkPreview() != null ? Collections.singletonList(multiShareArgs.getLinkPreview()) : Collections.emptyList(), - validatedMentions); + Collections.emptyList()); outgoingMessages.add(outgoingMediaMessage); + } else { + for (final Slide slide : slideDeck.getSlides()) { + SlideDeck singletonDeck = new SlideDeck(); + singletonDeck.addSlide(slide); - // XXX We must do this to avoid sending out messages to the same recipient with the same - // sentTimestamp. If we do this, they'll be considered dupes by the receiver. - ThreadUtil.sleep(5); + OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient, + singletonDeck, + body, + System.currentTimeMillis(), + subscriptionId, + 0L, + false, + ThreadDatabase.DistributionTypes.DEFAULT, + storyType, + null, + false, + null, + Collections.emptyList(), + Collections.emptyList(), + validatedMentions); + + outgoingMessages.add(outgoingMediaMessage); + + // XXX We must do this to avoid sending out messages to the same recipient with the same + // sentTimestamp. If we do this, they'll be considered dupes by the receiver. + ThreadUtil.sleep(5); + } } } else { OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient,