Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Bitkit/Components/PaykitAmountText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ struct PaykitAmountText: View {
}

func usdDisplayAmount(_ cents: UInt64, locale: Locale = .current) -> String {
(Decimal(cents) / 100).formatted(.number.precision(.fractionLength(2)).locale(locale))
formatFiatAmount(Decimal(cents) / 100, locale: locale)
}
2 changes: 1 addition & 1 deletion Bitkit/Managers/UsdtWalletManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ extension String {

func formattedUsdCents(locale: Locale = .current) -> String {
guard let value = UInt64(self) else { return "—" }
return "$" + (Decimal(value) / 100).formatted(.number.locale(locale).precision(.fractionLength(2)))
return "$" + usdDisplayAmount(value, locale: locale)
}
}

Expand Down
11 changes: 7 additions & 4 deletions Bitkit/Models/Currency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,18 @@ func isSuffixSymbolCurrency(_ currencyCode: String) -> Bool {
suffixSymbolCurrencies.contains(currencyCode)
}

/// Formats a fiat amount the way the in-app currency display does: 2 fraction digits,
/// Formats a fiat amount the way the in-app currency display does: at least 2 fraction digits,
/// "." decimal / "," grouping. Single source of truth shared by the app and the widget
/// extension so both produce identical strings.
func formatFiatAmount(_ value: Decimal) -> String {
func formatFiatAmount(_ value: Decimal, maximumFractionDigits: Int = 2,
roundingMode: NumberFormatter.RoundingMode = .halfEven, locale: Locale = .current) -> String
{
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
formatter.locale = Locale.current
formatter.maximumFractionDigits = maximumFractionDigits
formatter.roundingMode = roundingMode
formatter.locale = locale
formatter.decimalSeparator = "."
formatter.groupingSeparator = ","
return formatter.string(from: value as NSDecimalNumber) ?? "0.00"
Expand Down
1 change: 0 additions & 1 deletion Bitkit/Resources/Localization/ar.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1236,7 +1236,6 @@
"usdt__failed" = "فشل";
"usdt__fee_note" = "تُدفع جميع الرسوم بـ USDT. قد تكون الرسوم النهائية أقل.";
"usdt__hide_details" = "إخفاء التفاصيل";
"usdt__history_sync" = "لا تزال مزامنة النشاط الأقدم جارية. رصيدك محدَّث عند توفر الاتصال.";
"usdt__network_warning" = "اختر الشبكة التي يستخدمها المستلم. قد يؤدي الإرسال إلى عنوان على شبكة خاطئة إلى فقدان الأموال.";
"usdt__pending" = "معلق";
"usdt__received" = "مستلم";
Expand Down
1 change: 0 additions & 1 deletion Bitkit/Resources/Localization/ca.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1666,7 +1666,6 @@
"usdt__failed" = "Ha fallat";
"usdt__fee_note" = "Totes les tarifes es paguen en USDT. La tarifa final pot ser inferior.";
"usdt__hide_details" = "Amagar detalls";
"usdt__history_sync" = "L'activitat anterior encara s'està sincronitzant. El teu saldo està al dia quan hi ha connexió.";
"usdt__network_warning" = "Selecciona la xarxa que utilitza el destinatari. Si envies a una adreça d'una xarxa incorrecta, pots perdre els fons.";
"usdt__pending" = "Pendent";
"usdt__received" = "Rebut";
Expand Down
1 change: 0 additions & 1 deletion Bitkit/Resources/Localization/cs.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1806,7 +1806,6 @@
"usdt__failed" = "Selhání";
"usdt__fee_note" = "Všechny poplatky se platí v USDT. Konečný poplatek může být nižší.";
"usdt__hide_details" = "Skrýt podrobnosti";
"usdt__history_sync" = "Starší aktivita se stále synchronizuje. Když je připojení k dispozici, je váš zůstatek aktuální.";
"usdt__network_warning" = "Vyberte síť, kterou používá příjemce. Odesláním na adresu v nesprávné síti můžete přijít o prostředky.";
"usdt__pending" = "Čekající";
"usdt__received" = "Přijato";
Expand Down
1 change: 0 additions & 1 deletion Bitkit/Resources/Localization/de.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1803,7 +1803,6 @@
"usdt__failed" = "Fehlgeschlagen";
"usdt__fee_note" = "Alle Gebühren werden in USDT bezahlt. Die endgültige Gebühr kann niedriger sein.";
"usdt__hide_details" = "Details ausblenden";
"usdt__history_sync" = "Ältere Aktivitäten werden noch synchronisiert. Dein Guthaben ist aktuell, sobald eine Verbindung besteht.";
"usdt__network_warning" = "Wähle das Netzwerk, das der Empfänger verwendet. Beim Senden an eine Adresse im falschen Netzwerk kann Guthaben verloren gehen.";
"usdt__pending" = "Ausstehend";
"usdt__received" = "Empfangen";
Expand Down
1 change: 0 additions & 1 deletion Bitkit/Resources/Localization/el.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1537,7 +1537,6 @@
"usdt__failed" = "Απέτυχε";
"usdt__fee_note" = "Όλα τα τέλη πληρώνονται σε USDT. Το τελικό τέλος μπορεί να είναι χαμηλότερο.";
"usdt__hide_details" = "Απόκρυψη λεπτομερειών";
"usdt__history_sync" = "Η παλαιότερη δραστηριότητα συγχρονίζεται ακόμα. Το υπόλοιπό σου είναι ενημερωμένο όταν υπάρχει σύνδεση.";
"usdt__network_warning" = "Επίλεξε το δίκτυο που χρησιμοποιεί ο παραλήπτης. Η αποστολή σε διεύθυνση σε λάθος δίκτυο μπορεί να οδηγήσει σε απώλεια κεφαλαίων.";
"usdt__pending" = "Εκκρεμεί";
"usdt__received" = "Ελήφθη";
Expand Down
1 change: 0 additions & 1 deletion Bitkit/Resources/Localization/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1898,7 +1898,6 @@
"usdt__failed" = "Failed";
"usdt__fee_note" = "All fees are paid in USDT. Final fee may be lower.";
"usdt__hide_details" = "Hide details";
"usdt__history_sync" = "Older activity is still syncing. Your balance is up to date when the connection is available.";
"usdt__maximum_fee" = "Maximum fee";
"usdt__network_warning" = "Select the network used by the recipient. Sending to an address on the wrong network can lose funds.";
"usdt__pending" = "Pending";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1809,7 +1809,6 @@
"usdt__failed" = "Fallido";
"usdt__fee_note" = "Todas las comisiones se pagan en USDT. La comisión final puede ser menor.";
"usdt__hide_details" = "Ocultar detalles";
"usdt__history_sync" = "La actividad anterior aún se está sincronizando. Su saldo estará actualizado cuando haya conexión.";
"usdt__network_warning" = "Seleccione la red que usa el destinatario. Si envía a una dirección en la red equivocada, puede perder los fondos.";
"usdt__pending" = "Pendiente";
"usdt__received" = "Recibido";
Expand Down
1 change: 0 additions & 1 deletion Bitkit/Resources/Localization/es.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1686,7 +1686,6 @@
"usdt__failed" = "Fallido";
"usdt__fee_note" = "Todas las comisiones se pagan en USDT. La comisión final puede ser menor.";
"usdt__hide_details" = "Ocultar detalles";
"usdt__history_sync" = "La actividad anterior aún se está sincronizando. Su saldo estará actualizado cuando haya conexión.";
"usdt__network_warning" = "Seleccione la red que usa el destinatario. Si envía a una dirección en la red equivocada, puede perder los fondos.";
"usdt__pending" = "Pendiente";
"usdt__received" = "Recibido";
Expand Down
1 change: 0 additions & 1 deletion Bitkit/Resources/Localization/fr.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1817,7 +1817,6 @@
"usdt__failed" = "Échec";
"usdt__fee_note" = "Tous les frais sont payés en USDT. Les frais finaux peuvent être inférieurs.";
"usdt__hide_details" = "Masquer les détails";
"usdt__history_sync" = "L'activité plus ancienne est encore en cours de synchronisation. Votre solde est à jour lorsque la connexion est disponible.";
"usdt__network_warning" = "Sélectionnez le réseau utilisé par le destinataire. Un envoi vers une adresse sur le mauvais réseau peut entraîner une perte de fonds.";
"usdt__pending" = "En attente";
"usdt__received" = "Reçu";
Expand Down
1 change: 0 additions & 1 deletion Bitkit/Resources/Localization/it.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1776,7 +1776,6 @@
"usdt__failed" = "Fallito";
"usdt__fee_note" = "Tutte le commissioni sono pagate in USDT. La commissione finale potrebbe essere inferiore.";
"usdt__hide_details" = "Nascondi dettagli";
"usdt__history_sync" = "Le attività meno recenti sono ancora in sincronizzazione. Il tuo saldo è aggiornato quando la connessione è disponibile.";
"usdt__network_warning" = "Seleziona la rete usata dal destinatario. Inviare a un indirizzo sulla rete sbagliata può causare la perdita dei fondi.";
"usdt__pending" = "In attesa";
"usdt__received" = "Ricevuto";
Expand Down
1 change: 0 additions & 1 deletion Bitkit/Resources/Localization/nl.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1811,7 +1811,6 @@
"usdt__failed" = "Mislukt";
"usdt__fee_note" = "Alle vergoedingen worden in USDT betaald. De uiteindelijke vergoeding kan lager zijn.";
"usdt__hide_details" = "Details verbergen";
"usdt__history_sync" = "Oudere activiteit wordt nog gesynchroniseerd. Uw saldo is actueel zodra er verbinding is.";
"usdt__network_warning" = "Selecteer het netwerk dat de ontvanger gebruikt. Verzenden naar een adres op het verkeerde netwerk kan tot verlies van geld leiden.";
"usdt__pending" = "In behandeling";
"usdt__received" = "Ontvangen";
Expand Down
1 change: 0 additions & 1 deletion Bitkit/Resources/Localization/pl.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1816,7 +1816,6 @@
"usdt__failed" = "Niepowodzenie";
"usdt__fee_note" = "Wszystkie opłaty są płacone w USDT. Ostateczna opłata może być niższa.";
"usdt__hide_details" = "Ukryj szczegóły";
"usdt__history_sync" = "Starsza aktywność wciąż się synchronizuje. Saldo jest aktualne, gdy połączenie jest dostępne.";
"usdt__network_warning" = "Wybierz sieć używaną przez odbiorcę. Wysłanie na adres w niewłaściwej sieci może skutkować utratą środków.";
"usdt__pending" = "W toku";
"usdt__received" = "Otrzymano";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1817,7 +1817,6 @@
"usdt__failed" = "Falhou";
"usdt__fee_note" = "Todas as taxas são pagas em USDT. A taxa final pode ser menor.";
"usdt__hide_details" = "Ocultar detalhes";
"usdt__history_sync" = "A atividade mais antiga ainda está sincronizando. Seu saldo fica atualizado sempre que houver conexão.";
"usdt__network_warning" = "Selecione a rede usada pelo destinatário. Enviar para um endereço na rede errada pode causar a perda dos fundos.";
"usdt__pending" = "Pendente";
"usdt__received" = "Recebido";
Expand Down
1 change: 0 additions & 1 deletion Bitkit/Resources/Localization/pt.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1463,7 +1463,6 @@
"usdt__failed" = "Falhou";
"usdt__fee_note" = "Todas as taxas são pagas em USDT. A taxa final pode ser inferior.";
"usdt__hide_details" = "Ocultar detalhes";
"usdt__history_sync" = "A atividade mais antiga ainda está a sincronizar. O seu saldo está atualizado sempre que houver ligação.";
"usdt__network_warning" = "Selecione a rede usada pelo destinatário. Enviar para um endereço na rede errada pode resultar na perda de fundos.";
"usdt__pending" = "Pendente";
"usdt__received" = "Recebido";
Expand Down
1 change: 0 additions & 1 deletion Bitkit/Resources/Localization/ru.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1797,7 +1797,6 @@
"usdt__failed" = "Не удалось";
"usdt__fee_note" = "Все комиссии оплачиваются в USDT. Итоговая комиссия может быть ниже.";
"usdt__hide_details" = "Скрыть детали";
"usdt__history_sync" = "Более ранняя активность ещё синхронизируется. Ваш баланс актуален, когда есть подключение.";
"usdt__network_warning" = "Выберите сеть, которую использует получатель. Отправка на адрес в неверной сети может привести к потере средств.";
"usdt__pending" = "В ожидании";
"usdt__received" = "Получено";
Expand Down
2 changes: 1 addition & 1 deletion Bitkit/Views/Home/HomeWalletView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ struct HomeWalletView: View {
} icon: {
Image("tether-circle").resizable().frame(width: 24, height: 24)
} amount: {
BodyMSBText(settings.hideBalance ? " • • • • •" : usdt.balance.map { usdtOverviewAmount($0) } ?? "—",
BodyMSBText(settings.hideBalance ? " • • • • •" : usdt.balance.map { "$ " + usdtOverviewAmount($0) } ?? "—",
kerning: 0, lineHeight: 22)
.lineLimit(1).minimumScaleFactor(0.7)
}
Expand Down
5 changes: 2 additions & 3 deletions Bitkit/Views/PaymentRequests/CreatePaymentRequestView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ struct PaymentRequestAmountView: View {
)

if asset == .usd {
NumberPadAmountText(value: usdtInputAmount(dollarAmount.isEmpty ? "0" : dollarAmount), symbol: "$")
NumberPadAmountText(value: dollarAmount.isEmpty ? "0" : dollarAmount, symbol: "$")
.accessibilityIdentifier("\(testIdentifierPrefix)AmountField")
} else {
NumberPadTextField(viewModel: amountViewModel, showEditButton: false, isFocused: true,
Expand Down Expand Up @@ -377,7 +377,6 @@ struct PaymentRequestAmountView: View {
}
NumberPad(
type: asset == .usd ? .decimal : amountViewModel.getNumberPadType(currency: currency),
decimalSeparator: asset == .usd ? Locale.current.decimalSeparator ?? "." : ".",
errorKey: amountViewModel.errorKey
) { key in
entryConversion.reset()
Expand Down Expand Up @@ -414,7 +413,7 @@ struct PaymentRequestAmountView: View {
if asset == .btc {
amountViewModel.updateFromSats(initialDraft.amount.atomic, currency: currency)
} else {
dollarAmount = initialDraft.amount.inputValue
dollarAmount = initialDraft.amount.atomic == 0 ? "" : initialDraft.amount.inputValue
}
}
}
Expand Down
1 change: 0 additions & 1 deletion Bitkit/Views/Wallets/Send/SendAmountView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,6 @@ struct SendAmountView: View {

NumberPad(
type: app.paykitUsesUsdt ? .decimal : amountViewModel.getNumberPadType(currency: currency),
decimalSeparator: app.paykitUsesUsdt ? Locale.current.decimalSeparator ?? "." : ".",
errorKey: amountViewModel.errorKey,
isDisabled: !canEditAmount
) { key in
Expand Down
2 changes: 1 addition & 1 deletion Bitkit/Views/Wallets/UsdtReceiveAmountView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct UsdtReceiveAmountView: View {
.fixedSize(horizontal: false, vertical: true)
Divider()
Spacer(minLength: 34)
NumberPad(type: .decimal, decimalSeparator: Locale.current.decimalSeparator ?? ".", isDisabled: busy,
NumberPad(type: .decimal, isDisabled: busy,
onDeleteLongPress: { amount = "" })
{ key in
amount = NumberPadInputHandler.handleInput(key: key, current: amount, maxLength: 21, maxDecimals: 6)
Expand Down
1 change: 0 additions & 1 deletion Bitkit/Views/Wallets/UsdtSendView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,6 @@ struct UsdtSendView: View {
CustomDivider()
NumberPad(
type: .decimal,
decimalSeparator: Locale.current.decimalSeparator ?? ".",
isDisabled: busy || embedded,
onDeleteLongPress: { amount = ""; maximumQuote = nil; isMaximumAmount = false; error = nil },
onPress: enterAmount
Expand Down
20 changes: 4 additions & 16 deletions Bitkit/Views/Wallets/UsdtWalletScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ struct UsdtWalletScreen: View {
.balanceVisibilityToggle()
.accessibilityElement(children: .contain)
.accessibilityIdentifier("UsdtBalance")
if let error = usdt.errorMessage {
BodySText(error, textColor: .brandAccent)
}
activityList
}
}
Expand Down Expand Up @@ -115,9 +112,6 @@ struct UsdtWalletScreen: View {
.buttonStyle(.plain)
.accessibilityIdentifier("UsdtActivity-\(index)")
}
if !usdt.historyComplete {
BodySText(t("usdt__history_sync"), textColor: .textSecondary)
}
}
}
}
Expand Down Expand Up @@ -149,7 +143,7 @@ struct UsdtAmountHeader: View {
CaptionMText("USDT", textColor: .textSecondary, lineHeight: 18)
}
DisplayText(
"<accent>\(prefix)$</accent> " + (hideBalance ? " • • • • •" : displayAmount ?? usdtInputAmount(amount)),
"<accent>\(prefix)$</accent> " + (hideBalance ? " • • • • •" : displayAmount ?? amount),
accentColor: .textSecondary, accentFont: Fonts.extraBold
)
.lineLimit(1).minimumScaleFactor(0.5)
Expand Down Expand Up @@ -200,18 +194,12 @@ func usdtDisplaySats(amount: String, rate: PaykitExchangeRate?, now: Date = Date
}

func usdtOverviewAmount(_ amount: UInt64, locale: Locale = .current) -> String {
let format = Decimal.FormatStyle.number.precision(.fractionLength(2))
.rounded(rule: .toNearestOrAwayFromZero).locale(locale)
if amount > 0, amount < 10000 {
return "<" + (Decimal(1) / 100).formatted(format)
return "<" + formatFiatAmount(Decimal(1) / 100, locale: locale)
}
return (Decimal(amount) / 1_000_000).formatted(format)
return formatFiatAmount(Decimal(amount) / 1_000_000, roundingMode: .halfUp, locale: locale)
}

func usdtExactAmount(_ amount: UInt64, locale: Locale = .current) -> String {
(Decimal(amount) / 1_000_000).formatted(.number.precision(.fractionLength(2 ... 6)).locale(locale))
}

func usdtInputAmount(_ amount: String, locale: Locale = .current) -> String {
amount.replacingOccurrences(of: ".", with: locale.decimalSeparator ?? ".")
formatFiatAmount(Decimal(amount) / 1_000_000, maximumFractionDigits: 6, locale: locale)
}
23 changes: 9 additions & 14 deletions BitkitTests/PaykitAmountTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,20 @@ final class PaykitAmountTests: XCTestCase {
let amount = try PaykitAmount(asset: asset, value: value)
XCTAssertEqual(amount.inputValue, expected)
XCTAssertEqual(try PaykitAmount(asset: asset, value: amount.inputValue), amount)
if asset == .usdt {
XCTAssertEqual(usdtInputAmount(amount.inputValue, locale: Locale(identifier: "en_US")), expected)
XCTAssertEqual(
usdtInputAmount(amount.inputValue, locale: Locale(identifier: "de_DE")),
expected.replacingOccurrences(of: ".", with: ",")
)
}
}
XCTAssertEqual(try PaykitAmount(asset: .usdt, value: "150.50").value, "150.5")
}

func testUsdDisplayPreservesCentsAndUsesLocaleGrouping() {
for (cents, us, de): (UInt64, String, String) in [
(0, "0.00", "0,00"), (1, "0.01", "0,01"), (100, "1.00", "1,00"),
(15050, "150.50", "150,50"), (123_456, "1,234.56", "1.234,56"),
(.max, "184,467,440,737,095,516.15", "184.467.440.737.095.516,15"),
func testUsdDisplayPreservesCentsAndMatchesFiatSeparatorsAcrossRegions() {
for (cents, expected): (UInt64, String) in [
(0, "0.00"), (1, "0.01"), (100, "1.00"), (15050, "150.50"), (123_456, "1,234.56"),
(.max, "184,467,440,737,095,516.15"),
] {
XCTAssertEqual(usdDisplayAmount(cents, locale: Locale(identifier: "en_US")), us, "cents: \(cents)")
XCTAssertEqual(usdDisplayAmount(cents, locale: Locale(identifier: "de_DE")), de, "cents: \(cents)")
for identifier in ["en_US", "de_DE", "nl_NL", "fr_FR"] {
let locale = Locale(identifier: identifier)
XCTAssertEqual(usdDisplayAmount(cents, locale: locale), expected, identifier)
XCTAssertEqual(usdDisplayAmount(cents, locale: locale), formatFiatAmount(Decimal(cents) / 100, locale: locale))
}
}
}

Expand Down
Loading
Loading