mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 00:59:49 +01:00
Add import and tombstones for mobile coin payments.
This commit is contained in:
@@ -132,5 +132,6 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
|
||||
void onReportSpamLearnMoreClicked();
|
||||
void onMessageRequestAcceptOptionsClicked();
|
||||
void onItemDoubleClick(MultiselectPart multiselectPart);
|
||||
void onPaymentTombstoneClicked();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -135,7 +135,7 @@ public final class MenuState {
|
||||
hasGift = true;
|
||||
}
|
||||
|
||||
if (messageRecord.isPaymentNotification()) {
|
||||
if (messageRecord.isPaymentNotification() || messageRecord.isPaymentTombstone()) {
|
||||
hasPayment = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -85,6 +85,7 @@ object MessageConstraintsUtil {
|
||||
!message.isRemoteDelete &&
|
||||
!message.hasGiftBadge() &&
|
||||
!message.isPaymentNotification &&
|
||||
!message.isPaymentTombstone &&
|
||||
(currentTime - message.dateSent < SEND_THRESHOLD || message.toRecipient.isSelf)
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,8 @@ fun MessageRecord.isTextOnly(context: Context): Boolean {
|
||||
!hasSticker() &&
|
||||
!isCaptionlessMms(context) &&
|
||||
!hasGiftBadge() &&
|
||||
!isPaymentNotification()
|
||||
!isPaymentNotification &&
|
||||
!isPaymentTombstone
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user