Skip to content

Commit 5a05d59

Browse files
refactor: balanceview
1 parent ce57b79 commit 5a05d59

File tree

4 files changed

+138
-93
lines changed

4 files changed

+138
-93
lines changed

BDKSwiftExampleWallet.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
774586B52DB7B2BC00A631E1 /* BalanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 774586B42DB7B2BC00A631E1 /* BalanceView.swift */; };
1011
77F0FDC92DA9A93D00B30E4F /* Connection+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77F0FDC82DA9A93700B30E4F /* Connection+Extensions.swift */; };
1112
A733D6D02A81113000F333B4 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = A733D6CF2A81113000F333B4 /* Localizable.xcstrings */; };
1213
A73F7A362A3B778E00B87FC6 /* Int+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A73F7A352A3B778E00B87FC6 /* Int+Extensions.swift */; };
@@ -108,6 +109,7 @@
108109
/* End PBXContainerItemProxy section */
109110

110111
/* Begin PBXFileReference section */
112+
774586B42DB7B2BC00A631E1 /* BalanceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceView.swift; sourceTree = "<group>"; };
111113
77F0FDC82DA9A93700B30E4F /* Connection+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Connection+Extensions.swift"; sourceTree = "<group>"; };
112114
A733D6CF2A81113000F333B4 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
113115
A73F7A352A3B778E00B87FC6 /* Int+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+Extensions.swift"; sourceTree = "<group>"; };
@@ -268,6 +270,7 @@
268270
AE2381B92C61255100F6B00C /* Receive */,
269271
AE2381B82C61254B00F6B00C /* Send */,
270272
AE2381B72C61254200F6B00C /* Settings */,
273+
774586B42DB7B2BC00A631E1 /* BalanceView.swift */,
271274
);
272275
path = View;
273276
sourceTree = "<group>";
@@ -668,6 +671,7 @@
668671
AEE6C74C2ABCB3E200442ADD /* Transaction+Extensions.swift in Sources */,
669672
AE0C30F72A804A2D008F1EAE /* TransactionListView.swift in Sources */,
670673
AE29ED152BBE36C500EB9C4F /* TransactionListViewModel.swift in Sources */,
674+
774586B52DB7B2BC00A631E1 /* BalanceView.swift in Sources */,
671675
AEB905C32A7EEBF000CD0337 /* BackupInfo.swift in Sources */,
672676
AE783A072AB4F7C7005F0CBA /* FeeView.swift in Sources */,
673677
AE2B8C1D2A9678C900815B2F /* FeeService.swift in Sources */,

