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)])
}
)