ContentView.swift (5932B)
1 // 2 // ContentView.swift 3 // lightninglink 4 // 5 // Created by William Casarin on 2022-01-07. 6 // 7 8 import SwiftUI 9 import AVFoundation 10 11 extension Notification.Name { 12 static var sentPayment: Notification.Name { 13 return Notification.Name("did send payment") 14 } 15 } 16 17 enum ActiveAlert: Identifiable { 18 var id: String { 19 switch self { 20 case .pay: 21 return "pay" 22 } 23 } 24 25 case pay(InvoiceAmount, String) 26 } 27 28 enum ActiveSheet: Identifiable { 29 var id: String { 30 switch self { 31 case .qr: 32 return "qrcode" 33 case .pay: 34 return "paysheet" 35 } 36 } 37 38 case qr 39 case pay(InvoiceAmount, String) 40 } 41 42 struct Funds { 43 public var onchain_sats: Int64 44 public var channel_sats: Int64 45 46 public static var empty = Funds(onchain_sats: 0, channel_sats: 0) 47 48 public static func from_listfunds(fs: ListFunds) -> Funds { 49 var onchain_sats: Int64 = 0 50 var channel_sats: Int64 = 0 51 52 for channel in fs.channels { 53 channel_sats += channel.channel_sat 54 } 55 56 for output in fs.outputs { 57 onchain_sats += output.value 58 } 59 60 return Funds(onchain_sats: onchain_sats, channel_sats: channel_sats) 61 } 62 } 63 64 let SCAN_TYPES: [AVMetadataObject.ObjectType] = [.qr] 65 66 struct ContentView: View { 67 @State private var info: GetInfo 68 @State private var active_sheet: ActiveSheet? 69 @State private var active_alert: ActiveAlert? 70 @State private var has_alert: Bool 71 @State private var last_pay: Pay? 72 @State private var funds: Funds 73 74 private var lnlink: LNLink 75 76 init(info: GetInfo, lnlink: LNLink, funds: ListFunds) { 77 self.info = info 78 self.lnlink = lnlink 79 self.has_alert = false 80 self.funds = Funds.from_listfunds(fs: funds) 81 } 82 83 func refresh_funds() { 84 let ln = LNSocket() 85 guard ln.connect_and_init(node_id: self.lnlink.node_id, host: self.lnlink.host) else { 86 return 87 } 88 let funds = fetch_funds(ln: ln, token: lnlink.token) 89 self.funds = Funds.from_listfunds(fs: funds) 90 } 91 92 func format_last_pay() -> String { 93 guard let pay = last_pay else { 94 return "" 95 } 96 97 if (pay.msatoshi >= 1000) { 98 let sats = pay.msatoshi / 1000 99 let fee = (pay.msatoshi_sent - pay.msatoshi) / 1000 100 return "-\(sats) sats (\(fee) sats fee)" 101 } 102 103 return "-\(pay.msatoshi) msats (\(pay.msatoshi_sent) msats sent)" 104 } 105 106 func check_pay() { 107 guard let (amt, inv) = get_clipboard_invoice() else { 108 self.active_sheet = .qr 109 self.has_alert = false 110 return 111 } 112 113 self.active_sheet = nil 114 self.active_alert = .pay(amt, inv) 115 self.has_alert = true 116 } 117 118 var body: some View { 119 VStack { 120 Group { 121 Text(self.info.alias) 122 .font(.largeTitle) 123 .padding() 124 Text("\(self.info.num_active_channels) active channels") 125 Text("\(self.info.msatoshi_fees_collected / 1000) sats collected in fees") 126 } 127 Spacer() 128 Text("\(format_last_pay())") 129 .foregroundColor(Color.red) 130 131 Text("\(self.funds.channel_sats) sats") 132 .font(.title) 133 .padding() 134 Text("\(self.funds.onchain_sats) onchain") 135 Spacer() 136 HStack { 137 Spacer() 138 Button("Pay", action: check_pay) 139 .font(.title) 140 .padding() 141 } 142 } 143 .alert("Use invoice in clipboard?", isPresented: $has_alert, presenting: active_alert) { alert in 144 Button("Use QR") { 145 self.has_alert = false 146 self.active_sheet = .qr 147 } 148 Button("Yes") { 149 self.has_alert = false 150 self.active_alert = nil 151 switch alert { 152 case .pay(let amt, let inv): 153 self.active_sheet = .pay(amt, inv) 154 } 155 } 156 } 157 .sheet(item: $active_sheet) { sheet in 158 switch sheet { 159 case .qr: 160 CodeScannerView(codeTypes: SCAN_TYPES) { res in 161 switch res { 162 case .success(let scan_res): 163 let code = scan_res.string 164 var invstr: String = code 165 if code.starts(with: "lightning:") { 166 let index = code.index(code.startIndex, offsetBy: 10) 167 invstr = String(code[index...]) 168 } 169 let m_parsed = parseInvoiceAmount(invstr) 170 guard let parsed = m_parsed else { 171 return 172 } 173 self.active_sheet = .pay(parsed, invstr) 174 175 case .failure: 176 self.active_sheet = nil 177 return 178 } 179 180 } 181 182 case .pay(let amt, let raw): 183 PayView(invoice_str: raw, amount: amt, lnlink: self.lnlink) 184 } 185 } 186 .onReceive(NotificationCenter.default.publisher(for: .sentPayment)) { payment in 187 last_pay = payment.object as! Pay 188 self.active_sheet = nil 189 refresh_funds() 190 } 191 } 192 } 193 194 /* 195 struct ContentView_Previews: PreviewProvider { 196 static var previews: some View { 197 Group { 198 ContentView(info: .empty, lnlink: ln, token: "", funds: .empty) 199 } 200 } 201 } 202 */ 203 204 205 func get_clipboard_invoice() -> (InvoiceAmount, String)? { 206 guard let inv = UIPasteboard.general.string else { 207 return nil 208 } 209 210 guard let amt = parseInvoiceAmount(inv) else { 211 return nil 212 } 213 214 return (amt, inv) 215 }