BDKSwiftExampleWallet/View Model/Receive/ReceiveViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ extension ReceiveViewModel {
110110
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
111111
if let message = messages.first,
112112
let record = message.records.first,
113-
let _ = String(data: record.payload, encoding: .utf8)
113+
String(data: record.payload, encoding: .utf8) != nil
114114
{
115115
// Handle response
116116
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//
2+
// BalanceView.swift
3+
// BDKSwiftExampleWallet
4+
//
5+
// Created by Rubens Machion on 22/04/25.
6+
//
7+
8+
import SwiftUI
9+
10+
struct BalanceView: View {
11+
12+
@State private var balanceTextPulsingOpacity: Double = 0.7
13+
14+
private var format: BalanceDisplayFormat
15+
private let balance: UInt64
16+
private var fiatPrice: Double
17+
private var satsPrice: Double {
18+
let usdValue = Double(balance).valueInUSD(price: fiatPrice)
19+
return usdValue
20+
}
21+
22+
private var currencySymbol: some View {
23+
Image(systemName: format == .fiat ? "dollarsign" : "bitcoinsign")
24+
.foregroundStyle(.secondary)
25+
.font(.title)
26+
.fontWeight(.thin)
27+
.transition(
28+
.asymmetric(
29+
insertion: .move(edge: .leading).combined(with: .opacity),
30+
removal: .move(edge: .trailing).combined(with: .opacity)
31+
)
32+
)
33+
.opacity(format == .sats || format == .bip21q ? 0 : 1)
34+
.id("symbol-\(format)")
35+
.animation(.spring(response: 0.3, dampingFraction: 0.7), value: format)
36+
}
37+
38+
@MainActor
39+
private var formattedBalance: String {
40+
switch format {
41+
case .sats:
42+
return balance.formatted(.number)
43+
case .bitcoin:
44+
return String(format: "%.8f", Double(balance) / 100_000_000)
45+
case .bitcoinSats:
46+
return balance.formattedSatoshis()
47+
case .bip21q:
48+
return balance.formatted(.number)
49+
case .fiat:
50+
return satsPrice.formatted(.number.precision(.fractionLength(2)))
51+
}
52+
}
53+
54+
@MainActor
55+
var balanceText: some View {
56+
Text(format == .fiat && satsPrice == 0 ? "00.00" : formattedBalance)
57+
.contentTransition(.numericText(countsDown: true))
58+
.fontWeight(.semibold)
59+
.fontDesign(.rounded)
60+
.foregroundStyle(
61+
format == .fiat && satsPrice == 0 ? .secondary : .primary
62+
)
63+
.opacity(
64+
format == .fiat && satsPrice == 0 ? balanceTextPulsingOpacity : 1
65+
)
66+
.animation(.spring(response: 0.4, dampingFraction: 0.8), value: format)
67+
.animation(.easeInOut, value: satsPrice)
68+
.onAppear {
69+
withAnimation(.easeInOut(duration: 1.5).repeatForever(autoreverses: true)) {
70+
balanceTextPulsingOpacity = 0.3
71+
}
72+
}
73+
}
74+
75+
private var unitText: some View {
76+
Text(format.displayText)
77+
.foregroundStyle(.secondary)
78+
.fontWeight(.thin)
79+
.transition(
80+
.asymmetric(
81+
insertion: .move(edge: .trailing).combined(with: .opacity),
82+
removal: .move(edge: .leading).combined(with: .opacity)
83+
)
84+
)
85+
.id("format-\(format)")
86+
.animation(.spring(response: 0.3, dampingFraction: 0.7), value: format)
87+
}
88+
89+
init(format: BalanceDisplayFormat, balance: UInt64, fiatPrice: Double) {
90+
self.format = format
91+
self.balance = balance
92+
self.fiatPrice = fiatPrice
93+
}
94+
95+
var body: some View {
96+
buildBalance()
97+
}
98+
99+
@ViewBuilder
100+
private func buildBalance() -> some View {
101+
VStack(spacing: 10) {
102+
HStack(spacing: 15) {
103+
if format != .sats && format != .bip21q {
104+
currencySymbol
105+
}
106+
balanceText
107+
unitText
108+
}
109+
.font(.largeTitle)
110+
.lineLimit(1)
111+
.minimumScaleFactor(0.5)
112+
}
113+
.accessibilityLabel("Bitcoin Balance")
114+
.accessibilityValue(formattedBalance)
115+
.sensoryFeedback(.selection, trigger: format)
116+
.padding(.vertical, 35.0)
117+
}
118+
}
119+
120+
#if DEBUG
121+
#Preview {
122+
BalanceView(
123+
format: .bip21q,
124+
balance: 5000,
125+
fiatPrice: 89000
126+
)
127+
}
128+
#endif

BDKSwiftExampleWallet/View/WalletView.swift

Lines changed: 5 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
// Created by Matthew Ramsden on 5/23/23.
66
//
77

8-
import BitcoinDevKit
98
import BitcoinUI
109
import SwiftUI
1110

@@ -14,7 +13,6 @@ struct WalletView: View {
1413
.bitcoinSats
1514
@Bindable var viewModel: WalletViewModel
1615
@Binding var sendNavigationPath: NavigationPath
17-
@State private var balanceTextPulsingOpacity: Double = 0.7
1816
@State private var isFirstAppear = true
1917
@State private var newTransactionSent = false
2018
@State private var showAllTransactions = false
@@ -30,30 +28,18 @@ struct WalletView: View {
3028

3129
VStack(spacing: 20) {
3230

33-
VStack(spacing: 10) {
34-
HStack(spacing: 15) {
35-
if balanceFormat != .sats && balanceFormat != .bip21q {
36-
currencySymbol
37-
}
38-
balanceText
39-
unitText
40-
}
41-
.font(.largeTitle)
42-
.lineLimit(1)
43-
.minimumScaleFactor(0.5)
44-
}
45-
.accessibilityLabel("Bitcoin Balance")
46-
.accessibilityValue(formattedBalance)
47-
.onTapGesture {
31+
BalanceView(
32+
format: balanceFormat,
33+
balance: viewModel.balanceTotal,
34+
fiatPrice: viewModel.price
35+
).onTapGesture {
4836
withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
4937
balanceFormat =
5038
BalanceDisplayFormat.allCases[
5139
(balanceFormat.index + 1) % BalanceDisplayFormat.allCases.count
5240
]
5341
}
5442
}
55-
.sensoryFeedback(.selection, trigger: balanceFormat)
56-
.padding(.vertical, 35.0)
5743

5844
VStack {
5945
HStack {
@@ -270,80 +256,7 @@ struct WalletView: View {
270256
}
271257
}
272258
}
273-
274259
}
275-
276-
}
277-
278-
extension WalletView {
279-
280-
@MainActor
281-
var formattedBalance: String {
282-
switch balanceFormat {
283-
case .sats:
284-
return viewModel.balanceTotal.formatted(.number)
285-
case .bitcoin:
286-
return String(format: "%.8f", Double(viewModel.balanceTotal) / 100_000_000)
287-
case .bitcoinSats:
288-
return viewModel.balanceTotal.formattedSatoshis()
289-
case .bip21q:
290-
return viewModel.balanceTotal.formatted(.number)
291-
case .fiat:
292-
return viewModel.satsPrice.formatted(.number.precision(.fractionLength(2)))
293-
}
294-
}
295-
296-
private var currencySymbol: some View {
297-
Image(systemName: balanceFormat == .fiat ? "dollarsign" : "bitcoinsign")
298-
.foregroundStyle(.secondary)
299-
.font(.title)
300-
.fontWeight(.thin)
301-
.transition(
302-
.asymmetric(
303-
insertion: .move(edge: .leading).combined(with: .opacity),
304-
removal: .move(edge: .trailing).combined(with: .opacity)
305-
)
306-
)
307-
.opacity(balanceFormat == .sats || balanceFormat == .bip21q ? 0 : 1)
308-
.id("symbol-\(balanceFormat)")
309-
.animation(.spring(response: 0.3, dampingFraction: 0.7), value: balanceFormat)
310-
}
311-
312-
@MainActor
313-
var balanceText: some View {
314-
Text(balanceFormat == .fiat && viewModel.satsPrice == 0 ? "00.00" : formattedBalance)
315-
.contentTransition(.numericText(countsDown: true))
316-
.fontWeight(.semibold)
317-
.fontDesign(.rounded)
318-
.foregroundStyle(
319-
balanceFormat == .fiat && viewModel.satsPrice == 0 ? .secondary : .primary
320-
)
321-
.opacity(
322-
balanceFormat == .fiat && viewModel.satsPrice == 0 ? balanceTextPulsingOpacity : 1
323-
)
324-
.animation(.spring(response: 0.4, dampingFraction: 0.8), value: balanceFormat)
325-
.animation(.easeInOut, value: viewModel.satsPrice)
326-
.onAppear {
327-
withAnimation(.easeInOut(duration: 1.5).repeatForever(autoreverses: true)) {
328-
balanceTextPulsingOpacity = 0.3
329-
}
330-
}
331-
}
332-
333-
private var unitText: some View {
334-
Text(balanceFormat.displayText)
335-
.foregroundStyle(.secondary)
336-
.fontWeight(.thin)
337-
.transition(
338-
.asymmetric(
339-
insertion: .move(edge: .trailing).combined(with: .opacity),
340-
removal: .move(edge: .leading).combined(with: .opacity)
341-
)
342-
)
343-
.id("format-\(balanceFormat)")
344-
.animation(.spring(response: 0.3, dampingFraction: 0.7), value: balanceFormat)
345-
}
346-
347260
}
348261

349262
#if DEBUG

0 commit comments

Comments
 (0)