From 5bf014ce8021743ab47cb331ceda933ffd0bc2a2 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Wed, 12 Aug 2026 12:19:38 -0400 Subject: [PATCH] refactor(activity): simplify transaction row titles Walk back the client-side title embellishments for buy/sell/deposit rows: Purchased, Sold and Added now render the server text verbatim instead of appending the token name or overriding USDF buys to "Added Money". Received tips read "Tip from "; the label lives in strings.xml (title_activity_tipFrom) and is resolved through an injected ResourceHelper rather than a hardcoded literal. --- .../transaction-history/build.gradle.kts | 1 + .../internal/TransactionItemMapper.kt | 32 ++++++++----------- .../src/main/res/values/strings.xml | 5 +++ .../TransactionItemMapperTest.kt | 32 ++++++++----------- 4 files changed, 33 insertions(+), 37 deletions(-) create mode 100644 apps/flipcash/shared/transaction-history/src/main/res/values/strings.xml diff --git a/apps/flipcash/shared/transaction-history/build.gradle.kts b/apps/flipcash/shared/transaction-history/build.gradle.kts index 5380887db2..519e6aa0ce 100644 --- a/apps/flipcash/shared/transaction-history/build.gradle.kts +++ b/apps/flipcash/shared/transaction-history/build.gradle.kts @@ -21,4 +21,5 @@ dependencies { implementation(project(":libs:datetime")) testImplementation(libs.bundles.unit.testing) + testImplementation(testFixtures(project(":ui:resources"))) } diff --git a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/internal/TransactionItemMapper.kt b/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/internal/TransactionItemMapper.kt index 0e91941194..ccf3685c62 100644 --- a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/internal/TransactionItemMapper.kt +++ b/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/internal/TransactionItemMapper.kt @@ -4,12 +4,12 @@ import com.flipcash.app.core.feed.ActivityFeedMessageWithToken import com.flipcash.app.core.feed.MessageMetadata import com.flipcash.app.core.feed.MessageSubstitution import com.flipcash.services.models.UserProfile +import com.flipcash.shared.transactionhistory.R import com.flipcash.shared.transactionhistory.TransactionAvatar import com.flipcash.shared.transactionhistory.TransactionListItem import com.getcode.opencode.mapper.Mapper import com.getcode.opencode.model.core.ID -import com.getcode.opencode.model.financial.Token -import com.getcode.solana.keys.Mint +import com.getcode.util.resources.ResourceHelper import com.getcode.utils.hexEncodedString import javax.inject.Inject @@ -19,7 +19,9 @@ import javax.inject.Inject * inside the paging transform; profiles arrive reactively as the cache is observed, so a * not-yet-cached counterparty simply resolves later when its profile lands. */ -internal class TransactionItemMapper @Inject constructor(): Mapper>, TransactionListItem> { +internal class TransactionItemMapper @Inject constructor( + private val resources: ResourceHelper, +): Mapper>, TransactionListItem> { override fun map(from: Pair>): TransactionListItem { val (source, profiles) = from val msg = source.message @@ -45,7 +47,7 @@ internal class TransactionItemMapper @Inject constructor(): Mapper"; + * Otherwise the server sends a bare verb (e.g. "Tipped", "Received") and the client completes it + * with the relevant subject, resolved reactively (the bare verb shows until it lands): + * - received tips read "Tip from "; * - tips/sends and anything else with a counterparty append the **counterparty** name — "Tipped Sally". + * + * Buys/sells/deposits carry no counterparty, so they render the server text verbatim + * ("Purchased", "Sold", "Added"). */ private fun resolveTitle( + resources: ResourceHelper, meta: MessageMetadata?, text: String, substitutions: List, counterparty: UserProfile?, - token: Token?, profiles: Map, ): String { if (substitutions.isNotEmpty()) { @@ -91,17 +95,9 @@ private fun resolveTitle( } val counterpartyName = counterparty?.displayName?.takeIf { it.isNotBlank() } - val tokenName = token?.name?.takeIf { it.isNotBlank() } ?: token?.symbol?.takeIf { it.isNotBlank() } return when (meta) { is MessageMetadata.ReceivedCrypto -> - if (counterpartyName != null) "$text Tip From $counterpartyName" else text - MessageMetadata.BoughtToken -> - // Buying dollars (USDF — the only buyable stablecoin) is just adding money — read it as - // such, not "Purchased Dollars". - if (token?.address == Mint.usdf) "Added Money" - else if (tokenName != null) "$text $tokenName" else text - MessageMetadata.SoldToken -> - if (tokenName != null) "$text $tokenName" else text + if (counterpartyName != null) resources.getString(R.string.title_activity_tipFrom, counterpartyName) else text else -> if (counterpartyName != null) "$text $counterpartyName" else text } diff --git a/apps/flipcash/shared/transaction-history/src/main/res/values/strings.xml b/apps/flipcash/shared/transaction-history/src/main/res/values/strings.xml new file mode 100644 index 0000000000..adc370f5d7 --- /dev/null +++ b/apps/flipcash/shared/transaction-history/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + + Tip from %1$s + diff --git a/apps/flipcash/shared/transaction-history/src/test/kotlin/com/flipcash/shared/transactionhistory/TransactionItemMapperTest.kt b/apps/flipcash/shared/transaction-history/src/test/kotlin/com/flipcash/shared/transactionhistory/TransactionItemMapperTest.kt index 6626e85b81..ad4ec34384 100644 --- a/apps/flipcash/shared/transaction-history/src/test/kotlin/com/flipcash/shared/transactionhistory/TransactionItemMapperTest.kt +++ b/apps/flipcash/shared/transaction-history/src/test/kotlin/com/flipcash/shared/transactionhistory/TransactionItemMapperTest.kt @@ -8,6 +8,7 @@ import com.flipcash.app.core.feed.MessageSubstitution import com.flipcash.services.models.UserProfile import com.flipcash.shared.transactionhistory.internal.TransactionItemMapper import com.getcode.opencode.model.core.ID +import com.getcode.util.resources.FakeResourceHelper import com.getcode.opencode.model.financial.CurrencyCode import com.getcode.opencode.model.financial.Fiat import com.getcode.opencode.model.financial.HolderMetrics @@ -24,7 +25,9 @@ import org.junit.Test class TransactionItemMapperTest { - private val mapper = TransactionItemMapper() + private val resources = FakeResourceHelper() + .stub(R.string.title_activity_tipFrom, "Tip from %1\$s") + private val mapper = TransactionItemMapper(resources) private val knownUserId: ID = listOf(0x0A, 0x0B, 0x0C) private val knownProfile = UserProfile.Empty.copy(displayName = "Sally The Streamer") @@ -112,50 +115,41 @@ class TransactionItemMapperTest { } @Test - fun `received tip reads Received Tip From the counterparty`() { + fun `received tip reads Tip from the counterparty`() { val msg = feedMessage(metadata = MessageMetadata.ReceivedCrypto(userId = knownUserId)) .copy(text = "Received", textSubstitutions = emptyList()) val item = mapper.map(ActivityFeedMessageWithToken(msg, token = null) to cached) - assertEquals("Received Tip From Sally The Streamer", item.title) + assertEquals("Tip from Sally The Streamer", item.title) } @Test - fun `bought token appends the token name`() { + fun `bought token renders the server text verbatim`() { val token = token(address = Mint.usdc, name = "Dad Cash", symbol = "DADCASH") val msg = feedMessage(metadata = MessageMetadata.BoughtToken) .copy(text = "Purchased", textSubstitutions = emptyList()) val item = mapper.map(ActivityFeedMessageWithToken(msg, token) to emptyMap()) - assertEquals("Purchased Dad Cash", item.title) + assertEquals("Purchased", item.title) } @Test - fun `bought dollars reads Added Money instead of Purchased Dollars`() { + fun `bought dollars renders the server text verbatim`() { val msg = feedMessage(metadata = MessageMetadata.BoughtToken) - .copy(text = "Purchased", textSubstitutions = emptyList()) + .copy(text = "Added", textSubstitutions = emptyList()) val item = mapper.map(ActivityFeedMessageWithToken(msg, usdfToken()) to emptyMap()) - assertEquals("Added Money", item.title) + assertEquals("Added", item.title) } @Test - fun `sold token appends the token name`() { + fun `sold token renders the server text verbatim`() { val token = token(address = Mint.usdc, name = "Dad Cash", symbol = "DADCASH") val msg = feedMessage(metadata = MessageMetadata.SoldToken) .copy(text = "Sold", textSubstitutions = emptyList()) val item = mapper.map(ActivityFeedMessageWithToken(msg, token) to emptyMap()) - assertEquals("Sold Dad Cash", item.title) - } - - @Test - fun `bought token stays bare until token metadata resolves`() { - val msg = feedMessage(metadata = MessageMetadata.BoughtToken) - .copy(text = "Purchased", textSubstitutions = emptyList()) - val item = mapper.map(ActivityFeedMessageWithToken(msg, token = null) to emptyMap()) - - assertEquals("Purchased", item.title) + assertEquals("Sold", item.title) } @Test