Skip to content

Commit b32f79e

Browse files
authored
feat: add balance formatting across the app
1 parent de4e9ca commit b32f79e

11 files changed

+142
-53
lines changed

BDKSwiftExampleWallet/Model/BalanceDisplayFormat.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ enum BalanceDisplayFormat: String, CaseIterable, Codable {
1515
case fiat = "usd"
1616
case bip177 = "bip177"
1717

18+
var displayPrefix: String {
19+
switch self {
20+
case .bitcoinSats, .bitcoin, .bip177: return ""
21+
case .fiat: return "$"
22+
default : return ""
23+
}
24+
}
25+
1826
var displayText: String {
1927
switch self {
2028
case .sats, .bitcoinSats: return "sats"
@@ -23,6 +31,22 @@ enum BalanceDisplayFormat: String, CaseIterable, Codable {
2331
case .fiat: return "USD"
2432
}
2533
}
34+
35+
func formatted(_ btcAmount: UInt64, fiatPrice: Double) -> String {
36+
switch self {
37+
case .sats:
38+
return btcAmount.formatted(.number)
39+
case .bitcoin:
40+
return String(format: "%.8f", Double(btcAmount) / 100_000_000)
41+
case .bitcoinSats:
42+
return btcAmount.formattedSatoshis()
43+
case .fiat:
44+
let satsPrice = Double(btcAmount).valueInUSD(price: fiatPrice)
45+
return satsPrice.formatted(.number.precision(.fractionLength(2)))
46+
case .bip177:
47+
return btcAmount.formattedBip177()
48+
}
49+
}
2650
}
2751

2852
extension BalanceDisplayFormat {

BDKSwiftExampleWallet/Resources/Localizable.xcstrings

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,12 +204,12 @@
204204
}
205205
}
206206
},
207-
"%@%lld sats" : {
207+
"%@%@ %@" : {
208208
"localizations" : {
209209
"en" : {
210210
"stringUnit" : {
211211
"state" : "new",
212-
"value" : "%1$@%2$lld sats"
212+
"value" : "%1$@%2$@ %3$@"
213213
}
214214
}
215215
}
@@ -288,6 +288,7 @@
288288
}
289289
},
290290
"%llu sats" : {
291+
"extractionState" : "stale",
291292
"localizations" : {
292293
"fr" : {
293294
"stringUnit" : {

BDKSwiftExampleWallet/View Model/Activity/ActivityListViewModel.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class ActivityListViewModel {
2222
var totalScripts: UInt64 = 0
2323
var walletSyncState: WalletSyncState
2424
var walletViewError: AppError?
25+
var fiatPrice: Double
2526

2627
private var updateProgress: @Sendable (UInt64, UInt64) -> Void {
2728
{ [weak self] inspected, total in
@@ -41,11 +42,13 @@ class ActivityListViewModel {
4142
init(
4243
bdkClient: BDKClient = .live,
4344
transactions: [CanonicalTx] = [],
44-
walletSyncState: WalletSyncState = .notStarted
45+
walletSyncState: WalletSyncState = .notStarted,
46+
fiatPrice: Double
4547
) {
4648
self.bdkClient = bdkClient
4749
self.transactions = transactions
4850
self.walletSyncState = walletSyncState
51+
self.fiatPrice = fiatPrice
4952

5053
// Preload cached data synchronously so UI has content before first render
5154
// transactions + listUnspent items are available from the persisted wallet db

BDKSwiftExampleWallet/View/Activity/ActivityListView.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import BitcoinDevKit
99
import SwiftUI
1010

1111
struct ActivityListView: View {
12+
@AppStorage("balanceDisplayFormat") private var balanceFormat: BalanceDisplayFormat =
13+
.bitcoinSats
1214
@Bindable var viewModel: ActivityListViewModel
1315

1416
var body: some View {
@@ -26,13 +28,16 @@ struct ActivityListView: View {
2628
TransactionListView(
2729
viewModel: .init(),
2830
transactions: viewModel.transactions,
29-
walletSyncState: viewModel.walletSyncState
31+
walletSyncState: viewModel.walletSyncState,
32+
format: balanceFormat,
33+
fiatPrice: viewModel.fiatPrice
3034
)
3135
.transition(.blurReplace)
3236
} else {
3337
LocalOutputListView(
3438
localOutputs: viewModel.localOutputs,
35-
walletSyncState: viewModel.walletSyncState
39+
walletSyncState: viewModel.walletSyncState,
40+
fiatPrice: viewModel.fiatPrice
3641
)
3742
.transition(.blurReplace)
3843
}
@@ -80,5 +85,5 @@ struct CustomSegmentedControl: View {
8085
}
8186

8287
#Preview {
83-
ActivityListView(viewModel: .init())
88+
ActivityListView(viewModel: .init(fiatPrice: 714.23))
8489
}

BDKSwiftExampleWallet/View/Activity/LocalOutputItemView.swift

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ import BitcoinDevKit
99
import SwiftUI
1010

1111
struct LocalOutputItemView: View {
12+
@AppStorage("balanceDisplayFormat") private var balanceFormat: BalanceDisplayFormat =
13+
.bitcoinSats
1214
@Environment(\.dynamicTypeSize) var dynamicTypeSize
1315
let isRedacted: Bool
1416
let output: LocalOutput
17+
let fiatPrice: Double
1518

1619
var body: some View {
1720
HStack(spacing: 15) {
@@ -53,14 +56,24 @@ struct LocalOutputItemView: View {
5356
.redacted(reason: isRedacted ? .placeholder : [])
5457

5558
Spacer()
56-
57-
Text("\(output.txout.value.toSat()) sats")
58-
.font(.subheadline)
59-
.fontWeight(.semibold)
60-
.fontDesign(.rounded)
61-
.lineLimit(1)
62-
.redacted(reason: isRedacted ? .placeholder : [])
63-
59+
60+
Group {
61+
HStack {
62+
Text(balanceFormat.displayPrefix)
63+
Text(
64+
balanceFormat.formatted(
65+
output.txout.value.toSat(),
66+
fiatPrice: fiatPrice
67+
)
68+
)
69+
Text(balanceFormat.displayText)
70+
}
71+
}
72+
.font(.subheadline)
73+
.fontWeight(.semibold)
74+
.fontDesign(.rounded)
75+
.lineLimit(1)
76+
.redacted(reason: isRedacted ? .placeholder : [])
6477
}
6578
.padding(.vertical, 15.0)
6679
.padding(.vertical, 5.0)
@@ -71,6 +84,7 @@ struct LocalOutputItemView: View {
7184
#Preview {
7285
LocalOutputItemView(
7386
isRedacted: false,
74-
output: .mock
87+
output: .mock,
88+
fiatPrice: 714.23
7589
)
7690
}

BDKSwiftExampleWallet/View/Activity/LocalOutputListView.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ import SwiftUI
1111
struct LocalOutputListView: View {
1212
let localOutputs: [LocalOutput]
1313
let walletSyncState: WalletSyncState
14+
let fiatPrice: Double
1415

1516
var body: some View {
1617
List {
1718
if localOutputs.isEmpty && walletSyncState == .syncing {
1819
LocalOutputItemView(
1920
isRedacted: true,
20-
output: .mock
21+
output: .mock,
22+
fiatPrice: .zero
2123
)
2224
.listRowInsets(EdgeInsets())
2325
.listRowSeparator(.hidden)
@@ -30,7 +32,8 @@ struct LocalOutputListView: View {
3032
ForEach(localOutputs, id: \.outpoint) { output in
3133
LocalOutputItemView(
3234
isRedacted: false,
33-
output: output
35+
output: output,
36+
fiatPrice: fiatPrice
3437
)
3538
}
3639
.listRowInsets(EdgeInsets())
@@ -43,5 +46,5 @@ struct LocalOutputListView: View {
4346
}
4447

4548
#Preview {
46-
LocalOutputListView(localOutputs: [.mock], walletSyncState: .synced)
49+
LocalOutputListView(localOutputs: [.mock], walletSyncState: .synced, fiatPrice: 714.23)
4750
}

BDKSwiftExampleWallet/View/Activity/TransactionDetailView.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import BitcoinUI
1010
import SwiftUI
1111

1212
struct TransactionDetailView: View {
13+
@AppStorage("balanceDisplayFormat") private var balanceFormat: BalanceDisplayFormat =
14+
.bitcoinSats
1315
@Bindable var viewModel: TransactionDetailViewModel
1416
@State private var isCopied = false
1517
@State private var showCheckmark = false
1618
let txDetails: TxDetails
19+
let fiatPrice: Double
1720

1821
var body: some View {
1922

@@ -55,8 +58,14 @@ struct TransactionDetailView: View {
5558

5659
VStack(spacing: 8) {
5760
HStack {
58-
Text(abs(txDetails.balanceDelta).delimiter)
59-
Text("sats")
61+
Text(balanceFormat.displayPrefix)
62+
Text(
63+
balanceFormat.formatted(
64+
UInt64(abs(txDetails.balanceDelta)),
65+
fiatPrice: fiatPrice
66+
)
67+
)
68+
Text(balanceFormat.displayText)
6069
}
6170
.lineLimit(1)
6271
.minimumScaleFactor(0.5)
@@ -168,7 +177,8 @@ struct TransactionDetailView: View {
168177
viewModel: .init(
169178
bdkClient: .mock
170179
),
171-
txDetails: .mock
180+
txDetails: .mock,
181+
fiatPrice: 714.23
172182
)
173183
}
174184
#endif

BDKSwiftExampleWallet/View/Activity/TransactionItemView.swift

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@ struct TransactionItemView: View {
1313
@Environment(\.dynamicTypeSize) var dynamicTypeSize
1414
let txDetails: TxDetails
1515
let isRedacted: Bool
16+
private let format: BalanceDisplayFormat
17+
private var fiatPrice: Double
18+
19+
init(
20+
txDetails: TxDetails,
21+
isRedacted: Bool,
22+
format: BalanceDisplayFormat,
23+
fiatPrice: Double
24+
) {
25+
self.txDetails = txDetails
26+
self.isRedacted = isRedacted
27+
self.format = format
28+
self.fiatPrice = fiatPrice
29+
}
1630

1731
var body: some View {
1832

@@ -98,10 +112,11 @@ struct TransactionItemView: View {
98112
Spacer()
99113

100114
let delta = txDetails.balanceDelta
101-
let prefix = delta >= 0 ? "+ " : "- "
102-
let amount = abs(delta)
115+
let prefix = (delta >= 0 ? "+ " : "- ").appending("\(format.displayPrefix) ")
116+
let amount = format.formatted(UInt64(abs(delta)), fiatPrice: fiatPrice)
117+
let suffix = format.displayText
103118

104-
Text("\(prefix)\(amount) sats")
119+
Text("\(prefix)\(amount) \(suffix)")
105120
.font(.subheadline)
106121
.fontWeight(.semibold)
107122
.fontDesign(.rounded)
@@ -120,7 +135,9 @@ struct TransactionItemView: View {
120135
#Preview {
121136
TransactionItemView(
122137
txDetails: .mock,
123-
isRedacted: false
138+
isRedacted: false,
139+
format: .bip177,
140+
fiatPrice: 714.23
124141
)
125142
}
126143
#endif

BDKSwiftExampleWallet/View/Activity/TransactionListView.swift

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,32 @@ struct TransactionListView: View {
1313
@Bindable var viewModel: TransactionListViewModel
1414
let transactions: [CanonicalTx]
1515
let walletSyncState: WalletSyncState
16-
16+
private let format: BalanceDisplayFormat
17+
private let fiatPrice: Double
18+
19+
init(
20+
viewModel: TransactionListViewModel,
21+
transactions: [CanonicalTx],
22+
walletSyncState: WalletSyncState,
23+
format: BalanceDisplayFormat,
24+
fiatPrice: Double
25+
) {
26+
self.viewModel = viewModel
27+
self.transactions = transactions
28+
self.walletSyncState = walletSyncState
29+
self.format = format
30+
self.fiatPrice = fiatPrice
31+
}
32+
1733
var body: some View {
1834

1935
List {
2036
if transactions.isEmpty && walletSyncState == .syncing {
2137
TransactionItemView(
2238
txDetails: .mock,
23-
isRedacted: true
39+
isRedacted: true,
40+
format: format,
41+
fiatPrice: fiatPrice
2442
)
2543
.listRowInsets(EdgeInsets())
2644
.listRowSeparator(.hidden)
@@ -94,12 +112,15 @@ struct TransactionListView: View {
94112
viewModel: .init(
95113
bdkClient: .live
96114
),
97-
txDetails: txDetails
115+
txDetails: txDetails,
116+
fiatPrice: fiatPrice
98117
)
99118
) {
100119
TransactionItemView(
101120
txDetails: txDetails,
102-
isRedacted: false
121+
isRedacted: false,
122+
format: format,
123+
fiatPrice: fiatPrice
103124
)
104125
}
105126

@@ -139,7 +160,9 @@ struct TransactionListView: View {
139160
transactions: [
140161
.mock
141162
],
142-
walletSyncState: .synced
163+
walletSyncState: .synced,
164+
format: .bip177,
165+
fiatPrice: 714.23
143166
)
144167
}
145168
#Preview {
@@ -148,7 +171,9 @@ struct TransactionListView: View {
148171
bdkClient: .mock
149172
),
150173
transactions: [],
151-
walletSyncState: .synced
174+
walletSyncState: .synced,
175+
format: .bip177,
176+
fiatPrice: 714.23
152177
)
153178
}
154179
#endif

0 commit comments

Comments
 (0)