Skip to content

Commit 1aa1d33

Browse files
authored
Merge pull request #271 from synonymdev/fix/send-manual-entry
Fix/send manual entry
2 parents 019f89f + 44944ef commit 1aa1d33

File tree

6 files changed

+132
-25
lines changed

6 files changed

+132
-25
lines changed

Bitkit/Components/MoneyStack.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ struct MoneyStack: View {
88
var showEyeIcon: Bool = false
99
var enableSwipeGesture: Bool = false
1010
var testIdPrefix: String = "TotalBalance"
11+
var onTap: (() -> Void)?
1112

1213
@EnvironmentObject var app: AppViewModel
1314
@EnvironmentObject var currency: CurrencyViewModel
@@ -111,6 +112,11 @@ struct MoneyStack: View {
111112
.accessibilityIdentifier(testIdPrefix)
112113
.contentShape(Rectangle())
113114
.onTapGesture {
115+
if let onTap {
116+
onTap()
117+
return
118+
}
119+
114120
let previousDisplay = currency.primaryDisplay
115121

116122
withAnimation(springAnimation) {

Bitkit/ViewModels/AppViewModel.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ class AppViewModel: ObservableObject {
88
@Published var scannedLightningInvoice: LightningInvoice?
99
@Published var scannedOnchainInvoice: OnChainInvoice?
1010
@Published var selectedWalletToPayFrom: WalletType = .onchain
11+
@Published var manualEntryInput: String = ""
12+
@Published var isManualEntryInputValid: Bool = false
1113

1214
// LNURL
1315
@Published var lnurlPayData: LnurlPayData?
@@ -59,6 +61,7 @@ class AppViewModel: ObservableObject {
5961
private let coreService: CoreService
6062
private let sheetViewModel: SheetViewModel
6163
private let navigationViewModel: NavigationViewModel
64+
private var manualEntryValidationSequence: UInt64 = 0
6265

6366
init(
6467
lightningService: LightningService = .shared,
@@ -327,6 +330,38 @@ extension AppViewModel {
327330
selectedWalletToPayFrom = .onchain // Reset to default
328331
lnurlPayData = nil
329332
lnurlWithdrawData = nil
333+
resetManualEntryInput()
334+
}
335+
}
336+
337+
// MARK: Manual entry validation
338+
339+
extension AppViewModel {
340+
func normalizeManualEntry(_ value: String) -> String {
341+
value.filter { !$0.isWhitespace }
342+
}
343+
344+
func resetManualEntryInput() {
345+
manualEntryValidationSequence &+= 1
346+
manualEntryInput = ""
347+
isManualEntryInputValid = false
348+
}
349+
350+
func validateManualEntryInput(_ rawValue: String) async {
351+
manualEntryValidationSequence &+= 1
352+
let currentSequence = manualEntryValidationSequence
353+
354+
let normalized = normalizeManualEntry(rawValue)
355+
356+
guard !normalized.isEmpty else {
357+
isManualEntryInputValid = false
358+
return
359+
}
360+
361+
let isValid = await (try? decode(invoice: normalized)) != nil
362+
363+
guard currentSequence == manualEntryValidationSequence else { return }
364+
isManualEntryInputValid = isValid
330365
}
331366
}
332367

Bitkit/Views/Wallets/Send/SendAmountView.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,18 @@ struct SendAmountView: View {
127127
.padding(.horizontal, 16)
128128
.sheetBackground()
129129
.onAppear {
130-
if let invoice = app.scannedOnchainInvoice {
130+
if let invoice = app.scannedOnchainInvoice, invoice.amountSatoshis > 0 {
131131
// Set the amount to the scanned onchain invoice amount if it exists
132132
amountViewModel.updateFromSats(invoice.amountSatoshis, currency: currency)
133+
wallet.sendAmountSats = invoice.amountSatoshis
134+
} else if let lightningInvoice = app.scannedLightningInvoice,
135+
lightningInvoice.amountSatoshis > 0,
136+
wallet.sendAmountSats == nil || wallet.sendAmountSats == 0
137+
{
138+
amountViewModel.updateFromSats(lightningInvoice.amountSatoshis, currency: currency)
139+
wallet.sendAmountSats = lightningInvoice.amountSatoshis
140+
} else if let existingAmount = wallet.sendAmountSats, existingAmount > 0 {
141+
amountViewModel.updateFromSats(existingAmount, currency: currency)
133142
}
134143

135144
// Calculate max sendable amount for onchain transactions

Bitkit/Views/Wallets/Send/SendConfirmationView.swift

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,22 @@ struct SendConfirmationView: View {
7979

8080
VStack(alignment: .leading, spacing: 0) {
8181
if app.selectedWalletToPayFrom == .lightning, let invoice = app.scannedLightningInvoice {
82-
MoneyStack(sats: Int(wallet.sendAmountSats ?? invoice.amountSatoshis), showSymbol: true, testIdPrefix: "ReviewAmount")
83-
.padding(.bottom, 44)
82+
MoneyStack(
83+
sats: Int(wallet.sendAmountSats ?? invoice.amountSatoshis),
84+
showSymbol: true,
85+
testIdPrefix: "ReviewAmount",
86+
onTap: navigateToAmount
87+
)
88+
.padding(.bottom, 44)
8489
lightningView(invoice)
8590
} else if app.selectedWalletToPayFrom == .onchain, let invoice = app.scannedOnchainInvoice {
86-
MoneyStack(sats: Int(wallet.sendAmountSats ?? invoice.amountSatoshis), showSymbol: true, testIdPrefix: "ReviewAmount")
87-
.padding(.bottom, 44)
91+
MoneyStack(
92+
sats: Int(wallet.sendAmountSats ?? invoice.amountSatoshis),
93+
showSymbol: true,
94+
testIdPrefix: "ReviewAmount",
95+
onTap: navigateToAmount
96+
)
97+
.padding(.bottom, 44)
8898
onchainView(invoice)
8999
}
90100
}
@@ -421,12 +431,10 @@ struct SendConfirmationView: View {
421431
@ViewBuilder
422432
func onchainView(_ invoice: OnChainInvoice) -> some View {
423433
VStack(alignment: .leading, spacing: 0) {
424-
VStack(alignment: .leading, spacing: 8) {
425-
CaptionMText(t("wallet__send_to"))
426-
BodySSBText(invoice.address.ellipsis(maxLength: 20))
427-
.lineLimit(1)
428-
.truncationMode(.middle)
429-
}
434+
editableInvoiceSection(
435+
title: t("wallet__send_to"),
436+
value: invoice.address
437+
)
430438
.padding(.bottom)
431439
.frame(maxWidth: .infinity, alignment: .leading)
432440

@@ -487,14 +495,12 @@ struct SendConfirmationView: View {
487495
}
488496

489497
@ViewBuilder
490-
func lightningView(_: LightningInvoice) -> some View {
498+
func lightningView(_ invoice: LightningInvoice) -> some View {
491499
VStack(alignment: .leading, spacing: 0) {
492-
VStack(alignment: .leading, spacing: 8) {
493-
CaptionMText(t("wallet__send_invoice"))
494-
BodySSBText(app.scannedLightningInvoice?.bolt11.ellipsis(maxLength: 20) ?? "")
495-
.lineLimit(1)
496-
.truncationMode(.middle)
497-
}
500+
editableInvoiceSection(
501+
title: t("wallet__send_invoice"),
502+
value: invoice.bolt11
503+
)
498504
.padding(.bottom)
499505
.frame(maxWidth: .infinity, alignment: .leading)
500506

@@ -565,6 +571,45 @@ struct SendConfirmationView: View {
565571
}
566572
}
567573

574+
@ViewBuilder
575+
private func editableInvoiceSection(title: String, value: String) -> some View {
576+
Button {
577+
navigateToManual(with: value)
578+
} label: {
579+
VStack(alignment: .leading, spacing: 8) {
580+
CaptionMText(title)
581+
BodySSBText(value.ellipsis(maxLength: 20))
582+
.lineLimit(1)
583+
.truncationMode(.middle)
584+
}
585+
}
586+
.buttonStyle(.plain)
587+
.accessibilityIdentifier("ReviewUri")
588+
}
589+
590+
private func navigateToManual(with value: String) {
591+
guard !value.isEmpty else { return }
592+
app.manualEntryInput = value
593+
Task { await app.validateManualEntryInput(value) }
594+
595+
if let manualIndex = navigationPath.firstIndex(of: .manual) {
596+
navigationPath = Array(navigationPath.prefix(manualIndex + 1))
597+
} else {
598+
navigationPath = [.manual]
599+
}
600+
}
601+
602+
private func navigateToAmount() {
603+
if let amountIndex = navigationPath.lastIndex(of: .amount) {
604+
navigationPath = Array(navigationPath.prefix(amountIndex + 1))
605+
} else {
606+
if let confirmIndex = navigationPath.lastIndex(of: .confirm) {
607+
navigationPath = Array(navigationPath.prefix(confirmIndex))
608+
}
609+
navigationPath.append(.amount)
610+
}
611+
}
612+
568613
private func calculateTransactionFee() async {
569614
guard app.selectedWalletToPayFrom == .onchain else {
570615
return

Bitkit/Views/Wallets/Send/SendEnterManuallyView.swift

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,18 @@ struct SendEnterManuallyView: View {
55
@EnvironmentObject var currency: CurrencyViewModel
66
@EnvironmentObject var settings: SettingsViewModel
77
@Binding var navigationPath: [SendRoute]
8-
@State private var text = ""
98
@FocusState private var isTextEditorFocused: Bool
109

10+
private var manualEntryBinding: Binding<String> {
11+
Binding(
12+
get: { app.manualEntryInput },
13+
set: { newValue in
14+
app.manualEntryInput = newValue
15+
Task { await app.validateManualEntryInput(newValue) }
16+
}
17+
)
18+
}
19+
1120
var body: some View {
1221
VStack {
1322
SheetHeader(title: t("wallet__send_bitcoin"), showBackButton: true)
@@ -16,12 +25,12 @@ struct SendEnterManuallyView: View {
1625
.frame(maxWidth: .infinity, alignment: .leading)
1726

1827
ZStack(alignment: .topLeading) {
19-
if text.isEmpty {
28+
if app.manualEntryInput.isEmpty {
2029
TitleText(t("wallet__send_address_placeholder"), textColor: .textSecondary)
2130
.padding(20)
2231
}
2332

24-
TextEditor(text: $text)
33+
TextEditor(text: manualEntryBinding)
2534
.focused($isTextEditorFocused)
2635
.padding(EdgeInsets(top: -10, leading: -5, bottom: -5, trailing: -5))
2736
.padding(20)
@@ -31,16 +40,16 @@ struct SendEnterManuallyView: View {
3140
.foregroundColor(.textPrimary)
3241
.accentColor(.brandAccent)
3342
.submitLabel(.done)
34-
.dismissKeyboardOnReturn(text: $text, isFocused: $isTextEditorFocused)
35-
.accessibilityValue(text)
43+
.dismissKeyboardOnReturn(text: manualEntryBinding, isFocused: $isTextEditorFocused)
44+
.accessibilityValue(app.manualEntryInput)
3645
.accessibilityIdentifier("RecipientInput")
3746
}
3847
.background(Color.white06)
3948
.cornerRadius(8)
4049

4150
Spacer(minLength: 16)
4251

43-
CustomButton(title: "Continue", isDisabled: text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) {
52+
CustomButton(title: "Continue", isDisabled: !app.isManualEntryInputValid) {
4453
await handleContinue()
4554
}
4655
.buttonBottomPadding(isFocused: isTextEditorFocused)
@@ -56,7 +65,9 @@ struct SendEnterManuallyView: View {
5665
}
5766

5867
func handleContinue() async {
59-
let uri = text.trimmingCharacters(in: .whitespacesAndNewlines)
68+
let uri = app.normalizeManualEntry(app.manualEntryInput)
69+
70+
guard !uri.isEmpty, app.isManualEntryInputValid else { return }
6071

6172
do {
6273
try await app.handleScannedData(uri)

Bitkit/Views/Wallets/Send/SendOptionsView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ struct SendOptionsView: View {
5858
title: t("wallet__recipient_manual"),
5959
testID: "RecipientManual"
6060
) {
61+
app.resetManualEntryInput()
6162
navigationPath.append(.manual)
6263
}
6364
}

0 commit comments

Comments
 (0)