Implement badge gifting behind feature flag.

This commit is contained in:
Alex Hart
2022-05-02 14:29:42 -03:00
committed by Greyson Parrelli
parent 5d16d1cd23
commit a4a4665aaa
164 changed files with 4999 additions and 486 deletions

View File

@@ -77,6 +77,9 @@ import org.signal.core.util.concurrent.SimpleTask;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.badges.gifts.OpenableGiftItemDecoration;
import org.thoughtcrime.securesms.badges.gifts.viewgift.received.ViewReceivedGiftBottomSheet;
import org.thoughtcrime.securesms.badges.gifts.viewgift.sent.ViewSentGiftBottomSheet;
import org.thoughtcrime.securesms.components.ConversationScrollToView;
import org.thoughtcrime.securesms.components.ConversationTypingView;
import org.thoughtcrime.securesms.components.TypingStatusRepository;
@@ -308,6 +311,10 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
RecyclerViewColorizer recyclerViewColorizer = new RecyclerViewColorizer(list);
OpenableGiftItemDecoration openableGiftItemDecoration = new OpenableGiftItemDecoration(requireContext());
getViewLifecycleOwner().getLifecycle().addObserver(openableGiftItemDecoration);
list.addItemDecoration(openableGiftItemDecoration);
list.addItemDecoration(multiselectItemDecoration);
list.setItemAnimator(conversationItemAnimator);
@@ -413,6 +420,17 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
getChildFragmentManager().setFragmentResultListener(ViewReceivedGiftBottomSheet.REQUEST_KEY, getViewLifecycleOwner(), (key, bundle) -> {
if (bundle.getBoolean(ViewReceivedGiftBottomSheet.RESULT_NOT_NOW, false)) {
Snackbar.make(view.getRootView(), R.string.ConversationFragment__you_can_redeem_your_badge_later, Snackbar.LENGTH_SHORT)
.setTextColor(Color.WHITE)
.show();
}
});
}
private @NonNull GiphyMp4ProjectionRecycler initializeGiphyMp4() {
int maxPlayback = GiphyMp4PlaybackPolicy.maxSimultaneousPlaybackInConversation();
List<GiphyMp4ProjectionPlayerHolder> holders = GiphyMp4ProjectionPlayerHolder.injectVideoViews(requireContext(),
@@ -1950,6 +1968,19 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
RecipientBottomSheetDialogFragment.create(target, recipient.get().getGroupId().orElse(null)).show(getParentFragmentManager(), "BOTTOM");
}
@Override
public void onViewGiftBadgeClicked(@NonNull MessageRecord messageRecord) {
if (!MessageRecordUtil.hasGiftBadge(messageRecord)) {
return;
}
if (messageRecord.isOutgoing()) {
ViewSentGiftBottomSheet.show(getChildFragmentManager(), (MmsMessageRecord) messageRecord);
} else {
ViewReceivedGiftBottomSheet.show(getChildFragmentManager(), (MmsMessageRecord) messageRecord);
}
}
}
public void refreshList() {

View File

@@ -7,6 +7,7 @@ import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.badges.models.Badge;
import org.thoughtcrime.securesms.conversation.colors.ChatColors;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.mediasend.Media;
@@ -33,6 +34,7 @@ public class ConversationIntents {
private static final String EXTRA_STARTING_POSITION = "starting_position";
private static final String EXTRA_FIRST_TIME_IN_SELF_CREATED_GROUP = "first_time_in_group";
private static final String EXTRA_WITH_SEARCH_OPEN = "with_search_open";
private static final String EXTRA_GIFT_BADGE = "gift_badge";
private ConversationIntents() {
}
@@ -72,6 +74,7 @@ public class ConversationIntents {
private final int startingPosition;
private final boolean firstTimeInSelfCreatedGroup;
private final boolean withSearchOpen;
private final Badge giftBadge;
static Args from(@NonNull Intent intent) {
if (isBubbleIntent(intent)) {
@@ -84,7 +87,8 @@ public class ConversationIntents {
ThreadDatabase.DistributionTypes.DEFAULT,
-1,
false,
false);
false,
null);
}
return new Args(RecipientId.from(Objects.requireNonNull(intent.getStringExtra(EXTRA_RECIPIENT))),
@@ -96,7 +100,8 @@ public class ConversationIntents {
intent.getIntExtra(EXTRA_DISTRIBUTION_TYPE, ThreadDatabase.DistributionTypes.DEFAULT),
intent.getIntExtra(EXTRA_STARTING_POSITION, -1),
intent.getBooleanExtra(EXTRA_FIRST_TIME_IN_SELF_CREATED_GROUP, false),
intent.getBooleanExtra(EXTRA_WITH_SEARCH_OPEN, false));
intent.getBooleanExtra(EXTRA_WITH_SEARCH_OPEN, false),
intent.getParcelableExtra(EXTRA_GIFT_BADGE));
}
private Args(@NonNull RecipientId recipientId,
@@ -108,7 +113,8 @@ public class ConversationIntents {
int distributionType,
int startingPosition,
boolean firstTimeInSelfCreatedGroup,
boolean withSearchOpen)
boolean withSearchOpen,
@Nullable Badge giftBadge)
{
this.recipientId = recipientId;
this.threadId = threadId;
@@ -120,6 +126,7 @@ public class ConversationIntents {
this.startingPosition = startingPosition;
this.firstTimeInSelfCreatedGroup = firstTimeInSelfCreatedGroup;
this.withSearchOpen = withSearchOpen;
this.giftBadge = giftBadge;
}
public @NonNull RecipientId getRecipientId() {
@@ -169,6 +176,10 @@ public class ConversationIntents {
public boolean isWithSearchOpen() {
return withSearchOpen;
}
public @Nullable Badge getGiftBadge() {
return giftBadge;
}
}
public final static class Builder {
@@ -187,6 +198,7 @@ public class ConversationIntents {
private String dataType;
private boolean firstTimeInSelfCreatedGroup;
private boolean withSearchOpen;
private Badge giftBadge;
private Builder(@NonNull Context context,
@NonNull RecipientId recipientId,
@@ -255,7 +267,12 @@ public class ConversationIntents {
this.firstTimeInSelfCreatedGroup = true;
return this;
}
public Builder withGiftBadge(@NonNull Badge badge) {
this.giftBadge = badge;
return this;
}
public @NonNull Intent build() {
if (stickerLocator != null && media != null) {
throw new IllegalStateException("Cannot have both sticker and media array");
@@ -281,6 +298,7 @@ public class ConversationIntents {
intent.putExtra(EXTRA_BORDERLESS, isBorderless);
intent.putExtra(EXTRA_FIRST_TIME_IN_SELF_CREATED_GROUP, firstTimeInSelfCreatedGroup);
intent.putExtra(EXTRA_WITH_SEARCH_OPEN, withSearchOpen);
intent.putExtra(EXTRA_GIFT_BADGE, giftBadge);
if (draftText != null) {
intent.putExtra(EXTRA_TEXT, draftText);

View File

@@ -71,6 +71,8 @@ import org.thoughtcrime.securesms.MediaPreviewActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.badges.BadgeImageView;
import org.thoughtcrime.securesms.badges.gifts.GiftMessageView;
import org.thoughtcrime.securesms.badges.gifts.OpenableGift;
import org.thoughtcrime.securesms.components.AlertView;
import org.thoughtcrime.securesms.components.AudioView;
import org.thoughtcrime.securesms.components.AvatarImageView;
@@ -147,6 +149,9 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
/**
* A view that displays an individual conversation item within a conversation
* thread. Used by ComposeMessageActivity's ListActivity via a ConversationAdapter.
@@ -156,7 +161,8 @@ import java.util.concurrent.TimeUnit;
*/
public final class ConversationItem extends RelativeLayout implements BindableConversationItem,
RecipientForeverObserver
RecipientForeverObserver,
OpenableGift
{
private static final String TAG = Log.tag(ConversationItem.class);
@@ -207,6 +213,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
private Stub<BorderlessImageView> stickerStub;
private Stub<ViewOnceMessageView> revealableStub;
private Stub<Button> callToActionStub;
private Stub<GiftMessageView> giftViewStub;
private @Nullable EventListener eventListener;
private int defaultBubbleColor;
@@ -224,6 +231,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
private final UrlClickListener urlClickListener = new UrlClickListener();
private final Rect thumbnailMaskingRect = new Rect();
private final TouchDelegateChangedListener touchDelegateChangedListener = new TouchDelegateChangedListener();
private final GiftMessageViewCallback giftMessageViewCallback = new GiftMessageViewCallback();
private final Context context;
@@ -298,6 +306,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
this.badgeImageView = findViewById(R.id.badge);
this.storyReactionLabelWrapper = findViewById(R.id.story_reacted_label_holder);
this.storyReactionLabel = findViewById(R.id.story_reacted_label);
this.giftViewStub = new Stub<>(findViewById(R.id.gift_view_stub));
setOnClickListener(new ClickListener(null));
@@ -914,6 +923,10 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
return MessageRecordUtil.isViewOnceMessage(messageRecord);
}
private boolean isGiftMessage(MessageRecord messageRecord) {
return MessageRecordUtil.hasGiftBadge(messageRecord);
}
private void setBodyText(@NonNull MessageRecord messageRecord,
@Nullable String searchQuery,
boolean messageRequestAccepted)
@@ -935,7 +948,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
bodyText.setText(italics);
bodyText.setVisibility(View.VISIBLE);
bodyText.setOverflowText(null);
} else if (isCaptionlessMms(messageRecord) || isStoryReaction(messageRecord)) {
} else if (isCaptionlessMms(messageRecord) || isStoryReaction(messageRecord) || isGiftMessage(messageRecord)) {
bodyText.setVisibility(View.GONE);
} else {
Spannable styledText = conversationMessage.getDisplayBody(getContext());
@@ -1004,6 +1017,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
if (giftViewStub.resolved()) giftViewStub.get().setVisibility(View.GONE);
revealableStub.get().setMessage((MmsMessageRecord) messageRecord, hasWallpaper);
revealableStub.get().setOnClickListener(revealableClickListener);
@@ -1020,6 +1034,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
if (giftViewStub.resolved()) giftViewStub.get().setVisibility(View.GONE);
sharedContactStub.get().setContact(((MediaMmsMessageRecord) messageRecord).getSharedContacts().get(0), glideRequests, locale);
sharedContactStub.get().setEventListener(sharedContactEventListener);
@@ -1039,6 +1054,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
if (giftViewStub.resolved()) giftViewStub.get().setVisibility(View.GONE);
//noinspection ConstantConditions
LinkPreview linkPreview = ((MmsMessageRecord) messageRecord).getLinkPreviews().get(0);
@@ -1083,6 +1099,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
if (giftViewStub.resolved()) giftViewStub.get().setVisibility(View.GONE);
audioViewStub.get().setAudio(Objects.requireNonNull(((MediaMmsMessageRecord) messageRecord).getSlideDeck().getAudioSlide()), new AudioViewCallbacks(), showControls, true);
audioViewStub.get().setDownloadClickListener(singleDownloadClickListener);
@@ -1108,6 +1125,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
if (giftViewStub.resolved()) giftViewStub.get().setVisibility(View.GONE);
//noinspection ConstantConditions
documentViewStub.get().setDocument(((MediaMmsMessageRecord) messageRecord).getSlideDeck().getDocumentSlide(), showControls);
@@ -1130,6 +1148,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
if (giftViewStub.resolved()) giftViewStub.get().setVisibility(View.GONE);
if (hasSticker(messageRecord)) {
//noinspection ConstantConditions
@@ -1159,6 +1178,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
if (giftViewStub.resolved()) giftViewStub.get().setVisibility(View.GONE);
List<Slide> thumbnailSlides = ((MmsMessageRecord) messageRecord).getSlideDeck().getThumbnailSlides();
mediaThumbnailStub.require().setMinimumThumbnailWidth(readDimen(isCaptionlessMms(messageRecord) ? R.dimen.media_bubble_min_width_solo
@@ -1202,6 +1222,20 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
canPlayContent = (GiphyMp4PlaybackPolicy.autoplay() || allowedToPlayInline) && mediaItem != null;
}
} else if (isGiftMessage(messageRecord)) {
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.require().setVisibility(GONE);
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(GONE);
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(GONE);
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(GONE);
if (revealableStub.resolved()) revealableStub.get().setVisibility(GONE);
MmsMessageRecord mmsMessageRecord = (MmsMessageRecord) messageRecord;
giftViewStub.get().setGiftBadge(glideRequests, Objects.requireNonNull(mmsMessageRecord.getGiftBadge()), messageRecord.isOutgoing(), giftMessageViewCallback);
giftViewStub.get().setVisibility(VISIBLE);
footer.setVisibility(VISIBLE);
} else {
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.require().setVisibility(View.GONE);
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
@@ -1210,6 +1244,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
if (giftViewStub.resolved()) giftViewStub.get().setVisibility(View.GONE);
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ViewUtil.updateLayoutParamsIfNonNull(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -1699,7 +1734,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
background = R.drawable.message_bubble_background_received_alone;
outliner.setRadius(bigRadius);
pulseOutliner.setRadius(bigRadius);
bodyBubbleCorners = null;
bodyBubbleCorners = new Projection.Corners(bigRadius);
}
} else if (isStartOfMessageCluster(current, previous, isGroupThread)) {
if (current.isOutgoing()) {
@@ -1711,7 +1746,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
background = R.drawable.message_bubble_background_received_start;
setOutlinerRadii(outliner, bigRadius, bigRadius, bigRadius, smallRadius);
setOutlinerRadii(pulseOutliner, bigRadius, bigRadius, bigRadius, smallRadius);
bodyBubbleCorners = null;
bodyBubbleCorners = getBodyBubbleCorners(bigRadius, bigRadius, bigRadius, smallRadius);
}
} else if (isEndOfMessageCluster(current, next, isGroupThread)) {
if (current.isOutgoing()) {
@@ -1723,7 +1758,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
background = R.drawable.message_bubble_background_received_end;
setOutlinerRadii(outliner, smallRadius, bigRadius, bigRadius, bigRadius);
setOutlinerRadii(pulseOutliner, smallRadius, bigRadius, bigRadius, bigRadius);
bodyBubbleCorners = null;
bodyBubbleCorners = getBodyBubbleCorners(smallRadius, bigRadius, bigRadius, bigRadius);
}
} else {
if (current.isOutgoing()) {
@@ -1735,7 +1770,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
background = R.drawable.message_bubble_background_received_middle;
setOutlinerRadii(outliner, smallRadius, bigRadius, bigRadius, smallRadius);
setOutlinerRadii(pulseOutliner, smallRadius, bigRadius, bigRadius, smallRadius);
bodyBubbleCorners = null;
bodyBubbleCorners = getBodyBubbleCorners(smallRadius, bigRadius, bigRadius, smallRadius);
}
}
@@ -2004,6 +2039,41 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
}
}
@Override
public @Nullable Projection getOpenableGiftProjection() {
boolean isViewedAndIncoming = !messageRecord.isOutgoing() && messageRecord.getViewedReceiptCount() > 0;
if (!isGiftMessage(messageRecord) || messageRecord.isRemoteDelete() || isViewedAndIncoming) {
return null;
}
return Projection.relativeToViewRoot(bodyBubble, bodyBubbleCorners)
.translateX(bodyBubble.getTranslationX())
.translateX(getTranslationX())
.scale(bodyBubble.getScaleX());
}
@Override
public long getGiftId() {
return messageRecord.getId();
}
@Override
public void setOpenGiftCallback(@NonNull Function1<? super OpenableGift, Unit> openGift) {
if (giftViewStub.resolved()) {
bodyBubble.setOnClickListener(unused -> openGift.invoke(this));
giftViewStub.get().onGiftNotOpened();
}
}
@Override
public void clearOpenGiftCallback() {
if (giftViewStub.resolved()) {
bodyBubble.setOnClickListener(null);
bodyBubble.setClickable(false);
giftViewStub.get().onGiftOpened();
}
}
private class SharedContactEventListener implements SharedContactView.EventListener {
@Override
public void onAddToContactsClicked(@NonNull Contact contact) {
@@ -2179,6 +2249,14 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
}
}
private class GiftMessageViewCallback implements GiftMessageView.Callback {
@Override
public void onViewGiftBadgeClicked() {
eventListener.onViewGiftBadgeClicked(messageRecord);
}
}
private class ClickListener implements View.OnClickListener {
private OnClickListener parent;

View File

@@ -114,6 +114,7 @@ import org.thoughtcrime.securesms.TransportOption;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.TombstoneAttachment;
import org.thoughtcrime.securesms.audio.AudioRecorder;
import org.thoughtcrime.securesms.badges.gifts.thanks.GiftThanksSheet;
import org.thoughtcrime.securesms.components.AnimatingToggle;
import org.thoughtcrime.securesms.components.ComposeText;
import org.thoughtcrime.securesms.components.ConversationSearchBottomBar;
@@ -485,6 +486,9 @@ public class ConversationParentFragment extends Fragment
// TODO [alex] LargeScreenSupport -- This will need to be built from requireArguments()
ConversationIntents.Args args = ConversationIntents.Args.from(requireActivity().getIntent());
if (savedInstanceState == null && args.getGiftBadge() != null) {
GiftThanksSheet.show(getChildFragmentManager(), args.getRecipientId(), args.getGiftBadge());
}
isSearchRequested = args.isWithSearchOpen();
@@ -2979,7 +2983,7 @@ public class ConversationParentFragment extends Fragment
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.get().getExpiresInSeconds());
QuoteModel quote = result.isViewOnce() ? null : inputPanel.getQuote().orElse(null);
List<Mention> mentions = new ArrayList<>(result.getMentions());
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient.get(), new SlideDeck(), result.getBody(), System.currentTimeMillis(), -1, expiresIn, result.isViewOnce(), distributionType, result.getStoryType(), null, false, quote, Collections.emptyList(), Collections.emptyList(), mentions);
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient.get(), new SlideDeck(), result.getBody(), System.currentTimeMillis(), -1, expiresIn, result.isViewOnce(), distributionType, result.getStoryType(), null, false, quote, Collections.emptyList(), Collections.emptyList(), mentions, null);
OutgoingMediaMessage secureMessage = new OutgoingSecureMediaMessage(message);
final Context context = requireContext().getApplicationContext();
@@ -3055,7 +3059,7 @@ public class ConversationParentFragment extends Fragment
}
}
OutgoingMediaMessage outgoingMessageCandidate = new OutgoingMediaMessage(Recipient.resolved(recipientId), slideDeck, body, System.currentTimeMillis(), subscriptionId, expiresIn, viewOnce, distributionType, StoryType.NONE, null, false, quote, contacts, previews, mentions);
OutgoingMediaMessage outgoingMessageCandidate = new OutgoingMediaMessage(Recipient.resolved(recipientId), slideDeck, body, System.currentTimeMillis(), subscriptionId, expiresIn, viewOnce, distributionType, StoryType.NONE, null, false, quote, contacts, previews, mentions, null);
final SettableFuture<Void> future = new SettableFuture<>();
final Context context = requireContext().getApplicationContext();

View File

@@ -7,6 +7,7 @@ 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.recipients.Recipient;
import org.thoughtcrime.securesms.util.MessageRecordUtil;
import java.util.Set;
import java.util.stream.Collectors;
@@ -82,6 +83,7 @@ final class MenuState {
boolean hasInMemory = false;
boolean hasPendingMedia = false;
boolean mediaIsSelected = false;
boolean hasGift = false;
for (MultiselectPart part : selectedParts) {
MessageRecord messageRecord = part.getMessageRecord();
@@ -115,6 +117,10 @@ final class MenuState {
if (messageRecord.isRemoteDelete()) {
remoteDelete = true;
}
if (MessageRecordUtil.hasGiftBadge(messageRecord)) {
hasGift = true;
}
}
boolean shouldShowForwardAction = !actionMessage &&
@@ -122,6 +128,7 @@ final class MenuState {
!viewOnce &&
!remoteDelete &&
!hasPendingMedia &&
!hasGift &&
selectedParts.size() <= MAX_FORWARDABLE_COUNT;
int uniqueRecords = selectedParts.stream()
@@ -144,6 +151,7 @@ final class MenuState {
!viewOnce &&
messageRecord.isMms() &&
!hasPendingMedia &&
!hasGift &&
!messageRecord.isMmsNotification() &&
((MediaMmsMessageRecord)messageRecord).containsMediaSlide() &&
((MediaMmsMessageRecord)messageRecord).getSlideDeck().getStickerSlide() == null)
@@ -152,7 +160,7 @@ final class MenuState {
.shouldShowReplyAction(canReplyToMessage(conversationRecipient, actionMessage, messageRecord, shouldShowMessageRequest, isNonAdminInAnnouncementGroup));
}
return builder.shouldShowCopyAction(!actionMessage && !remoteDelete && hasText)
return builder.shouldShowCopyAction(!actionMessage && !remoteDelete && hasText && !hasGift)
.shouldShowDeleteAction(!hasInMemory && onlyContainsCompleteMessages(selectedParts))
.shouldShowReactions(!conversationRecipient.isReleaseNotes())
.build();
@@ -180,6 +188,7 @@ final class MenuState {
messageRecord.isSecure() &&
(!conversationRecipient.isGroup() || conversationRecipient.isActiveGroup()) &&
!messageRecord.getRecipient().isBlocked() &&
!MessageRecordUtil.hasGiftBadge(messageRecord) &&
!conversationRecipient.isReleaseNotes();
}

View File

@@ -107,7 +107,7 @@ class MultiselectForwardFragment :
view.minimumHeight = resources.displayMetrics.heightPixels
val contactSearchRecycler: RecyclerView = view.findViewById(R.id.contact_selection_list)
contactSearchMediator = ContactSearchMediator(this, contactSearchRecycler, FeatureFlags.shareSelectionLimit(), this::getConfiguration)
contactSearchMediator = ContactSearchMediator(this, contactSearchRecycler, FeatureFlags.shareSelectionLimit(), !isSingleRecipientSelection(), this::getConfiguration)
callback = findListener()!!
disposables.bindTo(viewLifecycleOwner.lifecycle)
@@ -155,24 +155,11 @@ class MultiselectForwardFragment :
}
sendButton.setOnClickListener {
sendButton.isEnabled = false
StoryDialogs.guardWithAddToYourStoryDialog(
requireContext(),
contactSearchMediator.getSelectedContacts(),
onAddToStory = {
performSend()
},
onEditViewers = {
sendButton.isEnabled = true
HideStoryFromDialogFragment().show(childFragmentManager, null)
},
onCancel = {
sendButton.isEnabled = true
}
)
onSend(it)
}
sendButton.visible = !isSingleRecipientSelection()
shareSelectionRecycler.adapter = shareSelectionAdapter
bottomBar.visible = false
@@ -180,6 +167,11 @@ class MultiselectForwardFragment :
container.addView(bottomBarAndSpacer)
contactSearchMediator.getSelectionState().observe(viewLifecycleOwner) { contactSelection ->
if (contactSelection.isNotEmpty() && isSingleRecipientSelection()) {
onSend(sendButton)
return@observe
}
shareSelectionAdapter.submitList(contactSelection.mapIndexed { index, key -> ShareSelectionMappingModel(key.requireShareContact(), index == 0) })
addMessage.visible = !forceDisableAddMessage && contactSelection.any { key -> key !is ContactSearchKey.RecipientSearchKey.Story } && getMultiShareArgs().isNotEmpty()
@@ -276,6 +268,25 @@ class MultiselectForwardFragment :
.show()
}
private fun onSend(sendButton: View) {
sendButton.isEnabled = false
StoryDialogs.guardWithAddToYourStoryDialog(
requireContext(),
contactSearchMediator.getSelectedContacts(),
onAddToStory = {
performSend()
},
onEditViewers = {
sendButton.isEnabled = true
HideStoryFromDialogFragment().show(childFragmentManager, null)
},
onCancel = {
sendButton.isEnabled = true
}
)
}
private fun performSend() {
viewModel.send(addMessage.text.toString(), contactSearchMediator.getSelectedContacts())
}
@@ -390,6 +401,10 @@ class MultiselectForwardFragment :
return Util.isDefaultSmsProvider(requireContext()) && requireArguments().getBoolean(ARG_CAN_SEND_TO_NON_PUSH)
}
private fun isSingleRecipientSelection(): Boolean {
return requireArguments().getBoolean(ARG_SELECT_SINGLE_RECIPIENT, false)
}
private fun isSelectedMediaValidForStories(): Boolean {
return requireListener<Callback>().canSendMediaToStories() && getMultiShareArgs().all { it.isValidForStories }
}
@@ -422,6 +437,7 @@ class MultiselectForwardFragment :
const val ARG_TITLE = "multiselect.forward.fragment.title"
const val ARG_FORCE_DISABLE_ADD_MESSAGE = "multiselect.forward.fragment.force.disable.add.message"
const val ARG_FORCE_SELECTION_ONLY = "multiselect.forward.fragment.force.disable.add.message"
const val ARG_SELECT_SINGLE_RECIPIENT = "multiselect.forward.framgent.select.single.recipient"
const val RESULT_KEY = "result_key"
const val RESULT_SELECTION = "result_selection_recipients"
const val RESULT_SENT = "result_sent"
@@ -460,6 +476,7 @@ class MultiselectForwardFragment :
putInt(ARG_TITLE, multiselectForwardFragmentArgs.title)
putBoolean(ARG_FORCE_DISABLE_ADD_MESSAGE, multiselectForwardFragmentArgs.forceDisableAddMessage)
putBoolean(ARG_FORCE_SELECTION_ONLY, multiselectForwardFragmentArgs.forceSelectionOnly)
putBoolean(ARG_SELECT_SINGLE_RECIPIENT, multiselectForwardFragmentArgs.selectSingleRecipient)
}
}
}

View File

@@ -28,13 +28,15 @@ import java.util.function.Consumer
* @param title The title to display at the top of the sheet
* @param forceDisableAddMessage Hide the add message field even if it would normally be available.
* @param forceSelectionOnly Force the fragment to only select recipients, never actually performing the send.
* @param selectSingleRecipient Only allow the selection of a single recipient.
*/
class MultiselectForwardFragmentArgs @JvmOverloads constructor(
val canSendToNonPush: Boolean,
val multiShareArgs: List<MultiShareArgs> = listOf(),
@StringRes val title: Int = R.string.MultiselectForwardFragment__forward_to,
val forceDisableAddMessage: Boolean = false,
val forceSelectionOnly: Boolean = false
val forceSelectionOnly: Boolean = false,
val selectSingleRecipient: Boolean = false
) {
companion object {