Add import and tombstones for mobile coin payments.

This commit is contained in:
Clark
2024-05-31 17:03:59 -04:00
committed by Cody Henthorne
parent 1e35403c87
commit d85ab37828
19 changed files with 399 additions and 9 deletions

View File

@@ -132,5 +132,6 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
void onReportSpamLearnMoreClicked();
void onMessageRequestAcceptOptionsClicked();
void onItemDoubleClick(MultiselectPart multiselectPart);
void onPaymentTombstoneClicked();
}
}

View File

@@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.ChatUpdateMessage
import org.thoughtcrime.securesms.backup.v2.proto.GroupCall
import org.thoughtcrime.securesms.backup.v2.proto.IndividualCall
import org.thoughtcrime.securesms.backup.v2.proto.MessageAttachment
import org.thoughtcrime.securesms.backup.v2.proto.PaymentNotification
import org.thoughtcrime.securesms.backup.v2.proto.Quote
import org.thoughtcrime.securesms.backup.v2.proto.Reaction
import org.thoughtcrime.securesms.backup.v2.proto.SendStatus
@@ -46,22 +47,31 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailureSet
import org.thoughtcrime.securesms.database.model.GroupCallUpdateDetailsUtil
import org.thoughtcrime.securesms.database.model.Mention
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
import org.thoughtcrime.securesms.database.model.databaseprotos.CryptoValue
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
import org.thoughtcrime.securesms.database.model.databaseprotos.PaymentTombstone
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails
import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent
import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent
import org.thoughtcrime.securesms.mms.QuoteModel
import org.thoughtcrime.securesms.payments.CryptoValueUtil
import org.thoughtcrime.securesms.payments.Direction
import org.thoughtcrime.securesms.payments.State
import org.thoughtcrime.securesms.payments.proto.PaymentMetaData
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.JsonUtils
import org.whispersystems.signalservice.api.backup.MediaName
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
import org.whispersystems.signalservice.api.payments.Money
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.util.UuidUtil
import org.whispersystems.signalservice.internal.push.DataMessage
import java.math.BigInteger
import java.util.Optional
import java.util.UUID
/**
* An object that will ingest all fo the [ChatItem]s you want to write, buffer them until hitting a specified batch size, and then batch insert them
@@ -283,6 +293,22 @@ class ChatItemImportInserter(
}
}
}
if (this.paymentNotification != null) {
followUp = { messageRowId ->
val uuid = tryRestorePayment(this, chatRecipientId)
if (uuid != null) {
db.update(
MessageTable.TABLE_NAME,
contentValuesOf(
MessageTable.BODY to uuid.toString(),
MessageTable.TYPE to ((contentValues.getAsLong(MessageTable.TYPE) and MessageTypes.SPECIAL_TYPES_MASK.inv()) or MessageTypes.SPECIAL_TYPE_PAYMENTS_NOTIFICATION)
),
"${MessageTable.ID}=?",
SqlUtil.buildArgs(messageRowId)
)
}
}
}
if (this.standardMessage != null) {
val bodyRanges = this.standardMessage.text?.bodyRanges
if (!bodyRanges.isNullOrEmpty()) {
@@ -370,11 +396,42 @@ class ChatItemImportInserter(
this.standardMessage != null -> contentValues.addStandardMessage(this.standardMessage)
this.remoteDeletedMessage != null -> contentValues.put(MessageTable.REMOTE_DELETED, 1)
this.updateMessage != null -> contentValues.addUpdateMessage(this.updateMessage)
this.paymentNotification != null -> contentValues.addPaymentNotification(this, chatRecipientId)
}
return contentValues
}
private fun tryRestorePayment(chatItem: ChatItem, chatRecipientId: RecipientId): UUID? {
val paymentNotification = chatItem.paymentNotification!!
val amount = paymentNotification.amountMob?.tryParseMoney() ?: return null
val fee = paymentNotification.feeMob?.tryParseMoney() ?: return null
if (paymentNotification.transactionDetails?.failedTransaction != null) {
return null
}
val transaction = paymentNotification.transactionDetails?.transaction
val mobileCoinIdentification = transaction?.mobileCoinIdentification?.toLocal() ?: return null
return SignalDatabase.payments.restoreFromBackup(
chatRecipientId,
transaction.timestamp ?: 0,
transaction.blockIndex ?: 0,
paymentNotification.note ?: "",
if (chatItem.outgoing != null) Direction.SENT else Direction.RECEIVED,
transaction.status.toLocalStatus(),
amount,
fee,
transaction.transaction?.toByteArray(),
transaction.receipt?.toByteArray(),
mobileCoinIdentification,
chatItem.incoming?.read ?: true
)
}
private fun ChatItem.toReactionContentValues(messageId: Long): List<ContentValues> {
val reactions: List<Reaction> = when {
this.standardMessage != null -> this.standardMessage.reactions
@@ -551,6 +608,104 @@ class ChatItemImportInserter(
this.put(MessageTable.TYPE, typeFlags)
}
/**
* Add the payment notification to the chat item.
*
* Note we add a tombstone first, then post insertion update it to a proper notification
*/
private fun ContentValues.addPaymentNotification(chatItem: ChatItem, chatRecipientId: RecipientId) {
val paymentNotification = chatItem.paymentNotification!!
if (chatItem.paymentNotification.amountMob.isNullOrEmpty()) {
addPaymentTombstoneNoAmount()
return
}
val amount = paymentNotification.amountMob?.tryParseMoney() ?: return addPaymentTombstoneNoAmount()
val fee = paymentNotification.feeMob?.tryParseMoney() ?: return addPaymentTombstoneNoAmount()
if (chatItem.paymentNotification.transactionDetails?.failedTransaction != null) {
addFailedPaymentNotification(chatItem, amount, fee, chatRecipientId)
return
}
addPaymentTombstoneNoMetadata(chatItem.paymentNotification)
}
private fun PaymentNotification.TransactionDetails.MobileCoinTxoIdentification.toLocal(): PaymentMetaData {
return PaymentMetaData(
mobileCoinTxoIdentification = PaymentMetaData.MobileCoinTxoIdentification(
publicKey = this.publicKey,
keyImages = this.keyImages
)
)
}
private fun ContentValues.addFailedPaymentNotification(chatItem: ChatItem, amount: Money, fee: Money, chatRecipientId: RecipientId) {
val uuid = SignalDatabase.payments.restoreFromBackup(
chatRecipientId,
0,
0,
chatItem.paymentNotification?.note ?: "",
if (chatItem.outgoing != null) Direction.SENT else Direction.RECEIVED,
State.FAILED,
amount,
fee,
null,
null,
null,
chatItem.incoming?.read ?: true
)
if (uuid != null) {
put(MessageTable.BODY, uuid.toString())
put(MessageTable.TYPE, getAsLong(MessageTable.TYPE) or MessageTypes.SPECIAL_TYPE_PAYMENTS_NOTIFICATION)
} else {
addPaymentTombstoneNoMetadata(chatItem.paymentNotification!!)
}
}
private fun ContentValues.addPaymentTombstoneNoAmount() {
put(MessageTable.TYPE, getAsLong(MessageTable.TYPE) or MessageTypes.SPECIAL_TYPE_PAYMENTS_TOMBSTONE)
}
private fun ContentValues.addPaymentTombstoneNoMetadata(paymentNotification: PaymentNotification) {
put(MessageTable.TYPE, getAsLong(MessageTable.TYPE) or MessageTypes.SPECIAL_TYPE_PAYMENTS_TOMBSTONE)
val amount = tryParseCryptoValue(paymentNotification.amountMob)
val fee = tryParseCryptoValue(paymentNotification.feeMob)
put(
MessageTable.MESSAGE_EXTRAS,
MessageExtras(
paymentTombstone = PaymentTombstone(
note = paymentNotification.note,
amount = amount,
fee = fee
)
).encode()
)
}
private fun String?.tryParseMoney(): Money? {
if (this.isNullOrEmpty()) {
return null
}
val amountCryptoValue = tryParseCryptoValue(this)
return if (amountCryptoValue != null) {
CryptoValueUtil.cryptoValueToMoney(amountCryptoValue)
} else {
null
}
}
private fun tryParseCryptoValue(bigIntegerString: String?): CryptoValue? {
if (bigIntegerString == null) {
return null
}
val amount = try {
BigInteger(bigIntegerString).toString()
} catch (e: NumberFormatException) {
return null
}
return CryptoValue(mobileCoinValue = CryptoValue.MobileCoinValue(picoMobileCoin = amount))
}
private fun ContentValues.addQuote(quote: Quote) {
this.put(MessageTable.QUOTE_ID, quote.targetSentTimestamp ?: MessageTable.QUOTE_TARGET_MISSING_ID)
this.put(MessageTable.QUOTE_AUTHOR, backupState.backupToLocalRecipientId[quote.authorId]!!.serialize())
@@ -561,6 +716,15 @@ class ChatItemImportInserter(
this.put(MessageTable.QUOTE_MISSING, (quote.targetSentTimestamp == null).toInt())
}
private fun PaymentNotification.TransactionDetails.Transaction.Status?.toLocalStatus(): State {
return when (this) {
PaymentNotification.TransactionDetails.Transaction.Status.INITIAL -> State.INITIAL
PaymentNotification.TransactionDetails.Transaction.Status.SUBMITTED -> State.SUBMITTED
PaymentNotification.TransactionDetails.Transaction.Status.SUCCESSFUL -> State.SUCCESSFUL
else -> State.INITIAL
}
}
private fun Quote.Type.toLocalQuoteType(): Int {
return when (this) {
Quote.Type.UNKNOWN -> QuoteModel.Type.NORMAL.code

View File

@@ -110,6 +110,7 @@ import org.thoughtcrime.securesms.database.MediaTable;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.Quote;
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.events.PartProgressEvent;
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicy;
@@ -261,6 +262,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
private final TouchDelegateChangedListener touchDelegateChangedListener = new TouchDelegateChangedListener();
private final DoubleTapEditTouchListener doubleTapEditTouchListener = new DoubleTapEditTouchListener();
private final GiftMessageViewCallback giftMessageViewCallback = new GiftMessageViewCallback();
private final PaymentTombstoneClickListener paymentTombstoneClickListener = new PaymentTombstoneClickListener();
private final Context context;
@@ -1038,7 +1040,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) || isGiftMessage(messageRecord) || messageRecord.isPaymentNotification()) {
} else if (isCaptionlessMms(messageRecord) || isStoryReaction(messageRecord) || isGiftMessage(messageRecord) || messageRecord.isPaymentNotification() || messageRecord.isPaymentTombstone()) {
bodyText.setText(null);
bodyText.setOverflowText(null);
bodyText.setVisibility(View.GONE);
@@ -1396,8 +1398,29 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
MmsMessageRecord mediaMmsMessageRecord = (MmsMessageRecord) messageRecord;
paymentViewStub.setVisibility(View.VISIBLE);
paymentViewStub.get().setOnTombstoneClickListener(paymentTombstoneClickListener);
paymentViewStub.get().bindPayment(conversationRecipient.get(), Objects.requireNonNull(mediaMmsMessageRecord.getPayment()), colorizer);
footer.setVisibility(VISIBLE);
} else if (messageRecord.isPaymentTombstone()) {
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);
if (giftViewStub.resolved()) giftViewStub.get().setVisibility(View.GONE);
if (joinCallLinkStub.resolved()) joinCallLinkStub.get().setVisibility(View.GONE);
MmsMessageRecord mediaMmsMessageRecord = (MmsMessageRecord) messageRecord;
paymentViewStub.setVisibility(View.VISIBLE);
paymentViewStub.get().setOnTombstoneClickListener(paymentTombstoneClickListener);
MessageExtras messageExtras = mediaMmsMessageRecord.getMessageExtras();
paymentViewStub.get().bindPaymentTombstone(mediaMmsMessageRecord.isOutgoing(), conversationRecipient.get(), messageExtras == null ? null : messageExtras.paymentTombstone, colorizer);
footer.setVisibility(VISIBLE);
} else {
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.require().setVisibility(View.GONE);
@@ -2352,6 +2375,16 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
return null;
}
private class PaymentTombstoneClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
if (eventListener != null) {
eventListener.onPaymentTombstoneClicked();
} else {
passthroughClickListener.onClick(v);
}
}
}
private class SharedContactEventListener implements SharedContactView.EventListener {
@Override
public void onAddToContactsClicked(@NonNull Contact contact) {

View File

@@ -135,7 +135,7 @@ public final class MenuState {
hasGift = true;
}
if (messageRecord.isPaymentNotification()) {
if (messageRecord.isPaymentNotification() || messageRecord.isPaymentTombstone()) {
hasPayment = true;
}
}

View File

@@ -14,11 +14,14 @@ import org.signal.core.util.dp
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.quotes.QuoteViewColorTheme
import org.thoughtcrime.securesms.conversation.colors.Colorizer
import org.thoughtcrime.securesms.database.model.databaseprotos.PaymentTombstone
import org.thoughtcrime.securesms.databinding.PaymentMessageViewBinding
import org.thoughtcrime.securesms.payments.CryptoValueUtil
import org.thoughtcrime.securesms.payments.Direction
import org.thoughtcrime.securesms.payments.Payment
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.visible
import org.whispersystems.signalservice.api.payments.Money
/**
* Showing payment information in conversation.
@@ -30,11 +33,25 @@ class PaymentMessageView @JvmOverloads constructor(
private val binding: PaymentMessageViewBinding
private var onTombstoneClickListener: OnClickListener? = null
init {
binding = PaymentMessageViewBinding.inflate(LayoutInflater.from(context), this, true)
}
fun bindPayment(recipient: Recipient, payment: Payment, colorizer: Colorizer) {
fun bindPayment(recipient: Recipient, payment: Payment?, colorizer: Colorizer) {
if (payment == null) {
binding.paymentTombstone.visible = true
binding.paymentAmount.visible = false
binding.paymentInprogress.visible = false
binding.paymentAmountLayout.setOnClickListener {
onTombstoneClickListener?.onClick(it)
}
return
}
binding.paymentAmountLayout.setOnClickListener(null)
binding.paymentTombstone.visible = false
val outgoing = payment.direction == Direction.SENT
binding.paymentDirection.apply {
@@ -69,6 +86,51 @@ class PaymentMessageView @JvmOverloads constructor(
ViewCompat.setBackgroundTintList(binding.paymentAmountLayout, ColorStateList.valueOf(quoteViewColorTheme.getBackgroundColor(context)))
}
fun bindPaymentTombstone(outgoing: Boolean, recipient: Recipient, paymentTombstone: PaymentTombstone?, colorizer: Colorizer) {
val amount: Money? = if (paymentTombstone?.amount != null) {
CryptoValueUtil.cryptoValueToMoney(paymentTombstone.amount)
} else {
null
}
binding.paymentDirection.apply {
if (outgoing) {
text = context.getString(R.string.PaymentMessageView_you_sent_s, recipient.getShortDisplayName(context))
setTextColor(colorizer.getOutgoingFooterTextColor(context))
} else {
text = context.getString(R.string.PaymentMessageView_s_sent_you, recipient.getShortDisplayName(context))
setTextColor(colorizer.getIncomingFooterTextColor(context, recipient.hasWallpaper))
}
}
if (amount == null) {
binding.paymentTombstone.visible = true
binding.paymentAmount.visible = false
binding.paymentInprogress.visible = false
binding.paymentAmountLayout.setOnClickListener {
onTombstoneClickListener?.onClick(it)
}
return
}
val note = paymentTombstone?.note ?: ""
binding.paymentAmountLayout.setOnClickListener(null)
binding.paymentTombstone.visible = false
binding.paymentNote.apply {
text = note
visible = note.isNotEmpty()
setTextColor(if (outgoing) colorizer.getOutgoingBodyTextColor(context) else colorizer.getIncomingBodyTextColor(context, recipient.hasWallpaper))
}
val quoteViewColorTheme = QuoteViewColorTheme.resolveTheme(outgoing, false, recipient.hasWallpaper)
binding.paymentAmount.visible = true
binding.paymentInprogress.visible = false
binding.paymentAmount.setTextColor(quoteViewColorTheme.getForegroundColor(context))
binding.paymentAmount.setMoney(amount, 0L, currencyTypefaceSpan)
ViewCompat.setBackgroundTintList(binding.paymentAmountLayout, ColorStateList.valueOf(quoteViewColorTheme.getBackgroundColor(context)))
}
private fun getInProgressDrawable(@ColorInt color: Int): IndeterminateDrawable<CircularProgressIndicatorSpec> {
val spec = CircularProgressIndicatorSpec(context, null).apply {
indicatorInset = 0
@@ -82,6 +144,10 @@ class PaymentMessageView @JvmOverloads constructor(
return drawable
}
fun setOnTombstoneClickListener(listener: OnClickListener?) {
this.onTombstoneClickListener = listener
}
companion object {
private val currencyTypefaceSpan = TypefaceSpan("sans-serif-light")
}

View File

@@ -2367,12 +2367,26 @@ class ConversationFragment :
private fun handleViewPaymentDetails(conversationMessage: ConversationMessage) {
val record: MmsMessageRecord = conversationMessage.messageRecord as? MmsMessageRecord ?: return
val payment = record.payment ?: return
val payment = record.payment
if (payment == null || record.isPaymentTombstone) {
showPaymentTombstoneLearnMoreDialog()
return
}
if (record.isPaymentNotification) {
startActivity(PaymentsActivity.navigateToPaymentDetails(requireContext(), payment.uuid))
}
}
private fun showPaymentTombstoneLearnMoreDialog() {
val dialogBuilder = MaterialAlertDialogBuilder(requireContext())
dialogBuilder
.setTitle(R.string.PaymentTombstoneLearnMoreDialog_title)
.setMessage(R.string.PaymentTombstoneLearnMoreDialog_message)
.setPositiveButton(android.R.string.ok, null)
dialogBuilder.show()
}
private fun handleDisplayDetails(conversationMessage: ConversationMessage) {
val recipientSnapshot = viewModel.recipientSnapshot ?: return
MessageDetailsFragment.create(conversationMessage.messageRecord, recipientSnapshot.id).show(childFragmentManager, null)
@@ -2801,6 +2815,10 @@ class ConversationFragment :
DoubleTapEditEducationSheet(conversationMessage).show(childFragmentManager, DoubleTapEditEducationSheet.KEY)
}
override fun onPaymentTombstoneClicked() {
this@ConversationFragment.showPaymentTombstoneLearnMoreDialog()
}
override fun onMessageWithErrorClicked(messageRecord: MessageRecord) {
val recipientId = viewModel.recipientSnapshot?.id ?: return
if (messageRecord.isIdentityMismatchFailure) {

View File

@@ -117,6 +117,7 @@ public interface MessageTypes {
long SPECIAL_TYPE_REPORTED_SPAM = 0x500000000L;
long SPECIAL_TYPE_MESSAGE_REQUEST_ACCEPTED = 0x600000000L;
long SPECIAL_TYPE_PAYMENTS_ACTIVATED = 0x800000000L;
long SPECIAL_TYPE_PAYMENTS_TOMBSTONE = 0x900000000L;
long IGNORABLE_TYPESMASK_WHEN_COUNTING = END_SESSION_BIT | KEY_EXCHANGE_IDENTITY_UPDATE_BIT | KEY_EXCHANGE_IDENTITY_VERIFIED_BIT;
@@ -132,6 +133,10 @@ public interface MessageTypes {
return (type & SPECIAL_TYPES_MASK) == SPECIAL_TYPE_PAYMENTS_NOTIFICATION;
}
static boolean isPaymentTombstone(long type) {
return (type & SPECIAL_TYPES_MASK) == SPECIAL_TYPE_PAYMENTS_TOMBSTONE;
}
static boolean isPaymentsRequestToActivate(long type) {
return (type & SPECIAL_TYPES_MASK) == SPECIAL_TYPE_PAYMENTS_ACTIVATE_REQUEST;
}

View File

@@ -182,6 +182,28 @@ public final class PaymentTable extends DatabaseTable implements RecipientIdData
}
}
@WorkerThread
public UUID restoreFromBackup(@NonNull RecipientId recipientId,
long timestamp,
long blockIndex,
@NonNull String note,
@NonNull Direction direction,
@NonNull State state,
@NonNull Money amount,
@NonNull Money fee,
@Nullable byte[] transaction,
@Nullable byte[] receipt,
@Nullable PaymentMetaData metaData,
boolean seen) {
UUID uuid = UUID.randomUUID();
try {
create(uuid, recipientId, null, timestamp, blockIndex, note, direction, state, amount, fee, transaction, receipt, metaData, seen);
} catch (SerializationException | PublicKeyConflictException e) {
return null;
}
return uuid;
}
@WorkerThread
private void create(@NonNull UUID uuid,
@Nullable RecipientId recipientId,
@@ -439,7 +461,7 @@ public final class PaymentTable extends DatabaseTable implements RecipientIdData
if (payment != null && record instanceof MmsMessageRecord) {
return ((MmsMessageRecord) record).withPayment(payment);
} else {
throw new AssertionError("Payment not found for message");
Log.w(TAG, "Payment not found for message");
}
}
return record;

View File

@@ -61,7 +61,7 @@ public final class ThreadBodyUtil {
return format(EmojiStrings.GIFT, getGiftSummary(context, record), null);
} else if (MessageRecordUtil.isStoryReaction(record)) {
return new ThreadBody(getStoryReactionSummary(context, record));
} else if (record.isPaymentNotification()) {
} else if (record.isPaymentNotification() || record.isPaymentTombstone()) {
return format(EmojiStrings.CARD, context.getString(R.string.ThreadRecord_payment), null);
} else if (record.isPaymentsRequestToActivate()) {
return format(EmojiStrings.CARD, getPaymentActivationRequestSummary(context, record), null);

View File

@@ -229,6 +229,10 @@ public abstract class DisplayRecord {
return MessageTypes.isPaymentsNotification(type);
}
public boolean isPaymentTombstone() {
return MessageTypes.isPaymentTombstone(type);
}
public boolean isPaymentsRequestToActivate() {
return MessageTypes.isPaymentsRequestToActivate(type);
}

View File

@@ -25,16 +25,20 @@ import org.thoughtcrime.securesms.database.MessageTypes;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
import org.thoughtcrime.securesms.database.model.databaseprotos.CryptoValue;
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge;
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.payments.CryptoValueUtil;
import org.thoughtcrime.securesms.payments.Payment;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.signalservice.api.payments.FormatterOptions;
import org.whispersystems.signalservice.api.payments.Money;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -219,6 +223,18 @@ public class MmsMessageRecord extends MessageRecord {
return emphasisAdded(context.getString(R.string.MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported));
} else if (isPaymentNotification() && payment != null) {
return new SpannableString(context.getString(R.string.MessageRecord__payment_s, payment.getAmount().toString(FormatterOptions.defaults())));
} else if (isPaymentTombstone() || isPaymentNotification()) {
MessageExtras extras = getMessageExtras();
Money amount = null;
if (extras != null && extras.paymentTombstone != null && extras.paymentTombstone.amount != null) {
amount = CryptoValueUtil.cryptoValueToMoney(extras.paymentTombstone.amount);
}
if (amount == null) {
return new SpannableString(context.getString(R.string.MessageRecord__payment_tombstone));
} else {
return new SpannableString(context.getString(R.string.MessageRecord__payment_s, amount.toString(FormatterOptions.defaults())));
}
}
return super.getDisplayBody(context);

View File

@@ -232,7 +232,7 @@ class MessageNotification(threadRecipient: Recipient, record: MessageRecord) : N
ThreadBodyUtil.getFormattedBodyForNotification(context, record, null)
} else if (record.isStoryReaction()) {
ThreadBodyUtil.getFormattedBodyForNotification(context, record, null)
} else if (record.isPaymentNotification) {
} else if (record.isPaymentNotification || record.isPaymentTombstone) {
ThreadBodyUtil.getFormattedBodyForNotification(context, record, null)
} else {
getBodyWithMentionsAndStyles(context, record)
@@ -342,7 +342,7 @@ class ReactionNotification(threadRecipient: Recipient, record: MessageRecord, va
context.getString(R.string.MessageNotifier_reacted_s_to_your_sticker, EMOJI_REPLACEMENT_STRING)
} else if (record.isMms && record.isViewOnce) {
context.getString(R.string.MessageNotifier_reacted_s_to_your_view_once_media, EMOJI_REPLACEMENT_STRING)
} else if (record.isPaymentNotification) {
} else if (record.isPaymentNotification || record.isPaymentTombstone) {
context.getString(R.string.MessageNotifier_reacted_s_to_your_payment, EMOJI_REPLACEMENT_STRING)
} else if (!bodyIsEmpty) {
context.getString(R.string.MessageNotifier_reacted_s_to_s, EMOJI_REPLACEMENT_STRING, body)

View File

@@ -85,6 +85,7 @@ object MessageConstraintsUtil {
!message.isRemoteDelete &&
!message.hasGiftBadge() &&
!message.isPaymentNotification &&
!message.isPaymentTombstone &&
(currentTime - message.dateSent < SEND_THRESHOLD || message.toRecipient.isSelf)
}

View File

@@ -139,7 +139,8 @@ fun MessageRecord.isTextOnly(context: Context): Boolean {
!hasSticker() &&
!isCaptionlessMms(context) &&
!hasGiftBadge() &&
!isPaymentNotification()
!isPaymentNotification &&
!isPaymentTombstone
)
}