Add in-chat payment messages.

This commit is contained in:
Cody Henthorne
2022-11-10 14:45:45 -05:00
committed by Greyson Parrelli
parent 28193c2f61
commit 1dc29fda12
43 changed files with 708 additions and 98 deletions

View File

@@ -7,12 +7,14 @@ import androidx.annotation.Nullable;
import com.annimon.stream.Stream;
import org.signal.core.util.Stopwatch;
import org.signal.core.util.logging.Log;
import org.signal.paging.PagedDataSource;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.conversation.ConversationData.MessageRequestData;
import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationMessageFactory;
import org.thoughtcrime.securesms.database.MessageDatabase;
import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord;
@@ -24,11 +26,12 @@ import org.thoughtcrime.securesms.database.model.ReactionRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.database.model.UpdateDescription;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.payments.Payment;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.signal.core.util.Stopwatch;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.util.ArrayList;
import java.util.Collection;
@@ -39,6 +42,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/**
@@ -96,6 +100,7 @@ public class ConversationDataSource implements PagedDataSource<MessageId, Conver
MentionHelper mentionHelper = new MentionHelper();
AttachmentHelper attachmentHelper = new AttachmentHelper();
ReactionHelper reactionHelper = new ReactionHelper();
PaymentHelper paymentHelper = new PaymentHelper();
Set<ServiceId> referencedIds = new HashSet<>();
try (MmsSmsDatabase.Reader reader = MmsSmsDatabase.readerFor(db.getConversation(threadId, start, length))) {
@@ -105,6 +110,7 @@ public class ConversationDataSource implements PagedDataSource<MessageId, Conver
mentionHelper.add(record);
reactionHelper.add(record);
attachmentHelper.add(record);
paymentHelper.add(record);
UpdateDescription description = record.getUpdateDisplayBody(context, null);
if (description != null) {
@@ -138,6 +144,12 @@ public class ConversationDataSource implements PagedDataSource<MessageId, Conver
records = attachmentHelper.buildUpdatedModels(context, records);
stopwatch.split("attachment-models");
paymentHelper.fetchPayments();
stopwatch.split("payments");
records = paymentHelper.buildUpdatedModels(records);
stopwatch.split("payment-models");
for (ServiceId serviceId : referencedIds) {
Recipient.resolved(RecipientId.from(serviceId));
}
@@ -192,6 +204,12 @@ public class ConversationDataSource implements PagedDataSource<MessageId, Conver
stopwatch.split("attachments");
if (record.isPaymentNotification()) {
record = SignalDatabase.payments().updateMessageWithPayment(record);
}
stopwatch.split("payments");
return ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(ApplicationDependencies.getApplication(), record, mentions);
} else {
return null;
@@ -303,4 +321,40 @@ public class ConversationDataSource implements PagedDataSource<MessageId, Conver
}
}
private static class PaymentHelper {
private final Map<UUID, Long> paymentMessages = new HashMap<>();
private final Map<Long, Payment> messageIdToPayment = new HashMap<>();
public void add(MessageRecord messageRecord) {
if (messageRecord.isMms() && messageRecord.isPaymentNotification()) {
UUID paymentUuid = UuidUtil.parseOrNull(messageRecord.getBody());
if (paymentUuid != null) {
paymentMessages.put(paymentUuid, messageRecord.getId());
}
}
}
public void fetchPayments() {
List<Payment> payments = SignalDatabase.payments().getPayments(paymentMessages.keySet());
for (Payment payment : payments) {
if (payment != null) {
messageIdToPayment.put(paymentMessages.get(payment.getUuid()), payment);
}
}
}
@NonNull List<MessageRecord> buildUpdatedModels(@NonNull List<MessageRecord> records) {
return records.stream()
.map(record -> {
if (record instanceof MediaMmsMessageRecord) {
Payment payment = messageIdToPayment.get(record.getId());
if (payment != null) {
return ((MediaMmsMessageRecord) record).withPayment(payment);
}
}
return record;
})
.collect(Collectors.toList());
}
}
}

View File

@@ -1168,6 +1168,15 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
});
}
private void handleViewPaymentDetails(MessageRecord message) {
if (message instanceof MediaMmsMessageRecord) {
MediaMmsMessageRecord mediaMessage = (MediaMmsMessageRecord) message;
if (mediaMessage.isPaymentNotification() && mediaMessage.getPayment() != null) {
startActivity(PaymentsActivity.navigateToPaymentDetails(requireContext(), mediaMessage.getPayment().getUuid()));
}
}
}
private void performSave(final MediaMmsMessageRecord message) {
List<SaveAttachmentTask.Attachment> attachments = Stream.of(message.getSlideDeck().getSlides())
.filter(s -> s.getUri() != null && (s.hasImage() || s.hasVideo() || s.hasAudio() || s.hasDocument()))
@@ -2287,6 +2296,9 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
case COPY:
handleCopyMessage(conversationMessage.getMultiselectCollection().toSet());
break;
case PAYMENT_DETAILS:
handleViewPaymentDetails(conversationMessage.getMessageRecord());
break;
case MULTISELECT:
handleEnterMultiSelect(conversationMessage);
break;

View File

@@ -95,6 +95,7 @@ import org.thoughtcrime.securesms.conversation.colors.ChatColors;
import org.thoughtcrime.securesms.conversation.colors.Colorizer;
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectCollection;
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart;
import org.thoughtcrime.securesms.conversation.ui.payment.PaymentMessageView;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.database.MessageDatabase;
@@ -181,14 +182,14 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
private static final long MAX_CLUSTERING_TIME_DIFF = TimeUnit.MINUTES.toMillis(3);
private static final int CONDENSED_MODE_MAX_LINES = 3;
private ConversationMessage conversationMessage;
private MessageRecord messageRecord;
private Optional<MessageRecord> nextMessageRecord;
private Locale locale;
private boolean groupThread;
private LiveRecipient recipient;
private GlideRequests glideRequests;
private ValueAnimator pulseOutlinerAlphaAnimator;
private ConversationMessage conversationMessage;
private MessageRecord messageRecord;
private Optional<MessageRecord> nextMessageRecord;
private Locale locale;
private boolean groupThread;
private LiveRecipient recipient;
private GlideRequests glideRequests;
private ValueAnimator pulseOutlinerAlphaAnimator;
private Optional<MessageRecord> previousMessage;
private ConversationItemDisplayMode displayMode;
@@ -224,6 +225,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
private Stub<ViewOnceMessageView> revealableStub;
private Stub<Button> callToActionStub;
private Stub<GiftMessageView> giftViewStub;
private Stub<PaymentMessageView> paymentViewStub;
private @Nullable EventListener eventListener;
private int defaultBubbleColor;
@@ -325,6 +327,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
this.storyReactionLabel = findViewById(R.id.story_reacted_label);
this.giftViewStub = new Stub<>(findViewById(R.id.gift_view_stub));
this.quotedIndicator = findViewById(R.id.quoted_indicator);
this.paymentViewStub = new Stub<>(findViewById(R.id.payment_view_stub));
setOnClickListener(new ClickListener(null));
@@ -1000,7 +1003,9 @@ 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)) {
} else if (isCaptionlessMms(messageRecord) || isStoryReaction(messageRecord) || isGiftMessage(messageRecord) || messageRecord.isPaymentNotification()) {
bodyText.setText(null);
bodyText.setOverflowText(null);
bodyText.setVisibility(View.GONE);
} else {
Spannable styledText = conversationMessage.getDisplayBody(getContext());
@@ -1076,6 +1081,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
if (giftViewStub.resolved()) giftViewStub.get().setVisibility(View.GONE);
paymentViewStub.setVisibility(View.GONE);
revealableStub.get().setMessage((MmsMessageRecord) messageRecord, hasWallpaper);
revealableStub.get().setOnClickListener(revealableClickListener);
@@ -1093,6 +1099,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
if (giftViewStub.resolved()) giftViewStub.get().setVisibility(View.GONE);
paymentViewStub.setVisibility(View.GONE);
sharedContactStub.get().setContact(((MediaMmsMessageRecord) messageRecord).getSharedContacts().get(0), glideRequests, locale);
sharedContactStub.get().setEventListener(sharedContactEventListener);
@@ -1113,6 +1120,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
if (giftViewStub.resolved()) giftViewStub.get().setVisibility(View.GONE);
paymentViewStub.setVisibility(View.GONE);
//noinspection ConstantConditions
LinkPreview linkPreview = ((MmsMessageRecord) messageRecord).getLinkPreviews().get(0);
@@ -1160,6 +1168,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
if (giftViewStub.resolved()) giftViewStub.get().setVisibility(View.GONE);
paymentViewStub.setVisibility(View.GONE);
audioViewStub.get().setAudio(Objects.requireNonNull(((MediaMmsMessageRecord) messageRecord).getSlideDeck().getAudioSlide()), new AudioViewCallbacks(), showControls, true);
audioViewStub.get().setDownloadClickListener(singleDownloadClickListener);
@@ -1186,6 +1195,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
if (giftViewStub.resolved()) giftViewStub.get().setVisibility(View.GONE);
paymentViewStub.setVisibility(View.GONE);
//noinspection ConstantConditions
documentViewStub.get().setDocument(
@@ -1213,6 +1223,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
if (giftViewStub.resolved()) giftViewStub.get().setVisibility(View.GONE);
paymentViewStub.setVisibility(View.GONE);
if (hasSticker(messageRecord)) {
//noinspection ConstantConditions
@@ -1243,6 +1254,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
if (giftViewStub.resolved()) giftViewStub.get().setVisibility(View.GONE);
paymentViewStub.setVisibility(View.GONE);
List<Slide> thumbnailSlides = ((MmsMessageRecord) messageRecord).getSlideDeck().getThumbnailSlides();
mediaThumbnailStub.require().setMinimumThumbnailWidth(readDimen(isCaptionlessMms(messageRecord) ? R.dimen.media_bubble_min_width_solo
@@ -1296,11 +1308,28 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(GONE);
if (revealableStub.resolved()) revealableStub.get().setVisibility(GONE);
paymentViewStub.setVisibility(View.GONE);
MmsMessageRecord mmsMessageRecord = (MmsMessageRecord) messageRecord;
giftViewStub.get().setGiftBadge(glideRequests, Objects.requireNonNull(mmsMessageRecord.getGiftBadge()), messageRecord.isOutgoing(), giftMessageViewCallback);
giftViewStub.get().setVisibility(VISIBLE);
footer.setVisibility(VISIBLE);
} else if (messageRecord.isPaymentNotification()) {
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);
MediaMmsMessageRecord mediaMmsMessageRecord = (MediaMmsMessageRecord) messageRecord;
paymentViewStub.setVisibility(View.VISIBLE);
paymentViewStub.get().bindPayment(messageRecord.getIndividualRecipient(), Objects.requireNonNull(mediaMmsMessageRecord.getPayment()), colorizer);
footer.setVisibility(VISIBLE);
} else {
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.require().setVisibility(View.GONE);
@@ -1311,6 +1340,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE);
if (giftViewStub.resolved()) giftViewStub.get().setVisibility(View.GONE);
paymentViewStub.setVisibility(View.GONE);
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ViewUtil.updateLayoutParamsIfNonNull(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);

View File

@@ -761,6 +761,10 @@ public final class ConversationReactionOverlay extends FrameLayout {
items.add(new ActionItem(R.drawable.ic_copy_24_tinted, getResources().getString(R.string.conversation_selection__menu_copy), () -> handleActionItemClicked(Action.COPY)));
}
if (menuState.shouldShowPaymentDetails()) {
items.add(new ActionItem(R.drawable.ic_payments_24, getResources().getString(R.string.conversation_selection__menu_payment_details), () -> handleActionItemClicked(Action.PAYMENT_DETAILS)));
}
items.add(new ActionItem(R.drawable.ic_select_24_tinted, getResources().getString(R.string.conversation_selection__menu_multi_select), () -> handleActionItemClicked(Action.MULTISELECT)));
if (menuState.shouldShowDetailsAction()) {
@@ -976,6 +980,7 @@ public final class ConversationReactionOverlay extends FrameLayout {
DOWNLOAD,
COPY,
MULTISELECT,
PAYMENT_DETAILS,
VIEW_INFO,
DELETE,
}

View File

@@ -544,7 +544,7 @@ public final class ConversationUpdateItem extends FrameLayout
});
actionButton.setText(R.string.ConversationActivity__invite_to_signal);
} else if (conversationMessage.getMessageRecord().isRequestToActivatePayments() && !conversationMessage.getMessageRecord().isOutgoing() && !SignalStore.paymentsValues().mobileCoinPaymentsEnabled()) {
} else if (conversationMessage.getMessageRecord().isPaymentsRequestToActivate() && !conversationMessage.getMessageRecord().isOutgoing() && !SignalStore.paymentsValues().mobileCoinPaymentsEnabled()) {
actionButton.setText(R.string.ConversationUpdateItem_activate_payments);
actionButton.setVisibility(VISIBLE);
actionButton.setOnClickListener(v -> {

View File

@@ -24,6 +24,7 @@ final class MenuState {
private final boolean copy;
private final boolean delete;
private final boolean reactions;
private final boolean paymentDetails;
private MenuState(@NonNull Builder builder) {
forward = builder.forward;
@@ -34,6 +35,7 @@ final class MenuState {
copy = builder.copy;
delete = builder.delete;
reactions = builder.reactions;
paymentDetails = builder.paymentDetails;
}
boolean shouldShowForwardAction() {
@@ -68,6 +70,10 @@ final class MenuState {
return reactions;
}
boolean shouldShowPaymentDetails() {
return paymentDetails;
}
static MenuState getMenuState(@NonNull Recipient conversationRecipient,
@NonNull Set<MultiselectPart> selectedParts,
boolean shouldShowMessageRequest,
@@ -84,6 +90,7 @@ final class MenuState {
boolean hasPendingMedia = false;
boolean mediaIsSelected = false;
boolean hasGift = false;
boolean hasPayment = false;
for (MultiselectPart part : selectedParts) {
MessageRecord messageRecord = part.getMessageRecord();
@@ -121,6 +128,10 @@ final class MenuState {
if (MessageRecordUtil.hasGiftBadge(messageRecord)) {
hasGift = true;
}
if (messageRecord.isPaymentNotification()) {
hasPayment = true;
}
}
boolean shouldShowForwardAction = !actionMessage &&
@@ -129,6 +140,7 @@ final class MenuState {
!remoteDelete &&
!hasPendingMedia &&
!hasGift &&
!hasPayment &&
selectedParts.size() <= MAX_FORWARDABLE_COUNT;
int uniqueRecords = selectedParts.stream()
@@ -160,9 +172,10 @@ final class MenuState {
.shouldShowReplyAction(canReplyToMessage(conversationRecipient, actionMessage, messageRecord, shouldShowMessageRequest, isNonAdminInAnnouncementGroup));
}
return builder.shouldShowCopyAction(!actionMessage && !remoteDelete && hasText && !hasGift)
return builder.shouldShowCopyAction(!actionMessage && !remoteDelete && hasText && !hasGift && !hasPayment)
.shouldShowDeleteAction(!hasInMemory && onlyContainsCompleteMessages(selectedParts))
.shouldShowReactions(!conversationRecipient.isReleaseNotes())
.shouldShowPaymentDetails(hasPayment)
.build();
}
@@ -206,7 +219,7 @@ final class MenuState {
messageRecord.isInMemoryMessageRecord() ||
messageRecord.isChangeNumber() ||
messageRecord.isBoostRequest() ||
messageRecord.isRequestToActivatePayments() ||
messageRecord.isPaymentsRequestToActivate() ||
messageRecord.isPaymentsActivated();
}
@@ -220,6 +233,7 @@ final class MenuState {
private boolean copy;
private boolean delete;
private boolean reactions;
private boolean paymentDetails;
@NonNull Builder shouldShowForwardAction(boolean forward) {
this.forward = forward;
@@ -261,6 +275,11 @@ final class MenuState {
return this;
}
@NonNull Builder shouldShowPaymentDetails(boolean paymentDetails) {
this.paymentDetails = paymentDetails;
return this;
}
@NonNull
MenuState build() {
return new MenuState(this);

View File

@@ -46,7 +46,7 @@ class MessageQuotesRepository {
@WorkerThread
private fun getMessageInQuoteChainSync(application: Application, messageId: MessageId): List<ConversationMessage> {
val originalRecord: MessageRecord? = if (messageId.mms) {
var originalRecord: MessageRecord? = if (messageId.mms) {
SignalDatabase.mms.getMessageRecordOrNull(messageId.id)
} else {
SignalDatabase.sms.getMessageRecordOrNull(messageId.id)
@@ -66,7 +66,7 @@ class MessageQuotesRepository {
.buildUpdatedModels(replyRecords)
.map { replyRecord ->
val replyQuote: Quote? = replyRecord.getQuote()
if (replyQuote != null && replyQuote.id == originalRecord.dateSent) {
if (replyQuote != null && replyQuote.id == originalRecord!!.dateSent) {
(replyRecord as MediaMmsMessageRecord).withoutQuote()
} else {
replyRecord
@@ -74,6 +74,10 @@ class MessageQuotesRepository {
}
.map { ConversationMessageFactory.createWithUnresolvedData(application, it) }
if (originalRecord.isPaymentNotification) {
originalRecord = SignalDatabase.payments.updateMessageWithPayment(originalRecord)
}
val originalMessage: List<ConversationMessage> = ConversationDataSource.ReactionHelper()
.apply {
add(originalRecord)

View File

@@ -0,0 +1,63 @@
package org.thoughtcrime.securesms.conversation.ui.payment
import android.content.Context
import android.content.res.ColorStateList
import android.text.style.TypefaceSpan
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.core.view.ViewCompat
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.quotes.QuoteViewColorTheme
import org.thoughtcrime.securesms.conversation.colors.Colorizer
import org.thoughtcrime.securesms.databinding.PaymentMessageViewBinding
import org.thoughtcrime.securesms.payments.Direction
import org.thoughtcrime.securesms.payments.Payment
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.visible
/**
* Showing payment information in conversation.
*/
class PaymentMessageView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
private val binding: PaymentMessageViewBinding
init {
binding = PaymentMessageViewBinding.inflate(LayoutInflater.from(context), this, true)
}
fun bindPayment(recipient: Recipient, payment: Payment, colorizer: Colorizer) {
val outgoing = payment.direction == Direction.SENT
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()))
}
}
binding.paymentNote.apply {
text = payment.note
visible = payment.note.isNotEmpty()
setTextColor(if (outgoing) colorizer.getOutgoingBodyTextColor(context) else colorizer.getIncomingBodyTextColor(context, recipient.hasWallpaper()))
}
val quoteViewColorTheme = QuoteViewColorTheme.resolveTheme(outgoing, false, recipient.hasWallpaper())
binding.paymentAmount.setTextColor(quoteViewColorTheme.getForegroundColor(context))
binding.paymentAmount.setMoney(payment.amount, 0L, currencyTypefaceSpan)
ViewCompat.setBackgroundTintList(binding.paymentAmountLayout, ColorStateList.valueOf(quoteViewColorTheme.getBackgroundColor(context)))
}
companion object {
private val currencyTypefaceSpan = TypefaceSpan("sans-serif-light")
}
}