diff --git a/Bitkit/AppScene.swift b/Bitkit/AppScene.swift index dff8a3719..2b6e5b23c 100644 --- a/Bitkit/AppScene.swift +++ b/Bitkit/AppScene.swift @@ -10,10 +10,11 @@ struct AppScene: View { @StateObject private var navigation = NavigationViewModel() @StateObject private var network = NetworkMonitor() @StateObject private var sheets = SheetViewModel() - @StateObject private var wallet = WalletViewModel() + @StateObject private var wallet: WalletViewModel @StateObject private var currency = CurrencyViewModel() @StateObject private var blocktank = BlocktankViewModel() - @StateObject private var activity = ActivityListViewModel() + @StateObject private var activity: ActivityListViewModel + @StateObject private var feeEstimatesManager: FeeEstimatesManager @StateObject private var transfer: TransferViewModel @StateObject private var widgets = WidgetsViewModel() @StateObject private var pushManager = PushNotificationManager.shared @@ -48,10 +49,16 @@ struct AppScene: View { _app = StateObject(wrappedValue: AppViewModel(sheetViewModel: sheetViewModel, navigationViewModel: navigationViewModel)) _sheets = StateObject(wrappedValue: sheetViewModel) _navigation = StateObject(wrappedValue: navigationViewModel) - let walletVm = WalletViewModel(transferService: transferService, sheetViewModel: sheetViewModel) + let feeEstimatesManager = FeeEstimatesManager() + let walletVm = WalletViewModel( + transferService: transferService, + sheetViewModel: sheetViewModel, + feeEstimatesManager: feeEstimatesManager + ) _wallet = StateObject(wrappedValue: walletVm) _currency = StateObject(wrappedValue: CurrencyViewModel()) _blocktank = StateObject(wrappedValue: BlocktankViewModel()) + _feeEstimatesManager = StateObject(wrappedValue: feeEstimatesManager) _activity = StateObject(wrappedValue: ActivityListViewModel(transferService: transferService)) _transfer = StateObject(wrappedValue: TransferViewModel( transferService: transferService, @@ -112,6 +119,7 @@ struct AppScene: View { .environmentObject(wallet) .environmentObject(currency) .environmentObject(blocktank) + .environmentObject(feeEstimatesManager) .environmentObject(activity) .environmentObject(transfer) .environmentObject(widgets) diff --git a/Bitkit/Assets.xcassets/icons/clock-clockwise.imageset/Contents.json b/Bitkit/Assets.xcassets/icons/clock-clockwise.imageset/Contents.json new file mode 100644 index 000000000..a636b044c --- /dev/null +++ b/Bitkit/Assets.xcassets/icons/clock-clockwise.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "clock-clockwise.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Bitkit/Assets.xcassets/icons/clock-clockwise.imageset/clock-clockwise.pdf b/Bitkit/Assets.xcassets/icons/clock-clockwise.imageset/clock-clockwise.pdf new file mode 100644 index 000000000..25cdf619d Binary files /dev/null and b/Bitkit/Assets.xcassets/icons/clock-clockwise.imageset/clock-clockwise.pdf differ diff --git a/Bitkit/Components/Activity/ActivityList.swift b/Bitkit/Components/Activity/ActivityList.swift index b1de91b6a..00bcf0405 100644 --- a/Bitkit/Components/Activity/ActivityList.swift +++ b/Bitkit/Components/Activity/ActivityList.swift @@ -3,6 +3,7 @@ import SwiftUI struct ActivityList: View { @EnvironmentObject var activity: ActivityListViewModel + @EnvironmentObject var feeEstimatesManager: FeeEstimatesManager @State private var isHorizontalSwipe = false let viewType: ActivityViewType @@ -27,7 +28,7 @@ struct ActivityList: View { case let .activity(item): NavigationLink(value: Route.activityDetail(item)) { - ActivityRow(item: item, feeEstimates: activity.feeEstimates) + ActivityRow(item: item, feeEstimates: feeEstimatesManager.estimates) } .accessibilityIdentifier("Activity-\(index)") .disabled(isHorizontalSwipe) diff --git a/Bitkit/Components/FeeItem.swift b/Bitkit/Components/FeeItem.swift index 2cd4f5104..eb3c12e79 100644 --- a/Bitkit/Components/FeeItem.swift +++ b/Bitkit/Components/FeeItem.swift @@ -6,10 +6,16 @@ struct FeeItem: View { let amount: UInt64 let isSelected: Bool let isDisabled: Bool + /// When set (e.g. for custom speed with fee estimates), shown instead of `speed.range` as the subtitle. + var rangeOverride: String? let onPress: () -> Void @EnvironmentObject var currency: CurrencyViewModel + private var rangeText: String { + rangeOverride ?? speed.range + } + var body: some View { VStack(spacing: 0) { Divider() @@ -23,8 +29,8 @@ struct FeeItem: View { HStack { VStack(alignment: .leading, spacing: 0) { - BodyMSBText(speed.displayTitle, textColor: isDisabled ? .gray3 : .textPrimary) - BodySSBText(speed.displayDescription, textColor: isDisabled ? .gray3 : .textSecondary) + BodyMSBText(speed.title, textColor: isDisabled ? .gray3 : .textPrimary) + BodySSBText(rangeText, textColor: isDisabled ? .gray3 : .textSecondary) } Spacer() diff --git a/Bitkit/Components/MoneyText.swift b/Bitkit/Components/MoneyText.swift index ea2b1b34c..66dd49452 100644 --- a/Bitkit/Components/MoneyText.swift +++ b/Bitkit/Components/MoneyText.swift @@ -19,6 +19,10 @@ enum MoneyUnitType { struct MoneyText: View { let sats: Int var unitType: MoneyUnitType = .primary + /// When set, overrides user preference so the value is always shown in this unit. + var forceUnit: PrimaryDisplay? + /// When set, overrides user preference so the value is always shown in this denomination. + var forceDisplayUnit: BitcoinDisplayUnit? var size: MoneySize = .display var symbol: Bool? var enableHide: Bool = false @@ -33,7 +37,8 @@ struct MoneyText: View { // MARK: - Computed Properties private var unit: PrimaryDisplay { - unitType == .secondary ? (currency.primaryDisplay == .bitcoin ? .fiat : .bitcoin) : currency.primaryDisplay + if let forceUnit { return forceUnit } + return unitType == .secondary ? (currency.primaryDisplay == .bitcoin ? .fiat : .bitcoin) : currency.primaryDisplay } private var showSymbol: Bool { @@ -138,7 +143,8 @@ extension MoneyText { case .fiat: return converted.formatted case .bitcoin: - return converted.bitcoinDisplay(unit: currency.displayUnit).value + let displayUnit = forceDisplayUnit ?? currency.displayUnit + return converted.bitcoinDisplay(unit: displayUnit).value } } diff --git a/Bitkit/Managers/FeeEstimatesManager.swift b/Bitkit/Managers/FeeEstimatesManager.swift new file mode 100644 index 000000000..9749a50ad --- /dev/null +++ b/Bitkit/Managers/FeeEstimatesManager.swift @@ -0,0 +1,40 @@ +import BitkitCore +import Foundation +import SwiftUI + +/// Single source of truth for on-chain fee estimates (fast/mid/slow). Fetches and caches +/// When the dev "override fees" setting is on, returns fixed rates for UI work without hitting the backend. +@MainActor +final class FeeEstimatesManager: ObservableObject { + @Published private(set) var estimates: FeeRates? + + /// Dev setting: use hardcoded fee rates for UI development. Toggle is in Dev settings (regtest). + @AppStorage("devOverrideFeeEstimates") var devOverrideFeeEstimates = false + + private let coreService: CoreService + + init(coreService: CoreService = .shared) { + self.coreService = coreService + } + + /// Fetches fee rates and updates the cache. + /// - Parameter refresh: If true, forces a fresh fetch; otherwise may use backend cache. + /// - Returns: Current fee rates, or nil if unavailable. + @discardableResult + func getEstimates(refresh: Bool = false) async -> FeeRates? { + if devOverrideFeeEstimates { + let rates = FeeRates(fast: 10, mid: 7, slow: 3) + estimates = rates + return rates + } + + do { + let rates = try await coreService.blocktank.fees(refresh: refresh) + estimates = rates + return rates + } catch { + Logger.error("Failed to get fee estimates: \(error)", context: "FeeEstimatesManager") + return nil + } + } +} diff --git a/Bitkit/Models/TransactionSpeed.swift b/Bitkit/Models/TransactionSpeed.swift index d0b553f9b..9f06c0b0c 100644 --- a/Bitkit/Models/TransactionSpeed.swift +++ b/Bitkit/Models/TransactionSpeed.swift @@ -42,55 +42,33 @@ public enum TransactionSpeed: Equatable, Hashable, RawRepresentable { // MARK: - Display Properties public extension TransactionSpeed { - var displayTitle: String { + /// Component used to build fee localization keys (e.g. "fee__fast__title", "fee__fast__longTitle"). + var feeKeyComponent: String { switch self { - case .fast: return t("fee__fast__title") - case .normal: return t("fee__normal__title") - case .slow: return t("fee__slow__title") - case .custom: return t("fee__custom__title") + case .fast: return "fast" + case .normal: return "normal" + case .slow: return "slow" + case .custom: return "custom" } } - var displayDescription: String { - switch self { - case .fast: return t("fee__fast__description") - case .normal: return t("fee__normal__description") - case .slow: return t("fee__slow__description") - case .custom: return t("fee__custom__description") - } + var title: String { t("fee__\(feeKeyComponent)__title") } + var longTitle: String { t("fee__\(feeKeyComponent)__longTitle") } + var description: String { t("fee__\(feeKeyComponent)__description") } + var shortDescription: String { t("fee__\(feeKeyComponent)__shortDescription") } + var range: String { t("fee__\(feeKeyComponent)__range") } + var longRange: String { t("fee__\(feeKeyComponent)__longRange") } + + var isCustom: Bool { + if case .custom = self { return true } + return false } var customSetSpeed: String? { guard case let .custom(satsPerVByte) = self else { return nil } return "\(satsPerVByte) \(t("common__sat_vbyte_compact"))" } -} - -// MARK: - Settings Display Properties - -public extension TransactionSpeed { - var displayLabel: String { - switch self { - case .fast: return t("settings__fee__fast__label") - case .normal: return t("settings__fee__normal__label") - case .slow: return t("settings__fee__slow__label") - case .custom: return t("settings__fee__custom__label") - } - } - - var displayValue: String { - switch self { - case .fast: return t("settings__fee__fast__value") - case .normal: return t("settings__fee__normal__value") - case .slow: return t("settings__fee__slow__value") - case .custom: return t("settings__fee__custom__value") - } - } -} - -// MARK: - UI Properties -public extension TransactionSpeed { var iconName: String { switch self { case .fast: return "speed-fast" @@ -113,6 +91,16 @@ public extension TransactionSpeed { // MARK: - Business Logic public extension TransactionSpeed { + /// Key suffix for fee tier localization (matches "fee__{tier}__{variant}" in Localizable.strings). + enum FeeTierVariant: String { + case title + case longTitle + case description + case shortDescription + case range + case longRange + } + /// Returns the fee rate in satoshis per virtual byte for this speed /// - Parameter feeRates: Current network fee rates /// - Returns: Fee rate in sat/vB @@ -135,51 +123,18 @@ public extension TransactionSpeed { return rate >= minRate && rate <= maxRate } - /// Determines the appropriate fee description for a given fee rate - /// - Parameters: - /// - feeRate: The fee rate in satoshis per virtual byte - /// - feeEstimates: Current network fee estimates - /// - Returns: Localized fee description string - static func getFeeDescription(feeRate: UInt64, feeEstimates: FeeRates) -> String { - // Check against fee estimates in order of priority (highest to lowest) - if feeRate >= UInt64(feeEstimates.fast) { - return t("fee__fast__shortDescription") - } else if feeRate >= UInt64(feeEstimates.mid) { - return t("fee__normal__shortDescription") - } else if feeRate >= UInt64(feeEstimates.slow) { - return t("fee__slow__shortDescription") - } else { - // For rates below slow, use minimum - return t("fee__minimum__shortDescription") - } + /// Tier derived from a fee rate and current estimates (fast/normal/slow/minimum). Use with fee localization keys or getFeeTierLocalized. + static func feeTierKeyComponent(for feeRate: UInt64, feeEstimates: FeeRates?) -> String { + guard let estimates = feeEstimates else { return "normal" } + if feeRate >= UInt64(estimates.fast) { return "fast" } + if feeRate >= UInt64(estimates.mid) { return "normal" } + if feeRate >= UInt64(estimates.slow) { return "slow" } + return "minimum" } - /// Determines the appropriate fee description for a given fee rate with fallback - /// - Parameters: - /// - feeRate: The fee rate in satoshis per virtual byte - /// - feeEstimates: Current network fee estimates (optional) - /// - Returns: Localized fee description string with fallback - static func getFeeDescription(feeRate: UInt64, feeEstimates: FeeRates?) -> String { - guard let estimates = feeEstimates else { - // Fallback when no fee estimates are available - return t("fee__normal__shortDescription") - } - - return getFeeDescription(feeRate: feeRate, feeEstimates: estimates) - } -} - -// MARK: - Equatable Implementation - -public extension TransactionSpeed { - static func == (lhs: TransactionSpeed, rhs: TransactionSpeed) -> Bool { - switch (lhs, rhs) { - case (.fast, .fast), (.normal, .normal), (.slow, .slow): - return true - case let (.custom(lhsRate), .custom(rhsRate)): - return lhsRate == rhsRate - default: - return false - } + /// Returns the localized string for a fee rate's tier and the given variant (e.g. title, description, shortDescription). + static func getFeeTierLocalized(feeRate: UInt64, feeEstimates: FeeRates?, variant: FeeTierVariant) -> String { + let tier = feeTierKeyComponent(for: feeRate, feeEstimates: feeEstimates) + return t("fee__\(tier)__\(variant.rawValue)") } } diff --git a/Bitkit/Resources/Localization/ca.lproj/Localizable.strings b/Bitkit/Resources/Localization/ca.lproj/Localizable.strings index 405a03b15..6d82522dc 100644 --- a/Bitkit/Resources/Localization/ca.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/ca.lproj/Localizable.strings @@ -17,7 +17,7 @@ "cards__lightningReady__title" = "Llest"; "cards__lightningReady__description" = "Conectat!"; "cards__transferPending__title" = "Transferència"; -"cards__transferPending__description" = "Llest en ±{duration}"; +"cards__transferPending__description" = "Llest en {duration}"; "cards__transferClosingChannel__title" = "Initialisation"; "cards__transferClosingChannel__description" = "Mantenir l\'aplicació oberta"; "cards__pin__title" = "Segur"; @@ -99,6 +99,14 @@ "fee__custom__description" = "Depèn de la tarifa"; "fee__custom__shortRange" = "Depèn de la tarifa"; "fee__custom__shortDescription" = "Depèn de la tarifa"; +"fee__fast__longTitle" = "Ràpid (més car)"; +"fee__fast__longRange" = "± 10-20 minuts"; +"fee__normal__longTitle" = "Normal"; +"fee__normal__longRange" = "± 20-60 minuts"; +"fee__slow__longTitle" = "Lent (més barat)"; +"fee__slow__longRange" = "± 1-2 hores"; +"fee__custom__longTitle" = "Personalitzat"; +"fee__custom__longRange" = "Depèn de la tarifa"; "lightning__transfer_intro__title" = "Saldo de\ndespesa"; "lightning__transfer_intro__text" = "Finança el teu saldo de despesa per gaudir de transaccions instantànies i barates amb amics, família i comerciants."; "lightning__transfer_intro__button" = "Començar"; @@ -667,17 +675,6 @@ "settings__adv__electrum_server" = "Servidor Electrum"; "settings__adv__rgs_server" = "Rapid-Gossip-Sync"; "settings__adv__bitcoin_network" = "Xarxa Bitcoin"; -"settings__fee__fast__label" = "Ràpid (més car)"; -"settings__fee__fast__value" = "Ràpid"; -"settings__fee__fast__description" = "± 10-20 minuts"; -"settings__fee__normal__label" = "Normal"; -"settings__fee__normal__value" = "Normal"; -"settings__fee__normal__description" = "± 20-60 minuts"; -"settings__fee__slow__label" = "Lent (més barat)"; -"settings__fee__slow__value" = "Lent"; -"settings__fee__slow__description" = "± 1-2 hores"; -"settings__fee__custom__label" = "Personalitzat"; -"settings__fee__custom__value" = "Personalitzat"; "settings__addr__no_addrs_with_funds" = "No s\'han trobat adreces amb fons en cercar \"{searchTxt}\""; "settings__addr__no_addrs_str" = "No s\'han trobat adreces en cercar \"{searchTxt}\""; "settings__addr__index" = "Index: {index}"; @@ -894,9 +891,9 @@ "wallet__activity_pending" = "Pendent"; "wallet__activity_failed" = "Fallit"; "wallet__activity_transfer" = "Transferència"; -"wallet__activity_transfer_savings_pending" = "Des de despesa (±{duration})"; +"wallet__activity_transfer_savings_pending" = "Des de despesa ({duration})"; "wallet__activity_transfer_savings_done" = "Des de despesa"; -"wallet__activity_transfer_spending_pending" = "Des d\'estalvis (±{duration})"; +"wallet__activity_transfer_spending_pending" = "Des d\'estalvis ({duration})"; "wallet__activity_transfer_spending_done" = "Des d\'estalvis"; "wallet__activity_transfer_to_spending" = "A despesa"; "wallet__activity_transfer_to_savings" = "A estalvis"; diff --git a/Bitkit/Resources/Localization/cs.lproj/Localizable.strings b/Bitkit/Resources/Localization/cs.lproj/Localizable.strings index 1844973aa..e665f7a59 100644 --- a/Bitkit/Resources/Localization/cs.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/cs.lproj/Localizable.strings @@ -17,7 +17,7 @@ "cards__lightningReady__title" = "Připraveno"; "cards__lightningReady__description" = "Připojeno!"; "cards__transferPending__title" = "Převod"; -"cards__transferPending__description" = "Připraveno za ±{duration}"; +"cards__transferPending__description" = "Připraveno za {duration}"; "cards__transferClosingChannel__title" = "Zahájení"; "cards__transferClosingChannel__description" = "Nechte aplikaci otevřenou"; "cards__pin__title" = "Zabezpečit"; @@ -80,25 +80,33 @@ "fee__instant__shortRange" = "2-10s"; "fee__instant__shortDescription" = "+/- 2s"; "fee__fast__title" = "Rychle"; +"fee__fast__longTitle" = "Rychlá (dražší)"; "fee__fast__description" = "+/- 10-20 minut"; -"fee__fast__shortRange" = "10-20m"; "fee__fast__shortDescription" = "+/- 10m"; +"fee__fast__shortRange" = "10-20m"; +"fee__fast__longRange" = "± 10-20 minut"; "fee__normal__title" = "Standardní"; +"fee__normal__longTitle" = "Standardní"; "fee__normal__description" = "+/- 20-60 minut"; -"fee__normal__shortRange" = "20-60m"; "fee__normal__shortDescription" = "+/- 20m"; +"fee__normal__shortRange" = "20-60m"; +"fee__normal__longRange" = "± 20-60 minut"; "fee__slow__title" = "Pomalu"; +"fee__slow__longTitle" = "Pomalá (levnější)"; "fee__slow__description" = "+/- 1-2 hodiny"; -"fee__slow__shortRange" = "1-2h"; "fee__slow__shortDescription" = "+/- 1h"; +"fee__slow__shortRange" = "1-2h"; +"fee__slow__longRange" = "± 1-2 hodiny"; "fee__minimum__title" = "Minimum"; "fee__minimum__description" = "+2 hodiny"; "fee__minimum__shortRange" = "+2h"; "fee__minimum__shortDescription" = "+2h"; "fee__custom__title" = "Vlastní"; +"fee__custom__longTitle" = "Vlastní"; "fee__custom__description" = "Odvíjí se od poplatku"; -"fee__custom__shortRange" = "Odvíjí se od poplatku"; "fee__custom__shortDescription" = "Odvíjí se od poplatku"; +"fee__custom__shortRange" = "Odvíjí se od poplatku"; +"fee__custom__longRange" = "Odvíjí se od poplatku"; "lightning__transfer_intro__title" = "Disponibilní\nZůstatek"; "lightning__transfer_intro__text" = "Navyšte svůj disponibilní zůstatek a užívejte si okamžité a levné transakce s přáteli, rodinou a obchodníky."; "lightning__transfer_intro__button" = "Začít"; @@ -752,18 +760,6 @@ "settings__adv__rgs_server" = "Rapid-Gossip-Sync"; "settings__adv__web_relay" = "Slashtags web přenos"; "settings__adv__bitcoin_network" = "bitcoinová síť"; -"settings__fee__fast__label" = "Rychlá (dražší)"; -"settings__fee__fast__value" = "Rychle"; -"settings__fee__fast__description" = "± 10-20 minut"; -"settings__fee__normal__label" = "Standardní"; -"settings__fee__normal__value" = "Standardní"; -"settings__fee__normal__description" = "± 20-60 minut"; -"settings__fee__slow__label" = "Pomalá (levnější)"; -"settings__fee__slow__value" = "Pomalu"; -"settings__fee__slow__description" = "± 1-2 hodiny"; -"settings__fee__custom__label" = "Vlastní"; -"settings__fee__custom__value" = "Vlastní"; -"settings__fee__custom__description" = "Odvíjí se od poplatku"; "settings__addr__no_addrs" = "Žádné adresy k zobrazení"; "settings__addr__loading" = "Nahrávání adres..."; "settings__addr__no_funds_receiving" = "Pod typem adresy {addressType} nebyly nalezeny žádné prostředky, přijímající adresy do indexu {index}."; @@ -1030,12 +1026,12 @@ "wallet__activity_pending" = "Zpracovávání"; "wallet__activity_failed" = "Selhání"; "wallet__activity_transfer" = "Převod"; -"wallet__activity_transfer_savings_pending" = "Z disponibilního zůstatku (±{duration})"; +"wallet__activity_transfer_savings_pending" = "Z disponibilního zůstatku ({duration})"; "wallet__activity_transfer_savings_done" = "Z útraty"; -"wallet__activity_transfer_spending_pending" = "Z úspor (±{duration})"; +"wallet__activity_transfer_spending_pending" = "Z úspor ({duration})"; "wallet__activity_transfer_spending_done" = "Z úspor"; "wallet__activity_transfer_to_spending" = "Do útrat"; -"wallet__activity_transfer_pending" = "Převod (±{duration})"; +"wallet__activity_transfer_pending" = "Převod ({duration})"; "wallet__activity_transfer_to_savings" = "Do úspor"; "wallet__activity_confirms_in" = "Potvrzení během {feeRateDescription}"; "wallet__activity_confirms_in_boosted" = "Posílení. Potvrzení během {feeRateDescription}"; diff --git a/Bitkit/Resources/Localization/de.lproj/Localizable.strings b/Bitkit/Resources/Localization/de.lproj/Localizable.strings index 89415cb11..3c3f6fb75 100644 --- a/Bitkit/Resources/Localization/de.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/de.lproj/Localizable.strings @@ -17,7 +17,7 @@ "cards__lightningReady__title" = "Bereit"; "cards__lightningReady__description" = "Verbunden!"; "cards__transferPending__title" = "Übertragung"; -"cards__transferPending__description" = "Bereit in ±{duration}"; +"cards__transferPending__description" = "Bereit in {duration}"; "cards__transferClosingChannel__title" = "Initiieren"; "cards__transferClosingChannel__description" = "App geöffnet lassen"; "cards__pin__title" = "Sichern"; @@ -80,25 +80,33 @@ "fee__instant__shortRange" = "2-10s"; "fee__instant__shortDescription" = "±2s"; "fee__fast__title" = "Schnell"; +"fee__fast__longTitle" = "Schnell (teurer)"; "fee__fast__description" = "±10-20 Minuten"; -"fee__fast__shortRange" = "10-20m"; "fee__fast__shortDescription" = "±10m"; +"fee__fast__shortRange" = "10-20m"; +"fee__fast__longRange" = "± 10-20 Minuten"; "fee__normal__title" = "Normal"; +"fee__normal__longTitle" = "Normal"; "fee__normal__description" = "±20-60 Minuten"; -"fee__normal__shortRange" = "20-60m"; "fee__normal__shortDescription" = "±20m"; +"fee__normal__shortRange" = "20-60m"; +"fee__normal__longRange" = "± 20-60 Minuten"; "fee__slow__title" = "Langsam"; +"fee__slow__longTitle" = "Langsam (günstiger)"; "fee__slow__description" = "±1-2 Stunden"; -"fee__slow__shortRange" = "1-2h"; "fee__slow__shortDescription" = "±1h"; +"fee__slow__shortRange" = "1-2h"; +"fee__slow__longRange" = "± 1-2 Stunden"; "fee__minimum__title" = "Minimum"; "fee__minimum__description" = "+2 Stunden"; "fee__minimum__shortRange" = "+2h"; "fee__minimum__shortDescription" = "+2h"; "fee__custom__title" = "Benutzerdefiniert"; +"fee__custom__longTitle" = "Benutzerdefiniert"; "fee__custom__description" = "Abhängig von der Gebühr"; -"fee__custom__shortRange" = "Abhängig von der Gebühr"; "fee__custom__shortDescription" = "Abhängig von der Gebühr"; +"fee__custom__shortRange" = "Abhängig von der Gebühr"; +"fee__custom__longRange" = "Abhängig von der Gebühr"; "lightning__transfer_intro__title" = "Spending\nBalance"; "lightning__transfer_intro__text" = "Fülle dein Guthaben auf, um sofortige und günstige Transaktionen mit Freunden, Familie und Händlern zu genießen."; "lightning__transfer_intro__button" = "Los geht\'s"; @@ -750,18 +758,6 @@ "settings__adv__rgs_server" = "Rapid-Gossip-Sync"; "settings__adv__web_relay" = "Slashtags Web-Relay"; "settings__adv__bitcoin_network" = "Bitcoin-Netzwerk"; -"settings__fee__fast__label" = "Schnell (teurer)"; -"settings__fee__fast__value" = "Schnell"; -"settings__fee__fast__description" = "± 10-20 Minuten"; -"settings__fee__normal__label" = "Normal"; -"settings__fee__normal__value" = "Normal"; -"settings__fee__normal__description" = "± 20-60 Minuten"; -"settings__fee__slow__label" = "Langsam (günstiger)"; -"settings__fee__slow__value" = "Langsam"; -"settings__fee__slow__description" = "± 1-2 Stunden"; -"settings__fee__custom__label" = "Benutzerdefiniert"; -"settings__fee__custom__value" = "Benutzerdefiniert"; -"settings__fee__custom__description" = "Abhängig von der Gebühr"; "settings__addr__no_addrs" = "Keine Adressen zum Anzeigen"; "settings__addr__loading" = "Lade Adressen..."; "settings__addr__no_funds_receiving" = "Keine Gelder unter dem {addressType} Adresstyp gefunden, Empfangsadressen bis Index {index}."; @@ -1028,12 +1024,12 @@ "wallet__activity_pending" = "Ausstehend"; "wallet__activity_failed" = "Fehlgeschlagen"; "wallet__activity_transfer" = "Übertragung"; -"wallet__activity_transfer_savings_pending" = "Von Ausgabenkonto (±{duration})"; +"wallet__activity_transfer_savings_pending" = "Von Ausgabenkonto ({duration})"; "wallet__activity_transfer_savings_done" = "Von Ausgabenkonto"; -"wallet__activity_transfer_spending_pending" = "Von Sparkonto (±{duration})"; +"wallet__activity_transfer_spending_pending" = "Von Sparkonto ({duration})"; "wallet__activity_transfer_spending_done" = "Von Sparkonto"; "wallet__activity_transfer_to_spending" = "Zum Ausgabenkonto"; -"wallet__activity_transfer_pending" = "Übertragung (±{duration})"; +"wallet__activity_transfer_pending" = "Übertragung ({duration})"; "wallet__activity_transfer_to_savings" = "Zu Ersparnissen"; "wallet__activity_confirms_in" = "Bestätigungen in {feeRateDescription}"; "wallet__activity_confirms_in_boosted" = "Beschleunigung. Bestätigungen in {feeRateDescription}"; diff --git a/Bitkit/Resources/Localization/el.lproj/Localizable.strings b/Bitkit/Resources/Localization/el.lproj/Localizable.strings index 205f9b93b..e0f50ee0c 100644 --- a/Bitkit/Resources/Localization/el.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/el.lproj/Localizable.strings @@ -78,25 +78,33 @@ "fee__instant__shortRange" = "2-10 δευτ."; "fee__instant__shortDescription" = "±2 δευτ."; "fee__fast__title" = "Γρήγορα"; +"fee__fast__longTitle" = "Γρήγορα (πιο ακριβό)"; "fee__fast__description" = "±10-20 λεπτά"; -"fee__fast__shortRange" = "10-20 λεπτά"; "fee__fast__shortDescription" = "±10 λεπτά"; +"fee__fast__shortRange" = "10-20 λεπτά"; +"fee__fast__longRange" = "± 10-20 λεπτά"; "fee__normal__title" = "Κανονικά"; +"fee__normal__longTitle" = "Κανονικά"; "fee__normal__description" = "±20-60 λεπτά"; -"fee__normal__shortRange" = "20-60 λεπτά"; "fee__normal__shortDescription" = "±20 λεπτά"; +"fee__normal__shortRange" = "20-60 λεπτά"; +"fee__normal__longRange" = "± 20-60 λεπτά"; "fee__slow__title" = "Αργά"; +"fee__slow__longTitle" = "Αργά (φθηνότερο)"; "fee__slow__description" = "±1-2 ώρες"; -"fee__slow__shortRange" = "1-2 ώρες"; "fee__slow__shortDescription" = "±1 ώρα"; +"fee__slow__shortRange" = "1-2 ώρες"; +"fee__slow__longRange" = "± 1-2 ώρες"; "fee__minimum__title" = "Ελάχιστο"; "fee__minimum__description" = "+2 ώρες"; "fee__minimum__shortRange" = "+2 ώρες"; "fee__minimum__shortDescription" = "+2 ώρες"; "fee__custom__title" = "Προσαρμοσμένο"; +"fee__custom__longTitle" = "Προσαρμοσμένο"; "fee__custom__description" = "Εξαρτάται από το τέλος"; -"fee__custom__shortRange" = "Εξαρτάται από το τέλος"; "fee__custom__shortDescription" = "Εξαρτάται από το τέλος"; +"fee__custom__shortRange" = "Εξαρτάται από το τέλος"; +"fee__custom__longRange" = "Εξαρτάται από την προμήθεια"; "lightning__transfer_intro__title" = "Υπόλοιπο\nΔαπανών"; "lightning__transfer_intro__text" = "Χρηματοδοτήστε το υπόλοιπο δαπανών σας για να απολαύσετε άμεσες και φθηνές συναλλαγές με φίλους, οικογένεια και εμπόρους."; "lightning__transfer_intro__button" = "Ξεκινήστε"; @@ -609,15 +617,6 @@ "settings__adv__lightning_node" = "Κόμβος Lightning"; "settings__adv__electrum_server" = "Διακομιστής Electrum"; "settings__adv__rgs_server" = "Rapid-Gossip-Sync"; -"settings__fee__fast__label" = "Γρήγορα (πιο ακριβό)"; -"settings__fee__fast__value" = "Γρήγορα"; -"settings__fee__fast__description" = "± 10-20 λεπτά"; -"settings__fee__normal__label" = "Κανονικά"; -"settings__fee__normal__value" = "Κανονικά"; -"settings__fee__slow__label" = "Αργά (φθηνότερο)"; -"settings__fee__slow__value" = "Αργά"; -"settings__fee__custom__label" = "Προσαρμοσμένο"; -"settings__fee__custom__value" = "Προσαρμοσμένο"; "settings__addr__no_addrs_with_funds" = "Δεν βρέθηκαν διευθύνσεις με κεφάλαια κατά την αναζήτηση \"{searchTxt}\""; "settings__addr__no_addrs_str" = "Δεν βρέθηκαν διευθύνσεις κατά την αναζήτηση \"{searchTxt}\""; "settings__addr__check_balances" = "Έλεγχος Υπολοίπων"; @@ -762,9 +761,9 @@ "wallet__activity_pending" = "Εκκρεμεί"; "wallet__activity_failed" = "Απέτυχε"; "wallet__activity_transfer" = "Μεταφορά"; -"wallet__activity_transfer_savings_pending" = "Από Δαπάνες (±{duration})"; +"wallet__activity_transfer_savings_pending" = "Από Δαπάνες ({duration})"; "wallet__activity_transfer_savings_done" = "Από Δαπάνες"; -"wallet__activity_transfer_spending_pending" = "Από Αποταμιεύσεις (±{duration})"; +"wallet__activity_transfer_spending_pending" = "Από Αποταμιεύσεις ({duration})"; "wallet__activity_transfer_spending_done" = "Από Αποταμιεύσεις"; "wallet__activity_transfer_to_spending" = "Στις Δαπάνες"; "wallet__activity_transfer_to_savings" = "Στις Αποταμιεύσεις"; diff --git a/Bitkit/Resources/Localization/en.lproj/Localizable.strings b/Bitkit/Resources/Localization/en.lproj/Localizable.strings index c297be4f6..3eb2ce402 100644 --- a/Bitkit/Resources/Localization/en.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/en.lproj/Localizable.strings @@ -18,7 +18,7 @@ "cards__lightningReady__title" = "Ready"; "cards__lightningReady__description" = "Connected!"; "cards__transferPending__title" = "Transfer"; -"cards__transferPending__description" = "Ready in ±{duration}"; +"cards__transferPending__description" = "Ready in {duration}"; "cards__transferClosingChannel__title" = "Initiating"; "cards__transferClosingChannel__description" = "Keep app open"; "cards__pin__title" = "Secure"; @@ -83,29 +83,39 @@ "common__show_details" = "Show Details"; "common__success" = "Success"; "fee__instant__title" = "Instant"; -"fee__instant__description" = "±2-10 seconds"; -"fee__instant__shortRange" = "2-10s"; +"fee__instant__description" = "±2 seconds"; "fee__instant__shortDescription" = "±2s"; +"fee__instant__range" = "±2-10 seconds"; "fee__fast__title" = "Fast"; -"fee__fast__description" = "±10-20 minutes"; -"fee__fast__shortRange" = "10-20m"; +"fee__fast__longTitle" = "Fast (more expensive)"; +"fee__fast__description" = "±10 minutes"; "fee__fast__shortDescription" = "±10m"; +"fee__fast__range" = "±10-20 minutes"; +"fee__fast__longRange" = "± 10-20 minutes"; "fee__normal__title" = "Normal"; -"fee__normal__description" = "±20-60 minutes"; -"fee__normal__shortRange" = "20-60m"; +"fee__normal__longTitle" = "Normal"; +"fee__normal__description" = "±20 minutes"; "fee__normal__shortDescription" = "±20m"; +"fee__normal__range" = "±20-60 minutes"; +"fee__normal__longRange" = "± 20-60 minutes"; "fee__slow__title" = "Slow"; -"fee__slow__description" = "±1-2 hours"; -"fee__slow__shortRange" = "1-2h"; +"fee__slow__longTitle" = "Slow (cheaper)"; +"fee__slow__description" = "±1 hours"; "fee__slow__shortDescription" = "±1h"; +"fee__slow__range" = "±1-2 hours"; +"fee__slow__longRange" = "± 1-2 hours"; "fee__minimum__title" = "Minimum"; +"fee__minimum__longTitle" = "Minimum"; "fee__minimum__description" = "+2 hours"; -"fee__minimum__shortRange" = "+2h"; "fee__minimum__shortDescription" = "+2h"; +"fee__minimum__range" = "+2 hours"; +"fee__minimum__longRange" = "+ 2 hours"; "fee__custom__title" = "Custom"; +"fee__custom__longTitle" = "Custom"; "fee__custom__description" = "Depends on the fee"; -"fee__custom__shortRange" = "Depends on the fee"; "fee__custom__shortDescription" = "Depends on the fee"; +"fee__custom__range" = "Depends on the fee"; +"fee__custom__longRange" = "Depends on fee"; "lightning__transfer_intro__title" = "Spending\nBalance"; "lightning__transfer_intro__text" = "Fund your spending balance to enjoy instant and cheap transactions with friends, family, and merchants."; "lightning__transfer_intro__button" = "Get Started"; @@ -812,18 +822,6 @@ "settings__adv__rgs_server" = "Rapid-Gossip-Sync"; "settings__adv__web_relay" = "Slashtags Web Relay"; "settings__adv__bitcoin_network" = "Bitcoin Network"; -"settings__fee__fast__label" = "Fast (more expensive)"; -"settings__fee__fast__value" = "Fast"; -"settings__fee__fast__description" = "± 10-20 minutes"; -"settings__fee__normal__label" = "Normal"; -"settings__fee__normal__value" = "Normal"; -"settings__fee__normal__description" = "± 20-60 minutes"; -"settings__fee__slow__label" = "Slow (cheaper)"; -"settings__fee__slow__value" = "Slow "; -"settings__fee__slow__description" = "± 1-2 hours"; -"settings__fee__custom__label" = "Custom"; -"settings__fee__custom__value" = "Custom"; -"settings__fee__custom__description" = "Depends on fee"; "settings__addr__no_addrs" = "No Addresses To Display"; "settings__addr__loading" = "Loading Addresses..."; "settings__addr__no_funds_receiving" = "No funds found under the {addressType} address type, receiving addresses up to index {index}."; @@ -1096,23 +1094,24 @@ "wallet__activity_pending" = "Pending"; "wallet__activity_failed" = "Failed"; "wallet__activity_transfer" = "Transfer"; -"wallet__activity_transfer_savings_pending" = "From Spending (±{duration})"; +"wallet__activity_transferring" = "Transferring"; +"wallet__activity_transfer_savings_pending" = "From Spending ({duration})"; "wallet__activity_transfer_savings_done" = "From Spending"; -"wallet__activity_transfer_spending_pending" = "From Savings (±{duration})"; +"wallet__activity_transfer_spending_pending" = "From Savings ({duration})"; "wallet__activity_transfer_spending_done" = "From Savings"; "wallet__activity_transfer_to_spending" = "To Spending"; "wallet__activity_transfer_in_progress" = "TRANSFER IN PROGRESS"; -"wallet__activity_transfer_pending" = "Transfer (±{duration})"; +"wallet__activity_transfer_pending" = "Transfer ({duration})"; "wallet__activity_transfer_ready_in" = "TRANSFER READY IN {duration}"; "wallet__activity_transfer_to_savings" = "To Savings"; "wallet__activity_confirms_in" = "Confirms in {feeRateDescription}"; -"wallet__activity_confirms_in_boosted" = "Boosting. Confirms in {feeRateDescription}"; "wallet__activity_low_fee" = "Fee potentially too low"; "wallet__activity_bitcoin_sent" = "Sent Bitcoin"; "wallet__activity_bitcoin_received" = "Received Bitcoin"; "wallet__activity_error_get" = "Transaction Retrieval Failed"; "wallet__activity_error_get_description" = "Bitkit was not able to fetch the transaction data."; "wallet__activity_error_tx_not_found" = "The transaction was not found."; +"wallet__activity_in_transfer" = "In Transfer ({duration})"; "wallet__activity_confirming" = "Confirming"; "wallet__activity_confirmed" = "Confirmed"; "wallet__activity_removed" = "Removed from Mempool"; diff --git a/Bitkit/Resources/Localization/es-419.lproj/Localizable.strings b/Bitkit/Resources/Localization/es-419.lproj/Localizable.strings index e3decc0ec..c7af3fd77 100644 --- a/Bitkit/Resources/Localization/es-419.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/es-419.lproj/Localizable.strings @@ -18,7 +18,7 @@ "cards__lightningReady__title" = "Listo"; "cards__lightningReady__description" = "¡Conectado!"; "cards__transferPending__title" = "Transferir"; -"cards__transferPending__description" = "Listo en ±{duration}"; +"cards__transferPending__description" = "Listo en {duration}"; "cards__transferClosingChannel__title" = "Iniciando..."; "cards__transferClosingChannel__description" = "Mantener app abierta"; "cards__pin__title" = "PROTEGER"; @@ -82,25 +82,33 @@ "fee__instant__shortRange" = "2-10s"; "fee__instant__shortDescription" = "±2s"; "fee__fast__title" = "Rápido"; +"fee__fast__longTitle" = "Rápido (más caro)"; "fee__fast__description" = "±10-20 minutos"; -"fee__fast__shortRange" = "10-20m"; "fee__fast__shortDescription" = "±10m"; +"fee__fast__shortRange" = "10-20m"; +"fee__fast__longRange" = "± 10-20 minutos"; "fee__normal__title" = "Normal"; +"fee__normal__longTitle" = "Normal"; "fee__normal__description" = "±20-60 minutos"; -"fee__normal__shortRange" = "20-60m"; "fee__normal__shortDescription" = "±20m"; +"fee__normal__shortRange" = "20-60m"; +"fee__normal__longRange" = "± 20-60 minutos"; "fee__slow__title" = "Lento"; +"fee__slow__longTitle" = "Lento (más barato)"; "fee__slow__description" = "±1-2 horas"; -"fee__slow__shortRange" = "1-2h"; "fee__slow__shortDescription" = "±1h"; +"fee__slow__shortRange" = "1-2h"; +"fee__slow__longRange" = "± 1-2 horas"; "fee__minimum__title" = "Mínimo"; "fee__minimum__description" = "+2 horas"; "fee__minimum__shortRange" = "+2h"; "fee__minimum__shortDescription" = "+2h"; "fee__custom__title" = "Personalizado"; +"fee__custom__longTitle" = "personalizado"; "fee__custom__description" = "Depende de la tarifa"; -"fee__custom__shortRange" = "Depende de la tarifa"; "fee__custom__shortDescription" = "Depende de la tarifa"; +"fee__custom__shortRange" = "Depende de la tarifa"; +"fee__custom__longRange" = "Depende de la tasa"; "lightning__transfer_intro__title" = "Saldo de\nGasto"; "lightning__transfer_intro__text" = "Fondee el saldo de gastos para disfrutar de transacciones baratas e instantáneas con amigos y comercios."; "lightning__transfer_intro__button" = "Empezar"; @@ -763,18 +771,6 @@ "settings__adv__rgs_server" = "Rapid-Gossip-Sync"; "settings__adv__web_relay" = "Slashtags Web Relay"; "settings__adv__bitcoin_network" = "Red Bitcoin"; -"settings__fee__fast__label" = "Rápido (más caro)"; -"settings__fee__fast__value" = "Rápido"; -"settings__fee__fast__description" = "± 10-20 minutos"; -"settings__fee__normal__label" = "Normal"; -"settings__fee__normal__value" = "Normal"; -"settings__fee__normal__description" = "± 20-60 minutos"; -"settings__fee__slow__label" = "Lento (más barato)"; -"settings__fee__slow__value" = "Lento "; -"settings__fee__slow__description" = "± 1-2 horas"; -"settings__fee__custom__label" = "personalizado"; -"settings__fee__custom__value" = "personalizado"; -"settings__fee__custom__description" = "Depende de la tasa"; "settings__addr__no_addrs" = "No hay direcciones que mostrar"; "settings__addr__loading" = "Cargando direcciones..."; "settings__addr__no_funds_receiving" = "No se han encontrado fondos bajo el tipo de dirección {addressType}, recibiendo direcciones hasta el índice {index}."; @@ -1041,12 +1037,12 @@ "wallet__activity_pending" = "Pendiente"; "wallet__activity_failed" = "Fallido"; "wallet__activity_transfer" = "Transferir"; -"wallet__activity_transfer_savings_pending" = "Del Gastos (±{duration})"; +"wallet__activity_transfer_savings_pending" = "Del Gastos ({duration})"; "wallet__activity_transfer_savings_done" = "De Gastos"; -"wallet__activity_transfer_spending_pending" = "De Ahorros (±{duration})"; +"wallet__activity_transfer_spending_pending" = "De Ahorros ({duration})"; "wallet__activity_transfer_spending_done" = "Desde Ahorros"; "wallet__activity_transfer_to_spending" = "A Gastos"; -"wallet__activity_transfer_pending" = "Transferencia (±{duration})"; +"wallet__activity_transfer_pending" = "Transferencia ({duration})"; "wallet__activity_transfer_to_savings" = "A ahorros"; "wallet__activity_confirms_in" = "Confirma en {feeRateDescription}"; "wallet__activity_confirms_in_boosted" = "Impulsando. Confirma en {feeRateDescription}"; diff --git a/Bitkit/Resources/Localization/es.lproj/Localizable.strings b/Bitkit/Resources/Localization/es.lproj/Localizable.strings index c6913e07e..41d1b42b0 100644 --- a/Bitkit/Resources/Localization/es.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/es.lproj/Localizable.strings @@ -16,7 +16,7 @@ "cards__lightningReady__title" = "Preparado"; "cards__lightningReady__description" = "¡Conectado!"; "cards__transferPending__title" = "Transferir"; -"cards__transferPending__description" = "Listo en ±{duration}"; +"cards__transferPending__description" = "Listo en {duration}"; "cards__transferClosingChannel__description" = "Mantenga abierta la app"; "cards__pin__title" = "PROTEGER"; "cards__pin__description" = "Establezca un código PIN"; @@ -79,25 +79,33 @@ "fee__instant__shortRange" = "2-10s"; "fee__instant__shortDescription" = "±2s"; "fee__fast__title" = "Rápido"; +"fee__fast__longTitle" = "Rápido (más caro)"; "fee__fast__description" = "±10-20 minutos"; -"fee__fast__shortRange" = "10-20m"; "fee__fast__shortDescription" = "±10m"; +"fee__fast__shortRange" = "10-20m"; +"fee__fast__longRange" = "± 10-20 minutos"; "fee__normal__title" = "Normal"; +"fee__normal__longTitle" = "Normal"; "fee__normal__description" = "±20-60 minutos"; -"fee__normal__shortRange" = "20-60m"; "fee__normal__shortDescription" = "±20m"; +"fee__normal__shortRange" = "20-60m"; +"fee__normal__longRange" = "± 20-60 minutos"; "fee__slow__title" = "Lento"; +"fee__slow__longTitle" = "Lento (más barato)"; "fee__slow__description" = "±1-2 horas"; -"fee__slow__shortRange" = "1-2h"; "fee__slow__shortDescription" = "±1h"; +"fee__slow__shortRange" = "1-2h"; +"fee__slow__longRange" = "± 1-2 horas"; "fee__minimum__title" = "Mínimo"; "fee__minimum__description" = "+2 horas"; "fee__minimum__shortRange" = "+2h"; "fee__minimum__shortDescription" = "+2h"; "fee__custom__title" = "Personalizar"; +"fee__custom__longTitle" = "Personalizar"; "fee__custom__description" = "Depende de la tarifa"; -"fee__custom__shortRange" = "Depende de la tarifa"; "fee__custom__shortDescription" = "Depende de la tarifa"; +"fee__custom__shortRange" = "Depende de la tarifa"; +"fee__custom__longRange" = "Depende de la comisión"; "lightning__transfer_intro__title" = "Saldo de\nGasto"; "lightning__transfer_intro__text" = "Rellene el saldo de gasto para disfrutar de transacciones baratas e instantáneas con amigos, familia y comerciantes."; "lightning__transfer_intro__button" = "Empezar"; @@ -688,18 +696,6 @@ "settings__adv__electrum_server" = "Servidor Electrum"; "settings__adv__rgs_server" = "Rapid-Gossip-Sync"; "settings__adv__bitcoin_network" = "Red Bitcoin"; -"settings__fee__fast__label" = "Rápido (más caro)"; -"settings__fee__fast__value" = "Rápido"; -"settings__fee__fast__description" = "± 10-20 minutos"; -"settings__fee__normal__label" = "Normal"; -"settings__fee__normal__value" = "Normal"; -"settings__fee__normal__description" = "± 20-60 minutos"; -"settings__fee__slow__label" = "Lento (más barato)"; -"settings__fee__slow__value" = "Lento "; -"settings__fee__slow__description" = "± 1-2 horas"; -"settings__fee__custom__label" = "Personalizar"; -"settings__fee__custom__value" = "Personalizar"; -"settings__fee__custom__description" = "Depende de la comisión"; "settings__addr__no_addrs" = "No hay direcciones para mostrar"; "settings__addr__loading" = "Cargando direcciones..."; "settings__addr__no_addrs_with_funds" = "No se encontraron direcciones con fondos al buscar \"{searchTxt}\""; @@ -918,9 +914,9 @@ "wallet__activity_pending" = "Pendiente"; "wallet__activity_failed" = "Fallido"; "wallet__activity_transfer" = "Transferir"; -"wallet__activity_transfer_savings_pending" = "Desde Gasto (±{duration})"; +"wallet__activity_transfer_savings_pending" = "Desde Gasto ({duration})"; "wallet__activity_transfer_savings_done" = "Desde Gasto"; -"wallet__activity_transfer_spending_pending" = "Desde Ahorros (±{duration})"; +"wallet__activity_transfer_spending_pending" = "Desde Ahorros ({duration})"; "wallet__activity_transfer_spending_done" = "Desde Ahorros"; "wallet__activity_transfer_to_spending" = "A Gasto"; "wallet__activity_transfer_to_savings" = "A Ahorros"; diff --git a/Bitkit/Resources/Localization/fr.lproj/Localizable.strings b/Bitkit/Resources/Localization/fr.lproj/Localizable.strings index 4f65eb31b..31a45c660 100644 --- a/Bitkit/Resources/Localization/fr.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/fr.lproj/Localizable.strings @@ -18,7 +18,7 @@ "cards__lightningReady__title" = "Prêt"; "cards__lightningReady__description" = "Connecté !"; "cards__transferPending__title" = "Transfert"; -"cards__transferPending__description" = "Prêt dans ±{duration}"; +"cards__transferPending__description" = "Prêt dans {duration}"; "cards__transferClosingChannel__title" = "Initialisation"; "cards__transferClosingChannel__description" = "Maintenir l\'application ouverte"; "cards__pin__title" = "Sécurité"; @@ -84,25 +84,33 @@ "fee__instant__shortRange" = "2-10s"; "fee__instant__shortDescription" = "±2s"; "fee__fast__title" = "Rapide"; +"fee__fast__longTitle" = "Rapide (plus cher)"; "fee__fast__description" = "±10-20 minutes"; -"fee__fast__shortRange" = "10-20min"; "fee__fast__shortDescription" = "±10min"; +"fee__fast__shortRange" = "10-20min"; +"fee__fast__longRange" = "± 10-20 minutes"; "fee__normal__title" = "Normal"; +"fee__normal__longTitle" = "Normal"; "fee__normal__description" = "±20-60 minutes"; -"fee__normal__shortRange" = "20-60min"; "fee__normal__shortDescription" = "±20min"; +"fee__normal__shortRange" = "20-60min"; +"fee__normal__longRange" = "± 20-60 minutes"; "fee__slow__title" = "Lent"; +"fee__slow__longTitle" = "Lent (moins cher)"; "fee__slow__description" = "±1-2 heures"; -"fee__slow__shortRange" = "1-2h"; "fee__slow__shortDescription" = "±1h"; +"fee__slow__shortRange" = "1-2h"; +"fee__slow__longRange" = "± 1-2 heures"; "fee__minimum__title" = "Minimum"; "fee__minimum__description" = "+2 heures"; "fee__minimum__shortRange" = "+2h"; "fee__minimum__shortDescription" = "+2h"; "fee__custom__title" = "Personnalisé"; +"fee__custom__longTitle" = "Personnalisé"; "fee__custom__description" = "Dépend des frais"; -"fee__custom__shortRange" = "Dépend des frais"; "fee__custom__shortDescription" = "Dépend des frais"; +"fee__custom__shortRange" = "Dépend des frais"; +"fee__custom__longRange" = "Dépend du montant des frais"; "lightning__transfer_intro__title" = "Dépenses \nSolde"; "lightning__transfer_intro__text" = "Alimentez votre solde de dépenses pour profiter de transactions instantanées et bon marché avec vos amis, votre famille et les commerçants."; "lightning__transfer_intro__button" = "Commencer"; @@ -775,18 +783,6 @@ "settings__adv__rgs_server" = "Rapid-Gossip-Sync"; "settings__adv__web_relay" = "Slashtags Web Relay"; "settings__adv__bitcoin_network" = "Réseau Bitcoin"; -"settings__fee__fast__label" = "Rapide (plus cher)"; -"settings__fee__fast__value" = "Rapide"; -"settings__fee__fast__description" = "± 10-20 minutes"; -"settings__fee__normal__label" = "Normal"; -"settings__fee__normal__value" = "Normal"; -"settings__fee__normal__description" = "± 20-60 minutes"; -"settings__fee__slow__label" = "Lent (moins cher)"; -"settings__fee__slow__value" = "Lent"; -"settings__fee__slow__description" = "± 1-2 heures"; -"settings__fee__custom__label" = "Personnalisé"; -"settings__fee__custom__value" = "Personnalisé"; -"settings__fee__custom__description" = "Dépend du montant des frais"; "settings__addr__no_addrs" = "Aucune adresse à afficher"; "settings__addr__loading" = "Chargement des adresses..."; "settings__addr__no_funds_receiving" = "Aucun fonds n\'a été trouvé sous le type d\'adresse {addressType}, recevant des adresses jusqu\'à l\'index {index}."; @@ -1054,12 +1050,12 @@ "wallet__activity_pending" = "En attente"; "wallet__activity_failed" = "Échec"; "wallet__activity_transfer" = "Transfert"; -"wallet__activity_transfer_savings_pending" = "Depuis le compte Dépenses (±{duration})"; +"wallet__activity_transfer_savings_pending" = "Depuis le compte Dépenses ({duration})"; "wallet__activity_transfer_savings_done" = "Depuis le compte Dépenses"; -"wallet__activity_transfer_spending_pending" = "Depuis l\'Épargne (±{duration})"; +"wallet__activity_transfer_spending_pending" = "Depuis l\'Épargne ({duration})"; "wallet__activity_transfer_spending_done" = "Déplacé vers l\'Épargne"; "wallet__activity_transfer_to_spending" = "Vers le compte Dépenses"; -"wallet__activity_transfer_pending" = "Transfert (±{duration})"; +"wallet__activity_transfer_pending" = "Transfert ({duration})"; "wallet__activity_transfer_ready_in" = "TRANSFERT PRÊT DANS {duration}"; "wallet__activity_transfer_to_savings" = "Vers l'épargne"; "wallet__activity_confirms_in" = "Confirmé dans {feeRateDescription}"; diff --git a/Bitkit/Resources/Localization/it.lproj/Localizable.strings b/Bitkit/Resources/Localization/it.lproj/Localizable.strings index 9832602b8..962eb1b9a 100644 --- a/Bitkit/Resources/Localization/it.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/it.lproj/Localizable.strings @@ -17,7 +17,7 @@ "cards__lightningReady__title" = "Pronto"; "cards__lightningReady__description" = "Connesso!"; "cards__transferPending__title" = "Trasferisci"; -"cards__transferPending__description" = "Pronto in ±{duration}"; +"cards__transferPending__description" = "Pronto in {duration}"; "cards__transferClosingChannel__description" = "Tieni l\'app aperta"; "cards__pin__title" = "Sicuro"; "cards__pin__description" = "Imposta un codice PIN"; @@ -82,25 +82,33 @@ "fee__instant__shortRange" = "2-10s"; "fee__instant__shortDescription" = "±2s"; "fee__fast__title" = "Veloce"; +"fee__fast__longTitle" = "Veloce (più costoso)"; "fee__fast__description" = "±10-20 minuti"; -"fee__fast__shortRange" = "10-20m"; "fee__fast__shortDescription" = "±10m"; +"fee__fast__shortRange" = "10-20m"; +"fee__fast__longRange" = "± 10-20 minuti"; "fee__normal__title" = "Normale"; +"fee__normal__longTitle" = "Normale"; "fee__normal__description" = "±20-60 minuti"; -"fee__normal__shortRange" = "20-60m"; "fee__normal__shortDescription" = "±20m"; +"fee__normal__shortRange" = "20-60m"; +"fee__normal__longRange" = "± 20-60 minuti"; "fee__slow__title" = "Lenta"; +"fee__slow__longTitle" = "Lento (più economico)"; "fee__slow__description" = "±1-2 ore"; -"fee__slow__shortRange" = "1-2h"; "fee__slow__shortDescription" = "±1h"; +"fee__slow__shortRange" = "1-2h"; +"fee__slow__longRange" = "± 1-2 ore"; "fee__minimum__title" = "Minimo"; "fee__minimum__description" = "+2 ore"; "fee__minimum__shortRange" = "+2h"; "fee__minimum__shortDescription" = "+2h"; "fee__custom__title" = "Custom"; +"fee__custom__longTitle" = "Custom"; "fee__custom__description" = "Dipende dalle fee"; -"fee__custom__shortRange" = "Dipende dalle fee"; "fee__custom__shortDescription" = "Dipende dalle fee"; +"fee__custom__shortRange" = "Dipende dalle fee"; +"fee__custom__longRange" = "Dipende dalla commissione"; "lightning__transfer_intro__title" = "Conto\ndi Spesa"; "lightning__transfer_intro__text" = "Manda fondi al tuo conto di spesa per usufruire di transazioni istantanee ed economiche con amici, familiari e commercianti."; "lightning__transfer_intro__button" = "Inizia"; @@ -749,18 +757,6 @@ "settings__adv__rgs_server" = "Rapid-Gossip-Sync"; "settings__adv__web_relay" = "Slashtags Web Relay"; "settings__adv__bitcoin_network" = "Rete Bitcoin"; -"settings__fee__fast__label" = "Veloce (più costoso)"; -"settings__fee__fast__value" = "Veloce"; -"settings__fee__fast__description" = "± 10-20 minuti"; -"settings__fee__normal__label" = "Normale"; -"settings__fee__normal__value" = "Normale"; -"settings__fee__normal__description" = "± 20-60 minuti"; -"settings__fee__slow__label" = "Lento (più economico)"; -"settings__fee__slow__value" = "Lento"; -"settings__fee__slow__description" = "± 1-2 ore"; -"settings__fee__custom__label" = "Custom"; -"settings__fee__custom__value" = "Custom"; -"settings__fee__custom__description" = "Dipende dalla commissione"; "settings__addr__no_addrs" = "Nessun indirizzo da mostrare"; "settings__addr__loading" = "Caricamento Indirizzi..."; "settings__addr__no_funds_receiving" = "Non sono stati trovati fondi sotto il tipo di indirizzo {addressType}, ricevendo indirizzi fino all\'indice {index}."; @@ -1013,9 +1009,9 @@ "wallet__activity_pending" = "In attesa"; "wallet__activity_failed" = "Fallito"; "wallet__activity_transfer" = "Trasferisci"; -"wallet__activity_transfer_savings_pending" = "Da Saldo Spendibile (±{duration})"; +"wallet__activity_transfer_savings_pending" = "Da Saldo Spendibile ({duration})"; "wallet__activity_transfer_savings_done" = "Da Saldo Spendibile"; -"wallet__activity_transfer_spending_pending" = "Dai risparmi (±{duration})"; +"wallet__activity_transfer_spending_pending" = "Dai risparmi ({duration})"; "wallet__activity_transfer_spending_done" = "Da Risparmi"; "wallet__activity_transfer_to_spending" = "A Saldo Spendibile"; "wallet__activity_transfer_to_savings" = "Ai Risparmi"; diff --git a/Bitkit/Resources/Localization/nl.lproj/Localizable.strings b/Bitkit/Resources/Localization/nl.lproj/Localizable.strings index 890b6bcf2..ad7c6a655 100644 --- a/Bitkit/Resources/Localization/nl.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/nl.lproj/Localizable.strings @@ -17,7 +17,7 @@ "cards__lightningReady__title" = "Gereed"; "cards__lightningReady__description" = "Verbonden!"; "cards__transferPending__title" = "Overboeken"; -"cards__transferPending__description" = "Klaar in ±{duration}"; +"cards__transferPending__description" = "Klaar in {duration}"; "cards__transferClosingChannel__title" = "Initiieren"; "cards__transferClosingChannel__description" = "Houd de app open"; "cards__pin__title" = "Beveilig"; @@ -83,25 +83,33 @@ "fee__instant__shortRange" = "2-10s"; "fee__instant__shortDescription" = "±2s"; "fee__fast__title" = "Snel"; +"fee__fast__longTitle" = "Snel (duurdere optie)"; "fee__fast__description" = "±10-20 minuten"; -"fee__fast__shortRange" = "10-20m"; "fee__fast__shortDescription" = "±10m"; +"fee__fast__shortRange" = "10-20m"; +"fee__fast__longRange" = "± 10-20 minuten"; "fee__normal__title" = "Normaal"; +"fee__normal__longTitle" = "Normaal"; "fee__normal__description" = "±20-60 minuten"; -"fee__normal__shortRange" = "20-60m"; "fee__normal__shortDescription" = "±20m"; +"fee__normal__shortRange" = "20-60m"; +"fee__normal__longRange" = "± 20-60 minuten"; "fee__slow__title" = "Langzaam"; +"fee__slow__longTitle" = "Langzaam (goedkopere optie)"; "fee__slow__description" = "±1-2 uur"; -"fee__slow__shortRange" = "1-2h"; "fee__slow__shortDescription" = "±1h"; +"fee__slow__shortRange" = "1-2h"; +"fee__slow__longRange" = "± 1-2 uur"; "fee__minimum__title" = "Minimum"; "fee__minimum__description" = "+2 uur"; "fee__minimum__shortRange" = "+2h"; "fee__minimum__shortDescription" = "+2h"; "fee__custom__title" = "Op maat"; +"fee__custom__longTitle" = "Op maat"; "fee__custom__description" = "Afhankelijk van het tarief"; -"fee__custom__shortRange" = "Afhankelijk van het tarief"; "fee__custom__shortDescription" = "Afhankelijk van het tarief"; +"fee__custom__shortRange" = "Afhankelijk van het tarief"; +"fee__custom__longRange" = "Afhankelijk van de kosten"; "lightning__transfer_intro__title" = "Bestedings\nSaldo"; "lightning__transfer_intro__text" = "Activeer uw bestedingssaldo en profiteer van directe en goedkope transacties met vrienden, familie en winkeliers."; "lightning__transfer_intro__button" = "Starten"; @@ -770,18 +778,6 @@ "settings__adv__rgs_server" = "Rapid-Gossip-Sync"; "settings__adv__web_relay" = "Slashtags Web Relais"; "settings__adv__bitcoin_network" = "Bitcoin Netwerk"; -"settings__fee__fast__label" = "Snel (duurdere optie)"; -"settings__fee__fast__value" = "Snel"; -"settings__fee__fast__description" = "± 10-20 minuten"; -"settings__fee__normal__label" = "Normaal"; -"settings__fee__normal__value" = "Normaal"; -"settings__fee__normal__description" = "± 20-60 minuten"; -"settings__fee__slow__label" = "Langzaam (goedkopere optie)"; -"settings__fee__slow__value" = "Langzaam"; -"settings__fee__slow__description" = "± 1-2 uur"; -"settings__fee__custom__label" = "Op maat"; -"settings__fee__custom__value" = "Op maat"; -"settings__fee__custom__description" = "Afhankelijk van de kosten"; "settings__addr__no_addrs" = "Geen Addressen Om Te Tonen"; "settings__addr__loading" = "Adressen Aan Het Laden..."; "settings__addr__no_funds_receiving" = "Geen geld gevonden voor het {addressType}adres type, ontvangstadressen tot index {index}."; @@ -1049,12 +1045,12 @@ "wallet__activity_pending" = "Onderweg"; "wallet__activity_failed" = "Mislukt"; "wallet__activity_transfer" = "Overboeken"; -"wallet__activity_transfer_savings_pending" = "Van Bestedingssaldo (±{duration})"; +"wallet__activity_transfer_savings_pending" = "Van Bestedingssaldo ({duration})"; "wallet__activity_transfer_savings_done" = "Van Bestedingssaldo"; -"wallet__activity_transfer_spending_pending" = "Van Spaargeld (±{duration})"; +"wallet__activity_transfer_spending_pending" = "Van Spaargeld ({duration})"; "wallet__activity_transfer_spending_done" = "Van Spaargeld"; "wallet__activity_transfer_to_spending" = "Naar Bestedingssaldo"; -"wallet__activity_transfer_pending" = "Übertragung (±{duration})"; +"wallet__activity_transfer_pending" = "Übertragung ({duration})"; "wallet__activity_transfer_ready_in" = "OVERBOEKING GEREED IN {duration}"; "wallet__activity_transfer_to_savings" = "Naar Spaargeld"; "wallet__activity_confirms_in" = "Zal bevestigen in {feeRateDescription}"; diff --git a/Bitkit/Resources/Localization/pl.lproj/Localizable.strings b/Bitkit/Resources/Localization/pl.lproj/Localizable.strings index 809c1c9b4..26da89e40 100644 --- a/Bitkit/Resources/Localization/pl.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/pl.lproj/Localizable.strings @@ -17,7 +17,7 @@ "cards__lightningReady__title" = "Gotowe"; "cards__lightningReady__description" = "Połączone!"; "cards__transferPending__title" = "Transfery"; -"cards__transferPending__description" = "Gotowe za ±{duration}"; +"cards__transferPending__description" = "Gotowe za {duration}"; "cards__transferClosingChannel__title" = "Inicjowanie"; "cards__transferClosingChannel__description" = "Pozostaw aplikację włączoną"; "cards__pin__title" = "Zabezpiecz"; @@ -83,25 +83,33 @@ "fee__instant__shortRange" = "2-10s"; "fee__instant__shortDescription" = "±2s"; "fee__fast__title" = "Szybko"; +"fee__fast__longTitle" = "Szybko (drożej)"; "fee__fast__description" = "±10-20 minut"; -"fee__fast__shortRange" = "10-20m"; "fee__fast__shortDescription" = "±10m"; +"fee__fast__shortRange" = "10-20m"; +"fee__fast__longRange" = "± 10-20 minut"; "fee__normal__title" = "Normalnie"; +"fee__normal__longTitle" = "Normalnie"; "fee__normal__description" = "±20-60 minut"; -"fee__normal__shortRange" = "20-60m"; "fee__normal__shortDescription" = "±20m"; +"fee__normal__shortRange" = "20-60m"; +"fee__normal__longRange" = "± 20-60 minut"; "fee__slow__title" = "Wolno"; +"fee__slow__longTitle" = "Wolno (taniej)"; "fee__slow__description" = "±1-2 godzin"; -"fee__slow__shortRange" = "1-2h"; "fee__slow__shortDescription" = "±1h"; +"fee__slow__shortRange" = "1-2h"; +"fee__slow__longRange" = "± 1-2 godziny"; "fee__minimum__title" = "Minimum"; "fee__minimum__description" = "+2 godziny"; "fee__minimum__shortRange" = "+2h"; "fee__minimum__shortDescription" = "+2h"; "fee__custom__title" = "Własna"; +"fee__custom__longTitle" = "Własna"; "fee__custom__description" = "Zależy od opłaty"; -"fee__custom__shortRange" = "Zależy od opłaty"; "fee__custom__shortDescription" = "Zależy od opłaty"; +"fee__custom__shortRange" = "Zależy od opłaty"; +"fee__custom__longRange" = "Zależy od opłaty transakcyjnej"; "lightning__transfer_intro__title" = "Saldo \nWydatków"; "lightning__transfer_intro__text" = "Zasil swoje saldo wydatków, aby cieszyć się natychmiastowymi i tanimi transakcjami z przyjaciółmi, rodziną i sprzedawcami."; "lightning__transfer_intro__button" = "Rozpocznij"; @@ -776,18 +784,6 @@ "settings__adv__rgs_server" = "Rapid-Gossip-Sync"; "settings__adv__web_relay" = "Slashtags Web Relay"; "settings__adv__bitcoin_network" = "Sieć Bitcoin"; -"settings__fee__fast__label" = "Szybko (drożej)"; -"settings__fee__fast__value" = "Szybko"; -"settings__fee__fast__description" = "± 10-20 minut"; -"settings__fee__normal__label" = "Normalnie"; -"settings__fee__normal__value" = "Normalnie"; -"settings__fee__normal__description" = "± 20-60 minut"; -"settings__fee__slow__label" = "Wolno (taniej)"; -"settings__fee__slow__value" = "Wolno"; -"settings__fee__slow__description" = "± 1-2 godziny"; -"settings__fee__custom__label" = "Własna"; -"settings__fee__custom__value" = "Własna"; -"settings__fee__custom__description" = "Zależy od opłaty transakcyjnej"; "settings__addr__no_addrs" = "Brak adresów do wyświetlenia"; "settings__addr__loading" = "Ładowanie adresów..."; "settings__addr__no_funds_receiving" = "Brak środków pod adresem typu {addressType}, sprawdzono adresy odbiorcze do indeksu {index}."; @@ -1055,12 +1051,12 @@ "wallet__activity_pending" = "W toku"; "wallet__activity_failed" = "Niepowodzenie"; "wallet__activity_transfer" = "Transfery"; -"wallet__activity_transfer_savings_pending" = "Z salda wydatków (±{duration})"; +"wallet__activity_transfer_savings_pending" = "Z salda wydatków ({duration})"; "wallet__activity_transfer_savings_done" = "Z salda wydatków"; -"wallet__activity_transfer_spending_pending" = "Z oszczędności (±{duration})"; +"wallet__activity_transfer_spending_pending" = "Z oszczędności ({duration})"; "wallet__activity_transfer_spending_done" = "Z oszczędności"; "wallet__activity_transfer_to_spending" = "Do salda wydatków"; -"wallet__activity_transfer_pending" = "Transfer (±{duration})"; +"wallet__activity_transfer_pending" = "Transfer ({duration})"; "wallet__activity_transfer_ready_in" = "TRANSFER GOTOWY ZA {duration}"; "wallet__activity_transfer_to_savings" = "Na oszczędności"; "wallet__activity_confirms_in" = "Potwierdzenie w {feeRateDescription}"; diff --git a/Bitkit/Resources/Localization/pt-BR.lproj/Localizable.strings b/Bitkit/Resources/Localization/pt-BR.lproj/Localizable.strings index 4d0911b8f..fb0c672a2 100644 --- a/Bitkit/Resources/Localization/pt-BR.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/pt-BR.lproj/Localizable.strings @@ -17,7 +17,7 @@ "cards__lightningReady__title" = "Pronto"; "cards__lightningReady__description" = "Conectado!"; "cards__transferPending__title" = "Transferência"; -"cards__transferPending__description" = "Pronto em ±{duration}"; +"cards__transferPending__description" = "Pronto em {duration}"; "cards__transferClosingChannel__title" = "Iniciando"; "cards__transferClosingChannel__description" = "Mantenha o app aberto"; "cards__pin__title" = "Segurança"; @@ -83,25 +83,33 @@ "fee__instant__shortRange" = "2-10s"; "fee__instant__shortDescription" = "±2s"; "fee__fast__title" = "Rápido"; +"fee__fast__longTitle" = "Rápido (mais caro)"; "fee__fast__description" = "±10-20 minutos"; -"fee__fast__shortRange" = "10-20m"; "fee__fast__shortDescription" = "±10m"; +"fee__fast__shortRange" = "10-20m"; +"fee__fast__longRange" = "± 10-20 minutos"; "fee__normal__title" = "Normal"; +"fee__normal__longTitle" = "Normal"; "fee__normal__description" = "±20-60 minutos"; -"fee__normal__shortRange" = "20-60m"; "fee__normal__shortDescription" = "±20m"; +"fee__normal__shortRange" = "20-60m"; +"fee__normal__longRange" = "± 20-60 minutos"; "fee__slow__title" = "Devagar"; +"fee__slow__longTitle" = "Lento (mais barato)"; "fee__slow__description" = "±1-2 horas"; -"fee__slow__shortRange" = "1-2h"; "fee__slow__shortDescription" = "±1h"; +"fee__slow__shortRange" = "1-2h"; +"fee__slow__longRange" = "± 1-2 horas"; "fee__minimum__title" = "Mínimo"; "fee__minimum__description" = "+2 horas"; -"fee__minimum__shortRange" = "+2h"; "fee__minimum__shortDescription" = "+2h"; +"fee__minimum__shortRange" = "+2h"; "fee__custom__title" = "Personalizada"; +"fee__custom__longTitle" = "Personalizada"; "fee__custom__description" = "Depende da taxa"; -"fee__custom__shortRange" = "Depende da taxa"; "fee__custom__shortDescription" = "Depende da taxa"; +"fee__custom__shortRange" = "Depende da taxa"; +"fee__custom__longRange" = "Depende da taxa"; "lightning__transfer_intro__title" = "Saldo de\nGastos"; "lightning__transfer_intro__text" = "Use seu saldo de gastos para desfrutar de transações instantâneas e baratas com amigos, familiares e comerciantes."; "lightning__transfer_intro__button" = "Começar"; @@ -777,18 +785,6 @@ "settings__adv__rgs_server" = "Servidor Rapid-Gossip-Sync"; "settings__adv__web_relay" = "Web Relay de Slashtags"; "settings__adv__bitcoin_network" = "Rede Bitcoin"; -"settings__fee__fast__label" = "Rápido (mais caro)"; -"settings__fee__fast__value" = "Rápido"; -"settings__fee__fast__description" = "± 10-20 minutos"; -"settings__fee__normal__label" = "Normal"; -"settings__fee__normal__value" = "Normal"; -"settings__fee__normal__description" = "± 20-60 minutos"; -"settings__fee__slow__label" = "Lento (mais barato)"; -"settings__fee__slow__value" = "Lento "; -"settings__fee__slow__description" = "± 1-2 horas"; -"settings__fee__custom__label" = "Personalizada"; -"settings__fee__custom__value" = "Personalizada"; -"settings__fee__custom__description" = "Depende da taxa"; "settings__addr__no_addrs" = "Nenhum Endereço para Exibir"; "settings__addr__loading" = "Carregando Endereços..."; "settings__addr__no_funds_receiving" = "Não foram encontrados fundos nos primeiros {index} endereços {addressType} de recebimento."; @@ -1056,12 +1052,12 @@ "wallet__activity_pending" = "Pendente"; "wallet__activity_failed" = "Falha"; "wallet__activity_transfer" = "Transferência"; -"wallet__activity_transfer_savings_pending" = "Do Saldo de Gastos (±{duration})"; +"wallet__activity_transfer_savings_pending" = "Do Saldo de Gastos ({duration})"; "wallet__activity_transfer_savings_done" = "Do Saldo de Gastos"; -"wallet__activity_transfer_spending_pending" = "Da Poupança (±{duration})"; +"wallet__activity_transfer_spending_pending" = "Da Poupança ({duration})"; "wallet__activity_transfer_spending_done" = "Da Poupança"; "wallet__activity_transfer_to_spending" = "Para o Saldo de Gastos"; -"wallet__activity_transfer_pending" = "Transferência (±{duration})"; +"wallet__activity_transfer_pending" = "Transferência ({duration})"; "wallet__activity_transfer_ready_in" = "TRANSFERÊNCIA PRONTA EM {duration}"; "wallet__activity_transfer_to_savings" = "Para Poupança"; "wallet__activity_confirms_in" = "Confirma em {feeRateDescription}"; diff --git a/Bitkit/Resources/Localization/ru.lproj/Localizable.strings b/Bitkit/Resources/Localization/ru.lproj/Localizable.strings index 517fc0f82..8acdc6b8f 100644 --- a/Bitkit/Resources/Localization/ru.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/ru.lproj/Localizable.strings @@ -17,7 +17,7 @@ "cards__lightningReady__title" = "Готово"; "cards__lightningReady__description" = "Подключено!"; "cards__transferPending__title" = "Перенос"; -"cards__transferPending__description" = "Готово за ±{duration}"; +"cards__transferPending__description" = "Готово за {duration}"; "cards__transferClosingChannel__description" = "Держите приложение открытым"; "cards__pin__title" = "Безопасность"; "cards__pin__description" = "Установите PIN-код"; @@ -83,25 +83,33 @@ "fee__instant__shortRange" = "2-10с"; "fee__instant__shortDescription" = "±2с"; "fee__fast__title" = "Быстро"; +"fee__fast__longTitle" = "Быстро (дороже)"; "fee__fast__description" = "±10-20 минут"; -"fee__fast__shortRange" = "10-20м"; "fee__fast__shortDescription" = "±10м"; +"fee__fast__shortRange" = "10-20м"; +"fee__fast__longRange" = "± 10-20 минут"; "fee__normal__title" = "Нормально"; +"fee__normal__longTitle" = "Нормально"; "fee__normal__description" = "±20-60 минут"; -"fee__normal__shortRange" = "20-60м"; "fee__normal__shortDescription" = "±20м"; +"fee__normal__shortRange" = "20-60м"; +"fee__normal__longRange" = "± 20-60 минут"; "fee__slow__title" = "Медленно"; +"fee__slow__longTitle" = "Медленно (дешевле)"; "fee__slow__description" = "±1-2 часа"; -"fee__slow__shortRange" = "1-2ч"; "fee__slow__shortDescription" = "±1ч"; +"fee__slow__shortRange" = "1-2ч"; +"fee__slow__longRange" = "± 1-2 часа"; "fee__minimum__title" = "Минимум"; "fee__minimum__description" = "+2 часа"; "fee__minimum__shortRange" = "+2ч"; "fee__minimum__shortDescription" = "+2ч"; "fee__custom__title" = "Другое"; +"fee__custom__longTitle" = "Другое"; "fee__custom__description" = "Зависит от комиссии"; -"fee__custom__shortRange" = "Зависит от комиссии"; "fee__custom__shortDescription" = "Зависит от комиссии"; +"fee__custom__shortRange" = "Зависит от комиссии"; +"fee__custom__longRange" = "Зависит от комиссии"; "lightning__transfer_intro__title" = "Расходы\nБаланс"; "lightning__transfer_intro__text" = "Пополните свой баланс расходов, чтобы наслаждаться мгновенными и недорогими транзакциями с друзьями, семьёй и продавцами."; "lightning__transfer_intro__button" = "Начать"; @@ -777,18 +785,6 @@ "settings__adv__electrum_server" = "Сервер Electrum"; "settings__adv__rgs_server" = "Rapid-Gossip-Sync"; "settings__adv__bitcoin_network" = "Биткойн-Сеть"; -"settings__fee__fast__label" = "Быстро (дороже)"; -"settings__fee__fast__value" = "Быстро"; -"settings__fee__fast__description" = "± 10-20 минут"; -"settings__fee__normal__label" = "Нормально"; -"settings__fee__normal__value" = "Нормально"; -"settings__fee__normal__description" = "± 20-60 минут"; -"settings__fee__slow__label" = "Медленно (дешевле)"; -"settings__fee__slow__value" = "Медленно"; -"settings__fee__slow__description" = "± 1-2 часа"; -"settings__fee__custom__label" = "Другое"; -"settings__fee__custom__value" = "Другое"; -"settings__fee__custom__description" = "Зависит от комиссии"; "settings__addr__no_addrs" = "Нет Адресов для Отображения"; "settings__addr__loading" = "Загрузка Адресов..."; "settings__addr__no_funds_receiving" = "Средства на {addressType} адресах не найдены, адреса Получения до индекса {index}."; @@ -1044,13 +1040,13 @@ "wallet__activity_pending" = "В ожидании"; "wallet__activity_failed" = "Не удалось"; "wallet__activity_transfer" = "Перенос"; -"wallet__activity_transfer_savings_pending" = "От Расходов (±{duration}м)"; +"wallet__activity_transfer_savings_pending" = "От Расходов ({duration}м)"; "wallet__activity_transfer_savings_done" = "От Расходов"; -"wallet__activity_transfer_spending_pending" = "От Сбережений (±{duration}м)"; +"wallet__activity_transfer_spending_pending" = "От Сбережений ({duration}м)"; "wallet__activity_transfer_spending_done" = "От Сбережений"; "wallet__activity_transfer_to_spending" = "В Расходы"; "wallet__activity_transfer_in_progress" = "ПЕРЕВОД В ПРОЦЕССЕ"; -"wallet__activity_transfer_pending" = "Перевод (±{duration})"; +"wallet__activity_transfer_pending" = "Перевод ({duration})"; "wallet__activity_transfer_ready_in" = "ПЕРЕВОД ГОТОВ ЧЕРЕЗ {duration}"; "wallet__activity_transfer_to_savings" = "В Сбережения"; "wallet__activity_confirms_in" = "Подтверждение через {feeRateDescription}"; diff --git a/Bitkit/ViewModels/ActivityListViewModel.swift b/Bitkit/ViewModels/ActivityListViewModel.swift index 8a9c6c614..8935b1d50 100644 --- a/Bitkit/ViewModels/ActivityListViewModel.swift +++ b/Bitkit/ViewModels/ActivityListViewModel.swift @@ -46,7 +46,6 @@ class ActivityListViewModel: ObservableObject { private var activitiesChangedCancellable: AnyCancellable? @Published private(set) var availableTags: [String] = [] - @Published private(set) var feeEstimates: FeeRates? = nil private func updateAvailableTags() async { do { @@ -57,15 +56,6 @@ class ActivityListViewModel: ObservableObject { } } - private func updateFeeEstimates() async { - do { - feeEstimates = try await coreService.blocktank.fees(refresh: false) - } catch { - Logger.error("Failed to load fee estimates: \(error)", context: "ActivityListViewModel") - feeEstimates = nil - } - } - init( coreService: CoreService = .shared, lightningService: LightningService = .shared, @@ -157,9 +147,8 @@ class ActivityListViewModel: ObservableObject { let onchain = try await coreService.activity.get(filter: .onchain) onchainActivities = await filterOutReplacedSentTransactions(onchain) - // Update available tags and fee estimates + // Update available tags await updateAvailableTags() - await updateFeeEstimates() } catch { Logger.error(error, context: "Failed to sync activities") } diff --git a/Bitkit/ViewModels/CurrencyViewModel.swift b/Bitkit/ViewModels/CurrencyViewModel.swift index 873cad5c4..3fa527d7d 100644 --- a/Bitkit/ViewModels/CurrencyViewModel.swift +++ b/Bitkit/ViewModels/CurrencyViewModel.swift @@ -2,8 +2,8 @@ import Foundation import SwiftUI enum PrimaryDisplay: String { - case bitcoin = "Bitcoin" - case fiat = "Fiat" + case bitcoin + case fiat } @MainActor diff --git a/Bitkit/ViewModels/SettingsViewModel.swift b/Bitkit/ViewModels/SettingsViewModel.swift index 22b6e9b83..300774e62 100644 --- a/Bitkit/ViewModels/SettingsViewModel.swift +++ b/Bitkit/ViewModels/SettingsViewModel.swift @@ -96,7 +96,7 @@ class SettingsViewModel: NSObject, ObservableObject { @AppStorage("enableQuickpay") var enableQuickpay: Bool = false @AppStorage("quickpayAmount") var quickpayAmount: Double = 5 @AppStorage("enableNotifications") var enableNotifications: Bool = false - @AppStorage("enableNotificationsAmount") var enableNotificationsAmount: Bool = false // TODO: remove this + @AppStorage("enableNotificationsAmount") var enableNotificationsAmount: Bool = false @AppStorage("ignoresSwitchUnitToast") var ignoresSwitchUnitToast: Bool = false @AppStorage("ignoresHideBalanceToast") var ignoresHideBalanceToast: Bool = false diff --git a/Bitkit/ViewModels/TransferViewModel.swift b/Bitkit/ViewModels/TransferViewModel.swift index a9426149d..828a88151 100644 --- a/Bitkit/ViewModels/TransferViewModel.swift +++ b/Bitkit/ViewModels/TransferViewModel.swift @@ -125,26 +125,11 @@ class TransferViewModel: ObservableObject { order: IBtOrder, speed: TransactionSpeed, txFee: UInt64, + satsPerVbyte: UInt32, utxosToSpend: [SpendableUtxo]? = nil, - satsPerVbyte: UInt32? = nil, isMaxAmount: Bool = false, maxSendableAmount: UInt64? = nil ) async throws { - let rate: UInt32 - if let satsPerVbyte { - rate = satsPerVbyte - } else { - var fees = try? await coreService.blocktank.fees(refresh: true) - if fees == nil { - Logger.warn("Failed to fetch fresh fee rate, using cached rate.") - fees = try await coreService.blocktank.fees(refresh: false) - } - guard let fees else { - throw AppError(message: "Fees unavailable from bitkit-core", debugMessage: nil) - } - rate = speed.getFeeRate(from: fees) - } - guard let address = order.payment?.onchain?.address else { throw AppError(message: "Order payment onchain address is nil", debugMessage: nil) } @@ -164,7 +149,7 @@ class TransferViewModel: ObservableObject { let txid = try await lightningService.send( address: address, sats: order.feeSat, - satsPerVbyte: rate, + satsPerVbyte: satsPerVbyte, utxosToSpend: utxosToSpend, isMaxAmount: isMaxAmount ) @@ -182,7 +167,7 @@ class TransferViewModel: ObservableObject { txId: txid, address: address, isReceive: false, - feeRate: UInt64(rate), + feeRate: UInt64(satsPerVbyte), isTransfer: true, channelId: nil, createdAt: currentTime diff --git a/Bitkit/ViewModels/WalletViewModel.swift b/Bitkit/ViewModels/WalletViewModel.swift index e6ffeca84..cc03094e8 100644 --- a/Bitkit/ViewModels/WalletViewModel.swift +++ b/Bitkit/ViewModels/WalletViewModel.swift @@ -50,6 +50,7 @@ class WalletViewModel: ObservableObject { private let balanceManager: BalanceManager private let transferService: TransferService private let sheetViewModel: SheetViewModel + private let feeEstimatesManager: FeeEstimatesManager @Published var isRestoringWallet = false @Published var balanceInTransferToSavings: Int = 0 @@ -63,7 +64,8 @@ class WalletViewModel: ObservableObject { electrumConfigService: ElectrumConfigService = ElectrumConfigService(), rgsConfigService: RgsConfigService = RgsConfigService(), transferService: TransferService, - sheetViewModel: SheetViewModel + sheetViewModel: SheetViewModel, + feeEstimatesManager: FeeEstimatesManager ) { self.lightningService = lightningService self.coreService = coreService @@ -71,6 +73,7 @@ class WalletViewModel: ObservableObject { self.rgsConfigService = rgsConfigService self.transferService = transferService self.sheetViewModel = sheetViewModel + self.feeEstimatesManager = feeEstimatesManager balanceManager = BalanceManager( lightningService: lightningService, transferService: transferService, @@ -84,7 +87,7 @@ class WalletViewModel: ObservableObject { lightningService: .shared, blocktankService: CoreService.shared.blocktank ) - self.init(transferService: transferService, sheetViewModel: SheetViewModel()) + self.init(transferService: transferService, sheetViewModel: SheetViewModel(), feeEstimatesManager: FeeEstimatesManager()) } func setWalletExistsState() throws { @@ -337,17 +340,17 @@ class WalletViewModel: ObservableObject { /// Sets the fee rate for the send flow /// - Parameter speed: The transaction speed determining the fee rate. If nil, the user's default transaction speed will be used. func setFeeRate(speed: TransactionSpeed) async throws { - var fees = try? await coreService.blocktank.fees(refresh: true) - if fees == nil { + var feeEstimates = await feeEstimatesManager.getEstimates(refresh: true) + if feeEstimates == nil { Logger.warn("Failed to fetch fresh fee rate, using cached rate.") - fees = try await coreService.blocktank.fees(refresh: false) + feeEstimates = await feeEstimatesManager.getEstimates(refresh: false) } - guard let fees else { + guard let feeEstimates else { throw AppError(message: "Fees unavailable from bitkit-core", debugMessage: nil) } - selectedFeeRateSatsPerVByte = speed.getFeeRate(from: fees) + selectedFeeRateSatsPerVByte = speed.getFeeRate(from: feeEstimates) Logger.info("Selected fee rate: \(selectedFeeRateSatsPerVByte ?? 0) sats/vbyte for speed: \(speed)") } @@ -383,35 +386,19 @@ class WalletViewModel: ObservableObject { /// Gets fee limits for custom fee input /// - Returns: Tuple with (minFee, maxFee) in sat/vB func getFeeLimits() async -> (minFee: UInt32, maxFee: UInt32) { - do { - guard let fees = try await coreService.blocktank.fees(refresh: false) else { - return (minFee: 1, maxFee: 999) - } - - let slowRate = TransactionSpeed.slow.getFeeRate(from: fees) - let fastRate = TransactionSpeed.fast.getFeeRate(from: fees) - - // Set minimum to slow rate, maximum to 3x fast rate (capped at 999) - let minFee = slowRate - // TODO: check what the max fee rate should be - let maxFee = min(fastRate * 3, 999) - - return (minFee: minFee, maxFee: maxFee) - } catch { - Logger.error("Failed to get fee limits: \(error)") + guard let feeEstimates = await feeEstimatesManager.getEstimates(refresh: false) else { return (minFee: 1, maxFee: 999) } - } - /// Gets the current fee estimates for display - /// - Returns: FeeRates object with current network rates, or nil if unavailable - func getCurrentFeeEstimates() async -> FeeRates? { - do { - return try await coreService.blocktank.fees(refresh: false) - } catch { - Logger.error("Failed to get fee estimates: \(error)") - return nil - } + let slowRate = TransactionSpeed.slow.getFeeRate(from: feeEstimates) + let fastRate = TransactionSpeed.fast.getFeeRate(from: feeEstimates) + + // Set minimum to slow rate, maximum to 3x fast rate (capped at 999) + let minFee = slowRate + // TODO: check what the max fee rate should be + let maxFee = min(fastRate * 3, 999) + + return (minFee: minFee, maxFee: maxFee) } /// Calculates the fee for a transaction diff --git a/Bitkit/Views/Settings/Advanced/SweepConfirmView.swift b/Bitkit/Views/Settings/Advanced/SweepConfirmView.swift index aa5fa3780..559850ef7 100644 --- a/Bitkit/Views/Settings/Advanced/SweepConfirmView.swift +++ b/Bitkit/Views/Settings/Advanced/SweepConfirmView.swift @@ -76,7 +76,7 @@ struct SweepConfirmView: View { if viewModel.estimatedFee > 0, !viewModel.isPreparingTransaction { HStack(spacing: 0) { - BodySSBText("\(viewModel.selectedSpeed.displayTitle) (") + BodySSBText("\(viewModel.selectedSpeed.title) (") MoneyText(sats: Int(viewModel.estimatedFee), size: .bodySSB, symbol: true, symbolColor: .textPrimary) BodySSBText(")") } @@ -86,7 +86,7 @@ struct SweepConfirmView: View { .frame(width: 12, height: 12) .padding(.leading, 6) } else { - BodySSBText(viewModel.selectedSpeed.displayTitle) + BodySSBText(viewModel.selectedSpeed.title) } } } @@ -101,7 +101,7 @@ struct SweepConfirmView: View { .frame(width: 16, height: 16) .padding(.trailing, 4) - BodySSBText(viewModel.selectedSpeed.displayDescription) + BodySSBText(viewModel.selectedSpeed.description) } } } diff --git a/Bitkit/Views/Settings/DevSettingsView.swift b/Bitkit/Views/Settings/DevSettingsView.swift index 4326198f3..73e4d2756 100644 --- a/Bitkit/Views/Settings/DevSettingsView.swift +++ b/Bitkit/Views/Settings/DevSettingsView.swift @@ -4,6 +4,7 @@ import UIKit struct DevSettingsView: View { @EnvironmentObject var app: AppViewModel @EnvironmentObject var activity: ActivityListViewModel + @EnvironmentObject var feeEstimatesManager: FeeEstimatesManager @EnvironmentObject var notificationManager: PushNotificationManager @EnvironmentObject var session: SessionManager @EnvironmentObject var wallet: WalletViewModel @@ -11,6 +12,7 @@ struct DevSettingsView: View { var body: some View { VStack(alignment: .leading, spacing: 0) { NavigationBar(title: t("settings__dev_title")) + .padding(.horizontal, 16) ScrollView(showsIndicators: false) { VStack(alignment: .leading, spacing: 0) { @@ -20,6 +22,14 @@ struct DevSettingsView: View { } } + if Env.network == .regtest { + SettingsListLabel( + title: "Override Fees", + rightIcon: nil, + toggle: $feeEstimatesManager.devOverrideFeeEstimates + ) + } + NavigationLink(value: Route.ldkDebug) { SettingsListLabel(title: "LDK") } @@ -132,20 +142,21 @@ struct DevSettingsView: View { SettingsListLabel(title: "Wipe Wallet", rightIcon: nil) } } + .padding(.horizontal, 16) + .bottomSafeAreaPadding() } } .navigationBarHidden(true) - .padding(.horizontal, 16) - .bottomSafeAreaPadding() } } #Preview { DevSettingsView() - .environmentObject(WalletViewModel()) .environmentObject(AppViewModel()) .environmentObject(ActivityListViewModel()) + .environmentObject(FeeEstimatesManager()) .environmentObject(NavigationViewModel()) + .environmentObject(WalletViewModel()) .environmentObject(WidgetsViewModel()) .preferredColorScheme(.dark) } diff --git a/Bitkit/Views/Settings/GeneralSettingsView.swift b/Bitkit/Views/Settings/GeneralSettingsView.swift index 200be2888..454d14e84 100644 --- a/Bitkit/Views/Settings/GeneralSettingsView.swift +++ b/Bitkit/Views/Settings/GeneralSettingsView.swift @@ -39,7 +39,7 @@ struct GeneralSettingsView: View { NavigationLink(value: Route.transactionSpeedSettings) { SettingsListLabel( title: t("settings__general__speed"), - rightText: settings.defaultTransactionSpeed.displayTitle + rightText: settings.defaultTransactionSpeed.title ) } .accessibilityElement(children: .contain) diff --git a/Bitkit/Views/Settings/TransactionSpeed/CustomSpeedView.swift b/Bitkit/Views/Settings/TransactionSpeed/CustomSpeedView.swift index 02ec05607..269e783c9 100644 --- a/Bitkit/Views/Settings/TransactionSpeed/CustomSpeedView.swift +++ b/Bitkit/Views/Settings/TransactionSpeed/CustomSpeedView.swift @@ -26,8 +26,14 @@ struct CustomSpeedView: View { CaptionMText(t("common__sat_vbyte")) .padding(.bottom, 16) - MoneyText(sats: Int(feeRate), symbol: true) - .padding(.bottom, 16) + MoneyText( + sats: Int(feeRate), + forceUnit: .bitcoin, + forceDisplayUnit: .modern, + symbol: true, + color: feeRate == 0 ? .textSecondary : .textPrimary + ) + .padding(.bottom, 16) // Total fee estimate if isValid { diff --git a/Bitkit/Views/Settings/TransactionSpeed/TransactionSpeedSettingsView.swift b/Bitkit/Views/Settings/TransactionSpeed/TransactionSpeedSettingsView.swift index add5c6cb4..28ba8e549 100644 --- a/Bitkit/Views/Settings/TransactionSpeed/TransactionSpeedSettingsView.swift +++ b/Bitkit/Views/Settings/TransactionSpeed/TransactionSpeedSettingsView.swift @@ -5,15 +5,10 @@ struct TransactionSpeedSettingsRow: View { let isSelected: Bool let onSelect: () -> Void var customSetSpeed: String? - var testIdentifier: String? - - var iconColor: Color { - switch speed { - case .custom: - return .textSecondary - default: - return .brandAccent - } + var rangeOverride: String? + + private var rangeText: String { + rangeOverride ?? speed.longRange } var body: some View { @@ -22,13 +17,13 @@ struct TransactionSpeedSettingsRow: View { Image(speed.iconName) .resizable() .scaledToFit() - .foregroundColor(iconColor) + .foregroundColor(speed.iconColor) .frame(width: 32, height: 32) .padding(.trailing, 16) VStack(alignment: .leading, spacing: 0) { - BodyMSBText(speed.displayTitle, textColor: .textPrimary) - BodySSBText(speed.displayDescription, textColor: .textSecondary) + BodyMSBText(speed.longTitle, textColor: .textPrimary) + BodySSBText(rangeText, textColor: .textSecondary) } Spacer() @@ -49,16 +44,20 @@ struct TransactionSpeedSettingsRow: View { .contentShape(Rectangle()) } .buttonStyle(PlainButtonStyle()) - .accessibilityIdentifierIfPresent(testIdentifier) + .accessibilityIdentifierIfPresent(speed.feeKeyComponent) } } struct TransactionSpeedSettingsView: View { + @EnvironmentObject var feeEstimatesManager: FeeEstimatesManager @EnvironmentObject var navigation: NavigationViewModel @EnvironmentObject var settings: SettingsViewModel - @State private var showingCustomAlert = false - @State private var customRate: String = "" + /// When custom default fee rate is set, returns the tier-based range description (e.g. "± 10-20 minutes"). + private func customSpeedRange() -> String? { + guard case let .custom(rate) = settings.defaultTransactionSpeed else { return nil } + return TransactionSpeed.getFeeTierLocalized(feeRate: UInt64(rate), feeEstimates: feeEstimatesManager.estimates, variant: .longRange) + } var body: some View { VStack(alignment: .leading, spacing: 0) { @@ -68,7 +67,7 @@ struct TransactionSpeedSettingsView: View { ScrollView(showsIndicators: false) { VStack(alignment: .leading, spacing: 0) { CaptionMText(t("settings__general__speed_default")) - .frame(maxWidth: .infinity, alignment: .leading) + .frame(height: 50) VStack(spacing: 0) { TransactionSpeedSettingsRow( @@ -77,8 +76,7 @@ struct TransactionSpeedSettingsView: View { onSelect: { settings.defaultTransactionSpeed = .fast navigation.navigateBack() - }, - testIdentifier: "fast" + } ) Divider() @@ -89,8 +87,7 @@ struct TransactionSpeedSettingsView: View { onSelect: { settings.defaultTransactionSpeed = .normal navigation.navigateBack() - }, - testIdentifier: "normal" + } ) Divider() @@ -101,22 +98,19 @@ struct TransactionSpeedSettingsView: View { onSelect: { settings.defaultTransactionSpeed = .slow navigation.navigateBack() - }, - testIdentifier: "slow" + } ) Divider() TransactionSpeedSettingsRow( speed: .custom(satsPerVByte: 1), // Placeholder - isSelected: { - if case .custom = settings.defaultTransactionSpeed { true } else { false } - }(), + isSelected: settings.defaultTransactionSpeed.isCustom, onSelect: { navigation.navigate(.customSpeedSettings) }, customSetSpeed: settings.defaultTransactionSpeed.customSetSpeed, - testIdentifier: "custom" + rangeOverride: customSpeedRange() ) } } @@ -125,13 +119,16 @@ struct TransactionSpeedSettingsView: View { .navigationBarHidden(true) .padding(.horizontal, 16) .bottomSafeAreaPadding() + .task { await feeEstimatesManager.getEstimates() } } } #Preview { NavigationStack { TransactionSpeedSettingsView() + .environmentObject(NavigationViewModel()) .environmentObject(SettingsViewModel.shared) + .environmentObject(FeeEstimatesManager()) } .preferredColorScheme(.dark) } diff --git a/Bitkit/Views/Transfer/FundManualConfirmView.swift b/Bitkit/Views/Transfer/FundManualConfirmView.swift index ad7156621..8bac5f8e1 100644 --- a/Bitkit/Views/Transfer/FundManualConfirmView.swift +++ b/Bitkit/Views/Transfer/FundManualConfirmView.swift @@ -2,6 +2,7 @@ import SwiftUI struct FundManualConfirmView: View { @EnvironmentObject var app: AppViewModel + @EnvironmentObject var feeEstimatesManager: FeeEstimatesManager @EnvironmentObject var navigation: NavigationViewModel @EnvironmentObject var transfer: TransferViewModel @EnvironmentObject var wallet: WalletViewModel @@ -13,15 +14,10 @@ struct FundManualConfirmView: View { @State private var networkFeeSat: UInt64 = 0 private func loadFees(refresh: Bool) async { - do { - let coreService = CoreService.shared - if let feeRates = try await coreService.blocktank.fees(refresh: refresh) { - let fastFeeRate = TransactionSpeed.fast.getFeeRate(from: feeRates) - let estimatedTxSize: UInt64 = 250 // TODO: find a way to pre calculate actual tx size - networkFeeSat = UInt64(fastFeeRate) * estimatedTxSize - } - } catch { - Logger.error("Failed to fetch fee rates: \(error)") + if let feeRates = await feeEstimatesManager.getEstimates(refresh: refresh) { + let fastFeeRate = TransactionSpeed.fast.getFeeRate(from: feeRates) + let estimatedTxSize: UInt64 = 250 // TODO: find a way to pre calculate actual tx size + networkFeeSat = UInt64(fastFeeRate) * estimatedTxSize } } @@ -110,6 +106,7 @@ struct FundManualConfirmView: View { .environmentObject(AppViewModel()) .environmentObject(CurrencyViewModel()) .environmentObject(TransferViewModel()) + .environmentObject(FeeEstimatesManager()) } .preferredColorScheme(.dark) } diff --git a/Bitkit/Views/Transfer/SpendingAmount.swift b/Bitkit/Views/Transfer/SpendingAmount.swift index 5ba46af88..9c39cdbe9 100644 --- a/Bitkit/Views/Transfer/SpendingAmount.swift +++ b/Bitkit/Views/Transfer/SpendingAmount.swift @@ -4,6 +4,7 @@ struct SpendingAmount: View { @EnvironmentObject var app: AppViewModel @EnvironmentObject var blocktank: BlocktankViewModel @EnvironmentObject var currency: CurrencyViewModel + @EnvironmentObject var feeEstimatesManager: FeeEstimatesManager @EnvironmentObject var navigation: NavigationViewModel @EnvironmentObject var transfer: TransferViewModel @EnvironmentObject var wallet: WalletViewModel @@ -146,13 +147,12 @@ struct SpendingAmount: View { return } - let coreService = CoreService.shared let lightningService = LightningService.shared do { let address = try await lightningService.newAddress() - guard let feeRates = try await coreService.blocktank.fees(refresh: true) else { + guard let feeEstimates = await feeEstimatesManager.getEstimates(refresh: true) else { await MainActor.run { let balance = UInt64(wallet.spendableOnchainBalanceSats) availableAmount = balance @@ -161,7 +161,7 @@ struct SpendingAmount: View { } return } - let fastFeeRate = TransactionSpeed.fast.getFeeRate(from: feeRates) + let fastFeeRate = TransactionSpeed.fast.getFeeRate(from: feeEstimates) // Calculate max sendable amount (balance minus transaction fee) let calculatedAvailableAmount = try await wallet.calculateMaxSendableAmount( diff --git a/Bitkit/Views/Transfer/SpendingConfirm.swift b/Bitkit/Views/Transfer/SpendingConfirm.swift index 77d8c1591..161e417d7 100644 --- a/Bitkit/Views/Transfer/SpendingConfirm.swift +++ b/Bitkit/Views/Transfer/SpendingConfirm.swift @@ -6,6 +6,7 @@ struct SpendingConfirm: View { let order: IBtOrder @EnvironmentObject var app: AppViewModel + @EnvironmentObject var feeEstimatesManager: FeeEstimatesManager @EnvironmentObject var navigation: NavigationViewModel @EnvironmentObject var settings: SettingsViewModel @EnvironmentObject var transfer: TransferViewModel @@ -136,6 +137,7 @@ struct SpendingConfirm: View { } private func onConfirm() async { + guard let rate = satsPerVbyte else { return } isPaying = true do { @@ -143,8 +145,8 @@ struct SpendingConfirm: View { order: currentOrder, speed: .fast, txFee: transactionFee, + satsPerVbyte: rate, utxosToSpend: selectedUtxos, - satsPerVbyte: satsPerVbyte, isMaxAmount: shouldUseSendAll, maxSendableAmount: maxSendableAmount ) @@ -167,15 +169,14 @@ struct SpendingConfirm: View { private func calculateTransactionFee() async { do { - let coreService = CoreService.shared let lightningService = LightningService.shared - guard let feeRates = try await coreService.blocktank.fees(refresh: true) else { - Logger.error("SpendingConfirm: feeRates is nil") + guard let feeEstimates = await feeEstimatesManager.getEstimates(refresh: true) else { + Logger.error("SpendingConfirm: feeEstimates is nil") return } - let fastFeeRate = TransactionSpeed.fast.getFeeRate(from: feeRates) + let fastFeeRate = TransactionSpeed.fast.getFeeRate(from: feeEstimates) guard let address = currentOrder.payment?.onchain?.address else { throw AppError(message: "Order payment onchain address is nil", debugMessage: nil) diff --git a/Bitkit/Views/Wallets/Activity/ActivityIcon.swift b/Bitkit/Views/Wallets/Activity/ActivityIcon.swift index 5a1bec360..13810c493 100644 --- a/Bitkit/Views/Wallets/Activity/ActivityIcon.swift +++ b/Bitkit/Views/Wallets/Activity/ActivityIcon.swift @@ -2,6 +2,11 @@ import BitkitCore import SwiftUI struct ActivityIcon: View { + enum Context { + case row + case detail + } + let isLightning: Bool let status: PaymentState? let confirmed: Bool? @@ -11,10 +16,13 @@ struct ActivityIcon: View { let isTransfer: Bool let doesExist: Bool let isCpfpChild: Bool + let context: Context - init(activity: Activity, size: CGFloat = 32, isCpfpChild: Bool = false) { + init(activity: Activity, size: CGFloat = 32, isCpfpChild: Bool = false, context: Context = .detail) { self.size = size self.isCpfpChild = isCpfpChild + self.context = context + switch activity { case let .lightning(ln): isLightning = true @@ -38,28 +46,7 @@ struct ActivityIcon: View { var body: some View { Group { if isLightning { - if status == .failed { - CircularIcon( - icon: "x-circle", - iconColor: .purpleAccent, - backgroundColor: .purple16, - size: size - ) - } else if status == .pending { - CircularIcon( - icon: "hourglass-simple", - iconColor: .purpleAccent, - backgroundColor: .purple16, - size: size - ) - } else { - CircularIcon( - icon: txType == .sent ? "arrow-up" : "arrow-down", - iconColor: .purpleAccent, - backgroundColor: .purple16, - size: size - ) - } + lightningIcon } else if !doesExist { CircularIcon( icon: "x-mark", @@ -69,13 +56,20 @@ struct ActivityIcon: View { ) } else if isCpfpChild || (isBoosted && !(confirmed ?? false)) { CircularIcon( - icon: "timer-alt", + icon: "clock-clockwise", iconColor: .yellow, backgroundColor: .yellow16, size: size ) + } else if confirmed == false, txType == .sent, context == .row { + CircularIcon( + icon: "hourglass-simple", + iconColor: .brandAccent, + backgroundColor: .brand16, + size: size + ) } else { - let paymentIcon = txType == PaymentType.sent ? "arrow-up" : "arrow-down" + let paymentIcon = txType == .sent ? "arrow-up" : "arrow-down" let (iconColor, backgroundColor): (Color, Color) = if isTransfer { // From savings (to spending) = sent = orange, From spending (to savings) = received = purple txType == .sent ? (.brandAccent, .brand16) : (.purpleAccent, .purple16) @@ -93,6 +87,22 @@ struct ActivityIcon: View { .accessibilityIdentifierIfPresent(iconAccessibilityIdentifier) } + @ViewBuilder + private var lightningIcon: some View { + if status == .failed { + CircularIcon(icon: "x-circle", iconColor: .purpleAccent, backgroundColor: .purple16, size: size) + } else if status == .pending { + CircularIcon(icon: "hourglass-simple", iconColor: .purpleAccent, backgroundColor: .purple16, size: size) + } else { + CircularIcon( + icon: txType == .sent ? "arrow-up" : "arrow-down", + iconColor: .purpleAccent, + backgroundColor: .purple16, + size: size + ) + } + } + private var iconAccessibilityIdentifier: String? { if !isLightning, isBoosted, !(confirmed ?? false) { return "BoostingIcon" diff --git a/Bitkit/Views/Wallets/Activity/ActivityItemView.swift b/Bitkit/Views/Wallets/Activity/ActivityItemView.swift index 4ddd5f4c3..65386a6b2 100644 --- a/Bitkit/Views/Wallets/Activity/ActivityItemView.swift +++ b/Bitkit/Views/Wallets/Activity/ActivityItemView.swift @@ -7,6 +7,7 @@ struct ActivityItemView: View { @EnvironmentObject var activityList: ActivityListViewModel @EnvironmentObject var app: AppViewModel @EnvironmentObject var currency: CurrencyViewModel + @EnvironmentObject var feeEstimatesManager: FeeEstimatesManager @EnvironmentObject var navigation: NavigationViewModel @EnvironmentObject var sheets: SheetViewModel @EnvironmentObject var wallet: WalletViewModel @@ -52,11 +53,6 @@ struct ActivityItemView: View { isSent ? "-" : "+" } - private var feeDescription: String { - guard case let .onchain(activity) = item else { return "" } - return TransactionSpeed.getFeeDescription(feeRate: activity.feeRate, feeEstimates: activityList.feeEstimates) - } - private var activity: (timestamp: UInt64, fee: UInt64?, value: UInt64, txType: PaymentType) { switch viewModel.activity { case let .lightning(activity): @@ -89,6 +85,15 @@ struct ActivityItemView: View { return isLightning ? .purpleAccent : .brandAccent } + private var duration: String { + guard case let .onchain(activity) = item else { return "" } + return TransactionSpeed.getFeeTierLocalized( + feeRate: activity.feeRate, + feeEstimates: feeEstimatesManager.estimates, + variant: .shortDescription + ) + } + private var transferChannelId: String? { guard case let .onchain(activity) = viewModel.activity else { return nil } return activity.channelId @@ -285,20 +290,20 @@ struct ActivityItemView: View { .frame(width: 16, height: 16) BodySSBText(t("wallet__activity_confirmed"), textColor: .greenAccent) } else if activity.isBoosted { - Image("hourglass-simple") + Image("timer-alt") .foregroundColor(.yellowAccent) .frame(width: 16, height: 16) - BodySSBText( - t("wallet__activity_confirms_in_boosted", variables: ["feeRateDescription": feeDescription]), - textColor: .yellowAccent - ) + BodySSBText(t("wallet__activity_boosting"), textColor: .yellowAccent) } else { // Use accent color for transfers (purple for from spending, orange for from savings) let statusColor = isTransfer ? accentColor : .brandAccent + let statusText = isTransfer ? t("wallet__activity_in_transfer", variables: ["duration": duration]) : + t("wallet__activity_confirming") + Image("hourglass-simple") .foregroundColor(statusColor) .frame(width: 16, height: 16) - BodySSBText(t("wallet__activity_confirming"), textColor: statusColor) + BodySSBText(statusText, textColor: statusColor) } } } @@ -610,6 +615,7 @@ struct ActivityItemView_Previews: PreviewProvider { .previewDisplayName("Onchain Payment") } .environmentObject(AppViewModel()) + .environmentObject(FeeEstimatesManager()) .preferredColorScheme(.dark) } } diff --git a/Bitkit/Views/Wallets/Activity/ActivityLatest.swift b/Bitkit/Views/Wallets/Activity/ActivityLatest.swift index 85e9a29aa..841c1bffa 100644 --- a/Bitkit/Views/Wallets/Activity/ActivityLatest.swift +++ b/Bitkit/Views/Wallets/Activity/ActivityLatest.swift @@ -2,6 +2,7 @@ import SwiftUI struct ActivityLatest: View { @EnvironmentObject private var activity: ActivityListViewModel + @EnvironmentObject private var feeEstimatesManager: FeeEstimatesManager @EnvironmentObject private var navigation: NavigationViewModel @EnvironmentObject private var sheets: SheetViewModel @EnvironmentObject private var wallet: WalletViewModel @@ -46,7 +47,7 @@ struct ActivityLatest: View { LazyVStack(alignment: .leading, spacing: 16) { ForEach(Array(zip(items.indices, items)), id: \.1) { index, item in NavigationLink(value: Route.activityDetail(item)) { - ActivityRow(item: item, feeEstimates: activity.feeEstimates) + ActivityRow(item: item, feeEstimates: feeEstimatesManager.estimates) } .accessibilityIdentifier("ActivityShort-\(index)") } @@ -72,5 +73,8 @@ struct ActivityLatest: View { } } .animation(.spring(response: 0.4, dampingFraction: 0.8), value: shouldShowBanner) + .task { + await feeEstimatesManager.getEstimates(refresh: false) + } } } diff --git a/Bitkit/Views/Wallets/Activity/ActivityRowLightning.swift b/Bitkit/Views/Wallets/Activity/ActivityRowLightning.swift index 3ba9000de..ba354cbb8 100644 --- a/Bitkit/Views/Wallets/Activity/ActivityRowLightning.swift +++ b/Bitkit/Views/Wallets/Activity/ActivityRowLightning.swift @@ -1,26 +1,6 @@ import BitkitCore import SwiftUI -private struct ActivityStatus: View { - let txType: PaymentType - let status: PaymentState - - var body: some View { - switch status { - case .failed: - BodyMSBText(t("wallet__activity_failed")) - case .pending: - BodyMSBText(t("wallet__activity_pending")) - case .succeeded: - if txType == .sent { - BodyMSBText(t("wallet__activity_sent")) - } else { - BodyMSBText(t("wallet__activity_received")) - } - } - } -} - struct ActivityRowLightning: View { let item: LightningActivity @@ -40,14 +20,28 @@ struct ActivityRowLightning: View { return DateFormatterHelpers.getActivityItemDate(item.timestamp) } + private var status: String { + switch item.status { + case .failed: + return t("wallet__activity_failed") + case .pending: + return t("wallet__activity_pending") + case .succeeded: + return item.txType == .sent ? t("wallet__activity_sent") : t("wallet__activity_received") + } + } + + private var description: String { + return item.message.isEmpty ? formattedTime : item.message + } + var body: some View { HStack(spacing: 16) { - ActivityIcon(activity: .lightning(item), size: 40) + ActivityIcon(activity: .lightning(item), size: 40, context: .row) VStack(alignment: .leading, spacing: 2) { - ActivityStatus(txType: item.txType, status: item.status) - CaptionBText(item.message.isEmpty ? formattedTime : item.message) - .lineLimit(1) + BodyMSBText(status).lineLimit(1) + CaptionBText(description).lineLimit(1) } Spacer() diff --git a/Bitkit/Views/Wallets/Activity/ActivityRowOnchain.swift b/Bitkit/Views/Wallets/Activity/ActivityRowOnchain.swift index b0b24f89e..aa975570a 100644 --- a/Bitkit/Views/Wallets/Activity/ActivityRowOnchain.swift +++ b/Bitkit/Views/Wallets/Activity/ActivityRowOnchain.swift @@ -1,30 +1,10 @@ import BitkitCore import SwiftUI -private struct ActivityStatus: View { - let txType: PaymentType - let confirmed: Bool - let isTransfer: Bool - let isCpfpChild: Bool - - var body: some View { - if isTransfer { - BodyMSBText(t("wallet__activity_transfer")) - } else { - if isCpfpChild { - BodyMSBText(t("wallet__activity_boost_fee")) - } else if txType == .sent { - BodyMSBText(t("wallet__activity_sent")) - } else { - BodyMSBText(t("wallet__activity_received")) - } - } - } -} - struct ActivityRowOnchain: View { let item: OnchainActivity let feeEstimates: FeeRates? + @State private var isCpfpChild: Bool = false private var amountPrefix: String { @@ -39,17 +19,21 @@ struct ActivityRowOnchain: View { } } - private var formattedTime: String { - return DateFormatterHelpers.getActivityItemDate(item.timestamp) - } - private var feeDescription: String { - TransactionSpeed.getFeeDescription(feeRate: item.feeRate, feeEstimates: feeEstimates) + TransactionSpeed.getFeeTierLocalized(feeRate: item.feeRate, feeEstimates: feeEstimates, variant: .shortDescription) } - private var durationWithoutSymbol: String { - // Remove ± symbol since localization strings already include it - feeDescription.replacingOccurrences(of: "±", with: "") + private var status: String { + if item.isTransfer { + return item.confirmed ? t("wallet__activity_transfer") : t("wallet__activity_transferring") + } + if isCpfpChild { + return t("wallet__activity_boost_fee") + } + if item.isBoosted && !item.confirmed { + return t("wallet__activity_boosting") + } + return item.txType == .sent ? t("wallet__activity_sent") : t("wallet__activity_received") } private var description: String { @@ -66,15 +50,15 @@ struct ActivityRowOnchain: View { case .sent: return item.confirmed ? t("wallet__activity_transfer_spending_done") : - t("wallet__activity_transfer_spending_pending", variables: ["duration": durationWithoutSymbol]) + t("wallet__activity_transfer_spending_pending", variables: ["duration": feeDescription]) case .received: return item.confirmed ? t("wallet__activity_transfer_savings_done") : - t("wallet__activity_transfer_savings_pending", variables: ["duration": durationWithoutSymbol]) + t("wallet__activity_transfer_savings_pending", variables: ["duration": feeDescription]) } } else { if item.confirmed { - return formattedTime + return DateFormatterHelpers.getActivityItemDate(item.timestamp) } else { return t("wallet__activity_confirms_in", variables: ["feeRateDescription": feeDescription]) } @@ -83,18 +67,11 @@ struct ActivityRowOnchain: View { var body: some View { HStack(spacing: 16) { - ActivityIcon(activity: .onchain(item), size: 40, isCpfpChild: isCpfpChild) + ActivityIcon(activity: .onchain(item), size: 40, isCpfpChild: isCpfpChild, context: .row) VStack(alignment: .leading, spacing: 2) { - ActivityStatus( - txType: item.txType, - confirmed: item.confirmed, - isTransfer: item.isTransfer, - isCpfpChild: isCpfpChild - ) - .lineLimit(1) - CaptionBText(description) - .lineLimit(1) + BodyMSBText(status).lineLimit(1) + CaptionBText(description).lineLimit(1) } .fixedSize(horizontal: false, vertical: true) diff --git a/Bitkit/Views/Wallets/Send/SendConfirmationView.swift b/Bitkit/Views/Wallets/Send/SendConfirmationView.swift index 9406e60fb..e4ace1ab9 100644 --- a/Bitkit/Views/Wallets/Send/SendConfirmationView.swift +++ b/Bitkit/Views/Wallets/Send/SendConfirmationView.swift @@ -2,9 +2,9 @@ import BitkitCore import SwiftUI struct SendConfirmationView: View { - @EnvironmentObject var activityListViewModel: ActivityListViewModel @EnvironmentObject var app: AppViewModel @EnvironmentObject var currency: CurrencyViewModel + @EnvironmentObject var feeEstimatesManager: FeeEstimatesManager @EnvironmentObject var settings: SettingsViewModel @EnvironmentObject var sheets: SheetViewModel @EnvironmentObject var wallet: WalletViewModel @@ -400,7 +400,7 @@ struct SendConfirmationView: View { .padding(.trailing, 4) if transactionFee > 0 { - let feeText = "\(wallet.selectedSpeed.displayTitle) (" + let feeText = "\(wallet.selectedSpeed.title) (" HStack(spacing: 0) { BodySSBText(feeText) MoneyText(sats: transactionFee, size: .bodySSB, symbol: true, symbolColor: .textPrimary) @@ -427,7 +427,13 @@ struct SendConfirmationView: View { .frame(width: 16, height: 16) .padding(.trailing, 4) - BodySSBText(wallet.selectedSpeed.displayDescription) + BodySSBText( + TransactionSpeed.getFeeTierLocalized( + feeRate: UInt64(wallet.selectedFeeRateSatsPerVByte ?? 0), + feeEstimates: feeEstimatesManager.estimates, + variant: .range + ) + ) } } .frame(maxWidth: .infinity, alignment: .leading) diff --git a/Bitkit/Views/Wallets/Send/SendFeeRate.swift b/Bitkit/Views/Wallets/Send/SendFeeRate.swift index f6292cfbf..781eca395 100644 --- a/Bitkit/Views/Wallets/Send/SendFeeRate.swift +++ b/Bitkit/Views/Wallets/Send/SendFeeRate.swift @@ -4,28 +4,18 @@ import SwiftUI struct SendFeeRate: View { @EnvironmentObject var app: AppViewModel @EnvironmentObject var currency: CurrencyViewModel + @EnvironmentObject var feeEstimatesManager: FeeEstimatesManager @EnvironmentObject var settings: SettingsViewModel @EnvironmentObject var wallet: WalletViewModel @Binding var navigationPath: [SendRoute] - @State private var feeEstimates: FeeRates? @State private var transactionFees: [TransactionSpeed: UInt64] = [:] - private var onchainBalance: UInt64 { - // This would come from wallet balance - return UInt64(wallet.totalBalanceSats) - } - private var currentCustomFeeRate: UInt32 { - // Get the current custom fee rate from wallet or settings - if let walletFeeRate = wallet.selectedFeeRateSatsPerVByte { - return walletFeeRate - } else if case let .custom(rate) = settings.defaultTransactionSpeed { - return rate - } else { - return 1 // Default fallback - } + if let rate = wallet.selectedFeeRateSatsPerVByte { return rate } + if case let .custom(rate) = settings.defaultTransactionSpeed { return rate } + return 1 } private func getFee(for speed: TransactionSpeed) -> UInt64 { @@ -34,10 +24,9 @@ struct SendFeeRate: View { private func isDisabled(for speed: TransactionSpeed) -> Bool { let fee = getFee(for: speed) - let hasEnoughBalance = onchainBalance >= wallet.sendAmountSats! + fee - + guard let amount = wallet.sendAmountSats else { return true } // Disable if not enough balance and not already selected - return !hasEnoughBalance && wallet.selectedSpeed != speed + return wallet.totalBalanceSats < amount + fee && wallet.selectedSpeed != speed } private func selectFee(_ speed: TransactionSpeed) { @@ -54,17 +43,22 @@ struct SendFeeRate: View { } } - private func loadFeeEstimates() async { - let estimates = await wallet.getCurrentFeeEstimates() - await MainActor.run { - feeEstimates = estimates - } + /// Tier-based range for custom fee (e.g. "10–20 min") from current estimates. + private var customFeeRangeOverride: String { + TransactionSpeed.getFeeTierLocalized( + feeRate: UInt64(currentCustomFeeRate), + feeEstimates: feeEstimatesManager.estimates, + variant: .range + ) + } + private func loadFeeEstimates() async { + await feeEstimatesManager.getEstimates() await calculateTransactionFees() } private func calculateTransactionFees() async { - guard let estimates = feeEstimates, + guard let estimates = feeEstimatesManager.estimates, let address = app.scannedOnchainInvoice?.address, let amountSats = wallet.sendAmountSats else { @@ -141,7 +135,8 @@ struct SendFeeRate: View { speed: .custom(satsPerVByte: currentCustomFeeRate), amount: getFee(for: .custom(satsPerVByte: currentCustomFeeRate)), isSelected: wallet.selectedSpeed == .custom(satsPerVByte: currentCustomFeeRate), - isDisabled: isDisabled(for: .custom(satsPerVByte: currentCustomFeeRate)) + isDisabled: isDisabled(for: .custom(satsPerVByte: currentCustomFeeRate)), + rangeOverride: customFeeRangeOverride ) { navigationPath.append(.feeCustom) } diff --git a/Bitkit/Views/Wallets/Sheets/BoostSheet.swift b/Bitkit/Views/Wallets/Sheets/BoostSheet.swift index 41403cef9..32ea8c9bf 100644 --- a/Bitkit/Views/Wallets/Sheets/BoostSheet.swift +++ b/Bitkit/Views/Wallets/Sheets/BoostSheet.swift @@ -26,6 +26,7 @@ struct BoostSheet: View { @EnvironmentObject private var wallet: WalletViewModel @EnvironmentObject private var currency: CurrencyViewModel @EnvironmentObject private var activityList: ActivityListViewModel + @EnvironmentObject private var feeEstimatesManager: FeeEstimatesManager @EnvironmentObject private var navigation: NavigationViewModel let config: BoostSheetItem @@ -82,7 +83,7 @@ struct BoostSheet: View { else { return "" } - return converted.formattedWithSymbol() + return converted.formattedWithSymbol(withSpace: true) } var body: some View { @@ -90,12 +91,8 @@ struct BoostSheet: View { VStack(alignment: .leading, spacing: 0) { SheetHeader(title: t("wallet__boost_title")) - VStack(spacing: 16) { - BodyMText( - t("wallet__boost_fee_recomended"), - textColor: .textSecondary - ) - .multilineTextAlignment(.center) + VStack(alignment: .leading, spacing: 16) { + BodySText(t("wallet__boost_fee_recomended")) // Fee display section if isEditingFee { @@ -121,9 +118,25 @@ struct BoostSheet: View { Spacer() VStack(spacing: 4) { - BodySSBText("₿ \(currentFeeRate)/vbyte (\(fiatFeeString))") + BodySSBText("₿ \(currentFeeRate)/vbyte") if currentFeeRate > 0 { - BodySSBText("₿ \(estimatedFeeSats)", textColor: Color.textSecondary) + HStack(spacing: 6) { + MoneyText( + sats: Int(estimatedFeeSats), + size: .bodySSB, + symbol: true, + color: Color.textSecondary + ) + + BodySSBText( + TransactionSpeed.getFeeTierLocalized( + feeRate: UInt64(currentFeeRate), + feeEstimates: feeEstimatesManager.estimates, + variant: .description + ), + textColor: .textSecondary + ) + } } } @@ -146,7 +159,7 @@ struct BoostSheet: View { .accessibilityIdentifier("Plus") } - CustomButton(title: "Use Suggested Fee", size: .small) { + CustomButton(title: "Use Suggested Fee", variant: .secondary, size: .small) { isEditingFee = false editedFeeRate = nil } @@ -163,15 +176,8 @@ struct BoostSheet: View { .foregroundColor(.yellowAccent) VStack(alignment: .leading, spacing: 2) { - BodyMSBText( - t("wallet__boost"), - textColor: .white - ) - - FootnoteText( - "\(t("settings__fee__fast__description"))", - textColor: .textSecondary - ) + BodyMSBText(t("wallet__boost"), textColor: .textPrimary) + BodySSBText(TransactionSpeed.fast.description, textColor: .textSecondary) } Spacer() @@ -183,7 +189,16 @@ struct BoostSheet: View { HStack(spacing: 8) { VStack(alignment: .trailing, spacing: 2) { if let feeRate { - BodySSBText("₿ \(estimatedFeeSats)") + HStack(spacing: 2) { + BodySSBText("₿ \(estimatedFeeSats)") + + if !fetchingFees { + Image("pencil") + .resizable() + .frame(width: 16, height: 16) + .foregroundColor(feeRate != nil ? .textPrimary : .gray) + } + } } else if fetchingFees { ProgressView() .scaleEffect(0.8) @@ -191,17 +206,7 @@ struct BoostSheet: View { BodySSBText("--") } - BodySSBText( - fiatFeeString, - textColor: .textSecondary - ) - } - - if !fetchingFees { - Image("pencil") - .resizable() - .frame(width: 16, height: 16) - .foregroundColor(feeRate != nil ? .textSecondary : .gray) + BodySSBText(fiatFeeString, textColor: .textSecondary) } } } @@ -216,10 +221,7 @@ struct BoostSheet: View { Spacer() - SwipeButton( - title: t("wallet__boost_swipe"), - accentColor: .yellowAccent - ) { + SwipeButton(title: t("wallet__boost_swipe"), accentColor: .yellowAccent) { try await performBoost() } } @@ -454,6 +456,7 @@ struct BoostSheet: View { .environmentObject(WalletViewModel()) .environmentObject(CurrencyViewModel()) .environmentObject(ActivityListViewModel()) + .environmentObject(FeeEstimatesManager()) .presentationDetents([.height(400)]) } )