lnd-demo-app/wallet/ViewControllers/PaymentsVC.swift
2023-06-08 09:36:06 +03:00

404 lines
16 KiB
Swift

//
// PaymentsVC.swift
// wallet
//
// Created by Adriana Epure on 22.08.2022.
// Copyright © 2022 Jason. All rights reserved.
//
import UIKit
import QRCodeScanner
class PaymentsVC: CustomViewController<PaymentsViewModel> {
@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<UITouch>, 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)
}
}
}