mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 01:40:07 +01:00
Add in-chat payment activation requests.
Co-authored-by: Varsha <varsha@mobilecoin.com>
This commit is contained in:
@@ -104,6 +104,8 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
|
||||
void onBlockJoinRequest(@NonNull Recipient recipient);
|
||||
void onRecipientNameClicked(@NonNull RecipientId target);
|
||||
void onInviteToSignalClicked();
|
||||
void onActivatePaymentsClicked();
|
||||
void onSendPaymentClicked(@NonNull RecipientId recipientId);
|
||||
|
||||
/** @return true if handled, false if you want to let the normal url handling continue */
|
||||
boolean onUrlClicked(@NonNull String url);
|
||||
|
||||
@@ -9,4 +9,5 @@ public final class EmojiStrings {
|
||||
public static final String FILE = "\uD83D\uDCCE";
|
||||
public static final String STICKER = "\u2B50";
|
||||
public static final String GIFT = "\uD83C\uDF81";
|
||||
public static final String CARD = "\uD83D\uDCB3";
|
||||
}
|
||||
|
||||
@@ -142,6 +142,7 @@ import org.thoughtcrime.securesms.main.Material3OnScrollHelperBinder;
|
||||
import org.thoughtcrime.securesms.messagedetails.MessageDetailsFragment;
|
||||
import org.thoughtcrime.securesms.messagerequests.MessageRequestState;
|
||||
import org.thoughtcrime.securesms.messagerequests.MessageRequestViewModel;
|
||||
import org.thoughtcrime.securesms.mms.AttachmentManager;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
@@ -149,6 +150,7 @@ import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.TextSlide;
|
||||
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile;
|
||||
import org.thoughtcrime.securesms.notifications.v2.ConversationId;
|
||||
import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
@@ -174,6 +176,7 @@ import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.HtmlUtil;
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable;
|
||||
import org.thoughtcrime.securesms.util.MessageRecordUtil;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.thoughtcrime.securesms.util.Projection;
|
||||
import org.thoughtcrime.securesms.util.RemoteDeleteUtil;
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||
@@ -2104,6 +2107,17 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
||||
conversationViewModel.markGiftBadgeRevealed(messageRecord.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivatePaymentsClicked() {
|
||||
Intent intent = new Intent(requireContext(), PaymentsActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendPaymentClicked(@NonNull RecipientId recipientId) {
|
||||
AttachmentManager.selectPayment(ConversationFragment.this, recipient.get());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUnopenedGift(View itemView, MessageRecord messageRecord) {
|
||||
|
||||
@@ -249,6 +249,7 @@ import org.thoughtcrime.securesms.mms.VideoSlide;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.notifications.v2.ConversationId;
|
||||
import org.thoughtcrime.securesms.payments.CanNotSendPaymentDialog;
|
||||
import org.thoughtcrime.securesms.payments.MobileCoinPublicAddress;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.profiles.spoofing.ReviewBannerView;
|
||||
import org.thoughtcrime.securesms.profiles.spoofing.ReviewCardDialogFragment;
|
||||
@@ -294,6 +295,7 @@ import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.MessageRecordUtil;
|
||||
import org.thoughtcrime.securesms.util.MessageUtil;
|
||||
import org.thoughtcrime.securesms.util.PlayStoreUtil;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
|
||||
import org.thoughtcrime.securesms.util.SmsUtil;
|
||||
@@ -1217,11 +1219,7 @@ public class ConversationParentFragment extends Fragment
|
||||
AttachmentManager.selectLocation(this, PICK_LOCATION, getSendButtonColor(sendButton.getSelectedSendType()));
|
||||
break;
|
||||
case PAYMENT:
|
||||
if (ExpiringProfileCredentialUtil.isValid(recipient.get().getExpiringProfileKeyCredential())) {
|
||||
AttachmentManager.selectPayment(this, recipient.getId());
|
||||
} else {
|
||||
CanNotSendPaymentDialog.show(requireActivity());
|
||||
}
|
||||
AttachmentManager.selectPayment(this, recipient.get());
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
@@ -544,7 +544,23 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
});
|
||||
|
||||
actionButton.setText(R.string.ConversationActivity__invite_to_signal);
|
||||
} else {
|
||||
} else if (conversationMessage.getMessageRecord().isRequestToActivatePayments() && !conversationMessage.getMessageRecord().isOutgoing() && !SignalStore.paymentsValues().mobileCoinPaymentsEnabled()) {
|
||||
actionButton.setText(R.string.ConversationUpdateItem_activate_payments);
|
||||
actionButton.setVisibility(VISIBLE);
|
||||
actionButton.setOnClickListener(v -> {
|
||||
if (batchSelected.isEmpty() && eventListener != null) {
|
||||
eventListener.onActivatePaymentsClicked();
|
||||
}
|
||||
});
|
||||
} else if (conversationMessage.getMessageRecord().isPaymentsActivated() && !conversationMessage.getMessageRecord().isOutgoing()) {
|
||||
actionButton.setText(R.string.ConversationUpdateItem_send_payment);
|
||||
actionButton.setVisibility(VISIBLE);
|
||||
actionButton.setOnClickListener(v -> {
|
||||
if (batchSelected.isEmpty() && eventListener != null) {
|
||||
eventListener.onSendPaymentClicked(conversationMessage.getMessageRecord().getIndividualRecipient().getId());
|
||||
}
|
||||
});
|
||||
} else{
|
||||
actionButton.setVisibility(GONE);
|
||||
actionButton.setOnClickListener(null);
|
||||
}
|
||||
|
||||
@@ -205,7 +205,9 @@ final class MenuState {
|
||||
messageRecord.isChatSessionRefresh() ||
|
||||
messageRecord.isInMemoryMessageRecord() ||
|
||||
messageRecord.isChangeNumber() ||
|
||||
messageRecord.isBoostRequest();
|
||||
messageRecord.isBoostRequest() ||
|
||||
messageRecord.isRequestToActivatePayments() ||
|
||||
messageRecord.isPaymentsActivated();
|
||||
}
|
||||
|
||||
private final static class Builder {
|
||||
|
||||
@@ -228,6 +228,16 @@ class MessageQuotesBottomSheet : FixedRoundedCornerBottomSheetDialogFragment() {
|
||||
dismiss()
|
||||
getAdapterListener().onViewGiftBadgeClicked(messageRecord)
|
||||
}
|
||||
|
||||
override fun onActivatePaymentsClicked() {
|
||||
dismiss()
|
||||
getAdapterListener().onActivatePaymentsClicked()
|
||||
}
|
||||
|
||||
override fun onSendPaymentClicked(recipientId: RecipientId) {
|
||||
dismiss()
|
||||
getAdapterListener().onSendPaymentClicked(recipientId)
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.google.android.mms.pdu_alt.NotificationInd;
|
||||
|
||||
import net.zetetic.database.sqlcipher.SQLiteStatement;
|
||||
|
||||
import org.signal.core.util.CursorExtensionsKt;
|
||||
import org.signal.core.util.CursorUtil;
|
||||
import org.signal.core.util.SQLiteDatabaseExtensionsKt;
|
||||
import org.signal.core.util.SqlUtil;
|
||||
@@ -517,6 +518,15 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns,
|
||||
return data;
|
||||
}
|
||||
|
||||
public List<Long> getIncomingPaymentRequestThreads() {
|
||||
Cursor cursor = SQLiteDatabaseExtensionsKt.select(getReadableDatabase(), "DISTINCT " + THREAD_ID)
|
||||
.from(getTableName())
|
||||
.where("(" + getTypeField() + " & " + Types.BASE_TYPE_MASK + ") = " + Types.BASE_INBOX_TYPE + " AND (" + getTypeField() + " & ?) != 0", Types.SPECIAL_TYPE_ACTIVATE_PAYMENTS_REQUEST)
|
||||
.run();
|
||||
|
||||
return CursorExtensionsKt.readToList(cursor, c -> CursorUtil.requireLong(c, THREAD_ID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remapRecipient(@NonNull RecipientId fromId, @NonNull RecipientId toId) {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
@@ -78,6 +78,8 @@ import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingGroupUpdateMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingPaymentsActivatedMessages;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingRequestToActivatePaymentMessages;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.QuoteModel;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
@@ -1794,6 +1796,10 @@ public class MmsDatabase extends MessageDatabase {
|
||||
return new OutgoingGroupUpdateMessage(recipient, new MessageGroupContext(body, Types.isGroupV2(outboxType)), attachments, timestamp, 0, false, quote, contacts, previews, mentions);
|
||||
} else if (Types.isExpirationTimerUpdate(outboxType)) {
|
||||
return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn);
|
||||
} else if (Types.isRequestToActivatePayments(outboxType)) {
|
||||
return new OutgoingRequestToActivatePaymentMessages(recipient, timestamp, expiresIn);
|
||||
} else if (Types.isPaymentsActivated(outboxType)) {
|
||||
return new OutgoingPaymentsActivatedMessages(recipient, timestamp, expiresIn);
|
||||
}
|
||||
|
||||
GiftBadge giftBadge = null;
|
||||
@@ -1987,7 +1993,7 @@ public class MmsDatabase extends MessageDatabase {
|
||||
updateThread);
|
||||
|
||||
boolean isNotStoryGroupReply = retrieved.getParentStoryId() == null || !retrieved.getParentStoryId().isGroupReply();
|
||||
if (!Types.isExpirationTimerUpdate(mailbox) && !retrieved.getStoryType().isStory() && isNotStoryGroupReply) {
|
||||
if (!Types.isPaymentsActivated(mailbox) && !Types.isRequestToActivatePayments(mailbox) && !Types.isExpirationTimerUpdate(mailbox) && !retrieved.getStoryType().isStory() && isNotStoryGroupReply) {
|
||||
boolean incrementUnreadMentions = !retrieved.getMentions().isEmpty() && retrieved.getMentions().stream().anyMatch(m -> m.getRecipientId().equals(Recipient.self().getId()));
|
||||
SignalDatabase.threads().incrementUnread(threadId, 1, incrementUnreadMentions ? 1 : 0);
|
||||
SignalDatabase.threads().update(threadId, true);
|
||||
@@ -2013,6 +2019,14 @@ public class MmsDatabase extends MessageDatabase {
|
||||
type |= Types.EXPIRATION_TIMER_UPDATE_BIT;
|
||||
}
|
||||
|
||||
if (retrieved.isActivatePaymentsRequest()) {
|
||||
type |= Types.SPECIAL_TYPE_ACTIVATE_PAYMENTS_REQUEST;
|
||||
}
|
||||
|
||||
if (retrieved.isPaymentsActivated()) {
|
||||
type |= Types.SPECIAL_TYPE_PAYMENTS_ACTIVATED;
|
||||
}
|
||||
|
||||
return insertMessageInbox(retrieved, contentLocation, threadId, type);
|
||||
}
|
||||
|
||||
@@ -2044,6 +2058,20 @@ public class MmsDatabase extends MessageDatabase {
|
||||
type |= Types.SPECIAL_TYPE_GIFT_BADGE;
|
||||
}
|
||||
|
||||
if (retrieved.isActivatePaymentsRequest()) {
|
||||
if (hasSpecialType) {
|
||||
throw new MmsException("Cannot insert message with multiple special types.");
|
||||
}
|
||||
type |= Types.SPECIAL_TYPE_ACTIVATE_PAYMENTS_REQUEST;
|
||||
}
|
||||
|
||||
if (retrieved.isPaymentsActivated()) {
|
||||
if (hasSpecialType) {
|
||||
throw new MmsException("Cannot insert message with multiple special types.");
|
||||
}
|
||||
type |= Types.SPECIAL_TYPE_PAYMENTS_ACTIVATED;
|
||||
}
|
||||
|
||||
return insertMessageInbox(retrieved, "", threadId, type);
|
||||
}
|
||||
|
||||
@@ -2211,6 +2239,20 @@ public class MmsDatabase extends MessageDatabase {
|
||||
type |= Types.SPECIAL_TYPE_GIFT_BADGE;
|
||||
}
|
||||
|
||||
if (message.isRequestToActivatePayments()) {
|
||||
if (hasSpecialType) {
|
||||
throw new MmsException("Cannot insert message with multiple special types.");
|
||||
}
|
||||
type |= Types.SPECIAL_TYPE_ACTIVATE_PAYMENTS_REQUEST;
|
||||
}
|
||||
|
||||
if (message.isPaymentsActivated()) {
|
||||
if (hasSpecialType) {
|
||||
throw new MmsException("Cannot insert message with multiple special types.");
|
||||
}
|
||||
type |= Types.SPECIAL_TYPE_PAYMENTS_ACTIVATED;
|
||||
}
|
||||
|
||||
Map<RecipientId, EarlyReceiptCache.Receipt> earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(message.getSentTimeMillis());
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
|
||||
@@ -140,9 +140,11 @@ public interface MmsSmsColumns {
|
||||
protected static final long ENCRYPTION_REMOTE_LEGACY_BIT = 0x02000000;
|
||||
|
||||
// Special message types
|
||||
public static final long SPECIAL_TYPES_MASK = 0xF00000000L;
|
||||
public static final long SPECIAL_TYPE_STORY_REACTION = 0x100000000L;
|
||||
public static final long SPECIAL_TYPE_GIFT_BADGE = 0x200000000L;
|
||||
public static final long SPECIAL_TYPES_MASK = 0xF00000000L;
|
||||
public static final long SPECIAL_TYPE_STORY_REACTION = 0x100000000L;
|
||||
public static final long SPECIAL_TYPE_GIFT_BADGE = 0x200000000L;
|
||||
protected static final long SPECIAL_TYPE_ACTIVATE_PAYMENTS_REQUEST = 0x400000000L;
|
||||
protected static final long SPECIAL_TYPE_PAYMENTS_ACTIVATED = 0x800000000L;
|
||||
|
||||
public static boolean isStoryReaction(long type) {
|
||||
return (type & SPECIAL_TYPES_MASK) == SPECIAL_TYPE_STORY_REACTION;
|
||||
@@ -152,6 +154,14 @@ public interface MmsSmsColumns {
|
||||
return (type & SPECIAL_TYPES_MASK) == SPECIAL_TYPE_GIFT_BADGE;
|
||||
}
|
||||
|
||||
public static boolean isRequestToActivatePayments(long type) {
|
||||
return (type & SPECIAL_TYPES_MASK) == SPECIAL_TYPE_ACTIVATE_PAYMENTS_REQUEST;
|
||||
}
|
||||
|
||||
public static boolean isPaymentsActivated(long type) {
|
||||
return (type & SPECIAL_TYPES_MASK) == SPECIAL_TYPE_PAYMENTS_ACTIVATED;
|
||||
}
|
||||
|
||||
public static boolean isDraftMessageType(long type) {
|
||||
return (type & BASE_TYPE_MASK) == BASE_DRAFT_TYPE;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ import org.signal.core.util.SQLiteDatabaseExtensionsKt;
|
||||
import org.signal.core.util.SqlUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.util.Pair;
|
||||
import org.thoughtcrime.securesms.components.settings.app.chats.sms.SmsExportState;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchSet;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
|
||||
@@ -52,6 +52,10 @@ public final class ThreadBodyUtil {
|
||||
return String.format("%s %s", EmojiStrings.GIFT, getGiftSummary(context, record));
|
||||
} else if (MessageRecordUtil.isStoryReaction(record)) {
|
||||
return getStoryReactionSummary(context, record);
|
||||
} else if (MessageRecordUtil.isPaymentActivationRequest(record)) {
|
||||
return String.format("%s %s", EmojiStrings.CARD, getPaymentActivationRequestSummary(context, record));
|
||||
} else if (MessageRecordUtil.isPaymentsActivated(record)) {
|
||||
return String.format("%s %s", EmojiStrings.CARD, getPaymentActivatedSummary(context, record));
|
||||
}
|
||||
|
||||
boolean hasImage = false;
|
||||
@@ -94,6 +98,22 @@ public final class ThreadBodyUtil {
|
||||
return context.getString(R.string.ThreadRecord__reacted_s_to_your_story, messageRecord.getDisplayBody(context));
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull String getPaymentActivationRequestSummary(@NonNull Context context, @NonNull MessageRecord messageRecord) {
|
||||
if (messageRecord.isOutgoing()) {
|
||||
return context.getString(R.string.ThreadRecord_you_sent_request);
|
||||
} else {
|
||||
return context.getString(R.string.ThreadRecord_wants_you_to_activate_payments, messageRecord.getRecipient().getShortDisplayName(context));
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull String getPaymentActivatedSummary(@NonNull Context context, @NonNull MessageRecord messageRecord) {
|
||||
if (messageRecord.isOutgoing()) {
|
||||
return context.getString(R.string.ThreadRecord_you_activated_payments);
|
||||
} else {
|
||||
return context.getString(R.string.ThreadRecord_can_accept_payments, messageRecord.getRecipient().getShortDisplayName(context));
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull String format(@NonNull Context context, @NonNull MessageRecord record, @NonNull String emoji, @StringRes int defaultStringRes) {
|
||||
return String.format("%s %s", emoji, getBodyOrDefault(context, record, defaultStringRes));
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.text.SpannableString;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -231,4 +232,12 @@ public abstract class DisplayRecord {
|
||||
public boolean isPendingInsecureSmsFallback() {
|
||||
return SmsDatabase.Types.isPendingInsecureSmsFallbackType(type);
|
||||
}
|
||||
|
||||
public boolean isRequestToActivatePayments() {
|
||||
return SmsDatabase.Types.isRequestToActivatePayments(type);
|
||||
}
|
||||
|
||||
public boolean isPaymentsActivated() {
|
||||
return SmsDatabase.Types.isPaymentsActivated(type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +240,12 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
int messageResource = SignalStore.misc().getSmsExportPhase().isSmsSupported() ? R.string.MessageRecord__you_will_no_longer_be_able_to_send_sms_messages_from_signal_soon
|
||||
: R.string.MessageRecord__you_can_no_longer_send_sms_messages_in_signal;
|
||||
return fromRecipient(getIndividualRecipient(), r -> context.getString(messageResource, r.getDisplayName(context)), R.drawable.ic_update_info_16);
|
||||
} else if (isRequestToActivatePayments()) {
|
||||
return isOutgoing() ? fromRecipient(getIndividualRecipient(), r -> context.getString(R.string.MessageRecord_you_sent_request, r.getShortDisplayName(context)), R.drawable.ic_card_activate_payments)
|
||||
: fromRecipient(getIndividualRecipient(), r -> context.getString(R.string.MessageRecord_wants_you_to_activate_payments, r.getShortDisplayName(context)), R.drawable.ic_card_activate_payments);
|
||||
} else if (isPaymentsActivated()) {
|
||||
return isOutgoing() ? staticUpdateDescription(context.getString(R.string.MessageRecord_you_activated_payments), R.drawable.ic_card_activate_payments)
|
||||
: fromRecipient(getIndividualRecipient(), r -> context.getString(R.string.MessageRecord_can_accept_payments, r.getShortDisplayName(context)), R.drawable.ic_card_activate_payments);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -570,7 +576,8 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
return isGroupAction() || isJoined() || isExpirationTimerUpdate() || isCallLog() ||
|
||||
isEndSession() || isIdentityUpdate() || isIdentityVerified() || isIdentityDefault() ||
|
||||
isProfileChange() || isGroupV1MigrationEvent() || isChatSessionRefresh() || isBadDecryptType() ||
|
||||
isChangeNumber() || isBoostRequest() || isThreadMergeEventType() || isSmsExportType();
|
||||
isChangeNumber() || isBoostRequest() || isThreadMergeEventType() || isSmsExportType() ||
|
||||
isRequestToActivatePayments() || isPaymentsActivated();
|
||||
}
|
||||
|
||||
public boolean isMediaPending() {
|
||||
|
||||
@@ -102,6 +102,7 @@ public final class JobManagerFactories {
|
||||
put(ForceUpdateGroupV2Job.KEY, new ForceUpdateGroupV2Job.Factory());
|
||||
put(ForceUpdateGroupV2WorkerJob.KEY, new ForceUpdateGroupV2WorkerJob.Factory());
|
||||
put(GiftSendJob.KEY, new GiftSendJob.Factory());
|
||||
put(SendPaymentsActivatedJob.KEY, new SendPaymentsActivatedJob.Factory());
|
||||
put(GroupV1MigrationJob.KEY, new GroupV1MigrationJob.Factory());
|
||||
put(GroupCallUpdateSendJob.KEY, new GroupCallUpdateSendJob.Factory());
|
||||
put(GroupCallPeekJob.KEY, new GroupCallPeekJob.Factory());
|
||||
|
||||
@@ -249,8 +249,7 @@ public class MmsDownloadJob extends BaseJob {
|
||||
List<RecipientId> recipients = new ArrayList<>(members);
|
||||
group = Optional.of(SignalDatabase.groups().getOrCreateMmsGroupForMembers(recipients));
|
||||
}
|
||||
|
||||
IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, TimeUnit.SECONDS.toMillis(retrieved.getDate()), -1, System.currentTimeMillis(), attachments, subscriptionId, 0, false, false, false, Optional.of(sharedContacts));
|
||||
IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, TimeUnit.SECONDS.toMillis(retrieved.getDate()), -1, System.currentTimeMillis(), attachments, subscriptionId, 0, false, false, false, Optional.of(sharedContacts), false, false);
|
||||
Optional<InsertResult> insertResult = database.insertMessageInbox(message, contentLocation, threadId);
|
||||
|
||||
if (insertResult.isPresent()) {
|
||||
|
||||
@@ -107,7 +107,7 @@ public final class PaymentNotificationSendJob extends BaseJob {
|
||||
}
|
||||
|
||||
SignalServiceDataMessage dataMessage = SignalServiceDataMessage.newBuilder()
|
||||
.withPayment(new SignalServiceDataMessage.Payment(new SignalServiceDataMessage.PaymentNotification(payment.getReceipt(), payment.getNote())))
|
||||
.withPayment(new SignalServiceDataMessage.Payment(new SignalServiceDataMessage.PaymentNotification(payment.getReceipt(), payment.getNote()), null))
|
||||
.build();
|
||||
|
||||
SendMessageResult sendMessageResult = messageSender.sendDataMessage(address, unidentifiedAccess, ContentHint.DEFAULT, dataMessage, IndividualSendEvents.EMPTY, false, recipient.needsPniSignature());
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
@@ -212,6 +213,7 @@ public class PushMediaSendJob extends PushSendJob {
|
||||
List<SharedContact> sharedContacts = getSharedContactsFor(message);
|
||||
List<SignalServicePreview> previews = getPreviewsFor(message);
|
||||
SignalServiceDataMessage.GiftBadge giftBadge = getGiftBadgeFor(message);
|
||||
SignalServiceDataMessage.Payment payment = getPaymentActivation(message);
|
||||
SignalServiceDataMessage.Builder mediaMessageBuilder = SignalServiceDataMessage.newBuilder()
|
||||
.withBody(message.getBody())
|
||||
.withAttachments(serviceAttachments)
|
||||
@@ -223,7 +225,8 @@ public class PushMediaSendJob extends PushSendJob {
|
||||
.withSharedContacts(sharedContacts)
|
||||
.withPreviews(previews)
|
||||
.withGiftBadge(giftBadge)
|
||||
.asExpirationUpdate(message.isExpirationUpdate());
|
||||
.asExpirationUpdate(message.isExpirationUpdate())
|
||||
.withPayment(payment);
|
||||
|
||||
if (message.getParentStoryId() != null) {
|
||||
try {
|
||||
@@ -278,6 +281,22 @@ public class PushMediaSendJob extends PushSendJob {
|
||||
}
|
||||
}
|
||||
|
||||
private SignalServiceDataMessage.Payment getPaymentActivation(OutgoingMediaMessage message) {
|
||||
SignalServiceProtos.DataMessage.Payment.Activation.Type type = null;
|
||||
|
||||
if (message.isRequestToActivatePayments()) {
|
||||
type = SignalServiceProtos.DataMessage.Payment.Activation.Type.REQUEST;
|
||||
} else if (message.isPaymentsActivated()) {
|
||||
type = SignalServiceProtos.DataMessage.Payment.Activation.Type.ACTIVATED;
|
||||
}
|
||||
|
||||
if (type != null) {
|
||||
return new SignalServiceDataMessage.Payment(null, new SignalServiceDataMessage.PaymentActivation(type));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static long getMessageId(@NonNull Data data) {
|
||||
return data.getLong(KEY_MESSAGE_ID);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.jobmanager.Data
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.mms.OutgoingPaymentsActivatedMessages
|
||||
import org.thoughtcrime.securesms.sms.MessageSender
|
||||
|
||||
/**
|
||||
* Send payments activated message to all recipients of payment activation request
|
||||
*/
|
||||
class SendPaymentsActivatedJob(parameters: Parameters) : BaseJob(parameters) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(SendPaymentsActivatedJob::class.java)
|
||||
|
||||
const val KEY = "SendPaymentsActivatedJob"
|
||||
}
|
||||
|
||||
constructor() : this(parameters = Parameters.Builder().build())
|
||||
|
||||
override fun serialize(): Data = Data.Builder().build()
|
||||
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
@Suppress("UsePropertyAccessSyntax")
|
||||
override fun onRun() {
|
||||
if (!SignalStore.paymentsValues().mobileCoinPaymentsEnabled()) {
|
||||
Log.w(TAG, "Payments aren't enabled, not going to attempt to send activation messages.")
|
||||
return
|
||||
}
|
||||
|
||||
val threadIds: List<Long> = SignalDatabase.mms.getIncomingPaymentRequestThreads()
|
||||
|
||||
for (threadId in threadIds) {
|
||||
val recipient = SignalDatabase.threads.getRecipientForThreadId(threadId)
|
||||
if (recipient != null) {
|
||||
MessageSender.send(
|
||||
context,
|
||||
OutgoingPaymentsActivatedMessages(recipient, System.currentTimeMillis(), 0),
|
||||
threadId,
|
||||
false,
|
||||
null,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
Log.w(TAG, "Unable to send activation message for thread: $threadId")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShouldRetry(e: Exception): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onFailure() {
|
||||
Log.w(TAG, "Failed to submit send of payments activated messages")
|
||||
}
|
||||
|
||||
class Factory : Job.Factory<SendPaymentsActivatedJob> {
|
||||
override fun create(parameters: Parameters, data: Data): SendPaymentsActivatedJob {
|
||||
return SendPaymentsActivatedJob(parameters)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,6 +295,8 @@ public final class MessageContentProcessor {
|
||||
else if (message.getReaction().isPresent() && message.getStoryContext().isPresent()) messageId = handleStoryReaction(content, message, senderRecipient);
|
||||
else if (message.getReaction().isPresent()) messageId = handleReaction(content, message, senderRecipient);
|
||||
else if (message.getRemoteDelete().isPresent()) messageId = handleRemoteDelete(content, message, senderRecipient);
|
||||
else if (message.isActivatePaymentsRequest()) messageId = handlePaymentActivation(content, message, smsMessageId, senderRecipient, receivedTime, true, false);
|
||||
else if (message.isPaymentsActivated()) messageId = handlePaymentActivation(content, message, smsMessageId, senderRecipient, receivedTime, false, true);
|
||||
else if (message.getPayment().isPresent()) handlePayment(content, message, senderRecipient);
|
||||
else if (message.getStoryContext().isPresent()) messageId = handleStoryReply(content, message, senderRecipient, receivedTime);
|
||||
else if (message.getGiftBadge().isPresent()) messageId = handleGiftMessage(content, message, senderRecipient, threadRecipient, receivedTime);
|
||||
@@ -809,6 +811,60 @@ public final class MessageContentProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isActivatePaymentsRequest True if payments activation request message.
|
||||
* @param isPaymentsActivated True if payments activated message.
|
||||
* @throws StorageFailedException
|
||||
*/
|
||||
private @Nullable MessageId handlePaymentActivation(@NonNull SignalServiceContent content,
|
||||
@NonNull SignalServiceDataMessage message,
|
||||
@NonNull Optional<Long> smsMessageId,
|
||||
@NonNull Recipient senderRecipient,
|
||||
long receivedTime,
|
||||
boolean isActivatePaymentsRequest,
|
||||
boolean isPaymentsActivated)
|
||||
throws StorageFailedException
|
||||
{
|
||||
try {
|
||||
MessageDatabase database = SignalDatabase.mms();
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(senderRecipient.getId(),
|
||||
content.getTimestamp(),
|
||||
content.getServerReceivedTimestamp(),
|
||||
receivedTime,
|
||||
StoryType.NONE,
|
||||
null,
|
||||
false,
|
||||
-1,
|
||||
TimeUnit.SECONDS.toMillis(message.getExpiresInSeconds()),
|
||||
false,
|
||||
false,
|
||||
content.isNeedsReceipt(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
content.getServerUuid(),
|
||||
null,
|
||||
isActivatePaymentsRequest,
|
||||
isPaymentsActivated);
|
||||
|
||||
Optional<InsertResult> insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
|
||||
if (smsMessageId.isPresent()) {
|
||||
SignalDatabase.sms().deleteMessage(smsMessageId.get());
|
||||
}
|
||||
|
||||
if (insertResult.isPresent()) {
|
||||
return new MessageId(insertResult.get().getMessageId(), true);
|
||||
}
|
||||
} catch (MmsException e) {
|
||||
throw new StorageFailedException(e, content.getSender().getIdentifier(), content.getSenderDevice());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sideEffect True if the event is side effect of a different message, false if the message itself was an expiration update.
|
||||
@@ -862,7 +918,9 @@ public final class MessageContentProcessor {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
content.getServerUuid(),
|
||||
null);
|
||||
null,
|
||||
false,
|
||||
false);
|
||||
|
||||
Optional<InsertResult> insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
|
||||
|
||||
@@ -1423,7 +1481,9 @@ public final class MessageContentProcessor {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
content.getServerUuid(),
|
||||
null);
|
||||
null,
|
||||
false,
|
||||
false);
|
||||
|
||||
insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
|
||||
|
||||
@@ -1587,7 +1647,9 @@ public final class MessageContentProcessor {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
content.getServerUuid(),
|
||||
null);
|
||||
null,
|
||||
false,
|
||||
false);
|
||||
|
||||
Optional<InsertResult> insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
|
||||
|
||||
@@ -1690,7 +1752,9 @@ public final class MessageContentProcessor {
|
||||
getMentions(message.getMentions()),
|
||||
Optional.empty(),
|
||||
content.getServerUuid(),
|
||||
null);
|
||||
null,
|
||||
false,
|
||||
false);
|
||||
|
||||
Optional<InsertResult> insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
|
||||
|
||||
@@ -1768,7 +1832,9 @@ public final class MessageContentProcessor {
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
content.getServerUuid(),
|
||||
giftBadge);
|
||||
giftBadge,
|
||||
false,
|
||||
false);
|
||||
|
||||
insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
|
||||
} catch (MmsException e) {
|
||||
@@ -1832,7 +1898,9 @@ public final class MessageContentProcessor {
|
||||
mentions,
|
||||
sticker,
|
||||
content.getServerUuid(),
|
||||
null);
|
||||
null,
|
||||
false,
|
||||
false);
|
||||
|
||||
insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
|
||||
|
||||
|
||||
@@ -39,7 +39,10 @@ import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.concurrent.SimpleTask;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
@@ -52,27 +55,36 @@ import org.thoughtcrime.securesms.components.location.SignalPlace;
|
||||
import org.thoughtcrime.securesms.conversation.MessageSendType;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.MediaDatabase;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
|
||||
import org.thoughtcrime.securesms.maps.PlacePickerActivity;
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Fragment;
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity;
|
||||
import org.thoughtcrime.securesms.payments.CanNotSendPaymentDialog;
|
||||
import org.thoughtcrime.securesms.payments.MobileCoinPublicAddress;
|
||||
import org.thoughtcrime.securesms.payments.PaymentsAddressException;
|
||||
import org.thoughtcrime.securesms.payments.create.CreatePaymentFragmentArgs;
|
||||
import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity;
|
||||
import org.thoughtcrime.securesms.payments.preferences.RecipientHasNotEnabledPaymentsDialog;
|
||||
import org.thoughtcrime.securesms.payments.preferences.model.PayeeParcelable;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.providers.DeprecatedPersistentBlobProvider;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
import org.whispersystems.signalservice.api.util.ExpiringProfileCredentialUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
@@ -81,6 +93,7 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
public class AttachmentManager {
|
||||
@@ -419,11 +432,45 @@ public class AttachmentManager {
|
||||
fragment.startActivityForResult(intent, requestCode);
|
||||
}
|
||||
|
||||
public static void selectPayment(@NonNull Fragment fragment, @NonNull RecipientId recipientId) {
|
||||
Intent intent = new Intent(fragment.requireContext(), PaymentsActivity.class);
|
||||
intent.putExtra(PaymentsActivity.EXTRA_PAYMENTS_STARTING_ACTION, R.id.action_directly_to_createPayment);
|
||||
intent.putExtra(PaymentsActivity.EXTRA_STARTING_ARGUMENTS, new CreatePaymentFragmentArgs.Builder(new PayeeParcelable(recipientId)).build().toBundle());
|
||||
fragment.startActivity(intent);
|
||||
public static void selectPayment(@NonNull Fragment fragment, @NonNull Recipient recipient) {
|
||||
if (!ExpiringProfileCredentialUtil.isValid(recipient.getExpiringProfileKeyCredential())) {
|
||||
CanNotSendPaymentDialog.show(fragment.requireContext());
|
||||
return;
|
||||
}
|
||||
|
||||
SimpleTask.run(fragment.getViewLifecycleOwner().getLifecycle(),
|
||||
() -> {
|
||||
try {
|
||||
return ProfileUtil.getAddressForRecipient(recipient);
|
||||
} catch (IOException | PaymentsAddressException e) {
|
||||
Log.w(TAG, "Could not get address for recipient: ", e);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
(address) -> {
|
||||
if (address != null) {
|
||||
Intent intent = new Intent(fragment.requireContext(), PaymentsActivity.class);
|
||||
intent.putExtra(PaymentsActivity.EXTRA_PAYMENTS_STARTING_ACTION, R.id.action_directly_to_createPayment);
|
||||
intent.putExtra(PaymentsActivity.EXTRA_STARTING_ARGUMENTS, new CreatePaymentFragmentArgs.Builder(new PayeeParcelable(recipient.getId())).build().toBundle());
|
||||
fragment.startActivity(intent);
|
||||
} else if (FeatureFlags.paymentsRequestActivateFlow()) {
|
||||
showRequestToActivatePayments(fragment.requireContext(), recipient);
|
||||
} else {
|
||||
RecipientHasNotEnabledPaymentsDialog.show(fragment.requireContext());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void showRequestToActivatePayments(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(context.getString(R.string.AttachmentManager__not_activated_payments, recipient.getShortDisplayName(context)))
|
||||
.setMessage(context.getString(R.string.AttachmentManager__request_to_activate_payments))
|
||||
.setPositiveButton(context.getString(R.string.AttachmentManager__send_request), (dialog, which) -> {
|
||||
OutgoingRequestToActivatePaymentMessages outgoingMessage = new OutgoingRequestToActivatePaymentMessages(recipient, System.currentTimeMillis(), 0);
|
||||
MessageSender.send(context, outgoingMessage, SignalDatabase.threads().getOrCreateThreadIdFor(recipient), false, null, null);
|
||||
})
|
||||
.setNegativeButton(context.getString(R.string.AttachmentManager__cancel), null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private @Nullable Uri getSlideUri() {
|
||||
|
||||
@@ -38,7 +38,9 @@ class IncomingMediaMessage(
|
||||
sharedContacts: List<Contact> = emptyList(),
|
||||
linkPreviews: List<LinkPreview> = emptyList(),
|
||||
mentions: List<Mention> = emptyList(),
|
||||
val giftBadge: GiftBadge? = null
|
||||
val giftBadge: GiftBadge? = null,
|
||||
val isActivatePaymentsRequest: Boolean = false,
|
||||
val isPaymentsActivated: Boolean = false
|
||||
) {
|
||||
|
||||
val attachments: List<Attachment> = ArrayList(attachments)
|
||||
@@ -61,7 +63,9 @@ class IncomingMediaMessage(
|
||||
expirationUpdate: Boolean,
|
||||
viewOnce: Boolean,
|
||||
unidentified: Boolean,
|
||||
sharedContacts: Optional<List<Contact>>
|
||||
sharedContacts: Optional<List<Contact>>,
|
||||
activatePaymentsRequest: Boolean,
|
||||
paymentsActivated: Boolean
|
||||
) : this(
|
||||
from = from,
|
||||
groupId = groupId.orElse(null),
|
||||
@@ -79,6 +83,8 @@ class IncomingMediaMessage(
|
||||
serverGuid = null,
|
||||
attachments = attachments?.let { ArrayList<Attachment>(it) } ?: emptyList(),
|
||||
sharedContacts = ArrayList<Contact>(sharedContacts.orElse(emptyList())),
|
||||
isActivatePaymentsRequest = activatePaymentsRequest,
|
||||
isPaymentsActivated = paymentsActivated
|
||||
)
|
||||
|
||||
constructor(
|
||||
@@ -103,7 +109,9 @@ class IncomingMediaMessage(
|
||||
mentions: Optional<List<Mention>>,
|
||||
sticker: Optional<Attachment>,
|
||||
serverGuid: String?,
|
||||
giftBadge: GiftBadge?
|
||||
giftBadge: GiftBadge?,
|
||||
activatePaymentsRequest: Boolean,
|
||||
paymentsActivated: Boolean
|
||||
) : this(
|
||||
from = from,
|
||||
groupId = if (group.isPresent) GroupId.v2(group.get().masterKey) else null,
|
||||
@@ -126,6 +134,8 @@ class IncomingMediaMessage(
|
||||
sharedContacts = sharedContacts.orElse(emptyList()),
|
||||
linkPreviews = linkPreviews.orElse(emptyList()),
|
||||
mentions = mentions.orElse(emptyList()),
|
||||
giftBadge = giftBadge
|
||||
giftBadge = giftBadge,
|
||||
isActivatePaymentsRequest = activatePaymentsRequest,
|
||||
isPaymentsActivated = paymentsActivated
|
||||
)
|
||||
}
|
||||
|
||||
@@ -193,6 +193,14 @@ public class OutgoingMediaMessage {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isRequestToActivatePayments() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isPaymentsActivated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public long getSentTimeMillis() {
|
||||
return sentTimeMillis;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.thoughtcrime.securesms.mms
|
||||
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import java.util.LinkedList
|
||||
|
||||
/**
|
||||
* Specialized message sent to request someone activate payments.
|
||||
*/
|
||||
class OutgoingRequestToActivatePaymentMessages(
|
||||
recipient: Recipient,
|
||||
sentTimeMillis: Long,
|
||||
expiresIn: Long
|
||||
) : OutgoingSecureMediaMessage(
|
||||
recipient,
|
||||
"",
|
||||
LinkedList(),
|
||||
sentTimeMillis,
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION,
|
||||
expiresIn,
|
||||
false,
|
||||
StoryType.NONE,
|
||||
null,
|
||||
false,
|
||||
null, emptyList(), emptyList(), emptyList(),
|
||||
null
|
||||
) {
|
||||
override fun isRequestToActivatePayments(): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specialized message sent to indicate you activated payments. Intended to only
|
||||
* be sent to those that sent requests prior to activation.
|
||||
*/
|
||||
class OutgoingPaymentsActivatedMessages(
|
||||
recipient: Recipient,
|
||||
sentTimeMillis: Long,
|
||||
expiresIn: Long
|
||||
) : OutgoingSecureMediaMessage(
|
||||
recipient,
|
||||
"",
|
||||
LinkedList(),
|
||||
sentTimeMillis,
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION,
|
||||
expiresIn,
|
||||
false,
|
||||
StoryType.NONE,
|
||||
null,
|
||||
false,
|
||||
null, emptyList(), emptyList(), emptyList(),
|
||||
null
|
||||
) {
|
||||
override fun isPaymentsActivated(): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.PaymentLedgerUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
|
||||
import org.thoughtcrime.securesms.jobs.SendPaymentsActivatedJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.util.AsynchronousCallback;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
@@ -25,7 +26,10 @@ public class PaymentsHomeRepository {
|
||||
SignalStore.paymentsValues().setMobileCoinPaymentsEnabled(true);
|
||||
try {
|
||||
ProfileUtil.uploadProfile(ApplicationDependencies.getApplication());
|
||||
ApplicationDependencies.getJobManager().add(PaymentLedgerUpdateJob.updateLedger());
|
||||
ApplicationDependencies.getJobManager()
|
||||
.startChain(PaymentLedgerUpdateJob.updateLedger())
|
||||
.then(new SendPaymentsActivatedJob())
|
||||
.enqueue();
|
||||
callback.onComplete(null);
|
||||
} catch (PaymentsRegionException e) {
|
||||
SignalStore.paymentsValues().setMobileCoinPaymentsEnabled(false);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package org.thoughtcrime.securesms.payments.preferences;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
/**
|
||||
@@ -19,16 +20,17 @@ public final class RecipientHasNotEnabledPaymentsDialog {
|
||||
public static void show(@NonNull Context context) {
|
||||
show(context, null);
|
||||
}
|
||||
|
||||
public static void show(@NonNull Context context, @Nullable Runnable onDismissed) {
|
||||
new AlertDialog.Builder(context).setTitle(R.string.ConfirmPaymentFragment__invalid_recipient)
|
||||
.setMessage(R.string.ConfirmPaymentFragment__this_person_has_not_activated_payments)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
if (onDismissed != null) {
|
||||
onDismissed.run();
|
||||
}
|
||||
})
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
new MaterialAlertDialogBuilder(context).setTitle(R.string.ConfirmPaymentFragment__invalid_recipient)
|
||||
.setMessage(R.string.ConfirmPaymentFragment__this_person_has_not_activated_payments)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
if (onDismissed != null) {
|
||||
onDismissed.run();
|
||||
}
|
||||
})
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +104,7 @@ public final class FeatureFlags {
|
||||
private static final String HIDE_CONTACTS = "android.hide.contacts";
|
||||
private static final String SMS_EXPORT_MEGAPHONE_DELAY_DAYS = "android.smsExport.megaphoneDelayDays.2";
|
||||
public static final String CREDIT_CARD_PAYMENTS = "android.credit.card.payments";
|
||||
private static final String PAYMENTS_REQUEST_ACTIVATE_FLOW = "android.payments.requestActivateFlow";
|
||||
|
||||
/**
|
||||
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
||||
@@ -159,7 +160,8 @@ public final class FeatureFlags {
|
||||
STORIES_LOCALE,
|
||||
HIDE_CONTACTS,
|
||||
SMS_EXPORT_MEGAPHONE_DELAY_DAYS,
|
||||
CREDIT_CARD_PAYMENTS
|
||||
CREDIT_CARD_PAYMENTS,
|
||||
PAYMENTS_REQUEST_ACTIVATE_FLOW
|
||||
);
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -222,7 +224,8 @@ public final class FeatureFlags {
|
||||
RECIPIENT_MERGE_V2,
|
||||
STORIES,
|
||||
SMS_EXPORT_MEGAPHONE_DELAY_DAYS,
|
||||
CREDIT_CARD_PAYMENTS
|
||||
CREDIT_CARD_PAYMENTS,
|
||||
PAYMENTS_REQUEST_ACTIVATE_FLOW
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -570,6 +573,11 @@ public final class FeatureFlags {
|
||||
return getBoolean(CREDIT_CARD_PAYMENTS, Environment.IS_STAGING);
|
||||
}
|
||||
|
||||
/** Whether client supports sending a request to another to activate payments */
|
||||
public static boolean paymentsRequestActivateFlow() {
|
||||
return getBoolean(PAYMENTS_REQUEST_ACTIVATE_FLOW, false);
|
||||
}
|
||||
|
||||
/** Only for rendering debug info. */
|
||||
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
||||
return new TreeMap<>(REMOTE_VALUES);
|
||||
|
||||
@@ -44,6 +44,12 @@ fun MessageRecord.hasThumbnail(): Boolean =
|
||||
fun MessageRecord.isStoryReaction(): Boolean =
|
||||
isMms && MmsSmsColumns.Types.isStoryReaction((this as MmsMessageRecord).type)
|
||||
|
||||
fun MessageRecord.isPaymentActivationRequest(): Boolean =
|
||||
isMms && MmsSmsColumns.Types.isRequestToActivatePayments((this as MmsMessageRecord).type)
|
||||
|
||||
fun MessageRecord.isPaymentsActivated(): Boolean =
|
||||
isMms && MmsSmsColumns.Types.isPaymentsActivated((this as MmsMessageRecord).type)
|
||||
|
||||
fun MessageRecord.isBorderless(context: Context): Boolean {
|
||||
return isCaptionlessMms(context) &&
|
||||
hasThumbnail() &&
|
||||
|
||||
Reference in New Issue
Block a user