From a41f64e5206a0943485770e546c6a1c54d3b091f Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 21 Aug 2026 15:56:02 -0400 Subject: [PATCH 1/2] feat(deposit): show a USDC to Dollars conversion graphic The USDC deposit education screen, reached from Add Money -> Other Wallet, pointed its arrow the wrong way for a deposit and spelled the reserve as "USDF". Both this screen and WithdrawIntroScreen carried their own private copy of the same two-coins-and-an-arrow art, so promote it to FlipcashUI as ConversionGraphic, parameterised by which coin sits on each side, and have both call it. Mirrors the Android component of the same name. Copy drops the USDF branding. The "Deposit USDC" title stays as-is; the UI-test page object asserts on it. --- .../Main/Buy/USDCDepositEducationScreen.swift | 29 ++------ .../Withdraw/WithdrawIntroScreen.swift | 21 +----- .../FlipcashUI/Views/ConversionGraphic.swift | 67 +++++++++++++++++++ 3 files changed, 73 insertions(+), 44 deletions(-) create mode 100644 FlipcashUI/Sources/FlipcashUI/Views/ConversionGraphic.swift diff --git a/Flipcash/Core/Screens/Main/Buy/USDCDepositEducationScreen.swift b/Flipcash/Core/Screens/Main/Buy/USDCDepositEducationScreen.swift index b596df971..7c3ee95e4 100644 --- a/Flipcash/Core/Screens/Main/Buy/USDCDepositEducationScreen.swift +++ b/Flipcash/Core/Screens/Main/Buy/USDCDepositEducationScreen.swift @@ -9,8 +9,8 @@ import SwiftUI import FlipcashCore import FlipcashUI -/// Pre-flight for the USDC → USDF conversion: explains that incoming Solana -/// USDC is auto-converted 1:1 to USDF on receipt. Pass `onDepositOtherCurrencies` +/// Pre-flight for the USDC → Dollars conversion: explains that incoming Solana +/// USDC is auto-converted 1:1 to Dollars on receipt. Pass `onDepositOtherCurrencies` /// to expose a subtle escape hatch below Next. struct USDCDepositEducationScreen: View { @@ -33,16 +33,16 @@ struct USDCDepositEducationScreen: View { VStack(spacing: 24) { Spacer() - ConversionGraphic() + ConversionGraphic(from: .usdcOnSolana, to: .dollars) .accessibilityElement(children: .ignore) - .accessibilityLabel("Convert USDC to USDF") + .accessibilityLabel("Convert USDC to Dollars") VStack(spacing: 8) { Text("Deposit USDC") .font(.appTextLarge) .foregroundStyle(Color.textMain) - Text("Your Solana USDC will be converted 1:1 to USD on Flipcash (USDF)") + Text("Your USDC will be converted 1:1 to Dollars on Flipcash") .font(.appTextMedium) .foregroundStyle(Color.textSecondary) .multilineTextAlignment(.center) @@ -68,22 +68,3 @@ struct USDCDepositEducationScreen: View { .toolbarTitleDisplayMode(.inline) } } - -private struct ConversionGraphic: View { - var body: some View { - HStack(spacing: 16) { - BadgedIcon( - icon: Image.asset(.buyUSDC), - badge: Image.asset(.buySolana) - ) - - Image.system(.arrowRight) - .foregroundStyle(Color.textSecondary) - - Image.asset(.buyFlipcash) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 100, height: 100) - } - } -} diff --git a/Flipcash/Core/Screens/Settings/Withdraw/WithdrawIntroScreen.swift b/Flipcash/Core/Screens/Settings/Withdraw/WithdrawIntroScreen.swift index 5df5a480d..ba611838c 100644 --- a/Flipcash/Core/Screens/Settings/Withdraw/WithdrawIntroScreen.swift +++ b/Flipcash/Core/Screens/Settings/Withdraw/WithdrawIntroScreen.swift @@ -16,7 +16,7 @@ struct WithdrawIntroScreen: View { VStack(spacing: 24) { Spacer() - ConversionGraphic() + ConversionGraphic(from: .dollars, to: .usdcOnSolana) .accessibilityElement(children: .ignore) .accessibilityLabel("Convert Dollars to USDC") @@ -44,22 +44,3 @@ struct WithdrawIntroScreen: View { .toolbarTitleDisplayMode(.inline) } } - -private struct ConversionGraphic: View { - var body: some View { - HStack(spacing: 16) { - Image.asset(.buyDollars) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 100, height: 100) - - Image.system(.arrowRight) - .foregroundStyle(Color.textSecondary) - - BadgedIcon( - icon: Image.asset(.buyUSDC), - badge: Image.asset(.buySolana) - ) - } - } -} diff --git a/FlipcashUI/Sources/FlipcashUI/Views/ConversionGraphic.swift b/FlipcashUI/Sources/FlipcashUI/Views/ConversionGraphic.swift new file mode 100644 index 000000000..f00956b40 --- /dev/null +++ b/FlipcashUI/Sources/FlipcashUI/Views/ConversionGraphic.swift @@ -0,0 +1,67 @@ +// +// ConversionGraphic.swift +// FlipcashUI +// + +import SwiftUI + +/// A coin face `ConversionGraphic` can show on either side of the arrow. +public enum ConversionCoin { + /// The gold "Dollars" coin — Flipcash's own reserve currency. + case dollars + + /// The USDC coin with the Solana badge on its bottom-trailing corner. + case usdcOnSolana +} + +/// The "converted 1:1" education art: two coins separated by an arrow. +/// +/// Composed here rather than shipped as one flattened asset so the same art serves both +/// directions — Dollars → USDC when withdrawing, and the inverse when adding money from +/// another wallet. +public struct ConversionGraphic: View { + + private let from: ConversionCoin + private let to: ConversionCoin + + public init(from: ConversionCoin, to: ConversionCoin) { + self.from = from + self.to = to + } + + public var body: some View { + HStack(spacing: 16) { + coin(from) + + Image.system(.arrowRight) + .foregroundStyle(Color.textSecondary) + + coin(to) + } + } + + @ViewBuilder + private func coin(_ coin: ConversionCoin) -> some View { + switch coin { + case .dollars: + Image.asset(.buyDollars) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 100, height: 100) + + case .usdcOnSolana: + // Badge is an overlay, so the coin still lays out as a flat 100pt square. + BadgedIcon( + icon: Image.asset(.buyUSDC), + badge: Image.asset(.buySolana) + ) + } + } +} + +#Preview { + VStack(spacing: 40) { + ConversionGraphic(from: .dollars, to: .usdcOnSolana) + ConversionGraphic(from: .usdcOnSolana, to: .dollars) + } +} From f3f580a80796a2d4106ae5931a112b3f1ca175f3 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 21 Aug 2026 15:56:09 -0400 Subject: [PATCH 2/2] fix(swap): drop the "of X" suffix when a swap lands in Dollars The processing success title reads " of ", which names what the user received. That only helps when the destination is a community currency: adding money and converting into the reserve both rendered "$1.00 of Dollars", where the amount alone already said it. Suppress the suffix for those two, keeping it everywhere it still reads correctly. A convert already carries its destination as targetMint, so the check costs nothing to thread through. --- .../Main/AddMoney/AddMoneyProcessingViewModel.swift | 4 +++- .../Main/Currency Swap/SwapProcessingViewModel.swift | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Flipcash/Core/Screens/Main/AddMoney/AddMoneyProcessingViewModel.swift b/Flipcash/Core/Screens/Main/AddMoney/AddMoneyProcessingViewModel.swift index f6bfae25d..4d90bb41b 100644 --- a/Flipcash/Core/Screens/Main/AddMoney/AddMoneyProcessingViewModel.swift +++ b/Flipcash/Core/Screens/Main/AddMoney/AddMoneyProcessingViewModel.swift @@ -33,7 +33,9 @@ final class AddMoneyProcessingViewModel { case .processing: return "This Will Take a Minute" case .success: - return "\(input.amount.nativeAmount.formatted()) of USDF" + // No "of " here: adding money always lands in the reserve, so + // "$1.00 of Dollars" only restates what the amount already says. + return input.amount.nativeAmount.formatted() case .failed: return "Something Went Wrong" } diff --git a/Flipcash/Core/Screens/Main/Currency Swap/SwapProcessingViewModel.swift b/Flipcash/Core/Screens/Main/Currency Swap/SwapProcessingViewModel.swift index 89f71028a..f1e472b39 100644 --- a/Flipcash/Core/Screens/Main/Currency Swap/SwapProcessingViewModel.swift +++ b/Flipcash/Core/Screens/Main/Currency Swap/SwapProcessingViewModel.swift @@ -25,6 +25,10 @@ class SwapProcessingViewModel { case .success: if let exchangedFiat { switch swapType { + case .convert where targetMint == .usdf: + // Converting into the reserve would read "$1.00 of Dollars" — + // the amount alone already says it. + return exchangedFiat.nativeAmount.formatted() case .buyWithReserves, .buyWithCurrency, .convert: // Convert names its destination via `currencyName`, so the // same "X of " reads correctly there too. @@ -94,8 +98,9 @@ class SwapProcessingViewModel { private let swapId: SwapId private let swapType: SwapType - /// The token being bought — analytics-only; nil on the sell path, where - /// `amount.mint` already names the subject token. + /// The token being bought, or a convert's destination; nil on the sell path, + /// where `amount.mint` already names the subject token. Feeds analytics, and + /// the success title's check for a conversion landing in the reserve. private let targetMint: PublicKey? private let currencyName: String private let amount: ExchangedFiat