2019-12-06 04:38:47 -08:00
//
// S e n d V i e w C o n t r o l l e r . s w i f t
// Z c a s h L i g h t C l i e n t S a m p l e
//
// C r e a t e d b y F r a n c i s c o G i n d r e o n 1 2 / 3 / 1 9 .
// C o p y r i g h t © 2 0 1 9 E l e c t r i c C o i n C o m p a n y . A l l r i g h t s r e s e r v e d .
//
import UIKit
import ZcashLightClientKit
import KRProgressHUD
2022-10-02 19:11:17 -07:00
2019-12-06 04:38:47 -08:00
class SendViewController : UIViewController {
@IBOutlet weak var addressLabel : UILabel !
@IBOutlet weak var amountLabel : UILabel !
@IBOutlet weak var addressTextField : UITextField !
@IBOutlet weak var amountTextField : UITextField !
@IBOutlet weak var balanceLabel : UILabel !
2020-01-14 14:25:14 -08:00
@IBOutlet weak var verifiedBalanceLabel : UILabel !
2019-12-06 04:38:47 -08:00
@IBOutlet weak var maxFunds : UISwitch !
@IBOutlet weak var sendButton : UIButton !
2019-12-16 14:25:45 -08:00
@IBOutlet weak var synchronizerStatusLabel : UILabel !
2020-06-18 16:53:11 -07:00
@IBOutlet weak var memoField : UITextView !
@IBOutlet weak var charactersLeftLabel : UILabel !
let characterLimit : Int = 512
2019-12-06 04:38:47 -08:00
2021-09-17 06:49:58 -07:00
var wallet = Initializer . shared
// s w i f t l i n t : d i s a b l e : n e x t i m p l i c i t l y _ u n w r a p p e d _ o p t i o n a l
2019-12-06 04:38:47 -08:00
var synchronizer : Synchronizer !
2019-12-16 14:25:45 -08:00
2019-12-06 04:38:47 -08:00
override func viewDidLoad ( ) {
super . viewDidLoad ( )
synchronizer = AppDelegate . shared . sharedSynchronizer
2022-10-31 16:45:58 -07:00
let tapRecognizer = UITapGestureRecognizer ( target : self , action : #selector ( viewTapped ( _ : ) ) )
self . view . addGestureRecognizer ( tapRecognizer )
setUp ( )
2022-10-27 03:51:38 -07:00
Task { @ MainActor in
// s w i f t l i n t : d i s a b l e : n e x t f o r c e _ t r y
try ! await synchronizer . prepare ( )
}
2019-12-06 04:38:47 -08:00
}
2019-12-16 14:25:45 -08:00
override func viewDidAppear ( _ animated : Bool ) {
super . viewDidAppear ( animated )
2022-10-27 03:51:38 -07:00
Task { @ MainActor in
do {
try await synchronizer . start ( retry : false )
self . synchronizerStatusLabel . text = SDKSynchronizer . textFor ( state : synchronizer . status )
} catch {
self . synchronizerStatusLabel . text = SDKSynchronizer . textFor ( state : synchronizer . status )
fail ( error )
}
2019-12-16 14:25:45 -08:00
}
2022-04-26 13:53:01 -07:00
}
2019-12-16 14:25:45 -08:00
2019-12-06 04:38:47 -08:00
@objc func viewTapped ( _ recognizer : UITapGestureRecognizer ) {
let point = recognizer . location ( in : self . view )
if addressTextField . isFirstResponder && ! addressTextField . frame . contains ( point ) {
addressTextField . resignFirstResponder ( )
2021-09-17 06:49:58 -07:00
} else if amountTextField . isFirstResponder && ! amountTextField . frame . contains ( point ) {
2019-12-06 04:38:47 -08:00
amountTextField . resignFirstResponder ( )
2020-06-18 16:53:11 -07:00
} else if memoField . isFirstResponder &&
! memoField . frame . contains ( point ) {
memoField . resignFirstResponder ( )
2019-12-06 04:38:47 -08:00
}
}
2019-12-16 14:25:45 -08:00
2019-12-06 04:38:47 -08:00
func setUp ( ) {
balanceLabel . text = format ( balance : wallet . getBalance ( ) )
2020-01-14 14:25:14 -08:00
verifiedBalanceLabel . text = format ( balance : wallet . getVerifiedBalance ( ) )
2019-12-06 04:38:47 -08:00
toggleSendButton ( )
2020-06-18 16:53:11 -07:00
memoField . text = " "
memoField . layer . borderColor = UIColor . gray . cgColor
memoField . layer . borderWidth = 1
memoField . layer . cornerRadius = 5
charactersLeftLabel . text = textForCharacterCount ( 0 )
2019-12-16 14:25:45 -08:00
let center = NotificationCenter . default
2021-09-17 06:49:58 -07:00
center . addObserver (
self ,
selector : #selector ( synchronizerStarted ( _ : ) ) ,
name : Notification . Name . synchronizerStarted ,
object : synchronizer
)
center . addObserver (
self ,
selector : #selector ( synchronizerSynced ( _ : ) ) ,
name : Notification . Name . synchronizerSynced ,
object : synchronizer
)
center . addObserver (
self ,
selector : #selector ( synchronizerStopped ( _ : ) ) ,
name : Notification . Name . synchronizerStopped ,
object : synchronizer
)
center . addObserver (
self ,
selector : #selector ( synchronizerUpdated ( _ : ) ) ,
name : Notification . Name . synchronizerProgressUpdated ,
object : synchronizer
)
2019-12-06 04:38:47 -08:00
}
2022-06-22 12:45:37 -07:00
func format ( balance : Zatoshi = Zatoshi ( ) ) -> String {
" Zec \( balance . formattedString ? ? " 0.0 " ) "
2019-12-06 04:38:47 -08:00
}
func toggleSendButton ( ) {
sendButton . isEnabled = isFormValid ( )
}
func maxFundsOn ( ) {
2022-06-22 12:45:37 -07:00
let fee = Zatoshi ( 10000 )
let max : Zatoshi = wallet . getVerifiedBalance ( ) - fee
amountTextField . text = format ( balance : max )
2019-12-06 04:38:47 -08:00
amountTextField . isEnabled = false
}
func maxFundsOff ( ) {
amountTextField . isEnabled = true
}
func isFormValid ( ) -> Bool {
2019-12-16 14:25:45 -08:00
switch synchronizer . status {
case . synced :
return isBalanceValid ( ) && isAmountValid ( ) && isRecipientValid ( )
default :
return false
2021-09-17 06:49:58 -07:00
}
2019-12-06 04:38:47 -08:00
}
func isBalanceValid ( ) -> Bool {
2022-06-22 12:45:37 -07:00
wallet . getVerifiedBalance ( ) > . zero
2019-12-06 04:38:47 -08:00
}
func isAmountValid ( ) -> Bool {
2021-09-17 06:49:58 -07:00
guard
let value = amountTextField . text ,
2022-06-22 12:45:37 -07:00
let amount = NumberFormatter . zcashNumberFormatter . number ( from : value ) . flatMap ( { Zatoshi ( $0 . int64Value ) } ) ,
amount <= wallet . getVerifiedBalance ( )
2021-09-17 06:49:58 -07:00
else {
return false
2019-12-06 04:38:47 -08:00
}
2021-09-17 06:49:58 -07:00
2019-12-06 04:38:47 -08:00
return true
}
func isRecipientValid ( ) -> Bool {
2020-06-09 17:23:46 -07:00
guard let addr = self . addressTextField . text else {
return false
}
2022-08-20 15:10:22 -07:00
return wallet . isValidSaplingAddress ( addr ) || wallet . isValidTransparentAddress ( addr )
2019-12-06 04:38:47 -08:00
}
@IBAction func maxFundsValueChanged ( _ sender : Any ) {
2020-06-18 16:53:11 -07:00
if maxFunds . isOn {
maxFundsOn ( )
} else {
maxFundsOff ( )
}
}
2019-12-06 04:38:47 -08:00
@IBAction func send ( _ sender : Any ) {
guard isFormValid ( ) else {
2020-03-09 13:25:27 -07:00
loggerProxy . warn ( " WARNING: Form is invalid " )
2019-12-06 04:38:47 -08:00
return
}
2021-09-17 06:49:58 -07:00
let alert = UIAlertController (
title : " About To send funds! " ,
// s w i f t l i n t : d i s a b l e : n e x t l i n e _ l e n g t h
message : " This is an ugly confirmation message. You should come up with something fancier that lets the user be sure about sending funds without disturbing the user experience with an annoying alert like this one " ,
preferredStyle : UIAlertController . Style . alert
)
let sendAction = UIAlertAction (
title : " Send! " ,
style : UIAlertAction . Style . default ,
handler : { _ in
self . send ( )
}
)
let cancelAction = UIAlertAction (
title : " Go back! I'm not sure about this. " ,
style : UIAlertAction . Style . destructive ,
handler : { _ in
self . cancel ( )
}
)
2019-12-06 04:38:47 -08:00
alert . addAction ( sendAction )
alert . addAction ( cancelAction )
self . present ( alert , animated : true , completion : nil )
}
func send ( ) {
2022-06-22 12:45:37 -07:00
guard
isFormValid ( ) ,
let amount = amountTextField . text ,
let zec = NumberFormatter . zcashNumberFormatter . number ( from : amount ) . flatMap ( { Zatoshi ( $0 . int64Value ) } ) ,
let recipient = addressTextField . text
else {
2020-03-09 13:25:27 -07:00
loggerProxy . warn ( " WARNING: Form is invalid " )
2019-12-06 04:38:47 -08:00
return
}
2022-10-02 19:11:17 -07:00
guard let spendingKey = try ? DerivationTool (
networkType : kZcashNetwork . networkType
)
. deriveUnifiedSpendingKey (
seed : DemoAppConfig . seed ,
accountIndex : 0
)
else {
2022-08-20 15:10:22 -07:00
loggerProxy . error ( " NO SPENDING KEY " )
2019-12-06 04:38:47 -08:00
return
}
KRProgressHUD . show ( )
2022-09-13 03:19:56 -07:00
Task { @ MainActor in
do {
let pendingTransaction = try await synchronizer . sendToAddress (
2022-09-30 05:53:34 -07:00
spendingKey : spendingKey ,
2022-09-13 03:19:56 -07:00
zatoshi : zec ,
2022-10-02 19:11:17 -07:00
// s w i f t l i n t : d i s a b l e : n e x t f o r c e _ t r y
toAddress : try ! Recipient ( recipient , network : kZcashNetwork . networkType ) ,
memo : try ! self . memoField . text . asMemo ( )
2022-09-13 03:19:56 -07:00
)
2019-12-06 04:38:47 -08:00
KRProgressHUD . dismiss ( )
2020-03-09 13:25:27 -07:00
loggerProxy . info ( " transaction created: \( pendingTransaction ) " )
2022-09-13 03:19:56 -07:00
} catch {
fail ( error )
loggerProxy . error ( " SEND FAILED: \( error ) " )
2019-12-06 04:38:47 -08:00
}
}
}
func fail ( _ error : Error ) {
2021-09-17 06:49:58 -07:00
let alert = UIAlertController (
title : " Send failed! " ,
message : " \( error ) " ,
preferredStyle : UIAlertController . Style . alert
)
2019-12-06 04:38:47 -08:00
let action = UIAlertAction ( title : " OK :( " , style : UIAlertAction . Style . default , handler : nil )
alert . addAction ( action )
self . present ( alert , animated : true , completion : nil )
}
2021-09-17 06:49:58 -07:00
func cancel ( ) { }
2019-12-16 14:25:45 -08:00
// MARK: s y n c h r o n i z e r n o t i f i c a t i o n s
@objc func synchronizerUpdated ( _ notification : Notification ) {
2020-06-09 17:23:46 -07:00
DispatchQueue . main . async { [ weak self ] in
guard let self = self else {
return
}
self . synchronizerStatusLabel . text = SDKSynchronizer . textFor ( state : self . synchronizer . status )
}
2019-12-16 14:25:45 -08:00
}
@objc func synchronizerStarted ( _ notification : Notification ) {
2020-06-09 17:23:46 -07:00
DispatchQueue . main . async { [ weak self ] in
guard let self = self else {
return
}
self . synchronizerStatusLabel . text = SDKSynchronizer . textFor ( state : self . synchronizer . status )
}
2019-12-16 14:25:45 -08:00
}
@ objc func synchronizerStopped ( _ notification : Notification ) {
2020-06-09 17:23:46 -07:00
DispatchQueue . main . async { [ weak self ] in
guard let self = self else {
return
}
self . synchronizerStatusLabel . text = SDKSynchronizer . textFor ( state : self . synchronizer . status )
}
2019-12-16 14:25:45 -08:00
}
@objc func synchronizerSynced ( _ notification : Notification ) {
2020-06-09 17:23:46 -07:00
DispatchQueue . main . async { [ weak self ] in
guard let self = self else {
return
}
self . synchronizerStatusLabel . text = SDKSynchronizer . textFor ( state : self . synchronizer . status )
}
2019-12-16 14:25:45 -08:00
}
2020-06-18 16:53:11 -07:00
func textForCharacterCount ( _ count : Int ) -> String {
" \( count ) of \( characterLimit ) bytes left "
}
2019-12-06 04:38:47 -08:00
}
extension SendViewController : UITextFieldDelegate {
func textFieldShouldBeginEditing ( _ textField : UITextField ) -> Bool {
if textField = = amountTextField {
2020-06-18 16:53:11 -07:00
return ! maxFunds . isOn
2019-12-06 04:38:47 -08:00
}
return true
}
func textFieldDidEndEditing ( _ textField : UITextField ) {
textField . resignFirstResponder ( )
toggleSendButton ( )
}
func textFieldShouldReturn ( _ textField : UITextField ) -> Bool {
if textField = = amountTextField {
addressTextField . becomeFirstResponder ( )
return false
}
textField . resignFirstResponder ( )
return true
}
}
2019-12-16 14:25:45 -08:00
2020-06-18 16:53:11 -07:00
extension SendViewController : UITextViewDelegate {
func textView ( _ textView : UITextView , shouldChangeTextIn range : NSRange , replacementText text : String ) -> Bool {
let userPressedDelete = text . isEmpty && range . length > 0
return textView . text . utf8 . count < characterLimit || userPressedDelete
}
func textViewDidChange ( _ textView : UITextView ) {
self . charactersLeftLabel . text = textForCharacterCount ( textView . text . utf8 . count )
}
}
2019-12-16 14:25:45 -08:00
extension SDKSynchronizer {
2021-06-14 15:56:32 -07:00
static func textFor ( state : SyncStatus ) -> String {
2020-06-18 16:53:11 -07:00
switch state {
2021-06-14 15:56:32 -07:00
case . downloading ( let progress ) :
return " Downloading \( progress . progressHeight ) / \( progress . targetHeight ) "
2021-09-17 06:49:58 -07:00
2021-06-14 15:56:32 -07:00
case . enhancing ( let enhanceProgress ) :
return " Enhancing tx \( enhanceProgress . enhancedTransactions ) of \( enhanceProgress . totalTransactions ) "
2021-09-17 06:49:58 -07:00
2021-06-14 15:56:32 -07:00
case . fetching :
return " fetching UTXOs "
2021-09-17 06:49:58 -07:00
2021-06-14 15:56:32 -07:00
case . scanning ( let scanProgress ) :
return " Scanning: \( scanProgress . progressHeight ) / \( scanProgress . targetHeight ) "
2021-09-17 06:49:58 -07:00
2020-06-18 16:53:11 -07:00
case . disconnected :
return " disconnected 💔 "
2021-09-17 06:49:58 -07:00
2020-06-18 16:53:11 -07:00
case . stopped :
return " Stopped 🚫 "
2021-09-17 06:49:58 -07:00
2020-06-18 16:53:11 -07:00
case . synced :
return " Synced 😎 "
2021-09-17 06:49:58 -07:00
2021-05-19 14:49:08 -07:00
case . unprepared :
return " Unprepared 😅 "
2021-09-17 06:49:58 -07:00
2021-06-14 15:56:32 -07:00
case . validating :
return " Validating "
2021-09-17 06:49:58 -07:00
2021-06-14 15:56:32 -07:00
case . error ( let e ) :
return " Error: \( e . localizedDescription ) "
2020-06-18 16:53:11 -07:00
}
}
2019-12-16 14:25:45 -08:00
}
2022-10-02 19:11:17 -07:00
extension Optional where Wrapped = = String {
func asMemo ( ) throws -> Memo {
switch self {
case . some ( let string ) :
return try Memo ( string : string )
case . none :
return . empty
}
}
}