ReceiveView.swift (6673B)
1 // 2 // ReceiveView.swift 3 // lightninglink 4 // 5 // Created by William Casarin on 2022-03-25. 6 // 7 8 import SwiftUI 9 import AVFoundation 10 import CoreImage.CIFilterBuiltins 11 import Combine 12 13 struct QRData { 14 let img: Image 15 let data: String 16 } 17 18 struct ReceiveView: View { 19 @State private var loading: Bool = true 20 @State private var qr_data: QRData? = nil 21 @State private var description: String = "" 22 @State private var issuer: String = "" 23 @State private var amount: Int64? = nil 24 @State private var amount_str: String = "" 25 @State private var making: Bool = false 26 @State private var is_offer: Bool = false 27 @FocusState private var is_kb_focused: Bool 28 @Binding var rate: ExchangeRate? 29 30 let lnlink: LNLink 31 32 @Environment(\.presentationMode) var presentationMode 33 34 var form: some View { 35 ProgressView() 36 .progressViewStyle(.circular) 37 } 38 39 func invoice_details_form() -> some View { 40 Group { 41 Form { 42 Section(header: Text("Invoice Details")) { 43 Toggle("Offer (bolt12)", isOn: $is_offer) 44 .padding() 45 46 TextField("Description", text: $description) 47 .font(.body) 48 .focused($is_kb_focused) 49 .padding() 50 51 if self.is_offer { 52 TextField("Issuer", text: $issuer) 53 .font(.body) 54 .focused($is_kb_focused) 55 .padding() 56 } 57 58 AmountInput(text: $amount_str, placeholder: "any") { parsed in 59 if let str = parsed.msats_str { 60 self.amount_str = str 61 } 62 if let msats = parsed.msats { 63 self.amount = msats 64 } 65 } 66 .padding() 67 .focused($is_kb_focused) 68 } 69 } 70 .frame(height: 350) 71 72 if self.amount_str != "", let msats = self.amount { 73 if let rate = self.rate { 74 Text("\(msats_to_fiat(msats: msats, xr: rate))") 75 .foregroundColor(.gray) 76 } 77 } 78 } 79 } 80 81 var body: some View { 82 VStack { 83 Text("Receive payment") 84 .font(.title) 85 86 Spacer() 87 88 if let qr = self.qr_data { 89 QRCodeView(qr: qr) 90 } else { 91 if making { 92 ProgressView() 93 .progressViewStyle(.circular) 94 } else { 95 invoice_details_form() 96 } 97 } 98 99 Spacer() 100 101 HStack { 102 if !is_kb_focused { 103 Button("Back") { 104 dismiss() 105 } 106 } else { 107 Button("Close") { 108 close_keyboard() 109 } 110 } 111 112 Spacer() 113 114 115 116 if !self.making && self.qr_data == nil { 117 Button("Receive") { 118 self.making = true 119 120 // 12h 121 let h12 = UInt64(Date().timeIntervalSince1970 + 60 * 60 * 12) 122 make_invoice(lnlink: lnlink, expiry: h12, description: self.description, amount: self.amount, issuer: self.issuer, is_offer: self.is_offer) { res in 123 switch res { 124 case .failure: 125 self.making = false 126 break 127 case .success(let invres): 128 let upper = invres.uppercased() 129 let img = generate_qr(from: upper) 130 self.making = false 131 self.qr_data = QRData(img: img, data: upper) 132 } 133 } 134 } 135 .font(.title) 136 } 137 } 138 } 139 .padding() 140 } 141 142 private func close_keyboard() { 143 UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), 144 to: nil, from: nil, for: nil) 145 } 146 147 private func dismiss() { 148 self.presentationMode.wrappedValue.dismiss() 149 } 150 151 } 152 153 154 func generate_qr(from string: String) -> Image { 155 let context = CIContext() 156 let filter = CIFilter.qrCodeGenerator() 157 158 filter.message = Data(string.uppercased().utf8) 159 if let output_img = filter.outputImage { 160 if let cgimg = context.createCGImage(output_img, from: output_img.extent) { 161 let uiimg = UIImage(cgImage: cgimg) 162 return Image(uiImage: uiimg).interpolation(.none) 163 } 164 } 165 166 let uiimg = UIImage(systemName: "xmark.circle") ?? UIImage() 167 return Image(uiImage: uiimg) 168 } 169 170 func make_invoice(lnlink: LNLink, expiry: UInt64, description: String?, amount: Int64?, issuer: String?, is_offer: Bool, callback: @escaping (RequestRes<String>) -> ()) { 171 let ln = LNSocket() 172 173 ln.genkey() 174 guard ln.connect_and_init(node_id: lnlink.node_id, host: lnlink.host) else { 175 return 176 } 177 178 DispatchQueue.global(qos: .background).async { 179 var amt: InvoiceAmount = .any 180 if let a = amount { 181 amt = .amount(a) 182 } 183 184 let desc = description ?? "lnlink invoice" 185 186 if is_offer { 187 let res = rpc_offer(ln: ln, token: lnlink.token, amount: amt, description: desc, issuer: issuer) 188 callback(res.map{ $0.bolt12 }) 189 } else { 190 let res = rpc_invoice(ln: ln, token: lnlink.token, amount: amt, description: desc, expiry: expiry) 191 callback(res.map{ $0.bolt11 }) 192 } 193 } 194 } 195 196 struct QRCodeView: View { 197 let qr: QRData 198 @State var copied: Bool = false 199 200 var body: some View { 201 Group { 202 qr.img 203 .resizable() 204 .scaledToFit() 205 .frame(width: 300, height: 300) 206 .onTapGesture { 207 AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate)) 208 UIPasteboard.general.string = self.qr.data 209 copied = true 210 } 211 212 Text("\(!copied ? "Tap QR to copy invoice" : "Copied!")") 213 .font(.subheadline) 214 .foregroundColor(.gray) 215 216 } 217 218 } 219 }