// // PaymentsVC.swift // wallet // // Created by Adriana Epure on 22.08.2022. // Copyright © 2022 Jason. All rights reserved. // import UIKit import QRCodeScanner class PaymentsVC: CustomViewController { @IBOutlet weak var paymentElements: UIStackView! @IBOutlet weak var selectedListControl: UISegmentedControl! @IBOutlet weak var tableView: UITableView! @IBOutlet weak var paymentButtonsStack: UIStackView! @IBOutlet weak var localBalanceValue: UILabel! @IBOutlet weak var remoteBalanceValue: UILabel! @IBOutlet weak var walletBalanceValue: UILabel! @IBOutlet weak var estimateFeeValue: UILabel! @IBOutlet weak var feeStackView: UIStackView! //Send @IBOutlet weak var sendStackView: UIStackView! @IBOutlet weak var sendPaymentAddress: UITextView! @IBOutlet weak var payBtn: UIButton! @IBOutlet weak var paymentInfoStack: UIStackView! @IBOutlet weak var paymentInfoLbl: UILabel! @IBOutlet weak var memoLbl: UILabel! @IBOutlet weak var paymentInfoCreated: UILabel! @IBOutlet weak var paymentInfoExpiry: UILabel! @IBOutlet weak var cancelPayBtn: UIButton! //Receive @IBOutlet weak var receiveStackView: UIStackView! @IBOutlet weak var invoiceAmount: UITextField! @IBOutlet weak var invoiceComment: UITextField! @IBOutlet weak var amountStepper: UIStepper! @IBOutlet weak var receivePaymentAddress: UITextView! @IBOutlet weak var generateInvoiceBtn: UIButton! @IBOutlet weak var cancelReceiveBtn: UIButton! override func viewDidLoad() { super.viewDidLoad() viewModel.getInfo() title = "Payments" tableView.delegate = self tableView.dataSource = self selectedListControl.selectedSegmentIndex == 0 ? viewModel.listPayments() : viewModel.listInvoices() } override func viewModelDidLoad() { viewModel.invoices.observe = { [weak self] invoices in self?.tableView.reloadData() } viewModel.payments.observe = { [weak self] payments in self?.tableView.reloadData() } viewModel.channelBalance.observe = { [weak self] balance in self?.localBalanceValue.text = String(balance) } viewModel.walletBalance.observe = { [weak self] walletBalance in self?.walletBalanceValue.text = String(walletBalance.total) } viewModel.isLoading.observe = { [weak self] isLoading in self?.viewModel.updateBalance() } viewModel.paymentInfo.observe = { [weak self] paymentInfo in if let paymentInfo = paymentInfo{ self?.paymentInfoStack.isHidden = false self?.payBtn.isHidden = false self?.paymentInfoLbl.text = String(paymentInfo.numSatoshis) self?.paymentInfoCreated.text = paymentInfo.timestamp.getAsDate().format(style: .medium) self?.paymentInfoExpiry.text = String(paymentInfo.timestamp.getAsDate().adding(seconds: Int(paymentInfo.expiry)).format(style: .medium)) self?.memoLbl.text = String(paymentInfo.description_p) }else{ self?.paymentInfoStack.isHidden = true self?.payBtn.isHidden = true self?.paymentInfoCreated.text = "" self?.paymentInfoLbl.text = "" self?.paymentInfoExpiry.text = "" self?.memoLbl.text = "" } } viewModel.load() } //MARK: - Selection @IBAction func didChangeSelection(_ sender: Any) { selectedListControl.selectedSegmentIndex == 0 ? viewModel.listPayments() : viewModel.listInvoices() tableView.reloadData() } @IBAction func didPressCancel(_ sender: Any) { showHideViews(isSending: !sendStackView.isHidden, isFinished: true) } //MARK: - Send @IBAction func didPressSend(_ sender: Any) { sendPaymentAddress.text = "Input Address ..." sendPaymentAddress.textColor = UIColor.lightGray sendPaymentAddress.delegate = self sendPaymentAddress.layer.borderColor = UIColor.lightGray.cgColor sendPaymentAddress.layer.borderWidth = 1 showHideViews(isSending: true, isFinished: false) } @IBAction func didPressScan(_ sender: Any) { // Create an instance of QRCodeScanViewController let viewController = QRCodeScanViewController.create() // Set itself as delegate viewController.delegate = self // Present the view controller self.present(viewController, animated: true) } @IBAction func didPressPay(_ sender: Any) { self.selectedListControl.selectedSegmentIndex = 0 self.selectedListControl.sendActions(for: .valueChanged) if let paymentAddress = sendPaymentAddress.text{ debugPrint("Invoice ", paymentAddress) self.viewModel.payInvoice(invoice: paymentAddress) { response in self.showAlert(title: "Payment", errorMsg: "Payment Request Sent") } onFailure: { error in self.showAlert(title: "Payment", errorMsg: error.debugDescription) } self.showAlert(title: "Payment", errorMsg: "Payment Request Sent") }else{ self.showAlert(title: "Payment", errorMsg: "Payment Request Failed") } showHideViews(isSending: true, isFinished: true) } //MARK: - Receive @IBAction func didPressReceive(_ sender: Any) { invoiceComment.delegate = self invoiceAmount.delegate = self showHideViews(isSending: false, isFinished: false) } @IBAction func didUpdateStepper(_ sender: UIStepper) { invoiceAmount.text = String(String(format: "%.f", sender.value)) estimateFeeValue.text = String(String(format: "%.2f", sender.value/1000)) generateInvoiceBtn.isHidden = false } @IBAction func didPressGenerateBtn(_ sender: Any) { self.selectedListControl.selectedSegmentIndex = 1 self.selectedListControl.sendActions(for: .valueChanged) guard let amountText = invoiceAmount.text else{ self.showAlert(title: "Invoice", errorMsg: "Empty Amount") return } debugPrint("✅", amountText) guard let amount:Int = Int(amountText) else{ self.showAlert(title: "Invoice", errorMsg: "Invalid Amount") return } guard let balance:Int = viewModel.walletBalance.value?.total else { self.showAlert(title: "Invoice", errorMsg: "Invalid wallet balance") return } if(amount > balance){ self.showAlert(title: "Invoice", errorMsg: "Insufficient funds") }else if amount == 0{ self.showAlert(title: "Invoice", errorMsg: "No amount selected") }else{ createInvoice(amount: amount, comment: invoiceComment.text ?? "") } showHideViews(isSending: false, isFinished: true) } func showHideViews(isSending: Bool, isFinished: Bool){ sendStackView.isHidden = isFinished || (!isFinished && !isSending) receiveStackView.isHidden = isFinished || (!isFinished && isSending) feeStackView.isHidden = isFinished || (!isFinished && isSending) paymentButtonsStack.isHidden = !isFinished tableView.isHidden = !isFinished selectedListControl.isHidden = !isFinished generateInvoiceBtn.isHidden = true payBtn.isHidden = true if isFinished{ invoiceComment.text = "" invoiceAmount.text = "0" estimateFeeValue.text = "0" sendPaymentAddress.text = "" } } } //MARK: - Create Invoice extension PaymentsVC{ func createInvoice(amount: Int, comment: String){ self.showLoadingView() viewModel.createInvoice(amount: amount, comment: comment) { paymentRequest in self.showContentView() self.showAlert(title: "Invoice", address: paymentRequest) } onFailure: { error in self.showContentView() self.showAlert(title: "Invoice", errorMsg: error.debugDescription) } } } extension PaymentsVC: UITableViewDelegate, UITableViewDataSource{ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let emptyLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height)) emptyLabel.text = "" emptyLabel.textAlignment = NSTextAlignment.center self.tableView.backgroundView = emptyLabel if selectedListControl.selectedSegmentIndex == 0{ if let noOfPayments = viewModel.payments.value?.count{ return noOfPayments }else{ emptyLabel.text = "No Payments" self.tableView.separatorStyle = UITableViewCell.SeparatorStyle.none return 0 } }else{ if let noOfInvoices = viewModel.invoices.value?.count{ return noOfInvoices }else{ emptyLabel.text = "No Invoices" self.tableView.separatorStyle = UITableViewCell.SeparatorStyle.none return 0 } } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let reuseCellIdentifier = "cellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: reuseCellIdentifier) if (!(cell != nil)) { cell = UITableViewCell(style: .subtitle, reuseIdentifier: reuseCellIdentifier) } cell?.textLabel?.numberOfLines = 0 cell?.detailTextLabel?.numberOfLines = 0 if selectedListControl.selectedSegmentIndex == 0{ if let payment = viewModel.payments.value?[indexPath.row]{ cell?.textLabel?.text = "Status: \(payment.status) \nAmount: \(payment.value) sat" cell?.detailTextLabel?.text = "\nDate: \(payment.creationDate.getAsDate()) \nFee:\(payment.feeSat) \nPayment hash:\n\(payment.paymentRequest.description)" } }else{ if let invoice = viewModel.invoices.value?[indexPath.row]{ cell?.textLabel?.text = "Status: \(invoice.state) \nAmount: \(invoice.value) sat" cell?.detailTextLabel?.text = "\nDate Created: \(invoice.creationDate.getAsDate()) \nMemo:\(invoice.memo) \nPayment request: \(invoice.paymentRequest)" } } return cell! } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if selectedListControl.selectedSegmentIndex == 0{ if let payment = viewModel.payments.value?[indexPath.row]{ UIPasteboard.general.string = payment.paymentRequest.description } }else{ if let invoice = viewModel.invoices.value?[indexPath.row]{ self.showAlert(title: "Invoice", address: invoice.paymentRequest) } } } } extension PaymentsVC: UITextFieldDelegate{ /** * Called when 'return' key pressed. return NO to ignore. */ func textFieldShouldReturn(_ textField: UITextField) -> Bool { if !(invoiceAmount.text?.isEmpty ?? true){ generateInvoiceBtn.isHidden = false } textField.resignFirstResponder() return true } func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if textField == invoiceAmount{ let allowedCharacters = CharacterSet.decimalDigits let characterSet = CharacterSet(charactersIn: string) return allowedCharacters.isSuperset(of: characterSet) } return true } /** * Called when the user click on the view (outside the UITextField). */ func touchesBegan(touches: Set, withEvent event: UIEvent?) { self.view.endEditing(true) } } extension PaymentsVC: UITextPasteDelegate { func textPasteConfigurationSupporting(_ textPasteConfigurationSupporting: UITextPasteConfigurationSupporting, shouldAnimatePasteOf attributedString: NSAttributedString, to textRange: UITextRange) -> Bool { return false } } extension PaymentsVC: UITextViewDelegate{ func textViewDidBeginEditing(_ textView: UITextView) { if textView.textColor == UIColor.lightGray { textView.text = nil textView.textColor = UIColor.black } } func textViewDidEndEditing(_ textView: UITextView) { if textView.text.isEmpty { textView.text = "Input Address ..." textView.textColor = UIColor.lightGray } } func textViewDidChange(_ textView: UITextView) { if(textView.text == UIPasteboard.general.string){ } } func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { // Combine the textView text and the replacement text to // create the updated text string let currentText:String = textView.text let updatedText = (currentText as NSString).replacingCharacters(in: range, with: text) // If updated text view will be empty, add the placeholder // and set the cursor to the beginning of the text view if updatedText.isEmpty { textView.text = "Input Address ..." textView.textColor = UIColor.lightGray textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument) } // Else if the text view's placeholder is showing and the // length of the replacement string is greater than 0, set // the text color to black then set its text to the // replacement string else if textView.textColor == UIColor.lightGray && !text.isEmpty { textView.textColor = UIColor.black textView.text = text }else if text == "\n" { textView.resignFirstResponder() self.viewModel.getPaymentDetail(invoice: textView.text) return false } // For every other case, the text should change with the usual // behavior... else { return true } // ...otherwise return false since the updates have already // been made return false } func textViewDidChangeSelection(_ textView: UITextView) { if self.view.window != nil { if textView.textColor == UIColor.lightGray { textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument) } } } } extension PaymentsVC: QRCodeScanViewControllerDelegate{ // MARK: QRCodeScanViewControllerDelegate /// Called when the camera scans a QR code /// - Parameters: /// - viewController: View controller that scanned the QR code /// - value: String encoded in the QR code func qrCodeScanViewController(_ viewController: QRCodeScanViewController, didScanQRCode value: String) { // Dismiss the view controller viewController.dismiss(animated: true) { self.sendPaymentAddress.text = value self.sendPaymentAddress.tintColor = UIColor.black self.viewModel.getPaymentDetail(invoice: value) } } }