kopia lustrzana https://github.com/ryukoposting/Signal-Android
Allow forwarding of Text Stories.
rodzic
43ad0b2294
commit
e3c491860a
|
@ -130,13 +130,15 @@ class MultiselectForwardFragment :
|
||||||
|
|
||||||
container.addView(bottomBar)
|
container.addView(bottomBar)
|
||||||
|
|
||||||
contactSearchMediator.getSelectionState().observe(viewLifecycleOwner) {
|
contactSearchMediator.getSelectionState().observe(viewLifecycleOwner) { contactSelection ->
|
||||||
shareSelectionAdapter.submitList(it.mapIndexed { index, key -> ShareSelectionMappingModel(key.requireShareContact(), index == 0) })
|
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.animation = AnimationUtils.loadAnimation(requireContext(), R.anim.slide_fade_from_bottom)
|
||||||
bottomBar.visible = true
|
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.animation = AnimationUtils.loadAnimation(requireContext(), R.anim.slide_fade_to_bottom)
|
||||||
bottomBar.visible = false
|
bottomBar.visible = false
|
||||||
}
|
}
|
||||||
|
@ -162,8 +164,6 @@ class MultiselectForwardFragment :
|
||||||
sendButton.isEnabled = it.stage == MultiselectForwardState.Stage.Selection
|
sendButton.isEnabled = it.stage == MultiselectForwardState.Stage.Selection
|
||||||
}
|
}
|
||||||
|
|
||||||
addMessage.visible = getMultiShareArgs().isNotEmpty()
|
|
||||||
|
|
||||||
setFragmentResultListener(CreateStoryWithViewersFragment.REQUEST_KEY) { _, bundle ->
|
setFragmentResultListener(CreateStoryWithViewersFragment.REQUEST_KEY) { _, bundle ->
|
||||||
val recipientId: RecipientId = bundle.getParcelable(CreateStoryWithViewersFragment.STORY_RECIPIENT)!!
|
val recipientId: RecipientId = bundle.getParcelable(CreateStoryWithViewersFragment.STORY_RECIPIENT)!!
|
||||||
contactSearchMediator.setKeysSelected(setOf(ContactSearchKey.Story(recipientId)))
|
contactSearchMediator.setKeysSelected(setOf(ContactSearchKey.Story(recipientId)))
|
||||||
|
@ -282,40 +282,48 @@ class MultiselectForwardFragment :
|
||||||
query = contactSearchState.query
|
query = contactSearchState.query
|
||||||
|
|
||||||
if (Stories.isFeatureEnabled() && isSelectedMediaValidForStories()) {
|
if (Stories.isFeatureEnabled() && isSelectedMediaValidForStories()) {
|
||||||
|
val expandedConfig: ContactSearchConfiguration.ExpandConfig? = if (isSelectedMediaValidForNonStories()) {
|
||||||
|
ContactSearchConfiguration.ExpandConfig(
|
||||||
|
isExpanded = contactSearchState.expandedSections.contains(ContactSearchConfiguration.SectionKey.STORIES)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
addSection(
|
addSection(
|
||||||
ContactSearchConfiguration.Section.Stories(
|
ContactSearchConfiguration.Section.Stories(
|
||||||
groupStories = contactSearchState.groupStories,
|
groupStories = contactSearchState.groupStories,
|
||||||
includeHeader = true,
|
includeHeader = true,
|
||||||
headerAction = getHeaderAction(childFragmentManager),
|
headerAction = getHeaderAction(childFragmentManager),
|
||||||
expandConfig = ContactSearchConfiguration.ExpandConfig(
|
expandConfig = expandedConfig
|
||||||
isExpanded = contactSearchState.expandedSections.contains(ContactSearchConfiguration.SectionKey.STORIES)
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSelectedMediaValidForNonStories()) {
|
||||||
|
if (query.isNullOrEmpty()) {
|
||||||
|
addSection(
|
||||||
|
ContactSearchConfiguration.Section.Recents(
|
||||||
|
includeHeader = true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (query.isNullOrEmpty()) {
|
|
||||||
addSection(
|
addSection(
|
||||||
ContactSearchConfiguration.Section.Recents(
|
ContactSearchConfiguration.Section.Individuals(
|
||||||
includeHeader = true
|
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 }
|
return getMultiShareArgs().all { it.isValidForStories }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isSelectedMediaValidForNonStories(): Boolean {
|
||||||
|
return getMultiShareArgs().all { it.isValidForNonStories }
|
||||||
|
}
|
||||||
|
|
||||||
override fun onGroupStoryClicked() {
|
override fun onGroupStoryClicked() {
|
||||||
ChooseGroupStoryBottomSheet().show(parentFragmentManager, ChooseGroupStoryBottomSheet.GROUP_STORY)
|
ChooseGroupStoryBottomSheet().show(parentFragmentManager, ChooseGroupStoryBottomSheet.GROUP_STORY)
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,7 @@ class MultiselectForwardFragmentArgs(
|
||||||
|
|
||||||
val linkPreview = mediaMessage?.linkPreviews?.firstOrNull()
|
val linkPreview = mediaMessage?.linkPreviews?.firstOrNull()
|
||||||
builder.withLinkPreview(linkPreview)
|
builder.withLinkPreview(linkPreview)
|
||||||
|
builder.asTextStory(mediaMessage?.storyType?.isTextStory ?: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conversationMessage.messageRecord.isMms && conversationMessage.multiselectCollection.isMediaSelected(selectedParts)) {
|
if (conversationMessage.messageRecord.isMms && conversationMessage.multiselectCollection.isMediaSelected(selectedParts)) {
|
||||||
|
|
|
@ -61,13 +61,17 @@ class MultiselectForwardRepository(context: Context) {
|
||||||
val results = mappedArgs.sortedBy { it.timestamp }.map { MultiShareSender.sendSync(it) }
|
val results = mappedArgs.sortedBy { it.timestamp }.map { MultiShareSender.sendSync(it) }
|
||||||
|
|
||||||
if (additionalMessage.isNotEmpty()) {
|
if (additionalMessage.isNotEmpty()) {
|
||||||
val additional = MultiShareArgs.Builder(sharedContactsAndThreads)
|
val additional = MultiShareArgs.Builder(sharedContactsAndThreads.filterNot { it.isStory }.toSet())
|
||||||
.withDraftText(additionalMessage)
|
.withDraftText(additionalMessage)
|
||||||
.build()
|
.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 {
|
} else {
|
||||||
handleResults(results, resultHandlers)
|
handleResults(results, resultHandlers)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||||
import org.thoughtcrime.securesms.mediasend.Media;
|
import org.thoughtcrime.securesms.mediasend.Media;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.ParcelUtil;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -37,6 +38,7 @@ public final class MultiShareArgs implements Parcelable {
|
||||||
private final List<Mention> mentions;
|
private final List<Mention> mentions;
|
||||||
private final long timestamp;
|
private final long timestamp;
|
||||||
private final long expiresAt;
|
private final long expiresAt;
|
||||||
|
private final boolean isTextStory;
|
||||||
|
|
||||||
private MultiShareArgs(@NonNull Builder builder) {
|
private MultiShareArgs(@NonNull Builder builder) {
|
||||||
shareContactAndThreads = builder.shareContactAndThreads;
|
shareContactAndThreads = builder.shareContactAndThreads;
|
||||||
|
@ -51,6 +53,7 @@ public final class MultiShareArgs implements Parcelable {
|
||||||
mentions = builder.mentions == null ? new ArrayList<>() : new ArrayList<>(builder.mentions);
|
mentions = builder.mentions == null ? new ArrayList<>() : new ArrayList<>(builder.mentions);
|
||||||
timestamp = builder.timestamp;
|
timestamp = builder.timestamp;
|
||||||
expiresAt = builder.expiresAt;
|
expiresAt = builder.expiresAt;
|
||||||
|
isTextStory = builder.isTextStory;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected MultiShareArgs(Parcel in) {
|
protected MultiShareArgs(Parcel in) {
|
||||||
|
@ -65,6 +68,7 @@ public final class MultiShareArgs implements Parcelable {
|
||||||
mentions = in.createTypedArrayList(Mention.CREATOR);
|
mentions = in.createTypedArrayList(Mention.CREATOR);
|
||||||
timestamp = in.readLong();
|
timestamp = in.readLong();
|
||||||
expiresAt = in.readLong();
|
expiresAt = in.readLong();
|
||||||
|
isTextStory = ParcelUtil.readBoolean(in);
|
||||||
|
|
||||||
String linkedPreviewString = in.readString();
|
String linkedPreviewString = in.readString();
|
||||||
LinkPreview preview;
|
LinkPreview preview;
|
||||||
|
@ -109,6 +113,10 @@ public final class MultiShareArgs implements Parcelable {
|
||||||
return viewOnce;
|
return viewOnce;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isTextStory() {
|
||||||
|
return isTextStory;
|
||||||
|
}
|
||||||
|
|
||||||
public @Nullable LinkPreview getLinkPreview() {
|
public @Nullable LinkPreview getLinkPreview() {
|
||||||
return linkPreview;
|
return linkPreview;
|
||||||
}
|
}
|
||||||
|
@ -126,7 +134,11 @@ public final class MultiShareArgs implements Parcelable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isValidForStories() {
|
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() {
|
public @NonNull InterstitialContentType getInterstitialContentType() {
|
||||||
|
@ -174,6 +186,7 @@ public final class MultiShareArgs implements Parcelable {
|
||||||
dest.writeTypedList(mentions);
|
dest.writeTypedList(mentions);
|
||||||
dest.writeLong(timestamp);
|
dest.writeLong(timestamp);
|
||||||
dest.writeLong(expiresAt);
|
dest.writeLong(expiresAt);
|
||||||
|
ParcelUtil.writeBoolean(dest, isTextStory);
|
||||||
|
|
||||||
if (linkPreview != null) {
|
if (linkPreview != null) {
|
||||||
try {
|
try {
|
||||||
|
@ -201,7 +214,8 @@ public final class MultiShareArgs implements Parcelable {
|
||||||
.withStickerLocator(stickerLocator)
|
.withStickerLocator(stickerLocator)
|
||||||
.withMentions(mentions)
|
.withMentions(mentions)
|
||||||
.withTimestamp(timestamp)
|
.withTimestamp(timestamp)
|
||||||
.withExpiration(expiresAt);
|
.withExpiration(expiresAt)
|
||||||
|
.asTextStory(isTextStory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean requiresInterstitial() {
|
private boolean requiresInterstitial() {
|
||||||
|
@ -224,6 +238,7 @@ public final class MultiShareArgs implements Parcelable {
|
||||||
private List<Mention> mentions;
|
private List<Mention> mentions;
|
||||||
private long timestamp;
|
private long timestamp;
|
||||||
private long expiresAt;
|
private long expiresAt;
|
||||||
|
private boolean isTextStory;
|
||||||
|
|
||||||
public Builder(@NonNull Set<ShareContactAndThread> shareContactAndThreads) {
|
public Builder(@NonNull Set<ShareContactAndThread> shareContactAndThreads) {
|
||||||
this.shareContactAndThreads = shareContactAndThreads;
|
this.shareContactAndThreads = shareContactAndThreads;
|
||||||
|
@ -284,6 +299,11 @@ public final class MultiShareArgs implements Parcelable {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @NonNull Builder asTextStory(boolean isTextStory) {
|
||||||
|
this.isTextStory = isTextStory;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public @NonNull MultiShareArgs build() {
|
public @NonNull MultiShareArgs build() {
|
||||||
return new MultiShareArgs(this);
|
return new MultiShareArgs(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ public final class MultiShareSender {
|
||||||
|
|
||||||
if ((recipient.isMmsGroup() || recipient.getEmail().isPresent()) && !isMmsEnabled) {
|
if ((recipient.isMmsGroup() || recipient.getEmail().isPresent()) && !isMmsEnabled) {
|
||||||
results.add(new MultiShareSendResult(shareContactAndThread, MultiShareSendResult.Type.MMS_NOT_ENABLED));
|
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());
|
sendMediaMessage(context, multiShareArgs, recipient, slideDeck, transport, shareContactAndThread.getThreadId(), forceSms, expiresIn, multiShareArgs.isViewOnce(), subscriptionId, mentions, shareContactAndThread.isStory());
|
||||||
results.add(new MultiShareSendResult(shareContactAndThread, MultiShareSendResult.Type.SUCCESS));
|
results.add(new MultiShareSendResult(shareContactAndThread, MultiShareSendResult.Type.SUCCESS));
|
||||||
} else if (shareContactAndThread.isStory()) {
|
} else if (shareContactAndThread.isStory()) {
|
||||||
|
@ -184,32 +184,52 @@ public final class MultiShareSender {
|
||||||
SignalDatabase.groups().markDisplayAsStory(recipient.requireGroupId());
|
SignalDatabase.groups().markDisplayAsStory(recipient.requireGroupId());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final Slide slide : slideDeck.getSlides()) {
|
if (multiShareArgs.isTextStory()) {
|
||||||
SlideDeck singletonDeck = new SlideDeck();
|
|
||||||
singletonDeck.addSlide(slide);
|
|
||||||
|
|
||||||
OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient,
|
OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient,
|
||||||
singletonDeck,
|
new SlideDeck(),
|
||||||
body,
|
body,
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
expiresIn,
|
0L,
|
||||||
isViewOnce,
|
false,
|
||||||
ThreadDatabase.DistributionTypes.DEFAULT,
|
ThreadDatabase.DistributionTypes.DEFAULT,
|
||||||
storyType,
|
storyType.toTextStoryType(),
|
||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
null,
|
null,
|
||||||
Collections.emptyList(),
|
Collections.emptyList(),
|
||||||
multiShareArgs.getLinkPreview() != null ? Collections.singletonList(multiShareArgs.getLinkPreview())
|
multiShareArgs.getLinkPreview() != null ? Collections.singletonList(multiShareArgs.getLinkPreview())
|
||||||
: Collections.emptyList(),
|
: Collections.emptyList(),
|
||||||
validatedMentions);
|
Collections.emptyList());
|
||||||
|
|
||||||
outgoingMessages.add(outgoingMediaMessage);
|
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
|
OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient,
|
||||||
// sentTimestamp. If we do this, they'll be considered dupes by the receiver.
|
singletonDeck,
|
||||||
ThreadUtil.sleep(5);
|
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 {
|
} else {
|
||||||
OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient,
|
OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient,
|
||||||
|
|
Ładowanie…
Reference in New Issue