diff --git a/Bitkit/Components/MoneyText.swift b/Bitkit/Components/MoneyText.swift index 826f3168..ea2b1b34 100644 --- a/Bitkit/Components/MoneyText.swift +++ b/Bitkit/Components/MoneyText.swift @@ -51,8 +51,14 @@ struct MoneyText: View { private var displayText: String { if showSymbol { let baseSymbol = unit == .bitcoin ? "₿" : fiatSymbol - let symbolPart = prefix != nil ? "\(prefix!) \(baseSymbol)" : "\(baseSymbol)" - return "\(symbolPart) \(formattedValue)" + let isSuffix = unit == .fiat && isFiatSymbolSuffix + if isSuffix { + let prefixPart = prefix != nil ? "\(prefix!) " : "" + return "\(prefixPart)\(formattedValue) \(baseSymbol)" + } else { + let symbolPart = prefix != nil ? "\(prefix!) \(baseSymbol)" : "\(baseSymbol)" + return "\(symbolPart) \(formattedValue)" + } } else { return prefix != nil ? "\(prefix!) \(formattedValue)" : formattedValue } @@ -118,6 +124,10 @@ extension MoneyText { return converted.symbol } + private var isFiatSymbolSuffix: Bool { + isSuffixSymbolCurrency(currency.selectedCurrency) + } + private var formattedValue: String { if hideBalance { return displayDots diff --git a/Bitkit/Components/NumberPadTextField.swift b/Bitkit/Components/NumberPadTextField.swift index f32c7f35..63c11e52 100644 --- a/Bitkit/Components/NumberPadTextField.swift +++ b/Bitkit/Components/NumberPadTextField.swift @@ -77,11 +77,15 @@ struct NumberPadTextField: View { @ViewBuilder private var primaryDisplayView: some View { + let isSuffix = currency.primaryDisplay == .fiat && isSuffixSymbolCurrency(currency.selectedCurrency) + let symbolText = currency.primaryDisplay == .bitcoin ? "₿" : currency.symbol + HStack(spacing: 6) { - // Symbol - Text(currency.primaryDisplay == .bitcoin ? "₿" : currency.symbol) - .font(.custom(Fonts.extraBold, size: 44)) - .foregroundColor(.textSecondary) + if !isSuffix { + Text(symbolText) + .font(.custom(Fonts.extraBold, size: 44)) + .foregroundColor(.textSecondary) + } // Value and placeholder (Text(viewModel.displayText) @@ -89,6 +93,12 @@ struct NumberPadTextField: View { + Text(viewModel.getPlaceholder(currency: currency)) .foregroundColor(isFocused ? .textSecondary : .textPrimary)) .font(.custom(Fonts.black, size: 44)) + + if isSuffix { + Text(symbolText) + .font(.custom(Fonts.extraBold, size: 44)) + .foregroundColor(.textSecondary) + } } } } diff --git a/Bitkit/Models/Currency.swift b/Bitkit/Models/Currency.swift index 6e8a7eb8..464e2d24 100644 --- a/Bitkit/Models/Currency.swift +++ b/Bitkit/Models/Currency.swift @@ -40,6 +40,8 @@ struct ConvertedAmount { let sats: UInt64 let btcValue: Decimal + var isSymbolSuffix: Bool { isSuffixSymbolCurrency(currency) } + init(value: Decimal, formatted: String, symbol: String, currency: String, flag: String, sats: UInt64) { self.value = value self.formatted = formatted @@ -50,6 +52,11 @@ struct ConvertedAmount { btcValue = Decimal(sats) / 100_000_000 } + func formattedWithSymbol(withSpace: Bool = false) -> String { + let separator = withSpace ? " " : "" + return isSymbolSuffix ? "\(formatted)\(separator)\(symbol)" : "\(symbol)\(separator)\(formatted)" + } + struct BitcoinDisplayComponents { let symbol: String let value: String @@ -75,3 +82,23 @@ struct ConvertedAmount { } } } + +func isSuffixSymbolCurrency(_ currencyCode: String) -> Bool { + suffixSymbolCurrencies.contains(currencyCode) +} + +private let suffixSymbolCurrencies: Set = [ + "BGN", // Bulgarian Lev (10,00 лв) + "CHF", // Swiss Franc (10.00 CHF) + "CZK", // Czech Koruna (10,00 Kč) + "DKK", // Danish Krone (10,00 kr) + "HRK", // Croatian Kuna (10,00 kn) + "HUF", // Hungarian Forint (10 000 Ft) + "ISK", // Icelandic Króna (10.000 kr) + "NOK", // Norwegian Krone (10,00 kr) + "PLN", // Polish Złoty (0,35 zł) + "RON", // Romanian Leu (10,00 lei) + "RUB", // Russian Ruble (10,00 ₽) + "SEK", // Swedish Krona (10,00 kr) + "TRY", // Turkish Lira (10,00 ₺) +] diff --git a/Bitkit/ViewModels/Widgets/WeatherViewModel.swift b/Bitkit/ViewModels/Widgets/WeatherViewModel.swift index 7342519c..f25f70aa 100644 --- a/Bitkit/ViewModels/Widgets/WeatherViewModel.swift +++ b/Bitkit/ViewModels/Widgets/WeatherViewModel.swift @@ -166,7 +166,7 @@ class WeatherViewModel: ObservableObject { throw AppError(message: "Currency conversion unavailable", debugMessage: "Failed to convert \(fee) satoshis to fiat currency") } - return "\(converted.symbol) \(converted.formatted)" + return converted.formattedWithSymbol(withSpace: true) } deinit { diff --git a/Bitkit/Views/Wallets/Receive/ReceiveCjitConfirmation.swift b/Bitkit/Views/Wallets/Receive/ReceiveCjitConfirmation.swift index 704823fe..7158683a 100644 --- a/Bitkit/Views/Wallets/Receive/ReceiveCjitConfirmation.swift +++ b/Bitkit/Views/Wallets/Receive/ReceiveCjitConfirmation.swift @@ -14,14 +14,14 @@ struct ReceiveCjitConfirmation: View { guard let converted = currency.convert(sats: entry.networkFeeSat) else { return String(entry.networkFeeSat) } - return "\(converted.symbol)\(converted.formatted)" + return converted.formattedWithSymbol() } private func formattedServiceFee() -> String { guard let converted = currency.convert(sats: entry.serviceFeeSat) else { return String(entry.serviceFeeSat) } - return "\(converted.symbol)\(converted.formatted)" + return converted.formattedWithSymbol() } var receiveAmount: Int { diff --git a/Bitkit/Views/Wallets/Sheets/BoostSheet.swift b/Bitkit/Views/Wallets/Sheets/BoostSheet.swift index 9ee5af76..41403cef 100644 --- a/Bitkit/Views/Wallets/Sheets/BoostSheet.swift +++ b/Bitkit/Views/Wallets/Sheets/BoostSheet.swift @@ -82,7 +82,7 @@ struct BoostSheet: View { else { return "" } - return "\(converted.symbol)\(converted.formatted)" + return converted.formattedWithSymbol() } var body: some View { diff --git a/BitkitTests/CurrencyTests.swift b/BitkitTests/CurrencyTests.swift new file mode 100644 index 00000000..43945eb9 --- /dev/null +++ b/BitkitTests/CurrencyTests.swift @@ -0,0 +1,114 @@ +@testable import Bitkit +import XCTest + +final class CurrencyTests: XCTestCase { + // MARK: - isSuffixSymbolCurrency + + func testIsSuffixSymbolCurrency_ReturnsTrueForPLN() { + XCTAssertTrue(isSuffixSymbolCurrency("PLN")) + } + + func testIsSuffixSymbolCurrency_ReturnsTrueForCZK() { + XCTAssertTrue(isSuffixSymbolCurrency("CZK")) + } + + func testIsSuffixSymbolCurrency_ReturnsTrueForSEK() { + XCTAssertTrue(isSuffixSymbolCurrency("SEK")) + } + + func testIsSuffixSymbolCurrency_ReturnsTrueForCHF() { + XCTAssertTrue(isSuffixSymbolCurrency("CHF")) + } + + func testIsSuffixSymbolCurrency_ReturnsFalseForUSD() { + XCTAssertFalse(isSuffixSymbolCurrency("USD")) + } + + func testIsSuffixSymbolCurrency_ReturnsFalseForEUR() { + XCTAssertFalse(isSuffixSymbolCurrency("EUR")) + } + + func testIsSuffixSymbolCurrency_ReturnsFalseForGBP() { + XCTAssertFalse(isSuffixSymbolCurrency("GBP")) + } + + func testIsSuffixSymbolCurrency_ReturnsFalseForUnknownCurrency() { + XCTAssertFalse(isSuffixSymbolCurrency("XYZ")) + } + + // MARK: - ConvertedAmount.isSymbolSuffix + + func testConvertedAmount_IsSymbolSuffix_TrueForPLN() { + let converted = ConvertedAmount( + value: 0.35, formatted: "0.35", symbol: "zł", + currency: "PLN", flag: "🇵🇱", sats: 100 + ) + XCTAssertTrue(converted.isSymbolSuffix) + } + + func testConvertedAmount_IsSymbolSuffix_FalseForUSD() { + let converted = ConvertedAmount( + value: 10.50, formatted: "10.50", symbol: "$", + currency: "USD", flag: "🇺🇸", sats: 1000 + ) + XCTAssertFalse(converted.isSymbolSuffix) + } + + // MARK: - ConvertedAmount.formattedWithSymbol + + func testFormattedWithSymbol_PrefixCurrency() { + let converted = ConvertedAmount( + value: 10.50, formatted: "10.50", symbol: "$", + currency: "USD", flag: "🇺🇸", sats: 1000 + ) + XCTAssertEqual(converted.formattedWithSymbol(), "$10.50") + } + + func testFormattedWithSymbol_SuffixCurrency() { + let converted = ConvertedAmount( + value: 0.35, formatted: "0.35", symbol: "zł", + currency: "PLN", flag: "🇵🇱", sats: 100 + ) + XCTAssertEqual(converted.formattedWithSymbol(), "0.35zł") + } + + func testFormattedWithSymbol_SuffixCurrencyCZK() { + let converted = ConvertedAmount( + value: 250.00, formatted: "250.00", symbol: "Kč", + currency: "CZK", flag: "🇨🇿", sats: 50000 + ) + XCTAssertEqual(converted.formattedWithSymbol(), "250.00Kč") + } + + func testFormattedWithSymbol_PrefixCurrencyEUR() { + let converted = ConvertedAmount( + value: 10.00, formatted: "10.00", symbol: "€", + currency: "EUR", flag: "🇪🇺", sats: 1000 + ) + XCTAssertEqual(converted.formattedWithSymbol(), "€10.00") + } + + func testFormattedWithSymbol_SuffixCurrencyCHF() { + let converted = ConvertedAmount( + value: 50.00, formatted: "50.00", symbol: "CHF", + currency: "CHF", flag: "🇨🇭", sats: 10000 + ) + XCTAssertEqual(converted.formattedWithSymbol(), "50.00CHF") + } + + func testFormattedWithSymbol_PrefixCurrency_WithSpace() { + let converted = ConvertedAmount( + value: 10.50, formatted: "10.50", symbol: "$", + currency: "USD", flag: "🇺🇸", sats: 1000 + ) + XCTAssertEqual(converted.formattedWithSymbol(withSpace: true), "$ 10.50") + } + + func testFormattedWithSymbol_SuffixCurrency_WithSpace() { + let converted = ConvertedAmount( + value: 0.35, formatted: "0.35", symbol: "zł", + currency: "PLN", flag: "🇵🇱", sats: 100 + ) + XCTAssertEqual(converted.formattedWithSymbol(withSpace: true), "0.35 zł") + } +}