404 lines
16 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|