diff --git a/UnifyWallet.xcodeproj/project.pbxproj b/UnifyWallet.xcodeproj/project.pbxproj index 0c716d1..55dd57d 100644 --- a/UnifyWallet.xcodeproj/project.pbxproj +++ b/UnifyWallet.xcodeproj/project.pbxproj @@ -225,7 +225,7 @@ 0A832A532C33906D00F25C46 /* DescriptorInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DescriptorInfo.swift; sourceTree = ""; }; 0A832A552C3395A500F25C46 /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = ""; }; 0A832A572C33B1D400F25C46 /* NetworkInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkInfo.swift; sourceTree = ""; }; - 0A9AB0F52C3B198E002551E2 /* UnifyWalletmacOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UnifyWalletmacOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 0A9AB0F52C3B198E002551E2 /* UnifyWalletDesktop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UnifyWalletDesktop.app; sourceTree = BUILT_PRODUCTS_DIR; }; 2A0C438B71D5FD0248DF6734 /* Pods_UnifyWalletmacOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_UnifyWalletmacOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 560ADF9DDDB431D7C6A7E9BC /* Pods_UnifyWallet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_UnifyWallet.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AE13A5377A734BAA9755D892 /* Pods-UnifyWallet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnifyWallet.debug.xcconfig"; path = "Target Support Files/Pods-UnifyWallet/Pods-UnifyWallet.debug.xcconfig"; sourceTree = ""; }; @@ -285,7 +285,7 @@ isa = PBXGroup; children = ( 0A8329C82C29F00F00F25C46 /* UnifyWallet.app */, - 0A9AB0F52C3B198E002551E2 /* UnifyWalletmacOS.app */, + 0A9AB0F52C3B198E002551E2 /* UnifyWalletDesktop.app */, ); name = Products; sourceTree = ""; @@ -506,9 +506,9 @@ productReference = 0A8329C82C29F00F00F25C46 /* UnifyWallet.app */; productType = "com.apple.product-type.application"; }; - 0A9AB0B12C3B198E002551E2 /* UnifyWalletmacOS */ = { + 0A9AB0B12C3B198E002551E2 /* UnifyWalletDesktop */ = { isa = PBXNativeTarget; - buildConfigurationList = 0A9AB0F22C3B198E002551E2 /* Build configuration list for PBXNativeTarget "UnifyWalletmacOS" */; + buildConfigurationList = 0A9AB0F22C3B198E002551E2 /* Build configuration list for PBXNativeTarget "UnifyWalletDesktop" */; buildPhases = ( CCFC471BD4CF29F863132118 /* [CP] Check Pods Manifest.lock */, 0A9AB0B62C3B198E002551E2 /* Sources */, @@ -521,13 +521,13 @@ ); dependencies = ( ); - name = UnifyWalletmacOS; + name = UnifyWalletDesktop; packageProductDependencies = ( 0A9AB0B22C3B198E002551E2 /* NostrSDK */, 0A9AB0B42C3B198E002551E2 /* SwiftUICoreImage */, ); productName = UnifyWallet; - productReference = 0A9AB0F52C3B198E002551E2 /* UnifyWalletmacOS.app */; + productReference = 0A9AB0F52C3B198E002551E2 /* UnifyWalletDesktop.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -563,7 +563,7 @@ projectRoot = ""; targets = ( 0A8329C72C29F00F00F25C46 /* UnifyWallet */, - 0A9AB0B12C3B198E002551E2 /* UnifyWalletmacOS */, + 0A9AB0B12C3B198E002551E2 /* UnifyWalletDesktop */, ); }; /* End PBXProject section */ @@ -922,7 +922,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = UnifyWallet/UnifyWallet.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"UnifyWallet/Preview Content\""; DEVELOPMENT_TEAM = 8JHDU5M9KD; ENABLE_HARDENED_RUNTIME = YES; @@ -950,7 +950,7 @@ "LD_RUNPATH_SEARCH_PATHS[sdk=iphoneos*]" = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.0.1; + MARKETING_VERSION = 0.0.2; PRODUCT_BUNDLE_IDENTIFIER = com.dentonllc.UnifyWallet; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; @@ -971,7 +971,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = UnifyWallet/UnifyWallet.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"UnifyWallet/Preview Content\""; DEVELOPMENT_TEAM = 8JHDU5M9KD; ENABLE_HARDENED_RUNTIME = YES; @@ -998,7 +998,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.0.1; + MARKETING_VERSION = 0.0.2; PRODUCT_BUNDLE_IDENTIFIER = com.dentonllc.UnifyWallet; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; @@ -1019,7 +1019,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = UnifyWallet/UnifyWallet.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = "\"UnifyWallet/Preview Content\""; DEVELOPMENT_TEAM = 8JHDU5M9KD; ENABLE_HARDENED_RUNTIME = YES; @@ -1046,7 +1046,7 @@ "LD_RUNPATH_SEARCH_PATHS[sdk=iphoneos*]" = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 0.0.1; + MARKETING_VERSION = 0.0.2; PRODUCT_BUNDLE_IDENTIFIER = com.dentonllc.UnifyWallet; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; @@ -1066,7 +1066,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = UnifyWallet/UnifyWallet.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = "\"UnifyWallet/Preview Content\""; DEVELOPMENT_TEAM = 8JHDU5M9KD; ENABLE_HARDENED_RUNTIME = YES; @@ -1092,7 +1092,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 0.0.1; + MARKETING_VERSION = 0.0.2; PRODUCT_BUNDLE_IDENTIFIER = com.dentonllc.UnifyWallet; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; @@ -1125,7 +1125,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 0A9AB0F22C3B198E002551E2 /* Build configuration list for PBXNativeTarget "UnifyWalletmacOS" */ = { + 0A9AB0F22C3B198E002551E2 /* Build configuration list for PBXNativeTarget "UnifyWalletDesktop" */ = { isa = XCConfigurationList; buildConfigurations = ( 0A9AB0F32C3B198E002551E2 /* Debug */, diff --git a/UnifyWallet/BitcoinCore/BitcoinCoreRPC.swift b/UnifyWallet/BitcoinCore/BitcoinCoreRPC.swift index 168c489..25f7ac3 100644 --- a/UnifyWallet/BitcoinCore/BitcoinCoreRPC.swift +++ b/UnifyWallet/BitcoinCore/BitcoinCoreRPC.swift @@ -58,18 +58,18 @@ class BitcoinCoreRPC { } var request = URLRequest(url: url) - var timeout = 10.0 - - switch method.stringValue { - case "gettxoutsetinfo": - timeout = 1000.0 - - case "importmulti", "deriveaddresses", "loadwallet": - timeout = 60.0 - - default: - break - } + let timeout = 60.0 + +// switch method.stringValue { +// case "gettxoutsetinfo": +// timeout = 1000.0 +// +// case "importmulti", "deriveaddresses", "loadwallet": +// timeout = 60.0 +// +// default: +// break +// } let loginString = String(format: "%@:%@", "PayJoin", rpcPass) let loginData = loginString.data(using: String.Encoding.utf8)! diff --git a/UnifyWallet/Extensions.swift b/UnifyWallet/Extensions.swift index bfa3fe0..649831b 100644 --- a/UnifyWallet/Extensions.swift +++ b/UnifyWallet/Extensions.swift @@ -22,6 +22,19 @@ public extension String { let components = self.components(separatedBy: .whitespacesAndNewlines) return components.filter { !$0.isEmpty }.joined(separator: " ") } + + var withSpaces: String { + var addressToDisplay = "" + for (i, c) in self.enumerated() { + addressToDisplay += "\(c)" + if i > 0 && i < self.count - 2 { + if i.isMultiple(of: 4) { + addressToDisplay += " - " + } + } + } + return addressToDisplay + } } @@ -42,6 +55,7 @@ public extension Double { var btcBalanceWithSpaces: String { var btcBalance = abs(self.rounded(toPlaces: 8)).avoidNotation btcBalance = btcBalance.replacingOccurrences(of: ",", with: "") + if !btcBalance.contains(".") { btcBalance += ".0" } diff --git a/UnifyWallet/Keys.swift b/UnifyWallet/Keys.swift index 4b4a476..ae8b877 100644 --- a/UnifyWallet/Keys.swift +++ b/UnifyWallet/Keys.swift @@ -11,6 +11,25 @@ import LibWally enum Keys { + static func donationAddress() -> String? { + let randomInt = Int.random(in: 0..<100) + + + + let networkSetting = UserDefaults.standard.object(forKey: "network") as? String ?? "Signet" + var network: Network = .testnet + + if networkSetting == "Mainnet" { + network = .mainnet + } + + guard let hdKey = try? HDKey(base58: "xpub6C1DcRZo4RfYHE5F4yiA2m26wMBLr33qP4xpVdzY1EkHyUdaxwHhAvAUpohwT4ajjd1N9nt7npHrjd3CLqzgfbEYPknaRW8crT2C9xmAy3G"), + let path = try? BIP32Path(string: "0/\(randomInt)"), + let address = try? hdKey.derive(using: path).address(type: .payToWitnessPubKeyHash), let x = try? Address(scriptPubKey: address.scriptPubKey, network: network) else { return nil } + + return x.description + } + static func seed() -> String? { var words: String? let bytesCount = 32 diff --git a/UnifyWallet/UnifyWalletApp.swift b/UnifyWallet/UnifyWalletApp.swift index ceacfb7..d1f7b07 100644 --- a/UnifyWallet/UnifyWalletApp.swift +++ b/UnifyWallet/UnifyWalletApp.swift @@ -21,6 +21,9 @@ struct UnifyWalletApp: App { var body: some Scene { WindowGroup { HomeView() + #if os(iOS) + .onAppear(perform: UIApplication.shared.addTapGestureRecognizer) + #endif } } @@ -55,10 +58,37 @@ struct UnifyWalletApp: App { return } + // MARK: Normal flow +// UserDefaults.standard.setValue("38332", forKey: "rpcPort") +// UserDefaults.standard.setValue("Signet", forKey: "network") +// +// guard let encRpcPass = Crypto.encrypt(Crypto.privKeyData()) else { +// showNotSavedAlert = true +// return +// } +// +// let dict: [String:Any] = [ +// "rpcPass": encRpcPass, +// "rpcUser": "Unify", +// "rpcAddress": "127.0.0.1", +// "rpcPort": "38332" +// ] + + // MARK: Demo mode + guard let rpcauthcreds = RPCAuth().generateCreds(username: "Unify", password: "1d52e89e0c16a7cc57cbda4954eebf4ba7864e113e5c8bbaa8ab662c8af9ce91") else { + showNotSavedAlert = true + return + } + UserDefaults.standard.setValue("38332", forKey: "rpcPort") UserDefaults.standard.setValue("Signet", forKey: "network") - - guard let encRpcPass = Crypto.encrypt(Crypto.privKeyData()) else { + UserDefaults.standard.setValue(true, forKey: "torEnabled") + UserDefaults.standard.setValue("FullyNoded-da09e4c7b0fc6187c2c1bd2ace56bad7ba25406da168d7b16b4793ef81a082f9", forKey: "walletName") + // Specify a wallet too. + + let rpcpass = rpcauthcreds.password + + guard let encRpcPass = Crypto.encrypt(rpcpass.data(using: .utf8)!) else { showNotSavedAlert = true return } @@ -66,13 +96,19 @@ struct UnifyWalletApp: App { let dict: [String:Any] = [ "rpcPass": encRpcPass, "rpcUser": "Unify", - "rpcAddress": "127.0.0.1", + "rpcAddress": "rarokrtgsiwy42pcgmrp2sdslrt2efpt56rbhjvnwjnje2os64p3t5qd.onion", "rpcPort": "38332" ] - - saveCreds(entityName: "RPCCredentials", dict: dict) - - createDefaultTorCreds() + + let d = Data("smile pool offer seat betray sponsor build genius vault follow glad near".utf8) + guard let encryptedSigner = Crypto.encrypt(d) else { return } + DataManager.saveEntity(entityName: "BIP39Signer", dict: ["encryptedData": encryptedSigner]) { signerSaved in + guard signerSaved else { return } + + saveCreds(entityName: "RPCCredentials", dict: dict) + + createDefaultTorCreds() + } return } @@ -112,3 +148,22 @@ struct UnifyWalletApp: App { } } } + +#if os(iOS) +extension UIApplication { + func addTapGestureRecognizer() { + guard let window = windows.first else { return } + let tapGesture = UITapGestureRecognizer(target: window, action: #selector(UIView.endEditing)) + tapGesture.requiresExclusiveTouchType = false + tapGesture.cancelsTouchesInView = false + tapGesture.delegate = self + window.addGestureRecognizer(tapGesture) + } +} + +extension UIApplication: UIGestureRecognizerDelegate { + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true // set to `false` if you don't want to detect tap during other gestures + } +} +#endif diff --git a/UnifyWallet/Views/History/HistoryView.swift b/UnifyWallet/Views/History/HistoryView.swift index 12bbfc9..582f40d 100644 --- a/UnifyWallet/Views/History/HistoryView.swift +++ b/UnifyWallet/Views/History/HistoryView.swift @@ -25,6 +25,7 @@ struct HistoryView: View { #if os(macOS) .scaleEffect(0.5) #endif + .padding(.trailing) } else { Button() { listTransactions() @@ -32,7 +33,8 @@ struct HistoryView: View { Image(systemName: "arrow.clockwise") .foregroundStyle(.blue) } - } + .padding(.trailing) + } } Form() { List() { diff --git a/UnifyWallet/Views/Receive/Receive Child Views/InvoiceView.swift b/UnifyWallet/Views/Receive/Receive Child Views/InvoiceView.swift index d158545..50ff06c 100644 --- a/UnifyWallet/Views/Receive/Receive Child Views/InvoiceView.swift +++ b/UnifyWallet/Views/Receive/Receive Child Views/InvoiceView.swift @@ -29,6 +29,9 @@ struct InvoiceView: View, DirectMessageEncrypting { @State private var showingPassphraseAlert = false @State private var passphrase = "" @State private var passphraseConfirm = "" + @State private var nostrConnected = false + @State var startDate = Date.now + @State var timeElapsed: Int = 0 let invoiceAmount: Double @@ -41,6 +44,26 @@ struct InvoiceView: View, DirectMessageEncrypting { var body: some View { Form() { + let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() + + Toggle("Nostr status", isOn: $nostrConnected) + .onChange(of: nostrConnected) { oldValue, newValue in + if !newValue { + StreamManager.shared.closeWebSocket() + } else { + if StreamManager.shared.webSocket == nil { + connectToNostr() + } + } + } + .onReceive(timer) { firedDate in + timeElapsed = Int(firedDate.timeIntervalSince(startDate)) + if timeElapsed.isMultiple(of: 10) { + nostrConnected = StreamManager.shared.webSocket != nil + } + + } + if let ourKeypair = ourKeypair { let url = "bitcoin:\(invoiceAddress)?amount=\(invoiceAmount)&pj=nostr:\(ourKeypair.publicKey.npub)" @@ -69,6 +92,8 @@ struct InvoiceView: View, DirectMessageEncrypting { } label: { Image(systemName: "doc.on.doc") } + + ShareLink("Share", item: standardInvoice) } HStack() { @@ -80,7 +105,7 @@ struct InvoiceView: View, DirectMessageEncrypting { .foregroundStyle(.blue) } - Text(invoiceAddress) + Text(invoiceAddress.withSpaces) } HStack() { @@ -121,6 +146,8 @@ struct InvoiceView: View, DirectMessageEncrypting { } label: { Image(systemName: "doc.on.doc") } + + ShareLink("Share", item: url) } HStack() { @@ -132,7 +159,7 @@ struct InvoiceView: View, DirectMessageEncrypting { .foregroundStyle(.blue) } - Text(invoiceAddress) + Text(invoiceAddress.withSpaces) } HStack() { @@ -240,6 +267,9 @@ struct InvoiceView: View, DirectMessageEncrypting { } else { HStack() { ProgressView() + #if os(macOS) + .scaleEffect(0.5) + #endif Text(" Waiting on response from sender...") .foregroundStyle(.secondary) @@ -273,7 +303,10 @@ struct InvoiceView: View, DirectMessageEncrypting { txSent = false ourKeypair = nil ourKeypair = Keypair() - connectToNostr() + if StreamManager.shared.webSocket == nil { + connectToNostr() + } + }) .alert(errorToDisplay, isPresented: $showError) { Button("OK", role: .cancel) {} @@ -289,7 +322,6 @@ struct InvoiceView: View, DirectMessageEncrypting { .textFieldStyle(.roundedBorder) .frame(maxWidth: .infinity, maxHeight: .infinity) .padding(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12)) - //} } @@ -330,11 +362,15 @@ struct InvoiceView: View, DirectMessageEncrypting { private func connectToNostr() { StreamManager.shared.openWebSocket(relayUrlString: urlString, peerNpub: nil, p: ourKeypair!.publicKey.hex) - StreamManager.shared.eoseReceivedBlock = { _ in } + StreamManager.shared.eoseReceivedBlock = { _ in + nostrConnected = true + } StreamManager.shared.errorReceivedBlock = { nostrError in if nostrError != "" { showError(desc: nostrError) + nostrConnected = false + StreamManager.shared.closeWebSocket() } } @@ -414,9 +450,7 @@ struct InvoiceView: View, DirectMessageEncrypting { return } - print("invoiceAddress: \(invoiceAddress)") // Failing for regtest address :( - let invoiceAddress = try! Address(string: invoiceAddress) var allInputsSegwit = false @@ -612,6 +646,11 @@ struct QRView: View { } label: { Image(systemName: "doc.on.doc") } + + ShareLink(item: Image(uiImage: image), preview: SharePreview("Invoice", image: Image(uiImage: image))) { + Label("Share", systemImage: "square.and.arrow.up") + } + } .alert("Invoice copied ✓", isPresented: $showCopiedAlert) { Button("OK", role: .cancel) { } @@ -635,6 +674,10 @@ struct QRView: View { } label: { Image(systemName: "doc.on.doc") } + + ShareLink(item: Image(nsImage: image), preview: SharePreview("Invoice", image: Image(nsImage: image))) { + Label("Share", systemImage: "square.and.arrow.up") + } } .alert("Invoice copied ✓", isPresented: $showCopiedAlert) { diff --git a/UnifyWallet/Views/Receive/Receive Child Views/ReceiveAddInputView.swift b/UnifyWallet/Views/Receive/Receive Child Views/ReceiveAddInputView.swift index 8e77692..02cd1fa 100644 --- a/UnifyWallet/Views/Receive/Receive Child Views/ReceiveAddInputView.swift +++ b/UnifyWallet/Views/Receive/Receive Child Views/ReceiveAddInputView.swift @@ -34,7 +34,7 @@ struct ReceiveAddInputView: View { Section() { ForEach(utxos, id:\.self) { utxo in let amt = utxo.amount!.btcBalanceWithSpaces - let addr = utxo.address! + let addr = utxo.address!.withSpaces let txt = addr + " " + amt Text(txt) } @@ -42,7 +42,7 @@ struct ReceiveAddInputView: View { Text("Select utxo's to pay the output") } footer: { - if totalAmtSelected > invoiceAmount { + if totalAmtSelected > outputAmount { NavigationLink(value: ReceiveNavigationLinkValues.invoiceView(invoiceAmount: invoiceAmount, invoiceAddress: invoiceAddress, additionalInputs: utxosToSpend, diff --git a/UnifyWallet/Views/Receive/Receive Child Views/ReceiveAddOutputView.swift b/UnifyWallet/Views/Receive/Receive Child Views/ReceiveAddOutputView.swift index 47a893e..308d415 100644 --- a/UnifyWallet/Views/Receive/Receive Child Views/ReceiveAddOutputView.swift +++ b/UnifyWallet/Views/Receive/Receive Child Views/ReceiveAddOutputView.swift @@ -14,6 +14,7 @@ struct ReceiveAddOutputView: View { @State private var additionalOutputAddress = "" @State private var additionalOutputAmount = "" @State private var spendableAmount = 0.0 + @State private var isShowingScanner = false let invoiceAmount: Double @@ -35,7 +36,7 @@ struct ReceiveAddOutputView: View { Section() { HStack() { - Label("BTC Amount", systemImage: "bitcoinsign.circle") + Label("Amount", systemImage: "bitcoinsign.circle") .frame(maxWidth: 200, alignment: .leading) Spacer() @@ -47,7 +48,7 @@ struct ReceiveAddOutputView: View { } HStack() { - Label("Recipient address", systemImage: "arrow.down.forward.circle") + Label("Address", systemImage: "arrow.down.forward.circle") .frame(maxWidth: 200, alignment: .leading) Spacer() @@ -58,6 +59,29 @@ struct ReceiveAddOutputView: View { #endif .autocorrectionDisabled() + + #if os(iOS) + Button { + isShowingScanner = true + } label: { + Image(systemName: "qrcode.viewfinder") + } + .sheet(isPresented: $isShowingScanner) { + CodeScannerView(codeTypes: [.qr], simulatedData: "", completion: handleScan) + } + #endif + } + + if additionalOutputAddress != "" { + Text(additionalOutputAddress.withSpaces) + } else { + Button { + guard let donationAddress = Keys.donationAddress() else { return } + + additionalOutputAddress = donationAddress + } label: { + Text("Donation address") + } } } header: { Text("Additional Output") @@ -109,6 +133,30 @@ struct ReceiveAddOutputView: View { } +#if os(iOS) + func handleScan(result: Result) { + isShowingScanner = false + switch result { + case .success(let result): + let invoice = Invoice(result.string) + guard let address = invoice.address, + let amount = invoice.amount else { + + self.additionalOutputAddress = result.string + + return + } + + self.additionalOutputAddress = address + self.additionalOutputAmount = "\(amount)" + + case .failure(let error): + print("Scanning failed: \(error.localizedDescription)") + } + } +#endif + + private func getSpendableAmount() { for input in utxos { spendableAmount += input.amount! diff --git a/UnifyWallet/Views/Receive/ReceiveView.swift b/UnifyWallet/Views/Receive/ReceiveView.swift index e3ea9d5..9278079 100644 --- a/UnifyWallet/Views/Receive/ReceiveView.swift +++ b/UnifyWallet/Views/Receive/ReceiveView.swift @@ -19,6 +19,9 @@ struct ReceiveView: View { @State private var torEnabled = false @State private var showSpinner = false @State private var balance = 0.0 + @State private var fetchingBalance = false + @State private var fetchingAddress = false + @State private var isShowingScanner = false var body: some View { @@ -36,12 +39,24 @@ struct ReceiveView: View { } Section("Balance") { - Label(balance.btcBalanceWithSpaces, systemImage: "bitcoinsign.circle") + HStack() { + Label(balance.btcBalanceWithSpaces, systemImage: "bitcoinsign.circle") + + if fetchingBalance { + Spacer() + + ProgressView() + #if os(macOS) + .scaleEffect(0.5) + #endif + } + } + } Section("Create Invoice") { HStack() { - Label("Invoice amount", systemImage: "bitcoinsign.circle") + Label("Amount", systemImage: "bitcoinsign.circle") .frame(maxWidth: 200, alignment: .leading) Spacer() @@ -53,7 +68,7 @@ struct ReceiveView: View { } HStack() { - Label("Invoice address", systemImage: "arrow.down.forward.circle") + Label("Address", systemImage: "arrow.down.forward.circle") .frame(maxWidth: 200, alignment: .leading) Spacer() @@ -64,7 +79,18 @@ struct ReceiveView: View { #endif .autocorrectionDisabled() - if !showSpinner { + #if os(iOS) + Button { + isShowingScanner = true + } label: { + Image(systemName: "qrcode.viewfinder") + } + .sheet(isPresented: $isShowingScanner) { + CodeScannerView(codeTypes: [.qr], simulatedData: "", completion: handleScan) + } + #endif + + if !fetchingAddress { Button { fetchAddress() } label: { @@ -72,7 +98,7 @@ struct ReceiveView: View { } } - if showSpinner { + if fetchingAddress { ProgressView() #if os(macOS) .scaleEffect(0.5) @@ -81,6 +107,10 @@ struct ReceiveView: View { #endif } } + + if address != "" { + Text(address.withSpaces) + } } if let amountDouble = Double(amount), amountDouble > 0 && address != "" { @@ -164,6 +194,30 @@ struct ReceiveView: View { } +#if os(iOS) + func handleScan(result: Result) { + isShowingScanner = false + switch result { + case .success(let result): + let invoice = Invoice(result.string) + guard let address = invoice.address, + let amount = invoice.amount else { + + self.address = result.string + + return + } + + self.address = address + self.amount = "\(amount)" + + case .failure(let error): + print("Scanning failed: \(error.localizedDescription)") + } + } +#endif + + private func displayError(desc: String) { errDesc = desc showError = true @@ -171,10 +225,12 @@ struct ReceiveView: View { private func fetchAddress() { - showSpinner = true + fetchingAddress = true let p = Get_New_Address(["address_type": "bech32"]) BitcoinCoreRPC.shared.btcRPC(method: .getnewaddress(param: p)) { (response, errorDesc) in + self.fetchingAddress = false + guard let address = response as? String else { displayError(desc: errorDesc ?? "Unknown error from getnewaddress.") showSpinner = false @@ -182,16 +238,19 @@ struct ReceiveView: View { } self.address = address - self.showSpinner = false } } private func getUtxos() { + fetchingBalance = true + balance = 0.0 let p = List_Unspent([:]) utxos.removeAll() BitcoinCoreRPC.shared.btcRPC(method: .listunspent(p)) { (response, errorDesc) in + fetchingBalance = false + guard let response = response as? [[String: Any]] else { displayError(desc: errorDesc ?? "Unknown error from listunspent.") //showSpinner = false diff --git a/UnifyWallet/Views/Send/SendView.swift b/UnifyWallet/Views/Send/SendView.swift index 7d325ef..63da14c 100644 --- a/UnifyWallet/Views/Send/SendView.swift +++ b/UnifyWallet/Views/Send/SendView.swift @@ -48,7 +48,7 @@ struct SendView: View, DirectMessageEncrypting { } else { Section("Pay Invoice") { if let uploadedInvoice = uploadedInvoice { - Label("\(uploadedInvoice.address!)", systemImage: "arrow.up.forward.circle") + Label("\(uploadedInvoice.address!.withSpaces)", systemImage: "arrow.up.forward.circle") Label(uploadedInvoice.amount!.btcBalanceWithSpaces, systemImage: "bitcoinsign.circle") } @@ -105,6 +105,7 @@ struct SendView: View, DirectMessageEncrypting { private func getUtxos() { + balance = 0.0 let p = List_Unspent([:]) utxos.removeAll() diff --git a/UnifyWallet/Views/Send/Sending Child Views/BroadcastView.swift b/UnifyWallet/Views/Send/Sending Child Views/BroadcastView.swift index 182f8e8..00130a7 100644 --- a/UnifyWallet/Views/Send/Sending Child Views/BroadcastView.swift +++ b/UnifyWallet/Views/Send/Sending Child Views/BroadcastView.swift @@ -31,6 +31,9 @@ struct BroadcastView: View, DirectMessageEncrypting { if sending { VStack() { ProgressView() + #if os(macOS) + .scaleEffect(0.5) + #endif } .alert(errorDesc, isPresented: $showError) { Button("OK", role: .cancel) {} @@ -47,7 +50,7 @@ struct BroadcastView: View, DirectMessageEncrypting { .font(.title) .fontWeight(.bold) - Text("Send \(invoice.amount!.btcBalanceWithSpaces) to \(invoice.address!)? This is final!") + Text("Send \(invoice.amount!.btcBalanceWithSpaces) to \(invoice.address!.withSpaces)? This is final!") .font(.body) .multilineTextAlignment(.center) .padding(.horizontal, 40) @@ -82,10 +85,6 @@ struct BroadcastView: View, DirectMessageEncrypting { .frame(width: 200, height: 200.0) .aspectRatio(contentMode: .fit) - Text("Broadcast ✓") - .font(.title) - .fontWeight(.bold) - Text(txid) .font(.body) .multilineTextAlignment(.center) diff --git a/UnifyWallet/Views/Send/Sending Child Views/SendUtxoView.swift b/UnifyWallet/Views/Send/Sending Child Views/SendUtxoView.swift index 0d36696..e5099c6 100644 --- a/UnifyWallet/Views/Send/Sending Child Views/SendUtxoView.swift +++ b/UnifyWallet/Views/Send/Sending Child Views/SendUtxoView.swift @@ -69,7 +69,7 @@ struct SendUtxoView: View, DirectMessageEncrypting { .font(.title) .fontWeight(.bold) - Text("Send \(invoice.amount!.btcBalanceWithSpaces) to \(invoice.address!)?") + Text("Send \(invoice.amount!.btcBalanceWithSpaces) to \(invoice.address!.withSpaces)?") .font(.body) .multilineTextAlignment(.center) .padding(.horizontal, 40) @@ -293,7 +293,11 @@ struct SendUtxoView: View, DirectMessageEncrypting { } StreamManager.shared.errorReceivedBlock = { nostrError in - showError(desc: "Nostr received error: \(nostrError)") + //showError(desc: "Nostr received error: \(nostrError)") + errorToDisplay = nostrError + showError = true + StreamManager.shared.closeWebSocket() + StreamManager.shared.openWebSocket(relayUrlString: urlString, peerNpub: recipientsNpub, p: nil) } StreamManager.shared.onDoneBlock = { nostrResponse in @@ -572,5 +576,8 @@ struct SendUtxoView: View, DirectMessageEncrypting { struct Spinner: View { var body: some View { ProgressView("Waiting for payjoin proposal") + #if os(macOS) + .scaleEffect(0.5) + #endif } } diff --git a/UnifyWallet/Views/Send/Sending Child Views/SignedProposalView.swift b/UnifyWallet/Views/Send/Sending Child Views/SignedProposalView.swift index 88ccf15..f1b6221 100644 --- a/UnifyWallet/Views/Send/Sending Child Views/SignedProposalView.swift +++ b/UnifyWallet/Views/Send/Sending Child Views/SignedProposalView.swift @@ -126,7 +126,7 @@ struct SignedProposalView: View, DirectMessageEncrypting { VStack(alignment: .trailing) { if bold { - Text(outputAddress) + Text(outputAddress.withSpaces) .bold(bold) .foregroundStyle(.primary) @@ -135,7 +135,7 @@ struct SignedProposalView: View, DirectMessageEncrypting { .foregroundStyle(.primary) } else { - Text(outputAddress) + Text(outputAddress.withSpaces) .bold(bold) .foregroundStyle(.secondary)