diff --git a/android/app/src/main/java/org/bitcoinppl/cove/CoinControlManager.kt b/android/app/src/main/java/org/bitcoinppl/cove/CoinControlManager.kt index b3c023024..859ac2a57 100644 --- a/android/app/src/main/java/org/bitcoinppl/cove/CoinControlManager.kt +++ b/android/app/src/main/java/org/bitcoinppl/cove/CoinControlManager.kt @@ -136,9 +136,6 @@ class CoinControlManager internal constructor( buttonPresentation(key) } - val totalSelectedAmount: String - get() = displayAmount(totalSelected) - val totalSelectedSats: Long get() = totalSelected.asSats().toLong() @@ -211,6 +208,24 @@ class CoinControlManager internal constructor( fun displayAmount(amount: Amount, showUnit: Boolean = true): String = if (showUnit) amount.fmtStringWithUnit(unit) else amount.fmtString(unit) + fun displayAmountWithFiat( + amount: Amount, + prices: PriceResponse?, + currency: FiatCurrency, + ): String = + withRustOr(displayAmount(amount)) { + displayAmountWithFiat(amount, prices, currency) + } + + fun displayFiatAmount( + amount: Amount, + prices: PriceResponse?, + currency: FiatCurrency, + ): String? = + withRustOr(null) { + displayFiatAmount(amount, prices, currency) + } + suspend fun reloadLabels() { withRustOrSuspend(Unit) { reloadLabels() diff --git a/android/app/src/main/java/org/bitcoinppl/cove/flows/CoinControlFlow/UtxoListScreen.kt b/android/app/src/main/java/org/bitcoinppl/cove/flows/CoinControlFlow/UtxoListScreen.kt index d50cfb55b..b876368d9 100644 --- a/android/app/src/main/java/org/bitcoinppl/cove/flows/CoinControlFlow/UtxoListScreen.kt +++ b/android/app/src/main/java/org/bitcoinppl/cove/flows/CoinControlFlow/UtxoListScreen.kt @@ -116,8 +116,15 @@ fun UtxoListScreen( utxos = manager.utxos, selected = manager.selected, lockStateLoadFailed = manager.lockStateLoadFailed, - totalSelectedAmount = manager.totalSelectedAmount, + totalSelectedAmount = + manager.displayAmountWithFiat( + manager.totalSelected, + app.prices, + app.selectedFiatCurrency, + ), searchQuery = manager.search, + prices = app.prices, + fiatCurrency = app.selectedFiatCurrency, onBack = { app.popRoute() }, onToggleUnit = { manager.dispatch(org.bitcoinppl.cove_core.CoinControlManagerAction.ToggleUnit) @@ -183,6 +190,8 @@ private fun UtxoListScreenContent( lockStateLoadFailed: Boolean, totalSelectedAmount: String, searchQuery: String, + prices: org.bitcoinppl.cove_core.PriceResponse?, + fiatCurrency: org.bitcoinppl.cove_core.FiatCurrency, onBack: () -> Unit, onToggleUnit: () -> Unit, onToggle: (org.bitcoinppl.cove_core.types.Utxo) -> Unit, @@ -352,8 +361,17 @@ private fun UtxoListScreenContent( Column(modifier = Modifier.verticalScroll(rememberScrollState())) { utxos.forEachIndexed { index, utxo -> UtxoItemRow( - manager = manager, utxo = utxo, + amount = + UtxoRowAmount( + bitcoin = manager.displayAmount(utxo.amount), + fiat = + manager.displayFiatAmount( + utxo.amount, + prices, + fiatCurrency, + ), + ), selected = utxo.spendable && selected.contains(utxo.id), onToggle = { onToggle(utxo) }, onSetSpendability = { @@ -458,11 +476,17 @@ private fun UtxoListScreenContent( } } +// fiat is null when no prices are available, so the row hides the fiat line +private data class UtxoRowAmount( + val bitcoin: String, + val fiat: String?, +) + @OptIn(ExperimentalFoundationApi::class) @Composable private fun UtxoItemRow( - manager: org.bitcoinppl.cove.CoinControlManager, utxo: org.bitcoinppl.cove_core.types.Utxo, + amount: UtxoRowAmount, selected: Boolean, onToggle: () -> Unit, onSetSpendability: () -> Unit, @@ -508,11 +532,23 @@ private fun UtxoItemRow( } Column(horizontalAlignment = Alignment.End) { Text( - manager.displayAmount(utxo.amount), + amount.bitcoin, fontWeight = FontWeight.Normal, fontSize = 13.sp, color = MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, ) + amount.fiat?.let { fiat -> + Spacer(Modifier.height(4.dp)) + Text( + fiat, + color = MaterialTheme.colorScheme.onSurfaceVariant, + fontSize = 12.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } Spacer(Modifier.height(4.dp)) Text( utxo.displayDate, diff --git a/android/app/src/main/java/org/bitcoinppl/cove_core/cove.kt b/android/app/src/main/java/org/bitcoinppl/cove_core/cove.kt index 23bf0826c..1b6acda5e 100644 --- a/android/app/src/main/java/org/bitcoinppl/cove_core/cove.kt +++ b/android/app/src/main/java/org/bitcoinppl/cove_core/cove.kt @@ -1567,6 +1567,10 @@ internal object IntegrityCheckingUniffiLib { ): Short external fun uniffi_cove_checksum_method_rustcoincontrolmanager_utxos( ): Short + external fun uniffi_cove_checksum_method_rustcoincontrolmanager_display_amount_with_fiat( + ): Short + external fun uniffi_cove_checksum_method_rustcoincontrolmanager_display_fiat_amount( + ): Short external fun uniffi_cove_checksum_method_rustconnectivitymanager_is_connected( ): Short external fun uniffi_cove_checksum_method_rustconnectivitymanager_set_connection_state( @@ -2725,6 +2729,10 @@ internal object UniffiLib { ): RustBufferBitcoinUnit.ByValue external fun uniffi_cove_fn_method_rustcoincontrolmanager_utxos(`ptr`: Long,uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue + external fun uniffi_cove_fn_method_rustcoincontrolmanager_display_amount_with_fiat(`ptr`: Long,`amount`: Long,`prices`: RustBuffer.ByValue,`currency`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + external fun uniffi_cove_fn_method_rustcoincontrolmanager_display_fiat_amount(`ptr`: Long,`amount`: Long,`prices`: RustBuffer.ByValue,`currency`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue external fun uniffi_cove_fn_clone_coincontrolmanagerstate(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, ): Long external fun uniffi_cove_fn_free_coincontrolmanagerstate(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, @@ -4614,6 +4622,12 @@ private fun uniffiCheckApiChecksums(lib: IntegrityCheckingUniffiLib) { if (lib.uniffi_cove_checksum_method_rustcoincontrolmanager_utxos() != 43520.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_cove_checksum_method_rustcoincontrolmanager_display_amount_with_fiat() != 29447.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_cove_checksum_method_rustcoincontrolmanager_display_fiat_amount() != 8420.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_cove_checksum_method_rustconnectivitymanager_is_connected() != 47607.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -21365,6 +21379,21 @@ public interface RustCoinControlManagerInterface { fun `utxos`(): List + /** + * Formats an amount followed by its fiat value in brackets (e.g. "50,000 SATS ($31.25)") + * + * Used for the selected total. Falls back to the bitcoin amount on its own when no + * prices are available, so the amount is never followed by empty brackets. + */ + fun `displayAmountWithFiat`(`amount`: Amount, `prices`: PriceResponse?, `currency`: FiatCurrency): kotlin.String + + /** + * Formats the fiat value of an amount on its own (e.g. "$31.25" or "31.25 CHF") + * + * Returns None when no prices are available, so the fiat line can be hidden. + */ + fun `displayFiatAmount`(`amount`: Amount, `prices`: PriceResponse?, `currency`: FiatCurrency): kotlin.String? + companion object } @@ -21624,6 +21653,49 @@ open class RustCoinControlManager: Disposable, AutoCloseable, RustCoinControlMan + /** + * Formats an amount followed by its fiat value in brackets (e.g. "50,000 SATS ($31.25)") + * + * Used for the selected total. Falls back to the bitcoin amount on its own when no + * prices are available, so the amount is never followed by empty brackets. + */override fun `displayAmountWithFiat`(`amount`: Amount, `prices`: PriceResponse?, `currency`: FiatCurrency): kotlin.String { + return FfiConverterString.lift( + callWithHandle { + uniffiRustCall() { _status -> + UniffiLib.uniffi_cove_fn_method_rustcoincontrolmanager_display_amount_with_fiat( + it, + + FfiConverterTypeAmount.lower(`amount`), + FfiConverterOptionalTypePriceResponse.lower(`prices`), + FfiConverterTypeFiatCurrency.lower(`currency`),_status) +} + } + ) + } + + + + /** + * Formats the fiat value of an amount on its own (e.g. "$31.25" or "31.25 CHF") + * + * Returns None when no prices are available, so the fiat line can be hidden. + */override fun `displayFiatAmount`(`amount`: Amount, `prices`: PriceResponse?, `currency`: FiatCurrency): kotlin.String? { + return FfiConverterOptionalString.lift( + callWithHandle { + uniffiRustCall() { _status -> + UniffiLib.uniffi_cove_fn_method_rustcoincontrolmanager_display_fiat_amount( + it, + + FfiConverterTypeAmount.lower(`amount`), + FfiConverterOptionalTypePriceResponse.lower(`prices`), + FfiConverterTypeFiatCurrency.lower(`currency`),_status) +} + } + ) + } + + + @@ -66743,6 +66815,38 @@ public object FfiConverterOptionalTypeMigration: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PriceResponse? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypePriceResponse.read(buf) + } + + override fun allocationSize(value: PriceResponse?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypePriceResponse.allocationSize(value) + } + } + + override fun write(value: PriceResponse?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypePriceResponse.write(value, buf) + } + } +} + + + + /** * @suppress */ diff --git a/ios/Cove/CoinControlManager.swift b/ios/Cove/CoinControlManager.swift index f80bbfb66..202a0309f 100644 --- a/ios/Cove/CoinControlManager.swift +++ b/ios/Cove/CoinControlManager.swift @@ -137,10 +137,6 @@ private enum CoinControlManagerError: LocalizedError { selection.total } - public var totalSelectedAmount: String { - displayAmount(self.totalSelected) - } - public var totalSelectedSats: Int { Int(self.totalSelected.asSats()) } @@ -177,6 +173,16 @@ private enum CoinControlManagerError: LocalizedError { showUnit ? amount.fmtStringWithUnit(unit: unit) : amount.fmtString(unit: unit) } + func displayAmountWithFiat(_ amount: Amount, prices: PriceResponse?, currency: FiatCurrency) -> String { + guard let rust else { return displayAmount(amount) } + + return rust.displayAmountWithFiat(amount: amount, prices: prices, currency: currency) + } + + func displayFiatAmount(_ amount: Amount, prices: PriceResponse?, currency: FiatCurrency) -> String? { + rust?.displayFiatAmount(amount: amount, prices: prices, currency: currency) + } + func reloadLabels() async { guard let rust else { return } diff --git a/ios/Cove/Flows/CoinControlFlow/UtxoListScreen.swift b/ios/Cove/Flows/CoinControlFlow/UtxoListScreen.swift index 279450cd0..c32e83521 100644 --- a/ios/Cove/Flows/CoinControlFlow/UtxoListScreen.swift +++ b/ios/Cove/Flows/CoinControlFlow/UtxoListScreen.swift @@ -299,12 +299,22 @@ private struct UtxoSortButton: View { } private struct UtxoSelectionSection: View { + @Environment(AppManager.self) private var app + let manager: CoinControlManager let onShowTransaction: (Utxo) -> Void @Binding var showLockedSelectionAlert: Bool @Binding var utxoLockUpdateError: String? + private var totalSelectedAmount: String { + manager.displayAmountWithFiat( + manager.totalSelected, + prices: app.prices, + currency: app.selectedFiatCurrency + ) + } + var body: some View { VStack(spacing: 8) { UtxoSelectionHeader( @@ -328,12 +338,12 @@ private struct UtxoSelectionSection: View { .padding(.horizontal) } - Text(manager.totalSelectedAmount) + Text(totalSelectedAmount) .font(.caption2.weight(.semibold)) .foregroundStyle(.secondary) .opacity(manager.selected.isEmpty ? 0 : 0.8) .contentTransition(.numericText()) - .animation(.easeInOut(duration: 0.1), value: manager.totalSelectedAmount) + .animation(.easeInOut(duration: 0.1), value: totalSelectedAmount) } } } @@ -544,6 +554,8 @@ private struct UtxoListToolbarMenu: View { // MARK: - Row private struct UtxoRow: View { + @Environment(AppManager.self) private var app + var manager: CoinControlManager let utxo: Utxo let onLockedSelectionAttempt: () -> Void @@ -590,6 +602,16 @@ private struct UtxoRow: View { .font(.footnote) .fontWeight(.regular) + if let fiat = manager.displayFiatAmount( + utxo.amount, + prices: app.prices, + currency: app.selectedFiatCurrency + ) { + Text(fiat) + .font(.caption) + .foregroundColor(.secondary) + } + Text(utxo.date()) .font(.caption) .foregroundColor(.secondary) @@ -615,6 +637,7 @@ private struct UtxoRow: View { manager: CoinControlManager(RustCoinControlManager.previewNew()) ) .environment(WalletManager(preview: .only)) + .environment(AppManager.shared) } } @@ -626,5 +649,6 @@ private struct UtxoRow: View { ) ) .environment(WalletManager(preview: .only)) + .environment(AppManager.shared) } } diff --git a/ios/CoveCore/Sources/CoveCore/generated/cove.swift b/ios/CoveCore/Sources/CoveCore/generated/cove.swift index 70eea32c0..5a6416baa 100644 --- a/ios/CoveCore/Sources/CoveCore/generated/cove.swift +++ b/ios/CoveCore/Sources/CoveCore/generated/cove.swift @@ -8905,6 +8905,21 @@ public protocol RustCoinControlManagerProtocol: AnyObject, Sendable { func utxos() -> [Utxo] + /** + * Formats an amount followed by its fiat value in brackets (e.g. "50,000 SATS ($31.25)") + * + * Used for the selected total. Falls back to the bitcoin amount on its own when no + * prices are available, so the amount is never followed by empty brackets. + */ + func displayAmountWithFiat(amount: Amount, prices: PriceResponse?, currency: FiatCurrency) -> String + + /** + * Formats the fiat value of an amount on its own (e.g. "$31.25" or "31.25 CHF") + * + * Returns None when no prices are available, so the fiat line can be hidden. + */ + func displayFiatAmount(amount: Amount, prices: PriceResponse?, currency: FiatCurrency) -> String? + } open class RustCoinControlManager: RustCoinControlManagerProtocol, @unchecked Sendable { fileprivate let handle: UInt64 @@ -9079,6 +9094,41 @@ open func utxos() -> [Utxo] { }) } + /** + * Formats an amount followed by its fiat value in brackets (e.g. "50,000 SATS ($31.25)") + * + * Used for the selected total. Falls back to the bitcoin amount on its own when no + * prices are available, so the amount is never followed by empty brackets. + */ +open func displayAmountWithFiat(amount: Amount, prices: PriceResponse?, currency: FiatCurrency) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffiCallStatus in + uniffi_cove_fn_method_rustcoincontrolmanager_display_amount_with_fiat( + self.uniffiCloneHandle(), + FfiConverterTypeAmount_lower(amount), + FfiConverterOptionTypePriceResponse.lower(prices), + FfiConverterTypeFiatCurrency_lower(currency),uniffiCallStatus + ) +}) +} + + /** + * Formats the fiat value of an amount on its own (e.g. "$31.25" or "31.25 CHF") + * + * Returns None when no prices are available, so the fiat line can be hidden. + */ +open func displayFiatAmount(amount: Amount, prices: PriceResponse?, currency: FiatCurrency) -> String? { + return try! FfiConverterOptionString.lift(try! rustCall() { + uniffiCallStatus in + uniffi_cove_fn_method_rustcoincontrolmanager_display_fiat_amount( + self.uniffiCloneHandle(), + FfiConverterTypeAmount_lower(amount), + FfiConverterOptionTypePriceResponse.lower(prices), + FfiConverterTypeFiatCurrency_lower(currency),uniffiCallStatus + ) +}) +} + } @@ -44503,6 +44553,30 @@ fileprivate struct FfiConverterOptionTypeMigration: FfiConverterRustBuffer { } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterOptionTypePriceResponse: FfiConverterRustBuffer { + typealias SwiftType = PriceResponse? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypePriceResponse.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypePriceResponse.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -47629,6 +47703,12 @@ private let initializationResult: InitializationResult = { if (uniffi_cove_checksum_method_rustcoincontrolmanager_utxos() != 43520) { return InitializationResult.apiChecksumMismatch } + if (uniffi_cove_checksum_method_rustcoincontrolmanager_display_amount_with_fiat() != 29447) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_cove_checksum_method_rustcoincontrolmanager_display_fiat_amount() != 8420) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_cove_checksum_method_rustconnectivitymanager_is_connected() != 47607) { return InitializationResult.apiChecksumMismatch } diff --git a/ios/CoveTests/HotWalletCreateScreenLayoutTests.swift b/ios/CoveTests/HotWalletCreateScreenLayoutTests.swift index acc3912cf..266797d90 100644 --- a/ios/CoveTests/HotWalletCreateScreenLayoutTests.swift +++ b/ios/CoveTests/HotWalletCreateScreenLayoutTests.swift @@ -447,6 +447,7 @@ final class HotWalletCreateScreenLayoutTests: XCTestCase { manager: CoinControlManager(RustCoinControlManager.previewNew()) ) .environment(WalletManager(preview: .only)) + .environment(AppManager.shared) } .frame(width: size.width, height: size.height), size: size diff --git a/rust/src/manager/coin_control_manager.rs b/rust/src/manager/coin_control_manager.rs index ff6cb34a9..c78aca75d 100644 --- a/rust/src/manager/coin_control_manager.rs +++ b/rust/src/manager/coin_control_manager.rs @@ -1,3 +1,4 @@ +mod display; mod state; use std::{ diff --git a/rust/src/manager/coin_control_manager/display.rs b/rust/src/manager/coin_control_manager/display.rs new file mode 100644 index 000000000..e95f21028 --- /dev/null +++ b/rust/src/manager/coin_control_manager/display.rs @@ -0,0 +1,133 @@ +use std::sync::Arc; + +use cove_types::amount::Amount; + +use crate::{ + fiat::{FiatCurrency, client::PriceResponse}, + wallet::amount_display, +}; + +use super::RustCoinControlManager; + +#[uniffi::export] +impl RustCoinControlManager { + /// Formats the fiat value of an amount on its own (e.g. "$31.25" or "31.25 CHF") + /// + /// Returns None when no prices are available, so the fiat line can be hidden. + #[uniffi::method] + pub fn display_fiat_amount( + &self, + amount: Arc, + prices: Option>, + currency: FiatCurrency, + ) -> Option { + let prices = prices?; + let fiat = amount_display::convert_amount_to_fiat(&amount, &prices, currency); + Some(amount_display::fmt_fiat_amount(currency, fiat, true)) + } + + /// Formats an amount followed by its fiat value in brackets (e.g. "50,000 SATS ($31.25)") + /// + /// Used for the selected total. Falls back to the bitcoin amount on its own when no + /// prices are available, so the amount is never followed by empty brackets. + #[uniffi::method] + pub fn display_amount_with_fiat( + &self, + amount: Arc, + prices: Option>, + currency: FiatCurrency, + ) -> String { + let unit = self.state.lock().unit; + let bitcoin = amount.fmt_string_with_unit(unit); + + match self.display_fiat_amount(amount, prices, currency) { + Some(fiat) => format!("{bitcoin} ({fiat})"), + None => bitcoin, + } + } +} + +#[cfg(test)] +mod tests { + use cove_types::unit::BitcoinUnit; + + use super::*; + + /// 0.0005 BTC (50,000 sats) is worth 31.25 at this price + fn prices() -> Arc { + Arc::new(PriceResponse { + time: 0, + fetched_at: 0, + usd: 62_500, + eur: 62_500, + gbp: 62_500, + cad: 62_500, + chf: 62_500, + aud: 62_500, + jpy: 62_500, + }) + } + + fn manager(unit: BitcoinUnit) -> RustCoinControlManager { + let manager = RustCoinControlManager::preview_new(1, 0); + manager.state.lock().unit = unit; + + manager + } + + #[test] + fn display_fiat_amount_formats_the_selected_currency() { + let manager = manager(BitcoinUnit::Sat); + let amount = Arc::new(Amount::from_sat(50_000)); + + assert_eq!( + manager.display_fiat_amount(amount.clone(), Some(prices()), FiatCurrency::Usd), + Some("$31.25".to_string()) + ); + assert_eq!( + manager.display_fiat_amount(amount, Some(prices()), FiatCurrency::Chf), + Some("31.25 CHF".to_string()) + ); + } + + #[test] + fn display_fiat_amount_is_none_without_prices() { + let manager = manager(BitcoinUnit::Sat); + let amount = Arc::new(Amount::from_sat(50_000)); + + assert_eq!(manager.display_fiat_amount(amount, None, FiatCurrency::Usd), None); + } + + #[test] + fn display_amount_with_fiat_appends_fiat_value_in_brackets() { + let manager = manager(BitcoinUnit::Sat); + let amount = Arc::new(Amount::from_sat(50_000)); + + assert_eq!( + manager.display_amount_with_fiat(amount, Some(prices()), FiatCurrency::Usd), + "50,000 SATS ($31.25)" + ); + } + + #[test] + fn display_amount_with_fiat_uses_the_selected_unit_and_currency() { + let manager = manager(BitcoinUnit::Btc); + let amount = Arc::new(Amount::from_sat(50_000)); + + assert_eq!( + manager.display_amount_with_fiat(amount, Some(prices()), FiatCurrency::Chf), + "0.0005 BTC (31.25 CHF)" + ); + } + + #[test] + fn display_amount_with_fiat_omits_brackets_without_prices() { + let manager = manager(BitcoinUnit::Sat); + let amount = Arc::new(Amount::from_sat(50_000)); + + assert_eq!( + manager.display_amount_with_fiat(amount, None, FiatCurrency::Usd), + "50,000 SATS" + ); + } +} diff --git a/rust/src/manager/wallet_manager/display.rs b/rust/src/manager/wallet_manager/display.rs index 6655618d7..e8c99476e 100644 --- a/rust/src/manager/wallet_manager/display.rs +++ b/rust/src/manager/wallet_manager/display.rs @@ -111,8 +111,8 @@ impl RustWalletManager { #[uniffi::method] pub fn convert_to_fiat(&self, amount: Arc, prices: Arc) -> f64 { let currency = self.selected_fiat_currency(); - let price = prices.get_for_currency(currency) as f64; - ((amount.as_btc() * price) * 100.0).ceil() / 100.0 + + amount_display::convert_amount_to_fiat(&amount, &prices, currency) } #[uniffi::method(default(with_suffix = true))] diff --git a/rust/src/wallet/amount_display.rs b/rust/src/wallet/amount_display.rs index 17f932a56..2bd42e109 100644 --- a/rust/src/wallet/amount_display.rs +++ b/rust/src/wallet/amount_display.rs @@ -4,7 +4,10 @@ use cove_util::format::NumberFormatter as _; use crate::{ database::Database, - fiat::{FiatCurrency, client::FIAT_CLIENT}, + fiat::{ + FiatCurrency, + client::{FIAT_CLIENT, PriceResponse}, + }, transaction::{Amount, SentAndReceived, TransactionDirection, Unit}, wallet::metadata::WalletMetadata, }; @@ -144,6 +147,22 @@ pub(crate) fn display_fiat_amount_with_currency( return FIAT_MASK.to_string(); } + fmt_fiat_amount(currency, amount, with_suffix) +} + +/// Converts a bitcoin amount into its fiat value, rounded up to the nearest cent +pub(crate) fn convert_amount_to_fiat( + amount: &Amount, + prices: &PriceResponse, + currency: FiatCurrency, +) -> f64 { + let price = prices.get_for_currency(currency) as f64; + + ((amount.as_btc() * price) * 100.0).ceil() / 100.0 +} + +/// Formats a fiat value with its currency symbol and suffix, e.g. "$31.25" or "31.25 CHF" +pub(crate) fn fmt_fiat_amount(currency: FiatCurrency, amount: f64, with_suffix: bool) -> String { let fiat = amount.thousands_fiat(); let symbol = currency.symbol(); let suffix = currency.suffix();