From 37192491b38b4534eda81e64d97e60860b53d156 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 24 Jul 2026 15:31:58 -0400 Subject: [PATCH] feat(chat): distinguish tipped cash from sent cash in messaging Fetch the updated flipcash messaging proto, which adds CashContent.Action (SENT/TIPPED), and carry the new action field through the messaging stack: - Domain: add MessageContent.Cash.Action (defaults to SENT) - Proto mappers: map action in/out with a SENT fallback - Persistence: round-trip action through the serialized DTO + entity mapper - Chat UI: show tip-specific copy ("You tipped" / "Someone tipped you") in the cash bubble and the conversation-list preview when action is TIPPED --- .../core/src/main/res/values/strings.xml | 2 ++ .../shared/chat/ui/ChatSummaryMapping.kt | 10 +++--- .../flipcash/shared/chat/ui/MessageBubble.kt | 33 ++++++++++++++++--- .../converters/ChatTypeConverters.kt | 1 + .../sources/mapper/chat/ChatEntityMapper.kt | 3 ++ .../src/main/proto/messaging/v1/model.proto | 8 +++++ .../network/extensions/LocalToProtobuf.kt | 6 ++++ .../network/extensions/ProtobufToLocal.kt | 4 +++ .../services/models/chat/MessageContent.kt | 8 ++++- 9 files changed, 65 insertions(+), 10 deletions(-) diff --git a/apps/flipcash/core/src/main/res/values/strings.xml b/apps/flipcash/core/src/main/res/values/strings.xml index 757aa4483..77ac9e66c 100644 --- a/apps/flipcash/core/src/main/res/values/strings.xml +++ b/apps/flipcash/core/src/main/res/values/strings.xml @@ -848,6 +848,8 @@ %1$s of %2$s You sent %1$s You received %1$s + You tipped %1$s + Someone tipped you %1$s You: %1$s Is Typing… diff --git a/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/ui/ChatSummaryMapping.kt b/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/ui/ChatSummaryMapping.kt index 1eafa76ee..57d2a0289 100644 --- a/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/ui/ChatSummaryMapping.kt +++ b/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/ui/ChatSummaryMapping.kt @@ -59,11 +59,13 @@ private fun ChatSummary.formatPreview( } else { formatted } - if (sentBySelf) { - resources.getString(R.string.label_chat_preview_sentCash, label) - } else { - resources.getString(R.string.label_chat_preview_receivedCash, label) + val previewRes = when (content.action) { + MessageContent.Cash.Action.TIPPED -> + if (sentBySelf) R.string.label_chat_preview_tippedCash else R.string.label_chat_preview_receivedTip + MessageContent.Cash.Action.SENT -> + if (sentBySelf) R.string.label_chat_preview_sentCash else R.string.label_chat_preview_receivedCash } + resources.getString(previewRes, label) } // TODO: diff --git a/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/ui/MessageBubble.kt b/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/ui/MessageBubble.kt index 864c08037..c1cdb5782 100644 --- a/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/ui/MessageBubble.kt +++ b/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/ui/MessageBubble.kt @@ -98,6 +98,7 @@ fun ContentBubble( tokenName = content.tokenName, tokenImageUrl = content.tokenImageUrl, isFromSelf = item.isFromSelf, + action = content.action, position = position, maxWidth = bubbleMaxWidth, onClick = { @@ -152,6 +153,7 @@ private fun CashBubble( isFromSelf: Boolean, position: BubblePosition, maxWidth: Dp, + action: MessageContent.Cash.Action = MessageContent.Cash.Action.SENT, onClick: () -> Unit = { }, modifier: Modifier = Modifier, ) { @@ -208,12 +210,14 @@ private fun CashBubble( .padding(top = CodeTheme.dimens.grid.x5, bottom = CodeTheme.dimens.grid.x8), horizontalAlignment = Alignment.CenterHorizontally, ) { + val subtitleRes = when (action) { + MessageContent.Cash.Action.TIPPED -> + if (isFromSelf) R.string.subtitle_youTipped else R.string.subtitle_someoneTippedYou + MessageContent.Cash.Action.SENT -> + if (isFromSelf) R.string.subtitle_youSent else R.string.subtitle_youReceived + } Text( - text = if (isFromSelf) { - stringResource(R.string.subtitle_youSent) - } else { - stringResource(R.string.subtitle_youReceived) - }, + text = stringResource(subtitleRes), style = CodeTheme.typography.caption.copy( fontWeight = FontWeight.Medium, ), @@ -483,6 +487,25 @@ private fun Preview_CashBubble_NoTokenName() { } } +@Preview +@PreviewWrapper(FlipcashThemeWrapper::class) +@Composable +private fun Preview_CashBubble_Tipped() { + CompositionLocalProvider( + LocalExchange provides ExchangeStub(context = LocalContext.current) + ) { + CashBubble( + amount = Fiat(fiat = 5.0), + tokenName = "USDF", + tokenImageUrl = "", + isFromSelf = false, + position = BubblePosition.Solo, + maxWidth = 300.dp, + action = MessageContent.Cash.Action.TIPPED, + ) + } +} + @Preview @PreviewWrapper(FlipcashThemeWrapper::class) @Composable diff --git a/apps/flipcash/shared/persistence/db/src/main/kotlin/com/flipcash/app/persistence/converters/ChatTypeConverters.kt b/apps/flipcash/shared/persistence/db/src/main/kotlin/com/flipcash/app/persistence/converters/ChatTypeConverters.kt index 335d783ca..d48f1f82c 100644 --- a/apps/flipcash/shared/persistence/db/src/main/kotlin/com/flipcash/app/persistence/converters/ChatTypeConverters.kt +++ b/apps/flipcash/shared/persistence/db/src/main/kotlin/com/flipcash/app/persistence/converters/ChatTypeConverters.kt @@ -120,6 +120,7 @@ sealed interface MessageContentSerialized { val mint: String = "", val tokenName: String = "", val tokenImageUrl: String = "", + val action: String = "SENT", ) : MessageContentSerialized @Serializable diff --git a/apps/flipcash/shared/persistence/sources/src/main/kotlin/com/flipcash/app/persistence/sources/mapper/chat/ChatEntityMapper.kt b/apps/flipcash/shared/persistence/sources/src/main/kotlin/com/flipcash/app/persistence/sources/mapper/chat/ChatEntityMapper.kt index 711a720d8..6bb0f3a01 100644 --- a/apps/flipcash/shared/persistence/sources/src/main/kotlin/com/flipcash/app/persistence/sources/mapper/chat/ChatEntityMapper.kt +++ b/apps/flipcash/shared/persistence/sources/src/main/kotlin/com/flipcash/app/persistence/sources/mapper/chat/ChatEntityMapper.kt @@ -215,6 +215,7 @@ private fun MessageContent.toSerialized(): MessageContentSerialized = when (this mint = mint.base58(), tokenName = tokenName, tokenImageUrl = tokenImageUrl, + action = action.name, ) is MessageContent.Reply -> MessageContentSerialized.Reply( repliedMessageId = repliedMessageId, @@ -242,6 +243,8 @@ private fun MessageContentSerialized.toDomain(): MessageContent = when (this) { mint = Mint(mint.decodeBase58().toList()), tokenName = tokenName, tokenImageUrl = tokenImageUrl, + action = MessageContent.Cash.Action.entries.firstOrNull { it.name == action } + ?: MessageContent.Cash.Action.SENT, ) is MessageContentSerialized.Reply -> MessageContent.Reply( repliedMessageId = repliedMessageId, diff --git a/definitions/flipcash/protos/src/main/proto/messaging/v1/model.proto b/definitions/flipcash/protos/src/main/proto/messaging/v1/model.proto index 1819e06db..33f8e330a 100644 --- a/definitions/flipcash/protos/src/main/proto/messaging/v1/model.proto +++ b/definitions/flipcash/protos/src/main/proto/messaging/v1/model.proto @@ -125,6 +125,14 @@ message CashContent { // Reserved for receiver, which will is required for group chats reserved 3; + + // Action for how the cash was sent. Clietns should always show SENT as a + // fallback. + enum Action { + SENT = 0; + TIPPED = 1; + } + Action action = 4; } // Reply content diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/extensions/LocalToProtobuf.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/extensions/LocalToProtobuf.kt index 740d557d1..e736c53e4 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/extensions/LocalToProtobuf.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/extensions/LocalToProtobuf.kt @@ -121,6 +121,12 @@ internal fun MessageContent.asContent(): MessagingModel.Content { .setQuarks(amount.quarks) .setMint(Common.PublicKey.newBuilder().setValue(mint.bytes.toByteString())) ) + .setAction( + when (action) { + MessageContent.Cash.Action.TIPPED -> MessagingModel.CashContent.Action.TIPPED + MessageContent.Cash.Action.SENT -> MessagingModel.CashContent.Action.SENT + } + ) ) .build() is MessageContent.Reply -> MessagingModel.Content.newBuilder() diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/extensions/ProtobufToLocal.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/extensions/ProtobufToLocal.kt index e68652727..75b27cbbf 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/extensions/ProtobufToLocal.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/extensions/ProtobufToLocal.kt @@ -154,6 +154,10 @@ internal fun MessagingModel.Content.toMessageContent(): MessageContent { currencyCode = CurrencyCode.tryValueOf(cash.amount.currency) ?: CurrencyCode.USD, ), mint = cash.amount.mint.value.toByteArray().toMint(), + action = when (cash.action) { + MessagingModel.CashContent.Action.TIPPED -> MessageContent.Cash.Action.TIPPED + else -> MessageContent.Cash.Action.SENT + }, ) MessagingModel.Content.TypeCase.REPLY -> MessageContent.Reply( repliedMessageId = reply.repliedMessageId.value, diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/models/chat/MessageContent.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/models/chat/MessageContent.kt index 96cf5f8c7..5cc174789 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/models/chat/MessageContent.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/models/chat/MessageContent.kt @@ -13,7 +13,13 @@ sealed interface MessageContent { val mint: Mint, val tokenName: String = "", val tokenImageUrl: String = "", - ) : MessageContent + val action: Action = Action.SENT, + ) : MessageContent { + enum class Action { + SENT, + TIPPED, + } + } data class Reply( val repliedMessageId: Long, val content: List,