lnlink

iOS app for connecting to lightning nodes
git clone git://jb55.com/lnlink
Log | Files | Refs | Submodules | README | LICENSE

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 }